Commit d920324d by chenjinjing

no message

parents
.idea
/out
/node_modules
/test
/public
/logs
/video
*.logs
*.zip
*.mp4
*.png
/nm_backups
/res
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "启动程序",
"program": "${workspaceFolder}/src/main.ts",
"outFiles": [
"${workspaceFolder}/**/*.js"
]
}
]
}
\ No newline at end of file
{
"compile-hero.disable-compile-files-on-did-save-code": true
}
\ No newline at end of file
<?xml version="1.0" encoding="gb2312"?>
<!-- k12.cms.xml -->
<!--
<product> 的各项属性是对产品基本信息的描述:
name 属性为产品名称;
version 属性为产品的版本号,必须是两位小数:如:1.00、3.03、4.10……;
innerHost 属性为产品所在服务器的内部 IP 地址或网址;
outerPort 属性为与 innerHost 对应的 WEB 发布端口,默认值为 80(即 HTTP 的默认端口);
outerHost 属性为产品所在服务器的外部 IP 地址或网址;
outerPort 属性为与 outerHost 对应的 WEB 发布端口,默认值为 80(即 HTTP 的默认端口);
managePath 属性为产品后台管理页面相对于发布目录的路径。
-->
<product name="南模公智能问答" version="1.00" innerHost="10.98.240.51" innerPort="13276"
outerHost="10.98.240.51" outerPort="13276" managePath="/gzn/app/admin/">
<!--
当前产品(南模公智能问答 1.00)需要被UAC自动调用的 XMLRPC 接口函数:
host 属性为提供 XMLRPC 服务文件的主机地址,可使用域名,若与UAC在同一台服务器上,可以使用“localhost”;
port 属性为提供 XMLRPC 服务文件所使用的发布端口;
path 属性为提供 XMLRPC 服务的文件相对于发布目录所在的路径;
username 属性为 XMLRPC 服务验证的用户名;
password 属性为 XMLRPC 服务验证的密码;
<method> 的内容为 XMLRPC 方法名,只能填写UAC规定的方法名(具体的方法定义见后);
若无需UAC调用任何函数,可去掉 <xmlrpc> .... </xmlrpc> 部分
-->
<xmlrpc host="10.98.240.51" port="13277" path="/gzn/rpc/" username="K12RPC" password="K12RPCPwd">
<methods>
<method>user.addUser</method>
<method>user.updateUserCommon</method>
<method>user.updateUserPassword</method>
<method>product.getConfig</method>
<method>product.setConfig</method>
</methods>
</xmlrpc>
<!--
当前产品(南模公智能问答)包含的所有模块设置(UAC中会以模块为单位显示应用入口),每个模块对应一个 <module>:
name 属性为模块名称;
path 属性为产品模块相对于发布目录的路径;
logo 属性是产品模块使用的 LOGO 图片,图片放在“发布目录/platform/data/templates/images/logo/”目录下。
若当前产品为单一模块的产品,则 <modules> 中只需加入一个 <module>, 此 <module> 的 name 属性和产品名称相同。
-->
<modules>
<module name="南模公智能问答" path="/" logo="gzn.gif" />
</modules>
</product>
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "screen",
"version": "1.0.0",
"description": "",
"main": "main.ts",
"dependencies": {
"@alicloud/sms-sdk": "^1.1.6",
"@types/node": "^10.12.18",
"bson": "^6.1.0",
"compression": "^1.7.4",
"connect-history-api-fallback": "^2.0.0",
"express": "^4.21.2",
"express-async-handler": "^1.1.4",
"express-history-api-fallback": "^2.2.1",
"formidable": "^1.2.1",
"iconv-lite": "^0.7.0",
"log4js": "^6.6.1",
"lru-cache": "^4.1.5",
"md5": "^2.2.1",
"moment": "^2.24.0",
"mongoose": "^7.6.0",
"mysql": "^2.18.1",
"mysql2": "^3.6.0",
"node-xlsx": "^0.16.1",
"nodemailer": "^6.1.1",
"qs": "^6.11.0",
"request": "^2.88.0",
"sequelize": "^6.32.1",
"ssh2": "^1.17.0",
"svg-captcha": "^1.3.12",
"tencentcloud-sdk-nodejs": "^4.0.562",
"winston": "^3.17.0",
"ws": "^5.2.2",
"xml2js": "^0.4.23",
"xmlrpc": "^1.3.2"
},
"devDependencies": {
"@types/express": "^5.0.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "cjj",
"license": "ISC",
"bin": "./out/main.js",
"pkg": {
"scripts": "out/**/*.js",
"assets": [
"public/**/*",
"res/**/*",
"images/**/*",
"video/**/*"
],
"outputPath": "dist"
}
}
<config>
<port>13276</port>
<sign></sign>
<dbServer>http://127.0.0.1:13276</dbServer>
<mysqldb>
<!-- 本地mysql配置 -->
<mysqlHost>localhost</mysqlHost>
<mysqlPort>3306</mysqlPort>
<mysqlUser>root</mysqlUser>
<mysqlPwd>123456</mysqlPwd>
<dataBase>publicIntelligence</dataBase>
<!-- 服务器mysql配置 -->
<!-- <mysqlHost>127.0.0.1</mysqlHost>
<mysqlPort>3306</mysqlPort>
<mysqlUser>root</mysqlUser>
<mysqlPwd>123456</mysqlPwd>
<dataBase>publicIntelligence</dataBase> -->
</mysqldb>
<!-- XML-RPC服务器配置 -->
<xmlRpcServer>
<enabled>true</enabled>
<port>13277</port>
<host>localhost</host>
<path>/gzn/rpc</path>
<auth>
<username>K12RPC</username>
<password>K12RPCPwd</password>
</auth>
</xmlRpcServer>
</config>
This diff is collapsed. Click to expand it.
/**
* 请求数据中心类型
*/
export enum OPERATIONALDATATYPE {
增加 = '/puxin/dataserver/mysql/table/add',
修改 = '/puxin/dataserver/mysql/table/update',
删除 = '/puxin/dataserver/mysql/table/delete',
查询单个 = '/puxin/dataserver/mysql/table/find/one',
查询多个 = '/puxin/dataserver/mysql/table/find/many',
分页查询 = '/puxin/dataserver/mysql/table/find/manytopage',
查询数据量 = '/puxin/dataserver/mysql/table/find/count',
多表联查 = '/puxin/dataserver/mysql/table/find/aggragate',
多表分页 = '/puxin/dataserver/mysql/table/find/aggragatetopage',
多表单个 = '/puxin/dataserver/mysql/table/find/aggragateone'
}
/**
* 表名
*/
export enum TABLENAME {
维度表 = "dimension",
题目表 = "question",
答题记录表 = "answer_record",
答题记录明细表 = "answer_record_detail",
狮子形象表 = "nm_lion_image",
统一用户表 = "uac_user",
管理后台用户 = "adminUser",
服务开启时间表 = "opening_time",
}
export enum TABLEID {
维度表 = "d",
题目表 = "q",
答题记录表 = "ar",
答题记录明细表 = "ard",
狮子形象表 = "nli",
统一用户表 = "uac",
管理后台用户 = "au",
服务开启时间表 = "ot",
}
export enum OPERATIONTYPEENUM {
= 1,
,
,
}
/**上传文件类型 */
export enum FILETYPE {
word = 1,
pdf,
图片,
视频,
多类型
}
export enum TYPEENUM {
string = 1,
number,
object,
array,
boolean,
}
export enum AUTHENTICATIONTYPEENNUM {
一般注册用户 = 0,
行政管理人员 = 1,
教职员工,
学生,
家长
}
export enum STATE {
= 0,
= 1
}
export enum ERRORENUM {
不存在表 = 1,
身份验证失败,
缺少必要参数_表名,
数据表不存在,
参数错误,
添加时数据对象为空,
修改时数据对象为空,
该方法仅可进行数据操作,
数据操作失败,
该方法仅可进行查询操作,
分页请设置当前页数,
数据查询失败,
该方法仅可进行联合查询操作,
数据联合查询失败,
INVALID_REQUEST,
INTERNAL_SERVER_ERROR,
文件不存在,
该身份证号码重复,
账号或密码错误,
请求参数错误,
您的登录已失效,
答题记录不存在,
系统繁忙请稍后重试,
网络连接失败,
服务不可用,
请求超时,
获取用户信息失败,
非法登录,
重复答题,
数据不存在
}
/**
* 只用做code码定义
*/
export enum ERRORCODEENUM {
身份验证失败 = 5001,
缺少必要参数_表名 = 5002,
数据表不存在,
}
let bizErrorMsgMap = {};
for (let key in ERRORENUM) {
bizErrorMsgMap[ERRORENUM[key]] = key;
}
export function getBizMsg(param) {
return bizErrorMsgMap[param];
}
\ No newline at end of file
/**
* 表统一管理
*/
export const TablesConfig = {}
export let EccTableConfig = {};
function initEccTableConfig() {
for(let tableChName in TablesConfig) {
let {tableName, schema} = TablesConfig[tableChName];
EccTableConfig[tableName] ={};
for (let filesName in schema) {
let valueType = typeof schema[filesName];
let value = schema[filesName];
EccTableConfig[tableName][filesName] = {type:'', notMustHave:false};
if (valueType == "function") {
EccTableConfig[tableName][filesName].type = value.name;
} else if (valueType == "object") {
if (!value.type) {
EccTableConfig[tableName][filesName].type = `[${value[0].name}]`;
} else {
if (typeof value.type == "function") {
EccTableConfig[tableName][filesName].type = value.type.name
} else EccTableConfig[tableName][filesName].type = `[${value.type[0].name}]`;
if (value.index) EccTableConfig[tableName][filesName].notMustHave = true;
}
}
}
}
console.log('table eccConfig init success');
}
initEccTableConfig();
\ No newline at end of file
import { table } from "console";
import { Association } from "sequelize";
const { Sequelize, DataTypes } = require('sequelize');
export const TablesConfig = [
{
tableNameCn:'维度表',
tableName:'dimension',
schema:{
dimension_id: {
type:Sequelize.STRING(50), //表示属性的数据类型
allowNull:false, //表示当前列是否允许为空, false表示该列不能为空
primaryKey:true, //表示主键
unique:true //表示该列的值必须唯一
},
dimension_name:{type:Sequelize.STRING(50)}, //维度名称(如家国情怀、国际视野)
direction:{type:Sequelize.STRING(10)}, //所属大方向(公/智/能)
},
association: [
{type:"hasMany", check:"question", foreignKey:"dimension_id"}
]
},
{
tableNameCn:'题目表',
tableName:'question',
schema:{
question_id: {
type:Sequelize.STRING(50), //表示属性的数据类型
allowNull:false, //表示当前列是否允许为空, false表示该列不能为空
primaryKey:true, //表示主键
unique:true //表示该列的值必须唯一
},
dimension_id:{type:Sequelize.STRING(50), allowNull:false}, //关联维度表的维度ID
question_content:{type:Sequelize.TEXT}, //题目具体描述
question_order:{type:Sequelize.INTEGER(2)}, //题目在维度内的序号(1-4)
},
association: [
{type:"hasMany", check:"answer_record_detail", foreignKey:"question_id"}
]
},
{
tableNameCn:'答题记录表',
tableName:'answer_record',
schema:{
record_id: {
type:Sequelize.STRING(50),
allowNull:false,
primaryKey:true,
unique:true
},
student_id:{type:Sequelize.STRING(50), allowNull:false}, // 关联学生id
student_name:{type:Sequelize.STRING(50)}, // 学生名称
total_score:{type:Sequelize.INTEGER}, // 总分
answer_time:{type:Sequelize.DATE}, // 答题时间
answer_status:{type:Sequelize.INTEGER}, // 答题状态:0未完成 1已完成
},
association: [
{type:"hasMany", check:"answer_record_detail", foreignKey:"record_id"}
]
},
{
tableNameCn:'答题记录明细表',
tableName:'answer_record_detail',
schema:{
detail_id: {
type:Sequelize.STRING(50),
allowNull:false,
primaryKey:true,
unique:true
},
record_id:{type:Sequelize.STRING(50), allowNull:false}, // 关联答题记录表id
question_id:{type:Sequelize.STRING(50), allowNull:false}, // 关联题目id
score:{type:Sequelize.INTEGER}, // 题目得分(1-7分)
},
association: []
},
{
tableNameCn:'狮子形象表',
tableName:'nm_lion_image',
schema:{
lion_id: {
type:Sequelize.STRING(50),
allowNull:false,
primaryKey:true,
unique:true
},
standard_name:{type:Sequelize.STRING(255), allowNull:false}, // 狮子名称(标准名)
alias_name:{type:Sequelize.STRING(255), allowNull:true}, // 狮子名称(别名)
characteristic:{type:Sequelize.TEXT, allowNull:false}, // 特征描述
judgment_condition:{type:Sequelize.STRING(255), allowNull:false}, // 判定条件
magic_artifact:{type:Sequelize.STRING(255), allowNull:false}, // 法器(优势)名称
magic_artifact_text:{type:Sequelize.TEXT, allowNull:false}, // 法器文字描述
suggestion:{type:Sequelize.STRING(255), allowNull:false}, // 镜囊(建议)名称
suggestion_text:{type:Sequelize.TEXT, allowNull:false}, // 镜囊文字描述
lion_image:{type:Sequelize.STRING(255), allowNull:false}, // 狮子形象(图片地址)
},
association: []
},
{
tableNameCn: 'UAC统一用户表',
tableName: 'uac_user',
schema: {
user_id: {
type: DataTypes.STRING(20), //登录用户名(主键)
allowNull: false,
primaryKey: true,
unique:true
},
group_id: {type: DataTypes.INTEGER, allowNull: false}, //所属组ID
user_name: {type: DataTypes.STRING(20), allowNull: false}, //真实姓名
nickname: {type: DataTypes.STRING(20), allowNull: true}, //昵称
gender: {type: DataTypes.STRING(10), allowNull: true}, //性别(男/女)
email: {type: DataTypes.STRING(50), allowNull: true}, //电子邮件
user_type: {type: DataTypes.STRING(5), allowNull: false}, //用户身份代码(5位,分别代表一般注册用户、行政管理人员、教职员工、学生、家长)
active_flag: {type: DataTypes.INTEGER, allowNull: false, defaultValue: 2}, //用户状态标志(0-禁用,1-正常,2-未激活)
admin_flag: {type: DataTypes.INTEGER, allowNull: false, defaultValue: 0}, //帐号标志(-1:公共帐号,0:一般成员,1:所属组组长,2:超级管理员)
create_date: {type: DataTypes.STRING(19), allowNull: false}, //创建时间(格式:YYYY-MM-DD HH:MM:SS)
extend_info: {type: DataTypes.STRING(250), allowNull: true}, //用户扩展信息(JSON格式)
person_id: {type: DataTypes.STRING(15), allowNull: true}, //自然人ID(额外信息)
id_type: {type: DataTypes.INTEGER, allowNull: true}, //证件类型(1-居民身份证,2-香港特区身份证明...)
id_card: {type: DataTypes.STRING(30), allowNull: true}, //证件号码
name: {type: DataTypes.STRING(50), allowNull: true}, //证件姓名
verified_status: {type: DataTypes.INTEGER, allowNull: true, defaultValue: 0}, //实名认证状态(0-未认证,1-已认证,2-人工认证)
phone: { type: DataTypes.STRING(11), allowNull: true}, //手机号
birthday: {type: DataTypes.STRING(10), allowNull: true}, //生日(格式:YYYY-MM-DD)
password: {type: DataTypes.STRING(20), allowNull: true}, //密码(额外信息)
forbidden_reason: {type: DataTypes.INTEGER, allowNull: true}, //禁用状态标识(1-管理员禁用...7-毕业禁用)
xhjw_user_id: {type: DataTypes.INTEGER, allowNull: true}, //教务系统用户ID
is_sx: {type: DataTypes.INTEGER, allowNull: true, defaultValue: 0}, //师训标记(0-未关联,1-已关联)
order_id: {type: DataTypes.INTEGER, allowNull: true, defaultValue: 0}, //排序号(数字越大越靠前)
student_user_id: {type: DataTypes.STRING(20), allowNull: true}, //家长关联的学生帐号
is_train: {type: DataTypes.INTEGER, allowNull: true, defaultValue: 0}, //是否参训人员(0-否,1-是)
zw_mark: {type: DataTypes.INTEGER, allowNull: true, defaultValue: 0}, //政务标识(0-无,1-有,2-继承组)
eduid: {type: DataTypes.STRING(40), allowNull: true}, //EduID
uoid: {type: DataTypes.INTEGER, allowNull: true, defaultValue: 0}, //本组内排序号
user_mark: {type: DataTypes.STRING(20), allowNull: true}, //用户标识(政务,院务,校务)
},
association: []
},
{
tableNameCn:'管理后台用户',
tableName:'adminUser',
schema:{
aId: {
type:Sequelize.STRING(255), //表示属性的数据类型
allowNull:false, //表示当前列是否允许为空, false表示该列不能为空
primaryKey:true, //表示主键
unique:true //表示该列的值必须唯一
},
loginId:{type:Sequelize.STRING(255), allowNull:false}, //用户
token:{type:Sequelize.STRING(255)}, //token
tokenMs:{type:Sequelize.DATE}, //token的时间
name:{type:Sequelize.STRING(255)},//用户名称
permission:{type:Sequelize.INTEGER, allowNull: true, defaultValue: 0}, //本系统权限-是否管理员(0-否,1-是)
},
association: []
},
{
tableNameCn:'服务开启时间',
tableName:'opening_time',
schema:{
otId:{
type:Sequelize.STRING(255), //表示属性的数据类型
allowNull:false, //表示当前列是否允许为空, false表示该列不能为空
primaryKey:true, //表示主键
unique:true //表示该列的值必须唯一
},
startTime:{type:Sequelize.DATE}, //开始时间
endTime:{type:Sequelize.DATE}, //结束时间
isOpen:{type:Sequelize.INTEGER, allowNull: true, defaultValue: 0}, //是否启用该时间段配置(0-否,1-是)
updated_by:{type:Sequelize.STRING(255)}, //最后修改人
updated_at:{type:Sequelize.DATE}, //最后修改时间
},
association: []
}
];
\ No newline at end of file
const path = require('path');
import * as fs from "fs";
import { BizError } from "../util/bizError";
import { analysisXml } from "../util/myXML";
import { ServerConfig } from "../config/systemClass";
export let systemConfig = new ServerConfig();
const ConfigName = "serverConfig.xml";
export async function initConfig() {
try {
let buff = fs.readFileSync(path.join(__dirname.substring(0, __dirname.indexOf("out")), ConfigName));
let configStr = buff.toString();
let configInfo: any = await analysisXml(configStr);
if (!configInfo || !configInfo.config) throw new BizError('xml中无配置');
let { port, sign, dbServer, mysqldb, xmlRpcServer } = configInfo.config;
// 基本配置
systemConfig.port = parseInt(port[0]);
systemConfig.sign = sign[0];
systemConfig.dbPath = dbServer[0];
// MySQL配置
if (mysqldb) {
let dbConfigInfo = mysqldb[0];
systemConfig.mysqldb = { host: '', port: 0, user: '', pwd: '', dataBase: '' };
if (dbConfigInfo.mysqlHost && dbConfigInfo.mysqlPort && dbConfigInfo.mysqlUser && dbConfigInfo.dataBase) {
systemConfig.mysqldb.host = dbConfigInfo.mysqlHost[0];
systemConfig.mysqldb.port = parseInt(dbConfigInfo.mysqlPort[0]);
systemConfig.mysqldb.user = dbConfigInfo.mysqlUser[0];
systemConfig.mysqldb.pwd = dbConfigInfo.mysqlPwd[0] || "";
systemConfig.mysqldb.dataBase = dbConfigInfo.dataBase[0];
}
}
// XML-RPC服务器配置
if (xmlRpcServer) {
let rpcConfig = xmlRpcServer[0];
systemConfig.xmlRpcServer = {
enabled: rpcConfig.enabled && rpcConfig.enabled[0] === 'true',
port: rpcConfig.port ? parseInt(rpcConfig.port[0]) : 8000,
host: rpcConfig.host ? rpcConfig.host[0] : 'localhost',
path: rpcConfig.path ? rpcConfig.path[0] : '/rpc',
auth: {
username: rpcConfig.auth && rpcConfig.auth[0].username ? rpcConfig.auth[0].username[0] : 'K12RPC',
password: rpcConfig.auth && rpcConfig.auth[0].password ? rpcConfig.auth[0].password[0] : 'K12RPC!Pwd1901'
}
};
} else {
// 默认配置
systemConfig.xmlRpcServer = {
enabled: false,
port: 8000,
host: 'localhost',
path: '/rpc',
auth: {
username: 'K12RPC',
password: 'K12RPC!Pwd1901'
}
};
}
console.log("config init success");
console.log("XML-RPC配置:", systemConfig.xmlRpcServer);
} catch(err) {
console.log('ERROR => 服务器配置解析错误 请检查根目录下 serverConfig.xml 文件是否正确');
console.log(err);
throw new BizError("服务器配置解析错误 请检查根目录下 serverConfig.xml 文件是否正确");
}
}
function analysisMongoConnectStr(path, port, dataBase, w, timeOutMs) {
return `mongodb://${path}:${port}/${dataBase}?w=${w}&wtimeoutMS=${timeOutMs}`;
}
\ No newline at end of file
/**
* 系统配置类
*
*/
export class ServerConfig {
/**系统配置 */
port:number;
sign:string;
dbPath:string;
mysqldb:{
host:string,
port:number,
user:string,
pwd:string,
dataBase:string,
}
/** XML-RPC服务器配置 */
xmlRpcServer: {
enabled: boolean;
port: number;
host: string;
path: string;
auth: {
username: string;
password: string;
};
};
}
\ No newline at end of file
import { mysqlModelMap } from "../model/sqlModelBind";
/**
* 添加数据
* @param tableModel
* @param data
* @returns
*/
export async function addData(tableName:string, data:any) {
let tableModel = mysqlModelMap[tableName];
let dataArray = [];
if (!Array.isArray(data)) {
dataArray.push(data);
} else dataArray = data;
await tableModel.bulkCreate(dataArray);
return { isSuccess:true };
}
\ No newline at end of file
export async function delData(tableModel, param) {
await tableModel.destroy({where:param});
return {isSuccess:true};
}
\ No newline at end of file
import { Op, Sequelize } from "sequelize";
import { ERRORENUM } from "../config/errorEnum";
import { mysqlModelMap } from "../model/sqlModelBind";
import { BizError } from "../util/bizError";
/**
* where条件查询参数
* @param param
* %like%:模糊查询 {列名: {"%like%": }}
* %gt%:大于 {列名: {"%gt%": }}
* %gte%:大于等于 {列名: {"%gte%": }}
* %lt%:小于 {列名: {"%lt%": }}
* %lte%:小于等于 {列名: {"%lte%": }}
* %between%:查询范围内数据 {列名: {"%between%": ["开始参数", "结束参数"]}} ---BETWEEN 开始参数 AND 结束参数 列>开始参数 and 列<结束参数
* %notBetween%:查询不在范围内数据 {列名: {"%notBetween%": ["开始参数", "结束参数"]}} ---NOT BETWEEN 开始参数 AND 结束参数
* %orderDesc%: order by DESC {"%orderDesc%": "列名"}
* %limit%: {"%limit%": 数量}
* @param column
* @returns
*/
function analysisParamToWhere(param, column) {
let where = {};
let order = [];
let group = "";
let literal = "";
let limit = 0;
for (let key in param) {
if (typeof param[key] == "object") {
where[key] = {};
for (let whereKey in param[key]) {
switch (whereKey) {
case "%like%":
where[key][Op.like] = `%${param[key]["%like%"]}%`;
break;
case "%gt%":
where[key][Op.gt] = param[key]["%gt%"];
break;
case "%gte%":
where[key][Op.gte] = param[key]["%gte%"];
break;
case "%lt%":
where[key][Op.lt] = param[key]["%lt%"];
break;
case "%lte%":
where[key][Op.lte] = param[key]["%lte%"];
break;
case "%between%":
where[key][Op.between] = param[key]["%between%"];
break;
case "%notBetween%":
where[key][Op.notBetween] = param[key]["%notBetween%"];
break;
case "%in%":
where[key][Op.in] = param[key]["%in%"];
break;
case "%notIn%":
where[key][Op.notIn] = param[key]["%notIn%"];
break;
case "%ne%":
where[key][Op.ne] = param[key]["%ne%"];
break;
case "%regexp%":
where[key][Op.regexp] = param[key]["%regexp%"];
break;
}
}
} else {
switch (key) {
case "%orderDesc%":
order = [[Sequelize.col(param[key]), "DESC"]];
break;
case "%orderAsc%":
order = [[Sequelize.col(param[key]), "ASC"]];
break;
case "%limit%":
limit = param[key];
break;
case "%group%":
group = param[key];
break;
case "%literal%":
literal = param["%literal%"];
break;
default: where[key] = param[key];
}
}
}
let selectParam: any = { where };
if (column && column.length) selectParam.attributes = column;
if (order && order.length) selectParam.order = order;
if (limit) selectParam.limit = limit;
if (group) selectParam.group = group;
if (literal) selectParam.where = Sequelize.literal(literal);
return selectParam;
}
/**
* 查询单个数据
* @param tableModel 表对象
* @param param
* @returns
*/
export async function selectOneDataByParam(tableName, param, column) {
let tableModel = mysqlModelMap[tableName];
let selectParam = analysisParamToWhere(param, column);
let data = await tableModel.findOne(selectParam);
data = data || {};
return { data };
}
/**
* 查询多个数据
* @param tableName 表对象
* @param param
* @returns
*/
export async function selectDataListByParam(tableName, param, column) {
let tableModel = mysqlModelMap[tableName];
let selectParam = analysisParamToWhere(param, column);
let data = await tableModel.findAll(selectParam);
return { data };
}
/**
* 分页查询
* @param tableModel
* @param param
* @param pageNumber
* @param pageSize
* @returns
*/
export async function selectDataListToPageByParam(tableName, param, column, pageNumber: number, pageSize: number) {
let tableModel = mysqlModelMap[tableName];
let selectParam: any = analysisParamToWhere(param, column);
selectParam.limit = pageSize || 10;
selectParam.offset = (pageNumber - 1) * 10;
let data = await tableModel.findAll(selectParam);
return { data };
}
export async function selectDataCountByParam(tableName, param) {
let tableModel = mysqlModelMap[tableName];
let selectParam: any = analysisParamToWhere(param, []);
let data = await tableModel.count(selectParam);
return { data };
}
export async function associationSelect(tableName: string, param) {
let model = mysqlModelMap[tableName];
if (!model) throw new BizError(ERRORENUM.不存在表);
let data = await model.aggragateData(param);
return {data};
// try {
// let data = await model.aggragateData(param);
// return { data };
// } catch (error) {
// throw new BizError(ERRORENUM.数据查询失败, error.message);
// }
}
/**
* 多表联查 列表
* @param tableModel
* @param includeConf {"表名":["",""] }
* @param param
* @param column
* @returns
*/
export async function selectDataToTableAssociation(tableName, includeConf, param, column) {
let tableModel = mysqlModelMap[tableName];
let include = [];
for (let tableName in includeConf) {
if (!mysqlModelMap[tableName]) throw new BizError(ERRORENUM.不存在表, `尝试进行多表联查,但是不存在${tableName}`);
let {where, column} = includeConf[tableName];
let includeInfomation = analysisParamToWhere(where, column);
includeInfomation.model = mysqlModelMap[tableName];
include.push(includeInfomation);
}
let selectParam: any = analysisParamToWhere(param, column);
selectParam.include = include;
let data = await tableModel.findAll(selectParam);
return { data };
}
/**
* 多表联查 分页
* @param tableModel
* @param includeConf {"表名":["",""] }
* @param param
* @param column
* @returns
*/
export async function selectDataToTableAssociationToPage(tableName, includeConf, param, column, pageNumber: number, pageSize: number) {
let tableModel = mysqlModelMap[tableName];
let include = [];
for (let tableName in includeConf) {
if (!mysqlModelMap[tableName]) throw new BizError(ERRORENUM.不存在表, `尝试进行多表联查,但是不存在${tableName}`);
let { where, column } = includeConf[tableName];
let includeInfomation = analysisParamToWhere(where, column);
includeInfomation.model = mysqlModelMap[tableName];
include.push(includeInfomation);
}
let selectParam: any = analysisParamToWhere(param, column);
selectParam.include = include;
selectParam.limit = pageSize || 10;
selectParam.offset = (pageNumber - 1) * 10;
let data = await tableModel.findAll(selectParam);
return { data };
}
/**
* 多表查询单个
* @param tableModel
* @param includeConf
* @param param
* @param column
* @returns
*/
export async function selectOneDataToTableAssociation(tableName, includeConf, param, column) {
let tableModel = mysqlModelMap[tableName];
let include = [];
for (let tableName in includeConf) {
if (!mysqlModelMap[tableName]) throw new BizError(ERRORENUM.不存在表, `尝试进行多表联查,但是不存在${tableName}`);
let { where, column } = includeConf[tableName];
let includeInfomation = analysisParamToWhere(where, column);
includeInfomation.model = mysqlModelMap[tableName];
include.push(includeInfomation);
}
let selectParam: any = analysisParamToWhere(param, column);
selectParam.include = include;
let data = await tableModel.findOne(selectParam);
data = data || {};
return { data };
}
\ No newline at end of file
import { Op, Sequelize } from "sequelize";
import { mysqlModelMap } from "../model/sqlModelBind";
/**
* where条件查询参数
* @param param
* %like%:模糊查询 {列名: {"%like%": }}
* %gt%:大于 {列名: {"%gt%": }}
* %gte%:大于等于 {列名: {"%gte%": }}
* %lt%:小于 {列名: {"%lt%": }}
* %lte%:小于等于 {列名: {"%lte%": }}
* %between%:查询范围内数据 {列名: {"%between%": ["开始参数", "结束参数"]}} ---BETWEEN 开始参数 AND 结束参数
* %notBetween%:查询不在范围内数据 {列名: {"%notBetween%": ["开始参数", "结束参数"]}} ---NOT BETWEEN 开始参数 AND 结束参数
* %orderDesc%: order by DESC {"%orderDesc%": "列名"}
* %limit%: {"%limit%": 数量}
* @returns
*/
function analysisParamToWhere(param) {
let where = {};
let order = [];
let limit = 0;
for (let key in param) {
if (typeof param[key] == "object") {
where[key] = {};
for (let whereKey in param[key]){
switch(whereKey) {
case "%like%":
where[key][Op.like] = `%${param[key]["%like%"]}%`;
break;
case "%gt%":
where[key][Op.gt] = param[key]["%gt%"];
break;
case "%gte%":
where[key][Op.gte] = param[key]["%gte%"];
break;
case "%lt%":
where[key][Op.lt] = param[key]["%lt%"];
break;
case "%lte%":
where[key][Op.lte] = param[key]["%lte%"];
break;
case "%between%":
where[key][Op.between] = param[key]["%between%"];
break;
case "%notBetween%":
where[key][Op.notBetween] = param[key]["%notBetween%"];
break;
case "%in%":
where[key][Op.in] = param[key]["%in%"];
break;
case "%notIn%":
where[key][Op.notIn] = param[key]["%notIn%"];
break;
}
}
}else {
switch (key) {
case "%orderDesc%":
order = [[Sequelize.col(param[key]), "DESC"]];
break;
case "%orderAsc%":
order = [[Sequelize.col(param[key]), "ASC"]];
break;
case "%limit%":
limit = param[key];
break;
default: where[key] = param[key];
}
}
}
let selectParam:any = {where};
if (order && order.length) selectParam.order = order;
if (limit) selectParam.limit = limit;
return selectParam;
}
export async function updateManyData(tableName, param:object, data:object) {
let tableModel = mysqlModelMap[tableName];
let where = analysisParamToWhere(param);
await tableModel.update(data, where);
return {isSuccess:true};
}
\ No newline at end of file
import { systemConfig } from "../config/serverConfig";
//导入sequelize模块
const Sequelize = require('sequelize');
var mysqlDB;
export async function initMysqlDB() {
mysqlDB = new Sequelize(systemConfig.mysqldb.dataBase,systemConfig.mysqldb.user,systemConfig.mysqldb.pwd,{
host:systemConfig.mysqldb.host,
port:systemConfig.mysqldb.port,
dialect:'mysql', //数据库类型
pool:{ //数据库连接池
max:20, //最大连接对象的个数
min:5, //最小连接对象的个数
idle:1000 //最长等待时间,单位为毫秒
},
timezone: '+08:00', //东八时区
dialectOptions: {
dateStrings: true,
typeCast: true
},
});
}
export { mysqlDB };
\ No newline at end of file
import { initConfig, systemConfig} from "./config/serverConfig";
import * as mysqlDB from "./db/mysqlInit";
import { initMysqlModel } from "./model/sqlModelBind";
import { httpServer } from "./net/http_server";
import { initUACIntegration } from './biz/UAC';
import { BackupService } from './biz/dataBackup';
async function lanuch() {
/**初始化配置解析 */
await initConfig();
/**初始化数据库 */
// await mongoDB.initDB();
// await initModel();
/**初始化sql */
await mysqlDB.initMysqlDB();
await initMysqlModel();
/**创建http服务 */
httpServer.createServer(systemConfig.port);
console.log('This indicates that the server is started successfully.');
backup();
// 应用启动时初始化UAC集成
const xmlRpcServer = await initUACIntegration();
if (xmlRpcServer) {
console.log('🟢 XML-RPC服务器启动成功,正在监听端口:', systemConfig.xmlRpcServer.port);
} else {
console.error('🔴 XML-RPC服务器启动失败');
}
}
lanuch();
function backup() {
const backupService = new BackupService({
host: systemConfig.mysqldb.host,
user: systemConfig.mysqldb.user,
password: systemConfig.mysqldb.pwd,
database: systemConfig.mysqldb.dataBase,
localBackupPath: './nm_backups', // 本地临时备份路径
remoteBackupPath: '/mnt/nm_gzn/backups', // 远程服务器备份路径
remoteHost: '123.207.147.179', //远程服务器ip
remoteUser: 'root', //远程服务器用户名
remotePassword: 'GNIWT20110919!@@@', // 远程服务器密码
sshPort: 22, // SSH端口
retentionDays: 30,
keepLocalBackup: true
});
// 先进行测试
backupService.testBackup()
.then(() => {
console.log('启动定时备份(每天凌晨2:00执行)');
// 启动定时备份(每天凌晨2:00执行)
backupService.startScheduledBackup(2, 0);
})
.catch(error => {
console.error('备份配置测试失败,请检查配置:', error);
});
// 也可以立即执行一次备份
// backupService.backupNow()
// .then(result => console.log('立即备份完成:', result))
// .catch(error => console.error('立即备份失败:', error));
}
import { bizlive } from "tencentcloud-sdk-nodejs";
import { ERRORCODEENUM } from "../config/errorEnum";
/**
* 中间件 错误返回
* @param err
* @param req
* @param res
* @param next
*/
export function httpErrorHandler(err, req, res, next) {
console.log("in httpErrorHandler");
console.log(err);
//todo 自定义错误编码
if (err) {
if ( ERRORCODEENUM[err.message] ) {
res.success({success:false, msg:err.message, code:ERRORCODEENUM[err.message]});
next();
}
else {
res.success({success:false, msg:err.message, code:500});
next();
}
}
}
\ No newline at end of file
import { ERRORENUM } from "../config/errorEnum";
import { systemConfig } from "../config/serverConfig";
import { EccTableConfig } from "../config/mongoTableConfig";
import { BizError } from "../util/bizError";
import { mysqlModelMap } from "../model/sqlModelBind";
import { selectOneDataByParam } from "../data/findData";
import { TABLENAME } from "../config/dbEnum";
/**
* 中间件 校验连接对象token
* @param req
* @param res
* @param next
* @returns
*/
export async function checkMongoSign(req, res, next) {
if (!req.headers) req.headers = {};
let sign = req.headers.sign;
let table = req.headers.table;
if (sign != systemConfig.sign) return next( new BizError(ERRORENUM.身份验证失败, `传入的sign值为:${sign}`) );
if (!table) return next( new BizError(ERRORENUM.缺少必要参数_表名, `传入的table值为:${table}`) );
if (!EccTableConfig[table]) return next( new BizError(ERRORENUM.不存在表, `传入的table值为:${table}`) );
next();
}
/**
* 中间件 校验连接对象token
* @param req
* @param res
* @param next
* @returns
*/
export async function checkMySqlSign(req, res, next) {
if (!req.headers) req.headers = {};
let sign = req.headers.sign;
let table = req.headers.table;
if (sign != systemConfig.sign) return next( new BizError(ERRORENUM.身份验证失败, `传入的sign值为:${sign}`) );
if (!table) return next( new BizError(ERRORENUM.缺少必要参数_表名, `传入的table值为:${table}`) );
if (!mysqlModelMap[table]) return next( new BizError(ERRORENUM.不存在表, `传入的table值为:${table}`) );
req.tableModel = mysqlModelMap[table];
next();
}
export async function checkUser(req, res, next) {
if (!req.headers) req.headers = {};
const userId = req.headers.userid || "";
const reqToken = req.headers.token || "";
if (!userId) return next(new BizError(ERRORENUM.身份验证失败, `studentId:${userId} token:${reqToken}`));
let userDbData:any = await selectOneDataByParam(TABLENAME.管理后台用户, {loginId:userId}, ["loginId", "token"]);
if (!userDbData || !userDbData.data || !userDbData.data.loginId) return next(new BizError(ERRORENUM.非法登录, `userId:${userId} token:${reqToken}`));
if (userDbData.data.token != reqToken) return next(new BizError(ERRORENUM.身份验证失败, `studentId:${userId} `));
const userName = req.headers.username || "";
req.userInfo = {
studentId:userId,
studentName:userName
}
next();
}
export function watch(req, res, next) {
res.success = success.bind({res:res, req:req});
return next();
}
/**
* 中间件正确返回方法
* @param data
*/
function success(data) {
let resultPack;
if (data ) {
if ( data.success === undefined || data.success === true ) {
resultPack = {data, success:true, code:200};
}
else {
resultPack = data;
}
}else {
resultPack = {code:500, success:false, msg:'result is null'};
}
this.res.send(resultPack);
}
/**
* mysql 数据层
*/
import { TablesConfig } from "../config/mysqlTableConfig";
import { mysqlDB } from "../db/mysqlInit";
let mysqlModelMap = {};
export async function initMysqlModel() {
/**初始化表 */
for (let i =0; i < TablesConfig.length; i++) {
let { tableName, schema } = TablesConfig[i];
let schemaConf = {
freezeTableName:true, //true表示使用给定的表名,false表示模型名后加s作为表名
timestamps:false //true表示给模型加上时间戳属性(createAt updateAt),false表示不带时间戳属性
};
let model = mysqlDB.define( tableName, schema, schemaConf);
mysqlModelMap[tableName] = await model.sync({}).then();
// try {
// await model.sync({ force: false });
// mysqlModelMap[tableName] = model;
// } catch (error) {
// console.error(`同步表 ${tableName} 失败:`, error);
// }
}
/**初始化表关联 */
for (let i =0; i < TablesConfig.length; i++) {
let { tableName, association } = TablesConfig[i];
association.forEach( (item:any) => {
if (item) {
let {type, check, foreignKey} = item;
if (type == "hasOne") {
mysqlModelMap[check].hasOne(mysqlModelMap[tableName]);
} else if (type == "hasMany") {
mysqlModelMap[tableName].hasMany(mysqlModelMap[check], {foreignKey});
}
mysqlModelMap[check].belongsTo(mysqlModelMap[tableName], {foreignKey});
// else if (type === "belongsTo") {
// mysqlModelMap[tableName].belongsTo(mysqlModelMap[check], { foreignKey });
// }
console.log("---->", mysqlModelMap[tableName].getTableName());
console.log("====>", mysqlModelMap[check].getTableName());
}
});
}
}
export { mysqlModelMap };
\ No newline at end of file
import express = require('express');
import bodyParser = require('body-parser');
import routers = require('../routers/router');
import compression = require('compression');
import { watch } from '../middleware/watch';
import { httpErrorHandler } from '../middleware/httpErrorHandler';
import * as path from "path";
// import * as fallback from 'express-history-api-fallback';
import historyFallback from 'express-history-api-fallback'; // 正确导入默认导出的函数
export class httpServer {
static createServer(port: number) {
var httpServer = express();
httpServer.all('*', (req, res, next) => {
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header('Access-Control-Allow-Headers', 'Content-Type,request-origin,userid,token,username');
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
res.header('Access-Control-Allow-Credentials', 'true');
res.header("X-Powered-By", ' 3.2.1');
next();
});
httpServer.use(express.static('public'));
httpServer.use(express.static('img'));
httpServer.use(compression());
httpServer.use(watch);
httpServer.use(bodyParser.json({ limit: "500kb" }));
routers.setRouter(httpServer);
httpServer.use(httpErrorHandler);
const root = path.join(__dirname, "../../public");
httpServer.use(express.static(root))
// 使用 fallback 函数
// httpServer.use(fallback('index.html', { root }));
httpServer.use(historyFallback('index.html', { root }));
console.log('web listen on port:' + port);
httpServer.listen(port);
console.log('server listen on port:' + port);
return httpServer;
}
}
\ No newline at end of file
/**
* 公智能评价答题
*/
import asyncHandler from 'express-async-handler';
import * as questionBiz from '../biz/question';
import * as authenticationBiz from '../biz/authentication';
import { checkUser } from '../middleware/user';
export function setRouter(httpServer) {
/**单点登录 */
httpServer.post('/gzn/sso/verify', asyncHandler(authenticationBiz.checkSession));
/**答题 */
httpServer.post('/gzn/question/checkanswered', checkUser, asyncHandler(checkStudentHasAnswered));
httpServer.post('/gzn/question/getppeningtime', checkUser, asyncHandler(getFirstOpeningTimeConfig));
httpServer.post('/gzn/question/setopeningtime', checkUser, asyncHandler(setOpeningTimeConfig));
httpServer.post('/gzn/question/answer', checkUser, asyncHandler(questionsByDirection));
httpServer.post('/gzn/question/completeanswer', checkUser, asyncHandler(completeAnswer));
httpServer.post('/gzn/question/finishanswer', checkUser, asyncHandler(finishAnswer));
httpServer.post('/gzn/question/answerresult', checkUser, asyncHandler(answerResultWithLionImage));
/**管理答题记录 */
httpServer.post('/gzn/admin/allanswerrecords', checkUser, asyncHandler(allStudentAnswerRecords));
httpServer.post('/gzn/admin/studentdetailed', checkUser, asyncHandler(studentDetailed));
httpServer.post('/gzn/admin/batchstudentdetailed', checkUser, asyncHandler(batchStudentDetailed));
}
/**
* 判断学生是否重复答题
* @param req
* @param res
*/
async function checkStudentHasAnswered(req, res) {
const UserInfo = req.userInfo;
let result = await questionBiz.checkStudentHasAnswered(UserInfo.studentId);
res.success(result);
}
/**
* 服务时间配置回显
* @param req
* @param res
*/
async function getFirstOpeningTimeConfig(req, res) {
const userInfo = req.userInfo;
let result = await questionBiz.getFirstOpeningTimeConfig();
res.success(result);
}
/**
* 创建或更新服务时间配置
* @param req
* @param res
*/
async function setOpeningTimeConfig(req, res) {
const UserInfo = req.userInfo;
let {startTime, endTime, isOpen, name, otId} = req.body;
let result = await questionBiz.setOpeningTimeConfig(startTime, endTime, isOpen, name, otId);
res.success(result);
}
// async function checkIsInOpeningTime(req, res) {
// const UserInfo = req.userInfo;
// let {}
// }
/**
* 题目
* @param req
* @param res
*/
async function questionsByDirection(req, res) {
const UserInfo = req.userInfo;
let result = await questionBiz.getQuestionsByDirection();
res.success(result);
}
/**
* 批量完成答题接口
* @param req UserInfo.studentName
* @param res
*/
async function completeAnswer(req, res) {
const UserInfo = req.userInfo;
let {studentName, answers} = req.body;
let result = await questionBiz.completeAnswerBatch(UserInfo.studentId, studentName, answers);
res.success(result);
}
/**
* 完成全部答题
* @param req
* @param res
*/
async function finishAnswer(req, res) {
const UserInfo = req.userInfo;
let {record_id} = req.body;
let result = await questionBiz.finishAnswer(record_id);
res.success(result);
}
/**
* 获取测评得分以及狮子形象
* @param req
* @param res
*/
async function answerResultWithLionImage(req, res) {
const UserInfo = req.userInfo;
let {record_id} = req.body;
let result = await questionBiz.getAnswerResultWithLionImage(record_id);
res.success(result);
}
//管理员页面 ==============================================================================================
/**
* 获取所有学生答题记录及详细得分
* @param req
* @param res
*/
async function allStudentAnswerRecords(req, res) {
const UserInfo = req.userInfo;
let {page} = req.body;
let result = await questionBiz.getAllStudentAnswerRecordsOptimized(page);
// let result = await questionBiz.getAllStudentAnswerRecords();
res.success(result);
}
/**
* 单个答题记录数据下载
* @param req
* @param res
*/
async function studentDetailed(req, res) {
const UserInfo = req.userInfo;
let {record_id} = req.body;
let result = await questionBiz.getStudentDetailed(record_id);
res.success(result);
}
/**
* 批量答题记录数据下载
* @param req
* @param res
*/
async function batchStudentDetailed(req, res) {
const UserInfo = req.userInfo;
let {record_id} = req.body;
let result = await questionBiz.getBatchStudentDetailed(record_id);
res.success(result);
}
/**
* 总路由入口
*/
import * as questionRouter from './question';
export function setRouter(httpServer){
questionRouter.setRouter(httpServer);
}
// test-xmlrpc.js
const xmlrpc = require('xmlrpc');
console.log('🧪 测试所有XML-RPC方法...');
const client = xmlrpc.createClient({
host: '127.0.0.1',
port: 13277, // HTTPS默认端口
path: '/gzn/rpc',
url: 'http://127.0.0.1:13277/gzn/rpc' // 明确指定完整URL
});
// 格式化日期为 YYYY-MM-DD HH:MM:SS 格式
function formatDate(date) {
const d = new Date(date);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
const hours = String(d.getHours()).padStart(2, '0');
const minutes = String(d.getMinutes()).padStart(2, '0');
const seconds = String(d.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
// 测试数据 - 根据表结构调整字段值
const testUser = {
user_id: 'test_user_005',
group_id: 1,
user_name: 'testuser',
user_type: 'stu', // 修改为3个字符,符合 STRING(5) 限制
active_flag: 1,
admin_flag: 0,
create_date: formatDate(new Date()), // 使用格式化后的日期
name: '测试用户',
verified_status: 0,
is_sx: 0,
order_id: 0,
is_train: 0,
zw_mark: 0,
uoid: 0
};
const testUpdateUser = {
user_id: 'test_user_003',
group_id: 1,
user_name: 'testuserupdate',
user_type: 'stu', // 修改为3个字符,符合 STRING(5) 限制
active_flag: 1,
admin_flag: 0,
create_date: formatDate(new Date()), // 使用格式化后的日期
name: '测试用户',
verified_status: 0,
is_sx: 0,
order_id: 0,
is_train: 0,
zw_mark: 0,
uoid: 0
};
const testGroup = {
group_id: 1001,
group_name: '测试组',
parent_id: 0,
thread_id: 1,
group_flag: 1
};
// 修改参数传递方式 - 将参数展开而不是作为数组
const testMethods = [
{
name: 'user.syncUser',
params: testUser,
description: '用户同步'
},
{
name: 'user.addUser',
params: testUser,
description: '添加用户'
},
{
name: 'user.updateUserCommon',
params: testUpdateUser,
description: '更新用户'
},
{
name: 'user.deleteUser',
params: "'test_user_001','test_user_002'",
description: '删除用户'
},
{
name: 'system.listMethods',
params: null,
description: '列出方法'
}
];
// 顺序测试所有方法
let currentIndex = 0;
function testNextMethod() {
if (currentIndex >= testMethods.length) {
console.log('✅ 所有方法测试完成');
return;
}
const method = testMethods[currentIndex];
console.log(`\n${currentIndex + 1}. 测试 ${method.name} (${method.description})...`);
console.log('📤 发送参数:', JSON.stringify(method.params, null, 2));
// 根据参数类型决定如何调用
if (method.params === null || method.params === undefined) {
client.methodCall(method.name, [], (err, result) => {
handleResponse(err, result, method);
});
} else if (Array.isArray(method.params)) {
client.methodCall(method.name, method.params, (err, result) => {
handleResponse(err, result, method);
});
} else {
client.methodCall(method.name, [method.params], (err, result) => {
handleResponse(err, result, method);
});
}
}
function handleResponse(err, result, method) {
if (err) {
console.error(`❌ ${method.name} 失败:`, err.message);
} else {
console.log(`✅ ${method.name} 成功:`, result);
}
currentIndex++;
setTimeout(testNextMethod, 1000);
}
// 开始测试
testNextMethod();
// 超时处理
setTimeout(() => {
console.log('\n⏰ 测试超时');
process.exit(0);
}, 30000);
\ No newline at end of file
import { ERRORENUM } from "../config/errorEnum";
import { BizError } from "../util/bizError";
/**
* 校验value是否符合传入的枚举
* @param name 被掉用名称 用于输出异常日志
* @param key 目标字段 用于输出异常日志
* @param enumConf 目标枚举
* @param value 目标值
* 无返回 有异常直接报错
*/
export function eccEnumValue(name:string, key:string, enumConf, value:any) {
let eccSuccess = true;
if ( typeof value == 'number' ) {
if (!enumConf[value] ) eccSuccess = false;
} else if (Array.isArray(value)) {
value.forEach(item => {
if ( !enumConf[item] ) eccSuccess = false;
});
}
if (!eccSuccess) throw new BizError(ERRORENUM.文件不存在, `${name} 下的 ${key} 字段值为 ${value} 不满足枚举范围`);
}
/**
* 将枚举值转换成对应的枚举名(key)
* @param enumConf 目标枚举
* @param value 目标值
* @returns string 返回字符串 如果传入多个枚举值,就拼接字符串
*/
export function changeEnumValue(enumConf, value:any) {
if (!value) return '';
if ( typeof value == 'number' ) {
let str = enumConf[value];
/**特化处理 */
if(/_dou/.test(str)) str = str.replace(/_dou/gm, ",");
if(/_zyh/.test(str)) str = str.replace(/_zyh/gm, "“");
if(/_yyh/.test(str)) str = str.replace(/_yyh/gm, "”");
if(/_dun/.test(str)) str = str.replace(/_dun/gm, "、");
if(/_ju/.test(str)) str = str.replace(/_ju/gm, "。");
return str
} else if (typeof value == 'string') {
try {//兼容数据库 '[1,2,3]'
value = JSON.parse(value);
}catch(err) {
return enumConf[parseInt(value)];
}
}
let str = "";
value.forEach((item, index) => {
let subStr = enumConf[item];
/**特化处理 */
if(/_dou/.test(subStr)) subStr = subStr.replace(/_dou/gm, ",");
if(/_zyh/.test(subStr)) subStr = subStr.replace(/_zyh/gm, "“");
if(/_yyh/.test(subStr)) subStr = subStr.replace(/_yyh/gm, "”");
if(/_dun/.test(subStr)) subStr = subStr.replace(/_dun/gm, "、");
if(/_ju/.test(subStr)) subStr = subStr.replace(/_ju/gm, "。");
str += subStr;
if (index == value.length-1) str+="";
else str += ","
});
return str;
}
\ No newline at end of file
import { ERRORENUM } from "../config/errorEnum";
import { BizError } from "../util/bizError";
import { TYPEENUM } from "../config/enum";
/**
* 根据conf配置校验请求参数
* @param conf 配置
* @param param 表单
* @param skipKeys []不必填的字段
*/
export function eccReqParamater(conf:object, param, skipKeys?) {
skipKeys = skipKeys || [];
let skipMap = {};
skipKeys.forEach(keyName => {
skipMap[keyName] = 1;
});
/**校验多余字段 */
for (let key in param) {
if (!conf[key]) throw new BizError(ERRORENUM.参数错误, `多余${key}字段`);
}
/**校验必填和缺失字段 */
for (let key in conf) {
let confType = conf[key];
let value = param[key];
let valueType = typeof value;
if ( value == null || value == undefined ) {
if (!skipMap[key]) throw new BizError(ERRORENUM.参数错误, `缺失${key}字段`);
} else {
let isError = false;
let errorStr = "";
switch(confType) {
case 'Number':
if ( confType.toLowerCase() != valueType ) isError = true;
else {
if ((""+param[key]).indexOf('.') > -1) {
param[key] = parseInt(`${param[key] *100}`)/100;
}
}
break;
case 'String':
case 'Boolean':
case 'Object':
if ( confType.toLowerCase() != valueType ) isError = true;
break;
case '[Number]':
if ( !Array.isArray(param[key]) ) isError = true;
for (let i =0; i < param[key].length; i++) {
let item = param[key][i];
if ( typeof item != 'number' ) {
isError = true;
errorStr = `${key}应是number型数组其中下标${i}${typeof item}`;
}
}
break;
case '[Object]':
if ( !Array.isArray(param[key]) ) isError = true;
for (let i =0; i < param[key].length; i++) {
let item = param[key][i];
if ( typeof item != 'object' ) {
isError = true;
errorStr = `${key}应是object型数组其中下标${i}${typeof item}`;
}
}
break;
case '[String]':
if ( !Array.isArray(param[key]) ) isError = true;
for (let i =0; i < param[key].length; i++) {
let item = param[key][i];
if ( typeof item != 'string' ) {
isError = true;
errorStr = `${key}应是String型数组其中下标${i}${typeof item}`;
}
}
break;
// case 'Address':
// /**地址类型 基本数据类型为数组字符串但是要判断层级关系 */
// if ( !Array.isArray(param[key]) ) {
// isError = true;
// errorStr = `${key}应是数组形`;
// }
// if ( param[key].length != 4) {
// isError = true;
// errorStr = `${key}超过特定长度4 目前长度 ${param[key].length}`;
// }
// for (let i =0; i < param[key].length; i++) {
// let item = param[key][i];
// if ( typeof item != 'string' ) {
// isError = true;
// errorStr = `${key}应是string型数组其中下标${i}是${typeof item}`;
// }
// }
// /** 不符合规则的 */
// let nullIndex = -1;
// for (let i = 0; i < param[key].length; i++) {
// if (nullIndex != -1) {//出现过空 第一次出现后的位置 都不能有值
// if (param[key]) {
// //做一个特化
// throw new BizError(ERRORENUM.地址数据不完整, `${key} 下标 ${nullIndex} 为空 `);
// }
// }
// if (nullIndex == -1 && !param[key][i]) {
// /**按顺序第一次赋值 */
// nullIndex = i;
// }
// }
// break;
}
errorStr = isError && errorStr == "" ? `${key}应该是${confType}型 而不是${valueType}`: errorStr;
if (isError) throw new BizError(ERRORENUM.参数错误, errorStr);
}
}
return param;
}
//对象判空
export function objectKeyIsNull(obj, ...keyNames) {
let isNull = false;
for (let i = 0; i < keyNames.length; i++) {
let keyStr = keyNames[i];
let moreKeyList = keyStr.split(".");
let lastObj;
for (let j = 0; j < moreKeyList.length; j++) {
lastObj = obj[moreKeyList[j]];
if (!lastObj) {
isNull = true;
break;
}
}
if (isNull) break;
}
return isNull;
}
/**
* 校验类型
* @param target 目标值
* @param type TYPEENUM枚举值
* @returns 通过 = true 不通过 = false
*/
export function checkType(target, type) {
if (target == undefined || target == null) return false;
switch (type) {
case TYPEENUM.string:
if (typeof target == 'string') {
return true;
}
break;
case TYPEENUM.number:
if (typeof target == 'number') {
return true;
}
break;
case TYPEENUM.object:
if (typeof target == 'object' && !Array.isArray(target) ) {
return true;
}
break;
case TYPEENUM.array:
if (typeof target == 'object' && Array.isArray(target) ) {
return true;
}
break;
case TYPEENUM.boolean:
if (typeof target == 'boolean') {
return true;
}
break;
};
return false;
}
export function checkStrLeng(str, length?) {
length = length ? length : 40;
let result = true;
if (str.length < length) result = false;
return result;
}
import moment = require("moment");
import { ERRORENUM } from "../config/errorEnum";
import { BizError } from "../util/bizError";
import { FILETYPE } from "../config/enum";
const md5 = require("md5");
export function randomId(tableName:string) {
let randomStr = `${new Date().valueOf()}_${Math.ceil(Math.random()*100000)}`;
return `${tableName}_${md5(randomStr)}`;
}
export function getUserToken(loginId:string) {
return md5(`${loginId}_${Math.ceil(Math.random()*1000)}${new Date().valueOf()}`);
}
export function getMySqlMs(time?) {
time = time || new Date().valueOf();
// time += (8*3600*1000);
return moment(time).format("YYYY-MM-DD HH:mm:ss");
}
export function getClientMs(time) {
if (!time) return new Date().valueOf();
return new Date(time).valueOf();
}
export function getPartyMemberId(param) {
return md5(`${param}-${new Date().valueOf()}-${Math.ceil(Math.random() * 10000)}`);
}
export function getDefPwd(phone:string) {
return md5(`${phone.slice(5, 11)}`);
}
export function getFileType(fileName) {
let fileType = 0;
fileName.forEach(info => {
let repList = info.split(".");
let type = repList[repList.length-1];
if (!type) throw new BizError(ERRORENUM.文件不存在, `文件名 ${info}`);
let typeNum = 0;
switch(type) {
case 'pdf': typeNum = FILETYPE.pdf; break;
case 'doc':
case 'docx': typeNum = FILETYPE.word; break;
case 'jpg':
case 'png': typeNum = FILETYPE.图片; break;
};
if (typeNum) {
if (!fileType) fileType = typeNum;
else if (fileType != typeNum) fileType = FILETYPE.多类型;
}
});
return fileType;
}
const xlsx = require('node-xlsx');
const path = require('path');
/**
* onceSheetBecomeOfblockData 将excel文件的指定sheet解析成数据块数据
* @param fileName 文件名称
* @param sheetName 表名称
* @returns [ {blockData:数据块(二维数组), blockTitle:"数据标题"}]
*/
export function onceSheetBecomeOfblockData(fileName, sheetName) {
let {sheetMap} = getExcel( path.join(__dirname.substring(0,__dirname.indexOf("out")), "res", fileName ));
// return sheetMap;
let thisBlockData = getBlockData(sheetMap[sheetName]);
return thisBlockData;
}
/**
* excelBecomeOfBlockData 将excel所有的sheet解析成数据块
* @param fileName 文件名称
* @returns {"sheetName1":[ {blockData:数据块(二维数组), blockTitle:"数据标题"}], ...}
*/
export function excelBecomeOfBlockData(fileName) {
let {sheetMap} = getExcel( path.join(__dirname.substring(0,__dirname.indexOf("out")), "res", fileName ));
let result = {};
for (let sheetName in sheetMap) {
result[sheetName] = getBlockData(sheetMap[sheetName]);
}
return result;
}
/**
* planaryArrayBecomeOfBlockData 将符合excel规则的sheet二维数组转为 数据块
* @param dataList excel解出来的数据
* @returns thisBlockData 返回数据块集合 格式:blockList = [ {blockData:数据块(二维数组), blockTitle:"数据标题"}]
*/
export function planaryArrayBecomeOfBlockData(planaryArray) {
return getBlockData(planaryArray);;
}
//===
/**
* getBlockData 数据分块
* @param dataList 解析出来的excel二维数组
* @returns 返回数据块集合 格式:blockList = [ {blockData:数据块(二维数组), blockTitle:"数据标题"}]
*/
function getBlockData(dataList) {
let blockList = [];
for (let i = 0; i < 999; i++) {
let {blockData, blockTitle, notItem, delDataList} = checkBlock(dataList);
if (notItem) break;
dataList = delDataList;
if (blockTitle) blockList.push({blockData, blockTitle});
}
return blockList;
}
function getListFristNotNullItemIndex(list) { //获取起始坐标
if (!list.length) return null;
for (let i = 0; i < list.length; i++) {
if (list[i]) return i;
}
}
function getListFirstNullItemIndex(startX, list) { //获取第一个为空的坐标
if (!list.length) return null;
let checkItem = false;
let firstItemIndex = 0;
for (let i = startX; i <= list.length; i++) {
let item = list[i];
if (!checkItem && item) checkItem = true;
if (checkItem && !item) {
firstItemIndex = i;
break;
}
}
return firstItemIndex;
}
function listRegionIsNull(list, startX, endX) { //指定区间内数据是否未空
let isNull = true;
if ( !list.length ) return isNull;
for (let i = startX; i < endX; i++) {
let item = list[i];
if (item) {
isNull = false;
break;
}
}
return isNull;
}
function thisListNotItem(list) {
for (let i = 0; i < list.length; i++) {
if (list[i]) return false;
}
return true
}
function checkBlock(dataList) {
//纵向有效起始点
let startY = 0;
let startX = 0;
let isNotBlockTitle = false; //没有块标题
let isLook = false;
let endX = 0;//x轴最长结束下标 【包括下标】
let blockTitle = ''; //标题块名称
let notItem = true;
for (let i = 0; i < dataList.length; i++) {
let childList = dataList[i] || [];
if (!thisListNotItem(childList)) {
if ( !isLook ) {
let thisRoowStartX = getListFristNotNullItemIndex(childList);
let thisRoowLastItem = childList[thisRoowStartX + 1];
let LastList = dataList[i+1] || [];
// let lastRoowStartX = getListFristNotNullItemIndex(LastList);
let lastRoowHaveItem = LastList[thisRoowStartX];
if ( thisRoowLastItem || (LastList.length && lastRoowHaveItem) ) {
if (lastRoowHaveItem && thisRoowLastItem ) {
isNotBlockTitle = true; //不存在标题块
blockTitle = `${thisRoowStartX}_${i}`;
startY = i;
startX = thisRoowStartX;
}
else {
blockTitle = dataList[i][thisRoowStartX];
dataList[i][thisRoowStartX] = null;
if ( thisRoowLastItem ) { // 同行存在元素 标题在y轴上
startY = i;
startX = thisRoowStartX + 1;
} else { // 同行存在元素 标题在x轴上
startY = i + 1;
startX = thisRoowStartX;
}
}
isLook = true;
} else { //只有标题 无内容
console.log(dataList[i][thisRoowStartX]);
dataList[i][thisRoowStartX] = null;
}
} else {
//测量最大连续长度
let firstNullX = getListFirstNullItemIndex(startX, childList);
if (firstNullX) endX = Math.max(endX, firstNullX-1);
break;
}
notItem = false;
}
}
let endY = 0;//y轴连续下标 【包括下标】
let yInfoStart = false;
let yInfoEnd = false;
for (let y = startY; y < dataList.length; y++) {
//纵向找连续性
let thisRoow = dataList[y];
let regionIsNull = listRegionIsNull(thisRoow, startX, endX);
if (!regionIsNull) {
endY = y;
if (!yInfoStart) yInfoStart = true;
}
if (yInfoStart && regionIsNull) yInfoEnd = true;
if (yInfoEnd) break;
}
let blockData = [];
for (let y = startY; y <= endY; y++) {
let onceList = [];
for (let x = startX; x <= endX; x++) {
onceList.push(dataList[y][x]);
dataList[y][x] = null;
}
blockData.push(onceList);
}
return {blockData, blockTitle, delDataList:dataList,notItem};
}
//获取单个excel文件的数据
function getExcel(filePath) {
const workSheetsFromFile = xlsx.parse(filePath);
let sheetMap = {};
let sheetList = [];
for (let i = 0; i < workSheetsFromFile.length; i++) {
let sheetInfo = workSheetsFromFile[i];
sheetMap[sheetInfo.name] = sheetInfo.data;
sheetList.push(sheetInfo);
}
return {sheetMap, sheetList}
}
\ No newline at end of file
/**
* 异常类
* 需要和log4js共同使用
*/
import { getBizMsg } from "../config/errorEnum";
import { logError } from "./log";
export class BizError extends Error {
statusCode(statusCode: any) {
throw new Error('Method not implemented.');
}
constructor(...msgs) {
let reqErrorMsg = '';
let logErrorMsg = '';
for (let i = 0; i <msgs.length; i++) {
if (!i) {
let msg = getBizMsg(msgs[i]);
reqErrorMsg = msg;
logErrorMsg = msg;
} else {
logErrorMsg += ` | ${msgs[i]} `;
}
}
logError(logErrorMsg);
super(reqErrorMsg);
}
}
/**
* 日志类
* 包括错误日志 普通日志
* 日志存放在根目录的logs内
*/
let log4js = require('log4js');
let path = require('path');
//log路径
export const systemLogPath = {
errorLogFile:"error",
errorLogDir:"error",
handleLogFile:"handle",
handleLogDir:"handle"
}
//日志根目录
// let baseLogPath = path.resolve(__dirname.substring(0, __dirname.indexOf("out")), 'logs');
let baseLogPath = path.resolve('./', 'logs');
let errFile = path.resolve(baseLogPath, systemLogPath.errorLogDir, systemLogPath.errorLogFile);
let handFile =path.resolve(baseLogPath, systemLogPath.handleLogDir, systemLogPath.handleLogFile);
let config = {
appenders:
{
"rule-console": {"type": "console"},
"errorLogger": {
"type": "dateFile", // 日志类型
"filename": errFile, // 输出文件名
"pattern": "yyyy-MM-dd.log", // 后缀
"alwaysIncludePattern": true, // 上面两个参数是否合并
"encoding": "utf-8", // 编码格式
"maxLogSize": 1000, // 最大存储内容
"numBackups": 3, // 当文件内容超过文件存储空间时,备份文件的数量
"path": `/${systemLogPath.errorLogDir}`
},
"handleLogger": {
"type": "dateFile",
"filename": handFile,
"pattern": "yyyy-MM-dd.log",
"alwaysIncludePattern": true,
"encoding": "utf-8",
"maxLogSize": 1000,
"numBackups": 3,
"path": `/${systemLogPath.handleLogDir}`
}
},
categories: {
"default": {"appenders": ["rule-console"], "level": "all"}, //这个配置一定要有
"errorLogger": {"appenders": ["errorLogger"], "level": "error"},
"handleLogger": {"appenders": ["handleLogger"], "level": "all"}
},
"baseLogPath": path.resolve(baseLogPath, systemLogPath.handleLogDir, systemLogPath.handleLogFile)
};
log4js.configure(config); //加载配置文件
//调用预先定义的日志名称
let errorLogger = log4js.getLogger("errorLogger");
let handleLogger = log4js.getLogger("handleLogger");
let consoleLogger = log4js.getLogger("rule-console");
//错误日志
export function logError(...errStrs) {
let str = "";
errStrs.forEach(item => {
str += item + " | ";
});
errorLogger.error(`errorInfo => ${str}`);
}
//普通日志
export function logHandle(msgStr:string) {
handleLogger.info(`logInfo => ${msgStr}`);
}
//输出日志
export function logConsole(logStr:string) {
consoleLogger.info(`logInfo => ${logStr}`);
}
/**
* 解析xml
*/
var xml2js = require("xml2js");
/**
*
* @param str 需要解析的xml文本
* @returns 解析好的对象
*/
export function analysisXml(str) {
return new Promise( (resolve, reject) => {
xml2js.parseString(str, (err, result) => {
if (err) return reject(err);
return resolve(result);
});
});
}
\ No newline at end of file
/**
* 零碎的通用工具
*/
import moment = require("moment");
/**
* 匹配新旧对象变化
* 将newObj 与 oldObj 比对,将newObj中发生变化的key返回
* 使用前需要校验对象中的内容
* @param newObj 新对象
* @param oldObj 旧对象
* @returns [key] 发生变化的key
*/
export function checkChange(newObj, oldObj) {
let changeKeyList = [];
for (let newKey in newObj) {
if (`${newObj[newKey]}` != `${oldObj[newKey]}`) changeKeyList.push(newKey);
}
return changeKeyList;
}
/**
* 根据conf截取data中的数据
* @param conf
* @param data
* @returns
*/
export function extractData(conf, data, isAdmin) {
let result = {};
for (let key in conf) {
let confInfo = conf[key];
if (confInfo.changeDate) {
if (isAdmin) result[key] = data[key] ? moment(data[key]).format("YYYY-MM-DD") : '-';
else result[key] = data[key] || 0;
} else if (confInfo.isAdd && isAdmin) {
let addStr = "";
data[key].forEach(str => {
addStr += str;
});
result[key] = addStr;
}
else {
result[key] = data[key];
if (typeof result[key] == 'string' && !result[key]) result[key] = '';
}
}
return result;
}
/**
* 校验数据对象是否有空
* @param data
* @param sensitive 敏感校验 true时 0 和 ""会校验失败 false时 校验成功
* @returns true/false true = 有空值 false=无空值
*/
export function checkDataHaveNull(data:object, sensitive:boolean) {
if (Array.isArray(data)) return data.length == 0;
if (Object.keys(data).length == 0) return true;
let success = false;
for (let key in data) {
if (data[key] == null || data[key] == undefined) success = true;
if (sensitive) {
if (data[key] === 0 || data[key] === "" ) success = true;
}
}
return success;
}
\ No newline at end of file
/**
* 请求工具
*/
import * as request from 'request';
import { BizError } from './bizError';
/**
* 请求接口(get)
* @param url 路由
* @param query 请求参数
* @param headers 请求头
* @returns
*/
export function get(url:string, query?, headers?) {
if (!url || (url.search(/http:/) && url.search(/https:/)) ) throw new BizError(!url ? "请求地址为空" : "请求地址错误");
return new Promise((resolve, reject)=>{
let paramater:any = { url, json:true };
if (query) paramater.qs = query;
if (headers) paramater.headers = headers;
request.get(paramater, function (err, r, body) {
if (err) return reject(err);
if (r && r.statusCode != 200) return reject(new Error('httpError:'+r.statusCode));
resolve(body);
});
})
}
export function post(url, body, headers) {
if (!url || (url.search(/http:/) && url.search(/https:/)) ) throw new BizError(!url ? "请求地址为空" : "请求地址错误");
let header = {"content-type": "application/json"};
return new Promise((resolve, reject)=>{
request({
url: url,
method: "POST",
json: true,
headers: Object.assign(header, headers),
body: body
}, function(error, response, body) {
if (!error && response.statusCode == 200) {
resolve(body);
}
else {
// reject(error)
}
});
})
}
export function postForm(url, body, headers) {
if (!url || (url.search(/http:/) && url.search(/https:/)) ) throw new BizError(!url ? "请求地址为空" : "请求地址错误");
return new Promise((resolve, reject)=>{
request({
url: url,
method: "POST",
json: true,
form:body
}, function(error, response, res) {
if (!error) {
resolve(res);
}
else {
reject(error)
}
});
})
}
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2017",
"sourceMap": true,
"rootDir":"./src",
"outDir":"./out",
"esModuleInterop": true,
// "strict": true,
},
"exclude": [
"node_modules",
]
}
// {
// "compilerOptions": {
// "target": "ES6",
// "module": "commonjs",
// "esModuleInterop": true,
// "outDir": "./dist",
// "strict": true
// },
// "include": ["src/**/*"]
// }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment