diff --git a/controller/RegisterController.py b/controller/RegisterController.py new file mode 100644 index 0000000..07b2bfb --- /dev/null +++ b/controller/RegisterController.py @@ -0,0 +1,182 @@ +from robyn import jsonify, Response +from app import app +import os +import uuid +from service.UserService import user_service + +# 头像上传目录 +AVATAR_UPLOAD_FOLDER = 'resource/avatar' + +@app.post("/api/register") +def register_route(request): + """用户注册接口""" + try: + # 获取表单数据 + form_data = getattr(request, 'form_data', {}) if hasattr(request, 'form_data') else {} + + # 获取文件上传 + files = getattr(request, 'files', {}) if hasattr(request, 'files') else {} + + username = form_data.get("username", "").strip() + password = form_data.get("password", "").strip() + + # 验证必填字段 + if not username or not password: + return Response( + status_code=400, + description=jsonify({"success": False, "message": "用户名和密码不能为空"}), + headers={"Content-Type": "application/json; charset=utf-8"} + ) + + # 检查用户名是否已存在 + existing_user = user_service.get_user_by_username(username) + if existing_user: + return Response( + status_code=409, + description=jsonify({"success": False, "message": "用户名已存在"}), + headers={"Content-Type": "application/json; charset=utf-8"} + ) + + # 处理头像上传 + avatar_path = "/resource/avatar/4.png" # 默认头像 + + # 获取头像文件 + avatar_file = files.get('avatar') + + # 如果有头像文件,处理上传 + if avatar_file: + # 获取文件内容和文件名 + file_content = None + filename = "" + + # Robyn框架中,files字典的值通常是字节数据 + if isinstance(avatar_file, bytes): + file_content = avatar_file + filename = "avatar.jpg" + elif hasattr(avatar_file, 'content'): + file_content = avatar_file.content + filename = getattr(avatar_file, 'filename', 'avatar.jpg') + elif hasattr(avatar_file, 'read'): + file_content = avatar_file.read() + filename = getattr(avatar_file, 'filename', 'avatar.jpg') + else: + filename = avatar_file if isinstance(avatar_file, str) else 'avatar.jpg' + + # 如果我们有文件内容,继续处理 + if file_content: + # 处理文件名 + file_extension = filename.split('.')[-1] if '.' in filename else 'jpg' + + # 验证文件类型 + is_valid_image = False + + # 通过扩展名验证 + if file_extension.lower() in ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']: + is_valid_image = True + + # 如果扩展名验证失败,尝试读取文件头验证 + if not is_valid_image: + try: + if (file_content.startswith(b'\xFF\xD8\xFF') or # JPEG + file_content.startswith(b'\x89PNG\r\n\x1a\n') or # PNG + file_content.startswith(b'GIF87a') or # GIF + file_content.startswith(b'GIF89a')): # GIF + is_valid_image = True + except: + pass + + if not is_valid_image: + return Response( + status_code=400, + description=jsonify({"success": False, "message": "不支持的图片格式"}), + headers={"Content-Type": "application/json; charset=utf-8"} + ) + + # 确保上传目录存在 + if not os.path.exists(AVATAR_UPLOAD_FOLDER): + os.makedirs(AVATAR_UPLOAD_FOLDER) + + # 生成唯一文件名 + unique_filename = f"{uuid.uuid4().hex}.{file_extension}" + file_path = os.path.join(AVATAR_UPLOAD_FOLDER, unique_filename) + + # 保存文件 + try: + with open(file_path, 'wb') as f: + f.write(file_content) + avatar_path = f"/{file_path}" + except Exception as e: + print(f"保存头像失败: {e}") + return Response( + status_code=500, + description=jsonify({"success": False, "message": "头像保存失败"}), + headers={"Content-Type": "application/json; charset=utf-8"} + ) + + # 创建用户 + user_id = user_service.create_user(username, password, avatar_path) + if user_id: + return Response( + status_code=201, + description=jsonify({ + "success": True, + "message": "注册成功", + "user": { + "id": user_id, + "username": username, + "avatar": avatar_path + } + }), + headers={"Content-Type": "application/json; charset=utf-8"} + ) + else: + return Response( + status_code=500, + description=jsonify({"success": False, "message": "注册失败,请稍后再试"}), + headers={"Content-Type": "application/json; charset=utf-8"} + ) + except Exception as e: + print(f"注册异常: {e}") + return Response( + status_code=500, + description=jsonify({"success": False, "message": f"注册失败: {str(e)}"}), + headers={"Content-Type": "application/json; charset=utf-8"} + ) + +@app.get("/api/checkUsername") +def check_username_route(request): + """检查用户名是否可用""" + try: + # 获取查询参数 + username = "" + + # 尝试从query_params获取 + if hasattr(request, 'query_params') and request.query_params: + username = request.query_params.get("username", "").strip() + + if not username: + return Response( + status_code=400, + description=jsonify({"success": False, "message": "用户名不能为空"}), + headers={"Content-Type": "application/json; charset=utf-8"} + ) + + # 检查用户名是否已存在 + existing_user = user_service.get_user_by_username(username) + + return Response( + status_code=200, + description=jsonify({ + "success": True, + "available": existing_user is None, + "message": "用户名可用" if not existing_user else "用户名已存在" + }), + headers={"Content-Type": "application/json; charset=utf-8"} + ) + except Exception as e: + print(f"检查用户名异常: {e}") + return Response( + status_code=500, + description=jsonify({"success": False, "message": f"检查失败: {str(e)}"}), + headers={"Content-Type": "application/json; charset=utf-8"} + ) \ No newline at end of file diff --git a/resource/avatar/1_1766477129_1706.jpg b/resource/avatar/1_1766477129_1706.jpg new file mode 100644 index 0000000..2c96fbf Binary files /dev/null and b/resource/avatar/1_1766477129_1706.jpg differ diff --git a/resource/avatar/f8c6732809a04f74bc4f3856b949de5e.jpg b/resource/avatar/f8c6732809a04f74bc4f3856b949de5e.jpg new file mode 100644 index 0000000..2c96fbf Binary files /dev/null and b/resource/avatar/f8c6732809a04f74bc4f3856b949de5e.jpg differ diff --git a/service/UserService.py b/service/UserService.py index 9971234..fa765b8 100644 --- a/service/UserService.py +++ b/service/UserService.py @@ -122,6 +122,47 @@ class UserService: except Exception as e: print(f"验证密码失败: {e}") return False + + def create_user(self, username: str, password: str, avatar_path: str = None) -> int: + """创建新用户""" + try: + # 检查数据库连接状态 + if not self.mysql.is_connected(): + print("数据库未连接,尝试重新连接...") + if not self.mysql.connect(): + print("数据库连接失败") + return 0 + + # 使用bcrypt加密密码 + hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') + + # 设置默认头像 + if not avatar_path: + avatar_path = "/resource/avatar/4.png" + + # 插入新用户 + sql = "INSERT INTO users (username, password, avatar) VALUES (%s, %s, %s)" + print(f"执行SQL: {sql}") + print(f"参数: {username}, {hashed_password}, {avatar_path}") + + # 执行插入 + result = self.mysql.execute_update(sql, (username, hashed_password, avatar_path)) + + if result > 0: + # 获取插入的用户ID + id_sql = "SELECT LAST_INSERT_ID() as user_id" + id_result = self.mysql.execute_query(id_sql) + if id_result: + user_id = id_result[0].get("user_id", 0) + print(f"插入结果,用户ID: {user_id}") + return user_id + + return 0 + except Exception as e: + print(f"创建用户失败: {e}") + import traceback + traceback.print_exc() + return 0 # 创建全局用户服务实例 user_service = UserService() diff --git a/vue/src/api/register.js b/vue/src/api/register.js new file mode 100644 index 0000000..7a0f90d --- /dev/null +++ b/vue/src/api/register.js @@ -0,0 +1,33 @@ +// src/api/register.js +import request from '@/utils/request'; + +/** + * 用户注册 + * 后端接口:POST /api/register + * @param {FormData} formData - 包含用户名、密码和头像的表单数据 + */ +export function register(formData) { + return request({ + url: '/api/register', + method: 'post', + data: formData, + headers: { + 'Content-Type': 'multipart/form-data' + } + }); +} + +/** + * 检查用户名是否可用 + * 后端接口:GET /api/checkUsername + * @param {string} username - 要检查的用户名 + */ +export function checkUsername(username) { + return request({ + url: '/api/checkUsername', + method: 'get', + params: { + username + } + }); +} \ No newline at end of file diff --git a/vue/src/router/index.js b/vue/src/router/index.js index 1be6dfc..1cd308a 100644 --- a/vue/src/router/index.js +++ b/vue/src/router/index.js @@ -1,5 +1,6 @@ import { createRouter, createWebHistory } from 'vue-router' import Login from '../system/Login.vue' +import Register from '../system/Register.vue' import Index from '../system/Index.vue' import Profile from '../system/Profile.vue' @@ -14,6 +15,11 @@ const routes = [ component: Login }, { + path: '/register', + name: 'Register', + component: Register + }, + { path: '/index', name: 'Index', component: Index diff --git a/vue/src/system/Login.vue b/vue/src/system/Login.vue index 830e35d..8300464 100644 --- a/vue/src/system/Login.vue +++ b/vue/src/system/Login.vue @@ -72,7 +72,7 @@ @@ -151,6 +151,11 @@ const handleLogin = async () => { loading.value = false; } }; + +// 跳转到注册页面 +const goToRegister = () => { + router.push('/register'); +}; + + \ No newline at end of file diff --git a/vue/vue.config.js b/vue/vue.config.js index 2bd1a22..c5f47fd 100644 --- a/vue/vue.config.js +++ b/vue/vue.config.js @@ -46,6 +46,20 @@ module.exports = defineConfig({ '^/updatePassword': '/api/updatePassword' } }, + '/register': { + target: 'http://localhost:8088', // 注册接口代理 + changeOrigin: true, + pathRewrite: { + '^/register': '/api/register' + } + }, + '/checkUsername': { + target: 'http://localhost:8088', // 检查用户名接口代理 + changeOrigin: true, + pathRewrite: { + '^/checkUsername': '/api/checkUsername' + } + }, '/resource': { target: 'http://localhost:8088', // 更新为8088端口 changeOrigin: true diff --git a/web_main.py b/web_main.py index 08d6da8..b5c7dbf 100644 --- a/web_main.py +++ b/web_main.py @@ -1,5 +1,6 @@ from app import app import controller.LoginController +import controller.RegisterController from service.UserService import init_mysql_connection import os