Files
zhgh_jsahvc_new/tools/person-governance-api-mock.js
T

225 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
/*
* 人员数据治理 API 配置测试服务,仅使用 Node.js 内置模块。
* 启动此文件后默认监听 http://127.0.0.1:18080,可通过 HOST、PORT 环境变量修改。
* 所有人员信息均为模拟数据,每次启动生成相同的 100 条记录,不连接数据库。
*
* API 配置示例(地址中的主机需保证 Java 服务能够访问):
* Token 地址:http://127.0.0.1:18080/api/token,POST,JSON 请求体 {}
* Access_Token 路径:data->access_token
* Expires_in 路径:data->expires_in,单位:SECONDS
* 数据地址:http://127.0.0.1:18080/api/persons,GET 或 POST
* 返回码路径:code,成功返回值:0,消息路径:msg
* 人员数组路径:data->list,总数路径:data->total
* 数据字段按人员接口字段表生成,全部为字符串;码值为模拟值。
* 字段映射示例:gh→loginname、xm→username、xb→sex、sjhm→mobile、yx→email、lxrq→arrivalAtSchoolDate。
* gh、grbsm 均为唯一标识,可按实际目标中间表字段配置。
* 分页 Query 参数:pageNumber=${页码}、pageSize=${数量},例如起始页码 1、数量 20。
* POST JSON 也可使用 {"pageNumber":"${页码}","pageSize":"${数量}"}。
* 不配置分页参数时一次返回全部 100 条数据。
* 默认不要求认证;MOCK_REQUIRE_TOKEN=1 时,数据接口要求以下任意一种凭证:
* Header Authorization: Bearer ${Access_Token}
* Query 或 JSON 参数 access_token: ${Access_Token}
*/
const http = require('node:http');
const { URL } = require('node:url');
const HOST = process.env.HOST || '127.0.0.1';
const PORT = Number(process.env.PORT || 18080);
const REQUIRE_TOKEN = process.env.MOCK_REQUIRE_TOKEN === '1';
const TOKEN = 'person-governance-mock-token';
const MAX_BODY_BYTES = 1024 * 1024;
const persons = Array.from({ length: 100 }, (_, index) => {
const number = index + 1;
const suffix = String(number).padStart(3, '0');
const department = index % 5;
const departmentCode = `MOCK-D${department + 1}`;
const departmentName = ['计算机学院', '机械工程学院', '经济管理学院', '外国语学院', '教务处'][department];
const female = number % 2 === 0;
const retired = number % 10 === 0;
const birthYear = retired ? 1960 + index % 5 : 1980 + index % 15;
const month = String(index % 12 + 1).padStart(2, '0');
const day = String(index % 28 + 1).padStart(2, '0');
const leader = number % 20 === 0;
return {
xm: `测试人员${suffix}`,
gh: `MOCK${suffix}`,
bmmz: departmentCode,
bm: departmentName,
zcszbm: departmentName,
zcszbmmz: departmentCode,
nl: String(2026 - birthYear),
jg: '江苏南京',
jgmz: '320100',
xb: female ? '女' : '男',
xbmz: female ? '2' : '1',
csrq: `${birthYear}-${month}-${day}`,
mz: '汉族',
mzmz: '01',
gjmz: '156',
gj: '中国',
zzmm: number % 3 === 0 ? '中共党员' : '群众',
zzmmmz: number % 3 === 0 ? '01' : '13',
zjlx: '测试证件',
zjlxmz: 'MOCK',
zjhm: `MOCK-ID-${suffix}`,
lxrq: `2020-${month}-01`,
zgqk: retired ? '退休' : '在岗',
zgqkmz: retired ? '02' : '01',
ryxz: retired ? '离退休人员' : '在职人员',
ryxzmz: retired ? '02' : '01',
yrxs: '事业编制',
yrxsmz: '01',
qzjz: '全职',
qzjzmz: '01',
xrzw: leader ? '部门负责人' : '教师',
zwjb: leader ? '正处级' : '无',
zwjbmz: leader ? '01' : '00',
rxzsj: '2022-09-01',
zgxl: number % 3 === 0 ? '硕士研究生' : '博士研究生',
zgxlmz: number % 3 === 0 ? '14' : '11',
rkxymz: departmentCode,
rkxy: departmentName,
gwlb: '专业技术岗',
gwlbmz: '01',
gwdj: '专业技术七级',
gwdjmz: '07',
gwmc: '教学科研岗',
gwmcmz: '01',
grjsdj: '无',
grjsdjmz: '00',
grdjsj: '',
sjhm: `00000000${suffix}`,
rysfmz: '01',
rysf: '教职工',
daszdw: '测试学校档案馆',
grbsm: `MOCK-PERSON-${suffix}`,
gwxz: '固定岗位',
gwxzmz: '01',
dezc: '无',
dezcmz: '00',
dezcjb: '无',
dezcjbmz: '00',
sfbmfzr: leader ? '是' : '否',
sfbmfzrmz: leader ? '1' : '0',
xldfgbm: '',
xldfgbmmz: '',
zcldfzbmmz: leader ? departmentCode : '',
zcldfzbm: leader ? departmentName : '',
zjsj: '2022-09-01',
xzw2: '',
zw2rzsj: '',
xzw3: '',
xzw4: '',
fdylb: '非辅导员',
fdylbmz: '00',
yx: `mock${suffix}@example.com`,
lxdh: `00000000${suffix}`,
qq: String(900000000 + number),
gz: '教学'
};
});
function reply(response, status, code, msg, data = null) {
response.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
response.end(JSON.stringify({ code, msg, data }));
}
function readBody(request) {
return new Promise((resolve, reject) => {
const chunks = [];
let bytes = 0;
let tooLarge = false;
request.on('data', chunk => {
bytes += chunk.length;
if (bytes > MAX_BODY_BYTES) {
if (!tooLarge) {
tooLarge = true;
reject(Object.assign(new Error('请求体超过 1MB'), { status: 413 }));
}
return;
}
chunks.push(chunk);
});
request.on('error', reject);
request.on('end', () => {
if (tooLarge) return;
const body = Buffer.concat(chunks).toString('utf8');
if (!body.trim()) return resolve({});
try {
const contentType = (request.headers['content-type'] || '').split(';')[0].trim();
const value = contentType === 'application/x-www-form-urlencoded'
? Object.fromEntries(new URLSearchParams(body)) : JSON.parse(body);
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new Error('请求体必须为对象');
}
resolve(value);
} catch (error) {
reject(Object.assign(new Error('请求体须为 JSON 对象或表单参数'), { status: 400 }));
}
});
});
}
function positiveInteger(value, fallback, name) {
if (value === undefined) return fallback;
if (!/^\d+$/.test(String(value)) || !Number.isSafeInteger(Number(value)) || Number(value) < 1) {
throw Object.assign(new Error(`${name} 必须为正整数`), { status: 400 });
}
return Number(value);
}
const server = http.createServer(async (request, response) => {
try {
const url = new URL(request.url, 'http://localhost');
if (!['/api/token', '/api/persons'].includes(url.pathname)) {
request.resume();
return reply(response, 404, 404, '接口不存在');
}
if (!['GET', 'POST'].includes(request.method)) {
request.resume();
response.setHeader('Allow', 'GET, POST');
return reply(response, 405, 405, '仅支持 GET 和 POST');
}
const body = await readBody(request);
const params = { ...Object.fromEntries(url.searchParams), ...body };
if (url.pathname === '/api/token') {
return reply(response, 200, 0, '成功', {
access_token: TOKEN, expires_in: 7200, token_type: 'Bearer'
});
}
if (REQUIRE_TOKEN && request.headers.authorization !== `Bearer ${TOKEN}`
&& params.access_token !== TOKEN) {
return reply(response, 401, 401, 'Token 无效或未提供');
}
const pageNumber = positiveInteger(params.pageNumber ?? params.page, 1, 'pageNumber');
const pageSize = positiveInteger(params.pageSize, 100, 'pageSize');
if (pageSize > 1000) {
return reply(response, 400, 400, 'pageSize 不能超过 1000');
}
const start = (pageNumber - 1) * pageSize;
return reply(response, 200, 0, '成功', {
list: persons.slice(start, start + pageSize),
total: persons.length,
pageNumber,
pageSize
});
} catch (error) {
const status = error.status || 500;
reply(response, status, status, status === 500 ? '测试服务处理失败' : error.message);
}
});
server.on('error', error => {
console.error(`测试服务启动失败:${error.message}`);
process.exitCode = 1;
});
server.listen(PORT, HOST, () => {
console.log(`人员治理测试服务:http://${HOST}:${PORT},已预填充 ${persons.length} 条模拟数据`);
console.log('Token 接口:/api/token;人员接口:/api/persons');
});