// 会员 const util = require('../utils') const db = require('../data') const Token = require('../token') const EnDeNum = require('../utils/encrypt-decrypt-number') const dayjs = require('dayjs') const cache = new (require('node-cache'))() let [sql, rs] = ['', null] /** * 兼容旧网站cookie验证方式,加密ID和密码(过渡方案) * API内部调用,同步 */ const MemberCookie = (id, password) => { // 取出MD5中的数字 const r = password.split('').filter(item => !isNaN(item)) // 5位随机数 + md5中的数字(前7位)+ 5位随机数 + 10位时间戳*2 + 5位随机数 = 32位 const s = util.RndNum(5) + r.slice(0, 7).join('') + util.RndNum(5) + dayjs().unix() * 2 + util.RndNum(5) return id + '.' + password + '.' + s } exports.MemberCookie = MemberCookie /** * 登录 * post: /login */ exports.Login = async ctx => { const body = ctx.request.body let u = db.escape(body.u) // 登录名 let s = body.s // 学校ID let times = cache.get(s + '-' + u + '-times') || 0 if (cache.get(s + '-' + u)) { throw { code: 11, message: '访问过于频繁,请稍后再试' } } else if (times > 10) { cache.set(s + '-' + u + '-times', times++, 60 * 30) throw { code: 12, message: '登录失败次数过多,30分钟后重试' } } else { cache.set(s + '-' + u, 1, 30) let p = body.p // 密码MD5 let r = typeof body.r === 'string' ? body.r.toUpperCase() : 'STUDENT' // 角色:学校管理员admin let c = body.c // 手机验证码 let v = body.v let openid = db.escape(body.openid) // 学校 if (util.isInteger(s)) { if (openid) { // 学校用户微信 openid 登录 sql = `select top 1 id,state,password from v_member with (nolock) where member_type_top=${s} and weixin_openid='${openid}'` if (r === 'ADMIN') { sql += ' and (isschooladmin=1 or isconsultant=1)' } else { sql += ' and isstudent=1' } } else if (/^\d{6}$/.test(c)) { // 学校用户手机验证码登录 if (!util.isMobile(u)) { throw { code: 4, message: '手机号码(' + u + ')格式不正确' } } else if (/\d{6,}/.test(v)) { if (EnDeNum.decrypt(v) !== c) { throw { code: 6, message: '验证码(' + c + ')不正确' } } else { sql = `select top 1 id,state,password from v_member with (nolock) where member_type_top=${s} and mobile='${u}'` if (r === 'ADMIN') { sql += ' and (isschooladmin=1 or isconsultant=1)' } else { sql += ' and isstudent=1' } } } else { sql = `select top 1 id,state,password from v_member with (nolock) where member_type_top=${s} and mobile='${u}'` if (r === 'ADMIN') { sql += ' and (isschooladmin=1 or isconsultant=1)' } else { sql += ' and isstudent=1' } } } // 学校用户登录名密码登录(手机号,Email,ID,编号,用户名) else if (!util.isMD5(p)) { throw { code: 7, message: '密码MD5格式不正确' } } else { sql = `select top 1 id,state,password from v_member with (nolock) where member_type_top=${s}` if (util.isEmail(u)) { sql += ` and email='${u}'` } else if (/^\d+$/.test(u)) { if (u.length <= 8) { sql += ` and (id=${u} or number='${u}')` } else { sql += ` and number='${u}'` } } else if (r === 'ADMIN') { sql += ` and (username='${u}' or number='${u}') and (isschooladmin=1 or isconsultant=1)` } else { sql += ` and number='${u}' and isstudent=1` } } } else if (openid) { // 普通用户微信 openid 登录 sql = `select top 1 id,state,password from member with (nolock) where weixin_openid='${openid}'` } else if (/^\d{4,6}$/.test(c)) { // 普通用户手机号直接登录(验证码是4位的,不判断验证码,雪莲调用) // 普通用户手机验证码登录(跟密码无关,所以都取出密码字段以生成cookie) if (!util.isMobile(u)) { throw { code: 4, message: '手机号码(' + u + ')格式不正确' } } else { sql = `select top 1 id,state,password from member with (nolock) where mobile='${u}'` } } else if (!util.isMD5(p)) { // 普通用户密码登录 throw { code: 7, message: '密码MD5格式不正确' } } else { sql = 'select top 1 id,state,password from member with (nolock) where' if (util.isMobile(u)) { sql += ` mobile='${u}'` } else if (util.isEmail(u)) { sql += ` email='${u}'` } else if (util.isInteger(u)) { sql += ` id=${u}` } else { sql += ` username='${u}'` } } // 开始查询 if (sql) { rs = await db.select(sql) if (rs === null) { cache.set(s + '-' + u + '-times', times++) throw { code: 1, message: '用户名或密码不正确' } } else if (util.isMD5(p) && p !== rs.password) { cache.set(s + '-' + u + '-times', times++) throw { code: 10, message: '用户名或密码不正确' } } else if (rs.state !== 5) { cache.set(s + '-' + u + '-times', times++) throw { code: 2, message: '用户名或密码不正确' } } else if ('e10adc3949ba59abbe56e057f20f883e,c33367701511b4f6020ec61ded352059'.includes(rs.password)) { const id = rs.id const data = EnDeNum.encrypt(id) cache.set(s + '-' + u + '-times', times++) ctx.body = { code: 9, message: '用户名或密码不正确', data } } else { const fields = 'id,number,realname,sex,birthday,age,password,mobile,email,qq,type,balance,face,bind_consultant_id,zx_times,[right],isSchoolAdmin,isconsultant,weixin_openid,member_type_path,member_type_top,address,[group]' const id = rs.id const password = rs.password db.execute(`update member set lasttime=getdate(),lastip='${util.GuestIP(ctx)}',logtimes=logtimes+1 where id=${id}`) sql = 'select top 1 ' + fields + ' from v_member with (nolock) where id=' + id rs = await db.select(sql) if (rs) { const data = Object.assign(rs, { token: Token.Sign({ id }), cookie: MemberCookie(id, password), key: EnDeNum.encrypt(id) }) cache.del(s + '-' + u + '-times') ctx.body = { code: 0, data } } else { cache.set(s + '-' + u + '-times', times++) throw { code: 1, message: '用户名或密码不正确' } } } } else { throw { code: 8, message: '登录出错' } } } } /** * 用户信息,id=0取当前登录用户,id=加密后数字取对应用户 * get:/member/:id */ exports.GetInfo = async ctx => { const id = ctx.params.id === '0' ? Token.Decode(ctx).id : EnDeNum.decrypt(ctx.params.id) if (id) { const fields = 'id,number,realname,sex,birthday,age,password,mobile,email,qq,type,balance,face,bind_consultant_id,zx_times,[right],isschooladmin,isconsultant,weixin_openid,member_type_path,member_type_top,address' sql = 'select top 1 ' + fields + ' from v_member with (nolock) where id=' + id rs = await db.select(sql) if (rs) { const data = { token: Token.Sign({ id: rs.id }), cookie: MemberCookie(rs.id, rs.password), key: EnDeNum.encrypt(rs.id) } Object.assign(data, rs) ctx.body = { code: 0, data } } else { throw { code: 1, message: '用户未找到' } } } else { throw { code: -2 } } } /** * 修改生日(id:0、混淆后ID) * put: /member/:id/birthday */ exports.Birthday = async ctx => { let u = ctx.params.id let b = ctx.request.body.b let id = null if (!util.isDate(b)) { throw { code: 2, message: '日期格式不正确' } } // 接口中member为0的都表示取当前登录用户ID,用于Vue项目 else if (u === '0') { id = Token.Decode(ctx).id } // 通过混淆后的会员ID else if (/^\d+$/.test(u)) { id = EnDeNum.decrypt(u) } // 开始修改 if (id) { sql = `update member set birthday='${b}' where id=${id}` db.execute(sql) ctx.body = { code: 0 } } else { ctx.body = { code: 1, message: '用户(' + u + ')未找到' } } } /** * 获取自己的待测量表(id为0) * get: /member/:id/totestlist */ exports.ScaleListByDistr = async ctx => { let uid = Token.Decode(ctx).id let retestday = 0 let lb = [] let rs = null let hide_lb_outofage = false if (!util.isInteger(uid)) { throw { code: -2 } } else { // 查询会员 let member = await db.select(`select top 1 type,grade,customgroup,member_type_path as path,member_type_top as school,age from v_member with (nolock) where id=${uid}`) if (member === null) { throw { code: 1, message: `ID ${uid} 未找到` } } else { if (!member.grade) { member.grade = 0 } if (!member.customgroup) { member.customgroup = 0 } // 查询学校设置的重复测评天数,是否隐藏不在年龄范围内的量表 if (member.school !== 0) { rs = await db.select(`select top 1 retestday,hide_lb_outofage from school where id=${member.school}`) retestday = rs.retestday || 0 hide_lb_outofage = rs.hide_lb_outofage } // 待测量表 sql = `select lb from test_batch where todate+1>=getdate() and ((type in (${member.path.substring(2)}) and (grade=0 or grade=${member.grade} or grade is null) and (customgroup=0 or customgroup=${member.customgroup} or customgroup is null)) or member=${uid})` rs = await db.select(sql) rs && rs.forEach(value => lb = lb.concat(value.lb.split(','))) // 已测量表 sql = `select lb from test with (nolock) where member=${uid} and valid=1` if (retestday > 0) { sql += ` and datetime>'${dayjs().subtract(retestday, 'day').format('YYYY-MM-DD HH:mm')}'` } rs = await db.select(sql) rs && rs.forEach(value => lb = lb.filter(element => element !== String(value.lb))) // 查询量表列表 if (lb.length === 0) { ctx.body = { code: 0, data: [] } } else { lb = lb.join() sql = `select id,picture,name_ch,name_fr,intro2 from lb where id in (${lb})` hide_lb_outofage && (sql += ' and age_min<=' + member.age + ' and age_max>=' + member.age) sql += ' order by sort' rs = await db.select(sql) ctx.body = { code: 0, data: rs || [] } } } } } /** * 修改会员资料(id为0或加密后) * post: /member/:id */ exports.ModifyInfo = async ctx => { const id = ctx.params.id === '0' ? Token.Decode(ctx).id : EnDeNum.decrypt(ctx.params.id) if (id) { const body = ctx.request.body sql = 'update member set lasttime=getdate()' body.number && (sql += `,number='${body.number}'`) body.realname && (sql += `,realname=N'${body.realname}'`) body.sex && '01'.includes(body.sex) && (sql += `,sex=${body.sex}`) util.isDate(body.birthday) && (sql += `,birthday='${body.birthday}'`) util.isInteger(body.type) && (sql += `,type=${body.type}`) util.isInteger(body.grade) && (sql += `,grade=${body.grade}`) util.isMobile(body.mobile) && (sql += `,mobile='${body.mobile}'`) util.isEmail(body.email) && (sql += `,email='${body.email}'`) // address限定50字以内 body.address && (sql += `,address='${body.address}'`) util.isMD5(body.password) && (sql += `,[password]='${body.password}'`) body.intro && (sql += `,intro='${body.intro}'`) body.idcard_picture && (sql += `,idcard_picture='${body.idcard_picture}'`) sql += ` where id=${id}` db.execute(sql) ctx.body = { code: 0 } } else { throw { code: -2 } } } /** * 获取新的会员ID,API内部使用,异步,必须加括号 */ const NewID = async () => { rs = await db.scalar('select top 1 id from member order by id desc') return rs + 1 } exports.NewID = NewID /** * 查询手机号码是否已经存在(存在data=1,不存在data=0) * get: /member/mobile/exist */ exports.MobileExist = async ctx => { const mobile = ctx.query.m if (!util.isMobile(mobile)) { ctx.body = { code: 1, message: '手机号(' + mobile + ')格式不对' } } else { rs = await db.select(`select top 1 id from member with (nolock) where mobile='${mobile}'`) ctx.body = { code: 0, data: rs ? 1 : 0 } } } /** * 注册会员,成功后自动登录 * post: /regist */ exports.Regist = async ctx => { const body = ctx.request.body const username = body.u const code = body.c // 为1时表示同时获取用户详情 const full = body.f // 用户角色,暂用于远程咨询系统咨询师注册(consultant) const role = body.r // 为空的话自动取手机号后6位 let password = body.p let type = body.t let uid = 0 if (!util.isInteger(type)) { type = 4 } sql = '' // 手机注册 if (util.isMobile(username)) { // 验证码是4位的,不判断验证码,雪莲调用 if (/^\d{4}$/.test(code)) { rs = await db.select(`select top 1 id from member with (nolock) where mobile='${username}'`) if (rs) { throw { code: 1, message: '手机号码(' + username + ')已存在' } } else { !util.isMD5(password) && (password = util.MD5(username.slice(-6))) uid = await NewID() sql = 'insert into member(id,[password],type,[group],realname,sex,birthday,mobile,[state],balance) values(' + uid + `,'${password}'` + ',' + type + ',1' + `,'${String(username).substring(7)}'` + ',1' + ',getdate()' + `,'${username}'` + ',5' + ',0' + ');' } } else if (!/^\d{6}$/.test(code)) { throw { code: 7, message: '手机验证码格式不对' } } else { rs = await db.select(`select top 1 id from member with (nolock) where mobile='${username}'`) if (rs) { throw { code: 1, message: '手机号码(' + username + ')已存在' } } else { !util.isMD5(password) && (password = util.MD5(username.slice(-6))) uid = await NewID() uid += Number(util.RndNum(1)) sql = 'insert into member(id,[password],type,[group],realname,sex,birthday,mobile,[state],balance) values(' + uid + `,'${password}'` + ',' + type + ',1' + `,'${String(username).substring(7)}'` + ',1' + ',dateadd(year,-18,getdate())' + `,'${username}'` + ',5' + ',0' + ');' } } } // 邮箱注册 else if (util.isEmail(username)) { rs = await db.select(`select top 1 id from member with (nolock) where email='${username}'`) if (rs) { throw { code: 2, message: 'Email(' + username + ')已存在' } } else { uid = await NewID() sql = 'insert into member(id,[password],type,[group],realname,sex,birthday,email,[state],balance) values(' + uid + `,'${password}'` + ',' + type + ',1' + `,'${uid}'` + ',1' + ',getdate()' + `,'${username}'` + ',5' + ',0' + ');' } } else if (util.isInteger(username)) { throw { code: 3, message: '用户名(' + username + ')不能全为数字' } } else if (!/^\w+$/.test(username)) { throw { code: 4, message: '用户名只能包含字母、数字、下划线或其组合' } } // 用户名注册 else { rs = await db.select(`select top 1 id from member with (nolock) where username='${username}'`) if (rs) { throw { code: 5, message: '用户名(' + username + ')已存在' } } else { uid = await NewID() sql = 'insert into member(id,[password],type,[group],realname,sex,birthday,username,[state],balance) values(' + uid + `,'${password}'` + ',' + type + ',1' + `,N'${username}'` + ',1' + ',getdate()' + `,'${username}'` + ',5' + ',0' + ');' } } // 执行插入 if (sql === '') { throw { code: -1 } } else { if (type === '52535') { sql += `insert into therapy_member(id,datetime) values(${uid},getdate());` } else if (role === 'consultant') { uid = await NewID() sql += 'insert into consultant(id,specialty,orientation,experience,training,status,datetime) values(' + uid + `,'${body.specialty}'` + `,'${body.orientation}'` + `,'${body.experience}'` + `,'${body.training}'` + ',0' + ',getdate()' + ');' } let data = { id: uid, token: Token.Sign({ id: uid }), cookie: MemberCookie(uid, password), key: EnDeNum.encrypt(uid) } if (full) { await db.execute(sql) rs = await db.select('select top 1 * from v_member with (nolock) where id=' + uid) data = Object.assign({}, rs, data) } else { db.execute(sql) } ctx.body = { code: 0, data } } }