/** * 邀请注册管理控制器(后台管理) * 提供邀请注册相关的接口,不需要登录验证 */ const Framework = require("../../framework/node-core-framework.js"); const dayjs = require('dayjs'); const email_service = require('../services/email_service.js'); module.exports = { /** * @swagger * /admin_api/invite/register: * post: * summary: 邀请注册 * description: 通过邀请码注册新用户,注册成功后给邀请人增加3天试用期 * tags: [后台-邀请注册] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - email * - password * - email_code * properties: * email: * type: string * description: 邮箱地址 * example: 'user@example.com' * password: * type: string * description: 密码 * example: 'password123' * email_code: * type: string * description: 验证码 * example: '123456' * invite_code: * type: string * description: 邀请码(选填) * example: 'INV123_ABC123' * responses: * 200: * description: 注册成功 */ 'POST /invite/register': async (ctx) => { try { const body = ctx.getBody(); const { email, password, email_code, invite_code } = body; // 验证必填参数 if (!email || !password || !email_code) { return ctx.fail('邮箱、密码和验证码不能为空'); } // 验证邮箱格式 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { return ctx.fail('邮箱格式不正确'); } // 验证密码长度 if (password.length < 6) { return ctx.fail('密码长度不能少于6位'); } // 统一邮箱地址为小写 const email_normalized = email.toLowerCase().trim(); // 验证验证码 const emailVerifyResult = await verifyEmailCode(email_normalized, email_code); if (!emailVerifyResult.success) { return ctx.fail(emailVerifyResult.message || '验证码错误或已过期'); } const { pla_account } = await Framework.getModels(); // 检查邮箱是否已注册(使用统一的小写邮箱) const existingUser = await pla_account.findOne({ where: { login_name: email_normalized } }); if (existingUser) { return ctx.fail('该邮箱已被注册'); } // 验证邀请码(如果提供了邀请码) let inviter = null; let inviter_id = null; if (invite_code) { // 解析邀请码,获取邀请人ID // 邀请码格式:INV{user_id}_{timestamp} const inviteMatch = invite_code.match(/^INV(\d+)_/); if (!inviteMatch) { return ctx.fail('邀请码格式不正确'); } inviter_id = parseInt(inviteMatch[1]); // 验证邀请人是否存在 inviter = await pla_account.findOne({ where: { id: inviter_id } }); if (!inviter) { return ctx.fail('邀请码无效,邀请人不存在'); } } // 生成设备SN码(基于邮箱和时间戳) const sn_code = `SN${Date.now()}${Math.random().toString(36).substr(2, 6).toUpperCase()}`; // 创建新用户(使用统一的小写邮箱) const newUser = await pla_account.create({ name: email_normalized.split('@')[0], // 默认使用邮箱用户名作为名称 sn_code: sn_code, device_id: '', // 设备ID由客户端登录时提供 platform_type: 'boss', // 默认平台类型 login_name: email_normalized, pwd: password, keyword: '', is_enabled: 1, is_delete: 0, authorization_date: null, authorization_days: 0 }); // 给邀请人增加3天试用期 if (inviter) { const inviterData = inviter.toJSON(); const currentAuthDate = inviterData.authorization_date; const currentAuthDays = inviterData.authorization_days || 0; let newAuthDate = currentAuthDate; let newAuthDays = currentAuthDays + 3; // 增加3天 // 如果当前没有授权日期,则从今天开始 if (!currentAuthDate) { newAuthDate = new Date(); } else { // 如果当前授权已过期,从今天开始计算 const currentEndDate = dayjs(currentAuthDate).add(currentAuthDays, 'day'); const now = dayjs(); if (currentEndDate.isBefore(now)) { newAuthDate = new Date(); newAuthDays = 3; // 重新设置为3天 } } // 更新邀请人的授权信息 await pla_account.update( { authorization_date: newAuthDate, authorization_days: newAuthDays }, { where: { id: inviter_id } } ); // 记录邀请记录 const { invite_record } = await Framework.getModels(); await invite_record.create({ inviter_id: inviter_id, inviter_sn_code: inviter.sn_code, invitee_id: newUser.id, invitee_sn_code: newUser.sn_code, invitee_phone: email, // 使用邮箱代替手机号 invite_code: invite_code, register_time: new Date(), reward_status: 1, // 已发放 reward_type: 'trial_days', reward_value: 3, is_delete: 0 }); console.log(`[邀请注册] 用户 ${email_normalized} 通过邀请码 ${invite_code} 注册成功,邀请人 ${inviter.sn_code} 获得3天试用期`); } else { console.log(`[邀请注册] 用户 ${email_normalized} 注册成功(无邀请码)`); } return ctx.success({ message: '注册成功', user: { id: newUser.id, sn_code: newUser.sn_code, login_name: newUser.login_name } }); } catch (error) { console.error('邀请注册失败:', error); return ctx.fail('注册失败: ' + error.message); } }, /** * @swagger * /admin_api/invite/send-email-code: * post: * summary: 发送验证码 * description: 发送验证码到指定邮箱地址 * tags: [后台-邀请注册] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - email * properties: * email: * type: string * description: 邮箱地址 * example: 'user@example.com' * responses: * 200: * description: 发送成功 */ 'POST /invite/send-email-code': async (ctx) => { try { const body = ctx.getBody(); const { email } = body; if (!email) { return ctx.fail('邮箱地址不能为空'); } // 验证邮箱格式 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { return ctx.fail('邮箱格式不正确'); } // 统一邮箱地址为小写 const email_normalized = email.toLowerCase().trim(); // 发送验证码 const emailResult = await sendEmailCode(email_normalized); if (!emailResult.success) { return ctx.fail(emailResult.message || '发送验证码失败'); } return ctx.success({ message: '验证码已发送', expire_time: emailResult.expire_time || 300 // 默认5分钟过期 }); } catch (error) { console.error('发送验证码失败:', error); return ctx.fail('发送验证码失败: ' + error.message); } } }; /** * 发送验证码 * @param {string} email 邮箱地址 * @returns {Promise<{success: boolean, message?: string, expire_time?: number}>} */ async function sendEmailCode(email) { try { // 获取框架的 Redis 服务 const redis_service = Framework.getServices().redisService; // 统一邮箱地址为小写,避免大小写不一致导致的问题 const email_lower = email.toLowerCase().trim(); // 生成6位随机验证码 const code = Math.floor(100000 + Math.random() * 900000).toString(); // Redis key const redis_key = `email_code:${email_lower}`; // 验证码数据 const code_data = { code: code, created_at: Date.now() }; // 存储到 Redis,设置 5 分钟过期时间(300秒) try { await redis_service.set(redis_key, JSON.stringify(code_data), 300); } catch (redis_error) { console.error(`[邮箱验证] Redis 存储失败: ${email_lower}`, redis_error); return { success: false, message: '验证码存储失败,请稍后重试' }; } console.log(`[邮箱验证] 生成验证码: ${email_lower} -> ${code}, 已存储到 Redis (5分钟过期)`); // 调用邮件服务发送验证码 const email_result = await email_service.send_verification_code(email_lower, code); if (!email_result.success) { // 如果邮件发送失败,删除已生成的验证码 try { await redis_service.del(redis_key); } catch (del_error) { console.error(`[邮箱验证] 删除验证码失败:`, del_error); } console.error(`[邮箱验证] 邮件发送失败,已删除验证码: ${email_lower}`); return { success: false, message: email_result.message || '发送验证码失败' }; } console.log(`[邮箱验证] 验证码已发送到 ${email_lower}: ${code} (5分钟内有效)`); return { success: true, expire_time: 300 }; } catch (error) { console.error('发送验证码失败:', error); return { success: false, message: error.message || '发送验证码失败' }; } } /** * 验证验证码 * @param {string} email 邮箱地址 * @param {string} code 验证码 * @returns {Promise<{success: boolean, message?: string}>} */ async function verifyEmailCode(email, code) { try { // 统一邮箱地址为小写,避免大小写不一致导致的问题 const email_lower = email.toLowerCase().trim(); console.log(`[邮箱验证] 开始验证: ${email_lower}, 验证码: ${code}`); // Redis key const redis_key = `email_code:${email_lower}`; // 从 Redis 获取验证码 let cached_str; try { cached_str = await redis_service.get(redis_key); } catch (redis_error) { console.error(`[邮箱验证] Redis 获取失败:`, redis_error); return { success: false, message: '验证码获取失败,请稍后重试' }; } if (!cached_str) { console.log(`[邮箱验证] 未找到该邮箱的验证码: ${email_lower}`); return { success: false, message: '验证码不存在或已过期,请重新获取' }; } // 解析验证码数据 let cached; try { cached = JSON.parse(cached_str); } catch (parse_error) { console.error(`[邮箱验证] 解析验证码数据失败:`, parse_error); try { await redis_service.del(redis_key); } catch (del_error) { console.error(`[邮箱验证] 删除异常数据失败:`, del_error); } return { success: false, message: '验证码数据异常,请重新获取' }; } console.log(`[邮箱验证] 找到验证码,创建时间: ${new Date(cached.created_at).toLocaleString()}`); // 验证码是否正确 if (cached.code !== code) { console.log(`[邮箱验证] 验证码错误,期望: ${cached.code}, 实际: ${code}`); return { success: false, message: '验证码错误' }; } // 验证成功后删除缓存 try { await redis_service.del(redis_key); } catch (del_error) { console.error(`[邮箱验证] 删除验证码失败:`, del_error); } console.log(`[邮箱验证] 验证成功: ${email_lower}`); return { success: true }; } catch (error) { console.error('验证验证码失败:', error); return { success: false, message: error.message || '验证失败' }; } }