@cloudcome/utils-uni 1.34.0 → 1.35.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,130 @@
1
+ import { request } from "./cloud.mjs";
2
+ import { objectMap } from "@cloudcome/utils-core/object";
3
+ import { tryFlatten } from "@cloudcome/utils-core/try";
4
+ //#region src/weixin/token.ts
5
+ /**
6
+ * 构建微信 access_token 获取服务。
7
+ *
8
+ * 自动处理缓存逻辑:优先从临时数据中获取,不存在时调用微信 API 获取并缓存。
9
+ *
10
+ * @param options - 构造选项
11
+ * @returns 获取 access_token 的异步函数
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const getAccessToken = await buildWeixinAccessTokenService({
16
+ * appId: 'wx123',
17
+ * appSecret: 'secret',
18
+ * getTempDataService: () => kv.get('token'),
19
+ * setTempDataService: (token, ttl) => kv.set('token', token, ttl),
20
+ * })
21
+ * const token = await getAccessToken()
22
+ * ```
23
+ */
24
+ async function buildWeixinAccessTokenService(options) {
25
+ const { appId, appSecret, _mockRequest, getTempDataService, setTempDataService } = options;
26
+ return async function getWeixinAccessTokenService() {
27
+ let accessToken = await getTempDataService();
28
+ if (accessToken) return accessToken;
29
+ const { data: accessInfo } = await (_mockRequest || request)({
30
+ url: "https://api.weixin.qq.com/cgi-bin/token",
31
+ method: "GET",
32
+ query: {
33
+ grant_type: "client_credential",
34
+ appid: appId,
35
+ secret: appSecret
36
+ }
37
+ });
38
+ if (!accessInfo.access_token) throw new Error(accessInfo.errmsg || "获取 access_token 失败");
39
+ accessToken = accessInfo.access_token;
40
+ const [err] = await tryFlatten(setTempDataService(accessToken, accessInfo.expires_in * 1e3));
41
+ if (err) console.error("设置临时数据失败", err);
42
+ return accessToken;
43
+ };
44
+ }
45
+ //#endregion
46
+ //#region src/weixin/notice.ts
47
+ /**
48
+ * 构建微信订阅消息发送服务。
49
+ *
50
+ * 封装微信订阅消息发送逻辑,自动处理 access_token 获取、openId 查找、
51
+ * 字段长度截断(thing 类型 20 字符,character_string 类型 32 字符)等。
52
+ *
53
+ * @template T - payload 字段类型
54
+ * @param options - 构造选项
55
+ * @returns 发送订阅消息的函数
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * const sendNotice = buildSendWeixinNoticeService({
60
+ * templateId: 'tmpl_abc123',
61
+ * getWeixinAccessTokenService: getAccessToken,
62
+ * getUserWeixinOpenId: async (userId) => {
63
+ * const user = await db.collection('users').doc(userId).get()
64
+ * return user.data?.openId
65
+ * },
66
+ * })
67
+ *
68
+ * await sendNotice({
69
+ * userId: 'user-123',
70
+ * clientEnv: 'release',
71
+ * payload: { thing1: '订单已发货', number1: 12345 },
72
+ * page: '/pages/order/detail?id=12345',
73
+ * })
74
+ * ```
75
+ */
76
+ function buildSendWeixinNoticeService(options) {
77
+ const { templateId, getWeixinAccessTokenService, getUserWeixinOpenId, _mockRequest } = options;
78
+ return async function sendWeixinNoticeService(sendData) {
79
+ const { userId, page, payload } = sendData;
80
+ const wxOpenId = await getUserWeixinOpenId(userId);
81
+ if (!wxOpenId) throw new Error("用户未绑定微信");
82
+ const accessToken = await getWeixinAccessTokenService();
83
+ const { data } = await (_mockRequest || request)({
84
+ url: `https://api.weixin.qq.com/cgi-bin/message/subscribe/send`,
85
+ method: "POST",
86
+ query: { access_token: accessToken },
87
+ data: {
88
+ touser: wxOpenId,
89
+ template_id: templateId,
90
+ page: page.replace(/^\//, ""),
91
+ miniprogram_state: sendData.clientEnv === "trial" ? "trial" : "formal",
92
+ lang: "zh_CN",
93
+ data: objectMap(payload, (val, key) => ({ value: _fixPayloadValue(key, val) }))
94
+ }
95
+ });
96
+ if (data.errcode === 43101) return;
97
+ if (data.errcode !== 0) throw new Error(data.errmsg || "发送失败,未知错误");
98
+ };
99
+ }
100
+ /**
101
+ * 修复通知 payload 字段值,根据微信模板字段类型自动截断。
102
+ *
103
+ * 截断规则:
104
+ * - thing 类型:20 字符以内
105
+ * - character_string 类型:32 字符以内
106
+ * - 其他类型:不处理
107
+ *
108
+ * @param key - 模板字段 key(如 thing1, number1)
109
+ * @param val - 字段值
110
+ * @returns 修复后的值
111
+ */
112
+ function _fixPayloadValue(key, val) {
113
+ if (key.startsWith("thing")) return _autoEllipsis(val.toString(), 20);
114
+ if (key.startsWith("character_string")) return _autoEllipsis(val.toString(), 32);
115
+ return val;
116
+ }
117
+ /**
118
+ * 字符串超长时自动截断并添加省略号。
119
+ *
120
+ * @param val - 原始字符串
121
+ * @param len - 最大长度
122
+ * @returns 截断后的字符串
123
+ */
124
+ function _autoEllipsis(val, len) {
125
+ return val.length > len ? `${val.slice(0, len - 3)}...` : val;
126
+ }
127
+ //#endregion
128
+ export { buildSendWeixinNoticeService, buildWeixinAccessTokenService };
129
+
130
+ //# sourceMappingURL=weixin.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weixin.mjs","names":[],"sources":["../src/weixin/token.ts","../src/weixin/notice.ts"],"sourcesContent":["import { tryFlatten } from '@cloudcome/utils-core/try';\nimport { request } from '../cloud';\n\n/**\n * 构建微信 access_token 获取服务的选项\n */\nexport type BuildWeixinAccessTokenServiceOptions = {\n /**\n * 微信小程序应用ID\n */\n appId: string;\n\n /**\n * 微信小程序应用密钥\n */\n appSecret: string;\n\n /**\n * 获取临时数据(用于缓存 access_token)\n * @returns 缓存的 access_token,无缓存时返回空字符串\n */\n getTempDataService: () => Promise<string>;\n\n /**\n * 设置临时数据(用于缓存 access_token)\n * @param accessToken access_token 值\n * @param expiresIn 过期时间,单位毫秒\n */\n setTempDataService: (accessToken: string, expiresIn: number) => Promise<void>;\n\n /**\n * 模拟请求函数,用于单元测试注入\n */\n _mockRequest?: typeof request;\n};\n\n/**\n * 构建微信 access_token 获取服务。\n *\n * 自动处理缓存逻辑:优先从临时数据中获取,不存在时调用微信 API 获取并缓存。\n *\n * @param options - 构造选项\n * @returns 获取 access_token 的异步函数\n *\n * @example\n * ```ts\n * const getAccessToken = await buildWeixinAccessTokenService({\n * appId: 'wx123',\n * appSecret: 'secret',\n * getTempDataService: () => kv.get('token'),\n * setTempDataService: (token, ttl) => kv.set('token', token, ttl),\n * })\n * const token = await getAccessToken()\n * ```\n */\nexport async function buildWeixinAccessTokenService(options: BuildWeixinAccessTokenServiceOptions) {\n const { appId, appSecret, _mockRequest, getTempDataService, setTempDataService } = options;\n\n return async function getWeixinAccessTokenService() {\n let accessToken = await getTempDataService();\n if (accessToken) return accessToken;\n\n const { data: accessInfo } = await (_mockRequest || request)<{\n access_token: string;\n expires_in: number;\n errmsg: string;\n errcode: number;\n }>({\n url: 'https://api.weixin.qq.com/cgi-bin/token',\n method: 'GET',\n query: {\n grant_type: 'client_credential',\n appid: appId,\n secret: appSecret,\n },\n });\n\n if (!accessInfo.access_token) throw new Error(accessInfo.errmsg || '获取 access_token 失败');\n accessToken = accessInfo.access_token;\n\n const [err] = await tryFlatten(setTempDataService(accessToken, accessInfo.expires_in * 1000));\n if (err) console.error('设置临时数据失败', err);\n\n return accessToken;\n };\n}\n","import { request } from '@/cloud';\nimport { objectMap } from '@cloudcome/utils-core/object';\n\n/**\n * 发送微信订阅消息的数据结构\n * @template T - payload 字段类型,key 为模板字段名,value 为 string | number\n */\nexport type SendData<T> = {\n /**\n * 用户ID,用于查找对应的微信 openId\n */\n userId: string;\n\n /**\n * 小程序环境\n * - `develop`: 开发版\n * - `trial`: 体验版\n * - `release`: 正式版\n */\n clientEnv: 'develop' | 'trial' | 'release';\n\n /**\n * 通知数据,key 对应模板字段(如 thing1, number1)\n */\n payload: T;\n\n /**\n * 点击消息后跳转的页面路径,开头 `/` 会被自动移除\n */\n page: string;\n};\n\n/**\n * 构建微信订阅消息发送服务的选项\n */\nexport type BuildSendWeixinNoticeServiceOptions = {\n /**\n * 订阅消息模板ID\n */\n templateId: string;\n\n /**\n * 获取微信 access_token 的服务函数\n */\n getWeixinAccessTokenService: () => Promise<string>;\n\n /**\n * 根据用户ID获取微信 openId\n * @param userId - 用户ID\n * @returns 微信 openId,未绑定时返回空字符串\n */\n getUserWeixinOpenId: (userId: string) => Promise<string>;\n\n /**\n * 模拟请求函数,用于单元测试注入\n */\n _mockRequest?: typeof request;\n};\n\n/**\n * 构建微信订阅消息发送服务。\n *\n * 封装微信订阅消息发送逻辑,自动处理 access_token 获取、openId 查找、\n * 字段长度截断(thing 类型 20 字符,character_string 类型 32 字符)等。\n *\n * @template T - payload 字段类型\n * @param options - 构造选项\n * @returns 发送订阅消息的函数\n *\n * @example\n * ```ts\n * const sendNotice = buildSendWeixinNoticeService({\n * templateId: 'tmpl_abc123',\n * getWeixinAccessTokenService: getAccessToken,\n * getUserWeixinOpenId: async (userId) => {\n * const user = await db.collection('users').doc(userId).get()\n * return user.data?.openId\n * },\n * })\n *\n * await sendNotice({\n * userId: 'user-123',\n * clientEnv: 'release',\n * payload: { thing1: '订单已发货', number1: 12345 },\n * page: '/pages/order/detail?id=12345',\n * })\n * ```\n */\nexport function buildSendWeixinNoticeService<T extends Record<string, number | string>>(\n options: BuildSendWeixinNoticeServiceOptions,\n) {\n const { templateId, getWeixinAccessTokenService, getUserWeixinOpenId, _mockRequest } = options;\n\n return async function sendWeixinNoticeService(sendData: SendData<T>) {\n const { userId, page, payload } = sendData;\n const wxOpenId = await getUserWeixinOpenId(userId);\n if (!wxOpenId) throw new Error('用户未绑定微信');\n\n const accessToken = await getWeixinAccessTokenService();\n const { data } = await (_mockRequest || request)<{\n errcode: number;\n errmsg: string;\n }>({\n url: `https://api.weixin.qq.com/cgi-bin/message/subscribe/send`,\n method: 'POST',\n query: {\n access_token: accessToken,\n },\n data: {\n touser: wxOpenId,\n template_id: templateId,\n page: page.replace(/^\\//, ''),\n miniprogram_state: sendData.clientEnv === 'trial' ? 'trial' : 'formal',\n lang: 'zh_CN',\n data: objectMap(payload, (val, key) => ({\n value: _fixPayloadValue(key as string, val),\n })),\n },\n });\n\n if (data.errcode === 43101) return;\n\n if (data.errcode !== 0) throw new Error(data.errmsg || '发送失败,未知错误');\n };\n}\n\n/**\n * 修复通知 payload 字段值,根据微信模板字段类型自动截断。\n *\n * 截断规则:\n * - thing 类型:20 字符以内\n * - character_string 类型:32 字符以内\n * - 其他类型:不处理\n *\n * @param key - 模板字段 key(如 thing1, number1)\n * @param val - 字段值\n * @returns 修复后的值\n */\nfunction _fixPayloadValue(key: string, val: number | string) {\n if (key.startsWith('thing')) return _autoEllipsis(val.toString(), 20);\n if (key.startsWith('character_string')) return _autoEllipsis(val.toString(), 32);\n return val;\n}\n\n/**\n * 字符串超长时自动截断并添加省略号。\n *\n * @param val - 原始字符串\n * @param len - 最大长度\n * @returns 截断后的字符串\n */\nfunction _autoEllipsis(val: string, len: number) {\n return val.length > len ? `${val.slice(0, len - 3)}...` : val;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAuDA,eAAsB,8BAA8B,SAA+C;CACjG,MAAM,EAAE,OAAO,WAAW,cAAc,oBAAoB,uBAAuB;CAEnF,OAAO,eAAe,8BAA8B;EAClD,IAAI,cAAc,MAAM,oBAAoB;EAC5C,IAAI,aAAa,OAAO;EAExB,MAAM,EAAE,MAAM,eAAe,OAAO,gBAAgB,SAKjD;GACD,KAAK;GACL,QAAQ;GACR,OAAO;IACL,YAAY;IACZ,OAAO;IACP,QAAQ;IACT;GACF,CAAC;EAEF,IAAI,CAAC,WAAW,cAAc,MAAM,IAAI,MAAM,WAAW,UAAU,qBAAqB;EACxF,cAAc,WAAW;EAEzB,MAAM,CAAC,OAAO,MAAM,WAAW,mBAAmB,aAAa,WAAW,aAAa,IAAK,CAAC;EAC7F,IAAI,KAAK,QAAQ,MAAM,YAAY,IAAI;EAEvC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACKX,SAAgB,6BACd,SACA;CACA,MAAM,EAAE,YAAY,6BAA6B,qBAAqB,iBAAiB;CAEvF,OAAO,eAAe,wBAAwB,UAAuB;EACnE,MAAM,EAAE,QAAQ,MAAM,YAAY;EAClC,MAAM,WAAW,MAAM,oBAAoB,OAAO;EAClD,IAAI,CAAC,UAAU,MAAM,IAAI,MAAM,UAAU;EAEzC,MAAM,cAAc,MAAM,6BAA6B;EACvD,MAAM,EAAE,SAAS,OAAO,gBAAgB,SAGrC;GACD,KAAK;GACL,QAAQ;GACR,OAAO,EACL,cAAc,aACf;GACD,MAAM;IACJ,QAAQ;IACR,aAAa;IACb,MAAM,KAAK,QAAQ,OAAO,GAAG;IAC7B,mBAAmB,SAAS,cAAc,UAAU,UAAU;IAC9D,MAAM;IACN,MAAM,UAAU,UAAU,KAAK,SAAS,EACtC,OAAO,iBAAiB,KAAe,IAAI,EAC5C,EAAE;IACJ;GACF,CAAC;EAEF,IAAI,KAAK,YAAY,OAAO;EAE5B,IAAI,KAAK,YAAY,GAAG,MAAM,IAAI,MAAM,KAAK,UAAU,YAAY;;;;;;;;;;;;;;;AAgBvE,SAAS,iBAAiB,KAAa,KAAsB;CAC3D,IAAI,IAAI,WAAW,QAAQ,EAAE,OAAO,cAAc,IAAI,UAAU,EAAE,GAAG;CACrE,IAAI,IAAI,WAAW,mBAAmB,EAAE,OAAO,cAAc,IAAI,UAAU,EAAE,GAAG;CAChF,OAAO;;;;;;;;;AAUT,SAAS,cAAc,KAAa,KAAa;CAC/C,OAAO,IAAI,SAAS,MAAM,GAAG,IAAI,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudcome/utils-uni",
3
- "version": "1.34.0",
3
+ "version": "1.35.1",
4
4
  "description": "cloudcome utils for uni-app",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -15,6 +15,9 @@
15
15
  ],
16
16
  "database": [
17
17
  "./dist/database.d.ts"
18
+ ],
19
+ "weixin": [
20
+ "./dist/weixin.d.ts"
18
21
  ]
19
22
  }
20
23
  },
@@ -40,6 +43,11 @@
40
43
  "types": "./dist/database.d.ts",
41
44
  "import": "./dist/database.mjs",
42
45
  "require": "./dist/database.cjs"
46
+ },
47
+ "./weixin": {
48
+ "types": "./dist/weixin.d.ts",
49
+ "import": "./dist/weixin.mjs",
50
+ "require": "./dist/weixin.cjs"
43
51
  }
44
52
  },
45
53
  "files": [
@@ -49,22 +57,22 @@
49
57
  "node": ">=22"
50
58
  },
51
59
  "dependencies": {
52
- "@cloudcome/utils-core": "~1.20.0",
53
- "@cloudcome/utils-vue": "~1.13.3",
60
+ "@cloudcome/utils-core": "~1.21.0",
61
+ "@cloudcome/utils-vue": "~1.14.1",
54
62
  "@dcloudio/types": "^3.4.31",
55
63
  "@dcloudio/uni-app": "3.0.0-alpha-5000820260430001",
56
64
  "vue": "^3.5.34",
57
65
  "zod": "^4.4.3"
58
66
  },
59
67
  "keywords": [
60
- "utils",
61
68
  "cloudcome",
69
+ "utils",
70
+ "utils-browser",
62
71
  "utils-core",
63
- "utils-vue",
72
+ "utils-node",
64
73
  "utils-react",
65
74
  "utils-uni",
66
- "utils-node",
67
- "utils-browser"
75
+ "utils-vue"
68
76
  ],
69
77
  "homepage": "https://cloudcome.github.io/utils/",
70
78
  "license": "MIT",