算法备案,微信小程序类目深度合成类目(AI绘画、AI换脸、AI问答)审核通过办法

算法备案,微信小程序类目深度合成类目(AI绘画、AI换脸、AI问答)审核通过办法

1、小程序发布新版本说类目不符

微信小程序的类目审核过审核 深度合成(AI问答、AI换脸、AI绘画)

昨天提交小程序被拒绝了,发现新增了一个深度合成类目,其中包含小程序只要涉及到AI问答,AI绘画,AI换脸都需要补充类目才能提交审核新版本了。

如果你也是发版小程序新版本,发现提示类目不符,要求添加深度合成(AI绘画、AI换脸、AI问答)类目要求。因为算法备案的新规落实了,具体的备案系统是 “互联网信息服务算法备案系统”

我也遇到了同样的问题,只要被判定为使用了深度合成的小程序都会要求审核添加新的深度合成的类目

现在经过研究一次性通过了微信小程序 深度合成-AI问答、深度合成-AI换脸、深度合成-AI绘画三个类目审核。

 

​2、过类目审核的2种方式

方式1: 自己去完成自己的算法备案

方式2: 使用三方提供商已经算法备案过的算法服务证明(也就是合同)

⚠️注意 只有企业(含个体户)的才开放了深度合成类目,个人的小程序不行,存量已经上架的暂时也没解决方案,个人的暂时解决不了。

方式1比较麻烦了,周期太长了。

方式2是目前推荐的,一般1个工作日可以搞定。

方式2的办法就是找到已经算法备案的公司提供了公有云服务,这里我们选择百度云

3、准备的材料和步骤

营业执照(个体户的营业执照也可以)

手机号码

企业公章

步骤

注册百度云帐号并认证成公司 (注意主体公司要和小程序一致)

申请千帆大模型平台

生成合同并盖公司的章子(拍照成图片,多张拼接成一张长图)

算法备案网站百度的备案截图

最后去审核小程序类目吧,选三方,上传资料

一般1个工作日即可审核通过

最后如果你自己折腾后还是不通过

 

如果你有需求+威💚(留言备注:小程序),我提供过小程序深度合成类目服务

微信小程序的类目审核过审核 深度合成(AI问答、AI换脸、AI绘画)

微信小程序的类目审核过审核 深度合成(AI问答、AI换脸、AI绘画)

1. 需求

业务上对业务表就行重构,重新设计了新表,需要原有的数据准实时同步到新表(目标表)

字段类型和数据值等做了修改需要处理映射

例如

1是,2否需要映射为 1是,0否

varchar存储的时间转换为datetime

int存储的时间戳要转换为datetime

新表设计了新状态码,需要按照对照表做数据值映射

三表合一逻辑结构为

源表主键/唯一建 类别 需要的字段 源表 目标表 目标表主键
id 事实主表 10+ old_orders new_orders id(订单号)
order_id 业务延迟产生数据 2+ old_orders_extend1
order_id 业务延迟产生数据 1个 old_orders_extend2

需要实现,三张源表的的insert和update操作准实时的方式同步到同目标目标表中

2.设计

2.1 功能性设计

准实时,必须使用MySQL binlog订阅CDC的方式,使用CloudCanal作为准实时同步数据平台

字段类型和数据值的转化映射,使用CloudCanal的自定义数据处理插件功能,自编写业务处理代码(java)实现

2.2 鲁棒性设计

建表

null处理,目标表字段设置为允许为null,虽然在数据库上常规来讲设置为not null更性能,但是考虑综合收益新表就尽量不使用 not null,这样有更好的数据导入容错性

容量,目标表表字段容量大小必须>= 源表对应的字段,一般情况保持原有大小一致

数据转换

格式化,时间字段格式化,文本格式的字段类型转换为datatime类型时做好异常捕获,确保格式化为正确的日期,保障数据写入更新不出问题

脏数据,其他脏数据的预处理

2.3 CloudCanal任务设计

事实主表建立一个任务实例,这样做保障事实主表独立

业务延迟产生数据的表建立一个任务实例,这样做和事实主表的同步独立,互不影响

3.实现

3.1 目标表建表

注意符合鲁棒性设计中的建表设计

具体内容略…

3.2 编写自定义数据处理插件

常规的一些转换操作看CloudCanal提供的示例代码即可

处理技术重点在,业务延迟产生数据的时候,需要把insert和update都转换为update

处理insert时候,需要同时保留getAfterColumnMap和getBeforeColumnMap,由于insert是没有变更前数据的,所以设置getBeforeColumnMap为getAfterColumnMap即可

核心代码为

//  本文来自 www.iamle.com 流水理鱼
public static void changeOrderExtend1FillColumnMap(CustomFieldV2 oriBf, LinkedHashMap<String, CustomFieldV2> target) {
    CustomFieldV2 bf = CustomFieldV2.buildField(oriBf.getFieldName(), oriBf.getValue(), oriBf.getSqlType(), oriBf.isKey(), oriBf.isNull(), oriBf.isUpdated());
    target.put(oriBf.getFieldName(), bf);
}

private static void processOrderExtend1InsertAndUpdateToUpdate(CustomData data, List<CustomData> re, EventTypeInSdk eventType) {
    for (CustomRecordV2 oriRecord : data.getRecords()) {
        CustomRecordV2 updateRecord = new CustomRecordV2();

        if (eventType.equals(EventTypeInSdk.INSERT)) {
            // 需要处理 afterColumnMap > beforeColumnMap
            for (Map.Entry<String, CustomFieldV2> f : oriRecord.getAfterColumnMap().entrySet()) {
                changeOrderExtend1FillColumnMap(f.getValue(), updateRecord.getBeforeColumnMap());
            }

            for (Map.Entry<String, CustomFieldV2> f : oriRecord.getAfterKeyColumnMap().entrySet()) {
                changeOrderExtend1FillColumnMap(f.getValue(), updateRecord.getBeforeKeyColumnMap());
            }
        }

        if (eventType.equals(EventTypeInSdk.UPDATE)) {
            // 需要处理 beforeColumnMap
            for (Map.Entry<String, CustomFieldV2> f : oriRecord.getBeforeColumnMap().entrySet()) {
                changeOrderExtend1FillColumnMap(f.getValue(), updateRecord.getBeforeColumnMap());
            }

            for (Map.Entry<String, CustomFieldV2> f : oriRecord.getBeforeKeyColumnMap().entrySet()) {
                changeOrderExtend1FillColumnMap(f.getValue(), updateRecord.getBeforeKeyColumnMap());
            }
        }

        for (Map.Entry<String, CustomFieldV2> f : oriRecord.getAfterColumnMap().entrySet()) {
            changeOrderExtend1FillColumnMap(f.getValue(), updateRecord.getAfterColumnMap());
        }

        for (Map.Entry<String, CustomFieldV2> f : oriRecord.getAfterKeyColumnMap().entrySet()) {
            changeOrderExtend1FillColumnMap(f.getValue(), updateRecord.getAfterKeyColumnMap());
        }

        List<CustomRecordV2> updateRecords = new ArrayList<>();
        updateRecords.add(updateRecord);

        SchemaInfo updateSchemaInfo = data.cloneSchemaInfo(data.getSchemaInfo());
        CustomData updateData = new CustomData(updateSchemaInfo, EventTypeInSdk.UPDATE, updateRecords);

        re.add(updateData);
    }
}

工程项目代码参考:

cloudcanal-data-process 本工程汇集了 CloudCanal 数据处理插件,以达成数据自定义 transformation 目标

https://gitee.com/clougence/cloudcanal-data-process

问题!
insert改写为update在增量同步阶段工作正常,在全量迁移阶段会报错!
截止2023年3月代码中没有办法判断任务阶段是在 全量迁移还是增量同步,所以只能在第一次建立任务的时候不要改写insert成为update,待任务正常执行时候,再重新上传激活代码处理包,
让insert改写update在增量同步阶段工作
由于全量迁移阶段的insert数据没同步,这个暂时只有单独手动处理更新到目标表去

2023-03-14 14:22:50.791 [full-task-executor-7-thd-1] INFO  c.c.c.b.service.task.parser.full.RdbSinglePkPageScanner - [FINISH SCAN!]null.你的库.old_orders_extend1, cost time 222 ms.migrated count is 1774
2023-03-14 14:22:51.660 [full-apply-disruptor-6-thd-3] ERROR c.c.c.task.applier.full.FullDisruptorExceptionHandler - disruptor process full event error,msg:IllegalArgumentException: unsupported one FullEvent with multi CustomData.
java.lang.IllegalArgumentException: unsupported one FullEvent with multi CustomData.
 at com.clougence.cloudcanal.task.data.process.pkg.editorv2.FullDataDeSerializer.deserialize(FullDataDeSerializer.java:59)
 at com.clougence.cloudcanal.task.data.process.pkg.editorv2.FullDataDeSerializer.deserialize(FullDataDeSerializer.java:18)
 at com.clougence.cloudcanal.task.data.process.pkg.V2PkgProcessor.process(V2PkgProcessor.java:125)
 at com.clougence.cloudcanal.task.data.process.pkg.CustomPkgProcessor.process(CustomPkgProcessor.java:73)
 at com.clougence.cloudcanal.task.data.process.DataProcHandlerImpl.process(DataProcHandlerImpl.java:19)
 at com.clougence.cloudcanal.task.data.process.FullDataProcWorkerHandler.onEvent(FullDataProcWorkerHandler.java:32)
 at com.clougence.cloudcanal.task.data.process.FullDataProcWorkerHandler.onEvent(FullDataProcWorkerHandler.java:12)
 at com.lmax.disruptor.WorkProcessor.run(WorkProcessor.java:143)
 at java.lang.Thread.run(Thread.java:748)

3.3 CloudCanal任务建立

事实主表建立一个任务实例

old_orders 到 new_orders,并配置好字段映射

业务延迟产生数据的表建立一个任务实例

old_orders_extend1 到 new_orders,并配置好字段映射

old_orders_extend2 到 new_orders,并配置好字段映射

建立任务时在配置时候 数据处理 界面上传自定义代码的jar包

自定义代码jar包更新

激活后需要重启任务(PS:需要重启至少2次才生效,这个是CloudCanal bug,截止2023-3-10 还未解决)

3.4 实现结果

源表的insert和update都会准实时的同步到目标表达到设计目标

1. 数据平台数仓平台架构设计大图

1.1 基于Apache Doris(以下简称Doris)的实时数仓架构大图

基于Apache Doris的实时数仓架构图 流水理鱼 wwek

1.1 架构图说明

数据源
主要是业务数据库MySQL,当然也可以是其他的关系型数据库

数据集成和处理
实时,原封不动同步的数据使用CloudCanal;需要复杂的数据加工处理,使用Flink SQL ,并用Dinky FlinkSQL Studio 实时计算平台来开发、管理、运行Flink SQL
离线,使用DataX、SeaTunel,并用海豚调度(DolphinScheduler)编排任务调度进行数据集成和处理

数据仓库
主体是使用Doris作为数据仓库
ES、MySQL、Redis作为辅助数仓,同时ES也作为搜索引擎使用,数据同步复用该套架构

数据应用
总体分类2大类
自己开发API、BI等数据应用服务
三方BI ,商业Tableau、帆软BI、乾坤,开源Superset、Metabase等之上构建数据应用服务

2. 为什么是Doris?

2.1 Doris核心优势

Apache Doris 简单易用、高性能和统一的分析数据库,他是开源的!

简单易用
部署只需两个进程,不依赖其他系统;在线集群扩缩容,自动副本修复;兼容 MySQL 协议,并且使用标准 SQL

高性能
依托列式存储引擎、现代的 MPP 架构、向量化查询引擎、预聚合物化视图、数据索引的实现,在低延迟和高吞吐查询上, 都达到了极速性能

统一数仓
单一系统,可以同时支持实时数据服务、交互数据分析和离线数据处理场景

联邦查询
支持对 Hive、Iceberg、Hudi 等数据湖和 MySQL、Elasticsearch 等数据库的联邦查询分析

多种导入
支持从 HDFS/S3 等批量拉取导入和 MySQL Binlog/Kafka 等流式拉取导入;支持通过HTTP接口进行微批量推送写入和 JDBC 中使用 Insert 实时推送写入

生态丰富
Spark 利用 Spark Doris Connector 读取和写入 Doris;Flink Doris Connector 配合 Flink CDC 实现数据 Exactly Once 写入 Doris;利用 DBT Doris Adapter,可以很容易的在 Doris 中完成数据转化

2.2 实际使用体验

查询性能满足需求
实战下来,千万级数据主表,再join几个小表,聚合查询,8C16G硬件配置单机运行Doris,查询能在3s内
join支持友好,一个查询关联10+个表后查询也毫无压力

文本作者为 流水理鱼 wwek https://www.iamle.com

2.3 杀鸡用牛刀Hadoop、Hive

Hadoop、Hive大象固然好,但是对于大部分中小型企业来说,这个就是牛刀,现在的主流数据湖也是牛刀,大部分中小公司研发都会面临杀鸡难道用牛刀的情况。
传统的大数据基本都是玩离线的,业务要求的实时性如何做到
所以TB级别的数据应该用对应的解决方案,那就是MPP架构的Doris

2.4 平替商业分析型数据库

能够平替阿里云ADB(阿里云的分析型数据库)
和相同的数据库表,想同的服务器配置,在体验上大部分Doris比阿里云ADB更快,小部分相当或更慢(非严格的测试对比,仅仅是自己特定的场景下的结果)

3、让架构实际落地的渐进式方案

3.1 V1.0 解决MySQL不能做OLAP分析查询的问题

绝大多数中小公司都有朴实的需求,我要实时大屏,业务数据统计报表
而这个时候你发现MySQL已经不支持当前的报表统计查询了,已经卡爆了,加从库也不行
这就是V1.0 落地方案要解决的问题

MySQL是行存数据库也即是面向OLTP的,不是面向OLAP分析查询的,所以不适合做数据报表等数据应用,数据量不大,时间跨度不大用MySQL从库还能一战,但终归你会遇到MySQL已经不支持你的报表查询SQL的时候

如何最低成本的让各类数据应用能实时、高性能的查询,答案是Doirs
V1.0 落地数仓核心是:原封不动的实时同步了一份业务数据库MySQL中的表到 Doris

对应架构图中
数据源 数据集成 数据仓库 数据应用
业务MySQL 》 CloudCanal 》 Doris 》 数据查询应用
其实这就相当于完成了数据仓库的ODS层,直接用ODS层,在数仓中使用原始业务库表,是最简单的开始,这个时候已经支持绝大多数据应用了,业务需求大多数都能得到满足。

数据应用方面可以很易用,因为Doirs支持MySQL协议,又支持标准SQL
所以不管是商业或开源BI软件,自己程序开发API都能快速进行数据应用服务支持

在落地成本方面,1-2人开发者(甚至还不是数据开发工程师),服务器16C32G * 3 人、机资源即可落地

3.2 V2.0 解决更大的数据、更复杂的数据加工的问题

如果有更多的数据集成的需求,还有更复杂的ETL数据加工的需求
推荐使用:
实时 Flink SQL 使用 Dinky FlinkSQL Studio 实时计算平台来开发、管理、运行Flink SQL
离线 DataX、SeaTunnel 使用海豚(DolphinScheduler)调度编排和调度

实时场景下一般使用CDC,离线场景下一般使用SQL查询抽取

3.3 V3.0 走向成熟的数仓分层、数据治理

该阶段建立 数仓分层模型
建立数仓分层模型的好处:数据结构化更清晰、数据血缘追踪、增强数据复用能力、简化复杂问题、减少业务影响、统一数据口径

ADS层
数据集市

DWS层
分析主题域,“轻粒度汇总表”

DWD层
业务主题划分域,并打成“事实明细宽表”

ODS层
贴源层,也就是业务数据原封不动的同步过来存储到数仓

DIM层
维度数据

具体的细节怎么设计、规范怎么定,这已超出了本文的范围,到了这个阶段您也不需要看本文了

1. 问题和解决办法

1.1 SELECT 字段中的非聚合函数包裹的字段,必须在 GROUP BY中申明

# SQLSTATE[HY000]: General error: 1064 '`u`.`name`' must be an aggregate expression or appear in GROUP BY clause

这个问题会是遇到最多的问题(实际上在大数据场景,例如hive中也有这个要求的)
因为ADB(MySQL)引擎是MySQL所以对这个没要求,导致迁移到Doris的时候这个问题最为突出
时间处理函数、字符串处理函数这种是非聚合函数,所以也是需要在GROUP BY中申明的

1.2 Doris的 GROUP_CONCAT函数中字段前不支持用distinct去重 #11932

https://github.com/StarRocks/starrocks/issues/8079
array_distinct(array_agg(str_col))
to get a distinct a array value.
And if you want to make it a value you can use the following function
array_join(array_distinct(array_agg(str_col)), ‘,’)

也就是先取出列为数组,然后去重数组,再把数组拼接字符串

ps:新版本的Doris已经支持,见
[Bug] doris的group_concat函数不支持distinct #11932

1.3 GROUP_CONCAT函数中的字段不支持int类型

需要拼接的字段如果为int类型那么必须先强转成字符串,使用CAST函数
GROUP_CONCAT(CAST( id整形字段 as STRING), ‘,’)

1.4 SUBSTRING_INDEX函数不支持

截止2022年11月11日需要使用UDF,也就是用户定义函数解决
Apache Doris 1.1 版本只支持原生UDF,也就是需要重新编译整个Doris,1.2 版本开始支持Java UDF 可以动态挂载
StarRocks 2.2.0 版本开始支持Java UDF 可以动态挂载

提供一个已经实现好的StarRocks Java UDF,可直接使用(由同事贡献)
代码

package com.starrocks.udf;  

import org.apache.commons.lang3.StringUtils;  

/**  
 * 根据下标截取  
 *  
 * @author dingyoukun  
 * @date 2022-10-26 14:30  
 **/public class SubStringByIndex {  
    public final String evaluate (String targetStr, String str, Integer index) {  
        Boolean desc = false;  
        if (index < 0 ) {  
            desc = true;  
            index = Math.abs(index);  
        }  

        String result = targetStr;  
        String partStr = str;  

        if (StringUtils.isBlank(targetStr)) {  
            return result;  
        }  

        if (index == 0) {  
            return targetStr;  
        }  

        if (desc) {  
            targetStr = new StringBuffer(targetStr).reverse().toString();  
            partStr = new StringBuffer(partStr).reverse().toString();  
        }  
        int beginIndex = 0;  
        int count = 0;  
        while ((beginIndex = targetStr.indexOf(partStr, beginIndex)) != -1) {  
            count++;  
            if (count == index) {  
                if (desc) {  
                    targetStr = new StringBuffer(targetStr).reverse().toString();  
                    result = targetStr.substring(targetStr.length() - beginIndex);  
                } else {  
                    result = targetStr.substring(0, beginIndex);  
                }  
                return result;  
            }  
            beginIndex = beginIndex + partStr.length();  
        }  
        return result;  
    }  
}

2. 参考

https://docs.starrocks.io/zh-cn/latest/introduction/StarRocks_intro
https://github.com/StarRocks/starrocks/issues

https://github.com/apache/doris/issues
https://doris.apache.org/zh-CN/docs/summary/basic-summary

[Apache Doris Java UDF https://doris.apache.org/zh-CN/docs/dev/ecosystem/udf/java-user-defined-function] (https://doris.apache.org/zh-CN/docs/dev/ecosystem/udf/java-user-defined-function)
[StarRocks Java UDF https://docs.starrocks.io/zh-cn/latest/sql-reference/sql-functions/JAVA_UDF] (https://docs.starrocks.io/zh-cn/latest/sql-reference/sql-functions/JAVA_UDF)


title: 使用N8N工作流自动化解决三方API数据对接

0、n8n是什么?

n8n 是免费的基于节点的工作流自动化工具,可以轻松实现跨不同服务的任务自动化。它可以自托管,易于扩展,因此也可以与内部工具一起使用。

1、安装

参见n8n官方文档^[1],推荐docker安装

2、需求案例

在商业推广中往往有一些API对接的需求,细分下来有这几类
* ① 通用广告投放平台 如果是广点通、巨量引擎、橙子建站线索API对接等
* ② 我方开放API
* ③ 三方API对接
市场推广活动会产生三方API对接的需求。
这样的需求去基于代码开发的方式对接,对于开发者来说价值不高,使用N8N能解决这种对接问题

例如一个推广服务商使用API提供线索,需要实现全自动化同步到我方系统

工作流逻辑上就是: 计划任务周期运行 > 读取数据通过API(三方) > 数据转换处理 > 写入数据通过API(我方)

3、n8n实现

n8n工作流可视化效果图
使用N8N工作流自动化解决三方API数据对接

共需 4个 核心节点(node)(写入excel文件可忽略)
计划任务周期运行Cron CORE NODES > Flow > Cron
读取数据通过API(三方)DEVELOPMENT > HTTP Request
数据转换处理 CORE NODES > Function
写入数据通过API(我方)DEVELOPMENT > HTTP Request

主要说下数据转换处理

数据转换处理把接口返回数据变成 n8n中 items列表
接口返回数据在n8n中默认为items,由于返回的多条数据是在json中data字段中,需要做个转换取出来转换成n8n中 items列表供下游节点使用。
items[0].json.data 取接口返回数据,json数据,data字段

let rows = []; 

for (item of items[0].json.data) {
  rows.push(item);
}

console.log('Done!');
return rows;

在n8n中的函数代码都是js编写的

数据转换处理好后,items就有多个了,本工作流中取到了3个items,对于「写入数据通过API」就会调用3次

4、参考

1. n8n官方文档

基于FreeSWITCH自建呼叫中心中台 流水理鱼|wwek PPT分享

目录导航

  • 业务需求背景情况 – 业务需求、背景情况
  • 业务系统如何外呼 – 点呼、群呼、AI
  • 呼叫中心通话链路 – 通话序列图、呼叫线路、SIP协议介绍
  • 呼叫中心中台架构 – 呼叫中心中台架构设计(基础版)
  • FreeSWITCH-介绍 – FreeSWITCH电话软交换
  • FreeSWITCH-拨通第一个电话 – SIP Hello World
  • FreeSWITCH-集成 – FreeSWITCH系统集成设计和实现
  • FreeSWITCH-中台API封装 – 把FreeSWITCH的能力封装为中台API

业务需求背景情况

  • CRM中常见需要对ToB、ToC客户进行电话回访、电话销售
  • 在打电话这个事上,企业需求比个人需求要求更多,最基本的可系统集成、有话单、有录音等
  • 无论时代如何变,传统的基于运营商电话的接通需求是一直稳定存在的
  • 三方商业呼叫系统有很多,okcc、合力忆捷、天润、容联七陌、网易七鱼等
  • 自研呼叫解决3个主要核心问题。能力上,定制开发扩展性拉满;成本上,比购买商业呼叫系统便宜;安全上,数据在手
  • 自研呼叫需要具备条件,公司业务按年为单位长期有呼叫需求,有开发人员资源

业务系统如何外呼

  • 呼叫方式上,单个点击拨打(点呼)、批量呼通再分配排队坐席(群呼)、AI自动呼叫
  • 坐席(打电话人)软电话登录,登录呼叫中心的软电话客户端(无客户端的为网页浏览器客户端)
  • 业务系统中点击拨打、或者操作建立群呼、AI呼叫任务
  • 业务系统通过API调用呼叫中心控制发起每通电话,接下来看看通话序列图

查看文章

☎️呼叫中心通话链路 – 呼叫线路示意图

“`mermaid {theme: ‘neutral’, scale: 0.66}
graph LR
A[呼叫中心系统] –>|呼叫线路选择| B(选择线路网关1)
B –> |SIP|C{呼叫线路商1线路路由}
A[呼叫中心系统] –>|呼叫线路选择| Bn(选择线路网关N)
C –>|电信运营商落地1| D[A地固话号]
C –>|电信运营商落地2| E[B地固话号]
C –>|电信运营商落地3| F[C地手机号]
Bn –> |SIP|Cn{呼叫线路商N线路路由}
Cn –>|电信运营商落地1| Dn[A地固话号]
Cn –>|电信运营商落地2| En[B地手机号]
Cn –>|电信运营商落地3| Fn[C地手机号]

<pre><code><br /><br /># 呼叫中心通话链路 – 呼叫线路介绍
– 呼叫线路商提供的线路在 呼叫中心系统 有多个称呼,线路网关、SIP中继、中继网关、落地线路
– 呼叫线路的称呼,不论叫什么,他都是电话的通道,使用SIP协议对接
– 呼叫线路商使用VOS语言运营系统作为支持软件,VOS处于VOIP运营垄断地位
– 不使用呼叫线路商,直找电信运营商对接线路,用SIP协议对接
– 这样就相当于只有一条落地线路了,没有丰富的线路资源来优化线路路由,需按业务需求选择
– **呼叫线路商的本质是聚合多条、多地、多类型电信运营商线路资源**
一个典型的线路对接信息
</code></pre>

线路备注:xx线路
IP:8.8.8.8
UDP端口:5060 (SIP协议)
主叫送:20220601
被叫加前缀:6
并发:500
限制:一天一次
盲区:北、新、西

<pre><code><br /># SIP协议介绍
SIP(Session Initiation Protocol,会话初始协议)[^1]是由IETF(Internet Engineering Task Force,因特网工程任务组)制定的多媒体通信协议。广泛应用于CS(Circuit Switched,电路交换)、NGN(Next Generation Network,下一代网络)以及IMS(IP Multimedia Subsystem,IP多媒体子系统)的网络中,可以支持并应用于语音、视频、数据等多媒体业务,同时也可以应用于Presence(呈现)、Instant Message(即时消息)等特色业务。可以说,有IP网络的地方就有SIP协议的存在。SIP类似于HTTP
– **说人话!SIP协议用来在IP网络上做电话通讯**
[^1]: [一文详解 SIP 协议](https://www.cnblogs.com/xiaxveliang/p/12434170.html)

# SIP协议介绍-SIP Server
![](https://static.iamle.com/note/202207011111385.png)
– FreeSWITCH就是一个SIP Server,也是一个B2BUA,后面具体讲 FreeSWITCH
– **呼叫中心的SIP Server桥接 A leg 和 B leg,这样A和B就建立通话了**
– B2BUA看起来唬人,靠背嘛,就是A leg 和 B leg靠背桥接起来
– 支持SIP协议的软电话客户端常用的有:Linphone、MicroSIP、Eyebeam等

# SIP协议介绍-SIP Server 和 A leg 通讯
<img class="m-1 h-110 rounded" src="https://static.iamle.com/note/202207011131334.png" />

# SIP协议介绍-SIP Server 和 B leg 通讯
<img class="m-1 h-110 rounded" src="https://static.iamle.com/note/202207011124286.png" />

# 呼叫中心中台架构 – 呼叫中心中台架构设计(基础版)目标
– 基础版设计目标,实现点击拨打(点呼)
– **呼叫能力的高级抽象**
## 呼叫(主叫号码,被叫号码,[线路网关], [拓展数据])

– 主被叫号码既可以是内部的坐席分机号码,也可以是手机号码
– 线路网关是选填参数,支持多个逗号分割,为空使用系统默认配置网关
– 拓展数据是选填参数,呼叫系统在后续通话技术后原样传回业务系统
– 拓展数据,用于业务自身逻辑

layout: center

# 呼叫中心中台架构 – 呼叫中心中台架构设计(基础版)架构图
<img class="m-1 h-110 rounded" src="https://static.iamle.com/note/202207011731635.png" />

# FreeSWITCH – 介绍
– FreeSWITCH 是一个作为背靠背用户代理实现的开源运营商级电话平台。由于这种设计,它可以执行大量不同的任务,从PBX到传输交换机、TTS(文本到语音)转换、音频和视频会议主机,甚至是VoIP电话等等。
– 是一款非常好用的电话软交换框架,支持跨平台,扩展性良好,配置灵活。
– 可以在很多平台上运行,包括 Linux、Mac OS X、BSD、Solaris,甚至 Windows。
– 可以处理来自 IP 网络 (VoIP) 和 PSTN(普通的固定电话)的语音、视频和文本通信。
– 支持所有流行的 VoIP 协议以及与 PRIs 的接口。
– 支持 OPUS、iLBC、Speex、GSM、G711、G722 等多种语音编解码,支持 G723、G729 等语音编解码的透传模式。
– 可以当作 PBX、SBC、媒体服务器、业务服务器等不同的通信节点来使用
– 本身是在 MPL 1.1 (Mozilla 公共许可证) 下许可的,但是一些单独的模块可能使用其他许可证。
– **说人话 FreeSWITCH 是一个软件实现的电话交换平台,开源、模块化、功能丰富**
– **市面上绝大多商业呼叫中心都是基于 FreeSWITCH 为核心开发的**

layout: center

# FreeSWITCH – 总体结构
<img class="m-1 h-120 rounded" src="https://static.iamle.com/note/202207011403991.png" />

layout: center

# FreeSWITCH – 总体结构
<img class="m-1 h-120 rounded" src="https://static.iamle.com/note/202207011405842.png" />

layout: center

# FreeSWITCH – 配置文件目录结构
/etc/freeswitch# tree -Ld 3
</code></pre>

├── freeswitch.xml 主xml文件,就是它将所有配置文件“粘”到一起,生成一个大的xml文件
├── vars.xml 常用变量
├── autoload_configs 一般都是模块级的配置文件,每个模块对应一个。文件名一般以 module_name.conf.xml 方式命名。
│   ├── *.conf.xml
├── chatplan 聊天计划
├── dialplan 拨号计划
├── directory 用户目录,分级用户账号
│   ├── default 默认的用户目录配置
│   │   ├── *.xml SIP用户,每用户一个文件
├── sip_profiles
│   ├── external SIP中继网关配置
│   │   ├── *.xml
│   ├── external.xml
│   └── internal.xml
├── ivr_menus IVR 菜单
├── jingle_profiles 连接Google Talk的相关配置
├── lang 多语言支持
├── mrcp_profiles MRCP的相关配置, 用于跟第三方语音合成和语音识别系统对接
├── skinny_profiles 思科SCCP协议话机的配置文件
├── tls tls证书
├── extensions.conf

<pre><code>- FreeSWITCH的配置文件由众多XML配置文件构建

layout: two-cols

# FreeSWITCH – 控制客户端和开发者接口
FreeSWITCH如何操作控制和开发对接?[^1]
– ① fs_cli 为命令行控制接口,也就是敲命令控制[^2]
– ② ESL(Event Socket Library) 通过事件接口和FreeSWITCH交互控制,fs_cli本质上也是走的ESL一样的底层流程
– ③ mod_xml_curl、mod_xml_rpc、lua脚本语言、自编写模块等更多方式实现和FreeSWITCH交互控制
::right::
</code></pre>

输入 fs_cli 进入命令行控制
.=======================================================.
| _____ ____ ____ _ ___ |
| | <strong><em>/ ___| / ___| | |</em> <em>| |
| | |</em> _</strong> \ | | | | | | |
| | <em>| <strong><em>) | | |</em></strong>| |___ | | |
| |</em>| |____/ ____|_____|___| |
| |
.=======================================================.
| Anthony Minessale II, Ken Rice, |
| Michael Jerris, Travis Cross |
| FreeSWITCH (http://www.freeswitch.org) |
| Paypal Donations Appreciated: paypal@freeswitch.org |
| Brought to you by ClueCon http://www.cluecon.com/ |
.=======================================================.
Type /help to see a list of commands
+OK log level [7]
freeswitch@callcenter>

<pre><code>[^1]: [FreeSWITCH Client and Developer Interfaces](https://freeswitch.org/confluence/display/FREESWITCH/Client+and+Developer+Interfaces)
[^2]: [Command Line Interface (fs_cli)](https://freeswitch.org/confluence/pages/viewpage.action?pageId=1048948)

# FreeSWITCH – 拨通第一个电话 – 分机和分机
<img class="m-1 h-90 rounded" src="https://static.iamle.com/note/202207011645715.jpg" />
* fs_cli 命令行让分机1000和分机1002通话&gt; originate user/1000 'bridge:user/1002' inline
* 分机1000使用软电话客户端Linphone,分机1002使用软电话客户端MicroSIP注册在了FreeSWITCH服务器

# FreeSWITCH – 拨通第一个电话 – 分机和手机
<img class="m-1 h-90 rounded" src="https://static.iamle.com/note/202207041015836.png" />
* fs_cli 命令行让分机1000通过网关和手机通话&gt; originate user/1000 'bridge:{origination_caller_id_number=网关主叫}sofia/gateway/网关名/被叫前缀+手机号码' inline
* 分机1000使用软电话客户端Linphone,被叫为手机号

# FreeSWITCH – FreeSWITCH系统集成设计
FreeSWITCH 的配置都是 XML的,最朴素的想法,如何实现动态配置能力?

和 FreeSWITCH 集成需要解决下列问题
|问题|方案|
| —- | —- |
| 分机动态配置 | mod_xml_curl 提供分机动态配置能力,开发对应的API输出分机XML配置文件 |
| 拨号计划动态配置 | mod_xml_curl 提供拨号计划动态配置能力,开发对应的API输出拨号计划XML配置文件 |
| 网关动态配置 | 低频需求暂时手工加载配置文件,开发对应的API可以一键生成网关XML配置文件 |
| CDR话单存储 | mod_xml_cdr 提供话单推送能力,开发对应的API接收话单推送 |
| Record录音对象存储生成URL | api_hangup_hook挂机后回调处理上传录音文件,开发对应API接收上传的录音文件,并上传到对象存储 |
| WebHook回调CDR和Record录音到业务系统 | 在CDR和Record都有了的时候执行回调 |

# FreeSWITCH – FreeSWITCH系统集成实现
集成实现采用golang编程语言开发,框架采用goframe v2

实现如下API,提供给FreeSWITCH集成
|API|实现方式| 说明 |
| —- | —- | —- |
| /fsapi/xml_curl | 读取数据库分机表生成分机XML配置文件 | mod_xml_curl模块对接 |
| /fsapi/cdr | 接收CDR话单并存储数据库CDR话单表 | mod_xml_cdr模块对接 |
| /fsapi/upload_audio | 接收录音文件上传,并上传到对象存储生成录音URL | api_hangup_hook挂机后回调处理上传录音文件 |


layout: full

# FreeSWITCH – 呼叫中心中台API封装实现
中台API和 FreeSWITCH系统集成API放同一个golang项目

|API|实现方式| 说明 |
| —- | —- | —- |
| /v1/call/callback| 调用ESL接口发送事件命令 |呼叫-回拨 参数:主叫号码、被叫号码、网关名称(多个逗号分割)、拓展数据|
| /v1/extension/list| 读取数据库分机表 |分机列表|
| /v1/extension/detail| 读取数据库分机表 |分机详情 参数:分机号|
| /v1/gateway/list| 读取数据库分机表 | 网关列表|
| /v1/gateway/detail| 读取数据库分机表 | 网关详情|
| /v1/gateway/detail_xml| 读取数据库分机表 | 网关详情XML配置文件|
| /v1/extension/online/list| 调用ESL接口 |在线分机列表|
| /v1/gateway/online/list| 调用ESL接口 |在线网关列表|
<!–

<style>
h1 {<br />
font-size: 20px;<br />
}<br />
</style> –>

# FreeSWITCH – 呼叫中心中台API封装实现-呼叫实现
– **呼叫实现核心逻辑**
使用拨号计划组合多个"APP"实现录音、录音采样率设置、挂机后上传录音等能力
拨号计划使用inline即内联模式[^1],ESL接口发送一条实践命令即可完成
</code></pre>

'app1:arg1,app2:arg2,app3:arg3' inline

<pre><code>呼叫模板
</code></pre>

"originate [参数]user/分机号
'
set:media_bug_answer_req=true,
set:record_sample_rate=8000,
set:RECORD_STEREO=true,
set:cc_record_filename=$${recordings_dir}/${strftime(%%Y-%%m-%%d)}/${uuid}.mp3,
export:nolocal:execute_on_answer=record_session:${cc_record_filename},
set:curl_sendfile_url=%s,
set:api_hangup_hook=system curl -XPOST -F \"files=@${cc_record_filename}\" ${curl_sendfile_url}?uuid=${uuid} &,
set:continue_on_failure=true,
set:hangup_after_bridge=true,
set:session_in_hangup_hook=true,
set:ringback=$${sounds_dir}/music/8000/ponce-preludio-in-e-major.wav,
bridge:[参数]sofia/gateway/网关名/前缀+被叫号码,
playback:$${sounds_dir}/zh/cn/link/misc/misc-your_call_has_been_terminated.wav,
info:,
hangup:
'
inline"
“`

⏬基于FreeSWITCH自建呼叫中心中台 PDF