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:
337
api/controllers/authController.js
Normal file
337
api/controllers/authController.js
Normal 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: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user