提交 4b30ac7c authored 作者: 王鹏飞's avatar 王鹏飞

refactor: 重构项目

上级 0995a54b
# 查到当前目录就可以了,不用再往下查找
root = true
# 对所有文件制定规范
[*]
charset = utf-8 # 字符编码
end_of_line = lf # 从左往右写
indent_size = 2 # tab键长度2个空格
indent_style = space
insert_final_newline = true # 保存自动加上一个空行
trim_trailing_whitespace = true # 每行最后空格去掉
{
"extends": "standard",
"plugins": [],
"parser": "babel-eslint",
"rules": {
"no-new": "off",
"no-debugger": "off"
},
"globals": {
"window": false,
"$": false,
"webConf": false
}
}
# Dependencies
node_modules/
# Environment variables
.env
.env.local
.env.*.local
# Logs
logs/
*.log
npm-debug.log*
pm2_*.log
# Upload temporary files
upload_tmp/
# OS files
.DS_Store .DS_Store
node_modules Thumbs.db
npm-debug.log
server-dist # IDE
node-dist .vscode/
dist .idea/
dist.zip *.swp
*.swo
# code protect - prevent submit code below
# Build
build/
dist/
ezijing-node-server-1.0.0 / 2019-10-30
======================================
* 追加接口,getInfo 是通过页面授权方式获取 unionID 和 openID
* 对应落地页测试页面 ezijing-pages/src/kelley/mobile-index.html
ezijing-node-server-1.0.0 / 2019-08-28
======================================
* 基于Node,开发的一套 提供 API的接口服务。
* 新增 api/test 接口。目的: 接口测试。
# 项目要求 # ezijing-node-server
* 书写时,每次完成书写,都需要进行代码规范检测,使用指令: `npm run lint`
* 提供的所有新接口 与 修改的接口 都必须 记录在 History.md 中。
* 所有提供的接口,必须在 postMan 中 进行 测试并通过,同时记录在 POSTMAN 中。
### 新增模块 Modern Node.js API server for WeChat services.
* request 引入文件
\ No newline at end of file ## Quick Start
```bash
# Install dependencies
npm install
# Development (with hot reload)
npm run dev
# Production
npm start
```
## Project Structure
```
src/
├── index.js # Entry point
├── app.js # Express app
├── config.js # Configuration
├── lib/ # Shared utilities
├── middleware/ # Global middleware
└── modules/ # Feature modules
├── wechat/ # WeChat SDK
└── wx-chart/ # WeChat chart data
```
## API Endpoints
| Method | Path | Description |
|--------|------|-------------|
| GET | `/health` | Health check |
| POST | `/share/getsignature` | Get WeChat JS-SDK signature |
| POST | `/share/token` | Get share token |
| POST | `/getInfo` | Get WeChat user info |
| GET | `/get/wx-chart/:key` | Get chart value |
| GET | `/set/wx-chart/:key` | Set chart value |
## Environment Variables
```env
NODE_ENV=development
SERVER_PORT=4101
DATA_DIR=../node-server-data
# WeChat config (numbered format)
WX_APPID_1=your_appid
WX_SECRET_1=your_secret
```
## Scripts
- `npm run dev` - Development with hot reload
- `npm start` - Production
- `npm run lint` - ESLint check
- `npm run lint:fix` - ESLint fix
- `npm run deploy:test` - Deploy to test
- `npm run deploy:prod` - Deploy to production
import globals from 'globals'
export default [
{
ignores: ['node_modules/', 'build/', 'dist/'],
},
{
files: ['src/**/*.js'],
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
globals: {
...globals.node,
},
},
rules: {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'no-console': 'off',
'comma-dangle': ['error', 'always-multiline'],
semi: ['error', 'never'],
quotes: ['error', 'single'],
},
},
]
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"baseUrl": ".",
"checkJs": false,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
This source diff could not be displayed because it is too large. You can view the blob instead.
{ {
"name": "nodejs", "name": "ezijing-node-server",
"version": "1.0.0", "version": "2.0.0",
"description": "ezijing-node-server", "description": "ezijing-node-server - Modern Node.js API server",
"main": "index.js", "type": "module",
"main": "src/index.js",
"scripts": { "scripts": {
"lint": "eslint --ext .js server/", "start": "node src/index.js",
"lint:fix": "eslint --fix --ext .js server/", "dev": "cross-env NODE_ENV=development node --watch src/index.js",
"deployTest": "cross-env NODE_ENV=test SERVER_PORT=5101 pm2 start ./server/distRun.js -i 1 -n node-server-api -o upload_tmp/pm2_ezijing-node-server.stdout.log -e upload_tmp/pm2_ezijing-node-server.stderr.log --log-date-format 'YYYY-MM-DD HH:mm Z'", "lint": "eslint src/",
"deploy": "cross-env NODE_ENV=production SERVER_PORT=5101 pm2 start ./server/distRun.js -i 1 -n node-server-api -o /data2/logs/nginx-web/pm2/ezijing-node-server/pm2_ezijing-node-server.stdout.log -e /data2/logs/nginx-web/pm2/ezijing-node-server/pm2_ezijing-node-server.stderr.log --log-date-format 'YYYY-MM-DD HH:mm Z'", "lint:fix": "eslint --fix src/",
"deploy": "cross-env NODE_ENV=production SERVER_PORT=5101 pm2 start src/index.js -n ezijing-node-server",
"reload": "pm2 reload ezijing-node-server" "reload": "pm2 reload ezijing-node-server"
}, },
"author": "ZYX", "author": "King",
"license": "ISC", "license": "ISC",
"eslintIgnore": [ "engines": {
"build/" "node": ">=18.0.0"
], },
"devDependencies": { "devDependencies": {
"babel-core": "^6.26.3", "eslint": "^9.15.0",
"babel-eslint": "^8.2.6", "globals": "^15.12.0",
"eslint": "^4.19.1", "pino-pretty": "^13.0.0"
"eslint-config-standard": "^11.0.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-node": "^6.0.1",
"eslint-plugin-promise": "^3.8.0",
"eslint-plugin-standard": "^3.1.0"
}, },
"dependencies": { "dependencies": {
"@god/node-com": "^2.0.13", "axios": "^1.13.0",
"body-parser": "^1.18.3", "cors": "^2.8.5",
"child_process": "^1.0.2", "cross-env": "^7.0.3",
"cross-env": "^5.2.0", "dotenv": "^16.4.5",
"express": "^4.16.4", "express": "^4.21.1",
"multer": "^1.4.1", "pino": "^9.5.0",
"request": "^2.88.0", "pino-http": "^10.3.0"
"sha1": "^1.1.1"
} }
} }
const state = process.env.NODE_ENV
let conf = {}
if (state === 'test') {
} else if (state === 'production') {
}
module.exports = conf
const fs = require('fs')
const conf = require('../config')
const com = require('@god/node-com')
const Proxy = com.Proxy
const ckeditorUpload = (req, res) => {
/* 文件上传 ckeditor4.5以上返回格式 */
function sendHtml (url, msg) {
res.json({
'uploaded': url ? 1 : 0,
'fileName': 'image',
'url': url,
'error': {
'message': msg
}
})
}
if (req.files.length) {
if (!req.files[0].mimetype.startsWith('image')) { sendHtml('', '文件类型错误,请上传图片'); return }
if (req.files[0].size > 10 * 1024 * 1024) { sendHtml('', '图片大小超限'); return }
req.files[0].fieldname = 'file'
Proxy.reqHttps({
hostname: req.headers.reqhost, // 直接传 host,会被 kong的nginx接管,不能再调用,所以改为自定义host
path: '/api/lms/util/upload-file',
method: 'POST',
data: req.body || {},
query: req.query || {},
files: req.files,
headers: {
'tenant': req.headers.tenant,
'Cookie': req.headers.cookie || '',
'User-Agent': req.headers['user-agent'],
'Content-Type': 'multipart/form-data'
}
}, function (str, obj) {
try {
const _json = JSON.parse(str)
if (_json.success) {
sendHtml(_json.url, '')
} else {
sendHtml('', '上传错误,请重试')
}
} catch (e) {
sendHtml('', '上传错误,请重试1')
}
})
}
}
module.exports = {
ckeditorUpload: ckeditorUpload
}
const fs = require('fs')
// 自带模块 crypto
const crypto = require('crypto')
const path = require('path')
const com = require('@god/node-com')
const Proxy = com.Proxy
const md5 = com.Tool.md5
// [开方平台API](https://open.esign.cn/doc/detail?id=opendoc%2Fpaas_api%2Fvwtg6m&namespace=opendoc%2Fpaas_api)
const appPro1 = {
AppId: '5111635045',
AppSecret: '0f35445117f738c9292c52ad8af94064'
}
// 数据文件必须存 固定地点
const appPro2 = {
AppId: '5111635068',
AppSecret: '596c54b16ea817be0a1952d75e600435',
fileId: 'a830a10b55ff46bebc0edc4cec15ebc0',
// accountId: '1ca237fc1e6a466e878bc2274a9c1cec',
// flowId: '9926fcd1956d41ab8e081c4480562f53'
}
const appTest = {
AppId: '7438837529',
AppSecret: '1994cd14f5f3eb3c8d23b23c4250919e',
fileId: 'a830a10b55ff46bebc0edc4cec15ebc0',
// accountId: '1ca237fc1e6a466e878bc2274a9c1cec',
// flowId: '9926fcd1956d41ab8e081c4480562f53'
}
let _accessToken = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJnSWQiOiIzZWVkMDZkODE1MmQ0YzNlOGMwOGM2ZTQzNjA5ODViZCIsImFwcElkIjoiNTExMTYzNTA2OCIsIm9JZCI6IjhiYTNkMDY0ZDZjYTQ0MThhM2Q0YzNkNTI4MDVlOWJiIiwidGltZXN0YW1wIjoxNjA5NDMxOTg5NTQ4fQ.3F4izbojHlWj80zA4mUcgBZTWJn9IDzQsd-tHQw7cr8'
let _info = {
domain_test: 'smlopenapi.esign.cn',
domain_pro: 'openapi.esign.cn'
}
// const _app = appPro2
// const _domain = _info.domain_pro
const _app = appTest
const _domain = _info.domain_test
const _getAccessToken = (req, res) => {
Proxy.reqHttps({
hostname: _domain,
path: '/v1/oauth2/access_token',
method: 'GET',
data: {},
query: {
appId: _app.AppId,
secret: _app.AppSecret,
grantType: 'client_credentials'
},
headers: {
'Host': _domain
}
}, function (str, obj) {
let jsonErr = { code: 500, message: '_getAccessToken 接口 调用失败' }
let _json = {}
try {
_json = JSON.parse(str)
} catch (e) {
console.log(e)
res.status(200).json(jsonErr)
return
}
if (_json.code !== 0) {
res.status(200).json(_json)
return
}
_accessToken = _json.data.token
res.status(200).json(_json)
})
}
// 需要传一个 file - 暂时只支持单文件
const _getUploadUrl = (req, res) => {
// 暂时先指定 originalname,中文本地服务存在问题
req.files[0].originalname = req.body.name || req.files[0].originalname
// 计算 content-md5
let buffer = fs.readFileSync(req.files[0].path)
let str_md5 = crypto.createHash('md5').update(buffer).digest('hex')
console.log(str_md5)
let str_base64_md5 = Buffer.from(crypto.createHash('md5').update(buffer).digest()).toString('base64')
console.log(str_base64_md5)
// 重命名
fs.renameSync(req.files[0].path, req.files[0].destination + req.files[0].originalname)
req.files[0].path = req.files[0].destination + req.files[0].originalname
Proxy.reqHttps({
hostname: _domain,
path: '/v1/files/getUploadUrl',
method: 'POST',
data: {
accountId: '',
contentMd5: str_base64_md5,
contentType: 'application/octet-stream',
convert2Pdf: /pdf/gi.test(req.files[0].originalname),
fileName: req.files[0].originalname, // req.files[0].originalname 暂时存在一个问题,1.本地服务中文乱码
fileSize: req.files[0].size
},
query: {},
headers: {
'Host': _domain,
'Content-Type': 'application/json; charset=UTF-8',
'X-Tsign-Open-Token': _accessToken,
'X-Tsign-Open-App-Id': _app.AppId
}
}, function (str, obj) {
let jsonErr = { code: 500, message: '_getUploadUrl 接口 第一步调用失败' }
let _json = {}
try {
_json = JSON.parse(str)
} catch (e) {
console.log(e)
res.status(200).json(jsonErr)
return
}
if (_json.code !== 0) {
res.status(200).json(_json)
return
}
// 上一个接口调用没问题,再调用下一个接口
let fileId = _json.data.fileId
console.log(fileId)
_uploadFileFromOctetStream(_json.data.uploadUrl, str_base64_md5, req.files[0].path, (_json) => {
_json.fileId = fileId
_app.fileId = fileId
res.status(200).json(_json)
})
})
}
// 通过 application/octet-stream 方式 上传 文件
const _uploadFileFromOctetStream = (uploadUrl, str_base64_md5, filePath, callback) => {
let buffer = fs.readFileSync(filePath)
console.log(uploadUrl)
Proxy.reqHttps({
hostname: uploadUrl.replace(/http(s|):\/\//gi, '').replace(/\/.*$/gi, ''),
path: uploadUrl.replace(/http(s|):\/\/[^\/]*?\//gi, '/'),
method: 'PUT',
data: Buffer.from(buffer, 'binary'),
query: {},
headers: {
'content-md5': str_base64_md5,
'content-type': 'application/octet-stream',
}
}, function (str, obj) {
let jsonErr = { code: 500, message: '_getUploadUrl 接口 第二步 _uploadFileFromOctetStream 调用失败' }
let _json = {}
try {
_json = JSON.parse(str)
} catch (e) {
console.log(e)
_json = jsonErr
}
callback(_json)
})
}
// 根据 fileId 查询文件上传状态
const _getInfoToUploadFile = (req, res) => {
Proxy.reqHttps({
hostname: _domain,
path: '/v1/files/' + req.body.fileId,
method: 'GET',
data: {},
query: {},
headers: {
'Host': _domain,
'Content-Type': 'application/json; charset=UTF-8',
'X-Tsign-Open-Token': _accessToken,
'X-Tsign-Open-App-Id': _app.AppId
}
}, function (str, obj) {
console.log(str)
// callback(str)
res.status(200).json(JSON.parse(str))
})
}
// 先调用创建用户 再一建发起签署,返回url
const getOneStepUrl = (req, res) => {
_token(req, res, () => {
_createUser(req, res, (accountId) => {
req.accountId = accountId
_signFlowId(req, res, (flowId) => {
req.flowId = flowId
_getUrl(req, res, (_json) => {
res.status(200).json(_json)
})
})
})
})
}
const _token = (req, res, callback) => {
// 获取token
Proxy.reqHttps({
hostname: _domain,
path: '/v1/oauth2/access_token',
method: 'GET',
data: {},
query: {
appId: _app.AppId,
secret: _app.AppSecret,
grantType: 'client_credentials'
},
headers: {
'Host': _domain
}
}, function (str, obj) {
let jsonErr = { code: 500, message: 'getOneStepUrl 接口 第一步 /v1/oauth2/access_token调用失败' }
let _json = {}
try {
_json = JSON.parse(str)
} catch (e) {
console.log(e)
res.status(200).json(jsonErr)
return
}
if (_json.code !== 0) {
res.status(200).json(_json)
return
}
_accessToken = _json.data.token
callback && callback()
})
}
const _createUser = (req, res, callback) => {
// 创建用户
Proxy.reqHttps({
hostname: _domain,
path: '/v1/accounts/createByThirdPartyUserId',
method: 'POST',
data: {
"email": req.body.email || "",
"idNumber": "",
"idType": "",
"mobile": req.body.mobile || "",
"name": req.body.name || "张彦新",
"thirdPartyUserId": req.body.uuid || "17601621100"
},
query: {},
headers: {
'Host': _domain,
'Content-Type': 'application/json; charset=UTF-8',
'X-Tsign-Open-Token': _accessToken,
'X-Tsign-Open-App-Id': _app.AppId
}
}, function (str, obj) {
let jsonErr = { code: 500, message: 'getOneStepUrl 接口 第二步 /v1/accounts/createByThirdPartyUserId调用失败' }
let _json = {}
try {
_json = JSON.parse(str)
} catch (e) {
console.log(e)
res.status(200).json(jsonErr)
return
}
// 账号已存在 - 可以继续签署
if (_json.code !== 0 && _json.code !== 53000000) {
res.status(200).json(_json)
return
}
// accountId 传入,防止高并发时,出现意外
callback && callback(_json.data.accountId)
})
}
const _signFlowId = (req, res, callback) => {
// 一建发起签署
Proxy.reqHttps({
hostname: _domain,
path: '/api/v2/signflows/createFlowOneStep',
method: 'POST',
data: {
"attachments": [],
"copiers": [],
"docs":[{
"fileId": _app.fileId,
"fileName": "",
"filePassword": ""
}],
"flowInfo": {
"autoArchive": true,
"autoInitiate": true,
"businessScene": "Test 一键发起部署",
"flowConfigInfo": {
"noticeDeveloperUrl": "",
"noticeType": "",
"redirectUrl": "",
"signPlatform": ""
},
"initiatorAccountId": "",
"initiatorAuthorizedAccountId": "",
"remark": ""
},
"signers": [{
"platformSign": false,
"signerAccount": {
"authorizedAccountId": "",
"signerAccountId": req.accountId
},
"signfields": [{
"autoExecute": false,
"fileId": _app.fileId,
"signType": 0,
"posBean": {
"posPage": ""
},
"sealId": "",
"sealType": "0, 1",
"signDateBean": {
"format": ""
}
}],
"thirdOrderNo": ""
}]
},
query: {},
headers: {
'Host': _domain,
'Content-Type': 'application/json; charset=UTF-8',
'X-Tsign-Open-Token': _accessToken,
'X-Tsign-Open-App-Id': _app.AppId
}
}, function (str, obj) {
let jsonErr = { code: 500, message: 'getOneStepUrl 接口 第三步 /api/v2/signflows/createFlowOneStep调用失败' }
let _json = {}
try {
_json = JSON.parse(str)
} catch (e) {
console.log(e)
res.status(200).json(jsonErr)
return
}
if (_json.code !== 0) {
res.status(200).json(_json)
return
}
// 每次生成的flowId不一致,防止高并发时出现意外
callback && callback(_json.data.flowId)
})
}
const _getUrl = (req, res, callback) => {
// 获取url
Proxy.reqHttps({
hostname: _domain,
path: '/v1/signflows/' + req.flowId + '/executeUrl',
method: 'GET',
data: {},
query: {
flowId: req.flowId,
accountId: req.accountId
},
headers: {
'Host': _domain,
'Content-Type': 'application/json; charset=UTF-8',
'X-Tsign-Open-Token': _accessToken,
'X-Tsign-Open-App-Id': _app.AppId
}
}, function (str, obj) {
let jsonErr = { code: 500, message: 'getOneStepUrl 接口 第四步 /v1/signflows/{flowId}/executeUrl调用失败' }
let _json = {}
try {
_json = JSON.parse(str)
} catch (e) {
console.log(e)
res.status(200).json(jsonErr)
return
}
if (_json.code !== 0) {
res.status(200).json(_json)
return
}
console.log(_json)
_json.data.flowId = req.flowId
_json.data.accountId = req.accountId
_json.data.fileId = _app.fileId
callback && callback(_json)
})
}
// 获取签署后的文件
const getAfterSignFile = (req, res) => {
Proxy.reqHttps({
hostname: _domain,
path: '/v1/signflows/' + req.body.flowId + '/documents',
method: 'GET',
data: {},
query: {},
headers: {
'Host': _domain,
'Content-Type': 'application/json; charset=UTF-8',
'X-Tsign-Open-Token': _accessToken,
'X-Tsign-Open-App-Id': _app.AppId
}
}, function (str, obj) {
console.log(str)
// callback(str)
res.status(200).json(JSON.parse(str))
})
}
module.exports = {
_getAccessToken: _getAccessToken,
_getUploadUrl: _getUploadUrl,
_getInfoToUploadFile: _getInfoToUploadFile,
getOneStepUrl: getOneStepUrl,
getAfterSignFile: getAfterSignFile
}
var com = require('@god/node-com')
var _ReqHttp = com.Proxy.reqHttp
var _MD5 = com.Tool.md5
var conf = {
AppId: 1257095260,
AppSecret: 'GNSE00361135750782C81E59I5CNQEEK',
StreamAppId: 1400255568,
bizid: '33393',
sessionId: '',
outStreamId: ''
}
/* 根据腾讯云 - 创建混流 */
var getFlow = function (req, res) {
var _time = parseInt(new Date().getTime() / 1000)
var _opt = _getOpt(_time)
_opt.data = _dealCreateData(req, _time)
_opt.headers['Content-Length'] = _opt.data.length
_ReqHttp.req(_opt, function (dataStr, cookieStr) {
debugger
setTimeout(() => {
_concelFlow()
}, 5000)
res.send(conf)
})
}
/* 取消混流 */
var _concelFlow = function (req, res) {
var _time = parseInt(new Date().getTime() / 1000)
var _opt = _getOpt(_time)
_opt.data = _dealCancelData(req, _time)
_opt.headers['Content-Length'] = _opt.data.length
_ReqHttp.req(_opt, function (dataStr, cookieStr) {
debugger
})
}
/* 处理 混流建立 方式 */
var _dealCreateData = function (req, _time) {
conf.sessionId = 'ne4MjXHKFce1yIwNMkKv9QtV6_hy0RoT' || _MD5('zyx_' + _time)
conf.outStreamId = '33393_ca44d26966c309105baca4dbd10bb279' || ('stream_' + conf.sessionId)
console.log(_time, conf.AppId, conf.sessionId, conf.outStreamId, req.body)
return JSON.stringify({
'timestamp': _time, // UNIX 时间戳数
'eventId': _time, // 取随机数即可,标识一次网络请求
'interface': {
'interfaceName': 'Mix_StreamV2', // 固定值
'para': {
'app_id': conf.AppId, // 填写直播 APPID
'interface': 'mix_streamv2.start_mix_stream_advanced', // 固定值
// 'interface': 'mix_streamv2.cancel_mix_stream', // 固定值
'mix_stream_template_id': 0,
'mix_stream_session_id': conf.sessionId, // 标识一次网络请求
'output_stream_id': conf.outStreamId, // 填输出流 ID
'output_stream_type': 0, // 生成新流时,写1
'input_stream_list': [{
// 背景画面
'input_stream_id': '33393_ca44d26966c309105baca4dbd10bb279', // 流 ID
'layout_params': {
'image_layer': 1, // 图层号,背景填1
'image_width': 640,
'image_height': 360
}
}, {
// 小画面1
'input_stream_id': '33393_9806f61c2d2193106b5bad448e80c3d8', // 流 ID
'layout_params': {
'image_layer': 2, // 图层标识号
'image_width': 120, // 画面宽度
'image_height': 90, // 画面高度
'location_x': 10, // x偏移:相对于背景画面左上角的横向偏移
'location_y': 10 // y偏移:相对于背景画面左上角的纵向偏移
}
}]
}
}
})
}
/* 处理 混流取消 方式 */
var _dealCancelData = function (req, _time) {
return JSON.stringify({
'timestamp': _time, // UNIX 时间戳数
'eventId': _time, // 混流事件ID,取时间戳即可
'interface': {
'interfaceName': 'Mix_StreamV2', // 固定值
'para': {
'app_id': conf.AppId,
'interface': 'mix_streamv2.cancel_mix_stream', // 取消混流
// 'interface': 'mix_streamv2.start_mix_stream_advanced', // 取消混流
'mix_stream_session_id': conf.sessionId,
'output_stream_id': conf.outStreamId
}
}
})
}
/* 鉴权 - 返回参数 */
var _getOpt = function (_time) {
/* 定义鉴权 */
var str = 'appid=' + conf.AppId + '&interface=Mix_StreamV2&t=' + (_time + 60) + '&sign=' + _MD5(conf.AppSecret + (_time + 60))
var opt = {
hostname: 'fcgi.video.qcloud.com',
path: '/common_access?' + str,
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
data: {}
}
return opt
}
module.exports = {
getFlow
}
const fs = require('fs')
const path = require('path')
const com = require('@god/node-com')
const Proxy = com.Proxy
const getData = (req, res) => {
Proxy.reqHttps({
hostname: 'lms-api.ezijing.com',
path: '/v2/education/courses/' + req.path.replace(/\/abc\/test\/cba\//gi, ''),
data: {},
query: {},
headers: {
'Host': 'lms-api.ezijing.com',
'tenant': req.headers.tenant,
'token': req.headers.token,
'apikey': req.headers.apikey,
'User-Agent': req.headers['user-agent'],
'Content-Type': 'application/x-www-form-urlencoded'
}
}, function (str, cookieStr) {
console.error('接口读取数据', req.headers.token, str)
res.status(200).json(JSON.parse(str))
})
}
module.exports = {
getData: getData
}
const fs = require('fs')
const path = require('path')
const com = require('@god/node-com')
const Proxy = com.Proxy
const md5 = com.Tool.md5
const use = (req, res) => {
Proxy.reqHttps({
hostname: 'zws-api.ezijing.com',
path: req.baseUrl,
method: req.method,
data: req.body || {},
query: req.query || {},
headers: {
'Host': 'zws-api.ezijing.com',
'Cookie': req.headers.cookie || '',
'apikey': 'CXOIO7njD7o3RScs7MSHhvxeG4hbwX2S',
'User-Agent': req.headers['user-agent'],
'Content-Type': 'application/x-www-form-urlencoded'
}
}, function (str, obj) {
console.error('获取cookie - 123456789', req.headers.cookie)
if (/image/gi.test(obj.resHeader['content-type'])) {
// 这种方式不够高级,还要先生成一个图片文件,能不能直接返回一个文档流
res.header('Content-Type', obj.resHeader['content-type'])
str = str.replace(/^data:image\/\w+;base64,/, "")
let bf = new Buffer(str, 'base64')
fs.writeFileSync('upload_tmp/image.' + md5(bf) + '.png', bf)
fs.createReadStream('upload_tmp/image.' + md5(bf) + '.png').pipe(res)
// 直接返回 base64字符串
// res.header('Content-Type', 'text/plain')
// res.status(200).send(str)
} else {
res.status(200).json(JSON.parse(str))
}
})
}
module.exports = {
use: use
}
// var com = require('@god/node-com')
var callback = function (req, res) {
var _body = req.body || {}
console.log(JSON.stringify(_body))
res.status(200).json({ 'msg': '正常' })
}
module.exports = {
callback: callback
}
var com = require('@god/node-com')
var _MD5 = com.Tool.md5
var isRun = function (req, res) {
var json = JSON.parse(JSON.stringify(req.headers))
res.status(200).json(req.headers)
return
var operate = req.body.type || ''
var apikey = req.headers.apikey || ''
var timestamp = Math.floor(new Date().getTime() / 100000000) + '00000000'
var md5Str = _MD5('godzyx.com' + timestamp)
if (operate === '1') { res.status(200).json({ 'MD5-auth': md5Str }); return }
if (apikey === md5Str) {
res.status(200).json({ 'msg': '数据正在处理' })
} else {
res.status(200).json({ 'msg': '拒绝访问该接口' })
}
}
module.exports = {
isRun: isRun
}
// 这个接口页面,主要处理微信相关的接口
const _request = require('request')
const sha1 = require('sha1')
const _config = {
'weixinsecret': {
'wxd6109d07f6396e5c': 'd80a330735fc82f3fd6aba425481e8fd',
'wxe1849e28e176c67b': '90072420a6b6941b7b8af38afd019b9f',
'wxb9651d97aabb1a06': 'fc40521654d4a0157fb1bdf15dce760f',
'wx451c01d40d090d7a': 'c740445d23458904dd7cc8f2e1bc2601',
'wxb0280dd47666d2e9': 'f5f6e01dfa06da3a29b7fa710a287073',
'wxfe67f37095e08d2f': '689d4d5a5c3b1290e8ba7cc6fe872b28'
}
}
let share_json = { // eslint-disable-line
jsapi_ticket: '', // 上一步获取的 ticket
noncestr: '', // 随机字符串
timestamp: '' // 事件戳
}
// 获取token
function getsignature (req, res) {
let access_token_url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential' // eslint-disable-line
let jsapi_ticket_url = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=' // eslint-disable-line
let get_access_token_url = access_token_url + '&appid=' + req.body.appId + '&secret=' + _config.weixinsecret[req.body.appId] // eslint-disable-line
new Promise((resolve, reject) => {
return _request.get(get_access_token_url, function (error, response, body) {
return error ? reject(error) : resolve(body)
})
}).then((newResult) => {
console.log(11, newResult)
let url = jsapi_ticket_url + JSON.parse(newResult).access_token // eslint-disable-line
// 获取 ticket
return new Promise((resolve, reject) => {
_request.get(url, function (err, rs, body) {
console.log(22, body)
share_json.jsapi_ticket = JSON.parse(body).ticket
share_json.noncestr = Math.random().toString(36).substr(2, 15) // 随机字符串
share_json.timestamp = parseInt(new Date().getTime() / 1000) + '' // 事件戳
share_json.url = encodeURIComponent(req.body.url || req.headers.referer)
let str1 = 'jsapi_ticket=' + share_json.jsapi_ticket + '&noncestr=' + share_json.noncestr + '&timestamp=' + share_json.timestamp + '&url=' + share_json.url
let token = sha1(str1)
return err ? reject(err) : res.send({
url: share_json.url,
ticket: share_json.jsapi_ticket,
token: token, // 上一步获取的 ticket
noncestr: share_json.noncestr, // 随机字符串
timestamp: share_json.timestamp
})
})
})
})
.catch(function (e) {
console.log('有错误')
})
}
// 获取token后分享
const wxGetsignature = function (req, res) {
getsignature(req, res)
}
// 微信分享
const wxShare = function (req, res) {
if (share_json.jsapi_ticket) {
let str1 = 'jsapi_ticket=' + share_json.jsapi_ticket + '&noncestr=' + share_json.noncestr + '&timestamp=' + share_json.timestamp + '&url=' + req.headers.referer
let token = sha1(str1)
return res.send({
token: token, // 上一步获取的 ticket
noncestr: share_json.noncestr, // 随机字符串
timestamp: share_json.timestamp
})
} else {
getsignature(req, res)
}
}
// 获取用户信息
const wxGetInfo = function (req, res) {
let access_token_url = 'https://api.weixin.qq.com/sns/oauth2/access_token?' // eslint-disable-line
let get_userInfo_url = 'https://api.weixin.qq.com/sns/userinfo?' // eslint-disable-line
let get_access_token_url = access_token_url + 'appid=' + req.body.appId + '&secret=' + _config.weixinsecret[req.body.appId] + '&code=' + req.body.code + '&grant_type=authorization_code' // eslint-disable-line
new Promise((resolve, reject) => {
return _request.get(get_access_token_url, function (error, response, body) {
return error ? reject(error) : resolve(body)
})
}).then((newResult) => {
if (JSON.parse(newResult).errmsg) { res.send({ err: '授权获取信息失败', errmsg: JSON.parse(newResult).errmsg }); return }
let url = get_userInfo_url + 'access_token=' + JSON.parse(newResult).access_token + '&openid=' + JSON.parse(newResult).openid + '&lang=zh_CN' // eslint-disable-line
// 获取 ticket
return new Promise((resolve, reject) => {
_request.get(url, function (err, rs, body) {
if (JSON.parse(body).errmsg) { res.send({ err: '获取信息失败', errmsg: JSON.parse(newResult).errmsg }); return }
return err ? reject(err) : res.send(JSON.parse(body))
})
})
}).catch(function (e) {
console.log('有错误')
})
}
module.exports = {
wxGetsignature,
wxShare,
wxGetInfo
}
const fs = require('fs')
const path = require('path')
const com = require('@god/node-com')
const _mkdir = com.Tool.Directory.mkdir
const getTable = (req, res) => {
let path1 = path.resolve(process.cwd(), '../node-server-data/cloud-class.json')
if (!fs.existsSync(path1)) {
_mkdir(path1.replace(/\/[^\/]*?$/gi, ''), () => {
fs.writeFileSync(path1, JSON.stringify({}), 'utf8')
})
res.status(200).json({ 'msg': '文件不存在,创建文件' })
return
}
let _json = JSON.parse(fs.readFileSync(path1).toString() || '{}')
res.status(200).json(_json)
}
const setTable = (req, res) => {
let path1 = path.resolve(process.cwd(), '../node-server-data/cloud-class.json')
if (!fs.existsSync(path1)) {
_mkdir(path1)
res.status(200).json({ 'msg': '文件不存在,创建文件' })
return
}
let _json = JSON.parse(fs.readFileSync(path1).toString() || '{}')
if (req.body.chapterId) {
_json[req.body.chapterId] = req.body.url
} else {
for (let k in req.body) {
_json[k] = req.body[k]
}
}
fs.writeFileSync(path1, JSON.stringify(_json), 'utf8')
res.status(200).json(_json)
}
module.exports = {
getTable: getTable,
setTable: setTable
}
const fs = require('fs')
const path = require('path')
const com = require('@god/node-com')
const _mkdir = com.Tool.Directory.mkdir
const get = (req, res) => {
let path1 = path.resolve(process.cwd(), '../node-server-data/wxchart-success.json')
if (!fs.existsSync(path1)) {
_mkdir(path1.replace(/\/[^\/]*?$/gi, ''), () => {
fs.writeFileSync(path1, JSON.stringify({}), 'utf8')
})
res.status(200).json({ 'msg': '文件不存在,创建文件' })
return
}
let _json = JSON.parse(fs.readFileSync(path1).toString() || '{}')
let keyStr = req.baseUrl.replace(/\/get\/wx-chart\//gi, '')
/* 获取对应值 */
res.status(200).json({ code: _json[keyStr] !== undefined ? _json[keyStr] : -1 })
}
const set = (req, res) => {
let path1 = path.resolve(process.cwd(), '../node-server-data/wxchart-success.json')
if (!fs.existsSync(path1)) {
_mkdir(path1.replace(/\/[^\/]*?$/gi, ''))
res.status(200).json({ 'msg': '文件不存在,创建文件' })
return
}
let _json = JSON.parse(fs.readFileSync(path1).toString() || '{}')
let keyStr = req.baseUrl.replace(/\/set\/wx-chart\//gi, '')
_json[keyStr] = parseInt(req.query.val || 0)
/* 设置对应值 */
fs.writeFileSync(path1, JSON.stringify(_json), 'utf8')
res.status(200).json(_json)
}
module.exports = {
get: get,
set: set
}
const express = require('express')
const app = express()
app.set('port', process.env.SERVER_PORT || 4101)
app.use('/', require('./routes'))
app.set('trust proxy', true)
app.listen(app.get('port'), function () {
console.log('Express server 🌎 listening on:http://localhost:' + app.get('port'))
})
apps:
- script: ./server/server.js
name: vue-todo
# 这个 可以配置 多个 ,如:env_test 等。通过 excute中 指令进行区分
env_production:
NODE_ENV: production
# 使用localhost,而不用0.0.0.0。则外网不能直接通过IP访问这个服务。只能使用127.0.0.1访问,再通过nginx反向代理域名可以访问到当前服务
HOST: localhost
PORT: 8888
- excute: pm2 start pm2.yml --env production
const Router = require('express').Router
const router = Router()
const bodyParser = require('body-parser')
const multer = require('multer')
const upload = multer({ dest: 'upload_tmp/' })
const _test = require('../controller/TestMonitor')
const _wx = require('../controller/WxMonitor')
const _mfm = require('../controller/MixedFlowMonitor')
const _rtmp = require('../controller/RTMPMonitor')
const _sccm = require('../controller/setCloudClassMonitor')
const _nts = require('../controller/NextToSend')
const _pr = require('../controller/ProxyRequest')
const _wxcsm = require('../controller/wxChartSuccessMonitor')
const _cm = require('../controller/CkeditorMonitor')
const _es = require('../controller/ESignMonitor')
router.use(bodyParser.json({ limit: '60mb' }))
router.use(bodyParser.urlencoded({ extended: true, limit: '60mb' }))
router.use(bodyParser.text({ limit: '60mb' }))
router.use((req, res, next) => {
/* 设置允许哪些外域访问此服务器。如果你要发送cookies,不要将其赋值为'*' */
res.header('Access-Control-Allow-Origin', req.headers.origin || '*')
/* 设置允许发送cookies。可选值:true,如果不允许就省略此字段 */
res.header('Access-Control-Allow-Credentials', 'true')
if (req.method === 'OPTIONS') {
/* 设置允许那些方法访问此服务器 */
res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS')
/* 设置允许那些头文件访问此服务器,数组中可以设置一些自定义首部 */
let strArr = ['token', 'tenant', 'cur-json', 'version']
for (let k in req.headers) { strArr.push(k) }
res.header('Access-Control-Allow-Headers', strArr.join(', '))
/* 设置单个接口 OPTIONS 的最大有效时长 */
res.header('Access-Control-Max-Age', 600)
/* 以下设置OPTIONS返回并给空内容 */
res.header('Content-Type', 'text/plain charset=UTF-8')
res.header('Content-Length', 0)
res.status(204).end()
return
}
next()
})
router.post('/api/test', upload.any(), _test.isRun)
router.get('/api/test', _test.isRun)
// 重新获取token 在分享
router.post('/share/getsignature', _wx.wxGetsignature)
// 微信分享
router.post('/share/token', _wx.wxShare)
// 获取用户信息
router.post('/getInfo', _wx.wxGetInfo)
// 云端混流
router.post('/set/mixed-flow', _mfm.getFlow)
// 推流回调
router.post('/get/rtmp/callback', _rtmp.callback)
// 获取云课堂对照表
router.get('/get/cloud-class', _sccm.getTable)
// 设置云课堂对照表 - 添加key-value
router.post('/set/cloud-class', _sccm.setTable)
// 学习系统获取 - 视频课程列表 - 接口转发
router.get('/abc/test/cba/*', _nts.getData)
// 接口转发 - 解决跨域问题
router.use('/v1/pay/wechat/*', _pr.use)
// 支持微信过审 - 接口
router.use('/get/wx-chart/*', _wxcsm.get)
router.use('/set/wx-chart/*', _wxcsm.set)
// 支持 ckeditor 图片上传
router.use('/form/ckeditor-upload*', upload.any(), _cm.ckeditorUpload)
// 对接 e签宝 传入 姓名+手机号 返回 签署url
router.use('/get/sign/access/token', upload.any(), _es._getAccessToken)
router.use('/get/sign/upload/url', upload.any(), _es._getUploadUrl)
router.use('/get/sign/info/upload/file', upload.any(), _es._getInfoToUploadFile)
router.use('/get/sign/url', upload.any(), _es.getOneStepUrl)
router.use('/get/sign/download/url', upload.any(), _es.getAfterSignFile)
router.use('/api/*', upload.any(), (req, res) => { res.send('暂无该接口') })
router.use('*', upload.any(), (req, res) => { res.send('接口未定义') })
module.exports = router
import express from 'express'
import cors from 'cors'
import pinoHttp from 'pino-http'
import logger from './lib/logger.js'
import { notFound, errorHandler } from './middleware/error.js'
import wechatRoutes from './modules/wechat/wechat.routes.js'
import wxChartRoutes from './modules/wx-chart/wx-chart.routes.js'
const app = express()
// Trust proxy
app.set('trust proxy', true)
// Request logging
app.use(pinoHttp({ logger, autoLogging: process.env.NODE_ENV !== 'test' }))
// Middleware
app.use(cors({ credentials: true }))
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
// Health check
app.get('/health', (req, res) => res.json({ status: 'ok', timestamp: Date.now() }))
// Modules
app.use(wechatRoutes)
app.use(wxChartRoutes)
// Error handling
app.use(notFound)
app.use(errorHandler)
export default app
import 'dotenv/config'
import path from 'path'
const config = {
env: process.env.NODE_ENV || 'development',
port: parseInt(process.env.SERVER_PORT || '4101', 10) || 4101,
dataDir: process.env.DATA_DIR || path.resolve(process.cwd(), '../node-server-data'),
wechat: { apps: {} },
}
// Load WeChat config from env
let i = 1
while (process.env[`WX_APPID_${i}`] && process.env[`WX_SECRET_${i}`]) {
config.wechat.apps[process.env[`WX_APPID_${i}`]] = process.env[`WX_SECRET_${i}`]
i++
}
// Fallback
if (Object.keys(config.wechat.apps).length === 0) {
config.wechat.apps = {
wxd6109d07f6396e5c: 'd80a330735fc82f3fd6aba425481e8fd',
wxe1849e28e176c67b: '90072420a6b6941b7b8af38afd019b9f',
wxb9651d97aabb1a06: 'fc40521654d4a0157fb1bdf15dce760f',
wx451c01d40d090d7a: 'c740445d23458904dd7cc8f2e1bc2601',
wxb0280dd47666d2e9: 'f5f6e01dfa06da3a29b7fa710a287073',
wxfe67f37095e08d2f: '689d4d5a5c3b1290e8ba7cc6fe872b28',
}
}
export default config
import app from './app.js'
import config from './config.js'
import logger from './lib/logger.js'
const server = app.listen(config.port, () => {
logger.info({ port: config.port, env: config.env }, 'Server started 🚀')
})
// Graceful shutdown
const shutdown = (signal) => {
logger.info(`${signal} received, shutting down...`)
server.close(() => {
logger.info('Server closed')
process.exit(0)
})
}
process.on('SIGTERM', () => shutdown('SIGTERM'))
process.on('SIGINT', () => shutdown('SIGINT'))
process.on('unhandledRejection', (err) => logger.error({ err }, 'Unhandled Rejection'))
process.on('uncaughtException', (err) => {
logger.error({ err }, 'Uncaught Exception')
process.exit(1)
})
import fs from 'fs/promises'
import { existsSync } from 'fs'
import path from 'path'
import logger from './logger.js'
export const ensureDir = async (filePath) => {
const dir = path.dirname(filePath)
await fs.mkdir(dir, { recursive: true })
}
export const readJson = async (filePath, defaultValue = {}) => {
try {
await ensureDir(filePath)
if (!existsSync(filePath)) {
await fs.writeFile(filePath, JSON.stringify(defaultValue, null, 2), 'utf8')
return defaultValue
}
const content = await fs.readFile(filePath, 'utf8')
return JSON.parse(content || '{}')
} catch (error) {
logger.error({ err: error, filePath }, 'Failed to read JSON file')
throw error
}
}
export const writeJson = async (filePath, data) => {
try {
await ensureDir(filePath)
await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8')
return data
} catch (error) {
logger.error({ err: error, filePath }, 'Failed to write JSON file')
throw error
}
}
import pino from 'pino'
const logger = pino({
level: process.env.LOG_LEVEL || (process.env.NODE_ENV === 'production' ? 'info' : 'debug'),
transport:
process.env.NODE_ENV !== 'production'
? { target: 'pino-pretty', options: { colorize: true, translateTime: 'SYS:standard', ignore: 'pid,hostname' } }
: undefined,
})
export default logger
import logger from '../lib/logger.js'
export const notFound = (req, res) => {
res.status(404).json({
success: false,
error: { message: '接口未定义', path: req.path, method: req.method },
})
}
export const errorHandler = (err, req, res, _next) => {
const statusCode = err.statusCode || err.status || 500
logger.error({ err, url: req.url, method: req.method }, 'Request error')
res.status(statusCode).json({
success: false,
error: {
message: process.env.NODE_ENV === 'production' && statusCode === 500 ? 'Internal Server Error' : err.message,
},
})
}
import * as wechatService from './wechat.service.js'
import logger from '../../lib/logger.js'
export const getSignature = async (req, res, next) => {
try {
const { appId, url } = req.body
if (!appId) return res.status(400).json({ success: false, error: { message: 'appId is required' } })
const ticket = await wechatService.getCachedTicket(appId)
const encodedUrl = encodeURIComponent(url || req.headers.referer || '')
const signature = wechatService.generateSignature(
ticket.jsapi_ticket,
ticket.noncestr,
ticket.timestamp,
encodedUrl
)
res.json({
success: true,
data: {
url: encodedUrl,
ticket: ticket.jsapi_ticket,
token: signature,
noncestr: ticket.noncestr,
timestamp: ticket.timestamp,
},
})
} catch (error) {
logger.error({ err: error }, 'getSignature error')
next(error)
}
}
export const share = async (req, res, next) => {
try {
const { appId } = req.body
if (!appId) return res.status(400).json({ success: false, error: { message: 'appId is required' } })
const ticket = await wechatService.getCachedTicket(appId)
const signature = wechatService.generateSignature(
ticket.jsapi_ticket,
ticket.noncestr,
ticket.timestamp,
req.headers.referer || ''
)
res.json({ success: true, data: { token: signature, noncestr: ticket.noncestr, timestamp: ticket.timestamp } })
} catch (error) {
logger.error({ err: error }, 'share error')
next(error)
}
}
export const getInfo = async (req, res, next) => {
try {
const { appId, code } = req.body
if (!appId || !code)
return res.status(400).json({ success: false, error: { message: 'appId and code are required' } })
const userInfo = await wechatService.getUserInfo(appId, code)
res.json({ success: true, data: userInfo })
} catch (error) {
logger.error({ err: error }, 'getInfo error')
next(error)
}
}
import { Router } from 'express'
import * as controller from './wechat.controller.js'
const router = Router()
router.post('/share/getsignature', controller.getSignature)
router.post('/share/token', controller.share)
router.post('/getInfo', controller.getInfo)
export default router
import crypto from 'crypto'
import axios from 'axios'
import config from '../../config.js'
let ticketCache = { jsapi_ticket: '', noncestr: '', timestamp: '', expiresAt: 0 }
const CACHE_DURATION = 60 * 60 * 1000 // 1 hour
export const getAccessToken = async (appId) => {
const secret = config.wechat.apps[appId]
if (!secret) throw new Error(`WeChat app secret not found for appId: ${appId}`)
const { data } = await axios.get('https://api.weixin.qq.com/cgi-bin/token', {
params: { grant_type: 'client_credential', appid: appId, secret },
})
if (data.errcode) throw new Error(`Failed to get access token: ${data.errmsg}`)
return data.access_token
}
export const getJsapiTicket = async (appId) => {
const accessToken = await getAccessToken(appId)
const { data } = await axios.get('https://api.weixin.qq.com/cgi-bin/ticket/getticket', {
params: { type: 'jsapi', access_token: accessToken },
})
if (data.errcode !== 0) throw new Error(`Failed to get JSAPI ticket: ${data.errmsg}`)
return data.ticket
}
export const getCachedTicket = async (appId) => {
const now = Date.now()
if (ticketCache.jsapi_ticket && ticketCache.expiresAt > now) return ticketCache
const ticket = await getJsapiTicket(appId)
ticketCache = {
jsapi_ticket: ticket,
noncestr: Math.random().toString(36).substr(2, 15),
timestamp: Math.floor(Date.now() / 1000).toString(),
expiresAt: now + CACHE_DURATION,
}
return ticketCache
}
export const generateSignature = (ticket, noncestr, timestamp, url) => {
const str = `jsapi_ticket=${ticket}&noncestr=${noncestr}&timestamp=${timestamp}&url=${url}`
return crypto.createHash('sha1').update(str).digest('hex')
}
export const getUserInfo = async (appId, code) => {
const secret = config.wechat.apps[appId]
if (!secret) throw new Error(`WeChat app secret not found for appId: ${appId}`)
const { data: tokenData } = await axios.get('https://api.weixin.qq.com/sns/oauth2/access_token', {
params: { appid: appId, secret, code, grant_type: 'authorization_code' },
})
if (tokenData.errcode) throw new Error(`Failed to get access token: ${tokenData.errmsg}`)
const { data: userInfo } = await axios.get('https://api.weixin.qq.com/sns/userinfo', {
params: { access_token: tokenData.access_token, openid: tokenData.openid, lang: 'zh_CN' },
})
if (userInfo.errcode) throw new Error(`Failed to get user info: ${userInfo.errmsg}`)
return userInfo
}
import * as service from './wx-chart.service.js'
import logger from '../../lib/logger.js'
export const get = async (req, res, next) => {
try {
const key = req.baseUrl.replace(/\/get\/wx-chart\//gi, '')
if (!key) return res.status(400).json({ success: false, error: { message: 'Key is required' } })
const value = await service.getValue(key)
res.json({ success: true, data: { code: value } })
} catch (error) {
logger.error({ err: error }, 'wx-chart get error')
next(error)
}
}
export const set = async (req, res, next) => {
try {
const key = req.baseUrl.replace(/\/set\/wx-chart\//gi, '')
if (!key) return res.status(400).json({ success: false, error: { message: 'Key is required' } })
const data = await service.setValue(key, req.query.val || 0)
res.json({ success: true, data })
} catch (error) {
logger.error({ err: error }, 'wx-chart set error')
next(error)
}
}
import { Router } from 'express'
import * as controller from './wx-chart.controller.js'
const router = Router()
router.use('/get/wx-chart/*', controller.get)
router.use('/set/wx-chart/*', controller.set)
export default router
import path from 'path'
import config from '../../config.js'
import { readJson, writeJson } from '../../lib/file.js'
const DATA_FILE = path.join(config.dataDir, 'wxchart-success.json')
export const getValue = async (key) => {
const data = await readJson(DATA_FILE, {})
return data[key] !== undefined ? data[key] : -1
}
export const setValue = async (key, value) => {
const data = await readJson(DATA_FILE, {})
data[key] = parseInt(value) || 0
await writeJson(DATA_FILE, data)
return data
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论