feat(admin): add user-level direct permission system and enhance activity tracking
Features: - Add user_permissions table for direct user-to-permission grants (ops:user-ops) - Merge role_permissions + user_permissions in auth chain (login, middleware, getCurrentUser) - Add getUserQueryScope support for USER role with ops:user-ops (cross-tenant access) - Unify cross-tenant operation checks via getUserQueryScope (remove hardcoded SUPER_ADMIN checks) - Add 3 new API endpoints: GET/PUT /:id/permissions, GET /options/permissions - Support ops:user-ops as alternative permission on all user/tenant management routes - Frontend: add user-ops permission toggle on UserFormPage and UserDetailPage - Enhance DC module activity tracking (StreamAIController, SessionController, QuickActionController) - Fix DC AIController user ID extraction and feature name consistency - Add verify-activity-tracking.ts validation script - Update deployment checklist and admin module documentation DB Migration: 20260309_add_user_permissions_table Made-with: Cursor
This commit is contained in:
@@ -187,27 +187,35 @@ export function requirePermission(requiredPermission: string): preHandlerHookHan
|
||||
return;
|
||||
}
|
||||
|
||||
const allowed = await prisma.role_permissions.findFirst({
|
||||
// 1. 检查角色权限
|
||||
const roleAllowed = await prisma.role_permissions.findFirst({
|
||||
where: {
|
||||
role: request.user.role as any,
|
||||
permissions: {
|
||||
code: requiredPermission,
|
||||
},
|
||||
permissions: { code: requiredPermission },
|
||||
},
|
||||
select: { permission_id: true },
|
||||
});
|
||||
if (roleAllowed) return;
|
||||
|
||||
if (!allowed) {
|
||||
logger.warn('权限不足', {
|
||||
userId: request.user.userId,
|
||||
role: request.user.role,
|
||||
requiredPermission,
|
||||
});
|
||||
return reply.status(403).send({
|
||||
error: 'Forbidden',
|
||||
message: `需要权限: ${requiredPermission}`,
|
||||
});
|
||||
}
|
||||
// 2. 检查用户直授权限
|
||||
const userAllowed = await prisma.user_permissions.findFirst({
|
||||
where: {
|
||||
user_id: request.user.userId,
|
||||
permissions: { code: requiredPermission },
|
||||
},
|
||||
select: { permission_id: true },
|
||||
});
|
||||
if (userAllowed) return;
|
||||
|
||||
logger.warn('权限不足', {
|
||||
userId: request.user.userId,
|
||||
role: request.user.role,
|
||||
requiredPermission,
|
||||
});
|
||||
return reply.status(403).send({
|
||||
error: 'Forbidden',
|
||||
message: `需要权限: ${requiredPermission}`,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -229,27 +237,35 @@ export function requireAnyPermission(...requiredPermissions: string[]): preHandl
|
||||
return;
|
||||
}
|
||||
|
||||
const allowed = await prisma.role_permissions.findFirst({
|
||||
// 1. 检查角色权限
|
||||
const roleAllowed = await prisma.role_permissions.findFirst({
|
||||
where: {
|
||||
role: request.user.role as any,
|
||||
permissions: {
|
||||
code: { in: requiredPermissions },
|
||||
},
|
||||
permissions: { code: { in: requiredPermissions } },
|
||||
},
|
||||
select: { permission_id: true },
|
||||
});
|
||||
if (roleAllowed) return;
|
||||
|
||||
if (!allowed) {
|
||||
logger.warn('权限不足(任一权限检查失败)', {
|
||||
userId: request.user.userId,
|
||||
role: request.user.role,
|
||||
requiredPermissions,
|
||||
});
|
||||
return reply.status(403).send({
|
||||
error: 'Forbidden',
|
||||
message: `需要权限之一: ${requiredPermissions.join(', ')}`,
|
||||
});
|
||||
}
|
||||
// 2. 检查用户直授权限
|
||||
const userAllowed = await prisma.user_permissions.findFirst({
|
||||
where: {
|
||||
user_id: request.user.userId,
|
||||
permissions: { code: { in: requiredPermissions } },
|
||||
},
|
||||
select: { permission_id: true },
|
||||
});
|
||||
if (userAllowed) return;
|
||||
|
||||
logger.warn('权限不足(任一权限检查失败)', {
|
||||
userId: request.user.userId,
|
||||
role: request.user.role,
|
||||
requiredPermissions,
|
||||
});
|
||||
return reply.status(403).send({
|
||||
error: 'Forbidden',
|
||||
message: `需要权限之一: ${requiredPermissions.join(', ')}`,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -112,8 +112,8 @@ export class AuthService {
|
||||
throw new Error('账号已被禁用,请联系管理员');
|
||||
}
|
||||
|
||||
// 4. 获取用户权限和模块列表
|
||||
const permissions = await this.getUserPermissions(user.role);
|
||||
// 4. 获取用户权限(角色权限 + 用户直授权限合并)和模块列表
|
||||
const permissions = await this.getUserPermissions(user.role, user.id);
|
||||
const modules = await this.getUserModules(user.id);
|
||||
|
||||
// 4.5 原子递增 token 版本号(数据库强一致,避免并发登录竞态)
|
||||
@@ -163,7 +163,7 @@ export class AuthService {
|
||||
departmentName: user.departments?.name,
|
||||
isDefaultPassword: user.is_default_password,
|
||||
permissions,
|
||||
modules, // 新增:返回模块列表
|
||||
modules,
|
||||
},
|
||||
tokens,
|
||||
};
|
||||
@@ -218,8 +218,8 @@ export class AuthService {
|
||||
throw new Error('账号已被禁用,请联系管理员');
|
||||
}
|
||||
|
||||
// 5. 获取用户权限和模块列表
|
||||
const permissions = await this.getUserPermissions(user.role);
|
||||
// 5. 获取用户权限(角色权限 + 用户直授权限合并)和模块列表
|
||||
const permissions = await this.getUserPermissions(user.role, user.id);
|
||||
const modules = await this.getUserModules(user.id);
|
||||
|
||||
// 5.5 原子递增 token 版本号(数据库强一致,避免并发登录竞态)
|
||||
@@ -290,7 +290,7 @@ export class AuthService {
|
||||
throw new Error('用户不存在');
|
||||
}
|
||||
|
||||
const permissions = await this.getUserPermissions(user.role);
|
||||
const permissions = await this.getUserPermissions(user.role, userId);
|
||||
const modules = await this.getUserModules(userId);
|
||||
|
||||
return {
|
||||
@@ -306,7 +306,7 @@ export class AuthService {
|
||||
departmentName: user.departments?.name,
|
||||
isDefaultPassword: user.is_default_password,
|
||||
permissions,
|
||||
modules, // 新增:返回模块列表
|
||||
modules,
|
||||
// 2026-01-28: 个人中心扩展字段
|
||||
avatarUrl: user.avatarUrl,
|
||||
status: user.status,
|
||||
@@ -471,13 +471,25 @@ export class AuthService {
|
||||
/**
|
||||
* 获取用户权限列表
|
||||
*/
|
||||
private async getUserPermissions(role: string): Promise<string[]> {
|
||||
private async getUserPermissions(role: string, userId?: string): Promise<string[]> {
|
||||
const rolePermissions = await prisma.role_permissions.findMany({
|
||||
where: { role: role as any },
|
||||
include: { permissions: true },
|
||||
});
|
||||
|
||||
return rolePermissions.map(rp => rp.permissions.code);
|
||||
const merged = new Set(rolePermissions.map(rp => rp.permissions.code));
|
||||
|
||||
if (userId) {
|
||||
const directPermissions = await prisma.user_permissions.findMany({
|
||||
where: { user_id: userId },
|
||||
include: { permissions: true },
|
||||
});
|
||||
for (const dp of directPermissions) {
|
||||
merged.add(dp.permissions.code);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(merged).sort();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -84,9 +84,11 @@ export async function createUser(
|
||||
try {
|
||||
const creator = request.user!;
|
||||
|
||||
// HOSPITAL_ADMIN 和 PHARMA_ADMIN 只能在自己的租户内创建用户
|
||||
// 跨租户创建检查:SUPER_ADMIN 和持有 ops:user-ops 的用户可跨租户
|
||||
const scope = await userService.getUserQueryScope(creator.role, creator.tenantId, creator.userId);
|
||||
const canCrossTenant = Object.keys(scope).length === 0;
|
||||
if (
|
||||
(creator.role === 'HOSPITAL_ADMIN' || creator.role === 'PHARMA_ADMIN') &&
|
||||
!canCrossTenant &&
|
||||
request.body.tenantId !== creator.tenantId
|
||||
) {
|
||||
return reply.status(403).send({
|
||||
@@ -360,10 +362,11 @@ export async function updateUserModules(
|
||||
try {
|
||||
const updater = request.user!;
|
||||
|
||||
// SUPER_ADMIN 可以操作任意租户
|
||||
// HOSPITAL_ADMIN/PHARMA_ADMIN 只能操作自己租户的用户
|
||||
// 跨租户操作检查:SUPER_ADMIN 和持有 ops:user-ops 的用户可操作任意租户
|
||||
const scope = await userService.getUserQueryScope(updater.role, updater.tenantId, updater.userId);
|
||||
const canCrossTenant = Object.keys(scope).length === 0; // 空 scope = 无限制
|
||||
if (
|
||||
updater.role !== 'SUPER_ADMIN' &&
|
||||
!canCrossTenant &&
|
||||
request.body.tenantId !== updater.tenantId
|
||||
) {
|
||||
return reply.status(403).send({
|
||||
@@ -463,6 +466,92 @@ export async function importUsers(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户直授权限
|
||||
* PUT /api/admin/users/:id/permissions
|
||||
*/
|
||||
export async function updateUserPermissions(
|
||||
request: FastifyRequest<{
|
||||
Params: { id: string };
|
||||
Body: { permissions: string[] };
|
||||
}>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const updater = request.user!;
|
||||
const result = await userService.updateUserDirectPermissions(
|
||||
request.params.id,
|
||||
request.body.permissions,
|
||||
updater.userId
|
||||
);
|
||||
|
||||
return reply.send({
|
||||
code: 0,
|
||||
message: '用户权限更新成功',
|
||||
data: { permissions: result },
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('[UserController] updateUserPermissions error:', error);
|
||||
|
||||
if (error.message.includes('不存在')) {
|
||||
return reply.status(404).send({ code: 404, message: error.message });
|
||||
}
|
||||
|
||||
return reply.status(500).send({
|
||||
code: 500,
|
||||
message: error.message || '更新权限失败',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户直授权限
|
||||
* GET /api/admin/users/:id/permissions
|
||||
*/
|
||||
export async function getUserPermissions(
|
||||
request: FastifyRequest<{ Params: { id: string } }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const directPerms = await userService.getUserDirectPermissions(request.params.id);
|
||||
return reply.send({
|
||||
code: 0,
|
||||
message: 'success',
|
||||
data: { permissions: directPerms },
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('[UserController] getUserPermissions error:', error);
|
||||
return reply.status(500).send({
|
||||
code: 500,
|
||||
message: error.message || '获取权限失败',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有可直授权限列表
|
||||
* GET /api/admin/users/options/permissions
|
||||
*/
|
||||
export async function getPermissionOptions(
|
||||
_request: FastifyRequest,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const permissions = await userService.getAllDirectGrantablePermissions();
|
||||
return reply.send({
|
||||
code: 0,
|
||||
message: 'success',
|
||||
data: permissions,
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('[UserController] getPermissionOptions error:', error);
|
||||
return reply.status(500).send({
|
||||
code: 500,
|
||||
message: error.message || '获取权限列表失败',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有租户列表(用于下拉选择)
|
||||
*/
|
||||
|
||||
@@ -28,25 +28,25 @@ export async function tenantRoutes(fastify: FastifyInstance) {
|
||||
// 创建租户
|
||||
// POST /api/admin/tenants
|
||||
fastify.post('/', {
|
||||
preHandler: [authenticate, requirePermission('tenant:create')],
|
||||
preHandler: [authenticate, requireAnyPermission('tenant:create', 'ops:user-ops')],
|
||||
handler: tenantController.createTenant,
|
||||
});
|
||||
|
||||
// 更新租户信息
|
||||
// PUT /api/admin/tenants/:id
|
||||
fastify.put('/:id', {
|
||||
preHandler: [authenticate, requirePermission('tenant:edit')],
|
||||
preHandler: [authenticate, requireAnyPermission('tenant:edit', 'ops:user-ops')],
|
||||
handler: tenantController.updateTenant,
|
||||
});
|
||||
|
||||
// 更新租户状态
|
||||
// PUT /api/admin/tenants/:id/status
|
||||
fastify.put('/:id/status', {
|
||||
preHandler: [authenticate, requirePermission('tenant:edit')],
|
||||
preHandler: [authenticate, requireAnyPermission('tenant:edit', 'ops:user-ops')],
|
||||
handler: tenantController.updateTenantStatus,
|
||||
});
|
||||
|
||||
// 删除租户
|
||||
// 删除租户(仅保留严格权限,运营人员不应删除租户)
|
||||
// DELETE /api/admin/tenants/:id
|
||||
fastify.delete('/:id', {
|
||||
preHandler: [authenticate, requirePermission('tenant:delete')],
|
||||
@@ -56,7 +56,7 @@ export async function tenantRoutes(fastify: FastifyInstance) {
|
||||
// 配置租户模块
|
||||
// PUT /api/admin/tenants/:id/modules
|
||||
fastify.put('/:id/modules', {
|
||||
preHandler: [authenticate, requirePermission('tenant:edit')],
|
||||
preHandler: [authenticate, requireAnyPermission('tenant:edit', 'ops:user-ops')],
|
||||
handler: tenantController.configureModules,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -31,28 +31,28 @@ export async function userRoutes(fastify: FastifyInstance) {
|
||||
// 创建用户
|
||||
// POST /api/admin/users
|
||||
fastify.post('/', {
|
||||
preHandler: [authenticate, requirePermission('user:create')],
|
||||
preHandler: [authenticate, requireAnyPermission('user:create', 'ops:user-ops')],
|
||||
handler: userController.createUser,
|
||||
});
|
||||
|
||||
// 更新用户
|
||||
// PUT /api/admin/users/:id
|
||||
fastify.put('/:id', {
|
||||
preHandler: [authenticate, requirePermission('user:edit')],
|
||||
preHandler: [authenticate, requireAnyPermission('user:edit', 'ops:user-ops')],
|
||||
handler: userController.updateUser,
|
||||
});
|
||||
|
||||
// 更新用户状态(启用/禁用)
|
||||
// PUT /api/admin/users/:id/status
|
||||
fastify.put('/:id/status', {
|
||||
preHandler: [authenticate, requirePermission('user:edit')],
|
||||
preHandler: [authenticate, requireAnyPermission('user:edit', 'ops:user-ops')],
|
||||
handler: userController.updateUserStatus,
|
||||
});
|
||||
|
||||
// 重置用户密码
|
||||
// POST /api/admin/users/:id/reset-password
|
||||
fastify.post('/:id/reset-password', {
|
||||
preHandler: [authenticate, requirePermission('user:edit')],
|
||||
preHandler: [authenticate, requireAnyPermission('user:edit', 'ops:user-ops')],
|
||||
handler: userController.resetUserPassword,
|
||||
});
|
||||
|
||||
@@ -77,7 +77,7 @@ export async function userRoutes(fastify: FastifyInstance) {
|
||||
// 更新用户在指定租户的模块权限
|
||||
// PUT /api/admin/users/:id/modules
|
||||
fastify.put('/:id/modules', {
|
||||
preHandler: [authenticate, requirePermission('user:edit')],
|
||||
preHandler: [authenticate, requireAnyPermission('user:edit', 'ops:user-ops')],
|
||||
handler: userController.updateUserModules,
|
||||
});
|
||||
|
||||
@@ -86,12 +86,35 @@ export async function userRoutes(fastify: FastifyInstance) {
|
||||
// 批量导入用户
|
||||
// POST /api/admin/users/import
|
||||
fastify.post('/import', {
|
||||
preHandler: [authenticate, requirePermission('user:create')],
|
||||
preHandler: [authenticate, requireAnyPermission('user:create', 'ops:user-ops')],
|
||||
handler: userController.importUsers,
|
||||
});
|
||||
|
||||
// ==================== 直授权限管理 ====================
|
||||
|
||||
// 获取用户直授权限
|
||||
// GET /api/admin/users/:id/permissions
|
||||
fastify.get('/:id/permissions', {
|
||||
preHandler: [authenticate, requireAnyPermission('user:view', 'ops:user-ops')],
|
||||
handler: userController.getUserPermissions,
|
||||
});
|
||||
|
||||
// 更新用户直授权限
|
||||
// PUT /api/admin/users/:id/permissions
|
||||
fastify.put('/:id/permissions', {
|
||||
preHandler: [authenticate, requireAnyPermission('user:edit', 'ops:user-ops')],
|
||||
handler: userController.updateUserPermissions,
|
||||
});
|
||||
|
||||
// ==================== 辅助接口 ====================
|
||||
|
||||
// 获取所有可直授权限列表
|
||||
// GET /api/admin/users/options/permissions
|
||||
fastify.get('/options/permissions', {
|
||||
preHandler: [authenticate, requireAnyPermission('user:view', 'ops:user-ops')],
|
||||
handler: userController.getPermissionOptions,
|
||||
});
|
||||
|
||||
// 获取所有租户列表(用于下拉选择)
|
||||
// GET /api/admin/users/options/tenants
|
||||
fastify.get('/options/tenants', {
|
||||
|
||||
@@ -60,6 +60,19 @@ export async function getUserQueryScope(
|
||||
const departmentId = userId ? await getUserDepartmentId(userId) : undefined;
|
||||
return { tenantId, departmentId };
|
||||
}
|
||||
case 'USER': {
|
||||
// USER 角色持有 ops:user-ops 权限时可访问管理端
|
||||
// 检查是否有直授权限(有则给予跨租户查看能力,否则限制本租户)
|
||||
if (userId) {
|
||||
const directPerms = await prisma.user_permissions.findMany({
|
||||
where: { user_id: userId },
|
||||
include: { permissions: { select: { code: true } } },
|
||||
});
|
||||
const hasOps = directPerms.some(dp => dp.permissions.code === 'ops:user-ops');
|
||||
if (hasOps) return {}; // 运营人员可查看全部用户
|
||||
}
|
||||
return { tenantId }; // 无 ops 权限则限制本租户
|
||||
}
|
||||
default:
|
||||
throw new Error('无权限访问用户管理');
|
||||
}
|
||||
@@ -280,12 +293,20 @@ export async function getUserById(userId: string, scope: UserQueryScope): Promis
|
||||
})
|
||||
);
|
||||
|
||||
// 获取用户权限(基于角色)
|
||||
// 获取用户权限(角色权限 + 用户直授权限合并)
|
||||
const rolePermissions = await prisma.role_permissions.findMany({
|
||||
where: { role: user.role },
|
||||
include: { permissions: true },
|
||||
});
|
||||
const permissions = rolePermissions.map((rp) => rp.permissions.code);
|
||||
const directPermissions = await prisma.user_permissions.findMany({
|
||||
where: { user_id: user.id },
|
||||
include: { permissions: true },
|
||||
});
|
||||
const permSet = new Set([
|
||||
...rolePermissions.map((rp) => rp.permissions.code),
|
||||
...directPermissions.map((dp) => dp.permissions.code),
|
||||
]);
|
||||
const permissions = Array.from(permSet).sort();
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
@@ -885,6 +906,72 @@ export async function getModulesByTenant(tenantId: string) {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户直授权限(不依赖角色,直接给用户授予/移除权限)
|
||||
*/
|
||||
export async function updateUserDirectPermissions(
|
||||
userId: string,
|
||||
permissionCodes: string[],
|
||||
updaterId: string
|
||||
): Promise<string[]> {
|
||||
const user = await prisma.user.findUnique({ where: { id: userId } });
|
||||
if (!user) {
|
||||
throw new Error('用户不存在');
|
||||
}
|
||||
|
||||
// 查询权限 ID
|
||||
const perms = await prisma.permissions.findMany({
|
||||
where: { code: { in: permissionCodes } },
|
||||
});
|
||||
const validCodes = perms.map((p) => p.code);
|
||||
const invalidCodes = permissionCodes.filter((c) => !validCodes.includes(c));
|
||||
if (invalidCodes.length > 0) {
|
||||
throw new Error(`以下权限代码不存在: ${invalidCodes.join(', ')}`);
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.user_permissions.deleteMany({ where: { user_id: userId } });
|
||||
|
||||
if (perms.length > 0) {
|
||||
await tx.user_permissions.createMany({
|
||||
data: perms.map((p) => ({
|
||||
user_id: userId,
|
||||
permission_id: p.id,
|
||||
})),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
logger.info('[UserService] User direct permissions updated', {
|
||||
userId,
|
||||
permissionCodes,
|
||||
updatedBy: updaterId,
|
||||
});
|
||||
|
||||
return validCodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户直授权限列表(不含角色权限)
|
||||
*/
|
||||
export async function getUserDirectPermissions(userId: string): Promise<string[]> {
|
||||
const directPerms = await prisma.user_permissions.findMany({
|
||||
where: { user_id: userId },
|
||||
include: { permissions: true },
|
||||
});
|
||||
return directPerms.map((dp) => dp.permissions.code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统所有可直授的权限列表
|
||||
*/
|
||||
export async function getAllDirectGrantablePermissions(): Promise<Array<{ code: string; name: string; description: string | null }>> {
|
||||
return prisma.permissions.findMany({
|
||||
select: { code: true, name: true, description: true },
|
||||
orderBy: { code: 'asc' },
|
||||
});
|
||||
}
|
||||
|
||||
// ============ 辅助函数 ============
|
||||
|
||||
function getModuleName(code: string): string {
|
||||
|
||||
@@ -180,7 +180,7 @@ export class AIController {
|
||||
activityService.log(
|
||||
user.tenantId,
|
||||
user.tenantName || null,
|
||||
user.id || user.userId,
|
||||
user.userId || user.id,
|
||||
user.name || null,
|
||||
'DC',
|
||||
'智能数据清洗',
|
||||
@@ -208,11 +208,11 @@ export class AIController {
|
||||
activityService.log(
|
||||
user.tenantId,
|
||||
user.tenantName || null,
|
||||
user.id,
|
||||
user.name,
|
||||
user.userId || user.id,
|
||||
user.name || null,
|
||||
'DC',
|
||||
'Tool C AI代码',
|
||||
'USE',
|
||||
'智能数据清洗',
|
||||
'COMPLETE',
|
||||
`session:${sessionId}, retries:${result.retryCount}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { sessionService } from '../services/SessionService.js';
|
||||
// @ts-ignore - uuid 类型定义
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { prisma } from '../../../../config/database.js';
|
||||
import { activityService } from '../../../../common/services/activity.service.js';
|
||||
|
||||
/**
|
||||
* 获取用户ID(从JWT Token中获取)
|
||||
@@ -71,6 +72,23 @@ export class QuickActionController {
|
||||
const userId = getUserId(request);
|
||||
|
||||
logger.info(`[QuickAction] 执行快速操作: action=${action}, sessionId=${sessionId}`);
|
||||
|
||||
// 埋点:DC 快速操作
|
||||
try {
|
||||
const user = (request as any).user;
|
||||
if (user) {
|
||||
activityService.log(
|
||||
user.tenantId,
|
||||
user.tenantName || null,
|
||||
user.userId || user.id,
|
||||
user.name || null,
|
||||
'DC',
|
||||
'智能数据清洗',
|
||||
'USE',
|
||||
`quick-action:${action}, session:${sessionId}`,
|
||||
);
|
||||
}
|
||||
} catch { /* 埋点失败不影响主业务 */ }
|
||||
|
||||
// 1. 验证参数
|
||||
if (!sessionId || !action || !params) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import { sessionService } from '../services/SessionService.js';
|
||||
import { dataProcessService } from '../services/DataProcessService.js';
|
||||
import { jobQueue } from '../../../../common/jobs/index.js';
|
||||
import * as xlsx from 'xlsx';
|
||||
import { activityService } from '../../../../common/services/activity.service.js';
|
||||
|
||||
/**
|
||||
* 获取用户ID(从JWT Token中获取)
|
||||
@@ -92,6 +93,23 @@ export class SessionController {
|
||||
|
||||
logger.info(`[SessionController] Session创建成功: ${sessionResult.id}, jobId: ${sessionResult.jobId}`);
|
||||
|
||||
// 埋点:DC 上传数据文件
|
||||
try {
|
||||
const user = (request as any).user;
|
||||
if (user) {
|
||||
activityService.log(
|
||||
user.tenantId,
|
||||
user.tenantName || null,
|
||||
user.userId || user.id,
|
||||
user.name || null,
|
||||
'DC',
|
||||
'智能数据清洗',
|
||||
'USE',
|
||||
`upload:${fileName}, session:${sessionResult.id}`,
|
||||
);
|
||||
}
|
||||
} catch { /* 埋点失败不影响主业务 */ }
|
||||
|
||||
// 6. 返回Session信息 + jobId(用于前端轮询)
|
||||
return reply.code(201).send({
|
||||
success: true,
|
||||
|
||||
@@ -16,6 +16,7 @@ import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { logger } from '../../../../common/logging/index.js';
|
||||
import { aiCodeService } from '../services/AICodeService.js';
|
||||
import { sessionService } from '../services/SessionService.js';
|
||||
import { activityService } from '../../../../common/services/activity.service.js';
|
||||
|
||||
// ==================== 类型定义 ====================
|
||||
|
||||
@@ -49,6 +50,23 @@ export class StreamAIController {
|
||||
const { sessionId, message, maxRetries = 3 } = request.body as StreamProcessBody;
|
||||
|
||||
logger.info(`[StreamAI] 收到流式处理请求: sessionId=${sessionId}`);
|
||||
|
||||
// 埋点:记录 DC 智能数据清洗启动
|
||||
try {
|
||||
const user = (request as any).user;
|
||||
if (user) {
|
||||
activityService.log(
|
||||
user.tenantId,
|
||||
user.tenantName || null,
|
||||
user.userId || user.id,
|
||||
user.name || null,
|
||||
'DC',
|
||||
'智能数据清洗',
|
||||
'START',
|
||||
`session:${sessionId}`,
|
||||
);
|
||||
}
|
||||
} catch { /* 埋点失败不影响主业务 */ }
|
||||
|
||||
// 参数验证
|
||||
if (!sessionId || !message) {
|
||||
@@ -151,6 +169,23 @@ export class StreamAIController {
|
||||
retryCount: attempt,
|
||||
});
|
||||
|
||||
// 埋点:记录 DC 智能数据清洗完成
|
||||
try {
|
||||
const user = (request as any).user;
|
||||
if (user) {
|
||||
activityService.log(
|
||||
user.tenantId,
|
||||
user.tenantName || null,
|
||||
user.userId || user.id,
|
||||
user.name || null,
|
||||
'DC',
|
||||
'智能数据清洗',
|
||||
'COMPLETE',
|
||||
`session:${sessionId}, retries:${attempt}`,
|
||||
);
|
||||
}
|
||||
} catch { /* 埋点失败不影响主业务 */ }
|
||||
|
||||
// 发送结束标记
|
||||
reply.raw.write('data: [DONE]\n\n');
|
||||
reply.raw.end();
|
||||
|
||||
Reference in New Issue
Block a user