Files
AIclinicalresearch/backend/src/modules/admin/controllers/userController.ts
HaHafeng 91ae80888e feat(admin,rvw,asl,frontend): Batch import redesign + RVW parallel skills + UI improvements
Backend:
- Redesign batch user import: add autoInheritModules param, users auto-inherit tenant modules when true
- Add module validation: reject modules not subscribed by the tenant
- Soften department validation: skip instead of fail when department name not found
- Fix RVW skill status semantics: review findings (ERROR issues) no longer mark skill as error
- Add parallel execution support to SkillExecutor via parallelGroup
- Configure Editorial + Methodology skills to run in parallel (~240s -> ~130s)
- Update legacy bridge error message to user-friendly text

Frontend:
- Redesign ImportUserModal: 4-step flow (select tenant -> upload -> preview -> result)
- Simplify import template: remove tenant code and module columns
- Show tenant subscribed modules before import with auto-inherit option
- Fix isLegacyEmbed modules bypassing RouteGuard and TopNavigation permission checks
- Hide ASL fulltext screening (step 3), renumber subsequent nav items
- Add ExtractionWorkbenchGuide page when no taskId provided
- Update legacy system error message to network-friendly text

Docs:
- Update deployment changelog with BE-9, FE-11 entries

Made-with: Cursor
2026-03-05 22:04:36 +08:00

532 lines
13 KiB
TypeScript

/**
* 用户管理控制器
* @description 处理用户管理相关的 HTTP 请求
*/
import { FastifyRequest, FastifyReply } from 'fastify';
import { logger } from '../../../common/logging/index.js';
import * as userService from '../services/userService.js';
import {
ListUsersQuery,
CreateUserRequest,
UpdateUserRequest,
AssignTenantRequest,
UpdateUserModulesRequest,
ImportUserRow,
} from '../types/user.types.js';
/**
* 获取用户列表
*/
export async function listUsers(
request: FastifyRequest<{ Querystring: ListUsersQuery }>,
reply: FastifyReply
) {
try {
const user = request.user!;
const scope = await userService.getUserQueryScope(user.role, user.tenantId, user.userId);
const result = await userService.listUsers(request.query, scope);
return reply.send({
code: 0,
message: 'success',
data: result,
});
} catch (error: any) {
logger.error('[UserController] listUsers error:', error);
return reply.status(500).send({
code: 500,
message: error.message || '获取用户列表失败',
});
}
}
/**
* 获取用户详情
*/
export async function getUserById(
request: FastifyRequest<{ Params: { id: string } }>,
reply: FastifyReply
) {
try {
const user = request.user!;
const scope = await userService.getUserQueryScope(user.role, user.tenantId, user.userId);
const result = await userService.getUserById(request.params.id, scope);
if (!result) {
return reply.status(404).send({
code: 404,
message: '用户不存在',
});
}
return reply.send({
code: 0,
message: 'success',
data: result,
});
} catch (error: any) {
logger.error('[UserController] getUserById error:', error);
return reply.status(500).send({
code: 500,
message: error.message || '获取用户详情失败',
});
}
}
/**
* 创建用户
*/
export async function createUser(
request: FastifyRequest<{ Body: CreateUserRequest }>,
reply: FastifyReply
) {
try {
const creator = request.user!;
// HOSPITAL_ADMIN 和 PHARMA_ADMIN 只能在自己的租户内创建用户
if (
(creator.role === 'HOSPITAL_ADMIN' || creator.role === 'PHARMA_ADMIN') &&
request.body.tenantId !== creator.tenantId
) {
return reply.status(403).send({
code: 403,
message: '无权限在其他租户创建用户',
});
}
const result = await userService.createUser(request.body, creator.userId);
return reply.status(201).send({
code: 0,
message: '用户创建成功',
data: result,
});
} catch (error: any) {
logger.error('[UserController] createUser error:', error);
if (error.message.includes('已存在')) {
return reply.status(400).send({
code: 400,
message: error.message,
});
}
return reply.status(500).send({
code: 500,
message: error.message || '创建用户失败',
});
}
}
/**
* 更新用户
*/
export async function updateUser(
request: FastifyRequest<{ Params: { id: string }; Body: UpdateUserRequest }>,
reply: FastifyReply
) {
try {
const updater = request.user!;
const scope = await userService.getUserQueryScope(updater.role, updater.tenantId, updater.userId);
const result = await userService.updateUser(
request.params.id,
request.body,
scope,
updater.userId
);
return reply.send({
code: 0,
message: '用户更新成功',
data: result,
});
} catch (error: any) {
logger.error('[UserController] updateUser error:', error);
if (error.message.includes('不存在') || error.message.includes('无权限')) {
return reply.status(404).send({
code: 404,
message: error.message,
});
}
if (error.message.includes('已存在')) {
return reply.status(400).send({
code: 400,
message: error.message,
});
}
return reply.status(500).send({
code: 500,
message: error.message || '更新用户失败',
});
}
}
/**
* 更新用户状态(启用/禁用)
*/
export async function updateUserStatus(
request: FastifyRequest<{
Params: { id: string };
Body: { status: 'active' | 'disabled' };
}>,
reply: FastifyReply
) {
try {
const updater = request.user!;
const scope = await userService.getUserQueryScope(updater.role, updater.tenantId, updater.userId);
await userService.updateUserStatus(
request.params.id,
request.body.status,
scope,
updater.userId
);
return reply.send({
code: 0,
message: request.body.status === 'active' ? '用户已启用' : '用户已禁用',
});
} catch (error: any) {
logger.error('[UserController] updateUserStatus error:', error);
if (error.message.includes('不存在') || error.message.includes('无权限')) {
return reply.status(404).send({
code: 404,
message: error.message,
});
}
return reply.status(500).send({
code: 500,
message: error.message || '更新状态失败',
});
}
}
/**
* 重置用户密码
*/
export async function resetUserPassword(
request: FastifyRequest<{ Params: { id: string } }>,
reply: FastifyReply
) {
try {
const resetter = request.user!;
const scope = await userService.getUserQueryScope(resetter.role, resetter.tenantId, resetter.userId);
await userService.resetUserPassword(request.params.id, scope, resetter.userId);
return reply.send({
code: 0,
message: '密码已重置为默认密码',
});
} catch (error: any) {
logger.error('[UserController] resetUserPassword error:', error);
if (error.message.includes('不存在') || error.message.includes('无权限')) {
return reply.status(404).send({
code: 404,
message: error.message,
});
}
return reply.status(500).send({
code: 500,
message: error.message || '重置密码失败',
});
}
}
/**
* 分配租户给用户
*/
export async function assignTenantToUser(
request: FastifyRequest<{
Params: { id: string };
Body: AssignTenantRequest;
}>,
reply: FastifyReply
) {
try {
const assigner = request.user!;
// 只有 SUPER_ADMIN 可以分配租户
if (assigner.role !== 'SUPER_ADMIN') {
return reply.status(403).send({
code: 403,
message: '无权限分配租户',
});
}
await userService.assignTenantToUser(request.params.id, request.body, assigner.userId);
return reply.send({
code: 0,
message: '租户分配成功',
});
} catch (error: any) {
logger.error('[UserController] assignTenantToUser error:', error);
if (error.message.includes('不存在')) {
return reply.status(404).send({
code: 404,
message: error.message,
});
}
if (error.message.includes('已是')) {
return reply.status(400).send({
code: 400,
message: error.message,
});
}
return reply.status(500).send({
code: 500,
message: error.message || '分配租户失败',
});
}
}
/**
* 从租户移除用户
*/
export async function removeTenantFromUser(
request: FastifyRequest<{
Params: { id: string; tenantId: string };
}>,
reply: FastifyReply
) {
try {
const remover = request.user!;
// 只有 SUPER_ADMIN 可以移除租户
if (remover.role !== 'SUPER_ADMIN') {
return reply.status(403).send({
code: 403,
message: '无权限移除租户',
});
}
await userService.removeTenantFromUser(
request.params.id,
request.params.tenantId,
remover.userId
);
return reply.send({
code: 0,
message: '租户移除成功',
});
} catch (error: any) {
logger.error('[UserController] removeTenantFromUser error:', error);
if (error.message.includes('不存在') || error.message.includes('不是')) {
return reply.status(404).send({
code: 404,
message: error.message,
});
}
if (error.message.includes('不能移除')) {
return reply.status(400).send({
code: 400,
message: error.message,
});
}
return reply.status(500).send({
code: 500,
message: error.message || '移除租户失败',
});
}
}
/**
* 更新用户在指定租户的模块权限
*/
export async function updateUserModules(
request: FastifyRequest<{
Params: { id: string };
Body: UpdateUserModulesRequest;
}>,
reply: FastifyReply
) {
try {
const updater = request.user!;
// SUPER_ADMIN 可以操作任意租户
// HOSPITAL_ADMIN/PHARMA_ADMIN 只能操作自己租户的用户
if (
updater.role !== 'SUPER_ADMIN' &&
request.body.tenantId !== updater.tenantId
) {
return reply.status(403).send({
code: 403,
message: '无权限修改其他租户的用户模块权限',
});
}
await userService.updateUserModules(request.params.id, request.body, updater.userId);
return reply.send({
code: 0,
message: '模块权限更新成功',
});
} catch (error: any) {
logger.error('[UserController] updateUserModules error:', error);
if (error.message.includes('不是')) {
return reply.status(404).send({
code: 404,
message: error.message,
});
}
if (error.message.includes('不在')) {
return reply.status(400).send({
code: 400,
message: error.message,
});
}
return reply.status(500).send({
code: 500,
message: error.message || '更新模块权限失败',
});
}
}
/**
* 批量导入用户
*/
export async function importUsers(
request: FastifyRequest<{
Body: {
users: ImportUserRow[];
defaultTenantId?: string;
autoInheritModules?: boolean;
};
}>,
reply: FastifyReply
) {
try {
const importer = request.user!;
const autoInheritModules = request.body.autoInheritModules ?? true;
// 确定默认租户
let defaultTenantId = request.body.defaultTenantId;
if (!defaultTenantId) {
if (importer.role === 'SUPER_ADMIN') {
return reply.status(400).send({
code: 400,
message: '请指定默认租户',
});
}
defaultTenantId = importer.tenantId;
}
// HOSPITAL_ADMIN/PHARMA_ADMIN 只能导入到自己的租户
if (
(importer.role === 'HOSPITAL_ADMIN' || importer.role === 'PHARMA_ADMIN') &&
defaultTenantId !== importer.tenantId
) {
return reply.status(403).send({
code: 403,
message: '无权限导入到其他租户',
});
}
const result = await userService.importUsers(
request.body.users,
defaultTenantId,
importer.userId,
autoInheritModules
);
return reply.send({
code: 0,
message: `导入完成:成功 ${result.success} 条,失败 ${result.failed}`,
data: result,
});
} catch (error: any) {
logger.error('[UserController] importUsers error:', error);
return reply.status(500).send({
code: 500,
message: error.message || '批量导入失败',
});
}
}
/**
* 获取所有租户列表(用于下拉选择)
*/
export async function getAllTenants(request: FastifyRequest, reply: FastifyReply) {
try {
const result = await userService.getAllTenants();
return reply.send({
code: 0,
message: 'success',
data: result,
});
} catch (error: any) {
logger.error('[UserController] getAllTenants error:', error);
return reply.status(500).send({
code: 500,
message: error.message || '获取租户列表失败',
});
}
}
/**
* 获取租户的科室列表(用于下拉选择)
*/
export async function getDepartmentsByTenant(
request: FastifyRequest<{ Params: { tenantId: string } }>,
reply: FastifyReply
) {
try {
const result = await userService.getDepartmentsByTenant(request.params.tenantId);
return reply.send({
code: 0,
message: 'success',
data: result,
});
} catch (error: any) {
logger.error('[UserController] getDepartmentsByTenant error:', error);
return reply.status(500).send({
code: 500,
message: error.message || '获取科室列表失败',
});
}
}
/**
* 获取租户的模块列表(用于模块配置)
*/
export async function getModulesByTenant(
request: FastifyRequest<{ Params: { tenantId: string } }>,
reply: FastifyReply
) {
try {
const result = await userService.getModulesByTenant(request.params.tenantId);
return reply.send({
code: 0,
message: 'success',
data: result,
});
} catch (error: any) {
logger.error('[UserController] getModulesByTenant error:', error);
return reply.status(500).send({
code: 500,
message: error.message || '获取模块列表失败',
});
}
}