jobs.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. const express = require('express');
  2. const router = express.Router();
  3. const { pool } = require('../config/db');
  4. const crypto = require('crypto');
  5. // 安全解析 JSON 或返回原值
  6. const safeParseJSON = (data) => {
  7. if (typeof data === 'string') {
  8. try {
  9. return JSON.parse(data);
  10. } catch (e) {
  11. return null;
  12. }
  13. }
  14. return data;
  15. };
  16. // 获取所有职位
  17. router.get('/', async (req, res) => {
  18. try {
  19. const [jobs] = await pool.query(`
  20. SELECT id, title, department, location, description, status,
  21. pending_resumes AS pendingResumes,
  22. passed_resumes AS passedResumes,
  23. ai_criteria AS aiCriteria,
  24. created_at AS createdAt,
  25. updated_at AS updatedAt
  26. FROM jobs
  27. WHERE status != 'deleted'
  28. ORDER BY created_at DESC
  29. `);
  30. // 安全解析 aiCriteria
  31. const processedJobs = jobs.map(job => ({
  32. ...job,
  33. aiCriteria: safeParseJSON(job.aiCriteria)
  34. }));
  35. res.json(processedJobs);
  36. } catch (error) {
  37. console.error('Get jobs error:', error);
  38. res.status(500).json({ error: 'Server error' });
  39. }
  40. });
  41. // 创建新职位(修正版)
  42. router.post('/', async (req, res) => {
  43. const connection = await pool.getConnection();
  44. try {
  45. await connection.beginTransaction();
  46. const {
  47. title,
  48. department,
  49. location,
  50. description = '', // 默认空字符串
  51. status = 'draft',
  52. pendingResumes = 0, // 添加默认值
  53. passedResumes = 0, // 添加默认值
  54. aiCriteria = {} // 默认空对象
  55. } = req.body;
  56. // 直接插入,使用数据库自增ID
  57. const [result] = await connection.query(
  58. `INSERT INTO jobs (
  59. title, department, location, description, status,
  60. pending_resumes, passed_resumes, ai_criteria,
  61. created_at, updated_at
  62. ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
  63. [
  64. title,
  65. department,
  66. location,
  67. description,
  68. status,
  69. pendingResumes,
  70. passedResumes,
  71. JSON.stringify(aiCriteria),
  72. new Date(),
  73. new Date()
  74. ]
  75. );
  76. // 获取新创建的记录
  77. const [newJob] = await connection.query(
  78. 'SELECT * FROM jobs WHERE id = ?',
  79. [result.insertId]
  80. );
  81. await connection.commit();
  82. res.status(201).json({
  83. ...newJob[0],
  84. id: newJob[0].id, // 保持原始数值ID
  85. pendingResumes: newJob[0].pending_resumes,
  86. passedResumes: newJob[0].passed_resumes,
  87. aiCriteria: safeParseJSON(newJob[0].ai_criteria),
  88. createdAt: newJob[0].created_at,
  89. updatedAt: newJob[0].updated_at
  90. });
  91. } catch (error) {
  92. await connection.rollback();
  93. console.error('Create job error:', error);
  94. res.status(500).json({
  95. error: 'Failed to create job',
  96. details: process.env.NODE_ENV === 'development' ? {
  97. message: error.message,
  98. stack: error.stack
  99. } : undefined
  100. });
  101. } finally {
  102. connection.release();
  103. }
  104. });
  105. // 更新职位
  106. router.put('/:id', async (req, res) => {
  107. try {
  108. const { id } = req.params;
  109. const updates = req.body;
  110. const updateFields = [];
  111. const queryParams = [];
  112. // 动态构建更新语句
  113. for (const [key, value] of Object.entries(updates)) {
  114. if (key === 'aiCriteria') {
  115. updateFields.push('ai_criteria = ?');
  116. queryParams.push(JSON.stringify(value));
  117. } else {
  118. updateFields.push(`${key} = ?`);
  119. queryParams.push(value);
  120. }
  121. }
  122. if (updateFields.length === 0) {
  123. return res.status(400).json({ error: 'No fields to update' });
  124. }
  125. queryParams.push(id);
  126. await pool.query(
  127. `UPDATE jobs
  128. SET ${updateFields.join(', ')}, updated_at = ?
  129. WHERE id = ?`,
  130. [...queryParams, new Date()]
  131. );
  132. const [updatedJob] = await pool.query(
  133. 'SELECT * FROM jobs WHERE id = ?',
  134. [id]
  135. );
  136. if (updatedJob.length === 0) {
  137. return res.status(404).json({ error: 'Job not found' });
  138. }
  139. res.json({
  140. ...updatedJob[0],
  141. pendingResumes: updatedJob[0].pending_resumes,
  142. passedResumes: updatedJob[0].passed_resumes,
  143. aiCriteria: safeParseJSON(updatedJob[0].ai_criteria),
  144. createdAt: updatedJob[0].created_at,
  145. updatedAt: updatedJob[0].updated_at
  146. });
  147. } catch (error) {
  148. console.error('Update job error:', error);
  149. res.status(500).json({ error: 'Server error' });
  150. }
  151. });
  152. // 删除职位
  153. router.delete('/:id', async (req, res) => {
  154. try {
  155. const { id } = req.params;
  156. // 软删除
  157. await pool.query(
  158. `UPDATE jobs SET status = 'deleted', updated_at = ?
  159. WHERE id = ?`,
  160. [new Date(), id]
  161. );
  162. res.status(204).end();
  163. } catch (error) {
  164. console.error('Delete job error:', error);
  165. res.status(500).json({ error: 'Server error' });
  166. }
  167. });
  168. // 触发职位筛选
  169. router.post('/:id/screen', async (req, res) => {
  170. try {
  171. const { id } = req.params;
  172. // 获取职位信息
  173. const [jobs] = await pool.query(
  174. 'SELECT * FROM jobs WHERE id = ?',
  175. [id]
  176. );
  177. if (jobs.length === 0) {
  178. return res.status(404).json({ error: 'Job not found' });
  179. }
  180. const job = jobs[0];
  181. // 使用安全解析方法
  182. const aiCriteria = safeParseJSON(job.ai_criteria);
  183. if (!aiCriteria) {
  184. return res.status(400).json({ error: 'Invalid AI criteria format' });
  185. }
  186. // 这里应该调用AI筛选逻辑
  187. // 模拟筛选过程
  188. await new Promise(resolve => setTimeout(resolve, 2000));
  189. // 更新筛选结果
  190. const [candidates] = await pool.query(
  191. 'SELECT id FROM candidates WHERE job_id = ?',
  192. [id]
  193. );
  194. const passedCount = Math.floor(candidates.length * 0.6);
  195. await pool.query(
  196. `UPDATE jobs
  197. SET pending_resumes = ?, passed_resumes = ?, updated_at = ?
  198. WHERE id = ?`,
  199. [candidates.length, passedCount, new Date(), id]
  200. );
  201. // 更新候选人状态
  202. await pool.query(
  203. `UPDATE candidates
  204. SET status = CASE WHEN RAND() < 0.6 THEN 'passed' ELSE 'rejected' END
  205. WHERE job_id = ? AND status = 'pending'`,
  206. [id]
  207. );
  208. const [updatedJob] = await pool.query(
  209. 'SELECT * FROM jobs WHERE id = ?',
  210. [id]
  211. );
  212. res.json({
  213. ...updatedJob[0],
  214. pendingResumes: updatedJob[0].pending_resumes,
  215. passedResumes: updatedJob[0].passed_resumes,
  216. aiCriteria: safeParseJSON(updatedJob[0].ai_criteria),
  217. createdAt: updatedJob[0].created_at,
  218. updatedAt: updatedJob[0].updated_at
  219. });
  220. } catch (error) {
  221. console.error('Screen job error:', error);
  222. res.status(500).json({ error: 'Server error' });
  223. }
  224. });
  225. module.exports = router;