Browse Source

✨ feat: 增加邮箱注册功能

Pchen. 7 months ago
parent
commit
54664cb588

+ 56 - 0
apis/User/Captcha/ImageCaptcha.js

@@ -0,0 +1,56 @@
+const svgCaptcha = require('svg-captcha');
+const md5 = require("md5");
+const API = require("../../../lib/API");
+const { BaseStdResponse } = require("../../../BaseStdResponse");
+const Redis = require('../../../plugin/DataBase/Redis'); 
+
+// 生成图片验证码
+class ImageCaptcha extends API {
+    constructor() {
+        super();
+
+        this.setMethod("GET");
+        this.setPath("/User/ImageCaptcha");
+    }
+
+    async onRequest(req, res) {
+        const options = {
+            size: 4, // 4个字母
+            noise: 2, // 干扰线2条
+            color: true, 
+            background: "#666",
+        }
+        const captcha = svgCaptcha.create(options) //字母和数字随机验证码
+        let { text, data } = captcha
+
+        text = text.toLowerCase() 
+
+        data = Buffer.from(data).toString('base64');
+        // 构建Base64编码的Data URL
+        data = `data:image/svg+xml;base64,${data}`;
+
+        const id = md5(Date.now() + text);
+
+        try {
+            await Redis.set(`captcha:${id}`, text, {
+                EX: 600
+            });
+        } catch (err) {
+            this.logger.error(`获取图片验证码失败!${err.stack}`);
+            return res.json({
+                ...BaseStdResponse.DATABASE_ERR,
+                msg:'获取图片验证码失败!'
+            })
+        }
+
+        res.json({
+            ...BaseStdResponse.OK,
+            data: {
+                img: data,
+                id
+            }
+        })
+    }
+}
+
+module.exports.ImageCaptcha = ImageCaptcha;

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

@@ -0,0 +1,77 @@
+const API = require("../../../lib/API");
+const { BaseStdResponse } = require("../../../BaseStdResponse");
+const Redis = require('../../../plugin/DataBase/Redis');
+const sendEmail = require('../../../plugin/Email/Email');
+
+// 发送邮箱验证码
+class SendEmail extends API {
+    constructor() {
+        super();
+
+        this.setMethod("POST");
+        this.setPath("/User/SendEmail");
+    }
+
+    async onRequest(req, res) {
+        const { email, text, id, type } = req.body;
+
+        if ([email, text, id, type].some(value => value === '' || value === null || value === undefined)) {
+            res.json({
+                ...BaseStdResponse.MISSING_PARAMETER,
+                endpoint: 1513126
+            });
+            return;
+        }
+
+        try {
+            const code = await Redis.get(`captcha:${id}`);
+            if (!code || code != text)
+                return res.json({
+                    ...BaseStdResponse.SMS_CHECK_FAIL,
+                    msg: '验证码输入错误或已过期'
+                })
+
+            await Redis.del(`captcha:${id}`);
+        } catch (err) {
+            this.logger.error(`验证图片验证码失败!${err.stack}`);
+            return res.json({
+                ...BaseStdResponse.DATABASE_ERR,
+                msg: '验证失败!'
+            })
+        }
+
+        let content;
+        switch (type) {
+            case 'register':
+                content = '您正在注册Double_X考勤系统账号,';
+                break;
+            case 'forget':
+                content = '您正在找回Double_X考勤系统账号,';
+                break;
+            default:
+                return res.json({
+                    ...BaseStdResponse.METHOD_NOT_EXIST
+                })
+        }
+
+        const code = Math.random().toFixed(6).slice(-6);
+        try {
+            await Redis.set(`email:${email}`, code, {
+                EX: 600
+            });
+            await sendEmail(email, '验证码', `${content}您的验证码为:${code}。此验证码10分钟内有效,请妥善保管您的验证码,非本人操作请忽略。`);
+        } catch (err) {
+            this.logger.error(`发送邮箱验证码失败!${err.stack}`);
+            return res.json({
+                ...BaseStdResponse.SMS_SEND_FAIL,
+                msg: '请检查邮箱格式后再试!'
+            })
+        }
+
+        res.json({
+            ...BaseStdResponse.OK
+        })
+    }
+}
+
+module.exports.SendEmail = SendEmail;

+ 20 - 0
apis/User/Captcha/VerifyImageCaptcha.js

@@ -0,0 +1,20 @@
+const md5 = require("md5");
+const API = require("../../../lib/API");
+const { BaseStdResponse } = require("../../../BaseStdResponse");
+const Redis = require('../../../plugin/DataBase/Redis'); 
+
+// 验证图片验证码 暂未启用
+class VerifyImageCaptcha extends API {
+    constructor() {
+        super();
+
+        this.setMethod("GET");
+        this.setPath("/User/VerifyImageCaptcha");
+    }
+
+    async onRequest(req, res) {
+        
+    }
+}
+
+module.exports.VerifyImageCaptcha = VerifyImageCaptcha;

+ 96 - 0
apis/User/Login/ForgetPassword.js

@@ -0,0 +1,96 @@
+const md5 = require("md5");
+const API = require("../../../lib/API");
+const bcryptjs = require('bcryptjs');
+const db = require("../../../plugin/DataBase/db");
+const { BaseStdResponse } = require("../../../BaseStdResponse");
+const Redis = require('../../../plugin/DataBase/Redis');
+const sendEmail = require('../../../plugin/Email/Email');
+
+// 找回密码
+class ForgetPassword extends API {
+    constructor() {
+        super();
+
+        this.setMethod("POST");
+        this.setPath("/User/ForgetPassword");
+    }
+
+    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;
+    }
+
+    createSession(uuid, salt) {
+        return md5(`${uuid}${salt}${new Date().getTime()}`);
+    }
+
+    async onRequest(req, res) {
+        let { username, email, code, password } = req.body;
+
+        if ([username, email, code, password].some(value => value === '' || value === null || value === undefined)) {
+            res.json({
+                ...BaseStdResponse.MISSING_PARAMETER,
+                endpoint: 1513126
+            });
+            return;
+        }
+
+        if (!this.CheckPassword(password))
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '密码需在8到16位之间,且包含字母和数字'
+            })
+
+        let sql = 'SELECT id FROM users WHERE username = ? AND email = ?';
+        let Rows = await db.query(sql, [username, email]);
+        if (Rows.length === 0)
+            return res.json({
+                ...BaseStdResponse.USER_NOT_EXISTS,
+                msg: '用户名和邮箱不匹配!'
+            })
+
+        password = atob(password);
+
+        try {
+            const VerifyCode = await Redis.get(`email:${email}`);
+            if (!VerifyCode || VerifyCode != code)
+                return res.json({
+                    ...BaseStdResponse.SMS_CHECK_FAIL,
+                    msg: '邮箱验证码输入错误或已过期'
+                })
+
+        } catch (err) {
+            this.logger.error(`验证邮箱验证码失败!${err.stack}`);
+            return res.json({
+                ...BaseStdResponse.DATABASE_ERR,
+                msg: '验证失败!'
+            })
+        }
+
+        
+        const session = this.createSession(code, Math.random().toFixed(6).slice(-6));
+        const hashPassword = bcryptjs.hashSync(password, 10);
+
+        sql = 'UPDATE users SET password = ? , session = ? WHERE id = ?';
+        let result = await db.query(sql, [hashPassword, session, Rows[0].id]);
+
+        if (result && result.affectedRows > 0) {
+            await Redis.del(`email:${email}`);
+
+            res.json({
+                ...BaseStdResponse.OK
+            });
+            await sendEmail(email, '密码修改提醒', `您已成功修改Double_X考勤账号密码,用户名${username},修改时间:${new Date().toLocaleString()}`);
+        } else {
+            res.json({ ...BaseStdResponse.ERR, endpoint: 7894378, msg: '修改失败!'});
+        }
+    }
+}
+
+module.exports.ForgetPassword = ForgetPassword;

+ 84 - 0
apis/User/Login/Login.js

@@ -0,0 +1,84 @@
+const md5 = require("md5");
+const API = require("../../../lib/API");
+const bcryptjs = require('bcryptjs');
+const { BaseStdResponse } = require("../../../BaseStdResponse");
+const db = require("../../../plugin/DataBase/db");
+const Redis = require('../../../plugin/DataBase/Redis');
+const sendEmail = require('../../../plugin/Email/Email');
+
+// 用户注册
+class Register extends API {
+    constructor() {
+        super();
+
+        this.setMethod("POST");
+        this.setPath("/User/Login");
+    }
+
+    createSession(uuid, salt) {
+        return md5(`${uuid}${salt}${new Date().getTime()}`);
+    }
+
+    async onRequest(req, res) {
+        let { username, password, text, id } = req.body;
+
+        if ([username, password, text, id].some(value => value === '' || value === null || value === undefined)) {
+            res.json({
+                ...BaseStdResponse.MISSING_PARAMETER,
+                endpoint: 1513126
+            });
+            return;
+        }
+
+        password = atob(password);
+
+        try {
+            const code = await Redis.get(`captcha:${id}`);
+            if (!code || code != text)
+                return res.json({
+                    ...BaseStdResponse.ERR,
+                    msg: '验证码错误或已过期!'
+                })
+        } catch (err) {
+            this.logger.error(`验证图片验证码失败!${err.stack}`);
+            return res.json({
+                ...BaseStdResponse.DATABASE_ERR,
+                msg: '验证失败!'
+            })
+        }
+
+        let sql = 'SELECT * FROM users WHERE username = ? OR email = ?';
+        let rows = await db.query(sql, [username, username]);
+
+        if (!rows || rows.length !== 1 || !bcryptjs.compareSync(password, rows[0].password))
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '用户名或密码错误'
+            })
+
+        const session = this.createSession(text, Math.random().toFixed(6).slice(-6));
+
+        sql = 'UPDATE users SET session = ? WHERE id = ?';
+        let result = await db.query(sql, [session, rows[0].id]);
+
+        if (result && result.affectedRows > 0) {
+            res.json({
+                ...BaseStdResponse.OK,
+                data: {
+                    uuid: rows[0].uuid,
+                    username: rows[0].username,
+                    wxid: rows[0].wxid,
+                    email: rows[0].email,
+                    avatar: rows[0].avatar,
+                    session
+                }
+            });
+            await Redis.del(`captcha:${id}`);
+            await sendEmail(rows[0].email, '账户登录提醒', `您的Double_X考勤账号${rows[0].username}在${new Date().toLocaleString()}登录了Double_X考勤系统,登录ip:${req.headers['x-forwarded-for'] || req.ip}`);
+        } else {
+            res.json({ ...BaseStdResponse.ERR, endpoint: 7894378, msg: '登录失败!' });
+        }
+    }
+}
+
+module.exports.Register = Register;

+ 125 - 0
apis/User/Login/Register.js

@@ -0,0 +1,125 @@
+const md5 = require("md5");
+const API = require("../../../lib/API");
+const bcryptjs = require('bcryptjs');
+const db = require("../../../plugin/DataBase/db");
+const { BaseStdResponse } = require("../../../BaseStdResponse");
+const Redis = require('../../../plugin/DataBase/Redis');
+const sendEmail = require('../../../plugin/Email/Email');
+
+// 用户注册
+class Register extends API {
+    constructor() {
+        super();
+
+        this.setMethod("POST");
+        this.setPath("/User/Register");
+    }
+
+    createSession(uuid, salt) {
+        return md5(`${uuid}${salt}${new Date().getTime()}`);
+    }
+
+    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;
+    }
+
+    checkUsername(username) {
+        const regex = /^[\u4e00-\u9fa5A-Za-z0-9]{2,8}$/;
+        return regex.test(username);
+    }
+
+    async onRequest(req, res) {
+        let { username, email, code, password } = req.body;
+
+        if ([username, email, code, password].some(value => value === '' || value === null || value === undefined)) {
+            res.json({
+                ...BaseStdResponse.MISSING_PARAMETER,
+                endpoint: 1513126
+            });
+            return;
+        }
+
+        if(!this.checkUsername(username))
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '用户名需在2到8位之间,且只能含有英文字母和汉字'
+            })
+
+        password = atob(password);
+
+        if (!this.CheckPassword(password))
+            return res.json({
+                ...BaseStdResponse.ERR,
+                msg: '密码需在8到16位之间,且包含字母和数字'
+            })
+
+        try {
+            const VerifyCode = await Redis.get(`email:${email}`);
+            if (!VerifyCode || VerifyCode != code)
+                return res.json({
+                    ...BaseStdResponse.SMS_CHECK_FAIL,
+                    msg: '邮箱验证码输入错误或已过期'
+                })
+
+        } catch (err) {
+            this.logger.error(`验证邮箱验证码失败!${err.stack}`);
+            return res.json({
+                ...BaseStdResponse.DATABASE_ERR,
+                msg: '验证失败!'
+            })
+        }
+
+        let sql = 'SELECT username FROM users WHERE username = ?';
+        let UsernameRows = await db.query(sql, [username]);
+        if (UsernameRows.length > 0)
+            return res.json({
+                ...BaseStdResponse.USER_ALREADY_EXISTS,
+                msg: '用户名已被占用!'
+            })
+
+        sql = 'SELECT email FROM users WHERE email = ?';
+        let EmailRows = await db.query(sql, [username]);
+        if (EmailRows.length > 0)
+            return res.json({
+                ...BaseStdResponse.USER_ALREADY_EXISTS,
+                msg: '该邮箱已被注册!'
+            })
+
+        // 查询users表中是否有用户
+        const userCountQuery = 'SELECT COUNT(*) as count FROM users';
+        const userCountResult = await db.query(userCountQuery);
+        const userCount = userCountResult.count;
+
+        // 如果是第一个注册的用户 授予admin权限
+        const admin = userCount === 0 ? 1 : 0;
+
+        const uuid = md5(Date.now() + email + code);
+        const session = this.createSession(code, Math.random().toFixed(6).slice(-6));
+        const hashPassword = bcryptjs.hashSync(password, 10);
+        const avatar = 'https://vthc.cn/img/avatar.png';
+
+        sql = 'INSERT INTO users (uuid, username, avatar, session, admin, email, password) VALUES (?, ?, ?, ?, ?, ?, ?)';
+        let result = await db.query(sql, [uuid, username, avatar, session, admin, email, hashPassword]);
+
+        if (result && result.affectedRows > 0) {
+            // 注册成功后删除邮箱对应的验证码 避免注册失败后重复获取
+            await Redis.del(`email:${email}`);
+
+            res.json({
+                ...BaseStdResponse.OK
+            });
+            await sendEmail(email, '账号注册成功', `您已成功注册Double_X考勤账号,用户名${username},注册时间:${new Date().toLocaleString()}`);
+        } else {
+            res.json({ ...BaseStdResponse.ERR, endpoint: 7894378, msg: '注册失败!'});
+        }
+    }
+}
+
+module.exports.Register = Register;

+ 0 - 93
apis/User/WXWorkLogin.js

@@ -1,93 +0,0 @@
-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) {
-        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]);
-                avatar = avatar && avatar != '' ? avatar : 'https://git.vthc.cn/avatars/1';
-                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);
-
-                // TODO 企微扫码登录不会返回用户头像 这里疏忽了 后面再改 
-                avatar = avatar && avatar != '' ? avatar : 'https://git.vthc.cn/avatars/1';
-                uuid = md5(Date.now() + wxid + code);
-
-                // 查询users表中是否有用户
-                const userCountQuery = 'SELECT COUNT(*) as count FROM users';
-                const [userCountResult] = await db.query(userCountQuery);
-                const userCount = userCountResult.count;
-
-                // 如果是第一个注册的用户 授予admin权限
-                const admin = userCount === 0 ? 1 : 0;
-
-                const insertQuery = 'INSERT INTO users (uuid, username, wxid, avatar, session, admin) VALUES (?, ?, ?, ?, ?, ?)';
-                result = await db.query(insertQuery, [uuid, username, wxid, avatar, session, admin]);
-            }
-
-            if (result && result.affectedRows > 0) {
-                return res.json({
-                    ...BaseStdResponse.OK,
-                    data: {
-                        uuid,
-                        username,
-                        wxid,
-                        avatar,
-                        session
-                    }
-                });
-            } else {
-                return res.json({ ...BaseStdResponse.ERR, endpoint: 7894378, msg: '登录失败!' });
-            }
-        } catch (error) {
-            this.logger.error(`企业微信登录失败!${error.stack}`)
-            return res.json({ ...BaseStdResponse.ERR, endpoint: 7894379, msg: '登录失败!' });
-        }
-    }
-}
-
-module.exports.WXWorkLogin = WXWorkLogin;

+ 75 - 0
apis/User/WXWorkLogin/BindWXWork.js

@@ -0,0 +1,75 @@
+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 BindWXWork extends API {
+    constructor() {
+        super();
+        this.setMethod("POST");
+        this.setPath("/User/BindWXWork");
+    }
+
+    createSession(uuid, salt) {
+        return md5(`${uuid}${salt}${new Date().getTime()}`);
+    }
+
+    async onRequest(req, 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 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 {
+                return res.json({
+                    ...BaseStdResponse.USER_NOT_EXISTS
+                })
+            }
+
+            if (result && result.affectedRows > 0) {
+                return res.json({
+                    ...BaseStdResponse.OK,
+                    data: {
+                        uuid,
+                        username,
+                        wxid,
+                        email,
+                        avatar,
+                        session
+                    }
+                });
+            } else {
+                return res.json({ ...BaseStdResponse.ERR, endpoint: 7894378, msg: '登录失败!' });
+            }
+        } catch (error) {
+            this.logger.error(`企业微信登录失败!${error.stack}`)
+            return res.json({ ...BaseStdResponse.ERR, endpoint: 7894379, msg: '登录失败!' });
+        }
+    }
+}
+
+module.exports.BindWXWork = BindWXWork;

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

@@ -0,0 +1,71 @@
+const md5 = require("md5");
+const API = require("../../../lib/API");
+const { BaseStdResponse } = require("../../../BaseStdResponse");
+const db = require("../../../plugin/DataBase/db");
+const { 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) {
+        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 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 {
+                return res.json({
+                    ...BaseStdResponse.USER_NOT_EXISTS
+                })
+            }
+
+            if (result && result.affectedRows > 0) {
+                return res.json({
+                    ...BaseStdResponse.OK,
+                    data: {
+                        uuid,
+                        username,
+                        wxid,
+                        email,
+                        avatar,
+                        session
+                    }
+                });
+            } else {
+                return res.json({ ...BaseStdResponse.ERR, endpoint: 7894378, msg: '登录失败!' });
+            }
+        } catch (error) {
+            this.logger.error(`企业微信登录失败!${error.stack}`)
+            return res.json({ ...BaseStdResponse.ERR, endpoint: 7894379, msg: '登录失败!' });
+        }
+    }
+}
+
+module.exports.WXWorkLogin = WXWorkLogin;

+ 3 - 3
apis/User/WXWorkUrl.js → apis/User/WXWorkLogin/WXWorkUrl.js

@@ -1,6 +1,6 @@
-const API = require("../../lib/API");
-const { BaseStdResponse } = require("../../BaseStdResponse");
-const config = require("../../config.json");
+const API = require("../../../lib/API");
+const { BaseStdResponse } = require("../../../BaseStdResponse");
+const config = require("../../../config.json");
 
 
 // 构造网页授权链接
 // 构造网页授权链接
 class WXWorkUrl extends API {
 class WXWorkUrl extends API {

+ 7 - 0
config-example.json

@@ -17,5 +17,12 @@
         "corpsecret": "",
         "corpsecret": "",
         "agentid": 1000003
         "agentid": 1000003
     },
     },
+    "email": {
+        "host": "smtp.qq.com",
+        "port": 465,
+        "secure": true,
+        "user": "",
+        "password": ""
+    },
     "url": "http://example.com"
     "url": "http://example.com"
 }
 }

+ 5 - 1
package.json

@@ -10,11 +10,15 @@
   "license": "ISC",
   "license": "ISC",
   "dependencies": {
   "dependencies": {
     "axios": "^1.7.4",
     "axios": "^1.7.4",
+    "bcryptjs": "^2.4.3",
     "body-parser": "^1.20.2",
     "body-parser": "^1.20.2",
     "chalk": "^4.1.2",
     "chalk": "^4.1.2",
     "cors": "^2.8.5",
     "cors": "^2.8.5",
     "express": "^4.19.2",
     "express": "^4.19.2",
     "md5": "^2.3.0",
     "md5": "^2.3.0",
-    "mysql2": "^3.11.0"
+    "mysql2": "^3.11.0",
+    "nodemailer": "^6.9.14",
+    "redis": "^4.7.0",
+    "svg-captcha": "^1.4.0"
   }
   }
 }
 }

+ 16 - 0
plugin/DataBase/Redis.js

@@ -1,6 +1,7 @@
 const redis = require('redis');
 const redis = require('redis');
 const config = require('../../config.json');
 const config = require('../../config.json');
 const Logger = require('../../lib/Logger');
 const Logger = require('../../lib/Logger');
+const path = require('path');
 
 
 class RedisClient {
 class RedisClient {
     constructor() {
     constructor() {
@@ -22,10 +23,25 @@ class RedisClient {
         this.client.on('connect', () => {
         this.client.on('connect', () => {
             this.logger.info('Redis连接成功!');
             this.logger.info('Redis连接成功!');
         });
         });
+
+        // 在连接建立时标记为已连接状态
+        this.client.on('ready', () => {
+            this.logger.info('Redis客户端已就绪!');
+        });
+
+        // 在客户端关闭时处理事件
+        this.client.on('end', () => {
+            this.logger.warn('Redis客户端连接已关闭!');
+        });
     }
     }
 
 
     // 获取 Redis 客户端实例
     // 获取 Redis 客户端实例
     getClient() {
     getClient() {
+        if (!this.client.isOpen) {
+            this.client.connect().catch((err) => {
+                this.logger.error(`重新连接Redis时出错:${err.stack}`);
+            });
+        }
         return this.client;
         return this.client;
     }
     }
 }
 }

+ 42 - 0
plugin/Email/Email.js

@@ -0,0 +1,42 @@
+const nodemailer = require('nodemailer');
+const config = require('../../config.json');
+const path = require('path');
+const Logger = require('../../lib/Logger');
+
+const logger = new Logger(path.join(__dirname, '../../logs/Email.log'), 'INFO');
+
+const transporter = nodemailer.createTransport({
+    host: config.email.host,
+    port: config.email.port,
+    secure: config.email.secure,
+    auth: {
+        user: config.email.user,
+        pass: config.email.password
+    }
+})
+
+async function sendEmail(email, subject, content) {
+    return new Promise((resolve, reject) => {
+        const mail = {
+            from: `Double_X考勤 <${transporter.options.auth.user}>`,
+            to: email,
+            subject: subject,
+            text: content
+        }
+        try {
+            transporter.sendMail(mail, (error) => {
+                if (error) {
+                    logger.error('邮件发送失败:', error);
+                    reject(error);
+                } else {
+                    resolve();
+                }
+            })
+        } catch (error) {
+            logger.error('邮件发送失败:', error);
+            reject(error);
+        }
+    })
+}
+
+module.exports = sendEmail;