Browse Source

✨ feat: 增加用户信息管理接口

 - 用户可绑定和解绑企业微信
 - 用户可换绑邮箱及修改密码
Pchen. 7 months ago
parent
commit
2ada0f52c1

+ 3 - 0
apis/User/Captcha/SendEmail.js

@@ -48,6 +48,9 @@ class SendEmail extends API {
             case 'forget':
             case 'forget':
                 content = '您正在找回Double_X考勤系统账号,';
                 content = '您正在找回Double_X考勤系统账号,';
                 break;
                 break;
+            case 'bind':
+                content = '您正在进行换绑邮箱操作,';
+                break;
             default:
             default:
                 return res.json({
                 return res.json({
                     ...BaseStdResponse.METHOD_NOT_EXIST
                     ...BaseStdResponse.METHOD_NOT_EXIST

+ 81 - 0
apis/User/UserInfo/BindEmail.js

@@ -0,0 +1,81 @@
+const API = require("../../../lib/API");
+const db = require("../../../plugin/DataBase/db");
+const { BaseStdResponse } = require("../../../BaseStdResponse");
+const Redis = require('../../../plugin/DataBase/Redis');
+const sendEmail = require('../../../plugin/Email/Email');
+const AccessControl = require("../../../lib/AccessControl");
+
+class BindEmail extends API {
+    constructor() {
+        super();
+
+        this.setMethod("POST");
+        this.setPath("/User/BindEmail");
+    }
+
+    async onRequest(req, res) {
+        let { uuid, session, email, code } = req.body;
+
+        if ([uuid, session, email, code].some(value => value === '' || value === null || value === undefined)) {
+            return res.json({
+                ...BaseStdResponse.MISSING_PARAMETER,
+                endpoint: 1513126
+            });
+        }
+
+        // 检查 session 是否有效
+        if (!await AccessControl.checkSession(uuid, session)) {
+            return res.json({
+                ...BaseStdResponse.ACCESS_DENIED,
+                endpoint: 48153145
+            });
+        }
+
+        const VerifyCode = await Redis.get(`email:${email}`);
+        if (!VerifyCode || VerifyCode != code)
+            return res.json({
+                ...BaseStdResponse.SMS_CHECK_FAIL,
+                msg: '邮箱验证码输入错误或已过期'
+            })
+
+        let sql = 'SELECT email FROM users WHERE email = ?';
+        let EmailRows = await db.query(sql, [email]);
+        if (EmailRows.length > 0)
+            return res.json({
+                ...BaseStdResponse.USER_ALREADY_EXISTS,
+                msg: '该邮箱已被注册!'
+            })
+
+        sql = 'UPDATE users SET email = ? WHERE uuid = ?';
+        let result = await db.query(sql, [email, uuid]);
+
+        if (result && result.affectedRows > 0) {
+            // 注册成功后删除邮箱对应的验证码 避免注册失败后重复获取
+            await Redis.del(`email:${email}`);
+
+            sql = 'SELECT username, wxid, avatar FROM users WHERE uuid = ?';
+            let rows = await db.query(sql, [uuid]);
+            if(!rows || rows.length === 0)
+                return res.json({
+                    ...BaseStdResponse.DATABASE_ERR
+                })
+
+            res.json({
+                ...BaseStdResponse.OK,
+                data: {
+                    uuid,
+                    username: rows[0].username,
+                    wxid: rows[0].wxid,
+                    email,
+                    avatar: rows[0].avatar,
+                    session
+                }
+            });
+            await sendEmail(email, '换绑邮箱成功', `您的Double_X考勤账号换绑邮箱成功,操作时间:${new Date().toLocaleString()}`);
+        } else {
+            res.json({ ...BaseStdResponse.ERR, endpoint: 7894378, msg: '操作失败!' });
+        }
+    }
+}
+
+module.exports.BindEmail = BindEmail;

+ 29 - 31
apis/User/WXWorkLogin/BindWXWork.js → apis/User/UserInfo/BindWXWork.js

@@ -1,12 +1,8 @@
-const md5 = require("md5");
 const API = require("../../../lib/API");
 const API = require("../../../lib/API");
 const { BaseStdResponse } = require("../../../BaseStdResponse");
 const { BaseStdResponse } = require("../../../BaseStdResponse");
-
 const db = require("../../../plugin/DataBase/db");
 const db = require("../../../plugin/DataBase/db");
-const {
-    getUserInfo,
-    getUserID
-} = require("../../../plugin/WXWork/GetInfo");
+const { getUserID } = require("../../../plugin/WXWork/GetInfo");
+const AccessControl = require("../../../lib/AccessControl");
 
 
 class BindWXWork extends API {
 class BindWXWork extends API {
     constructor() {
     constructor() {
@@ -15,43 +11,42 @@ class BindWXWork extends API {
         this.setPath("/User/BindWXWork");
         this.setPath("/User/BindWXWork");
     }
     }
 
 
-    createSession(uuid, salt) {
-        return md5(`${uuid}${salt}${new Date().getTime()}`);
-    }
-
     async onRequest(req, res) {
     async onRequest(req, res) {
-        const { code } = req.body;
-        if (!code) {
+        const { uuid, session, code } = req.body;
+        if (!uuid || !session || !code) {
             res.json({ ...BaseStdResponse.MISSING_PARAMETER, endpoint: 7841686 });
             res.json({ ...BaseStdResponse.MISSING_PARAMETER, endpoint: 7841686 });
             return;
             return;
         }
         }
 
 
+        // 检查 session 是否有效
+        if (!await AccessControl.checkSession(uuid, session)) {
+            return res.json({
+                ...BaseStdResponse.ACCESS_DENIED,
+                endpoint: 48153145
+            });
+        }
+
         try {
         try {
             const idRes = await getUserID(code);
             const idRes = await getUserID(code);
             if (!idRes || !idRes.success) {
             if (!idRes || !idRes.success) {
-                return res.json({ ...BaseStdResponse.ERR, endpoint: 7894377, msg: `登录失败!${idRes.msg}` });
+                return res.json({ ...BaseStdResponse.ERR, endpoint: 7894377, msg: `绑定失败!${idRes.msg}` });
             }
             }
 
 
             const wxid = idRes.userid;
             const wxid = idRes.userid;
-            const session = this.createSession(wxid, Math.random().toFixed(6).slice(-6));
-
-            let sql = 'SELECT id, uuid, username, avatar, email FROM users WHERE wxid = ?';
-            let rows = await db.query(sql, [wxid]);
 
 
-            let id, result, uuid, username, avatar, email;
-            if (rows.length > 0) {
-                ({ id, uuid, username, avatar, email } = rows[0]);
-                avatar = avatar && avatar != '' ? avatar : 'https://vthc.cn/img/avatar.png';
-                const updateQuery = 'UPDATE users SET session = ? WHERE wxid = ?';
-                result = await db.query(updateQuery, [session, id]); 
-            } else {
+            let sql = 'UPDATE users SET wxid = ? WHERE uuid = ?';
+            let result = await db.query(sql, [wxid, uuid]);
+            if (!result || result.affectedRows !== 1)
                 return res.json({
                 return res.json({
-                    ...BaseStdResponse.USER_NOT_EXISTS
+                    ...BaseStdResponse.DATABASE_ERR
                 })
                 })
-            }
 
 
-            if (result && result.affectedRows > 0) {
-                return res.json({
+            sql = 'SELECT uuid, username, avatar, email, session FROM users WHERE uuid = ?';
+            let rows = await db.query(sql, [uuid]);
+
+            if (rows.length > 0) {
+                let { id, uuid, username, avatar, email, session } = rows[0];
+                res.json({
                     ...BaseStdResponse.OK,
                     ...BaseStdResponse.OK,
                     data: {
                     data: {
                         uuid,
                         uuid,
@@ -62,12 +57,15 @@ class BindWXWork extends API {
                         session
                         session
                     }
                     }
                 });
                 });
+                
             } else {
             } else {
-                return res.json({ ...BaseStdResponse.ERR, endpoint: 7894378, msg: '登录失败!' });
+                return res.json({
+                    ...BaseStdResponse.USER_NOT_EXISTS
+                })
             }
             }
         } catch (error) {
         } catch (error) {
-            this.logger.error(`企业微信登录失败!${error.stack}`)
-            return res.json({ ...BaseStdResponse.ERR, endpoint: 7894379, msg: '登录失败!' });
+            this.logger.error(`绑定企业微信失败!${error.stack}`)
+            return res.json({ ...BaseStdResponse.ERR, endpoint: 7894379, msg: '绑定失败!' });
         }
         }
     }
     }
 }
 }

+ 82 - 0
apis/User/UserInfo/ChangePassword.js

@@ -0,0 +1,82 @@
+const API = require("../../../lib/API");
+const db = require("../../../plugin/DataBase/db");
+const { BaseStdResponse } = require("../../../BaseStdResponse");
+const sendEmail = require('../../../plugin/Email/Email');
+const AccessControl = require("../../../lib/AccessControl");
+const bcryptjs = require('bcryptjs');
+
+class ChangePassword extends API {
+    constructor() {
+        super();
+
+        this.setMethod("POST");
+        this.setPath("/User/ChangePassword");
+    }
+
+    CheckPassword(password) {
+        if (password.length < 8 || password.length > 16) {
+            return false;
+        }
+
+        const hasLetter = /[a-zA-Z]/.test(password);
+        const hasNumber = /\d/.test(password);
+
+        return hasLetter && hasNumber;
+    }
+
+    async onRequest(req, res) {
+        let { uuid, session, oldpassword, password } = req.body;
+
+        if ([uuid, session, oldpassword, password].some(value => value === '' || value === null || value === undefined)) {
+            return res.json({
+                ...BaseStdResponse.MISSING_PARAMETER,
+                endpoint: 1513126
+            });
+        }
+
+        // 检查 session 是否有效
+        if (!await AccessControl.checkSession(uuid, session)) {
+            return res.json({
+                ...BaseStdResponse.ACCESS_DENIED,
+                endpoint: 48153145
+            });
+        }
+
+        oldpassword = atob(oldpassword);
+        password = atob(password);
+
+        if (!this.CheckPassword(password))
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '密码需在8到16位之间,且包含字母和数字'
+            })
+
+        let sql = 'SELECT email, password FROM users WHERE uuid = ?';
+        let rows = await db.query(sql, [uuid]);
+        if(!rows || rows.length === 0)
+            return res.json({
+                ...BaseStdResponse.DATABASE_ERR
+            })
+
+        if (!bcryptjs.compareSync(oldpassword, rows[0].password))
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '密码错误!'
+            })
+
+        const hashPassword = bcryptjs.hashSync(password, 10);
+        sql = 'UPDATE users SET password = ? WHERE uuid = ?';
+        let result = await db.query(sql, [hashPassword, uuid]);
+
+        if (result && result.affectedRows > 0) {
+            res.json({
+                ...BaseStdResponse.OK
+            });
+            await sendEmail(rows[0].email, '更改成功', `您的Double_X考勤账号更改密码成功,操作时间:${new Date().toLocaleString()}`);
+        } else {
+            res.json({ ...BaseStdResponse.ERR, endpoint: 7894378, msg: '操作失败!' });
+        }
+    }
+}
+
+module.exports.ChangePassword = ChangePassword;

+ 3 - 3
apis/User/GetPermissions.js → apis/User/UserInfo/GetPermissions.js

@@ -1,6 +1,6 @@
-const API = require("../../lib/API");
-const { BaseStdResponse } = require("../../BaseStdResponse");
-const AccessControl = require("../../lib/AccessControl");
+const API = require("../../../lib/API");
+const { BaseStdResponse } = require("../../../BaseStdResponse");
+const AccessControl = require("../../../lib/AccessControl");
 
 
 // 获取用户权限
 // 获取用户权限
 class GetPermissions extends API {
 class GetPermissions extends API {

+ 5 - 5
apis/User/UpdateInfo.js → apis/User/UserInfo/UpdateInfo.js

@@ -1,11 +1,11 @@
-const API = require("../../lib/API");
-const { BaseStdResponse } = require("../../BaseStdResponse");
-const db = require("../../plugin/DataBase/db");
-const AccessControl = require("../../lib/AccessControl");
+const API = require("../../../lib/API");
+const { BaseStdResponse } = require("../../../BaseStdResponse");
+const db = require("../../../plugin/DataBase/db");
+const AccessControl = require("../../../lib/AccessControl");
 const {
 const {
     getUserInfo,
     getUserInfo,
     getUserID
     getUserID
-} = require("../../plugin/WXWork/GetInfo");
+} = require("../../../plugin/WXWork/GetInfo");
 
 
 class UpdateInfo extends API {
 class UpdateInfo extends API {
     constructor() {
     constructor() {

+ 0 - 1
apis/User/WXWorkLogin/WXWorkLogin.js

@@ -37,7 +37,6 @@ class WXWorkLogin extends API {
             let id, result, uuid, username, avatar, email;
             let id, result, uuid, username, avatar, email;
             if (rows.length > 0) {
             if (rows.length > 0) {
                 ({ id, uuid, username, avatar, email } = rows[0]);
                 ({ id, uuid, username, avatar, email } = rows[0]);
-                avatar = avatar && avatar != '' ? avatar : 'https://vthc.cn/img/avatar.png';
                 const updateQuery = 'UPDATE users SET session = ? WHERE wxid = ?';
                 const updateQuery = 'UPDATE users SET session = ? WHERE wxid = ?';
                 result = await db.query(updateQuery, [session, id]);
                 result = await db.query(updateQuery, [session, id]);
             } else {
             } else {

+ 1 - 1
apis/User/WXWorkLogin/WXWorkUrl.js

@@ -18,7 +18,7 @@ class WXWorkUrl extends API {
         if(type === 'login')
         if(type === 'login')
             redirect = encodeURIComponent(`${config.url}/#/Login/Stage2?num=${num}`);
             redirect = encodeURIComponent(`${config.url}/#/Login/Stage2?num=${num}`);
         else
         else
-            redirect = encodeURIComponent(`${config.url}/#/UpdateInfo/Stage2?num=${num}`);
+            redirect = encodeURIComponent(`${config.url}/#/UpdateInfo/BindWXWork?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}`
         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({
         return res.json({

+ 1 - 0
package.json

@@ -16,6 +16,7 @@
     "cors": "^2.8.5",
     "cors": "^2.8.5",
     "express": "^4.19.2",
     "express": "^4.19.2",
     "md5": "^2.3.0",
     "md5": "^2.3.0",
+    "multer": "^1.4.5-lts.1",
     "mysql2": "^3.11.0",
     "mysql2": "^3.11.0",
     "nodemailer": "^6.9.14",
     "nodemailer": "^6.9.14",
     "redis": "^4.7.0",
     "redis": "^4.7.0",