指针原来是套娃的博客 - 指针原来是套娃的
type: channel
link: https://blog.imtwa.top/index.php/author/1/
description: 指针原来是套娃的
items: 一种曲折的uniapp接入第三方sdk方式
items: 微信支付sdk回调问题
items: 手写系列一：实现防抖节流
items: webpack学习笔记
items: vscode插件
items: 关于虚拟dom复用导致的组件渲染不一致问题
items: mysql开发规范
items: vue项目commit自动格式化并提交
items: js工具函数大全
items: webRTC与webSocket接入智谱视频通话API避坑指南

一种曲折的uniapp接入第三方sdk方式
type: item
date: 2026-06-01T10:17:10+08:00
description: uniapp当前痛点采用uniapp框架开发时，很多情况下会收到第三方原生生态的制约，一些第三方的服务可能只有安卓、ios、web的sdk，并不会单独对uniapp开发适配的sdk。如果想要用uts的方式进行接入，需要每个端都写一套适配的代码（安卓、ios或鸿蒙），这样带来的开发成本会很高。而且引入过多的sdk会提高app的内存大小，在云打包时安卓如果超过150MB，每打包一次都要收费。我们的项目就遇到了这样的问题，使用到的第三方服务没有提供uniapp版本的插件，需要手动的接入原生的sdk，非常复杂，而且sdk还有很多类型声明和官方文档上的不一致，成了开发的卡点。通过翻阅uniapp文档，我们发现了一种叫做renderjs的方式，可以在视图层运行的JS。renderjs介绍与使用官方文档：https://uniapp.dcloud.net.cn/tutorial/renderjs.html#renderjsuniapp打包的app，其实是一种桥接技术，将原生能力和webview桥接起来，所以在官方文档的划分中，有逻辑层和视图层两个概念。我们编写的代码都会运行在逻辑层中，通过一种特定的通信来控制视图层，所以这样会有不少的性能损耗，也是代码里不能频繁的更新视图的原因。在逻辑层中，也没有dom相关的元素给我们使用，而视图层不一样。视图层由webview渲染，拥有dom和window，renderjs天然的处于webview环境中，可以直接操作视图dom，毫无通讯损耗。最关键的是它相当于在浏览器环境运行的代码，可以接入第三方的web端sdk，正常的获取dom和window元素，不会像逻辑层的代码一样，抛出各种不兼容。使用renderjs，需要先在template中挂载view标签，并单独写一个script。代码如下： <view :prop="rtmConfig" :change:prop="agora.handleConfig" style="display: none"></view> agora可以自定义命名，rtmConfig是正常的逻辑层代码的变量，当rtmConfig绑定的数据发生改变后，会立即通知到agora的handleConfig中，这样我们就实现了逻辑层传递内容到renderjs中，函数有四个参数:newValue: 变化后的新值 oldValue: 变化前的旧值 ownerInstance: 组件实例对象，可以获取逻辑层的函数，ownerInstance.callMethod('handleRenderjsBridge', data); instance: 当前 renderjs 模块所在视图层的实例对象，通过 `instance.$el` 可以直接获取当前绑定的 DOM 元素通过document.createElement('script')挂载js，即可实现加载第三方sdk，当sdk处于本地时，h5与app上路径有些区别，app中路径为'static/js/xxx.js'，h5中使用'/static/js/xxx.js'。逻辑层通知renderjs，修改绑定的变量即可，但是renderjs通知给逻辑层就有一些曲折。因为renderjs当前只能支持vue2的代码，不支持调用setup形式中的函数，我们的处理方案是写一层vue2的script作为桥接，vue2与setup的业务逻辑中通过uni.$emit全局通讯的方式传递。这里有一个踩坑点需要注意：renderJs需要写在页面级别文件中，不能放在组件里嵌套，绑定的dom元素也需要在页面级别，不能嵌套，不然会报错Mt.setAttribute is not a function。当时排查了很久，发现从组件改到页面级文件就可以了。使用效果我们使用这套方案接入了声网RTM的sdk和国外的Stripe支付sdk，减少了很多的开发工作。在后续处理一个视频需求时，发现插件市场也有同样思路处理的视频组件，使用体验非常好，避免了很多原生video的问题：https://ext.dcloud.net.cn/plugin?id=19654以上。

微信支付sdk回调问题
type: item
date: 2026-05-29T18:32:54+08:00
description: 正常的app内购流程如下:请求后端获取订单号 ➞ 调用微信sdk发起支付 ➞ 得到微信支付成功回调 ➞ 上报后端进行校验 ➞ 一次支付完成但是在微信支付的sdk中有一个非常奇怪的行为，如果用户完成支付后不点击微信页面的“返回商家”按钮，sdk不会回调通知给客户端，导致客户端获取的状态不正确。经百度查阅资料，这种行为在2017年社区中就有人提出了，但是微信方面始终没有修复。如果客户端想要正常的处理支付成功后的逻辑，需要和后端保持沟通，从服务端获取正确的支付状态。

手写系列一：实现防抖节流
type: item
date: 2025-03-26T21:38:00+08:00
description: 防抖和节流的区别 防抖是在指定时间内只执行一次，如果 再次触发则重新计时 ，即只处理 最后一次响应 。 节流是在指定时间内只执行一次，如果再次触发则不执行，也就是 只执行第一次，后面的触发不管，等到时间过去才再响应一次触发。 防抖常用在输入框搜索、滚动加载、表单提交等高频触发只用处理最后一次响应的地方。 节流常用在滚动监听、窗口变化等，在高频的变化中一段时间内只响应第一次变化，并给出多个中间态，以实现 平滑 的变化。手写实现防抖/** * 防抖在是指定时间内只执行一次，如果再次触发则重新计时 * 一段时间内只处理最后一次事件响应 * 定时器实现 */ function debounce(fn, delay) { let timer = null; return function (...args) { console.log(args); if (timer) { clearTimeout(timer); } timer = setTimeout(() => { fn.apply(this, args); }, delay); }; } const log = debounce((...args) => { console.log("防抖，只执行",args); }, 100); log(1, 2, 3); log(4, 5, 6); setTimeout(() => { log(7, 8, 9); }, 1000); 效果：手写实现节流/** * 节流是指定时间内只执行一次，如果再次触发则不执行 * 一段时间内只处理第一次事件响应 * 定时器实现 */ function throttle(fn, delay) { let timer = null; return function (...args) { console.log(args); // 当第一次获得定时器后，除非定时器到时清除，都会阻止后面的触发 if (!timer) { timer = setTimeout(() => { fn.apply(this, args); timer = null; }, delay); } }; } /** * 时间戳实现 */ function throttle(fn, delay) { let last = 0; // 设置为0 确保第一次触发 return function (...args) { console.log(args); const now = Date.now(); // 下一次超过间隔时间才执行 if (now - last >= delay) { fn.apply(this, args); last = now; } }; } const log = throttle((...args) => { console.log("节流，只执行", args); }, 100); log(1, 2, 3); log(4, 5, 6); setTimeout(() => { log(7, 8, 9); }, 1000);效果：

webpack学习笔记
type: item
date: 2025-03-21T15:13:00+08:00
description: 一、webpack安装与配置官网：https://www.webpackjs.com/npm i webpack webpack-cli // 运行 npx webpack基本配置默认情况下，webpack会读取webpack.config.js文件作为配置文件，也可以通过CLI参数--config来指定某个配置文件 // webpack的基本配置 module.exports = { // 编译模式 "development" | "production" mode: "development", // 配置源码地图 具体配置：https://www.webpackjs.com/configuration/devtool/ devtool: "source-map", // 入口 webpack从入口开始依赖解析 entry:{ // 属性名：chunk的名称， 属性值：入口模块（启动模块） main: "./src/index.js" }, // 出口配置 output: { // 公用的公共路径 / publicPath: "/", // 输出目录为 dist 默认为dist path: path.resolve(__dirname, "dist"), // js 输出到 dist/js/xxx filename: "js/[name].[chunkhash:5].js" }, // loader配置 运行时从从右到左，从下到上 module: { // 模块的匹配规则 rules: [ // 规则1 { // 正则表达式，匹配模块的路径 test: /index\.js$/, // 匹配到了之后，使用哪些加载器 use: ["./loaders/loader1", "./loaders/loader2"] }, // 规则2 { // 正则表达式，匹配模块的路径 test: /\.js$/, // 匹配到了之后，使用哪些加载器 use: ["./loaders/loader3", "./loaders/loader4"] } ] }, // 插件配置 运行顺序看内部生命周期钩子触发顺序，监听相同生命周期时从上到下（从左到右） plugins: [ new MyPlugin({ // 插件内部配置 }) ], // 开发处理器配置 需要安装webpack-dev-server 只在开发环境有效 devServer: { port: 8080, // 自动打开网页 open: true, // 开启热替换HMR 需要在代码里写入 module.hot.accept(); 接受热更新 hot:true, // 代理规则 proxy: { "/api": { target: "http://blog.imtwa.com", //更改请求头中的host和origin changeOrigin: true } } }, // 优化配置 optimization: { splitChunks: { // 分包配置 chunks: "all", // 缓存组配置 cacheGroups:{ } }, }, resolve: { alias: { // 别名 @ = src目录 "@": path.resolve(__dirname, "src"), // 别名 _ = 工程根目录 _: __dirname, }, }, stats: { // 打包时使用不同的颜色区分信息 colors: true, // 打包时不显示具体模块信息 modules: false, // 打包时不显示入口模块信息 entrypoints: false, // 打包时不显示子模块信息 children: false, } };二、webpack打包流程webpacke是实现模块化的重要构建工具，它是将开发时态的代码转成运行时态代码，所以webpack不会影响代码的开发和运行，只会在打包时影响打包过程。所以webpack是运行在node环境下，与浏览器环境无关。初始化此阶段，webpack会将CLI参数、配置文件、默认配置进行融合，形成一个最终的配置对象。编译1、创建chunkchunk是webpack在内部构建过程中的一个概念，译为块，它表示通过某个入口找到的所有依赖的统称。根据入口模块（默认为./src/index.js）创建一个chunk每个chunk都有至少两个属性：name：默认为mainid：唯一编号，开发环境和name相同，生产环境是一个数字，从0开始2、构建所有依赖模块webpack只能分析js代码，将js代码读取后进行AST抽象语法树分析，找到每个代码的依赖，并转换代码。loader在做AST前生效，处理源代码。AST在线测试工具：http…

vscode插件
type: item
date: 2025-03-21T13:02:00+08:00
description: 因为换了几台电脑，每次都要重新安装vscode和插件，这里记录一下用到的插件和对应的配置。Chinese (Simplified) (简体中文) Languagevscode简体中文配置Live Server建立本地服务器，实时显示服务器端页面Remote Repositories查看远程仓库Code Runner执行node代码indent-rainbow缩进颜色显示Draw.io Integration绘图插件any-rule多种正则Regex Previewer检查正则ESLint检查js格式化Prettier - Code formatter格式化检查与修复GitLens — Git supercharged显示git提交CodeGeeX: AI Coding Assistant智谱ai编程助手Markdown Preview Enhancedmarkdown文件预览显示首选项 开启Enable Script Execution（js脚本执行） 启用目录F1 搜索 markdown preview enhanced: customize css 配置css样式/* Please visit the URL below for more information: */ /* https://shd101wyy.github.io/markdown-preview-enhanced/#/customize-css */ .markdown-preview.markdown-preview { // modify your style here // eg: background-color: blue; font-family: "consolas", "Noto Sans S Chinese"; font-size: 1em; } .markdown-img-description{ text-align: center; margin-top: -1em; color: #666;; margin-bottom: 2em; } html body img{ border:2px solid #ccc; } .markdown-p-center{ text-align: center; }F1 搜索 markdown preview enhanced: extend parser 配置jsconst scripts = ` `; var fs = require("fs"); module.exports = { onWillParseMarkdown: function(markdown) { return new Promise((resolve, reject) => { const reg = /\!\[(.*)\]\((\S+)\)/gm; markdown = markdown.replace(reg, function(match, g1, g2) { var width = "100%"; if (g1) { var w = g1.split("|"); if (w.length > 1) { width = w[1] + "px"; g1 = w[0]; } } return ` ${g1} `; }); resolve(markdown); }); }, onDidParseMarkdown: function(html) { return new Promise((resolve, reject) => { return resolve(scripts + html); }); } };

关于虚拟dom复用导致的组件渲染不一致问题
type: item
date: 2024-12-06T16:10:00+08:00
description: 今天开发时，有一个页面需要使用自定义的列表组件，共有两个列表，用按钮切换列表展示，但是第二个列表不需要头部的选择框。本来配置selection选项即可，但是切换过来第二个列表无论怎么配置还是带着选择框的样式，无法去掉，相同配置放到其他页面却没有选择框。代码和样式如下：<-- 部分配置已忽略 --> <el-table-wrap v-if="invoiceStatus === 'bill'" :data="dataList" > <-- 配置选择框 --> <el-table-column type="selection" width="40"> </el-table-column> </el-table-wrap> <el-table-wrap v-else :data="customDataList" > <-- 没有配置 --> </el-table-wrap>但是第二个实现出来却是这样的：但是奇怪的是，同样的配置在其它界面就没有选择框，最后经过调试发现把v-if改成v-show显示没有问题了。<-- 部分配置已忽略 --> <el-table-wrap v-show="invoiceStatus === 'bill'" :data="dataList" > <el-table-column type="selection" width="40"> </el-table-column> </el-table-wrap> <el-table-wrap v-show="invoiceStatus === 'custom'" :data="customDataList" > </el-table-wrap>原来是v-if和v-show的区别，v-if会把元素销毁，再创建元素的时候，vue在虚拟dom阶段会复用之前的节点，导致第二个列表携带着第一个列表的配置。v-show只是隐藏显示，两个列表会同时创建，vue会自动标识加上key值。当然给列表组件加上key也可以解决这个问题，给一个加上即可。<-- 部分配置已忽略 --> <el-table-wrap v-if="invoiceStatus === 'bill'" key="bill" :data="dataList" > <el-table-column type="selection" width="40"> </el-table-column> </el-table-wrap> <el-table-wrap v-else :data="customDataList" > </el-table-wrap>有不一致配置时避免使用v-if改为v-show，或者加上key值。复现demo： <el-button type="primary" @click="selection = !selection">切换</el-button> <el-table v-if="selection" :data="tableData"> <el-table-column type="selection" width="55"/> <el-table-column prop="name" label="姓名" width="120"/> <el-table-column prop="address" label="地址" show-overflow-tooltip/> </el-table> <el-table v-else :data="tableData"> <el-table-column prop="name" label="姓名" width="120"/> <el-table-column prop="address" label="地址" show-overflow-tooltip/> </el-table>

mysql开发规范
type: item
date: 2024-12-04T10:18:00+08:00
description: 1. 规范背景与目的MySQL数据库与 Oracle、 SQL Server 等数据库相比，有其内核上的优势与劣势。我们在使用MySQL数据库的时候需要遵循一定规范，扬长避短。本规范旨在帮助或指导RD、QA、OP等技术人员做出适合线上业务的数据库设计。在数据库变更和处理流程、数据库表设计、SQL编写等方面予以规范，从而为公司业务系统稳定、健康地运行提供保障。2. 设计规范2.1 数据库设计以下所有规范会按照【高危】、【强制】、【建议】三个级别进行标注，遵守优先级从高到低。对于不满足【高危】和【强制】两个级别的设计，审核人员应强制打回要求修改。2.1.1 库名【强制】库的名称必须控制在32个字符以内，相关模块的表名与表名之间尽量体现join的关系，如user表和user_login表。【强制】库的名称格式：业务系统名称_子系统名，同一模块使用的表名尽量使用统一前缀。【强制】一般分库名称命名格式是库通配名_编号，编号从0开始递增，比如wenda_001以时间进行分库的名称格式是“库通配名_时间”【强制】创建数据库时必须显式指定字符集，并且字符集只能是utf8mb4。创建数据库SQL举例：create database db1 default character set utf8mb4;。2.1.2 表结构【强制】表和列的名称必须控制在32个字符以内，表名只能使用字母、数字和下划线，一律小写。【强制】表名要求模块名强相关，如师资系统采用”sz”作为前缀，渠道系统采用”qd”作为前缀等。【强制】创建表时必须显式指定字符集为utf8mb4。【强制】创建表时必须显式指定表存储引擎类型，必须为InnoDB。因为Innodb表支持事务、行锁、宕机恢复、MVCC等关系型数据库重要特性，为业界使用最多的MySQL存储引擎。而这是其他大多数存储引擎不具备的。【强制】建表必须有comment【数仓要求】【强制】建表必须有行数据的创建时间字段create_time和最后更新时间字段update_time，同时该字段应使用入库时间，不要使用程序生成时间，避免数据库与服务器时间差异引起的数据问题【数仓要求】【建议】建表时关于主键：(1)强制要求主键为id，类型为int或bigint，且为auto_increment(2)标识表里每一行主体的字段不要设为主键，建议设为其他字段如user_id，order_id等，并建立unique key索引。因为如果设为主键且主键值为随机插入，则会导致innodb内部page分裂和大量随机I/O，性能下降。【建议】表中所有字段必须都是NOT NULL属性，业务可以根据需要定义DEFAULT值。因为使用NULL值会存在每一行都会占用额外存储空间、数据迁移容易出错、聚合函数计算结果偏差等问题。【建议】建议对表里的blob、text等大字段，垂直拆分到其他表里，仅在需要读这些对象的时候才去select。【建议】反范式设计：把经常需要join查询的字段，在其他表里冗余一份。如user_name属性在user_account，user_login_log等表里冗余一份，减少join查询。【强制】中间表用于保留中间结果集，名称必须以tmp_开头。备份表用于备份或抓取源表快照，名称必须以bak_开头。中间表和备份表定期清理。【强制】对于超过100W行的大表进行alter table，必须经过运维同学审核，并在业务低峰期执行。因为alter table会产生表锁，期间阻塞对于该表的所有写入，对于业务可能会产生极大影响。2.1.3 列数据类型优化【建议】表中的自增列（auto_increment属性），推荐使用bigint类型。因为无符号int存储范围为-2147483648~2147483647（大约21亿左右），溢出后会导致报错。【建议】业务中选择性很少的状态status、类型type等字段推荐使用tinytint或者smallint类型节省存储空间。【建议】业务中IP地址字段推荐使用int类型，不推荐用char(15)。因为int只占4字节，可以用如下函数相互转换，而char(15)占用至少15字节。一旦表数据行数到了1亿，那么要多用1.1G存储空间。 SQL：select inet_aton('192.168.2.12'); select inet_ntoa(3232236044); PHP: ip2long(‘192.168.2.12’); long2ip(3530427185);【建议】不推荐使用enum，set。 因为它们浪费空间，且枚举值写死了，变更不方便。推荐使用tinyint或smallint。【建议】不推荐使用blob，text等类型。它们都比较浪费硬盘和内存空间。在加载表数据时，会读取大字段到内存里从而浪费内存空间，…

vue项目commit自动格式化并提交
type: item
date: 2024-10-09T17:08:00+08:00
description: 在 Vue 项目中，全局格式化（通常指的是代码格式化、样式格式化、数据格式化等）是一个重要的实践，它有助于维护代码的一致性和可读性，促进团队协作，减少因格式不一致导致的合并冲突，以及提高代码质量。先看效果：如果出现错误的commit做到commit的时候自动格式化，需要一下几个步骤：一 配置prettier安装Prettier： npm install --save-dev prettier 或者使用yarn yarn add --dev prettier配置Prettier:项目根目录新建配置文件,配置包括.prettierrc, .prettierrc.json, 或者在package.json内的prettier字段。例如创建一个.prettierrc文件，并设置一些基本规则：基本规则:{ "eslintIntegration": true, //设置为true时，Prettier会尝试避免与ESLint中的格式化规则冲突。这意呀着，如果ESLint和Prettier在格式化代码时有不同的规则，Prettier会尽量遵循ESLint的规则（前提是你已经安装了ESLint并配置了相应的规则）。 "stylelintIntegration": true, //类似于eslintIntegration，但它是针对CSS的。设置为true时，Prettier会尝试避免与Stylelint中的格式化规则冲突。这对于Vue项目中的 部分特别有用，但请注意，Prettier对CSS的支持可能不如Stylelint全面。 "tabWidth": 2, //设置缩进时使用的空格数。在你的配置中，它被设置为2，意味着每次缩进将使用两个空格。 "printWidth": 120, //指定代码行的最大长度。Prettier会尝试将代码格式化为不超过这个宽度的行。在你的配置中，它被设置为120个字符。 "singleQuote": true, //设置为true时，Prettier将使用单引号（'）而不是双引号（"）来包裹字符串。 "semi": false, //设置为true时，Prettier会在语句末尾添加分号。这是JavaScript中常见的做法，尽管ESLint等工具可能允许在某些情况下省略分号。 "arrowParens": "avoid", //控制箭头函数参数周围的括号。在你的配置中，它被设置为"avoid"，这意味着Prettier会尽可能避免在箭头函数参数周围添加括号，除非这是必要的（例如，当参数是一个对象或数组时）。 "trailingComma": "none", //控制多行数组、对象字面量等末尾是否添加逗号。在你的配置中，它被设置为"none"，意味着Prettier不会在末尾添加逗号。 "bracketSpacing": true //设置为true时，Prettier会在对象字面量、数组字面量等的大括号{}或方括号[]内部添加空格。这有助于提高代码的可读性。 }详细版本:{ "printWidth": 200, // 指定行的最大长度 "tabWidth": 4, // 指定缩进的空格数 "useTabs": true, // 是否使用制表符进行缩进，默认为 false "singleQuote": true, // 是否使用单引号，默认为 false "quoteProps": "as-needed", // 对象属性是否使用引号，默认为 "as-needed" "trailingComma": "none", // 是否使用尾随逗号（末尾的逗号），可以是 "none"、"es5"、"all" 三个选项 "bracketSpacing": true, // 对象字面量中的括号是否有空格，默认为 true "jsxBracketSameLine": false, // JSX 标签的右括号是否与前一行的末尾对齐，默认为 false "arrowParens": "always", // 箭头函数参数是否使用圆括号，默认为 "always" "rangeStart": 0, // 指定格式化的范围的起始位置 "requirePragma": false, // 是否需要在文件顶部添加特殊的注释才能进行格式化，默认为 false "insertPragma": false, // 是否在格式化后的文件顶部插入特殊的注释，默认为 false "proseWrap": "preserve", // 是否保留 markdown 文件中的换行符，默认为 "preserve" "htmlWhitespaceSensitivity": "ignore", // 指定 HTML 文件中空格敏感度的配置选项，可以是 "css"、"…

js工具函数大全
type: item
date: 2024-10-09T15:30:00+08:00
description: 效验相关 /** * 手机号码 * @param val 当前值字符串 * @returns 返回 true: 手机号码正确 */ export function verifyPhone(val) { // false: 手机号码不正确 // if ( // !/^((12[0-9])|(13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(16([0-3]|[5-9]))|(17([0-3]|[5-9]))|(18[0,5-9]))\d{8}$/.test( // val // ) // ) { if (!/^1[3-9]\d{9}$/.test(val)) { return false } else { return true } } /** * 国内电话号码 * @param val 当前值字符串 * @returns 返回 true: 国内电话号码正确 */ export function verifyTelPhone(val) { // false: 国内电话号码不正确 if (!/\d{3}-\d{8}|\d{4}-\d{7}/.test(val)) return false // true: 国内电话号码正确 else return true } /** * 密码 (以字母开头，长度在6~16之间，只能包含字母、数字和下划线) * @param val 当前值字符串 * @returns 返回 true: 密码正确 */ export function verifyPassword(val) { // false: 密码不正确 if (!/^[a-zA-Z]\w{5,15}$/.test(val)) return false // true: 密码正确 else return true } /** * 强密码 (字母+数字+特殊字符，长度在6-16之间) * @param val 当前值字符串 * @returns 返回 true: 强密码正确 */ export function verifyPasswordPowerful(val) { // false: 强密码不正确 if ( !/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test( val ) ) { return false } else { return true } } /** * 密码强度 * @param val 当前值字符串 * @description 弱：纯数字，纯字母，纯特殊字符 * @description 中：字母+数字，字母+特殊字符，数字+特殊字符 * @description 强：字母+数字+特殊字符 * @returns 返回处理后的字符串：弱、中、强 */ export function verifyPasswordStrength(val) { let v = '' // 弱：纯数字，纯字母，纯特殊字符 if (/^(?:\d+|[a-zA-Z]+|[!@#$%^&\.*]+){6,16}$/.test(val)) v = '弱' // 中：字母+数字，字母+特殊字符，数字+特殊字符 if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(val)) v = '中' // 强：字母+数字+特殊字符 if ( /^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test( val ) ) { v = '强' } // 返回结果 return v } /** * IP地址 * @param val 当前值字符串 * @returns 返回 true: IP地址正确 */ export function verifyIPAddress(val) { // false: IP地址不正确 if ( !/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/.test( v…

webRTC与webSocket接入智谱视频通话API避坑指南
type: item
date: 2024-10-08T11:02:00+08:00
description: 先看效果：智谱新上线的音视频通话大模型拥有理解音频和画面的能力，接入该api，需要使用webSocket协议不断传输音频和视频的base64编码。WebRTC（Web Real-Time Communication）是一个支持网页浏览器进行实时语音对话或视频对话的技术，webRTC最强大的功能是无需服务器，可以实现浏览器端对端的音视频通话，这里只使用了webRTC实现视频画面显示。这里就详细说一下从开发到跑起这个demo踩的一些不得不说的血泪坑。一技术栈使用vue2，webRTC自然就选用了vue-webRTC，npm地址：https://www.npmjs.com/package/vue-webrtc/v/2.0.0?activeTab=readme通过看它的readme文档发现，最新版是3.0.1，从3.0.0开始支持vue3，vue2使用的是2.0.0版本。跑vue-webRTC这个示例没有出现问题，但是上线的时候却发现一直报错：Permissions policy violation: camera is not allowed in this document.网上有关这些的文档都是说没有授权导致，在这里查资料卡了很久。线上使用的也是https协议，也授权了权限，调试很多次一直报错，终于在一篇文档里发现了问题： 【Web安全】Permissions Policy（权限策略）详解 不知道是谁给线上环境的响应头里设置了不允许使用麦克风和摄像头，也是吐血了。这里需要从Nginx修改配置，于是换了一个线上环境，成功解决。二智谱接入模型需要使用webSocket协议，这很简单，使用原生js new WebSocket(url)即可，提供了.send()方法发送数据，.onopen方法监听建立连接成功 .onmessage方法监听服务器返回。部分代码如下： this.ws = new WebSocket(this.socketURL + `?Authorization=${APIKey}`) this.ws.onopen = () => { console.log('WebSocket 连接已打开') } this.ws.onmessage = event => { let data = JSON.parse(event.data) console.log('收到服务器消息:', data) } // 发送消息 this.ws.send(JSON.stringify(message))但是接入的时候需要传入apiKey进行鉴权，api文档里写的是将key放在Header头里面，这完全就没有考虑浏览器调用情况！浏览器端对写入header头很严格，而且这也不是简单的get post请求，可以用axios工具设置请求头。所以当时有两种方法，1、找一个可以支持websocket协议写入请求头的库 2、使用@fortaine/fetch-event-source发送fetch请求，fetch是websocket的替代方案，可以解决传复杂参数问题。因为有一个已经封装好了的可以用fetch的库，所以当时选择通过fetch协议调用。通过一番操作以后，发post请求报405 发get请求报400，调试到这里真是吐血了！！用文档给的java代码调用就没有问题，这个文档根本就没有考虑浏览器方案。服务器端只支持websocket协议，而websocket或eventsource是get请求，带参只能跟在url地址后面。所以发post会报405请求方法不对，后端又只从请求头里拿鉴权信息，发get带过去的它都不认。经过一番协商，后端改了鉴权方法，从url地址里拿key信息，这下直接用原生webSocket协议都行了。调试半天，后端改一下协议十分钟搞定。三搞定传输以后，剩下的就比较简单了，从音视频流里获取视频和音频数据，再转成base64编码，直接发送即可。特别感谢：https://github.com/2fps/recorder作者整理的关于音频的资料很详细。请求获取音视频流： // 获得音视频数据流 getUserMedia() { if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { navigator.mediaDevices .getUserMedia({ audio: true, // 设置为 true 以请求音频流 video: true }) .then(stream => { // 拿到音视频数据流 this.activeStream = stream // 包含视频&&音频 this.videoStream = new MediaStream(stream…