登录权限管理
This commit is contained in:
parent
d62de12d1f
commit
6d462df532
33
app.js
33
app.js
|
|
@ -1,15 +1,24 @@
|
|||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const sequelize = require('./config/db');
|
||||
const { authMiddleware } = require('./middleware/auth');
|
||||
const { operationLogMiddleware } = require('./utils/logger');
|
||||
|
||||
// 加载模型关联关系
|
||||
require('./models');
|
||||
|
||||
// 导入路由
|
||||
const regionRoutes = require('./routes/region');
|
||||
const apartmentRoutes = require('./routes/apartment');
|
||||
const roomRoutes = require('./routes/room');
|
||||
const rentalRoutes = require('./routes/rental');
|
||||
const statisticsRoutes = require('./routes/statistics');
|
||||
const waterBillRoutes = require('./routes/waterBill');
|
||||
const electricityBillRoutes = require('./routes/electricityBill');
|
||||
const authRoutes = require('./routes/auth');
|
||||
const userRoutes = require('./routes/user');
|
||||
const roleRoutes = require('./routes/role');
|
||||
const menuRoutes = require('./routes/menu');
|
||||
const logRoutes = require('./routes/log');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
|
@ -19,14 +28,20 @@ app.use(cors());
|
|||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// 路由
|
||||
app.use('/api/regions', regionRoutes);
|
||||
app.use('/api/apartments', apartmentRoutes);
|
||||
app.use('/api/rooms', roomRoutes);
|
||||
app.use('/api/rentals', rentalRoutes);
|
||||
app.use('/api/statistics', statisticsRoutes);
|
||||
app.use('/api/water-bills', waterBillRoutes);
|
||||
app.use('/api/electricity-bills', electricityBillRoutes);
|
||||
// 公开路由(不需要认证)
|
||||
app.use('/api/auth', authRoutes);
|
||||
|
||||
// 需要认证的路由
|
||||
app.use('/api/apartments', authMiddleware, operationLogMiddleware({ module: '公寓管理' }), apartmentRoutes);
|
||||
app.use('/api/rooms', authMiddleware, operationLogMiddleware({ module: '房间管理' }), roomRoutes);
|
||||
app.use('/api/rentals', authMiddleware, operationLogMiddleware({ module: '租房管理' }), rentalRoutes);
|
||||
app.use('/api/statistics', authMiddleware, operationLogMiddleware({ module: '统计分析' }), statisticsRoutes);
|
||||
app.use('/api/water-bills', authMiddleware, operationLogMiddleware({ module: '水费管理' }), waterBillRoutes);
|
||||
app.use('/api/electricity-bills', authMiddleware, operationLogMiddleware({ module: '电费管理' }), electricityBillRoutes);
|
||||
app.use('/api/users', authMiddleware, operationLogMiddleware({ module: '用户管理' }), userRoutes);
|
||||
app.use('/api/roles', authMiddleware, operationLogMiddleware({ module: '角色管理' }), roleRoutes);
|
||||
app.use('/api/menus', authMiddleware, operationLogMiddleware({ module: '菜单管理' }), menuRoutes);
|
||||
app.use('/api/logs', authMiddleware, logRoutes);
|
||||
|
||||
// 测试接口
|
||||
app.get('/', (req, res) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const { Apartment, Region } = require('../models');
|
||||
const { Apartment } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// 格式化时间(考虑时区,转换为北京时间)
|
||||
|
|
@ -11,22 +11,11 @@ const formatDate = (date) => {
|
|||
|
||||
// 格式化公寓数据
|
||||
const formatApartmentData = (apartment) => {
|
||||
const formattedApartment = {
|
||||
return {
|
||||
...apartment.toJSON(),
|
||||
createTime: formatDate(apartment.createTime),
|
||||
updateTime: formatDate(apartment.updateTime)
|
||||
};
|
||||
|
||||
// 格式化关联数据
|
||||
if (formattedApartment.Region) {
|
||||
formattedApartment.Region = {
|
||||
...formattedApartment.Region,
|
||||
createTime: formatDate(formattedApartment.Region.createTime),
|
||||
updateTime: formatDate(formattedApartment.Region.updateTime)
|
||||
};
|
||||
}
|
||||
|
||||
return formattedApartment;
|
||||
};
|
||||
|
||||
// 获取所有公寓(支持搜索和分页)
|
||||
|
|
@ -51,7 +40,6 @@ const getAllApartments = async (req, res) => {
|
|||
// 查询公寓数据
|
||||
const { count, rows } = await Apartment.findAndCountAll({
|
||||
where,
|
||||
include: [Region],
|
||||
limit: parseInt(pageSize),
|
||||
offset: parseInt(offset)
|
||||
});
|
||||
|
|
@ -76,8 +64,7 @@ const getApartmentById = async (req, res) => {
|
|||
try {
|
||||
const { id } = req.params;
|
||||
const apartment = await Apartment.findOne({
|
||||
where: { id, isDeleted: 0 },
|
||||
include: [Region]
|
||||
where: { id, isDeleted: 0 }
|
||||
});
|
||||
if (!apartment) {
|
||||
return res.status(404).json({ error: '公寓不存在' });
|
||||
|
|
@ -92,8 +79,13 @@ const getApartmentById = async (req, res) => {
|
|||
// 创建公寓
|
||||
const createApartment = async (req, res) => {
|
||||
try {
|
||||
const { regionId, name, address } = req.body;
|
||||
const apartment = await Apartment.create({ regionId, name, address });
|
||||
const { name, address } = req.body;
|
||||
const apartment = await Apartment.create({
|
||||
name,
|
||||
address,
|
||||
createBy: req.user.id,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
res.status(201).json(apartment);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
|
|
@ -104,14 +96,18 @@ const createApartment = async (req, res) => {
|
|||
const updateApartment = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { regionId, name, address } = req.body;
|
||||
const { name, address } = req.body;
|
||||
const apartment = await Apartment.findOne({
|
||||
where: { id, isDeleted: 0 }
|
||||
});
|
||||
if (!apartment) {
|
||||
return res.status(404).json({ error: '公寓不存在' });
|
||||
}
|
||||
await apartment.update({ regionId, name, address });
|
||||
await apartment.update({
|
||||
name,
|
||||
address,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
res.status(200).json(apartment);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
|
|
@ -128,7 +124,10 @@ const deleteApartment = async (req, res) => {
|
|||
if (!apartment) {
|
||||
return res.status(404).json({ error: '公寓不存在' });
|
||||
}
|
||||
await apartment.update({ isDeleted: 1 });
|
||||
await apartment.update({
|
||||
isDeleted: 1,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
res.status(200).json({ message: '公寓删除成功' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
|
|
@ -153,8 +152,7 @@ const listApartments = async (req, res) => {
|
|||
|
||||
// 查询公寓数据
|
||||
const apartments = await Apartment.findAll({
|
||||
where,
|
||||
include: [Region]
|
||||
where
|
||||
});
|
||||
|
||||
// 格式化数据
|
||||
|
|
|
|||
|
|
@ -0,0 +1,296 @@
|
|||
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
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
const { Op } = require('sequelize');
|
||||
const OperationLog = require('../models/OperationLog');
|
||||
const LoginLog = require('../models/LoginLog');
|
||||
|
||||
// 获取操作日志列表
|
||||
exports.getOperationLogs = async (req, res) => {
|
||||
try {
|
||||
const { page = 1, pageSize = 20, username, module, action, status, startTime, endTime } = req.query;
|
||||
|
||||
// 构建查询条件
|
||||
const where = {};
|
||||
if (username) {
|
||||
where.username = { [Op.like]: `%${username}%` };
|
||||
}
|
||||
if (module) {
|
||||
where.module = module;
|
||||
}
|
||||
if (action) {
|
||||
where.action = action;
|
||||
}
|
||||
if (status) {
|
||||
where.status = status;
|
||||
}
|
||||
if (startTime && endTime) {
|
||||
where.createTime = {
|
||||
[Op.between]: [new Date(startTime), new Date(endTime)]
|
||||
};
|
||||
}
|
||||
|
||||
// 查询日志列表
|
||||
const { count, rows } = await OperationLog.findAndCountAll({
|
||||
where,
|
||||
order: [['createTime', 'DESC']],
|
||||
offset: (page - 1) * pageSize,
|
||||
limit: parseInt(pageSize)
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
list: rows,
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取操作日志错误:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '获取操作日志失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 获取登录日志列表
|
||||
exports.getLoginLogs = async (req, res) => {
|
||||
try {
|
||||
const { page = 1, pageSize = 20, username, loginType, status, startTime, endTime } = req.query;
|
||||
|
||||
// 构建查询条件
|
||||
const where = {};
|
||||
if (username) {
|
||||
where.username = { [Op.like]: `%${username}%` };
|
||||
}
|
||||
if (loginType) {
|
||||
where.loginType = loginType;
|
||||
}
|
||||
if (status) {
|
||||
where.status = status;
|
||||
}
|
||||
if (startTime && endTime) {
|
||||
where.createTime = {
|
||||
[Op.between]: [new Date(startTime), new Date(endTime)]
|
||||
};
|
||||
}
|
||||
|
||||
// 查询日志列表
|
||||
const { count, rows } = await LoginLog.findAndCountAll({
|
||||
where,
|
||||
order: [['createTime', 'DESC']],
|
||||
offset: (page - 1) * pageSize,
|
||||
limit: parseInt(pageSize)
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
list: rows,
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取登录日志错误:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '获取登录日志失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 清空操作日志
|
||||
exports.clearOperationLogs = async (req, res) => {
|
||||
try {
|
||||
const { startTime, endTime } = req.body;
|
||||
const where = {};
|
||||
|
||||
if (startTime && endTime) {
|
||||
where.createTime = {
|
||||
[Op.between]: [new Date(startTime), new Date(endTime)]
|
||||
};
|
||||
}
|
||||
|
||||
await OperationLog.destroy({ where });
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '清空成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('清空操作日志错误:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '清空操作日志失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 清空登录日志
|
||||
exports.clearLoginLogs = async (req, res) => {
|
||||
try {
|
||||
const { startTime, endTime } = req.body;
|
||||
const where = {};
|
||||
|
||||
if (startTime && endTime) {
|
||||
where.createTime = {
|
||||
[Op.between]: [new Date(startTime), new Date(endTime)]
|
||||
};
|
||||
}
|
||||
|
||||
await LoginLog.destroy({ where });
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '清空成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('清空登录日志错误:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '清空登录日志失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,563 @@
|
|||
const { Op } = require('sequelize');
|
||||
const Menu = require('../models/Menu');
|
||||
const RoleMenu = require('../models/RoleMenu');
|
||||
const { logOperation } = require('../utils/logger');
|
||||
|
||||
// 获取菜单树
|
||||
exports.getMenuTree = async (req, res) => {
|
||||
try {
|
||||
const { type, status } = req.query;
|
||||
|
||||
const where = { isDeleted: false };
|
||||
if (type) {
|
||||
where.type = type;
|
||||
}
|
||||
if (status) {
|
||||
where.status = status;
|
||||
}
|
||||
|
||||
const menus = await Menu.findAll({
|
||||
where,
|
||||
order: [['sort', 'ASC'], ['createdAt', 'ASC']]
|
||||
});
|
||||
|
||||
const menuTree = buildTree(menus);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: menuTree
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取菜单树失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '获取菜单树失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 获取菜单列表(平铺)
|
||||
exports.getMenuList = async (req, res) => {
|
||||
try {
|
||||
const { page = 1, pageSize = 10, name, type, status } = req.query;
|
||||
|
||||
const where = { isDeleted: false };
|
||||
if (name) {
|
||||
where.name = { [Op.like]: `%${name}%` };
|
||||
}
|
||||
if (type) {
|
||||
where.type = type;
|
||||
}
|
||||
if (status) {
|
||||
where.status = status;
|
||||
}
|
||||
|
||||
const { count, rows } = await Menu.findAndCountAll({
|
||||
where,
|
||||
order: [['sort', 'ASC'], ['createdAt', 'ASC']],
|
||||
offset: (page - 1) * pageSize,
|
||||
limit: parseInt(pageSize)
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
list: rows,
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取菜单列表失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '获取菜单列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 获取菜单详情
|
||||
exports.getMenuById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const menu = await Menu.findByPk(id, {
|
||||
where: { isDeleted: false }
|
||||
});
|
||||
|
||||
if (!menu) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '菜单不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: menu
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取菜单详情失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '获取菜单详情失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 创建菜单
|
||||
exports.createMenu = async (req, res) => {
|
||||
try {
|
||||
const { parentId, name, code, type, path, component, icon, sort, visible, status } = req.body;
|
||||
|
||||
if (!name || !code || !type) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '菜单名称、编码和类型不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
if (type === 'menu' && !path) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '菜单类型必须填写路由路径'
|
||||
});
|
||||
}
|
||||
|
||||
const existingMenu = await Menu.findOne({
|
||||
where: { code, isDeleted: false }
|
||||
});
|
||||
|
||||
if (existingMenu) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '菜单编码已存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (parentId) {
|
||||
const parentMenu = await Menu.findByPk(parentId, {
|
||||
where: { isDeleted: false }
|
||||
});
|
||||
if (!parentMenu) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '父菜单不存在'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const menu = await Menu.create({
|
||||
parentId: parentId || null,
|
||||
name,
|
||||
code,
|
||||
type,
|
||||
path: type === 'menu' ? path : null,
|
||||
component,
|
||||
icon,
|
||||
sort: sort || 0,
|
||||
visible: visible || 'show',
|
||||
status: status || 'active',
|
||||
createBy: req.user.id,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
|
||||
await logOperation({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
module: '菜单管理',
|
||||
action: '创建',
|
||||
description: `创建菜单: ${name}`,
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
ip: req.ip,
|
||||
status: 'success'
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '创建成功',
|
||||
data: menu
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建菜单失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '创建菜单失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 更新菜单
|
||||
exports.updateMenu = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { parentId, name, code, type, path, component, icon, sort, visible, status } = req.body;
|
||||
|
||||
const menu = await Menu.findByPk(id, {
|
||||
where: { isDeleted: false }
|
||||
});
|
||||
|
||||
if (!menu) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '菜单不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (code && code !== menu.code) {
|
||||
const existingMenu = await Menu.findOne({
|
||||
where: { code, isDeleted: false, id: { [Op.ne]: id } }
|
||||
});
|
||||
|
||||
if (existingMenu) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '菜单编码已存在'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (parentId) {
|
||||
if (parseInt(parentId) === parseInt(id)) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '不能将菜单设置为自己的子菜单'
|
||||
});
|
||||
}
|
||||
|
||||
const parentMenu = await Menu.findByPk(parentId, {
|
||||
where: { isDeleted: false }
|
||||
});
|
||||
if (!parentMenu) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '父菜单不存在'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
updateBy: req.user.id
|
||||
};
|
||||
if (parentId !== undefined) updateData.parentId = parentId || null;
|
||||
if (name !== undefined) updateData.name = name;
|
||||
if (code !== undefined) updateData.code = code;
|
||||
if (type !== undefined) updateData.type = type;
|
||||
if (path !== undefined) updateData.path = path;
|
||||
if (component !== undefined) updateData.component = component;
|
||||
if (icon !== undefined) updateData.icon = icon;
|
||||
if (sort !== undefined) updateData.sort = sort;
|
||||
if (visible !== undefined) updateData.visible = visible;
|
||||
if (status !== undefined) updateData.status = status;
|
||||
|
||||
await menu.update(updateData);
|
||||
|
||||
await logOperation({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
module: '菜单管理',
|
||||
action: '更新',
|
||||
description: `更新菜单: ${menu.name}`,
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
ip: req.ip,
|
||||
status: 'success'
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '更新成功',
|
||||
data: menu
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新菜单失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '更新菜单失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 删除菜单
|
||||
exports.deleteMenu = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const menu = await Menu.findByPk(id, {
|
||||
where: { isDeleted: false }
|
||||
});
|
||||
|
||||
if (!menu) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '菜单不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const children = await Menu.findAll({
|
||||
where: { parentId: id, isDeleted: false }
|
||||
});
|
||||
|
||||
if (children.length > 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '该菜单下有子菜单,无法删除'
|
||||
});
|
||||
}
|
||||
|
||||
await menu.update({
|
||||
isDeleted: true,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
|
||||
await RoleMenu.destroy({
|
||||
where: { menuId: id }
|
||||
});
|
||||
|
||||
await logOperation({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
module: '菜单管理',
|
||||
action: '删除',
|
||||
description: `删除菜单: ${menu.name}`,
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
ip: req.ip,
|
||||
status: 'success'
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('删除菜单失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '删除菜单失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 获取角色的菜单权限
|
||||
exports.getRoleMenus = async (req, res) => {
|
||||
try {
|
||||
const { roleId } = req.params;
|
||||
|
||||
const role = await Menu.findOne({
|
||||
where: { id: roleId, isDeleted: false }
|
||||
});
|
||||
|
||||
if (!role) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '角色不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const Role = require('../models/Role');
|
||||
const roleData = await Role.findByPk(roleId, {
|
||||
include: [{
|
||||
model: Menu,
|
||||
as: 'menus',
|
||||
where: { isDeleted: false },
|
||||
through: { attributes: [] }
|
||||
}]
|
||||
});
|
||||
|
||||
const menus = roleData ? roleData.menus : [];
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: menus
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取角色菜单失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '获取角色菜单失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 分配菜单权限给角色
|
||||
exports.assignMenusToRole = async (req, res) => {
|
||||
try {
|
||||
const { roleId } = req.params;
|
||||
const { menuIds } = req.body;
|
||||
|
||||
if (!Array.isArray(menuIds)) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '菜单ID必须是数组'
|
||||
});
|
||||
}
|
||||
|
||||
const Role = require('../models/Role');
|
||||
const role = await Role.findByPk(roleId);
|
||||
|
||||
if (!role) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '角色不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await RoleMenu.destroy({
|
||||
where: { roleId }
|
||||
});
|
||||
|
||||
if (menuIds.length > 0) {
|
||||
const roleMenus = menuIds.map(menuId => ({
|
||||
roleId,
|
||||
menuId
|
||||
}));
|
||||
await RoleMenu.bulkCreate(roleMenus);
|
||||
}
|
||||
|
||||
await logOperation({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
module: '菜单管理',
|
||||
action: '分配权限',
|
||||
description: `为角色 ${role.name} 分配菜单权限`,
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
ip: req.ip,
|
||||
status: 'success'
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '分配成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('分配菜单权限失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '分配菜单权限失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 获取当前用户的菜单权限
|
||||
exports.getUserMenus = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const roleId = req.user.roleId;
|
||||
|
||||
// 获取用户角色
|
||||
const User = require('../models/User');
|
||||
const user = await User.findByPk(userId, {
|
||||
include: [{
|
||||
model: require('../models/Role'),
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
// 超级管理员(isSuperAdmin为1或角色code为admin)返回所有菜单
|
||||
if (user.isSuperAdmin === 1 || (user.role && user.role.code === 'admin')) {
|
||||
// 先获取所有可见的菜单
|
||||
const menuList = await Menu.findAll({
|
||||
where: {
|
||||
isDeleted: false,
|
||||
status: 'active',
|
||||
visible: 'show'
|
||||
},
|
||||
order: [['sort', 'ASC']]
|
||||
});
|
||||
|
||||
// 再获取所有按钮
|
||||
const buttonList = await Menu.findAll({
|
||||
where: {
|
||||
isDeleted: false,
|
||||
status: 'active',
|
||||
type: 'button'
|
||||
},
|
||||
order: [['sort', 'ASC']]
|
||||
});
|
||||
|
||||
// 合并菜单和按钮
|
||||
const allMenus = [...menuList, ...buttonList];
|
||||
const menuTree = buildTree(allMenus);
|
||||
|
||||
return res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: menuTree
|
||||
});
|
||||
}
|
||||
|
||||
if (!user.role) {
|
||||
return res.status(403).json({
|
||||
code: 403,
|
||||
message: '用户没有分配角色'
|
||||
});
|
||||
}
|
||||
|
||||
// 普通用户返回角色分配的菜单
|
||||
const Role = require('../models/Role');
|
||||
const roleData = await Role.findByPk(roleId, {
|
||||
include: [{
|
||||
model: Menu,
|
||||
as: 'menus',
|
||||
where: {
|
||||
isDeleted: false,
|
||||
status: 'active'
|
||||
},
|
||||
through: { attributes: [] }
|
||||
}]
|
||||
});
|
||||
|
||||
if (!roleData || !roleData.menus) {
|
||||
return res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: []
|
||||
});
|
||||
}
|
||||
|
||||
// 按sort排序
|
||||
const menus = roleData.menus.sort((a, b) => a.sort - b.sort);
|
||||
|
||||
// 构建菜单树
|
||||
const menuTree = buildTree(menus);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: menuTree
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取用户菜单失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '获取用户菜单失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 构建菜单树
|
||||
function buildTree(menus, parentId = null) {
|
||||
return menus
|
||||
.filter(menu => menu.parentId === parentId)
|
||||
.map(menu => ({
|
||||
...menu.dataValues,
|
||||
children: buildTree(menus, menu.id)
|
||||
}));
|
||||
}
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
const { Region } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// 格式化时间(考虑时区,转换为北京时间)
|
||||
const formatDate = (date) => {
|
||||
if (!date) return null;
|
||||
// 创建一个新的Date对象,加上8小时的时区偏移
|
||||
const beijingDate = new Date(date.getTime() + 8 * 60 * 60 * 1000);
|
||||
return beijingDate.toISOString().split('T')[0];
|
||||
};
|
||||
|
||||
// 格式化区域数据
|
||||
const formatRegionData = (region) => {
|
||||
return {
|
||||
...region.toJSON(),
|
||||
createTime: formatDate(region.createTime),
|
||||
updateTime: formatDate(region.updateTime)
|
||||
};
|
||||
};
|
||||
|
||||
// 获取所有区域
|
||||
const getAllRegions = async (req, res) => {
|
||||
try {
|
||||
const regions = await Region.findAll({
|
||||
where: { isDeleted: 0 }
|
||||
});
|
||||
const formattedRegions = regions.map(formatRegionData);
|
||||
res.status(200).json(formattedRegions);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// 获取单个区域
|
||||
const getRegionById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const region = await Region.findOne({
|
||||
where: { id, isDeleted: 0 }
|
||||
});
|
||||
if (!region) {
|
||||
return res.status(404).json({ error: '区域不存在' });
|
||||
}
|
||||
const formattedRegion = formatRegionData(region);
|
||||
res.status(200).json(formattedRegion);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// 创建区域
|
||||
const createRegion = async (req, res) => {
|
||||
try {
|
||||
const { name, description } = req.body;
|
||||
const region = await Region.create({ name, description });
|
||||
res.status(201).json(region);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// 更新区域
|
||||
const updateRegion = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name, description } = req.body;
|
||||
const region = await Region.findOne({
|
||||
where: { id, isDeleted: 0 }
|
||||
});
|
||||
if (!region) {
|
||||
return res.status(404).json({ error: '区域不存在' });
|
||||
}
|
||||
await region.update({ name, description });
|
||||
res.status(200).json(region);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// 删除区域(软删除)
|
||||
const deleteRegion = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const region = await Region.findOne({
|
||||
where: { id, isDeleted: 0 }
|
||||
});
|
||||
if (!region) {
|
||||
return res.status(404).json({ error: '区域不存在' });
|
||||
}
|
||||
await region.update({ isDeleted: 1 });
|
||||
res.status(200).json({ message: '区域删除成功' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// 获取所有区域(不分页)
|
||||
const listRegions = async (req, res) => {
|
||||
try {
|
||||
const { name } = req.query;
|
||||
|
||||
// 构建查询条件
|
||||
const where = { isDeleted: 0 };
|
||||
if (name) {
|
||||
where.name = {
|
||||
[Op.like]: `%${name}%`
|
||||
};
|
||||
}
|
||||
|
||||
const regions = await Region.findAll({ where });
|
||||
const formattedRegions = regions.map(formatRegionData);
|
||||
res.status(200).json(formattedRegions);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getAllRegions,
|
||||
listRegions,
|
||||
getRegionById,
|
||||
createRegion,
|
||||
updateRegion,
|
||||
deleteRegion
|
||||
};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
const { Rental, Room, Apartment, Region } = require('../models');
|
||||
const { Rental, Room, Apartment } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// 格式化时间(考虑时区,转换为北京时间)
|
||||
|
|
@ -122,13 +122,7 @@ const getAllRentals = async (req, res) => {
|
|||
include: [
|
||||
{
|
||||
model: Apartment,
|
||||
where: { isDeleted: 0 },
|
||||
include: [
|
||||
{
|
||||
model: Region,
|
||||
where: { isDeleted: 0 }
|
||||
}
|
||||
]
|
||||
where: { isDeleted: 0 }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -253,7 +247,9 @@ const createRental = async (req, res) => {
|
|||
endDate: body.endDate,
|
||||
rent: body.rent,
|
||||
deposit: deposit,
|
||||
status: body.status || 'active'
|
||||
status: body.status || 'active',
|
||||
createBy: req.user.id,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
|
||||
console.log('租房记录:', rental);
|
||||
|
|
@ -281,7 +277,16 @@ const updateRental = async (req, res) => {
|
|||
}
|
||||
// 处理押金,为空时设置为0
|
||||
const updateDeposit = deposit || 0;
|
||||
await rental.update({ roomId, tenantName, startDate, endDate, rent, deposit: updateDeposit, status });
|
||||
await rental.update({
|
||||
roomId,
|
||||
tenantName,
|
||||
startDate,
|
||||
endDate,
|
||||
rent,
|
||||
deposit: updateDeposit,
|
||||
status,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
res.status(200).json(rental);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
|
|
@ -298,7 +303,10 @@ const deleteRental = async (req, res) => {
|
|||
if (!rental) {
|
||||
return res.status(404).json({ error: '租房记录不存在' });
|
||||
}
|
||||
await rental.update({ isDeleted: 1 });
|
||||
await rental.update({
|
||||
isDeleted: 1,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
res.status(200).json({ message: '租房记录删除成功' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
|
|
@ -351,13 +359,7 @@ const listRentals = async (req, res) => {
|
|||
include: [
|
||||
{
|
||||
model: Apartment,
|
||||
where: { isDeleted: 0 },
|
||||
include: [
|
||||
{
|
||||
model: Region,
|
||||
where: { isDeleted: 0 }
|
||||
}
|
||||
]
|
||||
where: { isDeleted: 0 }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,319 @@
|
|||
const { Op } = require('sequelize');
|
||||
const Role = require('../models/Role');
|
||||
const { logOperation } = require('../utils/logger');
|
||||
|
||||
// 获取角色列表
|
||||
exports.getRoles = async (req, res) => {
|
||||
try {
|
||||
const { page = 1, pageSize = 10, name, status } = req.query;
|
||||
|
||||
const where = { isDeleted: 0 };
|
||||
if (name) where.name = { [Op.like]: `%${name}%` };
|
||||
if (status) where.status = status;
|
||||
|
||||
const { count, rows: roles } = await Role.findAndCountAll({
|
||||
where,
|
||||
limit: parseInt(pageSize),
|
||||
offset: (parseInt(page) - 1) * parseInt(pageSize),
|
||||
order: [['id', 'DESC']]
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取角色列表成功',
|
||||
data: {
|
||||
list: roles,
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize)
|
||||
}
|
||||
});
|
||||
|
||||
// 记录操作日志
|
||||
await logOperation({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
module: '角色管理',
|
||||
action: '查询',
|
||||
description: '获取角色列表',
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
ip: req.ip,
|
||||
status: 'success'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取角色列表错误:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '获取角色列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 获取角色详情
|
||||
exports.getRoleById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const role = await Role.findByPk(id, {
|
||||
where: { isDeleted: 0 }
|
||||
});
|
||||
|
||||
if (!role) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '角色不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取角色详情成功',
|
||||
data: role
|
||||
});
|
||||
|
||||
// 记录操作日志
|
||||
await logOperation({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
module: '角色管理',
|
||||
action: '查询',
|
||||
description: `获取角色详情,ID: ${id}`,
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
ip: req.ip,
|
||||
status: 'success'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取角色详情错误:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '获取角色详情失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 创建角色
|
||||
exports.createRole = async (req, res) => {
|
||||
try {
|
||||
const { name, code, description, permissions, status } = req.body;
|
||||
|
||||
// 参数验证
|
||||
if (!name || !code) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '角色名称和编码不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查角色编码是否已存在
|
||||
const existingRole = await Role.findOne({
|
||||
where: { code, isDeleted: 0 }
|
||||
});
|
||||
|
||||
if (existingRole) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '角色编码已存在'
|
||||
});
|
||||
}
|
||||
|
||||
const role = await Role.create({
|
||||
name,
|
||||
code,
|
||||
description,
|
||||
permissions,
|
||||
status: status || 'active',
|
||||
createBy: req.user.id,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '创建角色成功',
|
||||
data: role
|
||||
});
|
||||
|
||||
// 记录操作日志
|
||||
await logOperation({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
module: '角色管理',
|
||||
action: '创建',
|
||||
description: `创建角色: ${name}`,
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
ip: req.ip,
|
||||
status: 'success'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建角色错误:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '创建角色失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 更新角色
|
||||
exports.updateRole = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name, code, description, permissions, status } = req.body;
|
||||
|
||||
// 查找角色
|
||||
const role = await Role.findByPk(id, {
|
||||
where: { isDeleted: 0 }
|
||||
});
|
||||
|
||||
if (!role) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '角色不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查角色编码是否已存在(排除当前角色)
|
||||
if (code && code !== role.code) {
|
||||
const existingRole = await Role.findOne({
|
||||
where: { code, isDeleted: 0, id: { [Op.ne]: id } }
|
||||
});
|
||||
|
||||
if (existingRole) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '角色编码已存在'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await role.update({
|
||||
name: name || role.name,
|
||||
code: code || role.code,
|
||||
description: description !== undefined ? description : role.description,
|
||||
permissions: permissions !== undefined ? permissions : role.permissions,
|
||||
status: status || role.status,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '更新角色成功',
|
||||
data: role
|
||||
});
|
||||
|
||||
// 记录操作日志
|
||||
await logOperation({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
module: '角色管理',
|
||||
action: '更新',
|
||||
description: `更新角色: ${role.name}`,
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
ip: req.ip,
|
||||
status: 'success'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新角色错误:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '更新角色失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 删除角色
|
||||
exports.deleteRole = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
// 查找角色
|
||||
const role = await Role.findByPk(id, {
|
||||
where: { isDeleted: 0 }
|
||||
});
|
||||
|
||||
if (!role) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '角色不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查是否有用户使用此角色
|
||||
const userCount = await role.countUsers({
|
||||
where: { isDeleted: 0 }
|
||||
});
|
||||
|
||||
if (userCount > 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '该角色下还有用户,无法删除'
|
||||
});
|
||||
}
|
||||
|
||||
// 软删除
|
||||
await role.update({
|
||||
isDeleted: 1,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '删除角色成功'
|
||||
});
|
||||
|
||||
// 记录操作日志
|
||||
await logOperation({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
module: '角色管理',
|
||||
action: '删除',
|
||||
description: `删除角色: ${role.name}`,
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
ip: req.ip,
|
||||
status: 'success'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('删除角色错误:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '删除角色失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 获取所有角色(用于下拉选择)
|
||||
exports.getAllRoles = async (req, res) => {
|
||||
try {
|
||||
const roles = await Role.findAll({
|
||||
where: { isDeleted: 0, status: 'active' },
|
||||
attributes: ['id', 'name', 'code']
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取角色列表成功',
|
||||
data: roles
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取角色列表错误:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '获取角色列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -239,7 +239,9 @@ const createRoom = async (req, res) => {
|
|||
yearlyPrice: yearlyPrice === '' ? null : yearlyPrice,
|
||||
status,
|
||||
subStatus,
|
||||
otherStatus
|
||||
otherStatus,
|
||||
createBy: req.user.id,
|
||||
updateBy: req.user.id
|
||||
};
|
||||
const room = await Room.create(processedData);
|
||||
res.status(201).json(room);
|
||||
|
|
@ -268,7 +270,8 @@ const updateRoom = async (req, res) => {
|
|||
yearlyPrice: yearlyPrice === '' ? null : yearlyPrice,
|
||||
status,
|
||||
subStatus,
|
||||
otherStatus
|
||||
otherStatus,
|
||||
updateBy: req.user.id
|
||||
};
|
||||
await room.update(processedData);
|
||||
res.status(200).json(room);
|
||||
|
|
@ -287,7 +290,10 @@ const deleteRoom = async (req, res) => {
|
|||
if (!room) {
|
||||
return res.status(404).json({ error: '房间不存在' });
|
||||
}
|
||||
await room.update({ isDeleted: 1 });
|
||||
await room.update({
|
||||
isDeleted: 1,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
res.status(200).json({ message: '房间删除成功' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const { Room, Rental, Apartment, Region } = require('../models');
|
||||
const { Room, Rental, Apartment } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// 租金统计
|
||||
|
|
@ -111,156 +111,14 @@ const getRoomStatusStatistics = async (req, res) => {
|
|||
}
|
||||
};
|
||||
|
||||
// 区域房屋统计
|
||||
const getRegionHouseStatistics = async (req, res) => {
|
||||
try {
|
||||
const regions = await Region.findAll({
|
||||
where: { isDeleted: 0 },
|
||||
include: [
|
||||
{
|
||||
model: Apartment,
|
||||
where: { isDeleted: 0 },
|
||||
include: [{
|
||||
model: Room,
|
||||
where: { isDeleted: 0 }
|
||||
}]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const regionHouseStatistics = regions.map(region => {
|
||||
let empty = 0;
|
||||
let reserved = 0;
|
||||
let rented = 0;
|
||||
let soon_expire = 0;
|
||||
let expired = 0;
|
||||
let cleaning = 0;
|
||||
let maintenance = 0;
|
||||
|
||||
region.Apartments.forEach(apartment => {
|
||||
apartment.Rooms.forEach(room => {
|
||||
if (room.status === 'empty') {
|
||||
empty++;
|
||||
} else if (room.status === 'reserved') {
|
||||
reserved++;
|
||||
} else if (room.status === 'rented') {
|
||||
rented++;
|
||||
// 统计附属状态
|
||||
if (room.subStatus === 'soon_expire') {
|
||||
soon_expire++;
|
||||
} else if (room.subStatus === 'expired') {
|
||||
expired++;
|
||||
}
|
||||
// 统计其他状态
|
||||
if (room.otherStatus === 'cleaning') {
|
||||
cleaning++;
|
||||
} else if (room.otherStatus === 'maintenance') {
|
||||
maintenance++;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
region: region.name,
|
||||
empty,
|
||||
reserved,
|
||||
rented,
|
||||
soon_expire,
|
||||
expired,
|
||||
cleaning,
|
||||
maintenance,
|
||||
total: empty + reserved + rented
|
||||
};
|
||||
});
|
||||
|
||||
res.status(200).json(regionHouseStatistics);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// 区域公寓房间状态分布
|
||||
const getRegionApartmentHouseStatistics = async (req, res) => {
|
||||
try {
|
||||
const regions = await Region.findAll({
|
||||
where: { isDeleted: 0 },
|
||||
include: [
|
||||
{
|
||||
model: Apartment,
|
||||
where: { isDeleted: 0 },
|
||||
include: [{
|
||||
model: Room,
|
||||
where: { isDeleted: 0 }
|
||||
}]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const apartmentHouseStatistics = [];
|
||||
|
||||
regions.forEach(region => {
|
||||
region.Apartments.forEach(apartment => {
|
||||
let empty = 0;
|
||||
let reserved = 0;
|
||||
let rented = 0;
|
||||
let soon_expire = 0;
|
||||
let expired = 0;
|
||||
let cleaning = 0;
|
||||
let maintenance = 0;
|
||||
|
||||
apartment.Rooms.forEach(room => {
|
||||
if (room.status === 'empty') {
|
||||
empty++;
|
||||
} else if (room.status === 'reserved') {
|
||||
reserved++;
|
||||
} else if (room.status === 'rented') {
|
||||
rented++;
|
||||
// 统计附属状态
|
||||
if (room.subStatus === 'soon_expire') {
|
||||
soon_expire++;
|
||||
} else if (room.subStatus === 'expired') {
|
||||
expired++;
|
||||
}
|
||||
// 统计其他状态
|
||||
if (room.otherStatus === 'cleaning') {
|
||||
cleaning++;
|
||||
} else if (room.otherStatus === 'maintenance') {
|
||||
maintenance++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
apartmentHouseStatistics.push({
|
||||
region: region.name,
|
||||
apartment: apartment.name,
|
||||
empty,
|
||||
reserved,
|
||||
rented,
|
||||
soon_expire,
|
||||
expired,
|
||||
cleaning,
|
||||
maintenance,
|
||||
total: empty + reserved + rented
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
res.status(200).json(apartmentHouseStatistics);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// Dashboard统计数据
|
||||
const getDashboardStatistics = async (req, res) => {
|
||||
try {
|
||||
// 导入所有需要的模型
|
||||
const { Region, Apartment, Room, WaterBill, Rental } = require('../models');
|
||||
const { Apartment, Room, WaterBill, Rental } = require('../models');
|
||||
|
||||
// 并行查询所有统计数据
|
||||
const [regionCount, apartmentCount, roomCount, emptyRoomCount, reservedRoomCount, rentedRoomCount, soonExpireRoomCount, expiredRoomCount, collectedRentAmount, collectedWaterAmount] = await Promise.all([
|
||||
Region.count({ where: { isDeleted: 0 } }),
|
||||
const [apartmentCount, roomCount, emptyRoomCount, reservedRoomCount, rentedRoomCount, soonExpireRoomCount, expiredRoomCount, collectedRentAmount, collectedWaterAmount] = await Promise.all([
|
||||
Apartment.count({ where: { isDeleted: 0 } }),
|
||||
Room.count({ where: { isDeleted: 0 } }),
|
||||
Room.count({ where: { status: 'empty', isDeleted: 0 } }),
|
||||
|
|
@ -276,7 +134,6 @@ const getDashboardStatistics = async (req, res) => {
|
|||
|
||||
// 构造响应数据
|
||||
const dashboardStatistics = {
|
||||
regionCount,
|
||||
apartmentCount,
|
||||
roomCount,
|
||||
emptyRoomCount,
|
||||
|
|
@ -295,10 +152,70 @@ const getDashboardStatistics = async (req, res) => {
|
|||
}
|
||||
};
|
||||
|
||||
// 公寓房间状态分布统计
|
||||
const getApartmentRoomStatusStatistics = async (req, res) => {
|
||||
try {
|
||||
// 获取所有公寓(排除已删除的)
|
||||
const apartments = await Apartment.findAll({ where: { isDeleted: 0 } });
|
||||
|
||||
// 获取所有房间(排除已删除的)
|
||||
const rooms = await Room.findAll({ where: { isDeleted: 0 } });
|
||||
|
||||
// 构建公寓房间状态分布数据
|
||||
const apartmentRoomStatusStatistics = apartments.map(apartment => {
|
||||
const apartmentRooms = rooms.filter(room => room.apartmentId === apartment.id);
|
||||
|
||||
let empty = 0;
|
||||
let reserved = 0;
|
||||
let rented = 0;
|
||||
let soon_expire = 0;
|
||||
let expired = 0;
|
||||
let cleaning = 0;
|
||||
let maintenance = 0;
|
||||
|
||||
apartmentRooms.forEach(room => {
|
||||
if (room.status === 'empty') {
|
||||
empty++;
|
||||
} else if (room.status === 'reserved') {
|
||||
reserved++;
|
||||
} else if (room.status === 'rented') {
|
||||
rented++;
|
||||
if (room.subStatus === 'soon_expire') {
|
||||
soon_expire++;
|
||||
} else if (room.subStatus === 'expired') {
|
||||
expired++;
|
||||
}
|
||||
if (room.otherStatus === 'cleaning') {
|
||||
cleaning++;
|
||||
} else if (room.otherStatus === 'maintenance') {
|
||||
maintenance++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
apartment: apartment.name,
|
||||
empty,
|
||||
reserved,
|
||||
rented,
|
||||
soon_expire,
|
||||
expired,
|
||||
cleaning,
|
||||
maintenance,
|
||||
total: empty + reserved + rented
|
||||
};
|
||||
});
|
||||
|
||||
res.status(200).json(apartmentRoomStatusStatistics);
|
||||
} catch (error) {
|
||||
console.error('获取公寓房间状态分布数据时出错:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getRentStatistics,
|
||||
getRoomStatusStatistics,
|
||||
getRegionHouseStatistics,
|
||||
getRegionApartmentHouseStatistics,
|
||||
getDashboardStatistics
|
||||
getDashboardStatistics,
|
||||
getApartmentRoomStatusStatistics
|
||||
};
|
||||
|
|
@ -0,0 +1,587 @@
|
|||
const bcrypt = require('bcryptjs');
|
||||
const { Op } = require('sequelize');
|
||||
const User = require('../models/User');
|
||||
const Role = require('../models/Role');
|
||||
const { logOperation } = require('../utils/logger');
|
||||
|
||||
// 获取用户列表
|
||||
exports.getUserList = async (req, res) => {
|
||||
try {
|
||||
const { page = 1, pageSize = 10, username, roleId, status } = req.query;
|
||||
|
||||
// 构建查询条件
|
||||
const where = { isDeleted: 0 };
|
||||
if (username) {
|
||||
where.username = { [Op.like]: `%${username}%` };
|
||||
}
|
||||
if (roleId) {
|
||||
where.roleId = roleId;
|
||||
}
|
||||
if (status) {
|
||||
where.status = status;
|
||||
}
|
||||
|
||||
// 查询用户列表
|
||||
const { count, rows } = await User.findAndCountAll({
|
||||
where,
|
||||
attributes: ['id', 'username', 'nickname', 'roleId', 'status', 'createdAt', 'updatedAt'],
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role',
|
||||
attributes: ['id', 'name', 'code']
|
||||
}],
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: (page - 1) * pageSize,
|
||||
limit: parseInt(pageSize)
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
list: rows,
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取用户列表错误:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '获取用户列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 获取用户详情
|
||||
exports.getUserById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const user = await User.findByPk(id, {
|
||||
where: { isDeleted: 0 },
|
||||
attributes: ['id', 'username', 'nickname', 'roleId', 'status', 'createdAt', 'updatedAt'],
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role',
|
||||
attributes: ['id', 'name', 'code']
|
||||
}]
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: user
|
||||
});
|
||||
|
||||
// 记录操作日志
|
||||
await logOperation({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
module: '用户管理',
|
||||
action: '查询',
|
||||
description: `获取用户详情,ID: ${id}`,
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
ip: req.ip,
|
||||
status: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取用户详情错误:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '获取用户详情失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 创建用户
|
||||
exports.createUser = async (req, res) => {
|
||||
try {
|
||||
const { username, password, nickname, roleId } = req.body;
|
||||
|
||||
// 参数验证
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '用户名和密码不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
if (username.length < 3 || username.length > 20) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '用户名长度应在3-20个字符之间'
|
||||
});
|
||||
}
|
||||
|
||||
if (password.length < 6 || password.length > 20) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '密码长度应在6-20个字符之间'
|
||||
});
|
||||
}
|
||||
|
||||
if (!roleId) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '角色不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查角色是否存在
|
||||
const role = await Role.findByPk(roleId, {
|
||||
where: { isDeleted: 0, status: 'active' }
|
||||
});
|
||||
if (!role) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '角色不存在或已禁用'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查用户名是否已存在
|
||||
const existingUser = await User.findOne({ where: { username, isDeleted: 0 } });
|
||||
if (existingUser) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '用户名已存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 加密密码
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
// 创建用户
|
||||
const user = await User.create({
|
||||
username,
|
||||
password: hashedPassword,
|
||||
nickname: nickname || null,
|
||||
roleId,
|
||||
createBy: req.user.id,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '创建成功',
|
||||
data: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
nickname: user.nickname,
|
||||
roleId: user.roleId,
|
||||
createdAt: user.createdAt
|
||||
}
|
||||
});
|
||||
|
||||
// 记录操作日志
|
||||
await logOperation({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
module: '用户管理',
|
||||
action: '创建',
|
||||
description: `创建用户: ${username}`,
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
ip: req.ip,
|
||||
status: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建用户错误:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '创建用户失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 更新用户
|
||||
exports.updateUser = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { nickname, roleId, status } = req.body;
|
||||
|
||||
// 查找用户
|
||||
const user = await User.findByPk(id, {
|
||||
where: { isDeleted: 0 }
|
||||
});
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查角色是否存在
|
||||
if (roleId) {
|
||||
const role = await Role.findByPk(roleId, {
|
||||
where: { isDeleted: 0, status: 'active' }
|
||||
});
|
||||
if (!role) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '角色不存在或已禁用'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 构建更新数据
|
||||
const updateData = {
|
||||
updateBy: req.user.id
|
||||
};
|
||||
if (nickname !== undefined) updateData.nickname = nickname || null;
|
||||
if (roleId !== undefined) updateData.roleId = roleId;
|
||||
if (status !== undefined) updateData.status = status;
|
||||
|
||||
// 更新用户
|
||||
await user.update(updateData);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '更新成功',
|
||||
data: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
nickname: user.nickname,
|
||||
roleId: user.roleId,
|
||||
status: user.status,
|
||||
updatedAt: user.updatedAt
|
||||
}
|
||||
});
|
||||
|
||||
// 记录操作日志
|
||||
await logOperation({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
module: '用户管理',
|
||||
action: '更新',
|
||||
description: `更新用户: ${user.username}`,
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
ip: req.ip,
|
||||
status: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新用户错误:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '更新用户失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 删除用户
|
||||
exports.deleteUser = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
// 不能删除自己
|
||||
if (parseInt(id) === req.user.id) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '不能删除自己的账号'
|
||||
});
|
||||
}
|
||||
|
||||
// 查找用户
|
||||
const user = await User.findByPk(id, {
|
||||
where: { isDeleted: 0 }
|
||||
});
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 软删除
|
||||
await user.update({
|
||||
isDeleted: 1,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '删除成功'
|
||||
});
|
||||
|
||||
// 记录操作日志
|
||||
await logOperation({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
module: '用户管理',
|
||||
action: '删除',
|
||||
description: `删除用户: ${user.username}`,
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
ip: req.ip,
|
||||
status: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('删除用户错误:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '删除用户失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 重置用户密码
|
||||
exports.resetUserPassword = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const defaultPassword = '123456';
|
||||
|
||||
// 查找用户
|
||||
const user = await User.findByPk(id, {
|
||||
where: { isDeleted: 0 }
|
||||
});
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 加密默认密码
|
||||
const hashedPassword = await bcrypt.hash(defaultPassword, 10);
|
||||
|
||||
// 更新密码
|
||||
await user.update({
|
||||
password: hashedPassword,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '密码重置成功',
|
||||
data: {
|
||||
defaultPassword
|
||||
}
|
||||
});
|
||||
|
||||
// 记录操作日志
|
||||
await logOperation({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
module: '用户管理',
|
||||
action: '重置密码',
|
||||
description: `重置用户密码: ${user.username}`,
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
ip: req.ip,
|
||||
status: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('重置密码错误:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '重置密码失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 获取用户列表(用于下拉选择)
|
||||
exports.getAllUsers = async (req, res) => {
|
||||
try {
|
||||
const users = await User.findAll({
|
||||
where: { isDeleted: 0, status: 'active' },
|
||||
attributes: ['id', 'username', 'nickname']
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取用户列表成功',
|
||||
data: users
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取用户列表错误:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '获取用户列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 获取当前用户信息
|
||||
exports.getCurrentUserInfo = async (req, res) => {
|
||||
try {
|
||||
const user = await User.findByPk(req.user.id, {
|
||||
attributes: ['id', 'username', 'nickname', 'status', 'createdAt', 'updatedAt'],
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role',
|
||||
attributes: ['id', 'name', 'code']
|
||||
}]
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取用户信息成功',
|
||||
data: user
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '获取用户信息失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 更新个人资料
|
||||
exports.updateUserProfile = async (req, res) => {
|
||||
try {
|
||||
const { nickname } = req.body;
|
||||
|
||||
// 验证昵称
|
||||
if (!nickname || nickname.trim().length === 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '昵称不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
const user = await User.findByPk(req.user.id);
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await user.update({
|
||||
nickname,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
|
||||
// 记录操作日志
|
||||
await logOperation({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
module: '个人中心',
|
||||
action: '更新资料',
|
||||
description: '更新个人资料',
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
ip: req.ip,
|
||||
status: 'success'
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '个人资料更新成功',
|
||||
data: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
nickname: user.nickname,
|
||||
status: user.status,
|
||||
createdAt: user.createdAt,
|
||||
updatedAt: user.updatedAt
|
||||
}
|
||||
});
|
||||
} 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;
|
||||
|
||||
// 验证参数
|
||||
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(req.user.id);
|
||||
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,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
|
||||
// 记录操作日志
|
||||
await logOperation({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
module: '个人中心',
|
||||
action: '修改密码',
|
||||
description: '修改个人密码',
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
ip: req.ip,
|
||||
status: 'success'
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '密码修改成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('修改密码失败:', error);
|
||||
res.status(500).json({
|
||||
code: 500,
|
||||
message: '修改密码失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -134,7 +134,9 @@ const createWaterBill = async (req, res) => {
|
|||
usage,
|
||||
unitPrice,
|
||||
amount,
|
||||
status: status || 'unpaid'
|
||||
status: status || 'unpaid',
|
||||
createBy: req.user.id,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
|
||||
const formattedBill = formatWaterBillData(bill);
|
||||
|
|
@ -172,7 +174,8 @@ const updateWaterBill = async (req, res) => {
|
|||
usage,
|
||||
unitPrice,
|
||||
amount,
|
||||
status: status !== undefined ? status : bill.status
|
||||
status: status !== undefined ? status : bill.status,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
|
||||
const formattedBill = formatWaterBillData(bill);
|
||||
|
|
@ -192,7 +195,10 @@ const deleteWaterBill = async (req, res) => {
|
|||
if (!bill) {
|
||||
return res.status(404).json({ error: '水费记录不存在' });
|
||||
}
|
||||
await bill.update({ isDeleted: 1 });
|
||||
await bill.update({
|
||||
isDeleted: 1,
|
||||
updateBy: req.user.id
|
||||
});
|
||||
res.status(200).json({ message: '水费记录删除成功' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
const jwt = require('jsonwebtoken');
|
||||
const User = require('../models/User');
|
||||
|
||||
// JWT 密钥
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'rentease-secret-key';
|
||||
|
||||
// 生成 Token(2小时有效期)
|
||||
const generateToken = (user) => {
|
||||
return jwt.sign(
|
||||
{
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role: user.role?.code || user.role
|
||||
},
|
||||
JWT_SECRET,
|
||||
{ expiresIn: '2h' }
|
||||
);
|
||||
};
|
||||
|
||||
// 验证 Token 中间件
|
||||
const authMiddleware = async (req, res, next) => {
|
||||
try {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '未提供认证令牌'
|
||||
});
|
||||
}
|
||||
|
||||
const token = authHeader.substring(7);
|
||||
|
||||
// 验证 Token
|
||||
const decoded = jwt.verify(token, JWT_SECRET);
|
||||
|
||||
// 检查用户是否存在且状态正常
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
id: decoded.id,
|
||||
status: 'active'
|
||||
},
|
||||
attributes: ['id', 'username', 'nickname', 'roleId', 'status'],
|
||||
include: [{
|
||||
model: require('../models/Role'),
|
||||
as: 'role',
|
||||
attributes: ['id', 'name', 'code']
|
||||
}]
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '用户不存在或已被禁用'
|
||||
});
|
||||
}
|
||||
|
||||
// 将用户信息附加到请求对象
|
||||
req.user = user;
|
||||
next();
|
||||
} catch (error) {
|
||||
if (error.name === 'TokenExpiredError') {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '登录已过期,请重新登录'
|
||||
});
|
||||
}
|
||||
if (error.name === 'JsonWebTokenError') {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '无效的认证令牌'
|
||||
});
|
||||
}
|
||||
return res.status(500).json({
|
||||
code: 500,
|
||||
message: '认证失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 管理员权限检查中间件
|
||||
const adminMiddleware = (req, res, next) => {
|
||||
const userRole = req.user.role?.code || req.user.role;
|
||||
if (userRole !== 'admin') {
|
||||
return res.status(403).json({
|
||||
code: 403,
|
||||
message: '没有权限执行此操作'
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
generateToken,
|
||||
authMiddleware,
|
||||
adminMiddleware,
|
||||
JWT_SECRET
|
||||
};
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../config/db');
|
||||
const Region = require('./Region');
|
||||
|
||||
const Apartment = sequelize.define('Apartment', {
|
||||
id: {
|
||||
|
|
@ -9,15 +8,6 @@ const Apartment = sequelize.define('Apartment', {
|
|||
autoIncrement: true,
|
||||
comment: '公寓ID'
|
||||
},
|
||||
regionId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: Region,
|
||||
key: 'id'
|
||||
},
|
||||
comment: '所属区域ID'
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
|
|
@ -28,11 +18,21 @@ const Apartment = sequelize.define('Apartment', {
|
|||
allowNull: true,
|
||||
comment: '公寓地址'
|
||||
},
|
||||
createBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
createTime: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '创建时间'
|
||||
},
|
||||
updateBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '修改人ID'
|
||||
},
|
||||
updateTime: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
|
|
@ -50,8 +50,4 @@ const Apartment = sequelize.define('Apartment', {
|
|||
timestamps: false
|
||||
});
|
||||
|
||||
// 建立关联
|
||||
Apartment.belongsTo(Region, { foreignKey: 'regionId' });
|
||||
Region.hasMany(Apartment, { foreignKey: 'regionId' });
|
||||
|
||||
module.exports = Apartment;
|
||||
|
|
@ -59,11 +59,21 @@ const ElectricityBill = sequelize.define('ElectricityBill', {
|
|||
defaultValue: 'unpaid',
|
||||
comment: '状态(unpaid:未支付,paid:已支付)'
|
||||
},
|
||||
createBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
createTime: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '创建时间'
|
||||
},
|
||||
updateBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '修改人ID'
|
||||
},
|
||||
updateTime: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../config/db');
|
||||
|
||||
const LoginLog = sequelize.define('LoginLog', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
comment: '日志ID'
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '用户ID'
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '用户名'
|
||||
},
|
||||
loginType: {
|
||||
type: DataTypes.ENUM('login', 'logout'),
|
||||
allowNull: false,
|
||||
comment: '登录类型'
|
||||
},
|
||||
ip: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: 'IP地址'
|
||||
},
|
||||
userAgent: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '浏览器信息'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('success', 'fail'),
|
||||
allowNull: false,
|
||||
defaultValue: 'success',
|
||||
comment: '登录状态'
|
||||
},
|
||||
message: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
comment: '登录信息/失败原因'
|
||||
},
|
||||
createTime: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '创建时间'
|
||||
}
|
||||
}, {
|
||||
tableName: 'login_logs',
|
||||
timestamps: false,
|
||||
comment: '登录日志表'
|
||||
});
|
||||
|
||||
module.exports = LoginLog;
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../config/db');
|
||||
|
||||
const Menu = sequelize.define('Menu', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
parentId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
comment: '父菜单ID,null表示顶级菜单'
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '菜单名称'
|
||||
},
|
||||
code: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '菜单编码'
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.ENUM('menu', 'button'),
|
||||
allowNull: false,
|
||||
defaultValue: 'menu',
|
||||
comment: '类型:menu-菜单,button-按钮'
|
||||
},
|
||||
path: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: true,
|
||||
comment: '路由路径'
|
||||
},
|
||||
component: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: true,
|
||||
comment: '组件路径'
|
||||
},
|
||||
icon: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '菜单图标'
|
||||
},
|
||||
sort: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '排序号'
|
||||
},
|
||||
visible: {
|
||||
type: DataTypes.ENUM('show', 'hide'),
|
||||
allowNull: false,
|
||||
defaultValue: 'show',
|
||||
comment: '是否显示:show-显示,hide-隐藏'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'disabled'),
|
||||
allowNull: false,
|
||||
defaultValue: 'active',
|
||||
comment: '状态:active-启用,disabled-禁用'
|
||||
},
|
||||
createBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
updateBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '修改人ID'
|
||||
},
|
||||
isDeleted: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
comment: '是否删除:true-已删除,false-未删除'
|
||||
}
|
||||
}, {
|
||||
tableName: 'menus',
|
||||
timestamps: true,
|
||||
comment: '菜单表'
|
||||
});
|
||||
|
||||
module.exports = Menu;
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../config/db');
|
||||
|
||||
const OperationLog = sequelize.define('OperationLog', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
comment: '日志ID'
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '操作用户ID'
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '操作用户名'
|
||||
},
|
||||
module: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '操作模块'
|
||||
},
|
||||
action: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '操作类型'
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '操作描述'
|
||||
},
|
||||
method: {
|
||||
type: DataTypes.STRING(10),
|
||||
allowNull: true,
|
||||
comment: '请求方法'
|
||||
},
|
||||
url: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true,
|
||||
comment: '请求URL'
|
||||
},
|
||||
ip: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: 'IP地址'
|
||||
},
|
||||
params: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '请求参数'
|
||||
},
|
||||
result: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '操作结果'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('success', 'fail'),
|
||||
allowNull: false,
|
||||
defaultValue: 'success',
|
||||
comment: '操作状态'
|
||||
},
|
||||
duration: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '执行时长(毫秒)'
|
||||
},
|
||||
createTime: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '创建时间'
|
||||
}
|
||||
}, {
|
||||
tableName: 'operation_logs',
|
||||
timestamps: false,
|
||||
comment: '操作日志表'
|
||||
});
|
||||
|
||||
module.exports = OperationLog;
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../config/db');
|
||||
|
||||
const Region = sequelize.define('Region', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
comment: '区域ID'
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '区域名称'
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '区域描述'
|
||||
},
|
||||
createTime: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '创建时间'
|
||||
},
|
||||
updateTime: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
onUpdate: DataTypes.NOW,
|
||||
comment: '更新时间'
|
||||
},
|
||||
isDeleted: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '删除状态(0:未删除,1:已删除)'
|
||||
}
|
||||
}, {
|
||||
tableName: 'regions',
|
||||
timestamps: false
|
||||
});
|
||||
|
||||
module.exports = Region;
|
||||
|
|
@ -54,11 +54,21 @@ const Rental = sequelize.define('Rental', {
|
|||
allowNull: true,
|
||||
comment: '备注'
|
||||
},
|
||||
createBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
createTime: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '创建时间'
|
||||
},
|
||||
updateBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '修改人ID'
|
||||
},
|
||||
updateTime: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../config/db');
|
||||
|
||||
const Role = sequelize.define('Role', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '角色名称'
|
||||
},
|
||||
code: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '角色编码'
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '角色描述'
|
||||
},
|
||||
permissions: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
defaultValue: [],
|
||||
comment: '角色权限'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'disabled'),
|
||||
defaultValue: 'active',
|
||||
comment: '状态:active-启用,disabled-禁用'
|
||||
},
|
||||
createBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
updateBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '修改人ID'
|
||||
},
|
||||
isDeleted: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '删除状态(0:未删除,1:已删除)'
|
||||
}
|
||||
}, {
|
||||
tableName: 'roles',
|
||||
timestamps: true,
|
||||
comment: '角色表'
|
||||
});
|
||||
|
||||
module.exports = Role;
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../config/db');
|
||||
|
||||
const RoleMenu = sequelize.define('RoleMenu', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
roleId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '角色ID'
|
||||
},
|
||||
menuId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '菜单ID'
|
||||
}
|
||||
}, {
|
||||
tableName: 'role_menus',
|
||||
timestamps: true,
|
||||
comment: '角色菜单关联表'
|
||||
});
|
||||
|
||||
module.exports = RoleMenu;
|
||||
|
|
@ -56,11 +56,21 @@ const Room = sequelize.define('Room', {
|
|||
defaultValue: '',
|
||||
comment: '其他状态(cleaning:打扫中,maintenance:维修中)'
|
||||
},
|
||||
createBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
createTime: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '创建时间'
|
||||
},
|
||||
updateBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '修改人ID'
|
||||
},
|
||||
updateTime: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../config/db');
|
||||
|
||||
const User = sequelize.define('User', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '用户名'
|
||||
},
|
||||
password: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
comment: '密码(加密存储)'
|
||||
},
|
||||
nickname: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '昵称'
|
||||
},
|
||||
roleId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '角色ID'
|
||||
},
|
||||
isSuperAdmin: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '是否超级管理员(0:否,1:是)'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'disabled'),
|
||||
defaultValue: 'active',
|
||||
comment: '状态:active-启用,disabled-禁用'
|
||||
},
|
||||
createBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
updateBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '修改人ID'
|
||||
},
|
||||
isDeleted: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '删除状态(0:未删除,1:已删除)'
|
||||
}
|
||||
}, {
|
||||
tableName: 'users',
|
||||
timestamps: true,
|
||||
comment: '用户表'
|
||||
});
|
||||
|
||||
module.exports = User;
|
||||
|
|
@ -59,11 +59,21 @@ const WaterBill = sequelize.define('WaterBill', {
|
|||
defaultValue: 'unpaid',
|
||||
comment: '状态(unbilled:未出账,unpaid:未支付,paid:已支付)'
|
||||
},
|
||||
createBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
createTime: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '创建时间'
|
||||
},
|
||||
updateBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '修改人ID'
|
||||
},
|
||||
updateTime: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
|
|
|
|||
|
|
@ -1,15 +1,37 @@
|
|||
const Region = require('./Region');
|
||||
const Apartment = require('./Apartment');
|
||||
const Room = require('./Room');
|
||||
const Rental = require('./Rental');
|
||||
const WaterBill = require('./WaterBill');
|
||||
const ElectricityBill = require('./ElectricityBill');
|
||||
const User = require('./User');
|
||||
const Role = require('./Role');
|
||||
const Menu = require('./Menu');
|
||||
const RoleMenu = require('./RoleMenu');
|
||||
const OperationLog = require('./OperationLog');
|
||||
const LoginLog = require('./LoginLog');
|
||||
|
||||
// 关联关系
|
||||
User.belongsTo(Role, { foreignKey: 'roleId', as: 'role' });
|
||||
Role.hasMany(User, { foreignKey: 'roleId', as: 'users' });
|
||||
|
||||
// 菜单自关联(父子菜单)
|
||||
Menu.belongsTo(Menu, { foreignKey: 'parentId', as: 'parent' });
|
||||
Menu.hasMany(Menu, { foreignKey: 'parentId', as: 'children' });
|
||||
|
||||
// 角色与菜单多对多关联
|
||||
Role.belongsToMany(Menu, { through: RoleMenu, foreignKey: 'roleId', otherKey: 'menuId', as: 'menus' });
|
||||
Menu.belongsToMany(Role, { through: RoleMenu, foreignKey: 'menuId', otherKey: 'roleId', as: 'roles' });
|
||||
|
||||
module.exports = {
|
||||
Region,
|
||||
Apartment,
|
||||
Room,
|
||||
Rental,
|
||||
WaterBill,
|
||||
ElectricityBill
|
||||
ElectricityBill,
|
||||
User,
|
||||
Role,
|
||||
Menu,
|
||||
RoleMenu,
|
||||
OperationLog,
|
||||
LoginLog
|
||||
};
|
||||
|
|
@ -9,8 +9,10 @@
|
|||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"bcryptjs": "^3.0.3",
|
||||
"cors": "^2.8.6",
|
||||
"express": "^4.17.1",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"mysql2": "^2.3.3",
|
||||
"sequelize": "^6.37.7"
|
||||
}
|
||||
|
|
@ -58,6 +60,14 @@
|
|||
"resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||
},
|
||||
"node_modules/bcryptjs": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/bcryptjs/-/bcryptjs-3.0.3.tgz",
|
||||
"integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==",
|
||||
"bin": {
|
||||
"bcrypt": "bin/bcrypt"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.19.0.tgz",
|
||||
|
|
@ -102,6 +112,11 @@
|
|||
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/buffer-equal-constant-time": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.0.tgz",
|
||||
|
|
@ -200,6 +215,14 @@
|
|||
"resolved": "https://registry.npmmirror.com/dottie/-/dottie-2.0.7.tgz",
|
||||
"integrity": "sha512-7lAK2A0b3zZr3UC5aE69CPdCFR4RHW1o2Dr74TqFykxkUCBXSRJum/yPc7g8zRHJqWKomPLHwFLLoUnn8PXXRg=="
|
||||
},
|
||||
"node_modules/ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmmirror.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz",
|
||||
|
|
@ -385,11 +408,86 @@
|
|||
"resolved": "https://registry.npmmirror.com/is-property/-/is-property-1.0.2.tgz",
|
||||
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="
|
||||
},
|
||||
"node_modules/jsonwebtoken": {
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
|
||||
"integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
|
||||
"dependencies": {
|
||||
"jws": "^4.0.1",
|
||||
"lodash.includes": "^4.3.0",
|
||||
"lodash.isboolean": "^3.0.3",
|
||||
"lodash.isinteger": "^4.0.4",
|
||||
"lodash.isnumber": "^3.0.3",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.isstring": "^4.0.1",
|
||||
"lodash.once": "^4.0.0",
|
||||
"ms": "^2.1.1",
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jwa": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/jwa/-/jwa-2.0.1.tgz",
|
||||
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
|
||||
"dependencies": {
|
||||
"buffer-equal-constant-time": "^1.0.1",
|
||||
"ecdsa-sig-formatter": "1.0.11",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jws": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/jws/-/jws-4.0.1.tgz",
|
||||
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
|
||||
"dependencies": {
|
||||
"jwa": "^2.0.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="
|
||||
},
|
||||
"node_modules/lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
|
||||
},
|
||||
"node_modules/lodash.isboolean": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
|
||||
},
|
||||
"node_modules/lodash.isinteger": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
|
||||
},
|
||||
"node_modules/lodash.isnumber": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
|
||||
},
|
||||
"node_modules/lodash.isstring": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
|
||||
},
|
||||
"node_modules/lodash.once": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
|
||||
},
|
||||
"node_modules/long": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/long/-/long-4.0.0.tgz",
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@
|
|||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"bcryptjs": "^3.0.3",
|
||||
"cors": "^2.8.6",
|
||||
"express": "^4.17.1",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"mysql2": "^2.3.3",
|
||||
"sequelize": "^6.37.7"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const authController = require('../controllers/authController');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
|
||||
// 登录(不需要认证)
|
||||
router.post('/login', authController.login);
|
||||
|
||||
// 登出(需要认证)
|
||||
router.post('/logout', authMiddleware, authController.logout);
|
||||
|
||||
// 获取当前用户信息(需要认证)
|
||||
router.get('/user', authMiddleware, authController.getCurrentUser);
|
||||
|
||||
// 修改密码(需要认证)
|
||||
router.post('/change-password', authMiddleware, authController.changePassword);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const logController = require('../controllers/logController');
|
||||
const { authMiddleware, adminMiddleware } = require('../middleware/auth');
|
||||
|
||||
// 所有日志接口都需要认证和管理员权限
|
||||
router.use(authMiddleware, adminMiddleware);
|
||||
|
||||
// 获取操作日志列表
|
||||
router.get('/operation', logController.getOperationLogs);
|
||||
|
||||
// 获取登录日志列表
|
||||
router.get('/login', logController.getLoginLogs);
|
||||
|
||||
// 清空操作日志
|
||||
router.post('/operation/clear', logController.clearOperationLogs);
|
||||
|
||||
// 清空登录日志
|
||||
router.post('/login/clear', logController.clearLoginLogs);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const menuController = require('../controllers/menuController');
|
||||
const { authMiddleware, adminMiddleware } = require('../middleware/auth');
|
||||
|
||||
// 获取当前用户的菜单权限(只需要认证,不需要管理员权限)
|
||||
router.get('/user/menus', authMiddleware, menuController.getUserMenus);
|
||||
|
||||
// 所有菜单管理接口都需要认证和管理员权限
|
||||
router.use(authMiddleware, adminMiddleware);
|
||||
|
||||
// 获取菜单树
|
||||
router.get('/tree', menuController.getMenuTree);
|
||||
|
||||
// 获取菜单列表
|
||||
router.get('/', menuController.getMenuList);
|
||||
|
||||
// 获取菜单详情
|
||||
router.get('/:id', menuController.getMenuById);
|
||||
|
||||
// 创建菜单
|
||||
router.post('/', menuController.createMenu);
|
||||
|
||||
// 更新菜单
|
||||
router.put('/:id', menuController.updateMenu);
|
||||
|
||||
// 删除菜单
|
||||
router.delete('/:id', menuController.deleteMenu);
|
||||
|
||||
// 获取角色的菜单权限
|
||||
router.get('/role/:roleId', menuController.getRoleMenus);
|
||||
|
||||
// 分配菜单权限给角色
|
||||
router.post('/role/:roleId/assign', menuController.assignMenusToRole);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const regionController = require('../controllers/regionController');
|
||||
|
||||
// 路由
|
||||
router.get('/', regionController.getAllRegions);
|
||||
router.get('/list', regionController.listRegions);
|
||||
router.get('/:id', regionController.getRegionById);
|
||||
router.post('/', regionController.createRegion);
|
||||
router.put('/:id', regionController.updateRegion);
|
||||
router.delete('/:id', regionController.deleteRegion);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const roleController = require('../controllers/roleController');
|
||||
const { authMiddleware, adminMiddleware } = require('../middleware/auth');
|
||||
|
||||
// 所有角色管理接口都需要认证和管理员权限
|
||||
router.use(authMiddleware, adminMiddleware);
|
||||
|
||||
// 获取角色列表
|
||||
router.get('/', roleController.getRoles);
|
||||
|
||||
// 获取角色详情
|
||||
router.get('/:id', roleController.getRoleById);
|
||||
|
||||
// 创建角色
|
||||
router.post('/', roleController.createRole);
|
||||
|
||||
// 更新角色
|
||||
router.put('/:id', roleController.updateRole);
|
||||
|
||||
// 删除角色
|
||||
router.delete('/:id', roleController.deleteRole);
|
||||
|
||||
// 获取所有角色(用于下拉选择)
|
||||
router.get('/all/list', roleController.getAllRoles);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -5,8 +5,7 @@ const statisticsController = require('../controllers/statisticsController');
|
|||
// 路由
|
||||
router.get('/rent', statisticsController.getRentStatistics);
|
||||
router.get('/room-status', statisticsController.getRoomStatusStatistics);
|
||||
router.get('/region-house', statisticsController.getRegionHouseStatistics);
|
||||
router.get('/region-apartment-house', statisticsController.getRegionApartmentHouseStatistics);
|
||||
router.get('/dashboard', statisticsController.getDashboardStatistics);
|
||||
router.get('/apartment-room-status', statisticsController.getApartmentRoomStatusStatistics);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const userController = require('../controllers/userController');
|
||||
const { authMiddleware, adminMiddleware } = require('../middleware/auth');
|
||||
|
||||
// 个人中心接口(只需要认证)
|
||||
router.get('/info', authMiddleware, userController.getCurrentUserInfo);
|
||||
router.put('/profile', authMiddleware, userController.updateUserProfile);
|
||||
router.post('/change-password', authMiddleware, userController.changePassword);
|
||||
|
||||
// 管理员权限接口
|
||||
router.use(authMiddleware, adminMiddleware);
|
||||
|
||||
// 获取用户列表
|
||||
router.get('/', userController.getUserList);
|
||||
|
||||
// 获取用户详情
|
||||
router.get('/:id', userController.getUserById);
|
||||
|
||||
// 创建用户
|
||||
router.post('/', userController.createUser);
|
||||
|
||||
// 更新用户
|
||||
router.put('/:id', userController.updateUser);
|
||||
|
||||
// 删除用户
|
||||
router.delete('/:id', userController.deleteUser);
|
||||
|
||||
// 重置用户密码
|
||||
router.post('/:id/reset-password', userController.resetUserPassword);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
const OperationLog = require('../models/OperationLog');
|
||||
const LoginLog = require('../models/LoginLog');
|
||||
|
||||
/**
|
||||
* 记录操作日志
|
||||
* @param {Object} options 日志选项
|
||||
* @param {number} options.userId 用户ID
|
||||
* @param {string} options.username 用户名
|
||||
* @param {string} options.module 操作模块
|
||||
* @param {string} options.action 操作类型
|
||||
* @param {string} options.description 操作描述
|
||||
* @param {string} options.method 请求方法
|
||||
* @param {string} options.url 请求URL
|
||||
* @param {string} options.ip IP地址
|
||||
* @param {Object} options.params 请求参数
|
||||
* @param {Object} options.result 操作结果
|
||||
* @param {string} options.status 操作状态 success/fail
|
||||
* @param {number} options.duration 执行时长(毫秒)
|
||||
*/
|
||||
async function logOperation(options) {
|
||||
try {
|
||||
await OperationLog.create({
|
||||
userId: options.userId,
|
||||
username: options.username,
|
||||
module: options.module,
|
||||
action: options.action,
|
||||
description: options.description,
|
||||
method: options.method,
|
||||
url: options.url,
|
||||
ip: options.ip,
|
||||
params: options.params ? JSON.stringify(options.params) : null,
|
||||
result: options.result ? JSON.stringify(options.result) : null,
|
||||
status: options.status || 'success',
|
||||
duration: options.duration
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('记录操作日志失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录日志
|
||||
* @param {Object} options 日志选项
|
||||
* @param {number} options.userId 用户ID
|
||||
* @param {string} options.username 用户名
|
||||
* @param {string} options.loginType 登录类型 login/logout
|
||||
* @param {string} options.ip IP地址
|
||||
* @param {string} options.userAgent 浏览器信息
|
||||
* @param {string} options.status 登录状态 success/fail
|
||||
* @param {string} options.message 登录信息/失败原因
|
||||
*/
|
||||
async function logLogin(options) {
|
||||
try {
|
||||
await LoginLog.create({
|
||||
userId: options.userId,
|
||||
username: options.username,
|
||||
loginType: options.loginType,
|
||||
ip: options.ip,
|
||||
userAgent: options.userAgent,
|
||||
status: options.status || 'success',
|
||||
message: options.message
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('记录登录日志失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端IP地址
|
||||
* @param {Object} req Express请求对象
|
||||
* @returns {string} IP地址
|
||||
*/
|
||||
function getClientIp(req) {
|
||||
return req.headers['x-forwarded-for'] ||
|
||||
req.headers['x-real-ip'] ||
|
||||
req.connection.remoteAddress ||
|
||||
req.socket.remoteAddress ||
|
||||
'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作日志中间件
|
||||
* 自动记录API请求日志
|
||||
*/
|
||||
function operationLogMiddleware(options = {}) {
|
||||
const { module = '系统', excludePaths = ['/api/auth/login'] } = options;
|
||||
|
||||
return async (req, res, next) => {
|
||||
// 排除指定路径
|
||||
if (excludePaths.some(path => req.path.includes(path))) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
const originalSend = res.send;
|
||||
|
||||
// 捕获响应数据
|
||||
res.send = function(data) {
|
||||
res.responseData = data;
|
||||
return originalSend.call(this, data);
|
||||
};
|
||||
|
||||
res.on('finish', async () => {
|
||||
const duration = Date.now() - startTime;
|
||||
const user = req.user || {};
|
||||
|
||||
try {
|
||||
await OperationLog.create({
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
module: module,
|
||||
action: getActionFromMethod(req.method),
|
||||
description: `${req.method} ${req.path}`,
|
||||
method: req.method,
|
||||
url: req.originalUrl,
|
||||
ip: getClientIp(req),
|
||||
params: JSON.stringify({
|
||||
body: req.body,
|
||||
query: req.query,
|
||||
params: req.params
|
||||
}),
|
||||
result: res.responseData ? res.responseData.substring(0, 2000) : null,
|
||||
status: res.statusCode >= 200 && res.statusCode < 300 ? 'success' : 'fail',
|
||||
duration: duration
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('记录操作日志失败:', error);
|
||||
}
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据HTTP方法获取操作类型
|
||||
* @param {string} method HTTP方法
|
||||
* @returns {string} 操作类型
|
||||
*/
|
||||
function getActionFromMethod(method) {
|
||||
const actionMap = {
|
||||
'GET': '查询',
|
||||
'POST': '新增',
|
||||
'PUT': '修改',
|
||||
'PATCH': '修改',
|
||||
'DELETE': '删除'
|
||||
};
|
||||
return actionMap[method] || '其他';
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
logOperation,
|
||||
logLogin,
|
||||
getClientIp,
|
||||
operationLogMiddleware,
|
||||
getActionFromMethod
|
||||
};
|
||||
Loading…
Reference in New Issue