'use strict'; const { CrudService } = require('naf-framework-mongoose-free/lib/service'); const { BusinessError, ErrorCode } = require('naf-core').Error; const { ObjectId } = require('mongoose').Types; const _ = require('lodash'); const jwt = require('jsonwebtoken'); const assert = require('assert'); const Excel = require('exceljs'); const { sep } = require('path'); const fs = require('fs'); // 个人用户 class PersonalService extends CrudService { constructor(ctx) { super(ctx, 'personal'); this.redis = this.app.redis; this.model = this.ctx.model.User.Personal; this.adminModel = this.ctx.model.User.Admin; this.util = this.ctx.service.util.util; } async query(condition, { skip = 0, limit = 0 }) { const query = await this.dealQueryCondition(_.cloneDeep(condition)); const res = await this.model .find(query) .skip(parseInt(skip)) .limit(parseInt(limit)) .sort({ 'meta.createdAt': -1 }); return res; } async count(condition) { const query = await this.dealQueryCondition(_.cloneDeep(condition)); const res = await this.model.count(query); return res; } async dealQueryCondition({ role, code, ...condition } = {}) { condition = this.util.dealQuery(condition); // 查询业务管理 const busFind = async query => await this.adminModel.find({ ...query, role: '3' }, { code: 1 }); // 查询机构管理 const orgFind = async query => await this.adminModel.find({ ...query, role: '2' }, { code: 1 }); // 查询管理员 const aFind = async query => await this.adminModel.find({ ...query, role: '1' }, { code: 1 }); if (role === '1' && code) { // 管理员查询 // =>获取该code下的机构管理员列表 => 用机构管理员id 获取业务管理员列表 => 将code都整理出来作为查询条件 const a = await aFind({ code }); if (a.length <= 0) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到该管理员'); const aid = _.get(_.head(a), '_id'); const orgList = await orgFind({ pid: aid }); const busList = await busFind({ pid: orgList.map(i => i._id) }); const codes = [ ...orgList.map(i => i.code), ...busList.map(i => i.code), code ]; condition.code = codes; } else if (role === '2' && code) { // 机构查询 // =>获取该code下的业务管理员列表 => 将code都整理出来作为查询条件 const o = await orgFind({ code }); if (o.length <= 0) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到该机构'); const oid = _.get(_.head(o), '_id'); const busList = await busFind({ pid: oid }); const codes = [ ...busList.map(i => i.code), code ]; condition.code = codes; } else if (code) { // 业务查询 // code直接查询用户返回即可 condition.code = code; } // 没有code,超级管理员,说明不限制 return condition; } /** * 创建用户 * @param {Object} params 用户信息 */ async create({ password, ...data }) { if (!password) password = '123456'; data.password = { secret: password }; const { name, phone, code } = data; // 检查手机号 const num = await this.model.count({ phone, code, isdel: '0' }); if (num > 0) throw new BusinessError(ErrorCode.DATA_EXISTED, `姓名:${name} ; 手机号: ${phone} ;已有机构所属的个人用户使用该手机号`); return await this.model.create(data); } /** * 修改密码 * @param {Object} {id,password} 用户id和密码 */ async password({ id, password }) { const object = await this.model.findById(id); if (!object) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到用户的信息'); object.password = { secret: password }; await object.save(); } /** * 登陆 * @param {Object} params 登陆信息 * @property phone 手机号 * @property password 密码 */ async login({ phone, password }) { const object = await this.model.findOne({ phone, isdel: '0' }, '+password'); if (!object) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到用户的信息'); const { password: op, status } = object; const { secret } = op; if (status !== '1') throw new BusinessError(ErrorCode.ACCESS_DENIED, '拒绝访问!'); if (secret !== password) throw new BusinessError(ErrorCode.BAD_PASSWORD, '密码错误'); const data = _.omit(JSON.parse(JSON.stringify(object)), [ 'meta', 'password', '__v' ]); const { secret: secrets } = this.config.jwt; const token = jwt.sign(data, secrets); // 记录登陆 // let number = await this.redis.get('login_number') || 0; // number++; // await this.redis.set('login_number', number); return token; } // async delete({ id }) { // const object = await this.model.findById(id); // if (!object) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到用户的信息'); // object.isdel = '1'; // await object.save(); // } /** * 个人升变专家 * @param {Object} body 升变数据 * 升级的时候要把所属的信息(产品,需求等)换到升级后的数据上,一种数据迁移 */ async upgrade({ id, ...data }) { assert(id, '缺少个人用户ID'); const user = await this.model.findOne({ _id: ObjectId(id) }, '+password'); if (!user) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到用户的信息'); data = _.omit(data, [ 'meta', '__v' ]); data.user_id = id; // 创建专家 const is_expert = await this.ctx.model.User.Expert.count({ user_id: ObjectId(id) }); if (is_expert > 0) throw new BusinessError(ErrorCode.DATA_EXISTED, '已升级为专家,无需再次升级'); await this.ctx.model.User.Expert.create(data); user.is_expert = true; await user.save(); } async import({ uri }) { assert(uri, '未获取到文件地址'); const domain = 'http://127.0.0.1'; const file = await this.ctx.curl(`${domain}${uri}`); if (!(file && file.data)) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到指定文件'); } const workbook = new Excel.Workbook(); await workbook.xlsx.load(file.data); const sheet = workbook.getWorksheet(1); let meta = this.importMeta(); // 根据excel,将列序号加入meta中 const head = _.get(sheet.getRow(1), 'values', []); for (let i = 0; i < head.length; i++) { const e = head[i]; if (!e) continue; const r = meta.find(f => f.column === e); if (r) r.index = i; } // 过滤出列表中不必要且不存在的字段 meta = meta.filter(f => f.required || f.index); const errorList = []; const dataList = []; sheet.eachRow(async (row, index) => { if (index !== 1) { const values = row.values; const obj = {}; for (const m of meta) { const { required, key, index, method, column } = m; const value = values[index]; if (required && !value) { // 必填且没值的情况 errorList.push({ message: `第${index}行数据,缺少必填项 ${column};` }); continue; } if (_.isFunction(method)) obj[key] = method(value); else obj[key] = value; } dataList.push(obj); } }); if (errorList.length > 0) return errorList; for (const i of dataList) { try { await this.create(i); } catch (error) { errorList.push(error); } } return errorList; } importMeta() { return [ { required: true, column: '用户名', key: 'name' }, { required: true, column: '手机号', key: 'phone' }, { required: true, column: '登录密码', key: 'password' }, { required: true, column: '邀请码', key: 'code' }, { column: '身份证号', key: 'card' }, { column: '电子邮箱', key: 'email' }, { column: '联系地址', key: 'addr' }, { column: '办公电话', key: 'office_phone' }, { column: '所属辖区', key: 'juris' }, { column: '院校', key: 'school' }, { column: '专业', key: 'major' }, { column: '职务职称', key: 'zwzc' }, { column: '审核状态', key: 'status' }, ]; } async export() { // 查询位置 let skip = 0; // 一次处理多少条数据 const limit = 500; const total = await this.model.count({ isdel: 0 }); // 循环次数 const times = _.ceil(_.divide(total, limit)); const nowDate = new Date().getTime(); // 文件 名称 及 路径 const filename = `个人用户导出-${nowDate}.xlsx`; const root_path = _.get(this.ctx.app.config.export, 'root_path'); if (!fs.existsSync(`${root_path}`)) { // 如果不存在文件夹,就创建 fs.mkdirSync(`${root_path}`); } const excel_path = `${sep}excel${sep}`; const path = `${root_path}${excel_path}`; if (!fs.existsSync(path)) { // 如果不存在文件夹,就创建 fs.mkdirSync(path); } // 流式写入 const options = { filename: `${path}${filename}`, }; const workbook = new Excel.stream.xlsx.WorkbookWriter(options); const sheet = workbook.addWorksheet('sheet'); const eMeta = this.exportMeta(); const eColumn = eMeta.map(i => i.key); let needDeal = eMeta.map((i, index) => { if (_.isFunction(_.get(i, 'method'))) { i.index = index; return i; } }); needDeal = _.compact(needDeal); for (let i = 1; i <= times; i++) { if (i === 1) { const head = eMeta.map(i => i.zh); sheet.addRow(head).commit(); } const data = await this.model.find({ isdel: 0 }, '+password').skip(skip).limit(limit); for (const d of data) { const dup = Object.values(_.pick(d, eColumn)); // 处理特殊情况 for (const nd of needDeal) { const { index, method } = nd; dup[index] = method(dup[index]); } // 添加数据 sheet.addRow(dup).commit(); } skip += limit; } sheet.commit(); await workbook.commit(); return `/files/excel/${filename}`; } exportMeta() { return [ { key: 'name', zh: '用户名' }, { key: 'phone', zh: '手机号' }, { key: 'password', zh: '登录密码', method: i => _.get(i, 'secret') }, { key: 'code', zh: '邀请码' }, { key: 'card', zh: '身份证号' }, { key: 'email', zh: '邮箱' }, { key: 'addr', zh: '地址' }, { key: 'office_phone', zh: '办公电话' }, { key: 'juris', zh: '所属辖区' }, { key: 'school', zh: '院系' }, { key: 'major', zh: '专业' }, { key: 'zwzc', zh: '职务职称' }, { key: 'status', zh: '审核状态' }, ]; } } module.exports = PersonalService;