@kne/fastify-message 1.0.12 → 1.0.14

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.
@@ -1,5 +1,8 @@
1
1
  const fp = require('fastify-plugin');
2
2
 
3
+ const RECORD_TAG = '消息-发送记录';
4
+ const TEMPLATE_TAG = '消息-消息模版';
5
+
3
6
  module.exports = fp(async (fastify, options) => {
4
7
  const { services } = fastify[options.name];
5
8
 
@@ -9,16 +12,18 @@ module.exports = fp(async (fastify, options) => {
9
12
  {
10
13
  onRequest: options.getAuthenticate('record'),
11
14
  schema: {
12
- description: '获取消息发送记录列表',
15
+ tags: [RECORD_TAG],
16
+ description: '分页获取消息发送记录列表,按创建时间倒序;perPage 最大 100',
13
17
  summary: '查看发送记录',
14
18
  querystring: {
15
19
  type: 'object',
16
20
  properties: {
17
21
  currentPage: { type: 'integer', default: 1, description: '页码' },
18
- perPage: { type: 'integer', default: 20, description: '每页数量' },
22
+ perPage: { type: 'integer', default: 20, description: '每页数量,超过 100 按 100 处理' },
19
23
  filter: {
20
24
  type: 'object',
21
25
  default: {},
26
+ description: '筛选条件,均为精确匹配',
22
27
  properties: {
23
28
  type: { type: 'integer', description: '发送类型: 0=邮件, 1=短信' },
24
29
  code: { type: 'string', description: '模板编码' },
@@ -29,6 +34,7 @@ module.exports = fp(async (fastify, options) => {
29
34
  },
30
35
  response: {
31
36
  200: {
37
+ description: '发送记录分页结果',
32
38
  type: 'object',
33
39
  properties: {
34
40
  totalCount: { type: 'integer', description: '总数量' },
@@ -36,8 +42,10 @@ module.exports = fp(async (fastify, options) => {
36
42
  perPage: { type: 'integer', description: '每页数量' },
37
43
  pageData: {
38
44
  type: 'array',
45
+ description: '当前页发送记录列表',
39
46
  items: {
40
47
  type: 'object',
48
+ description: '发送记录',
41
49
  properties: {
42
50
  id: { type: 'string', description: '记录ID' },
43
51
  name: { type: 'string', description: '发送对象(邮箱/手机号)' },
@@ -58,12 +66,12 @@ module.exports = fp(async (fastify, options) => {
58
66
  async request => {
59
67
  const { currentPage = 1, perPage = 20, filter = {} } = request.query;
60
68
  const { type, code, name } = filter;
61
-
69
+
62
70
  const where = {};
63
71
  if (type !== undefined) where.type = type;
64
72
  if (code) where.code = code;
65
73
  if (name) where.name = name;
66
-
74
+
67
75
  return await services.record.list({ filter: where, perPage: Math.min(perPage, 100), currentPage });
68
76
  }
69
77
  );
@@ -74,7 +82,8 @@ module.exports = fp(async (fastify, options) => {
74
82
  {
75
83
  onRequest: options.getAuthenticate('template:send'),
76
84
  schema: {
77
- description: '根据模版发送消息',
85
+ tags: [TEMPLATE_TAG],
86
+ description: '使用指定模版渲染内容并通过对应类型的发送器(邮件 / 短信)发送,同时写入发送记录;模版不存在返回 404,模版已禁用返回 400',
78
87
  summary: '发送消息',
79
88
  body: {
80
89
  type: 'object',
@@ -82,11 +91,12 @@ module.exports = fp(async (fastify, options) => {
82
91
  properties: {
83
92
  templateId: { type: 'string', description: '模版ID' },
84
93
  name: { type: 'string', description: '发送对象(邮箱/手机号)' },
85
- props: { type: 'object', description: '模版变量', additionalProperties: true }
94
+ props: { type: 'object', description: '模版变量,用于渲染模版内容,不传为空对象', additionalProperties: true }
86
95
  }
87
96
  },
88
97
  response: {
89
98
  200: {
99
+ description: '发送成功',
90
100
  type: 'object',
91
101
  properties: {
92
102
  success: { type: 'boolean', description: '是否发送成功' }
@@ -117,7 +127,8 @@ module.exports = fp(async (fastify, options) => {
117
127
  {
118
128
  onRequest: options.getAuthenticate('record'),
119
129
  schema: {
120
- description: '获取单条消息发送记录详情',
130
+ tags: [RECORD_TAG],
131
+ description: '获取单条消息发送记录详情,记录不存在返回 404',
121
132
  summary: '查看发送记录详情',
122
133
  params: {
123
134
  type: 'object',
@@ -128,6 +139,7 @@ module.exports = fp(async (fastify, options) => {
128
139
  },
129
140
  response: {
130
141
  200: {
142
+ description: '发送记录详情',
131
143
  type: 'object',
132
144
  properties: {
133
145
  id: { type: 'string', description: '记录ID' },
@@ -162,16 +174,18 @@ module.exports = fp(async (fastify, options) => {
162
174
  {
163
175
  onRequest: options.getAuthenticate('template'),
164
176
  schema: {
165
- description: '获取消息模版列表',
177
+ tags: [TEMPLATE_TAG],
178
+ description: '分页获取消息模版列表,按创建时间倒序;perPage 最大 100',
166
179
  summary: '查看消息模版',
167
180
  querystring: {
168
181
  type: 'object',
169
182
  properties: {
170
183
  currentPage: { type: 'integer', default: 1, description: '页码' },
171
- perPage: { type: 'integer', default: 20, description: '每页数量' },
184
+ perPage: { type: 'integer', default: 20, description: '每页数量,超过 100 按 100 处理' },
172
185
  filter: {
173
186
  type: 'object',
174
187
  default: {},
188
+ description: '筛选条件,均为精确匹配',
175
189
  properties: {
176
190
  type: { type: 'integer', description: '模版类型: 0=邮件, 1=短信' },
177
191
  code: { type: 'string', description: '模版编码' },
@@ -183,6 +197,7 @@ module.exports = fp(async (fastify, options) => {
183
197
  },
184
198
  response: {
185
199
  200: {
200
+ description: '消息模版分页结果',
186
201
  type: 'object',
187
202
  properties: {
188
203
  totalCount: { type: 'integer', description: '总数量' },
@@ -190,8 +205,10 @@ module.exports = fp(async (fastify, options) => {
190
205
  perPage: { type: 'integer', description: '每页数量' },
191
206
  pageData: {
192
207
  type: 'array',
208
+ description: '当前页消息模版列表',
193
209
  items: {
194
210
  type: 'object',
211
+ description: '消息模版',
195
212
  properties: {
196
213
  id: { type: 'string', description: '模版ID' },
197
214
  name: { type: 'string', description: '模版名称' },
@@ -212,13 +229,13 @@ module.exports = fp(async (fastify, options) => {
212
229
  async request => {
213
230
  const { currentPage = 1, perPage = 20, filter = {} } = request.query;
214
231
  const { type, code, level, status } = filter;
215
-
232
+
216
233
  const where = {};
217
234
  if (type !== undefined) where.type = type;
218
235
  if (code) where.code = code;
219
236
  if (level !== undefined) where.level = level;
220
237
  if (status !== undefined) where.status = status;
221
-
238
+
222
239
  return await services.template.list({ filter: where, perPage: Math.min(perPage, 100), currentPage });
223
240
  }
224
241
  );
@@ -229,7 +246,8 @@ module.exports = fp(async (fastify, options) => {
229
246
  {
230
247
  onRequest: options.getAuthenticate('template'),
231
248
  schema: {
232
- description: '获取单个消息模版详情',
249
+ tags: [TEMPLATE_TAG],
250
+ description: '获取单个消息模版详情,模版不存在返回 404',
233
251
  summary: '查看消息模版详情',
234
252
  params: {
235
253
  type: 'object',
@@ -240,6 +258,7 @@ module.exports = fp(async (fastify, options) => {
240
258
  },
241
259
  response: {
242
260
  200: {
261
+ description: '消息模版详情',
243
262
  type: 'object',
244
263
  properties: {
245
264
  id: { type: 'string', description: '模版ID' },
@@ -1,4 +1,7 @@
1
1
  const fp = require('fastify-plugin');
2
+ const { success, statistics } = require('../schema/response');
3
+
4
+ const TAG = '消息-统计';
2
5
 
3
6
  module.exports = fp(async (fastify, options) => {
4
7
  const { services } = fastify[options.name];
@@ -8,7 +11,9 @@ module.exports = fp(async (fastify, options) => {
8
11
  {
9
12
  onRequest: options.getAuthenticate('statistics'),
10
13
  schema: {
14
+ tags: [TAG],
11
15
  summary: '获取消息统计概览',
16
+ description: '按时间范围汇总消息发送数量、类型/模板分布、每日与按小时趋势,以及模板数量统计;不支持的 range 报错,其他查询异常时返回空统计',
12
17
  query: {
13
18
  type: 'object',
14
19
  properties: {
@@ -17,7 +22,8 @@ module.exports = fp(async (fastify, options) => {
17
22
  type: { type: 'integer', description: '按消息类型筛选: 0=邮件, 1=短信' },
18
23
  code: { type: 'string', description: '按模板编码筛选' }
19
24
  }
20
- }
25
+ },
26
+ response: success('消息统计数据', statistics)
21
27
  }
22
28
  },
23
29
  async request => {
@@ -32,7 +38,9 @@ module.exports = fp(async (fastify, options) => {
32
38
  {
33
39
  onRequest: options.getAuthenticate('statistics'),
34
40
  schema: {
41
+ tags: [TAG],
35
42
  summary: 'SSE实时推送消息统计数据',
43
+ description: '以 text/event-stream 长连接按 interval 推送当天的实时统计(发送总数、类型/模板分布、按小时趋势等),客户端断开后停止;range 参数当前未使用',
36
44
  query: {
37
45
  type: 'object',
38
46
  properties: {
@@ -42,7 +50,8 @@ module.exports = fp(async (fastify, options) => {
42
50
  code: { type: 'string', description: '按模板编码筛选' },
43
51
  interval: { type: 'integer', minimum: 1, default: 5, description: '推送间隔时间(秒),最小1秒,默认5秒' }
44
52
  }
45
- }
53
+ },
54
+ response: { 200: { description: 'text/event-stream 事件流,每条 data 为当天实时统计 JSON' } }
46
55
  }
47
56
  },
48
57
  async (request, reply) => {
@@ -0,0 +1,33 @@
1
+ // 新增的响应 schema 只用于 OpenAPI 文档:字段可空、对象允许额外字段,避免序列化改变实际输出
2
+ const str = description => ({ type: 'string', nullable: true, description });
3
+ const num = description => ({ type: 'number', nullable: true, description });
4
+ // 结构不固定的字段不声明类型,原样输出
5
+ const any = description => ({ description });
6
+ const obj = (description, properties = {}) => ({ type: 'object', nullable: true, additionalProperties: true, description, properties });
7
+
8
+ const success = (description, schema) => ({ 200: Object.assign({}, schema || { type: 'object', additionalProperties: true }, { description }) });
9
+
10
+ const MESSAGE_TYPE = '消息类型:0 邮件,1 短信';
11
+
12
+ const statistics = {
13
+ type: 'object',
14
+ additionalProperties: true,
15
+ properties: {
16
+ range: str('时间范围:7d / 1m / 3m / 1y'),
17
+ rangeLabel: str('时间范围名称,如「近7天」'),
18
+ totalRecords: num('范围内发送的消息总数'),
19
+ byType: any(`按消息类型统计的发送数,键为类型(${MESSAGE_TYPE})`),
20
+ byCode: any('按模板编码统计的发送数,键为模板编码'),
21
+ recentTrend: any('每日趋势,每项为 { date, count }'),
22
+ recentTrendByType: any('每日按类型趋势,每项为 { date, type, count }'),
23
+ hourlyTrend: any('按小时趋势,每项为 { date, hour, count }'),
24
+ hourlyTrendByType: any('按小时、类型趋势,每项为 { date, hour, type, count }'),
25
+ templateStats: obj('模板统计(不受时间范围筛选)', {
26
+ total: num('模板总数'),
27
+ byStatus: any('按状态统计的模板数,键为状态(0 启用,1 禁用)'),
28
+ byType: any(`按类型统计的模板数,键为类型(${MESSAGE_TYPE})`)
29
+ })
30
+ }
31
+ };
32
+
33
+ module.exports = { str, num, any, obj, success, MESSAGE_TYPE, statistics };
@@ -19,7 +19,7 @@ module.exports = fp(async (fastify, options) => {
19
19
  const statisticsServices = fastify[`${options.name}Statistics`]?.services;
20
20
  if (!statisticsServices) return;
21
21
 
22
- const data = Object.assign({}, { total }, success > 0 ? { success } : {}, failed > 0 ? { failed } : {});
22
+ const data = Object.assign({}, total > 0 ? { total } : {}, success > 0 ? { success } : {}, failed > 0 ? { failed } : {});
23
23
  const unit = { total: 'count', success: 'count', failed: 'count' };
24
24
  const time = new Date();
25
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kne/fastify-message",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "管理消息发送,支持sms消息,email消息等.",
5
5
  "main": "index.js",
6
6
  "scripts": {