@kne/fastify-message 1.0.2 → 1.0.4
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/index.js +11 -2
- package/libs/controllers/main.js +187 -12
- package/libs/services/main.js +75 -48
- package/package.json +10 -3
package/index.js
CHANGED
|
@@ -13,7 +13,15 @@ module.exports = fp(async function (fastify, options) {
|
|
|
13
13
|
}
|
|
14
14
|
return fastify.account.models.user;
|
|
15
15
|
},
|
|
16
|
-
senders: {}
|
|
16
|
+
senders: {},
|
|
17
|
+
getAuthenticate: (type) => {
|
|
18
|
+
switch (type) {
|
|
19
|
+
case 'record':
|
|
20
|
+
case 'template':
|
|
21
|
+
default:
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
17
25
|
},
|
|
18
26
|
options
|
|
19
27
|
);
|
|
@@ -29,7 +37,8 @@ module.exports = fp(async function (fastify, options) {
|
|
|
29
37
|
getUserModel: options.getUserModel
|
|
30
38
|
})
|
|
31
39
|
],
|
|
32
|
-
['services', path.resolve(__dirname, './libs/services')]
|
|
40
|
+
['services', path.resolve(__dirname, './libs/services')],
|
|
41
|
+
['controllers', path.resolve(__dirname, './libs/controllers')]
|
|
33
42
|
]
|
|
34
43
|
});
|
|
35
44
|
|
package/libs/controllers/main.js
CHANGED
|
@@ -1,24 +1,148 @@
|
|
|
1
1
|
const fp = require('fastify-plugin');
|
|
2
2
|
|
|
3
3
|
module.exports = fp(async (fastify, options) => {
|
|
4
|
-
const { services } = fastify.
|
|
4
|
+
const { services } = fastify[options.name];
|
|
5
|
+
|
|
6
|
+
// 查看发送记录列表
|
|
7
|
+
fastify.get(
|
|
8
|
+
`${options.prefix}/records`,
|
|
9
|
+
{
|
|
10
|
+
onRequest: options.getAuthenticate('record'),
|
|
11
|
+
schema: {
|
|
12
|
+
description: '获取消息发送记录列表',
|
|
13
|
+
summary: '查看发送记录',
|
|
14
|
+
querystring: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
currentPage: { type: 'integer', default: 1, description: '页码' },
|
|
18
|
+
perPage: { type: 'integer', default: 20, description: '每页数量' },
|
|
19
|
+
'filter[type]': { type: 'integer', description: '发送类型: 0=邮件, 1=短信' },
|
|
20
|
+
'filter[code]': { type: 'string', description: '模板编码' },
|
|
21
|
+
'filter[name]': { type: 'string', description: '发送对象(邮箱/手机号)' }
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
response: {
|
|
25
|
+
200: {
|
|
26
|
+
type: 'object',
|
|
27
|
+
properties: {
|
|
28
|
+
totalCount: { type: 'integer', description: '总数量' },
|
|
29
|
+
currentPage: { type: 'integer', description: '当前页码' },
|
|
30
|
+
perPage: { type: 'integer', description: '每页数量' },
|
|
31
|
+
pageData: {
|
|
32
|
+
type: 'array',
|
|
33
|
+
items: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
id: { type: 'string', description: '记录ID' },
|
|
37
|
+
name: { type: 'string', description: '发送对象' },
|
|
38
|
+
type: { type: 'integer', description: '发送类型' },
|
|
39
|
+
code: { type: 'string', description: '模板编码' },
|
|
40
|
+
props: { type: 'object', description: '模板变量' },
|
|
41
|
+
content: { type: 'object', description: '消息内容' },
|
|
42
|
+
templateId: { type: 'string', description: '模板ID' },
|
|
43
|
+
createdAt: { type: 'string', description: '创建时间' }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
async request => {
|
|
53
|
+
const { currentPage = 1, perPage = 20 } = request.query;
|
|
54
|
+
const { 'filter[type]': type, 'filter[code]': code, 'filter[name]': name } = request.query;
|
|
55
|
+
|
|
56
|
+
const filter = {};
|
|
57
|
+
if (type !== undefined) filter.type = type;
|
|
58
|
+
if (code) filter.code = code;
|
|
59
|
+
if (name) filter.name = name;
|
|
60
|
+
|
|
61
|
+
return await services.record.list({ filter, perPage, currentPage });
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// 查看单条发送记录
|
|
66
|
+
fastify.get(
|
|
67
|
+
`${options.prefix}/records/:id`,
|
|
68
|
+
{
|
|
69
|
+
onRequest: options.getAuthenticate('record'),
|
|
70
|
+
schema: {
|
|
71
|
+
description: '获取单条消息发送记录详情',
|
|
72
|
+
summary: '查看发送记录详情',
|
|
73
|
+
params: {
|
|
74
|
+
type: 'object',
|
|
75
|
+
properties: {
|
|
76
|
+
id: { type: 'string', description: '记录ID' }
|
|
77
|
+
},
|
|
78
|
+
required: ['id']
|
|
79
|
+
},
|
|
80
|
+
response: {
|
|
81
|
+
200: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {
|
|
84
|
+
id: { type: 'string', description: '记录ID' },
|
|
85
|
+
name: { type: 'string', description: '发送对象' },
|
|
86
|
+
type: { type: 'integer', description: '发送类型' },
|
|
87
|
+
code: { type: 'string', description: '模板编码' },
|
|
88
|
+
props: { type: 'object', description: '模板变量' },
|
|
89
|
+
content: { type: 'object', description: '消息内容' },
|
|
90
|
+
templateId: { type: 'string', description: '模板ID' },
|
|
91
|
+
createdAt: { type: 'string', description: '创建时间' }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
async request => {
|
|
98
|
+
const { id } = request.params;
|
|
99
|
+
try {
|
|
100
|
+
return await services.record.detail({ id });
|
|
101
|
+
} catch (error) {
|
|
102
|
+
throw fastify.httpErrors.notFound(error.message);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// 查看消息模版列表
|
|
5
108
|
fastify.get(
|
|
6
|
-
`${options.prefix}/
|
|
109
|
+
`${options.prefix}/templates`,
|
|
7
110
|
{
|
|
8
|
-
onRequest:
|
|
111
|
+
onRequest: options.getAuthenticate('template'),
|
|
9
112
|
schema: {
|
|
10
|
-
description: '
|
|
11
|
-
summary: '
|
|
12
|
-
|
|
113
|
+
description: '获取消息模版列表',
|
|
114
|
+
summary: '查看消息模版',
|
|
115
|
+
querystring: {
|
|
116
|
+
type: 'object',
|
|
117
|
+
properties: {
|
|
118
|
+
currentPage: { type: 'integer', default: 1, description: '页码' },
|
|
119
|
+
perPage: { type: 'integer', default: 20, description: '每页数量' },
|
|
120
|
+
'filter[type]': { type: 'integer', description: '模版类型: 0=邮件, 1=短信' },
|
|
121
|
+
'filter[code]': { type: 'string', description: '模版编码' },
|
|
122
|
+
'filter[level]': { type: 'integer', description: '模版级别: 0=系统, 1=业务' },
|
|
123
|
+
'filter[status]': { type: 'integer', description: '状态: 0=启用, 1=禁用' }
|
|
124
|
+
}
|
|
125
|
+
},
|
|
13
126
|
response: {
|
|
14
127
|
200: {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
'
|
|
18
|
-
|
|
128
|
+
type: 'object',
|
|
129
|
+
properties: {
|
|
130
|
+
totalCount: { type: 'integer', description: '总数量' },
|
|
131
|
+
currentPage: { type: 'integer', description: '当前页码' },
|
|
132
|
+
perPage: { type: 'integer', description: '每页数量' },
|
|
133
|
+
pageData: {
|
|
134
|
+
type: 'array',
|
|
135
|
+
items: {
|
|
19
136
|
type: 'object',
|
|
20
137
|
properties: {
|
|
21
|
-
|
|
138
|
+
id: { type: 'string', description: '模版ID' },
|
|
139
|
+
name: { type: 'string', description: '模版名称' },
|
|
140
|
+
code: { type: 'string', description: '模版编码' },
|
|
141
|
+
type: { type: 'integer', description: '模版类型' },
|
|
142
|
+
level: { type: 'integer', description: '模版级别' },
|
|
143
|
+
status: { type: 'integer', description: '状态' },
|
|
144
|
+
content: { type: 'string', description: '模版内容' },
|
|
145
|
+
createdAt: { type: 'string', description: '创建时间' }
|
|
22
146
|
}
|
|
23
147
|
}
|
|
24
148
|
}
|
|
@@ -28,7 +152,58 @@ module.exports = fp(async (fastify, options) => {
|
|
|
28
152
|
}
|
|
29
153
|
},
|
|
30
154
|
async request => {
|
|
31
|
-
|
|
155
|
+
const { currentPage = 1, perPage = 20 } = request.query;
|
|
156
|
+
const { 'filter[type]': type, 'filter[code]': code, 'filter[level]': level, 'filter[status]': status } = request.query;
|
|
157
|
+
|
|
158
|
+
const filter = {};
|
|
159
|
+
if (type !== undefined) filter.type = type;
|
|
160
|
+
if (code) filter.code = code;
|
|
161
|
+
if (level !== undefined) filter.level = level;
|
|
162
|
+
if (status !== undefined) filter.status = status;
|
|
163
|
+
|
|
164
|
+
return await services.template.list({ filter, perPage, currentPage });
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// 查看单个消息模版
|
|
169
|
+
fastify.get(
|
|
170
|
+
`${options.prefix}/templates/:id`,
|
|
171
|
+
{
|
|
172
|
+
onRequest: options.getAuthenticate('template'),
|
|
173
|
+
schema: {
|
|
174
|
+
description: '获取单个消息模版详情',
|
|
175
|
+
summary: '查看消息模版详情',
|
|
176
|
+
params: {
|
|
177
|
+
type: 'object',
|
|
178
|
+
properties: {
|
|
179
|
+
id: { type: 'string', description: '模版ID' }
|
|
180
|
+
},
|
|
181
|
+
required: ['id']
|
|
182
|
+
},
|
|
183
|
+
response: {
|
|
184
|
+
200: {
|
|
185
|
+
type: 'object',
|
|
186
|
+
properties: {
|
|
187
|
+
id: { type: 'string', description: '模版ID' },
|
|
188
|
+
name: { type: 'string', description: '模版名称' },
|
|
189
|
+
code: { type: 'string', description: '模版编码' },
|
|
190
|
+
type: { type: 'integer', description: '模版类型' },
|
|
191
|
+
level: { type: 'integer', description: '模版级别' },
|
|
192
|
+
status: { type: 'integer', description: '状态' },
|
|
193
|
+
content: { type: 'string', description: '模版内容' },
|
|
194
|
+
createdAt: { type: 'string', description: '创建时间' }
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
async request => {
|
|
201
|
+
const { id } = request.params;
|
|
202
|
+
try {
|
|
203
|
+
return await services.template.detail({ id });
|
|
204
|
+
} catch (error) {
|
|
205
|
+
throw fastify.httpErrors.notFound(error.message);
|
|
206
|
+
}
|
|
32
207
|
}
|
|
33
208
|
);
|
|
34
209
|
});
|
package/libs/services/main.js
CHANGED
|
@@ -8,14 +8,9 @@ const { convert } = require('html-to-text');
|
|
|
8
8
|
const pify = require('pify');
|
|
9
9
|
|
|
10
10
|
module.exports = fp(async (fastify, options) => {
|
|
11
|
-
const emailConfig = Object.assign(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
port: 465,
|
|
15
|
-
secure: true
|
|
16
|
-
},
|
|
17
|
-
options.emailConfig
|
|
18
|
-
);
|
|
11
|
+
const emailConfig = Object.assign({}, {
|
|
12
|
+
port: 465, secure: true
|
|
13
|
+
}, options.emailConfig);
|
|
19
14
|
const isTest = options.isTest;
|
|
20
15
|
const { models, services } = fastify[options.name];
|
|
21
16
|
const includeTemplate = async dir => {
|
|
@@ -30,14 +25,10 @@ module.exports = fp(async (fastify, options) => {
|
|
|
30
25
|
const content = await fs.readFile(filePath, 'utf8');
|
|
31
26
|
const filename = file.replace(path.extname(file), '');
|
|
32
27
|
const tempArray = filename.split('_');
|
|
33
|
-
const code = tempArray[0],
|
|
34
|
-
type = tempArray[1] || 0,
|
|
35
|
-
name = tempArray[2] || code;
|
|
28
|
+
const code = tempArray[0], type = tempArray[1] || 0, name = tempArray[2] || code;
|
|
36
29
|
const codeTemplate = await models.template.findOne({
|
|
37
30
|
where: {
|
|
38
|
-
code,
|
|
39
|
-
type,
|
|
40
|
-
level: 0
|
|
31
|
+
code, type, level: 0
|
|
41
32
|
}
|
|
42
33
|
});
|
|
43
34
|
|
|
@@ -49,11 +40,7 @@ module.exports = fp(async (fastify, options) => {
|
|
|
49
40
|
continue;
|
|
50
41
|
}
|
|
51
42
|
await models.template.create({
|
|
52
|
-
code,
|
|
53
|
-
type,
|
|
54
|
-
name,
|
|
55
|
-
content,
|
|
56
|
-
level: 0
|
|
43
|
+
code, type, name, content, level: 0
|
|
57
44
|
});
|
|
58
45
|
console.log(`create template: ${code}`);
|
|
59
46
|
}
|
|
@@ -76,9 +63,7 @@ module.exports = fp(async (fastify, options) => {
|
|
|
76
63
|
const messageTemplate = async ({ code, type = 0, level = 0, props }) => {
|
|
77
64
|
const codeTemplate = await models.template.findOne({
|
|
78
65
|
where: {
|
|
79
|
-
code,
|
|
80
|
-
type,
|
|
81
|
-
level
|
|
66
|
+
code, type, level
|
|
82
67
|
}
|
|
83
68
|
});
|
|
84
69
|
if (!codeTemplate) {
|
|
@@ -88,11 +73,7 @@ module.exports = fp(async (fastify, options) => {
|
|
|
88
73
|
const content = parseTemplate(template(codeTemplate.content)(props).split(/<!--(\s*)-->/g));
|
|
89
74
|
|
|
90
75
|
return {
|
|
91
|
-
content,
|
|
92
|
-
props,
|
|
93
|
-
code,
|
|
94
|
-
type,
|
|
95
|
-
templateId: codeTemplate.id
|
|
76
|
+
content, props, code, type, templateId: codeTemplate.id
|
|
96
77
|
};
|
|
97
78
|
};
|
|
98
79
|
|
|
@@ -100,22 +81,8 @@ module.exports = fp(async (fastify, options) => {
|
|
|
100
81
|
targetOptions = Object.assign({}, targetOptions);
|
|
101
82
|
const { content, templateId } = await messageTemplate({ code, type, level, props });
|
|
102
83
|
const sendOptions = await (async () => {
|
|
84
|
+
const currentSender = options.senders?.[type];
|
|
103
85
|
if (type === 0) {
|
|
104
|
-
const currentClient = merge(
|
|
105
|
-
{},
|
|
106
|
-
{
|
|
107
|
-
host: emailConfig.host,
|
|
108
|
-
port: emailConfig.port,
|
|
109
|
-
secure: emailConfig.secure,
|
|
110
|
-
auth: {
|
|
111
|
-
user: emailConfig.user,
|
|
112
|
-
pass: emailConfig.pass
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
client
|
|
116
|
-
);
|
|
117
|
-
const smtp = nodemailer.createTransport(currentClient);
|
|
118
|
-
|
|
119
86
|
const mailOptions = {
|
|
120
87
|
...targetOptions,
|
|
121
88
|
from: `"${targetOptions.title || emailConfig.user}" <${emailConfig.user}>`,
|
|
@@ -127,11 +94,20 @@ module.exports = fp(async (fastify, options) => {
|
|
|
127
94
|
};
|
|
128
95
|
|
|
129
96
|
if (!isTest) {
|
|
130
|
-
|
|
97
|
+
if (typeof currentSender === 'function') {
|
|
98
|
+
await currentSender(mailOptions);
|
|
99
|
+
} else {
|
|
100
|
+
const currentClient = merge({}, {
|
|
101
|
+
host: emailConfig.host, port: emailConfig.port, secure: emailConfig.secure, auth: {
|
|
102
|
+
user: emailConfig.user, pass: emailConfig.pass
|
|
103
|
+
}
|
|
104
|
+
}, client);
|
|
105
|
+
const smtp = nodemailer.createTransport(currentClient);
|
|
106
|
+
await pify(smtp.sendMail.bind(smtp))(mailOptions);
|
|
107
|
+
}
|
|
131
108
|
}
|
|
132
109
|
return mailOptions;
|
|
133
110
|
}
|
|
134
|
-
const currentSender = options.senders?.[type];
|
|
135
111
|
if (typeof currentSender === 'function') {
|
|
136
112
|
const { content, templateId } = await messageTemplate({ code, type, level, props });
|
|
137
113
|
if (!isTest) {
|
|
@@ -145,9 +121,60 @@ module.exports = fp(async (fastify, options) => {
|
|
|
145
121
|
};
|
|
146
122
|
|
|
147
123
|
Object.assign(fastify[options.name].services, {
|
|
148
|
-
includeTemplate,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
124
|
+
includeTemplate, messageTemplate, parseTemplate, sendMessage,
|
|
125
|
+
|
|
126
|
+
// 发送记录相关服务
|
|
127
|
+
record: {
|
|
128
|
+
list: async ({ filter = {}, perPage = 20, currentPage = 1 }) => {
|
|
129
|
+
const { count, rows } = await models.record.findAndCountAll({
|
|
130
|
+
where: filter,
|
|
131
|
+
limit: perPage,
|
|
132
|
+
offset: (currentPage - 1) * perPage,
|
|
133
|
+
order: [['createdAt', 'DESC']]
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
pageData: rows,
|
|
138
|
+
totalCount: count,
|
|
139
|
+
perPage,
|
|
140
|
+
currentPage
|
|
141
|
+
};
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
detail: async ({ id }) => {
|
|
145
|
+
const record = await models.record.findByPk(id);
|
|
146
|
+
if (!record) {
|
|
147
|
+
throw new Error('记录不存在');
|
|
148
|
+
}
|
|
149
|
+
return record;
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
// 消息模版相关服务
|
|
154
|
+
template: {
|
|
155
|
+
list: async ({ filter = {}, perPage = 20, currentPage = 1 }) => {
|
|
156
|
+
const { count, rows } = await models.template.findAndCountAll({
|
|
157
|
+
where: filter,
|
|
158
|
+
limit: perPage,
|
|
159
|
+
offset: (currentPage - 1) * perPage,
|
|
160
|
+
order: [['createdAt', 'DESC']]
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
pageData: rows,
|
|
165
|
+
totalCount: count,
|
|
166
|
+
perPage,
|
|
167
|
+
currentPage
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
detail: async ({ id }) => {
|
|
172
|
+
const template = await models.template.findByPk(id);
|
|
173
|
+
if (!template) {
|
|
174
|
+
throw new Error('模版不存在');
|
|
175
|
+
}
|
|
176
|
+
return template;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
152
179
|
});
|
|
153
180
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kne/fastify-message",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "管理消息发送,支持sms消息,email消息等.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
"build:md": "npx @kne/md-doc",
|
|
11
11
|
"start:md": "npx @kne/md-doc --watch",
|
|
12
12
|
"prettier": "prettier --config .prettierrc --write '{libs/**/*,index}.{js,jsx,ts,tsx,json,css,scss}'",
|
|
13
|
-
"lint-staged": "npx lint-staged"
|
|
13
|
+
"lint-staged": "npx lint-staged",
|
|
14
|
+
"test": "mocha --exit test/**/*.test.js",
|
|
15
|
+
"test:coverage": "nyc --reporter=html --reporter=text mocha test/**/*.test.js --timeout 10000"
|
|
14
16
|
},
|
|
15
17
|
"lint-staged": {
|
|
16
18
|
"**/*.md": [
|
|
@@ -43,12 +45,17 @@
|
|
|
43
45
|
},
|
|
44
46
|
"devDependencies": {
|
|
45
47
|
"@fastify/env": "^4.4.0",
|
|
48
|
+
"@fastify/sensible": "^6.0.4",
|
|
46
49
|
"@kne/fastify-sequelize": "^2.0.1",
|
|
50
|
+
"chai": "^6.2.2",
|
|
47
51
|
"fastify": "^5.3.2",
|
|
48
52
|
"fastify-plugin": "^5.0.1",
|
|
49
53
|
"husky": "^9.0.11",
|
|
54
|
+
"mocha": "^11.7.5",
|
|
50
55
|
"nodemon": "^3.1.3",
|
|
51
|
-
"
|
|
56
|
+
"nyc": "^17.1.0",
|
|
57
|
+
"prettier": "^3.2.5",
|
|
58
|
+
"sqlite3": "^6.0.1"
|
|
52
59
|
},
|
|
53
60
|
"dependencies": {
|
|
54
61
|
"fs-extra": "^11.3.0",
|