297 lines
6.8 KiB
JavaScript
297 lines
6.8 KiB
JavaScript
const bcrypt = require('bcryptjs');
|
||
const User = require('../models/User');
|
||
const Role = require('../models/Role');
|
||
const Menu = require('../models/Menu');
|
||
const { generateToken } = require('../middleware/auth');
|
||
const { logLogin, getClientIp } = require('../utils/logger');
|
||
|
||
// 登录
|
||
exports.login = async (req, res) => {
|
||
const clientIp = getClientIp(req);
|
||
const userAgent = req.headers['user-agent'];
|
||
let username = req.body.username;
|
||
|
||
try {
|
||
const { password } = req.body;
|
||
|
||
// 参数验证
|
||
if (!username || !password) {
|
||
await logLogin({
|
||
username,
|
||
loginType: 'login',
|
||
ip: clientIp,
|
||
userAgent,
|
||
status: 'fail',
|
||
message: '用户名和密码不能为空'
|
||
});
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '用户名和密码不能为空'
|
||
});
|
||
}
|
||
|
||
// 查找用户
|
||
const user = await User.findOne({
|
||
where: { username },
|
||
include: [{
|
||
model: Role,
|
||
as: 'role'
|
||
}]
|
||
});
|
||
|
||
if (!user) {
|
||
await logLogin({
|
||
username,
|
||
loginType: 'login',
|
||
ip: clientIp,
|
||
userAgent,
|
||
status: 'fail',
|
||
message: '用户名或密码错误'
|
||
});
|
||
return res.status(401).json({
|
||
code: 401,
|
||
message: '用户名或密码错误'
|
||
});
|
||
}
|
||
|
||
// 检查用户状态
|
||
if (user.status === 'disabled') {
|
||
await logLogin({
|
||
userId: user.id,
|
||
username: user.username,
|
||
loginType: 'login',
|
||
ip: clientIp,
|
||
userAgent,
|
||
status: 'fail',
|
||
message: '账号已被禁用'
|
||
});
|
||
return res.status(401).json({
|
||
code: 401,
|
||
message: '账号已被禁用'
|
||
});
|
||
}
|
||
|
||
// 验证密码
|
||
const isPasswordValid = await bcrypt.compare(password, user.password);
|
||
if (!isPasswordValid) {
|
||
await logLogin({
|
||
userId: user.id,
|
||
username: user.username,
|
||
loginType: 'login',
|
||
ip: clientIp,
|
||
userAgent,
|
||
status: 'fail',
|
||
message: '用户名或密码错误'
|
||
});
|
||
return res.status(401).json({
|
||
code: 401,
|
||
message: '用户名或密码错误'
|
||
});
|
||
}
|
||
|
||
// 生成 Token
|
||
const token = generateToken(user);
|
||
|
||
// 获取用户菜单权限
|
||
let menus = [];
|
||
// 超级管理员(isSuperAdmin为1或角色code为admin)返回所有菜单
|
||
if (user.isSuperAdmin === 1 || (user.role && user.role.code === 'admin')) {
|
||
// 超级管理员返回所有菜单
|
||
const allMenus = await Menu.findAll({
|
||
where: {
|
||
isDeleted: false,
|
||
status: 'active',
|
||
visible: 'show'
|
||
},
|
||
order: [['sort', 'ASC']]
|
||
});
|
||
menus = buildMenuTree(allMenus);
|
||
} else if (user.roleId) {
|
||
// 普通用户返回角色分配的菜单
|
||
const roleData = await Role.findByPk(user.roleId, {
|
||
include: [{
|
||
model: Menu,
|
||
as: 'menus',
|
||
where: {
|
||
isDeleted: false,
|
||
status: 'active',
|
||
visible: 'show'
|
||
},
|
||
through: { attributes: [] }
|
||
}]
|
||
});
|
||
|
||
if (roleData && roleData.menus) {
|
||
const sortedMenus = roleData.menus.sort((a, b) => a.sort - b.sort);
|
||
menus = buildMenuTree(sortedMenus);
|
||
}
|
||
}
|
||
|
||
// 记录登录成功日志
|
||
await logLogin({
|
||
userId: user.id,
|
||
username: user.username,
|
||
loginType: 'login',
|
||
ip: clientIp,
|
||
userAgent,
|
||
status: 'success',
|
||
message: '登录成功'
|
||
});
|
||
|
||
// 返回用户信息和 Token
|
||
res.json({
|
||
code: 200,
|
||
message: '登录成功',
|
||
data: {
|
||
token,
|
||
userInfo: {
|
||
id: user.id,
|
||
username: user.username,
|
||
nickname: user.nickname,
|
||
role: user.role
|
||
},
|
||
menus
|
||
}
|
||
});
|
||
} catch (error) {
|
||
console.error('登录错误:', error);
|
||
await logLogin({
|
||
username,
|
||
loginType: 'login',
|
||
ip: clientIp,
|
||
userAgent,
|
||
status: 'fail',
|
||
message: error.message
|
||
});
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '登录失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 登出
|
||
exports.logout = async (req, res) => {
|
||
try {
|
||
// 记录登出日志
|
||
const user = req.user || {};
|
||
await logLogin({
|
||
userId: user.id,
|
||
username: user.username,
|
||
loginType: 'logout',
|
||
ip: getClientIp(req),
|
||
userAgent: req.headers['user-agent'],
|
||
status: 'success',
|
||
message: '登出成功'
|
||
});
|
||
|
||
res.json({
|
||
code: 200,
|
||
message: '登出成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('登出错误:', error);
|
||
res.json({
|
||
code: 200,
|
||
message: '登出成功'
|
||
});
|
||
}
|
||
};
|
||
|
||
// 构建菜单树
|
||
function buildMenuTree(menus, parentId = null) {
|
||
return menus
|
||
.filter(menu => menu.parentId === parentId)
|
||
.map(menu => ({
|
||
id: menu.id,
|
||
name: menu.name,
|
||
code: menu.code,
|
||
type: menu.type,
|
||
path: menu.path,
|
||
component: menu.component,
|
||
icon: menu.icon,
|
||
sort: menu.sort,
|
||
visible: menu.visible,
|
||
children: buildMenuTree(menus, menu.id)
|
||
}));
|
||
}
|
||
|
||
// 获取当前用户信息
|
||
exports.getCurrentUser = async (req, res) => {
|
||
try {
|
||
// req.user 由 authMiddleware 附加
|
||
res.json({
|
||
code: 200,
|
||
message: '获取成功',
|
||
data: req.user
|
||
});
|
||
} catch (error) {
|
||
console.error('获取用户信息错误:', error);
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '获取用户信息失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 修改密码
|
||
exports.changePassword = async (req, res) => {
|
||
try {
|
||
const { oldPassword, newPassword } = req.body;
|
||
const userId = req.user.id;
|
||
|
||
// 参数验证
|
||
if (!oldPassword || !newPassword) {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '原密码和新密码不能为空'
|
||
});
|
||
}
|
||
|
||
if (newPassword.length < 6) {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '新密码长度不能少于6位'
|
||
});
|
||
}
|
||
|
||
// 查找用户
|
||
const user = await User.findByPk(userId);
|
||
if (!user) {
|
||
return res.status(404).json({
|
||
code: 404,
|
||
message: '用户不存在'
|
||
});
|
||
}
|
||
|
||
// 验证原密码
|
||
const isPasswordValid = await bcrypt.compare(oldPassword, user.password);
|
||
if (!isPasswordValid) {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '原密码错误'
|
||
});
|
||
}
|
||
|
||
// 加密新密码
|
||
const hashedPassword = await bcrypt.hash(newPassword, 10);
|
||
|
||
// 更新密码
|
||
await user.update({ password: hashedPassword });
|
||
|
||
res.json({
|
||
code: 200,
|
||
message: '密码修改成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('修改密码错误:', error);
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '修改密码失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|