@ddn/egg-wechat 1.0.24 → 1.0.28

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.
@@ -27,8 +27,8 @@ class WCSService extends Service {
27
27
  } = this.app.config.wechat;
28
28
  const url = `${tokenUri}?grant_type=client_credential&appid=${appId}&secret=${appSecret}`;
29
29
  const res = await this.ctx.curl(url, jsonType);
30
- if (res.data.errcode){
31
- throw new Error(res.data.errmsg)
30
+ if (res.data.errcode) {
31
+ throw new Error(res.data.errmsg);
32
32
  }
33
33
  return res.data;
34
34
  }
@@ -42,8 +42,8 @@ class WCSService extends Service {
42
42
  async getTicket(token) {
43
43
  const url = `${ticketUri}?access_token=${token}&type=jsapi`;
44
44
  const res = await this.ctx.curl(url, jsonType);
45
- if (res.data.errcode){
46
- throw new Error(res.data.errmsg)
45
+ if (res.data.errcode) {
46
+ throw new Error(res.data.errmsg);
47
47
  }
48
48
  return res.data;
49
49
  }
package/app.js ADDED
@@ -0,0 +1,198 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 插件初始化入口:用于在应用启动前,动态覆盖 wechat 配置。
5
+ *
6
+ * 用法:在宿主项目的 config/config.*.js 中提供:
7
+ *
8
+ * exports.wechat = {
9
+ * // ...原有配置
10
+ * // init 可同步或异步,返回要覆盖/补充的配置对象
11
+ * init: async (app) => ({ componentAppId: 'wx...', componentAppSecret: '...' }),
12
+ * };
13
+ *
14
+ * @param {import('egg').Application} app Egg Application
15
+ */
16
+ module.exports = app => {
17
+ app.beforeStart(async () => {
18
+ const baseConfig = app.config.wechat || {};
19
+
20
+ const normalizeRuntimeCfg = (cfg) => {
21
+ if (!cfg || typeof cfg !== 'object') return null;
22
+ const next = Object.assign({}, cfg);
23
+ Object.keys(next).forEach(k => {
24
+ const v = next[k];
25
+ if (typeof v === 'string') next[k] = v.trim();
26
+ if (next[k] === undefined || next[k] === null || String(next[k]).trim() === '') {
27
+ delete next[k];
28
+ }
29
+ });
30
+ return Object.keys(next).length > 0 ? next : null;
31
+ };
32
+
33
+ const override = baseConfig.override && typeof baseConfig.override === 'object'
34
+ ? baseConfig.override
35
+ : null;
36
+
37
+ const init = baseConfig.init;
38
+
39
+ let initResult = null;
40
+ if (typeof init === 'function') {
41
+ try {
42
+ initResult = await init(app);
43
+ } catch (err) {
44
+ app.logger && app.logger.error && app.logger.error('[egg-wechat] wechat.init failed', err);
45
+ }
46
+ }
47
+
48
+ const finalOverride = initResult && typeof initResult === 'object' ? initResult : override;
49
+ if (finalOverride) {
50
+ // 就地合并,保证所有 service 都能读到更新后的值
51
+ Object.assign(baseConfig, finalOverride);
52
+ app.config.wechat = baseConfig;
53
+ }
54
+
55
+ const shouldEnableProvider = typeof baseConfig.getConfig === 'function';
56
+
57
+ const loadFromProvider = async () => {
58
+ if (!shouldEnableProvider) return null;
59
+
60
+ const ctx = app.createAnonymousContext();
61
+ try {
62
+ const cfg = await baseConfig.getConfig(ctx);
63
+ return normalizeRuntimeCfg(cfg);
64
+ } catch (err) {
65
+ app.logger && app.logger.warn && app.logger.warn('[egg-wechat] wechat.getConfig(ctx) failed', err);
66
+ return null;
67
+ }
68
+ };
69
+
70
+ const shouldEnableUnifiedConfig = (() => {
71
+ if (baseConfig.useUnifiedConfig === true) return true;
72
+ if (String(baseConfig.configSource || '').toLowerCase() === 'unified') return true;
73
+ // 自动兜底:当 component 配置缺失时尝试从统一配置读取(避免宿主必须写死 secret)
74
+ const hasStaticComponent = Boolean(baseConfig.componentAppId) && Boolean(baseConfig.componentAppSecret);
75
+ return !hasStaticComponent;
76
+ })();
77
+
78
+ const refreshIntervalMsRaw = baseConfig.unifiedConfigRefreshIntervalMs;
79
+ const refreshIntervalMs = Number.isFinite(Number(refreshIntervalMsRaw))
80
+ ? Math.max(Number(refreshIntervalMsRaw), 1000)
81
+ : 30 * 1000;
82
+
83
+ const loadFromUnifiedConfig = async () => {
84
+ // 在宿主没有 unifiedConfig service 时,静默跳过
85
+ const ctx = app.createAnonymousContext();
86
+ const unified = ctx?.service?.config?.unifiedConfig;
87
+ if (!unified || typeof unified.get !== 'function') return null;
88
+
89
+ // 统一配置键(以 ddn-hub 的 wechat_platform.* 为准)
90
+ const keys = {
91
+ appId: 'wechat_platform.app_id',
92
+ appSecret: 'wechat_platform.app_secret',
93
+ componentAppId: 'wechat_platform.component.app_id',
94
+ componentAppSecret: 'wechat_platform.component.app_secret',
95
+ componentToken: 'wechat_platform.component.token',
96
+ componentEncodingAESKey: 'wechat_platform.component.encoding_aes_key',
97
+ publicBaseUrl: 'wechat_platform.public_base_url',
98
+ };
99
+
100
+ const entries = await Promise.all(
101
+ Object.entries(keys).map(async ([k, key]) => {
102
+ try {
103
+ const v = await unified.get(key);
104
+ return [k, typeof v === 'string' ? v.trim() : v];
105
+ } catch (e) {
106
+ return [k, undefined];
107
+ }
108
+ })
109
+ );
110
+
111
+ const cfg = Object.fromEntries(entries);
112
+
113
+ return normalizeRuntimeCfg(cfg);
114
+ };
115
+
116
+ const applyRuntimeWechatConfig = (runtimeCfg) => {
117
+ if (!runtimeCfg || typeof runtimeCfg !== 'object') return;
118
+
119
+ // 仅在内存中合并:同时写到 app.wechatRuntimeConfig 与 app.config.wechat
120
+ // 目的:
121
+ // - service 读取 app.config.wechat 的代码无需大改
122
+ // - 运行时刷新能覆盖所有模块
123
+ app.wechatRuntimeConfig = Object.assign({}, app.wechatRuntimeConfig || {}, runtimeCfg);
124
+ app.config.wechat = Object.assign({}, app.config.wechat || {}, app.wechatRuntimeConfig);
125
+ };
126
+
127
+ // 对宿主/测试暴露一个手动刷新入口,避免等待定时器
128
+ app.wechatRefreshProviderConfig = async () => {
129
+ if (!shouldEnableProvider) return { enabled: false };
130
+ const cfg = await loadFromProvider();
131
+ if (cfg) applyRuntimeWechatConfig(cfg);
132
+ return { enabled: true, applied: Boolean(cfg) };
133
+ };
134
+
135
+ app.wechatRefreshUnifiedConfig = async () => {
136
+ if (!shouldEnableUnifiedConfig) return { enabled: false };
137
+ const cfg = await loadFromUnifiedConfig();
138
+ if (cfg) applyRuntimeWechatConfig(cfg);
139
+ return { enabled: true, applied: Boolean(cfg) };
140
+ };
141
+
142
+ if (shouldEnableProvider) {
143
+ try {
144
+ const cfg = await loadFromProvider();
145
+ if (cfg) applyRuntimeWechatConfig(cfg);
146
+ } catch (err) {
147
+ app.logger && app.logger.warn && app.logger.warn('[egg-wechat] provider initial load failed', err);
148
+ }
149
+
150
+ // 周期刷新 provider:避免进程长期持有过期配置
151
+ setInterval(async () => {
152
+ try {
153
+ const cfg = await loadFromProvider();
154
+ if (cfg) applyRuntimeWechatConfig(cfg);
155
+ } catch (err) {
156
+ app.logger && app.logger.warn && app.logger.warn('[egg-wechat] provider refresh failed', err);
157
+ }
158
+ }, refreshIntervalMs).unref();
159
+ }
160
+
161
+ if (shouldEnableUnifiedConfig) {
162
+ try {
163
+ const cfg = await loadFromUnifiedConfig();
164
+ if (cfg) applyRuntimeWechatConfig(cfg);
165
+ } catch (err) {
166
+ app.logger && app.logger.warn && app.logger.warn('[egg-wechat] unifiedConfig initial load failed', err);
167
+ }
168
+
169
+ // 周期刷新:在不重启进程的情况下感知统一配置变更
170
+ // 若宿主不需要,可显式设置 useUnifiedConfig=false 并提供静态配置
171
+ setInterval(async () => {
172
+ try {
173
+ const cfg = await loadFromUnifiedConfig();
174
+ if (cfg) applyRuntimeWechatConfig(cfg);
175
+ } catch (err) {
176
+ app.logger && app.logger.warn && app.logger.warn('[egg-wechat] unifiedConfig refresh failed', err);
177
+ }
178
+ }, refreshIntervalMs).unref();
179
+ }
180
+
181
+ // 仅输出字段存在性,避免泄露敏感值
182
+ const finalConfig = app.config.wechat || {};
183
+ if (app.logger && app.logger.info) {
184
+ app.logger.info('[egg-wechat] wechat config initialized', {
185
+ providerEnabled: Boolean(shouldEnableProvider),
186
+ useUnifiedConfig: Boolean(shouldEnableUnifiedConfig),
187
+ unifiedConfigRefreshIntervalMs: refreshIntervalMs,
188
+ appIdPresent: Boolean(finalConfig.appId),
189
+ appSecretPresent: Boolean(finalConfig.appSecret),
190
+ componentAppIdPresent: Boolean(finalConfig.componentAppId),
191
+ componentAppSecretPresent: Boolean(finalConfig.componentAppSecret),
192
+ componentTokenPresent: Boolean(finalConfig.componentToken),
193
+ componentEncodingAESKeyPresent: Boolean(finalConfig.componentEncodingAESKey),
194
+ publicBaseUrlPresent: Boolean(finalConfig.publicBaseUrl),
195
+ });
196
+ }
197
+ });
198
+ };
@@ -20,4 +20,12 @@ exports.wechat = {
20
20
  componentAppSecret: '',
21
21
  componentToken: '',
22
22
  componentEncodingAESKey: '',
23
+
24
+ // 可选:动态注入/覆盖配置(覆盖所有模块)
25
+ // init: async (app) => ({ componentAppId: 'wx...', componentAppSecret: '...' }),
26
+ // override: { componentAppId: 'wx...', componentAppSecret: '...' },
27
+
28
+ // 可选:运行时从统一配置读取并周期刷新(需要宿主提供 ctx.service.config.unifiedConfig.get)
29
+ // useUnifiedConfig: true,
30
+ // unifiedConfigRefreshIntervalMs: 30000,
23
31
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ddn/egg-wechat",
3
- "version": "1.0.24",
3
+ "version": "1.0.28",
4
4
  "description": "Eggjs plugin for wechat",
5
5
  "types": "./index.d.ts",
6
6
  "eggPlugin": {
@@ -16,6 +16,7 @@
16
16
  "wxapp"
17
17
  ],
18
18
  "dependencies": {
19
+ "wechat-crypto": "^0.0.2",
19
20
  "xml-js": "^1.6.11"
20
21
  },
21
22
  "peerDependencies": {
@@ -36,17 +37,18 @@
36
37
  "node": ">=8.0.0"
37
38
  },
38
39
  "scripts": {
39
- "test": "npm run lint -- --fix && egg-bin pkgfiles && npm run test-local",
40
+ "test": "npm run lint -- --fix && npm run test-local",
40
41
  "test-local": "egg-bin test",
41
42
  "cov": "egg-bin cov",
42
43
  "lint": "eslint .",
43
- "ci": "egg-bin pkgfiles --check && npm run lint && npm run cov",
44
- "pkgfiles": "egg-bin pkgfiles",
44
+ "ci": "npm run lint && npm run cov",
45
+ "pkgfiles": "echo 'pkgfiles step skipped'",
45
46
  "autod": "autod"
46
47
  },
47
48
  "files": [
48
49
  "app",
49
50
  "config",
51
+ "app.js",
50
52
  "index.d.ts"
51
53
  ],
52
54
  "ci": {