auth-record-kit
v1.5.18
Published
微信小程序可复用登录 + 记录组件包:手机号/微信一键授权登录、登录态维护与刷新、通用数据记录 SDK 与 UI 组件。基于微信云开发,零自建服务器即可复用。
Maintainers
Readme
auth-record-kit
微信小程序可复用「登录 + 记录」组件包。任意小程序几行代码即可获得:
- 📱 手机号 + 微信一键授权登录:
chooseAvatar+ 昵称输入获取基本信息,getPhoneNumber解密手机号;身份用openid标识,无需自建账号体系。 - 🔐 登录态维护与过期刷新:HMAC 签名 token(默认 7 天),客户端自动检测临近过期并静默刷新;过期后引导重新登录。
- 📝 通用数据记录:任意字段结构的记录增删查,自动按用户隔离(
openid)。 - ♻️ 高复用性:登录 SDK + 记录 SDK + 两个开箱即用 UI 组件(
auth-login/record-panel),基于微信云开发,无需自建服务器。
一、目录结构
auth-record-kit/
├── package.json # npm 元数据(miniprogram 字段指向 src)
├── README.md
├── src/ # 组件源码(前端)
│ ├── index.js # 统一入口:init / Auth / Record
│ ├── core/client.js # 统一客户端(配置、登录态、自动刷新、请求分发)
│ ├── auth/
│ │ ├── auth.js # 登录 SDK
│ │ └── auth-login/ # 登录 UI 组件(弹层:头像+昵称+手机号)
│ └── record/
│ ├── record.js # 记录 SDK
│ └── record-panel/ # 通用记录 CRUD UI 组件
└── cloud/
└── arkit/ # 云函数(后端):登录/刷新/记录 CRUD
├── index.js
└── package.json二、后端部署(云函数 arkit)
- 在微信开发者工具中,把
cloud/arkit上传并部署到你的云开发环境。 - 在云函数「配置 → 环境变量」中填写:
| 变量 | 说明 |
| --- | --- |
|
APP_ID| 小程序 AppId | |APP_SECRET| 小程序 AppSecret(解密手机号用) | |ARKIT_SECRET| 任意长随机串,作为 token 签名密钥(务必修改) | - 在云开发控制台新建两个集合(权限均设为「仅创建者可读写」):
arkit_users:用户表(openid / nickName / avatarUrl / phone / createdAt)arkit_records:默认记录表(openid / 业务字段 / createdAt)- 👉 不同业务用不同集合名即可复用同一套组件,互不干扰。
已用
wx-server-sdk的cloud.getWXContext().OPENID直接拿到用户身份,不需要自己做code2Session。
三、前端集成(两种方式任选)
方式 A:复制源码(最简单,无 npm 构建)
把整个 src/ 目录复制到你的小程序项目(例如 components/arkit/),在页面/全局 usingComponents 引用组件,在 app.js 里 require SDK。
方式 B:npm 包(推荐,已上架准备就绪)
- 小程序项目根目录执行
npm install auth-record-kit - 微信开发者工具 → 工具 → 构建 npm(本包
package.json已设miniprogram: "src",构建后组件位于miniprogram_npm/auth-record-kit/) - 全局
app.js引入 SDK:const arkit = require('auth-record-kit'); - 页面
usingComponents引用组件(路径不含/src,构建后src已被扁平到包根):{ "usingComponents": { "auth-login": "auth-record-kit/auth/auth-login/index", "record-panel": "auth-record-kit/record/record-panel/index" } }
💡 两种方式对比:方式 A(复制源码)适合不发布、直接嵌入;方式 B(npm)适合作为可复用组件包上架,版本可迭代、消费者
npm update即升级。
直接看可运行示例(npm 方式)
仓库内 examples/host/ 是一个最小宿主小程序,已改用 npm 引用。运行方式:
- 在
examples/host/内npm install auth-record-kit并执行「构建 npm」 - 用微信开发者工具打开
examples/host/编译预览 含登录弹层 + 记录面板完整接线,以及query/order-by/fields/count/getById/batchDelete/batchAdd交互演示(详见该目录 README)。
📦 发布上架清单(npm)
把本仓库作为小程序组件包上架前,逐项确认:
- [ ]
package.json的miniprogram字段指向组件源码目录(已设"src"),files仅含src/cloud/examples/README.md(测试不进包) - [ ] 组件
usingComponents路径对外统一用包名(auth-record-kit/...,不含/src),已在本 README「方式 B」与examples/host对齐 - [ ]
src/内仅依赖wx.*全局与相对 require,无crypto/process/fs/http/Buffer等 node 专属 API(已审计通过) - [ ] 云函数
cloud/arkit/单独部署:复制目录到云函数arkit→npm install(含wx-server-sdk)→ 上传;配置环境变量APP_ID/APP_SECRET/ARKIT_SECRET - [ ] 云数据库新建集合
arkit_users、arkit_records(权限「仅创建者可读写」);业务集合自定义命名即可复用 - [ ]
ARKIT_SECRET在云函数环境变量中改为自有长随机串(切勿保留默认值) - [ ] 发布前在真实小程序里跑一遍:
npm install→ 构建 npm → 真机/模拟器登录 + 增删查改全链路 - [ ] 版本号已 bump(
npm version或手改package.json+src/index.js的version),并写入下方更新日志
四、初始化
在 app.js 的 onLaunch 中初始化(只需一次):
// app.js
var arkit = require('./components/arkit/src/index.js'); // 方式 A 路径
// 若用 npm:var arkit = require('auth-record-kit');
App({
onLaunch() {
wx.cloud.init({ env: 'your-cloud-env-id', traceUser: true }); // 先初始化云开发
arkit.init({ mode: 'cloudbase', env: 'your-cloud-env-id' });
},
});arkit.init 支持:
| 参数 | 默认 | 说明 |
| --- | --- | --- |
| mode | 'cloudbase' | 'cloudbase'(云函数)或 'http'(自建后端) |
| env | '' | 云开发环境 ID |
| cloudFn | 'arkit' | 云函数名 |
| httpBase | '' | 自建后端基址(mode='http' 时必填) |
| autoRefresh | true | 启动后若 token 临近过期则静默刷新 |
| refreshAhead | 3600 | 距过期少于该秒数触发刷新 |
| timeout | 10000 | HTTP 请求超时(毫秒),仅 mode='http' 时生效 |
五、登录(UI 组件方式)
页面 JSON:
{
"usingComponents": {
"auth-login": "/components/arkit/src/auth/auth-login/index"
}
}页面 WXML:
<auth-login visible="{{showLogin}}" bind:success="onLogin" bind:cancel="onCancel" />页面 JS:
Page({
data: { showLogin: false },
onShow() {
const arkit = require('../../components/arkit/src/index.js');
if (!arkit.Auth.isLogin()) this.setData({ showLogin: true });
},
onLogin(e) {
console.log('登录成功', e.detail.userInfo);
this.setData({ showLogin: false });
},
onCancel() { this.setData({ showLogin: false }); },
});组件内部已调用
arkit.Auth.login(userInfo, { code })并写入登录态;success事件带回{ token, userInfo }。(旧版基础库返回cloudID时传{ cloudID }同样兼容。)
纯 SDK 方式(自定义登录界面):
const { Auth } = require('../../components/arkit/src/index.js');
// phone 参数支持 { code }(新版 getPhoneNumber)或 { cloudID }(旧版兼容)
await Auth.login({ nickName, avatarUrl }, { code: phoneCode }); // phoneCode 来自 getPhoneNumber 事件六、数据记录(组件方式)
页面 JSON:
{
"usingComponents": {
"record-panel": "/components/arkit/src/record/record-panel/index"
}
}页面 WXML:
<record-panel
collection="my_notes"
title="我的笔记"
form-fields="{{formFields}}"
list-fields="{{listFields}}"
bind:change="onChanged"
/>页面 JS:
Page({
data: {
formFields: [
{ key: 'title', label: '标题', type: 'text', placeholder: '一句话' },
{ key: 'content', label: '内容', type: 'textarea', placeholder: '详细记录…' },
],
listFields: [
{ key: 'title', label: '标题' },
{ key: 'content', label: '内容' },
],
},
});组件会自动:登录后拉取列表、弹层新增、删除确认,全部按当前用户隔离。
record-panel 支持的属性:
| 属性 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| collection | String | arkit_records | 记录集合名(不同业务用不同集合即可复用) |
| title | String | 我的记录 | 面板标题 |
| form-fields | Array | [] | 表单字段 [{ key, label, type:'text'\|'textarea'\|'number', placeholder }] |
| list-fields | Array | [] | 列表展示字段 [{ key, label }] |
| limit | Number | 20 | 每页条数(服务端最多 100) |
| can-add | Boolean | true | 是否显示「+ 记一笔」 |
| can-edit | Boolean | true | 是否显示「编辑」(点卡片编辑按钮,复用表单走 updateRecord) |
| can-delete | Boolean | true | 是否显示删除按钮 |
| auto-load | Boolean | true | 滚动到底部自动加载更多(IntersectionObserver);false 时显示手动「加载更多」按钮 |
| query | Object | {} | 服务端过滤条件,透传给 listRecords 的 query;运行时变更会自动重新加载(首屏不重复加载) |
| empty-text | String | 还没有记录,点「+ 记一笔」开始 | 列表为空时的提示文案,可自定义 |
| order-by | Object/Array | null | 排序 { field, dir }(dir 为 asc/desc),过服务端 safeOrderBy 白名单;缺省按 createdAt 倒序。支持复合排序:传数组 [{field,dir}, ...](最多 3 个字段,顺序即优先级,如先按 status 升序再按 createdAt 倒序)。字段名/方向非法时服务端拒绝并回退默认排序 |
| fields | Array | null | 字段投影:如 ['title','status'] 让服务端只返回指定字段(省流量),过 safeFields 白名单(字段名规则同排序、最多 10 个、自动去重、始终保留 _id);非法或 null 时返回全部字段 |
外部方法:宿主可通过 selectComponent 拿到面板实例后调用 reload() 手动刷新列表(如外部数据变更后),getById(id) 按 _id 拉取单条记录,batchDelete(ids) 批量删除多条记录(返回实际删除条数),count(query) 按条件计数(不 fetch 数据,省流量),groupCount(field, query) 按字段分组计数(看板数据,返回 [{_id,total}],不 fetch 明细)。query / order-by / fields 任一属性在运行时变化都会触发面板自动重新加载,无需手动调用 reload()。
分页:auto-load 默认开启——用户滚动到列表底部时自动拉取下一页并追加(IntersectionObserver 监听底部哨兵元素);设为 false 则回退为手动点击「加载更多」按钮。全部加载完毕后底部显示「没有更多了」。
安全提示:业务集合名仅允许字母/数字/下划线/连字符,且禁止复用系统集合
arkit_users(已在云函数safeCollection中拦截),防止越权读写用户表。
纯 SDK 方式:
const { Record } = require('../../components/arkit/src/index.js');
// 新增
await Record.addRecord('my_notes', { title: 'A', content: 'B' });
// 查询(分页)
const { list, total } = await Record.listRecords('my_notes', { skip: 0, limit: 20 });
// 只取需要的字段(省流量,服务端过 safeFields 白名单)
const { list: titles } = await Record.listRecords('my_notes', { fields: ['title', 'status'], limit: 20 });
// 更新 / 删除 / 批量删除
await Record.updateRecord('my_notes', id, { content: '新的' });
await Record.deleteRecord('my_notes', id);
const removed = await Record.batchDeleteRecords('my_notes', [id1, id2]); // 一次删多条,返回删除条数
// 单条查询(按 _id 拉取自己的记录,带归属隔离)
const rec = await Record.getRecord('my_notes', id); // 命中返回记录对象,不存在返回 null
const rec2 = await Record.getRecord('my_notes', id, { query: { status: 'done' } }); // 可选字段过滤
// 计数专用(不 fetch 记录数据,省流量;角标/进度条场景)
const total = await Record.countRecords('my_notes'); // 全部记录数
const drafts = await Record.countRecords('my_notes', { query: { status: 'draft' } }); // 带条件计数
// 分组聚合计数(看板场景:各状态各多少条,不 fetch 任何明细)
// 返回 [{_id:'draft',total:3},{_id:'done',total:5}],groupBy 过 safeGroupField 白名单防注入
const byStatus = await Record.groupCountRecords('my_notes', { groupBy: 'status' });
const doneByStatus = await Record.groupCountRecords('my_notes', { query: { status: 'done' }, groupBy: 'status' });
// 批量新增(一次插多条,减少云函数调用次数;每项 ≤50 字段、禁嵌套、最多 20 条,自动带 openid)
const n = await Record.batchAddRecords('my_notes', [{ title: 'A' }, { title: 'B' }]); // 返回插入条数record-panel 额外提供 getById(id) 方法:宿主通过 selectComponent 拿到实例后调用即可按 _id 拉取单条记录(同样带归属隔离,查无此条返回 null,失败弹 toast)。
record-panel 额外提供 batchDelete(ids) 方法:宿主通过 selectComponent 拿到实例后调用即可批量删除自己的多条记录(同样带 openid 归属隔离,仅删属于自己的记录,返回实际删除条数,失败弹 toast)。
record-panel 额外提供 batchAdd(items) 方法:宿主通过 selectComponent 拿到实例后调用即可批量新增自己的多条记录(同样带 openid 归属隔离,返回实际插入条数,失败弹 toast)。
record-panel 额外提供 count(query) 方法:宿主通过 selectComponent 拿到实例后调用即可按条件计数(不 fetch 数据,省流量;不传 query 时默认用面板的 query 属性,返回数字,失败弹 toast)。
record-panel 额外提供 groupCount(field, query) 方法:宿主通过 selectComponent 拿到实例后调用即可按字段分组聚合计数(看板场景,如「各状态各多少条」),返回 [{_id,total}],不 fetch 任何明细;第 1 个参数 field 为分组字段名(过服务端 safeGroupField 白名单防注入),第 2 个 query 可选(不传时默认用面板的 query 属性),失败弹 toast 返回 []。
record-panel 的「编辑」按钮会以现有记录预填表单,submit 时自动走 updateRecord(而非新增),无需删除重建。
---
## 七、登录态与刷新机制
- 登录成功后,`{ token, exp }` 存入 `storageKey`(默认 `arkit_session`),`userInfo` 存入 `arkit_userinfo`。
- 每次 SDK 请求前若检测 token 临近过期(默认提前 1 小时),自动调用 `refresh` 换发新 token;刷新失败则清空登录态,由上层引导重新登录。
- 宿主可随时 `arkit.Auth.isLogin()` / `arkit.Auth.getUser()` / `arkit.Auth.logout()` 读取或清除状态。
- 在 `init` 时传入 `onUnauthorized(res)`:当任意业务请求返回 `401`(token 失效/被服务端判定未登录)时触发,同时**自动清空本地登录态**(避免「本地显示已登录、实际请求全 401」的假登录死循环)。典型用法是跳转登录页:
```js
arkit.init({
mode: 'cloudbase',
env: 'your-cloud-env-id',
onUnauthorized(res) {
wx.showToast({ title: '登录已失效', icon: 'none' });
wx.navigateTo({ url: '/pages/login/login' }); // 引导重新登录
},
});- 刷新互斥:并发的
ensureLogin()/ 记录请求会复用同一次刷新,避免重复签发 token。 - 401 一次性自动重试:业务请求(非登录/刷新)若返回 401(token 刚好在服务端失效、时钟偏差等瞬时情况),SDK 会静默刷新一次并重试原请求;若刷新失败或重试仍 401,再清空本地态并触发
onUnauthorized。用户操作不会因 token 临界失效直接报错。
八、接入自建后端(mode='http')
如果你的小程序不打算用云开发,可把 arkit 云函数的逻辑原样搬到任意服务端,暴露 POST {httpBase}/arkit,请求体为 { action, token, ...data },返回 { code, ... }(code:0 成功,401 未登录)。前端只需:
arkit.init({ mode: 'http', httpBase: 'https://api.yoursite.com' });其余 Auth / Record 调用方式完全一致——这就是「统一接口」带来的可移植性。
九、合规与安全提示
- 手机号解密依赖
APP_SECRET,请仅存于云函数环境变量,不要下发到前端。 ARKIT_SECRET必须改成只有你知道的随机串,否则他人可伪造 token。- 集合权限设为「仅创建者可读写」,且云函数已按
openid强制隔离,避免越权读写他人数据。 - 客户端无法伪造归属:云函数对写入/查询/更新的业务数据统一
stripOpenid——丢弃客户端上传的openid/_openid/_id,一律以服务端从 token 解析的openid为准。即使客户端在data/query/patch里塞入他人 openid,也会被剥离,确保用户只能读写自己的数据。 - 用户资料收敛:
doLogin走sanitizeProfile——nickName去空格截到 32 字、avatarUrl仅放行http(s)且 ≤512 字符,避免脏数据/异常协议头像入库。 - 排序字段防注入:列表排序的
orderBy必须过safeOrderBy白名单——字段名仅允许「字母/下划线开头 + 字母数字下划线」、方向仅asc/desc,拒绝$操作符字段、带点路径与非法方向,防 NoSQL 注入;非法或缺失时回退默认createdAt倒序。支持复合排序(数组形式,最多 3 个字段,顺序即优先级),数组内任一元素非法则整体拒绝。 - 字段投影白名单:
recordList的fields须过safeFields白名单——元素为合法字段名(同排序规则)、最多 10 个、自动去重;非法/超长/非数组则整体忽略(返回全部字段)。投影始终保留_id,避免断更。防通过投影注入异常字段名。 - 分组字段防注入:
recordGroupCount的groupBy须过safeGroupField白名单——仅允许单个合法字段名(同safeFields规则),拒绝$操作符、带点路径与空串,杜绝 NoSQL 注入;非法整体拒绝。聚合以'$'+field为_id分组、_.aggregate.sum(1)计数,仅返回分组结果,不传输任何记录明细。 - 写入载荷护栏:
doRecordAdd/doRecordUpdate的业务数据均走safeRecord——限制字段数(≤50)、拒绝嵌套对象/数组(保持扁平)、超长字符串截断到 2000 字,防止记录被塞爆或写入非法类型。 - 用户昵称/头像通过
<input type="nickname">+chooseAvatar获取,符合微信 2022 后「不强制 getUserProfile」的规范。
十、测试
组件内置纯 Node 测试(无需微信开发者工具),基于 mock wx 全局,覆盖 token 签名校验、客户端登录态/刷新/onUnauthorized、记录 SDK 通路、集合安全校验、401 自动重试,共 116 项断言:
npm test
# 或等价
node test/run.js每个测试文件在独立子进程中运行,避免全局
wx互相污染;新增断言只需在对应*.test.js里ok(cond, '描述'),并到test/run.js注册文件名即可。
十一、更新日志
v1.5.18
- 分组聚合计数
recordGroupCount:看板类场景(如「各状态各多少条」「各分类各多少篇」)此前只能先把所有记录拉到前端再本地reduce,既浪费流量又泄露明细。本轮新增分组聚合计数,三端打通并只返回分组结果、不 fetch 任何明细:guard.js新增safeGroupField(groupBy):仅允许单个合法字段名(同safeFields规则),拒绝$操作符、带点路径、空串/数字/null,杜绝 NoSQL 注入。- 云函数新增
doRecordGroupCount:safeCollection校验 +safeGroupField校验groupBy+stripOpenid剥离保留字段 +where({ openid, ...query })归属隔离;aggregate().match().group({ _id: '$'+field, total: _.aggregate.sum(1) }).end()聚合,返回{ code:0, groups:[{_id,total}] },不传输任何记录数据。 - SDK
Record.groupCountRecords(collection, { query, groupBy })透传并返回分组数组,未登录拦截。 record-panel新增groupCount(field, query)便捷方法(第 1 参分组字段名、第 2 参可选 query;不传 query 默认用面板query属性,失败弹 toast 返回[])。
- 测试增强:
guard.test.js新增 7 条safeGroupField断言(合法放行/$where拒绝/带点路径拒绝/空串拒绝/数字拒绝/null 拒绝);record.test.js新增 3 条groupCountRecords断言(未登录拦截、返回分组数组、groupBy 透传)。总断言数 106 → 116。
v1.5.17(上架准备)
- 发布审计:确认
src/仅依赖wx.*与相对 require,无crypto/process/fs/http/Buffer等 node 专属 API;npm pack --dry-run包体 27 文件 / 33.6 kB,测试未入包。 - 修复 npm 接入文档 bug:
usingComponents路径由错误的auth-record-kit/src/...改为正确的auth-record-kit/...(构建 npm 后src已被扁平到包根)。 - 示例宿主
examples/host改为 npm 风格:require('auth-record-kit')+ 包名usingComponents,与发布后真实用法对齐;其 README 增加「构建 npm」运行步骤。 - 新增「📦 发布上架清单(npm)」:含 miniprogram 字段、
files、云函数部署、ARKIT_SECRET变更、真机验证等 8 项发布前核对项。
v1.5.16
- 批量新增
recordBatchAdd:对标已有的批量删除,一次插入多条(减少云函数调用次数)。三端打通:- 新增
safeRecordList(items)护栏(guard.js):非空对象数组、每项过safeRecord(≤50 字段/禁嵌套/超长截断)、最多 20 条;任一子项非法整体拒绝。 - 云函数
doRecordBatchAdd:safeCollection校验 +safeRecordList校验 + 每条强制写入服务端openid与serverDate(归属不可伪造);db.collection(col).add({ data: docs })批量插入,返回{ code:0, inserted }。 - SDK
Record.batchAddRecords(collection, items)透传并返回插入条数,未登录拦截。 record-panel新增batchAdd(items)便捷方法。
- 新增
- 测试增强:
guard.test.js新增safeRecordList5 条断言(放行/空数组/非数组/嵌套对象/超 20 条/嵌套数组);record.test.js新增 2 条断言(登录后返回插入条数、items透传)。总断言数 98 → 106。
v1.5.15
- Bug 修复(全量审计):本轮对全部核心文件做了逐行 bug 审计,修复 5 个真实 bug:
- [严重] 编辑数据丢失:
record-panel的edit()原从已启用fields投影的列表里取记录预填表单,未投影字段会被写成''覆盖真实数据。改为编辑前用getRecord拉完整记录(不经过投影)再预填,提交只更新formFields,其余字段保留。 - [中]
stripOpenid非对象泄漏:传入字符串/数字等非对象时原样返回,导致Object.assign({openid}, "abc")把字符串拆成{'0':'a',...}数字键污染查询条件。现一律返回{}。 - [低]
doRecordGet操作符查询误判 404:query 含{字段:{$ne:...}}等对象值时会恒返回 404。现跳过对象/数组值键,只做等式过滤。 - [中] 分页失败自动重试死循环:
autoLoad下分页失败后哨兵常驻视口,IntersectionObserver 会反复触发loadMore。现自动加载时若已loadMoreError则跳过(手动点"加载失败,点击重试"仍可用);load()成功时重置loadMoreError。 - [低]
recordUpdate/recordDelete/recordGet缺id校验:event.id缺失会让云数据库doc(undefined)抛错返回 500,现前置返回 400。
- [严重] 编辑数据丢失:
- 测试增强:
guard.test.js修订stripOpenid断言(纠正旧的错误断言并新增 3 条非对象回归用例)。总断言数 96 → 98。
v1.5.14
record-panel运行时属性自动重载扩展:原仅query变更触发自动 reload,本轮将order-by/fields一并纳入observers,三者任一在运行时变化都会触发面板自动重新加载(与query行为一致)。便于宿主做"排序下拉 / 投影切换"类交互时无需手动调reload()。examples/host升级为可交互演示:把前几轮补齐的能力真正用起来——状态筛选(驱动query)、排序字段+方向(驱动order-by)、字段投影切换(驱动fields)、计数按钮(count显示总/筛选条数)、getById单条查询、批量删除(逗号分隔_id,走batchDelete)。页面加id="panel"以便selectComponent调用外部方法;新增status表单/列表字段支撑筛选演示。index.js/index.wxml/index.wxss全部重写,纯 JS 经node --check验证。- 说明:本轮为组件行为增强 + 示例升级,未新增断言(observer 属小程序运行时行为,无法在 Node 单测覆盖);测试套件仍 96 项全绿。
v1.5.13
- 计数专用
recordCount:此前listRecords即便只要总数也会 fetch 全部记录数据,角标/进度条/"你有 N 条草稿"等场景浪费流量。本轮新增计数专用 API,只调.count()不 fetch 记录:- 云函数新增
doRecordCount:safeCollection校验 +stripOpenid剥离保留字段 +where({ openid, ...query }).count(),仅返回{ code:0, count },不传输任何记录数据。 - SDK
Record.countRecords(collection, { query })透传并返回数字,未登录拦截。 record-panel新增count(query)便捷方法(不传 query 时默认用面板query属性,失败弹 toast)。
- 云函数新增
- 测试增强:
record.test.js新增 3 条断言(未登录拦截、计数值返回、query透传)。总断言数 93 → 96。
v1.5.12
- 批量删除
recordBatchDelete:此前只能单条删(一次删一条要调多次云函数)。本轮补齐批量删除能力,三端打通:guard.js新增safeIds(ids):数组、非空、最多 20 个、元素须为字符串(云数据库_id形如 24 位 hex,允许更长)、自动去重;空数组/非数组/含非字符串/超长/超 20 个 → 返回null。- 云函数新增
doRecordBatchDelete:先过safeCollection校验集合名,再经safeIds校验ids,最后以where({ openid, _id: _.in(ids) })批量remove——关键:openid与_id双重条件,杜绝传入他人_id误删;返回{ code:0, removed }。 - SDK
Record.batchDeleteRecords(collection, ids)透传并返回removed;record-panel新增batchDelete(ids)便捷方法(带归属隔离,失败弹 toast)。
- 测试增强:
guard.test.js新增 6 条safeIds断言(合法/去重/非字符串拒绝/空数组/非数组/超 20);record.test.js新增 3 条断言(未登录拦截、removed返回、ids透传)。总断言数 84 → 93。
v1.5.11
- 列表字段投影
fields:真实列表常只需展示标题/状态等少量字段,全字段返回浪费流量。本轮新增:guard.js新增safeFields(fields):数组元素须为合法字段名(同safeOrderBy规则)、最多 10 个、自动去重;空数组/非数组/含非法字段名/超 10 个 → 返回null(不投影,返回全部字段)。cloud/index.js doRecordList:合法fields经白名单后构建投影对象(_id始终保留,避免断更),调用.field(proj);非法则全字段返回。- SDK
listRecords透传fields;record-panel新增fields属性(数组,默认null),load/loadMore统一携带。
- 测试增强:
guard.test.js新增 6 条safeFields断言(合法/去重/带点路径拒绝/非数组/超 10/空数组);record.test.js新增 1 条fields透传断言。总断言数 77 → 84。
v1.5.10
- 列表排序支持复合排序:原
safeOrderBy仅支持单字段{field, dir},真实场景常需「先按状态升序、再按时间倒序」等多字段排序。本轮改造:safeOrderBy兼容单对象与数组[{field,dir}, ...]两种用法,统一返回规范化数组(内部云函数逐条链式.orderBy,顺序即优先级);最多 3 个字段,超长/空数组拒绝;数组内任一元素非法则整体拒绝,杜绝注入滥用。record-panel的order-by属性现支持传数组(如[{field:'status',dir:'asc'},{field:'createdAt',dir:'desc'}]),load/loadMore统一携带、queryobserver 自动重载。- SDK
listRecords透传数组形式orderBy,由服务端白名单校验。
- 测试增强:
guard.test.js重写 safeOrderBy 断言并新增 4 条复合排序用例(数组放行/$字段整体拒绝/超 3 字段拒绝/空数组拒绝);record.test.js新增 1 条复合排序透传断言。总断言数 72 → 77。
v1.5.9
- 补齐单条查询
recordGet:CRUD 此前缺「按 id 查单条」这一环,本轮在 SDK / 云函数 / 测试三端打通。Record.getRecord(collection, id, { query }):成功返回记录对象,记录不存在或归属不符(服务端404)返回null,其他错误抛异常;未登录拦截。- 云函数新增
doRecordGet:先过safeCollection校验集合名,按_id取文档后以服务端 openid 做归属隔离;可选query作为 post-filter,所有键须与记录字段一致,否则当作404(不泄露「存在但非本人」的归属信息)。 record-panel新增getById(id)方法,供宿主通过selectComponent按_id拉取单条记录(同样带归属隔离)。
- 测试增强:
record.test.js新增 4 条断言(未登录拦截、getRecord成功返回、id/query透传、404 返回null),总断言数 68 → 72。
v1.5.3
- 修复示例宿主
bind:change语义混淆:examples/host中record-panel的bind:change原复用onLogin(显示"欢迎回来"),导致每次增删改记录后都弹欢迎提示。改为独立onChanged处理器,显示"已同步"。 - npm 发布包含
examples目录:package.json的files字段新增examples,npm 安装后可直接参考示例宿主接线方式。
v1.5.4
record-panel新增query属性:透传给listRecords的query服务端过滤条件;运行时变更自动重新加载(首屏不重复加载)。- 修复
loadMore丢失query:翻页拉取后续页时此前未带query,导致首页过滤、后续页不过滤的数据不一致 bug,现已统一带过滤条件。 record-panel新增empty-text属性:自定义空列表文案(默认"还没有记录,点「+ 记一笔」开始")。record-panel新增reload()方法:供宿主通过selectComponent外部调用手动刷新列表。- 测试增强:
record.test.js新增query透传断言(总断言数 51)。
v1.5.5
- 列表排序支持 + 注入防护:
listRecords/ 云函数doRecordList支持可选orderBy(默认createdAt倒序);新增safeOrderBy白名单校验(字段名仅允许字母/下划线开头 + 字母数字下划线、方向仅asc/desc),拒绝$操作符字段、带点路径、非法方向,防 NoSQL 注入。 record-panel新增order-by属性:透传排序条件,load/loadMore 统一携带,运行时变更随queryobserver 自动重载。- 测试增强:
guard.test.js新增 6 条safeOrderBy断言、record.test.js新增orderBy透传断言(总断言数 58)。
v1.5.6
init选项校验:mode非cloudbase/http时抛清晰错误;http模式缺失httpBase时抛清晰错误——避免在请求时才神秘失败,提升接入体验。- 测试增强:
client.test.js新增 2 条init校验断言(总断言数 60)。
v1.5.7
- 记录写入载荷护栏:新增
safeRecord——限制单条记录字段数(≤50)、拒绝嵌套对象/数组(保持扁平)、超长字符串截断到 2000 字,防止单条记录被塞爆或塞入非法类型;doRecordAdd调用,非法载荷返回400。 - 测试增强:
guard.test.js新增 7 条safeRecord断言(总断言数 67)。
v1.5.8
- 更新路径同样过
safeRecord护栏:doRecordUpdate的patch现在先经safeRecord(字段数≤50、禁嵌套、超长截断)再写入,与doRecordAdd防护一致;非法补丁返回400。 - 测试增强:
guard.test.js新增 1 条非字符串类型(布尔/数字/小数)保留断言(总断言数 68)。
v1.5.2
- HTTP 模式请求超时:
init新增timeout选项(默认 10000ms),wx.request添加超时防止挂起;可通过arkit.init({ timeout: 15000 })调整。 record-panel分页失败重试:loadMore()失败时设置loadMoreError标志,底部显示红色「加载失败,点击重试」(autoLoad 和手动模式均生效),用户点击即可重试加载下一页,已有列表不受影响。
v1.5.1
- 修复
record-panelCSS 类名冲突:表单按钮行(取消/保存)原与卡片内容行共用.rp-row类名,导致卡片行被错误添加margin-top和gap。改用独立.rp-btn-row类名。 doRecordUpdate添加updatedAt时间戳:更新记录时自动写入服务端时间(db.serverDate()),客户端无法伪造;便于追踪记录最后修改时间。- 云函数 HTTPS 请求添加超时保护:
getAccessToken和getPhoneNumberByCode添加 5s 超时 +destroy,防止微信接口无响应时云函数挂起超时。
v1.5.0
record-panel自动加载更多:新增auto-load属性(默认true),使用IntersectionObserver监听列表底部哨兵元素,用户滚动到底部时自动调loadMore()拉取下一页——无需手动点击。设为false回退为手动「加载更多」按钮。全部加载完毕后显示「没有更多了」。auth-loginbusy 卡死修复:添加visible属性 observer,组件隐藏时(visible: false)自动重置busy,防止登录请求进行中被关闭后再次打开时busy卡死无法登录。
v1.4.3
token.verify分隔符解析硬化:改用lastIndexOf('|')替代split('|')[0],防止 openid 包含|字符时被截断(防御性加固,微信 openid 实际不含|)。token.test.js新增含|的 openid 往返断言。record-panel加载失败体验改进:首次load()失败时设置loadError标志,显示「加载失败」+「重新加载」按钮(而非仅 toast 后留空白屏),用户可一键重试。
v1.4.2
- 云函数
doLogin对用户资料做收敛:sanitizeProfile将nickName去空格并截到 32 字、avatarUrl仅放行http(s)且 ≤512 字符,避免脏数据/异常协议头像入库。guard.js新增sanitizeProfile纯函数并补充单测。
v1.4.1
- 记录 SDK 接入「401 一次性自动刷新重试」:
core._withRetry在业务请求遇瞬时 401 时静默刷新并重试,避免用户操作因 token 临界失效直接报错;刷新失败/重试仍 401 才清态并触发onUnauthorized。_postProcess增加suppress401选项以支撑重试前的临时抑制。 - 新增
retry.test.js(4 断言)覆盖 401 自动重试通路。
v1.4.0
- 服务端 openid 隔离加固:云函数对
recordAdd/recordList/recordUpdate的业务数据统一stripOpenid,丢弃客户端上传的openid/_openid/_id,一律以服务端从 token 解析的 openid 为准——即使客户端在data/query/patch注入他人 openid 也会被剥离,杜绝越权读写他人数据。 guard.js新增stripOpenid纯函数并补充单测。
v1.3.0
record-panel补全 CRUD:新增「编辑」按钮(受can-edit控制),点按以现有记录预填表单,submit自动走updateRecord,无需删除重建。- 表单标题随新增/编辑动态切换(
formTitle)。 record.test.js新增updateRecord通路断言。
v1.2.0
- 客户端
refresh()增加并发互斥,多个ensureLogin复用同一次刷新,避免重复签发。 - 收到
401(非登录/刷新)时自动清空本地登录态并触发onUnauthorized,消除「假登录」死循环。 - 云函数新增
safeCollection集合安全校验:业务集合禁止复用arkit_users且名称仅允许[A-Za-z0-9_-],防集合名注入。 record-panel的number字段提交时自动转为数字类型。- 新增
guard.test.js集合校验单测;测试套件新增并发刷新、401 清态、集合校验用例(共 29 项断言)。
v1.1.0
- 纯函数
token.js抽出,可独立单测。 - 云函数
access_token内存缓存(提前 5 分钟视为有效),避免每次登录都请求微信接口命中限频。 - 手机号解密兼容旧版
cloudID回退路径。 - 客户端新增
onUnauthorized钩子与wx.cloud未初始化守卫。 auth-login支持cloudID、点击遮罩关闭、登录忙等防重复提交。record-panel改用预展开formItems规避动态 member 绑定脆弱点,新增分页loadMore与canAdd/canDelete属性。- 新增
test/自动化测试套件。
v1.0.0
- 基础能力:手机号 + 微信一键授权登录、HMAC 签名 token 与自动刷新、通用记录 SDK(cloudbase / http 双模式)、
auth-login/record-panel两个开箱即用 UI 组件。
