Selaa lähdekoodia

feat: 微信weapp登陆换取token逻辑

lanjianrong 2 viikkoa sitten
vanhempi
commit
05c8b5a2a4
3 muutettua tiedostoa jossa 164 lisäystä ja 0 poistoa
  1. 18 0
      app/const/weapp.js
  2. 46 0
      app/controller/weapp_controller.js
  3. 100 0
      app/service/weapp.js

+ 18 - 0
app/const/weapp.js

@@ -0,0 +1,18 @@
+'use strict';
+
+const baseUrl = 'https://api.weixin.qq.com/sns/';
+const AppID = 'wx8b90dea61de8dde4';
+const AppSecret = '4ef8ca9907ccbc4c8cca927454e119c1';
+const jwtSecret = '.wxapp_jwt_#*%)@!2nf874';
+const accessTokenExpiresIn = '2h'; // 业务token
+const refreshTokenExpiresIn = '7d'; // 续签token
+const redisExpire = 7 * 24 * 3600; // Redis 7天
+module.exports = {
+    baseUrl,
+    AppID,
+    AppSecret,
+    jwtSecret,
+    accessTokenExpiresIn,
+    refreshTokenExpiresIn,
+    redisExpire,
+};

+ 46 - 0
app/controller/weapp_controller.js

@@ -0,0 +1,46 @@
+'use strict';
+
+/**
+ * weapp控制器
+ *
+ * @author Lan
+ * @date 2025/12/22
+ * @version
+ */
+
+
+module.exports = app => {
+    class WeappController extends app.BaseController {
+        async login(ctx) {
+            try {
+                const {
+                    code,
+                } = ctx.request.body;
+                if (!code) {
+                    throw '微信授权失败, 请检查';
+                }
+                const token = await ctx.service.weapp.weappLogin(code);
+                ctx.body = { err: 0, msg: '登录成功', data: { access_token: token } };
+            } catch (error) {
+                this.log(error);
+                ctx.body = { err: 1, msg: error.toString(), data: null };
+            }
+        }
+
+        async refresh(ctx) {
+            const { refresh_token } = ctx.request.body;
+            if (!refresh_token) {
+                ctx.body = { err: 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 } };
+            } catch (error) {
+                this.log(error);
+                ctx.body = { err: 1, msg: '刷新失败', data: null };
+            }
+        }
+    }
+    return WeappController;
+};

+ 100 - 0
app/service/weapp.js

@@ -0,0 +1,100 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Lan
+ * @date 2025/12/22
+ * @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) {
+            const { code, account, password } = data;
+            const projectData = await this.ctx.service.project.getProjectByCode(
+                code.trim()
+            );
+            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,
+                enable: 1,
+            });
+            if (!accountData) {
+                throw '账号不存在或未启用';
+            }
+            // const encryptPassword = crypto.createHmac('sha1', data.account.trim()).update('!*#)385'+).digest().toString('base64');
+        }
+        // 微信小程序登录
+        async weappLogin(code) {
+            try {
+                const wxRes = await axios.get('https://api.weixin.qq.com/sns/jscode2session', {
+                    params: {
+                        appid: weappConfig.AppID,
+                        secret: weappConfig.AppSecret,
+                        js_code: code,
+                        grant_type: 'authorization_code',
+                    },
+                });
+
+                const { openid, session_key, errcode } = wxRes.data;
+                if (errcode || !openid) throw '微信登录失败';
+                // 2. 生成 access_token + refresh_token
+                const accessToken = app.jwt.sign(
+                    { openid, type: 'access' },
+                    weappConfig.jwtSecret,
+                    { expiresIn: weappConfig.accessTokenExpiresIn }
+                );
+
+                const refreshToken = app.jwt.sign(
+                    { openid, type: 'refresh' },
+                    weappConfig.jwtSecret,
+                    { expiresIn: weappConfig.refreshTokenExpiresIn }
+                );
+                // 3. 存入 Redis:openid 对应最新 refreshToken
+                const key = `user:${openid}:refresh_token`;
+                await app.redis.set(key, refreshToken, 'EX', weappConfig.redisExpire);
+                return { accessToken, refreshToken };
+            } catch (error) {
+                throw '微信登录失败';
+            }
+        }
+        /**
+         * 刷新 access token
+         * @param {string} refreshToken - 刷新 token
+         * @return {string} newAccessToken
+         */
+        async refresh(refreshToken) {
+            // 1. 验证 refreshToken
+            const decoded = jwt.verify(refreshToken, weappConfig.jwtSecret);
+            if (decoded.type !== 'refresh') {
+                throw 'token 类型错误';
+            }
+            const { openid } = decoded;
+            // 2. 校验 Redis 中是否存在(防止篡改/过期/被踢)
+            const redisKey = `user:${openid}: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 });
+            return newAccessToken;
+        }
+    }
+    return Weappchat;
+};