@rongyan/wxpost-server 0.1.0 → 0.1.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.
package/README.md CHANGED
@@ -223,7 +223,7 @@ curl -X POST http://localhost:3000/upload-material \
223
223
  | `title` | string | 是 | 超过 64 字自动截断 |
224
224
  | `author` | string | 否 | 最多 16 字 |
225
225
  | `digest` | string | 否 | 摘要,最多 128 字;不填则从正文自动截取 |
226
- | `content` | string | 是 | 正文 HTML,最多 2 万字 |
226
+ | `content` | string | 是 | 正文 HTML |
227
227
  | `content_source_url` | string | 否 | 点击"阅读原文"跳转的 URL |
228
228
  | `thumb_media_id` | string | news 类型必填 | 封面图的永久素材 MediaID |
229
229
  | `image_info` | object | newspic 类型必填 | 图片列表,`{ "list": [...] }`,最多 20 张 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rongyan/wxpost-server",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "微信公众号内容发布服务,支持多账号,提供图片上传、草稿管理和发布等 HTTP 接口",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/draft.js CHANGED
@@ -36,10 +36,9 @@ function validateArticle(a, idx) {
36
36
  if (a.digest.length > 128) return `${p}.digest 最多 128 字,当前 ${a.digest.length} 字`;
37
37
  }
38
38
 
39
- // content:必填,最多 2 万字
39
+ // content:必填
40
40
  if (!a.content) return `${p}.content 必填`;
41
41
  if (typeof a.content !== 'string') return `${p}.content 须为字符串`;
42
- if (a.content.length > 20000) return `${p}.content 最多 2 万字,当前 ${a.content.length} 字`;
43
42
 
44
43
  // content_source_url:选填
45
44
  if (a.content_source_url !== undefined && typeof a.content_source_url !== 'string') {
package/src/index.js CHANGED
@@ -14,7 +14,7 @@ const { addImageMaterial, MAX_SIZE: MATERIAL_MAX_SIZE } = require('./material');
14
14
  const { checkApiKey } = require('./auth');
15
15
  const logger = require('./logger');
16
16
 
17
- const BODY_LIMIT = 2 * 1024 * 1024; // 2MB
17
+ const BODY_LIMIT = 20 * 1024 * 1024; // 20MB,正文富文本可能较大,具体长度由微信 API 判定
18
18
 
19
19
  function sendJSON(res, statusCode, data) {
20
20
  if (statusCode >= 400 && data.error) res._logError = data.error;
@@ -80,7 +80,8 @@ function receiveFile(req, res, account, sizeLimit, processor, sizeLimitLabel) {
80
80
  let settled = false;
81
81
 
82
82
  bb.on('file', (_fieldname, stream, info) => {
83
- const { filename } = info;
83
+ // busboy binary 解析 multipart filename,需还原为 UTF-8
84
+ const filename = Buffer.from(info.filename, 'binary').toString('utf-8');
84
85
  const chunks = [];
85
86
 
86
87
  stream.on('data', (chunk) => chunks.push(chunk));
package/src/logger.js CHANGED
@@ -32,7 +32,7 @@ function getStream() {
32
32
  }
33
33
 
34
34
  const logFile = path.join(_logDir, `${today}.log`);
35
- _stream = fs.createWriteStream(logFile, { flags: 'a', encoding: 'utf-8' });
35
+ _stream = fs.createWriteStream(logFile, { flags: 'a' });
36
36
  _stream.on('error', () => {
37
37
  // 写文件出错(如磁盘满)时重置 stream,下次重试创建;不崩进程
38
38
  _stream = null;
@@ -62,14 +62,19 @@ function pruneOldLogs(logDir) {
62
62
  }
63
63
  }
64
64
 
65
+ function formatLocalTime(d) {
66
+ const pad2 = (n) => String(n).padStart(2, '0');
67
+ const pad3 = (n) => String(n).padStart(3, '0');
68
+ return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ` +
69
+ `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}.${pad3(d.getMilliseconds())}`;
70
+ }
71
+
65
72
  function write(level, message) {
66
- const now = new Date();
67
- const ts = now.toISOString().replace('T', ' ').slice(0, 23);
68
- const line = `[${ts}] [${level}] ${message}\n`;
69
- process.stdout.write(line);
73
+ const line = `[${formatLocalTime(new Date())}] [${level}] ${message}\n`;
74
+ process.stdout.write(Buffer.from(line, 'utf-8'));
70
75
  try {
71
76
  const stream = getStream();
72
- if (stream) stream.write(line);
77
+ if (stream) stream.write(Buffer.from(line, 'utf-8'));
73
78
  } catch {
74
79
  // 写文件失败不影响主流程
75
80
  }