Browse Source

Merge branch 'dev' of Pchen0/Double_X_Attendance into master

Pchen0 7 months ago
parent
commit
53ac4bd0c3

+ 3 - 3
README.md

@@ -4,11 +4,11 @@
 
 Double_X 考勤系统基于现代化的前端和后端技术栈开发,提供了灵活、智能的考勤管理解决方案。该系统主要功能包括单次打卡和每周循环打卡两种模式,用户可以根据需求自行设置打卡时段和周期,并支持补卡,满足不同场景下的考勤需求。
 
-前端技术栈采用 Vue 3、Vue Router 和 ElementPlus,带来了流畅的用户体验和高效的界面交互。通过集成高德地图 JS API,用户可以方便地在地图上选择考勤地点,并自定义考勤范围,实现精准定位和灵活管理。
+前端技术栈采用 Vue 3、Vue Router 和 ElementPlus,带来了流畅的用户体验和高效的界面交互。通过集成高德地图 JS API,用户可以方便地在地图上选择考勤地点,并自定义考勤范围,实现精准定位和灵活管理。系统支持企业微信登录和账号密码登录,方便用户根据自己的使用习惯选择合适的登录方式。
 
-后端技术栈基于 Node.js 和 Express 搭建,配合 MySQL 数据库,提供了稳定、高效的数据存储和管理功能。系统具备强大的数据处理能力,助力用户更好地管理团队考勤。
+后端技术栈基于 Node.js 和 Express 搭建,配合 MySQL 和 Redis 数据库,提供了稳定、高效的数据存储和管理功能。系统具备强大的数据处理能力,助力用户更好地管理团队考勤。
 
-Double_X 考勤系统支持多种考勤模式和灵活配置,致力于为用户提供便捷、智能的考勤解决方案。
+Double_X 考勤系统支持多种考勤模式和灵活配置,致力于为用户提供便捷、智能的考勤解决方案。通过支持企业微信登录和账号密码登录,进一步提升了系统的易用性和适应性,满足用户不同的考勤需求。
 
 ## 📦项目地址
 

+ 1 - 1
apis/ClockIn/AddAttendanceRecord.js

@@ -37,7 +37,7 @@ class AddAttendanceRecord extends API {
         }
 
         // 获取考勤项目
-        const sqlGetProject = 'SELECT user, day_of_week, loopy, begintime, endtime FROM kq_items WHERE id = ?';
+        const sqlGetProject = 'SELECT user, createTime, day_of_week, loopy, begintime, endtime FROM kq_items WHERE id = ?';
         let projectResult = await db.query(sqlGetProject, [project_id]);
 
         if (!projectResult || projectResult.length === 0) {

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

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

+ 2 - 4
apis/User/Login/Register.js

@@ -95,15 +95,13 @@ class Register extends API {
         // 查询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 admin = userCountResult[0].count === 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';
+        const avatar = '/avatar/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]);

+ 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 { BaseStdResponse } = require("../../../BaseStdResponse");
-
 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 {
     constructor() {
@@ -15,43 +11,42 @@ class BindWXWork extends API {
         this.setPath("/User/BindWXWork");
     }
 
-    createSession(uuid, salt) {
-        return md5(`${uuid}${salt}${new Date().getTime()}`);
-    }
-
     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 });
             return;
         }
 
+        // 检查 session 是否有效
+        if (!await AccessControl.checkSession(uuid, session)) {
+            return res.json({
+                ...BaseStdResponse.ACCESS_DENIED,
+                endpoint: 48153145
+            });
+        }
+
         try {
             const idRes = await getUserID(code);
             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 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({
-                    ...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,
                     data: {
                         uuid,
@@ -62,12 +57,15 @@ class BindWXWork extends API {
                         session
                     }
                 });
+                
             } else {
-                return res.json({ ...BaseStdResponse.ERR, endpoint: 7894378, msg: '登录失败!' });
+                return res.json({
+                    ...BaseStdResponse.USER_NOT_EXISTS
+                })
             }
         } 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 {

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

@@ -1,12 +1,13 @@
-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 {
     getUserInfo,
     getUserID
-} = require("../../plugin/WXWork/GetInfo");
+} = require("../../../plugin/WXWork/GetInfo");
 
+// 通过企业微信更新用户信息 已废弃
 class UpdateInfo extends API {
     constructor() {
         super();

+ 115 - 0
apis/User/UserInfo/UploadAvatar.js

@@ -0,0 +1,115 @@
+const API = require("../../../lib/API");
+const db = require("../../../plugin/DataBase/db");
+const { BaseStdResponse } = require("../../../BaseStdResponse");
+const AccessControl = require("../../../lib/AccessControl");
+const multer = require('multer');
+const path = require('path');
+
+// 配置Multer的存储选项
+const storage = multer.diskStorage({
+    destination: (req, file, cb) => {
+        cb(null, 'uploads/avatar/');
+    },
+    filename: (req, file, cb) => {
+        const uuid = req.params.uuid;
+        const fileExtension = path.extname(file.originalname);
+        cb(null, `${uuid}${fileExtension}`);
+    }
+});
+
+// 限制文件类型
+const fileFilter = (req, file, cb) => {
+    // 只接受以下扩展名的图片文件
+    const allowedTypes = /jpeg|jpg|png|gif/;
+    const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
+    const mimetype = allowedTypes.test(file.mimetype);
+
+    if (extname && mimetype) {
+        return cb(null, true);
+    } else {
+        cb(new Error('只允许上传图片文件 (jpeg, jpg, png, gif)'));
+    }
+};
+
+// 初始化Multer中间件
+const upload = multer({
+    storage: storage,
+    fileFilter: fileFilter,
+    limits: { fileSize: 3 * 1024 * 1024 } // 限制文件大小为3MB
+}).single('avatar');
+
+class UploadAvatar extends API {
+    constructor() {
+        super();
+
+        this.setMethod("POST");
+        this.setPath("/User/UploadAvatar/:uuid/:session");
+    }
+
+    async onRequest(req, res) {
+        // 使用Multer中间件处理文件上传
+        upload(req, res, async (err) => {
+            if (err) {
+                this.logger.error(`头像上传失败!${err.stack || ''}`)
+                return res.json({
+                    ...BaseStdResponse.ERR
+                });
+            }
+
+            let { uuid, session } = req.params;
+
+            if ([uuid, session].some(value => value === '' || value === null || value === undefined)) {
+                return res.json({
+                    ...BaseStdResponse.MISSING_PARAMETER,
+                    endpoint: 1513126
+                });
+            }
+
+            if (!await AccessControl.checkSession(uuid, session)) {
+                return res.json({
+                    ...BaseStdResponse.ACCESS_DENIED,
+                    endpoint: 48153145
+                });
+            }
+
+            if (!req.file) {
+                return res.json({
+                    ...BaseStdResponse.MISSING_PARAMETER,
+                    msg: '请上传头像文件'
+                });
+            }
+
+            const avatarPath = `/avatar/${req.file.filename}`; // 获取文件路径
+
+            let sql = 'UPDATE users SET avatar = ? WHERE uuid = ?';
+            let result = await db.query(sql, [avatarPath, uuid]);
+
+            if (result && result.affectedRows > 0) {
+                sql = 'SELECT username, wxid, avatar, email 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: rows[0].email,
+                        avatar: rows[0].avatar,
+                        session
+                    }
+                });
+            } else {
+                res.json({ ...BaseStdResponse.ERR, endpoint: 7894378, msg: '头像更新失败!' });
+            }
+        });
+    }
+}
+
+module.exports.UploadAvatar = UploadAvatar;

+ 7 - 9
apis/User/WXWorkLogin/WXWorkLogin.js

@@ -25,7 +25,7 @@ class WXWorkLogin extends API {
         try {
             const idRes = await getUserID(code);
             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;
@@ -34,17 +34,15 @@ class WXWorkLogin extends API {
             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 {
+            if (!rows || rows.length === 0)
                 return res.json({
                     ...BaseStdResponse.USER_NOT_EXISTS
                 })
-            }
+
+            let { id, uuid, username, avatar, email } = rows[0];
+
+            const updateQuery = 'UPDATE users SET session = ? WHERE id = ?';
+            const result = await db.query(updateQuery, [session, id]);
 
             if (result && result.affectedRows > 0) {
                 return res.json({

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

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

+ 23 - 23
data.sql

@@ -1,3 +1,21 @@
+DROP TABLE IF EXISTS `kq_records`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `kq_records` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `project_id` int(11) NOT NULL,
+  `uuid` varchar(32) NOT NULL,
+  `time` varchar(16) NOT NULL,
+  `commit` varchar(32) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+LOCK TABLES `kq_records` WRITE;
+/*!40000 ALTER TABLE `kq_records` DISABLE KEYS */;
+/*!40000 ALTER TABLE `kq_records` ENABLE KEYS */;
+UNLOCK TABLES;
+
 DROP TABLE IF EXISTS `kq_items`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
 /*!40101 SET character_set_client = utf8 */;
@@ -16,7 +34,7 @@ CREATE TABLE `kq_items` (
   `address` varchar(100) DEFAULT NULL,
   `admin` json DEFAULT NULL,
   PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 LOCK TABLES `kq_items` WRITE;
@@ -24,23 +42,6 @@ LOCK TABLES `kq_items` WRITE;
 /*!40000 ALTER TABLE `kq_items` ENABLE KEYS */;
 UNLOCK TABLES;
 
-DROP TABLE IF EXISTS `kq_records`;
-/*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!40101 SET character_set_client = utf8 */;
-CREATE TABLE `kq_records` (
-  `id` int(11) NOT NULL AUTO_INCREMENT,
-  `project_id` int(11) NOT NULL,
-  `uuid` varchar(32) NOT NULL,
-  `time` varchar(16) NOT NULL,
-  `commit` varchar(32) DEFAULT NULL,
-  PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-/*!40101 SET character_set_client = @saved_cs_client */;
-
-LOCK TABLES `kq_records` WRITE;
-/*!40000 ALTER TABLE `kq_records` DISABLE KEYS */;
-/*!40000 ALTER TABLE `kq_records` ENABLE KEYS */;
-UNLOCK TABLES;
 
 DROP TABLE IF EXISTS `users`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
@@ -49,21 +50,21 @@ CREATE TABLE `users` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `uuid` varchar(32) NOT NULL,
   `username` varchar(16) NOT NULL,
-  `wxid` varchar(32) NOT NULL,
+  `wxid` varchar(32) DEFAULT NULL,
   `avatar` varchar(100) NOT NULL,
   `session` varchar(100) NOT NULL,
   `admin` int(1) NOT NULL DEFAULT '0',
   `manage` int(1) NOT NULL DEFAULT '0',
+  `password` varchar(100) DEFAULT NULL,
+  `email` varchar(100) DEFAULT NULL,
   PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 LOCK TABLES `users` WRITE;
 /*!40000 ALTER TABLE `users` DISABLE KEYS */;
 /*!40000 ALTER TABLE `users` ENABLE KEYS */;
 UNLOCK TABLES;
-
-
 /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
 
 /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
@@ -73,4 +74,3 @@ UNLOCK TABLES;
 /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
 /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
 /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-

+ 3 - 0
lib/Server.js

@@ -20,6 +20,9 @@ class SERVER {
         //解决cors跨域
         this.app.use(cors());
 
+        //使用静态资源
+        this.app.use(express.static('./uploads'));
+
         // 初始化数据库连接
         this.db = new MySQL();
 

+ 1 - 0
package.json

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

BIN
uploads/avatar/avatar.png


BIN
uploads/avatar/baac4cdbbc7787293ec1b431d6971cdc.png