rentease-backend-new/controllers/authController.js

437 lines
12 KiB
JavaScript

const bcrypt = require('bcryptjs');
const User = require('../models/User');
const Role = require('../models/Role');
const Menu = require('../models/Menu');
const Tenant = require('../models/Tenant');
const Category = require('../models/Category');
const Setting = require('../models/Setting');
const { SubscriptionPlan } = require('../models');
const { generateToken } = require('../middleware/auth');
const { logLogin, getClientIp } = require('../utils/logger');
const response = require('../utils/response');
// 登录
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 response.badRequest(res, '用户名和密码不能为空');
}
// 查找用户
const user = await User.findOne({
where: { username },
include: [
{
model: Role,
as: 'role'
},
{
model: Tenant,
as: 'tenant'
}
]
});
if (!user) {
await logLogin({
username,
loginType: 'login',
ip: clientIp,
userAgent,
status: 'fail',
message: '用户名或密码错误'
});
return response.unauthorized(res, '用户名或密码错误');
}
// 检查用户状态
if (user.status === 'disabled') {
await logLogin({
userId: user.id,
username: user.username,
loginType: 'login',
ip: clientIp,
userAgent,
status: 'fail',
message: '账号已被禁用'
});
return response.unauthorized(res, '账号已被禁用');
}
// 验证密码
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 response.unauthorized(res, '用户名或密码错误');
}
// 检查租户计费状态(仅非系统管理员需要检查)
if (user.tenant && user.userType !== 'super_admin') {
const billingStatus = user.tenant.billingStatus;
if (billingStatus === 'trial_expired' || billingStatus === 'paid_expired') {
await logLogin({
userId: user.id,
username: user.username,
loginType: 'login',
ip: clientIp,
userAgent,
status: 'fail',
message: '账户已过期,请续费'
});
return response.forbidden(res, '您的账户已过期,请前往续费', {
expired: true,
billingStatus: billingStatus,
trialEndDate: user.tenant.trialEndDate,
paidEndDate: user.tenant.paidEndDate
});
}
}
// 生成 Token
const token = generateToken(user);
// 获取用户菜单权限
let menus = [];
// 系统管理员返回所有菜单
if (user.userType === 'super_admin') {
const allMenus = await Menu.findAll({
where: {
isDeleted: 0,
status: 'active'
},
order: [['sort', 'ASC']]
});
menus = buildMenuTree(allMenus);
} else if (user.roleId) {
// 租户管理员和普通用户返回角色分配的菜单
const roleData = await Role.findByPk(user.roleId, {
include: [{
model: Menu,
as: 'menus',
where: {
isDeleted: 0,
status: 'active'
},
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
response.success(res, '登录成功', {
token,
userInfo: {
id: user.id,
username: user.username,
nickname: user.nickname,
role: user.role,
tenantId: user.tenantId,
userType: user.userType
},
tenant: user.tenant,
menus
});
} catch (error) {
console.error('登录错误:', error);
await logLogin({
username,
loginType: 'login',
ip: clientIp,
userAgent,
status: 'fail',
message: error.message
});
response.serverError(res, '登录失败', error);
}
};
// 登出
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: '登出成功'
});
response.success(res, '登出成功');
} catch (error) {
console.error('登出错误:', error);
response.success(res, '登出成功');
}
};
// 构建菜单树
function buildMenuTree(menus, parentId = null) {
return menus
.filter(menu => menu.parentId === parentId)
.map(menu => {
// 获取菜单的原始数据(处理 Sequelize 模型对象)
const menuData = menu.toJSON ? menu.toJSON() : menu;
return {
id: menuData.id,
name: menuData.name,
code: menuData.code,
type: menuData.type,
path: menuData.path,
component: menuData.component,
icon: menuData.icon,
sort: menuData.sort,
visible: menuData.visible,
status: menuData.status,
isDeleted: menuData.isDeleted,
parentId: menuData.parentId,
children: buildMenuTree(menus, menuData.id)
};
});
}
// 获取当前用户信息
exports.getCurrentUser = async (req, res) => {
try {
// req.user 由 authMiddleware 附加
response.success(res, '获取成功', req.user);
} catch (error) {
console.error('获取用户信息错误:', error);
response.serverError(res, '获取用户信息失败', error);
}
};
// 修改密码
exports.changePassword = async (req, res) => {
try {
const { oldPassword, newPassword } = req.body;
const userId = req.user.id;
// 参数验证
if (!oldPassword || !newPassword) {
return response.badRequest(res, '原密码和新密码不能为空');
}
if (newPassword.length < 6) {
return response.badRequest(res, '新密码长度不能少于6位');
}
// 查找用户
const user = await User.findByPk(userId);
if (!user) {
return response.notFound(res, '用户不存在');
}
// 验证原密码
const isPasswordValid = await bcrypt.compare(oldPassword, user.password);
if (!isPasswordValid) {
return response.badRequest(res, '原密码错误');
}
// 加密新密码
const hashedPassword = await bcrypt.hash(newPassword, 10);
// 更新密码
await user.update({ password: hashedPassword });
response.success(res, '密码修改成功');
} catch (error) {
console.error('修改密码错误:', error);
response.serverError(res, '修改密码失败', error);
}
};
// 租户自助注册
exports.registerTenant = async (req, res) => {
const {
contactName,
contactPhone,
contactEmail,
adminUsername,
adminPassword
} = req.body;
try {
// 参数验证
if (!contactName || !contactPhone || !contactEmail || !adminUsername || !adminPassword) {
return response.badRequest(res, '请填写所有必填项');
}
// 检查管理员账号是否已存在
const existingUser = await User.findOne({ where: { username: adminUsername } });
if (existingUser) {
return response.badRequest(res, '管理员账号已存在');
}
// 查询默认套餐
const defaultPlan = await SubscriptionPlan.findOne({
where: {
isDefault: true,
isDeleted: 0
}
});
if (!defaultPlan) {
return response.serverError(res, '系统未配置默认套餐,请联系管理员');
}
// 生成租户编码:时间戳 + 随机数
const timestamp = Date.now().toString(36).toUpperCase();
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
const code = `T${timestamp}${random}`;
const now = new Date();
const trialEndDate = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
// 创建租户(使用默认套餐配置)
const tenant = await Tenant.create({
code,
contactName,
contactPhone,
contactEmail,
maxUsers: defaultPlan.maxUsers,
maxApartments: defaultPlan.maxApartments,
maxRooms: defaultPlan.maxRooms,
status: 'active',
billingStatus: 'trial_active',
planId: defaultPlan.id,
trialStartDate: now,
trialEndDate: trialEndDate,
currentPeriodStart: now,
currentPeriodEnd: trialEndDate
});
// 创建租户管理员账号
const hashedPassword = await bcrypt.hash(adminPassword, 10);
const user = await User.create({
username: adminUsername,
password: hashedPassword,
nickname: adminUsername,
tenantId: tenant.id,
userType: 'tenant_admin',
status: 'active'
});
// 创建默认管理员角色并分配所有菜单权限
const adminRole = await Role.create({
name: '管理员',
code: 'admin',
description: '租户默认管理员角色,拥有所有权限',
tenantId: tenant.id,
status: 'active'
});
// 获取基础菜单并分配给角色(新租户只分配基础功能菜单)
const basicMenus = await Menu.findAll({
where: {
isDeleted: 0,
status: 'active',
isBasic: 1
}
});
if (basicMenus.length > 0) {
const menuIds = basicMenus.map(menu => menu.id);
await adminRole.setMenus(menuIds);
}
// 将用户关联到管理员角色
await user.update({ roleId: adminRole.id });
// 初始化默认收支类目
const defaultCategories = [
// 收入类目
{ name: '租金', code: 'rent', type: 'income', sort: 1 },
{ name: '水费', code: 'water', type: 'income', sort: 2 },
{ name: '电费', code: 'electricity', type: 'income', sort: 3 },
{ name: '燃气费', code: 'gas', type: 'income', sort: 4 },
{ name: '押金', code: 'deposit', type: 'income', sort: 5 },
{ name: '物业费', code: 'property_fee', type: 'income', sort: 6 },
{ name: '违约金', code: 'penalty', type: 'income', sort: 7 },
{ name: '其他收入', code: 'other_income', type: 'income', sort: 8 },
// 支出类目
{ name: '维修费', code: 'maintenance', type: 'expense', sort: 1 },
{ name: '中介费', code: 'agency_fee', type: 'expense', sort: 2 },
{ name: '物业费', code: 'property_fee_expense', type: 'expense', sort: 3 },
{ name: '水费', code: 'water_expense', type: 'expense', sort: 4 },
{ name: '电费', code: 'electricity_expense', type: 'expense', sort: 5 },
{ name: '燃气费', code: 'gas_expense', type: 'expense', sort: 6 },
{ name: '装修费', code: 'renovation', type: 'expense', sort: 7 },
{ name: '其他支出', code: 'other_expense', type: 'expense', sort: 8 }
];
for (const category of defaultCategories) {
await Category.create({
...category,
tenantId: tenant.id,
status: 'active',
isDefault: 1,
createBy: user.id
});
}
// 初始化默认系统设置
const defaultSettings = [
{ key: 'expireReminderDays', value: '30', description: '房间到期提前提醒天数' }
];
for (const setting of defaultSettings) {
await Setting.create({
...setting,
tenantId: tenant.id,
createBy: user.id
});
}
response.success(res, '注册成功,欢迎试用', {
tenantId: tenant.id,
code: tenant.code,
status: tenant.status
});
} catch (error) {
console.error('租户注册失败:', error);
response.serverError(res, '注册失败', error);
}
};