当前位置: 首页 > news >正文

FreeSwitch的mod_translate模块详细,附带场景案例及代码示例

mod_translate 模块详细介绍

mod_translate 是 FreeSWITCH 中的一个拨号计划应用程序模块,用于对电话号码或字符串进行格式转换和翻译。它可以根据预定义的规则对输入的内容进行匹配和转换,常用于号码格式化、路由选择、号码屏蔽等场景。


主要功能

  1. 号码格式化:将输入的号码按照预定义的规则进行格式化。
  2. 号码翻译:根据规则将号码转换为另一种格式。
  3. 路由选择:根据号码前缀或其他规则选择不同的路由。
  4. 号码屏蔽:隐藏或替换号码中的部分数字。

使用场景

  1. 号码格式化

    • 将用户输入的号码统一格式化为国际标准格式(如 +1234567890)。
    • 去除号码中的特殊字符(如 -() 等)。
  2. 号码翻译

    • 将短号码(如 1001)翻译为完整的 E.164 格式号码(如 +1234567890)。
    • 根据号码前缀选择不同的路由(如 1 开头的号码路由到美国,44 开头的号码路由到英国)。
  3. 号码屏蔽

    • 隐藏号码中的部分数字(如将 +1234567890 转换为 +123****890)。
  4. 动态路由

    • 根据号码的特定规则动态选择网关或路由。

配置与使用

1. 配置 translate.conf.xml

mod_translate 的规则定义在 translate.conf.xml 文件中。以下是一个示例配置:

<configuration name="translate.conf" description="Number Translation Rules"><profiles><profile name="example_profile"><rule name="international_format"><!-- 匹配任意号码 --><condition field="destination_number" expression="^(\d+)$"><!-- 将号码格式化为国际标准格式 --><action application="set" data="translated_number=+1$1"/></condition></rule><rule name="shortcode_to_e164"><!-- 匹配短号码 1001 --><condition field="destination_number" expression="^1001$"><!-- 将短号码翻译为完整的 E.164 格式 --><action application="set" data="translated_number=+1234567890"/></condition></rule><rule name="mask_number"><!-- 匹配任意号码 --><condition field="destination_number" expression="^(\d{3})(\d{3})(\d{4})$"><!-- 屏蔽中间三位数字 --><action application="set" data="translated_number=$1***$3"/></condition></rule></profile></profiles>
</configuration>
2. 在拨号计划中使用 mod_translate

在拨号计划中,可以通过 translate 应用程序调用 mod_translate 对号码进行翻译。以下是一个示例拨号计划:

<extension name="Number Translation Example"><condition field="destination_number" expression="^(\d+)$"><!-- 调用 translate 应用程序,使用 example_profile 规则 --><action application="translate" data="example_profile"/><!-- 使用翻译后的号码进行呼叫 --><action application="bridge" data="sofia/gateway/my_gateway/${translated_number}"/></condition>
</extension>

代码示例

示例 1:号码格式化
  • 输入号码1234567890
  • 规则:将号码格式化为国际标准格式 +11234567890
  • 拨号计划
    <extension name="Format Number"><condition field="destination_number" expression="^(\d+)$"><action application="translate" data="example_profile:international_format"/><action application="log" data="INFO Translated Number: ${translated_number}"/><action application="bridge" data="sofia/gateway/my_gateway/${translated_number}"/></condition>
    </extension>
    
示例 2:短号码翻译
  • 输入号码1001
  • 规则:将短号码 1001 翻译为 +1234567890
  • 拨号计划
    <extension name="Shortcode Translation"><condition field="destination_number" expression="^1001$"><action application="translate" data="example_profile:shortcode_to_e164"/><action application="log" data="INFO Translated Number: ${translated_number}"/><action application="bridge" data="sofia/gateway/my_gateway/${translated_number}"/></condition>
    </extension>
    
示例 3:号码屏蔽
  • 输入号码1234567890
  • 规则:将号码屏蔽为 123***7890
  • 拨号计划
    <extension name="Mask Number"><condition field="destination_number" expression="^(\d{3})(\d{3})(\d{4})$"><action application="translate" data="example_profile:mask_number"/><action application="log" data="INFO Masked Number: ${translated_number}"/><action application="bridge" data="sofia/gateway/my_gateway/${translated_number}"/></condition>
    </extension>
    

调试与日志

  • 使用 sofia global siptrace on 开启 SIP 跟踪,查看号码翻译后的效果。
  • 在拨号计划中使用 log 应用程序记录翻译后的号码:
    <action application="log" data="INFO Translated Number: ${translated_number}"/>
    

总结

mod_translate 是一个强大的号码翻译和格式化工具,适用于多种场景,如号码格式化、路由选择、号码屏蔽等。通过灵活的规则配置,可以轻松实现复杂的号码处理逻辑。结合拨号计划中的 translate 应用程序,能够显著提升 FreeSWITCH 的号码处理能力。

如果需要对 +86 开头的号码(中国区号)进行特殊处理,可以在 translate.conf.xml 中定义针对 +86 的规则,并在拨号计划中调用这些规则。以下是详细的配置和代码示例:


场景描述

假设我们需要对 +86 开头的号码进行以下处理:

  1. 格式化:将 +8613712345678 格式化为 +86 137 1234 5678
  2. 路由选择:将 +86 开头的号码路由到特定的网关(如 china_gateway)。
  3. 号码屏蔽:将 +8613712345678 屏蔽为 +86 137 **** 5678

配置 translate.conf.xml

translate.conf.xml 中定义针对 +86 的规则:

<configuration name="translate.conf" description="Number Translation Rules"><profiles><profile name="china_profile"><!-- 规则 1:格式化 +86 号码 --><rule name="format_china_number"><condition field="destination_number" expression="^\+86(\d{3})(\d{4})(\d{4})$"><action application="set" data="translated_number=+86 $1 $2 $3"/></condition></rule><!-- 规则 2:屏蔽 +86 号码 --><rule name="mask_china_number"><condition field="destination_number" expression="^\+86(\d{3})(\d{4})(\d{4})$"><action application="set" data="translated_number=+86 $1 **** $3"/></condition></rule></profile></profiles>
</configuration>

在拨号计划中使用 mod_translate

示例 1:格式化 +86 号码
  • 输入号码+8613712345678
  • 规则:将号码格式化为 +86 137 1234 5678
  • 拨号计划
    <extension name="Format China Number"><condition field="destination_number" expression="^\+86(\d{3})(\d{4})(\d{4})$"><!-- 调用 translate 应用程序,使用 china_profile 规则 --><action application="translate" data="china_profile:format_china_number"/><!-- 记录翻译后的号码 --><action application="log" data="INFO Formatted China Number: ${translated_number}"/><!-- 使用翻译后的号码进行呼叫 --><action application="bridge" data="sofia/gateway/china_gateway/${translated_number}"/></condition>
    </extension>
    
示例 2:屏蔽 +86 号码
  • 输入号码+8613712345678
  • 规则:将号码屏蔽为 +86 137 **** 5678
  • 拨号计划
    <extension name="Mask China Number"><condition field="destination_number" expression="^\+86(\d{3})(\d{4})(\d{4})$"><!-- 调用 translate 应用程序,使用 china_profile 规则 --><action application="translate" data="china_profile:mask_china_number"/><!-- 记录屏蔽后的号码 --><action application="log" data="INFO Masked China Number: ${translated_number}"/><!-- 使用屏蔽后的号码进行呼叫 --><action application="bridge" data="sofia/gateway/china_gateway/${translated_number}"/></condition>
    </extension>
    
示例 3:路由 +86 号码到特定网关
  • 输入号码+8613712345678
  • 规则:将 +86 开头的号码路由到 china_gateway
  • 拨号计划
    <extension name="Route China Number"><condition field="destination_number" expression="^\+86\d+$"><!-- 直接路由到 china_gateway --><action application="bridge" data="sofia/gateway/china_gateway/${destination_number}"/></condition>
    </extension>
    

调试与日志

  • 使用 sofia global siptrace on 开启 SIP 跟踪,查看号码翻译后的效果。
  • 在拨号计划中使用 log 应用程序记录翻译后的号码:
    <action application="log" data="INFO Translated Number: ${translated_number}"/>
    

总结

通过 mod_translate,可以轻松实现对 +86 开头的号码进行格式化、屏蔽和路由选择。以下是关键点:

  1. 格式化:将 +8613712345678 格式化为 +86 137 1234 5678
  2. 屏蔽:将 +8613712345678 屏蔽为 +86 137 **** 5678
  3. 路由:将 +86 开头的号码路由到特定的网关(如 china_gateway)。

这种配置非常适合需要对中国号码进行特殊处理的场景,如国际呼叫、号码隐私保护等。

要实现号码归属地查询(如 13800138001 为中国北京,13303111232 为中国河北石家庄),可以通过以下步骤实现:

  1. 获取号码归属地数据:需要一个包含号码段和归属地信息的数据库或 API。
  2. 集成归属地查询功能:在 FreeSWITCH 中通过 Lua 脚本、HTTP 请求或数据库查询实现归属地查询。
  3. 返回归属地信息:将查询结果存储在变量中,并在拨号计划或日志中使用。

以下是具体的实现方案:


方案 1:使用 Lua 脚本查询本地数据库

步骤 1:准备归属地数据库
  • 使用 SQLite、MySQL 或其他数据库存储号码段和归属地信息。
  • 示例表结构:
    CREATE TABLE number_location (prefix VARCHAR(10) PRIMARY KEY, -- 号码前缀(如 138, 133)province VARCHAR(50),           -- 省份(如 北京, 河北)city VARCHAR(50)                -- 城市(如 北京, 石家庄)
    );
    
  • 示例数据:
    INSERT INTO number_location (prefix, province, city) VALUES
    ('138', '北京', '北京'),
    ('133', '河北', '石家庄'),
    ('139', '广东', '深圳');
    
步骤 2:编写 Lua 脚本

在 FreeSWITCH 中编写 Lua 脚本,查询数据库并返回归属地信息。

-- number_location.lua
local db = require "luasql.sqlite3"
local env = db.sqlite3()
local conn = env:connect("/path/to/number_location.db")function query_location(number)local prefix = string.sub(number, 1, 3) -- 提取号码前 3 位local cursor, err = conn:execute(string.format("SELECT province, city FROM number_location WHERE prefix = '%s'", prefix))if not cursor thenreturn nil, errendlocal row = cursor:fetch({}, "a")if row thenreturn row.province, row.cityendreturn nil, "No match found"
end-- 示例:查询号码归属地
local number = session:getVariable("destination_number")
local province, city = query_location(number)
if province and city thensession:setVariable("number_location", string.format("中国 %s %s", province, city))
elsesession:setVariable("number_location", "未知归属地")
end
步骤 3:在拨号计划中调用 Lua 脚本

在拨号计划中调用 Lua 脚本,并记录归属地信息。

<extension name="Number Location Query"><condition field="destination_number" expression="^(\d+)$"><!-- 调用 Lua 脚本查询归属地 --><action application="lua" data="number_location.lua"/><!-- 记录归属地信息 --><action application="log" data="INFO Number Location: ${number_location}"/><!-- 继续呼叫 --><action application="bridge" data="sofia/gateway/my_gateway/${destination_number}"/></condition>
</extension>

方案 2:使用 HTTP API 查询归属地

步骤 1:准备归属地 API
  • 使用第三方归属地查询 API(如 聚合数据 或 阿里云)。
  • 示例 API 请求:
    GET https://api.example.com/location?number=13800138001
    
  • 示例 API 响应:
    {"province": "北京","city": "北京"
    }
    
步骤 2:编写 Lua 脚本调用 API

在 FreeSWITCH 中编写 Lua 脚本,调用 HTTP API 查询归属地。

-- http_location.lua
local http = require "socket.http"
local ltn12 = require "ltn12"function query_location(number)local url = string.format("https://api.example.com/location?number=%s", number)local response = {}local res, code = http.request{url = url,sink = ltn12.sink.table(response)}if code == 200 thenlocal json = table.concat(response)local province, city = json:match('"province":"([^"]+)","city":"([^"]+)"')if province and city thenreturn province, cityendendreturn nil, "API request failed"
end-- 示例:查询号码归属地
local number = session:getVariable("destination_number")
local province, city = query_location(number)
if province and city thensession:setVariable("number_location", string.format("中国 %s %s", province, city))
elsesession:setVariable("number_location", "未知归属地")
end
步骤 3:在拨号计划中调用 Lua 脚本

在拨号计划中调用 Lua 脚本,并记录归属地信息。

<extension name="Number Location Query"><condition field="destination_number" expression="^(\d+)$"><!-- 调用 Lua 脚本查询归属地 --><action application="lua" data="http_location.lua"/><!-- 记录归属地信息 --><action application="log" data="INFO Number Location: ${number_location}"/><!-- 继续呼叫 --><action application="bridge" data="sofia/gateway/my_gateway/${destination_number}"/></condition>
</extension>

方案 3:使用内置的 mod_curl 查询归属地

如果不想使用 Lua 脚本,可以通过 mod_curl 直接调用 HTTP API。

步骤 1:配置 mod_curl

curl.conf.xml 中配置 API 请求:

<configuration name="curl.conf" description="cURL Configuration"><settings><param name="timeout" value="10"/></settings><profiles><profile name="location_api"><param name="url" value="https://api.example.com/location?number=${destination_number}"/><param name="method" value="GET"/></profile></profiles>
</configuration>
步骤 2:在拨号计划中使用 mod_curl

在拨号计划中调用 mod_curl,并解析 API 响应。

<extension name="Number Location Query"><condition field="destination_number" expression="^(\d+)$"><!-- 调用 HTTP API 查询归属地 --><action application="curl" data="location_api"/><!-- 解析 API 响应 --><action application="set" data="api_response=${curl_response_data}"/><action application="set" data="number_location=中国 ${api_response[province]} ${api_response[city]}"/><!-- 记录归属地信息 --><action application="log" data="INFO Number Location: ${number_location}"/><!-- 继续呼叫 --><action application="bridge" data="sofia/gateway/my_gateway/${destination_number}"/></condition>
</extension>

总结

实现号码归属地查询的三种方案:

  1. 本地数据库查询:适合数据量较小、查询频繁的场景。
  2. HTTP API 查询:适合使用第三方 API 的场景。
  3. mod_curl 查询:适合不想编写 Lua 脚本的场景。

根据实际需求选择合适的方案,并结合 FreeSWITCH 的拨号计划和日志功能,实现号码归属地查询和记录。要实现号码归属地查询(如 13800138001 为中国北京,13303111232 为中国河北石家庄),可以通过以下步骤实现:

  1. 获取号码归属地数据:需要一个包含号码段和归属地信息的数据库或 API。
  2. 集成归属地查询功能:在 FreeSWITCH 中通过 Lua 脚本、HTTP 请求或数据库查询实现归属地查询。
  3. 返回归属地信息:将查询结果存储在变量中,并在拨号计划或日志中使用。

以下是具体的实现方案:


方案 1:使用 Lua 脚本查询本地数据库

步骤 1:准备归属地数据库
  • 使用 SQLite、MySQL 或其他数据库存储号码段和归属地信息。
  • 示例表结构:
    CREATE TABLE number_location (prefix VARCHAR(10) PRIMARY KEY, -- 号码前缀(如 138, 133)province VARCHAR(50),           -- 省份(如 北京, 河北)city VARCHAR(50)                -- 城市(如 北京, 石家庄)
    );
    
  • 示例数据:
    INSERT INTO number_location (prefix, province, city) VALUES
    ('138', '北京', '北京'),
    ('133', '河北', '石家庄'),
    ('139', '广东', '深圳');
    
步骤 2:编写 Lua 脚本

在 FreeSWITCH 中编写 Lua 脚本,查询数据库并返回归属地信息。

-- number_location.lua
local db = require "luasql.sqlite3"
local env = db.sqlite3()
local conn = env:connect("/path/to/number_location.db")function query_location(number)local prefix = string.sub(number, 1, 3) -- 提取号码前 3 位local cursor, err = conn:execute(string.format("SELECT province, city FROM number_location WHERE prefix = '%s'", prefix))if not cursor thenreturn nil, errendlocal row = cursor:fetch({}, "a")if row thenreturn row.province, row.cityendreturn nil, "No match found"
end-- 示例:查询号码归属地
local number = session:getVariable("destination_number")
local province, city = query_location(number)
if province and city thensession:setVariable("number_location", string.format("中国 %s %s", province, city))
elsesession:setVariable("number_location", "未知归属地")
end
步骤 3:在拨号计划中调用 Lua 脚本

在拨号计划中调用 Lua 脚本,并记录归属地信息。

<extension name="Number Location Query"><condition field="destination_number" expression="^(\d+)$"><!-- 调用 Lua 脚本查询归属地 --><action application="lua" data="number_location.lua"/><!-- 记录归属地信息 --><action application="log" data="INFO Number Location: ${number_location}"/><!-- 继续呼叫 --><action application="bridge" data="sofia/gateway/my_gateway/${destination_number}"/></condition>
</extension>

方案 2:使用 HTTP API 查询归属地

步骤 1:准备归属地 API
  • 使用第三方归属地查询 API(如 聚合数据 或 阿里云)。
  • 示例 API 请求:
    GET https://api.example.com/location?number=13800138001
    
  • 示例 API 响应:
    {"province": "北京","city": "北京"
    }
    
步骤 2:编写 Lua 脚本调用 API

在 FreeSWITCH 中编写 Lua 脚本,调用 HTTP API 查询归属地。

-- http_location.lua
local http = require "socket.http"
local ltn12 = require "ltn12"function query_location(number)local url = string.format("https://api.example.com/location?number=%s", number)local response = {}local res, code = http.request{url = url,sink = ltn12.sink.table(response)}if code == 200 thenlocal json = table.concat(response)local province, city = json:match('"province":"([^"]+)","city":"([^"]+)"')if province and city thenreturn province, cityendendreturn nil, "API request failed"
end-- 示例:查询号码归属地
local number = session:getVariable("destination_number")
local province, city = query_location(number)
if province and city thensession:setVariable("number_location", string.format("中国 %s %s", province, city))
elsesession:setVariable("number_location", "未知归属地")
end
步骤 3:在拨号计划中调用 Lua 脚本

在拨号计划中调用 Lua 脚本,并记录归属地信息。

<extension name="Number Location Query"><condition field="destination_number" expression="^(\d+)$"><!-- 调用 Lua 脚本查询归属地 --><action application="lua" data="http_location.lua"/><!-- 记录归属地信息 --><action application="log" data="INFO Number Location: ${number_location}"/><!-- 继续呼叫 --><action application="bridge" data="sofia/gateway/my_gateway/${destination_number}"/></condition>
</extension>

方案 3:使用内置的 mod_curl 查询归属地

如果不想使用 Lua 脚本,可以通过 mod_curl 直接调用 HTTP API。

步骤 1:配置 mod_curl

curl.conf.xml 中配置 API 请求:

<configuration name="curl.conf" description="cURL Configuration"><settings><param name="timeout" value="10"/></settings><profiles><profile name="location_api"><param name="url" value="https://api.example.com/location?number=${destination_number}"/><param name="method" value="GET"/></profile></profiles>
</configuration>
步骤 2:在拨号计划中使用 mod_curl

在拨号计划中调用 mod_curl,并解析 API 响应。

<extension name="Number Location Query"><condition field="destination_number" expression="^(\d+)$"><!-- 调用 HTTP API 查询归属地 --><action application="curl" data="location_api"/><!-- 解析 API 响应 --><action application="set" data="api_response=${curl_response_data}"/><action application="set" data="number_location=中国 ${api_response[province]} ${api_response[city]}"/><!-- 记录归属地信息 --><action application="log" data="INFO Number Location: ${number_location}"/><!-- 继续呼叫 --><action application="bridge" data="sofia/gateway/my_gateway/${destination_number}"/></condition>
</extension>

总结

实现号码归属地查询的三种方案:

  1. 本地数据库查询:适合数据量较小、查询频繁的场景。
  2. HTTP API 查询:适合使用第三方 API 的场景。
  3. mod_curl 查询:适合不想编写 Lua 脚本的场景。

根据实际需求选择合适的方案,并结合 FreeSWITCH 的拨号计划和日志功能,实现号码归属地查询和记录。

如果接收到的号码为 FURX17733291111,其中 FURX 是前缀,后面是实际的号码 17733291111,并且有一个 MySQL 数据库表存储号码段和归属地信息,可以通过以下步骤实现归属地判断:


步骤 1:准备 MySQL 数据表

假设 MySQL 数据库中有一个表 number_location,结构如下:

CREATE TABLE number_location (prefix VARCHAR(10) PRIMARY KEY, -- 号码前缀(如 1773320)province VARCHAR(50),           -- 省份(如 河北省)city VARCHAR(50),               -- 城市(如 石家庄市)carrier VARCHAR(50)             -- 运营商(如 中国电信)
);

示例数据:

INSERT INTO number_location (prefix, province, city, carrier) VALUES
('1773320', '河北省', '石家庄市', '中国电信'),
('1773321', '河北省', '石家庄市', '中国移动'),
('1773322', '河北省', '石家庄市', '中国联通');

步骤 2:编写 Lua 脚本查询 MySQL 数据库

在 FreeSWITCH 中编写 Lua 脚本,连接 MySQL 数据库并查询号码归属地。

安装 Lua MySQL 驱动

确保 Lua 可以连接 MySQL 数据库。安装 luasql-mysql 驱动:

sudo apt install lua-sql-mysql
编写 Lua 脚本

创建 Lua 脚本 mysql_location.lua

-- mysql_location.lua
local mysql = require "luasql.mysql"
local env = mysql.mysql()
local conn = env:connect("database_name", "username", "password", "hostname", 3306)function query_location(number)-- 去除前缀 FURXlocal clean_number = string.gsub(number, "^FURX", "")-- 提取号码前 7 位(假设前缀长度为 7)local prefix = string.sub(clean_number, 1, 7)-- 查询数据库local cursor, err = conn:execute(string.format("SELECT province, city, carrier FROM number_location WHERE prefix = '%s'", prefix))if not cursor thenreturn nil, errendlocal row = cursor:fetch({}, "a")if row thenreturn row.province, row.city, row.carrierendreturn nil, "No match found"
end-- 示例:查询号码归属地
local number = session:getVariable("destination_number")
local province, city, carrier = query_location(number)
if province and city and carrier thensession:setVariable("number_location", string.format("中国 %s %s %s", province, city, carrier))
elsesession:setVariable("number_location", "未知归属地")
end

步骤 3:在拨号计划中调用 Lua 脚本

在拨号计划中调用 Lua 脚本,并记录归属地信息。

<extension name="Number Location Query"><condition field="destination_number" expression="^FURX(\d+)$"><!-- 调用 Lua 脚本查询归属地 --><action application="lua" data="mysql_location.lua"/><!-- 记录归属地信息 --><action application="log" data="INFO Number Location: ${number_location}"/><!-- 继续呼叫 --><action application="bridge" data="sofia/gateway/my_gateway/${destination_number}"/></condition>
</extension>

运行结果

  • 输入号码FURX17733291111
  • 处理后的号码17733291111
  • 查询的号码前缀1773320
  • 查询结果
    • 省份:河北省
    • 城市:石家庄市
    • 运营商:中国电信
  • 日志输出
    INFO Number Location: 中国 河北省 石家庄市 中国电信
    

优化:支持动态前缀长度

如果号码前缀长度不固定(如 1773320 是 7 位,1380013 是 6 位),可以动态匹配最长前缀。

修改 Lua 脚本中的 query_location 函数:

function query_location(number)-- 去除前缀 FURXlocal clean_number = string.gsub(number, "^FURX", "")-- 动态匹配最长前缀for i = 7, 3, -1 dolocal prefix = string.sub(clean_number, 1, i)local cursor, err = conn:execute(string.format("SELECT province, city, carrier FROM number_location WHERE prefix = '%s'", prefix))if not cursor thenreturn nil, errendlocal row = cursor:fetch({}, "a")if row thenreturn row.province, row.city, row.carrierendendreturn nil, "No match found"
end

总结

通过 Lua 脚本和 MySQL 数据库,可以实现对带有前缀的号码进行归属地判断。以下是关键点:

  1. 去除前缀:使用 string.gsub 去除前缀(如 FURX)。
  2. 查询数据库:根据号码前缀查询 MySQL 数据库,获取归属地信息。
  3. 动态前缀匹配:支持动态匹配最长前缀。

这种方案非常适合需要处理带有前缀的号码并查询归属地的场景。

如果号码前缀长度固定为 7 位,并且在数据库中匹配不到时,需要发起 HTTP 请求查询归属地,并将新号段的归属地信息存入数据库,可以通过以下步骤实现:


步骤 1:准备 MySQL 数据表

确保 MySQL 数据库中有一个表 number_location,结构如下:

CREATE TABLE number_location (prefix VARCHAR(10) PRIMARY KEY, -- 号码前缀(如 1773320)province VARCHAR(50),           -- 省份(如 河北省)city VARCHAR(50),               -- 城市(如 石家庄市)carrier VARCHAR(50)             -- 运营商(如 中国电信)
);

步骤 2:编写 Lua 脚本

在 FreeSWITCH 中编写 Lua 脚本,实现以下功能:

  1. 从号码中提取 7 位前缀。
  2. 查询 MySQL 数据库,如果匹配到则返回归属地信息。
  3. 如果未匹配到,则发起 HTTP 请求查询归属地。
  4. 将 HTTP 请求的结果存入 MySQL 数据库。
安装依赖

确保 Lua 可以连接 MySQL 和发起 HTTP 请求。安装以下依赖:

sudo apt install lua-sql-mysql lua-socket
编写 Lua 脚本

创建 Lua 脚本 dynamic_location.lua

-- dynamic_location.lua
local mysql = require "luasql.mysql"
local http = require "socket.http"
local ltn12 = require "ltn12"
local json = require "cjson" -- 如果需要解析 JSON 响应-- 连接 MySQL 数据库
local env = mysql.mysql()
local conn = env:connect("database_name", "username", "password", "hostname", 3306)-- 查询数据库
function query_database(prefix)local cursor, err = conn:execute(string.format("SELECT province, city, carrier FROM number_location WHERE prefix = '%s'", prefix))if not cursor thenreturn nil, errendlocal row = cursor:fetch({}, "a")if row thenreturn row.province, row.city, row.carrierendreturn nil, "No match found"
end-- 发起 HTTP 请求查询归属地
function query_http_api(prefix)local url = string.format("https://api.example.com/location?number=%s", prefix)local response = {}local res, code = http.request{url = url,sink = ltn12.sink.table(response)}if code == 200 thenlocal json_response = table.concat(response)local data = json.decode(json_response) -- 解析 JSON 响应return data.province, data.city, data.carrierendreturn nil, "API request failed"
end-- 将归属地信息存入数据库
function save_to_database(prefix, province, city, carrier)local query = string.format("INSERT INTO number_location (prefix, province, city, carrier) VALUES ('%s', '%s', '%s', '%s')", prefix, province, city, carrier)local res, err = conn:execute(query)if not res thenreturn nil, errendreturn true
end-- 主逻辑
local number = session:getVariable("destination_number")
local clean_number = string.gsub(number, "^FURX", "") -- 去除前缀 FURX
local prefix = string.sub(clean_number, 1, 7)         -- 提取前 7 位-- 查询数据库
local province, city, carrier = query_database(prefix)
if not province then-- 如果数据库中没有匹配,发起 HTTP 请求province, city, carrier = query_http_api(prefix)if province then-- 将新号段信息存入数据库save_to_database(prefix, province, city, carrier)elseprovince, city, carrier = "未知", "未知", "未知"end
end-- 设置归属地信息
session:setVariable("number_location", string.format("中国 %s %s %s", province, city, carrier))

步骤 3:在拨号计划中调用 Lua 脚本

在拨号计划中调用 Lua 脚本,并记录归属地信息。

<extension name="Dynamic Number Location Query"><condition field="destination_number" expression="^FURX(\d+)$"><!-- 调用 Lua 脚本查询归属地 --><action application="lua" data="dynamic_location.lua"/><!-- 记录归属地信息 --><action application="log" data="INFO Number Location: ${number_location}"/><!-- 继续呼叫 --><action application="bridge" data="sofia/gateway/my_gateway/${destination_number}"/></condition>
</extension>

运行结果

  1. 输入号码FURX17733291111
  2. 处理后的号码17733291111
  3. 查询的号码前缀1773320
  4. 数据库查询
    • 如果数据库中存在 1773320,则直接返回归属地信息。
    • 如果数据库中不存在 1773320,则发起 HTTP 请求查询归属地,并将结果存入数据库。
  5. 日志输出
    INFO Number Location: 中国 河北省 石家庄市 中国电信
    

HTTP API 响应示例

假设 HTTP API 的响应为 JSON 格式:

{"province": "河北省","city": "石家庄市","carrier": "中国电信"
}

总结

通过 Lua 脚本,可以实现以下功能:

  1. 去除前缀:从号码中去除前缀(如 FURX)。
  2. 数据库查询:根据 7 位前缀查询 MySQL 数据库。
  3. HTTP 请求:如果数据库中没有匹配,则发起 HTTP 请求查询归属地。
  4. 数据存储:将 HTTP 请求的结果存入 MySQL 数据库。

这种方案非常适合需要动态查询和存储号码归属地的场景。

在高并发场景下,频繁查询 MySQL 数据库确实可能造成 I/O 阻塞,影响系统性能。为了解决这个问题,可以采用缓存机制(如 Redis)来减少对 MySQL 的直接查询,同时优化查询逻辑和性能。以下是详细的优化方案和建议:


优化方案:Redis 缓存 + MySQL 持久化 + HTTP 请求

1. Redis 缓存
  • 作用:将号码归属地信息缓存到 Redis 中,减少对 MySQL 的直接查询。
  • 优点
    • Redis 是基于内存的键值存储,读写速度极快(微秒级)。
    • 减少 MySQL 的 I/O 压力,提升系统性能。
  • 实现
    • 在 Lua 脚本中优先查询 Redis。
    • 如果 Redis 中不存在,则查询 MySQL 或发起 HTTP 请求,并将结果存入 Redis。
2. MySQL 持久化
  • 作用:作为数据的持久化存储,存储所有号码归属地信息。
  • 优点
    • 数据持久化,避免 Redis 重启或崩溃导致数据丢失。
    • 支持复杂查询和数据统计分析。
3. HTTP 请求
  • 作用:当 Redis 和 MySQL 中都没有匹配的号码归属地信息时,发起 HTTP 请求查询第三方 API。
  • 优点
    • 动态获取最新的号码归属地信息。
    • 将查询结果存入 Redis 和 MySQL,避免重复查询。

具体实现

1. Redis 缓存设计
  • 键设计number_location:<前缀>,例如 number_location:1773320
  • 值设计:存储 JSON 格式的归属地信息,例如 {"province":"河北省","city":"石家庄市","carrier":"中国电信"}
2. Lua 脚本逻辑
  1. 查询 Redis
    • 如果 Redis 中存在匹配的键,则直接返回归属地信息。
  2. 查询 MySQL
    • 如果 Redis 中不存在,则查询 MySQL。
    • 如果 MySQL 中存在匹配的记录,则将结果存入 Redis 并返回。
  3. 发起 HTTP 请求
    • 如果 MySQL 中也不存在,则发起 HTTP 请求查询第三方 API。
    • 将 HTTP 请求的结果存入 Redis 和 MySQL,并返回。
3. Lua 脚本代码

以下是优化后的 Lua 脚本示例:

-- dynamic_location_redis.lua
local redis = require "redis"
local mysql = require "luasql.mysql"
local http = require "socket.http"
local ltn12 = require "ltn12"
local json = require "cjson"-- 连接 Redis
local redis_client = redis.connect("127.0.0.1", 6379)-- 连接 MySQL
local mysql_env = mysql.mysql()
local mysql_conn = mysql_env:connect("database_name", "username", "password", "hostname", 3306)-- 查询 Redis
function query_redis(prefix)local key = "number_location:" .. prefixlocal value = redis_client:get(key)if value thenreturn json.decode(value)endreturn nil
end-- 查询 MySQL
function query_mysql(prefix)local cursor, err = mysql_conn:execute(string.format("SELECT province, city, carrier FROM number_location WHERE prefix = '%s'", prefix))if not cursor thenreturn nil, errendlocal row = cursor:fetch({}, "a")if row thenreturn rowendreturn nil, "No match found"
end-- 发起 HTTP 请求
function query_http_api(prefix)local url = string.format("https://api.example.com/location?number=%s", prefix)local response = {}local res, code = http.request{url = url,sink = ltn12.sink.table(response)}if code == 200 thenreturn json.decode(table.concat(response))endreturn nil, "API request failed"
end-- 将归属地信息存入 Redis 和 MySQL
function save_location(prefix, province, city, carrier)-- 存入 Redislocal key = "number_location:" .. prefixlocal value = json.encode({province = province, city = city, carrier = carrier})redis_client:set(key, value)-- 存入 MySQLlocal query = string.format("INSERT INTO number_location (prefix, province, city, carrier) VALUES ('%s', '%s', '%s', '%s')", prefix, province, city, carrier)mysql_conn:execute(query)
end-- 主逻辑
local number = session:getVariable("destination_number")
local clean_number = string.gsub(number, "^FURX", "") -- 去除前缀 FURX
local prefix = string.sub(clean_number, 1, 7)         -- 提取前 7 位-- 查询 Redis
local location = query_redis(prefix)
if not location then-- 查询 MySQLlocation = query_mysql(prefix)if not location then-- 发起 HTTP 请求location = query_http_api(prefix)if location then-- 将新号段信息存入 Redis 和 MySQLsave_location(prefix, location.province, location.city, location.carrier)elselocation = {province = "未知", city = "未知", carrier = "未知"}endelse-- 将 MySQL 查询结果存入 Redissave_location(prefix, location.province, location.city, location.carrier)end
end-- 设置归属地信息
session:setVariable("number_location", string.format("中国 %s %s %s", location.province, location.city, location.carrier))

性能优化建议

1. 使用 LuaJIT
  • 优点
    • LuaJIT 是 Lua 的即时编译版本,性能比标准 Lua 高 5-10 倍。
    • 适合高并发场景,能够显著降低延迟。
  • 实现
    • 在 FreeSWITCH 中启用 LuaJIT(需编译支持 LuaJIT 的 FreeSWITCH 版本)。
2. 使用 FreeSWITCH 原生代码
  • 优点
    • 原生代码(C/C++)性能最优,延迟最低。
    • 适合对性能要求极高的场景。
  • 缺点
    • 开发复杂度高,维护成本大。
    • 适合有丰富 C/C++ 开发经验的团队。
3. 异步非阻塞设计
  • 优点
    • 使用异步非阻塞的方式查询 Redis、MySQL 和 HTTP API,避免阻塞主线程。
  • 实现
    • 使用 FreeSWITCH 的异步任务模块(如 mod_event_socket)或 Lua 协程。

总结

在高并发场景下,推荐以下方案:

  1. Redis 缓存:作为一级缓存,减少对 MySQL 的直接查询。
  2. MySQL 持久化:作为二级存储,保证数据持久化。
  3. HTTP 请求:作为动态数据源,获取最新归属地信息。
  4. LuaJIT:作为脚本引擎,提升性能。

如果对性能要求极高,可以考虑使用 FreeSWITCH 原生代码(C/C++)实现,但开发成本较高。对于大多数场景,LuaJIT + Redis + MySQL 的方案已经能够满足需求。

如果使用 LuaJIT,大部分 Lua 脚本代码可以直接运行,因为 LuaJIT 是 Lua 的兼容实现。不过,为了充分发挥 LuaJIT 的性能优势,可能需要对代码进行一些优化。此外,安装 LuaJIT 和相关的依赖项是必要的。

以下是详细的步骤和注意事项:


1. LuaJIT 的优势

  • 性能:LuaJIT 的性能比标准 Lua 高 5-10 倍,特别适合高并发场景。
  • 兼容性:LuaJIT 完全兼容 Lua 5.1,支持大部分 Lua 库和模块。
  • FFI(外部函数接口):LuaJIT 提供了 FFI 功能,可以直接调用 C 函数,进一步提升性能。

2. Lua 脚本代码是否需要修改?

大部分 Lua 脚本代码可以直接在 LuaJIT 中运行,无需修改。但如果想充分发挥 LuaJIT 的性能优势,可以考虑以下优化:

优化建议
  1. 使用 FFI 调用 C 函数

    • 对于性能敏感的部分(如 Redis、MySQL 操作),可以使用 LuaJIT 的 FFI 直接调用 C 库,避免使用 Lua 的绑定库(如 luasqllua-redis)。
    • 示例:
      local ffi = require "ffi"
      ffi.cdef[[int printf(const char *fmt, ...);
      ]]
      ffi.C.printf("Hello, %s!\n", "world")
      
  2. 避免全局变量

    • LuaJIT 对局部变量的访问速度更快,尽量减少全局变量的使用。
  3. 使用 LuaJIT 的 JIT 编译器

    • 确保 LuaJIT 的 JIT 编译器处于启用状态(默认启用),避免手动禁用。
  4. 优化字符串操作

    • LuaJIT 对字符串操作进行了优化,但仍需避免频繁的字符串拼接和操作。

3. 安装 LuaJIT 和依赖项

在 Ubuntu 22.04 上安装 LuaJIT
  1. 安装 LuaJIT:

    sudo apt update
    sudo apt install luajit
    
  2. 验证安装:

    luajit -v
    

    输出示例:

    LuaJIT 2.1.0-beta3 -- Copyright (C) 2005-2021 Mike Pall. http://luajit.org/
    
安装 LuaJIT 的 Redis 和 MySQL 驱动
  1. Redis

    • 使用 lua-resty-redis(基于 LuaJIT 的高性能 Redis 客户端)。
      luarocks install lua-resty-redis
      
  2. MySQL

    • 使用 luasql-mysql(兼容 LuaJIT)。
      luarocks install luasql-mysql
      
  3. HTTP 请求

    • 使用 lua-resty-http(基于 LuaJIT 的高性能 HTTP 客户端)。
      luarocks install lua-resty-http
      
  4. JSON 解析

    • 使用 cjson(高性能 JSON 解析库)。
      luarocks install lua-cjson
      

4. 修改 Lua 脚本以适配 LuaJIT

以下是一个适配 LuaJIT 的 Lua 脚本示例:

-- dynamic_location_luajit.lua
local redis = require "resty.redis"
local mysql = require "luasql.mysql"
local http = require "resty.http"
local cjson = require "cjson"-- 连接 Redis
local redis_client = redis:new()
local ok, err = redis_client:connect("127.0.0.1", 6379)
if not ok thenfreeswitch.consoleLog("ERR", "Failed to connect to Redis: " .. err)return
end-- 连接 MySQL
local mysql_env = mysql.mysql()
local mysql_conn = mysql_env:connect("database_name", "username", "password", "hostname", 3306)-- 查询 Redis
function query_redis(prefix)local key = "number_location:" .. prefixlocal value, err = redis_client:get(key)if value thenreturn cjson.decode(value)endreturn nil
end-- 查询 MySQL
function query_mysql(prefix)local cursor, err = mysql_conn:execute(string.format("SELECT province, city, carrier FROM number_location WHERE prefix = '%s'", prefix))if not cursor thenreturn nil, errendlocal row = cursor:fetch({}, "a")if row thenreturn rowendreturn nil, "No match found"
end-- 发起 HTTP 请求
function query_http_api(prefix)local http_client = http.new()local res, err = http_client:request_uri("https://api.example.com/location", {method = "GET",query = { number = prefix },})if res and res.status == 200 thenreturn cjson.decode(res.body)endreturn nil, "API request failed"
end-- 将归属地信息存入 Redis 和 MySQL
function save_location(prefix, province, city, carrier)-- 存入 Redislocal key = "number_location:" .. prefixlocal value = cjson.encode({province = province, city = city, carrier = carrier})redis_client:set(key, value)-- 存入 MySQLlocal query = string.format("INSERT INTO number_location (prefix, province, city, carrier) VALUES ('%s', '%s', '%s', '%s')", prefix, province, city, carrier)mysql_conn:execute(query)
end-- 主逻辑
local number = session:getVariable("destination_number")
local clean_number = string.gsub(number, "^FURX", "") -- 去除前缀 FURX
local prefix = string.sub(clean_number, 1, 7)         -- 提取前 7 位-- 查询 Redis
local location = query_redis(prefix)
if not location then-- 查询 MySQLlocation = query_mysql(prefix)if not location then-- 发起 HTTP 请求location = query_http_api(prefix)if location then-- 将新号段信息存入 Redis 和 MySQLsave_location(prefix, location.province, location.city, location.carrier)elselocation = {province = "未知", city = "未知", carrier = "未知"}endelse-- 将 MySQL 查询结果存入 Redissave_location(prefix, location.province, location.city, location.carrier)end
end-- 设置归属地信息
session:setVariable("number_location", string.format("中国 %s %s %s", location.province, location.city, location.carrier))

5. 在 FreeSWITCH 中启用 LuaJIT

  1. 确保 FreeSWITCH 编译时启用了 LuaJIT 支持:

    • 在编译 FreeSWITCH 时,添加 --enable-luajit 选项。
    • 示例:
      ./configure --enable-luajit
      make && make install
      
  2. 在 FreeSWITCH 中调用 LuaJIT 脚本:

    • 使用 luajit 作为解释器:
      <action application="lua" data="dynamic_location_luajit.lua"/>
      

总结

  • 代码修改:大部分 Lua 脚本可以直接运行,但可以通过 FFI 和优化进一步提升性能。
  • 依赖安装:需要安装 LuaJIT 和相关的高性能库(如 lua-resty-redislua-resty-http)。
  • 性能优势:LuaJIT 在高并发场景下性能显著优于标准 Lua,适合对性能要求较高的场景。

通过以上步骤,可以在 Ubuntu 22.04 上顺利使用 LuaJIT 运行 Lua 脚本,并充分发挥其性能优势。

相关文章:

FreeSwitch的mod_translate模块详细,附带场景案例及代码示例

mod_translate 模块详细介绍 mod_translate 是 FreeSWITCH 中的一个拨号计划应用程序模块&#xff0c;用于对电话号码或字符串进行格式转换和翻译。它可以根据预定义的规则对输入的内容进行匹配和转换&#xff0c;常用于号码格式化、路由选择、号码屏蔽等场景。 主要功能 号码…...

前端504错误分析

前端出现504错误(网关超时)通常是由于代理服务器未能及时从上游服务获取响应。以下是详细分析步骤和解决方案: 1. 确认错误来源 504含义:代理服务器(如Nginx、Apache)在等待后端服务响应时超时。常见架构:前端 → 代理服务器 → 后端服务,问题通常出在代理与后端之间。…...

在 .NET 8/9 中使用 AppUser 进行 JWT 令牌身份验证

文章目录 一、引言二、什么是 JSON Web 令牌&#xff1f;三、什么是 JSON Web 令牌结构&#xff1f;四、设置 JWT 令牌身份验证4.1 创建新的 .NET 8 Web API 项目4.2 安装所需的 NuGet 软件包4.3 创建 JWT 配置模型4.4 将 JWT 配置添加到您的 appsettings.json 中4.5 为 Config…...

基于python实现机器学习的心脏病预测系统

以下是一个基于 Python 实现的简单心脏病预测系统代码示例&#xff0c;我们将使用 Scikit - learn 库中的机器学习算法&#xff08;这里以逻辑回归为例&#xff09;&#xff0c;并使用公开的心脏病数据集。 步骤&#xff1a; 数据加载与预处理&#xff1a;加载心脏病数据集&a…...

使用 NVM 随意切换 Node.js 版本

安装nvm https://github.com/coreybutler/nvm-windows/releases nvm安装详细教程&#xff08;卸载旧的nodejs&#xff0c;安装nvm、node、npm、cnpm、yarn及环境变量配置&#xff09;-CSDN博客 验证 NVM 是否安装成功-查看版本 nvm --version安装指定版本的 Node.js nvm i…...

【Prometheus】prometheus结合pushgateway实现脚本运行状态监控

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…...

SpringBoot 项目配置动态数据源

目录 一、前言二、操作1、引入依赖2、配置默认数据库 13、定义数据源实体和 Repository4、定义动态数据源5、配置数据源6、定义切换数据源注解7、定义切面类8、使用注解切换数据源 一、前言 通过切面注解方式根据不同业务动态切换数据库 二、操作 1、引入依赖 <dependen…...

CSS基本选择器

1. 通配选择器 作用&#xff1a;可以选中所有的 HTML 元素。 语法&#xff1a; * { 属性名: 属性值; } 举例&#xff1a; <!DOCTYPE html> <html lang"zh-cn"> <head><meta charset"UTF-8"><meta name"viewport" …...

idea-代码补全快捷键

文章目录 前言idea-代码补全快捷键1. 基本补全2. 类型匹配补全3. 后缀补全4. 代码补全 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差&#xff0c;…...

基于SpringBoot+vue粮油商城小程序系统

粮油商城小程序为用户提供方便快捷的在线购物体验&#xff0c;包括大米、面粉、食用油、调味品等各种粮油产品的选购&#xff0c;用户可以浏览商品详情、对比价格、下单支付等操作。同时&#xff0c;商城还提供优惠活动、积分兑换等福利&#xff0c;让用户享受到更多实惠和便利…...

挪车小程序挪车二维码php+uniapp

一款基于FastAdminThinkPHP开发的匿名通知车主挪车微信小程序&#xff0c;采用匿名通话的方式&#xff0c;用户只能在有效期内拨打车主电话&#xff0c;过期失效&#xff0c;从而保护车主和用户隐私。提供微信小程序端和服务端源码&#xff0c;支持私有化部署。 更新日志 V1.0…...

企业内部知识库:安全协作打造企业智慧运营基石

内容概要 作为企业智慧运营的核心载体&#xff0c;企业内部知识库通过结构化的信息聚合与动态化的知识流动&#xff0c;为组织提供了从数据沉淀到价值转化的系统性框架。其底层架构以权限管理为核心&#xff0c;依托数据加密技术构建多层级访问控制机制&#xff0c;确保敏感信…...

网络安全推荐的视频教程 网络安全系列

第一章 网络安全概述 1.2.1 网络安全概念P4 网络安全是指网络系统的硬件、软件及其系统中的数据受到保护&#xff0c;不因偶然的或恶意的原因而遭到破坏、更改、泄露&#xff0c;系统连续可靠正常地运行&#xff0c;网络服务不中断。 1.2.3 网络安全的种类P5 &#xff08;1…...

麒麟管家全新升级,运维问题“一键修复”

麒麟管家是openKylin社区SystemManager SIG开发的一款面向社区用户&#xff0c;能倾听用户烦恼和诉求&#xff0c;也能提供便利途径、解决用户问题的系统管理类应用&#xff0c;可以为用户提供问题反馈、系统垃圾清理、电脑故障排查、硬件设备管理及系统小工具等一站式服务&…...

MVCC(多版本并发控制)机制讲解

MVCC&#xff08;Multi-Version Concurrency Control&#xff0c;多版本并发控制&#xff09;这是一个在数据库管理系统中非常重要的技术&#xff0c;尤其是在处理并发事务时。别担心&#xff0c;我会用简单易懂的方式来讲解&#xff0c;让你轻松掌握它的原理和作用。 1. 什么是…...

React 与 Vue 对比指南 - 上

React 与 Vue 对比指南 - 上 本文将展示如何在 React 和 Vue 中实现常见功能&#xff0c;从基础渲染到高级状态管理 Hello 分别使用 react 和 vue 写一个 Hello World&#xff01; react export default () > {return <div>Hello World!</div>; }vue <…...

开源协议深度解析:理解MIT、GPL、Apache等常见许可证

目录 前言1. MIT协议&#xff1a;自由而宽松的开源许可1.1 MIT协议的主要特点1.2 MIT协议的适用场景 2. GPL协议&#xff1a;自由软件的捍卫者2.1 GPL协议的核心理念2.2 GPL协议的适用场景 3. Apache License 2.0&#xff1a;开源与专利保护的平衡3.1 Apache License 2.0的主要…...

通用评估系统(五)- 前端部分总体说明

通用评估系统&#xff08;五&#xff09;- 前端部分总体说明 相关链接 Gitee地址通用评估系统&#xff08;一&#xff09;- 介绍通用评估系统&#xff08;二&#xff09;- 原型设计通用评估系统&#xff08;三&#xff09;- 前端部分通用评估系统&#xff08;四&#xff09;-…...

STM32GPIO

目录 GPIO基本结构GPIO位结构输入部分输出部分 输出模式GPIO模式浮空/上拉/下拉模拟输入开漏和推挽复用开漏/复用推挽 GPIO基本结构 GPIO是挂载在APB2总线上的外设。GPIO结构中的寄存器分为输入寄存器和输出寄存器&#xff0c;APB2总线通过输出寄存器向引脚发送数据&#xff0c…...

MyBatis拦截器终极指南:从原理到企业级实战

在本篇文章中&#xff0c;我们将深入了解如何编写一个 MyBatis 拦截器&#xff0c;并通过一个示例来展示如何在执行数据库操作&#xff08;如插入或更新&#xff09;时&#xff0c;自动填充某些字段&#xff08;例如 createdBy 和 updatedBy&#xff09;信息。本文将详细讲解拦…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API&#xff0c;用于在函数组件中使用 state 和其他 React 特性&#xff08;例如生命周期方法、context 等&#xff09;。Hooks 通过简洁的函数接口&#xff0c;解决了状态与 UI 的高度解耦&#xff0c;通过函数式编程范式实现更灵活 Rea…...

生成xcframework

打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式&#xff0c;可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

23-Oracle 23 ai 区块链表(Blockchain Table)

小伙伴有没有在金融强合规的领域中遇见&#xff0c;必须要保持数据不可变&#xff0c;管理员都无法修改和留痕的要求。比如医疗的电子病历中&#xff0c;影像检查检验结果不可篡改行的&#xff0c;药品追溯过程中数据只可插入无法删除的特性需求&#xff1b;登录日志、修改日志…...

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

AtCoder 第409​场初级竞赛 A~E题解

A Conflict 【题目链接】 原题链接&#xff1a;A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串&#xff0c;只有在同时为 o 时输出 Yes 并结束程序&#xff0c;否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

【机器视觉】单目测距——运动结构恢复

ps&#xff1a;图是随便找的&#xff0c;为了凑个封面 前言 在前面对光流法进行进一步改进&#xff0c;希望将2D光流推广至3D场景流时&#xff0c;发现2D转3D过程中存在尺度歧义问题&#xff0c;需要补全摄像头拍摄图像中缺失的深度信息&#xff0c;否则解空间不收敛&#xf…...

测试markdown--肇兴

day1&#xff1a; 1、去程&#xff1a;7:04 --11:32高铁 高铁右转上售票大厅2楼&#xff0c;穿过候车厅下一楼&#xff0c;上大巴车 &#xffe5;10/人 **2、到达&#xff1a;**12点多到达寨子&#xff0c;买门票&#xff0c;美团/抖音&#xff1a;&#xffe5;78人 3、中饭&a…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

c#开发AI模型对话

AI模型 前面已经介绍了一般AI模型本地部署&#xff0c;直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型&#xff0c;但是目前国内可能使用不多&#xff0c;至少实践例子很少看见。开发训练模型就不介绍了&am…...

Spring AI与Spring Modulith核心技术解析

Spring AI核心架构解析 Spring AI&#xff08;https://spring.io/projects/spring-ai&#xff09;作为Spring生态中的AI集成框架&#xff0c;其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似&#xff0c;但特别为多语…...