@dypnb/dev-tools 1.0.5 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,228 @@
1
+ #! /usr/bin/env node
2
+
3
+ // 将swagger 转换为 vue api代码
4
+ import path from "path";
5
+ import fs from "fs";
6
+ import http from "http";
7
+ import { getGlobalConfig, getDirname, errorLog, log, successLog, } from "../utils/index.js";
8
+ const __dirname = getDirname();
9
+
10
+
11
+ async function getSwaggerConfig() {
12
+ return await getGlobalConfig('swaggerConfig');
13
+ }
14
+
15
+ getSwaggerConfig().then(res => {
16
+ // swagger配置
17
+ const swaggerConfig = res;
18
+ if (!swaggerConfig) {
19
+ errorLog('swaggerConfig 未配置')
20
+ }
21
+
22
+ // 生成api文件地址
23
+ const srcFolder = `${process.env.PWD}${swaggerConfig.outputDir}`;
24
+ // swagger接口地址
25
+ const url = `${swaggerConfig.path}${swaggerConfig.staticPath}`;
26
+
27
+ genSwagger(srcFolder, url);
28
+
29
+ })
30
+
31
+
32
+ // 生成本地文件
33
+ function mkdirsSync(dirname) {
34
+ if (fs.existsSync(dirname)) {
35
+ return true;
36
+ } else {
37
+ if (mkdirsSync(path.dirname(dirname))) {
38
+ fs.mkdirSync(dirname);
39
+ return true;
40
+ }
41
+ }
42
+ }
43
+
44
+ function getPath(pathUrl) {
45
+ return path.resolve(__dirname, pathUrl);
46
+ }
47
+
48
+ function generateTemplate(arr) {
49
+ return `import request from '@/utils/request'\n`;
50
+ }
51
+
52
+ // 下划线转换驼峰
53
+ function toHump(name) {
54
+ return name.replace(/\/(\w)/g, function (all, letter) {
55
+ return letter.toUpperCase();
56
+ });
57
+ }
58
+
59
+ // 短横线转换驼峰
60
+ function shortToHump(name) {
61
+ return name.replace(/-(\w)/g, function (all, letter) {
62
+ return letter.toUpperCase();
63
+ });
64
+ }
65
+
66
+ // 去除花括号,获取干净的字段
67
+ function removeBrace(value) {
68
+ const regex = /\{(.+?)\}/g; // {} 花括号,大括号
69
+ const str = value.match(regex)[0] || "";
70
+ return str.replace(/\{|}/g, "");
71
+ }
72
+
73
+ /**
74
+ * 生成具体的api:
75
+ * export function postRsArticle(data) {
76
+ * return request({
77
+ * url: '/rs/article',
78
+ * method: 'post',
79
+ * data: data
80
+ * })
81
+ * }
82
+ */
83
+ function generateFunc(url, summary, type = "post") {
84
+ // 去除 url 环境前缀: /dev-risk-api/sc/apply/{applyId} ==> /sc/apply/{applyId}
85
+ // url = url.split('/');
86
+ // url.splice(1,1)
87
+ // url = url.join('/');
88
+
89
+ const isBrace = url.indexOf("{") !== -1;
90
+ let funcName = shortToHump(toHump(type + url));
91
+ let splitUrl = "";
92
+ let braceKey = "";
93
+ if (isBrace) {
94
+ splitUrl = url.split("{")[0];
95
+ braceKey = removeBrace(url);
96
+ funcName = shortToHump(toHump(type + splitUrl + braceKey));
97
+ }
98
+
99
+ const funcArguments = `${
100
+ isBrace
101
+ ? braceKey
102
+ : !isBrace && (type === "post" || type === "put")
103
+ ? "data"
104
+ : "query"
105
+ }`;
106
+ const funcUrl = `${!isBrace ? `'${url}'` : `'${splitUrl}' + ${braceKey}`}`;
107
+ const funcParams = `${
108
+ isBrace
109
+ ? ""
110
+ : !isBrace && (type === "post" || type === "put")
111
+ ? "\n data: data"
112
+ : "\n params: query"
113
+ }`;
114
+
115
+ return `
116
+ // ${summary || ""}
117
+ export function ${funcName}(${funcArguments}) {
118
+ return request({
119
+ url: ${funcUrl},
120
+ method: '${type}', ${funcParams}
121
+ })
122
+ }\n`;
123
+ }
124
+
125
+ function httpgetJson(url) {
126
+ return new Promise((resolve, reject) => {
127
+ http
128
+ .get(url, (res) => {
129
+ const { statusCode } = res;
130
+ const contentType = res.headers["content-type"];
131
+
132
+ let error;
133
+ if (statusCode !== 200) {
134
+ error = new Error("请求失败。\n" + `状态码: ${statusCode}`);
135
+ } else if (!/^application\/json/.test(contentType)) {
136
+ error = new Error(
137
+ "无效的 content-type.\n" +
138
+ `期望 application/json 但获取的是 ${contentType}`
139
+ );
140
+ }
141
+ if (error) {
142
+ errorLog(error.message);
143
+ // 消耗响应数据以释放内存
144
+ res.resume();
145
+ return;
146
+ }
147
+
148
+ res.setEncoding("utf8");
149
+ let rawData = "";
150
+ res.on("data", (chunk) => {
151
+ rawData += chunk;
152
+ });
153
+ res.on("end", () => {
154
+ try {
155
+ const parsedData = JSON.parse(rawData);
156
+ resolve(parsedData);
157
+ } catch (e) {
158
+ reject(`错误: ${e.message}`);
159
+ }
160
+ });
161
+ })
162
+ .on("error", (e) => {
163
+ reject(`错误: ${e.message}`);
164
+ });
165
+ });
166
+ }
167
+
168
+ async function genSwagger(srcFolder, url) {
169
+ log("获取远程json文件中...");
170
+ const { paths } = await httpgetJson(url);
171
+ successLog("获取成功正在生成api文件");
172
+ const obj = {};
173
+ /**
174
+ * 将数据转换成格式
175
+ * se-ex-exam-controller: [
176
+ * {
177
+ * folder:'exam'
178
+ * name:'/ex/exam'
179
+ * summary:'修改考试考卷'
180
+ * tag:'se-ex-exam-controller'
181
+ * type:'put'
182
+ * }
183
+ * ...
184
+ * ]
185
+ */
186
+ for (const name in paths) {
187
+ const path = paths[name] || {};
188
+ const pathKeys = Object.keys(path) || [];
189
+ for (let i = 0, len = pathKeys.length; i < len; i++) {
190
+ const apiType = pathKeys[i];
191
+ const tag = path[apiType].tags[0];
192
+ if (!tag) continue;
193
+ log(tag);
194
+ const urlArray = name.slice(1).split("/");
195
+ const folder = urlArray[1];
196
+ const item = {
197
+ summary: path[apiType].summary,
198
+ tag,
199
+ name,
200
+ type: apiType,
201
+ folder,
202
+ };
203
+ if (obj[path[apiType].tags[0]]) {
204
+ obj[path[apiType].tags[0]].push(item);
205
+ } else {
206
+ obj[path[apiType].tags[0]] = [item];
207
+ }
208
+ }
209
+ }
210
+ for (const tagName in obj) {
211
+ let jsString = "";
212
+ const requestTypes = [];
213
+ let folder = "";
214
+ for (const item of obj[tagName]) {
215
+ const requestType = requestTypes.filter((o) => o === item.type);
216
+ if (requestType.length === 0) requestTypes.push(item.type);
217
+ jsString += generateFunc(item.name, item.summary, item.type);
218
+ folder = item.folder;
219
+ }
220
+ jsString = generateTemplate(requestTypes) + jsString;
221
+ mkdirsSync(getPath(`${srcFolder}/${folder}`));
222
+ // console.log(jsString)
223
+ fs.writeFileSync(getPath(`${srcFolder}/${folder}/${tagName}.js`), jsString);
224
+ }
225
+ successLog("生成完毕");
226
+ }
227
+
228
+
package/src/index.js ADDED
@@ -0,0 +1,12 @@
1
+ const genPage = require("./gen-page");
2
+ const genSwagger = require("./gen-swagger");
3
+ const publishServer = require("./publish-server");
4
+ const wxServerNotice = require("./wx-server-notice");
5
+
6
+
7
+ module.exports = {
8
+ genPage,
9
+ genSwagger,
10
+ publishServer,
11
+ wxServerNotice
12
+ }
@@ -0,0 +1,130 @@
1
+ #! /usr/bin/env node
2
+
3
+ // scp2 : https://www.npmjs.com/package/
4
+ // 引入scp2
5
+ import client from "scp2";
6
+ import ora from "ora";
7
+ import moment from "moment";
8
+ import { getGlobalConfig, getGitInfo, errorLog, magentaLog, log, warningLog, successLog, cyanLog } from "../utils/index.js";
9
+ import { wxNotify } from "../wx-server-notice/index.js";
10
+ import pkg from 'ssh2';
11
+ const { Client } = pkg;
12
+
13
+ moment.locale('zh-cn');
14
+
15
+ async function getUploadServeConfig() {
16
+ return await getGlobalConfig();
17
+ }
18
+
19
+ getUploadServeConfig().then(res => {
20
+ const uploadServeConfig = res['uploadServeConfig'];
21
+ const wxServerConfig = res['wxServerConfig'];
22
+
23
+ const isProduction = process.env.VUE_APP_PACK_ENV === "pro"; // 是否是生产环境
24
+
25
+ const lineVersion = process.env.VUE_APP_LINE_VERSION; //线上环境版本(多个显示环境可使用此字段区分)
26
+
27
+ const isNewPro = isProduction && +lineVersion === 2; // 是否是新的线上版本
28
+
29
+ const host = !isNewPro ? uploadServeConfig.host[0] : uploadServeConfig.host[1];
30
+
31
+ const staticPath = !isProduction ? uploadServeConfig.staticPath.dev : uploadServeConfig.staticPath.pro;
32
+
33
+ const onlinePath = `${uploadServeConfig.protocol}://${host}/${staticPath}`
34
+
35
+ const gitInfo = getGitInfo();
36
+
37
+ const server = {
38
+ host, // 服务器ip
39
+ port: "22", // 端口一般默认22
40
+ locaPath: `${process.env.PWD}${uploadServeConfig.locaPath}`, // 本地打包文件的位置
41
+ ...uploadServeConfig.serverOption,
42
+ pathNmae: `${uploadServeConfig.serverOption.pathNmae}${staticPath}` // 上传到服务器的位置
43
+ };
44
+
45
+ const packInfo = {
46
+ ...(uploadServeConfig.baseInfo || {}),
47
+ serverPath: server.pathNmae
48
+ }
49
+
50
+
51
+ function wxMsg(status) {
52
+ return `<div class="gray">${moment().format('lll')}</div><div class="highlight">${uploadServeConfig.projectTitle}${isProduction ? "生产" : "开发"}环境发布${status}</div><div class="normal">commitId:${gitInfo.commitId}</div><div class="normal">commitMsg:${gitInfo.commitMsg}</div><div class="normal">访问地址:${onlinePath}</div>`
53
+ }
54
+
55
+
56
+ cyanLog("本次打包信息 =>", packInfo);
57
+
58
+
59
+
60
+ const spinner = ora(
61
+ "正在发布到" + (isProduction ? "生产" : "开发") + "服务器..."
62
+ );
63
+
64
+ // 创建shell脚本
65
+ const conn = new Client();
66
+
67
+ magentaLog("正在建立连接");
68
+
69
+ conn
70
+ .on("ready", function() {
71
+ log("已连接");
72
+ if (!server.pathNmae) {
73
+ log("连接已关闭");
74
+ conn.end();
75
+ return false;
76
+ }
77
+ // 这里我拼接了放置服务器资源目录的位置 ,首选通过rm -rf删除了这个目录下的文件
78
+ conn.exec("rm -rf " + server.pathNmae + "/*", function(err, stream) {
79
+ warningLog("已删除服务端文件")
80
+ stream.on("close", function(code, signal) {
81
+ log("开始上传");
82
+ spinner.start();
83
+ client.scp(
84
+ server.locaPath,
85
+ {
86
+ host: server.host,
87
+ port: server.port,
88
+ username: server.username,
89
+ password: server.password,
90
+ path: server.pathNmae
91
+ },
92
+ err => {
93
+ spinner.stop();
94
+ if (!err) {
95
+ successLog("Success! 成功发布到" + (isProduction ? "生产" : "开发") + "服务器!", `访问地址====>${onlinePath}`)
96
+ wxNotify({
97
+ msgtype: "textcard",
98
+ textcard: {
99
+ title: "发布成功",
100
+ description: wxMsg('成功'),
101
+ url: onlinePath,
102
+ btntxt: "更多"
103
+ }
104
+ }, wxServerConfig);
105
+ } else {
106
+ errorLog("发布失败.\n", err);
107
+ wxNotify({
108
+ msgtype: "textcard",
109
+ textcard: {
110
+ title: "发布失败",
111
+ description: wxMsg('失败'),
112
+ url: onlinePath,
113
+ btntxt: "更多"
114
+ }
115
+ }, wxServerConfig);
116
+ }
117
+ conn.end(); // 结束命令
118
+ }
119
+ );
120
+ });
121
+ });
122
+ })
123
+ .connect({
124
+ host: server.host,
125
+ port: server.port,
126
+ username: server.username,
127
+ password: server.password
128
+ //privateKey: '' //使用 私钥密钥登录 目前测试服务器不需要用到
129
+ });
130
+ })
@@ -0,0 +1,85 @@
1
+ import findup from "findup-sync";
2
+ import chalk from "chalk";
3
+ import execa from "execa";
4
+
5
+ // 最新 node 核心包的导入写法
6
+ import { fileURLToPath } from 'node:url'
7
+ import { dirname } from 'node:path'
8
+
9
+ export async function getGlobalConfig(type) {
10
+ const name = findup('dyp.config.js', {cwd: process.env.PWD});
11
+ const dypConfig = await import(name);
12
+ if (type) {
13
+ return dypConfig['default'][type];
14
+ }
15
+ return dypConfig['default'];
16
+ }
17
+
18
+ // 首字母大写
19
+ export function strCase(str) {
20
+ return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase());
21
+ }
22
+
23
+ // 获取文件名
24
+ export function getFilename() {
25
+ return fileURLToPath(import.meta.url);
26
+ }
27
+
28
+ // 获取根路径
29
+ export function getDirname() {
30
+ return dirname(fileURLToPath(import.meta.url))
31
+ }
32
+
33
+
34
+ export function log(mainMsg, laterMsg) {
35
+ return chalkLog('blue', mainMsg, laterMsg);
36
+ }
37
+
38
+ export function cyanLog(mainMsg, laterMsg = '') {
39
+ return chalkLog('cyan', mainMsg, laterMsg);
40
+ }
41
+
42
+ export function magentaLog(mainMsg, laterMsg = '') {
43
+ return chalkLog('magenta', mainMsg, laterMsg);
44
+ }
45
+
46
+ export function successLog(mainMsg, laterMsg = '') {
47
+ return chalkLog('green', mainMsg, laterMsg);
48
+ }
49
+
50
+ export function warningLog(mainMsg, laterMsg = '') {
51
+ return chalkLog('yellow', mainMsg, laterMsg);
52
+ }
53
+
54
+ export function errorLog(mainMsg, laterMsg = '') {
55
+ return chalkLog('red', mainMsg, laterMsg);
56
+ }
57
+
58
+ export function chalkLog(chalkType, mainMsg, laterMsg = '') {
59
+ if (!laterMsg) {
60
+ return console.log(chalk[chalkType](`${mainMsg}`));
61
+ }
62
+ return console.log(chalk[chalkType](`${mainMsg}`), laterMsg);
63
+
64
+ }
65
+
66
+
67
+ /**
68
+ * 获取最新 commit 提交信息
69
+ * git 获取提交信息参考:https://www.cnblogs.com/ruiy/p/15904295.html
70
+ * @returns {commitId, commitMsg}
71
+ */
72
+
73
+ export function getGitInfo() {
74
+ const shortCommid = execa.commandSync('git rev-parse --short HEAD');
75
+
76
+ const gitComMsg = `git log --pretty=format:“%s” ${shortCommid.stdout} -1`
77
+
78
+ const { stdout, stderr } = execa.commandSync(gitComMsg);
79
+
80
+ return {
81
+ commitId: shortCommid.stdout,
82
+ commitMsg: stdout
83
+ }
84
+ }
85
+
@@ -0,0 +1,59 @@
1
+ ### 需要的变量
2
+
3
+ ```txt
4
+ WX_COMPANY_ID= 企业ID
5
+ WX_APP_ID= 应用ID
6
+ WX_APP_SECRET= 应用 Secret
7
+
8
+ TIAN_API_KEY= 天行数据 key
9
+ ```
10
+
11
+ <details><summary>点击查看企业微信的注册步骤的详细示例</summary>
12
+
13
+ #### 第一步,注册企业
14
+
15
+ 用电脑打开[企业微信官网](https://work.weixin.qq.com/),注册一个企业。有手机号就可以注册,不用营业执照!不用营业执照!不用营业执照!
16
+
17
+ #### 第二步,创建应用
18
+
19
+ 注册成功后,点「管理企业」进入管理界面,选择「应用管理」 → 「自建」 → 「创建应用」
20
+
21
+ ![创建应用-1](images/qiyewx-2.png)
22
+
23
+ 应用名称随意填,可见范围选择公司名(或指定组织、个人,建议选择全部,然后在代码里指定用户)。
24
+
25
+ ![创建应用-2](images/qiyewx-3.png)
26
+
27
+ 指定成员或组织
28
+
29
+ ![指定范围](images/qiyewx-3-2.png)
30
+
31
+ 创建完成后进入应用详情页,可以得到应用 ID( agentid )①,应用 Secret( secret )②。
32
+
33
+ ![创建应用-3](images/qiyewx-3-1.png)
34
+
35
+ #### 第三步,获取企业 ID
36
+
37
+ 进入「[我的企业](https://work.weixin.qq.com/wework_admin/frame#profile)」页面,拉到最下边,可以得到企业 ID③。
38
+
39
+ ![企业ID](images/qiyewx-6.png)
40
+
41
+ #### 第四步,推送消息到微信
42
+
43
+ 进入「我的企业」 → 「[微信插件](https://work.weixin.qq.com/wework_admin/frame#profile/wxPlugin)」,拉到下边扫描二维码,关注以后即可收到推送的消息。
44
+
45
+ ![第四步](images/qiyewx-4.png)
46
+
47
+ #### 无法接收到消息的异常情况处理
48
+
49
+ PS:如果出现`接口请求正常,企业微信接受消息正常,个人微信无法收到消息`的情况:
50
+
51
+ 1. 进入「我的企业」 → 「微信插件」,拉到最下方,勾选 “允许成员在微信插件中接收和回复聊天消息”
52
+
53
+ ![异常情况-1](images/qiyewx-5.jpg)
54
+
55
+ 2. 在企业微信客户端 「我」 → 「设置」 → 「新消息通知」中关闭 “仅在企业微信中接受消息” 限制条件
56
+
57
+ ![异常情况-2](images/qiyewx-5-1.jpg)
58
+
59
+ </details>
@@ -0,0 +1,45 @@
1
+
2
+
3
+ /**
4
+ * @description 根据企业ID、应用secret 获取token
5
+ * @returns token
6
+ */
7
+ import axios from 'axios';
8
+ const BASE_URL = 'https://qyapi.weixin.qq.com'
9
+
10
+ // 获取token
11
+ export async function getToken({ id, secret }) {
12
+ try {
13
+ const response = await axios({
14
+ url: `${BASE_URL}/cgi-bin/gettoken?corpid=${id}&corpsecret=${secret}`,
15
+ method: 'GET',
16
+ headers: {
17
+ 'Content-Type': 'application/json',
18
+ },
19
+ })
20
+ return response.data.access_token
21
+ }
22
+ catch (error) {
23
+ console.log(error)
24
+ return ''
25
+ }
26
+ }
27
+
28
+ /**
29
+ * 发送消息通知到企业微信
30
+ *
31
+ * api示列: https://developer.work.weixin.qq.com/tutorial/application-message/3
32
+ * api: https://developer.work.weixin.qq.com/document/path/90372
33
+ */
34
+ export const postMsg = async(accessToken, config) => {
35
+ const response = await axios({
36
+ url: `${BASE_URL}/cgi-bin/message/send?access_token=${accessToken}`,
37
+ method: 'POST',
38
+ data: {
39
+ touser: config.touser || '@all',
40
+ ...config,
41
+ },
42
+ })
43
+ return response.data
44
+ }
45
+
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @name WXbot
3
+ * @description 获取环境变量参数,执行微信消息通知函数
4
+ */
5
+ import { getGlobalConfig, errorLog, successLog, } from "../utils/index.js";
6
+ import { getToken, postMsg } from "./api.js";
7
+
8
+ async function getWxServerConfig() {
9
+ return await getGlobalConfig('wxServerConfig');
10
+ }
11
+
12
+ // 主函数
13
+ export async function wxNotify(config, wxServerConfig) {
14
+ let configs= {};
15
+ if (wxServerConfig && Object.keys(wxServerConfig).length !== 0) {
16
+ configs = wxServerConfig;
17
+ } else {
18
+ configs = await getWxServerConfig();
19
+ }
20
+
21
+ const { WX_COMPANY_ID, WX_APP_ID, WX_APP_SECRET } = configs ;
22
+
23
+ try {
24
+ // 获取token
25
+ const accessToken = await getToken({
26
+ id: WX_COMPANY_ID,
27
+ secret: WX_APP_SECRET
28
+ });
29
+
30
+ // 发送消息
31
+ const defaultConfig = {
32
+ msgtype: "text",
33
+ agentid: WX_APP_ID,
34
+ ...config
35
+ };
36
+ const option = { ...defaultConfig, ...config };
37
+ const res = await postMsg(accessToken, option);
38
+ successLog("wx:信息发送成功!", res);
39
+ return true;
40
+ } catch (error) {
41
+ errorLog("wx:信息发送失败!", error);
42
+ return false;
43
+ }
44
+ }
45
+
46
+ // wxNotify({
47
+ // msgtype: "text",
48
+ // text: {
49
+ // content: `${moment().format('lll')}`
50
+ // }
51
+ // });
52
+
53
+ // wxNotify({
54
+ // msgtype: "textcard",
55
+ // textcard: {
56
+ // title: "应用发布提醒",
57
+ // description:
58
+ // '<div class="gray">2016年9月26日</div> <div class="normal">恭喜你抽中iPhone 7一台,领奖码:xxxx</div><div class="highlight">请于2016年10月10日前联系行政同事领取</div>',
59
+ // url: "www.baidu.com",
60
+ // btntxt: "更多"
61
+ // }
62
+ // });