Browse Source

✨ feat: 增加企业微信登录接口

Pchen. 8 months ago
parent
commit
5bdf32ebec

+ 83 - 0
apis/User/WXWorkLogin.js

@@ -0,0 +1,83 @@
+const md5 = require("md5");
+const API = require("../../lib/API");
+const { BaseStdResponse } = require("../../BaseStdResponse");
+const db = require("../../plugin/DataBase/db");
+const {
+    getUserInfo,
+    getUserID
+} = require("../../plugin/WXWork/GetInfo");
+
+class WXWorkLogin extends API {
+    constructor() {
+        super();
+        this.setMethod("POST");
+        this.setPath("/User/WXWorkLogin");
+    }
+
+    createSession(uuid, salt) {
+        return md5(`${uuid}${salt}${new Date().getTime()}`);
+    }
+
+    async onRequest(req, res) {
+        this.setAllowCORS(res);
+
+        const { code } = req.body;
+        if (!code) {
+            res.json({ ...BaseStdResponse.MISSING_PARAMETER, endpoint: 7841686 });
+            return;
+        }
+
+        try {
+            const idRes = await getUserID(code);
+            if (!idRes || !idRes.success) {
+                return res.json({ ...BaseStdResponse.ERR, endpoint: 7894377, msg: `登录失败!${idRes.msg}` });
+            }
+
+            const wxid = idRes.userid;
+            const session = this.createSession(wxid, Math.random().toFixed(6).slice(-6));
+
+            let sql = 'SELECT uuid, username, avatar FROM users WHERE wxid = ?';
+            let [rows] = await db.query(sql, [wxid]);
+
+            let result, uuid, username, avatar;
+            if (rows.length > 0) {
+                // 用户存在,更新 session
+                ({ uuid, username, avatar } = rows[0]);
+
+                const updateQuery = 'UPDATE users SET session = ? WHERE wxid = ?';
+                result = await db.query(updateQuery, [session, wxid]); 
+            } else {
+                // 用户不存在,注册用户
+                const infoRes = await getUserInfo(wxid);
+                if (!infoRes || !infoRes.success) {
+                    return res.json({ ...BaseStdResponse.ERR, endpoint: 7894198, msg: `登录失败!${infoRes.msg}` });
+                }
+
+                ({ name: username, avatar } = infoRes);
+                uuid = md5(Date.now() + wxid + code);
+
+                const insertQuery = 'INSERT INTO users (uuid, username, wxid, avatar, session) VALUES (?, ?, ?, ?, ?)';
+                result = await db.query(insertQuery, [uuid, username, wxid, avatar, session]);
+            }
+
+            if (result && result.affectedRows > 0) {
+                return res.json({
+                    ...BaseStdResponse.OK,
+                    data: {
+                        uuid,
+                        username,
+                        wxid,
+                        avatar,
+                        session
+                    }
+                });
+            } else {
+                return res.json({ ...BaseStdResponse.ERR, endpoint: 7894377, msg: '登录失败!' });
+            }
+        } catch (error) {
+            return res.json({ ...BaseStdResponse.ERR, endpoint: 7894377, msg: '登录失败!' });
+        }
+    }
+}
+
+module.exports.WXWorkLogin = WXWorkLogin;

+ 27 - 0
apis/User/WXWorkUrl.js

@@ -0,0 +1,27 @@
+const API = require("../../lib/API");
+const { BaseStdResponse } = require("../../BaseStdResponse");
+const config = require("../../config.json");
+
+// 构造网页授权链接
+class WXWorkUrl extends API {
+    constructor() {
+        super();
+
+        this.setMethod("GET");
+        this.setPath("/User/WXWorkUrl");
+    }
+
+    async onRequest(data, res) {
+        // this.setAllowCORS(res);
+
+        const { num } = data;
+        let redirect = encodeURIComponent(`https://kq.ctbu.top/#/UserLogin/Stage2?num=${num}`);
+        const url = `https://login.work.weixin.qq.com/wwlogin/sso/login?login_type=CorpApp&appid=${config.wxwork.corpid}&redirect_uri=${redirect}&state=STATE&agentid=${config.wxwork.agentid}`
+        return res.json({
+            ...BaseStdResponse.OK,
+            data: { url: url }
+        });
+    }
+}
+
+module.exports.WXWorkUrl = WXWorkUrl;

+ 2 - 1
lib/API.js

@@ -1,5 +1,6 @@
 const express = require('express');
 const Logger = require('./Logger');
+const path = require('path');
 
 class API {
     constructor() {
@@ -8,7 +9,7 @@ class API {
         this.path = '';
         this.method = 'get';
 
-        this.logger = new Logger(path.join(__dirname, '../logs/app.log'), 'INFO');
+        this.logger = new Logger(path.join(__dirname, '../logs/API.log'), 'INFO');
     }
 
     setNamespace(namespace, filename) {

+ 8 - 11
lib/Server.js

@@ -3,12 +3,12 @@ const path = require('path');
 const fs = require('fs');
 const config = require('../config.json');
 const Logger = require('./Logger');
-const MySQL = require('../plugin/MySQL');
+const MySQL = require('../plugin/DataBase/MySQL');
 
 class SERVER {
     constructor() {
         this.app = express();
-        this.port = config.port || 3000; 
+        this.port = config.port || 3000;
         this.apiDirectory = path.join(__dirname, '../apis'); // API 文件存放目录
 
         this.logger = new Logger(path.join(__dirname, '../logs/Server.log'), 'INFO');
@@ -17,7 +17,7 @@ class SERVER {
         this.app.use(express.json());
 
         // 初始化数据库连接
-        this.db = new MySQL(); 
+        this.db = new MySQL();
 
         // 加载 API 路由
         this.loadAPIs(this.apiDirectory);
@@ -26,17 +26,16 @@ class SERVER {
     // 测试数据库连接
     async initDB() {
         try {
+            this.logger.info('正在测试数据库连接');
             await this.db.connect();
-            this.logger.info('数据库连接成功');
+            await this.db.close();
         } catch (error) {
-            this.logger.error(`数据库连接失败: ${error.message}`);
+            this.logger.error(`数据库连接失败: ${error.stack}`);
             process.exit(1);
         }
     }
 
     loadAPIs(directory) {
-        this.logger.info('==============正在加载API==============');
-
         const items = fs.readdirSync(directory);
 
         items.forEach(item => {
@@ -51,8 +50,6 @@ class SERVER {
                 this.loadAPIFile(itemPath);
             }
         });
-
-        this.logger.info('==============API加载完成==============');
     }
 
     // 加载单个 API 文件
@@ -69,7 +66,7 @@ class SERVER {
                 }
             }
         } catch (error) {
-            this.logger.error(`加载API文件失败: ${filePath},错误: ${error.message}`);
+            this.logger.error(`加载API文件失败: ${filePath},错误: ${error.stack}`);
         }
     }
 
@@ -79,7 +76,7 @@ class SERVER {
         // 初始化数据库连接
         this.initDB().then(() => {
             this.app.listen(this.port, () => {
-                this.logger.info(`==========服务器正在 ${this.port} 上运行==========`);
+                this.logger.info(`==========服务器正在 ${this.port} 端口上运行==========`);
             });
         }).catch(err => {
             this.logger.error(`启动服务器失败: ${err.message}`);

+ 1 - 0
package.json

@@ -13,6 +13,7 @@
     "body-parser": "^1.20.2",
     "cors": "^2.8.5",
     "express": "^4.19.2",
+    "md5": "^2.3.0",
     "mysql": "^2.18.1",
     "mysql2": "^3.11.0"
   }

+ 3 - 2
plugin/MySQL.js → plugin/DataBase/MySQL.js

@@ -1,7 +1,7 @@
 const mysql = require('mysql2/promise');
 const path = require('path')
-const config = require('../config.json');
-const Logger = require('../lib/Logger');
+const config = require('../../config.json');
+const Logger = require('../../lib/Logger');
 
 class MySQL {
     constructor() {
@@ -49,6 +49,7 @@ class MySQL {
             }
         }
     }
+
 }
 
 module.exports = MySQL;

+ 9 - 0
plugin/DataBase/db.js

@@ -0,0 +1,9 @@
+const MySQL = require('./MySQL');
+
+const db = new MySQL();
+
+(async () => {
+    await db.connect();
+})();
+
+module.exports = db;

+ 44 - 0
plugin/WXWork/GetInfo.js

@@ -0,0 +1,44 @@
+const GetToken = require('./GetToken');
+const axios = require('axios');
+const path = require('path');
+const Logger = require('../../lib/Logger');
+
+const logger = new Logger(path.join(__dirname, '../../logs/WXWork.log'), 'INFO');
+
+async function getUserID(code) {
+
+    const token = await GetToken();
+    if (!token)
+        return { success: false, msg: '获取企微token失败!请联系管理员' };
+
+    let response = await axios.get(`https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=${token}&code=${code}`);
+    if (!response || response.data.errcode !== 0 || !response.data.userid) {
+        logger.error(`获取企微用户信息失败!原因:${response.data.errmsg}`)
+        return { success: false, msg: '获取企微用户信息失败!请联系管理员' };
+    }
+
+    return { success: true, userid: response.data.userid };
+}
+
+async function getUserInfo(userid) {
+
+    const token = await GetToken();
+    if (!token)
+        return { success: false, msg: '获取企微token失败!请联系管理员' };
+
+    let response = await axios.get(`https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=${token}&userid=${userid}`);
+    if (!response || response.data.errcode !== 0 || !response.data.name) {
+        logger.error(`获取企微用户信息失败!原因:${response.data.errmsg}`)
+        return { success: false, msg: '获取企微用户信息失败!请联系管理员' };
+    }
+
+    let name = response.data.name;
+    let avatar = response.data.avatar ? response.data.avatar : '';
+
+    return { success: true, name, avatar }
+}
+
+module.exports = {
+    getUserID,
+    getUserInfo
+}

+ 45 - 0
plugin/WXWork/GetToken.js

@@ -0,0 +1,45 @@
+const axios = require('axios');
+const config = require('../../config.json');
+const path = require('path');
+const Logger = require('../../lib/Logger');
+
+let token = '';
+let tokenExpiresAt = 0; // 用于跟踪token的过期时间
+const logger = new Logger(path.join(__dirname, '../../logs/WXWork.log'), 'INFO');
+
+const GetToken = async () => {
+    const currentTime = Date.now();
+
+    // 如果 token 未过期,直接返回 token
+    if (token && tokenExpiresAt > currentTime) {
+        return Promise.resolve(token);
+    }
+
+    // 否则重新获取 token
+    try {
+        const response = await axios.get('https://qyapi.weixin.qq.com/cgi-bin/gettoken', {
+            params: {
+                corpid: config.wxwork.corpid,
+                corpsecret: config.wxwork.corpsecret
+            }
+        });
+
+        const body = response.data;
+
+        if (body.errcode === 0) {
+            token = body.access_token;
+            // 设置新的过期时间
+            tokenExpiresAt = currentTime + (body.expires_in * 1000);
+            logger.info('获取企业微信 token 成功!');
+            return token;
+        } else {
+            logger.error(`获取企业微信 token 失败!原因:${body.errmsg}`);
+            throw new Error(body.errmsg);
+        }
+    } catch (error) {
+        logger.error(`获取企业微信 token 失败!原因:${error.message}`);
+        throw error;
+    }
+};
+
+module.exports = GetToken;