'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'); });