Compare commits

...

216 Commits

Author SHA1 Message Date
aixianling
456e55bb2e build(Dockerfile): 设置时区为亚洲/上海
- 在 Dockerfile 中添加环境变量 TZ=Asia/Shanghai
- 这个改动将使容器运行时使用北京时间
2025-01-08 16:33:47 +08:00
aixianling
8586b76ef2 feat(node): 添加自定义节点配置的 webhook 字段
- 在 node_custom_config 表单中新增 webhook 字段
- 通过 REST API 接口 /node/custom/addOrUpdate 添加或更新自定义节点配置时,支持 webhook 参数
2025-01-08 15:41:30 +08:00
aixianling
3767f1938a feat(webhook): 动态获取 webhook key
- 修改了 webhook通知功能,使其能够动态获取 key
- 在发送请求时,使用 info.webhook 作为 key 的值,如果为空则使用默认值
2025-01-07 10:26:42 +08:00
aixianling
99331d5b8d build(docker): 设置容器时区为亚洲上海
- 在 docker-compose.yml 文件中添加 TZ 环境变量
- 设置 TZ 为 Asia/Shanghai,以确保容器内时区与北京时间一致
2024-12-20 17:03:47 +08:00
aixianling
9fe354f832 build: 更新 Jenkins 服务器地址 2024-12-16 16:10:43 +08:00
aixianling
9d7e2136cc Support both web and wxwork types in getZip.js 2024-10-31 12:17:31 +08:00
aixianling
eeb5c0a511 重新构建干净的镜像 2024-10-21 14:41:15 +08:00
aixianling
8c0a797dc8 尝试用yum代入相关能力 2024-10-21 14:24:20 +08:00
aixianling
c8f7c169cc 修复异常 2024-10-21 14:15:15 +08:00
aixianling
cf6c8fdb45 调整镜像,使其支持apt包管理 2024-10-21 10:46:07 +08:00
aixianling
51ab4b62bc 调整镜像,使其支持bash脚本运行 2024-10-21 10:04:59 +08:00
aixianling
58c24d705f 清除单向的expect引入 2024-10-18 14:07:04 +08:00
aixianling
bf0a0b4e6a 引入expect库到容器中 2024-10-18 12:18:39 +08:00
aixianling
a748c2575b 引入expect库到容器中 2024-10-18 12:08:09 +08:00
aixianling
8851f2703a 更换容器运行后,调整所对应的文件路径 2024-10-18 11:21:46 +08:00
aixianling
c63a56d2c8 修正下获取构建id的异常问题 2024-10-18 10:55:03 +08:00
aixianling
47af53443e 修正下获取构建id的异常问题 2024-10-18 10:49:41 +08:00
aixianling
3d5bce566e 后续收尾 2024-10-18 10:34:07 +08:00
aixianling
bd8c7ebecb 修复监听构建任务的id获取异常问题 2024-10-18 10:24:59 +08:00
aixianling
c0a849cb9f 处理传值异常 2024-10-18 10:16:02 +08:00
aixianling
cc34a5cdea 提前建设默认路径 2024-10-18 09:38:24 +08:00
aixianling
e394e4d2ba 默认路径没有获取到 2024-10-18 09:28:54 +08:00
aixianling
6532b66240 新版集成jenkins的打包方案完成 2024-10-17 18:12:12 +08:00
aixianling
91ca77ffa8 测试一下集成脚本 2024-10-16 16:36:50 +08:00
aixianling
56021924ef 将错误日志保存到文件当中 2024-06-17 17:39:08 +08:00
aixianling
89987aeea1 数据库调整回oms 2023-12-19 11:22:01 +08:00
aixianling
39de61cede 企微考试提交 2023-12-18 15:18:37 +08:00
aixianling
6a782d4cda 企微考试提交 2023-12-15 21:54:06 +08:00
aixianling
50e8e24e2c 补充缺失的库 2023-12-14 15:02:01 +08:00
aixianling
064ed0a1d7 修改docker compose 版本 2023-12-14 14:51:48 +08:00
aixianling
30e58518fd 修改node版本 2023-12-12 11:20:08 +08:00
aixianling
d04491f17e 修改指定源 2023-12-12 11:18:21 +08:00
aixianling
3ea8420747 修改路径避免无权限 2023-12-12 09:37:56 +08:00
aixianling
458710b856 oms提供docker构建方式 2023-12-12 09:33:44 +08:00
aixianling
158c474183 oms提供docker构建方式 2023-12-12 09:32:44 +08:00
aixianling
3578b3bf3b oms提供docker构建方式 2023-12-12 09:23:23 +08:00
aixianling
a27cb95188 Merge branch 'gitee' into dev 2023-12-01 09:52:15 +08:00
aixianling
76ed5c0028 应对企微认证考试,写了node版的企微对接 2023-12-01 09:51:03 +08:00
aixianling
cad79f917a 调整初始化流程 2023-06-13 08:56:25 +08:00
aixianling
f517b48ad4 调整生产配置 2023-06-12 16:24:26 +08:00
aixianling
ff1f5ce6fa 调整pm2配置 2023-06-12 13:46:29 +08:00
aixianling
857825af4a forever 替换成pm2 2023-06-09 17:45:10 +08:00
aixianling
adc58b3e0d 修复保存是,会将null转为字符串存入 2023-03-15 11:02:23 +08:00
aixianling
9b947b64dc 清洗数据 2023-03-02 10:43:19 +08:00
aixianling
0917405f1a 还原处理 2023-03-02 10:24:36 +08:00
aixianling
664e909920 还原处理 2023-03-02 10:24:22 +08:00
aixianling
6b9a777653 还原处理 2023-03-02 10:08:20 +08:00
aixianling
c221ce8965 处理JSON对象异常 2023-03-01 18:10:08 +08:00
aixianling
ebcfff03cc 处理JSON.对象异常 2023-03-01 17:54:57 +08:00
aixianling
3e1991eaaa 处理JSON.对象异常 2023-03-01 17:52:04 +08:00
aixianling
f192d85b1b 处理JSON.对象异常 2023-03-01 17:39:17 +08:00
aixianling
2e30774aeb Merge remote-tracking branch 'origin/dev' into dev 2023-03-01 17:28:07 +08:00
aixianling
7c103f01a9 处理JSON.对象异常 2023-03-01 17:28:00 +08:00
dc33689db4 Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	src/websocket/custom/getZip.js
2023-02-17 11:40:09 +08:00
768982f6e3 调整ws返回格式 2023-02-17 11:38:30 +08:00
aixianling
ddbf988698 修复打包 2023-02-16 17:05:35 +08:00
2e5a162292 重新适配 2023-02-16 12:31:47 +08:00
7693e2fda1 重新适配 2023-02-16 12:17:18 +08:00
c168a1da0c 重新适配 2023-02-16 12:01:46 +08:00
aef22a3b14 调整成功返回属性 2023-02-16 10:39:41 +08:00
518efac58a 重新调整websocket的配置 2023-02-16 10:34:05 +08:00
aixianling
1bc5e3b9c4 增加express的服务端的异常捕获 2023-02-13 11:25:10 +08:00
aixianling
428fb054d5 增加express的服务端的异常捕获 2023-02-13 11:22:40 +08:00
aixianling
cd7f0f7158 node服务body最大值调整为50mb 2023-02-13 11:06:03 +08:00
aixianling
430d383b8d 调整低代码平台生成文件的路径 2023-02-13 10:42:25 +08:00
aixianling
8b727839fa 修复数据统计异常 2023-02-13 10:40:26 +08:00
aixianling
0a928d8231 调整接口 2023-02-10 19:33:57 +08:00
aixianling
46287ac2f5 前端接口监控统计完成 2023-02-10 16:07:37 +08:00
aixianling
4568fcd985 增加接口记录接口 2023-02-09 09:59:53 +08:00
aixianling
88e2a1f47a 调整列表工具类 2023-02-09 09:30:20 +08:00
aixianling
7acebbc35b 完善代码生成 2023-02-02 17:19:49 +08:00
aixianling
d988440bca sql语句修复 2023-02-02 15:03:50 +08:00
aixianling
0dd870859e sql语句修复 2023-02-02 15:03:37 +08:00
aixianling
c9020de8ec sql语句修复 2023-01-31 10:05:02 +08:00
aixianling
0f58ea80fa 优化排列顺序 2023-01-31 09:58:01 +08:00
aixianling
e7fd86b1e2 继续可持续性的竭泽而渔 2023-01-19 10:38:53 +08:00
aixianling
7bcc5c3de8 追加跟踪细粒度 2023-01-19 10:03:50 +08:00
aixianling
8f374c1221 追加跟踪细粒度 2023-01-19 09:54:27 +08:00
aixianling
e8a065beac 追加跟踪细粒度 2023-01-18 14:33:09 +08:00
aixianling
ae8bf19957 追加跟踪细粒度 2023-01-18 14:25:00 +08:00
aixianling
02001e78c2 增加杀死进程的操作 2023-01-18 14:06:31 +08:00
aixianling
766faee96e 优化打包方法 2023-01-18 12:08:51 +08:00
aixianling
de3549d82e 增加小程序发布的删除方法 2023-01-11 09:41:18 +08:00
aixianling
b5e1864c6a 增加异常抛出冗余 2022-12-30 09:53:49 +08:00
aixianling
dd764d704a 增加异常抛出冗余 2022-12-30 09:47:18 +08:00
aixianling
3639b88417 修复打包路径 2022-12-28 16:30:11 +08:00
aixianling
ce8a594870 修复打包路径 2022-12-28 16:27:37 +08:00
aixianling
4988586397 优化下载打包 2022-12-28 16:20:18 +08:00
aixianling
236ff91eaf 优化下载打包 2022-12-28 16:05:27 +08:00
aixianling
c7596d08da 消息机器人完成 2022-12-28 11:45:13 +08:00
aixianling
7e5c1b91d5 优化下载方法 2022-12-28 10:21:05 +08:00
aixianling
efb85bc936 Merge remote-tracking branch 'origin/dev' into dev 2022-12-26 09:37:31 +08:00
aixianling
b0d28d6332 修复表异常 2022-12-26 09:37:24 +08:00
root
bb0d7334e6 更新脚本权限 2022-12-26 09:28:36 +08:00
aixianling
26b6c7b1e6 自我更新脚本 2022-12-26 09:26:52 +08:00
aixianling
0d6f3e522d 修复表异常 2022-12-26 09:22:50 +08:00
aixianling
dfc4b7a84b 新版小程序打包 2022-12-23 18:02:51 +08:00
root
16b37d68c0 赋予文件操作权限 2022-12-23 14:44:49 +08:00
aixianling
3facfeb1fa 修复压缩脚本 2022-12-23 14:41:54 +08:00
aixianling
c48682825f 调整打包路径 2022-12-23 10:38:52 +08:00
6ae2164d52 打包脚本集中管理 2022-12-21 08:43:58 +08:00
aixianling
1f1cac75f1 扩展数据库字段长度 2022-11-14 14:41:56 +08:00
aixianling
3acf79d55e 补充小程序最后一环自动构建 2022-11-03 09:03:23 +08:00
aixianling
588518b8be 兼容异常 2022-08-23 14:02:01 +08:00
aixianling
ce6cb52bbf 兼容异常 2022-08-19 10:07:18 +08:00
aixianling
b94455ed76 兼容异常 2022-07-18 15:03:41 +08:00
aixianling
bf60b7f257 兼容异常 2022-07-18 15:01:28 +08:00
aixianling
679857473a 底部导航栏全部可以更换 2022-07-15 18:33:09 +08:00
aixianling
1302f1caa2 定制方案全新升级 2022-07-14 18:33:34 +08:00
aixianling
bfc3b49ce9 定制方案全新升级 2022-07-14 18:23:41 +08:00
aixianling
cd6af33167 定制方案全新升级 2022-07-14 18:23:00 +08:00
aixianling
e45c04f2da 定制方案全新升级 2022-07-14 18:22:27 +08:00
aixianling
6de1b4daca 定制方案全新升级 2022-07-14 18:21:38 +08:00
aixianling
3b68bc31a0 定制方案全新升级 2022-07-14 18:19:15 +08:00
aixianling
96854369a1 定制方案全新升级 2022-07-14 18:01:25 +08:00
aixianling
21102cdfe2 修复异常 2022-07-14 11:51:29 +08:00
aixianling
7a22c233fe 定制项目打包接口完成 2022-07-14 10:50:44 +08:00
aixianling
f800b249ca 修复异常 2022-07-07 18:31:14 +08:00
aixianling
6e18858abf 定制项目打包接口完成 2022-07-07 18:03:32 +08:00
aixianling
1308471a68 定制项目打包接口完成 2022-07-07 17:58:18 +08:00
aixianling
f0d998c71a 定制项目打包接口完成 2022-07-07 17:51:17 +08:00
aixianling
33e2c78d90 定制项目打包接口完成 2022-07-07 17:47:49 +08:00
aixianling
091deb59ef 定制项目打包接口完成 2022-07-07 17:43:04 +08:00
aixianling
7dd22f51fe 详情增加应用库列表 2022-07-04 17:50:01 +08:00
aixianling
43c59ec34e 增加关于标准库的筛选 2022-07-04 14:43:22 +08:00
aixianling
d553307ea8 提交一波定制项目 2022-07-04 14:17:59 +08:00
aixianling
c60a7362e7 修复接口 2022-07-04 10:02:41 +08:00
aixianling
3334a68f06 增加详情接口 2022-07-04 10:01:33 +08:00
aixianling
5b437886eb 封装增删改查 2022-07-04 09:47:51 +08:00
aixianling
716b80eedd 整合列表工具类 2022-07-02 15:16:30 +08:00
aixianling
e86b8b2f46 整合列表工具类 2022-07-02 15:15:05 +08:00
aixianling
90ab258629 更正模板 2022-07-01 18:12:01 +08:00
aixianling
3ee1dae426 更正模板 2022-07-01 17:51:19 +08:00
aixianling
9b634abb44 更正模板 2022-07-01 17:47:04 +08:00
aixianling
211ab357e4 增加指定npm脚本参数 2022-06-30 16:00:50 +08:00
aixianling
5e16f0ca9f 增加指定npm脚本参数 2022-06-30 15:53:10 +08:00
aixianling
6263a27aba 增加指定npm脚本参数 2022-06-30 15:21:02 +08:00
aixianling
00cc2d9e4f 修复相同appid不同版本数据混淆问题 2022-06-30 14:08:49 +08:00
aixianling
8b19051578 修复相同appid不同版本数据混淆问题 2022-06-30 13:39:29 +08:00
aixianling
11fbc716a0 修复相同appid不同版本数据混淆问题 2022-06-30 11:48:43 +08:00
aixianling
e07aef9832 修复相同appid不同版本数据混淆问题 2022-06-30 10:13:47 +08:00
aixianling
569fdcabe7 修复BUG 2022-06-24 10:09:26 +08:00
aixianling
eaaf373137 修复BUG 2022-06-24 09:38:45 +08:00
aixianling
fdc3bb0393 增加取消打包功能 2022-06-23 10:11:09 +08:00
aixianling
4a0528991e 增加node多版本兼容打包 2022-06-23 09:43:25 +08:00
aixianling
a0585e0914 增加node多版本兼容打包 2022-06-23 09:42:52 +08:00
aixianling
35019b0d0f 增加node多版本兼容打包 2022-06-23 09:35:44 +08:00
aixianling
8452180cfb 调整低代码生成路径 2022-06-22 17:15:43 +08:00
aixianling
9a2fac9e6f 低代码平台完成 2022-06-22 16:55:31 +08:00
aixianling
6a8fa6135a Merge remote-tracking branch 'origin/dev' into dev 2022-06-20 18:31:14 +08:00
aixianling
8bf02b4270 低代码平台 2022-06-20 18:31:06 +08:00
刘仕伟
4885bd9c27 修复异常 2022-06-19 22:15:54 +08:00
aixianling
ce2503cad0 优化脚本 2022-06-17 18:12:31 +08:00
aixianling
9c9e249f56 优化脚本 2022-06-17 18:04:44 +08:00
aixianling
11e1a726ae root=>/home/deploy 2022-06-17 17:46:30 +08:00
aixianling
bc76415d6f 更新下载脚本 2022-06-17 15:22:21 +08:00
aixianling
4fcdee39ed 排查异常 2022-06-17 15:00:34 +08:00
aixianling
e7c01a176c 返回最终日志 2022-06-17 14:57:59 +08:00
aixianling
c347c6706f 返回最终日志 2022-06-17 14:56:11 +08:00
aixianling
f81e1abdfe 返回最终日志 2022-06-17 14:48:38 +08:00
aixianling
00e1fc04de 迁移打包路径到/home/deploy 2022-06-17 14:24:47 +08:00
aixianling
087d34071f 优化异步进程对异常的捕获 2022-06-17 14:13:01 +08:00
aixianling
18e4065487 优化异步进程对异常的捕获 2022-06-17 13:55:48 +08:00
aixianling
50d0e9088c 优化日志打印 2022-06-17 13:49:52 +08:00
aixianling
fc2dfa1d2d 增加计算打包时间功能 2022-06-17 11:28:43 +08:00
aixianling
a57d6741ad 增加计算打包时间功能 2022-06-17 11:06:52 +08:00
aixianling
29701488e0 优化打包下载 2022-06-10 15:17:07 +08:00
aixianling
d813628d61 优化打包下载 2022-06-10 15:06:08 +08:00
aixianling
c48436301f 优化打包下载 2022-06-10 12:14:33 +08:00
aixianling
f48d8924da 优化打包下载 2022-06-10 12:08:33 +08:00
aixianling
bf23769076 优化打包下载 2022-06-10 12:07:06 +08:00
aixianling
d39134a71e 优化打包下载 2022-06-10 12:05:23 +08:00
aixianling
bc79fa48be 优化打包下载 2022-06-10 12:00:22 +08:00
aixianling
95663704d1 优化打包下载 2022-06-10 11:48:19 +08:00
aixianling
b677a3fe9f 同步产品库内容 2022-06-08 16:28:34 +08:00
aixianling
ac883222ca 同步产品库内容 2022-06-08 16:17:13 +08:00
aixianling
7afb62b16a 同步产品库内容 2022-06-08 16:06:32 +08:00
aixianling
ef1e2a0089 智能匹配增加appid和上传版本 2022-06-01 10:56:54 +08:00
aixianling
d3faa0c037 增加同一个小程序不同项目的兼容 2022-06-01 10:51:24 +08:00
aixianling
dd329d6e60 优化打包服务 2022-05-27 18:10:45 +08:00
aixianling
785157a65a 优化打包服务 2022-05-27 17:29:38 +08:00
aixianling
45c1a4f043 修复产品库接口 2022-05-27 15:11:55 +08:00
aixianling
64f0cf5708 优化代码 2022-05-27 09:54:10 +08:00
aixianling
be0732bbd1 精简代码 2022-05-26 18:51:49 +08:00
aixianling
bc2062ec76 精简代码 2022-05-26 18:36:13 +08:00
aixianling
12e878b041 精简代码 2022-05-26 18:32:07 +08:00
aixianling
c33e62cdeb 精简代码 2022-05-26 18:29:26 +08:00
aixianling
c45f0b6faf 更新部署服务 2022-05-26 18:17:25 +08:00
aixianling
81be840647 更新部署服务 2022-05-26 18:16:12 +08:00
aixianling
06a9b238e7 更新部署服务 2022-05-26 18:13:54 +08:00
aixianling
54c631f208 Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	src/rest/wechat/getZip.js
2022-05-24 09:30:55 +08:00
aixianling
9e9afb9c35 优化打包流程 2022-05-24 09:29:38 +08:00
1123a84d3a 刷新打包工程的app库 2022-05-20 23:14:55 +08:00
aixianling
585e6deb89 修复更新 2022-05-20 14:04:13 +08:00
aixianling
a68b911d39 修复筛选条件 2022-05-20 10:09:36 +08:00
aixianling
1b887f6837 修复筛选条件 2022-05-20 09:54:40 +08:00
aixianling
530eecaed7 增设埋点 2022-05-20 09:31:52 +08:00
aixianling
704811e1d9 修复下载接口 2022-05-19 11:17:53 +08:00
aixianling
b0724affcc 添加下载接口 2022-05-19 11:14:58 +08:00
aixianling
cbba345806 添加下载接口 2022-05-19 11:09:22 +08:00
aixianling
7c21a10862 增加筛选条件 2022-05-19 10:43:38 +08:00
aixianling
bb94243ec5 小程序发布 2022-05-18 18:23:04 +08:00
aixianling
93be499640 更新应用服务 2022-05-16 10:15:52 +08:00
aixianling
a49d799afe 更新应用服务 2022-05-16 10:08:20 +08:00
aixianling
c7f56d7958 增加了小程序产品库,应用模块管理 2022-05-12 10:40:23 +08:00
aixianling
ec7adbbe71 时间优化 2022-04-01 18:36:09 +08:00
aixianling
d8ba3fbb83 打包失败优化 2022-04-01 17:28:24 +08:00
aixianling
d7b3b041bb 打包失败优化 2022-04-01 17:01:56 +08:00
aixianling
1f0bf4cf73 修复下载无法使用问题 2022-04-01 16:48:42 +08:00
aixianling
948cc94338 更新服务 2022-04-01 16:05:56 +08:00
aixianling
94ce411071 打包业务 2022-03-31 18:25:48 +08:00
aixianling
82f09d89c8 增加了筛选条件 2022-03-31 15:26:22 +08:00
aixianling
5391d46b6f 修复更新 2022-03-31 14:05:54 +08:00
aixianling
7a71fd963a 增加forever模组用于开发热更新和部署服务进程 2022-03-31 09:33:50 +08:00
aixianling
f56c30780a 增加部署发布模块 2022-03-30 19:19:56 +08:00
aixianling
d48be5b91f node简易服务器完成 2022-03-29 15:04:07 +08:00
66 changed files with 2006 additions and 13 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,5 @@
/node_modules/
/package-lock.json
/zips/
/aicode/
/logs/

12
Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
FROM node:16-slim
LABEL authors="kubbo"
COPY . /app
WORKDIR /app
RUN npm i --registry=http://registry.npmmirror.com
EXPOSE 12525
ENV TZ=Asia/Shanghai
CMD ["node", "index.js"]

11
demo.js
View File

@@ -1,11 +0,0 @@
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})

12
docker-compose.yml Normal file
View File

@@ -0,0 +1,12 @@
version: "3"
services:
oms:
build:
context: .
container_name: "oms-service"
ports:
- "12525:12525"
environment:
- NODE_ENV
- TZ=Asia/Shanghai

24
index.js Normal file
View File

@@ -0,0 +1,24 @@
const express = require('express')
const xmlparser = require('express-xml-bodyparser');
require("express-async-errors")
const db = require('./src/utils/dbUitls')
const rest = require('./src/rest')
const ws = require('./src/websocket')
const ews = require('express-ws')
const chalk = require("chalk");
const log = console.log
const app = express();
ews(app)
const port = 12525
chalk.level = 1
app.listen(port, () => {
db.init()
app.use(express.json()) // for parsing application/json
app.use(xmlparser()); // for parsing application/xml
app.use(express.urlencoded({extended: true, limit: '50mb'})) // for parsing application/x-www-form-urlencoded
//启动服务并监听
Promise.all([rest.init(app)]).then(() => {
log(`${chalk.bgGreen.black(" DONE ")} serve is listening on ${port}`)
ws.init(app)
})
})

View File

@@ -4,7 +4,10 @@
"description": "node服务端",
"main": "index.js",
"scripts": {
"test": "node demo.js"
"dev": "nodemon index.js",
"pro": "pm2 restart pm2.config.js --only oms-node",
"deploy": "pm2 deploy pm2.config.js production",
"stop": "pm2 delete all"
},
"repository": {
"type": "git",
@@ -14,8 +17,28 @@
"node",
"js"
],
"engines": {
"node": "16.13.1"
},
"author": "kubbo",
"dependencies": {
"express": "^4.17.3"
"@wecom/crypto": "^1.0.1",
"axios": "^1.2.1",
"dayjs": "^1.11.0",
"express": "^4.17.3",
"express-async-errors": "^3.1.1",
"express-ws": "^5.0.2",
"express-xml-bodyparser": "^0.3.0",
"fast-csv": "^4.3.6",
"form-data": "^4.0.0",
"fs-extra": "^10.0.1",
"helmet": "^5.0.2",
"jsonwebtoken": "^9.0.2",
"mysql": "^2.18.1",
"uuid": "^8.3.2",
"xml2js": "^0.6.2"
},
"devDependencies": {
"chalk": "^4.1.2"
}
}

26
pm2.config.js Normal file
View File

@@ -0,0 +1,26 @@
module.exports = {
apps: [
{
name: 'dev',
script: "index.js",
watch: ["src", "tpl"],
ignore_watch: ["node_modules"],
shutdown_with_message: true,
},
{
name: 'oms-node',
script: "index.js",
watch: false
}
],
deploy: {
production: {
user: 'root',
host: '192.168.1.87',
ref: 'origin/dev',
repo: 'http://git.sinoecare.com/lab/dvcp-node-service.git',
path: '/home/deploy',
postDeploy: "npm run pro"
}
}
}

3
shell/fetch.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
./shell/update.sh oms-node服务 .
npm run pro

22
shell/move.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/bin/bash
Type=$1
Prj=$2
Deploy=$3
case "$Type" in
web)
cp -r $Prj/dist/* $Deploy
;;
wxwork)
cp -r $Prj/dist/build/h5/* $Deploy
;;
mp)
cp -r $Prj/dist/build/mp-weixin/* $Deploy
;;
*)
echo $"Usage: $0 (web|wxwork|mp)"
exit 1
esac
exit $?
exit $RETVAL

13
shell/update.sh Executable file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/expect
set Name [lindex $argv 0]
set Prj [lindex $argv 1]
puts "更新$Name"
cd $Prj
spawn git pull
expect "Username"
send "aixianling\r"
expect "Password"
send "axl123.0\r"
interact
cd ..

23
src/config/auth.js Normal file
View File

@@ -0,0 +1,23 @@
const auth = require("jsonwebtoken");
module.exports = {
//key wxtest token
secret: "ddb64c99f29d310c",
verifyToken(req, res, next) {
const token = req.headers['authorization'];
if (!token) {
return res.status(401).json({message: 'No token provided'});
}
auth.verify(token, this.secret, (err, decoded) => {
if (err) {
return res.status(403).json({message: 'Failed to authenticate token'});
}
req.userId = decoded.userId;
next();
});
},
generateToken(userId) {
return auth.sign({userId}, this.secret, {expiresIn: '24h'});
}
}

8
src/config/db.js Normal file
View File

@@ -0,0 +1,8 @@
module.exports = {
host: "192.168.1.87",
user: "root",
port: 3306,
password: "Cwy@2019",
database: "dvcp_oms_dev",
multipleStatements: true
}

View File

@@ -0,0 +1,20 @@
const {getSignature, decrypt} = require("@wecom/crypto");
const {addOrUpdate} = require("../../utils/dbUitls");
const token = "OTUlglGGoGm7EKKpKeg6tc"
const encodingAESKey = "LHLwd2nhbia4iCEfyDJkUPBb9TT7G8GMlWpgqzNHODi"
module.exports = {
action: "/wxwork/addressBook/action",
method: "post",
execute: (request, response) => {
const {msg_signature, timestamp, nonce, echostr} = request.query
const signature = getSignature(token, timestamp, nonce, echostr);
if (msg_signature == signature) {
const context = decrypt(encodingAESKey, echostr)
const {name, mobile: phone, email, userId} = context
addOrUpdate({
table: "sys_user", form: {name, email, phone, userId}
})
response.send({code: 0, message: "success"})
}
}
}

View File

@@ -0,0 +1,15 @@
const {getSignature, decrypt} = require("@wecom/crypto");
const token = "OTUlglGGoGm7EKKpKeg6tc"
const encodingAESKey = "LHLwd2nhbia4iCEfyDJkUPBb9TT7G8GMlWpgqzNHODi"
module.exports = {
action: "/wxwork/addressBook/action",
method: "get",
execute: (request, response) => {
const {msg_signature, timestamp, nonce, Encrypt} = request.query
const signature = getSignature(token, timestamp, nonce, Encrypt);
if (msg_signature == signature) {
const context = decrypt(encodingAESKey, Encrypt)
response.send(context)
}
}
}

View File

@@ -0,0 +1,48 @@
const dbUtils = require("../../utils/dbUitls");
const fs = require('fs');
const FormData = require('form-data');
const fastcsv = require('fast-csv');
const axios = require("axios");
//生成csv并上传至企微后台获取 media_id
const uploadCsv = (data = [], access_token) => {
// data数据格式
// {"name": "张三", "email": "john.doe@example.com",phone:"13388888888"},
const csv = data.map(e => {
return {
"姓名": e.name,
"账号": e.phone,
"手机号": e.phone,
"邮箱": e.email,
}
})
const form = new FormData()
const csvStream = fastcsv.format({headers: true})
csvStream.pipe(fs.createWriteStream('tmp.csv'))
data.forEach(item => csvStream.write(item))
csvStream.end()
form.append('media', fs.createReadStream('tmp.csv'))
return axios.post(`https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=${access_token}&type=file`, {
headers: form.getHeaders()
}).then(res => {
const {media_id} = res.data
return media_id
})
}
module.exports = {
action: "/wxwork/addressBook/syncUser",
method: "post",
execute: (request, response) => {
const {access_token} = request.body
// 获取原CA账号数据
dbUtils.list({
table: "sys_user",
}).then(data => uploadCsv(data, access_token))
.then(media_id => {
// 上传csv至企微后台
return axios.post(`https://qyapi.weixin.qq.com/cgi-bin/batch/replaceuser?access_token=${access_token}`, {
media_id
})
})
}
}

31
src/rest/aicode/add.js Normal file
View File

@@ -0,0 +1,31 @@
const dbUtils = require("../../utils/dbUitls");
const {v4: uuid} = require('uuid');
module.exports = {
action: "/node/aicode/addOrUpdate",
method: "post",
execute: (request, response) => {
let form = request.body, sql
if (form.id) {//编辑
let arr = Object.keys(form).filter(e => form[e]).map(e => {
if (typeof form[e] == "object") form[e] = JSON.stringify(form[e])
return `${e}='${form[e]}'`
})
sql = `update node_aicode set ${arr.join(",")} where id='${form.id}'`
} else {//新增
let cols = [], arr = []
Object.keys(form).map(e => {
if (form[e]) {
cols.push(e)
if (typeof form[e] == "object") form[e] = JSON.stringify(form[e])
arr.push(`'${form[e]}'`)
}
})
sql = `insert into node_aicode (id,${cols.join(",")}) values('${uuid()}',${arr.join(",")})`
}
dbUtils.query(sql).then(() => {
response.send({code: 0})
}).catch(err => {
response.send({code: 1, err: err.sqlMessage})
})
}
}

14
src/rest/aicode/delete.js Normal file
View File

@@ -0,0 +1,14 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/aicode/delete",
method: "post",
execute: (request, response) => {
let {query: {ids}} = request
ids = ids?.split(",")?.map(e => `'${e}'`)?.toString()
dbUtils.query(`delete from node_aicode where id in (${ids})`).then(() => {
response.send({code: 0})
}).catch(err => {
response.send({code: 1, err: err.sqlMessage})
})
}
}

28
src/rest/aicode/detail.js Normal file
View File

@@ -0,0 +1,28 @@
const dbUtils = require("../../utils/dbUitls");
const {checkJson} = require("../../tools");
module.exports = {
action: "/node/aicode/detail",
method: "post",
execute: (request, response) => {
let {id} = request.query
let condition = `where id='${id}'`
let sql = `select * from node_aicode ${condition} limit 0,1`
dbUtils.query(sql).then(res => res?.[0]).then(data => {
for (const k in data) {
if (checkJson(data[k])) {
data[k] = JSON.parse(data[k])
} else if (["props"].includes(k)) {
//关于json对象的处理
data[k] = data[k] || []
} else if (["btns"].includes(k)) {
//关于数组的处理
data[k] = data[k]?.split(",") || []
}
}
response.send({code: 0, data})
}).catch(err => {
response.send({code: 1, err: err.sqlMessage})
})
}
}

View File

@@ -0,0 +1,34 @@
const dbUtils = require("../../utils/dbUitls");
const fse = require("fs-extra");
const execute = require("../../tools/exec");
const generate = require("../../tools/generate");
module.exports = {
action: "/node/aicode/getCode",
method: "post",
execute: (request, response) => {
let id = request.query?.id, sql = `select * from node_aicode where id='${id}'`
dbUtils.query(sql).then(res => {
let info = res?.[0]
if (info?.id) {
let path = `./aicode/${info.id}`, zipPath = `${path}/${info.id}.zip`
generate(info, path).then(() => {
fse.pathExists(path, (err, exists) => {
console.log(`${path}=========>${exists}`)
if (exists) {
execute(`cd ${path}&&zip -r ${info.id}.zip .`)
.then(() => {
console.log('压缩完成!')
setTimeout(() => {
response.download(zipPath)
}, 1000)
})
} else response.send({code: 1, err: "没有打包文件!"})
})
})
} else response.send({code: 1, err: "无法找到应用信息"})
}).catch(err => {
console.log(err)
response.send({code: 1, err: err.sqlMessage})
})
}
}

28
src/rest/aicode/list.js Normal file
View File

@@ -0,0 +1,28 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/aicode/list",
method: "post",
execute: (request, response) => {
let total = 0, records = [], {size, current = 1, name = ""} = request.query
let condition = `where (name like '%${name}%' or appName like '%${name}%')`
Promise.all([
dbUtils.query(`select 1 from node_aicode ${condition}`).then(res => {
return total = res.length
}),
new Promise(resolve => {
let sql = `select * from node_aicode ${condition} limit ${(current-1)*size||0},${size||1}`
dbUtils.query(sql).then(res => {
records = res
resolve()
}).catch(err => {
response.send({code: 1, err: err.sqlMessage})
})
})
]).then(() => {
response.send({
code: 0,
data: {records, total}
})
})
}
}

View File

@@ -0,0 +1,27 @@
const dbUtils = require("../../utils/dbUitls");
const {v4: uuid} = require('uuid');
module.exports = {
action: "/node/autodeploy/addOrUpdate",
method: "post",
execute: (request, response) => {
let form = request.body, sql
if (form.id) {//编辑
let arr = Object.keys(form).filter(e => form[e]).map(e => `${e}='${form[e]}'`)
sql = `update node_autodeploy set ${arr.join(",")} where id='${form.id}'`
} else {//新增
let cols = [], arr = []
Object.keys(form).map(e => {
if (form[e]) {
cols.push(e)
arr.push(`'${form[e]}'`)
}
})
sql = `insert into node_autodeploy (id,${cols.join(",")}) values('${uuid()}',${arr.join(",")})`
}
dbUtils.query(sql).then(() => {
response.send({code: 0})
}).catch(err => {
response.send({code: 1, err: err.sqlMessage})
})
}
}

View File

@@ -0,0 +1,14 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/autodeploy/cancelZip",
method: "post",
execute: (request, response) => {
let id = request.query?.id, sql = `update node_autodeploy set zipTime=null where id='${id}'`
dbUtils.query(sql).then(() => {
response.send({code: 0, data: "取消成功!"})
}).catch(err => {
console.log(err)
response.send({code: 1, err: err.sqlMessage})
})
}
}

View File

@@ -0,0 +1,17 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/autodeploy/confirmZip",
method: "post",
execute: (request, response) => {
let id = request.query?.id, sql = `select * from node_autodeploy where id='${id}'`
dbUtils.query(sql).then(res => {
let info = res?.[0]
if (info?.id) {
response.send({code: 0, data: info})
} else response.send({code: 1, err: "无法找到git信息"})
}).catch(err => {
console.log(err)
response.send({code: 1, err: err.sqlMessage})
})
}
}

View File

@@ -0,0 +1,14 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/autodeploy/delete",
method: "post",
execute: (request, response) => {
let {query: {ids}} = request
ids = ids?.split(",")?.map(e => `'${e}'`)?.toString()
dbUtils.query(`delete from node_autodeploy where id in (${ids})`).then(() => {
response.send({code: 0})
}).catch(err => {
response.send({code: 1, err: err.sqlMessage})
})
}
}

View File

@@ -0,0 +1,33 @@
const dbUtils = require("../../utils/dbUitls");
const fse = require("fs-extra");
const execute = require("../../tools/exec");
module.exports = {
action: "/node/autodeploy/download",
method: "post",
execute: (request, response) => {
let id = request.query?.id, sql = `select * from node_autodeploy where id='${id}'`
dbUtils.query(sql).then(res => {
let info = res?.[0]
if (info?.id) {
let path = `${info.target}`, zipPath = `/zips/${info.id}.zip`
fse.removeSync(zipPath)
fse.pathExists(path, (err, exists) => {
console.log(`${path}=========>${exists}`)
if (exists) {
execute(`cd ${path}&&zip -r ${info.id}.zip .`)
.then(() => execute(`cd ${path}&&mv ${info.id}.zip /zips`))
.then(() => {
console.log('压缩完成!')
setTimeout(() => {
response.download(zipPath)
}, 1000)
})
} else response.send({code: 1, err: "没有打包文件!"})
})
} else response.send({code: 1, err: "无法找到git信息"})
}).catch(err => {
console.log(err)
response.send({code: 1, err: err.sqlMessage})
})
}
}

View File

@@ -0,0 +1,38 @@
const db = require("../../utils/dbUitls");
const dayjs = require("dayjs")
const execute = require("../../tools/exec");
const fse = require("fs-extra");
module.exports = {
action: "/node/autodeploy/getZip",
method: "post",
execute: (request, response) => {
let id = request.query?.id, sql = `select * from node_autodeploy where id='${id}'`
db.query(sql).then(res => {
let info = res?.[0]
if (info?.id) {
db.query(`update node_autodeploy set download=null,error=null,zipTime='${dayjs().format("YYYY-MM-DD HH:mm:ss")}' where id='${info.id}'`).then(() => {
fse.emptydir(info.target, () => {
response.send({code: 0})
})
}).then(() => {
if (!!info.type) {
let {type, zipPath, target, name, nodeVersion} = info,
updateCMD = `/root/node-service/update.sh ${name} ${zipPath}`,
buildCMD = `/root/node-service/build.sh ${type} ${zipPath} ${target} ${nodeVersion || '16.14.0'}`
return execute(updateCMD).then(() => execute(buildCMD))
} else {
return execute(`${info.updateShell}`).then(() => execute(`${info.libShell}`))
}
}).then(() => {
db.query(`update node_autodeploy set download='${dayjs().format("YYYY-MM-DD HH:mm:ss")}',error='' where id='${info.id}'`)
}).catch(err => {
console.log(err)
db.query(`update node_autodeploy set error='${err}',zipTime=null where id='${info.id}'`)
})
} else response.send({code: 1, err: "无法找到部署工程"})
}).catch(err => {
console.log(err)
response.send({code: 1, err: err.sqlMessage})
})
}
}

View File

@@ -0,0 +1,28 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/autodeploy/list",
method: "post",
execute: (request, response) => {
let total = 0, records = [], {size, current, name} = request.query
Promise.all([
dbUtils.query(`select 1 from node_autodeploy where name like '%${name}%'`).then(res => {
return total = res.length
}),
new Promise(resolve => {
let sql = `select * from node_autodeploy where name like '%${name}%' limit ${(current-1)*size},${size}`
dbUtils.query(sql).then(res => {
records = res
resolve()
}).catch(err => {
response.send({code: 1, err: err.sqlMessage})
})
})
]).then(() => {
response.send({
code: 0,
data: {records, total}
})
})
}
}

16
src/rest/custom/add.js Normal file
View File

@@ -0,0 +1,16 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/custom/addOrUpdate",
method: "post",
execute: (request, response) => {
let {id, name, type, customPath, apps, createTime, dist, version, zipTime, download, error, extra,webhook} = request.body
dbUtils.addOrUpdate({
table: 'node_custom_config',
form: {id, name, type, customPath, apps, createTime, dist, version, zipTime, download, error, extra,webhook}
}).then(data => {
response.send({code: 0, data})
}).catch(err => {
response.send({code: 1, err: err?.sqlMessage || err || ""})
})
}
}

View File

@@ -0,0 +1,14 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/custom/cancelZip",
method: "post",
execute: (request, response) => {
let id = request.query?.id, sql = `update node_custom_config set zipTime=null where id='${id}'`
dbUtils.query(sql).then(() => {
response.send({code: 0, data: "取消成功!"})
}).catch(err => {
console.log(err)
response.send({code: 1, err: err.sqlMessage})
})
}
}

13
src/rest/custom/delete.js Normal file
View File

@@ -0,0 +1,13 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/custom/delete",
method: "post",
execute: (request, response) => {
const {ids} = request.query
dbUtils.delete({table: 'node_custom_config', ids}).then(data => {
response.send({code: 0, data})
}).catch(err => {
response.send({code: 1, err: err?.sqlMessage || err || ""})
})
}
}

28
src/rest/custom/detail.js Normal file
View File

@@ -0,0 +1,28 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/custom/detail",
method: "post",
execute: (request, response) => {
let {id} = request.query
dbUtils.detail({table: 'node_custom_config', id}).then(data => {
data.apps = JSON.parse(data.apps || "[]")
data.extra = JSON.parse(data.extra.replace(/\n/g,'') || null)
Promise.all([
dbUtils.query(`select * from node_wechat_apps where id in (${data.apps?.map(e=>`'${e}'`)?.toString()||202207181503})`)
.then(res => data.appList = [data.appList, res].flat()),
dbUtils.query(`select * from node_wechat_apps where type='${data.type}' and id like '%project_${data.customPath}%'`)
.then(res => data.appList = [data.appList, res].flat())
]).then(() => {
data.appList = data.appList.filter(Boolean).map(e => {
if (data.type == 'mp') {
e.tabbar = data.extra?.tabBar.list?.map(c => c.id)?.includes(e.id)
}
return e
})
response.send({code: 0, data})
})
}).catch(err => {
response.send({code: 1, err: err?.sqlMessage || err || ""})
})
}
}

View File

@@ -0,0 +1,32 @@
const dbUtils = require("../../utils/dbUitls");
const fse = require("fs-extra");
const execute = require("../../tools/exec");
module.exports = {
action: "/node/custom/download",
method: "post",
execute: (request, response) => {
let id = request.query?.id
dbUtils.detail({table: 'node_custom_config', id}).then(info => {
if (info?.id) {
let path = `${info.dist || `/zips/${info.name}v${info.version}`}`, zipPath = `/zips/${info.id}.zip`
fse.removeSync(zipPath)
fse.pathExists(path, (err, exists) => {
console.log(`${path}=========>${exists}`)
if (exists) {
execute(`cd ${path}&&zip -r ${info.id}.zip .`)
.then(() => execute(`cd ${path}&&mv ${info.id}.zip /zips/`))
.then(() => {
console.log('压缩完成!')
setTimeout(() => {
response.download(zipPath)
}, 1000)
})
} else response.send({code: 1, err: "没有打包文件!"})
})
} else response.send({code: 1, err: "无法找到项目信息"})
}).catch(err => {
console.log(err)
response.send({code: 1, err: err.sqlMessage})
})
}
}

74
src/rest/custom/getZip.js Normal file
View File

@@ -0,0 +1,74 @@
const db = require("../../utils/dbUitls");
const execute = require("../../tools/exec")
const dayjs = require("dayjs")
const fse = require("fs-extra");
const axios = require('axios')
module.exports = {
action: "/node/custom/getZip",
method: "post",
execute: (request, response) => {
let {id} = request.query, uniCon = `id='${id}'`
db.query(`select *
from node_custom_config
where id = '${id}'`).then(res => {
let info = res?.[0] || {}, sql
if (info.id) {
sql = `update node_custom_config
set download=null,
error=null,
zipTime='${dayjs().format("YYYY-MM-DD HH:mm:ss")}'
where ${uniCon}`
if (info.type == 'web') {
const {name, version, dist = `${name}_v${version}`} = info
const buildConfig = {
web: {task: "devops-web", token: 'fLeOGSVIRs405Me'},
}[info.type]
db.query(sql).then(() => axios.get(`https://jenkins.sinoecare.com/view/devops/job/${buildConfig.task}/buildWithParameters`,null,{
params:{
token: buildConfig.token,
pid: id, dist
}
}))
.then(() => db.query(`update node_custom_config
set download='${dayjs().format("YYYY-MM-DD HH:mm:ss")}',
error=''
where ${uniCon}`))
.then(() => response.send({code: 0, msg: "打包任务已发送"}))
.catch(err => {
console.log(err)
return fse.outputFile(`./logs/errors/${dayjs().format("YYYY-MM-DD")}.log`, err)
})
} else {
const buildPath = {
web: 'base-web',
wxwork: 'base-wxcp',
mp: 'dvcp_v2_wxmp'
}[info.type] || {}
let path = `../${buildPath}`,
{dist = `../zips/${info.name}v${info.version || "1.0.0"}`} = info
Promise.all([
execute(`./shell/update.sh ${info.name} ${path}`),
db.query(sql)
]).then(() => setTimeout(() => {
response.send({code: 0})
}, 1000))
execute(`cd ${path}&&npm run apps&&node bin/pages.js ${id}&&npm run build`)
.then(() => fse.emptyDir(dist))
.then(() => execute(`./shell/move.sh ${info.type} ${path} ${dist}`))
.then(() => {
return db.query(`update node_custom_config
set download='${dayjs().format("YYYY-MM-DD HH:mm:ss")}',
error=''
where ${uniCon}`)
}).catch(err => {
console.log(err)
return fse.outputFile(`./logs/errors/${dayjs().format("YYYY-MM-DD")}.log`, err)
})
}
} else return response.send({code: 1, err: "无法找到定制项目信息"})
}).catch(err => {
console.log(err)
response.send({code: 1, err: err.sqlMessage})
})
}
}

16
src/rest/custom/list.js Normal file
View File

@@ -0,0 +1,16 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/custom/list",
method: "post",
execute: (request, response) => {
let {size, current, name, type} = request.query
dbUtils.list({
table: 'node_custom_config',
search: {size, current, type, name}, sort: 'download'
}).then(data => {
response.send({code: 0, data})
}).catch(err => {
response.send({code: 1, err: err?.sqlMessage || err || ""})
})
}
}

View File

@@ -0,0 +1,26 @@
const dbUtils = require("../../utils/dbUitls");
const axios = require("axios");
module.exports = {
action: "/node/custom/webhook",
method: "post",
execute: (request, response) => {
let id = request.query?.id
dbUtils.detail({table: 'node_custom_config', id}).then(info => {
if (info?.id) {
axios.post("https://qyapi.weixin.qq.com/cgi-bin/webhook/send", {
msgtype: "markdown",
markdown: {
content: `> 发布系统:${info.name}
> 发布版本:${info.version}
> 打包完成时间:${info.download}`
}
}, {params: {key: info.webhook || "a5971027-2dd3-4c23-a4e4-c99a962d25a7",}}).then(res => {
response.send({code: 0, msg: res.data})
})
} else response.send({code: 1, err: "无法找到项目信息"})
}).catch(err => {
console.log(err)
response.send({code: 1, err: err.sqlMessage})
})
}
}

18
src/rest/index.js Normal file
View File

@@ -0,0 +1,18 @@
const {findFile} = require("../utils/fsUtils");
const chalk = require("chalk");
const log = console.log
module.exports = {
init: ins => {
return findFile('./src/rest', file => {
if (!/index\.js/.test(file)) {
let rest = require(file.replace(/src[\\\/]rest/, '.'))
log(`${chalk.bgBlue.black(" REST ")} ${rest.action}`)
if (rest.method == "post") {
ins.post(rest.action, (req, res) => rest.execute(req, res))
} else if (rest.method == "get") {
ins.get(rest.action, (req, res) => rest.execute(req, res))
}
}
})
}
}

View File

@@ -0,0 +1,32 @@
const dbUtils = require("../../utils/dbUitls");
const addLog = ({id, status, path, device, url, createTime, nodeProcess, method, code, userName, error}) => dbUtils.addOrUpdate({
table: 'node_api_logs',
form: {id, status, path, device, url, createTime, nodeProcess, method, code, userName, error}
})
module.exports = {
action: "/node/monitorApi/addOrUpdate",
method: "post",
execute: (request, response) => {
dbUtils.batchInsert({
table: 'node_api_logs',
list: [request.body].flat().map(({id, status, path, device, url, createTime, nodeProcess, method, code, userName, error}) => ({
id,
status,
path,
device,
url,
createTime,
nodeProcess,
method,
code,
userName,
error
}))
}).then(data => {
response.send({code: 0, data})
}).catch(err => {
response.send({code: 1, err: err?.sqlMessage || err || ""})
})
}
}

View File

@@ -0,0 +1,16 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/monitorApi/list",
method: "post",
execute: (request, response) => {
let {size, current, name: path, status} = request.query
dbUtils.list({
table: 'node_api_logs', con: 'path',
search: {size, current, status, path}, sort: 'createTime'
}).then(data => {
response.send({code: 0, data})
}).catch(err => {
response.send({code: 1, err: err?.sqlMessage || err || ""})
})
}
}

View File

@@ -0,0 +1,17 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/monitorApi/sta",
method: "post",
execute: (request, response) => {
const data = {}
Promise.all([
dbUtils.query(`select path,method,SUM(1) as total,createTime FROM node_api_logs where status not like '200' GROUP BY path ORDER BY total DESC limit 0,10`).then(res => data.top10 = res),
dbUtils.query(`select status as name,SUM(1) as value FROM node_api_logs where status not like '200' GROUP BY status`).then(res => data.distribution = res)
]).then(() => {
response.send({code: 0, data})
}).catch(err => {
response.send({code: 1, err: err?.sqlMessage || err || ""})
})
}
}

View File

@@ -0,0 +1,13 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/sys/user",
method: "post",
execute: (request, response) => {
dbUtils.query(`select * from sys_user`).then(res => {
response.send({
code: 0,
data: res
})
})
}
}

16
src/rest/wechat/add.js Normal file
View File

@@ -0,0 +1,16 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/wxmp/addOrUpdate",
method: "post",
execute: (request, response) => {
let {appid, privateKey, projectPath, version, versionName, id, npmScript} = request.body,
form = {appid, privateKey, projectPath, version, versionName, id, npmScript}
dbUtils.addOrUpdate({
table: 'node_wxmp_deploy', form
}).then(data => {
response.send({code: 0, data})
}).catch(err => {
response.send({code: 1, err: err?.sqlMessage || err || ""})
})
}
}

View File

@@ -0,0 +1,17 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/wxmp/confirmZip",
method: "post",
execute: (request, response) => {
let {id} = request.query, sql = `select * from node_wxmp_deploy where id='${id}'`
dbUtils.query(sql).then(res => {
let info = res?.[0]
if (info?.appid) {
response.send({code: 0, data: info})
} else response.send({code: 1, err: "无法找到小程序信息"})
}).catch(err => {
console.log(err)
response.send({code: 1, err: err.sqlMessage})
})
}
}

13
src/rest/wechat/delete.js Normal file
View File

@@ -0,0 +1,13 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/wxmp/delete",
method: "post",
execute: (request, response) => {
const {ids} = request.query
dbUtils.delete({table: 'node_wxmp_deploy', ids}).then(data => {
response.send({code: 0, data})
}).catch(err => {
response.send({code: 1, err: err?.sqlMessage || err || ""})
})
}
}

16
src/rest/wechat/detail.js Normal file
View File

@@ -0,0 +1,16 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/wxmp/detail",
method: "post",
execute: (request, response) => {
let {id} = request.query
dbUtils.query(`select * from node_wxmp_deploy where id='${id}' limit 0,1`).then(res => {
response.send({
code: 0,
data: res?.[0]
})
}).catch(err => {
response.send({code: 1, err: err.sqlMessage})
})
}
}

View File

@@ -0,0 +1,26 @@
const fse = require("fs-extra");
const execute = require("../../tools/exec");
module.exports = {
action: "/node/wxmp/download",
method: "post",
execute: (request, response) => {
let {id} = request.query
if (id) {
let path = `/zips/${id}/`, zipPath = `/zips/${id}.zip`
fse.removeSync(zipPath)
fse.pathExists(path, (err, exists) => {
console.log(`${path}=========>${exists}`)
if (exists) {
execute(`cd ${path}&&zip -r ${id}.zip .`)
.then(() => execute(`cd ${path}&&mv ${id}.zip /zips`))
.then(() => {
console.log('压缩完成!')
setTimeout(() => {
response.download(zipPath)
}, 1000)
})
} else response.send({code: 1, err: "没有打包文件!"})
})
} else response.send({code: 1, err: "无法找到小程序信息"})
}
}

39
src/rest/wechat/getZip.js Normal file
View File

@@ -0,0 +1,39 @@
const db = require("../../utils/dbUitls");
const execute = require("../../tools/exec")
const dayjs = require("dayjs")
const fse = require("fs-extra");
const isDev = process.env.OS == 'Windows_NT'
module.exports = {
action: "/node/wxmp/getZip",
method: "post",
execute: (request, response) => {
const {id, appid} = request.query
db.query(`select * from node_wxmp_deploy where id='${id}'`).then(res => {
let info = res?.[0], sql
if (info?.id) {
sql = `update node_wxmp_deploy set error=null where id='${id}'`
} else return response.send({code: 1, err: "无法找到小程序信息"})
db.query(sql).then(() => setTimeout(() => {
response.send({code: 0})
}, 2000))
const path = info.projectPath || (isDev ? 'E:\\code\\cunwei\\dvcp_v2_wechat' : '/dvcp_v2_wechat'),
dest = (isDev ? `E:\\wxmpZips\\${id}` : `/zips/${id}/`),
processEnv = info.npmScript || 'build',
dist = info.npmScript ? `${path}/dist/${info.npmScript}/` : `${path}/dist/build/mp-weixin/`
execute(`cd ${path}&&npm run apps&&node bin/pages.js ${appid} ${id}&&npm run ${processEnv}`)
.then(() => fse.emptyDir(dest))
.then(() => fse.ensureDir(dist))
.then(() => fse.copy(dist, dest))
.then(() => fse.emptyDir(dist))
.then(() => {
db.query(`update node_wxmp_deploy set error='打包时间:${dayjs().format("YYYY-MM-DD HH:mm:ss")}' where id='${id}'`)
}).catch(err => {
console.log(err)
db.query(`update node_wxmp_deploy set error='${err}' where id='${id}'`)
})
}).catch(err => {
console.log(err)
response.send({code: 1, err: err.sqlMessage})
})
}
}

16
src/rest/wechat/list.js Normal file
View File

@@ -0,0 +1,16 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/wxmp/list",
method: "post",
execute: (request, response) => {
const {size, current, name, type} = request.query
dbUtils.list({
table: 'node_wxmp_deploy',
search: {size, current, type, name}
}).then(data => {
response.send({code: 0, data})
}).catch(err => {
response.send({code: 1, err: err?.sqlMessage || err || ""})
})
}
}

21
src/rest/wechat/sync.js Normal file
View File

@@ -0,0 +1,21 @@
const dbUtils = require("../../utils/dbUitls");
uid = e => [e.appid, e.corpId].join("_")
module.exports = {
action: "/node/wxmp/sync",
method: "post",
execute: (request, response) => {
let records = {}, meta = []
Promise.all([
dbUtils.query("select * from app_dvcp_config").then(res => meta = res),
dbUtils.query("select * from node_wxmp_deploy").then(res => res?.map(e => records[uid(e)] = e))
]).then(() => Promise.all(meta.map(e => {
const {name, miniapp_appid: appid, corp_id: corpId, web_url: webUrl} = e
const id = records[uid({appid, corpId})]?.id
delete e.system_info
return dbUtils.addOrUpdate({
table: 'node_wxmp_deploy',
form: {id, name, appid, corpId, webUrl, dvcpConfig: e}
})
}))).finally(() => response.send({code: 0, data: ""}))
}
}

View File

@@ -0,0 +1,42 @@
const dbUtils = require("../../utils/dbUitls");
const addOrUpdate = form => {
let cols = [], arr = []
Object.keys(form).map(e => {
if (form[e]) {
cols.push(e)
arr.push(`'${form[e]}'`)
}
})
return Promise.resolve(`insert into node_wechat_apps (${cols.join(",")}) values(${arr.join(",")})`)
}
module.exports = {
action: "/node/wechatapps/addOrUpdate",
method: "post",
execute: (request, response) => {
let form = request.body
if (form.type) {
dbUtils.query(`delete from node_wechat_apps where type='${form.type}'`).then(() => {
if (form.list?.length > 0) {
Promise.all(form.list.map(e => addOrUpdate(e).then(sql => dbUtils.query(sql)))).then(() => {
response.send({code: 0})
}).catch(err => {
response.send({code: 1, err: err.sqlMessage})
})
} else {
if (form.name) {
addOrUpdate(form).then(sql => dbUtils.query(sql)).then(() => {
response.send({code: 0})
}).catch(err => {
response.send({code: 1, err: err.sqlMessage})
})
} else response.send({code: 1, err: "name必填"})
}
}).catch(err => {
response.send({code: 1, err: err.sqlMessage})
})
} else response.send({code: 1, err: "缺少必要参数type"})
}
}

View File

@@ -0,0 +1,31 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/wechatapps/list",
method: "post",
execute: (request, response) => {
let total = 0, records = [], {size, current = 1, name = "", type, isMain} = request.query
if (type) {
let condition = `where type='${type}' and (name like '%${name}%' or label like '%${name}%')`
isMain && (condition += ` and libPath not like '%project%'`)
Promise.all([
dbUtils.query(`select 1 from node_wechat_apps ${condition}`).then(res => {
return total = res.length
}),
new Promise(resolve => {
let sql = `select * from node_wechat_apps ${condition} limit ${(current-1)*size||0},${size||1}`
dbUtils.query(sql).then(res => {
records = res
resolve()
}).catch(err => {
response.send({code: 1, err: err.sqlMessage})
})
})
]).then(() => {
response.send({
code: 0,
data: {records, total}
})
})
} else response.send({code: 1, err: 'type为必填参数'})
}
}

71
src/rest/wxtest/action.js Normal file
View File

@@ -0,0 +1,71 @@
const {getSignature, decrypt} = require("@wecom/crypto");
const {Parser} = require("xml2js");
const {query} = require("../../utils/dbUitls");
const dayjs = require("dayjs");
const axios = require("axios");
const {getAccessToken} = require("./getUserInfo");
const token = "pnYAdXEHYzYhIyzE6Qbs2L"
const encodingAESKey = "fHkOHrUGSVUmPjFmshLEFN2XbaqF3OxsuYgnJu6DB1G"
let accessToken
const reply = (agentid, to, content) => {
axios.post("https://qyapi.weixin.qq.com/cgi-bin/message/send", {
touser: to, agentid, msgtype: "markdown", markdown: {
content
}
}, {params: {access_token: accessToken}})
}
/**
* 获取当前值班人并将信息发送给询问人,同时通知值班人员
* @param params
*/
const handleReplyDutyInfo = (params) => {
const {FromUserName: touser, AgentID: agentid} = params,
now = dayjs().format("YYYY-MM-DD HH:mm:ss"),
sql = `select * from node_wx_test_duty where dutyStartTime<='${now}' and dutyEndTime>='${now}'`
query(sql).then(res => {
const info = res?.[0] || {}
if (info.dutyUserId) {
getAccessToken().then(access_token => {
accessToken = access_token
/**
* 回复查询值班信息给查询人
*/
reply(agentid, touser, `当前值班信息如下\n>值班时间:${info.dutyStartTime} - ${info.dutyEndTime}\n值班人员:${info.dutyUserName}\n`)
/**
* 通知提醒值班人员
*/
reply(agentid, info.dutyUserId, `今天轮到你值班了,请知悉~`)
})
} else {
/**
* 无值班人员
*/
reply(agentid, touser, `今天没有值班人员哦~`)
}
})
}
module.exports = {
action: "/node/wxtest/action",
method: "post",
execute: (request, response) => {
const {msg_signature, timestamp, nonce} = request.query,
{xml: {encrypt}} = request.body
const signature = getSignature(token, timestamp, nonce, encrypt[0]);
if (msg_signature == signature) {
const context = decrypt(encodingAESKey, encrypt[0])
const parser = new Parser({explicitArray: false})
parser.parseString(context.message, (err, result) => {
if (!err) {
if (result.xml.Content == '值班') {
handleReplyDutyInfo(result.xml)
}
response.send({code: 0, message: "success"})
} else response.send({code: 1, err})
})
} else response.send({code: 1, message: "error"})
}
}

View File

@@ -0,0 +1,17 @@
const {getSignature, decrypt} = require("@wecom/crypto");
const token = "pnYAdXEHYzYhIyzE6Qbs2L"
const encodingAESKey = "fHkOHrUGSVUmPjFmshLEFN2XbaqF3OxsuYgnJu6DB1G"
module.exports = {
action: "/node/wxtest/action",
method: "get",
execute: (request, response) => {
const {msg_signature, timestamp, nonce, echostr} = request.query
const signature = getSignature(token, timestamp, nonce, echostr);
if (msg_signature == signature) {
const context = decrypt(encodingAESKey, echostr)
response.send(context.message)
} else {
response.send({code: 1, message: "验证不通过"})
}
}
}

16
src/rest/wxtest/add.js Normal file
View File

@@ -0,0 +1,16 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/duty/addOrUpdate",
method: "post",
execute: (request, response) => {
let {id, dutyUserId,dutyUserName,dutyStartTime,dutyEndTime} = request.body
dbUtils.addOrUpdate({
table: 'node_wx_test_duty',
form: {id, dutyUserId,dutyUserName,dutyStartTime,dutyEndTime}
}).then(data => {
response.send({code: 0, data})
}).catch(err => {
response.send({code: 1, err: err?.sqlMessage || err || ""})
})
}
}

View File

@@ -0,0 +1,43 @@
const axios = require("axios");
const auth = require("../../config/auth.js")
const CORPID = "ww596787bb70f08288"
const SECRET = "ZEpS51fCRPznHG16k0z1lfxaH0VciEnBljeP9aR47VU"
/**
* 获取access_token
*/
const getAccessToken = (corpid = CORPID, secret = SECRET) => {
const url = `https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${corpid}&corpsecret=${secret}`
return axios.get(url).then(res => {
return res.data?.access_token
})
}
/**
* 获取 userId及其他信息
*/
const getUserInfo = (accessToken, code) => {
return axios.get(`https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo`, {
params: {access_token: accessToken, code}
})
}
module.exports = {
action: "/node/wxtest/token",
method: "post",
execute: (request, response) => {
const {code} = request.query
getAccessToken().then(token => getUserInfo(token, code)).then(res => {
const userid = res?.data?.userid
//userid换取我方系统token
if (userid) {
const token = auth.generateToken(userid)
response.send({code: 0, token})
} else {
response.send({code: 1, err: res.data})
}
}).catch(err => {
console.log(err)
response.send({code: 1, err})
})
},
getAccessToken
}

16
src/rest/wxtest/list.js Normal file
View File

@@ -0,0 +1,16 @@
const dbUtils = require("../../utils/dbUitls");
module.exports = {
action: "/node/duty/list",
method: "post",
execute: (request, response) => {
let {size, current} = request.query
dbUtils.list({
table: 'node_wx_test_duty',
search: {size, current}, sort: 'dutyStartTime', con: "dutyUserName"
}).then(data => {
response.send({code: 0, data})
}).catch(err => {
response.send({code: 1, err: err?.sqlMessage || err || ""})
})
}
}

23
src/rest/wxtest/token.js Normal file
View File

@@ -0,0 +1,23 @@
const axios = require("axios");
const CORPID = "ww596787bb70f08288"
const SECRET = "Bh3GT11_bzxSm03xZBY8otjw_WLWeLsduzDQweUohAY"
const getAccessToken = (corpid = CORPID, secret = SECRET) => {
const url = `https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${corpid}&corpsecret=${secret}`
return axios.get(url).then(res => {
return res.data?.access_token
})
}
module.exports = {
action: "/node/wxtest/access",
method: "post",
execute: (request, response) => {
getAccessToken().then(token => {
response.send({code: 0, token})
}).catch(err => {
console.log(err)
response.send({code: 1, err})
})
},
getAccessToken
}

12
src/tools/exec.js Normal file
View File

@@ -0,0 +1,12 @@
const cp = require("child_process");
const execute = (cmd, signal) => new Promise((resolve, reject) => {
const pid = cp.exec(cmd, {windowsHide: true, signal}, (err) => {
if (!err) {
resolve(cmd)
} else reject(err)
})
pid.stdout.on('data', data => {
console.log(data)
})
})
module.exports = execute

161
src/tools/generate.js Normal file
View File

@@ -0,0 +1,161 @@
const fse = require("fs-extra");
const {readFile} = require("../utils/fsUtils");
const {checkJson} = require("./index");
/**
* 生成入口页
*/
const genHome = (app, dest) => {
return readFile('./tpl/AppEntry.vue').then(data => {
let file = data.toString(),
content = file.replace(/@appName/g, app.appName)
.replace(/@name/g, app.name)
let dicts = `'yesOrNo'`
if (checkJson(app.props)) {
let props = JSON.parse(app.props)?.map(e => e.dict).filter(e => !!e)
props.length > 0 && (dicts = props.map(e => `'${e}'`).toString())
}
content = content.replace(/@dicts/g, dicts)
return fse.outputFileSync(`${dest}/${app.appName}.vue`, content)
})
}
/**
* 生成列表页
*/
const genList = (app, dest) => {
return readFile('./tpl/list.vue').then(data => {
let file = data.toString(),
content = file.replace(/@rightCode/g, app.rightCode)
.replace(/@name/g, app.name),
listProps = "", searchProps = "", btns = "", tableBtns = ""
if (checkJson(app.props)) {
let props = JSON.parse(app.props)
listProps = JSON.stringify(props.filter(e => e.isTable)?.map(e => {
delete e.isSearch
delete e.isTable
delete e.isDetail
return e
}) || null)?.replace(/"([^"]+)":/g, '$1:')
props.filter(e => e.isSearch && e.dict).map(e => {
searchProps += `<ai-select v-model="search.${e.prop}" placeholder="${e.label}" :selectList="dict.getDict('${e.dict}')"/>`
})
}
if (checkJson(app.btns)) {
let buttons = JSON.parse(app.btns)
buttons.map(e => {
if (e == "insertEnable") {
btns += `<el-button type="primary" icon="iconfont iconAdd" @click="handleAdd()">添加</el-button>`
} else if (e == "importEnable") {
btns += `<ai-import :instance="instance" name="${app.name}" title="导入${app.name}" suffixName="xlsx"
url="/app/${app.rightCode}/downloadTemplate" importUrl="/app/${app.rightCode}/import"
@onSuccess="page.current=1,getTableData()"/>`
} else if (e == "exportEnalbe") {
btns += `<ai-download url="/app/${app.rightCode}/export" :params="{...search,ids}" :instance="instance" fileName="${app.name}导出文件"/>`
} else if (e == "editEnable") {
tableBtns += `<el-button type="text" @click="handleAdd(row.id)">编辑</el-button>`
} else if (e == "deleteEnable") {
tableBtns += `<el-button type="text" @click="handleDelete(row.id)">删除</el-button>`
} else if (e == "batchDelEnable") {
btns += `<el-button icon="iconfont iconDelete" :disabled="!search.ids" @click="handleDelete(search.ids)">删除</el-button>`
}
})
if (!searchProps) {
//当没有筛选条件时,按钮替换原筛选条件位置
searchProps = btns
btns = ""
} else {
btns = `<ai-search-bar><template #left>${btns}</template></ai-search-bar>`
}
}
content = content.replace(/@listProps/g, listProps)
.replace(/@searchProps/g, searchProps)
.replace(/@btns/g, btns)
.replace(/@tableBtns/g, tableBtns)
return fse.outputFileSync(`${dest}/list.vue`, content)
})
}
/**
* 生成新增/编辑页
*/
const genAdd = (app, dest) => {
return readFile('./tpl/add.vue').then(data => {
let file = data.toString(),
content = file.replace(/@rightCode/g, app.rightCode)
.replace(/@name/g, app.name),
rules = "", domain = ""
if (checkJson(app.detailConfig)) {
let detail = JSON.parse(app.detailConfig), props = detail?.map(e => e.column).flat()
props.filter(e => e.mustFill == 1).map(e => {
rules += `${e.prop}: {required: true, message: "${e.fieldTips}"},`
})
detail.map(group => {
let fields = ``
group.column?.map(e => {
fields += `<el-form-item label="${e.fieldName}" prop="${e.prop}" ${e.grid == 1 ? '' : 'class="half"'}>${getComp(e)}</el-form-item>`
})
domain += `<ai-card title="${group.groupName}"><template #content> ${fields}</template></ai-card>`
})
}
content = content.replace(/@content/g, domain)
.replace(/@rules/g, rules)
return fse.outputFileSync(`${dest}/add.vue`, content)
})
}
const getComp = e => {
//字典下拉选择
if (e.type == 'dict') {
return `<ai-select v-model="form.${e.prop}" placeholder="${e.fieldName}" :selectList="dict.getDict('${e.dict}')" ${e.disable == 1 ? 'disabled' : ''}/>`
//单选radio
} else if (e.type == 'radio') {
return `<el-radio-group v-model="form.${e.prop}" ${e.disable == 1 ? 'disabled' : ''}>
<el-radio :label="item.dictValue" v-for="(item, i) in dict.getDict('${e.dict}')" :key="i">{{ item.dictName }}</el-radio>
</el-radio-group>`
//开关onOff
} else if (e.type == 'onOff') {
return `<el-switch v-model="form.${e.prop}" active-color="#26f" inactive-color="#ddd" active-value="1" inactive-value="0" ${e.disable == 1 ? 'disabled' : ''}/>`
//多选checkbox
} else if (e.type == 'checkbox') {
return `<el-checkbox-group v-model="form.${e.prop}" ${e.disable == 1 ? 'disabled' : ''}>
<el-checkbox v-for="(item, i) in dict.getDict('${e.dict}')" :label="item.dictValue" :key="i">{{ item.dictName }}</el-checkbox>
</el-checkbox-group>`
} else if (e.type == 'idNumber') {
return `<ai-id v-model="form.${e.prop}" ${e.disable == 1 ? 'disabled' : ''}/>`
//input输入框
} else if (['input', 'name', 'phone'].includes(e.type)) {
return `<el-input v-model="form.${e.prop}" placeholder="请输入${e.fieldName}" clearable ${e.disable == 1 ? 'disabled' : ''} :maxlength="${e.maxLength}" show-word-limit/>`
//number 输入框
} else if (e.type == 'number') {
return `<el-input-number v-model="form.${e.prop}" label="请输入${e.fieldName}" ${e.disable == 1 ? 'disabled' : ''} :precision="${e.decimalPlaces}" :max="${e.maxValue}" :min="${e.minValue}"/>`
//textarea输入框
} else if (e.type == 'textarea' || e.type == 'text') {
return `<el-input v-model="form.${e.prop}" placeholder="请输入${e.fieldName}" clearable ${e.disable == 1 ? 'disabled' : ''} :maxlength="${e.maxLength}" type="textarea" show-word-limit :rows="3"/>`
//日期选择
} else if (e.type == 'date') {
return `<el-date-picker style="width: 100%;" v-model="form.${e.prop}" type="date" placeholder="请选择" ${e.disable == 1 ? 'disabled' : ''} :value-format="${e.timePattern}"/>`
//日期带时分秒选择
} else if (e.type == 'datetime') {
return `<el-date-picker v-model="form.${e.prop}" type="datetime" placeholder="选择日期时间" ${e.disable == 1 ? 'disabled' : ''} :value-format="${e.timePattern}"/>`
//时间-时分秒选择
} else if (e.type == 'time') {
return `<el-time-picker v-model="form.${e.prop}" placeholder="请选择" ${e.disable == 1 ? 'disabled' : ''} :value-format="${e.timePattern}"/>`
//附件
} else if (e.type == 'upload') {
return ` <ai-uploader :instance="instance" isShowTip fileType="file" v-model="form.${e.prop}" ${e.disable == 1 ? 'disabled' : ''}
acceptType=".zip,.rar,.doc,.docx,.xls,.ppt,.pptx,.pdf,.txt,.jpg,.png,.xlsx" :limit="${e.fileMaxCount}" :maxSize="${e.fileChoseSize}"/>`
//富文本
} else if (e.type == 'rtf') {
return `<ai-editor v-model="form.${e.prop}" :instance="instance" ${e.disable == 1 ? 'disabled' : ''}/>`
//地区选择
} else if (e.type == 'area') {
return ` <ai-area-get :instance="instance" v-model="form.${e.prop}" :name.sync="form.${e.fieldDbName}_name" ${e.disable == 1 ? 'disabled' : ''}/>`
} else if (e.type == 'user') {
//人员选择
return `<ai-wechat-selecter slot="append" isShowUser :instance="instance" v-model="form.${e.prop}"/>`
}
}
const generate = (app, dest) => {
fse.emptydirSync(dest)
let tasks = [genHome(app, dest), genList(app, dest)]
app.detailType == 0 && tasks.push(genAdd(app, dest))
return Promise.all(tasks)
}
module.exports = generate

19
src/tools/index.js Normal file
View File

@@ -0,0 +1,19 @@
const checkJson = str => {
if (typeof str == 'object') {
try {
str = JSON.stringify(str)
} catch (e) {
return false
}
}
if (typeof str == 'string') {
try {
let obj = JSON.parse(str);
return !!(typeof obj == 'object' && obj);
} catch (e) {
return false;
}
}
return false;
}
module.exports = {checkJson}

96
src/utils/dbUitls.js Normal file
View File

@@ -0,0 +1,96 @@
const mysql = require("mysql");
const dbConfig = require("../config/db");
const {v4: uuid} = require("uuid");
const dayjs = require("dayjs");
const chalk = require("chalk");
const query = sql => new Promise((resolve, reject) => {
this.pool?.getConnection((err, conn) => {
if (err) {
reject(err)
} else {
conn.query(sql, (err, result) => {
if (err) {
reject(err)
} else {
conn.release()
resolve(result)
}
})
}
})
});
const insert = ({table, form}) => {
let sql
if (form.id) {//编辑
let arr = Object.entries(form).map(([e, v]) => {
if (v) {
if (typeof v == "object") {
v = JSON.stringify(v)
v = v.replace(/(')/g, "\\$1")
}
} else v = ''
return `${e}='${v}'`
})
sql = `update ${table} set ${arr.join(",")} where id='${form.id}'`
} else {//新增
let cols = [], arr = []
Object.entries(form).map(([e, v]) => {
if (v) {
cols.push(e)
if (typeof v == "object") {
v = JSON.stringify(v)
v = v.replace(/(')/g, "\\$1")
}
arr.push(`'${v}'`)
}
})
sql = `insert into ${table} (id,createTime,${cols.join(",")}) values('${uuid()}','${dayjs().format("YYYY-MM-DD HH:mm:ss")}',${arr.join(",")})`
}
return sql
}
module.exports = {
pool: null,
init: () => {
this.pool = mysql.createPool(dbConfig)
console.log(`${chalk.bgBlue.black(" DATABASE ")} 数据库已连接`)
},
query,
list: ({table, search, con = 'name', sort}) => {
//列表查询
let total = 0, records = []
if (table) {
const {current, size = 10} = search, params = JSON.parse(JSON.stringify(search))
const conValue = params[con] || ""
delete params.current
delete params.size
delete params[con]
const sqlCon = Object.keys(params).map(e => `and ${e}='${params[e]}'`).join(" ")
return Promise.all([
query(`select 1 from ${table} where ${con} like '%${conValue}%' ${sqlCon}`).then(res => {
return total = res.length
}),
query(`select * from ${table} where ${con} like '%${conValue}%' ${sqlCon} order by ${sort||'createTime'} desc limit ${((current-1)||0)*size},${size}`).then(res => {
return records = res
})
]).then(() => {
return {records, total}
})
}
},
batchInsert({table, list}) {
return query(list.map(form => insert({table, form})).join(";"))
},
addOrUpdate: ({table, form}) => {
//新增和更新
const sql = insert({table, form})
return query(sql)
},
delete: ({table, ids}) => {
ids = ids?.split(",")?.map(e => `'${e}'`)?.toString()
return query(`delete from ${table} where id in (${ids})`)
},
detail: ({table, id}) => {
return query(`select * from ${table} where id='${id}' limit 0,1`).then(res => res?.[0])
},
format: args => args.map(e => `${e.prop}`).join(" ")
}

38
src/utils/fsUtils.js Normal file
View File

@@ -0,0 +1,38 @@
const fs = require("fs")
const path = require("path")
const promisify = fn => {
return function () {
let args = arguments;
return new Promise(function (resolve, reject) {
[].push.call(args, function (err, result) {
if (err) {
console.log(err)
reject(err);
} else {
resolve(result);
}
});
fn.apply(null, args);
});
}
}
const readdir = promisify(fs.readdir)
const stat = promisify(fs.stat)
const findFile = (dir = '.', cb) => {
return readdir(dir).then(apps => {
return Promise.all(apps.map(e => {
let cPath = path.join(dir, e)
return stat(cPath).then(state => {
if (state.isDirectory()) {
return findFile(cPath, cb)
} else if (state.isFile()) {
cb && cb(cPath)
}
})
}))
})
}
const readFile = promisify(fs.readFile)
module.exports = {readdir, stat, findFile, readFile}

View File

@@ -0,0 +1,149 @@
const db = require("../../utils/dbUitls");
const execute = require("../../tools/exec")
const dayjs = require("dayjs")
const fse = require("fs-extra");
const axios = require('axios')
const http = axios.create({
baseURL: 'http://jenkins:114295d0b955e67a18b11917bd281ea9dc@121.37.155.68:35801',
})
const jobs = {}
let controller = new AbortController()
const handleZip = (id, ws) => {
const uniCon = `id='${id}'`
const sendMessage = data => ws.send(JSON.stringify(data))
class counter {
constructor(remark) {
this.count = 0
sendMessage({code: 0, progress: this.count, remark})
this.timer = setInterval(() => {
sendMessage({code: 0, progress: ++this.count, remark})
}, 6000)
}
set(progress, id) {
if (this.timer) clearInterval(this.timer)
this.count = progress
sendMessage({code: 0, progress: this.count, id})
this.timer = setInterval(() => {
sendMessage({code: 0, progress: ++this.count, id})
}, 4000)
}
stop(row) {
if (this.timer) clearInterval(this.timer)
sendMessage({code: 1, row, id: row.id})
}
finish(row) {
if (this.timer) clearInterval(this.timer)
sendMessage({code: 0, progress: 100, row, id: row.id})
}
}
const startUpdateSql = () => db.query(`update node_custom_config
set download=null,
error=null,
zipTime='${dayjs().format("YYYY-MM-DD HH:mm:ss")}'
where ${uniCon}`)
const endUpdateSql = () => db.query(`update node_custom_config
set download='${dayjs().format("YYYY-MM-DD HH:mm:ss")}',
error=''
where ${uniCon}`)
const errorUpdateSql = msg => db.query(`update node_custom_config
set error='${msg}',
zipTime=null
where ${uniCon}`)
db.detail({table: "node_custom_config", id}).then((info = {}) => {
if (info.id) {
const {signal} = controller;
const progress = new counter(`正在处理 ${info.name} 的打包工作...`)
if (['web','wxwork'].includes(info.type)) {
const {name, version, dist} = info
const deploy = dist?.trim() || `${name}v${version}`
const buildConfig = {
web: {task: "devops-web", token: 'fLeOGSVIRs405Me'},
wxwork: {task: "devops-h5", token: 'fLeOGSVIRs405Me'},
}[info.type]
let currentJob = 1
startUpdateSql()
.then(() => progress.set(30, id))
.then(() => fse.emptyDir(dist || `../zips/${deploy}`))
.then(() => http.get(`/view/devops/job/${buildConfig.task}/api/json`).then(res => currentJob = (res.data?.nextBuildNumber || 1)))
.then(() => http.get(`/view/devops/job/${buildConfig.task}/buildWithParameters`, {params: {token: buildConfig.token, pid: id, dist: deploy}}))
.then(() => new Promise((resolve, reject) => {
jobs[id] = {task: buildConfig.task, build: currentJob}
const timer = setInterval(() => {
http.get(`/view/devops/job/${buildConfig.task}/${currentJob}/api/json`).then(res => {
if (['SUCCESS', 'UNSTABLE'].includes(res.data.result)) {
clearInterval(timer)
progress.set(90, id)
endUpdateSql().then(resolve)
} else if (res.data.result == 'ABORTED') {
clearInterval(timer)
reject("构建取消")
} else if (res.data.result == 'FAILURE') {
clearInterval(timer)
reject("构建失败")
}
}).catch(() => 0)
}, 2000)
}))
.then(() => db.detail({table: "node_custom_config", id}))
.then(row => progress.finish(row))
.catch(err => {
console.log(err)
const msg = `执行失败:${err.cmd || err}`
return errorUpdateSql(msg)
.then(() => db.detail({table: "node_custom_config", id}))
.then(row => progress.stop(row))
.catch(() => 0)
})
} else {
const buildPath = {
web: 'base-web', wxwork: 'base-wxcp', mp: 'dvcp_v2_wxmp'
}[info.type] || {}
let path = `/${buildPath}`, {dist} = info
dist = dist || `/zips/${info.name}v${info.version || "1.0.0"}`
Promise.all([startUpdateSql(), execute(`./shell/update.sh ${info.name} ${path}`, signal)])
.then(cmd => progress.set(30, info?.id))
.then(() => execute(`cd ${path}&&npm run apps&&node bin/pages.js ${id}&&npm run build`, signal))
.then(cmd => progress.set(70, info?.id))
.then(() => fse.emptyDir(dist))
.then(() => execute(`./shell/move.sh ${info.type} ${path} ${dist}`, signal))
.then(cmd => progress.set(90, info?.id))
.then(() => endUpdateSql())
.then(() => db.detail({table: "node_custom_config", id}))
.then(row => progress.finish(row))
.catch(err => {
console.log(err)
const msg = `执行失败:${err.cmd}`
return errorUpdateSql(msg)
.then(() => db.detail({table: "node_custom_config", id}))
.then(row => progress.stop(row))
.catch(() => 0)
})
}
}
}).catch(err => {
console.log(err)
})
}
module.exports = {
action: "/custom/getZip", execute: (ws, request) => {
const {id, cid} = request
if (cid) {
if (jobs[cid]) {
const {task, build} = jobs[cid]
http.post(`/job/${task}/${build}/stop`)
} else {
controller.abort()
controller = new AbortController()
}
} else if (id) {
handleZip(id, ws)
}
},
}

34
src/websocket/index.js Normal file
View File

@@ -0,0 +1,34 @@
const {findFile} = require("../utils/fsUtils");
const chalk = require("chalk");
const dayjs = require("dayjs");
const {checkJson} = require("../tools");
const {log} = console
module.exports = {
init: ins => new Promise(resolve => {
let actions = {}
findFile('./src/websocket', file => {
if (!/index\.js/.test(file)) {
const item = require(file.replace(/src[\\\/]websocket/, '.'))
actions[item.action] = item.execute
}
}).then(() => {
ins.ws('/ws', ws => {
log(`${chalk.bgBlue.black(" WEBSOCKET ")} 服务已启动!`)
ws.send('您已成功连接到node websocket')
ws.onerror = () => ws.close()
ws.on('close', () => {
log(`${chalk.bgRed.black(" WEBSOCKET ")} 连接已断开!`)
clearInterval(heartBeat)
})
ws.on('message', res => {
if (checkJson(res)) {
const data = JSON.parse(res)
!!data?.action && actions[data.action]?.(ws, data)
}
})
let heartBeat = setInterval(() => ws.send(`heartBeat at ${dayjs().format("YYYY-MM-DD HH:mm:ss")}`), 5000)
resolve()
})
})
})
}

36
tpl/AppEntry.vue Normal file
View File

@@ -0,0 +1,36 @@
<template>
<section class="@appName">
<component :is="currentPage" v-bind="$props"/>
</section>
</template>
<script>
import List from "./list";
import Add from "./add";
export default {
name: "@appName",
components: {Add, List},
label: "@name",
props: {
instance: Function,
dict: Object,
permissions: Function
},
computed: {
currentPage() {
let {hash} = this.$route
return hash == "#add" ? Add : List
}
},
created() {
this.dict.load(@dicts)
}
}
</script>
<style lang="scss" scoped>
.@appName {
height: 100%;
}
</style>

81
tpl/add.vue Normal file
View File

@@ -0,0 +1,81 @@
<template>
<section class="add">
<ai-detail>
<ai-title slot="title" :title="pageTitle" isShowBottomBorder/>
<template #content>
<el-form ref="AddForm" :model="form" size="small" label-width="120px" :rules="rules">
@content
</el-form>
</template>
<template #footer>
<el-button @click="back">取消</el-button>
<el-button type="primary" @click="submit">提交</el-button>
</template>
</ai-detail>
</section>
</template>
<script>
export default {
name: "add",
props: {
instance: Function,
dict: Object,
permissions: Function
},
computed: {
isEdit: v => !!v.$route.query.id,
pageTitle: v => v.isEdit ? "编辑@name" : "新增@name"
},
data() {
return {
form: {},
rules: {
@rules
},
}
},
methods: {
getDetail() {
let {id} = this.$route.query
id && this.instance.post("/node/@rightCode/detail", null, {
params: {id}
}).then(res => {
if (res?.data) {
this.form = res.data
}
})
},
back() {
this.$router.push({})
},
submit() {
this.$refs.AddForm.validate(v => {
if (v) {
this.instance.post("/node/@rightCode/addOrUpdate", this.form).then(res => {
if (res?.code == 0) {
this.$message.success("提交成功!")
this.back()
}
})
}
})
},
},
created() {
this.getDetail()
}
}
</script>
<style lang="scss" scoped>
.add {
.half {
width: 50%;
& + .half {
margin-left: 16px;
}
}
}
</style>

82
tpl/list.vue Normal file
View File

@@ -0,0 +1,82 @@
<template>
<section class="list">
<ai-list>
<ai-title slot="title" title="@name" isShowBottomBorder/>
<template #content>
<ai-search-bar>
<template #left>
@searchProps
</template>
<template #right>
<el-input size="small" placeholder="搜索" v-model="search.name" clearable
@change="page.current=1,getTableData()"/>
</template>
</ai-search-bar>
@btns
<ai-table :tableData="tableData" :total="page.total" :current.sync="page.current" :size.sync="page.size"
@getList="getTableData" :col-configs="colConfigs" :dict="dict">
<el-table-column slot="options" label="操作" fixed="right" align="center" width="300">
<template slot-scope="{row}">
@tableBtns
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
</section>
</template>
<script>
export default {
name: "list",
props: {
instance: Function,
dict: Object,
permissions: Function
},
data() {
return {
search: {name: ""},
page: {current: 1, size: 10, total: 0},
tableData: [],
colConfigs: @listProps,
}
},
methods: {
getTableData() {
this.instance.post("/node/@rightCode/list", null, {
params: {...this.page, ...this.search}
}).then(res => {
if (res?.data) {
this.tableData = res.data.records
this.page.total = res.data.total
}
})
},
handleAdd(id) {
this.$router.push({hash: "#add", query: {id}})
},
handleDelete(ids) {
this.$confirm("是否要删除?").then(() => {
this.instance.post("/node/@rightCode/delete", null, {
params: {ids}
}).then(res => {
if (res?.code == 0) {
this.$message.success("删除成功")
this.getTableData()
}
})
}).catch(() => 0)
}
},
created() {
this.getTableData()
}
}
</script>
<style lang="scss" scoped>
.list {
height: 100%;
}
</style>