feat(rbac): 数据权限扩展至组织树与审计/登录日志

- orgs: 列表按可见部门过滤
- audit-logs/login-logs: 按操作人所属部门过滤(self 按 userId/username)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
liuyun 2026-10-11 16:24:51 +08:00
parent 78e537fa73
commit 537ec4fd91
4 changed files with 50 additions and 3 deletions

View File

@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { InjectPinoLogger, PinoLogger } from 'nestjs-pino';
import { BaseCrudService } from '../common/base-crud.service.js';
import { PrismaService } from '../prisma/prisma.service.js';
import { getRequestContext } from '../common/context/request-context.js';
import { ListAuditLogQuery } from './dto.js';
export interface AuditLogEntry {
@ -39,11 +40,28 @@ export class AuditLogsService extends BaseCrudService {
}
}
listAuditLogs(q: ListAuditLogQuery) {
async listAuditLogs(q: ListAuditLogQuery) {
const ctx = getRequestContext();
const where: Record<string, unknown> = {};
if (q.username) where.username = { contains: q.username };
if (q.module) where.module = { contains: q.module };
if (q.status) where.status = q.status;
// 数据权限:仅本人按 userId;否则按可见部门内的操作人
if (ctx && ctx.dataScope !== 'all') {
if (ctx.dataScope === 'self') {
where.userId = ctx.userId;
} else {
const userIds = ctx.orgCodes.length
? (
await this.prisma.client.user.findMany({
where: { tenantCode: ctx.tenantCode, orgCode: { in: ctx.orgCodes }, deletedAt: null },
select: { id: true },
})
).map((u) => u.id)
: [];
where.userId = { in: userIds };
}
}
return this.paginate(q.page, q.pageSize, where);
}
}

View File

@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { InjectPinoLogger, PinoLogger } from 'nestjs-pino';
import { BaseCrudService } from '../common/base-crud.service.js';
import { PrismaService } from '../prisma/prisma.service.js';
import { getRequestContext } from '../common/context/request-context.js';
import { ListLoginLogQuery } from './dto.js';
export interface LoginLogEntry {
@ -31,10 +32,27 @@ export class LoginLogsService extends BaseCrudService {
}
}
listLoginLogs(q: ListLoginLogQuery) {
async listLoginLogs(q: ListLoginLogQuery) {
const ctx = getRequestContext();
const where: Record<string, unknown> = {};
if (q.username) where.username = { contains: q.username };
if (q.status) where.status = q.status;
// 数据权限:仅本人按 username;否则按可见部门内的用户
if (ctx && ctx.dataScope !== 'all') {
if (ctx.dataScope === 'self') {
where.username = ctx.username;
} else {
const usernames = ctx.orgCodes.length
? (
await this.prisma.client.user.findMany({
where: { tenantCode: ctx.tenantCode, orgCode: { in: ctx.orgCodes }, deletedAt: null },
select: { username: true },
})
).map((u) => u.username)
: [];
where.username = { in: usernames };
}
}
return this.paginate(q.page, q.pageSize, where);
}
}

View File

@ -11,7 +11,7 @@ export class OrgsController {
@Get()
@RequirePermissions(Permissions.org.list)
list() {
return this.service.list({}, { sort: 'asc' });
return this.service.listOrgs();
}
@Get(':id')

View File

@ -1,6 +1,7 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { BaseCrudService } from '../common/base-crud.service.js';
import { PrismaService } from '../prisma/prisma.service.js';
import { getRequestContext } from '../common/context/request-context.js';
import { CreateOrgDto, UpdateOrgDto } from './dto.js';
@Injectable()
@ -9,6 +10,16 @@ export class OrgsService extends BaseCrudService {
super(prisma, 'org');
}
/** 组织树列表:按数据权限过滤(空 orgCodes=全部可见)。 */
listOrgs() {
const ctx = getRequestContext();
const where: Record<string, unknown> = {};
if (ctx && ctx.orgCodes.length > 0) {
where.code = { in: ctx.orgCodes };
}
return this.list(where, { sort: 'asc' });
}
async createOrg(dto: CreateOrgDto) {
await this.assertValidParent(dto.parentCode);
return this.create({ ...dto });