Files
autoAiWorkSys/api/controller_front/invite.js
张成 60f1c5e628 11
2025-12-20 00:07:32 +08:00

557 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 推广邀请管理控制器(客户端接口)
* 提供客户端调用的推广邀请相关接口
*/
const Framework = require("../../framework/node-core-framework.js");
const dayjs = require('dayjs');
const email_service = require('../services/email_service.js');
module.exports = {
/**
* @swagger
* /api/invite/info:
* post:
* summary: 获取邀请信息
* description: 根据设备SN码获取邀请码和邀请链接
* tags: [前端-推广邀请]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - sn_code
* properties:
* sn_code:
* type: string
* description: 设备SN码
* responses:
* 200:
* description: 获取成功
*/
'POST /invite/info': async (ctx) => {
try {
// 从body中获取sn_code
const body = ctx.getBody();
const { sn_code } = body;
if (!sn_code) {
return ctx.fail('请提供设备SN码');
}
const { pla_account } = await Framework.getModels();
// 根据sn_code查找用户
const user = await pla_account.findOne({
where: { sn_code }
});
if (!user) {
return ctx.fail('用户不存在');
}
// 生成邀请码基于用户ID和sn_code
const invite_code = `INV${user.id}_${Date.now().toString(36).toUpperCase()}`;
const invite_link = `https://work.light120.com/register.html?code=${invite_code}`;
// 保存邀请码到用户记录可以保存到invite_code字段如果没有则保存到备注或其他字段
// 这里暂时不保存到数据库,每次生成新的
return ctx.success({
invite_code,
invite_link,
user_id: user.id,
sn_code: user.sn_code
});
} catch (error) {
console.error('获取邀请信息失败:', error);
return ctx.fail('获取邀请信息失败: ' + error.message);
}
},
/**
* @swagger
* /api/invite/statistics:
* post:
* summary: 获取邀请统计
* description: 根据设备SN码获取邀请统计数据
* tags: [前端-推广邀请]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - sn_code
* properties:
* sn_code:
* type: string
* description: 设备SN码
* responses:
* 200:
* description: 获取成功
*/
'POST /invite/statistics': async (ctx) => {
try {
// 从body中获取sn_code
const body = ctx.getBody() || {};
const { sn_code } = body;
if (!sn_code) {
return ctx.fail('请提供设备SN码');
}
const { pla_account } = await Framework.getModels();
// 根据sn_code查找用户
const user = await pla_account.findOne({
where: { sn_code }
});
if (!user) {
return ctx.fail('用户不存在');
}
// 查询邀请统计
const { invite_record } = await Framework.getModels();
// 查询总邀请数
const totalInvites = await invite_record.count({
where: {
inviter_id: user.id,
is_delete: 0
}
});
// 查询已发放奖励的邀请数(活跃邀请)
const activeInvites = await invite_record.count({
where: {
inviter_id: user.id,
reward_status: 1,
is_delete: 0
}
});
// 查询总奖励天数
const rewardRecords = await invite_record.findAll({
where: {
inviter_id: user.id,
reward_status: 1,
is_delete: 0
},
attributes: ['reward_value']
});
const totalRewardDays = rewardRecords.reduce((sum, record) => {
return sum + (record.reward_value || 0);
}, 0);
return ctx.success({
totalInvites: totalInvites || 0
});
} catch (error) {
console.error('获取邀请统计失败:', error);
return ctx.fail('获取邀请统计失败: ' + error.message);
}
},
/**
* @swagger
* /api/invite/generate:
* post:
* summary: 生成邀请码
* description: 为用户生成新的邀请码
* tags: [前端-推广邀请]
* responses:
* 200:
* description: 生成成功
*/
'POST /invite/generate': async (ctx) => {
try {
// 从body中获取sn_code
const body = ctx.getBody() || {};
const { sn_code } = body;
if (!sn_code) {
return ctx.fail('请提供设备SN码');
}
const { pla_account } = await Framework.getModels();
// 根据sn_code查找用户
const user = await pla_account.findOne({
where: { sn_code }
});
if (!user) {
return ctx.fail('用户不存在');
}
// 生成新的邀请码
const invite_code = `INV${user.id}_${Date.now().toString(36).toUpperCase()}`;
const invite_link = `https://work.light120.com/register.html?code=${invite_code}`;
return ctx.success({
invite_code,
invite_link
});
} catch (error) {
console.error('生成邀请码失败:', error);
return ctx.fail('生成邀请码失败: ' + error.message);
}
},
/**
* @swagger
* /api/invite/records:
* post:
* summary: 获取邀请记录列表
* description: 根据设备SN码获取邀请记录列表
* tags: [前端-推广邀请]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - sn_code
* properties:
* sn_code:
* type: string
* description: 设备SN码
* page:
* type: integer
* description: 页码可选默认1
* pageSize:
* type: integer
* description: 每页数量可选默认20
* responses:
* 200:
* description: 获取成功
*/
'POST /invite/records': async (ctx) => {
try {
const body = ctx.getBody() || {};
const { sn_code, page = 1, pageSize = 20 } = body;
if (!sn_code) {
return ctx.fail('请提供设备SN码');
}
const { pla_account, invite_record } = await Framework.getModels();
// 根据sn_code查找用户
const user = await pla_account.findOne({
where: { sn_code }
});
if (!user) {
return ctx.fail('用户不存在');
}
// 查询邀请记录列表
const offset = (page - 1) * pageSize;
const records = await invite_record.findAll({
where: {
inviter_id: user.id,
is_delete: 0
},
order: [['register_time', 'DESC']],
limit: pageSize,
offset: offset
});
const total = await invite_record.count({
where: {
inviter_id: user.id,
is_delete: 0
}
});
// 格式化记录数据
const formattedRecords = records.map(record => {
const recordData = record.toJSON();
return {
id: recordData.id,
invitee_phone: recordData.invitee_phone,
invite_code: recordData.invite_code,
register_time: recordData.register_time,
reward_status: recordData.reward_status,
reward_value: recordData.reward_value,
reward_type: recordData.reward_type
};
});
return ctx.success({
list: formattedRecords,
total: total,
page: page,
pageSize: pageSize
});
} catch (error) {
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;
const { hashPassword, maskEmail } = require('../utils/crypto_utils');
// 验证必填参数
if (!email || !password || !email_code) {
return ctx.fail('邮箱、密码和验证码不能为空');
}
// 验证邮箱格式
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return ctx.fail('邮箱格式不正确');
}
// 验证密码长度
if (password.length < 6 || password.length > 50) {
return ctx.fail('密码长度必须在6-50位之间');
}
// 统一邮箱地址为小写
const email_normalized = email.toLowerCase().trim();
// 验证验证码
const emailVerifyResult = await email_service.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 hashedPassword = await hashPassword(password);
// 创建新用户(使用统一的小写邮箱和加密密码)
// 新用户注册赠送3天试用期
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: hashedPassword, // 使用加密后的密码
keyword: '',
is_enabled: 1,
is_delete: 0,
authorization_date: new Date(), // 新用户从注册日期开始
authorization_days: 3 // 赠送3天试用期
});
// 给邀请人增加3天试用期
if (inviter) {
const inviterData = inviter.toJSON();
const currentAuthDate = inviterData.authorization_date;
const currentAuthDays = inviterData.authorization_days || 0;
let newAuthDate;
let newAuthDays;
// 如果当前没有授权日期,则从今天开始
if (!currentAuthDate) {
newAuthDate = new Date();
newAuthDays = currentAuthDays + 3; // 累加3天
} else {
// 检查当前授权是否已过期
const currentEndDate = dayjs(currentAuthDate).add(currentAuthDays, 'day');
const now = dayjs();
if (currentEndDate.isBefore(now)) {
// 授权已过期重新激活并给予3天试用期
newAuthDate = new Date();
newAuthDays = 3; // 过期后重新激活给3天
} else {
// 授权未过期在现有基础上累加3天
newAuthDate = currentAuthDate;
newAuthDays = currentAuthDays + 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(`[邀请注册] 用户 ${maskEmail(email_normalized)} 通过邀请码 ${invite_code} 注册成功,邀请人 ${inviter.sn_code} 获得3天试用期`);
} else {
console.log(`[邀请注册] 用户 ${maskEmail(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) => {
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 email_service.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);
}
}
};