@jiexiaoyin/wecom-api 0.0.2
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.
- package/README.md +228 -0
- package/config.example.json +7 -0
- package/config.js +76 -0
- package/docs/approval-templates.example.json +11 -0
- package/docs/nginx-mirror.md +193 -0
- package/openclaw.plugin.json +15 -0
- package/package.json +34 -0
- package/plugin.cjs +172 -0
- package/plugin.ts +136 -0
- package/skills/wecom-api/SKILL.md +40 -0
- package/skills/wecom-api/index.js +288 -0
- package/skills/wecom-api/openclaw.plugin.json +10 -0
- package/src/callback-helper.js +198 -0
- package/src/config.cjs +286 -0
- package/src/core/permission.js +479 -0
- package/src/crypto.js +130 -0
- package/src/index.js +199 -0
- package/src/modules/addressbook/index.js +413 -0
- package/src/modules/addressbook_cache/index.js +365 -0
- package/src/modules/advanced/index.js +159 -0
- package/src/modules/app/index.js +102 -0
- package/src/modules/approval/index.js +146 -0
- package/src/modules/auth/index.js +103 -0
- package/src/modules/callback/index.js +1180 -0
- package/src/modules/chain/index.js +193 -0
- package/src/modules/checkin/index.js +142 -0
- package/src/modules/checkin_rules/index.js +251 -0
- package/src/modules/contact/index.js +481 -0
- package/src/modules/contact_stats/index.js +349 -0
- package/src/modules/custom/index.js +140 -0
- package/src/modules/customer/index.js +51 -0
- package/src/modules/disk/index.js +245 -0
- package/src/modules/document/index.js +282 -0
- package/src/modules/hr/index.js +93 -0
- package/src/modules/intelligence/index.js +346 -0
- package/src/modules/kf/index.js +74 -0
- package/src/modules/live/index.js +122 -0
- package/src/modules/media/index.js +183 -0
- package/src/modules/meeting/index.js +665 -0
- package/src/modules/message/index.js +402 -0
- package/src/modules/messenger/index.js +208 -0
- package/src/modules/moments/index.js +161 -0
- package/src/modules/msgaudit/index.js +24 -0
- package/src/modules/notify/index.js +81 -0
- package/src/modules/oceanengine/index.js +199 -0
- package/src/modules/openchat/index.js +197 -0
- package/src/modules/phone/index.js +45 -0
- package/src/modules/room/index.js +178 -0
- package/src/modules/schedule/index.js +246 -0
- package/src/modules/school/index.js +199 -0
- package/src/modules/security/index.js +223 -0
- package/src/modules/sensitive/index.js +170 -0
- package/src/modules/thirdparty/index.js +145 -0
- package/src/sdk/index.js +269 -0
- package/src/utils/callback-helper.js +198 -0
- package/test/callback-crypto.test.js +55 -0
- package/test/crypto.test.js +85 -0
- package/test/permission.test.js +115 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 企业微信回调处理工具函数
|
|
3
|
+
*
|
|
4
|
+
* 功能:
|
|
5
|
+
* 1. 自动识别加密/明文消息
|
|
6
|
+
* 2. 智能处理:加密消息自动解密,明文消息直接使用
|
|
7
|
+
* 3. 异常保护:解密失败时优雅返回,不影响主插件
|
|
8
|
+
* 4. 支持 Nginx Mirror 场景
|
|
9
|
+
*
|
|
10
|
+
* 使用场景:
|
|
11
|
+
* - 直接接收企业微信回调:收到消息,自动解密
|
|
12
|
+
* - Nginx Mirror 转发:收到消息,自动解密
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const xml2js = require('xml2js');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 解析 XML 为对象
|
|
19
|
+
* @param {string} xmlString - XML 字符串
|
|
20
|
+
* @returns {Promise<object>} 解析后的对象
|
|
21
|
+
*/
|
|
22
|
+
async function parseXML(xmlString) {
|
|
23
|
+
const parser = new xml2js.Parser({ explicitArray: false });
|
|
24
|
+
return parser.parseStringPromise(xmlString);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 检测消息是否加密
|
|
29
|
+
* @param {object} xml - 解析后的 XML 对象
|
|
30
|
+
* @returns {boolean} 是否加密
|
|
31
|
+
*/
|
|
32
|
+
function isEncryptedMessage(xml) {
|
|
33
|
+
return !!(xml?.xml?.Encrypt);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 解析 URL 参数
|
|
38
|
+
* @param {string} url - 请求 URL
|
|
39
|
+
* @returns {object} 解析后的参数
|
|
40
|
+
*/
|
|
41
|
+
function parseQueryParams(url) {
|
|
42
|
+
const query = new URLSearchParams(url.split('?')[1] || '');
|
|
43
|
+
return {
|
|
44
|
+
msgSignature: query.get('msg_signature') || query.get('signature') || '',
|
|
45
|
+
timestamp: query.get('timestamp') || '',
|
|
46
|
+
nonce: query.get('nonce') || '',
|
|
47
|
+
echostr: query.get('echostr') || '',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 读取请求 body
|
|
53
|
+
* @param {IncomingMessage} req - HTTP 请求对象
|
|
54
|
+
* @returns {Promise<string>} body 字符串
|
|
55
|
+
*/
|
|
56
|
+
async function readBody(req) {
|
|
57
|
+
const chunks = [];
|
|
58
|
+
for await (const chunk of req) {
|
|
59
|
+
chunks.push(chunk);
|
|
60
|
+
}
|
|
61
|
+
return Buffer.concat(chunks).toString();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 创建回调处理器
|
|
66
|
+
*
|
|
67
|
+
* @param {object} options - 配置选项
|
|
68
|
+
* @param {object} options.callbackInstance - 回调实例(包含 handle 方法)
|
|
69
|
+
* @param {function} options.onMessage - 消息处理回调(可选)
|
|
70
|
+
* @param {function} options.onDecryptFail - 解密失败回调(可选)
|
|
71
|
+
* @param {boolean} options.alwaysReturnSuccess - 解密失败时是否返回 success(默认 true)
|
|
72
|
+
* @returns {function} 处理函数
|
|
73
|
+
*/
|
|
74
|
+
function createCallbackHandler(options = {}) {
|
|
75
|
+
const {
|
|
76
|
+
callbackInstance = null,
|
|
77
|
+
onMessage = null,
|
|
78
|
+
onDecryptFail = null,
|
|
79
|
+
alwaysReturnSuccess = true,
|
|
80
|
+
} = options;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 回调处理函数
|
|
84
|
+
*/
|
|
85
|
+
return async function handleWecomCallback(req, res) {
|
|
86
|
+
// 响应函数
|
|
87
|
+
const respondSuccess = () => {
|
|
88
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
89
|
+
res.end('success');
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const respondError = (message) => {
|
|
93
|
+
console.log(`[callback-helper] 错误: ${message}`);
|
|
94
|
+
if (alwaysReturnSuccess) {
|
|
95
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
96
|
+
res.end('success');
|
|
97
|
+
} else {
|
|
98
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
99
|
+
res.end(message || 'error');
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
// 1. 解析参数和 body
|
|
105
|
+
const params = parseQueryParams(req.url);
|
|
106
|
+
const body = await readBody(req);
|
|
107
|
+
|
|
108
|
+
// 2. 解析 XML
|
|
109
|
+
const xml = await parseXML(body);
|
|
110
|
+
const encrypted = isEncryptedMessage(xml);
|
|
111
|
+
|
|
112
|
+
let message;
|
|
113
|
+
|
|
114
|
+
if (encrypted) {
|
|
115
|
+
// ========== 加密消息 ==========
|
|
116
|
+
console.log('[callback-helper] → 检测到加密消息,尝试解密');
|
|
117
|
+
|
|
118
|
+
if (!callbackInstance) {
|
|
119
|
+
console.log('[callback-helper] → 回调实例未初始化,跳过');
|
|
120
|
+
return respondSuccess();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const result = await callbackInstance.handle({
|
|
125
|
+
msgSignature: params.msgSignature,
|
|
126
|
+
timestamp: params.timestamp,
|
|
127
|
+
nonce: params.nonce,
|
|
128
|
+
xmlBody: body,
|
|
129
|
+
});
|
|
130
|
+
console.log('[callback-helper] → 解密成功');
|
|
131
|
+
message = result.message;
|
|
132
|
+
} catch (decryptError) {
|
|
133
|
+
// 解密失败,优雅处理
|
|
134
|
+
console.log('[callback-helper] → 解密失败:', decryptError.message);
|
|
135
|
+
if (onDecryptFail) {
|
|
136
|
+
onDecryptFail(decryptError, body);
|
|
137
|
+
}
|
|
138
|
+
return alwaysReturnSuccess ? respondSuccess() : respondError(decryptError.message);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
} else {
|
|
142
|
+
// ========== 明文消息(来自转发/mirror)==========
|
|
143
|
+
console.log('[callback-helper] → 检测到明文消息,跳过解密');
|
|
144
|
+
message = xml.xml || xml;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 3. 业务处理
|
|
148
|
+
if (message && onMessage) {
|
|
149
|
+
const eventType = message.Event || message.MsgType || 'unknown';
|
|
150
|
+
const fromUser = message.FromUserName || 'unknown';
|
|
151
|
+
console.log(`[callback-helper] → 事件: ${eventType} from ${fromUser}`);
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
await onMessage(message, {
|
|
155
|
+
encrypted,
|
|
156
|
+
raw: xml,
|
|
157
|
+
body,
|
|
158
|
+
});
|
|
159
|
+
} catch (handlerError) {
|
|
160
|
+
console.log('[callback-helper] → 消息处理回调出错:', handlerError.message);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 4. 返回 success
|
|
165
|
+
respondSuccess();
|
|
166
|
+
|
|
167
|
+
} catch (e) {
|
|
168
|
+
console.log('[callback-helper] → 回调处理异常:', e.message);
|
|
169
|
+
return alwaysReturnSuccess ? respondSuccess() : respondError(e.message);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return true;
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 便捷函数:创建标准回调处理器
|
|
178
|
+
*
|
|
179
|
+
* @param {object} callbackInstance - 回调实例
|
|
180
|
+
* @param {function} onEvent - 事件处理回调
|
|
181
|
+
* @returns {function} 处理函数
|
|
182
|
+
*/
|
|
183
|
+
function createStandardHandler(callbackInstance, onEvent = null) {
|
|
184
|
+
return createCallbackHandler({
|
|
185
|
+
callbackInstance,
|
|
186
|
+
onMessage: onEvent,
|
|
187
|
+
alwaysReturnSuccess: true,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = {
|
|
192
|
+
parseXML,
|
|
193
|
+
isEncryptedMessage,
|
|
194
|
+
parseQueryParams,
|
|
195
|
+
readBody,
|
|
196
|
+
createCallbackHandler,
|
|
197
|
+
createStandardHandler,
|
|
198
|
+
};
|
package/src/config.cjs
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 企业微信插件配置管理
|
|
3
|
+
*
|
|
4
|
+
* 支持多环境配置、敏感信息分离、便捷部署
|
|
5
|
+
*
|
|
6
|
+
* 配置文件优先级:
|
|
7
|
+
* 1. .env 文件(敏感信息)
|
|
8
|
+
* 2. config.{env}.json(环境配置)
|
|
9
|
+
* 3. config.json(默认配置)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
class Config {
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
this.env = options.env || process.env.NODE_ENV || 'development';
|
|
18
|
+
this.configDir = options.configDir || './';
|
|
19
|
+
this.envPrefix = options.envPrefix || 'WECOM_';
|
|
20
|
+
|
|
21
|
+
// 加载配置
|
|
22
|
+
this._config = this._loadConfig(options);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 加载配置
|
|
27
|
+
*/
|
|
28
|
+
_loadConfig(options) {
|
|
29
|
+
let config = {};
|
|
30
|
+
|
|
31
|
+
// 1. 加载默认配置
|
|
32
|
+
config = this._mergeConfig(config, 'config.json');
|
|
33
|
+
|
|
34
|
+
// 2. 加载环境配置
|
|
35
|
+
if (this.env !== 'development') {
|
|
36
|
+
config = this._mergeConfig(config, `config.${this.env}.json`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 3. 加载代码传入的配置
|
|
40
|
+
if (Object.keys(options).length > 0) {
|
|
41
|
+
config = { ...config, ...options };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 4. 从环境变量加载(覆盖其他配置)
|
|
45
|
+
config = this._loadFromEnv(config);
|
|
46
|
+
|
|
47
|
+
// 5. 加载 .env 文件(敏感信息)
|
|
48
|
+
config = this._loadEnvFile(config);
|
|
49
|
+
|
|
50
|
+
return config;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 合并配置文件
|
|
55
|
+
*/
|
|
56
|
+
_mergeConfig(config, filename) {
|
|
57
|
+
const filePath = path.join(this.configDir, filename);
|
|
58
|
+
if (fs.existsSync(filePath)) {
|
|
59
|
+
try {
|
|
60
|
+
const fileConfig = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
61
|
+
return { ...config, ...fileConfig };
|
|
62
|
+
} catch (e) {
|
|
63
|
+
console.warn(`配置文件 ${filename} 加载失败:`, e.message);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return config;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 从环境变量加载
|
|
71
|
+
*/
|
|
72
|
+
_loadFromEnv(config) {
|
|
73
|
+
const envMappings = {
|
|
74
|
+
'CORP_ID': 'corpId',
|
|
75
|
+
'CORP_SECRET': 'corpSecret',
|
|
76
|
+
'AGENT_ID': 'agentId',
|
|
77
|
+
'TOKEN': 'token',
|
|
78
|
+
'ENCODING_AES_KEY': 'encodingAESKey',
|
|
79
|
+
'CALLBACK_URL': 'callbackUrl'
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
for (const [envKey, configKey] of Object.entries(envMappings)) {
|
|
83
|
+
const envValue = process.env[`${this.envPrefix}${envKey}`];
|
|
84
|
+
if (envValue !== undefined) {
|
|
85
|
+
config[configKey] = envValue;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return config;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 加载 .env 文件
|
|
93
|
+
*/
|
|
94
|
+
_loadEnvFile(config) {
|
|
95
|
+
const envPath = path.join(this.configDir, '.env');
|
|
96
|
+
if (fs.existsSync(envPath)) {
|
|
97
|
+
try {
|
|
98
|
+
const envContent = fs.readFileSync(envPath, 'utf8');
|
|
99
|
+
const envVars = this._parseEnv(envContent);
|
|
100
|
+
|
|
101
|
+
const envMappings = {
|
|
102
|
+
'WECOM_CORP_ID': 'corpId',
|
|
103
|
+
'WECOM_CORP_SECRET': 'corpSecret',
|
|
104
|
+
'WECOM_AGENT_ID': 'agentId',
|
|
105
|
+
'WECOM_TOKEN': 'token',
|
|
106
|
+
'WECOM_ENCODING_AES_KEY': 'encodingAESKey'
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
for (const [envKey, configKey] of Object.entries(envMappings)) {
|
|
110
|
+
if (envVars[envKey]) {
|
|
111
|
+
config[configKey] = envVars[envKey];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} catch (e) {
|
|
115
|
+
console.warn('.env 文件加载失败:', e.message);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return config;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 解析 .env 内容
|
|
123
|
+
*/
|
|
124
|
+
_parseEnv(content) {
|
|
125
|
+
const result = {};
|
|
126
|
+
const lines = content.split('\n');
|
|
127
|
+
for (const line of lines) {
|
|
128
|
+
const trimmed = line.trim();
|
|
129
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
130
|
+
const [key, ...valueParts] = trimmed.split('=');
|
|
131
|
+
if (key && valueParts.length > 0) {
|
|
132
|
+
result[key.trim()] = valueParts.join('=').trim();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 获取配置值
|
|
141
|
+
*/
|
|
142
|
+
get(key, defaultValue = null) {
|
|
143
|
+
return this.config[key] || defaultValue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 设置配置值
|
|
148
|
+
*/
|
|
149
|
+
set(key, value) {
|
|
150
|
+
this.config[key] = value;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 获取完整配置
|
|
155
|
+
*/
|
|
156
|
+
getAll() {
|
|
157
|
+
return { ...this.config };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 获取企业基本配置(用于 SDK)
|
|
162
|
+
*/
|
|
163
|
+
getCorpConfig() {
|
|
164
|
+
return {
|
|
165
|
+
corpId: this.get('corpId'),
|
|
166
|
+
corpSecret: this.get('corpSecret'),
|
|
167
|
+
agentId: this.get('agentId')
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 获取回调配置
|
|
173
|
+
*/
|
|
174
|
+
getCallbackConfig() {
|
|
175
|
+
return {
|
|
176
|
+
token: this.get('token'),
|
|
177
|
+
encodingAESKey: this.get('encodingAESKey'),
|
|
178
|
+
corpId: this.get('corpId'),
|
|
179
|
+
callbackUrl: this.get('callbackUrl')
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 验证配置
|
|
185
|
+
*/
|
|
186
|
+
validate() {
|
|
187
|
+
const required = ['corpId', 'corpSecret'];
|
|
188
|
+
const missing = required.filter(key => !this.get(key));
|
|
189
|
+
return { valid: missing.length === 0, missing };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* 导出安全配置(不含敏感信息)
|
|
194
|
+
*/
|
|
195
|
+
exportSafeConfig() {
|
|
196
|
+
const safe = { ...this.config };
|
|
197
|
+
delete safe.corpSecret;
|
|
198
|
+
delete safe.token;
|
|
199
|
+
delete safe.encodingAESKey;
|
|
200
|
+
return safe;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 创建配置文件
|
|
205
|
+
*/
|
|
206
|
+
static init(options = {}) {
|
|
207
|
+
const configDir = options.configDir || './';
|
|
208
|
+
|
|
209
|
+
// 1. 创建 config.json
|
|
210
|
+
const defaultConfig = {
|
|
211
|
+
env: "development",
|
|
212
|
+
callbackUrl: "https://your-domain.com/wecom/callback",
|
|
213
|
+
maxHistorySize: 5000
|
|
214
|
+
};
|
|
215
|
+
fs.writeFileSync(
|
|
216
|
+
path.join(configDir, 'config.json'),
|
|
217
|
+
JSON.stringify(defaultConfig, null, 2)
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// 2. 创建 .env.example
|
|
221
|
+
const envExample = `# 企业微信配置
|
|
222
|
+
# 请复制此文件为 .env 并填入真实值
|
|
223
|
+
|
|
224
|
+
WECOM_CORP_ID=your_corp_id
|
|
225
|
+
WECOM_CORP_SECRET=your_corp_secret
|
|
226
|
+
WECOM_AGENT_ID=your_agent_id
|
|
227
|
+
WECOM_TOKEN=your_callback_token
|
|
228
|
+
WECOM_ENCODING_AES_KEY=your_encoding_aes_key
|
|
229
|
+
`;
|
|
230
|
+
fs.writeFileSync(
|
|
231
|
+
path.join(configDir, '.env.example'),
|
|
232
|
+
envExample
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// 3. 创建 .gitignore
|
|
236
|
+
const gitignore = `# 敏感配置
|
|
237
|
+
.env
|
|
238
|
+
config.local.json
|
|
239
|
+
*.local.json
|
|
240
|
+
|
|
241
|
+
# 日志
|
|
242
|
+
logs/
|
|
243
|
+
*.log
|
|
244
|
+
|
|
245
|
+
# 事件记录
|
|
246
|
+
events_*.json
|
|
247
|
+
`;
|
|
248
|
+
fs.writeFileSync(
|
|
249
|
+
path.join(configDir, '.gitignore'),
|
|
250
|
+
gitignore
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
console.log(`
|
|
254
|
+
✅ 配置文件已创建:
|
|
255
|
+
|
|
256
|
+
1. config.json - 基础配置(可提交到代码仓库)
|
|
257
|
+
2. .env.example - 环境变量示例(可提交)
|
|
258
|
+
3. .gitignore - Git 忽略配置
|
|
259
|
+
|
|
260
|
+
请执行以下步骤:
|
|
261
|
+
|
|
262
|
+
1. 复制 .env.example 为 .env
|
|
263
|
+
cp .env.example .env
|
|
264
|
+
|
|
265
|
+
2. 编辑 .env 填入真实配置
|
|
266
|
+
vim .env
|
|
267
|
+
|
|
268
|
+
3. 开始使用
|
|
269
|
+
npm run callback
|
|
270
|
+
`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// 导出类和便捷函数
|
|
275
|
+
module.exports = Config;
|
|
276
|
+
module.exports.Config = Config;
|
|
277
|
+
|
|
278
|
+
// 便捷函数:加载配置
|
|
279
|
+
module.exports.loadConfig = function(options = {}) {
|
|
280
|
+
const config = new Config(options);
|
|
281
|
+
return config.getAll();
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// ESM 导出
|
|
285
|
+
module.exports.__esModule = true;
|
|
286
|
+
module.exports.default = Config;
|