Files
autoAiWorkSys/api/controller_front/user.js
张成 aa2d03ee30 1
2025-12-21 19:30:51 +08:00

425 lines
14 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");
module.exports = {
/**
* @swagger
* /api/user/login:
* post:
* summary: 用户登录
* description: 通过邮箱和密码登录返回token、device_id和用户信息
* tags: [前端-用户管理]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - email
* - password
* properties:
* email:
* type: string
* description: 邮箱(登录名)
* example: 'user@example.com'
* password:
* type: string
* description: 密码
* example: 'password123'
* device_id:
* type: string
* description: 设备ID客户端生成可选如果提供则使用提供的否则使用数据库中的
* example: 'device_1234567890abcdef'
* responses:
* 200:
* description: 登录成功
* content:
* application/json:
* schema:
* type: object
* properties:
* code:
* type: integer
* description: 状态码0表示成功
* example: 0
* message:
* type: string
* description: 响应消息
* example: 'success'
* data:
* type: object
* properties:
* token:
* type: string
* description: 认证token
* example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
* device_id:
* type: string
* description: 设备ID
* example: 'device_123456'
* user:
* type: object
* description: 用户信息
* 400:
* description: 参数错误或用户不存在
* content:
* application/json:
* schema:
* type: object
* properties:
* code:
* type: integer
* example: 400
* message:
* type: string
* example: '用户不存在或密码错误'
*/
"POST /user/login": async (ctx) => {
const { login_name:email, password, device_id: client_device_id } = ctx.getBody();
const dayjs = require('dayjs');
const { verifyPassword, validateDeviceId, maskEmail } = require('../utils/crypto_utils');
// 参数验证
if (!email || !password) {
return ctx.fail('邮箱和密码不能为空');
}
// 统一邮箱地址为小写
const email_normalized = email.toLowerCase().trim();
// 验证邮箱格式
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email_normalized)) {
return ctx.fail('邮箱格式不正确');
}
// 验证密码长度
if (password.length < 6 || password.length > 50) {
return ctx.fail('密码长度不正确');
}
const { pla_account } = await Framework.getModels();
try {
// 根据邮箱查找用户(使用统一的小写邮箱)
const user = await pla_account.findOne({
where: {
login_name: email_normalized
}
});
if (!user) {
// 防止用户枚举攻击:统一返回相同的错误信息
return ctx.fail('邮箱或密码错误');
}
// 验证密码(支持新旧格式)
let isPasswordValid = false;
if (user.pwd && user.pwd.includes('$')) {
// 新格式:加密密码
isPasswordValid = await verifyPassword(password, user.pwd);
} else {
// 旧格式:明文密码(临时兼容,建议尽快迁移)
isPasswordValid = (user.pwd === password);
// 如果验证通过,自动升级为加密密码
if (isPasswordValid) {
const { hashPassword } = require('../utils/crypto_utils');
const hashedPassword = await hashPassword(password);
await pla_account.update(
{ pwd: hashedPassword },
{ where: { id: user.id } }
);
console.log(`[安全升级] 用户 ${maskEmail(email_normalized)} 的密码已自动加密`);
}
}
if (!isPasswordValid) {
// 记录失败尝试(用于后续添加防暴力破解功能)
console.warn(`[登录失败] 邮箱: ${maskEmail(email_normalized)}, 时间: ${new Date().toISOString()}`);
return ctx.fail('邮箱或密码错误');
}
// 检查账号是否启用
if (!user.is_enabled) {
return ctx.fail('账号已被禁用,请联系管理员');
}
// 检查授权状态(仅记录,不拒绝登录)
const userData = user.toJSON();
const authDate = userData.authorization_date;
const authDays = userData.authorization_days || 0;
// 使用工具函数计算剩余天数
const { calculateRemainingDays } = require('../utils/account_utils');
const remaining_days = calculateRemainingDays(authDate, authDays);
// 处理设备ID
let device_id = client_device_id;
// 验证客户端提供的 device_id 格式
if (client_device_id) {
// if (!validateDeviceId(client_device_id)) {
// return ctx.fail('设备ID格式不正确请重新登录');
// }
// 如果与数据库不同需要额外验证防止设备ID劫持
if (user.device_id && client_device_id !== user.device_id) {
// 记录设备更换
console.warn(`[设备更换] 用户 ${maskEmail(email_normalized)} 更换设备: ${user.device_id} -> ${client_device_id}`);
// TODO: 这里可以添加更多安全检查,如:
// - 发送验证码确认
// - 记录到安全日志
// - 限制更换频率
}
// 更新设备ID
if (client_device_id !== user.device_id) {
await pla_account.update(
{ device_id: client_device_id },
{ where: { id: user.id } }
);
}
} else {
// 使用数据库中的设备ID
device_id = user.device_id;
}
// 如果仍然没有设备ID返回错误
if (!device_id) {
return ctx.fail('设备ID不能为空请重新登录');
}
// 创建token
const token = Framework.getServices().tokenService.create({
sn_code: user.sn_code,
device_id: device_id,
id: user.id
});
// 构建安全的用户信息响应(白名单模式)
const safeUserInfo = {
id: user.id,
sn_code: user.sn_code,
login_name: maskEmail(user.login_name), // 脱敏处理
is_enabled: user.is_enabled,
authorization_date: user.authorization_date,
authorization_days: user.authorization_days,
platform_type: user.platform_type,
remaining_days: remaining_days,
auto_deliver: user.auto_deliver,
deliver_config: user.deliver_config,
created_at: user.create_time,
updated_at: user.last_modify_time
};
// 记录成功登录
console.log(`[登录成功] 用户 ${maskEmail(email_normalized)}, 剩余天数: ${remaining_days}`);
return ctx.success({
token,
device_id,
user: safeUserInfo
});
} catch (error) {
// 记录详细错误但不暴露给客户端
console.error('[登录异常]', {
error: error.message,
stack: error.stack,
email: maskEmail(email_normalized)
});
return ctx.fail('登录失败,请稍后重试');
}
},
/**
* @swagger
* /api/user/delivery-config/get:
* post:
* summary: 获取投递配置
* description: 根据设备SN码获取用户的投递配置返回 pla_account 表中的 deliver_config 对象
* tags: [前端-用户管理]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - sn_code
* properties:
* sn_code:
* type: string
* description: 设备SN码
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* code:
* type: integer
* example: 0
* message:
* type: string
* example: 'success'
* data:
* type: object
* properties:
* deliver_config:
* type: object
* description: 自动投递配置对象,如果不存在则返回 null
* nullable: true
*/
'POST /user/delivery-config/get': async (ctx) => {
try {
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('用户不存在');
}
// 从 pla_account 表的 deliver_config 字段获取配置
const deliver_config = user.deliver_config || null;
return ctx.success({ deliver_config });
} catch (error) {
console.error('[获取投递配置失败]', {
error: error.message,
timestamp: new Date().toISOString()
});
return ctx.fail('获取投递配置失败');
}
},
/**
* @swagger
* /api/user/delivery-config/save:
* post:
* summary: 保存投递配置
* description: 根据设备SN码保存用户的投递配置到 pla_account 表的 deliver_config 字段
* tags: [前端-用户管理]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - sn_code
* - deliver_config
* properties:
* sn_code:
* type: string
* description: 设备SN码
* deliver_config:
* type: object
* description: 自动投递配置对象
* properties:
* auto_delivery:
* type: boolean
* description: 是否启用自动投递
* interval:
* type: integer
* description: 投递间隔(分钟)
* min_salary:
* type: integer
* description: 最低薪资
* max_salary:
* type: integer
* description: 最高薪资
* scroll_pages:
* type: integer
* description: 滚动页数
* max_per_batch:
* type: integer
* description: 每批最多投递数
* filter_keywords:
* type: string
* description: 过滤关键词
* exclude_keywords:
* type: string
* description: 排除关键词
* start_time:
* type: string
* description: 开始时间格式HH:mm
* end_time:
* type: string
* description: 结束时间格式HH:mm
* workdays_only:
* type: boolean
* description: 仅工作日
* responses:
* 200:
* description: 保存成功
*/
'POST /user/delivery-config/save': async (ctx) => {
try {
const body = ctx.getBody();
const { sn_code, deliver_config } = body;
if (!sn_code) {
return ctx.fail('请提供设备SN码');
}
if (!deliver_config) {
return ctx.fail('请提供 deliver_config 配置对象');
}
// 验证 deliver_config 的基本结构
if (typeof deliver_config !== 'object') {
return ctx.fail('deliver_config 必须是对象');
}
const { pla_account } = await Framework.getModels();
// 根据 sn_code 查找账号
const user = await pla_account.findOne({
where: { sn_code }
});
if (!user) {
return ctx.fail('用户不存在');
}
// 更新 pla_account 表的 deliver_config 和 auto_deliver 字段
await pla_account.update(
{
deliver_config: deliver_config,
auto_deliver: deliver_config.auto_delivery ? 1 : 0
},
{ where: { id: user.id } }
);
console.log('[保存投递配置成功]', {
sn_code,
auto_delivery: deliver_config.auto_delivery,
timestamp: new Date().toISOString()
});
return ctx.success({ message: '配置保存成功' });
} catch (error) {
console.error('[保存投递配置失败]', {
error: error.message,
timestamp: new Date().toISOString()
});
return ctx.fail('保存投递配置失败');
}
}
}