[Feature]: 服务端新增eslint (#35)

1. 服务端新增eslint
2. 修复所有的lint问题
3. 优化部署,把build命令放到docker构建中
This commit is contained in:
luch 2023-12-28 14:31:33 +08:00 committed by GitHub
parent 3ec86cbe73
commit 08f3bb0578
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 1578 additions and 1309 deletions

View File

@ -21,12 +21,11 @@ COPY . /xiaoju-survey
RUN npm config set registry https://registry.npmjs.org/
# 安装项目依赖
RUN cd /xiaoju-survey/web && npm install
RUN cd /xiaoju-survey/server && npm install
RUN cd /xiaoju-survey/web && npm install && npm run build
# 构建项目,并把产物推送到服务公共目录
RUN cd /xiaoju-survey/web && npm run build
RUN cd /xiaoju-survey && cp -af ./web/dist/* ./server/src/apps/ui/public/
RUN cd /xiaoju-survey/server && npm install && npm run build
RUN cd /xiaoju-survey && mkdir -p ./build/apps/ui/public/ && cp -af ./web/dist/* ./server/build/apps/ui/public/
# 暴露端口 需要跟server的port一致
EXPOSE 3000

42
server/.eslintrc.json Normal file
View File

@ -0,0 +1,42 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"indent": [
"error",
2
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
],
"space-in-parens": ["error", "never"],
"key-spacing": ["error", { "mode": "strict" }],
"comma-spacing": ["error", { "before": false, "after": true }],
"arrow-spacing": ["error", { "before": true, "after": true }],
"space-before-blocks": 2,
"object-curly-spacing": ["error", "always"]
}
}

View File

@ -5,9 +5,9 @@
"main": "index.js",
"scripts": {
"build": "tsc",
"start:stable": "npm run build && SERVER_ENV=stable node ./build/index.js",
"start:preonline": "npm run build && SERVER_ENV=preonline node ./build/index.js",
"start:online": "npm run build && SERVER_ENV=online node ./build/index.js",
"start:stable": "SERVER_ENV=stable node ./build/index.js",
"start:preonline": "SERVER_ENV=preonline node ./build/index.js",
"start:online": "SERVER_ENV=online node ./build/index.js",
"start": "npm run start:online",
"local": "npx ts-node scripts/run-local.ts",
"dev": "npx ts-node-dev ./src/index.ts"
@ -17,7 +17,11 @@
"@types/koa": "^2.13.8",
"@types/koa-bodyparser": "^4.3.10",
"@types/koa-router": "^7.4.4",
"@types/koa-static": "^4.0.4",
"@typescript-eslint/eslint-plugin": "^6.15.0",
"@typescript-eslint/parser": "^6.15.0",
"cross-env": "^7.0.3",
"eslint": "^8.56.0",
"mongodb-memory-server": "^9.0.1",
"nodemon": "^2.0.20",
"ts-node": "^10.9.2",
@ -26,12 +30,14 @@
},
"dependencies": {
"crypto-js": "^4.2.0",
"glob": "^10.3.10",
"joi": "^17.9.2",
"jsonwebtoken": "^9.0.1",
"koa": "^2.14.2",
"koa-bodyparser": "^4.4.1",
"koa-pino-logger": "^4.0.0",
"koa-router": "^12.0.0",
"koa-static": "^4.0.3",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"mongodb": "^5.7.0",

View File

@ -1,10 +1,10 @@
const config = {
mongo: {
url: process.env.xiaojuSurveyMongoUrl || 'mongodb://localhost:27017',
dbName: 'xiaojuSurvey'
}
}
mongo: {
url: process.env.xiaojuSurveyMongoUrl || 'mongodb://localhost:27017',
dbName: 'xiaojuSurvey'
}
};
export function getConfig() {
return config
return config;
}

View File

@ -1,7 +1,7 @@
import { getConfig } from '../config/index'
import { getConfig } from '../config/index';
import MongoService from '../../../utils/mongoService'
import MongoService from '../../../utils/mongoService';
const config = getConfig()
const config = getConfig();
export const mongo = new MongoService({ url: config.mongo.url, dbName: config.mongo.dbName })
export const mongo = new MongoService({ url: config.mongo.url, dbName: config.mongo.dbName });

View File

@ -1,16 +1,17 @@
import { SurveyServer } from '../../decorator'
import { securityService } from './service/securityService'
import { SurveyApp, SurveyServer } from '../../decorator';
import { securityService } from './service/securityService';
@SurveyApp('/api/security')
export default class Security {
@SurveyServer({ type: 'rpc' })
async isHitKeys({ params, context }: { params: any, context: any }) {
async isHitKeys({ params, context }) {
const data = securityService.isHitKeys({
content: params.content,
dictType: params.dictType,
})
});
return {
result: data,
context, // 上下文主要是传递调用方信息使用比如traceid
}
};
}
}

View File

@ -1,14 +1,14 @@
import { mongo } from '../db/mongo'
import { DICT_TYPE } from '../../../types'
import { participle } from '../utils/index'
import { mongo } from '../db/mongo';
import { DICT_TYPE } from '../../../types';
import { participle } from '../utils/index';
class SecurityService {
async isHitKeys({ content, dictType }: { content: string, dictType: DICT_TYPE }) {
const securityDictModel = await mongo.getCollection({ collectionName: 'securityDict' })
const keywordList = participle({ content })
const hitCount = await securityDictModel.countDocuments({ keyword: { $in: keywordList }, type: dictType })
return hitCount > 0
}
async isHitKeys({ content, dictType }: { content: string, dictType: DICT_TYPE }) {
const securityDictModel = await mongo.getCollection({ collectionName: 'securityDict' });
const keywordList = participle({ content });
const hitCount = await securityDictModel.countDocuments({ keyword: { $in: keywordList }, type: dictType });
return hitCount > 0;
}
}
export const securityService = new SecurityService()
export const securityService = new SecurityService();

View File

@ -1,16 +1,16 @@
export function participle({ content, minLen, maxLen }: { content: string, minLen?: number, maxLen?: number }) {
const keys: Array<string> = []
minLen = minLen || 2
maxLen = maxLen || 13
for (let i = 0; i < content.length; i++) {
let tempStr = content[i]
for (let j = 1; j < maxLen && i + j < content.length; j++) {
tempStr += content[i + j]
if (j >= minLen - 1) {
keys.push(tempStr)
}
}
const keys: Array<string> = [];
minLen = minLen || 2;
maxLen = maxLen || 13;
for (let i = 0; i < content.length; i++) {
let tempStr = content[i];
for (let j = 1; j < maxLen && i + j < content.length; j++) {
tempStr += content[i + j];
if (j >= minLen - 1) {
keys.push(tempStr);
}
}
return keys
}
return keys;
}

View File

@ -1,10 +1,10 @@
const config = {
mongo: {
url: process.env.xiaojuSurveyMongoUrl || 'mongodb://localhost:27017',
dbName: 'xiaojuSurvey',
}
}
mongo: {
url: process.env.xiaojuSurveyMongoUrl || 'mongodb://localhost:27017',
dbName: 'xiaojuSurvey',
}
};
export function getConfig() {
return config
return config;
}

View File

@ -1,6 +1,6 @@
import { getConfig } from '../config/index'
import MongoService from '../../../utils/mongoService'
import { getConfig } from '../config/index';
import MongoService from '../../../utils/mongoService';
const config = getConfig()
const config = getConfig();
export const mongo = new MongoService({ url: config.mongo.url, dbName: config.mongo.dbName })
export const mongo = new MongoService({ url: config.mongo.url, dbName: config.mongo.dbName });

View File

@ -1,184 +1,185 @@
import { SurveyServer } from '../../decorator'
import { Request, Response } from 'koa'
import { surveyService } from './service/surveyService'
import { userService } from './service/userService'
import { surveyHistoryService } from './service/surveyHistoryService'
import { HISTORY_TYPE } from '../../types/index'
import { getValidateValue } from './utils/index'
import * as Joi from 'joi'
import { SurveyApp, SurveyServer } from '../../decorator';
import { surveyService } from './service/surveyService';
import { userService } from './service/userService';
import { surveyHistoryService } from './service/surveyHistoryService';
import { HISTORY_TYPE } from '../../types/index';
import { getValidateValue } from './utils/index';
import * as Joi from 'joi';
@SurveyApp('/api/surveyManage')
export default class SurveyManage {
@SurveyServer({type:'http',method:'get',routerName:'/getBannerData'})
async getBannerData({req,res}:{req:Request, res:Response}) {
const data = await surveyService.getBannerData()
return {
code:200,
data,
}
}
@SurveyServer({type:'http',method:'post',routerName:'/add'})
async add({req,res}:{req:Request, res:Response}) {
const params = getValidateValue(Joi.object({
remark: Joi.string().required(),
questionType: Joi.string().required(),
title: Joi.string().required(),
}).validate(req.body,{allowUnknown: true}));
params.userData = await userService.checkLogin({req})
const addRes = await surveyService.add(params)
return {
code:200,
data: {
id: addRes.pageId,
},
}
}
@SurveyServer({ type: 'http', method: 'get', routerName: '/getBannerData' })
async getBannerData() {
const data = await surveyService.getBannerData();
return {
code: 200,
data,
};
}
@SurveyServer({type:'http',method:'post',routerName:'/update'})
async update({req,res}:{req:Request, res:Response}) {
const surveyParams = getValidateValue(Joi.object({
surveyId: Joi.string().required(),
remark: Joi.string().required(),
title: Joi.string().required(),
}).validate(req.body,{allowUnknown: true}));
const userData = await userService.checkLogin({req})
surveyParams.userData = userData
const data = await surveyService.update(surveyParams)
return {
code:200,
data,
}
}
@SurveyServer({ type: 'http', method: 'post', routerName: '/add' })
async add({ req }) {
const params = getValidateValue(Joi.object({
remark: Joi.string().required(),
questionType: Joi.string().required(),
title: Joi.string().required(),
}).validate(req.body, { allowUnknown: true }));
params.userData = await userService.checkLogin({ req });
const addRes = await surveyService.add(params);
return {
code: 200,
data: {
id: addRes.pageId,
},
};
}
@SurveyServer({type:'http',method:'post',routerName:'/delete'})
async delete({req,res}:{req:Request, res:Response}) {
const surveyParams = getValidateValue(Joi.object({
surveyId: Joi.string().required(),
}).validate(req.body,{allowUnknown: true}));
const userData = await userService.checkLogin({req})
surveyParams.userData = userData
const data = await surveyService.delete(surveyParams)
return {
code:200,
data,
}
}
@SurveyServer({type:'http',method:'get',routerName:'/list'})
async list({req,res}:{req:Request, res:Response}) {
const condition = getValidateValue(Joi.object({
curPage: Joi.number().default(1),
pageSize: Joi.number().default(10),
}).validate(req.query,{allowUnknown: true}));
const userData = await userService.checkLogin({req})
const listRes = await surveyService.list({
pageNum:condition.curPage,
pageSize:condition.pageSize,
userData
})
return {
code:200,
data: listRes,
}
}
@SurveyServer({type:'http',method:'post',routerName:'/saveConf'})
async saveConf({req,res}:{req:Request, res:Response}) {
const surveyData = getValidateValue(Joi.object({
surveyId: Joi.string().required(),
configData: Joi.object().required(),
}).validate(req.body,{allowUnknown: true}));
const userData = await userService.checkLogin({req})
// 保存数据
const saveRes = await surveyService.saveConf(surveyData);
// 保存历史
const historyRes = await surveyHistoryService.addHistory({
surveyId:surveyData.surveyId,
configData:surveyData.configData,
type:HISTORY_TYPE.dailyHis,
userData
});
return {
code:200,
data:{
saveRes,
historyRes
}
}
}
@SurveyServer({ type: 'http', method: 'post', routerName: '/update' })
async update({ req }) {
const surveyParams = getValidateValue(Joi.object({
surveyId: Joi.string().required(),
remark: Joi.string().required(),
title: Joi.string().required(),
}).validate(req.body, { allowUnknown: true }));
const userData = await userService.checkLogin({ req });
surveyParams.userData = userData;
const data = await surveyService.update(surveyParams);
return {
code: 200,
data,
};
}
@SurveyServer({type:'http',method:'get',routerName:'/get'})
async get({req,res}:{req:Request, res:Response}) {
const params = getValidateValue(Joi.object({
surveyId: Joi.string().required(),
}).validate(req.query,{allowUnknown: true}));
const userData = await userService.checkLogin({req})
const data = await surveyService.get({
surveyId:params.surveyId,
userData
})
return {
code:200,
data,
}
}
@SurveyServer({type:'http',method:'get',routerName:'/getHistoryList'})
async getHistoryList({req,res}:{req:Request, res:Response}) {
const historyParams = getValidateValue(Joi.object({
surveyId: Joi.string().required(),
historyType: Joi.string().required(),
}).validate(req.query,{allowUnknown: true}));
const data = await surveyHistoryService.getHistoryList(historyParams)
return {
code:200,
data
@SurveyServer({ type: 'http', method: 'post', routerName: '/delete' })
async delete({ req }) {
const surveyParams = getValidateValue(Joi.object({
surveyId: Joi.string().required(),
}).validate(req.body, { allowUnknown: true }));
const userData = await userService.checkLogin({ req });
surveyParams.userData = userData;
const data = await surveyService.delete(surveyParams);
return {
code: 200,
data,
};
}
@SurveyServer({ type: 'http', method: 'get', routerName: '/list' })
async list({ req }) {
const condition = getValidateValue(Joi.object({
curPage: Joi.number().default(1),
pageSize: Joi.number().default(10),
}).validate(req.query, { allowUnknown: true }));
const userData = await userService.checkLogin({ req });
const listRes = await surveyService.list({
pageNum: condition.curPage,
pageSize: condition.pageSize,
userData
});
return {
code: 200,
data: listRes,
};
}
@SurveyServer({ type: 'http', method: 'post', routerName: '/saveConf' })
async saveConf({ req }) {
const surveyData = getValidateValue(Joi.object({
surveyId: Joi.string().required(),
configData: Joi.object().required(),
}).validate(req.body, { allowUnknown: true }));
const userData = await userService.checkLogin({ req });
// 保存数据
const saveRes = await surveyService.saveConf(surveyData);
// 保存历史
const historyRes = await surveyHistoryService.addHistory({
surveyId: surveyData.surveyId,
configData: surveyData.configData,
type: HISTORY_TYPE.dailyHis,
userData
});
return {
code: 200,
data: {
saveRes,
historyRes
}
}
};
}
@SurveyServer({type:'http',method:'post',routerName:'/publish'})
async publish({req,res}:{req:Request, res:Response}) {
const surveyParams = getValidateValue(Joi.object({
surveyId: Joi.string().required(),
}).validate(req.body,{allowUnknown: true}));
// 鉴权
const userData = await userService.checkLogin({req})
// 发布
surveyParams.userData = userData
const surveyData = await surveyService.publish(surveyParams)
// 保存历史
const historyRes = await surveyHistoryService.addHistory({
surveyId:surveyData.surveyConfRes.pageId,
configData:surveyData.surveyConfRes.code,
type:HISTORY_TYPE.publishHis,
userData
});
return {
code:200,
data:{
...surveyData,
historyRes
}
}
}
@SurveyServer({ type: 'http', method: 'get', routerName: '/get' })
async get({ req }) {
const params = getValidateValue(Joi.object({
surveyId: Joi.string().required(),
}).validate(req.query, { allowUnknown: true }));
const userData = await userService.checkLogin({ req });
const data = await surveyService.get({
surveyId: params.surveyId,
userData
});
return {
code: 200,
data,
};
}
@SurveyServer({type:'http',method:'get',routerName:'/data'})
async data({req,res}:{req:Request, res:Response}) {
const surveyParams = getValidateValue(Joi.object({
surveyId: Joi.string().required(),
isShowSecret:Joi.boolean().default(true), // 默认true就是需要脱敏
page: Joi.number().default(1),
pageSize: Joi.number().default(10),
}).validate(req.query,{allowUnknown: true}));
const userData = await userService.checkLogin({req})
const data = await surveyService.data({
userData,
surveyId:surveyParams.surveyId,
isShowSecret:surveyParams.isShowSecret,
pageNum:surveyParams.page,
pageSize:surveyParams.pageSize,
})
return {
code:200,
data
@SurveyServer({ type: 'http', method: 'get', routerName: '/getHistoryList' })
async getHistoryList({ req }) {
const historyParams = getValidateValue(Joi.object({
surveyId: Joi.string().required(),
historyType: Joi.string().required(),
}).validate(req.query, { allowUnknown: true }));
const data = await surveyHistoryService.getHistoryList(historyParams);
return {
code: 200,
data
};
}
@SurveyServer({ type: 'http', method: 'post', routerName: '/publish' })
async publish({ req }) {
const surveyParams = getValidateValue(Joi.object({
surveyId: Joi.string().required(),
}).validate(req.body, { allowUnknown: true }));
// 鉴权
const userData = await userService.checkLogin({ req });
// 发布
surveyParams.userData = userData;
const surveyData = await surveyService.publish(surveyParams);
// 保存历史
const historyRes = await surveyHistoryService.addHistory({
surveyId: surveyData.surveyConfRes.pageId,
configData: surveyData.surveyConfRes.code,
type: HISTORY_TYPE.publishHis,
userData
});
return {
code: 200,
data: {
...surveyData,
historyRes
}
}
};
}
@SurveyServer({ type: 'http', method: 'get', routerName: '/data' })
async data({ req }) {
const surveyParams = getValidateValue(Joi.object({
surveyId: Joi.string().required(),
isShowSecret: Joi.boolean().default(true), // 默认true就是需要脱敏
page: Joi.number().default(1),
pageSize: Joi.number().default(10),
}).validate(req.query, { allowUnknown: true }));
const userData = await userService.checkLogin({ req });
const data = await surveyService.data({
userData,
surveyId: surveyParams.surveyId,
isShowSecret: surveyParams.isShowSecret,
pageNum: surveyParams.page,
pageSize: surveyParams.pageSize,
});
return {
code: 200,
data
};
}
}

View File

@ -1,37 +1,37 @@
import { mongo } from '../db/mongo'
import { getStatusObject } from '../utils/index'
import { SURVEY_STATUS, HISTORY_TYPE, UserType } from '../../../types/index'
import { mongo } from '../db/mongo';
import { getStatusObject } from '../utils/index';
import { SURVEY_STATUS, HISTORY_TYPE, UserType } from '../../../types/index';
class SurveyHistoryService {
async addHistory(surveyData: { surveyId: string, configData: any, type: HISTORY_TYPE, userData: UserType }) {
const surveyHistory = await mongo.getCollection({ collectionName: 'surveyHistory' });
const surveyHistoryRes = await surveyHistory.insertOne({
curStatus: getStatusObject({ status: SURVEY_STATUS.new }),
pageId: surveyData.surveyId,
type: surveyData.type,
code: {
data: surveyData.configData
},
createDate: Date.now(),
operator: {
_id: surveyData.userData._id,
username: surveyData.userData.username,
}
})
return surveyHistoryRes
}
async addHistory(surveyData: { surveyId: string, configData: unknown, type: HISTORY_TYPE, userData: UserType }) {
const surveyHistory = await mongo.getCollection({ collectionName: 'surveyHistory' });
const surveyHistoryRes = await surveyHistory.insertOne({
curStatus: getStatusObject({ status: SURVEY_STATUS.new }),
pageId: surveyData.surveyId,
type: surveyData.type,
code: {
data: surveyData.configData
},
createDate: Date.now(),
operator: {
_id: surveyData.userData._id,
username: surveyData.userData.username,
}
});
return surveyHistoryRes;
}
async getHistoryList(historyParams: { surveyId: string, historyType: HISTORY_TYPE }) {
const surveyHistory = await mongo.getCollection({ collectionName: 'surveyHistory' });
const surveyHistoryListRes = await surveyHistory.find({
pageId: historyParams.surveyId,
type: historyParams.historyType,
})
.sort({ createDate: -1 })
.limit(100)
.toArray()
return mongo.convertId2StringByList(surveyHistoryListRes)
}
async getHistoryList(historyParams: { surveyId: string, historyType: HISTORY_TYPE }) {
const surveyHistory = await mongo.getCollection({ collectionName: 'surveyHistory' });
const surveyHistoryListRes = await surveyHistory.find({
pageId: historyParams.surveyId,
type: historyParams.historyType,
})
.sort({ createDate: -1 })
.limit(100)
.toArray();
return mongo.convertId2StringByList(surveyHistoryListRes);
}
}
export const surveyHistoryService = new SurveyHistoryService()
export const surveyHistoryService = new SurveyHistoryService();

View File

@ -1,351 +1,356 @@
import { mongo } from '../db/mongo'
import { rpcInvote } from '../../../rpc'
import { SURVEY_STATUS, QUESTION_TYPE, CommonError, UserType, DICT_TYPE } from '../../../types/index'
import { getStatusObject, genSurveyPath, getMapByKey, hanleSensitiveDate } from '../utils/index'
import * as path from "path";
import * as _ from "lodash";
import * as moment from "moment";
import { mongo } from '../db/mongo';
import { rpcInvote } from '../../../rpc';
import { SURVEY_STATUS, QUESTION_TYPE, CommonError, UserType, DICT_TYPE } from '../../../types/index';
import { getStatusObject, genSurveyPath, hanleSensitiveDate } from '../utils/index';
import * as path from 'path';
import * as _ from 'lodash';
import * as moment from 'moment';
import { getFile } from '../utils/index';
import { DataItem } from '../../../types/survey';
class SurveyService {
async checkSecurity({ content, dictType }: { content: string, dictType: DICT_TYPE }) {
const rpcResult = await rpcInvote<any, { result: boolean }>('security.isHitKeys', {
params: { content, dictType },
context: {}
})
return rpcResult.result
}
async checkSecurity({ content, dictType }: { content: string, dictType: DICT_TYPE }) {
const rpcResult = await rpcInvote<unknown, { result: boolean }>('security.isHitKeys', {
params: { content, dictType },
context: {}
});
return rpcResult.result;
}
async getBannerData() {
const bannerConfPath = path.resolve(__dirname, "../template/banner/index.json");
return require(bannerConfPath)
}
async getBannerData() {
const bannerConfPath = path.resolve(__dirname, '../template/banner/index.json');
return require(bannerConfPath);
}
async getCodeData({
questionType,
}: { questionType: QUESTION_TYPE }): Promise<any> {
const baseConfPath = path.resolve(__dirname, "../template/surveyTemplate/templateBase.json");
const templateConfPath = path.resolve(
__dirname,
`../template/surveyTemplate/survey/${questionType}.json`,
);
const baseConf = _.cloneDeep(require(baseConfPath))
const templateConf = _.cloneDeep(require(templateConfPath))
const codeData = _.merge(baseConf, templateConf);
const nowMoment = moment()
codeData.baseConf.begTime = nowMoment.format("YYYY-MM-DD HH:mm:ss")
codeData.baseConf.endTime = nowMoment.add(10, 'years').format("YYYY-MM-DD HH:mm:ss")
return codeData;
}
async getNewSurveyPath() {
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const surveyPath = genSurveyPath()
const surveyPathCount = await surveyMeta.countDocuments({ surveyPath })
if (surveyPathCount > 0) { return await this.getNewSurveyPath() }
return surveyPath
}
async getCodeData({
questionType,
}: { questionType: QUESTION_TYPE }): Promise<unknown> {
const baseConfPath = path.resolve(__dirname, '../template/surveyTemplate/templateBase.json');
const templateConfPath = path.resolve(
__dirname,
`../template/surveyTemplate/survey/${questionType}.json`,
);
async add(surveyMetaInfo: { remark: string, questionType: QUESTION_TYPE, title: string, userData: UserType }) {
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const now = Date.now()
const surveyPath = await this.getNewSurveyPath()
const surveyMetaRes = await surveyMeta.insertOne({
surveyPath,
remark: surveyMetaInfo.remark,
questionType: surveyMetaInfo.questionType,
title: surveyMetaInfo.title,
creator: surveyMetaInfo.userData.username,
owner: surveyMetaInfo.userData.username,
curStatus: getStatusObject({ status: SURVEY_STATUS.new }),
createDate: now,
updateDate: now,
})
const pageId = surveyMetaRes.insertedId.toString()
const surveyConf = await mongo.getCollection({ collectionName: 'surveyConf' });
const surveyConfRes = await surveyConf.insertOne({
pageId,
pageType: surveyMetaInfo.questionType,
curStatus: getStatusObject({ status: SURVEY_STATUS.new }),
code: await this.getCodeData({
questionType: surveyMetaInfo.questionType,
})
})
return {
pageId,
surveyMetaRes,
surveyConfRes
const [baseConfStr, templateConfStr] = await Promise.all([getFile(baseConfPath), getFile(templateConfPath)]);
const baseConf = JSON.parse(baseConfStr);
const templateConf = JSON.parse(templateConfStr);
const codeData = _.merge(baseConf, templateConf);
const nowMoment = moment();
codeData.baseConf.begTime = nowMoment.format('YYYY-MM-DD HH:mm:ss');
codeData.baseConf.endTime = nowMoment.add(10, 'years').format('YYYY-MM-DD HH:mm:ss');
return codeData;
}
async getNewSurveyPath() {
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const surveyPath = genSurveyPath();
const surveyPathCount = await surveyMeta.countDocuments({ surveyPath });
if (surveyPathCount > 0) { return await this.getNewSurveyPath(); }
return surveyPath;
}
async add(surveyMetaInfo: { remark: string, questionType: QUESTION_TYPE, title: string, userData: UserType }) {
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const now = Date.now();
const surveyPath = await this.getNewSurveyPath();
const surveyMetaRes = await surveyMeta.insertOne({
surveyPath,
remark: surveyMetaInfo.remark,
questionType: surveyMetaInfo.questionType,
title: surveyMetaInfo.title,
creator: surveyMetaInfo.userData.username,
owner: surveyMetaInfo.userData.username,
curStatus: getStatusObject({ status: SURVEY_STATUS.new }),
createDate: now,
updateDate: now,
});
const pageId = surveyMetaRes.insertedId.toString();
const surveyConf = await mongo.getCollection({ collectionName: 'surveyConf' });
const surveyConfRes = await surveyConf.insertOne({
pageId,
pageType: surveyMetaInfo.questionType,
curStatus: getStatusObject({ status: SURVEY_STATUS.new }),
code: await this.getCodeData({
questionType: surveyMetaInfo.questionType,
})
});
return {
pageId,
surveyMetaRes,
surveyConfRes
};
}
async update(surveyParams: { surveyId: string, remark: string, title: string, userData: UserType }) {
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const _id = mongo.getObjectIdByStr(surveyParams.surveyId);
const surveyMetaUpdateRes = await surveyMeta.updateOne({
_id,
owner: surveyParams.userData.username,
}, [{
$set: {
remark: surveyParams.remark,
title: surveyParams.title,
updateDate: Date.now(),
}
}, {
$set: {
'curStatus': {
$cond: {
if: {
$eq: ['$curStatus.status', 'new']
},
then: '$curStatus',
else: getStatusObject({ status: SURVEY_STATUS.editing })
}
}
}
}]);
if (surveyMetaUpdateRes.matchedCount < 1) {
throw new CommonError('更新问卷信息失败,问卷不存在或您不是该问卷所有者');
}
return {
surveyMetaUpdateRes
};
}
async update(surveyParams: { surveyId: string, remark: string, title: string, userData: UserType }) {
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const _id = mongo.getObjectIdByStr(surveyParams.surveyId)
const surveyMetaUpdateRes = await surveyMeta.updateOne({
_id,
owner: surveyParams.userData.username,
}, [{
$set: {
remark: surveyParams.remark,
title: surveyParams.title,
updateDate: Date.now(),
}
}, {
$set: {
"curStatus": {
$cond: {
if: {
$eq: ["$curStatus.status", "new"]
},
then: "$curStatus",
else: getStatusObject({ status: SURVEY_STATUS.editing })
}
}
}
}])
if (surveyMetaUpdateRes.matchedCount < 1) {
throw new CommonError("更新问卷信息失败,问卷不存在或您不是该问卷所有者")
}
return {
surveyMetaUpdateRes
}
async delete(surveyParams: { surveyId: string, userData: UserType }) {
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const _id = mongo.getObjectIdByStr(surveyParams.surveyId);
const surveyMetaDeleteRes = await surveyMeta.deleteOne({
_id,
owner: surveyParams.userData.username,
});
if (surveyMetaDeleteRes.deletedCount < 1) {
throw new CommonError('删除问卷失败,问卷已被删除或您不是该问卷所有者');
}
return {
surveyMetaDeleteRes
};
}
async delete(surveyParams: { surveyId: string, userData: UserType }) {
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const _id = mongo.getObjectIdByStr(surveyParams.surveyId)
const surveyMetaDeleteRes = await surveyMeta.deleteOne({
_id,
owner: surveyParams.userData.username,
})
if (surveyMetaDeleteRes.deletedCount < 1) {
throw new CommonError("删除问卷失败,问卷已被删除或您不是该问卷所有者")
}
return {
surveyMetaDeleteRes
}
}
async list(condition: { pageNum: number, pageSize: number, userData: UserType }) {
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const cond = {
owner: condition.userData.username
};
const data = await surveyMeta.find(cond)
.sort({ createDate: -1 })
.limit(condition.pageSize)
.skip((condition.pageNum - 1) * condition.pageSize)
.toArray();
const count = await surveyMeta.countDocuments(cond);
return { data: mongo.convertId2StringByList(data), count };
}
async list(condition: { pageNum: number, pageSize: number, userData: UserType }) {
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const cond = {
owner: condition.userData.username
getListHeadByDataList(dataList) {
const listHead = dataList.map(surveyItem => {
let othersCode;
if (surveyItem.type === 'radio-star') {
const rangeConfigKeys = Object.keys(surveyItem.rangeConfig);
if (rangeConfigKeys.length > 0) {
othersCode = [{ code: `${surveyItem.field}_custom`, option: '填写理由' }];
}
const data = await surveyMeta.find(cond)
.sort({ createDate: -1 })
.limit(condition.pageSize)
.skip((condition.pageNum - 1) * condition.pageSize)
.toArray()
const count = await surveyMeta.countDocuments(cond);
return { data: mongo.convertId2StringByList(data), count }
}
getListHeadByDataList(dataList) {
const listHead = dataList.map(surveyItem => {
let othersCode;
if (surveyItem.type === 'radio-star') {
const rangeConfigKeys = Object.keys(surveyItem.rangeConfig)
if (rangeConfigKeys.length > 0) {
othersCode = [{ code: `${surveyItem.field}_custom`, option: "填写理由" }]
}
} else {
othersCode = (surveyItem.options || [])
.filter(optionItem => optionItem.othersKey)
.map((optionItem) => {
return {
code: optionItem.othersKey,
option: optionItem.text
}
})
}
} else {
othersCode = (surveyItem.options || [])
.filter(optionItem => optionItem.othersKey)
.map((optionItem) => {
return {
field: surveyItem.field,
title: surveyItem.title,
type: surveyItem.type,
othersCode
}
})
listHead.push({
field: 'difTime',
title: '答题耗时(秒)',
type: 'text',
})
listHead.push({
field: 'createDate',
title: '提交时间',
type: 'text',
})
return listHead
code: optionItem.othersKey,
option: optionItem.text
};
});
}
return {
field: surveyItem.field,
title: surveyItem.title,
type: surveyItem.type,
othersCode
};
});
listHead.push({
field: 'difTime',
title: '答题耗时(秒)',
type: 'text',
});
listHead.push({
field: 'createDate',
title: '提交时间',
type: 'text',
});
return listHead;
}
async data(condition: { userData: UserType, surveyId: string, pageNum: number, pageSize: number, isShowSecret: boolean }) {
const surveyObjectId = mongo.getObjectIdByStr(condition.surveyId);
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const surveyMetaData = await surveyMeta.findOne({ _id: surveyObjectId });
if (surveyMetaData.owner !== condition.userData.username) {
throw new CommonError('问卷回收数据列表仅所有人才能打开');
}
const surveyPublish = await mongo.getCollection({ collectionName: 'surveyPublish' });
const publishConf = await surveyPublish.findOne({ pageId: condition.surveyId });
const dataList = publishConf?.code?.dataConf?.dataList || [];
const listHead = this.getListHeadByDataList(dataList);
const dataListMap = _.keyBy(dataList, 'field');
const surveySubmit = await mongo.getCollection({ collectionName: 'surveySubmit' });
const surveySubmitData = await surveySubmit.find({ pageId: condition.surveyId })
.sort({ createDate: -1 })
.limit(condition.pageSize)
.skip((condition.pageNum - 1) * condition.pageSize)
.toArray();
const listBody = surveySubmitData.map((surveySubmitResList) => {
const data = surveySubmitResList.data;
const dataKeys = Object.keys(data);
for (const itemKey of dataKeys) {
if (typeof itemKey !== 'string') { continue; }
if (itemKey.indexOf('data') !== 0) { continue; }
const itemConfigKey = itemKey.split('_')[0];
const itemConfig: DataItem = dataListMap[itemConfigKey];
// 题目删除会出现,数据列表报错
if (!itemConfig) { continue; }
const doSecretData = (data) => {
if (condition.isShowSecret) {
return hanleSensitiveDate(data);
} else {
return data;
}
};
data[itemKey] = doSecretData(data[itemKey]);
// 处理选项
if (itemConfig.type === 'radio-star' && !data[`${itemConfigKey}_custom`]) {
data[`${itemConfigKey}_custom`] = data[`${itemConfigKey}_${data[itemConfigKey]}`];
}
if (!itemConfig?.options?.length) { continue; }
const options = itemConfig.options;
const optionsMap = _.keyBy(options, 'hash');
const getText = e => doSecretData(optionsMap?.[e]?.text || e);
if (Array.isArray(data[itemKey])) {
data[itemKey] = data[itemKey].map(getText);
} else {
data[itemKey] = getText(data[itemKey]);
}
}
return {
...data,
difTime: (surveySubmitResList.difTime / 1000).toFixed(2),
createDate: moment(surveySubmitResList.createDate).format('YYYY-MM-DD HH:mm:ss')
};
});
const total = await surveySubmit.countDocuments({ pageId: condition.surveyId });
return {
total,
listHead,
listBody
};
}
async data(condition: { userData: UserType, surveyId: string, pageNum: number, pageSize: number, isShowSecret: boolean }) {
const surveyObjectId = mongo.getObjectIdByStr(condition.surveyId)
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const surveyMetaData = await surveyMeta.findOne({ _id: surveyObjectId })
if (surveyMetaData.owner !== condition.userData.username) {
throw new CommonError("问卷回收数据列表仅所有人才能打开")
}
const surveyPublish = await mongo.getCollection({ collectionName: 'surveyPublish' });
const publishConf = await surveyPublish.findOne({ pageId: condition.surveyId })
const dataList = publishConf?.code?.dataConf?.dataList || []
const listHead = this.getListHeadByDataList(dataList)
const dataListMap = getMapByKey({ data: dataList, key: 'field' })
const surveySubmit = await mongo.getCollection({ collectionName: 'surveySubmit' });
const surveySubmitData = await surveySubmit.find({ pageId: condition.surveyId })
.sort({ createDate: -1 })
.limit(condition.pageSize)
.skip((condition.pageNum - 1) * condition.pageSize)
.toArray()
const listBody = surveySubmitData.map((surveySubmitResList) => {
const data = surveySubmitResList.data
const dataKeys = Object.keys(data)
for (const itemKey of dataKeys) {
if (typeof itemKey !== 'string') { continue }
if (itemKey.indexOf("data") !== 0) { continue }
const itemConfigKey = itemKey.split("_")[0];
const itemConfig = dataListMap[itemConfigKey];
// 题目删除会出现,数据列表报错
if (!itemConfig) { continue }
const doSecretData = (data) => {
if (itemConfig.isSecret && condition.isShowSecret) {
return hanleSensitiveDate(data)
} else {
return data;
}
}
data[itemKey] = doSecretData(data[itemKey])
// 处理选项
if (itemConfig.type === 'radio-star' && !data[`${itemConfigKey}_custom`]) {
data[`${itemConfigKey}_custom`] = data[`${itemConfigKey}_${data[itemConfigKey]}`]
}
if (!itemConfig?.options?.length) { continue }
const options = itemConfig.options
const optionsMap = getMapByKey({ data: options, key: 'hash' })
const getText = e => doSecretData(optionsMap?.[e]?.text || e);
if (Array.isArray(data[itemKey])) {
data[itemKey] = data[itemKey].map(getText)
} else {
data[itemKey] = getText(data[itemKey])
}
}
return {
...data,
difTime: (surveySubmitResList.difTime / 1000).toFixed(2),
createDate: moment(surveySubmitResList.createDate).format("YYYY-MM-DD HH:mm:ss")
}
})
const total = await surveySubmit.countDocuments({ pageId: condition.surveyId });
return {
total,
listHead,
listBody
}
async get({ surveyId, userData }: { surveyId: string, userData: UserType }) {
const surveyObjectId = mongo.getObjectIdByStr(surveyId);
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const surveyMetaData = await surveyMeta.findOne({ _id: surveyObjectId });
if (!surveyMetaData) {
throw new CommonError('问卷不存在或已被删除');
}
async get({ surveyId, userData }: { surveyId: string, userData: UserType }) {
const surveyObjectId = mongo.getObjectIdByStr(surveyId)
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const surveyMetaData = await surveyMeta.findOne({ _id: surveyObjectId })
if (!surveyMetaData) {
throw new CommonError("问卷不存在或已被删除")
}
if (surveyMetaData.owner !== userData.username) {
throw new CommonError("问卷仅所有人才能打开")
}
const surveyMetaRes = mongo.convertId2StringByDoc(surveyMetaData)
const surveyConf = await mongo.getCollection({ collectionName: 'surveyConf' });
const surveyConfData = await surveyConf.findOne({ pageId: surveyId })
if (!surveyConfData) {
throw new CommonError("问卷配置不存在或已被删除")
}
const surveyConfRes = mongo.convertId2StringByDoc(surveyConfData)
return {
surveyMetaRes,
surveyConfRes
}
if (surveyMetaData.owner !== userData.username) {
throw new CommonError('问卷仅所有人才能打开');
}
async saveConf(surveyData: { surveyId: string, configData: any }) {
const surveyConf = await mongo.getCollection({ collectionName: 'surveyConf' });
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const saveRes = await surveyConf.updateOne({
pageId: surveyData.surveyId
}, {
$set: {
code: surveyData.configData,
}
})
const _id = mongo.getObjectIdByStr(surveyData.surveyId)
surveyMeta.updateOne({
_id,
}, [{
$set: {
"curStatus": {
$cond: {
if: {
$eq: ["$curStatus.status", "new"]
},
then: "$curStatus",
else: getStatusObject({ status: SURVEY_STATUS.editing })
}
}
}
}])
return saveRes
const surveyMetaRes = mongo.convertId2StringByDoc(surveyMetaData);
const surveyConf = await mongo.getCollection({ collectionName: 'surveyConf' });
const surveyConfData = await surveyConf.findOne({ pageId: surveyId });
if (!surveyConfData) {
throw new CommonError('问卷配置不存在或已被删除');
}
const surveyConfRes = mongo.convertId2StringByDoc(surveyConfData);
return {
surveyMetaRes,
surveyConfRes
};
}
async publish({ surveyId, userData }: { surveyId: string, userData: UserType }) {
const surveyObjectId = mongo.getObjectIdByStr(surveyId)
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const surveyConf = await mongo.getCollection({ collectionName: 'surveyConf' });
const surveyMetaRes = await surveyMeta.findOne({ _id: surveyObjectId })
if (!surveyMetaRes) {
throw new CommonError("问卷不存在或已被删除,无法发布")
}
if (surveyMetaRes.owner !== userData.username) {
throw new CommonError("只有问卷的所有者才能发布该问卷")
}
const surveyConfRes = await surveyConf.findOne({ pageId: surveyId })
if (!surveyConfRes) {
throw new CommonError("问卷配置不存在或已被删除,无法发布")
}
const surveyPublish = await mongo.getCollection({ collectionName: 'surveyPublish' });
// 清除id存储发布
delete surveyConfRes._id;
surveyConfRes.title = surveyMetaRes.title
surveyConfRes.curStatus = surveyMetaRes.curStatus
surveyConfRes.surveyPath = surveyMetaRes.surveyPath
const dataList = surveyConfRes?.code?.dataConf?.dataList || []
for (const data of dataList) {
const isDangerKey = await this.checkSecurity({ content: data.title, dictType: DICT_TYPE.danger })
if (isDangerKey) {
throw new CommonError("问卷存在非法关键字,不允许发布")
}
const isSecretKey = await this.checkSecurity({ content: data.title, dictType: DICT_TYPE.secret })
if (isSecretKey) {
data.isSecret = true
}
}
const publishRes = await surveyPublish.updateOne({
pageId: surveyId
}, {
$set: surveyConfRes
}, {
upsert: true //如果不存在则插入
});
const updateMetaRes = await surveyMeta.updateOne({
_id: surveyObjectId
}, {
$set: {
curStatus: getStatusObject({ status: SURVEY_STATUS.published }),
}
})
return {
updateMetaRes,
surveyConfRes,
publishRes
async saveConf(surveyData: { surveyId: string, configData: unknown }) {
const surveyConf = await mongo.getCollection({ collectionName: 'surveyConf' });
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const saveRes = await surveyConf.updateOne({
pageId: surveyData.surveyId
}, {
$set: {
code: surveyData.configData,
}
});
const _id = mongo.getObjectIdByStr(surveyData.surveyId);
surveyMeta.updateOne({
_id,
}, [{
$set: {
'curStatus': {
$cond: {
if: {
$eq: ['$curStatus.status', 'new']
},
then: '$curStatus',
else: getStatusObject({ status: SURVEY_STATUS.editing })
}
}
}
}]);
return saveRes;
}
async publish({ surveyId, userData }: { surveyId: string, userData: UserType }) {
const surveyObjectId = mongo.getObjectIdByStr(surveyId);
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const surveyConf = await mongo.getCollection({ collectionName: 'surveyConf' });
const surveyMetaRes = await surveyMeta.findOne({ _id: surveyObjectId });
if (!surveyMetaRes) {
throw new CommonError('问卷不存在或已被删除,无法发布');
}
if (surveyMetaRes.owner !== userData.username) {
throw new CommonError('只有问卷的所有者才能发布该问卷');
}
const surveyConfRes = await surveyConf.findOne({ pageId: surveyId });
if (!surveyConfRes) {
throw new CommonError('问卷配置不存在或已被删除,无法发布');
}
const surveyPublish = await mongo.getCollection({ collectionName: 'surveyPublish' });
// 清除id存储发布
delete surveyConfRes._id;
surveyConfRes.title = surveyMetaRes.title;
surveyConfRes.curStatus = surveyMetaRes.curStatus;
surveyConfRes.surveyPath = surveyMetaRes.surveyPath;
const dataList = surveyConfRes?.code?.dataConf?.dataList || [];
for (const data of dataList) {
const isDangerKey = await this.checkSecurity({ content: data.title, dictType: DICT_TYPE.danger });
if (isDangerKey) {
throw new CommonError('问卷存在非法关键字,不允许发布');
}
const isSecretKey = await this.checkSecurity({ content: data.title, dictType: DICT_TYPE.secret });
if (isSecretKey) {
data.isSecret = true;
}
}
const publishRes = await surveyPublish.updateOne({
pageId: surveyId
}, {
$set: surveyConfRes
}, {
upsert: true //如果不存在则插入
});
const updateMetaRes = await surveyMeta.updateOne({
_id: surveyObjectId
}, {
$set: {
curStatus: getStatusObject({ status: SURVEY_STATUS.published }),
}
});
return {
updateMetaRes,
surveyConfRes,
publishRes
};
}
}
export const surveyService = new SurveyService()
export const surveyService = new SurveyService();

View File

@ -1,19 +1,19 @@
import { rpcInvote } from '../../../rpc'
import { Request } from 'koa'
import { UserType, CommonError } from '../../../types/index'
import { rpcInvote } from '../../../rpc';
import { Request } from 'koa';
import { UserType, CommonError } from '../../../types/index';
class UserService {
async checkLogin({ req }: { req: Request }) {
if (!req.headers['authorization']) {
throw new CommonError('请先登录', 403)
}
const token = (String(req.headers['authorization']) || '').replace("Bearer ", "")
const rpcResult = await rpcInvote<any, { result: UserType }>('user.getUserByToken', {
params: { token },
context: req
})
return rpcResult.result
async checkLogin({ req }: { req: Request }) {
if (!req.headers['authorization']) {
throw new CommonError('请先登录', 403);
}
const token = (String(req.headers['authorization']) || '').replace('Bearer ', '');
const rpcResult = await rpcInvote<unknown, { result: UserType }>('user.getUserByToken', {
params: { token },
context: req
});
return rpcResult.result;
}
}
export const userService = new UserService()
export const userService = new UserService();

View File

@ -40,7 +40,6 @@
"addressType": 3,
"isAuto": false,
"urlKey": "",
"othersKeyMap": {},
"textRange": {
"min": {
"placeholder": "0",
@ -103,7 +102,6 @@
"exclude": false,
"addressType": 3,
"isAuto": false,
"othersKeyMap": {},
"textRange": {
"min": {
"placeholder": "0",

View File

@ -68,7 +68,6 @@
"valid": "",
"title": "标题1",
"answer": "",
"othersKeyMap": {},
"textRange": {
"min": {
"placeholder": "0",
@ -118,7 +117,6 @@
"type": "radio-star",
"title": "标题2",
"answer": "",
"othersKeyMap": {},
"options": [],
"textRange": {
"min": {

View File

@ -48,7 +48,6 @@
"jumpTo": "",
"startDate": "",
"endDate": "",
"othersKeyMap": {},
"textRange": {
"min": {
"placeholder": "0",
@ -134,7 +133,6 @@
},
"startDate": "",
"endDate": "",
"othersKeyMap": {},
"textRange": {
"min": {
"placeholder": "0",

View File

@ -64,7 +64,6 @@
"addressType": 3,
"isAuto": false,
"urlKey": "",
"othersKeyMap": {},
"textRange": {
"min": {
"placeholder": "0",
@ -122,7 +121,6 @@
},
"star": 5,
"addressType": 3,
"othersKeyMap": {},
"textRange": {
"min": {
"placeholder": "0",

View File

@ -1,37 +1,37 @@
const base58KeysObject = {
"1":0,"2":1,"3":2,"4":3,"5":4,"6":5,"7":6,"8":7,"9":8,
"A":9,"B":10,"C":11,"D":12,"E":13,"F":14,"G":15,"H":16,"J":17,
"K":18,"L":19,"M":20,"N":21,"P":22,"Q":23,"R":24,"S":25,"T":26,
"U":27,"V":28,"W":29,"X":30,"Y":31,"Z":32,
"a":33,"b":34,"c":35,"d":36,"e":37,"f":38,"g":39,"h":40,"i":41,"j":42,
"k":43,"m":44,"n":45,"o":46,"p":47,"q":48,"r":49,"s":50,"t":51,
"u":52,"v":53,"w":54,"x":55,"y":56,"z":57
'1': 0, '2': 1, '3': 2, '4': 3, '5': 4, '6': 5, '7': 6, '8': 7, '9': 8,
'A': 9, 'B': 10, 'C': 11, 'D': 12, 'E': 13, 'F': 14, 'G': 15, 'H': 16, 'J': 17,
'K': 18, 'L': 19, 'M': 20, 'N': 21, 'P': 22, 'Q': 23, 'R': 24, 'S': 25, 'T': 26,
'U': 27, 'V': 28, 'W': 29, 'X': 30, 'Y': 31, 'Z': 32,
'a': 33, 'b': 34, 'c': 35, 'd': 36, 'e': 37, 'f': 38, 'g': 39, 'h': 40, 'i': 41, 'j': 42,
'k': 43, 'm': 44, 'n': 45, 'o': 46, 'p': 47, 'q': 48, 'r': 49, 's': 50, 't': 51,
'u': 52, 'v': 53, 'w': 54, 'x': 55, 'y': 56, 'z': 57
};
const base58Keys = Object.keys(base58KeysObject);
const base58Len = 58n;
export function hex2Base58(hexNum:string):string
{
let base58NumArray =[];
let bigHexNumber = BigInt(`0x${hexNum}`);
while (bigHexNumber>=58n)
{
base58NumArray.unshift(base58Keys[(bigHexNumber % base58Len).toString()]);
bigHexNumber = bigHexNumber / base58Len;
}
base58NumArray.unshift(base58Keys[bigHexNumber.toString()]);
return base58NumArray.join('');
const base58NumArray =[];
let bigHexNumber = BigInt(`0x${hexNum}`);
while (bigHexNumber>=58n)
{
base58NumArray.unshift(base58Keys[(bigHexNumber % base58Len).toString()]);
bigHexNumber = bigHexNumber / base58Len;
}
base58NumArray.unshift(base58Keys[bigHexNumber.toString()]);
return base58NumArray.join('');
}
export function base582Hex(base58Num:string):string
{
let base58NumArray =base58Num.split('');
let big58Number = 0n;
let len = base58NumArray.length;
for(let i = 1;i<=len;i++)
{
let big58NumberTemp = BigInt(base58KeysObject[base58NumArray[len-i]])*(base58Len** BigInt(i-1));
big58Number += big58NumberTemp;
}
return big58Number.toString(16);
const base58NumArray =base58Num.split('');
let big58Number = 0n;
const len = base58NumArray.length;
for(let i = 1;i<=len;i++)
{
const big58NumberTemp = BigInt(base58KeysObject[base58NumArray[len-i]])*(base58Len** BigInt(i-1));
big58Number += big58NumberTemp;
}
return big58Number.toString(16);
}

View File

@ -1,8 +1,9 @@
import { SURVEY_STATUS, CommonError } from '../../../types/index'
import { hex2Base58 } from './base58'
import * as Joi from 'joi'
import { SURVEY_STATUS, CommonError } from '../../../types/index';
import { hex2Base58 } from './base58';
import * as Joi from 'joi';
import * as fs from 'fs';
export function getStatusObject({status}:{status:SURVEY_STATUS}) {
export function getStatusObject({ status }: { status: SURVEY_STATUS }) {
return {
status,
id: status,
@ -10,39 +11,51 @@ export function getStatusObject({status}:{status:SURVEY_STATUS}) {
};
}
export function getValidateValue<T=any>(validationResult:Joi.ValidationResult<T>):T {
if(validationResult.error) {
throw new CommonError(validationResult.error.details.map(e=>e.message).join())
export function getValidateValue<T = unknown>(validationResult: Joi.ValidationResult<T>): T {
if (validationResult.error) {
throw new CommonError(validationResult.error.details.map(e => e.message).join());
}
return validationResult.value;
}
export function genSurveyPath() {
return hex2Base58(process.hrtime.bigint().toString(16))
return hex2Base58(process.hrtime.bigint().toString(16));
}
export function getMapByKey({data,key}:{data:Array<any>,key:string}) {
const datamap = {}
for(const item of data) {
datamap[item[key]] = item
}
return datamap
}
export function hanleSensitiveDate(value:string = ''): string {
export function hanleSensitiveDate(value: string = ''): string {
if (!value) {
return '*'
return '*';
}
let str = '' + value
let str = '' + value;
if (str.length === 1) {
str = '*'
str = '*';
}
if (str.length === 2) {
str = str[0] + '*'
str = str[0] + '*';
}
if (str.length >= 3) {
str = str[0] + '***' + str.slice(str.length - 1)
str = str[0] + '***' + str.slice(str.length - 1);
}
return str
}
return str;
}
export const getFile = function(path, { encoding }: { encoding } = { encoding: 'utf-8' }): Promise<string> {
return new Promise((resolve, reject) => {
fs.stat(path, err => {
if (!err) {
fs.readFile(path, { encoding }, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data.toString());
}
});
} else {
reject(err);
}
});
});
};

View File

@ -1,17 +1,17 @@
const config = {
mongo:{
url: process.env.xiaojuSurveyMongoUrl ||'mongodb://localhost:27017',
dbName:'xiaojuSurvey',
},
session:{
expireTime: parseInt(process.env.xiaojuSurveySessionExpireTime) || 8*3600*1000
},
encrypt:{
type: process.env.xiaojuSurveyEncryptType ||'aes',
aesCodelength: parseInt(process.env.xiaojuSurveyAesCodelength) || 10 //aes密钥长度
}
}
mongo: {
url: process.env.xiaojuSurveyMongoUrl ||'mongodb://localhost:27017',
dbName: 'xiaojuSurvey',
},
session: {
expireTime: parseInt(process.env.xiaojuSurveySessionExpireTime) || 8*3600*1000
},
encrypt: {
type: process.env.xiaojuSurveyEncryptType ||'aes',
aesCodelength: parseInt(process.env.xiaojuSurveyAesCodelength) || 10 //aes密钥长度
}
};
export function getConfig() {
return config
return config;
}

View File

@ -1,6 +1,6 @@
import { getConfig } from '../config/index'
import MongoService from '../../../utils/mongoService'
import { getConfig } from '../config/index';
import MongoService from '../../../utils/mongoService';
const config = getConfig()
const config = getConfig();
export const mongo = new MongoService({ url: config.mongo.url, dbName: config.mongo.dbName })
export const mongo = new MongoService({ url: config.mongo.url, dbName: config.mongo.dbName });

View File

@ -1,52 +1,52 @@
import { SurveyServer } from '../../decorator'
import { Request, Response } from 'koa'
import { surveySubmitService } from './service/surveySubmitService'
import { surveyPublishService } from './service/surveyPublishService'
import { getValidateValue } from './utils/index'
import { checkSign } from './utils/checkSign'
import * as Joi from 'joi'
import { SurveyApp, SurveyServer } from '../../decorator';
import { surveySubmitService } from './service/surveySubmitService';
import { surveyPublishService } from './service/surveyPublishService';
import { getValidateValue } from './utils/index';
import { checkSign } from './utils/checkSign';
import * as Joi from 'joi';
@SurveyApp('/api/surveyPublish')
export default class SurveyPublish {
// 获取发布配置
@SurveyServer({ type: 'http', method: 'get', routerName: '/getSurveyPublish' })
async getSurveyPublish({ req, res }: { req: Request, res: Response }) {
async getSurveyPublish({ req }) {
const surveySubmitData = getValidateValue(Joi.object({
surveyPath: Joi.string().required(),
}).validate(req.query, { allowUnknown: true }));
const data = await surveyPublishService.get(surveySubmitData)
const data = await surveyPublishService.get(surveySubmitData);
return {
code: 200,
data: data.surveyPublishRes,
}
};
}
// 获取投票
@SurveyServer({ type: 'http', method: 'get', routerName: '/queryVote' })
async queryVote({ req, res }: { req: Request, res: Response }) {
async queryVote({ req }) {
const params = getValidateValue(Joi.object({
surveyPath: Joi.string().required(),
voteKeyList: Joi.string().required(),
}).validate(req.query, { allowUnknown: true }));
params.voteKeyList = params.voteKeyList.split(',')
const data = await surveyPublishService.queryVote(params)
params.voteKeyList = params.voteKeyList.split(',');
const data = await surveyPublishService.queryVote(params);
return {
code: 200,
data: data,
}
};
}
@SurveyServer({ type: 'http', method: 'get', routerName: '/getEncryptInfo' })
async getEncryptInfo({ req, res }: { req: Request, res: Response }) {
const data = await surveySubmitService.getEncryptInfo()
async getEncryptInfo() {
const data = await surveySubmitService.getEncryptInfo();
return {
code: 200,
data: data,
}
};
}
// 提交问卷
@SurveyServer({ type: 'http', method: 'post', routerName: '/submit' })
async submit({ req, res }: { req: Request, res: Response }) {
async submit({ req }) {
// 检查签名
checkSign(req.body)
checkSign(req.body);
// 校验参数
const surveySubmitData = getValidateValue(Joi.object({
surveyPath: Joi.string().required(),
@ -54,10 +54,10 @@ export default class SurveyPublish {
encryptType: Joi.string(),
sessionId: Joi.string(),
}).validate(req.body, { allowUnknown: true }));
await surveySubmitService.submit({ surveySubmitData })
await surveySubmitService.submit({ surveySubmitData });
return {
code: 200,
msg: "提交成功",
}
msg: '提交成功',
};
}
}

View File

@ -1,56 +1,66 @@
import { mongo } from '../db/mongo'
import { mongo } from '../db/mongo';
import { KeyStore } from '../../../types/keyStore';
// 该服务用于模拟redis
class SurveyKeyStoreService {
getKeyStoreResult(surveyKeyStoreData: Array<any>) {
const surveyKeyStoreReult = {}
for (const surveyKeyStoreItem of surveyKeyStoreData) {
surveyKeyStoreReult[surveyKeyStoreItem.key] = surveyKeyStoreItem.data
}
return surveyKeyStoreReult
getKeyStoreResult(surveyKeyStoreData: Array<KeyStore>) {
const surveyKeyStoreReult = {};
for (const surveyKeyStoreItem of surveyKeyStoreData) {
surveyKeyStoreReult[surveyKeyStoreItem.key] = surveyKeyStoreItem.data;
}
return surveyKeyStoreReult;
}
async set({ surveyPath, key, data, type }) {
const surveyKeyStore = await mongo.getCollection({ collectionName: 'surveyKeyStore' });
const setResult = await surveyKeyStore.updateOne({
key,
surveyPath,
type
}, {
$set: {
key,
surveyPath,
type,
data,
createDate: Date.now(),
updateDate: Date.now(),
}
}, {
upsert: true //如果不存在则插入
})
return setResult
}
async set({ surveyPath, key, data, type }) {
const surveyKeyStore = await mongo.getCollection({ collectionName: 'surveyKeyStore' });
const setResult = await surveyKeyStore.updateOne({
key,
surveyPath,
type
}, {
$set: {
key,
surveyPath,
type,
data,
createDate: Date.now(),
updateDate: Date.now(),
}
}, {
upsert: true //如果不存在则插入
});
return setResult;
}
async get({ surveyPath, key, type }) {
const surveyKeyStore = await mongo.getCollection({ collectionName: 'surveyKeyStore' });
const surveyKeyStoreData = await surveyKeyStore.findOne({
key,
surveyPath,
type
})
return surveyKeyStoreData?.data
}
async get({ surveyPath, key, type }) {
const surveyKeyStore = await mongo.getCollection({ collectionName: 'surveyKeyStore' });
const surveyKeyStoreData = await surveyKeyStore.findOne({
key,
surveyPath,
type
});
return surveyKeyStoreData?.data;
}
async getAll({ surveyPath, keyList, type }) {
const surveyKeyStore = await mongo.getCollection({ collectionName: 'surveyKeyStore' });
const surveyKeyStoreData = await surveyKeyStore.find({
key: { $in: keyList },
surveyPath,
type
}).toArray()
return this.getKeyStoreResult(surveyKeyStoreData)
}
async getAll({ surveyPath, keyList, type }) {
const surveyKeyStore = await mongo.getCollection({ collectionName: 'surveyKeyStore' });
const res = await surveyKeyStore.find({
key: { $in: keyList },
surveyPath,
type
}).toArray();
const surveyKeyStoreData : Array<KeyStore> = res.map(doc => {
return {
key: doc.key,
surveyPath: doc.surveyPath,
type: doc.type,
data: doc.data,
createDate: doc.createDate,
updateDate: doc.updateDate,
};
});
return this.getKeyStoreResult(surveyKeyStoreData);
}
}
export const surveyKeyStoreService = new SurveyKeyStoreService()
export const surveyKeyStoreService = new SurveyKeyStoreService();

View File

@ -1,31 +1,31 @@
import { mongo } from '../db/mongo'
import { surveyKeyStoreService } from './surveyKeyStoreService'
import { CommonError } from '../../../types/index'
import { mongo } from '../db/mongo';
import { surveyKeyStoreService } from './surveyKeyStoreService';
import { CommonError } from '../../../types/index';
class SurveyPublishService {
async get({ surveyPath }: { surveyPath: string }) {
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const surveyMetaData = await surveyMeta.findOne({ surveyPath })
if (!surveyMetaData) {
throw new CommonError('该问卷已不存在')
}
const surveyMetaRes = mongo.convertId2StringByDoc(surveyMetaData)
const surveyPublish = await mongo.getCollection({ collectionName: 'surveyPublish' });
const surveyPublishData = await surveyPublish.findOne({ pageId: surveyMetaRes._id.toString() }, { sort: { createDate: -1 } })
if (!surveyPublishData) {
throw new CommonError('该问卷未发布')
}
const surveyPublishRes = mongo.convertId2StringByDoc(surveyPublishData)
return {
surveyMetaRes,
surveyPublishRes
}
async get({ surveyPath }: { surveyPath: string }) {
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const surveyMetaData = await surveyMeta.findOne({ surveyPath });
if (!surveyMetaData) {
throw new CommonError('该问卷已不存在');
}
const surveyMetaRes = mongo.convertId2StringByDoc(surveyMetaData);
const surveyPublish = await mongo.getCollection({ collectionName: 'surveyPublish' });
const surveyPublishData = await surveyPublish.findOne({ pageId: surveyMetaRes._id.toString() }, { sort: { createDate: -1 } });
if (!surveyPublishData) {
throw new CommonError('该问卷未发布');
}
const surveyPublishRes = mongo.convertId2StringByDoc(surveyPublishData);
return {
surveyMetaRes,
surveyPublishRes
};
}
async queryVote({ surveyPath, voteKeyList }: { surveyPath: string, voteKeyList: Array<string> }) {
return await surveyKeyStoreService.getAll({ surveyPath, keyList: voteKeyList, type: 'vote' })
}
async queryVote({ surveyPath, voteKeyList }: { surveyPath: string, voteKeyList: Array<string> }) {
return await surveyKeyStoreService.getAll({ surveyPath, keyList: voteKeyList, type: 'vote' });
}
}
export const surveyPublishService = new SurveyPublishService()
export const surveyPublishService = new SurveyPublishService();

View File

@ -1,130 +1,132 @@
import { mongo } from '../db/mongo'
import { getStatusObject, getMapByKey, randomCode } from '../utils/index'
import { SURVEY_STATUS, CommonError } from '../../../types/index'
import { surveyKeyStoreService } from './surveyKeyStoreService'
import { getConfig } from '../config/index'
import * as CryptoJS from 'crypto-js'
import { mongo } from '../db/mongo';
import { getStatusObject, randomCode } from '../utils/index';
import { SURVEY_STATUS, CommonError } from '../../../types/index';
import { surveyKeyStoreService } from './surveyKeyStoreService';
import { getConfig } from '../config/index';
import * as CryptoJS from 'crypto-js';
import * as aes from 'crypto-js/aes';
import * as moment from 'moment'
const config = getConfig()
import * as moment from 'moment';
import { keyBy } from 'lodash';
const config = getConfig();
class SurveySubmitService {
async addSessionData(data) {
const surveySession = await mongo.getCollection({ collectionName: 'surveySession' });
const surveySessionRes = await surveySession.insertOne({
data,
expireDate: Date.now() + config.session.expireTime
})
return {
sessionId: surveySessionRes.insertedId.toString(),
...data
}
}
async addSessionData(data) {
const surveySession = await mongo.getCollection({ collectionName: 'surveySession' });
const surveySessionRes = await surveySession.insertOne({
data,
expireDate: Date.now() + config.session.expireTime
});
return {
sessionId: surveySessionRes.insertedId.toString(),
...data
};
}
async getSessionData(sessionId) {
const surveySession = await mongo.getCollection({ collectionName: 'surveySession' });
const sessionObjectId = mongo.getObjectIdByStr(sessionId)
const surveySessionRes = await surveySession.findOne({ _id: sessionObjectId })
await surveySession.deleteMany({ expireDate: { $lt: Date.now() } })
return { sessionId, data: surveySessionRes.data }
}
async getSessionData(sessionId) {
const surveySession = await mongo.getCollection({ collectionName: 'surveySession' });
const sessionObjectId = mongo.getObjectIdByStr(sessionId);
const surveySessionRes = await surveySession.findOne({ _id: sessionObjectId });
await surveySession.deleteMany({ expireDate: { $lt: Date.now() } });
return { sessionId, data: surveySessionRes.data };
}
async getEncryptInfo() {
const encryptType = config.encrypt.type
let data = {}
if (encryptType === 'aes') {
data = await this.addSessionData({
code: randomCode(config.encrypt.aesCodelength)
})
}
return {
encryptType,
data
}
async getEncryptInfo() {
const encryptType = config.encrypt.type;
let data = {};
if (encryptType === 'aes') {
data = await this.addSessionData({
code: randomCode(config.encrypt.aesCodelength)
});
}
return {
encryptType,
data
};
}
async submit({ surveySubmitData }: { surveySubmitData: any }) {
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const surveyMetaRes = mongo.convertId2StringByDoc(
await surveyMeta.findOne({ surveyPath: surveySubmitData.surveyPath })
)
if (!surveyMetaRes) {
throw new CommonError('该问卷已不存在,无法提交')
}
const pageId = surveyMetaRes._id.toString()
const surveyPublish = await mongo.getCollection({ collectionName: 'surveyPublish' });
const publishConf = await surveyPublish.findOne({ pageId })
const surveySubmit = await mongo.getCollection({ collectionName: 'surveySubmit' });
if (surveySubmitData.encryptType === 'base64') {
surveySubmitData.data = JSON.parse(decodeURIComponent(Buffer.from(surveySubmitData.data, "base64").toString()))
} else if (surveySubmitData.encryptType === 'aes') {
const sessionData = await this.getSessionData(surveySubmitData.sessionId)
surveySubmitData.data = JSON.parse(decodeURIComponent(aes.decrypt(surveySubmitData.data, sessionData.data.code).toString(CryptoJS.enc.Utf8)))
} else {
surveySubmitData.data = JSON.parse(surveySubmitData.data)
}
// 提交时间限制
const begTime = publishConf?.code?.baseConf?.begTime || 0
const endTime = publishConf?.code?.baseConf?.endTime || 0
if (begTime && endTime) {
const nowStamp = Date.now()
const begTimeStamp = new Date(begTime).getTime()
const endTimeStamp = new Date(endTime).getTime()
if (nowStamp < begTimeStamp || nowStamp > endTimeStamp) {
throw new CommonError('不在答题有效期内')
}
}
// 提交时间段限制
const answerBegTime = publishConf?.code?.baseConf?.answerBegTime || "00:00:00"
const answerEndTime = publishConf?.code?.baseConf?.answerEndTime || "23:59:59"
if (answerBegTime && answerEndTime) {
const nowStamp = Date.now()
const ymdString = moment().format('YYYY-MM-DD');
const answerBegTimeStamp = new Date(`${ymdString} ${answerBegTime}`).getTime()
const answerEndTimeStamp = new Date(`${ymdString} ${answerEndTime}`).getTime()
if (nowStamp < answerBegTimeStamp || nowStamp > answerEndTimeStamp) {
throw new CommonError('不在答题时段内')
}
}
// 提交总数限制
const tLimit = publishConf?.code?.baseConf?.tLimit || 0
if (tLimit > 0) {
// 提升性能可以使用redis
let nowSubmitCount = await surveySubmit.countDocuments({ surveyPath: surveySubmitData.surveyPath }) || 0
if (nowSubmitCount >= tLimit) {
throw new CommonError('超出提交总数限制')
}
}
// 投票信息保存
const dataList = publishConf?.code?.dataConf?.dataList || []
const dataListMap = getMapByKey({ data: dataList, key: 'field' })
const surveySubmitDataKeys = Object.keys(surveySubmitData.data)
for (const field of surveySubmitDataKeys) {
const configData = dataListMap[field]
if (configData && /vote/.exec(configData.type)) {
const voteData = (await surveyKeyStoreService.get({ surveyPath: surveySubmitData.surveyPath, key: field, type: 'vote' })) || { total: 0 }
const fields = Array.isArray(surveySubmitData.data[field]) ? surveySubmitData.data[field] : [surveySubmitData.data[field]]
for (const field of fields) {
voteData.total++;
if (!voteData[field]) {
voteData[field] = 1
} else {
voteData[field]++;
}
}
await surveyKeyStoreService.set({ surveyPath: surveySubmitData.surveyPath, key: field, data: voteData, type: 'vote' })
}
}
// 提交问卷
const surveySubmitRes = await surveySubmit.insertOne({
...surveySubmitData,
pageId,
curStatus: getStatusObject({ status: SURVEY_STATUS.new }),
createDate: Date.now()
})
return surveySubmitRes
async submit({ surveySubmitData }) {
const surveyMeta = await mongo.getCollection({ collectionName: 'surveyMeta' });
const surveyMetaRes = mongo.convertId2StringByDoc(
await surveyMeta.findOne({ surveyPath: surveySubmitData.surveyPath })
);
if (!surveyMetaRes) {
throw new CommonError('该问卷已不存在,无法提交');
}
const pageId = surveyMetaRes._id.toString();
const surveyPublish = await mongo.getCollection({ collectionName: 'surveyPublish' });
const publishConf = await surveyPublish.findOne({ pageId });
const surveySubmit = await mongo.getCollection({ collectionName: 'surveySubmit' });
if (surveySubmitData.encryptType === 'base64') {
surveySubmitData.data = JSON.parse(decodeURIComponent(Buffer.from(surveySubmitData.data, 'base64').toString()));
} else if (surveySubmitData.encryptType === 'aes') {
const sessionData = await this.getSessionData(surveySubmitData.sessionId);
surveySubmitData.data = JSON.parse(decodeURIComponent(aes.decrypt(surveySubmitData.data, sessionData.data.code).toString(CryptoJS.enc.Utf8)));
} else {
surveySubmitData.data = JSON.parse(surveySubmitData.data);
}
// 提交时间限制
const begTime = publishConf?.code?.baseConf?.begTime || 0;
const endTime = publishConf?.code?.baseConf?.endTime || 0;
if (begTime && endTime) {
const nowStamp = Date.now();
const begTimeStamp = new Date(begTime).getTime();
const endTimeStamp = new Date(endTime).getTime();
if (nowStamp < begTimeStamp || nowStamp > endTimeStamp) {
throw new CommonError('不在答题有效期内');
}
}
// 提交时间段限制
const answerBegTime = publishConf?.code?.baseConf?.answerBegTime || '00:00:00';
const answerEndTime = publishConf?.code?.baseConf?.answerEndTime || '23:59:59';
if (answerBegTime && answerEndTime) {
const nowStamp = Date.now();
const ymdString = moment().format('YYYY-MM-DD');
const answerBegTimeStamp = new Date(`${ymdString} ${answerBegTime}`).getTime();
const answerEndTimeStamp = new Date(`${ymdString} ${answerEndTime}`).getTime();
if (nowStamp < answerBegTimeStamp || nowStamp > answerEndTimeStamp) {
throw new CommonError('不在答题时段内');
}
}
// 提交总数限制
const tLimit = publishConf?.code?.baseConf?.tLimit || 0;
if (tLimit > 0) {
// 提升性能可以使用redis
const nowSubmitCount = await surveySubmit.countDocuments({ surveyPath: surveySubmitData.surveyPath }) || 0;
if (nowSubmitCount >= tLimit) {
throw new CommonError('超出提交总数限制');
}
}
// 投票信息保存
const dataList = publishConf?.code?.dataConf?.dataList || [];
const dataListMap = keyBy(dataList, 'field');
const surveySubmitDataKeys = Object.keys(surveySubmitData.data);
for (const field of surveySubmitDataKeys) {
const configData = dataListMap[field];
if (configData && /vote/.exec(configData.type)) {
const voteData = (await surveyKeyStoreService.get({ surveyPath: surveySubmitData.surveyPath, key: field, type: 'vote' })) || { total: 0 };
voteData.total++;
const fields = Array.isArray(surveySubmitData.data[field]) ? surveySubmitData.data[field] : [surveySubmitData.data[field]];
for (const field of fields) {
if (!voteData[field]) {
voteData[field] = 1;
} else {
voteData[field]++;
}
}
await surveyKeyStoreService.set({ surveyPath: surveySubmitData.surveyPath, key: field, data: voteData, type: 'vote' });
}
}
// 提交问卷
const surveySubmitRes = await surveySubmit.insertOne({
...surveySubmitData,
pageId,
curStatus: getStatusObject({ status: SURVEY_STATUS.new }),
createDate: Date.now()
});
return surveySubmitRes;
}
}
export const surveySubmitService = new SurveySubmitService()
export const surveySubmitService = new SurveySubmitService();

View File

@ -1,48 +1,48 @@
import {
createHash
createHash
} from 'crypto';
import { CommonError } from '../../../types/index'
import { CommonError } from '../../../types/index';
const hash256 = (text) => {
return createHash('sha256').update(text).digest('hex')
}
return createHash('sha256').update(text).digest('hex');
};
const undefinedToString = (data) => {
const res = {}
for (const key in data) {
if (data[key] === undefined) {
res[key] = ''
} else {
res[key] = data[key]
}
const res = {};
for (const key in data) {
if (data[key] === undefined) {
res[key] = '';
} else {
res[key] = data[key];
}
return res;
}
return res;
};
const getSignByData = (sourceData, ts) => {
const data = undefinedToString(sourceData)
const keysArr = Object.keys(data)
keysArr.sort()
let signArr = keysArr.map(key => {
if (typeof data[key] === 'string') {
return `${key}=${encodeURIComponent(data[key])}`
}
return `${key}=${JSON.stringify(data[key])}`
})
const sign = hash256(signArr.join('') + ts)
return `${sign}`
}
const getSignByData = (sourceData, ts) => {
const data = undefinedToString(sourceData);
const keysArr = Object.keys(data);
keysArr.sort();
const signArr = keysArr.map(key => {
if (typeof data[key] === 'string') {
return `${key}=${encodeURIComponent(data[key])}`;
}
return `${key}=${JSON.stringify(data[key])}`;
});
const sign = hash256(signArr.join('') + ts);
return `${sign}`;
};
export const checkSign = (sourceData) =>{
const sign = sourceData.sign
if(!sign) {
throw new CommonError('请求签名不存在')
}
delete sourceData.sign
const [inSign, ts] = sign.split('.');
const realSign = getSignByData(sourceData, ts)
if(inSign!==realSign) {
throw new CommonError('请求签名异常')
}
return true;
export const checkSign = (sourceData) => {
const sign = sourceData.sign;
if(!sign) {
throw new CommonError('请求签名不存在');
}
delete sourceData.sign;
const [inSign, ts] = sign.split('.');
const realSign = getSignByData(sourceData, ts);
if(inSign!==realSign) {
throw new CommonError('请求签名异常');
}
return true;
};

View File

@ -1,32 +1,24 @@
import { SURVEY_STATUS, CommonError } from '../../../types/index'
import * as Joi from 'joi'
import { SURVEY_STATUS, CommonError } from '../../../types/index';
import * as Joi from 'joi';
export function getMapByKey({data,key}:{data:Array<any>,key:string}) {
const datamap = {}
for(const item of data) {
datamap[item[key]] = item
}
return datamap
}
export function getStatusObject({status}:{status:SURVEY_STATUS}) {
export function getStatusObject({ status }: { status: SURVEY_STATUS }) {
return {
status,
id: status,
date: Date.now(),
};
}
export function getValidateValue<T=any>(validationResult:Joi.ValidationResult<T>):T {
if(validationResult.error) {
throw new CommonError(validationResult.error.details.map(e=>e.message).join())
export function getValidateValue<T = unknown>(validationResult: Joi.ValidationResult<T>): T {
if (validationResult.error) {
throw new CommonError(validationResult.error.details.map(e => e.message).join());
}
return validationResult.value;
}
export function randomCode(length) {
let charList:Array<string> = []
for(let i=0;i<length;i++) {
charList.push(Math.floor(Math.random()*16).toString(16))
const charList: Array<string> = [];
for (let i = 0; i < length; i++) {
charList.push(Math.floor(Math.random() * 16).toString(16));
}
return charList.join('')
return charList.join('');
}

View File

@ -1,29 +1,25 @@
import { SurveyServer } from '../../decorator'
import { Request, Response } from 'koa'
import { createReadStream } from 'fs'
import * as path from 'path'
import { SurveyApp, SurveyServer } from '../../decorator';
import { createReadStream } from 'fs';
import * as path from 'path';
@SurveyApp('')
export default class UI {
@SurveyServer({ type: 'http', method: 'get', routerName: '/render/(.*)' })
async render({ req, res }: {req:Request, res:Response}) {
@SurveyServer({ type: 'http', method: 'get', routerName: '/render/(.*)' })
async render({ res }) {
const filePath = path.join(__dirname, 'public', 'render.html');
res.type = path.extname(filePath)
return createReadStream(filePath)
res.type = path.extname(filePath);
return createReadStream(filePath);
}
@SurveyServer({ type: 'http', method: 'get', routerName: '/management/(.*)' })
async management({ req, res }: {req:Request, res:Response}) {
@SurveyServer({ type: 'http', method: 'get', routerName: '/management/(.*)' })
async management({ res }) {
const filePath = path.join(__dirname, 'public', 'management.html');
res.type = path.extname(filePath)
return createReadStream(filePath)
res.type = path.extname(filePath);
return createReadStream(filePath);
}
@SurveyServer({ type: 'http', method: 'get', routerName: '/(.*)' })
async index({ req, res }: {req:Request, res:Response}) {
let reqPath = req.path;
if (req.path === '/') {
reqPath = '/management.html'
}
const filePath = path.join(__dirname, 'public', reqPath);
res.type = path.extname(filePath)
return createReadStream(filePath)
@SurveyServer({ type: 'http', method: 'get', routerName: '/' })
async index({ res }) {
const filePath = path.join(__dirname, 'public', 'management.html');
res.type = path.extname(filePath);
return createReadStream(filePath);
}
}

View File

@ -1,14 +1,14 @@
const config = {
mongo:{
url: process.env.xiaojuSurveyMongoUrl ||'mongodb://localhost:27017',
dbName:'xiaojuSurvey',
},
jwt:{
secret: process.env.xiaojuSurveyJwtSecret || 'xiaojuSurveyJwtSecret',
expiresIn: process.env.xiaojuSurveyJwtExpiresIn || '8h',
}
}
mongo: {
url: process.env.xiaojuSurveyMongoUrl ||'mongodb://localhost:27017',
dbName: 'xiaojuSurvey',
},
jwt: {
secret: process.env.xiaojuSurveyJwtSecret || 'xiaojuSurveyJwtSecret',
expiresIn: process.env.xiaojuSurveyJwtExpiresIn || '8h',
}
};
export function getConfig() {
return config
return config;
}

View File

@ -1,6 +1,6 @@
import { getConfig } from '../config/index'
import MongoService from '../../../utils/mongoService'
import { getConfig } from '../config/index';
import MongoService from '../../../utils/mongoService';
const config = getConfig()
const config = getConfig();
export const mongo = new MongoService({ url: config.mongo.url, dbName: config.mongo.dbName })
export const mongo = new MongoService({ url: config.mongo.url, dbName: config.mongo.dbName });

View File

@ -1,83 +1,85 @@
import { SurveyServer } from '../../decorator'
import { Request, Response } from 'koa'
import * as Joi from 'joi'
import { userService } from './service/userService'
import { captchaService } from './service/captchaService'
import { getValidateValue } from './utils/index'
import { SurveyApp, SurveyServer } from '../../decorator';
import { Request, Response } from 'koa';
import * as Joi from 'joi';
import { userService } from './service/userService';
import { captchaService } from './service/captchaService';
import { getValidateValue } from './utils/index';
import { CommonError } from '../../types/index'
import { CommonError } from '../../types/index';
@SurveyApp('/api/user')
export default class User {
@SurveyServer({ type: 'http', method: 'post', routerName: '/register' })
async register({ req, res }: { req: Request, res: Response }) {
const userInfo = getValidateValue(Joi.object({
username: Joi.string().required(),
password: Joi.string().required(),
captchaId: Joi.string().required(),
captcha: Joi.string().required(),
}).validate(req.body, { allowUnknown: true }));
const isCorrect = await captchaService.checkCaptchaIsCorrect({ captcha: userInfo.captcha, id: userInfo.captchaId })
if (!isCorrect) {
throw new CommonError('验证码不正确')
}
const userRegisterRes = await userService.register({
username: userInfo.username,
password: userInfo.password,
})
// 删除验证码
captchaService.deleteCaptcha({ id: userInfo.captchaId })
return {
code: 200,
data: userRegisterRes,
}
@SurveyServer({ type: 'http', method: 'post', routerName: '/register' })
async register({ req }: { req: Request, res: Response }) {
const userInfo = getValidateValue(Joi.object({
username: Joi.string().required(),
password: Joi.string().required(),
captchaId: Joi.string().required(),
captcha: Joi.string().required(),
}).validate(req.body, { allowUnknown: true }));
const isCorrect = await captchaService.checkCaptchaIsCorrect({ captcha: userInfo.captcha, id: userInfo.captchaId });
if (!isCorrect) {
throw new CommonError('验证码不正确');
}
const userRegisterRes = await userService.register({
username: userInfo.username,
password: userInfo.password,
});
// 删除验证码
captchaService.deleteCaptcha({ id: userInfo.captchaId });
return {
code: 200,
data: userRegisterRes,
};
}
@SurveyServer({ type: 'http', method: 'post', routerName: '/login' })
async login({ req, res }: { req: Request, res: Response }) {
const userInfo = getValidateValue(Joi.object({
username: Joi.string().required(),
password: Joi.string().required(),
captchaId: Joi.string().required(),
captcha: Joi.string().required(),
}).validate(req.body, { allowUnknown: true }));
const isCorrect = await captchaService.checkCaptchaIsCorrect({ captcha: userInfo.captcha, id: userInfo.captchaId })
if (!isCorrect) {
throw new CommonError('验证码不正确')
}
const data = await userService.login({
username: userInfo.username,
password: userInfo.password,
})
// 删除验证码
captchaService.deleteCaptcha({ id: userInfo.captchaId })
return {
code: 200,
data,
}
@SurveyServer({ type: 'http', method: 'post', routerName: '/login' })
async login({ req }: { req: Request, res: Response }) {
const userInfo = getValidateValue(Joi.object({
username: Joi.string().required(),
password: Joi.string().required(),
captchaId: Joi.string().required(),
captcha: Joi.string().required(),
}).validate(req.body, { allowUnknown: true }));
const isCorrect = await captchaService.checkCaptchaIsCorrect({ captcha: userInfo.captcha, id: userInfo.captchaId });
if (!isCorrect) {
throw new CommonError('验证码不正确');
}
const data = await userService.login({
username: userInfo.username,
password: userInfo.password,
});
// 删除验证码
captchaService.deleteCaptcha({ id: userInfo.captchaId });
return {
code: 200,
data,
};
}
@SurveyServer({ type: 'rpc' })
async getUserByToken({ params, context }: { params: any, context: any }) {
const data = await userService.getUserByToken({ token: params.token })
return {
result: data,
context, // 上下文主要是传递调用方信息使用比如traceid
}
}
@SurveyServer({ type: 'rpc' })
async getUserByToken({ params, context }) {
const data = await userService.getUserByToken({ token: params.token });
return {
result: data,
context, // 上下文主要是传递调用方信息使用比如traceid
};
}
@SurveyServer({ type: 'http', method: 'post', routerName: '/captcha' })
async refreshCaptcha({ req }) {
const captchaData = captchaService.createCaptcha()
const res = await captchaService.addCaptchaData({ text: captchaData.text })
if (req.body && req.body.captchaId) {
// 删除验证码
captchaService.deleteCaptcha({ id: req.body.captchaId })
}
return {
code: 200,
data: {
id: res.insertedId,
img: captchaData.data,
},
}
@SurveyServer({ type: 'http', method: 'post', routerName: '/captcha' })
async refreshCaptcha({ req }) {
const captchaData = captchaService.createCaptcha();
const res = await captchaService.addCaptchaData({ text: captchaData.text });
if (req.body && req.body.captchaId) {
// 删除验证码
captchaService.deleteCaptcha({ id: req.body.captchaId });
}
return {
code: 200,
data: {
id: res.insertedId,
img: captchaData.data,
},
};
}
}

View File

@ -1,5 +1,5 @@
import { mongo } from '../db/mongo'
import { create } from 'svg-captcha'
import { mongo } from '../db/mongo';
import { create } from 'svg-captcha';
class CaptchaService {
createCaptcha() {
@ -9,7 +9,7 @@ class CaptchaService {
noise: 3, // 干扰线数量
color: true, // 启用彩色
background: '#f0f0f0', // 背景色
})
});
}
async addCaptchaData({ text }) {
@ -30,11 +30,11 @@ class CaptchaService {
async deleteCaptcha({ id }) {
const captchaDb = await mongo.getCollection({ collectionName: 'captcha' });
const _id = mongo.getObjectIdByStr(id)
const _id = mongo.getObjectIdByStr(id);
await captchaDb.deleteOne({
_id
})
});
}
}
export const captchaService = new CaptchaService()
export const captchaService = new CaptchaService();

View File

@ -1,81 +1,81 @@
import {
verify as jwtVerify,
sign as jwtSign
verify as jwtVerify,
sign as jwtSign
} from 'jsonwebtoken';
import {
createHash
createHash
} from 'crypto';
import { mongo } from '../db/mongo'
import { getStatusObject } from '../utils/index'
import { SURVEY_STATUS, CommonError } from '../../../types/index'
import { getConfig } from '../config/index'
const config = getConfig()
import { mongo } from '../db/mongo';
import { getStatusObject } from '../utils/index';
import { SURVEY_STATUS, CommonError } from '../../../types/index';
import { getConfig } from '../config/index';
const config = getConfig();
class UserService {
hash256(text) {
return createHash('sha256').update(text).digest('hex')
}
hash256(text) {
return createHash('sha256').update(text).digest('hex');
}
getToken(userInfo) {
return jwtSign(userInfo, config.jwt.secret, { expiresIn: config.jwt.expiresIn });
}
getToken(userInfo) {
return jwtSign(userInfo, config.jwt.secret, { expiresIn: config.jwt.expiresIn });
}
async register(userInfo: { username: string, password: string }) {
const user = await mongo.getCollection({ collectionName: 'user' });
const userRes = await user.findOne({
username: userInfo.username,
})
if (userRes) {
throw new CommonError('该用户已存在')
}
const userInsertRes = await user.insertOne({
username: userInfo.username,
password: this.hash256(userInfo.password),
curStatus: getStatusObject({ status: SURVEY_STATUS.new }),
createDate: Date.now()
})
const token = this.getToken({
_id: userInsertRes.insertedId.toString(),
username: userInfo.username
});
return { userInsertRes, token, username: userInfo.username }
async register(userInfo: { username: string, password: string }) {
const user = await mongo.getCollection({ collectionName: 'user' });
const userRes = await user.findOne({
username: userInfo.username,
});
if (userRes) {
throw new CommonError('该用户已存在');
}
const userInsertRes = await user.insertOne({
username: userInfo.username,
password: this.hash256(userInfo.password),
curStatus: getStatusObject({ status: SURVEY_STATUS.new }),
createDate: Date.now()
});
const token = this.getToken({
_id: userInsertRes.insertedId.toString(),
username: userInfo.username
});
return { userInsertRes, token, username: userInfo.username };
}
async login(userInfo: { username: string, password: string }) {
const user = await mongo.getCollection({ collectionName: 'user' });
const userRes = await user.findOne({
username: userInfo.username,
password: this.hash256(userInfo.password),
})
if (!userRes) {
throw new CommonError('用户名或密码错误')
}
const token = this.getToken({
_id: userRes._id.toString(),
username: userInfo.username
});
return { token, username: userInfo.username }
async login(userInfo: { username: string, password: string }) {
const user = await mongo.getCollection({ collectionName: 'user' });
const userRes = await user.findOne({
username: userInfo.username,
password: this.hash256(userInfo.password),
});
if (!userRes) {
throw new CommonError('用户名或密码错误');
}
const token = this.getToken({
_id: userRes._id.toString(),
username: userInfo.username
});
return { token, username: userInfo.username };
}
async getUserByToken(tokenInfo: { token: string }) {
let userInfo;
try {
userInfo = jwtVerify(tokenInfo.token, config.jwt.secret)
} catch (err) {
throw new CommonError('用户凭证无效或已过期', 403)
}
const user = await mongo.getCollection({ collectionName: 'user' });
const userRes = await user.findOne({
_id: mongo.getObjectIdByStr(userInfo._id),
})
if (!userRes) {
throw new CommonError('用户已不存在')
}
return mongo.convertId2StringByDoc(userRes);
async getUserByToken(tokenInfo: { token: string }) {
let userInfo;
try {
userInfo = jwtVerify(tokenInfo.token, config.jwt.secret);
} catch (err) {
throw new CommonError('用户凭证无效或已过期', 403);
}
const user = await mongo.getCollection({ collectionName: 'user' });
const userRes = await user.findOne({
_id: mongo.getObjectIdByStr(userInfo._id),
});
if (!userRes) {
throw new CommonError('用户已不存在');
}
return mongo.convertId2StringByDoc(userRes);
}
}
export const userService = new UserService()
export const userService = new UserService();

View File

@ -1,5 +1,5 @@
import { SURVEY_STATUS, CommonError } from '../../../types/index'
import * as Joi from 'joi'
import { SURVEY_STATUS, CommonError } from '../../../types/index';
import * as Joi from 'joi';
export function getStatusObject({ status }: { status: SURVEY_STATUS }) {
return {
status,
@ -7,9 +7,9 @@ export function getStatusObject({ status }: { status: SURVEY_STATUS }) {
date: Date.now(),
};
}
export function getValidateValue<T = any>(validationResult: Joi.ValidationResult<T>): T {
export function getValidateValue<T = unknown>(validationResult: Joi.ValidationResult<T>): T {
if (validationResult.error) {
throw new CommonError(validationResult.error.details.map(e => e.message).join())
throw new CommonError(validationResult.error.details.map(e => e.message).join());
}
return validationResult.value;
}

View File

@ -1,19 +1,29 @@
type ServerType = 'http' | 'websocket' | 'rpc'
export interface ServerValue {
export interface RouterOptions {
type: ServerType,
method: 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options';
routerName: string;
method?: 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options';
routerName?: string;
}
export const SurveyServerConfigKey = Symbol('appConfig') // vm环境和worker环境上下文不一致导致不能使用Symbol
export function SurveyServer(surveyServer) {
return function (target: any, propertyKey: string, _descriptor: PropertyDescriptor) {
if (!target[SurveyServerConfigKey]) {
target[SurveyServerConfigKey] = new Map<string, ServerValue>()
export const surveyServerKey = Symbol('surveyServer'); // vm环境和worker环境上下文不一致导致不能使用Symbol
export const surveyAppKey = Symbol('surveyApp');
export function SurveyApp(routerName) {
return (target: unknown) => {
if (!target[surveyAppKey]) {
target[surveyAppKey] = routerName;
}
target[SurveyServerConfigKey].set(
};
}
export function SurveyServer(options: RouterOptions) {
return function(target: unknown, propertyKey: string) {
if (!target[surveyServerKey]) {
target[surveyServerKey] = new Map<string, RouterOptions>();
}
target[surveyServerKey].set(
propertyKey,
surveyServer
)
}
options
);
};
}

View File

@ -1,21 +1,30 @@
import * as os from 'os'
import * as os from 'os';
import * as Koa from 'koa';
import * as KoaBodyparser from 'koa-bodyparser'
import * as logger from 'koa-pino-logger'
import { getRouter } from './router'
import { outputCatch } from './middleware/outputCatch'
import * as KoaBodyparser from 'koa-bodyparser';
import * as KoaStatic from 'koa-static';
import * as logger from 'koa-pino-logger';
import { initRouter } from './router';
import { outputCatch } from './middleware/outputCatch';
import * as path from 'path';
const app = new Koa();
app.use(outputCatch({ showErrorStack: true }))
app.use(logger())
app.use(KoaBodyparser({
async function main() {
const app = new Koa();
app.use(outputCatch({ showErrorStack: true }));
app.use(logger());
app.use(KoaBodyparser({
formLimit: '30mb',
jsonLimit: '30mb',
textLimit: '30mb',
xmlLimit: '30mb',
}))
}));
app.use(getRouter().routes())
const port = process.env.PORT || 3000
app.listen(port)
process.stdout.write(`${os.EOL}server run: http://127.0.0.1:${port} ${os.EOL}`)
await initRouter(app);
app.use(KoaStatic(path.join(__dirname, './apps/ui/public')));
const port = process.env.PORT || 3000;
app.listen(port);
process.stdout.write(`${os.EOL}server run: http://127.0.0.1:${port} ${os.EOL}`);
}
main();

View File

@ -1,13 +1,13 @@
export function outputCatch({ showErrorStack }: { showErrorStack: boolean }) {
return async function (ctx, next) {
try {
await next()
} catch (err) {
const outputData = { ...err }
if (showErrorStack) {
outputData.stack = err.stack;
}
return ctx.body = outputData
}
return async function(ctx, next) {
try {
await next();
} catch (err) {
const outputData = { ...err };
if (showErrorStack) {
outputData.stack = err.stack;
}
return ctx.body = outputData;
}
};
}

21
server/src/registry.ts Normal file
View File

@ -0,0 +1,21 @@
class AppRegistry {
private apps: Record<string, unknown>;
constructor() {
this.apps = {};
}
registerApp(name: string, service: unknown) {
if (!this.apps[name]) {
this.apps[name] = service;
}
}
getApp(name: string) {
return this.apps[name];
}
}
const appRegistry = new AppRegistry();
export default appRegistry;

View File

@ -1,39 +1,37 @@
import * as Router from 'koa-router';
import { Context } from 'koa'
import { SurveyServerConfigKey } from './decorator';
import Ui from './apps/ui/index'
import SurveyManage from './apps/surveyManage/index'
import SurveyPublish from './apps/surveyPublish/index'
import User from './apps/user/index'
import { Context } from 'koa';
import { RouterOptions, surveyAppKey, surveyServerKey } from './decorator';
import { glob } from 'glob';
import * as path from 'path';
import appRegistry from './registry';
function loadAppRouter(app, appRouter) {
const appServerConfigMap = app[SurveyServerConfigKey]
for (const [serveName, serveValue] of appServerConfigMap) {
const middleware = async (ctx: Context) => {
const data = await app[serveName]({ req: ctx.request, res: ctx.response })
return ctx.body = data
}
const method = serveValue.method || 'all'
const routerName = serveValue.routerName || `/${serveName}`
appRouter[method](routerName, middleware)
export async function initRouter(app) {
const rootRouter = new Router();
const entries = await glob(path.join(__dirname, './apps/*/index.{ts,js}'));
for (const entry of entries) {
const module = await import(entry);
const instance = new module.default();
const moduleRouter = new Router();
const serverConfig: Map<string, RouterOptions> = instance[surveyServerKey];
for (const [serverName, serverValue] of serverConfig.entries()) {
if (serverValue.routerName) {
const method = serverValue.method || 'get';
moduleRouter[method](serverValue.routerName, async (ctx: Context, next) => {
const ret = await instance[serverName]({ req: ctx.request, res: ctx.response }, next);
ctx.body = ret;
});
}
}
return appRouter
}
rootRouter.use(module.default[surveyAppKey], moduleRouter.routes());
export function getRouter() {
const rootRouter = new Router()
const apiAppMap = {
surveyManage: new SurveyManage(),
surveyPublish: new SurveyPublish(),
user: new User(),
}
for (const [apiAppName, apiApp] of Object.entries(apiAppMap)) {
const appRouter = new Router()
loadAppRouter(apiApp, appRouter)
rootRouter.use(`/api/${apiAppName}`, appRouter.routes(), rootRouter.allowedMethods())
}
loadAppRouter(new Ui(), rootRouter)
return rootRouter
appRegistry.registerApp(instance.constructor.name.toLowerCase(), instance);
}
// console.log(rootRouter);
app.use(rootRouter.routes());
}

View File

@ -1,11 +1,12 @@
export async function rpcInvote<P, R>(appServerName: string, params: P): Promise<R> {
const appServerNameData = /^(\w+)\.(\w+)$/.exec(appServerName);
if (!appServerNameData) {
throw new Error('rpc调用必须按照app.function名方式填写app和function名称只支持数字字母下划线')
}
const appName = appServerNameData[1];
const serverName = appServerNameData[2];
const App = require(`./apps/${appName}/index`).default
const instance = new App()
return await instance[serverName](params)
import appRegistry from './registry';
export function rpcInvote<P, R>(appServerName: string, params: P): Promise<R> {
const appServerNameData = /^(\w+)\.(\w+)$/.exec(appServerName);
if (!appServerNameData) {
throw new Error('rpc调用必须按照app.function名方式填写app和function名称只支持数字字母下划线');
}
const appName = appServerNameData[1];
const serverName = appServerNameData[2];
const instance = appRegistry.getApp(appName.toLowerCase());
return instance[serverName](params);
}

View File

@ -1,43 +1,53 @@
export enum DICT_TYPE {
danger = "danger",
secret = "secret"
danger = 'danger',
secret = 'secret'
}
export enum SURVEY_STATUS {
new = "new",
editing = "editing",
pausing = "pausing",
published = "published",
removed = "removed"
new = 'new',
editing = 'editing',
pausing = 'pausing',
published = 'published',
removed = 'removed'
}
export enum QUESTION_TYPE {
enps = "enps",
nps = "nps",
question = "question", //通用问卷
register = "register", //报名
vote = "vote" //投票
enps = 'enps',
nps = 'nps',
question = 'question', //通用问卷
register = 'register', //报名
vote = 'vote' //投票
}
export enum HISTORY_TYPE {
dailyHis = "dailyHis", //保存历史
publishHis = "publishHis" //发布历史
dailyHis = 'dailyHis', //保存历史
publishHis = 'publishHis' //发布历史
}
export interface StatusObj {
id: string;
status: string;
date: number;
}
export interface UserType {
_id: string;
username: string;
password: string;
curStatus: any;
curStatus: StatusObj;
createDate: number;
}
export class CommonError extends Error {
code: number
errmsg: number
constructor(msg, code = 500) {
super(msg)
this.errmsg = msg
this.code = code
}
code: number;
errmsg: number;
constructor(msg, code = 500) {
super(msg);
this.errmsg = msg;
this.code = code;
}
}
export interface AnyType {
[key: string]: unknown;
}

View File

@ -0,0 +1,8 @@
export interface KeyStore {
key: string;
surveyPath: string;
type: string;
data: unknown;
createDate: number;
updateDate: number;
}

120
server/src/types/survey.ts Normal file
View File

@ -0,0 +1,120 @@
export interface TitleConfig {
mainTitle: string;
subTitle: string;
}
export interface BannerConfig {
bgImage: string;
videoLink: string;
postImg: string;
}
export interface BannerConf {
titleConfig: TitleConfig;
bannerConfig: BannerConfig;
}
export interface TimeStep {
hour: number;
min: number;
}
export interface NPS {
leftText: string;
rightText: string;
}
export interface TextRange {
min: {
placeholder: string;
value: number;
};
max: {
placeholder: string;
value: number;
};
}
export interface DataItem {
isRequired: boolean;
showIndex: boolean;
showType: boolean;
showSpliter: boolean;
type: string;
valid: string;
field: string;
title: string;
placeholder: string;
randomSort: boolean;
checked: boolean;
minNum: string;
maxNum: string;
maxPhotos: number;
star: number;
timeStep: TimeStep;
nps: NPS;
placeholderDesc: string;
addressType: number;
isAuto: boolean;
urlKey: string;
textRange: TextRange;
options?: Option[];
importKey?: string;
importData?: string;
cOption?: string;
cOptions?: string[];
exclude?: boolean;
}
export interface Option {
text: string;
imageUrl: string;
others: boolean;
mustOthers: boolean;
othersKey: string;
placeholderDesc: string;
hash: string;
}
export interface DataConf {
dataList: DataItem[];
}
export interface ConfirmAgain {
is_again: boolean;
again_text: string;
}
export interface MsgContent {
msg_200: string;
msg_9001: string;
msg_9002: string;
msg_9003: string;
msg_9004: string;
}
export interface SubmitConf {
submitTitle: string;
confirmAgain: ConfirmAgain;
msgContent: MsgContent;
}
export interface BaseConf {
begTime: string;
endTime: string;
tLimit: string;
language: string;
}
export interface SkinConf {
skinColor: string;
inputBgColor: string;
}
export interface ParsedData {
bannerConf: BannerConf;
dataConf: DataConf;
submitConf: SubmitConf;
baseConf: BaseConf;
skinConf: SkinConf;
}

View File

@ -1,50 +1,55 @@
import { Collection, MongoClient, ObjectId } from 'mongodb'
import { CommonError } from '../types'
import { Collection, MongoClient, ObjectId } from 'mongodb';
import { CommonError } from '../types';
class MongoService {
isInit: boolean
client: MongoClient
dbName: string
constructor({ url, dbName }) {
this.client = new MongoClient(url);
this.dbName = dbName;
}
async getCollection({ collectionName }): Promise<Collection> {
try {
// 设置一个6秒的计时器
const timeoutPromise = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('连接超时'));
}, 6000); // 6秒超时
});
await Promise.race([this.client.connect(), timeoutPromise])
} catch (error) {
throw new CommonError('数据库连接错误:' + error.message)
}
try {
return this.client.db(this.dbName).collection(collectionName)
} catch (error) {
throw new CommonError(`get collection ${collectionName} error`)
}
}
convertId2StringByDoc(doc: any): any {
doc._id = doc._id.toString()
return doc
}
convertId2StringByList(list: Array<any>): Array<any> {
return list.map(e => {
return this.convertId2StringByDoc(e)
})
}
getObjectIdByStr(str: string) {
return new ObjectId(str)
}
// 定义一个通用类型,表示具有 _id 字段的对象
interface ObjectWithId {
_id: ObjectId;
// 其他可能的属性...
}
export default MongoService
class MongoService {
isInit: boolean;
client: MongoClient;
dbName: string;
constructor({ url, dbName }) {
this.client = new MongoClient(url);
this.dbName = dbName;
}
async getCollection({ collectionName }): Promise<Collection> {
try {
// 设置一个6秒的计时器
const timeoutPromise = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('连接超时'));
}, 6000); // 6秒超时
});
await Promise.race([this.client.connect(), timeoutPromise]);
} catch (error) {
throw new CommonError('数据库连接错误:' + error.message);
}
try {
return this.client.db(this.dbName).collection(collectionName);
} catch (error) {
throw new CommonError(`get collection ${collectionName} error`);
}
}
convertId2StringByDoc<T extends ObjectWithId>(doc: T): T {
return { ...doc, _id: doc._id.toString() };
}
convertId2StringByList<T extends ObjectWithId>(list: Array<T>): Array<T> {
return list.map(e => {
return this.convertId2StringByDoc(e);
});
}
getObjectIdByStr(str: string) {
return new ObjectId(str);
}
}
export default MongoService;

View File

@ -59,11 +59,10 @@ export const noListDataConfig = {
img: '/imgs/icons/list-empty.png',
};
export const statusMaps = {
new: '未发布',
editing: '修改中',
published: '已发布',
removed: '',
pausing: '',
};
};

View File

@ -11,7 +11,8 @@ const routes = [
{
path: '/survey',
name: 'survey',
component: () => import(/* webpackChunkName: "survey" */'../pages/list/index.vue'),
component: () =>
import(/* webpackChunkName: "survey" */ '../pages/list/index.vue'),
meta: {
needLogin: true,
title: '问卷列表',
@ -23,7 +24,8 @@ const routes = [
meta: {
needLogin: true,
},
component: () => import(/* webpackChunkName: "editPage" */'../pages/edit/index.vue'),
component: () =>
import(/* webpackChunkName: "editPage" */ '../pages/edit/index.vue'),
children: [
{
path: '',
@ -31,7 +33,10 @@ const routes = [
meta: {
needLogin: true,
},
component: () => import(/* webpackChunkName: "QuestionEditIndex" */'../pages/edit/pages/edit.vue'),
component: () =>
import(
/* webpackChunkName: "QuestionEditIndex" */ '../pages/edit/pages/edit.vue'
),
},
{
path: 'setting',
@ -39,7 +44,10 @@ const routes = [
meta: {
needLogin: true,
},
component: () => import(/* webpackChunkName: "QuestionEditSetting" */'../pages/edit/pages/setting.vue'),
component: () =>
import(
/* webpackChunkName: "QuestionEditSetting" */ '../pages/edit/pages/setting.vue'
),
},
{
path: 'resultConfig',
@ -47,7 +55,10 @@ const routes = [
meta: {
needLogin: true,
},
component: () => import(/* webpackChunkName: "QuestionEditResultConfig" */'../pages/edit/pages/resultConfig.vue'),
component: () =>
import(
/* webpackChunkName: "QuestionEditResultConfig" */ '../pages/edit/pages/resultConfig.vue'
),
},
],
},
@ -57,7 +68,10 @@ const routes = [
meta: {
needLogin: true,
},
component: () => import(/* webpackChunkName: "analysisPage" */'../pages/analysis/index.vue'),
component: () =>
import(
/* webpackChunkName: "analysisPage" */ '../pages/analysis/index.vue'
),
},
{
path: '/survey/:id/publishResult',
@ -65,7 +79,10 @@ const routes = [
meta: {
needLogin: true,
},
component: () => import(/* webpackChunkName: "publishResultPage" */'../pages/publishResult/index.vue'),
component: () =>
import(
/* webpackChunkName: "publishResultPage" */ '../pages/publishResult/index.vue'
),
},
{
path: '/create',
@ -74,13 +91,15 @@ const routes = [
needLogin: true,
title: '创建问卷',
},
component: () => import(/* webpackChunkName: "create" */'../pages/create/index.vue'),
component: () =>
import(/* webpackChunkName: "create" */ '../pages/create/index.vue'),
},
{
path: '/login',
name: 'login',
title: '登陆',
component: () => import(/* webpackChunkName: "login" */'../pages/login/index.vue'),
component: () =>
import(/* webpackChunkName: "login" */ '../pages/login/index.vue'),
},
];

View File

@ -1,4 +1,4 @@
import { forEach as _forEach, trim as _trim } from 'lodash';
import { forEach as _forEach, trim as _trim } from 'lodash';
import { escapeFilterXSS } from '@/common/xss';
// 获取选项的hash

View File

@ -1,4 +1,10 @@
import { forEach as _forEach, get as _get, isArray as _isArray, keys as _keys, set as _set } from 'lodash';
import {
forEach as _forEach,
get as _get,
isArray as _isArray,
keys as _keys,
set as _set,
} from 'lodash';
const regexpMap = {
nd: /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/,
@ -261,7 +267,7 @@ export default function (questionConfig) {
}
} else {
_forEach(options, (item) => {
const othersKey = `${field}_${item.hash}`
const othersKey = `${field}_${item.hash}`;
const { mustOthers } = item;
if (mustOthers) {
_set(validMap, othersKey, generateValidArr(true, ''));

View File

@ -1,7 +1,7 @@
import confirm from '../../components/confirm.vue';
import alert from '../../components/alert.vue';
import { isFunction as _isFunction} from 'lodash';
import { isFunction as _isFunction } from 'lodash';
export default {
install(Vue) {

View File

@ -1,6 +1,6 @@
import moment from 'moment';
// 引入中文
import 'moment/locale/zh-cn'
import 'moment/locale/zh-cn';
// 设置中文
moment.locale('zh-cn');
import adapter from '../adapter';

View File

@ -1,8 +1,7 @@
const { defineConfig } = require('@vue/cli-service');
const Webpack = require('webpack')
const Webpack = require('webpack');
// 分析打包时间
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin')
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
module.exports = defineConfig({
transpileDependencies: true,
@ -31,7 +30,7 @@ module.exports = defineConfig({
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
target: 'http://127.0.0.1:3000',
changeOrigin: true,
},
},
@ -47,19 +46,23 @@ module.exports = defineConfig({
open: true,
},
configureWebpack: {
plugins: [new Webpack.IgnorePlugin({resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/ })],
plugins: [
new Webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/,
}),
],
},
chainWebpack: (config) => {
config.module
.rule('js')
.test(/\.jsx?$/)
.exclude
.add(/node_modules/)
.end()
.exclude.add(/node_modules/)
.end()
.use('babel-loader')
.loader('babel-loader')
.end();
config.optimization.splitChunks({
cacheGroups: {
setterWidgets: {
@ -82,41 +85,40 @@ module.exports = defineConfig({
element: {
name: 'chunk-element-ui',
test: /[\\/]node_modules[\\/]element-ui[\\/]/,
chunks: "all",
chunks: 'all',
priority: 3,
reuseExistingChunk: true,
enforce: true
enforce: true,
},
moment: {
name: 'chunk-moment',
test: /[\\/]node_modules[\\/]moment[\\/]/,
chunks: "all",
chunks: 'all',
priority: 3,
reuseExistingChunk: true,
enforce: true
enforce: true,
},
'@wangeditor': {
name: 'chunk-wangeditor',
test: /[\\/]node_modules[\\/]@wangeditor[\\/]/,
chunks: "all",
chunks: 'all',
priority: 3,
reuseExistingChunk: true,
enforce: true
enforce: true,
},
common: {
//抽取所有入口页面都需要的公共chunk
name: "chunk-common",
chunks: "initial",
name: 'chunk-common',
chunks: 'initial',
minChunks: 2,
maxInitialRequests: 5,
minSize: 0,
priority: 1,
reuseExistingChunk: true,
enforce: true
}
enforce: true,
},
},
});
config.plugin('speed')
.use(SpeedMeasureWebpackPlugin)
config.plugin('speed').use(SpeedMeasureWebpackPlugin);
},
});