refactor(server): 清理全量 any 类型安全警告 (692 → 0)

- 全模块类型化:controller 的 req: any → AuthenticatedRequest/RequestUser,
  聚合查询 getRawMany 泛型标注、导入行/响应体定义具体 interface、
  catch (e: any) → unknown + 收窄、no-base-to-string 用 String() 显式转换
- 第三方无类型库边界(pdfkit/exceljs)文件级或单行 disable 并注明理由
- 顺带修复:get-business-context.tool 两个 require-await error、
  bills.controller 参数顺序隐患、main.ts compression 调用
- 运行时逻辑零改动;测试 142 套件 / 1065 用例全部通过
This commit is contained in:
2026-08-08 09:28:23 +08:00
parent 8b5fa98028
commit a644a8de42
63 changed files with 690 additions and 338 deletions

View File

@@ -6,7 +6,7 @@ import { AttendanceImportService } from './attendance-import.service';
import { OperationLogsService } from '../operation-logs/operation-logs.service';
import { AuthorizationService } from '../authorization';
import { logAudit } from '../common/with-audit-log';
import { extractRequestInfo } from '../common/request-utils';
import { extractRequestInfo, type RequestInfoSource } from '../common/request-utils';
import { RequirePermission } from '../auth/decorators/permission.decorator';
import { DingTalkImportDto } from './dto/dingtalk-import.dto';
import { QueryDingRawDto, MatchDingRecordDto } from './dto/attendance.dto';
@@ -35,7 +35,7 @@ export class AttendanceImportController extends AttendanceControllerBase {
async matchDingRecord(
@Param('id', ParseIntPipe) id: number,
@Body() dto: MatchDingRecordDto,
@Request() req: any,
@Request() req: { user: RequestUser },
) {
const result = await this.service.matchDingRecord(id, dto);
await logAudit(this.logService, req, {
@@ -69,7 +69,10 @@ export class AttendanceImportController extends AttendanceControllerBase {
*/
@Post('attendance-records/import/dingtalk')
@RequirePermission('attendance:create')
async importFromDingTalk(@Body() dto: DingTalkImportDto, @Request() req: { user: RequestUser }) {
async importFromDingTalk(
@Body() dto: DingTalkImportDto,
@Request() req: { user: RequestUser } & RequestInfoSource,
) {
const { ipAddress, userAgent } = extractRequestInfo(req);
const canManageAll = this.canManageAllAttendance(req);
let userIds: string[];

View File

@@ -6,7 +6,7 @@ import { AttendanceImportService } from './attendance-import.service';
import { OperationLogsService } from '../operation-logs/operation-logs.service';
import { AuthorizationService } from '../authorization';
import { logAudit } from '../common/with-audit-log';
import { extractRequestInfo } from '../common/request-utils';
import { extractRequestInfo, type RequestInfoSource } from '../common/request-utils';
import { RequirePermission } from '../auth/decorators/permission.decorator';
import {
BatchCreateAttendanceDto,
@@ -114,7 +114,10 @@ export class AttendanceRecordsController extends AttendanceControllerBase {
// ── Batch create attendance records ──
@Post('attendance-records/batch')
@RequirePermission('attendance:create')
async batchCreate(@Body() dto: BatchCreateAttendanceDto, @Request() req: any) {
async batchCreate(
@Body() dto: BatchCreateAttendanceDto,
@Request() req: { user: RequestUser } & RequestInfoSource,
) {
const { ipAddress, userAgent } = extractRequestInfo(req);
const canManageAll = this.canManageAllAttendance(req);
if (!canManageAll && dto.records.some((record) => record.classId == null)) {
@@ -144,7 +147,7 @@ export class AttendanceRecordsController extends AttendanceControllerBase {
// ── Generate attendance records from schedules (with optional date range) ──
@Post('attendance-records/generate-from-schedules')
@RequirePermission('attendance:create')
async generateFromSchedules(@Body() dto: GenerateFromSchedulesDto, @Request() req: any) {
async generateFromSchedules(@Body() dto: GenerateFromSchedulesDto, @Request() req: { user: RequestUser }) {
await this.assertClassAccess(req, dto.classId);
const result = await this.service.generateFromSchedules(dto);
await logAudit(this.logService, req, {
@@ -239,7 +242,7 @@ export class AttendanceRecordsController extends AttendanceControllerBase {
@RequirePermission('attendance:edit', 'attendance:self-edit')
async batchUpdateStatus(
@Body() dto: BatchUpdateAttendanceStatusDto,
@Request() req: any,
@Request() req: { user: RequestUser },
) {
const failedIds: number[] = [];
let updated = 0;
@@ -269,7 +272,7 @@ export class AttendanceRecordsController extends AttendanceControllerBase {
async update(
@Param('id', ParseIntPipe) id: number,
@Body() dto: UpdateAttendanceRecordDto,
@Request() req: any,
@Request() req: { user: RequestUser },
) {
const existing = await this.service.findAttendanceRecord(id);
if (existing.classId == null && !this.canManageAllAttendance(req)) {
@@ -286,7 +289,7 @@ export class AttendanceRecordsController extends AttendanceControllerBase {
// ── Delete a single attendance record ──
@Delete('attendance-records/:id')
@RequirePermission('attendance:edit', 'attendance:self-edit')
async remove(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
async remove(@Param('id', ParseIntPipe) id: number, @Request() req: { user: RequestUser }) {
const existing = await this.service.findAttendanceRecord(id);
if (existing.classId == null && !this.canManageAllAttendance(req)) {
throw new ForbiddenException('无权删除未关联班级的考勤记录');
@@ -334,7 +337,7 @@ export class AttendanceRecordsController extends AttendanceControllerBase {
async exportReport(
@Query() query: AttendanceReportQueryDto,
@Res() res: Response,
@Request() req: any,
@Request() req: { user: RequestUser },
) {
if (query.classId) await this.assertClassAccess(req, query.classId);
const reportData = await this.service.getReport(query, await this.getAccessibleClassIds(req));