Explorar o código

feat: 完善微信小程序登录功能,增加账号登录、手机号验证码登录及获取验证码接口

lanjianrong hai 2 meses
pai
achega
07346d5018

+ 2 - 2
app/const/weapp.js

@@ -1,8 +1,8 @@
 'use strict';
 
 const baseUrl = 'https://api.weixin.qq.com/sns/';
-const AppID = 'wx8b90dea61de8dde4';
-const AppSecret = '4ef8ca9907ccbc4c8cca927454e119c1';
+const AppID = 'wx0264954f9ec1ed5a';
+const AppSecret = 'ff67eb4bc4f8d61d0507a75f97711e8c';
 const jwtSecret = '.wxapp_jwt_#*%)@!2nf874';
 const accessTokenExpiresIn = '2h'; // 业务token
 const refreshTokenExpiresIn = '7d'; // 续签token

+ 67 - 8
app/controller/weapp_controller.js

@@ -11,7 +11,21 @@
 
 module.exports = app => {
     class WeappController extends app.BaseController {
-        async login(ctx) {
+        async accountLogin(ctx) {
+            try {
+                const { code, account, password } = ctx.request.body;
+                if (!code || !account || !password) {
+                    throw '缺少必要参数';
+                }
+                const token = await ctx.service.weapp.accountlogin({ code, account, password });
+                ctx.body = { code: 0, msg: '登录成功', data: { access_token: token } };
+            } catch (error) {
+                this.log(error);
+                ctx.body = { code: -1, msg: error.toString(), data: null };
+            }
+        }
+
+        async wxLogin(ctx) {
             try {
                 const {
                     code,
@@ -19,28 +33,73 @@ module.exports = app => {
                 if (!code) {
                     throw '微信授权失败, 请检查';
                 }
-                const token = await ctx.service.weapp.weappLogin(code);
-                ctx.body = { err: 0, msg: '登录成功', data: { access_token: token } };
+                const { accessToken, refreshToken, pList } = await ctx.service.weapp.weappLogin(code);
+                ctx.body = { code: 0, msg: '登录成功', data: { access_token: accessToken, refresh_token: refreshToken, projects: pList } };
             } catch (error) {
                 this.log(error);
-                ctx.body = { err: 1, msg: error.toString(), data: null };
+                ctx.body = { code: -1, msg: error.toString(), data: null };
+            }
+        }
+
+        // 获取手机验证码
+        async getCaptcha(ctx) {
+            try {
+                const { mobile } = ctx.request.query;
+                if (!mobile) {
+                    throw '缺少手机号';
+                }
+                await ctx.service.weapp.sendCaptcha(mobile);
+                ctx.body = { code: 0, msg: '验证码发送成功', data: null };
+            } catch (error) {
+                this.log(error);
+                ctx.body = { code: -1, msg: error.toString(), data: null };
+            }
+        }
+
+        // 手机号验证码登陆
+        async mobileLogin(ctx) {
+            try {
+                const { mobile, captcha } = ctx.request.body;
+                if (!mobile) {
+                    throw '缺少手机号';
+                }
+                if (!captcha) {
+                    throw '缺少验证码';
+                }
+                const token = await ctx.service.weapp.mobileLogin({ mobile, captcha });
+                ctx.body = { code: 0, msg: '登录成功', data: { access_token: token } };
+            } catch (error) {
+                this.log(error);
+                ctx.body = { code: -1, msg: error.toString(), data: null };
             }
         }
 
         async refresh(ctx) {
-            const { refresh_token } = ctx.request.body;
+            const { refresh_token } = ctx.request.query;
             if (!refresh_token) {
-                ctx.body = { err: 1, msg: '缺少 refresh_token', data: null };
+                ctx.body = { code: -1, msg: '缺少 refresh_token', data: null };
                 return;
             }
             try {
                 const token = await ctx.service.weapp.refresh(refresh_token);
-                ctx.body = { err: 0, msg: '刷新成功', data: { access_token: token } };
+                ctx.body = { code: 0, msg: '', data: { access_token: token } };
             } catch (error) {
                 this.log(error);
-                ctx.body = { err: 1, msg: '刷新失败', data: null };
+                ctx.body = { code: -1, msg: '', data: null };
             }
         }
+
+        // 获取用户信息
+        async getUserInfo(ctx) {
+            try {
+                const userInfo = await ctx.service.weapp.getUserInfo();
+                ctx.body = { code: 0, msg: '', data: userInfo };
+            } catch (error) {
+                this.log(error);
+                ctx.body = { code: -1, msg: '', data: null };
+            }
+        }
+
     }
     return WeappController;
 };

+ 20 - 0
app/middleware/weapp_auth.js

@@ -0,0 +1,20 @@
+'use strict';
+const weappConfig = require('../const/weapp');
+const jwt = require('jsonwebtoken');
+module.exports = (options, app) => {
+
+    return async function wechatAuth(ctx, next) {
+        const token = ctx.headers.authorization && ctx.headers.authorization.replace('Bearer ', '');
+        if (!token) return ctx.fail(401, '请登录');
+        try {
+            const decoded = jwt.verify(token, weappConfig.jwtSecret);
+            const accountInfo = await ctx.service.projectAccount.getDataById(decoded.accountId);
+            ctx.accountInfo = accountInfo;
+        } catch (error) {
+            console.log(error);
+            ctx.body = error;
+            return;
+        }
+        await next();
+    };
+};

+ 7 - 2
app/router.js

@@ -1232,6 +1232,11 @@ module.exports = app => {
     app.get('/wx/work/:corpid/test', wxWorkAuth, 'wechatController.workTest');
     app.get('/wx/tips', 'wechatController.tips');
 
-    // bimface
-    // app.get('/bimface', sessionAuth, 'bimfaceController.index');
+    // weapp
+    app.post('/wx/weapp/accountLogin', 'weappController.accountLogin');
+    app.post('/wx/weapp/wxLogin', 'weappController.wxLogin');
+    app.post('/wx/weapp/mobileLogin', 'weappController.mobileLogin');
+    app.get('/wx/weapp/getCaptcha', 'weappController.getCaptcha');
+    app.get('/wx/weapp/refresh', 'weappController.refresh');
+
 };

+ 1 - 0
app/service/project_account.js

@@ -701,6 +701,7 @@ module.exports = app => {
             const randString = this.ctx.helper.generateRandomString(6, 2);
             // 缓存15分钟(拼接电话,防止篡改)
             this.cache.set(cacheKey, randString + mobile, 'EX', 900);
+
             let result = false;
 
             // 发送短信

+ 104 - 27
app/service/weapp.js

@@ -8,14 +8,13 @@
  * @version
  */
 
-const crypto = require('crypto');
 const jwt = require('jsonwebtoken');
 const axios = require('axios');
 const weappConfig = require('../const/weapp');
 
 module.exports = app => {
     class Weappchat extends app.BaseService {
-        async login(data) {
+        async accountlogin(data) {
             const { code, account, password } = data;
             const projectData = await this.ctx.service.project.getProjectByCode(
                 code.trim()
@@ -23,24 +22,37 @@ module.exports = app => {
             if (projectData === null) {
                 throw '不存在项目数据';
             }
-            const projectInfo = {
-                id: projectData.id,
-                name: projectData.name,
-                code: projectData.code,
-                userAccount: projectData.user_account,
-            };
-            const projectService = this.ctx.service.projectAccount;
-            const accountData = await projectService.db.get(projectService.tableName, {
-                account: data.account,
-                projectId: projectInfo.id,
+            const projectAccount = await this.ctx.service.projectAccount.getDataByCondition({
+                account,
+                project_id: projectData.id,
                 enable: 1,
             });
-            if (!accountData) {
+            if (!projectAccount) {
                 throw '账号不存在或未启用';
             }
-            // const encryptPassword = crypto.createHmac('sha1', data.account.trim()).update('!*#)385'+).digest().toString('base64');
+            // 验证密码
+            const isPasswordValid = await this.ctx.service.projectAccount.loginAndMigrate(projectAccount, password.trim());
+            if (!isPasswordValid) {
+                throw '帐号或密码错误';
+            }
+            const accessToken = jwt.sign(
+                { accountId: projectAccount.id, type: 'access' },
+                weappConfig.jwtSecret,
+                { expiresIn: weappConfig.accessTokenExpiresIn }
+            );
+
+            const refreshToken = jwt.sign(
+                { accountId: projectAccount.id, type: 'refresh' },
+                weappConfig.jwtSecret,
+                { expiresIn: weappConfig.refreshTokenExpiresIn }
+            );
+                // 3. 存入 Redis:pid 对应最新 refreshToken
+            const key = `user:${projectAccount.id}:refresh_token`;
+            await app.redis.set(key, refreshToken, 'EX', weappConfig.redisExpire);
+            return { accessToken, refreshToken };
         }
-        // 微信小程序登录
+
+        // 微信快捷登录
         async weappLogin(code) {
             try {
                 const wxRes = await axios.get('https://api.weixin.qq.com/sns/jscode2session', {
@@ -52,28 +64,87 @@ module.exports = app => {
                     },
                 });
 
-                const { openid, session_key, errcode } = wxRes.data;
-                if (errcode || !openid) throw '微信登录失败';
+                const { openid, errcode } = wxRes.data;
+                console.log('openid', openid);
+
+                if (errcode || !openid) throw '微信授权登录失败';
+
+                const paList = await this.ctx.service.projectAccount.getAllDataByCondition({ where: { wx_openid: openid } });
+                const pidList = this.ctx.app._.uniq(this.ctx.app._.map(paList, 'project_id'));
+                const pList = [];
+                for (const p of pidList) {
+                    const project = await this.ctx.service.project.getDataById(p);
+                    if (project) {
+                        pList.push({ code: project.code, name: project.name });
+                    }
+                }
+                if (pList.length === 0) {
+                    throw '该微信号未绑定任何项目';
+                }
+
                 // 2. 生成 access_token + refresh_token
-                const accessToken = app.jwt.sign(
-                    { openid, type: 'access' },
+                const accessToken = jwt.sign(
+                    { accountId: openid, type: 'access' },
                     weappConfig.jwtSecret,
                     { expiresIn: weappConfig.accessTokenExpiresIn }
                 );
 
-                const refreshToken = app.jwt.sign(
-                    { openid, type: 'refresh' },
+                const refreshToken = jwt.sign(
+                    { accountId: openid, type: 'refresh' },
                     weappConfig.jwtSecret,
                     { expiresIn: weappConfig.refreshTokenExpiresIn }
                 );
-                // 3. 存入 Redis:openid 对应最新 refreshToken
+                // 3. 存入 Redis:pid 对应最新 refreshToken
                 const key = `user:${openid}:refresh_token`;
                 await app.redis.set(key, refreshToken, 'EX', weappConfig.redisExpire);
-                return { accessToken, refreshToken };
+                return { accessToken, refreshToken, pList };
             } catch (error) {
-                throw '微信登录失败';
+                throw error.toString();
             }
         }
+
+        // 其他手机号验证码登陆
+        async mobileLogin(data) {
+            // 1. 根据手机号查询账号
+            const projectAccount = await this.ctx.service.projectAccount.getDataByCondition({ auth_mobile: data.mobile });
+            if (!projectAccount) {
+                throw '手机号未绑定';
+            }
+            const cacheKey = 'smsCode:' + projectAccount.id;
+            const cacheCode = await this.cache.get(cacheKey);
+
+            if (!cacheCode || cacheCode !== (data.captcha + projectAccount.auth_mobile)) {
+                throw '验证码错误或已过期';
+            }
+            const accessToken = jwt.sign(
+                { accountId: projectAccount.id, type: 'access' },
+                weappConfig.jwtSecret,
+                { expiresIn: weappConfig.accessTokenExpiresIn }
+            );
+
+            const refreshToken = jwt.sign(
+                { accountId: projectAccount.id, type: 'refresh' },
+                weappConfig.jwtSecret,
+                { expiresIn: weappConfig.refreshTokenExpiresIn }
+            );
+                // 3. 存入 Redis:pid 对应最新 refreshToken
+            const key = `user:${projectAccount.id}:refresh_token`;
+            await app.redis.set(key, refreshToken, 'EX', weappConfig.redisExpire);
+            return { accessToken, refreshToken };
+        }
+
+        async sendCaptcha(mobile) {
+            const projectAccount = await this.ctx.service.projectAccount.getDataByCondition({ auth_mobile: mobile });
+            if (!projectAccount) {
+                throw '手机号未绑定';
+            }
+
+            const result = await this.ctx.service.projectAccount.setSMSCode(projectAccount.id, mobile);
+            if (!result) {
+                throw '验证码发送失败';
+            }
+        }
+
         /**
          * 刷新 access token
          * @param {string} refreshToken - 刷新 token
@@ -85,16 +156,22 @@ module.exports = app => {
             if (decoded.type !== 'refresh') {
                 throw 'token 类型错误';
             }
-            const { openid } = decoded;
+            const { accountId } = decoded;
             // 2. 校验 Redis 中是否存在(防止篡改/过期/被踢)
-            const redisKey = `user:${openid}:refresh_token`;
+            const redisKey = `user:${accountId}:refresh_token`;
             const redisRefreshToken = await app.redis.get(redisKey);
             if (!redisRefreshToken || redisRefreshToken !== refreshToken) {
                 throw 'refresh_token 无效或已过期';
             }
-            const newAccessToken = app.jwt.sign({ openid, type: 'access' }, weappConfig.jwtSecret, { expiresIn: weappConfig.accessTokenExpiresIn });
+            const newAccessToken = jwt.sign({ accountId, type: 'access' }, weappConfig.jwtSecret, { expiresIn: weappConfig.accessTokenExpiresIn });
             return newAccessToken;
         }
+
+        // 获取用户信息以及参与的标段等
+        async getUserInfo() {
+            const accountInfo = this.ctx.accountInfo;
+
+        }
     }
     return Weappchat;
 };