personal.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. 'use strict';
  2. const { CrudService } = require('naf-framework-mongoose/lib/service');
  3. const { BusinessError, ErrorCode } = require('naf-core').Error;
  4. const { ObjectId } = require('mongoose').Types;
  5. const _ = require('lodash');
  6. const jwt = require('jsonwebtoken');
  7. const assert = require('assert');
  8. const Excel = require('exceljs');
  9. const { sep } = require('path');
  10. const fs = require('fs');
  11. // 个人用户
  12. class PersonalService extends CrudService {
  13. constructor(ctx) {
  14. super(ctx, 'personal');
  15. this.redis = this.app.redis;
  16. this.model = this.ctx.model.Personal;
  17. this.adminModel = this.ctx.model.Admin;
  18. this.util = this.ctx.service.util.util;
  19. }
  20. async query(condition, { skip = 0, limit = 0 }) {
  21. const query = await this.dealQueryCondition(_.cloneDeep(condition));
  22. const res = await this.model
  23. .find(query)
  24. .skip(parseInt(skip))
  25. .limit(parseInt(limit))
  26. .sort({ 'meta.createdAt': -1 });
  27. return res;
  28. }
  29. async count(condition) {
  30. const query = await this.dealQueryCondition(_.cloneDeep(condition));
  31. const res = await this.model.count(query);
  32. return res;
  33. }
  34. async dealQueryCondition({ role, code, ...condition } = {}) {
  35. condition = this.util.dealQuery(condition);
  36. if (role === '1') {
  37. // code查询机构,取出机构的code,用code到model(个人用户)里查询用户,合为一个数组,返回
  38. const orgRes = await this.adminModel.find({ code }, { _id: 1 });
  39. const ids = orgRes.map(i => i._id);
  40. const res2 = await this.adminModel.find({ pid: ids, role: '2' }, { code });
  41. const codes = res2.map(i => i.code);
  42. condition.code = codes;
  43. condition.code.push(code);
  44. } else if (code) {
  45. // code直接查询用户返回即可
  46. condition.code = code;
  47. }
  48. return condition;
  49. }
  50. /**
  51. * 创建用户
  52. * @param {Object} params 用户信息
  53. */
  54. async create({ password, ...data }) {
  55. if (!password) password = '123456';
  56. data.password = { secret: password };
  57. const { name, phone, code } = data;
  58. // 检查手机号
  59. const num = await this.model.count({ phone, code, isdel: '0' });
  60. if (num > 0) throw new BusinessError(ErrorCode.DATA_EXISTED, `姓名:${name} ; 手机号: ${phone} ;已有机构所属的个人用户使用该手机号`);
  61. return await this.model.create(data);
  62. }
  63. /**
  64. * 修改密码
  65. * @param {Object} {id,password} 用户id和密码
  66. */
  67. async password({ id, password }) {
  68. const object = await this.model.findById(id);
  69. if (!object) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到用户的信息');
  70. object.password = { secret: password };
  71. await object.save();
  72. }
  73. /**
  74. * 登陆
  75. * @param {Object} params 登陆信息
  76. * @property phone 手机号
  77. * @property password 密码
  78. */
  79. async login({ phone, password, code }) {
  80. const object = await this.model.findOne({ phone, code, isdel: '0' }, '+password');
  81. if (!object) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到用户的信息');
  82. const { password: op, status } = object;
  83. const { secret } = op;
  84. if (status !== '1') throw new BusinessError(ErrorCode.ACCESS_DENIED, '拒绝访问!');
  85. if (secret !== password) throw new BusinessError(ErrorCode.BAD_PASSWORD, '密码错误');
  86. const data = _.omit(JSON.parse(JSON.stringify(object)), [ 'meta', 'password', '__v' ]);
  87. const { secret: secrets } = this.config.jwt;
  88. const token = jwt.sign(data, secrets);
  89. // 记录登陆
  90. // let number = await this.redis.get('login_number') || 0;
  91. // number++;
  92. // await this.redis.set('login_number', number);
  93. return token;
  94. }
  95. // async delete({ id }) {
  96. // const object = await this.model.findById(id);
  97. // if (!object) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到用户的信息');
  98. // object.isdel = '1';
  99. // await object.save();
  100. // }
  101. /**
  102. * 个人升变专家
  103. * @param {Object} body 升变数据
  104. * 升级的时候要把所属的信息(产品,需求等)换到升级后的数据上,一种数据迁移
  105. */
  106. async upgrade({ id, ...data }) {
  107. assert(id, '缺少个人用户ID');
  108. const user = await this.model.findOne({ _id: ObjectId(id) }, '+password');
  109. if (!user) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到用户的信息');
  110. data = _.omit(data, [ 'meta', '__v' ]);
  111. data.user_id = id;
  112. // 创建专家
  113. const is_expert = await this.ctx.model.Expert.count({ user_id: ObjectId(id) });
  114. if (is_expert > 0) throw new BusinessError(ErrorCode.DATA_EXISTED, '已升级为专家,无需再次升级');
  115. await this.ctx.model.Expert.create(data);
  116. user.is_expert = true;
  117. await user.save();
  118. }
  119. async import({ uri }) {
  120. assert(uri, '未获取到文件地址');
  121. const domain = 'http://127.0.0.1';
  122. const file = await this.ctx.curl(`${domain}${uri}`);
  123. if (!(file && file.data)) {
  124. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到指定文件');
  125. }
  126. const workbook = new Excel.Workbook();
  127. await workbook.xlsx.load(file.data);
  128. const sheet = workbook.getWorksheet(1);
  129. let meta = this.importMeta();
  130. // 根据excel,将列序号加入meta中
  131. const head = _.get(sheet.getRow(1), 'values', []);
  132. for (let i = 0; i < head.length; i++) {
  133. const e = head[i];
  134. if (!e) continue;
  135. const r = meta.find(f => f.column === e);
  136. if (r) r.index = i;
  137. }
  138. // 过滤出列表中不必要且不存在的字段
  139. meta = meta.filter(f => f.required || f.index);
  140. const errorList = [];
  141. const dataList = [];
  142. sheet.eachRow(async (row, index) => {
  143. if (index !== 1) {
  144. const values = row.values;
  145. const obj = {};
  146. for (const m of meta) {
  147. const { required, key, index, method, column } = m;
  148. const value = values[index];
  149. if (required && !value) {
  150. // 必填且没值的情况
  151. errorList.push({ message: `第${index}行数据,缺少必填项 ${column};` });
  152. continue;
  153. }
  154. if (_.isFunction(method)) obj[key] = method(value);
  155. else obj[key] = value;
  156. }
  157. dataList.push(obj);
  158. }
  159. });
  160. if (errorList.length > 0) return errorList;
  161. for (const i of dataList) {
  162. try {
  163. await this.create(i);
  164. } catch (error) {
  165. errorList.push(error);
  166. }
  167. }
  168. return errorList;
  169. }
  170. importMeta() {
  171. return [
  172. { required: true, column: '邀请码', key: 'code' },
  173. { required: true, column: '账号', key: 'phone' },
  174. { required: true, column: '登录密码', key: 'password' },
  175. { required: true, column: '联系电话', key: 'name' },
  176. { column: '身份证号', key: 'card' },
  177. { column: '电子邮箱', key: 'email' },
  178. { column: '联系地址', key: 'addr' },
  179. { column: '办公电话', key: 'office_phone' },
  180. { column: '所属辖区', key: 'juris' },
  181. { column: '院校', key: 'school' },
  182. { column: '专业', key: 'major' },
  183. { column: '职务职称', key: 'zwzc' },
  184. { column: '审核状态', key: 'status' },
  185. ];
  186. }
  187. async export() {
  188. // 查询位置
  189. let skip = 0;
  190. // 一次处理多少条数据
  191. const limit = 500;
  192. const total = await this.model.count({ isdel: 0 });
  193. // 循环次数
  194. const times = _.ceil(_.divide(total, limit));
  195. const nowDate = new Date().getTime();
  196. // 文件 名称 及 路径
  197. const filename = `个人用户导出-${nowDate}.xlsx`;
  198. const root_path = _.get(this.ctx.app.config.export, 'root_path');
  199. if (!fs.existsSync(`${root_path}`)) {
  200. // 如果不存在文件夹,就创建
  201. fs.mkdirSync(`${root_path}`);
  202. }
  203. const excel_path = `${sep}excel${sep}`;
  204. const path = `${root_path}${excel_path}`;
  205. if (!fs.existsSync(path)) {
  206. // 如果不存在文件夹,就创建
  207. fs.mkdirSync(path);
  208. }
  209. // 流式写入
  210. const options = {
  211. filename: `${path}${filename}`,
  212. };
  213. const workbook = new Excel.stream.xlsx.WorkbookWriter(options);
  214. const sheet = workbook.addWorksheet('sheet');
  215. const eMeta = this.exportMeta();
  216. const eColumn = eMeta.map(i => i.key);
  217. let needDeal = eMeta.map((i, index) => {
  218. if (_.isFunction(_.get(i, 'method'))) {
  219. i.index = index;
  220. return i;
  221. }
  222. });
  223. needDeal = _.compact(needDeal);
  224. for (let i = 1; i <= times; i++) {
  225. if (i === 1) {
  226. const head = eMeta.map(i => i.zh);
  227. sheet.addRow(head).commit();
  228. }
  229. const data = await this.model.find({ isdel: 0 }, '+password').skip(skip).limit(limit);
  230. for (const d of data) {
  231. const dup = Object.values(_.pick(d, eColumn));
  232. // 处理特殊情况
  233. for (const nd of needDeal) {
  234. const { index, method } = nd;
  235. dup[index] = method(dup[index]);
  236. }
  237. // 添加数据
  238. sheet.addRow(dup).commit();
  239. }
  240. skip += limit;
  241. }
  242. sheet.commit();
  243. await workbook.commit();
  244. return `/files/excel/${filename}`;
  245. }
  246. exportMeta() {
  247. return [
  248. { key: 'code', zh: '邀请码' },
  249. { key: 'phone', zh: '账号' },
  250. { key: 'password', zh: '登录密码', method: i => _.get(i, 'secret') },
  251. { key: 'name', zh: '联系电话' },
  252. { key: 'card', zh: '身份证号' },
  253. { key: 'email', zh: '邮箱' },
  254. { key: 'addr', zh: '地址' },
  255. { key: 'office_phone', zh: '办公电话' },
  256. { key: 'juris', zh: '所属辖区' },
  257. { key: 'school', zh: '院系' },
  258. { key: 'major', zh: '专业' },
  259. { key: 'zwzc', zh: '职务职称' },
  260. { key: 'status', zh: '审核状态' },
  261. ];
  262. }
  263. }
  264. module.exports = PersonalService;