1
This commit is contained in:
@@ -378,8 +378,8 @@
|
||||
<script>
|
||||
// 配置 API 地址(根据环境自动判断)
|
||||
const API_BASE_URL = window.location.hostname === 'localhost'
|
||||
? 'http://localhost:9097/admin_api'
|
||||
: 'https://work.light120.com/admin_api';
|
||||
? 'http://localhost:9097/api'
|
||||
: 'https://work.light120.com/api';
|
||||
|
||||
// 获取 URL 参数中的邀请码
|
||||
function getInviteCodeFromUrl() {
|
||||
@@ -423,7 +423,7 @@
|
||||
// 发送邮箱验证码
|
||||
async function sendEmailCode(email) {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/invite/send-email-code`, {
|
||||
const response = await fetch(`${API_BASE_URL}/invite/send_email_code`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -1,413 +0,0 @@
|
||||
/**
|
||||
* 邀请注册管理控制器(后台管理)
|
||||
* 提供邀请注册相关的接口,不需要登录验证
|
||||
*/
|
||||
|
||||
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 || '验证失败'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
*/
|
||||
|
||||
const Framework = require("../../framework/node-core-framework.js");
|
||||
const dayjs = require('dayjs');
|
||||
const email_service = require('../services/email_service.js');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
@@ -115,7 +117,7 @@ module.exports = {
|
||||
|
||||
// 查询邀请统计
|
||||
const { invite_record } = await Framework.getModels();
|
||||
|
||||
|
||||
// 查询总邀请数
|
||||
const totalInvites = await invite_record.count({
|
||||
where: {
|
||||
@@ -294,6 +296,406 @@ module.exports = {
|
||||
console.error('获取邀请记录列表失败:', error);
|
||||
return ctx.fail('获取邀请记录列表失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /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
|
||||
* /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) => {
|
||||
|
||||
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分钟过期
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
* @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 {
|
||||
// 获取框架的 Redis 服务
|
||||
const redis_service = Framework.getServices().redisService;
|
||||
|
||||
// 统一邮箱地址为小写,避免大小写不一致导致的问题
|
||||
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 || '验证失败'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
// 白名单URL - 不需要token验证的接口
|
||||
"allowUrls": ["/admin_api/sys_user/login", "/admin_api/sys_user/authorityMenus", "/admin_api/sys_user/register", "/api/user/loginByWeixin", "/file/", "/sys_file/", "/admin_api/win_data/viewLogInfo", "/api/user/wx_auth", '/api/docs', 'api/swagger.json', 'payment/notify', 'payment/refund-notify', 'wallet/transfer_notify', 'user/sms/send', 'user/sms/verify', '/api/version/check','/api/file/upload_file_to_oss_by_auto_work','/api/version/create', '/admin_api/invite/register', '/admin_api/invite/send-email-code'],
|
||||
"allowUrls": ["/admin_api/sys_user/login", "/admin_api/sys_user/authorityMenus", "/admin_api/sys_user/register", "/api/user/loginByWeixin", "/file/", "/sys_file/", "/admin_api/win_data/viewLogInfo", "/api/user/wx_auth", '/api/docs', 'api/swagger.json', 'payment/notify', 'payment/refund-notify', 'wallet/transfer_notify', 'user/sms/send', 'user/sms/verify', '/api/version/check','/api/file/upload_file_to_oss_by_auto_work','/api/version/create', 'register', 'send_email_code'],
|
||||
|
||||
|
||||
// AI服务配置
|
||||
|
||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "frameapi",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "frameapi",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@koa/cors": "^5.0.0",
|
||||
@@ -32,6 +32,7 @@
|
||||
"mysql2": "^1.7.0",
|
||||
"node-schedule": "latest",
|
||||
"node-uuid": "^1.4.8",
|
||||
"nodemailer": "^6.9.7",
|
||||
"redis": "^5.8.3",
|
||||
"sequelize": "^5.22.5",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
@@ -4837,6 +4838,15 @@
|
||||
"uuid": "bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemailer": {
|
||||
"version": "6.10.1",
|
||||
"resolved": "https://registry.npmmirror.com/nodemailer/-/nodemailer-6.10.1.tgz",
|
||||
"integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==",
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon": {
|
||||
"version": "2.0.22",
|
||||
"resolved": "https://registry.npmmirror.com/nodemon/-/nodemon-2.0.22.tgz",
|
||||
|
||||
Reference in New Issue
Block a user