主要更新: - ✅ 完成主题配色从暗色到亮蓝白配色的全面转换 - ✅ 实现高薪岗位页面及后端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>
338 lines
10 KiB
JavaScript
338 lines
10 KiB
JavaScript
/**
|
|
* 用户认证控制器
|
|
*/
|
|
|
|
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: '服务器错误'
|
|
});
|
|
}
|
|
};
|