| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- // 注册处理模块
- const formidable = require('formidable');
- const path = require('path');
- const fs = require('fs');
- const crypto = require('crypto');
- const { promisify } = require('util');
- const { getDatabase } = require('./sql');
- const mkdir = promisify(fs.mkdir);
- const access = promisify(fs.access);
- const copyFile = promisify(fs.copyFile);
- const unlink = promisify(fs.unlink);
- // 固定验证码
- const FIXED_VERIFICATION_CODE = '9527';
- // 头像存储目录
- const AVATAR_DIR = path.join(__dirname, 'avatar');
- // 确保头像目录存在
- async function ensureAvatarDir() {
- try {
- await access(AVATAR_DIR);
- } catch (error) {
- await mkdir(AVATAR_DIR, { recursive: true });
- console.log('[Register] 创建avatar目录:', AVATAR_DIR);
- }
- }
- // 密码加密(使用 SHA256)
- function hashPassword(password) {
- return crypto.createHash('sha256').update(password).digest('hex');
- }
- // 保存头像文件
- async function saveAvatar(file) {
- if (!file) {
- return null;
- }
- await ensureAvatarDir();
- // 生成唯一文件名
- const ext = path.extname(file.originalFilename || file.name || '');
- const filename = `${Date.now()}_${Math.random().toString(36).substring(7)}${ext}`;
- const filepath = path.join(AVATAR_DIR, filename);
- // 复制文件到目标位置
- await copyFile(file.filepath, filepath);
- // 返回相对路径(用于存储到数据库)
- return `avatar/${filename}`;
- }
- // 处理注册请求
- async function handleRegisterRequest(req, res) {
- // 设置 CORS 头
- res.setHeader('Access-Control-Allow-Origin', '*');
- res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
- if (req.method === 'OPTIONS') {
- res.writeHead(200);
- res.end();
- return;
- }
- if (req.method !== 'POST') {
- res.writeHead(405, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, message: 'Method not allowed' }));
- return;
- }
- try {
- const form = formidable.formidable({
- multiples: false,
- maxFileSize: 5 * 1024 * 1024, // 5MB
- keepExtensions: true
- });
- const [fields, files] = await form.parse(req);
- // 提取表单字段(formidable 可能返回数组)
- const username = Array.isArray(fields.username) ? fields.username[0] : (fields.username || '');
- const phone = Array.isArray(fields.phone) ? fields.phone[0] : (fields.phone || '');
- const code = Array.isArray(fields.code) ? fields.code[0] : (fields.code || '');
- const password = Array.isArray(fields.password) ? fields.password[0] : (fields.password || '');
- const passwordConfirm = Array.isArray(fields.passwordConfirm) ? fields.passwordConfirm[0] : (fields.passwordConfirm || '');
- // 验证必填字段
- if (!username || !phone || !code || !password || !passwordConfirm) {
- res.writeHead(400, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, message: '请填写所有必填字段' }));
- return;
- }
- // 验证用户名格式(现代网站标准)
- // 长度:4-20个字符
- if (username.length < 4 || username.length > 20) {
- res.writeHead(400, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, message: '用户名长度为4-20个字符' }));
- return;
- }
- // 只能包含字母、数字、下划线、连字符
- if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(username)) {
- res.writeHead(400, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, message: '用户名只能包含字母、数字、下划线和连字符,且必须以字母开头' }));
- return;
- }
- // 不能全部是数字
- if (/^\d+$/.test(username)) {
- res.writeHead(400, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, message: '用户名不能全部是数字' }));
- return;
- }
- // 转换为小写进行存储和查询(不区分大小写)
- const normalizedUsername = username.toLowerCase();
- // 验证手机号格式
- if (!/^1[3-9]\d{9}$/.test(phone)) {
- res.writeHead(400, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, message: '请输入正确的手机号' }));
- return;
- }
- // 验证验证码(固定验证码 9527)
- if (code !== FIXED_VERIFICATION_CODE) {
- res.writeHead(400, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, message: '验证码错误' }));
- return;
- }
- // 验证密码格式
- if (password.length < 8 || password.length > 20) {
- res.writeHead(400, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, message: '密码长度为8-20位' }));
- return;
- }
- if (!/[A-Z]/.test(password) || !/[a-z]/.test(password) || !/\d/.test(password)) {
- res.writeHead(400, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, message: '密码必须包含大小写字母和数字' }));
- return;
- }
- // 验证两次密码是否一致
- if (password !== passwordConfirm) {
- res.writeHead(400, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, message: '两次输入的密码不一致' }));
- return;
- }
- // 获取数据库实例
- const db = await getDatabase();
- // 检查用户名是否已存在(不区分大小写)
- const existingUserByUsername = await db.findUserByUsername(normalizedUsername);
- if (existingUserByUsername) {
- res.writeHead(400, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, message: '用户名已存在' }));
- return;
- }
- // 检查手机号是否已存在
- const existingUserByPhone = await db.findUserByPhone(phone);
- if (existingUserByPhone) {
- res.writeHead(400, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, message: '手机号已被注册' }));
- return;
- }
- // 处理头像上传
- let avatarPath = null;
- const avatarFile = Array.isArray(files.avatar) ? files.avatar[0] : (files.avatar || null);
- if (avatarFile && avatarFile.size > 0) {
- // 验证文件类型
- const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];
- if (!allowedTypes.includes(avatarFile.mimetype)) {
- res.writeHead(400, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, message: '头像必须是图片文件' }));
- return;
- }
- try {
- avatarPath = await saveAvatar(avatarFile);
- } catch (error) {
- console.error('[Register] 保存头像失败:', error);
- res.writeHead(500, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, message: '保存头像失败' }));
- return;
- }
- }
- // 加密密码
- const hashedPassword = hashPassword(password);
- // 创建用户(使用原始用户名显示,但存储时使用小写进行唯一性检查)
- const newUser = await db.createUser({
- username: username, // 显示时保持原始大小写
- phone,
- password: hashedPassword,
- avatar: avatarPath
- });
- // 清理临时文件
- if (avatarFile && avatarFile.filepath) {
- try {
- await unlink(avatarFile.filepath);
- } catch (error) {
- // 忽略删除临时文件错误
- }
- }
- res.writeHead(200, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({
- success: true,
- message: '注册成功',
- user: {
- id: newUser.id,
- username: newUser.username,
- phone: newUser.phone,
- avatar: newUser.avatar
- }
- }));
- } catch (error) {
- console.error('[Register] 注册处理错误:', error);
- res.writeHead(500, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, message: '服务器错误' }));
- }
- }
- module.exports = {
- handleRegisterRequest
- };
|