feat: 完成多多畅职就业服务平台核心功能开发

主要更新:
-  完成主题配色从暗色到亮蓝白配色的全面转换
-  实现高薪岗位页面及后端API集成
-  完成登录注册页面及认证系统
-  实现预招录确认功能
-  添加数据库管理和维护工具脚本
-  优化错误处理和用户体验

核心功能:
1. 首页 (index.html) - 3D地球、专业分类、过渡岗位
2. 高薪岗位页面 (high.html) - 岗位详情、预招录确认、成功案例
3. 登录注册 (auth.html) - 用户认证、专业分类选择
4. 后端API - RESTful接口,JWT认证,MySQL数据库

技术栈:
- 前端:Three.js, GSAP, 原生JavaScript
- 后端:Node.js, Express, MySQL
- 认证:JWT, bcrypt
- 样式:自定义CSS,响应式设计

数据库工具:
- kill-by-ids.js - 批量终止MySQL进程
- unlock-all-tables.js - 解锁数据库表
- init-db.js - 初始化数据库
- 其他管理脚本

🤖 Generated with Claude Code
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
KQL
2025-12-22 15:40:55 +08:00
parent 97e79e0f8c
commit 61698639ef
55 changed files with 13178 additions and 1298 deletions

View File

@@ -0,0 +1,272 @@
/**
* 投递记录控制器
*/
const db = require('../../config/database');
/**
* 创建投递记录
*/
exports.createApplication = async (req, res) => {
try {
const userId = req.user.id;
const {
job_type, job_name, company_name, company_short_name,
city, province, segment_name, application_data
} = req.body;
// 验证必填字段
if (!job_type || !job_name || !company_name) {
return res.status(400).json({
success: false,
message: '岗位类型、岗位名称和企业名称不能为空'
});
}
// 验证job_type
if (!['transition', 'referral'].includes(job_type)) {
return res.status(400).json({
success: false,
message: '无效的岗位类型'
});
}
// 检查是否已投递过相同岗位
const [existing] = await db.query(`
SELECT id FROM job_applications
WHERE user_id = ? AND job_name = ? AND company_name = ? AND status != 'withdrawn'
`, [userId, job_name, company_name]);
if (existing.length > 0) {
return res.status(400).json({
success: false,
message: '您已经投递过该岗位'
});
}
// 插入投递记录
const [result] = await db.query(`
INSERT INTO job_applications (
user_id, job_type, job_name, company_name, company_short_name,
city, province, segment_name, status, application_data
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?)
`, [
userId, job_type, job_name, company_name, company_short_name || null,
city || null, province || null, segment_name || null,
application_data ? JSON.stringify(application_data) : null
]);
res.status(201).json({
success: true,
message: '投递成功',
data: {
id: result.insertId
}
});
} catch (error) {
console.error('创建投递记录错误:', error);
res.status(500).json({
success: false,
message: '服务器错误'
});
}
};
/**
* 获取投递记录列表
*/
exports.getApplications = async (req, res) => {
try {
const userId = req.user.id;
const { job_type, status, page = 1, limit = 20 } = req.query;
// 构建查询条件
let whereClause = 'user_id = ?';
const params = [userId];
if (job_type) {
whereClause += ' AND job_type = ?';
params.push(job_type);
}
if (status) {
whereClause += ' AND status = ?';
params.push(status);
}
// 计算分页
const offset = (page - 1) * limit;
// 查询总数
const [countResult] = await db.query(
`SELECT COUNT(*) as total FROM job_applications WHERE ${whereClause}`,
params
);
const total = countResult[0].total;
// 查询列表
const [applications] = await db.query(`
SELECT
id, job_type, job_name, company_name, company_short_name,
city, province, segment_name, status, applied_at, updated_at, notes
FROM job_applications
WHERE ${whereClause}
ORDER BY applied_at DESC
LIMIT ? OFFSET ?
`, [...params, parseInt(limit), parseInt(offset)]);
res.json({
success: true,
data: {
list: applications,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
totalPages: Math.ceil(total / limit)
}
}
});
} catch (error) {
console.error('获取投递记录错误:', error);
res.status(500).json({
success: false,
message: '服务器错误'
});
}
};
/**
* 获取单个投递记录详情
*/
exports.getApplicationDetail = async (req, res) => {
try {
const userId = req.user.id;
const { id } = req.params;
const [applications] = await db.query(`
SELECT * FROM job_applications
WHERE id = ? AND user_id = ?
`, [id, userId]);
if (applications.length === 0) {
return res.status(404).json({
success: false,
message: '投递记录不存在'
});
}
const application = applications[0];
// 解析JSON字段
if (application.application_data) {
try {
application.application_data = JSON.parse(application.application_data);
} catch (e) {
application.application_data = null;
}
}
res.json({
success: true,
data: application
});
} catch (error) {
console.error('获取投递详情错误:', error);
res.status(500).json({
success: false,
message: '服务器错误'
});
}
};
/**
* 撤回投递
*/
exports.withdrawApplication = async (req, res) => {
try {
const userId = req.user.id;
const { id } = req.params;
// 检查投递记录是否存在且属于当前用户
const [applications] = await db.query(`
SELECT id, status FROM job_applications
WHERE id = ? AND user_id = ?
`, [id, userId]);
if (applications.length === 0) {
return res.status(404).json({
success: false,
message: '投递记录不存在'
});
}
const application = applications[0];
// 检查状态是否允许撤回
if (['offered', 'rejected', 'withdrawn'].includes(application.status)) {
return res.status(400).json({
success: false,
message: '该投递记录无法撤回'
});
}
// 更新状态为撤回
await db.query(`
UPDATE job_applications
SET status = 'withdrawn', updated_at = NOW()
WHERE id = ?
`, [id]);
res.json({
success: true,
message: '撤回成功'
});
} catch (error) {
console.error('撤回投递错误:', error);
res.status(500).json({
success: false,
message: '服务器错误'
});
}
};
/**
* 获取投递统计
*/
exports.getStatistics = async (req, res) => {
try {
const userId = req.user.id;
// 统计各状态的投递数量
const [stats] = await db.query(`
SELECT
COUNT(*) as total,
SUM(CASE WHEN job_type = 'transition' THEN 1 ELSE 0 END) as transition_count,
SUM(CASE WHEN job_type = 'referral' THEN 1 ELSE 0 END) as referral_count,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_count,
SUM(CASE WHEN status = 'reviewing' THEN 1 ELSE 0 END) as reviewing_count,
SUM(CASE WHEN status = 'interviewed' THEN 1 ELSE 0 END) as interviewed_count,
SUM(CASE WHEN status = 'offered' THEN 1 ELSE 0 END) as offered_count,
SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) as rejected_count
FROM job_applications
WHERE user_id = ?
`, [userId]);
res.json({
success: true,
data: stats[0]
});
} catch (error) {
console.error('获取统计错误:', error);
res.status(500).json({
success: false,
message: '服务器错误'
});
}
};

View File

@@ -0,0 +1,337 @@
/**
* 用户认证控制器
*/
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const db = require('../../config/database');
const JWT_SECRET = process.env.JWT_SECRET || 'duoduo-career-jwt-secret-key-2024';
const JWT_EXPIRE = process.env.JWT_EXPIRE || '7d';
/**
* 用户注册
*/
exports.register = async (req, res) => {
try {
const { username, password, email, phone, professional_category_code, professional_category } = req.body;
// 验证必填字段
if (!username || !password) {
return res.status(400).json({
success: false,
message: '真实姓名和密码不能为空'
});
}
// 验证真实姓名长度
if (username.length < 2 || username.length > 10) {
return res.status(400).json({
success: false,
message: '姓名长度必须在2-10个字符之间'
});
}
// 验证密码长度
if (password.length < 6) {
return res.status(400).json({
success: false,
message: '密码长度至少为6个字符'
});
}
// 验证手机号必填
if (!phone) {
return res.status(400).json({
success: false,
message: '手机号码不能为空'
});
}
// 检查手机号是否已被使用(手机号不允许重复)
const [existingPhones] = await db.query(
'SELECT id FROM users WHERE phone = ?',
[phone]
);
if (existingPhones.length > 0) {
return res.status(400).json({
success: false,
message: '该手机号已被注册'
});
}
// 如果提供了邮箱,检查是否已被使用
if (email) {
const [existingEmails] = await db.query(
'SELECT id FROM users WHERE email = ?',
[email]
);
if (existingEmails.length > 0) {
return res.status(400).json({
success: false,
message: '邮箱已被使用'
});
}
}
// 加密密码
const passwordHash = await bcrypt.hash(password, 10);
// 插入用户
const [result] = await db.query(
'INSERT INTO users (username, password_hash, email, phone, role, status) VALUES (?, ?, ?, ?, ?, ?)',
[username, passwordHash, email || null, phone || null, 'user', 'active']
);
// 创建用户资料记录
await db.query(
`INSERT INTO user_profiles (
user_id,
professional_category_code,
professional_category
) VALUES (?, ?, ?)`,
[
result.insertId,
professional_category_code || null,
professional_category || null
]
);
res.status(201).json({
success: true,
message: '注册成功',
data: {
id: result.insertId,
username
}
});
} catch (error) {
console.error('注册错误:', error);
res.status(500).json({
success: false,
message: '服务器错误,请稍后重试'
});
}
};
/**
* 用户登录
*/
exports.login = async (req, res) => {
try {
const { username, password } = req.body;
// 验证必填字段
if (!username || !password) {
return res.status(400).json({
success: false,
message: '用户名和密码不能为空'
});
}
// 查询用户
const [users] = await db.query(`
SELECT
u.id, u.username, u.password_hash, u.email, u.phone, u.role, u.status,
p.professional_category_code,
p.professional_category
FROM users u
LEFT JOIN user_profiles p ON u.id = p.user_id
WHERE u.username = ?
`, [username]);
if (users.length === 0) {
return res.status(401).json({
success: false,
message: '用户名或密码错误'
});
}
const user = users[0];
// 检查账号状态
if (user.status === 'banned') {
return res.status(403).json({
success: false,
message: '账号已被封禁'
});
}
if (user.status === 'inactive') {
return res.status(403).json({
success: false,
message: '账号未激活'
});
}
// 验证密码
const isPasswordValid = await bcrypt.compare(password, user.password_hash);
if (!isPasswordValid) {
return res.status(401).json({
success: false,
message: '用户名或密码错误'
});
}
// 尝试更新最后登录时间(非阻塞)
try {
await db.query(
'UPDATE users SET last_login = NOW() WHERE id = ?',
[user.id]
);
} catch (updateError) {
// 如果更新失败(如数据库锁定),记录错误但不影响登录
console.warn('更新登录时间失败:', updateError.message);
}
// 生成JWT
const token = jwt.sign(
{
id: user.id,
username: user.username,
role: user.role
},
JWT_SECRET,
{ expiresIn: JWT_EXPIRE }
);
res.json({
success: true,
message: '登录成功',
data: {
token,
user: {
id: user.id,
username: user.username,
email: user.email,
phone: user.phone,
role: user.role,
professional_category_code: user.professional_category_code,
professional_category: user.professional_category
}
}
});
} catch (error) {
console.error('登录错误:', error);
res.status(500).json({
success: false,
message: '服务器错误,请稍后重试'
});
}
};
/**
* 获取当前用户信息
*/
exports.getCurrentUser = async (req, res) => {
try {
const userId = req.user.id;
// 查询用户基本信息和资料
const [users] = await db.query(`
SELECT
u.id, u.username, u.email, u.phone, u.role, u.status, u.created_at, u.last_login,
p.real_name, p.gender, p.birth_date, p.education, p.major, p.school,
p.graduation_year, p.city, p.avatar_url, p.self_intro, p.skills,
p.professional_category_code,
p.professional_category
FROM users u
LEFT JOIN user_profiles p ON u.id = p.user_id
WHERE u.id = ?
`, [userId]);
if (users.length === 0) {
return res.status(404).json({
success: false,
message: '用户不存在'
});
}
const user = users[0];
// 解析JSON字段
if (user.skills) {
try {
user.skills = JSON.parse(user.skills);
} catch (e) {
user.skills = [];
}
}
res.json({
success: true,
data: user
});
} catch (error) {
console.error('获取用户信息错误:', error);
res.status(500).json({
success: false,
message: '服务器错误'
});
}
};
/**
* 更新用户资料
*/
exports.updateProfile = async (req, res) => {
try {
const userId = req.user.id;
const {
real_name, gender, birth_date, education, major, school,
graduation_year, city, address, avatar_url, self_intro, skills,
professional_category_code,
professional_category
} = req.body;
// 构建更新字段
const updates = {};
if (real_name !== undefined) updates.real_name = real_name;
if (gender !== undefined) updates.gender = gender;
if (birth_date !== undefined) updates.birth_date = birth_date;
if (education !== undefined) updates.education = education;
if (major !== undefined) updates.major = major;
if (school !== undefined) updates.school = school;
if (graduation_year !== undefined) updates.graduation_year = graduation_year;
if (city !== undefined) updates.city = city;
if (address !== undefined) updates.address = address;
if (avatar_url !== undefined) updates.avatar_url = avatar_url;
if (self_intro !== undefined) updates.self_intro = self_intro;
if (skills !== undefined) updates.skills = JSON.stringify(skills);
if (professional_category_code !== undefined) updates.professional_category_code = professional_category_code;
if (professional_category !== undefined) updates.professional_category = professional_category;
if (Object.keys(updates).length === 0) {
return res.status(400).json({
success: false,
message: '没有要更新的字段'
});
}
// 构建SQL
const fields = Object.keys(updates).map(key => `${key} = ?`).join(', ');
const values = Object.values(updates);
values.push(userId);
await db.query(
`UPDATE user_profiles SET ${fields}, updated_at = NOW() WHERE user_id = ?`,
values
);
res.json({
success: true,
message: '资料更新成功'
});
} catch (error) {
console.error('更新资料错误:', error);
res.status(500).json({
success: false,
message: '服务器错误'
});
}
};

View File

@@ -0,0 +1,251 @@
/**
* 高薪岗位控制器
* 基于high.html的最新数据结构
*/
const db = require('../../config/database');
/**
* 获取岗位列表
* GET /api/high-salary-jobs
* 支持分页、筛选、搜索
*/
exports.getList = async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 20;
const offset = (page - 1) * limit;
const location = req.query.location;
const keyword = req.query.keyword;
// 构建查询条件
let whereClause = 'WHERE is_active = 1';
const params = [];
if (location) {
whereClause += ' AND location = ?';
params.push(location);
}
if (keyword) {
whereClause += ' AND (title LIKE ? OR company LIKE ?)';
params.push(`%${keyword}%`, `%${keyword}%`);
}
// 查询总数
const [countResult] = await db.query(
`SELECT COUNT(*) as total FROM high_salary_jobs ${whereClause}`,
params
);
const total = countResult[0].total;
// 查询岗位列表
const [jobs] = await db.query(
`SELECT * FROM high_salary_jobs ${whereClause} ORDER BY display_order ASC, id DESC LIMIT ? OFFSET ?`,
[...params, limit, offset]
);
return res.json({
success: true,
data: {
jobs,
total,
page,
limit,
totalPages: Math.ceil(total / limit)
}
});
} catch (error) {
console.error('获取岗位列表失败:', error);
return res.status(500).json({
success: false,
message: '获取岗位列表失败',
error: error.message
});
}
};
/**
* 获取岗位详情
* GET /api/high-salary-jobs/:id
*/
exports.getById = async (req, res) => {
try {
const { id } = req.params;
const [jobs] = await db.query(
'SELECT * FROM high_salary_jobs WHERE id = ? AND is_active = 1',
[id]
);
if (jobs.length === 0) {
return res.status(404).json({
success: false,
message: '岗位不存在'
});
}
return res.json({
success: true,
data: jobs[0]
});
} catch (error) {
console.error('获取岗位详情失败:', error);
return res.status(500).json({
success: false,
message: '获取岗位详情失败',
error: error.message
});
}
};
/**
* 创建岗位(管理员)
* POST /api/high-salary-jobs
*/
exports.create = async (req, res) => {
try {
const {
title,
salary,
company,
location,
quota,
tags,
requirements,
description,
icon,
display_order
} = req.body;
// 验证必填字段
if (!title || !salary || !company || !location || quota === undefined) {
return res.status(400).json({
success: false,
message: '缺少必填字段'
});
}
const [result] = await db.query(
`INSERT INTO high_salary_jobs
(title, salary, company, location, quota, tags, requirements, description, icon, display_order)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
title,
salary,
company,
location,
quota,
JSON.stringify(tags || []),
requirements,
description,
icon || 'fa-microchip',
display_order || 0
]
);
res.status(201).json({
success: true,
message: '岗位创建成功',
data: { id: result.insertId }
});
} catch (error) {
console.error('创建岗位失败:', error);
res.status(500).json({
success: false,
message: '创建岗位失败',
error: error.message
});
}
};
/**
* 更新岗位(管理员)
* PUT /api/high-salary-jobs/:id
*/
exports.update = async (req, res) => {
try {
const { id } = req.params;
const updateData = req.body;
// 检查岗位是否存在
const [existing] = await db.query('SELECT id FROM high_salary_jobs WHERE id = ?', [id]);
if (existing.length === 0) {
return res.status(404).json({
success: false,
message: '岗位不存在'
});
}
const updates = [];
const params = [];
const allowedFields = ['title', 'salary', 'company', 'location', 'quota', 'tags', 'requirements', 'description', 'icon', 'display_order', 'is_active'];
for (const field of allowedFields) {
if (updateData[field] !== undefined) {
updates.push(`${field} = ?`);
params.push(field === 'tags' ? JSON.stringify(updateData[field]) : updateData[field]);
}
}
if (updates.length === 0) {
return res.status(400).json({
success: false,
message: '没有要更新的字段'
});
}
params.push(id);
await db.query(
`UPDATE high_salary_jobs SET ${updates.join(', ')} WHERE id = ?`,
params
);
res.json({
success: true,
message: '岗位更新成功'
});
} catch (error) {
console.error('更新岗位失败:', error);
res.status(500).json({
success: false,
message: '更新岗位失败',
error: error.message
});
}
};
/**
* 删除岗位(软删除)
* DELETE /api/high-salary-jobs/:id
*/
exports.delete = async (req, res) => {
try {
const { id } = req.params;
const [result] = await db.query(
'UPDATE high_salary_jobs SET is_active = 0 WHERE id = ?',
[id]
);
if (result.affectedRows === 0) {
return res.status(404).json({
success: false,
message: '岗位不存在'
});
}
res.json({
success: true,
message: '岗位删除成功'
});
} catch (error) {
console.error('删除岗位失败:', error);
res.status(500).json({
success: false,
message: '删除岗位失败',
error: error.message
});
}
};

View File

@@ -0,0 +1,224 @@
/**
* 成功案例控制器
* 基于high.html的最新数据结构 (三轨道弹幕系统)
*/
const db = require('../../config/database');
/**
* 获取成功案例列表 (按轨道分组)
* GET /api/success-stories
* 支持可选的track_type筛选
*/
exports.getList = async (req, res) => {
try {
const trackType = req.query.track_type;
// 构建查询条件
let whereClause = 'WHERE is_active = 1';
const params = [];
if (trackType && ['fast', 'slow', 'medium'].includes(trackType)) {
whereClause += ' AND track_type = ?';
params.push(trackType);
}
const [stories] = await db.query(
`SELECT
id,
name,
job,
company,
salary,
avatar_color,
track_type
FROM success_stories
${whereClause}
ORDER BY display_order ASC, id ASC`,
params
);
// 按轨道分组
const grouped = {
fast: stories.filter(s => s.track_type === 'fast'),
slow: stories.filter(s => s.track_type === 'slow'),
medium: stories.filter(s => s.track_type === 'medium')
};
return res.json({
success: true,
data: grouped
});
} catch (error) {
console.error('获取成功案例失败:', error);
return res.status(500).json({
success: false,
message: '获取成功案例失败',
error: error.message
});
}
};
/**
* 创建成功案例 (管理员)
* POST /api/success-stories
*/
exports.create = async (req, res) => {
try {
const {
name,
job,
company,
salary,
avatar_color,
track_type,
display_order
} = req.body;
// 验证必填字段
if (!name || !job || !company || !salary) {
return res.status(400).json({
success: false,
message: '缺少必填字段 (name, job, company, salary)'
});
}
// 验证track_type
if (track_type && !['fast', 'slow', 'medium'].includes(track_type)) {
return res.status(400).json({
success: false,
message: 'track_type 必须是 fast, slow 或 medium'
});
}
const [result] = await db.query(
`INSERT INTO success_stories
(name, job, company, salary, avatar_color, track_type, display_order)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
[
name,
job,
company,
salary,
avatar_color || '#2563eb',
track_type || 'fast',
display_order || 0
]
);
return res.status(201).json({
success: true,
message: '成功案例创建成功',
data: { id: result.insertId }
});
} catch (error) {
console.error('创建成功案例失败:', error);
return res.status(500).json({
success: false,
message: '创建成功案例失败',
error: error.message
});
}
};
/**
* 更新成功案例 (管理员)
* PUT /api/success-stories/:id
*/
exports.update = async (req, res) => {
try {
const { id } = req.params;
const updateData = req.body;
// 检查案例是否存在
const [existing] = await db.query(
'SELECT id FROM success_stories WHERE id = ?',
[id]
);
if (existing.length === 0) {
return res.status(404).json({
success: false,
message: '成功案例不存在'
});
}
const updates = [];
const params = [];
const allowedFields = ['name', 'job', 'company', 'salary', 'avatar_color', 'track_type', 'display_order', 'is_active'];
for (const field of allowedFields) {
if (updateData[field] !== undefined) {
// 验证track_type
if (field === 'track_type' && !['fast', 'slow', 'medium'].includes(updateData[field])) {
return res.status(400).json({
success: false,
message: 'track_type 必须是 fast, slow 或 medium'
});
}
updates.push(`${field} = ?`);
params.push(updateData[field]);
}
}
if (updates.length === 0) {
return res.status(400).json({
success: false,
message: '没有要更新的字段'
});
}
params.push(id);
await db.query(
`UPDATE success_stories SET ${updates.join(', ')} WHERE id = ?`,
params
);
return res.json({
success: true,
message: '成功案例更新成功'
});
} catch (error) {
console.error('更新成功案例失败:', error);
return res.status(500).json({
success: false,
message: '更新成功案例失败',
error: error.message
});
}
};
/**
* 删除成功案例 (软删除)
* DELETE /api/success-stories/:id
*/
exports.delete = async (req, res) => {
try {
const { id } = req.params;
const [result] = await db.query(
'UPDATE success_stories SET is_active = 0 WHERE id = ?',
[id]
);
if (result.affectedRows === 0) {
return res.status(404).json({
success: false,
message: '成功案例不存在'
});
}
return res.json({
success: true,
message: '成功案例删除成功'
});
} catch (error) {
console.error('删除成功案例失败:', error);
return res.status(500).json({
success: false,
message: '删除成功案例失败',
error: error.message
});
}
};

View File

@@ -0,0 +1,229 @@
/**
* 培训单元控制器
* 基于high.html的最新数据结构
*/
const db = require('../../config/database');
/**
* 获取所有培训单元
* GET /api/training-units
* 支持optionalAuth
*/
exports.getList = async (req, res) => {
try {
const userId = req.user ? req.user.id : null;
let query, params;
if (userId) {
// 已登录:查询确认状态
query = `
SELECT
tu.*,
tc.id as confirmation_id,
tc.confirmed_at
FROM training_units tu
LEFT JOIN training_confirmations tc ON tu.id = tc.training_unit_id AND tc.user_id = ?
ORDER BY tu.display_order ASC, tu.start_date ASC
`;
params = [userId];
} else {
// 未登录:仅返回单元列表
query = `
SELECT * FROM training_units
ORDER BY display_order ASC, start_date ASC
`;
params = [];
}
const [units] = await db.query(query, params);
// 格式化数据
const formattedUnits = units.map(unit => ({
id: unit.id,
title: unit.title,
start_date: unit.start_date,
related_positions: unit.related_positions,
related_companies: unit.related_companies,
status: unit.status,
is_confirmed: !!unit.confirmation_id,
confirmed_at: unit.confirmed_at || null
}));
return res.json({
success: true,
data: formattedUnits
});
} catch (error) {
console.error('获取培训单元失败:', error);
return res.status(500).json({
success: false,
message: '获取培训单元失败',
error: error.message
});
}
};
/**
* 确认预招录
* POST /api/training-units/:id/confirm
* 需要登录
*/
exports.confirm = async (req, res) => {
try {
const { id } = req.params;
const userId = req.user.id;
// 检查单元是否存在
const [units] = await db.query(
'SELECT id, title FROM training_units WHERE id = ?',
[id]
);
if (units.length === 0) {
return res.status(404).json({
success: false,
message: '培训单元不存在'
});
}
// 检查是否已确认
const [existing] = await db.query(
'SELECT id FROM training_confirmations WHERE user_id = ? AND training_unit_id = ?',
[userId, id]
);
if (existing.length > 0) {
return res.status(400).json({
success: false,
message: '您已确认过该单元'
});
}
// 插入确认记录(设置较短的超时时间)
try {
await db.query(
'INSERT INTO training_confirmations (user_id, training_unit_id) VALUES (?, ?)',
[userId, id]
);
} catch (insertError) {
// 特殊处理数据库锁定错误
if (insertError.code === 'ER_LOCK_WAIT_TIMEOUT') {
return res.status(503).json({
success: false,
message: '系统繁忙,请稍后再试',
error: '数据库暂时无法处理请求'
});
}
throw insertError;
}
return res.json({
success: true,
message: '确认成功'
});
} catch (error) {
// 处理唯一键冲突
if (error.code === 'ER_DUP_ENTRY') {
return res.status(400).json({
success: false,
message: '您已确认过该单元'
});
}
// 处理数据库锁定错误
if (error.code === 'ER_LOCK_WAIT_TIMEOUT') {
console.error('数据库锁定:', error.message);
return res.status(503).json({
success: false,
message: '系统繁忙,请稍后再试',
error: '数据库暂时无法处理请求'
});
}
console.error('确认预招录失败:', error);
return res.status(500).json({
success: false,
message: '确认失败,请稍后重试'
});
}
};
/**
* 取消预招录
* DELETE /api/training-units/:id/confirm
* 需要登录
*/
exports.cancelConfirm = async (req, res) => {
try {
const { id } = req.params;
const userId = req.user.id;
const [result] = await db.query(
'DELETE FROM training_confirmations WHERE user_id = ? AND training_unit_id = ?',
[userId, id]
);
if (result.affectedRows === 0) {
return res.status(404).json({
success: false,
message: '未找到确认记录'
});
}
return res.json({
success: true,
message: '取消成功'
});
} catch (error) {
console.error('取消预招录失败:', error);
return res.status(500).json({
success: false,
message: '取消预招录失败',
error: error.message
});
}
};
/**
* 获取我的确认列表
* GET /api/training-units/my-confirmations
* 需要登录
*/
exports.getMyConfirmations = async (req, res) => {
try {
const userId = req.user.id;
const [confirmations] = await db.query(`
SELECT
tu.*,
tc.confirmed_at
FROM training_confirmations tc
JOIN training_units tu ON tc.training_unit_id = tu.id
WHERE tc.user_id = ?
ORDER BY tc.confirmed_at DESC
`, [userId]);
const formattedData = confirmations.map(c => ({
id: c.id,
title: c.title,
start_date: c.start_date,
related_positions: c.related_positions,
related_companies: c.related_companies,
confirmed_at: c.confirmed_at
}));
return res.json({
success: true,
data: formattedData
});
} catch (error) {
console.error('获取确认列表失败:', error);
return res.status(500).json({
success: false,
message: '获取确认列表失败',
error: error.message
});
}
};

View File

@@ -0,0 +1,270 @@
/**
* 面试进度控制器
* 基于high.html的最新数据结构
*/
const db = require('../../config/database');
/**
* 获取我的面试进度列表
* GET /api/transition-progress
* 需要登录
*/
exports.getMyProgress = async (req, res) => {
try {
const userId = req.user.id;
const [records] = await db.query(
`SELECT
id,
company,
job,
status,
status_type,
created_at,
updated_at
FROM transition_progress
WHERE user_id = ?
ORDER BY created_at DESC`,
[userId]
);
return res.json({
success: true,
data: records
});
} catch (error) {
console.error('获取面试进度失败:', error);
return res.status(500).json({
success: false,
message: '获取面试进度失败',
error: error.message
});
}
};
/**
* 创建面试进度记录
* POST /api/transition-progress
* 需要登录
*/
exports.create = async (req, res) => {
try {
const userId = req.user.id;
const {
company,
job,
status,
status_type
} = req.body;
// 验证必填字段
if (!company || !job || !status) {
return res.status(400).json({
success: false,
message: '缺少必填字段 (company, job, status)'
});
}
// 验证status_type
if (status_type && !['offer', 'hr', 'ing', 'pending'].includes(status_type)) {
return res.status(400).json({
success: false,
message: 'status_type 必须是 offer, hr, ing 或 pending'
});
}
const [result] = await db.query(
`INSERT INTO transition_progress
(user_id, company, job, status, status_type)
VALUES (?, ?, ?, ?, ?)`,
[
userId,
company,
job,
status,
status_type || 'pending'
]
);
return res.status(201).json({
success: true,
message: '面试进度记录创建成功',
data: { id: result.insertId }
});
} catch (error) {
console.error('创建面试进度失败:', error);
return res.status(500).json({
success: false,
message: '创建面试进度失败',
error: error.message
});
}
};
/**
* 更新面试进度
* PUT /api/transition-progress/:id
* 需要登录
*/
exports.update = async (req, res) => {
try {
const { id } = req.params;
const userId = req.user.id;
const updateData = req.body;
// 检查记录是否存在且属于当前用户
const [existing] = await db.query(
'SELECT id FROM transition_progress WHERE id = ? AND user_id = ?',
[id, userId]
);
if (existing.length === 0) {
return res.status(404).json({
success: false,
message: '面试进度记录不存在或无权访问'
});
}
const updates = [];
const params = [];
const allowedFields = ['company', 'job', 'status', 'status_type'];
for (const field of allowedFields) {
if (updateData[field] !== undefined) {
// 验证status_type
if (field === 'status_type' && !['offer', 'hr', 'ing', 'pending'].includes(updateData[field])) {
return res.status(400).json({
success: false,
message: 'status_type 必须是 offer, hr, ing 或 pending'
});
}
updates.push(`${field} = ?`);
params.push(updateData[field]);
}
}
if (updates.length === 0) {
return res.status(400).json({
success: false,
message: '没有要更新的字段'
});
}
params.push(id);
await db.query(
`UPDATE transition_progress SET ${updates.join(', ')} WHERE id = ?`,
params
);
return res.json({
success: true,
message: '面试进度更新成功'
});
} catch (error) {
console.error('更新面试进度失败:', error);
return res.status(500).json({
success: false,
message: '更新面试进度失败',
error: error.message
});
}
};
/**
* 删除面试进度记录
* DELETE /api/transition-progress/:id
* 需要登录
*/
exports.delete = async (req, res) => {
try {
const { id } = req.params;
const userId = req.user.id;
const [result] = await db.query(
'DELETE FROM transition_progress WHERE id = ? AND user_id = ?',
[id, userId]
);
if (result.affectedRows === 0) {
return res.status(404).json({
success: false,
message: '面试进度记录不存在或无权访问'
});
}
return res.json({
success: true,
message: '面试进度删除成功'
});
} catch (error) {
console.error('删除面试进度失败:', error);
return res.status(500).json({
success: false,
message: '删除面试进度失败',
error: error.message
});
}
};
/**
* 获取所有面试进度记录 (管理员)
* GET /api/transition-progress/all
* 需要管理员权限
*/
exports.getAll = async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 20;
const offset = (page - 1) * limit;
const statusType = req.query.status_type;
// 构建查询条件
let whereClause = '';
const params = [];
if (statusType && ['offer', 'hr', 'ing', 'pending'].includes(statusType)) {
whereClause = 'WHERE status_type = ?';
params.push(statusType);
}
// 查询总数
const [countResult] = await db.query(
`SELECT COUNT(*) as total FROM transition_progress ${whereClause}`,
params
);
const total = countResult[0].total;
// 查询记录列表
const [records] = await db.query(
`SELECT
tp.*,
u.username
FROM transition_progress tp
LEFT JOIN users u ON tp.user_id = u.id
${whereClause}
ORDER BY tp.created_at DESC
LIMIT ? OFFSET ?`,
[...params, limit, offset]
);
return res.json({
success: true,
data: {
records,
total,
page,
limit,
totalPages: Math.ceil(total / limit)
}
});
} catch (error) {
console.error('获取所有面试进度失败:', error);
return res.status(500).json({
success: false,
message: '获取所有面试进度失败',
error: error.message
});
}
};

View File

@@ -0,0 +1,20 @@
/**
* 投递记录路由
*/
const express = require('express');
const router = express.Router();
const applicationController = require('../controllers/applicationController');
const { verifyToken } = require('../../middleware/auth');
// 所有路由都需要认证
router.use(verifyToken);
// 投递记录相关路由
router.post('/', applicationController.createApplication);
router.get('/', applicationController.getApplications);
router.get('/statistics', applicationController.getStatistics);
router.get('/:id', applicationController.getApplicationDetail);
router.put('/:id/withdraw', applicationController.withdrawApplication);
module.exports = router;

18
api/routes/auth.js Normal file
View File

@@ -0,0 +1,18 @@
/**
* 用户认证路由
*/
const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');
const { verifyToken } = require('../../middleware/auth');
// 公开路由(无需认证)
router.post('/register', authController.register);
router.post('/login', authController.login);
// 需要认证的路由
router.get('/me', verifyToken, authController.getCurrentUser);
router.put('/profile', verifyToken, authController.updateProfile);
module.exports = router;

View File

@@ -0,0 +1,19 @@
/**
* 高薪岗位路由
*/
const express = require('express');
const router = express.Router();
const controller = require('../controllers/highSalaryJobController');
const { optionalAuth, verifyToken, verifyAdmin } = require('../../middleware/auth');
// 公开/可选登录路由
router.get('/', optionalAuth, controller.getList);
router.get('/:id', optionalAuth, controller.getById);
// 管理员路由
router.post('/', verifyToken, verifyAdmin, controller.create);
router.put('/:id', verifyToken, verifyAdmin, controller.update);
router.delete('/:id', verifyToken, verifyAdmin, controller.delete);
module.exports = router;

View File

@@ -0,0 +1,18 @@
/**
* 成功案例路由
*/
const express = require('express');
const router = express.Router();
const controller = require('../controllers/successStoryController');
const { verifyToken, verifyAdmin } = require('../../middleware/auth');
// 公开路由(无需登录)
router.get('/', controller.getList);
// 管理员路由
router.post('/', verifyToken, verifyAdmin, controller.create);
router.put('/:id', verifyToken, verifyAdmin, controller.update);
router.delete('/:id', verifyToken, verifyAdmin, controller.delete);
module.exports = router;

View File

@@ -0,0 +1,18 @@
/**
* 培训单元路由
*/
const express = require('express');
const router = express.Router();
const controller = require('../controllers/trainingUnitController');
const { optionalAuth, verifyToken } = require('../../middleware/auth');
// 可选登录路由(未登录也可访问,但登录后会显示确认状态)
router.get('/', optionalAuth, controller.getList);
// 需要登录的路由
router.post('/:id/confirm', verifyToken, controller.confirm);
router.delete('/:id/confirm', verifyToken, controller.cancelConfirm);
router.get('/my-confirmations', verifyToken, controller.getMyConfirmations);
module.exports = router;

View File

@@ -0,0 +1,19 @@
/**
* 面试进度路由
*/
const express = require('express');
const router = express.Router();
const controller = require('../controllers/transitionProgressController');
const { verifyToken, verifyAdmin } = require('../../middleware/auth');
// 需要登录的路由
router.get('/', verifyToken, controller.getMyProgress);
router.post('/', verifyToken, controller.create);
router.put('/:id', verifyToken, controller.update);
router.delete('/:id', verifyToken, controller.delete);
// 管理员路由
router.get('/all', verifyToken, verifyAdmin, controller.getAll);
module.exports = router;