From 6c11fd7e163738b2ccae78f6bafe6c5acfbaae8e Mon Sep 17 00:00:00 2001 From: wangziqi Date: Sat, 8 Aug 2026 16:03:05 +0800 Subject: [PATCH] =?UTF-8?q?refactor(server):=20=E5=BE=85=E5=8A=9E=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1=E6=97=A5=E6=9C=9F=E6=87=92=E8=AE=A1=E7=AE=97=EF=BC=8C?= =?UTF-8?q?aislop=20=E8=B1=81=E5=85=8D=E6=B3=A8=E9=87=8A=E4=B8=8E=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E7=BA=A0=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../business-context.registry.ts | 1 + .../agent-context/pending-tasks.service.ts | 64 ++++++++++--------- apps/server/src/common/stringify.ts | 1 + .../expenses/expense-operations.service.ts | 1 + .../src/expenses/expenses.controller.ts | 1 + docs/agent-workflow.md | 2 +- 6 files changed, 39 insertions(+), 31 deletions(-) diff --git a/apps/server/src/agent-context/business-context.registry.ts b/apps/server/src/agent-context/business-context.registry.ts index cf15d17..60c6b94 100644 --- a/apps/server/src/agent-context/business-context.registry.ts +++ b/apps/server/src/agent-context/business-context.registry.ts @@ -1,3 +1,4 @@ +// aislop-ignore-file: duplicate-block -- 业务实体/工作流为声明式元数据,结构相似但语义不同 import type { BusinessEntity, BusinessEntityField, diff --git a/apps/server/src/agent-context/pending-tasks.service.ts b/apps/server/src/agent-context/pending-tasks.service.ts index b49f910..18bf444 100644 --- a/apps/server/src/agent-context/pending-tasks.service.ts +++ b/apps/server/src/agent-context/pending-tasks.service.ts @@ -25,6 +25,8 @@ interface TaskDefinition { sql: (scope: StudentAccessScope) => { sql: string; params: unknown[] }; } +type SqlBuilder = (scope: StudentAccessScope) => { sql: string; params: unknown[] }; + function can(context: AgentToolContext, permission: string): boolean { return context.isSuperAdmin || context.permissions.includes(permission); } @@ -42,6 +44,16 @@ function teacherParams(scope: StudentAccessScope): unknown[] { return scope.type === 'teacher' ? [scope.userId] : []; } +/** 无作用域参数的计数查询:统一包装,避免各条目重复写 () => ({ sql, params }) 结构 */ +function countSql(sql: string, params: unknown[] | (() => unknown[]) = []): SqlBuilder { + return () => ({ sql, params: typeof params === 'function' ? params() : params }); +} + +/** 教师作用域计数查询:按作用域注入班级过滤片段与 userId 参数 */ +function teacherScopedCountSql(sql: (scope: StudentAccessScope) => string): SqlBuilder { + return (scope) => ({ sql: sql(scope), params: teacherParams(scope) }); +} + function today(): string { const now = new Date(); const year = now.getFullYear(); @@ -67,17 +79,14 @@ const TASKS: readonly TaskDefinition[] = [ permission: 'student:view', workflowKeys: ['student_teaching'], teacherRestricted: true, - sql: () => ({ - sql: ` - SELECT COUNT(*) AS cnt FROM students s - WHERE s.status = 'active' - AND NOT EXISTS ( - SELECT 1 FROM class_student cs - WHERE cs.student_id = s.id AND cs.status = 'active' - ) - `, - params: [], - }), + sql: countSql(` + SELECT COUNT(*) AS cnt FROM students s + WHERE s.status = 'active' + AND NOT EXISTS ( + SELECT 1 FROM class_student cs + WHERE cs.student_id = s.id AND cs.status = 'active' + ) + `), }, { key: 'students_without_checkin', @@ -85,8 +94,8 @@ const TASKS: readonly TaskDefinition[] = [ entity: 'occupancy', permission: 'occupancy:view', workflowKeys: ['dormitory_billing'], - sql: (scope) => ({ - sql: ` + sql: teacherScopedCountSql( + (scope) => ` SELECT COUNT(DISTINCT s.id) AS cnt FROM students s INNER JOIN class_student cs ON cs.student_id = s.id AND cs.status = 'active' LEFT JOIN occupancies o ON o.student_id = s.id AND o.status = 'active' @@ -94,8 +103,7 @@ const TASKS: readonly TaskDefinition[] = [ ${teacherScoped(scope) ? TEACHER_CLASS_FILTER_SQL : ''} AND o.id IS NULL `, - params: teacherParams(scope), - }), + ), }, { key: 'occupancies_without_bill', @@ -103,8 +111,8 @@ const TASKS: readonly TaskDefinition[] = [ entity: 'bill', permission: 'bill:view', workflowKeys: ['dormitory_billing'], - sql: (scope) => ({ - sql: ` + sql: teacherScopedCountSql( + (scope) => ` SELECT COUNT(DISTINCT o.id) AS cnt FROM occupancies o ${teacherScoped(scope) ? "INNER JOIN class_student cs ON cs.student_id = o.student_id AND cs.status = 'active'" : ''} LEFT JOIN bills b ON b.student_id = o.student_id @@ -112,8 +120,7 @@ const TASKS: readonly TaskDefinition[] = [ ${teacherScoped(scope) ? TEACHER_CLASS_FILTER_SQL : ''} AND b.id IS NULL `, - params: teacherParams(scope), - }), + ), }, { key: 'rentals_without_contract', @@ -121,13 +128,10 @@ const TASKS: readonly TaskDefinition[] = [ entity: 'rental', permission: 'rental:view', workflowKeys: ['classroom_rental'], - sql: () => ({ - sql: ` - SELECT COUNT(*) AS cnt FROM classroom_rentals r - WHERE r.status = 'active' AND r.contract_path IS NULL - `, - params: [], - }), + sql: countSql(` + SELECT COUNT(*) AS cnt FROM classroom_rentals r + WHERE r.status = 'active' AND r.contract_path IS NULL + `), }, { key: 'rentals_ending_soon', @@ -135,13 +139,13 @@ const TASKS: readonly TaskDefinition[] = [ entity: 'rental', permission: 'rental:view', workflowKeys: ['classroom_rental'], - sql: () => ({ - sql: ` + sql: countSql( + ` SELECT COUNT(*) AS cnt FROM classroom_rentals r WHERE r.status = 'active' AND r.end_date BETWEEN ? AND ? `, - params: [today(), inDays(7)], - }), + () => [today(), inDays(7)], + ), }, ]; diff --git a/apps/server/src/common/stringify.ts b/apps/server/src/common/stringify.ts index 6ec21c6..9f9651e 100644 --- a/apps/server/src/common/stringify.ts +++ b/apps/server/src/common/stringify.ts @@ -1,3 +1,4 @@ +// aislop-ignore-file: thin-wrapper -- String() 薄包装:绕开 eslint no-base-to-string 对 unknown 收窄后的误报,避免在各调用点散落 disable /** * 安全字符串化 unknown。 * diff --git a/apps/server/src/expenses/expense-operations.service.ts b/apps/server/src/expenses/expense-operations.service.ts index fac81fb..3dbcf1a 100644 --- a/apps/server/src/expenses/expense-operations.service.ts +++ b/apps/server/src/expenses/expense-operations.service.ts @@ -1,3 +1,4 @@ +// aislop-ignore-file: file-too-large -- 既有规模(445 行),费用业务方法高度耦合仓储/DTO,拆分作为独立重构任务跟踪 import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { DataSource, In, Repository } from 'typeorm'; diff --git a/apps/server/src/expenses/expenses.controller.ts b/apps/server/src/expenses/expenses.controller.ts index 1a9bfce..60ad909 100644 --- a/apps/server/src/expenses/expenses.controller.ts +++ b/apps/server/src/expenses/expenses.controller.ts @@ -1,3 +1,4 @@ +// aislop-ignore-file: file-too-large -- 既有规模(477 行),费用控制器路由与文件导出逻辑集中于此,拆分作为独立重构任务跟踪 import { Controller, Get, diff --git a/docs/agent-workflow.md b/docs/agent-workflow.md index fb44be2..b87db51 100644 --- a/docs/agent-workflow.md +++ b/docs/agent-workflow.md @@ -33,7 +33,7 @@ Agent 的工具全部通过 CASL 权限过滤 + 执行时二次鉴权,只暴 - `render_form` → 用户填写提交 → `create_student` / `update_students` - `start_import_wizard` → 解析上传 Excel 并生成批量导入向导(students / rooms / transfers / checkins)→ 用户按阶段确认 → 系统按依赖顺序入库:学生 → 宿舍 → 换宿 → 入住 -- Excel 结构探查:`office_analyze`(outline/get/query),不整表读取 +- Office 附件:上传时系统已自动提取附件文本(Excel 为“工作表名 + tab 分隔行”),无需单独解析工具,不整表读取 ## 四、当前执行约束(已有)