This commit is contained in:
张成
2025-12-16 06:00:04 +08:00
parent a262fb7ff7
commit 119568f961
5 changed files with 647 additions and 11 deletions

View File

@@ -0,0 +1,211 @@
/**
* 投递记录管理控制器(客户端接口)
* 提供客户端调用的投递记录相关接口
*/
const Framework = require("../../framework/node-core-framework.js");
module.exports = {
/**
* @swagger
* /api/apply/list:
* post:
* summary: 获取投递记录列表
* description: 根据设备SN码分页获取投递记录
* tags: [前端-投递管理]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* sn_code:
* type: string
* description: 设备SN码
* seachOption:
* type: object
* description: 搜索条件
* pageOption:
* type: object
* description: 分页选项
* responses:
* 200:
* description: 获取成功
*/
'POST /apply/list': async (ctx) => {
try {
const models = Framework.getModels();
const { apply_records, op } = models;
const body = ctx.getBody();
// 从query或body中获取sn_code优先从query获取
const sn_code = ctx.query?.sn_code || body.sn_code;
if (!sn_code) {
return ctx.fail('请提供设备SN码');
}
const seachOption = body.seachOption || {};
const pageOption = body.pageOption || {};
// 获取分页参数
const page = pageOption.page || 1;
const pageSize = pageOption.pageSize || 20;
const limit = pageSize;
const offset = (page - 1) * pageSize;
const where = { sn_code };
// 平台筛选
if (seachOption.platform) {
where.platform = seachOption.platform;
}
// 投递状态筛选
if (seachOption.applyStatus) {
where.applyStatus = seachOption.applyStatus;
}
// 反馈状态筛选
if (seachOption.feedbackStatus) {
where.feedbackStatus = seachOption.feedbackStatus;
}
// 搜索:岗位名称、公司名称
if (seachOption.key && seachOption.value) {
const key = seachOption.key;
const value = seachOption.value;
if (key === 'jobTitle') {
where.jobTitle = { [op.like]: `%${value}%` };
} else if (key === 'companyName') {
where.companyName = { [op.like]: `%${value}%` };
} else {
// 默认搜索岗位名称或公司名称
where[op.or] = [
{ jobTitle: { [op.like]: `%${value}%` } },
{ companyName: { [op.like]: `%${value}%` } }
];
}
}
const result = await apply_records.findAndCountAll({
where,
limit,
offset,
order: [['applyTime', 'DESC']]
});
return ctx.success({
count: result.count,
total: result.count,
rows: result.rows,
list: result.rows
});
} catch (error) {
console.error('获取投递记录列表失败:', error);
return ctx.fail('获取投递记录列表失败: ' + error.message);
}
},
/**
* @swagger
* /api/apply/statistics:
* get:
* summary: 获取投递统计
* description: 根据设备SN码获取投递统计数据
* tags: [前端-投递管理]
* parameters:
* - in: query
* name: sn_code
* required: true
* schema:
* type: string
* description: 设备SN码
* responses:
* 200:
* description: 获取成功
*/
'GET /apply/statistics': async (ctx) => {
try {
const models = Framework.getModels();
const { apply_records } = models;
const { sn_code } = ctx.query;
const final_sn_code = sn_code;
if (!final_sn_code) {
return ctx.fail('请提供设备SN码');
}
const [
totalCount,
successCount,
failedCount,
pendingCount,
interviewCount
] = await Promise.all([
apply_records.count({ where: { sn_code: final_sn_code } }),
apply_records.count({ where: { sn_code: final_sn_code, applyStatus: 'success' } }),
apply_records.count({ where: { sn_code: final_sn_code, applyStatus: 'failed' } }),
apply_records.count({ where: { sn_code: final_sn_code, applyStatus: 'pending' } }),
apply_records.count({ where: { sn_code: final_sn_code, feedbackStatus: 'interview' } })
]);
return ctx.success({
totalCount,
successCount,
failedCount,
pendingCount,
interviewCount,
successRate: totalCount > 0 ? ((successCount / totalCount) * 100).toFixed(2) : 0,
interviewRate: totalCount > 0 ? ((interviewCount / totalCount) * 100).toFixed(2) : 0
});
} catch (error) {
console.error('获取投递统计失败:', error);
return ctx.fail('获取投递统计失败: ' + error.message);
}
},
/**
* @swagger
* /api/apply/detail:
* get:
* summary: 获取投递记录详情
* description: 根据投递ID获取详细信息
* tags: [前端-投递管理]
* parameters:
* - in: query
* name: applyId
* required: true
* schema:
* type: string
* description: 投递记录ID
* responses:
* 200:
* description: 获取成功
*/
'GET /apply/detail': async (ctx) => {
try {
const models = Framework.getModels();
const { apply_records } = models;
const { applyId } = ctx.query;
if (!applyId) {
return ctx.fail('投递记录ID不能为空');
}
const record = await apply_records.findOne({ where: { applyId } });
if (!record) {
return ctx.fail('投递记录不存在');
}
return ctx.success(record);
} catch (error) {
console.error('获取投递记录详情失败:', error);
return ctx.fail('获取投递记录详情失败: ' + error.message);
}
}
};

View File

@@ -0,0 +1,159 @@
/**
* 意见反馈管理控制器(客户端接口)
* 提供客户端调用的意见反馈相关接口
*/
const Framework = require("../../framework/node-core-framework.js");
module.exports = {
/**
* @swagger
* /api/feedback/submit:
* post:
* summary: 提交反馈
* description: 提交用户反馈
* tags: [前端-意见反馈]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - type
* - content
* properties:
* type:
* type: string
* description: 反馈类型
* content:
* type: string
* description: 反馈内容
* contact:
* type: string
* description: 联系方式(可选)
* responses:
* 200:
* description: 提交成功
*/
'POST /feedback/submit': async (ctx) => {
try {
// 从body中获取user_id和sn_code实际应该从token中解析
const body = ctx.getBody();
const user_id = body.user_id;
const sn_code = body.sn_code;
if (!user_id) {
return ctx.fail('请先登录');
}
const { type, content, contact } = body;
if (!type || !content) {
return ctx.fail('反馈类型和内容不能为空');
}
// 这里可以将反馈保存到数据库,暂时只返回成功
// 如果需要保存,可以创建一个 feedback 表
return ctx.success({
message: '反馈提交成功,感谢您的反馈!',
feedbackId: `FB${Date.now()}`
});
} catch (error) {
console.error('提交反馈失败:', error);
return ctx.fail('提交反馈失败: ' + error.message);
}
},
/**
* @swagger
* /api/feedback/list:
* post:
* summary: 获取反馈列表
* description: 根据用户ID获取反馈列表
* tags: [前端-意见反馈]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* page:
* type: integer
* description: 页码
* pageSize:
* type: integer
* description: 每页数量
* responses:
* 200:
* description: 获取成功
*/
'POST /feedback/list': async (ctx) => {
try {
// 从body中获取user_id实际应该从token中解析
const body = ctx.getBody();
const user_id = body.user_id;
if (!user_id) {
return ctx.fail('请先登录');
}
const page = body.page || 1;
const pageSize = body.pageSize || 20;
// 这里可以从数据库查询反馈列表,暂时返回空列表
return ctx.success({
count: 0,
total: 0,
rows: [],
list: []
});
} catch (error) {
console.error('获取反馈列表失败:', error);
return ctx.fail('获取反馈列表失败: ' + error.message);
}
},
/**
* @swagger
* /api/feedback/detail:
* get:
* summary: 获取反馈详情
* description: 根据反馈ID获取详细信息
* tags: [前端-意见反馈]
* parameters:
* - in: query
* name: feedbackId
* required: true
* schema:
* type: string
* description: 反馈ID
* responses:
* 200:
* description: 获取成功
*/
'GET /feedback/detail': async (ctx) => {
try {
// 从body中获取user_id实际应该从token中解析
const body = ctx.getBody() || {};
const user_id = body.user_id;
if (!user_id) {
return ctx.fail('请先登录');
}
const { feedbackId } = ctx.query;
if (!feedbackId) {
return ctx.fail('反馈ID不能为空');
}
// 这里可以从数据库查询反馈详情,暂时返回空
return ctx.fail('反馈记录不存在');
} catch (error) {
console.error('获取反馈详情失败:', error);
return ctx.fail('获取反馈详情失败: ' + error.message);
}
}
};

View File

@@ -0,0 +1,114 @@
/**
* 推广邀请管理控制器(客户端接口)
* 提供客户端调用的推广邀请相关接口
*/
const Framework = require("../../framework/node-core-framework.js");
module.exports = {
/**
* @swagger
* /api/invite/info:
* get:
* summary: 获取邀请信息
* description: 根据用户ID获取邀请码和邀请链接
* tags: [前端-推广邀请]
* responses:
* 200:
* description: 获取成功
*/
'GET /invite/info': async (ctx) => {
try {
// 从query或body中获取user_id实际应该从token中解析
const body = ctx.getBody();
const user_id = body.user_id || ctx.query?.user_id;
if (!user_id) {
return ctx.fail('请先登录');
}
// 生成邀请码基于用户ID
const invite_code = `INV${user_id}${Date.now().toString(36).toUpperCase()}`;
const invite_link = `https://work.light120.com/invite?code=${invite_code}`;
return ctx.success({
invite_code,
invite_link,
user_id
});
} catch (error) {
console.error('获取邀请信息失败:', error);
return ctx.fail('获取邀请信息失败: ' + error.message);
}
},
/**
* @swagger
* /api/invite/statistics:
* get:
* summary: 获取邀请统计
* description: 根据用户ID获取邀请统计数据
* tags: [前端-推广邀请]
* responses:
* 200:
* description: 获取成功
*/
'GET /invite/statistics': async (ctx) => {
try {
// 从query或body中获取user_id实际应该从token中解析
const body = ctx.getBody() || {};
const user_id = body.user_id || ctx.query?.user_id;
if (!user_id) {
return ctx.fail('请先登录');
}
// 这里可以从数据库查询邀请统计,暂时返回模拟数据
return ctx.success({
totalInvites: 0,
activeInvites: 0,
rewardPoints: 0,
rewardAmount: 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 {
// 从query或body中获取user_id实际应该从token中解析
const body = ctx.getBody() || {};
const user_id = body.user_id || ctx.query?.user_id;
if (!user_id) {
return ctx.fail('请先登录');
}
// 生成新的邀请码
const invite_code = `INV${user_id}${Date.now().toString(36).toUpperCase()}`;
const invite_link = `https://work.light120.com/invite?code=${invite_code}`;
return ctx.success({
invite_code,
invite_link
});
} catch (error) {
console.error('生成邀请码失败:', error);
return ctx.fail('生成邀请码失败: ' + error.message);
}
}
};

View File

@@ -26,6 +26,10 @@ module.exports = {
* type: string * type: string
* description: 密码 * description: 密码
* example: 'password123' * example: 'password123'
* device_id:
* type: string
* description: 设备ID客户端生成可选如果提供则使用提供的否则使用数据库中的
* example: 'device_1234567890abcdef'
* responses: * responses:
* 200: * 200:
* description: 登录成功 * description: 登录成功
@@ -71,9 +75,8 @@ module.exports = {
* example: '用户不存在或密码错误' * example: '用户不存在或密码错误'
*/ */
"POST /user/login": async (ctx) => { "POST /user/login": async (ctx) => {
const { phone, password } = ctx.getBody(); const { phone, password, device_id: client_device_id } = ctx.getBody();
const dayjs = require('dayjs'); const dayjs = require('dayjs');
const crypto = require('crypto');
// 验证参数 // 验证参数
if (!phone || !password) { if (!phone || !password) {
@@ -116,18 +119,21 @@ module.exports = {
} }
} }
// 生成设备ID(如果不存在,基于手机号和机器特征生成) // 处理设备ID:优先使用客户端传递的 device_id如果没有则使用数据库中的
let device_id = user.device_id; let device_id = client_device_id || user.device_id;
if (!device_id) {
// 生成唯一设备ID
const machineInfo = `${phone}_${Date.now()}_${Math.random()}`;
device_id = crypto.createHash('sha256').update(machineInfo).digest('hex').substring(0, 32);
// 保存设备ID到账号表 // 如果客户端提供了 device_id 且与数据库中的不同,则更新数据库
if (client_device_id && client_device_id !== user.device_id) {
await pla_account.update( await pla_account.update(
{ device_id: device_id }, { device_id: client_device_id },
{ where: { id: user.id } } { where: { id: user.id } }
); );
device_id = client_device_id;
}
// 如果既没有客户端传递的,数据库中也为空,则返回错误(不应该发生,因为客户端会生成)
if (!device_id) {
return ctx.fail('设备ID不能为空请重新登录');
} }
// 创建token // 创建token

146
api/model/ai_messages.js Normal file
View File

@@ -0,0 +1,146 @@
const Sequelize = require('sequelize');
/**
* AI消息表模型
* 记录用户与AI的交互消息
*/
module.exports = (db) => {
const ai_messages = db.define("ai_messages", {
// 用户信息
user_id: {
comment: '用户ID',
type: Sequelize.INTEGER,
allowNull: false,
defaultValue: 0
},
nickname: {
comment: '用户昵称',
type: Sequelize.STRING(100),
allowNull: true,
defaultValue: ''
},
// 消息信息
message_type: {
comment: '消息类型: question-提问, feedback-反馈, suggestion-建议',
type: Sequelize.STRING(50),
allowNull: false,
defaultValue: 'question'
},
content: {
comment: '消息内容',
type: Sequelize.TEXT,
allowNull: false,
defaultValue: ''
},
// AI回复信息
ai_response: {
comment: 'AI回复内容',
type: Sequelize.TEXT,
allowNull: true,
defaultValue: ''
},
ai_model: {
comment: 'AI模型名称',
type: Sequelize.STRING(50),
allowNull: true,
defaultValue: ''
},
ai_prompt: {
comment: 'AI提示词',
type: Sequelize.TEXT,
allowNull: true,
defaultValue: ''
},
// 处理状态
status: {
comment: '处理状态: pending-待处理, processing-处理中, completed-已完成, failed-处理失败',
type: Sequelize.STRING(20),
allowNull: false,
defaultValue: 'pending'
},
process_time: {
comment: '处理时间(毫秒)',
type: Sequelize.INTEGER,
allowNull: true,
defaultValue: 0
},
// 关联信息
related_id: {
comment: '关联ID如职位ID、聊天记录ID等',
type: Sequelize.STRING(100),
allowNull: true,
defaultValue: ''
},
related_type: {
comment: '关联类型: job-职位, chat-聊天, resume-简历',
type: Sequelize.STRING(50),
allowNull: true,
defaultValue: ''
},
// 其他信息
error_message: {
comment: '错误信息',
type: Sequelize.TEXT,
allowNull: true,
defaultValue: ''
},
notes: {
comment: '备注',
type: Sequelize.TEXT,
allowNull: true,
defaultValue: ''
},
// 时间戳
create_time: {
comment: '创建时间',
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.NOW
},
update_time: {
comment: '更新时间',
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.NOW
},
is_delete: {
comment: '是否删除',
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: false
}
}, {
timestamps: false,
indexes: [
{
unique: false,
fields: ['user_id']
},
{
unique: false,
fields: ['message_type']
},
{
unique: false,
fields: ['status']
},
{
unique: false,
fields: ['create_time']
},
{
unique: false,
fields: ['is_delete']
}
]
});
return ai_messages;
};