feat(用户管理): 实现用户注册审核功能
添加用户注册审核流程,包括以下变更: - 用户注册后状态为 pending 待审核 - 管理员可批准或拒绝注册申请 - 待审核用户无法登录系统 - 用户管理页面添加审核操作和状态筛选 - 添加数据库迁移脚本支持 pending 状态 - 更新前后端相关接口和页面逻辑
This commit is contained in:
@@ -36,7 +36,7 @@ const User = sequelize.define('User', {
|
||||
allowNull: true
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'inactive', 'locked'),
|
||||
type: DataTypes.ENUM('active', 'inactive', 'locked', 'pending'),
|
||||
defaultValue: 'active'
|
||||
},
|
||||
lastLoginTime: {
|
||||
|
||||
+44
-20
@@ -58,12 +58,12 @@ router.post('/register', async (req, res) => {
|
||||
email,
|
||||
phone,
|
||||
realName: realName || username,
|
||||
status: 'active'
|
||||
status: isFirstUser ? 'active' : 'pending'
|
||||
});
|
||||
|
||||
if (isFirstUser) {
|
||||
let adminRole = await Role.findOne({ where: { roleCode: 'admin' } });
|
||||
|
||||
|
||||
if (!adminRole) {
|
||||
adminRole = await Role.create({
|
||||
roleId: 'role_admin',
|
||||
@@ -74,37 +74,53 @@ router.post('/register', async (req, res) => {
|
||||
permissions: []
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
await UserRole.create({
|
||||
UserId: user.userId,
|
||||
RoleId: adminRole.roleId
|
||||
});
|
||||
|
||||
const token = generateToken(user);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '注册成功,已为您分配管理员权限',
|
||||
data: {
|
||||
user: {
|
||||
userId: user.userId,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
realName: user.realName
|
||||
},
|
||||
token,
|
||||
isFirstUser: true
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const defaultRole = await Role.findOne({ where: { roleCode: 'viewer' } });
|
||||
|
||||
|
||||
if (defaultRole) {
|
||||
await UserRole.create({
|
||||
UserId: user.userId,
|
||||
RoleId: defaultRole.roleId
|
||||
});
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '注册成功,请等待管理员审核',
|
||||
data: {
|
||||
user: {
|
||||
userId: user.userId,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
realName: user.realName
|
||||
},
|
||||
isFirstUser: false,
|
||||
pendingApproval: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const token = generateToken(user);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: isFirstUser ? '注册成功,已为您分配管理员权限' : '注册成功',
|
||||
data: {
|
||||
user: {
|
||||
userId: user.userId,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
realName: user.realName
|
||||
},
|
||||
token
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('注册错误:', error);
|
||||
res.status(500).json({
|
||||
@@ -148,6 +164,14 @@ router.post('/login', async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
if (user.status === 'pending') {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
code: 'PENDING_APPROVAL',
|
||||
message: '账户待审核,请联系管理员激活'
|
||||
});
|
||||
}
|
||||
|
||||
const isPasswordValid = await bcrypt.compare(password, user.password);
|
||||
if (!isPasswordValid) {
|
||||
user.loginCount = (user.loginCount || 0) + 1;
|
||||
|
||||
@@ -465,4 +465,82 @@ router.delete('/:userId/avatar', authMiddleware, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:userId/approve', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const user = await User.findByPk(req.params.userId);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (user.status !== 'pending') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '该用户不在待审核状态'
|
||||
});
|
||||
}
|
||||
|
||||
user.status = 'active';
|
||||
await user.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '用户审核通过',
|
||||
data: {
|
||||
userId: user.userId,
|
||||
username: user.username,
|
||||
status: user.status
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('审核用户错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '审核用户失败'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:userId/reject', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const user = await User.findByPk(req.params.userId);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (user.status !== 'pending') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '该用户不在待审核状态'
|
||||
});
|
||||
}
|
||||
|
||||
user.status = 'inactive';
|
||||
await user.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '已拒绝该用户的注册申请',
|
||||
data: {
|
||||
userId: user.userId,
|
||||
username: user.username,
|
||||
status: user.status
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('拒绝用户错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '操作失败'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
const { sequelize } = require('../db');
|
||||
|
||||
async function migrate() {
|
||||
try {
|
||||
console.log('开始迁移:添加 pending 状态到用户表...');
|
||||
|
||||
const queryInterface = sequelize.getQueryInterface();
|
||||
const dialect = sequelize.getDialect();
|
||||
|
||||
if (dialect === 'sqlite') {
|
||||
// SQLite 不支持 ALTER COLUMN,需要重建表
|
||||
console.log('检测到 SQLite 数据库,使用重建表方式迁移...');
|
||||
|
||||
// 1. 创建新表
|
||||
await queryInterface.createTable('users_new', {
|
||||
userId: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
},
|
||||
username: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
password: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
email: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
phone: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
realName: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
avatar: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
status: {
|
||||
type: sequelize.Sequelize.ENUM('active', 'inactive', 'locked', 'pending'),
|
||||
defaultValue: 'active'
|
||||
},
|
||||
lastLoginTime: {
|
||||
type: sequelize.Sequelize.DATE,
|
||||
allowNull: true
|
||||
},
|
||||
lastLoginIp: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
loginCount: {
|
||||
type: sequelize.Sequelize.INTEGER,
|
||||
defaultValue: 0
|
||||
},
|
||||
remark: {
|
||||
type: sequelize.Sequelize.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
createdAt: {
|
||||
type: sequelize.Sequelize.DATE,
|
||||
allowNull: false
|
||||
},
|
||||
updatedAt: {
|
||||
type: sequelize.Sequelize.DATE,
|
||||
allowNull: false
|
||||
}
|
||||
});
|
||||
|
||||
// 2. 复制数据
|
||||
await sequelize.query(`
|
||||
INSERT INTO users_new (
|
||||
userId, username, password, email, phone, realName, avatar,
|
||||
status, lastLoginTime, lastLoginIp, loginCount, remark, createdAt, updatedAt
|
||||
)
|
||||
SELECT
|
||||
userId, username, password, email, phone, realName, avatar,
|
||||
status, lastLoginTime, lastLoginIp, loginCount, remark, createdAt, updatedAt
|
||||
FROM users
|
||||
`);
|
||||
|
||||
// 3. 删除旧表
|
||||
await queryInterface.dropTable('users');
|
||||
|
||||
// 4. 重命名新表
|
||||
await queryInterface.renameTable('users_new', 'users');
|
||||
|
||||
// 5. 重新创建索引
|
||||
await queryInterface.addIndex('users', ['status']);
|
||||
await queryInterface.addIndex('users', ['username']);
|
||||
await queryInterface.addIndex('users', ['email']);
|
||||
|
||||
console.log('SQLite 迁移完成');
|
||||
} else if (dialect === 'mysql') {
|
||||
// MySQL 可以直接修改 ENUM
|
||||
console.log('检测到 MySQL 数据库,直接修改 ENUM...');
|
||||
await sequelize.query(`
|
||||
ALTER TABLE users
|
||||
MODIFY COLUMN status ENUM('active', 'inactive', 'locked', 'pending') DEFAULT 'active'
|
||||
`);
|
||||
console.log('MySQL 迁移完成');
|
||||
} else {
|
||||
console.log(`不支持的数据库类型: ${dialect},请手动迁移`);
|
||||
}
|
||||
|
||||
console.log('迁移成功完成!');
|
||||
} catch (error) {
|
||||
console.error('迁移失败:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
migrate();
|
||||
Reference in New Issue
Block a user