1
This commit is contained in:
211
api/controller_front/apply.js
Normal file
211
api/controller_front/apply.js
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
159
api/controller_front/feedback.js
Normal file
159
api/controller_front/feedback.js
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
114
api/controller_front/invite.js
Normal file
114
api/controller_front/invite.js
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -26,6 +26,10 @@ module.exports = {
|
||||
* type: string
|
||||
* description: 密码
|
||||
* example: 'password123'
|
||||
* device_id:
|
||||
* type: string
|
||||
* description: 设备ID(客户端生成,可选,如果提供则使用提供的,否则使用数据库中的)
|
||||
* example: 'device_1234567890abcdef'
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 登录成功
|
||||
@@ -71,9 +75,8 @@ module.exports = {
|
||||
* example: '用户不存在或密码错误'
|
||||
*/
|
||||
"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 crypto = require('crypto');
|
||||
|
||||
// 验证参数
|
||||
if (!phone || !password) {
|
||||
@@ -116,18 +119,21 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
|
||||
// 生成设备ID(如果不存在,基于手机号和机器特征生成)
|
||||
let 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,如果没有则使用数据库中的
|
||||
let device_id = client_device_id || user.device_id;
|
||||
|
||||
// 保存设备ID到账号表
|
||||
// 如果客户端提供了 device_id 且与数据库中的不同,则更新数据库
|
||||
if (client_device_id && client_device_id !== user.device_id) {
|
||||
await pla_account.update(
|
||||
{ device_id: device_id },
|
||||
{ device_id: client_device_id },
|
||||
{ where: { id: user.id } }
|
||||
);
|
||||
device_id = client_device_id;
|
||||
}
|
||||
|
||||
// 如果既没有客户端传递的,数据库中也为空,则返回错误(不应该发生,因为客户端会生成)
|
||||
if (!device_id) {
|
||||
return ctx.fail('设备ID不能为空,请重新登录');
|
||||
}
|
||||
|
||||
// 创建token
|
||||
|
||||
146
api/model/ai_messages.js
Normal file
146
api/model/ai_messages.js
Normal 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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user