school.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. 'use strict';
  2. const assert = require('assert');
  3. const _ = require('lodash');
  4. const { ObjectId } = require('mongoose').Types;
  5. const { CrudService } = require('naf-framework-mongoose/lib/service');
  6. const { BusinessError, ErrorCode } = require('naf-core').Error;
  7. const moment = require('moment');
  8. const XLSX = require('xlsx');
  9. const Excel = require('exceljs');
  10. const { sep } = require('path');
  11. class SchoolService extends CrudService {
  12. constructor(ctx) {
  13. super(ctx, 'schoolctrl');
  14. this.model = this.ctx.model.School;
  15. this.smodel = this.ctx.model.Student;
  16. this.umodel = this.ctx.model.User;
  17. this.tmodel = this.ctx.model.Trainplan;
  18. this.jmodel = this.ctx.model.Job;
  19. this.schmodel = this.ctx.model.Schtime;
  20. }
  21. async findByCodes({ code }) {
  22. if (!_.isArray(code) || code.length <= 0) return [];
  23. const res = await this.model.find({ code }, { name: 1, code: 1, level: 1, hascar: 1, address: 1 });
  24. return res;
  25. }
  26. async create(data) {
  27. const { code, name } = data;
  28. assert(code, '缺少学校代码');
  29. assert(name, '缺少学校名称');
  30. const res = await this.model.create(data);
  31. if (res) {
  32. const obj = {
  33. mobile: code,
  34. name,
  35. type: '2',
  36. uid: res._id,
  37. passwd: { secret: '12345678' },
  38. };
  39. await this.umodel.create(obj);
  40. }
  41. return res;
  42. }
  43. async delete({ id }) {
  44. await this.model.findByIdAndDelete(id);
  45. await this.umodel.deleteOne({ uid: id, type: '2' });
  46. return 'deleted';
  47. }
  48. async query({ name, ...data }, { skip, limit }) {
  49. const query = { ...data };
  50. if (name) {
  51. query.name = { $regex: name };
  52. }
  53. let res = await this.model.find(query).skip(parseInt(skip)).limit(parseInt(limit));
  54. if (res && res.length > 0) {
  55. res = JSON.parse(JSON.stringify(res));
  56. const ids = res.map((i) => i._id);
  57. const users = await this.umodel.find({ uid: { $in: ids } }, '+passwd');
  58. for (const tea of res) {
  59. const r = users.find((f) => f.uid === tea._id);
  60. if (r) {
  61. const passwd = _.get(r.passwd, 'secret');
  62. if (passwd) tea.passwd = passwd;
  63. }
  64. }
  65. }
  66. return res;
  67. }
  68. async count({ name, ...data } = {}) {
  69. const query = { ...data };
  70. if (name) {
  71. query.name = { $regex: name };
  72. }
  73. return await this.model.count(query);
  74. }
  75. // 去重查询学校
  76. async findSchool({ name, skip, limit, ...info }) {
  77. let data = [];
  78. const query = { ...info };
  79. if (name) {
  80. query.name = { $regex: name };
  81. }
  82. const AggregateInfo = [
  83. // 这里可以添加其他查询条件,例如过滤特定字段等
  84. { $match: query },
  85. // 去除重复数据
  86. { $group: { _id: '$code', uniqueData: { $first: '$$ROOT' } } },
  87. { $replaceRoot: { newRoot: '$uniqueData' } },
  88. { $addFields: { id: '$_id' } },
  89. { $sort: { code: 1 } },
  90. // 分页查询
  91. { $skip: parseInt(skip) },
  92. ];
  93. if (limit) AggregateInfo.push({ $limit: parseInt(limit) });
  94. const res = await this.model.aggregate(AggregateInfo);
  95. if (res && res.length > 0) {
  96. data = JSON.parse(JSON.stringify(res));
  97. const ids = res.map((i) => i._id);
  98. const users = await this.umodel.find({ uid: { $in: ids } }, '+passwd').lean();
  99. for (const tea of data) {
  100. const r = users.find((f) => f.uid === tea._id);
  101. if (r) {
  102. const passwd = _.get(r.passwd, 'secret');
  103. if (passwd) tea.passwd = passwd;
  104. }
  105. }
  106. }
  107. const count = await this.model.aggregate([{ $match: query }, { $group: { _id: '$code' } }, { $count: 'distinctCount' }]);
  108. const total = _.get(count[0], 'distinctCount') || 0;
  109. return { total, data };
  110. }
  111. async stuimport(data) {
  112. const { filepath, termid, schid, type, batchid } = data;
  113. assert(filepath, 'filepath不能为空');
  114. assert(termid, 'termid不能为空');
  115. assert(schid, 'schid不能为空');
  116. // 根据termid取得计划信息
  117. const plan = await this.tmodel.findOne({ 'termnum._id': ObjectId(termid) });
  118. if (!plan) {
  119. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '计划信息不存在');
  120. }
  121. const term = plan.termnum.id(termid);
  122. const planid = plan.id;
  123. const planyearid = plan.planyearid;
  124. // 检查这个范围的学生是否存在,存在的话是否更改过(classid,bedroomid这两项存不存在可以放过,但凡有一个人,就不行了)
  125. let dbStuList = await this.ctx.model.Student.find({ termid, batchid, schid });
  126. if (dbStuList.length > 0) {
  127. // 查这个学校的这期学生是否修改过班级 或 寝室
  128. const is_change = dbStuList.find((f) => f.classid ); // || 2024-07-05 修改:f.bedroomid去掉,因为现在不在中心培训了,不管寝室了
  129. if (is_change) {
  130. throw new BusinessError(
  131. ErrorCode.BUSINESS,
  132. '上报过的学生已经安排班级!若需要替换学生,让同性别的学生直接来和班主任说,修改信息即可.若还是有疑问,请和中心负责人联系(最好联系下)', // 或寝室
  133. );
  134. }
  135. }
  136. // 2021-06-07 如果学生已经绑定,那也不允许修改名单了
  137. const countOpenid = await this.ctx.model.Student.count({ termid, batchid, schid, openid: { $exists: true } });
  138. if (countOpenid > 0) throw new BusinessError(ErrorCode.BUSINESS, '已有学生绑定账号,名单无法修改.若有问题请联系中心负责人!');
  139. // 获取学校名称
  140. let school_name;
  141. const sch = await this.ctx.model.School.findOne({ code: schid });
  142. if (sch) school_name = sch.name;
  143. let domain = 'http://127.0.0.1';
  144. if (process.env.NODE_ENV === 'development') domain = 'http://jytz.jilinjobs.cn';
  145. const fullUrl = domain + filepath; // this.ctx.app.config.baseUrl http://127.0.0.1 http://jytz.jilinjobs.cn
  146. let studentList = await this.getDataFromExcel(fullUrl);
  147. const checkRes = await this.checkData(studentList);
  148. const { errorcode } = checkRes;
  149. if (errorcode === '1') {
  150. return checkRes;
  151. }
  152. // 2021-05-26 添加与数据库的对比,如果数据库里已经有这个身份证号,就需要提示
  153. const countStudent = await this.countStudent(studentList, planid);
  154. const { errorcode: csec } = countStudent;
  155. if (csec === '1') {
  156. return countStudent;
  157. }
  158. // 整理数据
  159. studentList = this.lastSetData(studentList, {
  160. planyearid,
  161. planid,
  162. batchid,
  163. termid,
  164. type,
  165. schid,
  166. school_name,
  167. });
  168. const num = await this.getschnum(plan, schid, batchid);
  169. // 查看要求人数和整理完最后的人数能不能对上
  170. if (studentList.length !== num) {
  171. const res = await this.jmodel.findOne({ code: schid, batchid });
  172. const reason = `学校上传人数${studentList.length > num ? '多于' : '少于'}预期人数,请联系中心管理员`;
  173. if (res) {
  174. res.reason = reason;
  175. res.filepath = filepath;
  176. await res.save();
  177. } else {
  178. const job = {
  179. code: schid,
  180. name: school_name,
  181. planid,
  182. termid,
  183. term: term.term,
  184. batchid,
  185. filepath,
  186. studs: JSON.stringify(studentList),
  187. plannum: num,
  188. schnum: studentList.length,
  189. isstore: '0',
  190. createtime: moment().format('YYYY-MM-DD HH:SS:mm'),
  191. type,
  192. };
  193. job.reason = reason;
  194. await this.jmodel.create(job);
  195. }
  196. throw new BusinessError(ErrorCode.SERVICE_FAULT, reason);
  197. } else {
  198. // 复制,删除,添加
  199. if (dbStuList.length > 0) {
  200. dbStuList = JSON.parse(JSON.stringify(dbStuList));
  201. dbStuList = dbStuList.map((i) => {
  202. delete i.meta;
  203. i.studentid = _.clone(i._id);
  204. delete i.id;
  205. delete i._id;
  206. return i;
  207. });
  208. await this.smodel.deleteMany({ termid, batchid, schid });
  209. await this.ctx.model.Dstudent.insertMany(dbStuList);
  210. }
  211. await this.smodel.insertMany(studentList);
  212. }
  213. return 'ok';
  214. }
  215. /**
  216. * 检查学生是否参加过这个计划以外的计划,参加过就不让来了
  217. * @param {Array} studentList 学生列表
  218. * @param {String} planid 计划id
  219. */
  220. async countStudent(studentList, planid) {
  221. let errorcode = '0';
  222. const errormsg = [];
  223. for (const stu of studentList) {
  224. const { name, id_number } = stu;
  225. let error = false;
  226. let msg = '';
  227. const count = await this.smodel.count({ id_number, planid: { $ne: planid } });
  228. if (count > 0) {
  229. error = true;
  230. msg = `${msg}${name}已经参加过培训`;
  231. }
  232. if (error) {
  233. errorcode = '1';
  234. stu.msg = msg;
  235. errormsg.push(stu);
  236. }
  237. }
  238. return { errorcode, errormsg };
  239. }
  240. // 取得学校预计人数
  241. async getschnum(plan, schid, batchid) {
  242. const schtime = await this.schmodel.findOne({ schid, planid: plan.id });
  243. const { arrange } = schtime;
  244. const r = arrange.find((f) => f.batchid === batchid);
  245. if (!r) {
  246. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '没有找到该学校的计划');
  247. }
  248. const { number } = r;
  249. return parseInt(number);
  250. }
  251. // 整理excel数据
  252. async getDataFromExcel(url) {
  253. // 请求文件
  254. const file = await this.ctx.curl(`${url}`);
  255. if (!(file && file.data)) {
  256. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到上传的名单');
  257. }
  258. const workbook = new Excel.Workbook();
  259. // 读取文件
  260. await workbook.xlsx.load(file.data);
  261. const worksheet = workbook.getWorksheet(1);
  262. if (!worksheet) {
  263. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未发现excel中有工作表');
  264. }
  265. // 获取表头,通过方法的返回值,将写死的表头数组返回 回来
  266. const cols = this.getStucolumn();
  267. // 第一行(表头)
  268. const headRow = worksheet.getRow(1);
  269. // 设置,检查表头
  270. headRow.eachCell((cell, coli) => {
  271. console.log(cell.value);
  272. if (cell.value !== '序号') {
  273. const r = cols.find((f) => f.key === cell.value);
  274. if (r) {
  275. const ri = cols.findIndex((f) => f.key === cell.value);
  276. // 表头符合要求,做上标记
  277. r.colIndex = coli;
  278. cols[ri] = r;
  279. } else {
  280. throw new BusinessError(`模板中"${cell.value}"列错误,请检查excel!`);
  281. }
  282. }
  283. });
  284. // 检查表头结果,如果有没有 colIndex,说明表头里有不符合要求的,退回去
  285. const excelIsRigth = cols.every((f) => f.colIndex);
  286. if (!excelIsRigth) throw new BusinessError(ErrorCode.DATA_INVALID, 'Excel表格格式不正确,请使用系统提供的模板,或重新下载模板!');
  287. // 删除掉第一行 表头行,这不是数据
  288. worksheet.spliceRows(0, 1);
  289. const stuList = [];
  290. const noWhite = (str) => str.replace(/\s*/g, '');
  291. // 整理数据,根据检查合格的表头行,获取每个格子的数据,制成[object]格式
  292. worksheet.eachRow((row) => {
  293. const stu = {};
  294. for (let i = 0; i < cols.length; i++) {
  295. const col = cols[i];
  296. if (!col) break;
  297. let val = _.trim(row.getCell(col.colIndex));
  298. if (col.column === 'id_number') val = val.toUpperCase();
  299. if (val && val !== '') val = noWhite(val);
  300. stu[col.column] = val;
  301. }
  302. stuList.push(stu);
  303. });
  304. return stuList;
  305. }
  306. // 数据校验
  307. async checkData(stuList) {
  308. const cols = this.getStucolumn();
  309. let errorcode = '0';
  310. const errormsg = [];
  311. for (const stu of stuList) {
  312. const { name } = stu;
  313. let error = false;
  314. let msg = '';
  315. // 各个字段检查,最低为非空检查
  316. for (const col of cols) {
  317. const { key, column } = col;
  318. if (!column) throw new BusinessError(ErrorCode.SERVICE_FAULT, '未找到导出的字段名');
  319. const val = _.get(stu, column);
  320. // 空校验
  321. if (!val || val === '') {
  322. error = true;
  323. msg = `${msg}"${key}"不能为空;`;
  324. continue;
  325. }
  326. // 性别校验
  327. if (column === 'gender') {
  328. if (!(val.includes('男') || val.includes('女'))) {
  329. error = true;
  330. msg = `${msg}性别错误;`;
  331. }
  332. continue;
  333. }
  334. // 身份证号校验
  335. if (column === 'id_number') {
  336. // 因为删除再添加的流程导致此处 不能 校验数据库中是否有这个身份证号
  337. // const res = await this.ctx.model.Student.findOne({ id_number: val });
  338. // if (!res) {
  339. const { pass, msg: idmsg } = this.ctx.service.school.idCodeValid(val);
  340. if (!pass) {
  341. error = true;
  342. msg = `${msg}${idmsg};`;
  343. }
  344. // } else {
  345. // error = true;
  346. // msg = `${msg}学生已存在`;
  347. // }
  348. const have_same = stuList.filter((f) => f.id_number === val && f.name !== name);
  349. if (have_same.length > 0) {
  350. error = true;
  351. const h = _.head(have_same);
  352. const num = have_same.length;
  353. if (num === 1) {
  354. msg = `${msg}身份证号与本次名单的"${h.name}"重复;`;
  355. } else msg = `${msg}身份证号与本次名单中"${h.name}"等${num}人重复;`;
  356. }
  357. continue;
  358. }
  359. // 手机号校验
  360. if (column === 'phone') {
  361. // 因为删除再添加的流程导致此处 不能 校验数据库中是否有这个手机号
  362. // const res = await this.ctx.model.Student.findOne({ phone: val });
  363. // if (!res) {
  364. if (!/^\d{11}$/i.test(val)) {
  365. error = true;
  366. msg = `${msg}手机号位数不正确;`;
  367. }
  368. // } else {
  369. // error = true;
  370. // msg = `${msg}学生库中已有该手机号,请检查手机号是否正确,若无误,请联系中心负责人`;
  371. // }
  372. const have_same = stuList.filter((f) => f.phone === val && f.name !== name);
  373. if (have_same.length > 0) {
  374. error = true;
  375. const h = _.head(have_same);
  376. const num = have_same.length;
  377. if (num === 1) {
  378. msg = `${msg}手机号与本次名单的"${h.name}"重复;`;
  379. } else msg = `${msg}手机号与本次名单中"${h.name}"等${num}人重复;`;
  380. }
  381. continue;
  382. }
  383. // 专业校验
  384. if (column === 'major') {
  385. if (val.includes('专业')) {
  386. error = true;
  387. msg = `${msg}专业列不能含有"专业"二字;`;
  388. }
  389. continue;
  390. }
  391. // 入学年份
  392. if (column === 'entry_year') {
  393. const m = /^\w{4}$/;
  394. if (!val.match(m)) {
  395. error = true;
  396. msg = `${msg}入学年份格式不正确,只填写4位数字;`;
  397. }
  398. continue;
  399. }
  400. // 毕业年份
  401. if (column === 'finish_year') {
  402. const m = /^\w{4}$/;
  403. if (!val.match(m)) {
  404. error = true;
  405. msg = `${msg}毕业年份格式不正确,只填写4位数字;`;
  406. }
  407. continue;
  408. }
  409. // 双困检查
  410. if (column === 'family_is_hard') {
  411. if (!(val.includes('是') || val.includes('否'))) {
  412. error = true;
  413. msg = `${msg}家庭是否困难填写"是"或"否";`;
  414. }
  415. continue;
  416. }
  417. if (column === 'have_grant') {
  418. if (!(val.includes('是') || val.includes('否'))) {
  419. error = true;
  420. msg = `${msg}是否获得过助学金填写"是"或"否";`;
  421. }
  422. continue;
  423. }
  424. }
  425. if (error) {
  426. errorcode = '1';
  427. stu.msg = msg;
  428. errormsg.push(stu);
  429. }
  430. }
  431. return { errorcode, errormsg };
  432. }
  433. // 最后整合数据
  434. lastSetData(stuList, data) {
  435. const cols = this.getStucolumn();
  436. const needChange = cols.filter((f) => f.change);
  437. stuList = stuList.map((i) => {
  438. const d = { ...i, ...data };
  439. for (const col of needChange) {
  440. const { column, change } = col;
  441. if (!column && change && _.isArray(change)) continue;
  442. const val = _.get(d, column);
  443. if (!val) continue;
  444. const r = change.find((f) => f.key === val);
  445. if (!r) continue;
  446. const { value } = r;
  447. d[column] = value;
  448. }
  449. return d;
  450. });
  451. return stuList;
  452. }
  453. // excel中学生字段
  454. getStucolumn() {
  455. const arr = [
  456. { key: '姓名', column: 'name' },
  457. { key: '性别', column: 'gender' },
  458. { key: '民族', column: 'nation' },
  459. { key: '身份证号', column: 'id_number' },
  460. { key: '学校名称', column: 'school_name' },
  461. { key: '学历层次', column: 'edua_level' },
  462. { key: '学制', column: 'edua_system' },
  463. { key: '院(系)', column: 'faculty' },
  464. { key: '专业', column: 'major' },
  465. { key: '入学年份', column: 'entry_year' },
  466. { key: '毕业年份', column: 'finish_year' },
  467. { key: '在校曾担任何种职务', column: 'school_job' },
  468. { key: '手机号', column: 'phone' },
  469. { key: 'QQ号', column: 'qq' },
  470. { key: '家庭所在地', column: 'family_place' },
  471. {
  472. key: '家庭是否困难',
  473. column: 'family_is_hard',
  474. change: [
  475. { key: '否', value: '0' },
  476. { key: '是', value: '1' },
  477. ],
  478. },
  479. {
  480. key: '是否获得过助学金',
  481. column: 'have_grant',
  482. change: [
  483. { key: '否', value: '0' },
  484. { key: '是', value: '1' },
  485. ],
  486. },
  487. ];
  488. return arr;
  489. }
  490. // 导出学校名单
  491. async exportSchool({ trainplanId }) {
  492. // 批次期次都在这里面
  493. const trainplan = await this.tmodel.find({ _id: trainplanId });
  494. const _headers = [{ key: 'title', title: '计划标题' }];
  495. // 需要打出的列表
  496. const _data = trainplan;
  497. const headers = _headers
  498. .map(({ title }) => title)
  499. .map((v, i) => Object.assign({}, { v, position: String.fromCharCode(65 + i) + 1 }))
  500. .reduce((prev, next) => Object.assign({}, prev, { [next.position]: { v: next.v } }), {});
  501. const data = _data
  502. .map((v, i) => _headers.map(({ key }, j) => Object.assign({}, { v: v[key], position: String.fromCharCode(65 + j) + (i + 2) })))
  503. .reduce((prev, next) => prev.concat(next))
  504. .reduce((prev, next) => Object.assign({}, prev, { [next.position]: { v: next.v } }), {});
  505. // 合并 headers 和 data
  506. const output = Object.assign({}, headers, data);
  507. // 获取所有单元格的位置
  508. const outputPos = Object.keys(output);
  509. // 计算出范围
  510. const ref = outputPos[0] + ':' + outputPos[outputPos.length - 1];
  511. // 构建 workbook 对象
  512. const nowDate = new Date().getTime();
  513. // 地址有问题,需要引用config变量
  514. const { repos_root_path: rp } = this.ctx.app.config.cdn;
  515. const { baseUrl: bu } = this.ctx.app.config;
  516. const path = `${rp}${sep}train${sep}${nowDate}.xlsx`;
  517. const respath = `${bu}/files/train/${nowDate}.xlsx`;
  518. const wb = {
  519. SheetNames: ['sheet0'],
  520. Sheets: { sheet0: Object.assign({}, output, { '!ref': ref }) },
  521. };
  522. // 导出 Excel
  523. XLSX.writeFile(wb, path);
  524. return respath;
  525. }
  526. async updateclass({ trainplanid, classid, rightHeader }) {
  527. assert(trainplanid && classid && rightHeader, '缺少参数项');
  528. // 根据全年计划表id查出对应的全年计划详细信息
  529. const trainplan = await this.model.findById(trainplanid);
  530. if (!trainplan) {
  531. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '全年计划信息不存在');
  532. }
  533. for (const term of trainplan.termnum) {
  534. for (const batch of term.batchnum) {
  535. const class_ = await batch.class.id(classid);
  536. if (class_) {
  537. class_.headteacherid = rightHeader;
  538. }
  539. }
  540. }
  541. return await trainplan.save();
  542. }
  543. async updatereteacher({ trainplanid, termid, reteacher }) {
  544. assert(trainplanid && termid && reteacher, '缺少参数项');
  545. // 根据全年计划表id查出对应的全年计划详细信息
  546. const trainplan = await this.model.findById(trainplanid);
  547. if (!trainplan) {
  548. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '全年计划信息不存在');
  549. }
  550. const term = await trainplan.termnum.id(termid);
  551. if (term) {
  552. term.reteacher = reteacher;
  553. }
  554. return await trainplan.save();
  555. }
  556. // 身份证验证
  557. idCodeValid(code) {
  558. // 身份证号合法性验证
  559. // 支持15位和18位身份证号
  560. // 支持地址编码、出生日期、校验位验证
  561. const city = {
  562. 11: '北京',
  563. 12: '天津',
  564. 13: '河北',
  565. 14: '山西',
  566. 15: '内蒙古',
  567. 21: '辽宁',
  568. 22: '吉林',
  569. 23: '黑龙江 ',
  570. 31: '上海',
  571. 32: '江苏',
  572. 33: '浙江',
  573. 34: '安徽',
  574. 35: '福建',
  575. 36: '江西',
  576. 37: '山东',
  577. 41: '河南',
  578. 42: '湖北 ',
  579. 43: '湖南',
  580. 44: '广东',
  581. 45: '广西',
  582. 46: '海南',
  583. 50: '重庆',
  584. 51: '四川',
  585. 52: '贵州',
  586. 53: '云南',
  587. 54: '西藏 ',
  588. 61: '陕西',
  589. 62: '甘肃',
  590. 63: '青海',
  591. 64: '宁夏',
  592. 65: '新疆',
  593. 71: '台湾',
  594. 81: '香港',
  595. 82: '澳门',
  596. 91: '国外 ',
  597. };
  598. let row = {
  599. pass: true,
  600. msg: '验证成功',
  601. };
  602. if (!code || !/^\d{6}(18|19|20)?\d{2}(0[1-9]|1[012])(0[1-9]|[12]\d|3[01])\d{3}(\d|[xX])$/.test(code)) {
  603. row = {
  604. pass: false,
  605. msg: '身份证号格式错误',
  606. };
  607. } else if (!city[code.substr(0, 2)]) {
  608. row = {
  609. pass: false,
  610. msg: '身份证号地址编码错误',
  611. };
  612. } else {
  613. // 18位身份证需要验证最后一位校验位
  614. if (code.length === 18) {
  615. code = code.split('');
  616. // ∑(ai×Wi)(mod 11)
  617. // 加权因子
  618. const factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
  619. // 校验位
  620. const parity = [1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2];
  621. let sum = 0;
  622. let ai = 0;
  623. let wi = 0;
  624. for (let i = 0; i < 17; i++) {
  625. ai = code[i];
  626. wi = factor[i];
  627. sum += ai * wi;
  628. }
  629. if (parity[sum % 11] != code[17].toUpperCase()) {
  630. row = {
  631. pass: false,
  632. msg: '身份证号校验位错误',
  633. };
  634. }
  635. }
  636. }
  637. return row;
  638. }
  639. }
  640. module.exports = SchoolService;