diff --git a/api/controller_front/apply.js b/api/controller_front/apply.js new file mode 100644 index 0000000..aa578db --- /dev/null +++ b/api/controller_front/apply.js @@ -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); + } + } +}; + diff --git a/api/controller_front/feedback.js b/api/controller_front/feedback.js new file mode 100644 index 0000000..4cfbd5d --- /dev/null +++ b/api/controller_front/feedback.js @@ -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); + } + } +}; + diff --git a/api/controller_front/invite.js b/api/controller_front/invite.js new file mode 100644 index 0000000..06e2801 --- /dev/null +++ b/api/controller_front/invite.js @@ -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); + } + } +}; + diff --git a/api/controller_front/user.js b/api/controller_front/user.js index 98368bf..8ff2dbc 100644 --- a/api/controller_front/user.js +++ b/api/controller_front/user.js @@ -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到账号表 + // 处理设备ID:优先使用客户端传递的 device_id,如果没有则使用数据库中的 + let device_id = client_device_id || user.device_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 diff --git a/api/model/ai_messages.js b/api/model/ai_messages.js new file mode 100644 index 0000000..e3701de --- /dev/null +++ b/api/model/ai_messages.js @@ -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; +}; +