commit e2a4d40c9e69c1aa5e431d3f404d3a2d7a571114 Author: xunhe Date: Thu Aug 27 17:34:35 2026 +0800 init: 2026Technology-Competition initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89ae8af --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Java +backend/**/target/ +backend/**/*.class +*.jar +*.war + +# Node +frontend/node_modules/ +frontend/dist/ + +# IDE +.idea/ +*.iml +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Env +.env.local +.env.production.local + +# Logs & build artifacts +backend/*.log* +frontend/*.log* +backend-run.log +frontend/e2e/*.log* +frontend/tsconfig.tsbuildinfo +frontend/test-results/ +frontend/e2e/playwright-report/ diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF new file mode 100644 index 0000000..f3231f1 --- /dev/null +++ b/META-INF/MANIFEST.MF @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +Created-By: Maven JAR Plugin 3.4.2 +Build-Jdk-Spec: 17 +Implementation-Title: ims-web +Implementation-Version: 1.0.0-SNAPSHOT + diff --git a/README.md b/README.md new file mode 100644 index 0000000..964359a --- /dev/null +++ b/README.md @@ -0,0 +1,169 @@ +# 指摘管理系统(IMS) + +基于 AI Agent 驱动的质量问题全生命周期管理系统。 + +## 技术栈 + +| 层级 | 技术选型 | +|------|---------| +| 前端 | React 19 + TypeScript + Vite + Ant Design 6 | +| 后端 | Java 17 + Spring Boot 3.5 + Spring Security | +| 数据库 | PostgreSQL 16 + pgvector | +| 缓存 | Redis 7 | +| 对象存储 | MinIO | +| AI 引擎 | Ollama(本地,默认)/ DeepSeek API(备选) | + +## 目录结构 + +``` +├── backend/ 后端(Maven 多模块) +│ ├── ims-common/ 公共模块(工具类、统一返回结构) +│ ├── ims-api/ API 模块(Controller 定义、DTO) +│ ├── ims-service/ 业务模块(Entity、Repository、Security) +│ └── ims-web/ Web 启动模块 +├── frontend/ 前端(Vite + React) +├── docs/ 设计文档 +└── docker-compose.yml 数据库层(PostgreSQL + Redis + MinIO) +``` + +## 快速启动 + +**说明**:数据库(PostgreSQL/Redis/MinIO)运行在 Docker 中,Docker 在 WSL2 (Ubuntu) 里。后端必须在 WSL2 里运行,前端可在 Windows 运行。 + +``` +┌─────────────────────────────────────────────┐ +│ Windows │ +│ ├─ WSL2 (Ubuntu) — Docker 数据库 │ +│ ├─ WSL2 (Ubuntu) — 后端 (Spring Boot 8080) │ +│ └─ Windows — 前端 (Vite 5173) │ +└─────────────────────────────────────────────┘ +``` + +### 环境要求 + +| 组件 | 版本要求 | 安装确认 | +|------|---------|---------| +| Java | 17+ | `java -version` | +| Maven | 3.8+ | `mvn -version` | +| Node.js | 18+ | `node -v` | +| npm | 9+ | `npm -v` | +| Docker | 24+ | `docker --version` | +| Docker Compose | V2 | `docker compose version` | +| WSL2 (Ubuntu) | 22.04+ | `wsl -l -v` | + +### 准备工作(只需做一次) + +#### 1. 启动基础设施(PostgreSQL + Redis + MinIO + Ollama) + +Ollama 作为 Docker 容器运行,无需单独安装。首次执行以下命令时,Docker 会自动从镜像仓库拉取所有服务。 + +在 WSL2 (Ubuntu) 终端执行: + +```bash +# 停掉 WSL2 自带的 Redis(避免端口冲突) +sudo systemctl stop redis-server 2>/dev/null + +# 进入项目目录(注意:你的项目路径可能不同,按实际修改) +cd /path/to/ims-master-test + +# 启动所有基础设施(含 Ollama) +docker compose up -d + +# 确认所有容器正常运行 +docker compose ps +``` + +#### 2. 初始化 MinIO Bucket(知识库上传需要) + +1. 浏览器打开 [http://localhost:9001](http://localhost:9001) +2. 账号:`minioadmin` / 密码:`minioadmin` +3. 左侧 Buckets → Create Bucket → 输入 `ims-attachments` → 确认 + +#### 3. 拉取 AI 模型(仅第一次需要) + +Ollama 容器启动时**不包含任何模型**,需要手动拉取。拉取的模型保存在 Docker 数据卷 `ollama_data` 中,后续重启容器无需重新拉取。 + +**第一次启动顺序:** +```bash +# 1. 启动所有服务(含 Ollama 容器) +docker compose up -d + +# 2. 拉取向量化模型(知识库检索需要) +docker exec ims-ollama ollama pull nomic-embed-text + +# 3. 拉取对话模型(AI Agent 分析需要) +docker exec ims-ollama ollama pull llama3.1:8b + +# 4. 确认模型已拉取 +docker exec ims-ollama ollama list +``` + +**后续启动:** 只需执行 `docker compose up -d`,之前拉取的模型自动可用,无需再执行 `docker exec ollama pull`。 + +> 模型文件较大(`llama3.1:8b` 约 4.9GB),拉取时间取决于网络,看到 `success` 即完成。 + +### 启动项目 + +需要同时开两个终端: + +**终端 1 — WSL2 (Ubuntu)**:启动后端 + +```bash +# 进入后端目录(路径按实际修改) +cd /path/to/ims-master-test/backend + +# 首次或改过代码后执行(编译打包) +mvn install -DskipTests -U + +# 启动后端 +cd ims-web && mvn spring-boot:run -Dspring-boot.run.profiles=dev +``` + +> 首次启动会下载大量依赖,可能需要几分钟。看到 `Started IMSApplication` 即启动成功。 + +**终端 2 — Windows PowerShell(或 WSL)**:启动前端 + +```powershell +cd /path/to/ims-master-test/frontend + +# 首次或改过 package.json 后执行 +npm install + +# 启动前端 +npm run dev +``` + +> 看到 `VITE v6.x.x ready in xxx ms` 即启动成功。 + +### 登录系统 + +浏览器访问 **http://localhost:5173**,使用以下账号登录: + +| 账号 | 密码 | 角色 | +|------|------|------| +| admin | Admin@2026 | 超级管理员 | + +### 验证启动是否正常 + +登录后: +1. 左侧菜单→**工作台**,页面正常显示即前后端通 +2. 左侧菜单→**知识库管理**,页面能正常打开 +3. 尝试上传一个 `.txt` 文件,状态变为 `completed` 即全链路正常 + +### 常见问题 + +| 问题 | 原因 | 解决 | +|------|------|------| +| 端口 6379 被占用 | WSL2 自带的 Redis 在运行 | `sudo systemctl stop redis-server` | +| 数据库连接失败 | 容器未就绪 | `docker compose ps` 检查,等几秒后再试 | +| MinIO bucket 不存在 | 未创建 | 打开 `http://localhost:9001` 创建 `ims-attachments` | +| Ollama 连不上 | Ollama 容器未运行或未就绪 | `docker compose ps` 检查 ollama 状态,`docker logs ims-ollama` 查看日志 | +| mvn: command not found | Java/Maven 未安装 | `sudo apt install openjdk-17-jdk maven -y` | +| npm: command not found | Node.js 未安装 | 从 [nodejs.org](https://nodejs.org) 下载安装 | +| Token 过期 403/跳登录 | 超过 30 分钟未操作 | 自动刷新 token,重新登录即可 | +| 上传文件报 500 | 依赖版本冲突 | 执行 `mvn install -DskipTests -U` 后重启 | + +## 分支说明 + +- `master` — 项目骨架,各组基于此分支创建功能分支 +- 各功能模块在独立分支上开发,完成后合并回 master diff --git a/_AI_USAGE_LOG.md b/_AI_USAGE_LOG.md new file mode 100644 index 0000000..e911f5e --- /dev/null +++ b/_AI_USAGE_LOG.md @@ -0,0 +1,7 @@ +# AI 使用日志 + +| 日期时间 | 范式步骤 | 修改摘要 | 涉及文件 | 使用模型 | +| --- | --- | --- | --- | --- | +| 2026-08-20 18:40 | Bug修复 | 修复 Spring Boot 循环依赖:ToolRegistry → AiAnalysisTool → ModelRoutingService → ToolRegistry,在 ModelRoutingService 构造器的 ToolRegistry 参数上添加 @Lazy 注解打破循环 | ModelRoutingService.java | mimo-v2.5-free | +| 2026-08-21 | Bug修复 | 从git历史恢复损坏的IssueRepository.java文件,解决100+个"IssueRepository cannot be resolved"编译错误;补充AiAnalysisControllerImpl.java缺失的LocalDateTime导入 | IssueRepository.java, AiAnalysisControllerImpl.java | mimo-v2.5-free | +| 2026-08-21 | 代码清理 | 清理11个文件中未使用的import(共13处)、1个未使用方法(vectorToString)、1个未使用字段(log)、1处不必要的@SuppressWarnings | AgentApprovalRequest.java, JwtUtil.java, ModelRoutingService.java, DocumentParserService.java, KnowledgeService.java, SearchLogService.java, SearchService.java, AgentControllerImpl.java, AuthControllerImpl.java, PromptService.java, RoutingEmbeddingService.java, DashboardService.java | mimo-v2.5-free | diff --git a/_DEVELOPMENT_GUIDE.md b/_DEVELOPMENT_GUIDE.md new file mode 100644 index 0000000..7560e52 --- /dev/null +++ b/_DEVELOPMENT_GUIDE.md @@ -0,0 +1,286 @@ +# 指摘管理系统 · 开发指南 + +## 1. 当前已完成(master 骨架) + +### 后端 +| 组件 | 状态 | 说明 | +|------|------|------| +| Maven 多模块结构 | ✅ | ims-common / ims-api / ims-service / ims-web | +| 数据库表 & Flyway 迁移 | ✅ | 22 张表,7 个迁移脚本 | +| JPA Entity + Repository | ✅ | 22 Entity + 22 Repository | +| Spring Security + JWT | ✅ | 登录接口真实实现 | +| 所有 Controller 定义 | ✅ | 11 个 Controller,接口签名已定义 | +| 统一返回结构 | ✅ | ApiResponse / PageResult / 全局异常处理 | +| CRUD DTO 类 | ✅ | 26 个请求/响应类 | +| docker-compose | ✅ | PostgreSQL+pgvector / Redis / MinIO | +| 配置文件 | ✅ | application.yml / dev / prod | + +### 前端 +| 组件 | 状态 | 说明 | +|------|------|------| +| Vite + React + TypeScript | ✅ | 项目初始化 | +| Ant Design 主题 | ✅ | 品牌色、圆角已配置 | +| 路由表 | ✅ | 全部 13 个页面路由 + 懒加载 + 路由守卫 | +| 登录页 | ✅ | 表单 + API 调用 + token 存储 + 跳转 | +| 全局 Layout | ✅ | 侧边栏菜单树 + 顶栏(用户/通知) | +| axios 封装 | ✅ | 请求拦截器(自动带 token)+ 响应拦截器(401 跳登录) | +| Redux Store | ✅ | auth slice + 各模块 slice 空壳 | + +--- + +## 2. 开发流程 + +### 两阶段 + +``` +Phase 1(你 ─ 当前阶段) + master + └── 实现知识库模块(后端 Service + 前端页面) + 完成后合并到 master,通知其他组 + +Phase 2(各组 ─ 知识库完成后) + master(含知识库模块) + ├── feature/group1 指摘 CRUD + 工作台 + 驾驶舱 + ├── feature/group2 批量录入 + 系统管理 + └── feature/group3 AI 分析 + Prompt + Agent + 各组从 master 拉分支 → 独立开发 → PR → 合入 master +``` + +### 分支策略 +``` +master ── 骨架 + 知识库模块,各组以此为基线 + ├── feature/group1 + ├── feature/group2 + └── feature/group3 +``` + +### 合并规则 +| 阶段 | 操作 | +|------|------| +| 日常开发 | 各组在自己分支上提交,不往 master 推 | +| 功能完成 | 发起 Pull Request,指定 reviewer 审核 | +| 审核通过 | 通过 PR 合入 master,禁止直接 push | + +### 不冲突保障 +各组修改范围完全不重叠: + +``` +backend/ims-api/src/main/java/com/ims/api/controller/ + ├── IssueController.java ← 一组 + ├── UserController.java ← 二组 + ├── KnowledgeController.java ← 你 + ├── AiAnalysisController.java ← 三组 + └── ...其余 Controller 同理 ← 各组对应 + +frontend/src/pages/ + ├── issues/ ← 一组 + ├── dashboard/ ← 一组 + ├── batch-input/ ← 二组 + ├── ai-analysis/ ← 三组 + ├── knowledge-base/ ← 你 + └── system/ ← 二组 + +backend/ims-service/src/main/java/com/ims/service/ + ├── entity/ ← 仅 master 改,各组不能动 + ├── repository/ ← 仅 master 改,各组不能动 + └── security/ ← 仅 master 改,各组不能动 +``` + +**核心原则**:Entity 和 Repository 是共享契约,任何人不得修改 master 上的 Entity 字段和 Repository 方法签名。 + +--- + +## 3. 各组分工 + +### 高优先级功能定义 + +| 优先级 | 功能 | 说明 | +|--------|------|------| +| **P0** | 批量上传 | 历史评审数据与新增数据的快速录入 | +| **P0** | AI 自动分析 | 针对上传数据实时提取风险点、改进项 | +| **P1** | 问题一览管理 | 对 AI 分析结果统一管理,支持进度追踪 | +| **P1** | 基础仪表盘 | 图表展示质量趋势与风险分布 | +| **P1** | 对策状况登记 | 针对问题登记解决方案,含负责人与期限 | + +### 优先级 vs 现有模块映射 + +| 优先级 | 对应模块 | 归属 | +|--------|---------|------| +| P0 批量上传 | 批次录入 + 导入导出 | 二组 | +| P0 AI 自动分析 | AI 智能分析 | 三组 | +| P1 问题一览管理 | 指摘列表 + 详情 | 一组 | +| P1 基础仪表盘 | 工作台 | 一组 | +| P1 对策状况登记 | 指摘状态流转 + 编辑 | 一组 | + +### 你 — 知识库模块(Phase 1 实施人) + +**前置依赖**:无 + +| # | 任务 | 涉及文件 | 说明 | +|---|------|---------|------| +| 1 | 文档上传 API | KnowledgeController + KnowledgeService | 接收文件,存入 MinIO,记录到 knowledge_documents | +| 2 | 文档解析 + 切片 | DocumentParserService | 使用 Apache Tika 解析 PDF/Word/TXT,按 500 Token 切片 | +| 3 | 向量化 + 写入 pgvector | VectorizationService | 调用 Ollama nomic-embed-text 向量化,写入 knowledge_chunks | +| 4 | 语义检索 | SearchService | 向量化 → pgvector 余弦相似度检索 → 重排序 | +| 5 | 检索审计 | SearchLogService | 记录每次检索的 query、耗时、命中数 | +| 6 | 知识库管理页面 | frontend/pages/knowledge-base/ | 文档列表、上传、删除、重新向量化 | +| 7 | EmbeddingService 接口 | ims-service 新增 | **供三组调用** | + +**EmbeddingService 接口契约(供三组调用):** +```java +public interface EmbeddingService { + List embed(String text); + List search(String query, int topK); +} +``` + +### 一组 — 指摘 CRUD + 工作台 + 驾驶舱 + +**前置依赖**:等 master 含知识库后拉分支 + +| # | 任务 | 前端页面 | 后端接口 | +|---|------|---------|---------| +| 1 | 指摘列表查询 | issues/list.tsx | GET /api/v1/issues | +| 2 | 创建指摘 | issues/new.tsx | POST /api/v1/issues | +| 3 | 指摘详情 | issues/detail.tsx | GET /api/v1/issues/{id} | +| 4 | 编辑指摘 | issues/edit.tsx | PUT /api/v1/issues/{id} | +| 5 | 状态流转 | 详情页内 | PATCH /api/v1/issues/{id}/status | +| 6 | 附件管理 | 详情页内 | POST /issues/{id}/attachments | +| 7 | 工作台仪表盘 | dashboard/index.tsx | GET /api/v1/dashboard/stats | +| 8 | 通知 | 顶栏铃铛 | GET /api/v1/notifications | +| 9 | Agent 指令 | 工作台 + 详情页 | POST /api/v1/agent/execute | +| 10 | Agent 审批 | 详情页 | POST /api/v1/agent/approval/* | + +### 二组 — 批量录入 + 系统管理 + +**前置依赖**:等 master 含知识库后拉分支 + +| # | 任务 | 前端页面 | 后端接口 | +|---|------|---------|---------| +| 1 | 批量导入 Excel | batch-input/index.tsx | POST /api/v1/import/excel | +| 2 | 模板下载 | 批量录入页 | GET /api/v1/import/template | +| 3 | 用户管理 | system/users.tsx | GET/POST /api/v1/users | +| 4 | 部门树 | 用户管理页 | GET /api/v1/departments | +| 5 | 角色权限 | system/roles.tsx | GET/POST /api/v1/roles | +| 6 | 系统日志 | system/logs.tsx | GET /api/v1/logs | +| 7 | Agent 管理 | system/agent-admin.tsx | GET/PUT /api/v1/agent/config | +| 8 | Prompt 模板管理 | Agent 管理页内 | GET/POST /api/v1/prompts | +| 9 | AI 配置管理 | Agent 管理页内 | GET/PUT /api/v1/ai/config | + +### 三组 — AI 分析 + Prompt 引擎 + Agent 核心 + +**前置依赖**: +- 等知识库模块的 EmbeddingService 接口就绪后对接 +- 可以先写 Mock 实现,不阻塞开发 + +| # | 任务 | 前端页面 | 后端实现 | +|---|------|---------|---------| +| 1 | AI 分析列表 | ai-analysis/index.tsx | GET /api/v1/ai/records | +| 2 | 批量生成分析 | AI 分析页 | POST /api/v1/ai/batch-generate | +| 3 | 分析反馈 | 分析详情 | POST /api/v1/ai/records/{id}/feedback | +| 4 | Prompt 模板引擎 | — | PromptTemplateEngine | +| 5 | Prompt 格式适配 | — | PromptFormatter | +| 6 | Spring AI 集成 | — | AiProviderConfig | +| 7 | 模型切换 + 容错 | — | ModelRoutingService | +| 8 | Agent ReAct 循环 | — | AgentOrchestrator | +| 9 | Agent 长期记忆 | — | MemoryService | +| 10 | 工具注册中心 | — | ToolRegistry | + +--- + +## 4. 开发约定 + +### 后端 +- Service 类放在 `com.ims.service.{模块名}`,接口在 `ims-api`,实现在 `ims-service` +- 类名格式:`{模块名}Service` +- API 路径(已定义,不要修改): + ``` + /api/v1/issues ← 一组 + /api/v1/users ← 二组 + /api/v1/knowledge ← 你 + /api/v1/ai ← 三组 + /api/v1/agent ← 一组 + /api/v1/prompts ← 三组 + ``` + +### 前端 +- 统一使用 `src/request.ts` 的 axios 实例 +- 组件 PascalCase 命名 + +### 数据库 +- Entity 字段任何人不得修改 +- 如需新增字段,加 Flyway 迁移脚本(版本号格式 `V{版本}__{说明}.sql`),通知其他组 + +### 代码风格 +- Java:Lombok `@Data` / `@Builder`,不写注释 +- TypeScript:使用类型定义,避免 `any` + +--- + +## 5. 启动指南 + +```bash +# 1. 启动数据库 +docker compose up -d + +# 2. 编译后端 +cd backend +mvn install -DskipTests + +# 3. 启动后端(端口 8080) +mvn spring-boot:run -pl ims-web -am + +# 4. 启动前端(新开终端) +cd frontend +npm install && npm run dev + +# 5. 浏览器打开 http://localhost:5173,用 admin / Admin@2026 登录 +``` + +--- + +## 6. 知识库模块详细设计 + +### 6.1 数据流 +``` +用户上传文档 (PDF/Word/TXT/MD) + ↓ +KnowledgeController.upload() + ↓ +MinIO 存储源文件 → knowledge_documents 记录 + ↓ +DocumentParserService 解析文本 (Tika) + ↓ +TextSplitter 切片 (500 Token, 10% 重叠) + ↓ +VectorizationService + ├── Ollama nomic-embed-text 向量化 + └→ 每个切片写入 knowledge_chunks (content + embedding) + ↓ +knowledge_documents.status = 'completed' +``` + +### 6.2 检索流程 +``` +用户输入 query + ↓ +SearchService.search(query, topK) + ├── EmbeddingModel.embed() 将 query 转为向量 + ├── pgvector 余弦相似度检索 (IVFFlat) + └→ 返回 Top K 结果 + ↓ +SearchLogService 记录审计日志 +``` + +--- + +## 7. 常见问题 + +**Q: 我改了 Entity 字段,别人不知道怎么办?** +A: 不要改。如需加字段,在群里通知所有人后各自 rebase。 + +**Q: 前端页面路由冲突怎么办?** +A: 路由表已按路径隔离,各组只关心自己的页面路径。 + +**Q: 后端编译报错怎么办?** +A: 先 `mvn clean compile` 检查。确认是 master 问题则在群里反馈。 diff --git a/agents.md b/agents.md new file mode 100644 index 0000000..128635a --- /dev/null +++ b/agents.md @@ -0,0 +1,70 @@ +使用中文回答问题 + +## 日志规则(自动执行) +每次创建或修改代码文件后,在项目根目录的 `_AI_USAGE_LOG.md` 中追加一条记录,必须包含以下字段:日期时间、范式步骤、修改摘要、涉及文件、使用模型 + +**说明**: + +- AI自动填充"日期时间""修改摘要""涉及文件"和"使用模型",若AI无法获取当前使用模型,可以手动加上。 +- "范式步骤"列根据实际情况写,比如需求分析、设计方案、实施计划等开发范式。 + + + +## 1. 在编码前三思 + +**别妄下定论。不要掩饰困惑。表面权衡。** + +在实施之前: + +- 明确表达你的假设。如果不确定,可以问。 +- 如果存在多种解读,就提出来——不要默默选择。 +- 如果有更简单的方法,请说明。必要时反驳。 +- 如果有什么不清楚的地方,就停止。说出什么让人困惑。问吧。 + +## 2. 简洁优先 + +**只需解决问题的最低限度代码。不要做任何推测性的。** + +- 除了被要求的部分,没有其他特征。 +- 一次性代码不做抽象。 +- 没有没有“灵活性”或“可配置性”,这是他们主动要求的。 +- 不可能的情景没有错误处理。 +- 如果你写了200行,可能只有50行,那就重写。 + +问问自己:“高级工程师会说这太复杂了吗?”如果是,那就简化。 + +## 3. 手术变更 + +**只触碰你必须触碰的部分。只收拾你自己的烂摊子。** + +编辑现有代码时: + +- 不要“改进”相邻的代码、注释或格式。 +- 不要重构那些没有坏掉的东西。 +- 即使你会用不同的方式,也要匹配现有的风格。 +- 如果你发现了无关的死代码,要提一提——不要删除。 + +当你的更改产生孤儿时: + +- 移除那些是你自己改动导致没用到的导入/变量/函数。 +- 除非被要求,不要删除已有的死代码。 + +测试:每一行更改的线条都应直接追踪到用户的请求。 + +## 4. 目标驱动执行 + +**定义成功标准。循环直到确认。** + +将任务转化为可验证的目标: + +- “添加验证”→“为无效输入写测试,然后让它们通过” +- “修复bug”→“写一个复现它的测试,然后让它通过”。 +- “重构X”→“确保测试在前后通过” + +对于多步骤任务,请提出简要计划: + +``` +1. [Step] → verify: [check] +2. [Step] → verify: [check] +3. [Step] → verify: [check] +``` \ No newline at end of file diff --git a/backend/ims-api/pom.xml b/backend/ims-api/pom.xml new file mode 100644 index 0000000..68e9cdd --- /dev/null +++ b/backend/ims-api/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + com.ims + ims-backend + 1.0.0-SNAPSHOT + + ims-api + + + com.ims + ims-common + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + org.projectlombok + lombok + true + + + diff --git a/backend/ims-api/src/main/java/com/ims/api/controller/LogController.java b/backend/ims-api/src/main/java/com/ims/api/controller/LogController.java new file mode 100644 index 0000000..f5fbf78 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/controller/LogController.java @@ -0,0 +1,24 @@ +package com.ims.api.controller; + +import com.ims.api.dto.system.LogQueryRequest; +import com.ims.api.dto.system.LogResponse; +import com.ims.api.service.system.LogService; +import com.ims.common.dto.ApiResponse; +import com.ims.common.dto.PageResult; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/logs") +public class LogController { + + private final LogService logService; + + public LogController(LogService logService) { + this.logService = logService; + } + + @GetMapping + public ApiResponse> list(@ModelAttribute LogQueryRequest request) { + return ApiResponse.success(logService.list(request)); + } +} diff --git a/backend/ims-api/src/main/java/com/ims/api/controller/RoleController.java b/backend/ims-api/src/main/java/com/ims/api/controller/RoleController.java new file mode 100644 index 0000000..a2590d2 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/controller/RoleController.java @@ -0,0 +1,42 @@ +package com.ims.api.controller; + +import com.ims.api.dto.system.PermissionResponse; +import com.ims.api.dto.system.RoleRequest; +import com.ims.api.dto.system.RoleResponse; +import com.ims.api.service.system.RoleService; +import com.ims.common.dto.ApiResponse; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/roles") +public class RoleController { + + private final RoleService roleService; + + public RoleController(RoleService roleService) { + this.roleService = roleService; + } + + @GetMapping + public ApiResponse> list() { + return ApiResponse.success(roleService.list()); + } + + @GetMapping("/permissions") + public ApiResponse> permissions() { + return ApiResponse.success(roleService.permissions()); + } + + @PostMapping + public ApiResponse create(@RequestBody @Valid RoleRequest request) { + return ApiResponse.success(roleService.create(request)); + } + + @PutMapping("/{id}") + public ApiResponse update(@PathVariable Long id, @RequestBody @Valid RoleRequest request) { + return ApiResponse.success(roleService.update(id, request)); + } +} diff --git a/backend/ims-api/src/main/java/com/ims/api/controller/UserController.java b/backend/ims-api/src/main/java/com/ims/api/controller/UserController.java new file mode 100644 index 0000000..f03a820 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/controller/UserController.java @@ -0,0 +1,77 @@ +package com.ims.api.controller; + +import com.ims.api.dto.system.UserRequest; +import com.ims.api.dto.system.UserResponse; +import com.ims.api.service.system.UserService; +import com.ims.common.dto.ApiResponse; +import com.ims.common.dto.PageResult; +import jakarta.validation.Valid; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; + +@RestController +@RequestMapping("/api/v1/users") +public class UserController { + + private final UserService userService; + + public UserController(UserService userService) { + this.userService = userService; + } + + @GetMapping + public ApiResponse> list( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int pageSize, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) Long departmentId, + @RequestParam(required = false) Boolean isActive) { + return ApiResponse.success(userService.list(page, pageSize, keyword, departmentId, isActive)); + } + + @PostMapping + public ApiResponse create(@RequestBody @Valid UserRequest request) { + return ApiResponse.success(userService.create(request)); + } + + @PutMapping("/{id}") + public ApiResponse update(@PathVariable Long id, @RequestBody @Valid UserRequest request) { + return ApiResponse.success(userService.update(id, request)); + } + + @PutMapping("/{id}/status") + public ApiResponse toggleStatus(@PathVariable Long id, @RequestParam Boolean isActive) { + userService.updateStatus(id, isActive); + return ApiResponse.success(null); + } + + @GetMapping("/export") + public void export(@RequestParam(required = false) String keyword, + @RequestParam(required = false) Long departmentId, + @RequestParam(required = false) Boolean isActive, + HttpServletResponse response) throws IOException { + PageResult result = userService.list(1, 100000, keyword, departmentId, isActive); + response.setContentType("text/csv;charset=UTF-8"); + response.setHeader("Content-Disposition", "attachment; filename=users.csv"); + response.getWriter().write("\uFEFF"); + response.getWriter().write("账号,姓名,邮箱,部门,角色,状态,Agent授权\n"); + for (UserResponse u : result.getItems()) { + String roles = u.getRoles() == null ? "" : String.join("|", u.getRoles()); + response.getWriter().write(String.join(",", + csv(u.getUserid()), csv(u.getUsername()), csv(u.getEmail()), + csv(u.getDepartmentName()), csv(roles), + Boolean.TRUE.equals(u.getIsActive()) ? "正常" : "禁用", + Boolean.TRUE.equals(u.getAgentAutoExecute()) ? "是" : "否") + "\n"); + } + response.getWriter().flush(); + } + + private String csv(String s) { + if (s == null) { + return ""; + } + return s.contains(",") ? "\"" + s + "\"" : s; + } +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentApprovalRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentApprovalRequest.java new file mode 100644 index 0000000..37d12f2 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentApprovalRequest.java @@ -0,0 +1,12 @@ +package com.ims.api.dto.agent; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class AgentApprovalRequest { + @NotNull + private Boolean approved; + + private String comment; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentConfigRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentConfigRequest.java new file mode 100644 index 0000000..32cf1e8 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentConfigRequest.java @@ -0,0 +1,10 @@ +package com.ims.api.dto.agent; + +import lombok.Data; + +@Data +public class AgentConfigRequest { + private Integer maxSteps; + private Boolean autoExecuteHighRisk; + private Integer userRateLimit; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentConfigResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentConfigResponse.java new file mode 100644 index 0000000..d6f643b --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentConfigResponse.java @@ -0,0 +1,10 @@ +package com.ims.api.dto.agent; + +import lombok.Data; + +@Data +public class AgentConfigResponse { + private Integer maxSteps; + private Boolean autoExecuteHighRisk; + private Integer userRateLimit; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentExecuteRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentExecuteRequest.java new file mode 100644 index 0000000..7207865 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentExecuteRequest.java @@ -0,0 +1,12 @@ +package com.ims.api.dto.agent; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class AgentExecuteRequest { + private Long issueId; + + @NotBlank + private String goal; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentExecuteResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentExecuteResponse.java new file mode 100644 index 0000000..65bea11 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentExecuteResponse.java @@ -0,0 +1,14 @@ +package com.ims.api.dto.agent; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AgentExecuteResponse { + private Long planId; + private String status; + private Boolean requiresApproval; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentMemoryRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentMemoryRequest.java new file mode 100644 index 0000000..2e18fcd --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentMemoryRequest.java @@ -0,0 +1,13 @@ +package com.ims.api.dto.agent; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class AgentMemoryRequest { + @NotBlank + private String issueSummary; + + @NotBlank + private String solutionSteps; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentMemoryResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentMemoryResponse.java new file mode 100644 index 0000000..494388f --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentMemoryResponse.java @@ -0,0 +1,14 @@ +package com.ims.api.dto.agent; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class AgentMemoryResponse { + private Long id; + private String issueSummary; + private String solutionSteps; + private java.math.BigDecimal effectivenessScore; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentPlanResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentPlanResponse.java new file mode 100644 index 0000000..dbbc333 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentPlanResponse.java @@ -0,0 +1,21 @@ +package com.ims.api.dto.agent; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class AgentPlanResponse { + private Long id; + private Long issueId; + private String issueNo; + private String issueTitle; + private String goal; + private String planSteps; + private String status; + private Boolean requiresApproval; + private String approvalStatus; + private String approvalComment; + private String modelProvider; + private LocalDateTime createdAt; + private LocalDateTime completedAt; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentSuggestFieldsRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentSuggestFieldsRequest.java new file mode 100644 index 0000000..2139c9b --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentSuggestFieldsRequest.java @@ -0,0 +1,14 @@ +package com.ims.api.dto.agent; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class AgentSuggestFieldsRequest { + @NotBlank(message = "标题不能为空") + private String title; + + private String description; + + private Long issueId; +} \ No newline at end of file diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentSuggestRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentSuggestRequest.java new file mode 100644 index 0000000..373f0fe --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/agent/AgentSuggestRequest.java @@ -0,0 +1,14 @@ +package com.ims.api.dto.agent; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class AgentSuggestRequest { + @NotNull + private Long issueId; + + @NotBlank + private String goal; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/ai/AiAnalysisRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/ai/AiAnalysisRequest.java new file mode 100644 index 0000000..a0be572 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/ai/AiAnalysisRequest.java @@ -0,0 +1,12 @@ +package com.ims.api.dto.ai; + +import lombok.Data; +import java.util.List; + +@Data +public class AiAnalysisRequest { + private List issueIds; + private String departmentId; + private String status; + private String phase; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/ai/AiAnalysisResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/ai/AiAnalysisResponse.java new file mode 100644 index 0000000..06b5d73 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/ai/AiAnalysisResponse.java @@ -0,0 +1,28 @@ +package com.ims.api.dto.ai; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class AiAnalysisResponse { + private Long id; + private Long issueId; + private String issueNo; + private String issueTitle; + private String departmentName; + private String category; + private String keywords; + private String extractedKeywords; + private String rootCause; + private String suggestion; + private String status; + private Integer helpfulCount; + private String promptTemplateId; + private Integer promptVersion; + private String modelProvider; + private String modelName; + private String errorMessage; + private LocalDateTime startedAt; + private LocalDateTime completedAt; + private LocalDateTime createdAt; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/ai/AiConfigRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/ai/AiConfigRequest.java new file mode 100644 index 0000000..bb3ee8d --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/ai/AiConfigRequest.java @@ -0,0 +1,23 @@ +package com.ims.api.dto.ai; + +import lombok.Data; + +@Data +public class AiConfigRequest { + private String provider; + private String ollamaBaseUrl; + private String ollamaChatModel; + private String ollamaEmbeddingModel; + private Float ollamaTemperature; + private Integer ollamaNumPredict; + private String deepseekApiKey; + private String deepseekModel; + private String deepseekEmbeddingModel; + private Boolean autoFallbackEnabled; + private Integer agentMaxSteps; + private Boolean autoExecuteHighRisk; + private Integer userRateLimit; + private Integer chunkSize; + private Integer chunkOverlap; + private Long maxUploadSize; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/ai/AiConfigResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/ai/AiConfigResponse.java new file mode 100644 index 0000000..71b9eb6 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/ai/AiConfigResponse.java @@ -0,0 +1,22 @@ +package com.ims.api.dto.ai; + +import lombok.Data; + +@Data +public class AiConfigResponse { + private String provider; + private String ollamaBaseUrl; + private String ollamaChatModel; + private String ollamaEmbeddingModel; + private Float ollamaTemperature; + private Integer ollamaNumPredict; + private String deepseekModel; + private String deepseekEmbeddingModel; + private Boolean autoFallbackEnabled; + private Integer agentMaxSteps; + private Boolean autoExecuteHighRisk; + private Integer userRateLimit; + private Integer chunkSize; + private Integer chunkOverlap; + private Long maxUploadSize; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/ai/AiFeedbackRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/ai/AiFeedbackRequest.java new file mode 100644 index 0000000..00527fa --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/ai/AiFeedbackRequest.java @@ -0,0 +1,12 @@ +package com.ims.api.dto.ai; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class AiFeedbackRequest { + @NotNull + private Boolean isHelpful; + + private String comment; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/auth/LoginRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/auth/LoginRequest.java new file mode 100644 index 0000000..c5caddb --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/auth/LoginRequest.java @@ -0,0 +1,13 @@ +package com.ims.api.dto.auth; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class LoginRequest { + @NotBlank(message = "账号不能为空") + private String username; + + @NotBlank(message = "密码不能为空") + private String password; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/auth/LoginResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/auth/LoginResponse.java new file mode 100644 index 0000000..3a76658 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/auth/LoginResponse.java @@ -0,0 +1,14 @@ +package com.ims.api.dto.auth; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class LoginResponse { + private String accessToken; + private String refreshToken; + private Long userId; + private String username; + private String roleName; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/dashboard/DashboardStatsResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/dashboard/DashboardStatsResponse.java new file mode 100644 index 0000000..eb4060d --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/dashboard/DashboardStatsResponse.java @@ -0,0 +1,72 @@ +package com.ims.api.dto.dashboard; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class DashboardStatsResponse { + private long pendingCount; + private long inProgressCount; + private long pendingConfirmCount; + private long closedCount; + private long todayNewCount; + private long monthlyClosedCount; + private List cards; + private List trend; + private List statusDistribution; + private List recentActivities; + private List insights; + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class CardInfo { + private String key; + private long count; + private String changeText; + private String suggestion; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class DailyTrend { + private String date; + private long newCount; + private long resolvedCount; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class StatusCount { + private String status; + private long count; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class Activity { + private String userName; + private String action; + private String issueNo; + private String title; + private LocalDateTime createdAt; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class Insight { + private String type; + private String title; + private String content; + } +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/imports/AgentValidateResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/imports/AgentValidateResponse.java new file mode 100644 index 0000000..d31ccc5 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/imports/AgentValidateResponse.java @@ -0,0 +1,13 @@ +package com.ims.api.dto.imports; + +import lombok.Data; +import java.util.List; + +@Data +public class AgentValidateResponse { + private boolean usable; + private String usableReason; + private List suggestions; + private String engine; + private long elapsedMs; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/imports/ImportConfirmRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/imports/ImportConfirmRequest.java new file mode 100644 index 0000000..405b078 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/imports/ImportConfirmRequest.java @@ -0,0 +1,10 @@ +package com.ims.api.dto.imports; + +import lombok.Data; +import java.util.List; + +@Data +public class ImportConfirmRequest { + private String fileName; + private List rows; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/imports/ImportPreviewResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/imports/ImportPreviewResponse.java new file mode 100644 index 0000000..dcab827 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/imports/ImportPreviewResponse.java @@ -0,0 +1,13 @@ +package com.ims.api.dto.imports; + +import lombok.Data; +import java.util.List; + +@Data +public class ImportPreviewResponse { + private int total; + private int validCount; + private int errorCount; + private boolean headerValid = true; + private List rows; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/imports/ImportRecordQueryRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/imports/ImportRecordQueryRequest.java new file mode 100644 index 0000000..861dd11 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/imports/ImportRecordQueryRequest.java @@ -0,0 +1,9 @@ +package com.ims.api.dto.imports; + +import lombok.Data; + +@Data +public class ImportRecordQueryRequest { + private int page = 1; + private int pageSize = 20; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/imports/ImportRecordResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/imports/ImportRecordResponse.java new file mode 100644 index 0000000..3e4833a --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/imports/ImportRecordResponse.java @@ -0,0 +1,17 @@ +package com.ims.api.dto.imports; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class ImportRecordResponse { + private Long id; + private String fileName; + private Integer totalCount; + private Integer successCount; + private Integer failCount; + private String status; + private String errorLog; + private String operator; + private LocalDateTime createdAt; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/imports/ImportRow.java b/backend/ims-api/src/main/java/com/ims/api/dto/imports/ImportRow.java new file mode 100644 index 0000000..d2c53fb --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/imports/ImportRow.java @@ -0,0 +1,25 @@ +package com.ims.api.dto.imports; + +import lombok.Data; +import java.util.List; + +@Data +public class ImportRow { + private Integer rowNo; + private String title; + private String docType; + private String phase; + private String priority; + private String deadline; + private String reviewDate; + private String subProject; + private String category; + private String impactLevel; + private String description; + private String assigneeUserid; + private String reviewerUserid; + private String validatorUserid; + private String departmentName; + private String status; + private List errors; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/imports/ImportSuggestion.java b/backend/ims-api/src/main/java/com/ims/api/dto/imports/ImportSuggestion.java new file mode 100644 index 0000000..4c38905 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/imports/ImportSuggestion.java @@ -0,0 +1,14 @@ +package com.ims.api.dto.imports; + +import lombok.Data; + +@Data +public class ImportSuggestion { + private Integer rowNo; + private String field; + private String fieldName; + private String original; + private String suggested; + private String reason; + private String level; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/issue/AttachmentResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/issue/AttachmentResponse.java new file mode 100644 index 0000000..9af8ac1 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/issue/AttachmentResponse.java @@ -0,0 +1,15 @@ +package com.ims.api.dto.issue; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class AttachmentResponse { + private Long id; + private String fileName; + private Long fileSize; + private String mimeType; + private String filePath; + private String uploadedByName; + private LocalDateTime createdAt; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/issue/BatchAgentRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/issue/BatchAgentRequest.java new file mode 100644 index 0000000..b89a9a8 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/issue/BatchAgentRequest.java @@ -0,0 +1,11 @@ +package com.ims.api.dto.issue; + +import lombok.Data; + +import java.util.List; + +@Data +public class BatchAgentRequest { + private List issueIds; + private String goal; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/issue/BatchAssignRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/issue/BatchAssignRequest.java new file mode 100644 index 0000000..6c4c352 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/issue/BatchAssignRequest.java @@ -0,0 +1,11 @@ +package com.ims.api.dto.issue; + +import lombok.Data; + +import java.util.List; + +@Data +public class BatchAssignRequest { + private List issueIds; + private Long assigneeId; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/issue/BatchNotifyRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/issue/BatchNotifyRequest.java new file mode 100644 index 0000000..7875a16 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/issue/BatchNotifyRequest.java @@ -0,0 +1,11 @@ +package com.ims.api.dto.issue; + +import lombok.Data; + +import java.util.List; + +@Data +public class BatchNotifyRequest { + private List issueIds; + private String content; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/issue/IssueCreateRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/issue/IssueCreateRequest.java new file mode 100644 index 0000000..eed55ba --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/issue/IssueCreateRequest.java @@ -0,0 +1,35 @@ +package com.ims.api.dto.issue; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +public class IssueCreateRequest { + @NotBlank(message = "指摘标题不能为空") + @Size(max = 200, message = "指摘标题长度不能超过 200") + private String title; + private String description; + private String phase; + private String subProject; + private String category; + private String impactLevel; + private String impactScope; + private String deployment; + private String pgmNo; + private BigDecimal reviewWorkload; + private BigDecimal responseWorkload; + private String responseContent; + private String ngReason; + private LocalDateTime responseCompletedAt; + private LocalDateTime confirmAt; + private Long assigneeId; + private Long departmentId; + private Long reviewerId; + private Long validatorId; + private String priority; + private String status; + private LocalDateTime deadline; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/issue/IssueListRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/issue/IssueListRequest.java new file mode 100644 index 0000000..283b011 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/issue/IssueListRequest.java @@ -0,0 +1,20 @@ +package com.ims.api.dto.issue; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class IssueListRequest { + private String status; + private String phase; + private String subProject; + private String priority; + private String impactLevel; + private String keyword; + private Long assigneeId; + private Long departmentId; + private LocalDateTime startDate; + private LocalDateTime endDate; + private int page = 1; + private int pageSize = 20; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/issue/IssueResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/issue/IssueResponse.java new file mode 100644 index 0000000..b1159ce --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/issue/IssueResponse.java @@ -0,0 +1,44 @@ +package com.ims.api.dto.issue; + +import lombok.Data; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +public class IssueResponse { + private Long id; + private String issueNo; + private String title; + private String description; + private String status; + private String priority; + private LocalDateTime deadline; + private String phase; + private String subProject; + private String category; + private String impactLevel; + private String impactScope; + private String deployment; + private String pgmNo; + private BigDecimal reviewWorkload; + private BigDecimal responseWorkload; + private String responseContent; + private String ngReason; + private LocalDateTime responseCompletedAt; + private LocalDateTime confirmAt; + private Long creatorId; + private String creatorName; + private Long assigneeId; + private String assigneeName; + private Long departmentId; + private String departmentName; + private Long reviewerId; + private String reviewerName; + private Long validatorId; + private String validatorName; + private String agentStatus; + private Long agentLastPlanId; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + private LocalDateTime closedAt; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/issue/IssueStatusRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/issue/IssueStatusRequest.java new file mode 100644 index 0000000..acb5de1 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/issue/IssueStatusRequest.java @@ -0,0 +1,11 @@ +package com.ims.api.dto.issue; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class IssueStatusRequest { + @NotBlank + private String status; + private String remark; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/issue/IssueUpdateRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/issue/IssueUpdateRequest.java new file mode 100644 index 0000000..960dd1d --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/issue/IssueUpdateRequest.java @@ -0,0 +1,31 @@ +package com.ims.api.dto.issue; + +import lombok.Data; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +public class IssueUpdateRequest { + private String title; + private String description; + private String status; + private String priority; + private LocalDateTime deadline; + private String phase; + private String subProject; + private String category; + private String impactLevel; + private String impactScope; + private String deployment; + private String pgmNo; + private BigDecimal reviewWorkload; + private BigDecimal responseWorkload; + private String responseContent; + private String ngReason; + private LocalDateTime responseCompletedAt; + private LocalDateTime confirmAt; + private Long assigneeId; + private Long departmentId; + private Long reviewerId; + private Long validatorId; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/knowledge/KnowledgeDocResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/knowledge/KnowledgeDocResponse.java new file mode 100644 index 0000000..e09ce27 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/knowledge/KnowledgeDocResponse.java @@ -0,0 +1,17 @@ +package com.ims.api.dto.knowledge; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class KnowledgeDocResponse { + private Long id; + private String name; + private Long fileSize; + private String fileType; + private Integer chunkCount; + private String status; + private String errorMessage; + private String uploadedByName; + private LocalDateTime createdAt; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/knowledge/KnowledgeLogQueryRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/knowledge/KnowledgeLogQueryRequest.java new file mode 100644 index 0000000..ccb9121 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/knowledge/KnowledgeLogQueryRequest.java @@ -0,0 +1,14 @@ +package com.ims.api.dto.knowledge; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class KnowledgeLogQueryRequest { + private Long userId; + private Long issueId; + private LocalDateTime startTime; + private LocalDateTime endTime; + private int page = 1; + private int pageSize = 20; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/knowledge/KnowledgeSearchRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/knowledge/KnowledgeSearchRequest.java new file mode 100644 index 0000000..f81b693 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/knowledge/KnowledgeSearchRequest.java @@ -0,0 +1,13 @@ +package com.ims.api.dto.knowledge; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class KnowledgeSearchRequest { + @NotBlank + private String query; + + private Integer topK = 5; + private Long issueId; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/knowledge/KnowledgeSearchResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/knowledge/KnowledgeSearchResponse.java new file mode 100644 index 0000000..53dcc77 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/knowledge/KnowledgeSearchResponse.java @@ -0,0 +1,11 @@ +package com.ims.api.dto.knowledge; + +import lombok.Data; + +@Data +public class KnowledgeSearchResponse { + private Long chunkId; + private String content; + private String docName; + private Double score; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/notification/NotificationResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/notification/NotificationResponse.java new file mode 100644 index 0000000..9c5fc69 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/notification/NotificationResponse.java @@ -0,0 +1,15 @@ +package com.ims.api.dto.notification; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class NotificationResponse { + private Long id; + private String title; + private String content; + private String type; + private String link; + private Boolean isRead; + private LocalDateTime createdAt; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptLogResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptLogResponse.java new file mode 100644 index 0000000..abf4529 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptLogResponse.java @@ -0,0 +1,17 @@ +package com.ims.api.dto.prompt; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class PromptLogResponse { + private Long id; + private String requestId; + private String templateId; + private Integer templateVersion; + private String renderedPrompt; + private Integer executionTimeMs; + private String llmModel; + private String modelProvider; + private LocalDateTime createdAt; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptRenderLogResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptRenderLogResponse.java new file mode 100644 index 0000000..69c1c0d --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptRenderLogResponse.java @@ -0,0 +1,20 @@ +package com.ims.api.dto.prompt; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class PromptRenderLogResponse { + private Long id; + private String requestId; + private String templateId; + private Integer templateVersion; + private String renderedPrompt; + private String variablesUsed; + private Integer tokensInput; + private Integer tokensOutput; + private Integer executionTimeMs; + private String llmModel; + private String modelProvider; + private LocalDateTime createdAt; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptStatsResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptStatsResponse.java new file mode 100644 index 0000000..8650cf7 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptStatsResponse.java @@ -0,0 +1,17 @@ +package com.ims.api.dto.prompt; + +import lombok.Data; + +@Data +public class PromptStatsResponse { + private String templateId; + private String name; + private String category; + private Long useCount; + private Double avgExecutionTimeMs; + private Integer latestVersion; + private Long totalTemplates; + private Long activeTemplates; + private Long totalVersions; + private Long totalRenders; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptTemplateRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptTemplateRequest.java new file mode 100644 index 0000000..840ffe2 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptTemplateRequest.java @@ -0,0 +1,22 @@ +package com.ims.api.dto.prompt; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class PromptTemplateRequest { + @NotBlank + private String templateId; + + @NotBlank + private String name; + + @NotBlank + private String category; + + @NotBlank + private String content; + + private String variables; + private String outputSchema; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptTemplateResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptTemplateResponse.java new file mode 100644 index 0000000..17e6b0c --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptTemplateResponse.java @@ -0,0 +1,20 @@ +package com.ims.api.dto.prompt; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class PromptTemplateResponse { + private Long id; + private String templateId; + private String name; + private String category; + private Integer version; + private String content; + private String variables; + private String outputSchema; + private Boolean isActive; + private Boolean isDefault; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptTestRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptTestRequest.java new file mode 100644 index 0000000..f982838 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptTestRequest.java @@ -0,0 +1,13 @@ +package com.ims.api.dto.prompt; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import java.util.Map; + +@Data +public class PromptTestRequest { + @NotBlank + private String templateId; + + private Map variables; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptTestResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptTestResponse.java new file mode 100644 index 0000000..1f3aa71 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptTestResponse.java @@ -0,0 +1,12 @@ +package com.ims.api.dto.prompt; + +import lombok.Data; + +@Data +public class PromptTestResponse { + private String templateId; + private Integer templateVersion; + private String renderedPrompt; + private Integer executionTimeMs; + private Integer tokenEstimate; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptVersionResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptVersionResponse.java new file mode 100644 index 0000000..c3e8a14 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/prompt/PromptVersionResponse.java @@ -0,0 +1,15 @@ +package com.ims.api.dto.prompt; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class PromptVersionResponse { + private Long id; + private String templateId; + private Integer version; + private String content; + private String changeLog; + private String createdBy; + private LocalDateTime createdAt; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/system/DepartmentResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/system/DepartmentResponse.java new file mode 100644 index 0000000..bbab44e --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/system/DepartmentResponse.java @@ -0,0 +1,13 @@ +package com.ims.api.dto.system; + +import lombok.Data; +import java.util.List; + +@Data +public class DepartmentResponse { + private Long id; + private String name; + private Long parentId; + private Integer sortOrder; + private List children; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/system/LogQueryRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/system/LogQueryRequest.java new file mode 100644 index 0000000..3100ae9 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/system/LogQueryRequest.java @@ -0,0 +1,17 @@ +package com.ims.api.dto.system; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class LogQueryRequest { + private String operator; + private LocalDateTime startTime; + private LocalDateTime endTime; + private String actionType; + private String resourceType; + private String keyword; + private String level; + private int page = 1; + private int pageSize = 20; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/system/LogResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/system/LogResponse.java new file mode 100644 index 0000000..35a1beb --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/system/LogResponse.java @@ -0,0 +1,14 @@ +package com.ims.api.dto.system; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class LogResponse { + private Long id; + private String operator; + private String action; + private String resource; + private String detail; + private LocalDateTime createdAt; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/system/PermissionResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/system/PermissionResponse.java new file mode 100644 index 0000000..79a2b49 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/system/PermissionResponse.java @@ -0,0 +1,11 @@ +package com.ims.api.dto.system; + +import lombok.Data; + +@Data +public class PermissionResponse { + private Long id; + private String code; + private String name; + private String resource; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/system/RoleRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/system/RoleRequest.java new file mode 100644 index 0000000..8f01c63 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/system/RoleRequest.java @@ -0,0 +1,19 @@ +package com.ims.api.dto.system; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import java.util.List; + +@Data +public class RoleRequest { + @NotBlank + private String name; + + private String description; + + private String dataScope; + + private Boolean agentAutoExecute; + + private List permissionIds; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/system/RoleResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/system/RoleResponse.java new file mode 100644 index 0000000..18b1e39 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/system/RoleResponse.java @@ -0,0 +1,16 @@ +package com.ims.api.dto.system; + +import lombok.Data; +import java.time.LocalDateTime; +import java.util.List; + +@Data +public class RoleResponse { + private Long id; + private String name; + private String description; + private String dataScope; + private Boolean agentAutoExecute; + private List permissionIds; + private LocalDateTime createdAt; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/system/UserRequest.java b/backend/ims-api/src/main/java/com/ims/api/dto/system/UserRequest.java new file mode 100644 index 0000000..c9edef9 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/system/UserRequest.java @@ -0,0 +1,26 @@ +package com.ims.api.dto.system; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import java.util.List; + +@Data +public class UserRequest { + @NotBlank + private String userid; + + @NotBlank + private String username; + + private String email; + + private String password; + + private Long departmentId; + + private List roleIds; + + private Boolean isActive; + + private Boolean agentAutoExecute; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/dto/system/UserResponse.java b/backend/ims-api/src/main/java/com/ims/api/dto/system/UserResponse.java new file mode 100644 index 0000000..67bcf58 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/dto/system/UserResponse.java @@ -0,0 +1,21 @@ +package com.ims.api.dto.system; + +import lombok.Data; +import java.time.LocalDateTime; +import java.util.List; + +@Data +public class UserResponse { + private Long id; + private String userid; + private String username; + private String email; + private Long departmentId; + private String departmentName; + private Boolean isActive; + private Boolean agentAutoExecute; + private List roleIds; + private List roles; + private LocalDateTime lastLoginAt; + private LocalDateTime createdAt; +} diff --git a/backend/ims-api/src/main/java/com/ims/api/service/agent/AgentConfigService.java b/backend/ims-api/src/main/java/com/ims/api/service/agent/AgentConfigService.java new file mode 100644 index 0000000..2aacf98 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/service/agent/AgentConfigService.java @@ -0,0 +1,11 @@ +package com.ims.api.service.agent; + +import com.ims.api.dto.agent.AgentConfigRequest; +import com.ims.api.dto.agent.AgentConfigResponse; + +public interface AgentConfigService { + + AgentConfigResponse getConfig(); + + void updateConfig(AgentConfigRequest request); +} diff --git a/backend/ims-api/src/main/java/com/ims/api/service/ai/ChatService.java b/backend/ims-api/src/main/java/com/ims/api/service/ai/ChatService.java new file mode 100644 index 0000000..bfa62d4 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/service/ai/ChatService.java @@ -0,0 +1,6 @@ +package com.ims.api.service.ai; + +public interface ChatService { + + String chat(String systemPrompt, String userPrompt); +} diff --git a/backend/ims-api/src/main/java/com/ims/api/service/prompt/PromptService.java b/backend/ims-api/src/main/java/com/ims/api/service/prompt/PromptService.java new file mode 100644 index 0000000..91bb4ff --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/service/prompt/PromptService.java @@ -0,0 +1,33 @@ +package com.ims.api.service.prompt; + +import com.ims.api.dto.prompt.PromptLogResponse; +import com.ims.api.dto.prompt.PromptStatsResponse; +import com.ims.api.dto.prompt.PromptTemplateRequest; +import com.ims.api.dto.prompt.PromptTemplateResponse; +import com.ims.api.dto.prompt.PromptTestRequest; +import com.ims.api.dto.prompt.PromptTestResponse; +import com.ims.api.dto.prompt.PromptVersionResponse; +import com.ims.common.dto.PageResult; + +import java.util.List; + +public interface PromptService { + + PageResult list(int page, int pageSize, String category, String keyword); + + PromptTemplateResponse detail(String templateId); + + PromptTemplateResponse create(PromptTemplateRequest request, String operatorUsername); + + PromptTemplateResponse update(String templateId, PromptTemplateRequest request, String operatorUsername); + + void rollback(String templateId, int version, String operatorUsername); + + PromptTestResponse test(PromptTestRequest request, String operatorUsername); + + List versions(String templateId); + + PageResult logs(int page, int pageSize); + + PromptStatsResponse stats(); +} diff --git a/backend/ims-api/src/main/java/com/ims/api/service/system/DepartmentService.java b/backend/ims-api/src/main/java/com/ims/api/service/system/DepartmentService.java new file mode 100644 index 0000000..02fa7e4 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/service/system/DepartmentService.java @@ -0,0 +1,10 @@ +package com.ims.api.service.system; + +import com.ims.api.dto.system.DepartmentResponse; + +import java.util.List; + +public interface DepartmentService { + + List tree(); +} diff --git a/backend/ims-api/src/main/java/com/ims/api/service/system/ImportService.java b/backend/ims-api/src/main/java/com/ims/api/service/system/ImportService.java new file mode 100644 index 0000000..ff3b742 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/service/system/ImportService.java @@ -0,0 +1,24 @@ +package com.ims.api.service.system; + +import com.ims.api.dto.imports.AgentValidateResponse; +import com.ims.api.dto.imports.ImportConfirmRequest; +import com.ims.api.dto.imports.ImportPreviewResponse; +import com.ims.api.dto.imports.ImportRecordResponse; +import com.ims.api.dto.imports.ImportRow; +import com.ims.common.dto.PageResult; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +public interface ImportService { + + byte[] generateTemplate(); + + ImportPreviewResponse preview(MultipartFile file); + + AgentValidateResponse aiValidate(List rows); + + ImportRecordResponse confirm(ImportConfirmRequest request, String operatorUsername); + + PageResult records(int page, int pageSize); +} diff --git a/backend/ims-api/src/main/java/com/ims/api/service/system/LogService.java b/backend/ims-api/src/main/java/com/ims/api/service/system/LogService.java new file mode 100644 index 0000000..a5aef7d --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/service/system/LogService.java @@ -0,0 +1,10 @@ +package com.ims.api.service.system; + +import com.ims.api.dto.system.LogQueryRequest; +import com.ims.api.dto.system.LogResponse; +import com.ims.common.dto.PageResult; + +public interface LogService { + + PageResult list(LogQueryRequest request); +} diff --git a/backend/ims-api/src/main/java/com/ims/api/service/system/RoleService.java b/backend/ims-api/src/main/java/com/ims/api/service/system/RoleService.java new file mode 100644 index 0000000..64750de --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/service/system/RoleService.java @@ -0,0 +1,18 @@ +package com.ims.api.service.system; + +import com.ims.api.dto.system.PermissionResponse; +import com.ims.api.dto.system.RoleRequest; +import com.ims.api.dto.system.RoleResponse; + +import java.util.List; + +public interface RoleService { + + List list(); + + List permissions(); + + RoleResponse create(RoleRequest request); + + RoleResponse update(Long id, RoleRequest request); +} diff --git a/backend/ims-api/src/main/java/com/ims/api/service/system/UserService.java b/backend/ims-api/src/main/java/com/ims/api/service/system/UserService.java new file mode 100644 index 0000000..2624884 --- /dev/null +++ b/backend/ims-api/src/main/java/com/ims/api/service/system/UserService.java @@ -0,0 +1,16 @@ +package com.ims.api.service.system; + +import com.ims.api.dto.system.UserRequest; +import com.ims.api.dto.system.UserResponse; +import com.ims.common.dto.PageResult; + +public interface UserService { + + PageResult list(int page, int pageSize, String keyword, Long departmentId, Boolean isActive); + + UserResponse create(UserRequest request); + + UserResponse update(Long id, UserRequest request); + + void updateStatus(Long id, Boolean isActive); +} diff --git a/backend/ims-common/pom.xml b/backend/ims-common/pom.xml new file mode 100644 index 0000000..533c960 --- /dev/null +++ b/backend/ims-common/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + com.ims + ims-backend + 1.0.0-SNAPSHOT + + ims-common + + + org.projectlombok + lombok + true + + + com.fasterxml.jackson.core + jackson-databind + + + io.jsonwebtoken + jjwt-api + + + io.jsonwebtoken + jjwt-impl + + + io.jsonwebtoken + jjwt-jackson + + + diff --git a/backend/ims-common/src/main/java/com/ims/common/constant/ResultCode.java b/backend/ims-common/src/main/java/com/ims/common/constant/ResultCode.java new file mode 100644 index 0000000..3ee4ed7 --- /dev/null +++ b/backend/ims-common/src/main/java/com/ims/common/constant/ResultCode.java @@ -0,0 +1,22 @@ +package com.ims.common.constant; + +import lombok.Getter; + +@Getter +public enum ResultCode { + SUCCESS(200, "success"), + BAD_REQUEST(400, "bad request"), + UNAUTHORIZED(401, "unauthorized"), + FORBIDDEN(403, "forbidden"), + NOT_FOUND(404, "not found"), + INTERNAL_ERROR(500, "internal server error"), + BUSINESS_ERROR(1001, "business error"); + + private final int code; + private final String message; + + ResultCode(int code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/backend/ims-common/src/main/java/com/ims/common/dto/ApiResponse.java b/backend/ims-common/src/main/java/com/ims/common/dto/ApiResponse.java new file mode 100644 index 0000000..20529f0 --- /dev/null +++ b/backend/ims-common/src/main/java/com/ims/common/dto/ApiResponse.java @@ -0,0 +1,37 @@ +package com.ims.common.dto; + +import com.ims.common.constant.ResultCode; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class ApiResponse { + private int code; + private String message; + private T data; + private String timestamp; + + public ApiResponse() { + this.timestamp = LocalDateTime.now().toString(); + } + + public static ApiResponse success(T data) { + ApiResponse response = new ApiResponse<>(); + response.setCode(ResultCode.SUCCESS.getCode()); + response.setMessage(ResultCode.SUCCESS.getMessage()); + response.setData(data); + return response; + } + + public static ApiResponse error(int code, String message) { + ApiResponse response = new ApiResponse<>(); + response.setCode(code); + response.setMessage(message); + return response; + } + + public static ApiResponse error(ResultCode resultCode) { + return error(resultCode.getCode(), resultCode.getMessage()); + } +} diff --git a/backend/ims-common/src/main/java/com/ims/common/dto/PageResult.java b/backend/ims-common/src/main/java/com/ims/common/dto/PageResult.java new file mode 100644 index 0000000..87f1779 --- /dev/null +++ b/backend/ims-common/src/main/java/com/ims/common/dto/PageResult.java @@ -0,0 +1,26 @@ +package com.ims.common.dto; + +import lombok.Data; +import java.util.Collections; +import java.util.List; + +@Data +public class PageResult { + private List items; + private long total; + private int page; + private int pageSize; + private int totalPages; + + public PageResult() { + this.items = Collections.emptyList(); + } + + public PageResult(List items, long total, int page, int pageSize) { + this.items = items; + this.total = total; + this.page = page; + this.pageSize = pageSize; + this.totalPages = pageSize > 0 ? (int) Math.ceil((double) total / pageSize) : 0; + } +} diff --git a/backend/ims-common/src/main/java/com/ims/common/exception/BusinessException.java b/backend/ims-common/src/main/java/com/ims/common/exception/BusinessException.java new file mode 100644 index 0000000..bceded1 --- /dev/null +++ b/backend/ims-common/src/main/java/com/ims/common/exception/BusinessException.java @@ -0,0 +1,19 @@ +package com.ims.common.exception; + +import com.ims.common.constant.ResultCode; +import lombok.Getter; + +@Getter +public class BusinessException extends RuntimeException { + private final int code; + + public BusinessException(String message) { + super(message); + this.code = ResultCode.BUSINESS_ERROR.getCode(); + } + + public BusinessException(int code, String message) { + super(message); + this.code = code; + } +} diff --git a/backend/ims-common/src/main/java/com/ims/common/util/JwtUtil.java b/backend/ims-common/src/main/java/com/ims/common/util/JwtUtil.java new file mode 100644 index 0000000..09a2e61 --- /dev/null +++ b/backend/ims-common/src/main/java/com/ims/common/util/JwtUtil.java @@ -0,0 +1,58 @@ +package com.ims.common.util; + +import io.jsonwebtoken.*; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class JwtUtil { + + private final SecretKey key; + private final long accessTokenExpiration; + private final long refreshTokenExpiration; + + public JwtUtil(String secret, long accessTokenExpiration, long refreshTokenExpiration) { + this.key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); + this.accessTokenExpiration = accessTokenExpiration; + this.refreshTokenExpiration = refreshTokenExpiration; + } + + public String generateAccessToken(Long userId, String username) { + return Jwts.builder() + .subject(username) + .claim("userId", userId) + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + accessTokenExpiration)) + .signWith(key, Jwts.SIG.HS256) + .compact(); + } + + public String generateRefreshToken(Long userId, String username) { + return Jwts.builder() + .subject(username) + .claim("userId", userId) + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + refreshTokenExpiration)) + .signWith(key, Jwts.SIG.HS256) + .compact(); + } + + public Claims parseToken(String token) { + return Jwts.parser() + .verifyWith(key) + .build() + .parseSignedClaims(token) + .getPayload(); + } + + public boolean validateToken(String token) { + try { + parseToken(token); + return true; + } catch (JwtException | IllegalArgumentException e) { + return false; + } + } +} diff --git a/backend/ims-service/pom.xml b/backend/ims-service/pom.xml new file mode 100644 index 0000000..344c8c2 --- /dev/null +++ b/backend/ims-service/pom.xml @@ -0,0 +1,89 @@ + + + 4.0.0 + + com.ims + ims-backend + 1.0.0-SNAPSHOT + + ims-service + + + com.ims + ims-common + + + com.ims + ims-api + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.postgresql + postgresql + runtime + + + org.flywaydb + flyway-core + + + org.flywaydb + flyway-database-postgresql + + + io.minio + minio + 8.5.17 + + + org.springframework.ai + spring-ai-ollama + + + org.springframework.ai + spring-ai-openai + + + org.apache.tika + tika-core + 2.9.2 + + + org.apache.tika + tika-parser-microsoft-module + 2.9.2 + + + org.apache.poi + poi-ooxml + 5.2.5 + + + commons-io + commons-io + 2.16.1 + + + org.projectlombok + lombok + true + + + diff --git a/backend/ims-service/src/main/java/com/ims/service/agent/AgentConfigService.java b/backend/ims-service/src/main/java/com/ims/service/agent/AgentConfigService.java new file mode 100644 index 0000000..02cd118 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/agent/AgentConfigService.java @@ -0,0 +1,37 @@ +package com.ims.service.agent; + +import com.ims.api.dto.ai.AiConfigRequest; +import com.ims.api.dto.agent.AgentConfigRequest; +import com.ims.service.knowledge.AiConfigService; +import org.springframework.stereotype.Service; + +import java.util.LinkedHashMap; +import java.util.Map; + +@Service +public class AgentConfigService { + + private final AiConfigService aiConfigService; + + public AgentConfigService(AiConfigService aiConfigService) { + this.aiConfigService = aiConfigService; + } + + public Map getConfig() { + com.ims.api.dto.ai.AiConfigResponse cfg = aiConfigService.getConfig(); + Map result = new LinkedHashMap<>(); + result.put("maxSteps", aiConfigService.getEffectiveMaxSteps()); + result.put("autoExecuteHighRisk", aiConfigService.getEffectiveAutoExecuteHighRisk()); + result.put("userRateLimit", aiConfigService.getEffectiveUserRateLimit()); + result.put("provider", cfg.getProvider()); + return result; + } + + public void updateConfig(AgentConfigRequest request) { + AiConfigRequest merged = new AiConfigRequest(); + merged.setAgentMaxSteps(request.getMaxSteps()); + merged.setAutoExecuteHighRisk(request.getAutoExecuteHighRisk()); + merged.setUserRateLimit(request.getUserRateLimit()); + aiConfigService.updateConfig(merged); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/agent/AgentConfigServiceImpl.java b/backend/ims-service/src/main/java/com/ims/service/agent/AgentConfigServiceImpl.java new file mode 100644 index 0000000..c7d65c9 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/agent/AgentConfigServiceImpl.java @@ -0,0 +1,63 @@ +package com.ims.service.agent; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ims.api.dto.agent.AgentConfigRequest; +import com.ims.api.dto.agent.AgentConfigResponse; +import com.ims.api.service.agent.AgentConfigService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.concurrent.TimeUnit; + +@Service +public class AgentConfigServiceImpl implements AgentConfigService { + + private static final String REDIS_KEY = "agent:config"; + + private final StringRedisTemplate redisTemplate; + private final ObjectMapper objectMapper; + + @Value("${agent.max-steps:10}") + private int defaultMaxSteps; + + @Value("${agent.auto-execute-high-risk:false}") + private boolean defaultAutoExecuteHighRisk; + + public AgentConfigServiceImpl(StringRedisTemplate redisTemplate, ObjectMapper objectMapper) { + this.redisTemplate = redisTemplate; + this.objectMapper = objectMapper; + } + + @Override + public AgentConfigResponse getConfig() { + String json = redisTemplate.opsForValue().get(REDIS_KEY); + if (json != null) { + try { + AgentConfigRequest request = objectMapper.readValue(json, AgentConfigRequest.class); + return toResponse(request); + } catch (JsonProcessingException ignored) {} + } + AgentConfigResponse response = new AgentConfigResponse(); + response.setMaxSteps(defaultMaxSteps); + response.setAutoExecuteHighRisk(defaultAutoExecuteHighRisk); + response.setUserRateLimit(10); + return response; + } + + @Override + public void updateConfig(AgentConfigRequest request) { + try { + redisTemplate.opsForValue().set(REDIS_KEY, objectMapper.writeValueAsString(request), 24, TimeUnit.HOURS); + } catch (JsonProcessingException ignored) {} + } + + private AgentConfigResponse toResponse(AgentConfigRequest request) { + AgentConfigResponse response = new AgentConfigResponse(); + response.setMaxSteps(request.getMaxSteps()); + response.setAutoExecuteHighRisk(request.getAutoExecuteHighRisk()); + response.setUserRateLimit(request.getUserRateLimit()); + return response; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/agent/AgentOrchestratorService.java b/backend/ims-service/src/main/java/com/ims/service/agent/AgentOrchestratorService.java new file mode 100644 index 0000000..a18fe28 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/agent/AgentOrchestratorService.java @@ -0,0 +1,795 @@ +package com.ims.service.agent; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.ims.api.dto.ai.AiConfigResponse; +import com.ims.api.dto.agent.AgentExecuteResponse; +import com.ims.common.exception.BusinessException; +import com.ims.service.agent.tool.Tool; +import com.ims.service.agent.tool.ToolRegistry; +import com.ims.service.ai.ModelRoutingService; +import com.ims.service.ai.PromptFormatter; +import com.ims.service.ai.PromptTemplateEngine; +import com.ims.service.ai.SystemContextBuilder; +import com.ims.service.entity.AgentMemory; +import com.ims.service.entity.AgentPlan; +import com.ims.service.entity.Department; +import com.ims.service.entity.Issue; +import com.ims.service.entity.PromptTemplate; +import com.ims.service.entity.ToolExecution; +import com.ims.service.entity.User; +import com.ims.service.knowledge.AiConfigService; +import com.ims.service.knowledge.SearchService; +import com.ims.service.repository.DepartmentRepository; +import com.ims.service.repository.AgentPlanRepository; +import com.ims.service.repository.AgentMemoryRepository; +import com.ims.service.repository.IssueRepository; +import com.ims.service.repository.ToolExecutionRepository; +import com.ims.service.repository.UserRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Service +public class AgentOrchestratorService { + + private static final Logger log = LoggerFactory.getLogger(AgentOrchestratorService.class); + private static final String SYS_ROLE_TEMPLATE = "SYS_ROLE_001"; + private static final String PLAN_TEMPLATE = "PLAN_001"; + + private static final String FIELD_SUGGEST_SYSTEM = "你是一个软件质量指摘分析助手。根据用户提供的指摘标题和描述," + + "从下面的字段枚举值中为每个字段选择最合适的一项,同时结合上下文补全文本与人员字段,仅输出一个JSON对象,不要输出任何其他文字。\n" + + "category: [功能缺陷, UI/UX问题, 性能问题, 安全漏洞, 文档错误]\n" + + "phase: [需求, 设计, 编码, 测试, 部署, 运维]\n" + + "impactLevel: [高, 中, 低]\n" + + "priority: [urgent, high, medium, low]\n" + + "subProject: [用户管理子系统, 权限控制子系统, 数据报表子系统]\n" + + "ngReason: 指摘的NG原因,用简短中文概括\n" + + "impactScope: 影响范围(如:前端适配、数据库查询)\n" + + "deployment: 部署位置或环境\n" + + "pgmNo: 关联PGM编号(如 PGM_AUTH_VIEW_001),无法判断时留空\n" + + "responseContent: 建议的对应内容/整改方案,2-3句话\n" + + "assigneeName, reviewerName, validatorName: 从可选项中选择对应人员姓名,无法确定时留空\n" + + "departmentName: 从可选项中选择归属部门名称,无法确定时留空\n" + + "deadline: 建议的整改截止日期,格式 YYYY-MM-DD,不早于今天\n" + + "输出格式: {\"category\":\"...\",\"phase\":\"...\",\"impactLevel\":\"...\",\"priority\":\"...\",\"subProject\":\"...\"," + + "\"ngReason\":\"...\",\"impactScope\":\"...\",\"deployment\":\"...\",\"pgmNo\":\"...\",\"responseContent\":\"...\"," + + "\"assigneeName\":\"...\",\"reviewerName\":\"...\",\"validatorName\":\"...\",\"departmentName\":\"...\",\"deadline\":\"YYYY-MM-DD\"}"; + + private static final String FIELD_SUGGEST_WITH_CASES_SYSTEM = "你是一个软件质量指摘分析助手。用户提供了指摘标题和描述," + + "同时附上了知识库检索到的相似案例。请先分析相似案例的处理经验,再结合当前指摘标题和描述," + + "从下面的字段枚举值中为每个字段选择最合适的一项,同时结合上下文补全文本与人员字段,仅输出一个JSON对象,不要输出任何其他文字。\n" + + "category: [功能缺陷, UI/UX问题, 性能问题, 安全漏洞, 文档错误]\n" + + "phase: [需求, 设计, 编码, 测试, 部署, 运维]\n" + + "impactLevel: [高, 中, 低]\n" + + "priority: [urgent, high, medium, low]\n" + + "subProject: [用户管理子系统, 权限控制子系统, 数据报表子系统]\n" + + "ngReason: 指摘的NG原因,用简短中文概括\n" + + "impactScope: 影响范围(如:前端适配、数据库查询)\n" + + "deployment: 部署位置或环境\n" + + "pgmNo: 关联PGM编号(如 PGM_AUTH_VIEW_001),无法判断时留空\n" + + "responseContent: 建议的对应内容/整改方案,2-3句话\n" + + "assigneeName, reviewerName, validatorName: 从可选项中选择对应人员姓名,无法确定时留空\n" + + "departmentName: 从可选项中选择归属部门名称,无法确定时留空\n" + + "deadline: 建议的整改截止日期,格式 YYYY-MM-DD,不早于今天\n" + + "输出格式: {\"category\":\"...\",\"phase\":\"...\",\"impactLevel\":\"...\",\"priority\":\"...\",\"subProject\":\"...\"," + + "\"ngReason\":\"...\",\"impactScope\":\"...\",\"deployment\":\"...\",\"pgmNo\":\"...\",\"responseContent\":\"...\"," + + "\"assigneeName\":\"...\",\"reviewerName\":\"...\",\"validatorName\":\"...\",\"departmentName\":\"...\",\"deadline\":\"YYYY-MM-DD\"}"; + + private static final Set CATEGORIES = Set.of("功能缺陷", "UI/UX问题", "性能问题", "安全漏洞", "文档错误"); + private static final Set PHASES = Set.of("需求", "设计", "编码", "测试", "部署", "运维"); + private static final Set IMPACT_LEVELS = Set.of("高", "中", "低"); + private static final Set PRIORITIES = Set.of("urgent", "high", "medium", "low"); + private static final Set SUB_PROJECTS = Set.of("用户管理子系统", "权限控制子系统", "数据报表子系统"); + + private final AgentPlanRepository planRepository; + private final ToolExecutionRepository toolExecutionRepository; + private final IssueRepository issueRepository; + private final UserRepository userRepository; + private final ToolRegistry toolRegistry; + private final PromptTemplateEngine promptEngine; + private final ModelRoutingService modelRoutingService; + private final SystemContextBuilder systemContextBuilder; + private final AiConfigService aiConfigService; + private final MemoryService memoryService; + private final PromptFormatter promptFormatter; + private final ObjectMapper objectMapper; + private final StringRedisTemplate redisTemplate; + private final TransactionTemplate transactionTemplate; + private final SearchService searchService; + private final DepartmentRepository departmentRepository; + + private final ExecutorService executor = Executors.newFixedThreadPool(2, r -> { + Thread t = new Thread(r, "async-exec-"); + t.setDaemon(true); + return t; + }); + + private final ConcurrentHashMap emitters = new ConcurrentHashMap<>(); + + public AgentOrchestratorService(AgentPlanRepository planRepository, + ToolExecutionRepository toolExecutionRepository, + IssueRepository issueRepository, + UserRepository userRepository, + ToolRegistry toolRegistry, + PromptTemplateEngine promptEngine, + ModelRoutingService modelRoutingService, + SystemContextBuilder systemContextBuilder, + AiConfigService aiConfigService, + MemoryService memoryService, + PromptFormatter promptFormatter, +ObjectMapper objectMapper, + StringRedisTemplate redisTemplate, + TransactionTemplate transactionTemplate, + SearchService searchService, + DepartmentRepository departmentRepository) { + this.planRepository = planRepository; + this.toolExecutionRepository = toolExecutionRepository; + this.issueRepository = issueRepository; + this.userRepository = userRepository; + this.toolRegistry = toolRegistry; + this.promptEngine = promptEngine; + this.modelRoutingService = modelRoutingService; + this.systemContextBuilder = systemContextBuilder; + this.aiConfigService = aiConfigService; + this.memoryService = memoryService; + this.promptFormatter = promptFormatter; + this.objectMapper = objectMapper; + this.redisTemplate = redisTemplate; + this.transactionTemplate = transactionTemplate; + this.searchService = searchService; + this.departmentRepository = departmentRepository; + } + + public AgentExecuteResponse execute(Long issueId, String goal, Long userId) { + checkRateLimit(userId); + Issue issue = null; + if (issueId != null) { + issue = issueRepository.findById(issueId) + .orElseThrow(() -> new BusinessException("指摘不存在: " + issueId)); + } + User user = userRepository.findById(userId).orElse(null); + + AgentPlan plan = new AgentPlan(); + plan.setIssue(issue); + plan.setGoal(goal); + plan.setPlanSteps("[]"); + plan.setStatus("pending"); + plan.setRequiresApproval(false); + plan.setModelProvider(modelRoutingService.currentProvider()); + plan.setCreatedBy(user); + planRepository.save(plan); + + executor.execute(() -> { + try { + transactionTemplate.executeWithoutResult(s -> runPlan(plan.getId(), userId)); + } catch (Exception e) { + log.error("Agent执行失败 planId={}", plan.getId(), e); + } + }); + return new AgentExecuteResponse(plan.getId(), "running", false); + } + + public Map getStatus(Long planId) { + AgentPlan plan = planRepository.findById(planId) + .orElseThrow(() -> new BusinessException("Agent计划不存在: " + planId)); + Map result = new LinkedHashMap<>(); + result.put("planId", plan.getId()); + result.put("issueId", plan.getIssue() != null ? plan.getIssue().getId() : null); + result.put("goal", plan.getGoal()); + result.put("planSteps", plan.getPlanSteps()); + result.put("status", plan.getStatus()); + result.put("requiresApproval", plan.getRequiresApproval()); + result.put("approvalStatus", plan.getApprovalStatus()); + result.put("approvalComment", plan.getApprovalComment()); + result.put("modelProvider", plan.getModelProvider()); + result.put("agentMessage", plan.getAgentMessage()); + result.put("createdAt", plan.getCreatedAt() == null ? null : plan.getCreatedAt().toString()); + result.put("completedAt", plan.getCompletedAt() == null ? null : plan.getCompletedAt().toString()); + + result.put("systemTemplateId", SYS_ROLE_TEMPLATE); + result.put("systemTemplateVersion", templateVersion(SYS_ROLE_TEMPLATE)); + result.put("planTemplateId", PLAN_TEMPLATE); + result.put("planTemplateVersion", templateVersion(PLAN_TEMPLATE)); + + List executions = toolExecutionRepository.findByPlanId(planId); + result.put("toolExecutions", executions.stream().map(this::toolExecutionToMap).toList()); + return result; + } + + public SseEmitter stream(Long planId) { + AgentPlan plan = planRepository.findById(planId) + .orElseThrow(() -> new BusinessException("Agent计划不存在: " + planId)); + SseEmitter emitter = new SseEmitter(600_000L); + emitters.put(planId, emitter); + emitter.onCompletion(() -> emitters.remove(planId)); + emitter.onTimeout(() -> emitters.remove(planId)); + emitter.onError(e -> emitters.remove(planId)); + + sendEvent(planId, Map.of("type", "thought", "content", "已连接执行流,等待 Agent 输出…")); + sendPromptAndModelInfo(planId); + + String status = plan.getStatus(); + if (!"pending".equals(status) && !"running".equals(status)) { + String message = plan.getAgentMessage() == null ? "" : plan.getAgentMessage(); + if (!"awaiting_approval".equals(status)) { + sendEvent(planId, Map.of("type", "result", "content", message)); + } + emitter.complete(); + emitters.remove(planId); + } + return emitter; + } + + private void sendEvent(Long planId, Map event) { + SseEmitter emitter = emitters.get(planId); + if (emitter == null) { + return; + } + try { + emitter.send(SseEmitter.event().name("message").data(event)); + } catch (IOException | IllegalStateException e) { + emitters.remove(planId); + emitter.completeWithError(e); + } + } + + private void completeStream(Long planId) { + SseEmitter emitter = emitters.remove(planId); + if (emitter != null) { + try { + emitter.complete(); + } catch (Exception ignored) {} + } + } + + private void sendPromptAndModelInfo(Long planId) { + AiConfigResponse cfg = aiConfigService.getConfig(); + String provider = modelRoutingService.currentProvider(); + String model = "deepseek".equalsIgnoreCase(provider) ? cfg.getDeepseekModel() : cfg.getOllamaChatModel(); + + Map systemPromptInfo = new LinkedHashMap<>(); + systemPromptInfo.put("type", "prompt_info"); + systemPromptInfo.put("templateId", SYS_ROLE_TEMPLATE); + systemPromptInfo.put("version", templateVersion(SYS_ROLE_TEMPLATE)); + systemPromptInfo.put("variables", List.of("userGoal", "issueContext", "availableTools")); + sendEvent(planId, systemPromptInfo); + + Map planPromptInfo = new LinkedHashMap<>(); + planPromptInfo.put("type", "prompt_info"); + planPromptInfo.put("templateId", PLAN_TEMPLATE); + planPromptInfo.put("version", templateVersion(PLAN_TEMPLATE)); + planPromptInfo.put("variables", List.of("userGoal", "issueContext", "similarCases", "availableTools")); + sendEvent(planId, planPromptInfo); + + Map modelInfo = new LinkedHashMap<>(); + modelInfo.put("type", "model_info"); + modelInfo.put("provider", provider); + modelInfo.put("model", model); + modelInfo.put("endpoint", cfg.getOllamaBaseUrl()); + sendEvent(planId, modelInfo); + } + + public Map approve(Long planId, String comment, Long userId) { + AgentPlan plan = planRepository.findById(planId) + .orElseThrow(() -> new BusinessException("Agent计划不存在: " + planId)); + if (!"requested".equals(plan.getApprovalStatus())) { + throw new BusinessException("该计划不在待审批状态"); + } + plan.setApprovalStatus("approved"); + plan.setApprovalComment(comment); + + ToolExecution pending = toolExecutionRepository.findByPlanId(planId).stream() + .filter(t -> "pending".equals(t.getStatus())) + .findFirst().orElse(null); + if (pending != null) { + try { + Map params = objectMapper.readValue(pending.getInputParams(), + new TypeReference>() {}); + if ("update_issue".equals(pending.getToolName()) && plan.getIssue() != null) { + params.put("issue_id", plan.getIssue().getId()); + } + Object output = runTool(plan, pending.getToolName(), params, pending); + if ("create_issue".equals(pending.getToolName())) { + linkCreatedIssue(plan, output); + } + } catch (Exception e) { + log.error("审批后工具执行失败 planId={}", planId, e); + pending.setStatus("failed"); + pending.setOutputResult(e.getMessage()); + toolExecutionRepository.save(pending); + finishPlan(plan, "failed"); + completeStream(planId); + return getStatus(planId); + } + } + finishPlan(plan, "completed"); + completeStream(planId); + return getStatus(planId); + } + + public Map reject(Long planId, String comment) { + AgentPlan plan = planRepository.findById(planId) + .orElseThrow(() -> new BusinessException("Agent计划不存在: " + planId)); + if (!"requested".equals(plan.getApprovalStatus())) { + throw new BusinessException("该计划不在待审批状态"); + } + plan.setApprovalStatus("rejected"); + plan.setApprovalComment(comment); + plan.setStatus("rejected"); + plan.setCompletedAt(LocalDateTime.now()); + planRepository.save(plan); + + List pending = toolExecutionRepository.findByPlanId(planId).stream() + .filter(t -> "pending".equals(t.getStatus())).toList(); + for (ToolExecution t : pending) { + t.setStatus("rejected"); + toolExecutionRepository.save(t); + } + + if (plan.getIssue() != null) { + issueRepository.findById(plan.getIssue().getId()).ifPresent(issue -> { + issue.setAgentStatus("rejected"); + issue.setAgentLastPlanId(planId); + issueRepository.save(issue); + }); + } + return getStatus(planId); + } + + public String suggest(Long issueId, String goal, Long userId) { + Issue issue = issueRepository.findById(issueId) + .orElseThrow(() -> new BusinessException("指摘不存在: " + issueId)); + User user = userRepository.findById(userId).orElse(null); + Map systemVars = systemContextBuilder.build(user, toolDescriptions()); + String systemPrompt = promptEngine.render(SYS_ROLE_TEMPLATE, systemVars); + String userPrompt = "请基于以下指摘上下文,输出字段修改建议(如标题/描述/工程阶段/优先级/影响度等字段的修改建议),仅输出建议内容,不要调用任何工具。\n\n" + + buildIssueContext(issue) + "\n\n用户诉求: " + goal; + ModelRoutingService.CallResult result = modelRoutingService.call(systemPrompt, userPrompt); + return result.text(); + } + + public Map suggestFields(String title, String description) { + Map result = new LinkedHashMap<>(); + List users = userRepository.findAll(); + List departments = departmentRepository.findAll(); + String userOptions = users.stream() + .map(u -> u.getUsername()) + .reduce((a, b) -> a + ", " + b).orElse(""); + String deptOptions = departments.stream() + .map(Department::getName) + .reduce((a, b) -> a + ", " + b).orElse(""); + StringBuilder userPrompt = new StringBuilder("指摘标题: ").append(title); + if (description != null && !description.isBlank()) { + userPrompt.append("\n指摘描述: ").append(description); + } + userPrompt.append("\n可选人员: ").append(userOptions) + .append("\n可选部门: ").append(deptOptions); + List similarCases = retrieveSimilarCases(title + (description != null ? " " + description : "")); + String system = FIELD_SUGGEST_SYSTEM; + if (!similarCases.isEmpty()) { + system = FIELD_SUGGEST_WITH_CASES_SYSTEM; + userPrompt.append("\n\n知识库相似案例(仅作参考):\n").append(similarCases.stream() + .map(c -> String.format("《%s》(相似度 %.2f): %s", c.getDocName(), c.getScore(), c.getContent())) + .reduce((a, b) -> a + "\n" + b) + .orElse("")); + } + try { + ModelRoutingService.CallResult callResult = modelRoutingService.call(system, userPrompt.toString()); + ObjectNode json = promptFormatter.extractJsonObject(callResult.text()); + if (json == null) { + return result; + } + putIfValid(result, json, "category", CATEGORIES); + putIfValid(result, json, "phase", PHASES); + putIfValid(result, json, "impactLevel", IMPACT_LEVELS); + putIfValid(result, json, "priority", PRIORITIES); + putIfValid(result, json, "subProject", SUB_PROJECTS); + putIfText(result, json, "ngReason", 200); + putIfText(result, json, "impactScope", 200); + putIfText(result, json, "deployment", 200); + putIfText(result, json, "pgmNo", 100); + putIfText(result, json, "responseContent", 1000); + putIfUser(result, json, "assigneeName", "assigneeId", users); + putIfUser(result, json, "reviewerName", "reviewerId", users); + putIfUser(result, json, "validatorName", "validatorId", users); + putIfDepartment(result, json, "departmentName", "departmentId", departments); + putIfDate(result, json, "deadline"); + } catch (Exception e) { + log.warn("字段智能填充失败 title={}", title, e); + } + return result; + } + + private void putIfText(Map out, ObjectNode node, String key, int maxLen) { + JsonNode value = node.get(key); + if (value != null && value.isTextual() && !value.asText().isBlank()) { + String text = value.asText().trim(); + out.put(key, text.length() > maxLen ? text.substring(0, maxLen) : text); + } + } + + private void putIfUser(Map out, ObjectNode node, String nameKey, String idKey, List users) { + JsonNode value = node.get(nameKey); + if (value == null || !value.isTextual()) { + return; + } + String name = value.asText().trim(); + users.stream() + .filter(u -> u.getUsername().equals(name)) + .findFirst() + .ifPresent(u -> out.put(idKey, u.getId())); + } + + private void putIfDepartment(Map out, ObjectNode node, String nameKey, String idKey, List departments) { + JsonNode value = node.get(nameKey); + if (value == null || !value.isTextual()) { + return; + } + String name = value.asText().trim(); + departments.stream() + .filter(d -> d.getName().equals(name)) + .findFirst() + .ifPresent(d -> out.put(idKey, d.getId())); + } + + private void putIfDate(Map out, ObjectNode node, String key) { + JsonNode value = node.get(key); + if (value == null || !value.isTextual()) { + return; + } + String text = value.asText().trim(); + try { + LocalDate date = LocalDate.parse(text); + if (!date.isBefore(LocalDate.now())) { + out.put(key, text); + } + } catch (Exception ignored) { + } + } + + private static final double SIMILARITY_THRESHOLD = 0.0; + + private List retrieveSimilarCases(String query) { + try { + List hits = searchService.search(query, 3).stream() + .filter(r -> r.getScore() >= SIMILARITY_THRESHOLD) + .toList(); + log.info("字段智能填充相似案例检索: query={} hits={}", query, hits.size()); + return hits; + } catch (Exception e) { + log.warn("知识库相似案例检索失败,回退直接分析 query={}", query, e); + return List.of(); + } + } + + private void putIfValid(Map out, ObjectNode node, String key, Set allowed) { + JsonNode value = node.get(key); + if (value != null && value.isTextual()) { + String text = value.asText().trim(); + if (allowed.contains(text)) { + out.put(key, text); + } + } + } + + private void runPlan(Long planId, Long userId) { + AgentPlan plan = planRepository.findById(planId).orElse(null); + if (plan == null) { + return; + } + User user = userRepository.findById(userId).orElse(null); + plan.setStatus("running"); + planRepository.save(plan); + sendEvent(planId, Map.of("type", "thought", "content", "正在加载指摘上下文并生成处理方案…")); + + Issue issue = null; + if (plan.getIssue() != null) { + issue = issueRepository.findById(plan.getIssue().getId()).orElse(null); + } + if (issue != null) { + issue.setAgentStatus("running"); + issue.setAgentLastPlanId(planId); + issueRepository.save(issue); + } + + boolean autoExecuteHighRisk = aiConfigService.getEffectiveAutoExecuteHighRisk(); + String similarCases = buildSimilarCases(issue); + List executedSteps = new ArrayList<>(); + + try { + Map systemVars = systemContextBuilder.build(user, toolDescriptions()); + String systemPrompt = promptEngine.render(SYS_ROLE_TEMPLATE, systemVars); + + Map planVars = buildPlanVariables(issue, plan.getGoal(), + similarCases, executedSteps, 1, 1); + String userPrompt = promptEngine.render(PLAN_TEMPLATE, planVars); + + sendPromptAndModelInfo(planId); + + ModelRoutingService.ToolCallResult result = modelRoutingService.callWithTools(systemPrompt, userPrompt); + if (result.toolCalls().isEmpty()) { + plan.setAgentMessage(result.text()); + sendEvent(planId, Map.of("type", "result", + "content", result.text() == null ? "方案已生成" : result.text())); + finishPlan(plan, "completed", executedSteps, issue); + completeStream(planId); + return; + } + for (ModelRoutingService.ToolCallInfo info : result.toolCalls()) { + String toolName = info.name(); + Map parameters = parseArguments(info.arguments()); + boolean isWrite = toolRegistry.isWrite(toolName); + if (isWrite && !autoExecuteHighRisk) { + sendEvent(planId, Map.of("type", "action", "tool", toolName, "params", parameters, "approval", true)); + sendEvent(planId, Map.of("type", "observation", "result", "写操作需人工审批,等待审批…")); + plan.setRequiresApproval(true); + plan.setApprovalStatus("requested"); + plan.setStatus("awaiting_approval"); + plan.setPlanSteps(toJson(executedSteps)); + plan.setAgentMessage("等待人工审批: 调用 " + toolName); + planRepository.save(plan); + + ToolExecution pending = createToolExecution(plan, toolName, parameters, "pending"); + pending.setOutputResult("等待人工审批"); + toolExecutionRepository.save(pending); + + if (issue != null) { + issue.setAgentStatus("awaiting_approval"); + issueRepository.save(issue); + } + return; + } + + sendEvent(planId, Map.of("type", "action", "tool", toolName, "params", parameters)); + Object output = runTool(plan, toolName, parameters, null); + sendEvent(planId, Map.of("type", "observation", "result", truncate(toDisplay(output)))); + executedSteps.add((executedSteps.size() + 1) + ". " + toolName + " → " + truncate(toJson(parameters))); + plan.setPlanSteps(toJson(executedSteps)); + planRepository.save(plan); + if ("create_issue".equals(toolName)) { + linkCreatedIssue(plan, output); + } + } + plan.setAgentMessage(result.text()); + sendEvent(planId, Map.of("type", "result", + "content", result.text() == null ? "方案已生成" : result.text())); + finishPlan(plan, "completed", executedSteps, issue); + completeStream(planId); + } catch (Exception e) { + log.error("Agent执行失败 planId={}", planId, e); + sendEvent(planId, Map.of("type", "error", "message", e.getMessage())); + plan.setStatus("failed"); + plan.setAgentMessage(e.getMessage()); + planRepository.save(plan); + if (issue != null) { + issue.setAgentStatus("failed"); + issueRepository.save(issue); + } + completeStream(planId); + } + } + + private Object runTool(AgentPlan plan, String toolName, Map parameters, ToolExecution existing) { + long start = System.currentTimeMillis(); + ToolExecution record = existing; + if (record == null) { + record = createToolExecution(plan, toolName, parameters, "running"); + } else { + record.setStatus("running"); + } + toolExecutionRepository.save(record); + try { + Tool tool = toolRegistry.get(toolName); + Object output = tool.execute(parameters); + record.setStatus("success"); + record.setOutputResult(truncate(toDisplay(output))); + record.setExecutionTimeMs(System.currentTimeMillis() - start); + toolExecutionRepository.save(record); + return output; + } catch (Exception e) { + record.setStatus("failed"); + record.setOutputResult(truncate(e.getMessage())); + record.setExecutionTimeMs(System.currentTimeMillis() - start); + toolExecutionRepository.save(record); + throw new BusinessException("工具 " + toolName + " 执行失败: " + e.getMessage()); + } + } + + private void linkCreatedIssue(AgentPlan plan, Object output) { + try { + if (output instanceof Map map && map.get("id") instanceof Number number) { + issueRepository.findById(number.longValue()).ifPresent(newIssue -> { + plan.setIssue(newIssue); + planRepository.save(plan); + }); + } + } catch (Exception e) { + log.warn("关联新建指摘到Agent计划失败: {}", e.getMessage()); + } + } + + private ToolExecution createToolExecution(AgentPlan plan, String toolName, + Map parameters, String status) { + ToolExecution record = new ToolExecution(); + record.setPlan(plan); + record.setToolName(toolName); + record.setInputParams(toJson(parameters)); + record.setStatus(status); + return record; + } + + private void finishPlan(AgentPlan plan, String status) { + finishPlan(plan, status, new ArrayList<>(), null); + } + + private void finishPlan(AgentPlan plan, String status, List executedSteps, Issue issue) { + plan.setStatus(status); + plan.setCompletedAt(LocalDateTime.now()); + if (!executedSteps.isEmpty()) { + plan.setPlanSteps(toJson(executedSteps)); + } + planRepository.save(plan); + + saveMemory(plan); + if (issue == null) { + if (plan.getIssue() != null) { + issueRepository.findById(plan.getIssue().getId()).ifPresent(i -> { + i.setAgentStatus(status); + i.setAgentLastPlanId(plan.getId()); + issueRepository.save(i); + }); + } + } else { + issue.setAgentStatus(status); + issueRepository.save(issue); + } + } + + private void saveMemory(AgentPlan plan) { + try { + String summary = plan.getGoal(); + String steps = plan.getPlanSteps(); + AgentMemory memory = memoryService.add(summary, steps); + if (memory != null) { + log.info("已保存Agent记忆 memoryId={}", memory.getId()); + } + } catch (Exception e) { + log.warn("保存Agent记忆失败: {}", e.getMessage()); + } + } + + private Map parseArguments(String arguments) { + if (arguments == null || arguments.isBlank()) { + return new LinkedHashMap<>(); + } + try { + return objectMapper.readValue(arguments, new TypeReference>() {}); + } catch (Exception e) { + return new LinkedHashMap<>(); + } + } + + private Map buildPlanVariables(Issue issue, String goal, String similarCases, + List executedSteps, int step, int maxSteps) { + Map vars = new LinkedHashMap<>(); + vars.put("userGoal", goal); + vars.put("issueContext", buildIssueContext(issue)); + vars.put("similarCases", similarCases); + vars.put("availableTools", toolDescriptions()); + vars.put("executedSteps", executedSteps); + vars.put("currentStep", step); + vars.put("maxSteps", maxSteps); + return vars; + } + + private String buildIssueContext(Issue issue) { + if (issue == null) { + return "(新建指摘场景,指摘尚未创建)"; + } + return "指摘编号: " + issue.getIssueNo() + "\n标题: " + issue.getTitle() + + "\n描述: " + (issue.getDescription() == null ? "" : issue.getDescription()) + + "\n状态: " + issue.getStatus() + ", 优先级: " + issue.getPriority() + + "\n阶段: " + (issue.getPhase() == null ? "" : issue.getPhase()); + } + + private String buildSimilarCases(Issue issue) { + if (issue == null) { + return ""; + } + try { + List memories = memoryService.searchSimilar(issue.getTitle(), 3); + if (memories.isEmpty()) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < memories.size(); i++) { + AgentMemory m = memories.get(i); + sb.append(i + 1).append(". ").append(m.getIssueSummary()) + .append(" | 步骤: ").append(m.getSolutionSteps()).append("\n"); + } + return sb.toString(); + } catch (Exception e) { + return ""; + } + } + + private List toolDescriptions() { + List list = new ArrayList<>(); + toolRegistry.descriptions().forEach((name, desc) -> list.add(name + " - " + desc)); + return list; + } + + private String templateVersion(String templateId) { + try { + PromptTemplate template = promptEngine.loadActive(templateId); + return template.getVersion() == null ? "" : String.valueOf(template.getVersion()); + } catch (Exception e) { + return ""; + } + } + + private void checkRateLimit(Long userId) { + String minuteKey = "agent:limit:" + userId + ":" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmm")); + Long count = redisTemplate.opsForValue().increment(minuteKey); + if (count != null && count == 1) { + redisTemplate.expire(minuteKey, java.time.Duration.ofSeconds(60)); + } + int limit = aiConfigService.getEffectiveUserRateLimit(); + if (count != null && count > limit) { + throw new BusinessException("Agent执行过于频繁,请稍后再试"); + } + } + + private Map toolExecutionToMap(ToolExecution t) { + Map m = new LinkedHashMap<>(); + m.put("id", t.getId()); + m.put("toolName", t.getToolName()); + m.put("inputParams", t.getInputParams()); + m.put("outputResult", t.getOutputResult()); + m.put("status", t.getStatus()); + m.put("executionTimeMs", t.getExecutionTimeMs()); + m.put("createdAt", t.getCreatedAt() == null ? null : t.getCreatedAt().toString()); + return m; + } + + private String toJson(Object value) { + try { + return objectMapper.writeValueAsString(value); + } catch (Exception e) { + return String.valueOf(value); + } + } + + private String toDisplay(Object value) { + if (value instanceof String) { + return (String) value; + } + return toJson(value); + } + + private String truncate(String text) { + if (text == null) { + return null; + } + return text.length() > 3000 ? text.substring(0, 3000) : text; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/agent/AgentService.java b/backend/ims-service/src/main/java/com/ims/service/agent/AgentService.java new file mode 100644 index 0000000..6508e29 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/agent/AgentService.java @@ -0,0 +1,226 @@ +package com.ims.service.agent; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ims.common.exception.BusinessException; +import com.ims.service.entity.AgentPlan; +import com.ims.service.entity.Issue; +import com.ims.service.entity.ToolExecution; +import com.ims.service.entity.User; +import com.ims.service.knowledge.SearchService; +import com.ims.service.notification.NotificationService; +import com.ims.service.repository.AgentPlanRepository; +import com.ims.service.repository.IssueRepository; +import com.ims.service.repository.ToolExecutionRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class AgentService { + + private final AgentPlanRepository agentPlanRepository; + private final ToolExecutionRepository toolExecutionRepository; + private final IssueRepository issueRepository; + private final NotificationService notificationService; + private final SearchService searchService; + private final OllamaChatService ollamaChatService; + private final ObjectMapper objectMapper; + + public AgentService(AgentPlanRepository agentPlanRepository, + ToolExecutionRepository toolExecutionRepository, + IssueRepository issueRepository, + NotificationService notificationService, + SearchService searchService, + OllamaChatService ollamaChatService, + ObjectMapper objectMapper) { + this.agentPlanRepository = agentPlanRepository; + this.toolExecutionRepository = toolExecutionRepository; + this.issueRepository = issueRepository; + this.notificationService = notificationService; + this.searchService = searchService; + this.ollamaChatService = ollamaChatService; + this.objectMapper = objectMapper; + } + + @Transactional + public Map execute(Long issueId, String goal, User user) { + Issue issue = issueRepository.findById(issueId) + .filter(i -> !Boolean.TRUE.equals(i.getIsDeleted())) + .orElseThrow(() -> new BusinessException("指摘不存在")); + + long searchStart = System.currentTimeMillis(); + List hits; + try { + hits = searchService.search(goal, 3); + } catch (Exception e) { + hits = new ArrayList<>(); + } + long searchMs = System.currentTimeMillis() - searchStart; + + String searchOutput = hits.isEmpty() + ? "知识库暂无匹配的相似案例" + : "知识库命中 " + hits.size() + " 条相似案例:" + hits.stream() + .map(h -> String.format("《%s》(相似度 %.2f): %s", h.getDocName(), h.getScore(), truncate(h.getContent(), 60))) + .reduce((a, b) -> a + ";" + b) + .orElse(""); + + String planText; + long genMs = 0; + String genOutput; + boolean genOk = true; + if (!hits.isEmpty()) { + String system = "你是一个指摘管理系统(IMS)的智能助理。根据用户指令和知识库检索到的相似案例,生成具体、可执行的对应方案。" + + "请按以下结构输出:\n1. 问题分析\n2. 处理步骤(分点列出)\n3. 验证方式\n语言简洁专业。"; + String userContent = "知识库相似案例:\n" + hits.stream() + .map(h -> "【案例 " + h.getDocName() + "】" + h.getContent()) + .reduce((a, b) -> a + "\n" + b).orElse("") + + "\n\n用户指令:" + goal; + try { + long genStart = System.currentTimeMillis(); + planText = ollamaChatService.chat(system, userContent); + genMs = System.currentTimeMillis() - genStart; + genOutput = truncate(planText, 300); + } catch (Exception e) { + genOk = false; + planText = "知识库检索完成,但方案生成失败(Ollama 暂不可用),已保留检索到的相似案例供参考。"; + genOutput = planText; + } + } else { + planText = "知识库未检索到相似案例,建议人工分析后处理。"; + genOutput = planText; + } + + AgentPlan plan = AgentPlan.builder() + .issue(issue) + .goal(goal) + .planSteps(planSteps(planText)) + .status("pending") + .requiresApproval(true) + .approvalStatus("pending") + .createdBy(user) + .modelProvider("ollama/" + ollamaChatService.getModel()) + .build(); + plan = agentPlanRepository.save(plan); + + saveTool(plan, "search_knowledge", + "{\"query\":\"" + safeJson(goal) + "\"}", + searchOutput, searchMs, "success"); + saveTool(plan, "generate_response", + "{\"goal\":\"" + safeJson(goal) + "\"}", + genOutput, genMs, genOk ? "success" : "failed"); + + issue.setAgentStatus("awaiting_approval"); + issue.setAgentLastPlanId(plan.getId()); + issueRepository.save(issue); + + Map result = new HashMap<>(); + result.put("planId", plan.getId()); + result.put("status", plan.getStatus()); + result.put("approvalStatus", plan.getApprovalStatus()); + result.put("requiresApproval", plan.getRequiresApproval()); + return result; + } + + @Transactional(readOnly = true) + public Map status(Long planId) { + AgentPlan plan = agentPlanRepository.findById(planId) + .orElseThrow(() -> new BusinessException("计划不存在")); + List tools = toolExecutionRepository.findByPlanId(planId); + Map map = new HashMap<>(); + map.put("planId", plan.getId()); + map.put("goal", plan.getGoal()); + map.put("status", plan.getStatus()); + map.put("approvalStatus", plan.getApprovalStatus()); + map.put("requiresApproval", plan.getRequiresApproval()); + map.put("createdAt", plan.getCreatedAt()); + map.put("tools", tools.stream().map(t -> { + Map tm = new HashMap<>(); + tm.put("toolName", t.getToolName()); + tm.put("inputParams", t.getInputParams()); + tm.put("outputResult", t.getOutputResult()); + tm.put("status", t.getStatus()); + tm.put("executionTimeMs", t.getExecutionTimeMs()); + return tm; + }).toList()); + return map; + } + + @Transactional + public void approve(Long planId, String comment) { + AgentPlan plan = agentPlanRepository.findById(planId) + .orElseThrow(() -> new BusinessException("计划不存在")); + plan.setApprovalStatus("approved"); + plan.setStatus("completed"); + plan.setApprovalComment(comment); + plan.setCompletedAt(LocalDateTime.now()); + agentPlanRepository.save(plan); + + Issue issue = plan.getIssue(); + issue.setAgentStatus("agent_driven"); + issueRepository.save(issue); + if (plan.getCreatedBy() != null) { + notificationService.notify(plan.getCreatedBy(), "Agent 指令已批准", + "您的 Agent 指令《" + plan.getGoal() + "》已获批准执行。", + "agent", "/issues/" + issue.getId(), issue); + } + } + + @Transactional + public void reject(Long planId, String comment) { + AgentPlan plan = agentPlanRepository.findById(planId) + .orElseThrow(() -> new BusinessException("计划不存在")); + plan.setApprovalStatus("rejected"); + plan.setStatus("completed"); + plan.setApprovalComment(comment); + plan.setCompletedAt(LocalDateTime.now()); + agentPlanRepository.save(plan); + + Issue issue = plan.getIssue(); + issue.setAgentStatus("human_driven"); + issueRepository.save(issue); + if (plan.getCreatedBy() != null) { + notificationService.notify(plan.getCreatedBy(), "Agent 指令被驳回", + "您的 Agent 指令《" + plan.getGoal() + "》被驳回。" + + (comment != null && !comment.isBlank() ? " 原因: " + comment : ""), + "agent", "/issues/" + issue.getId(), issue); + } + } + + private void saveTool(AgentPlan plan, String toolName, String inputParams, String output, long ms, String status) { + ToolExecution t = ToolExecution.builder() + .plan(plan) + .toolName(toolName) + .inputParams(inputParams) + .outputResult(output) + .status(status) + .executionTimeMs(ms) + .build(); + toolExecutionRepository.save(t); + } + + private String planSteps(String planText) { + try { + return objectMapper.writeValueAsString(List.of( + Map.of("step", 1, "action", "解析用户指令", "status", "done"), + Map.of("step", 2, "action", "检索知识库相似案例", "status", "done"), + Map.of("step", 3, "action", "生成对应方案", "status", "done"), + Map.of("step", 4, "action", "方案如下", "status", "done", "detail", truncate(planText, 500)))); + } catch (Exception e) { + return "[]"; + } + } + + private String truncate(String s, int max) { + if (s == null) return ""; + return s.length() <= max ? s : s.substring(0, max) + "..."; + } + + private String safeJson(String s) { + return s == null ? "" : s.replace("\"", "\\\""); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/agent/AgentToolPermissionService.java b/backend/ims-service/src/main/java/com/ims/service/agent/AgentToolPermissionService.java new file mode 100644 index 0000000..6280e1f --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/agent/AgentToolPermissionService.java @@ -0,0 +1,55 @@ +package com.ims.service.agent; + +import com.ims.service.entity.Permission; +import com.ims.service.entity.RolePermission; +import com.ims.service.entity.User; +import com.ims.service.entity.UserRole; +import com.ims.service.repository.PermissionRepository; +import com.ims.service.repository.RolePermissionRepository; +import com.ims.service.repository.UserRepository; +import com.ims.service.repository.UserRoleRepository; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class AgentToolPermissionService { + + private final UserRepository userRepository; + private final UserRoleRepository userRoleRepository; + private final RolePermissionRepository rolePermissionRepository; + private final PermissionRepository permissionRepository; + + public AgentToolPermissionService(UserRepository userRepository, + UserRoleRepository userRoleRepository, + RolePermissionRepository rolePermissionRepository, + PermissionRepository permissionRepository) { + this.userRepository = userRepository; + this.userRoleRepository = userRoleRepository; + this.rolePermissionRepository = rolePermissionRepository; + this.permissionRepository = permissionRepository; + } + + public boolean canUseTool(String username, String toolName) { + if (toolName == null || toolName.isBlank()) { + return false; + } + User user = userRepository.findByUsername(username) + .or(() -> userRepository.findByUserid(username)) + .orElse(null); + if (user == null) { + return false; + } + List roleIds = userRoleRepository.findByUserId(user.getId()) + .stream().map(UserRole::getRoleId).distinct().toList(); + if (roleIds.isEmpty()) { + return false; + } + List permissionIds = rolePermissionRepository.findByRoleIdIn(roleIds) + .stream().map(RolePermission::getPermissionId).distinct().toList(); + String code = "TOOL_" + toolName.toUpperCase().replace("-", "_"); + return permissionRepository.findAllById(permissionIds).stream() + .map(Permission::getCode) + .anyMatch(code::equals); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/agent/MemoryService.java b/backend/ims-service/src/main/java/com/ims/service/agent/MemoryService.java new file mode 100644 index 0000000..d8305e2 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/agent/MemoryService.java @@ -0,0 +1,108 @@ +package com.ims.service.agent; + +import com.ims.common.exception.BusinessException; +import com.ims.service.entity.AgentMemory; +import com.ims.service.knowledge.DeepSeekEmbeddingService; +import com.ims.service.knowledge.SearchService; +import com.ims.service.repository.AgentMemoryRepository; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.Query; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.List; + +@Service +public class MemoryService { + + private static final Logger log = LoggerFactory.getLogger(MemoryService.class); + + private final AgentMemoryRepository memoryRepository; + private final DeepSeekEmbeddingService deepSeekEmbeddingService; + + @PersistenceContext + private EntityManager entityManager; + + public MemoryService(AgentMemoryRepository memoryRepository, + DeepSeekEmbeddingService deepSeekEmbeddingService) { + this.memoryRepository = memoryRepository; + this.deepSeekEmbeddingService = deepSeekEmbeddingService; + } + + public Page list(int page, int pageSize) { + Pageable pageable = PageRequest.of(Math.max(page - 1, 0), pageSize); + return memoryRepository.findAll(pageable); + } + + public List searchSimilar(String text, int topK) { + String vector = embed(text); + if (vector == null) { + return Collections.emptyList(); + } + try { + return memoryRepository.findSimilar(vector, topK); + } catch (Exception e) { + log.warn("记忆向量检索失败: {}", e.getMessage()); + return Collections.emptyList(); + } + } + + public AgentMemory add(String issueSummary, String solutionSteps) { + String vector = embed(issueSummary); + Query q; + if (vector == null) { + q = entityManager.createNativeQuery( + "INSERT INTO agent_memories (issue_summary, solution_steps, effectiveness_score, created_at, updated_at) " + + "VALUES (:summary, CAST(:steps AS jsonb), :score, NOW(), NOW()) RETURNING id") + .setParameter("summary", issueSummary) + .setParameter("steps", solutionSteps) + .setParameter("score", BigDecimal.ZERO); + } else { + q = entityManager.createNativeQuery( + "INSERT INTO agent_memories (issue_summary, solution_steps, effectiveness_score, embedding, created_at, updated_at) " + + "VALUES (:summary, CAST(:steps AS jsonb), :score, CAST(:embedding AS vector), NOW(), NOW()) RETURNING id") + .setParameter("summary", issueSummary) + .setParameter("steps", solutionSteps) + .setParameter("score", BigDecimal.ZERO) + .setParameter("embedding", vector); + } + Number id = (Number) q.getSingleResult(); + return memoryRepository.findById(id.longValue()) + .orElseThrow(() -> new BusinessException("记忆保存失败: " + id)); + } + + public void delete(Long id) { + if (!memoryRepository.existsById(id)) { + throw new BusinessException("记忆不存在: " + id); + } + memoryRepository.deleteById(id); + } + + public AgentMemory update(Long id, String issueSummary, String solutionSteps) { + AgentMemory memory = memoryRepository.findById(id) + .orElseThrow(() -> new BusinessException("记忆不存在: " + id)); + memory.setIssueSummary(issueSummary); + memory.setSolutionSteps(solutionSteps); + return memoryRepository.save(memory); + } + + private String embed(String text) { + try { + List vector = deepSeekEmbeddingService.embed(text); + if (vector == null || vector.isEmpty()) { + return null; + } + return SearchService.vectorToPgvectorString(vector); + } catch (Exception e) { + log.warn("Agent记忆嵌入失败(需配置DeepSeek Key并保证1536维): {}", e.getMessage()); + return null; + } + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/agent/OllamaChatService.java b/backend/ims-service/src/main/java/com/ims/service/agent/OllamaChatService.java new file mode 100644 index 0000000..51ece53 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/agent/OllamaChatService.java @@ -0,0 +1,73 @@ +package com.ims.service.agent; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +@Service +public class OllamaChatService { + + private static final Logger log = LoggerFactory.getLogger(OllamaChatService.class); + + private final ObjectMapper objectMapper; + + @Value("${ollama.base-url:http://localhost:11434}") + private String baseUrl; + + @Value("${ollama.chat.model:llama3.1:8b}") + private String model; + + public OllamaChatService(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public String getModel() { + return model; + } + + public String chat(String system, String user) { + String requestBody; + try { + requestBody = objectMapper.writeValueAsString(Map.of( + "model", model, + "stream", false, + "messages", List.of( + Map.of("role", "system", "content", system), + Map.of("role", "user", "content", user)))); + } catch (Exception e) { + throw new RuntimeException("构建 Ollama 请求失败: " + e.getMessage(), e); + } + + try { + HttpURLConnection conn = (HttpURLConnection) URI.create(baseUrl + "/api/chat").toURL().openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setDoOutput(true); + conn.setConnectTimeout(30000); + conn.setReadTimeout(600000); + try (OutputStream os = conn.getOutputStream()) { + os.write(requestBody.getBytes(StandardCharsets.UTF_8)); + } + String json = new String(conn.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + JsonNode root = objectMapper.readTree(json); + JsonNode content = root.path("message").path("content"); + if (content.isMissingNode()) { + throw new RuntimeException("Ollama 响应缺少 message.content: " + json); + } + return content.asText(); + } catch (Exception e) { + log.error("Ollama chat failed", e); + throw new RuntimeException("Ollama 调用失败: " + e.getMessage(), e); + } + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/agent/tool/AgentFunctionCallbackAdapter.java b/backend/ims-service/src/main/java/com/ims/service/agent/tool/AgentFunctionCallbackAdapter.java new file mode 100644 index 0000000..47053c5 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/agent/tool/AgentFunctionCallbackAdapter.java @@ -0,0 +1,90 @@ +package com.ims.service.agent.tool; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.ai.model.function.FunctionCallback; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class AgentFunctionCallbackAdapter implements FunctionCallback { + + private final Tool tool; + private final ObjectMapper objectMapper; + + public AgentFunctionCallbackAdapter(Tool tool, ObjectMapper objectMapper) { + this.tool = tool; + this.objectMapper = objectMapper; + } + + @Override + public String getName() { + return tool.name(); + } + + @Override + public String getDescription() { + return tool.description(); + } + + @Override + public String getInputTypeSchema() { + return schemaFor(tool.name()); + } + + @Override + public String call(String functionArguments) { + Map parameters = parseArguments(functionArguments); + Object output = tool.execute(parameters); + return write(output); + } + + private Map parseArguments(String arguments) { + if (arguments == null || arguments.isBlank()) { + return new LinkedHashMap<>(); + } + try { + return objectMapper.readValue(arguments, new TypeReference>() {}); + } catch (Exception e) { + return new LinkedHashMap<>(); + } + } + + private String write(Object value) { + try { + return objectMapper.writeValueAsString(value); + } catch (Exception e) { + return String.valueOf(value); + } + } + + private String schemaFor(String name) { + return switch (name) { + case "create_issue" -> """ + {"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"phase":{"type":"string"},"priority":{"type":"string"},"impact_level":{"type":"string"},"category":{"type":"string"},"assignee_name":{"type":"string"},"department_id":{"type":"number"}},"required":["title"]} + """; + case "update_issue" -> """ + {"type":"object","properties":{"issue_id":{"type":"number"},"field":{"type":"string"},"value":{"type":"string"}},"required":["issue_id","field","value"]} + """; + case "query_issue" -> """ + {"type":"object","properties":{"issue_id":{"type":"number"}},"required":["issue_id"]} + """; + case "search_knowledge" -> """ + {"type":"object","properties":{"query":{"type":"string"},"top_k":{"type":"number"}},"required":["query"]} + """; + case "notify_overdue" -> """ + {"type":"object","properties":{"content":{"type":"string"}}} + """; + case "assign_pending" -> """ + {"type":"object","properties":{"assignee_name":{"type":"string"}},"required":["assignee_name"]} + """; + case "weekly_report" -> """ + {"type":"object","properties":{}} + """; + case "ai_analysis" -> """ + {"type":"object","properties":{"issue_id":{"type":"number"}},"required":["issue_id"]} + """; + default -> "{\"type\":\"object\",\"properties\":{}}"; + }; + } +} \ No newline at end of file diff --git a/backend/ims-service/src/main/java/com/ims/service/agent/tool/AiAnalysisTool.java b/backend/ims-service/src/main/java/com/ims/service/agent/tool/AiAnalysisTool.java new file mode 100644 index 0000000..331a0a1 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/agent/tool/AiAnalysisTool.java @@ -0,0 +1,282 @@ +package com.ims.service.agent.tool; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.ims.common.exception.BusinessException; +import com.ims.service.ai.KeywordExtractor; +import com.ims.service.ai.ModelRoutingService; +import com.ims.service.ai.PromptFormatter; +import com.ims.service.ai.PromptTemplateEngine; +import com.ims.service.entity.AiAnalysis; +import com.ims.service.entity.Issue; +import com.ims.service.entity.User; +import com.ims.service.repository.AiAnalysisRepository; +import com.ims.service.repository.IssueRepository; +import com.ims.service.repository.UserRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Component +public class AiAnalysisTool implements Tool { + + private static final Logger log = LoggerFactory.getLogger(AiAnalysisTool.class); + private static final String SYS_ROLE_TEMPLATE = "SYS_ROLE_001"; + private static final String ANALYSIS_TEMPLATE = "ANALYSIS_ROOT_CAUSE_001"; + + private final IssueRepository issueRepository; + private final AiAnalysisRepository analysisRepository; + private final UserRepository userRepository; + private final PromptTemplateEngine promptEngine; + private final ModelRoutingService modelRoutingService; + private final PromptFormatter promptFormatter; + private final KeywordExtractor keywordExtractor; + private final KnowledgeSearchTool knowledgeSearchTool; + + public AiAnalysisTool(IssueRepository issueRepository, + AiAnalysisRepository analysisRepository, + UserRepository userRepository, + PromptTemplateEngine promptEngine, + ModelRoutingService modelRoutingService, + PromptFormatter promptFormatter, + KeywordExtractor keywordExtractor, + KnowledgeSearchTool knowledgeSearchTool) { + this.issueRepository = issueRepository; + this.analysisRepository = analysisRepository; + this.userRepository = userRepository; + this.promptEngine = promptEngine; + this.modelRoutingService = modelRoutingService; + this.promptFormatter = promptFormatter; + this.keywordExtractor = keywordExtractor; + this.knowledgeSearchTool = knowledgeSearchTool; + } + + @Override + public String name() { + return "ai_analysis"; + } + + @Override + public String description() { + return "对指定指摘进行AI根因分析(只读),调用LLM分析问题分类、根因和整改建议,参数:issue_id(数字,必填)"; + } + + @Override + public boolean isWrite() { + return false; + } + + @Override + public Object execute(Map parameters) { + Long issueId = toLong(parameters.get("issue_id")); + if (issueId == null) { + throw new BusinessException("ai_analysis 缺少参数 issue_id"); + } + + Issue issue = issueRepository.findById(issueId) + .orElseThrow(() -> new BusinessException("指摘不存在: " + issueId)); + + AiAnalysis analysis = new AiAnalysis(); + analysis.setIssue(issue); + analysis.setStatus("processing"); + analysis.setPromptTemplateId(ANALYSIS_TEMPLATE); + analysis.setStartedAt(LocalDateTime.now()); + analysis.setExtractedKeywords(keywordExtractor.extract( + issue.getTitle(), issue.getDescription(), issue.getCategory(), issue.getPhase())); + analysisRepository.save(analysis); + + try { + Map systemVars = new LinkedHashMap<>(); + systemVars.put("userGoal", "进行根因分析"); + systemVars.put("issueContext", buildIssueContext(issue)); + systemVars.put("availableTools", List.of()); + String systemPrompt = promptEngine.render(SYS_ROLE_TEMPLATE, systemVars); + + Map analysisVars = buildAnalysisVariables(issue, analysis.getExtractedKeywords()); + String userPrompt = promptEngine.render(ANALYSIS_TEMPLATE, analysisVars); + + ModelRoutingService.CallResult result = modelRoutingService.call(systemPrompt, userPrompt); + + ObjectNode json = promptFormatter.extractJsonObject(result.text()); + if (json == null) { + log.warn("AI分析输出解析失败,尝试修复 issueId={}", issueId); + json = repairJsonOutput(result.text(), systemPrompt); + } + if (json == null) { + throw new BusinessException("LLM输出无法解析为JSON"); + } + + String category = text(json, "category"); + String rootCause = text(json, "rootCause"); + String suggestion = text(json, "suggestion"); + if ((category == null || category.isBlank()) + || (rootCause == null || rootCause.isBlank()) + || (suggestion == null || suggestion.isBlank())) { + throw new BusinessException("LLM输出缺少分析内容(category/rootCause/suggestion)"); + } + + analysis.setCategory(category); + analysis.setKeywords(joinArray(json, "keywords")); + analysis.setRootCause(rootCause); + analysis.setSuggestion(suggestion); + analysis.setStatus("completed"); + analysis.setPromptVersion(1); + analysis.setModelProvider(result.provider()); + analysis.setModelName(result.provider()); + analysis.setCompletedAt(LocalDateTime.now()); + analysisRepository.save(analysis); + + issue.setAiAnalysisId(analysis.getId()); + issueRepository.save(issue); + + Map map = new LinkedHashMap<>(); + map.put("analysisId", analysis.getId()); + map.put("category", category); + map.put("keywords", analysis.getKeywords()); + map.put("rootCause", rootCause); + map.put("suggestion", suggestion); + map.put("status", "completed"); + return map; + } catch (Exception e) { + log.error("AI分析工具执行失败 issueId={}", issueId, e); + analysis.setStatus("failed"); + analysis.setErrorMessage(e.getMessage()); + analysis.setCompletedAt(LocalDateTime.now()); + analysisRepository.save(analysis); + throw new BusinessException("AI分析失败: " + e.getMessage()); + } + } + + private String buildIssueContext(Issue issue) { + StringBuilder sb = new StringBuilder(); + sb.append("指摘编号: ").append(issue.getIssueNo()).append("\n"); + sb.append("标题: ").append(issue.getTitle()).append("\n"); + if (issue.getDescription() != null) { + sb.append("描述: ").append(issue.getDescription()).append("\n"); + } + if (issue.getPhase() != null) { + sb.append("工程阶段: ").append(issue.getPhase()).append("\n"); + } + if (issue.getCategory() != null) { + sb.append("问题分类: ").append(issue.getCategory()).append("\n"); + } + if (issue.getImpactLevel() != null) { + sb.append("影响度: ").append(issue.getImpactLevel()).append("\n"); + } + return sb.toString(); + } + + private Map buildAnalysisVariables(Issue issue, String extractedKeywords) { + Map vars = new HashMap<>(); + vars.put("issueNo", issue.getIssueNo()); + vars.put("issueTitle", issue.getTitle()); + vars.put("issueDescription", issue.getDescription() == null ? "" : issue.getDescription()); + vars.put("issuePhase", issue.getPhase() == null ? "" : issue.getPhase()); + vars.put("issueCategory", issue.getCategory() == null ? "" : issue.getCategory()); + vars.put("issueImpactLevel", issue.getImpactLevel() == null ? "" : issue.getImpactLevel()); + vars.put("issueKeywords", extractedKeywords); + vars.put("similarCases", buildKnowledgeContext(issue)); + return vars; + } + + private String buildKnowledgeContext(Issue issue) { + try { + StringBuilder query = new StringBuilder(issue.getTitle() == null ? "" : issue.getTitle()); + if (issue.getDescription() != null && !issue.getDescription().isBlank()) { + if (query.length() > 0) { + query.append(" "); + } + query.append(issue.getDescription()); + } + if (query.length() == 0) { + return ""; + } + List results = (List) knowledgeSearchTool.execute( + Map.of("query", query.toString(), "top_k", 5)); + if (results == null || results.isEmpty()) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < results.size(); i++) { + Object r = results.get(i); + if (r instanceof Map) { + Map m = (Map) r; + sb.append(i + 1).append(". ") + .append(m.get("doc_name")).append(": ") + .append(m.get("content")).append("\n"); + } + } + return sb.toString(); + } catch (Exception e) { + log.warn("知识库检索失败,降级为无相似案例: {}", e.getMessage()); + return ""; + } + } + + private ObjectNode repairJsonOutput(String broken, String systemPrompt) { + try { + String repairPrompt = "以下内容本应是严格的 JSON,但无法解析。" + + "请将修正后的完整 JSON 重新输出,禁止 Markdown 围栏、禁止解释性文字," + + "直接输出 JSON:\n\n" + broken; + ModelRoutingService.CallResult retry = modelRoutingService.call(systemPrompt, repairPrompt); + return promptFormatter.extractJsonObject(retry.text()); + } catch (Exception e) { + log.warn("JSON 修复重试失败: {}", e.getMessage()); + return null; + } + } + + private String text(ObjectNode json, String field) { + JsonNode node = get(json, field); + if (node == null || node.isNull()) { + return null; + } + return node.isContainerNode() ? node.toString() : node.asText(); + } + + private String joinArray(ObjectNode json, String field) { + JsonNode node = get(json, field); + if (node == null || !node.isArray()) { + return ""; + } + List items = new ArrayList<>(); + for (JsonNode n : (com.fasterxml.jackson.databind.node.ArrayNode) node) { + items.add(n.asText()); + } + return String.join(",", items); + } + + private JsonNode get(ObjectNode json, String field) { + JsonNode node = json.get(field); + if (node != null) { + return node; + } + for (Map.Entry entry : json.properties()) { + if (entry.getKey().equalsIgnoreCase(field)) { + return entry.getValue(); + } + } + return null; + } + + static Long toLong(Object value) { + if (value == null) { + return null; + } + if (value instanceof Number) { + return ((Number) value).longValue(); + } + try { + return Long.parseLong(String.valueOf(value).trim()); + } catch (NumberFormatException e) { + return null; + } + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/agent/tool/AssignPendingTool.java b/backend/ims-service/src/main/java/com/ims/service/agent/tool/AssignPendingTool.java new file mode 100644 index 0000000..ce15404 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/agent/tool/AssignPendingTool.java @@ -0,0 +1,76 @@ +package com.ims.service.agent.tool; + +import com.ims.common.exception.BusinessException; +import com.ims.service.entity.Issue; +import com.ims.service.entity.User; +import com.ims.service.issue.IssueService; +import com.ims.service.repository.IssueRepository; +import com.ims.service.repository.UserRepository; +import org.springframework.stereotype.Component; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Component +public class AssignPendingTool implements Tool { + + private final IssueRepository issueRepository; + private final IssueService issueService; + private final UserRepository userRepository; + + public AssignPendingTool(IssueRepository issueRepository, + IssueService issueService, + UserRepository userRepository) { + this.issueRepository = issueRepository; + this.issueService = issueService; + this.userRepository = userRepository; + } + + @Override + public String name() { + return "assign_pending"; + } + + @Override + public String description() { + return "为待处理指摘分配对应者(写操作,需审批),自动查找所有待处理(open)且未分配对应者的指摘并统一分配,参数:assignee_name(必填,对应者用户名)"; + } + + @Override + public boolean isWrite() { + return true; + } + + @Override + public Object execute(Map parameters) { + String assigneeName = parameters.get("assignee_name") == null ? "" : String.valueOf(parameters.get("assignee_name")); + if (assigneeName.isBlank()) { + throw new BusinessException("assign_pending 缺少参数 assignee_name"); + } + User assignee = userRepository.findByUsername(assigneeName) + .or(() -> userRepository.findByUserid(assigneeName)) + .orElse(null); + if (assignee == null) { + throw new BusinessException("对应者不存在: " + assigneeName); + } + List pending = issueRepository.findByIsDeletedFalseAndStatusAndAssigneeIsNull("open"); + if (pending.isEmpty()) { + Map map = new LinkedHashMap<>(); + map.put("assigned", 0); + map.put("message", "当前没有待处理且未分配对应者的指摘。"); + return map; + } + List ids = pending.stream().map(Issue::getId).toList(); + User operator = userRepository.findByUserid("admin") + .or(() -> userRepository.findByUsername("admin")).orElse(null); + int count = issueService.batchAssign(ids, assignee.getId(), operator); + + Map map = new LinkedHashMap<>(); + map.put("assigned", count); + map.put("targetCount", ids.size()); + map.put("assignee", assigneeName); + map.put("message", "已将 " + count + " 条待处理指摘分配给对应者 " + assigneeName); + return map; + } +} \ No newline at end of file diff --git a/backend/ims-service/src/main/java/com/ims/service/agent/tool/IssueCreateTool.java b/backend/ims-service/src/main/java/com/ims/service/agent/tool/IssueCreateTool.java new file mode 100644 index 0000000..7f4af10 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/agent/tool/IssueCreateTool.java @@ -0,0 +1,95 @@ +package com.ims.service.agent.tool; + +import com.ims.api.dto.issue.IssueCreateRequest; +import com.ims.common.exception.BusinessException; +import com.ims.service.entity.User; +import com.ims.service.issue.IssueService; +import com.ims.service.repository.UserRepository; +import org.springframework.stereotype.Component; + +import java.util.LinkedHashMap; +import java.util.Map; + +@Component +public class IssueCreateTool implements Tool { + + private final IssueService issueService; + private final UserRepository userRepository; + + public IssueCreateTool(IssueService issueService, UserRepository userRepository) { + this.issueService = issueService; + this.userRepository = userRepository; + } + + @Override + public String name() { + return "create_issue"; + } + + @Override + public String description() { + return "新建指摘(写操作,需审批),参数:title(必填), description, phase, priority, impact_level, category, assignee_name(对应者姓名), department_id(数字)"; + } + + @Override + public boolean isWrite() { + return true; + } + + @Override + public Object execute(Map parameters) { + String title = parameters.get("title") == null ? "" : String.valueOf(parameters.get("title")); + if (title.isBlank()) { + throw new BusinessException("create_issue 缺少参数 title"); + } + + IssueCreateRequest req = new IssueCreateRequest(); + req.setTitle(title); + req.setDescription(asString(parameters.get("description"))); + req.setPhase(asString(parameters.get("phase"))); + req.setPriority(asString(parameters.get("priority"))); + req.setCategory(asString(parameters.get("category"))); + req.setImpactLevel(asString(parameters.get("impact_level"))); + + String assigneeName = asString(parameters.get("assignee_name")); + if (!assigneeName.isBlank()) { + userRepository.findByUsername(assigneeName) + .or(() -> userRepository.findByUserid(assigneeName)) + .ifPresent(u -> req.setAssigneeId(u.getId())); + } + Long departmentId = toLong(parameters.get("department_id")); + if (departmentId != null) { + req.setDepartmentId(departmentId); + } + + User creator = userRepository.findByUserid("admin") + .or(() -> userRepository.findByUsername("admin")).orElse(null); + var resp = issueService.create(req, creator); + + Map map = new LinkedHashMap<>(); + map.put("id", resp.getId()); + map.put("issue_no", resp.getIssueNo()); + map.put("title", resp.getTitle()); + map.put("status", resp.getStatus()); + map.put("created", true); + return map; + } + + private String asString(Object value) { + return value == null ? "" : String.valueOf(value); + } + + private Long toLong(Object value) { + if (value instanceof Number) { + return ((Number) value).longValue(); + } + if (value == null) { + return null; + } + try { + return Long.parseLong(String.valueOf(value)); + } catch (NumberFormatException e) { + return null; + } + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/agent/tool/IssueQueryTool.java b/backend/ims-service/src/main/java/com/ims/service/agent/tool/IssueQueryTool.java new file mode 100644 index 0000000..f1d986b --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/agent/tool/IssueQueryTool.java @@ -0,0 +1,79 @@ +package com.ims.service.agent.tool; + +import com.ims.common.exception.BusinessException; +import com.ims.service.entity.Issue; +import com.ims.service.repository.IssueRepository; +import org.springframework.stereotype.Component; + +import java.util.LinkedHashMap; +import java.util.Map; + +@Component +public class IssueQueryTool implements Tool { + + private final IssueRepository issueRepository; + + public IssueQueryTool(IssueRepository issueRepository) { + this.issueRepository = issueRepository; + } + + @Override + public String name() { + return "query_issue"; + } + + @Override + public String description() { + return "查询指摘详情,参数:issue_id(数字)"; + } + + @Override + public boolean isWrite() { + return false; + } + + @Override + public Object execute(Map parameters) { + Long issueId = toLong(parameters.get("issue_id")); + if (issueId == null) { + throw new BusinessException("query_issue 缺少参数 issue_id"); + } + Issue issue = issueRepository.findById(issueId) + .orElseThrow(() -> new BusinessException("指摘不存在: " + issueId)); + return toMap(issue); + } + + private Map toMap(Issue issue) { + Map map = new LinkedHashMap<>(); + map.put("id", issue.getId()); + map.put("issue_no", issue.getIssueNo()); + map.put("title", issue.getTitle()); + map.put("description", issue.getDescription()); + map.put("status", issue.getStatus()); + map.put("priority", issue.getPriority()); + map.put("phase", issue.getPhase()); + map.put("category", issue.getCategory()); + map.put("impact_level", issue.getImpactLevel()); + map.put("sub_project", issue.getSubProject()); + map.put("pgm_no", issue.getPgmNo()); + map.put("ng_reason", issue.getNgReason()); + map.put("response_content", issue.getResponseContent()); + map.put("agent_status", issue.getAgentStatus()); + map.put("created_at", issue.getCreatedAt() == null ? null : issue.getCreatedAt().toString()); + return map; + } + + static Long toLong(Object value) { + if (value == null) { + return null; + } + if (value instanceof Number) { + return ((Number) value).longValue(); + } + try { + return Long.parseLong(String.valueOf(value).trim()); + } catch (NumberFormatException e) { + return null; + } + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/agent/tool/IssueUpdateTool.java b/backend/ims-service/src/main/java/com/ims/service/agent/tool/IssueUpdateTool.java new file mode 100644 index 0000000..b224d7a --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/agent/tool/IssueUpdateTool.java @@ -0,0 +1,91 @@ +package com.ims.service.agent.tool; + +import com.ims.common.exception.BusinessException; +import com.ims.service.entity.Issue; +import com.ims.service.repository.IssueRepository; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.util.LinkedHashMap; +import java.util.Map; + +@Component +public class IssueUpdateTool implements Tool { + + private final IssueRepository issueRepository; + + public IssueUpdateTool(IssueRepository issueRepository) { + this.issueRepository = issueRepository; + } + + @Override + public String name() { + return "update_issue"; + } + + @Override + public String description() { + return "更新指摘字段(写操作,需审批),参数:issue_id(数字), field(字段名), value(值);支持字段:status/priority/phase/title/description/response_content/ng_reason/response_workload/review_workload"; + } + + @Override + public boolean isWrite() { + return true; + } + + @Override + public Object execute(Map parameters) { + Long issueId = IssueQueryTool.toLong(parameters.get("issue_id")); + if (issueId == null) { + throw new BusinessException("update_issue 缺少参数 issue_id"); + } + String field = parameters.get("field") == null ? "" : String.valueOf(parameters.get("field")); + if (field.isBlank()) { + throw new BusinessException("update_issue 缺少参数 field"); + } + Object value = parameters.get("value"); + if (value == null) { + throw new BusinessException("update_issue 缺少参数 value"); + } + + Issue issue = issueRepository.findById(issueId) + .orElseThrow(() -> new BusinessException("指摘不存在: " + issueId)); + applyField(issue, field, value); + issueRepository.save(issue); + + Map map = new LinkedHashMap<>(); + map.put("id", issue.getId()); + map.put("issue_no", issue.getIssueNo()); + map.put("status", issue.getStatus()); + map.put("priority", issue.getPriority()); + map.put("phase", issue.getPhase()); + map.put("updated", true); + return map; + } + + private void applyField(Issue issue, String field, Object value) { + switch (field) { + case "status" -> issue.setStatus(String.valueOf(value)); + case "priority" -> issue.setPriority(String.valueOf(value)); + case "phase" -> issue.setPhase(String.valueOf(value)); + case "title" -> issue.setTitle(String.valueOf(value)); + case "description" -> issue.setDescription(String.valueOf(value)); + case "response_content" -> issue.setResponseContent(String.valueOf(value)); + case "ng_reason" -> issue.setNgReason(String.valueOf(value)); + case "response_workload" -> issue.setResponseWorkload(toDecimal(value)); + case "review_workload" -> issue.setReviewWorkload(toDecimal(value)); + default -> throw new BusinessException("不支持的字段: " + field); + } + } + + private BigDecimal toDecimal(Object value) { + if (value instanceof Number) { + return new BigDecimal(value.toString()); + } + try { + return new BigDecimal(String.valueOf(value)); + } catch (NumberFormatException e) { + throw new BusinessException("数值字段格式错误: " + value); + } + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/agent/tool/KnowledgeSearchTool.java b/backend/ims-service/src/main/java/com/ims/service/agent/tool/KnowledgeSearchTool.java new file mode 100644 index 0000000..411007e --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/agent/tool/KnowledgeSearchTool.java @@ -0,0 +1,54 @@ +package com.ims.service.agent.tool; + +import com.ims.service.knowledge.SearchService; +import org.springframework.stereotype.Component; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Component +public class KnowledgeSearchTool implements Tool { + + private final SearchService searchService; + + public KnowledgeSearchTool(SearchService searchService) { + this.searchService = searchService; + } + + @Override + public String name() { + return "search_knowledge"; + } + + @Override + public String description() { + return "检索本地知识库,参数:query(字符串), top_k(可选数字,默认5)"; + } + + @Override + public boolean isWrite() { + return false; + } + + @Override + public Object execute(Map parameters) { + String query = parameters.get("query") == null ? "" : String.valueOf(parameters.get("query")); + if (query.isBlank()) { + throw new com.ims.common.exception.BusinessException("search_knowledge 缺少参数 query"); + } + int topK = 5; + if (parameters.get("top_k") != null) { + topK = Math.max(1, IssueQueryTool.toLong(parameters.get("top_k")).intValue()); + } + List results = searchService.search(query, topK); + return results.stream().map(r -> { + Map m = new LinkedHashMap<>(); + m.put("chunk_id", r.getChunkId()); + m.put("content", r.getContent()); + m.put("doc_name", r.getDocName()); + m.put("score", r.getScore()); + return m; + }).toList(); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/agent/tool/NotifyOverdueTool.java b/backend/ims-service/src/main/java/com/ims/service/agent/tool/NotifyOverdueTool.java new file mode 100644 index 0000000..f1b7d98 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/agent/tool/NotifyOverdueTool.java @@ -0,0 +1,67 @@ +package com.ims.service.agent.tool; + +import com.ims.common.exception.BusinessException; +import com.ims.service.entity.Issue; +import com.ims.service.entity.User; +import com.ims.service.issue.IssueService; +import com.ims.service.repository.IssueRepository; +import com.ims.service.repository.UserRepository; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Component +public class NotifyOverdueTool implements Tool { + + private final IssueRepository issueRepository; + private final IssueService issueService; + private final UserRepository userRepository; + + public NotifyOverdueTool(IssueRepository issueRepository, + IssueService issueService, + UserRepository userRepository) { + this.issueRepository = issueRepository; + this.issueService = issueService; + this.userRepository = userRepository; + } + + @Override + public String name() { + return "notify_overdue"; + } + + @Override + public String description() { + return "催办逾期指摘(写操作,需审批),自动查找所有已逾期且未关闭的指摘并向对应者发送催办通知,参数:content(可选,催办内容)"; + } + + @Override + public boolean isWrite() { + return true; + } + + @Override + public Object execute(Map parameters) { + String content = parameters.get("content") == null ? "" : String.valueOf(parameters.get("content")); + List overdue = issueRepository.findByIsDeletedFalseAndStatusNotAndDeadlineBefore("closed", LocalDateTime.now()); + if (overdue.isEmpty()) { + Map map = new LinkedHashMap<>(); + map.put("notified", 0); + map.put("message", "当前没有逾期未关闭的指摘,无需催办。"); + return map; + } + List ids = overdue.stream().map(Issue::getId).toList(); + User operator = userRepository.findByUserid("admin") + .or(() -> userRepository.findByUsername("admin")).orElse(null); + int count = issueService.batchNotify(ids, content, operator); + + Map map = new LinkedHashMap<>(); + map.put("notified", count); + map.put("targetCount", ids.size()); + map.put("message", "已对 " + count + " 条逾期指摘执行催办(已向对应者发送通知)"); + return map; + } +} \ No newline at end of file diff --git a/backend/ims-service/src/main/java/com/ims/service/agent/tool/Tool.java b/backend/ims-service/src/main/java/com/ims/service/agent/tool/Tool.java new file mode 100644 index 0000000..4db4a94 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/agent/tool/Tool.java @@ -0,0 +1,14 @@ +package com.ims.service.agent.tool; + +import java.util.Map; + +public interface Tool { + + String name(); + + String description(); + + boolean isWrite(); + + Object execute(Map parameters); +} diff --git a/backend/ims-service/src/main/java/com/ims/service/agent/tool/ToolRegistry.java b/backend/ims-service/src/main/java/com/ims/service/agent/tool/ToolRegistry.java new file mode 100644 index 0000000..715cce6 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/agent/tool/ToolRegistry.java @@ -0,0 +1,51 @@ +package com.ims.service.agent.tool; + +import com.ims.common.exception.BusinessException; +import org.springframework.stereotype.Component; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Component +public class ToolRegistry { + + private final Map tools = new LinkedHashMap<>(); + + public ToolRegistry(List toolList) { + for (Tool tool : toolList) { + tools.put(tool.name(), tool); + } + } + + public Map descriptions() { + Map result = new LinkedHashMap<>(); + tools.forEach((name, tool) -> result.put(name, tool.description())); + return result; + } + + public List names() { + return List.copyOf(tools.keySet()); + } + + public List tools() { + return List.copyOf(tools.values()); + } + + public boolean contains(String name) { + return tools.containsKey(name); + } + + public Tool get(String name) { + Tool tool = tools.get(name); + if (tool == null) { + throw new BusinessException("未知工具: " + name); + } + return tool; + } + + public boolean isWrite(String name) { + Tool tool = tools.get(name); + return tool != null && tool.isWrite(); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/agent/tool/WeeklyReportTool.java b/backend/ims-service/src/main/java/com/ims/service/agent/tool/WeeklyReportTool.java new file mode 100644 index 0000000..4868101 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/agent/tool/WeeklyReportTool.java @@ -0,0 +1,118 @@ +package com.ims.service.agent.tool; + +import com.ims.service.entity.Issue; +import com.ims.service.entity.IssueLog; +import com.ims.service.repository.IssueLogRepository; +import com.ims.service.repository.IssueRepository; +import jakarta.persistence.EntityManager; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Component; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Component +public class WeeklyReportTool implements Tool { + + private final EntityManager entityManager; + private final IssueRepository issueRepository; + private final IssueLogRepository issueLogRepository; + + public WeeklyReportTool(EntityManager entityManager, + IssueRepository issueRepository, + IssueLogRepository issueLogRepository) { + this.entityManager = entityManager; + this.issueRepository = issueRepository; + this.issueLogRepository = issueLogRepository; + } + + @Override + public String name() { + return "weekly_report"; + } + + @Override + public String description() { + return "生成本周指摘处理统计报告(只读),统计本周新增/关闭/各状态分布/逾期/高优先级数量,参数:无"; + } + + @Override + public boolean isWrite() { + return false; + } + + @Override + public Object execute(Map parameters) { + LocalDate today = LocalDate.now(); + LocalDate monday = today.with(DayOfWeek.MONDAY); + LocalDateTime weekStart = monday.atStartOfDay(); + LocalDateTime now = LocalDateTime.now(); + + long weekNew = countBySql("select count(*) from issues where is_deleted = false and created_at >= ?1", weekStart); + long weekClosed = countBySql("select count(*) from issues where is_deleted = false and closed_at is not null and closed_at >= ?1", weekStart); + long overdue = countBySql("select count(*) from issues where is_deleted = false and status <> 'closed' and deadline < ?1", now); + long highRisk = countBySql("select count(*) from issues where is_deleted = false and status in ('open','in_progress') and priority in ('urgent','high')"); + + Map statusCounts = new LinkedHashMap<>(); + List rows = entityManager.createNativeQuery( + "select status, count(*) from issues where is_deleted = false group by status").getResultList(); + for (Object[] row : rows) { + statusCounts.put((String) row[0], ((Number) row[1]).longValue()); + } + + List recent = issueRepository + .findAll(PageRequest.of(0, 5, Sort.by(Sort.Direction.DESC, "createdAt"))).getContent(); + List recentLogs = issueLogRepository + .findAll(PageRequest.of(0, 5, Sort.by(Sort.Direction.DESC, "createdAt"))).getContent(); + + StringBuilder sb = new StringBuilder(); + sb.append("【本周指摘处理报告】").append(monday.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))) + .append(" ~ ").append(today.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))).append("\n"); + sb.append("一、总量:本周新增 ").append(weekNew).append(" 条,本周关闭 ").append(weekClosed).append(" 条\n"); + sb.append("二、状态分布:"); + statusCounts.forEach((k, v) -> sb.append(k).append("=").append(v).append(" ")); + sb.append("\n"); + sb.append("三、风险提示:逾期未关闭 ").append(overdue).append(" 条,待处理/进行中 high 及以上优先级 ").append(highRisk).append(" 条\n"); + sb.append("四、最新新增:"); + if (recent.isEmpty()) { + sb.append("无\n"); + } else { + for (Issue i : recent) { + sb.append("\n - ").append(i.getIssueNo()).append(" ").append(i.getTitle()) + .append(" (").append(i.getStatus()).append(")"); + } + sb.append("\n"); + } + sb.append("五、最近动态:"); + if (recentLogs.isEmpty()) { + sb.append("无\n"); + } else { + for (IssueLog l : recentLogs) { + sb.append("\n - ").append(l.getAction()); + if (l.getIssue() != null) { + sb.append(" ").append(l.getIssue().getIssueNo()); + } + if (l.getUser() != null) { + sb.append(" by ").append(l.getUser().getUsername()); + } + } + sb.append("\n"); + } + sb.append("报告生成时间:").append(now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + return sb.toString(); + } + + private long countBySql(String sql, Object... params) { + jakarta.persistence.Query q = entityManager.createNativeQuery(sql); + for (int i = 0; i < params.length; i++) { + q.setParameter(i + 1, params[i]); + } + return ((Number) q.getSingleResult()).longValue(); + } +} \ No newline at end of file diff --git a/backend/ims-service/src/main/java/com/ims/service/ai/AiAnalysisService.java b/backend/ims-service/src/main/java/com/ims/service/ai/AiAnalysisService.java new file mode 100644 index 0000000..f1fdddb --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/ai/AiAnalysisService.java @@ -0,0 +1,403 @@ +package com.ims.service.ai; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.ims.api.dto.ai.AiAnalysisRequest; +import com.ims.api.dto.ai.AiAnalysisResponse; +import com.ims.common.exception.BusinessException; +import com.ims.service.agent.tool.KnowledgeSearchTool; +import com.ims.service.entity.AiAnalysis; +import com.ims.service.entity.AiFeedback; +import com.ims.service.entity.Issue; +import com.ims.service.entity.ToolExecution; +import com.ims.service.entity.User; +import com.ims.service.repository.AiAnalysisRepository; +import com.ims.service.repository.AiFeedbackRepository; +import com.ims.service.repository.IssueRepository; +import com.ims.service.repository.RoleRepository; +import com.ims.service.knowledge.AiConfigService; +import com.ims.service.repository.ToolExecutionRepository; +import com.ims.service.repository.UserRepository; +import com.ims.service.repository.UserRoleRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.PostConstruct; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Service +public class AiAnalysisService { + + private static final Logger log = LoggerFactory.getLogger(AiAnalysisService.class); + private static final String SYS_ROLE_TEMPLATE = "SYS_ROLE_001"; + private static final String ANALYSIS_TEMPLATE = "ANALYSIS_ROOT_CAUSE_001"; + private static final String ANALYSIS_TOOL_NAME = "ai_analysis"; + + private final AiAnalysisRepository analysisRepository; + private final AiFeedbackRepository feedbackRepository; + private final IssueRepository issueRepository; + private final UserRepository userRepository; + private final UserRoleRepository userRoleRepository; + private final RoleRepository roleRepository; + private final ToolExecutionRepository toolExecutionRepository; + private final PromptTemplateEngine promptEngine; + private final ModelRoutingService modelRoutingService; + private final PromptFormatter promptFormatter; + private final AiConfigService aiConfigService; + private final SystemContextBuilder systemContextBuilder; + private final KeywordExtractor keywordExtractor; + private final KnowledgeSearchTool knowledgeSearchTool; + + private final ExecutorService executor = Executors.newFixedThreadPool(1, r -> { + Thread t = new Thread(r, "ai-analysis-"); + t.setDaemon(true); + return t; + }); + + public AiAnalysisService(AiAnalysisRepository analysisRepository, + AiFeedbackRepository feedbackRepository, + IssueRepository issueRepository, + UserRepository userRepository, + UserRoleRepository userRoleRepository, + RoleRepository roleRepository, + ToolExecutionRepository toolExecutionRepository, + PromptTemplateEngine promptEngine, + ModelRoutingService modelRoutingService, + PromptFormatter promptFormatter, + AiConfigService aiConfigService, + SystemContextBuilder systemContextBuilder, + KeywordExtractor keywordExtractor, + KnowledgeSearchTool knowledgeSearchTool) { + this.analysisRepository = analysisRepository; + this.feedbackRepository = feedbackRepository; + this.issueRepository = issueRepository; + this.userRepository = userRepository; + this.userRoleRepository = userRoleRepository; + this.roleRepository = roleRepository; + this.toolExecutionRepository = toolExecutionRepository; + this.promptEngine = promptEngine; + this.modelRoutingService = modelRoutingService; + this.promptFormatter = promptFormatter; + this.aiConfigService = aiConfigService; + this.systemContextBuilder = systemContextBuilder; + this.keywordExtractor = keywordExtractor; + this.knowledgeSearchTool = knowledgeSearchTool; + } + + @PostConstruct + public void resetStaleAnalysis() { + try { + List stale = analysisRepository.findByStatusIn( + List.of("pending", "processing"), PageRequest.of(0, 1000)); + if (stale.isEmpty()) { + return; + } + for (AiAnalysis a : stale) { + a.setStatus("failed"); + a.setErrorMessage("服务重启,分析被中断"); + a.setCompletedAt(LocalDateTime.now()); + } + analysisRepository.saveAll(stale); + log.warn("启动时重置遗留分析记录 {} 条为 failed", stale.size()); + } catch (Exception e) { + log.error("重置遗留分析记录失败", e); + } + } + + public int batchGenerate(AiAnalysisRequest request, Long userId) { + List issueIds = request.getIssueIds(); + if (issueIds == null || issueIds.isEmpty()) { + throw new BusinessException("请选择需要分析的指摘"); + } + for (Long issueId : issueIds) { + executor.execute(() -> analyzeOne(issueId, userId)); + } + return issueIds.size(); + } + + @Transactional + public void analyzeOne(Long issueId, Long userId) { + Issue issue = issueRepository.findById(issueId) + .orElseThrow(() -> new BusinessException("指摘不存在: " + issueId)); + + AiAnalysis analysis = new AiAnalysis(); + analysis.setIssue(issue); + analysis.setStatus("processing"); + analysis.setPromptTemplateId(ANALYSIS_TEMPLATE); + analysis.setStartedAt(LocalDateTime.now()); + analysis.setExtractedKeywords(keywordExtractor.extract( + issue.getTitle(), issue.getDescription(), issue.getCategory(), issue.getPhase())); + analysisRepository.save(analysis); + + ToolExecution execution = ToolExecution.builder() + .toolName(ANALYSIS_TOOL_NAME) + .inputParams("{\"issueId\":" + issueId + "}") + .status("running") + .build(); + toolExecutionRepository.save(execution); + long start = System.currentTimeMillis(); + + try { + User user = userRepository.findWithDepartmentById(userId).orElse(null); + Map systemVars = systemContextBuilder.build(user, null); + String systemPrompt = promptEngine.render(SYS_ROLE_TEMPLATE, systemVars); + + Map analysisVars = buildAnalysisVariables(issue, analysis.getExtractedKeywords()); + String userPrompt = promptEngine.render(ANALYSIS_TEMPLATE, analysisVars); + + String provider = modelRoutingService.currentProvider(); + String modelName = null; + ModelRoutingService.CallResult result; + try { + result = modelRoutingService.call(systemPrompt, userPrompt); + } catch (Exception e) { + modelName = null; + throw e; + } + modelName = result.provider().equals("deepseek") + ? aiConfigModelName("deepseek") + : aiConfigModelName("ollama"); + + ObjectNode json = promptFormatter.extractJsonObject(result.text()); + if (json == null) { + log.warn("AI输出解析失败,尝试修复重试 issueId={}", issueId); + json = repairJsonOutput(result.text(), systemPrompt); + } + if (json == null) { + throw new BusinessException("LLM输出无法解析为JSON: " + snippet(result.text())); + } + + String category = text(json, "category"); + String rootCause = text(json, "rootCause"); + String suggestion = text(json, "suggestion"); + if ((category == null || category.isBlank()) + || (rootCause == null || rootCause.isBlank()) + || (suggestion == null || suggestion.isBlank())) { + throw new BusinessException("LLM输出缺少分析内容(category/rootCause/suggestion): " + snippet(result.text())); + } + + analysis.setCategory(category); + analysis.setKeywords(joinArray(json, "keywords")); + analysis.setRootCause(rootCause); + analysis.setSuggestion(suggestion); + analysis.setStatus("completed"); + analysis.setPromptVersion(1); + analysis.setModelProvider(result.provider()); + analysis.setModelName(modelName); + analysis.setCompletedAt(LocalDateTime.now()); + analysisRepository.save(analysis); + + issue.setAiAnalysisId(analysis.getId()); + issueRepository.save(issue); + + execution.setStatus("success"); + execution.setOutputResult("分类: " + (category == null ? "" : category) + + " | 关键词: " + (analysis.getKeywords() == null ? "" : analysis.getKeywords())); + execution.setExecutionTimeMs(System.currentTimeMillis() - start); + toolExecutionRepository.save(execution); + } catch (Exception e) { + log.error("AI分析失败 issueId={}", issueId, e); + analysis.setStatus("failed"); + analysis.setErrorMessage(e.getMessage()); + analysis.setCompletedAt(LocalDateTime.now()); + analysisRepository.save(analysis); + + execution.setStatus("failed"); + execution.setOutputResult(snippet(e.getMessage())); + execution.setExecutionTimeMs(System.currentTimeMillis() - start); + toolExecutionRepository.save(execution); + } + } + + private ObjectNode repairJsonOutput(String broken, String systemPrompt) { + try { + String repairPrompt = "以下内容本应是严格的 JSON,但无法解析。" + + "请将修正后的完整 JSON 重新输出,禁止 Markdown 围栏、禁止解释性文字," + + "直接输出 JSON:\n\n" + broken; + ModelRoutingService.CallResult retry = modelRoutingService.call(systemPrompt, repairPrompt); + return promptFormatter.extractJsonObject(retry.text()); + } catch (Exception e) { + log.warn("JSON 修复重试失败: {}", e.getMessage()); + return null; + } + } + + private String aiConfigModelName(String provider) { + try { + com.ims.api.dto.ai.AiConfigResponse cfg = aiConfigService.getConfig(); + if ("deepseek".equalsIgnoreCase(provider)) { + return cfg.getDeepseekModel(); + } + return cfg.getOllamaChatModel(); + } catch (Exception ignored) { + return provider; + } + } + + public Page records(Long id, int page, int pageSize, Long issueId, Long departmentId, + String status, LocalDate startDate, LocalDate endDate) { + Pageable pageable = PageRequest.of(Math.max(page - 1, 0), pageSize); + LocalDateTime start = startDate == null ? LocalDateTime.of(1970, 1, 1, 0, 0) : startDate.atStartOfDay(); + LocalDateTime end = endDate == null ? LocalDateTime.of(2999, 12, 31, 23, 59, 59) : endDate.plusDays(1).atStartOfDay(); + Page result = analysisRepository.search(id, issueId, departmentId, status, start, end, pageable); + return result.map(this::toResponse); + } + + public List running() { + List list = analysisRepository.findByStatusIn( + List.of("pending", "processing"), PageRequest.of(0, 50)); + return list.stream().map(this::toResponse).toList(); + } + + @Transactional + public void feedback(Long analysisId, Long userId, Boolean isHelpful, String comment) { + AiAnalysis analysis = analysisRepository.findById(analysisId) + .orElseThrow(() -> new BusinessException("分析记录不存在: " + analysisId)); + User user = userRepository.findById(userId) + .orElseThrow(() -> new BusinessException("用户不存在: " + userId)); + + AiFeedback feedback = AiFeedback.builder() + .aiAnalysis(analysis) + .user(user) + .isHelpful(isHelpful) + .comment(comment) + .build(); + feedbackRepository.save(feedback); + + int delta = Boolean.TRUE.equals(isHelpful) ? 1 : -1; + int current = analysis.getHelpfulCount() == null ? 0 : analysis.getHelpfulCount(); + analysis.setHelpfulCount(Math.max(current + delta, 0)); + analysisRepository.save(analysis); + } + + private Map buildAnalysisVariables(Issue issue, String extractedKeywords) { + Map vars = new HashMap<>(); + vars.put("issueNo", issue.getIssueNo()); + vars.put("issueTitle", issue.getTitle()); + vars.put("issueDescription", issue.getDescription() == null ? "" : issue.getDescription()); + vars.put("issuePhase", issue.getPhase() == null ? "" : issue.getPhase()); + vars.put("issueCategory", issue.getCategory() == null ? "" : issue.getCategory()); + vars.put("issueImpactLevel", issue.getImpactLevel() == null ? "" : issue.getImpactLevel()); + vars.put("issueKeywords", extractedKeywords); + vars.put("similarCases", buildKnowledgeContext(issue)); + return vars; + } + + private String buildKnowledgeContext(Issue issue) { + try { + StringBuilder query = new StringBuilder(issue.getTitle() == null ? "" : issue.getTitle()); + if (issue.getDescription() != null && !issue.getDescription().isBlank()) { + if (query.length() > 0) { + query.append(" "); + } + query.append(issue.getDescription()); + } + if (query.length() == 0) { + return ""; + } + List results = (List) knowledgeSearchTool.execute( + Map.of("query", query.toString(), "top_k", 5)); + if (results == null || results.isEmpty()) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < results.size(); i++) { + Object r = results.get(i); + if (r instanceof Map) { + Map m = (Map) r; + sb.append(i + 1).append(". ") + .append(m.get("doc_name")).append(": ") + .append(m.get("content")).append("\n"); + } + } + return sb.toString(); + } catch (Exception e) { + log.warn("知识库检索失败,降级为无相似案例: {}", e.getMessage()); + return ""; + } + } + + private AiAnalysisResponse toResponse(AiAnalysis a) { + AiAnalysisResponse resp = new AiAnalysisResponse(); + resp.setId(a.getId()); + resp.setCategory(a.getCategory()); + resp.setKeywords(a.getKeywords()); + resp.setExtractedKeywords(a.getExtractedKeywords()); + resp.setRootCause(a.getRootCause()); + resp.setSuggestion(a.getSuggestion()); + resp.setStatus(a.getStatus()); + resp.setHelpfulCount(a.getHelpfulCount()); + resp.setPromptTemplateId(a.getPromptTemplateId()); + resp.setPromptVersion(a.getPromptVersion()); + resp.setModelProvider(a.getModelProvider()); + resp.setModelName(a.getModelName()); + resp.setErrorMessage(a.getErrorMessage()); + resp.setStartedAt(a.getStartedAt()); + resp.setCompletedAt(a.getCompletedAt()); + resp.setCreatedAt(a.getCreatedAt()); + if (a.getIssue() != null) { + resp.setIssueId(a.getIssue().getId()); + resp.setIssueNo(a.getIssue().getIssueNo()); + resp.setIssueTitle(a.getIssue().getTitle()); + if (a.getIssue().getDepartment() != null) { + resp.setDepartmentName(a.getIssue().getDepartment().getName()); + } + } + return resp; + } + + private String text(ObjectNode json, String field) { + JsonNode node = get(json, field); + if (node == null || node.isNull()) { + return null; + } + return node.isContainerNode() ? node.toString() : node.asText(); + } + + private String joinArray(ObjectNode json, String field) { + JsonNode node = get(json, field); + if (node == null || !node.isArray()) { + return ""; + } + List items = new ArrayList<>(); + for (JsonNode n : (ArrayNode) node) { + items.add(n.asText()); + } + return String.join(",", items); + } + + private JsonNode get(ObjectNode json, String field) { + JsonNode node = json.get(field); + if (node != null) { + return node; + } + for (Map.Entry entry : json.properties()) { + if (entry.getKey().equalsIgnoreCase(field)) { + return entry.getValue(); + } + } + return null; + } + + private String snippet(String text) { + if (text == null) { + return ""; + } + return text.length() > 300 ? text.substring(0, 300) + "..." : text; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/ai/AiProviderConfig.java b/backend/ims-service/src/main/java/com/ims/service/ai/AiProviderConfig.java new file mode 100644 index 0000000..56df7ce --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/ai/AiProviderConfig.java @@ -0,0 +1,83 @@ +package com.ims.service.ai; + +import com.ims.api.dto.ai.AiConfigResponse; +import com.ims.common.exception.BusinessException; +import com.ims.service.knowledge.AiConfigService; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.ollama.OllamaChatModel; +import org.springframework.ai.ollama.api.OllamaApi; +import org.springframework.ai.ollama.api.OllamaOptions; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.ai.openai.api.OpenAiApi; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestClient; +import org.springframework.web.reactive.function.client.WebClient; + +@Component +public class AiProviderConfig { + + private static final String DEEPSEEK_BASE_URL = "https://api.deepseek.com"; + private static final int CONNECT_TIMEOUT_MS = 15_000; + private static final int READ_TIMEOUT_MS = 900_000; + + private final AiConfigService aiConfigService; + + public AiProviderConfig(AiConfigService aiConfigService) { + this.aiConfigService = aiConfigService; + } + + public ChatModel buildChatModel(String provider, AiConfigResponse cfg) { + if ("deepseek".equalsIgnoreCase(provider)) { + return buildDeepseekModel(cfg); + } + return buildOllamaModel(cfg); + } + + public String defaultModelName(String provider, AiConfigResponse cfg) { + if ("deepseek".equalsIgnoreCase(provider)) { + return cfg.getDeepseekModel(); + } + return cfg.getOllamaChatModel(); + } + + private ChatModel buildOllamaModel(AiConfigResponse cfg) { + String baseUrl = cfg.getOllamaBaseUrl(); + String model = cfg.getOllamaChatModel(); + Double temperature = cfg.getOllamaTemperature() != null + ? cfg.getOllamaTemperature().doubleValue() : 0.3; + Integer numPredict = cfg.getOllamaNumPredict() != null ? cfg.getOllamaNumPredict() : 8192; + + OllamaOptions options = OllamaOptions.builder() + .model(model) + .temperature(temperature) + .numPredict(numPredict) + .build(); + return OllamaChatModel.builder() + .ollamaApi(new OllamaApi(baseUrl, restClientBuilder(), WebClient.builder())) + .defaultOptions(options) + .build(); + } + + private ChatModel buildDeepseekModel(AiConfigResponse cfg) { + String apiKey = aiConfigService.getEffectiveApiKey(); + if (apiKey == null || apiKey.isBlank()) { + throw new BusinessException("未配置 DeepSeek API Key"); + } + String model = cfg.getDeepseekModel(); + OpenAiApi api = new OpenAiApi(DEEPSEEK_BASE_URL, apiKey, restClientBuilder(), WebClient.builder()); + OpenAiChatOptions options = OpenAiChatOptions.builder() + .model(model) + .maxCompletionTokens(8192) + .build(); + return new OpenAiChatModel(api, options); + } + + private RestClient.Builder restClientBuilder() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(CONNECT_TIMEOUT_MS); + factory.setReadTimeout(READ_TIMEOUT_MS); + return RestClient.builder().requestFactory(factory); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/ai/DeepSeekChatService.java b/backend/ims-service/src/main/java/com/ims/service/ai/DeepSeekChatService.java new file mode 100644 index 0000000..1867846 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/ai/DeepSeekChatService.java @@ -0,0 +1,68 @@ +package com.ims.service.ai; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ims.api.service.ai.ChatService; +import com.ims.service.knowledge.AiConfigService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; + +import java.util.List; +import java.util.Map; + +@Service +public class DeepSeekChatService implements ChatService { + + private static final Logger log = LoggerFactory.getLogger(DeepSeekChatService.class); + private static final String API_URL = "https://api.deepseek.com/v1/chat/completions"; + + private final ObjectMapper objectMapper; + private final AiConfigService aiConfigService; + private final RestClient restClient; + + public DeepSeekChatService(ObjectMapper objectMapper, AiConfigService aiConfigService) { + this.objectMapper = objectMapper; + this.aiConfigService = aiConfigService; + this.restClient = RestClient.builder().build(); + } + + @Override + public String chat(String systemPrompt, String userPrompt) { + String apiKey = aiConfigService.getEffectiveApiKey(); + if (apiKey == null || apiKey.isBlank()) { + throw new IllegalStateException("DeepSeek API key is not configured"); + } + String model = aiConfigService.getConfig().getDeepseekModel(); + + Map body = Map.of( + "model", model, + "messages", List.of( + Map.of("role", "system", "content", systemPrompt), + Map.of("role", "user", "content", userPrompt) + ), + "stream", false, + "temperature", 0.3 + ); + + try { + String json = restClient.post() + .uri(API_URL) + .header("Authorization", "Bearer " + apiKey) + .header("Content-Type", "application/json") + .body(body) + .retrieve() + .body(String.class); + JsonNode root = objectMapper.readTree(json); + JsonNode content = root.path("choices").path(0).path("message").get("content"); + if (content == null || content.isMissingNode()) { + throw new IllegalStateException("DeepSeek chat empty response: " + json); + } + return content.asText(); + } catch (Exception e) { + log.error("DeepSeek chat failed", e); + throw new IllegalStateException("DeepSeek chat failed: " + e.getMessage(), e); + } + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/ai/KeywordExtractor.java b/backend/ims-service/src/main/java/com/ims/service/ai/KeywordExtractor.java new file mode 100644 index 0000000..826e0c3 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/ai/KeywordExtractor.java @@ -0,0 +1,47 @@ +package com.ims.service.ai; + +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +@Component +public class KeywordExtractor { + + private static final int MAX_KEYWORDS = 4; + + private static final List TERMS = Arrays.asList( + "需求", "设计", "编码", "实现", "测试", "验证", "环境", "数据", "接口", + "性能", "安全", "流程", "配置", "文档", "依赖", "版本", "部署", "运维", + "报错", "崩溃", "超时", "内存", "数据库", "缓存", "并发", "权限", + "兼容", "升级", "迁移", "备份", "日志", "异常", "边界", "加载", + "响应", "延迟", "质量", "验收", "规范", "标准", "自动化" + ); + + public String extract(String title, String description, String category, String phase) { + String text = (value(title) + " " + value(description) + " " + value(category) + " " + value(phase)).trim(); + if (text.isEmpty()) { + return ""; + } + List found = new ArrayList<>(); + for (String term : TERMS) { + if (text.contains(term)) { + found.add(term); + } + } + if (found.isEmpty()) { + return ""; + } + found.sort(Comparator.comparingInt(text::indexOf)); + if (found.size() > MAX_KEYWORDS) { + found = new ArrayList<>(found.subList(0, MAX_KEYWORDS)); + } + return String.join(",", found); + } + + private String value(String s) { + return s == null ? "" : s; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/ai/ModelRoutingService.java b/backend/ims-service/src/main/java/com/ims/service/ai/ModelRoutingService.java new file mode 100644 index 0000000..d1eb07e --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/ai/ModelRoutingService.java @@ -0,0 +1,225 @@ +package com.ims.service.ai; + +import com.ims.api.dto.ai.AiConfigResponse; +import com.ims.service.agent.tool.AgentFunctionCallbackAdapter; +import com.ims.service.agent.tool.ToolRegistry; +import com.ims.service.entity.AiCallLog; +import com.ims.service.knowledge.AiConfigService; +import com.ims.service.repository.AiCallLogRepository; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.model.function.FunctionCallback; +import org.springframework.ai.ollama.api.OllamaOptions; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class ModelRoutingService { + + private static final Logger log = LoggerFactory.getLogger(ModelRoutingService.class); + + private final AiProviderConfig aiProviderConfig; + private final AiConfigService aiConfigService; + private final AiCallLogRepository aiCallLogRepository; + private final ToolRegistry toolRegistry; + private final ObjectMapper objectMapper; + + private volatile String cachedProvider; + private volatile ChatModel cachedChatModel; + private volatile String cachedFallbackProvider; + private volatile ChatModel cachedFallbackChatModel; + + public ModelRoutingService(AiProviderConfig aiProviderConfig, AiConfigService aiConfigService, + AiCallLogRepository aiCallLogRepository, + @Lazy ToolRegistry toolRegistry, ObjectMapper objectMapper) { + this.aiProviderConfig = aiProviderConfig; + this.aiConfigService = aiConfigService; + this.aiCallLogRepository = aiCallLogRepository; + this.toolRegistry = toolRegistry; + this.objectMapper = objectMapper; + } + + public String currentProvider() { + AiConfigResponse cfg = aiConfigService.getConfig(); + String provider = cfg.getProvider(); + return provider == null || provider.isBlank() ? "ollama" : provider; + } + + public ChatModel chatModel() { + String provider = currentProvider(); + AiConfigResponse cfg = aiConfigService.getConfig(); + if (cachedProvider == null || !cachedProvider.equals(provider)) { + cachedProvider = provider; + cachedChatModel = aiProviderConfig.buildChatModel(provider, cfg); + log.info("路由切换 ChatModel -> {}", provider); + } + return cachedChatModel; + } + + private ChatModel fallbackChatModel() { + String provider = currentProvider(); + String fallback = "deepseek".equalsIgnoreCase(provider) ? "ollama" : "deepseek"; + AiConfigResponse cfg = aiConfigService.getConfig(); + if (cachedFallbackProvider == null || !cachedFallbackProvider.equals(fallback)) { + cachedFallbackProvider = fallback; + cachedFallbackChatModel = aiProviderConfig.buildChatModel(fallback, cfg); + } + return cachedFallbackChatModel; + } + + public CallResult call(String systemPrompt, String userPrompt) { + String provider = currentProvider(); + try { + String text = call(chatModel(), systemPrompt, userPrompt); + return new CallResult(provider, text); + } catch (Exception primaryError) { + AiConfigResponse cfg = aiConfigService.getConfig(); + boolean fallbackEnabled = cfg.getAutoFallbackEnabled() != null && cfg.getAutoFallbackEnabled(); + if (!fallbackEnabled) { + throw primaryError; + } + String fallback = "deepseek".equalsIgnoreCase(provider) ? "ollama" : "deepseek"; + log.warn("主引擎 {} 调用失败,降级到 {}: {}", provider, fallback, primaryError.getMessage()); + String text = call(fallbackChatModel(), systemPrompt, userPrompt); + return new CallResult(fallback, text); + } + } + + private String call(ChatModel model, String systemPrompt, String userPrompt) { + String provider = providerOf(model); + long start = System.currentTimeMillis(); + try { + Prompt prompt = new Prompt(new SystemMessage(systemPrompt), new UserMessage(userPrompt)); + ChatResponse response = model.call(prompt); + String text = response.getResult().getOutput().getText(); + saveLog(provider, modelNameOf(provider), "success", + System.currentTimeMillis() - start, snippet(text), null); + return text; + } catch (Exception e) { + saveLog(provider, modelNameOf(provider), "failed", + System.currentTimeMillis() - start, null, e.getMessage()); + throw e; + } + } + + public ToolCallResult callWithTools(String systemPrompt, String userPrompt) { + String provider = currentProvider(); + AiConfigResponse cfg = aiConfigService.getConfig(); + long start = System.currentTimeMillis(); + try { + if (!"ollama".equalsIgnoreCase(provider)) { + String text = call(chatModel(), systemPrompt, userPrompt); + return new ToolCallResult(provider, text, List.of()); + } + List callbacks = new ArrayList<>(); + for (var tool : toolRegistry.tools()) { + callbacks.add(new AgentFunctionCallbackAdapter(tool, objectMapper)); + } + Set names = callbacks.stream().map(FunctionCallback::getName).collect(Collectors.toSet()); + OllamaOptions options = OllamaOptions.builder() + .model(cfg.getOllamaChatModel()) + .temperature(cfg.getOllamaTemperature() != null ? cfg.getOllamaTemperature().doubleValue() : 0.3) + .numPredict(cfg.getOllamaNumPredict() != null ? cfg.getOllamaNumPredict() : 4096) + .toolCallbacks(callbacks) + .toolNames(names) + .internalToolExecutionEnabled(false) + .build(); + Prompt prompt = new Prompt(List.of(new SystemMessage(systemPrompt), new UserMessage(userPrompt)), options); + ChatResponse response = chatModel().call(prompt); + AssistantMessage message = response.getResult().getOutput(); + List infos = new ArrayList<>(); + if (message.getToolCalls() != null) { + for (AssistantMessage.ToolCall tc : message.getToolCalls()) { + infos.add(new ToolCallInfo(tc.name(), tc.arguments())); + } + } + String text = message.getText(); + saveLog(provider, modelNameOf(provider), "success", + System.currentTimeMillis() - start, snippet(text), null); + return new ToolCallResult(provider, text, infos); + } catch (Exception e) { + saveLog(provider, modelNameOf(provider), "failed", + System.currentTimeMillis() - start, null, e.getMessage()); + throw e; + } + } + + public record ToolCallInfo(String name, String arguments) { + } + + public record ToolCallResult(String provider, String text, List toolCalls) { + } + + private String providerOf(ChatModel model) { + if (model instanceof OpenAiChatModel) { + return "deepseek"; + } + return "ollama"; + } + + private String modelNameOf(String provider) { + AiConfigResponse cfg = aiConfigService.getConfig(); + return "deepseek".equalsIgnoreCase(provider) ? cfg.getDeepseekModel() : cfg.getOllamaChatModel(); + } + + private String snippet(String text) { + if (text == null) { + return null; + } + return text.length() > 200 ? text.substring(0, 200) : text; + } + + private void saveLog(String provider, String model, String status, long latencyMs, String snippet, String error) { + try { + AiCallLog log = AiCallLog.builder() + .provider(provider) + .model(model) + .status(status) + .latencyMs(latencyMs) + .responseSnippet(snippet) + .errorMessage(error) + .build(); + aiCallLogRepository.save(log); + } catch (Exception e) { + log.warn("保存 AI 调用日志失败: {}", e.getMessage()); + } + } + + public record CallResult(String provider, String text) { + } + + public Map test() { + String provider = currentProvider(); + AiConfigResponse cfg = aiConfigService.getConfig(); + Map result = new LinkedHashMap<>(); + long start = System.currentTimeMillis(); + try { + String text = call(chatModel(), "你是测试助手", "请仅回复两个字:正常"); + result.put("success", true); + result.put("provider", provider); + result.put("model", "deepseek".equalsIgnoreCase(provider) ? cfg.getDeepseekModel() : cfg.getOllamaChatModel()); + result.put("latencyMs", System.currentTimeMillis() - start); + result.put("reply", text == null ? "" : (text.length() > 200 ? text.substring(0, 200) : text)); + } catch (Exception e) { + result.put("success", false); + result.put("provider", provider); + result.put("error", e.getMessage()); + } + return result; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/ai/OllamaChatService.java b/backend/ims-service/src/main/java/com/ims/service/ai/OllamaChatService.java new file mode 100644 index 0000000..a50f854 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/ai/OllamaChatService.java @@ -0,0 +1,86 @@ +package com.ims.service.ai; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ims.api.service.ai.ChatService; +import com.ims.service.knowledge.AiConfigService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +@Service("aiChatOllamaService") +public class OllamaChatService implements ChatService { + + private static final Logger log = LoggerFactory.getLogger(OllamaChatService.class); + + private final ObjectMapper objectMapper; + private final AiConfigService aiConfigService; + private final int connectTimeoutMs; + private final int readTimeoutMs; + + public OllamaChatService(ObjectMapper objectMapper, AiConfigService aiConfigService, + @org.springframework.beans.factory.annotation.Value("${ollama.timeout.connect:10000}") int connectTimeoutMs, + @org.springframework.beans.factory.annotation.Value("${ollama.timeout.read:1800000}") int readTimeoutMs) { + this.objectMapper = objectMapper; + this.aiConfigService = aiConfigService; + this.connectTimeoutMs = connectTimeoutMs; + this.readTimeoutMs = readTimeoutMs; + } + + @Override + public String chat(String systemPrompt, String userPrompt) { + String baseUrl = aiConfigService.getConfig().getOllamaBaseUrl(); + String model = aiConfigService.getConfig().getOllamaChatModel(); + if (model == null || model.isBlank()) { + throw new IllegalStateException("Ollama chat model is not configured"); + } + + Map body = Map.of( + "model", model, + "messages", List.of( + Map.of("role", "system", "content", systemPrompt), + Map.of("role", "user", "content", userPrompt) + ), + "stream", false, + "options", Map.of("temperature", 0.3, "num_predict", 1024) + ); + + try { + String requestBody = objectMapper.writeValueAsString(body); + HttpURLConnection conn = (HttpURLConnection) URI.create(baseUrl + "/api/chat").toURL().openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setDoOutput(true); + conn.setConnectTimeout(connectTimeoutMs); + conn.setReadTimeout(readTimeoutMs); + + try (OutputStream os = conn.getOutputStream()) { + os.write(requestBody.getBytes(StandardCharsets.UTF_8)); + } + + int code = conn.getResponseCode(); + if (code != 200) { + String err = new String(conn.getErrorStream() == null ? new byte[0] : conn.getErrorStream().readAllBytes(), StandardCharsets.UTF_8); + throw new IllegalStateException("Ollama chat HTTP " + code + ": " + err); + } + + String json = new String(conn.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + JsonNode root = objectMapper.readTree(json); + JsonNode content = root.path("message").get("content"); + if (content == null || content.isMissingNode()) { + throw new IllegalStateException("Ollama chat empty response: " + json); + } + return content.asText(); + } catch (Exception e) { + log.error("Ollama chat failed", e); + throw new IllegalStateException("Ollama chat failed: " + e.getMessage(), e); + } + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/ai/PromptFormatter.java b/backend/ims-service/src/main/java/com/ims/service/ai/PromptFormatter.java new file mode 100644 index 0000000..6065882 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/ai/PromptFormatter.java @@ -0,0 +1,117 @@ +package com.ims.service.ai; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.stereotype.Component; + +@Component +public class PromptFormatter { + + private final ObjectMapper objectMapper; + + public PromptFormatter(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public ObjectNode extractJsonObject(String output) { + if (output == null || output.isBlank()) { + return null; + } + String text = output.trim(); + if (text.contains("```")) { + text = text.replaceAll("```(json)?", "").replaceAll("```", "").trim(); + } + int start = text.indexOf('{'); + if (start < 0) { + return null; + } + String candidate = extractObject(text, start); + if (candidate == null) { + return null; + } + ObjectNode node = tryParse(candidate); + if (node != null) { + return node; + } + node = tryParse(escapeNewlinesInStrings(candidate)); + if (node != null) { + return node; + } + node = tryParse(escapeNewlinesInStrings(candidate) + "}"); + if (node != null) { + return node; + } + return null; + } + + private ObjectNode tryParse(String json) { + try { + JsonNode node = objectMapper.readTree(json); + if (node instanceof ObjectNode) { + return (ObjectNode) node; + } + } catch (Exception ignored) { + } + return null; + } + + private String extractObject(String text, int start) { + boolean inString = false; + boolean escaped = false; + int depth = 0; + for (int i = start; i < text.length(); i++) { + char c = text.charAt(i); + if (escaped) { + escaped = false; + continue; + } + if (c == '\\') { + escaped = true; + continue; + } + if (c == '"') { + inString = !inString; + continue; + } + if (!inString) { + if (c == '{') { + depth++; + } else if (c == '}' && --depth == 0) { + return text.substring(start, i + 1); + } + } + } + return null; + } + + private String escapeNewlinesInStrings(String json) { + StringBuilder sb = new StringBuilder(json.length()); + boolean inString = false; + boolean escaped = false; + for (int i = 0; i < json.length(); i++) { + char c = json.charAt(i); + if (escaped) { + sb.append(c); + escaped = false; + continue; + } + if (c == '\\') { + sb.append(c); + escaped = true; + continue; + } + if (c == '"') { + inString = !inString; + sb.append(c); + continue; + } + if (inString && (c == '\n' || c == '\r' || c == '\t')) { + sb.append("\\n"); + continue; + } + sb.append(c); + } + return sb.toString(); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/ai/PromptTemplateEngine.java b/backend/ims-service/src/main/java/com/ims/service/ai/PromptTemplateEngine.java new file mode 100644 index 0000000..b7c886f --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/ai/PromptTemplateEngine.java @@ -0,0 +1,129 @@ +package com.ims.service.ai; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ims.common.exception.BusinessException; +import com.ims.service.entity.PromptRenderLog; +import com.ims.service.entity.PromptTemplate; +import com.ims.service.repository.PromptRenderLogRepository; +import com.ims.service.repository.PromptTemplateRepository; +import org.springframework.stereotype.Service; + +import java.util.Map; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Service +public class PromptTemplateEngine { + + private static final Pattern VAR_PATTERN = Pattern.compile("\\{\\{(\\w+)\\}\\}"); + + private final PromptTemplateRepository templateRepository; + private final PromptRenderLogRepository renderLogRepository; + private final ObjectMapper objectMapper; + + public PromptTemplateEngine(PromptTemplateRepository templateRepository, + PromptRenderLogRepository renderLogRepository, + ObjectMapper objectMapper) { + this.templateRepository = templateRepository; + this.renderLogRepository = renderLogRepository; + this.objectMapper = objectMapper; + } + + public PromptTemplate loadActive(String templateId) { + return templateRepository.findByTemplateIdAndIsActiveTrue(templateId) + .orElseThrow(() -> new BusinessException("Prompt模板不存在或已停用: " + templateId)); + } + + public String render(String templateId, Map variables) { + PromptTemplate template = loadActive(templateId); + return render(template, variables); + } + + public String render(PromptTemplate template, Map variables) { + validateVariables(template, variables); + String content = template.getContent(); + if (variables != null) { + for (Map.Entry entry : variables.entrySet()) { + String placeholder = "{{" + entry.getKey() + "}}"; + content = content.replace(placeholder, formatValue(entry.getValue())); + } + } + Matcher matcher = VAR_PATTERN.matcher(content); + return matcher.replaceAll(""); + } + + public String renderAndLog(String templateId, Map variables, + String modelProvider, String llmModel) { + long start = System.currentTimeMillis(); + PromptTemplate template = loadActive(templateId); + String rendered = render(template, variables); + long cost = System.currentTimeMillis() - start; + + PromptRenderLog log = new PromptRenderLog(); + log.setRequestId(UUID.randomUUID().toString().replace("-", "")); + log.setTemplateId(template.getTemplateId()); + log.setTemplateVersion(template.getVersion()); + log.setRenderedPrompt(rendered); + log.setVariablesUsed(toJson(variables)); + log.setTokensInput(estimateTokens(rendered)); + log.setExecutionTimeMs((int) cost); + log.setLlmModel(llmModel); + log.setModelProvider(modelProvider); + renderLogRepository.save(log); + return rendered; + } + + private void validateVariables(PromptTemplate template, Map variables) { + JsonNode requiredVars = parseVariables(template.getVariables()); + if (requiredVars == null || !requiredVars.isArray()) { + return; + } + for (JsonNode node : requiredVars) { + if (node.has("required") && node.get("required").asBoolean()) { + String name = node.get("name").asText(); + Object value = variables == null ? null : variables.get(name); + if (value == null || String.valueOf(value).isBlank()) { + throw new BusinessException("缺少必需变量: " + name); + } + } + } + } + + private JsonNode parseVariables(String variables) { + if (variables == null || variables.isBlank()) { + return null; + } + try { + return objectMapper.readTree(variables); + } catch (Exception e) { + return null; + } + } + + private String formatValue(Object value) { + if (value == null) { + return ""; + } + if (value instanceof String || value instanceof Number || value instanceof Boolean) { + return String.valueOf(value); + } + return toJson(value); + } + + private String toJson(Object value) { + if (value == null) { + return "{}"; + } + try { + return objectMapper.writeValueAsString(value); + } catch (Exception e) { + return String.valueOf(value); + } + } + + private int estimateTokens(String text) { + return text == null ? 0 : (int) Math.ceil(text.length() / 4.0); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/ai/RoutingChatService.java b/backend/ims-service/src/main/java/com/ims/service/ai/RoutingChatService.java new file mode 100644 index 0000000..0728ea0 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/ai/RoutingChatService.java @@ -0,0 +1,52 @@ +package com.ims.service.ai; + +import com.ims.api.service.ai.ChatService; +import com.ims.service.knowledge.AiConfigService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +@Service +public class RoutingChatService implements ChatService { + + private static final Logger log = LoggerFactory.getLogger(RoutingChatService.class); + + private final AiConfigService aiConfigService; + private final OllamaChatService ollamaChatService; + private final DeepSeekChatService deepSeekChatService; + + public RoutingChatService(AiConfigService aiConfigService, + OllamaChatService ollamaChatService, + DeepSeekChatService deepSeekChatService) { + this.aiConfigService = aiConfigService; + this.ollamaChatService = ollamaChatService; + this.deepSeekChatService = deepSeekChatService; + } + + @Override + public String chat(String systemPrompt, String userPrompt) { + String provider = aiConfigService.getConfig().getProvider(); + boolean fallbackEnabled = Boolean.TRUE.equals(aiConfigService.getConfig().getAutoFallbackEnabled()); + try { + if ("deepseek".equalsIgnoreCase(provider)) { + return deepSeekChatService.chat(systemPrompt, userPrompt); + } + return ollamaChatService.chat(systemPrompt, userPrompt); + } catch (Exception e) { + if (fallbackEnabled) { + try { + if ("deepseek".equalsIgnoreCase(provider)) { + log.warn("DeepSeek chat failed, fallback to Ollama: {}", e.getMessage()); + return ollamaChatService.chat(systemPrompt, userPrompt); + } + log.warn("Ollama chat failed, fallback to DeepSeek: {}", e.getMessage()); + return deepSeekChatService.chat(systemPrompt, userPrompt); + } catch (Exception fe) { + log.error("Fallback chat provider also failed", fe); + throw new IllegalStateException("AI chat unavailable: " + fe.getMessage(), fe); + } + } + throw new IllegalStateException("AI chat unavailable: " + e.getMessage(), e); + } + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/ai/SystemContextBuilder.java b/backend/ims-service/src/main/java/com/ims/service/ai/SystemContextBuilder.java new file mode 100644 index 0000000..35e1ec4 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/ai/SystemContextBuilder.java @@ -0,0 +1,70 @@ +package com.ims.service.ai; + +import com.ims.service.entity.Role; +import com.ims.service.entity.User; +import com.ims.service.entity.UserRole; +import com.ims.service.repository.RoleRepository; +import com.ims.service.repository.UserRoleRepository; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Component +public class SystemContextBuilder { + + private final UserRoleRepository userRoleRepository; + private final RoleRepository roleRepository; + private final ModelRoutingService modelRoutingService; + + public SystemContextBuilder(UserRoleRepository userRoleRepository, + RoleRepository roleRepository, + ModelRoutingService modelRoutingService) { + this.userRoleRepository = userRoleRepository; + this.roleRepository = roleRepository; + this.modelRoutingService = modelRoutingService; + } + + public Map build(User user, List availableTools) { + Map vars = new HashMap<>(); + if (user != null) { + vars.put("currentUserName", user.getUsername()); + vars.put("currentUserRole", resolveRoleName(user.getId())); + vars.put("currentUserDepartment", user.getDepartment() != null ? user.getDepartment().getName() : ""); + vars.put("agentAutoExecuteEnabled", user.getAgentAutoExecute() != null && user.getAgentAutoExecute()); + } else { + vars.put("currentUserName", "系统"); + vars.put("currentUserRole", "系统管理员"); + vars.put("currentUserDepartment", ""); + vars.put("agentAutoExecuteEnabled", false); + } + if (availableTools == null || availableTools.isEmpty()) { + vars.put("availableTools", "(本任务无需工具)"); + } else { + StringBuilder sb = new StringBuilder(); + for (String t : availableTools) { + sb.append("- ").append(t).append("\n"); + } + vars.put("availableTools", sb.toString()); + } + vars.put("currentTime", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + vars.put("modelProvider", modelRoutingService.currentProvider()); + return vars; + } + + private String resolveRoleName(Long userId) { + List userRoles = userRoleRepository.findByUserId(userId); + if (userRoles.isEmpty()) { + return ""; + } + List names = new ArrayList<>(); + for (UserRole ur : userRoles) { + roleRepository.findById(ur.getRoleId()).map(Role::getName).ifPresent(names::add); + } + return String.join(",", names); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/config/JacksonConfig.java b/backend/ims-service/src/main/java/com/ims/service/config/JacksonConfig.java new file mode 100644 index 0000000..c97c869 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/config/JacksonConfig.java @@ -0,0 +1,19 @@ +package com.ims.service.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class JacksonConfig { + + @Bean + public ObjectMapper objectMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + return mapper; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/config/JwtConfig.java b/backend/ims-service/src/main/java/com/ims/service/config/JwtConfig.java new file mode 100644 index 0000000..e8e5872 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/config/JwtConfig.java @@ -0,0 +1,24 @@ +package com.ims.service.config; + +import com.ims.common.util.JwtUtil; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class JwtConfig { + + @Value("${jwt.secret}") + private String secret; + + @Value("${jwt.access-token-expiration}") + private long accessTokenExpiration; + + @Value("${jwt.refresh-token-expiration}") + private long refreshTokenExpiration; + + @Bean + public JwtUtil jwtUtil() { + return new JwtUtil(secret, accessTokenExpiration, refreshTokenExpiration); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/config/MinioConfig.java b/backend/ims-service/src/main/java/com/ims/service/config/MinioConfig.java new file mode 100644 index 0000000..ed47617 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/config/MinioConfig.java @@ -0,0 +1,27 @@ +package com.ims.service.config; + +import io.minio.MinioClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MinioConfig { + + @Value("${minio.endpoint}") + private String endpoint; + + @Value("${minio.access-key}") + private String accessKey; + + @Value("${minio.secret-key}") + private String secretKey; + + @Bean + public MinioClient minioClient() { + return MinioClient.builder() + .endpoint(endpoint) + .credentials(accessKey, secretKey) + .build(); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/config/RedisConfig.java b/backend/ims-service/src/main/java/com/ims/service/config/RedisConfig.java new file mode 100644 index 0000000..f2047a4 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/config/RedisConfig.java @@ -0,0 +1,15 @@ +package com.ims.service.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.StringRedisTemplate; + +@Configuration +public class RedisConfig { + + @Bean + public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) { + return new StringRedisTemplate(connectionFactory); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/config/WebConfig.java b/backend/ims-service/src/main/java/com/ims/service/config/WebConfig.java new file mode 100644 index 0000000..52c1033 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/config/WebConfig.java @@ -0,0 +1,21 @@ +package com.ims.service.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +@Configuration +public class WebConfig { + + private final CorsConfigurationSource corsConfigurationSource; + + public WebConfig(CorsConfigurationSource corsConfigurationSource) { + this.corsConfigurationSource = corsConfigurationSource; + } + + @Bean + public CorsFilter corsFilter() { + return new CorsFilter(corsConfigurationSource); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/dashboard/DashboardService.java b/backend/ims-service/src/main/java/com/ims/service/dashboard/DashboardService.java new file mode 100644 index 0000000..d35f6f8 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/dashboard/DashboardService.java @@ -0,0 +1,206 @@ +package com.ims.service.dashboard; + +import com.ims.api.dto.dashboard.DashboardStatsResponse; +import com.ims.service.entity.IssueLog; +import com.ims.service.repository.IssueLogRepository; +import jakarta.persistence.EntityManager; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class DashboardService { + + private final EntityManager entityManager; + private final IssueLogRepository issueLogRepository; + + public DashboardService(EntityManager entityManager, IssueLogRepository issueLogRepository) { + this.entityManager = entityManager; + this.issueLogRepository = issueLogRepository; + } + + @Transactional(readOnly = true) + public DashboardStatsResponse stats() { + DashboardStatsResponse resp = new DashboardStatsResponse(); + Map statusCounts = statusCounts(); + resp.setPendingCount(statusCounts.getOrDefault("open", 0L)); + resp.setInProgressCount(statusCounts.getOrDefault("in_progress", 0L)); + resp.setPendingConfirmCount(statusCounts.getOrDefault("pending_confirm", 0L)); + resp.setClosedCount(statusCounts.getOrDefault("closed", 0L)); + resp.setTodayNewCount(todayNewCount()); + resp.setMonthlyClosedCount(monthlyClosedCount()); + + List distribution = new ArrayList<>(); + statusCounts.forEach((k, v) -> distribution.add(new DashboardStatsResponse.StatusCount(k, v))); + resp.setStatusDistribution(distribution); + + resp.setTrend(trend7Days()); + resp.setRecentActivities(recentActivities()); + resp.setInsights(insights()); + resp.setCards(buildCards()); + return resp; + } + + private List buildCards() { + Map counts = statusCounts(); + long pending = counts.getOrDefault("open", 0L); + long inProgress = counts.getOrDefault("in_progress", 0L); + long todayNew = todayNewCount(); + long monthlyClosed = monthlyClosedCount(); + + long overdue = countBySql( + "select count(*) from issues where is_deleted = false and status <> 'closed' and deadline < now()"); + long highRisk = countBySql( + "select count(*) from issues where is_deleted = false and status in ('open','in_progress') " + + "and priority in ('urgent','high')"); + + long yesterdayNew = countCreatedOn(LocalDate.now().minusDays(1)); + long lastMonthClosed = countClosedInMonth(LocalDate.now().minusMonths(1)); + + List cards = new ArrayList<>(); + cards.add(new DashboardStatsResponse.CardInfo("pendingCount", pending, + overdue > 0 ? "其中 " + overdue + " 条已逾期" : "无逾期", + overdue > 0 ? "建议启动催办,避免影响工程进度。" : "待处理指摘无逾期,请保持跟进。")); + cards.add(new DashboardStatsResponse.CardInfo("inProgressCount", inProgress, + highRisk > 0 ? "其中 " + highRisk + " 条 urgent/high" : "暂无高风险", + highRisk > 0 ? "高优先级指摘建议优先分配处理。" : "进行中指摘均在正常推进。")); + cards.add(new DashboardStatsResponse.CardInfo("monthlyClosedCount", monthlyClosed, + lastMonthClosed > 0 ? "较上月" + changeText(monthlyClosed, lastMonthClosed) : "上月无关闭", + "本月已关闭 " + monthlyClosed + " 条,处理效率" + (monthlyClosed >= lastMonthClosed ? "较上月提升。" : "较上月下降,建议跟进。"))); + cards.add(new DashboardStatsResponse.CardInfo("todayNewCount", todayNew, + "较昨日" + changeText(todayNew, yesterdayNew), + "今日新增 " + todayNew + " 条指摘,请及时确认并分配。")); + return cards; + } + + private String changeText(long current, long base) { + if (base <= 0) { + return current > 0 ? " +" + current : " 持平"; + } + long diff = current - base; + long pct = Math.round(diff * 100.0 / base); + return (diff >= 0 ? " +" : " ") + pct + "%"; + } + + private long countCreatedOn(LocalDate date) { + Object r = entityManager.createQuery( + "select count(i) from Issue i where i.createdAt >= :from and i.createdAt < :to and i.isDeleted = false") + .setParameter("from", date.atStartOfDay()) + .setParameter("to", date.plusDays(1).atStartOfDay()) + .getSingleResult(); + return ((Number) r).longValue(); + } + + private long countClosedInMonth(LocalDate month) { + Object r = entityManager.createNativeQuery( + "select count(*) from issues where is_deleted = false and closed_at is not null " + + "and CAST(closed_at AS DATE) >= :from and CAST(closed_at AS DATE) <= :to") + .setParameter("from", month.withDayOfMonth(1)) + .setParameter("to", month.withDayOfMonth(month.lengthOfMonth())) + .getSingleResult(); + return ((Number) r).longValue(); + } + + private long monthlyClosedCount() { + LocalDate today = LocalDate.now(); + Object r = entityManager.createNativeQuery( + "select count(*) from issues where is_deleted = false and closed_at is not null " + + "and CAST(closed_at AS DATE) >= :from and CAST(closed_at AS DATE) <= :to") + .setParameter("from", today.withDayOfMonth(1)) + .setParameter("to", today) + .getSingleResult(); + return ((Number) r).longValue(); + } + + private List insights() { + List list = new ArrayList<>(); + long highRisk = countBySql( + "select count(*) from issues where is_deleted = false and status in ('open','in_progress') " + + "and priority in ('urgent','high')"); + long overdue = countBySql( + "select count(*) from issues where is_deleted = false and status <> 'closed' and deadline < now()"); + long kbDocs = countBySql( + "select count(*) from knowledge_documents where status = 'completed'"); + list.add(new DashboardStatsResponse.Insight("high-risk", "高风险警报", + "进行中有 " + highRisk + " 条 urgent/high 优先级的指摘,建议优先跟进。")); + list.add(new DashboardStatsResponse.Insight("reminder", "任务提醒", + "有 " + overdue + " 条指摘已逾期未关闭,建议启动自动催办。")); + list.add(new DashboardStatsResponse.Insight("suggestion", "自动对应建议", + "知识库已收录 " + kbDocs + " 篇案例文档,Agent 可检索相似方案辅助对应。")); + return list; + } + + private long countBySql(String sql) { + Object r = entityManager.createNativeQuery(sql).getSingleResult(); + return ((Number) r).longValue(); + } + + @SuppressWarnings("unchecked") + private Map statusCounts() { + Map map = new HashMap<>(); + List rows = entityManager.createNativeQuery( + "select status, count(*) from issues where is_deleted = false group by status").getResultList(); + for (Object[] row : rows) { + map.put((String) row[0], ((Number) row[1]).longValue()); + } + return map; + } + + private long todayNewCount() { + LocalDateTime start = LocalDate.now().atStartOfDay(); + Object r = entityManager.createQuery( + "select count(i) from Issue i where i.createdAt >= :start and i.isDeleted = false") + .setParameter("start", start).getSingleResult(); + return ((Number) r).longValue(); + } + + private List trend7Days() { + LocalDate today = LocalDate.now(); + LocalDate from = today.minusDays(6); + Map created = countGroupedByDay("created_at", from); + Map closed = countGroupedByDay("closed_at", from); + List trend = new ArrayList<>(); + for (int i = 0; i < 7; i++) { + LocalDate d = from.plusDays(i); + trend.add(new DashboardStatsResponse.DailyTrend( + d.toString(), created.getOrDefault(d, 0L), closed.getOrDefault(d, 0L))); + } + return trend; + } + + @SuppressWarnings("unchecked") + private Map countGroupedByDay(String column, LocalDate from) { + Map map = new HashMap<>(); + List rows = entityManager.createNativeQuery( + "select CAST(" + column + " AS DATE), count(*) from issues where " + column + + " >= :from and is_deleted = false group by 1") + .setParameter("from", from.atStartOfDay()).getResultList(); + for (Object[] row : rows) { + map.put(((java.sql.Date) row[0]).toLocalDate(), ((Number) row[1]).longValue()); + } + return map; + } + + private List recentActivities() { + List logs = issueLogRepository + .findAll(PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "createdAt"))).getContent(); + List list = new ArrayList<>(); + for (IssueLog l : logs) { + list.add(new DashboardStatsResponse.Activity( + l.getUser() != null ? l.getUser().getUsername() : "", + l.getAction(), + l.getIssue() != null && l.getIssue().getIssueNo() != null ? l.getIssue().getIssueNo() : "", + l.getIssue() != null && l.getIssue().getTitle() != null ? l.getIssue().getTitle() : "", + l.getCreatedAt())); + } + return list; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/AgentMemory.java b/backend/ims-service/src/main/java/com/ims/service/entity/AgentMemory.java new file mode 100644 index 0000000..1c03577 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/AgentMemory.java @@ -0,0 +1,51 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "agent_memories") +public class AgentMemory { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "issue_summary", nullable = false, columnDefinition = "TEXT") + private String issueSummary; + + @JdbcTypeCode(SqlTypes.JSON) + @Column(name = "solution_steps", nullable = false, columnDefinition = "jsonb") + private String solutionSteps; + + @Column(name = "effectiveness_score", precision = 3, scale = 2) + private BigDecimal effectivenessScore; + + @Column(columnDefinition = "vector(1536)") + private String embedding; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + updatedAt = LocalDateTime.now(); + if (effectivenessScore == null) effectivenessScore = BigDecimal.ZERO; + } + + @PreUpdate + protected void onUpdate() { + updatedAt = LocalDateTime.now(); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/AgentPlan.java b/backend/ims-service/src/main/java/com/ims/service/entity/AgentPlan.java new file mode 100644 index 0000000..a4ac497 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/AgentPlan.java @@ -0,0 +1,65 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "agent_plans") +public class AgentPlan { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "issue_id", nullable = true) + private Issue issue; + + @Column(nullable = false, columnDefinition = "TEXT") + private String goal; + + @JdbcTypeCode(SqlTypes.JSON) + @Column(name = "plan_steps", nullable = false, columnDefinition = "jsonb") + private String planSteps; + + @Column(length = 20) + private String status; + + @Column(name = "requires_approval") + private Boolean requiresApproval; + + @Column(name = "approval_status", length = 20) + private String approvalStatus; + + @Column(name = "approval_comment", length = 500) + private String approvalComment; + + @Column(name = "agent_message", columnDefinition = "TEXT") + private String agentMessage; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "created_by") + private User createdBy; + + @Column(name = "model_provider", length = 20) + private String modelProvider; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @Column(name = "completed_at") + private LocalDateTime completedAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + if (status == null) status = "pending"; + if (requiresApproval == null) requiresApproval = false; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/AiAnalysis.java b/backend/ims-service/src/main/java/com/ims/service/entity/AiAnalysis.java new file mode 100644 index 0000000..c703d78 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/AiAnalysis.java @@ -0,0 +1,73 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "ai_analysis") +public class AiAnalysis { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "issue_id", nullable = false) + private Issue issue; + + @Column(length = 100) + private String category; + + @Column(length = 500) + private String keywords; + + @Column(name = "extracted_keywords", length = 500) + private String extractedKeywords; + + @Column(name = "root_cause", length = 1000) + private String rootCause; + + @Column(columnDefinition = "TEXT") + private String suggestion; + + @Column(length = 20) + private String status; + + @Column(name = "helpful_count") + private Integer helpfulCount; + + @Column(name = "prompt_template_id", length = 100) + private String promptTemplateId; + + @Column(name = "prompt_version") + private Integer promptVersion; + + @Column(name = "model_provider", length = 20) + private String modelProvider; + + @Column(name = "model_name", length = 50) + private String modelName; + + @Column(name = "error_message", columnDefinition = "TEXT") + private String errorMessage; + + @Column(name = "started_at") + private LocalDateTime startedAt; + + @Column(name = "completed_at") + private LocalDateTime completedAt; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + if (status == null) status = "pending"; + if (helpfulCount == null) helpfulCount = 0; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/AiCallLog.java b/backend/ims-service/src/main/java/com/ims/service/entity/AiCallLog.java new file mode 100644 index 0000000..a6e6167 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/AiCallLog.java @@ -0,0 +1,43 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "ai_call_logs") +public class AiCallLog { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(length = 20) + private String provider; + + @Column(length = 100) + private String model; + + @Column(length = 20) + private String status; + + @Column(name = "latency_ms") + private Long latencyMs; + + @Column(name = "response_snippet", columnDefinition = "TEXT") + private String responseSnippet; + + @Column(name = "error_message", columnDefinition = "TEXT") + private String errorMessage; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/AiFeedback.java b/backend/ims-service/src/main/java/com/ims/service/entity/AiFeedback.java new file mode 100644 index 0000000..b12d800 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/AiFeedback.java @@ -0,0 +1,39 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "ai_feedback") +public class AiFeedback { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "ai_analysis_id", nullable = false) + private AiAnalysis aiAnalysis; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @Column(name = "is_helpful", nullable = false) + private Boolean isHelpful; + + @Column(length = 500) + private String comment; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/Attachment.java b/backend/ims-service/src/main/java/com/ims/service/entity/Attachment.java new file mode 100644 index 0000000..3faead0 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/Attachment.java @@ -0,0 +1,45 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "attachments") +public class Attachment { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "issue_id", nullable = false) + private Issue issue; + + @Column(name = "file_name", nullable = false, length = 200) + private String fileName; + + @Column(name = "file_path", nullable = false, length = 500) + private String filePath; + + @Column(name = "file_size", nullable = false) + private Long fileSize; + + @Column(name = "mime_type", length = 100) + private String mimeType; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "uploaded_by", nullable = false) + private User uploadedBy; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/Department.java b/backend/ims-service/src/main/java/com/ims/service/entity/Department.java new file mode 100644 index 0000000..0250f30 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/Department.java @@ -0,0 +1,44 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "departments") +public class Department { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 100) + private String name; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parent_id") + private Department parent; + + @Column(name = "sort_order") + private Integer sortOrder; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + updatedAt = LocalDateTime.now(); + } + + @PreUpdate + protected void onUpdate() { + updatedAt = LocalDateTime.now(); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/ImportRecord.java b/backend/ims-service/src/main/java/com/ims/service/entity/ImportRecord.java new file mode 100644 index 0000000..bbd4c3a --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/ImportRecord.java @@ -0,0 +1,48 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "import_records") +public class ImportRecord { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "file_name", nullable = false, length = 255) + private String fileName; + + @Column(name = "total_count", nullable = false) + private Integer totalCount; + + @Column(name = "success_count", nullable = false) + private Integer successCount; + + @Column(name = "fail_count", nullable = false) + private Integer failCount; + + @Column(nullable = false, length = 20) + private String status; + + @Column(name = "error_log", columnDefinition = "TEXT") + private String errorLog; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "operator_id") + private User operator; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + if (status == null) status = "success"; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/Issue.java b/backend/ims-service/src/main/java/com/ims/service/entity/Issue.java new file mode 100644 index 0000000..16e7d08 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/Issue.java @@ -0,0 +1,133 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "issues") +public class Issue { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "issue_no", nullable = false, unique = true, length = 20) + private String issueNo; + + @Column(nullable = false, length = 200) + private String title; + + @Column(columnDefinition = "TEXT") + private String description; + + @Column(nullable = false, length = 30) + private String status; + + @Column(nullable = false, length = 10) + private String priority; + + private LocalDateTime deadline; + + @Column(name = "review_date") + private LocalDateTime reviewDate; + + @Column(length = 50) + private String phase; + + @Column(name = "sub_project", length = 100) + private String subProject; + + @Column(length = 50) + private String category; + + @Column(name = "impact_level", length = 10) + private String impactLevel; + + @Column(name = "impact_scope", length = 200) + private String impactScope; + + @Column(length = 100) + private String deployment; + + @Column(name = "pgm_no", length = 50) + private String pgmNo; + + @Column(name = "review_workload", precision = 5, scale = 1) + private BigDecimal reviewWorkload; + + @Column(name = "response_workload", precision = 5, scale = 1) + private BigDecimal responseWorkload; + + @Column(name = "response_content", columnDefinition = "TEXT") + private String responseContent; + + @Column(name = "ng_reason", length = 200) + private String ngReason; + + @Column(name = "response_completed_at") + private LocalDateTime responseCompletedAt; + + @Column(name = "confirm_at") + private LocalDateTime confirmAt; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "creator_id", nullable = false) + private User creator; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "assignee_id") + private User assignee; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "department_id", nullable = false) + private Department department; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "reviewer_id") + private User reviewer; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "validator_id") + private User validator; + + @Column(name = "ai_analysis_id") + private Long aiAnalysisId; + + @Column(name = "agent_last_plan_id") + private Long agentLastPlanId; + + @Column(name = "agent_status", length = 20) + private String agentStatus; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + @Column(name = "closed_at") + private LocalDateTime closedAt; + + @Column(name = "is_deleted") + private Boolean isDeleted; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + updatedAt = LocalDateTime.now(); + if (status == null) status = "draft"; + if (priority == null) priority = "medium"; + if (agentStatus == null) agentStatus = "human_driven"; + if (isDeleted == null) isDeleted = false; + } + + @PreUpdate + protected void onUpdate() { + updatedAt = LocalDateTime.now(); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/IssueLog.java b/backend/ims-service/src/main/java/com/ims/service/entity/IssueLog.java new file mode 100644 index 0000000..6f34d4a --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/IssueLog.java @@ -0,0 +1,45 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "issue_logs") +public class IssueLog { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "issue_id", nullable = false) + private Issue issue; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @Column(nullable = false, length = 30) + private String action; + + @Column(name = "from_status", length = 30) + private String fromStatus; + + @Column(name = "to_status", length = 30) + private String toStatus; + + @Column(length = 500) + private String remark; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/KnowledgeChunk.java b/backend/ims-service/src/main/java/com/ims/service/entity/KnowledgeChunk.java new file mode 100644 index 0000000..2dd60eb --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/KnowledgeChunk.java @@ -0,0 +1,41 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "knowledge_chunks") +public class KnowledgeChunk { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "doc_id", nullable = false) + private KnowledgeDocument doc; + + @Column(nullable = false, columnDefinition = "TEXT") + private String content; + + @Column(nullable = false, columnDefinition = "vector(768)") + private String embedding; + + @JdbcTypeCode(SqlTypes.JSON) + @Column(columnDefinition = "jsonb") + private String metadata; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/KnowledgeDocument.java b/backend/ims-service/src/main/java/com/ims/service/entity/KnowledgeDocument.java new file mode 100644 index 0000000..bbfb004 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/KnowledgeDocument.java @@ -0,0 +1,61 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "knowledge_documents") +public class KnowledgeDocument { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 255) + private String name; + + @Column(name = "file_path", nullable = false, length = 500) + private String filePath; + + @Column(name = "file_size", nullable = false) + private Long fileSize; + + @Column(name = "file_type", nullable = false, length = 20) + private String fileType; + + @Column(name = "chunk_count") + private Integer chunkCount; + + @Column(length = 20) + private String status; + + @Column(name = "error_message", columnDefinition = "TEXT") + private String errorMessage; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "uploaded_by", nullable = false) + private User uploadedBy; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + updatedAt = LocalDateTime.now(); + if (status == null) status = "pending"; + if (chunkCount == null) chunkCount = 0; + } + + @PreUpdate + protected void onUpdate() { + updatedAt = LocalDateTime.now(); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/KnowledgeSearchLog.java b/backend/ims-service/src/main/java/com/ims/service/entity/KnowledgeSearchLog.java new file mode 100644 index 0000000..0cc0ecb --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/KnowledgeSearchLog.java @@ -0,0 +1,49 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "knowledge_search_logs") +public class KnowledgeSearchLog { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "issue_id") + private Issue issue; + + @Column(nullable = false, columnDefinition = "TEXT") + private String query; + + @Column(name = "rewritten_query", columnDefinition = "TEXT") + private String rewrittenQuery; + + @Column(name = "top_k") + private Integer topK; + + @Column(name = "total_matches") + private Integer totalMatches; + + @Column(name = "duration_ms") + private Integer durationMs; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + if (topK == null) topK = 5; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/Notification.java b/backend/ims-service/src/main/java/com/ims/service/entity/Notification.java new file mode 100644 index 0000000..e83f655 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/Notification.java @@ -0,0 +1,49 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "notifications") +public class Notification { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @Column(nullable = false, length = 200) + private String title; + + @Column(nullable = false, length = 1000) + private String content; + + @Column(nullable = false, length = 30) + private String type; + + @Column(length = 500) + private String link; + + @Column(name = "is_read") + private Boolean isRead; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "issue_id") + private Issue issue; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + if (isRead == null) isRead = false; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/Permission.java b/backend/ims-service/src/main/java/com/ims/service/entity/Permission.java new file mode 100644 index 0000000..94267ba --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/Permission.java @@ -0,0 +1,28 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "permissions") +public class Permission { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true, length = 100) + private String code; + + @Column(nullable = false, length = 100) + private String name; + + @Column(nullable = false, length = 50) + private String resource; + + @Column(length = 255) + private String description; +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/PhaseRule.java b/backend/ims-service/src/main/java/com/ims/service/entity/PhaseRule.java new file mode 100644 index 0000000..6e77808 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/PhaseRule.java @@ -0,0 +1,55 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "phase_rules") +public class PhaseRule { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 100) + private String keyword; + + @Column(nullable = false, length = 50) + private String phase; + + @Column(name = "match_mode", nullable = false, length = 20) + private String matchMode; + + @Column(length = 50) + private String source; + + @Column(nullable = false) + private Boolean active; + + @Column(name = "sort_order", nullable = false) + private Integer sortOrder; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + updatedAt = LocalDateTime.now(); + if (matchMode == null) matchMode = "contains"; + if (active == null) active = true; + if (sortOrder == null) sortOrder = 0; + } + + @PreUpdate + protected void onUpdate() { + updatedAt = LocalDateTime.now(); + } +} \ No newline at end of file diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/PromptRenderLog.java b/backend/ims-service/src/main/java/com/ims/service/entity/PromptRenderLog.java new file mode 100644 index 0000000..7a5ffec --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/PromptRenderLog.java @@ -0,0 +1,59 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "prompt_render_logs") +public class PromptRenderLog { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "request_id", nullable = false, length = 64) + private String requestId; + + @Column(name = "template_id", nullable = false, length = 100) + private String templateId; + + @Column(name = "template_version", nullable = false) + private Integer templateVersion; + + @Column(name = "rendered_prompt", nullable = false, columnDefinition = "TEXT") + private String renderedPrompt; + + @JdbcTypeCode(SqlTypes.JSON) + @Column(name = "variables_used", columnDefinition = "jsonb") + private String variablesUsed; + + @Column(name = "tokens_input") + private Integer tokensInput; + + @Column(name = "tokens_output") + private Integer tokensOutput; + + @Column(name = "execution_time_ms") + private Integer executionTimeMs; + + @Column(name = "llm_model", length = 50) + private String llmModel; + + @Column(name = "model_provider", length = 20) + private String modelProvider; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/PromptTemplate.java b/backend/ims-service/src/main/java/com/ims/service/entity/PromptTemplate.java new file mode 100644 index 0000000..51219b5 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/PromptTemplate.java @@ -0,0 +1,73 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "prompt_templates") +public class PromptTemplate { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "template_id", nullable = false, unique = true, length = 100) + private String templateId; + + @Column(nullable = false, length = 200) + private String name; + + @Column(nullable = false, length = 50) + private String category; + + @Column(nullable = false) + private Integer version; + + @Column(nullable = false, columnDefinition = "TEXT") + private String content; + + @JdbcTypeCode(SqlTypes.JSON) + @Column(columnDefinition = "jsonb") + private String variables; + + @JdbcTypeCode(SqlTypes.JSON) + @Column(name = "output_schema", columnDefinition = "jsonb") + private String outputSchema; + + @Column(name = "is_active") + private Boolean isActive; + + @Column(name = "is_default") + private Boolean isDefault; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "created_by") + private User createdBy; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + updatedAt = LocalDateTime.now(); + if (version == null) version = 1; + if (isActive == null) isActive = true; + if (isDefault == null) isDefault = false; + } + + @PreUpdate + protected void onUpdate() { + updatedAt = LocalDateTime.now(); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/PromptTemplateVersion.java b/backend/ims-service/src/main/java/com/ims/service/entity/PromptTemplateVersion.java new file mode 100644 index 0000000..eace641 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/PromptTemplateVersion.java @@ -0,0 +1,41 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "prompt_template_versions") +public class PromptTemplateVersion { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "template_id", nullable = false, length = 100) + private String templateId; + + @Column(nullable = false) + private Integer version; + + @Column(nullable = false, columnDefinition = "TEXT") + private String content; + + @Column(name = "change_log", length = 500) + private String changeLog; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "created_by") + private User createdBy; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/Role.java b/backend/ims-service/src/main/java/com/ims/service/entity/Role.java new file mode 100644 index 0000000..12324b2 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/Role.java @@ -0,0 +1,35 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "roles") +public class Role { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true, length = 50) + private String name; + + @Column(length = 255) + private String description; + + @Column(name = "agent_auto_execute") + private Boolean agentAutoExecute; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + if (agentAutoExecute == null) agentAutoExecute = false; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/RolePermission.java b/backend/ims-service/src/main/java/com/ims/service/entity/RolePermission.java new file mode 100644 index 0000000..db70291 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/RolePermission.java @@ -0,0 +1,30 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.io.Serializable; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "role_permissions") +@IdClass(RolePermission.RolePermissionId.class) +public class RolePermission { + @Id + @Column(name = "role_id") + private Long roleId; + + @Id + @Column(name = "permission_id") + private Long permissionId; + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class RolePermissionId implements Serializable { + private Long roleId; + private Long permissionId; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/TaskExecution.java b/backend/ims-service/src/main/java/com/ims/service/entity/TaskExecution.java new file mode 100644 index 0000000..97c3e6a --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/TaskExecution.java @@ -0,0 +1,55 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "task_executions") +public class TaskExecution { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "task_id", nullable = false, unique = true, length = 64) + private String taskId; + + @Column(name = "task_type", nullable = false, length = 30) + private String taskType; + + @Column(nullable = false, length = 20) + private String status; + + @Column(name = "result_url", length = 500) + private String resultUrl; + + @Column(name = "error_message", columnDefinition = "TEXT") + private String errorMessage; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "created_by", nullable = false) + private User createdBy; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @Column(name = "started_at") + private LocalDateTime startedAt; + + @Column(name = "completed_at") + private LocalDateTime completedAt; + + @Column(name = "retry_count") + private Integer retryCount; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + if (status == null) status = "pending"; + if (retryCount == null) retryCount = 0; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/ToolExecution.java b/backend/ims-service/src/main/java/com/ims/service/entity/ToolExecution.java new file mode 100644 index 0000000..f3d5218 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/ToolExecution.java @@ -0,0 +1,48 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "tool_executions") +public class ToolExecution { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "plan_id") + private AgentPlan plan; + + @Column(name = "tool_name", nullable = false, length = 50) + private String toolName; + + @JdbcTypeCode(SqlTypes.JSON) + @Column(name = "input_params", nullable = false, columnDefinition = "jsonb") + private String inputParams; + + @Column(name = "output_result", columnDefinition = "TEXT") + private String outputResult; + + @Column(length = 20) + private String status; + + @Column(name = "execution_time_ms") + private Long executionTimeMs; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + if (status == null) status = "success"; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/User.java b/backend/ims-service/src/main/java/com/ims/service/entity/User.java new file mode 100644 index 0000000..8c2133b --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/User.java @@ -0,0 +1,61 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "users") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true, length = 50) + private String userid; + + @Column(nullable = false, unique = true, length = 50) + private String username; + + @Column(length = 100) + private String email; + + @Column(name = "password_hash", nullable = false, length = 255) + private String passwordHash; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "department_id", nullable = false) + private Department department; + + @Column(name = "is_active") + private Boolean isActive; + + @Column(name = "agent_auto_execute") + private Boolean agentAutoExecute; + + @Column(name = "last_login_at") + private LocalDateTime lastLoginAt; + + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + updatedAt = LocalDateTime.now(); + if (isActive == null) isActive = true; + if (agentAutoExecute == null) agentAutoExecute = false; + } + + @PreUpdate + protected void onUpdate() { + updatedAt = LocalDateTime.now(); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/entity/UserRole.java b/backend/ims-service/src/main/java/com/ims/service/entity/UserRole.java new file mode 100644 index 0000000..2372bf5 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/entity/UserRole.java @@ -0,0 +1,30 @@ +package com.ims.service.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.io.Serializable; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "user_roles") +@IdClass(UserRole.UserRoleId.class) +public class UserRole { + @Id + @Column(name = "user_id") + private Long userId; + + @Id + @Column(name = "role_id") + private Long roleId; + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class UserRoleId implements Serializable { + private Long userId; + private Long roleId; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/issue/AttachmentService.java b/backend/ims-service/src/main/java/com/ims/service/issue/AttachmentService.java new file mode 100644 index 0000000..21c1bd6 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/issue/AttachmentService.java @@ -0,0 +1,116 @@ +package com.ims.service.issue; + +import com.ims.api.dto.issue.AttachmentResponse; +import com.ims.common.exception.BusinessException; +import com.ims.service.entity.Attachment; +import com.ims.service.entity.Issue; +import com.ims.service.entity.User; +import com.ims.service.repository.AttachmentRepository; +import com.ims.service.repository.IssueRepository; +import io.minio.GetObjectArgs; +import io.minio.MinioClient; +import io.minio.PutObjectArgs; +import io.minio.RemoveObjectArgs; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.servlet.http.HttpServletResponse; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.UUID; + +@Service +public class AttachmentService { + + private final AttachmentRepository attachmentRepository; + private final IssueRepository issueRepository; + private final MinioClient minioClient; + + @Value("${minio.bucket}") + private String bucket; + + public AttachmentService(AttachmentRepository attachmentRepository, + IssueRepository issueRepository, + MinioClient minioClient) { + this.attachmentRepository = attachmentRepository; + this.issueRepository = issueRepository; + this.minioClient = minioClient; + } + + @Transactional(readOnly = true) + public List listByIssue(Long issueId) { + return attachmentRepository.findByIssueId(issueId).stream().map(this::toResponse).toList(); + } + + @Transactional + public AttachmentResponse upload(Long issueId, MultipartFile file, User user) { + Issue issue = issueRepository.findById(issueId) + .orElseThrow(() -> new BusinessException("指摘不存在")); + String objectKey = "attachments/" + issueId + "/" + UUID.randomUUID() + "_" + file.getOriginalFilename(); + try (var is = file.getInputStream()) { + minioClient.putObject(PutObjectArgs.builder() + .bucket(bucket) + .object(objectKey) + .stream(is, file.getSize(), -1) + .contentType(file.getContentType()) + .build()); + } catch (Exception e) { + throw new BusinessException("附件上传失败: " + e.getMessage()); + } + Attachment att = Attachment.builder() + .issue(issue) + .fileName(file.getOriginalFilename()) + .filePath(objectKey) + .fileSize(file.getSize()) + .mimeType(file.getContentType()) + .uploadedBy(user) + .build(); + return toResponse(attachmentRepository.save(att)); + } + + public void download(Long attachmentId, HttpServletResponse response) { + Attachment att = attachmentRepository.findById(attachmentId) + .orElseThrow(() -> new BusinessException("附件不存在")); + try (var obj = minioClient.getObject(GetObjectArgs.builder() + .bucket(bucket) + .object(att.getFilePath()) + .build())) { + String mime = att.getMimeType() != null ? att.getMimeType() : "application/octet-stream"; + String encoded = URLEncoder.encode(att.getFileName(), StandardCharsets.UTF_8).replace("+", "%20"); + response.setContentType(mime); + response.setHeader("Content-Disposition", "attachment; filename=\"" + encoded + "\""); + obj.transferTo(response.getOutputStream()); + response.flushBuffer(); + } catch (Exception e) { + throw new BusinessException("附件下载失败"); + } + } + + @Transactional + public void delete(Long attachmentId) { + Attachment att = attachmentRepository.findById(attachmentId) + .orElseThrow(() -> new BusinessException("附件不存在")); + try { + minioClient.removeObject(RemoveObjectArgs.builder() + .bucket(bucket) + .object(att.getFilePath()) + .build()); + } catch (Exception ignored) {} + attachmentRepository.deleteById(attachmentId); + } + + private AttachmentResponse toResponse(Attachment a) { + AttachmentResponse resp = new AttachmentResponse(); + resp.setId(a.getId()); + resp.setFileName(a.getFileName()); + resp.setFileSize(a.getFileSize()); + resp.setMimeType(a.getMimeType()); + resp.setFilePath(a.getFilePath()); + resp.setUploadedByName(a.getUploadedBy() != null ? a.getUploadedBy().getUsername() : null); + resp.setCreatedAt(a.getCreatedAt()); + return resp; + } +} \ No newline at end of file diff --git a/backend/ims-service/src/main/java/com/ims/service/issue/IssueService.java b/backend/ims-service/src/main/java/com/ims/service/issue/IssueService.java new file mode 100644 index 0000000..f12a5d2 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/issue/IssueService.java @@ -0,0 +1,468 @@ +package com.ims.service.issue; + +import com.ims.api.dto.issue.IssueCreateRequest; +import com.ims.api.dto.issue.IssueListRequest; +import com.ims.api.dto.issue.IssueResponse; +import com.ims.api.dto.issue.IssueUpdateRequest; +import com.ims.common.dto.PageResult; +import com.ims.common.exception.BusinessException; +import com.ims.service.entity.Department; +import com.ims.service.entity.Issue; +import com.ims.service.entity.IssueLog; +import com.ims.service.entity.User; +import com.ims.service.notification.NotificationService; +import com.ims.service.repository.DepartmentRepository; +import com.ims.service.repository.IssueLogRepository; +import com.ims.service.repository.IssueRepository; +import com.ims.service.repository.UserRepository; +import com.ims.service.security.DataScope; +import com.ims.service.security.DataScopeContext; +import jakarta.persistence.EntityManager; +import jakarta.persistence.TypedQuery; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.time.Year; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Service +public class IssueService { + + public static final String STATUS_DRAFT = "draft"; + public static final String STATUS_OPEN = "open"; + public static final String STATUS_IN_PROGRESS = "in_progress"; + public static final String STATUS_RESOLVED = "resolved"; + public static final String STATUS_VERIFIED = "verified"; + public static final String STATUS_CLOSED = "closed"; + public static final String STATUS_REJECTED = "rejected"; + + private static final Map> TRANSITIONS = Map.of( + STATUS_DRAFT, Set.of(STATUS_OPEN), + STATUS_OPEN, Set.of(STATUS_DRAFT, STATUS_IN_PROGRESS, STATUS_REJECTED), + STATUS_IN_PROGRESS, Set.of(STATUS_OPEN, STATUS_RESOLVED, STATUS_REJECTED), + STATUS_RESOLVED, Set.of(STATUS_IN_PROGRESS, STATUS_VERIFIED, STATUS_REJECTED), + STATUS_VERIFIED, Set.of(STATUS_RESOLVED, STATUS_IN_PROGRESS, STATUS_CLOSED, STATUS_REJECTED), + STATUS_CLOSED, Set.of(), + STATUS_REJECTED, Set.of(STATUS_OPEN, STATUS_CLOSED)); + + private static final Map STATUS_LABELS = Map.of( + STATUS_DRAFT, "草稿", + STATUS_OPEN, "待处理", + STATUS_IN_PROGRESS, "进行中", + STATUS_RESOLVED, "已解决", + STATUS_VERIFIED, "已验证", + STATUS_CLOSED, "已关闭", + STATUS_REJECTED, "已驳回"); + + private final IssueRepository issueRepository; + private final IssueLogRepository issueLogRepository; + private final UserRepository userRepository; + private final DepartmentRepository departmentRepository; + private final NotificationService notificationService; + private final EntityManager entityManager; + + public IssueService(IssueRepository issueRepository, + IssueLogRepository issueLogRepository, + UserRepository userRepository, + DepartmentRepository departmentRepository, + NotificationService notificationService, + EntityManager entityManager) { + this.issueRepository = issueRepository; + this.issueLogRepository = issueLogRepository; + this.userRepository = userRepository; + this.departmentRepository = departmentRepository; + this.notificationService = notificationService; + this.entityManager = entityManager; + } + + @Transactional + public IssueResponse create(IssueCreateRequest req, User creator) { + Department department = departmentRepository.findById( + req.getDepartmentId() != null ? req.getDepartmentId() : creator.getDepartment().getId()) + .orElseThrow(() -> new BusinessException("部门不存在")); + + String createStatus = req.getStatus() != null ? req.getStatus() : STATUS_DRAFT; + if (!STATUS_DRAFT.equals(createStatus)) { + throw new BusinessException("新建指摘仅支持草稿状态"); + } + + Issue issue = Issue.builder() + .issueNo(generateIssueNo()) + .title(req.getTitle()) + .description(req.getDescription()) + .status(createStatus) + .priority(req.getPriority() != null ? req.getPriority() : "medium") + .deadline(req.getDeadline()) + .phase(req.getPhase()) + .subProject(req.getSubProject()) + .category(req.getCategory()) + .impactLevel(req.getImpactLevel()) + .impactScope(req.getImpactScope()) + .deployment(req.getDeployment()) + .pgmNo(req.getPgmNo()) + .reviewWorkload(req.getReviewWorkload()) + .responseWorkload(req.getResponseWorkload()) + .responseContent(req.getResponseContent()) + .ngReason(req.getNgReason()) + .responseCompletedAt(req.getResponseCompletedAt()) + .confirmAt(req.getConfirmAt()) + .creator(creator) + .assignee(resolveUser(req.getAssigneeId())) + .department(department) + .reviewer(resolveUser(req.getReviewerId())) + .validator(resolveUser(req.getValidatorId())) + .isDeleted(false) + .build(); + issue = issueRepository.save(issue); + log(issue, creator, "CREATE", null, issue.getStatus(), "创建指摘"); + return toResponse(issue); + } + + @DataScope + @Transactional(readOnly = true) + public PageResult list(IssueListRequest req) { + StringBuilder jpql = new StringBuilder("select i from Issue i where i.isDeleted = false"); + StringBuilder countJpql = new StringBuilder("select count(i) from Issue i where i.isDeleted = false"); + Map params = new HashMap<>(); + + appendIfPresent(jpql, countJpql, params, "i.status = :status", "status", req.getStatus()); + appendIfPresent(jpql, countJpql, params, "i.phase = :phase", "phase", req.getPhase()); + appendIfPresent(jpql, countJpql, params, "i.subProject = :subProject", "subProject", req.getSubProject()); + appendIfPresent(jpql, countJpql, params, "i.priority = :priority", "priority", req.getPriority()); + appendIfPresent(jpql, countJpql, params, "i.impactLevel = :impactLevel", "impactLevel", req.getImpactLevel()); + if (req.getAssigneeId() != null) { + jpql.append(" and i.assignee.id = :assigneeId"); + countJpql.append(" and i.assignee.id = :assigneeId"); + params.put("assigneeId", req.getAssigneeId()); + } + if (req.getDepartmentId() != null) { + jpql.append(" and i.department.id = :departmentId"); + countJpql.append(" and i.department.id = :departmentId"); + params.put("departmentId", req.getDepartmentId()); + } + if (req.getStartDate() != null) { + jpql.append(" and i.createdAt >= :startDate"); + countJpql.append(" and i.createdAt >= :startDate"); + params.put("startDate", req.getStartDate()); + } + if (req.getEndDate() != null) { + jpql.append(" and i.createdAt <= :endDate"); + countJpql.append(" and i.createdAt <= :endDate"); + params.put("endDate", req.getEndDate()); + } + if (req.getKeyword() != null && !req.getKeyword().isBlank()) { + String kw = "%" + req.getKeyword() + "%"; + jpql.append(" and (i.title like :kw or i.issueNo like :kw or i.description like :kw)"); + countJpql.append(" and (i.title like :kw or i.issueNo like :kw or i.description like :kw)"); + params.put("kw", kw); + } + Set scopeDeptIds = DataScopeContext.get(); + if (scopeDeptIds != null && !scopeDeptIds.isEmpty()) { + jpql.append(" and i.department.id in :scopeDeptIds"); + countJpql.append(" and i.department.id in :scopeDeptIds"); + params.put("scopeDeptIds", scopeDeptIds); + } + jpql.append(" order by i.createdAt desc"); + + int p = Math.max(1, req.getPage()); + int ps = req.getPageSize() > 0 ? req.getPageSize() : 20; + + TypedQuery query = entityManager.createQuery(jpql.toString(), Issue.class); + TypedQuery countQuery = entityManager.createQuery(countJpql.toString(), Long.class); + params.forEach((k, v) -> { + query.setParameter(k, v); + countQuery.setParameter(k, v); + }); + query.setFirstResult((p - 1) * ps).setMaxResults(ps); + + long total = countQuery.getSingleResult(); + List items = query.getResultList().stream().map(this::toResponse).toList(); + return new PageResult<>(items, total, p, ps); + } + + @Transactional(readOnly = true) + public IssueResponse detail(Long id) { + return toResponse(getIssue(id)); + } + + @Transactional + public IssueResponse update(Long id, IssueUpdateRequest req, User user) { + Issue issue = getIssue(id); + if (req.getTitle() != null) issue.setTitle(req.getTitle()); + if (req.getDescription() != null) issue.setDescription(req.getDescription()); + if (req.getPriority() != null) issue.setPriority(req.getPriority()); + if (req.getDeadline() != null) issue.setDeadline(req.getDeadline()); + if (req.getPhase() != null) issue.setPhase(req.getPhase()); + if (req.getSubProject() != null) issue.setSubProject(req.getSubProject()); + if (req.getCategory() != null) issue.setCategory(req.getCategory()); + if (req.getImpactLevel() != null) issue.setImpactLevel(req.getImpactLevel()); + if (req.getImpactScope() != null) issue.setImpactScope(req.getImpactScope()); + if (req.getDeployment() != null) issue.setDeployment(req.getDeployment()); + if (req.getPgmNo() != null) issue.setPgmNo(req.getPgmNo()); + if (req.getReviewWorkload() != null) issue.setReviewWorkload(req.getReviewWorkload()); + if (req.getResponseWorkload() != null) issue.setResponseWorkload(req.getResponseWorkload()); + if (req.getResponseContent() != null) issue.setResponseContent(req.getResponseContent()); + if (req.getNgReason() != null) issue.setNgReason(req.getNgReason()); + if (req.getResponseCompletedAt() != null) issue.setResponseCompletedAt(req.getResponseCompletedAt()); + if (req.getConfirmAt() != null) issue.setConfirmAt(req.getConfirmAt()); + if (req.getAssigneeId() != null) issue.setAssignee(resolveUser(req.getAssigneeId())); + if (req.getDepartmentId() != null) { + issue.setDepartment(departmentRepository.findById(req.getDepartmentId()) + .orElseThrow(() -> new BusinessException("部门不存在"))); + } + if (req.getReviewerId() != null) issue.setReviewer(resolveUser(req.getReviewerId())); + if (req.getValidatorId() != null) issue.setValidator(resolveUser(req.getValidatorId())); + issueRepository.save(issue); + log(issue, user, "UPDATE", null, null, "更新指摘"); + return toResponse(issue); + } + + @Transactional + public void delete(Long id, User user) { + Issue issue = getIssue(id); + issue.setIsDeleted(true); + issueRepository.save(issue); + log(issue, user, "DELETE", null, null, "删除指摘"); + } + + @Transactional + public IssueResponse changeStatus(Long id, String newStatus, String remark, User user) { + Issue issue = getIssue(id); + String from = issue.getStatus(); + if (!TRANSITIONS.getOrDefault(from, Set.of()).contains(newStatus)) { + throw new BusinessException("非法的状态流转: " + from + " -> " + newStatus); + } + issue.setStatus(newStatus); + if (STATUS_CLOSED.equals(newStatus)) { + issue.setClosedAt(LocalDateTime.now()); + } + issueRepository.save(issue); + log(issue, user, "STATUS_CHANGE", from, newStatus, remark != null ? remark : "状态流转"); + sendStatusNotification(issue, from, newStatus); + return toResponse(issue); + } + + @Transactional + public void changeAgentMode(Long id, String mode, User user) { + if (!"human_driven".equals(mode) && !"agent_driven".equals(mode)) { + throw new BusinessException("非法模式: " + mode); + } + Issue issue = getIssue(id); + issue.setAgentStatus(mode); + issueRepository.save(issue); + log(issue, user, "AGENT_MODE", null, null, "切换Agent驱动模式: " + mode); + } + + @DataScope + @Transactional(readOnly = true) + public List findAllFiltered(IssueListRequest req) { + StringBuilder jpql = new StringBuilder("select i from Issue i where i.isDeleted = false"); + Map params = new HashMap<>(); + appendIfPresent(jpql, jpql, params, "i.status = :status", "status", req.getStatus()); + appendIfPresent(jpql, jpql, params, "i.phase = :phase", "phase", req.getPhase()); + appendIfPresent(jpql, jpql, params, "i.subProject = :subProject", "subProject", req.getSubProject()); + appendIfPresent(jpql, jpql, params, "i.priority = :priority", "priority", req.getPriority()); + appendIfPresent(jpql, jpql, params, "i.impactLevel = :impactLevel", "impactLevel", req.getImpactLevel()); + if (req.getAssigneeId() != null) { + jpql.append(" and i.assignee.id = :assigneeId"); + params.put("assigneeId", req.getAssigneeId()); + } + if (req.getDepartmentId() != null) { + jpql.append(" and i.department.id = :departmentId"); + params.put("departmentId", req.getDepartmentId()); + } + if (req.getStartDate() != null) { + jpql.append(" and i.createdAt >= :startDate"); + params.put("startDate", req.getStartDate()); + } + if (req.getEndDate() != null) { + jpql.append(" and i.createdAt <= :endDate"); + params.put("endDate", req.getEndDate()); + } + if (req.getKeyword() != null && !req.getKeyword().isBlank()) { + String kw = "%" + req.getKeyword() + "%"; + jpql.append(" and (i.title like :kw or i.issueNo like :kw or i.description like :kw)"); + params.put("kw", kw); + } + Set scopeDeptIds = DataScopeContext.get(); + if (scopeDeptIds != null && !scopeDeptIds.isEmpty()) { + jpql.append(" and i.department.id in :scopeDeptIds"); + params.put("scopeDeptIds", scopeDeptIds); + } + jpql.append(" order by i.createdAt desc"); + TypedQuery query = entityManager.createQuery(jpql.toString(), Issue.class); + params.forEach(query::setParameter); + return query.getResultList(); + } + + @Transactional + public int batchAssign(List issueIds, Long assigneeId, User user) { + User assignee = resolveUser(assigneeId); + if (assignee == null) { + throw new BusinessException("担当者不存在"); + } + int count = 0; + for (Long id : issueIds) { + Issue issue = getIssue(id); + issue.setAssignee(assignee); + issueRepository.save(issue); + log(issue, user, "BATCH_ASSIGN", null, null, "批量统一分配担当者"); + notificationService.notify(assignee, "指摘分配: " + issue.getIssueNo(), + "您被分配为指摘《" + issue.getTitle() + "》的担当者,请及时处理。", + "assign", "/issues/" + issue.getId(), issue); + count++; + } + return count; + } + + @Transactional + public int batchNotify(List issueIds, String content, User user) { + int count = 0; + String msg = (content != null && !content.isBlank()) ? content : "您的指摘已逾期或即将到期,请尽快处理。"; + for (Long id : issueIds) { + Issue issue = getIssue(id); + if (issue.getAssignee() != null) { + notificationService.notify(issue.getAssignee(), "指摘催办: " + issue.getIssueNo(), + "指摘《" + issue.getTitle() + "》" + msg, + "reminder", "/issues/" + issue.getId(), issue); + } + log(issue, user, "BATCH_NOTIFY", null, null, "批量催办"); + count++; + } + return count; + } + + @Transactional(readOnly = true) + public List> logs(Long issueId) { + return issueLogRepository.findByIssueIdOrderByCreatedAtDesc(issueId).stream().map(l -> { + Map m = new HashMap<>(); + m.put("action", l.getAction()); + m.put("fromStatus", l.getFromStatus()); + m.put("toStatus", l.getToStatus()); + m.put("remark", l.getRemark()); + m.put("userName", l.getUser() != null ? l.getUser().getUsername() : ""); + m.put("createdAt", l.getCreatedAt()); + return m; + }).toList(); + } + + private void appendIfPresent(StringBuilder jpql, StringBuilder countJpql, Map params, + String clause, String key, String value) { + if (value != null && !value.isBlank()) { + jpql.append(" and ").append(clause); + countJpql.append(" and ").append(clause); + params.put(key, value); + } + } + + private String generateIssueNo() { + String prefix = "ISSUE-" + Year.now().getValue() + "-"; + TypedQuery q = entityManager.createQuery( + "select max(i.issueNo) from Issue i where i.issueNo like :prefix", String.class) + .setParameter("prefix", prefix + "%"); + String max = q.getSingleResult(); + int next = 1; + if (max != null && max.length() > prefix.length()) { + try { + next = Integer.parseInt(max.substring(prefix.length())) + 1; + } catch (NumberFormatException ignored) {} + } + return prefix + String.format("%03d", next); + } + + private User resolveUser(Long userId) { + return userId != null ? userRepository.findById(userId).orElse(null) : null; + } + + private Issue getIssue(Long id) { + return issueRepository.findById(id) + .filter(i -> !Boolean.TRUE.equals(i.getIsDeleted())) + .orElseThrow(() -> new BusinessException("指摘不存在")); + } + + private void log(Issue issue, User user, String action, String from, String to, String remark) { + IssueLog l = IssueLog.builder() + .issue(issue) + .user(user) + .action(action) + .fromStatus(from) + .toStatus(to) + .remark(remark) + .build(); + issueLogRepository.save(l); + } + + private void sendStatusNotification(Issue issue, String from, String to) { + String title = "指摘状态更新: " + issue.getIssueNo(); + String content = "指摘《" + issue.getTitle() + "》状态从 " + STATUS_LABELS.getOrDefault(from, from) + + " 变更为 " + STATUS_LABELS.getOrDefault(to, to); + String link = "/issues/" + issue.getId(); + if (issue.getAssignee() != null) { + notificationService.notify(issue.getAssignee(), title, content, "status", link, issue); + } + notificationService.notify(issue.getCreator(), title, content, "status", link, issue); + } + + private IssueResponse toResponse(Issue i) { + IssueResponse r = new IssueResponse(); + r.setId(i.getId()); + r.setIssueNo(i.getIssueNo()); + r.setTitle(i.getTitle()); + r.setDescription(i.getDescription()); + r.setStatus(i.getStatus()); + r.setPriority(i.getPriority()); + r.setDeadline(i.getDeadline()); + r.setPhase(i.getPhase()); + r.setSubProject(i.getSubProject()); + r.setCategory(i.getCategory()); + r.setImpactLevel(i.getImpactLevel()); + r.setImpactScope(i.getImpactScope()); + r.setDeployment(i.getDeployment()); + r.setPgmNo(i.getPgmNo()); + r.setReviewWorkload(i.getReviewWorkload()); + r.setResponseWorkload(i.getResponseWorkload()); + r.setResponseContent(i.getResponseContent()); + r.setNgReason(i.getNgReason()); + r.setResponseCompletedAt(i.getResponseCompletedAt()); + r.setConfirmAt(i.getConfirmAt()); + if (i.getCreator() != null) { + r.setCreatorId(i.getCreator().getId()); + r.setCreatorName(i.getCreator().getUsername()); + } + if (i.getAssignee() != null) { + r.setAssigneeId(i.getAssignee().getId()); + r.setAssigneeName(i.getAssignee().getUsername()); + } + if (i.getDepartment() != null) { + r.setDepartmentId(i.getDepartment().getId()); + r.setDepartmentName(i.getDepartment().getName()); + } + if (i.getReviewer() != null) { + r.setReviewerId(i.getReviewer().getId()); + r.setReviewerName(i.getReviewer().getUsername()); + } + if (i.getValidator() != null) { + r.setValidatorId(i.getValidator().getId()); + r.setValidatorName(i.getValidator().getUsername()); + } + r.setAgentStatus(i.getAgentStatus()); + r.setAgentLastPlanId(i.getAgentLastPlanId()); + r.setCreatedAt(i.getCreatedAt()); + r.setUpdatedAt(i.getUpdatedAt()); + r.setClosedAt(i.getClosedAt()); + return r; + } + + public static String escapeCsv(String s) { + if (s == null) return ""; + if (s.contains(",") || s.contains("\"") || s.contains("\n")) { + return "\"" + s.replace("\"", "\"\"") + "\""; + } + return s; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/knowledge/AiConfigService.java b/backend/ims-service/src/main/java/com/ims/service/knowledge/AiConfigService.java new file mode 100644 index 0000000..04b7156 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/knowledge/AiConfigService.java @@ -0,0 +1,253 @@ +package com.ims.service.knowledge; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ims.api.dto.ai.AiConfigRequest; +import com.ims.api.dto.ai.AiConfigResponse; +import com.ims.service.notification.NotificationService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.concurrent.TimeUnit; + +@Service +public class AiConfigService { + + private static final String REDIS_KEY = "ai:config"; + + private final StringRedisTemplate redisTemplate; + private final ObjectMapper objectMapper; + private final NotificationService notificationService; + + @Value("${ai.provider}") + private String defaultProvider; + + @Value("${ollama.base-url:http://localhost:11434}") + private String defaultOllamaUrl; + + @Value("${ollama.embedding.model}") + private String defaultOllamaModel; + + @Value("${ollama.chat.model}") + private String defaultOllamaChatModel; + + @Value("${ollama.chat.options.temperature:0.3}") + private Float defaultTemperature; + + @Value("${ollama.chat.options.num-predict:4096}") + private Integer defaultNumPredict; + + @Value("${deepseek.api.key}") + private String defaultDeepseekKey; + + @Value("${deepseek.model}") + private String defaultDeepseekModel; + + @Value("${deepseek.embedding-model}") + private String defaultDeepseekEmbeddingModel; + + @Value("${ai.auto-fallback-enabled}") + private Boolean defaultAutoFallback; + + @Value("${agent.max-steps:10}") + private Integer defaultAgentMaxSteps; + + @Value("${agent.auto-execute-high-risk:false}") + private Boolean defaultAutoExecuteHighRisk; + + @Value("${agent.user-rate-limit:10}") + private Integer defaultUserRateLimit; + + @Value("${knowledge.chunk-size:200}") + private Integer defaultChunkSize; + + @Value("${knowledge.chunk-overlap:40}") + private Integer defaultChunkOverlap; + + @Value("${knowledge.max-upload-size:52428800}") + private Long defaultMaxUploadSize; + + public AiConfigService(StringRedisTemplate redisTemplate, ObjectMapper objectMapper, + NotificationService notificationService) { + this.redisTemplate = redisTemplate; + this.objectMapper = objectMapper; + this.notificationService = notificationService; + } + + public AiConfigResponse getConfig() { + String json = redisTemplate.opsForValue().get(REDIS_KEY); + if (json != null) { + try { + AiConfigRequest request = objectMapper.readValue(json, AiConfigRequest.class); + return toResponse(request); + } catch (JsonProcessingException ignored) {} + } + return defaultConfig(); + } + + public void updateConfig(AiConfigRequest request) { + AiConfigRequest merged = readStored(); + if (merged == null) { + merged = new AiConfigRequest(); + copyResponseToRequest(defaultConfig(), merged); + } + String before = merged.getProvider(); + mergeNonNull(merged, request); + saveConfig(merged); + String after = merged.getProvider(); + if (after != null && !after.equals(before)) { + notificationService.notifyAdmins("AI引擎切换", + "AI 推理引擎由 " + (before == null ? "默认" : before) + " 切换为 " + after); + } + } + + private AiConfigRequest readStored() { + String json = redisTemplate.opsForValue().get(REDIS_KEY); + if (json == null) { + return null; + } + try { + return objectMapper.readValue(json, AiConfigRequest.class); + } catch (JsonProcessingException e) { + return null; + } + } + + private void saveConfig(AiConfigRequest request) { + try { + String json = objectMapper.writeValueAsString(request); + redisTemplate.opsForValue().set(REDIS_KEY, json, 24, TimeUnit.HOURS); + } catch (JsonProcessingException ignored) {} + } + + private void mergeNonNull(AiConfigRequest target, AiConfigRequest source) { + if (source.getProvider() != null) target.setProvider(source.getProvider()); + if (source.getOllamaBaseUrl() != null) target.setOllamaBaseUrl(source.getOllamaBaseUrl()); + if (source.getOllamaChatModel() != null) target.setOllamaChatModel(source.getOllamaChatModel()); + if (source.getOllamaEmbeddingModel() != null) target.setOllamaEmbeddingModel(source.getOllamaEmbeddingModel()); + if (source.getOllamaTemperature() != null) target.setOllamaTemperature(source.getOllamaTemperature()); + if (source.getOllamaNumPredict() != null) target.setOllamaNumPredict(source.getOllamaNumPredict()); + if (source.getDeepseekApiKey() != null) target.setDeepseekApiKey(source.getDeepseekApiKey()); + if (source.getDeepseekModel() != null) target.setDeepseekModel(source.getDeepseekModel()); + if (source.getDeepseekEmbeddingModel() != null) target.setDeepseekEmbeddingModel(source.getDeepseekEmbeddingModel()); + if (source.getAutoFallbackEnabled() != null) target.setAutoFallbackEnabled(source.getAutoFallbackEnabled()); + if (source.getAgentMaxSteps() != null) target.setAgentMaxSteps(source.getAgentMaxSteps()); + if (source.getAutoExecuteHighRisk() != null) target.setAutoExecuteHighRisk(source.getAutoExecuteHighRisk()); + if (source.getUserRateLimit() != null) target.setUserRateLimit(source.getUserRateLimit()); + if (source.getChunkSize() != null) target.setChunkSize(source.getChunkSize()); + if (source.getChunkOverlap() != null) target.setChunkOverlap(source.getChunkOverlap()); + if (source.getMaxUploadSize() != null) target.setMaxUploadSize(source.getMaxUploadSize()); + } + + private void copyResponseToRequest(AiConfigResponse resp, AiConfigRequest req) { + req.setProvider(resp.getProvider()); + req.setOllamaBaseUrl(resp.getOllamaBaseUrl()); + req.setOllamaChatModel(resp.getOllamaChatModel()); + req.setOllamaEmbeddingModel(resp.getOllamaEmbeddingModel()); + req.setOllamaTemperature(resp.getOllamaTemperature()); + req.setOllamaNumPredict(resp.getOllamaNumPredict()); + req.setDeepseekModel(resp.getDeepseekModel()); + req.setDeepseekEmbeddingModel(resp.getDeepseekEmbeddingModel()); + req.setAutoFallbackEnabled(resp.getAutoFallbackEnabled()); + req.setAgentMaxSteps(resp.getAgentMaxSteps()); + req.setAutoExecuteHighRisk(resp.getAutoExecuteHighRisk()); + req.setUserRateLimit(resp.getUserRateLimit()); + req.setChunkSize(resp.getChunkSize()); + req.setChunkOverlap(resp.getChunkOverlap()); + req.setMaxUploadSize(resp.getMaxUploadSize()); + } + + public String getEffectiveApiKey() { + String json = redisTemplate.opsForValue().get(REDIS_KEY); + if (json != null) { + try { + AiConfigRequest request = objectMapper.readValue(json, AiConfigRequest.class); + if (request.getDeepseekApiKey() != null && !request.getDeepseekApiKey().isEmpty()) { + return request.getDeepseekApiKey(); + } + } catch (JsonProcessingException ignored) {} + } + return defaultDeepseekKey; + } + + private AiConfigResponse toResponse(AiConfigRequest request) { + AiConfigResponse resp = new AiConfigResponse(); + resp.setProvider(request.getProvider()); + resp.setOllamaBaseUrl(request.getOllamaBaseUrl()); + resp.setOllamaChatModel(request.getOllamaChatModel()); + resp.setOllamaEmbeddingModel(request.getOllamaEmbeddingModel()); + resp.setOllamaTemperature(request.getOllamaTemperature()); + resp.setOllamaNumPredict(request.getOllamaNumPredict()); + resp.setDeepseekModel(request.getDeepseekModel()); + resp.setDeepseekEmbeddingModel(request.getDeepseekEmbeddingModel()); + resp.setAutoFallbackEnabled(request.getAutoFallbackEnabled()); + resp.setAgentMaxSteps(request.getAgentMaxSteps()); + resp.setAutoExecuteHighRisk(request.getAutoExecuteHighRisk()); + resp.setUserRateLimit(request.getUserRateLimit()); + resp.setChunkSize(request.getChunkSize()); + resp.setChunkOverlap(request.getChunkOverlap()); + resp.setMaxUploadSize(request.getMaxUploadSize()); + return resp; + } + + private AiConfigResponse defaultConfig() { + AiConfigResponse config = new AiConfigResponse(); + config.setProvider(defaultProvider); + config.setOllamaBaseUrl(defaultOllamaUrl); + config.setOllamaChatModel(defaultOllamaChatModel); + config.setOllamaEmbeddingModel(defaultOllamaModel); + config.setOllamaTemperature(defaultTemperature); + config.setOllamaNumPredict(defaultNumPredict); + config.setDeepseekModel(defaultDeepseekModel); + config.setDeepseekEmbeddingModel(defaultDeepseekEmbeddingModel); + config.setAutoFallbackEnabled(defaultAutoFallback); + config.setAgentMaxSteps(defaultAgentMaxSteps); + config.setAutoExecuteHighRisk(defaultAutoExecuteHighRisk); + config.setUserRateLimit(defaultUserRateLimit); + config.setChunkSize(defaultChunkSize); + config.setChunkOverlap(defaultChunkOverlap); + config.setMaxUploadSize(defaultMaxUploadSize); + return config; + } + + public Float getEffectiveTemperature() { + Float t = getConfig().getOllamaTemperature(); + return t != null ? t : defaultTemperature; + } + + public Integer getEffectiveNumPredict() { + Integer n = getConfig().getOllamaNumPredict(); + return n != null ? n : defaultNumPredict; + } + + public Integer getEffectiveMaxSteps() { + Integer m = getConfig().getAgentMaxSteps(); + return m != null ? m : defaultAgentMaxSteps; + } + + public Boolean getEffectiveAutoExecuteHighRisk() { + Boolean b = getConfig().getAutoExecuteHighRisk(); + return b != null ? b : defaultAutoExecuteHighRisk; + } + + public Integer getEffectiveUserRateLimit() { + Integer l = getConfig().getUserRateLimit(); + return l != null ? l : defaultUserRateLimit; + } + + public Integer getEffectiveChunkSize() { + Integer c = getConfig().getChunkSize(); + return c != null && c > 0 ? c : defaultChunkSize; + } + + public Integer getEffectiveChunkOverlap() { + Integer o = getConfig().getChunkOverlap(); + return o != null && o >= 0 ? o : defaultChunkOverlap; + } + + public Long getEffectiveMaxUploadSize() { + Long m = getConfig().getMaxUploadSize(); + return m != null && m > 0 ? m : defaultMaxUploadSize; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/knowledge/DeepSeekEmbeddingService.java b/backend/ims-service/src/main/java/com/ims/service/knowledge/DeepSeekEmbeddingService.java new file mode 100644 index 0000000..44812b7 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/knowledge/DeepSeekEmbeddingService.java @@ -0,0 +1,63 @@ +package com.ims.service.knowledge; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@Service +public class DeepSeekEmbeddingService { + + private static final String API_URL = "https://api.deepseek.com/v1/embeddings"; + + private final RestClient restClient; + private final ObjectMapper objectMapper; + private final AiConfigService aiConfigService; + + public DeepSeekEmbeddingService(ObjectMapper objectMapper, AiConfigService aiConfigService) { + this.objectMapper = objectMapper; + this.aiConfigService = aiConfigService; + this.restClient = RestClient.builder().build(); + } + + public List embed(String text) { + String apiKey = aiConfigService.getEffectiveApiKey(); + String model = aiConfigService.getConfig().getDeepseekEmbeddingModel(); + + Map body = Map.of( + "model", model, + "input", text + ); + + try { + String json = restClient.post() + .uri(API_URL) + .header("Authorization", "Bearer " + apiKey) + .header("Content-Type", "application/json") + .body(body) + .retrieve() + .body(String.class); + + JsonNode root = objectMapper.readTree(json); + JsonNode data = root.get("data"); + if (data == null || !data.isArray() || data.isEmpty()) { + return Collections.emptyList(); + } + + JsonNode embedding = data.get(0).get("embedding"); + List result = new ArrayList<>(); + for (JsonNode n : embedding) { + result.add(n.floatValue()); + } + return result; + } catch (JsonProcessingException e) { + throw new RuntimeException("DeepSeek embedding failed", e); + } + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/knowledge/DocumentParserService.java b/backend/ims-service/src/main/java/com/ims/service/knowledge/DocumentParserService.java new file mode 100644 index 0000000..124bb50 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/knowledge/DocumentParserService.java @@ -0,0 +1,123 @@ +package com.ims.service.knowledge; + +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.tika.Tika; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Service +public class DocumentParserService { + + private static final Logger log = LoggerFactory.getLogger(DocumentParserService.class); + + private static final Pattern TOKEN_PATTERN = Pattern.compile( + "[\\u3040-\\u30ff\\u31f0-\\u31ff\\u3400-\\u4dbf\\u4e00-\\u9fff\\u3001-\\u303f\\uff00-\\uffef]|[A-Za-z0-9]+"); + + private final Tika tika = new Tika(); + + private final AiConfigService aiConfigService; + + public DocumentParserService(AiConfigService aiConfigService) { + this.aiConfigService = aiConfigService; + } + + public String extractText(InputStream inputStream, String fileName) { + String lower = fileName.toLowerCase(); + if (lower.endsWith(".xlsx") || lower.endsWith(".xls")) { + try { + return extractTextFromExcel(inputStream, fileName); + } catch (Throwable t) { + log.warn("POI parsing failed for {}, falling back to raw text", fileName, t); + return ""; + } + } + try { + return tika.parseToString(inputStream); + } catch (Exception e) { + throw new RuntimeException("Failed to parse document: " + fileName, e); + } + } + + private String extractTextFromExcel(InputStream inputStream, String fileName) { + try (Workbook workbook = createWorkbook(inputStream, fileName)) { + StringBuilder sb = new StringBuilder(); + DataFormatter formatter = new DataFormatter(); + for (int i = 0; i < workbook.getNumberOfSheets(); i++) { + Sheet sheet = workbook.getSheetAt(i); + if (sheet.getPhysicalNumberOfRows() == 0) continue; + for (Row row : sheet) { + for (Cell cell : row) { + String value = formatter.formatCellValue(cell); + if (value != null && !value.isEmpty()) { + sb.append(value).append(" "); + } + } + sb.append("\n"); + } + sb.append("\n"); + } + return sb.toString().trim(); + } catch (Throwable e) { + log.warn("Failed to parse Excel file: {}", fileName, e); + throw new RuntimeException("Failed to parse Excel: " + fileName, e); + } + } + + private Workbook createWorkbook(InputStream inputStream, String fileName) { + try { + if (fileName.toLowerCase().endsWith(".xlsx")) { + return new XSSFWorkbook(inputStream); + } + return new HSSFWorkbook(inputStream); + } catch (Throwable e) { + log.warn("Failed to create workbook for: {}", fileName, e); + throw new RuntimeException("Failed to create workbook: " + fileName, e); + } + } + + public List splitText(String text) { + int chunkSize = aiConfigService.getEffectiveChunkSize(); + int chunkOverlap = aiConfigService.getEffectiveChunkOverlap(); + List chunks = new ArrayList<>(); + List tokens = new ArrayList<>(); + Matcher matcher = TOKEN_PATTERN.matcher(text); + while (matcher.find()) { + tokens.add(matcher.group()); + } + int tokenCount = tokens.size(); + + if (tokenCount == 0) { + return chunks; + } + + if (tokenCount <= chunkSize) { + chunks.add(text); + return chunks; + } + + int start = 0; + while (start < tokenCount) { + int end = Math.min(start + chunkSize, tokenCount); + StringBuilder chunk = new StringBuilder(); + for (int i = start; i < end; i++) { + if (i > start) chunk.append(" "); + chunk.append(tokens.get(i)); + } + chunks.add(chunk.toString()); + + if (end >= tokenCount) break; + start = end - chunkOverlap; + } + + return chunks; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/knowledge/EmbeddingService.java b/backend/ims-service/src/main/java/com/ims/service/knowledge/EmbeddingService.java new file mode 100644 index 0000000..4079d54 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/knowledge/EmbeddingService.java @@ -0,0 +1,7 @@ +package com.ims.service.knowledge; + +import java.util.List; + +public interface EmbeddingService { + List embed(String text); +} diff --git a/backend/ims-service/src/main/java/com/ims/service/knowledge/KnowledgeService.java b/backend/ims-service/src/main/java/com/ims/service/knowledge/KnowledgeService.java new file mode 100644 index 0000000..463b4ad --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/knowledge/KnowledgeService.java @@ -0,0 +1,217 @@ +package com.ims.service.knowledge; + +import com.ims.api.dto.knowledge.KnowledgeDocResponse; +import com.ims.service.entity.KnowledgeDocument; +import com.ims.service.entity.User; +import com.ims.service.repository.KnowledgeChunkRepository; +import com.ims.service.repository.KnowledgeDocumentRepository; +import io.minio.MinioClient; +import io.minio.PutObjectArgs; +import io.minio.RemoveObjectArgs; +import jakarta.persistence.EntityManager; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.UUID; + +@Service +public class KnowledgeService { + + private final KnowledgeDocumentRepository documentRepository; + private final KnowledgeChunkRepository chunkRepository; + private final MinioClient minioClient; + private final DocumentParserService documentParserService; + private final OllamaEmbeddingService embeddingService; + private final EntityManager entityManager; + private final AiConfigService aiConfigService; + + @Value("${minio.bucket}") + private String bucket; + + public KnowledgeService(KnowledgeDocumentRepository documentRepository, + KnowledgeChunkRepository chunkRepository, + MinioClient minioClient, + DocumentParserService documentParserService, + OllamaEmbeddingService embeddingService, + EntityManager entityManager, + AiConfigService aiConfigService) { + this.documentRepository = documentRepository; + this.chunkRepository = chunkRepository; + this.minioClient = minioClient; + this.documentParserService = documentParserService; + this.embeddingService = embeddingService; + this.entityManager = entityManager; + this.aiConfigService = aiConfigService; + } + + public Page list(int page, int pageSize) { + return documentRepository.findAll(PageRequest.of(page - 1, pageSize)) + .map(this::toResponse); + } + + @Transactional + public KnowledgeDocResponse upload(MultipartFile file, User user) { + if (file.isEmpty()) { + throw new RuntimeException("File is empty"); + } + long maxUploadSize = aiConfigService.getEffectiveMaxUploadSize(); + if (file.getSize() > maxUploadSize) { + throw new RuntimeException("File size exceeds the maximum allowed size of " + (maxUploadSize / (1024 * 1024)) + "MB"); + } + String fileName = file.getOriginalFilename(); + String objectKey = UUID.randomUUID() + "_" + fileName; + String fileType = getFileType(fileName); + + KnowledgeDocument doc = KnowledgeDocument.builder() + .name(fileName) + .filePath(objectKey) + .fileSize(file.getSize()) + .fileType(fileType) + .status("processing") + .uploadedBy(user) + .build(); + doc = documentRepository.save(doc); + + byte[] fileBytes; + try { + fileBytes = file.getBytes(); + } catch (Exception e) { + doc.setStatus("failed"); + doc.setErrorMessage("Failed to read file"); + documentRepository.save(doc); + return toResponse(doc); + } + + try { + try (var is = new java.io.ByteArrayInputStream(fileBytes)) { + minioClient.putObject(PutObjectArgs.builder() + .bucket(bucket) + .object(objectKey) + .stream(is, fileBytes.length, -1) + .contentType(file.getContentType()) + .build()); + } catch (Exception ignored) {} + + String text; + try (var is = new java.io.ByteArrayInputStream(fileBytes)) { + text = documentParserService.extractText(is, fileName); + } catch (Throwable e) { + text = new String(fileBytes, java.nio.charset.StandardCharsets.UTF_8); + } + if (text == null || text.trim().isEmpty()) { + text = new String(fileBytes, java.nio.charset.StandardCharsets.UTF_8); + } + List chunks = documentParserService.splitText(text); + + int chunkCount = 0; + for (String chunkText : chunks) { + List embedding = embeddingService.embed(chunkText); + String vectorStr = SearchService.vectorToPgvectorString(embedding); + entityManager.createNativeQuery( + "INSERT INTO knowledge_chunks (doc_id, content, embedding, created_at) " + + "VALUES (:docId, :content, CAST(:embedding AS vector), NOW())") + .setParameter("docId", doc.getId()) + .setParameter("content", chunkText) + .setParameter("embedding", vectorStr) + .executeUpdate(); + chunkCount++; + } + + doc.setChunkCount(chunkCount); + doc.setStatus("completed"); + documentRepository.save(doc); + } catch (Throwable e) { + doc.setStatus("failed"); + doc.setErrorMessage(e.getMessage()); + documentRepository.save(doc); + } + + return toResponse(doc); + } + + @Transactional + public void delete(Long id) { + KnowledgeDocument doc = documentRepository.findById(id).orElse(null); + if (doc == null) return; + + try { + minioClient.removeObject(RemoveObjectArgs.builder() + .bucket(bucket) + .object(doc.getFilePath()) + .build()); + } catch (Exception ignored) {} + + chunkRepository.deleteByDocId(id); + documentRepository.deleteById(id); + } + + @Transactional + public void reindex(Long id) { + KnowledgeDocument doc = documentRepository.findById(id).orElse(null); + if (doc == null) return; + + chunkRepository.deleteByDocId(id); + doc.setStatus("processing"); + doc.setChunkCount(0); + documentRepository.save(doc); + + try { + String text = documentParserService.extractText(null, doc.getName()); + List chunks = documentParserService.splitText(text); + + int chunkCount = 0; + for (String chunkText : chunks) { + List embedding = embeddingService.embed(chunkText); + String vectorStr = SearchService.vectorToPgvectorString(embedding); + entityManager.createNativeQuery( + "INSERT INTO knowledge_chunks (doc_id, content, embedding, created_at) " + + "VALUES (:docId, :content, CAST(:embedding AS vector), NOW())") + .setParameter("docId", doc.getId()) + .setParameter("content", chunkText) + .setParameter("embedding", vectorStr) + .executeUpdate(); + chunkCount++; + } + + doc.setChunkCount(chunkCount); + doc.setStatus("completed"); + doc.setErrorMessage(null); + documentRepository.save(doc); + } catch (Throwable e) { + doc.setStatus("failed"); + doc.setErrorMessage(e.getMessage()); + documentRepository.save(doc); + } + } + + private String getFileType(String fileName) { + if (fileName == null) return "unknown"; + String lower = fileName.toLowerCase(); + if (lower.endsWith(".pdf")) return "pdf"; + if (lower.endsWith(".docx")) return "docx"; + if (lower.endsWith(".doc")) return "doc"; + if (lower.endsWith(".txt")) return "txt"; + if (lower.endsWith(".md")) return "md"; + if (lower.endsWith(".xlsx") || lower.endsWith(".xls")) return "xlsx"; + return "unknown"; + } + + private KnowledgeDocResponse toResponse(KnowledgeDocument doc) { + KnowledgeDocResponse resp = new KnowledgeDocResponse(); + resp.setId(doc.getId()); + resp.setName(doc.getName()); + resp.setFileSize(doc.getFileSize()); + resp.setFileType(doc.getFileType()); + resp.setChunkCount(doc.getChunkCount()); + resp.setStatus(doc.getStatus()); + resp.setErrorMessage(doc.getErrorMessage()); + resp.setUploadedByName(doc.getUploadedBy().getUsername()); + resp.setCreatedAt(doc.getCreatedAt()); + return resp; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/knowledge/OllamaEmbeddingService.java b/backend/ims-service/src/main/java/com/ims/service/knowledge/OllamaEmbeddingService.java new file mode 100644 index 0000000..24a3fdb --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/knowledge/OllamaEmbeddingService.java @@ -0,0 +1,70 @@ +package com.ims.service.knowledge; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +@Service +public class OllamaEmbeddingService { + + private static final Logger log = LoggerFactory.getLogger(OllamaEmbeddingService.class); + + private final ObjectMapper objectMapper; + private final AiConfigService aiConfigService; + + public OllamaEmbeddingService(ObjectMapper objectMapper, AiConfigService aiConfigService) { + this.objectMapper = objectMapper; + this.aiConfigService = aiConfigService; + } + + public List embed(String text) { + String baseUrl = aiConfigService.getConfig().getOllamaBaseUrl(); + String model = aiConfigService.getConfig().getOllamaEmbeddingModel(); + + String requestBody = "{\"model\":\"" + model + "\",\"prompt\":\"" + escapeJson(text) + "\"}"; + + try { + HttpURLConnection conn = (HttpURLConnection) URI.create(baseUrl + "/api/embeddings").toURL().openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setDoOutput(true); + conn.setConnectTimeout(30000); + conn.setReadTimeout(60000); + + try (OutputStream os = conn.getOutputStream()) { + byte[] input = requestBody.getBytes(StandardCharsets.UTF_8); + os.write(input, 0, input.length); + } + + String json = new String(conn.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + + JsonNode root = objectMapper.readTree(json); + JsonNode embedding = root.get("embedding"); + if (embedding == null || !embedding.isArray()) { + throw new RuntimeException("No embedding in response: " + json); + } + + List result = new ArrayList<>(); + for (JsonNode n : embedding) { + result.add(n.floatValue()); + } + return result; + } catch (Exception e) { + log.error("Ollama embedding failed", e); + throw new RuntimeException("Ollama embedding failed: " + e.getMessage(), e); + } + } + + private String escapeJson(String s) { + return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t"); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/knowledge/RoutingEmbeddingService.java b/backend/ims-service/src/main/java/com/ims/service/knowledge/RoutingEmbeddingService.java new file mode 100644 index 0000000..2c26f96 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/knowledge/RoutingEmbeddingService.java @@ -0,0 +1,34 @@ +package com.ims.service.knowledge; + +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class RoutingEmbeddingService implements EmbeddingService { + + private final AiConfigService aiConfigService; + private final OllamaEmbeddingService ollamaEmbeddingService; + private final DeepSeekEmbeddingService deepSeekEmbeddingService; + + public RoutingEmbeddingService(AiConfigService aiConfigService, + OllamaEmbeddingService ollamaEmbeddingService, + DeepSeekEmbeddingService deepSeekEmbeddingService) { + this.aiConfigService = aiConfigService; + this.ollamaEmbeddingService = ollamaEmbeddingService; + this.deepSeekEmbeddingService = deepSeekEmbeddingService; + } + + @Override + public List embed(String text) { + String provider = aiConfigService.getConfig().getProvider(); + System.out.println("=== ROUTING to provider: " + provider); + if ("deepseek".equalsIgnoreCase(provider)) { + return deepSeekEmbeddingService.embed(text); + } + System.out.println("=== CALLING OllamaEmbeddingService.embed()"); + List result = ollamaEmbeddingService.embed(text); + System.out.println("=== Ollama result size: " + result.size()); + return result; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/knowledge/SearchLogService.java b/backend/ims-service/src/main/java/com/ims/service/knowledge/SearchLogService.java new file mode 100644 index 0000000..d675245 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/knowledge/SearchLogService.java @@ -0,0 +1,39 @@ +package com.ims.service.knowledge; + +import com.ims.service.entity.KnowledgeSearchLog; +import com.ims.service.entity.User; +import com.ims.service.repository.KnowledgeSearchLogRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; + +@Service +public class SearchLogService { + + private final KnowledgeSearchLogRepository repository; + + public SearchLogService(KnowledgeSearchLogRepository repository) { + this.repository = repository; + } + + public void log(String query, String rewrittenQuery, Integer topK, Integer totalMatches, + Integer durationMs, User user) { + KnowledgeSearchLog log = KnowledgeSearchLog.builder() + .query(query) + .rewrittenQuery(rewrittenQuery) + .topK(topK) + .totalMatches(totalMatches) + .durationMs(durationMs) + .user(user) + .build(); + repository.save(log); + } + + public Page list(int page, int pageSize) { + return repository.findAll(PageRequest.of(page - 1, pageSize)); + } + + public long todayCount() { + return repository.count(); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/knowledge/SearchService.java b/backend/ims-service/src/main/java/com/ims/service/knowledge/SearchService.java new file mode 100644 index 0000000..876fc93 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/knowledge/SearchService.java @@ -0,0 +1,84 @@ +package com.ims.service.knowledge; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.Query; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class SearchService { + + private final RoutingEmbeddingService embeddingService; + private final SearchLogService searchLogService; + private final EntityManager entityManager; + + public SearchService(RoutingEmbeddingService embeddingService, + SearchLogService searchLogService, + EntityManager entityManager) { + this.embeddingService = embeddingService; + this.searchLogService = searchLogService; + this.entityManager = entityManager; + } + + public List search(String query, int topK) { + long start = System.currentTimeMillis(); + List queryVector = embeddingService.embed(query); + + String vectorStr = vectorToPgvectorString(queryVector); + String sql = "SELECT kc.id, kc.content, kd.name AS doc_name, " + + "1 - (kc.embedding <=> '" + vectorStr + "'::vector) AS score " + + "FROM knowledge_chunks kc " + + "JOIN knowledge_documents kd ON kd.id = kc.doc_id " + + "WHERE kd.status = 'completed' " + + "ORDER BY score DESC LIMIT :topK"; + + Query nativeQuery = entityManager.createNativeQuery(sql); + nativeQuery.setParameter("topK", topK); + + @SuppressWarnings("unchecked") + List rows = nativeQuery.getResultList(); + + List results = new ArrayList<>(); + for (Object[] row : rows) { + SearchResult r = new SearchResult(); + r.setChunkId(((Number) row[0]).longValue()); + r.setContent((String) row[1]); + r.setDocName((String) row[2]); + r.setScore(row[3] != null ? ((Number) row[3]).doubleValue() : 0.0); + results.add(r); + } + + long duration = System.currentTimeMillis() - start; + searchLogService.log(query, null, topK, results.size(), (int) duration, null); + + return results; + } + + public static String vectorToPgvectorString(List vector) { + StringBuilder sb = new StringBuilder("["); + for (int i = 0; i < vector.size(); i++) { + if (i > 0) sb.append(","); + sb.append(vector.get(i)); + } + sb.append("]"); + return sb.toString(); + } + + public static class SearchResult { + private Long chunkId; + private String content; + private String docName; + private Double score; + + public Long getChunkId() { return chunkId; } + public void setChunkId(Long chunkId) { this.chunkId = chunkId; } + public String getContent() { return content; } + public void setContent(String content) { this.content = content; } + public String getDocName() { return docName; } + public void setDocName(String docName) { this.docName = docName; } + public Double getScore() { return score; } + public void setScore(Double score) { this.score = score; } + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/notification/EmailService.java b/backend/ims-service/src/main/java/com/ims/service/notification/EmailService.java new file mode 100644 index 0000000..04bc6ab --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/notification/EmailService.java @@ -0,0 +1,19 @@ +package com.ims.service.notification; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +@Service +public class EmailService { + + private static final Logger log = LoggerFactory.getLogger(EmailService.class); + + public void send(String to, String subject, String content) { + if (to == null || to.isBlank()) { + log.warn("[FAKE-MAIL] 收件人邮箱为空,跳过发送 subject={}", subject); + return; + } + log.info("[FAKE-MAIL] to={} | subject={} | content={}", to, subject, content); + } +} \ No newline at end of file diff --git a/backend/ims-service/src/main/java/com/ims/service/notification/NotificationScheduler.java b/backend/ims-service/src/main/java/com/ims/service/notification/NotificationScheduler.java new file mode 100644 index 0000000..08d7f1e --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/notification/NotificationScheduler.java @@ -0,0 +1,75 @@ +package com.ims.service.notification; + +import com.ims.service.entity.AgentPlan; +import com.ims.service.entity.Issue; +import com.ims.service.entity.User; +import com.ims.service.repository.NotificationRepository; +import jakarta.persistence.EntityManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +@Component +public class NotificationScheduler { + + private static final Logger log = LoggerFactory.getLogger(NotificationScheduler.class); + + private final EntityManager entityManager; + private final NotificationService notificationService; + private final NotificationRepository notificationRepository; + + public NotificationScheduler(EntityManager entityManager, + NotificationService notificationService, + NotificationRepository notificationRepository) { + this.entityManager = entityManager; + this.notificationService = notificationService; + this.notificationRepository = notificationRepository; + } + + @Scheduled(cron = "0 */30 * * * *") + @Transactional + public void escalateApprovalTimeouts() { + LocalDateTime cutoff = LocalDateTime.now().minusHours(48); + List plans = entityManager.createQuery( + "select p from AgentPlan p where p.approvalStatus = 'requested' and p.createdAt < :cutoff", AgentPlan.class) + .setParameter("cutoff", cutoff) + .getResultList(); + for (AgentPlan plan : plans) { + User target = plan.getCreatedBy(); + Issue issue = plan.getIssue(); + if (target == null || issue == null) continue; + if (notificationRepository.existsByTypeAndIssueIdAndCreatedAtAfter( + "approval_timeout", issue.getId(), LocalDateTime.now().minusHours(24))) continue; + notificationService.notify(target, "审批超时提醒: " + issue.getIssueNo(), + "Agent 审批请求已超过 48 小时未响应,请及时处理或升级。", + "approval_timeout", "/issues/" + issue.getId(), issue); + log.info("审批超时升级 planId={}, issueId={}", plan.getId(), issue.getId()); + } + } + + @Scheduled(cron = "0 0 */6 * * *") + @Transactional + public void remindUpcomingDeadlines() { + LocalDateTime from = LocalDateTime.now(); + LocalDateTime to = from.plusDays(3); + List issues = entityManager.createQuery( + "select i from Issue i where i.isDeleted = false and i.status <> 'closed' " + + "and i.deadline is not null and i.deadline between :from and :to", Issue.class) + .setParameter("from", from) + .setParameter("to", to) + .getResultList(); + for (Issue issue : issues) { + if (issue.getAssignee() == null) continue; + if (notificationRepository.existsByTypeAndIssueIdAndCreatedAtAfter( + "deadline_reminder", issue.getId(), LocalDateTime.now().minusDays(1))) continue; + notificationService.notify(issue.getAssignee(), "整改即将到期: " + issue.getIssueNo(), + "指摘《" + issue.getTitle() + "》整改截止日期 " + issue.getDeadline() + " 临近,请尽快处理。", + "deadline_reminder", "/issues/" + issue.getId(), issue); + } + } +} \ No newline at end of file diff --git a/backend/ims-service/src/main/java/com/ims/service/notification/NotificationService.java b/backend/ims-service/src/main/java/com/ims/service/notification/NotificationService.java new file mode 100644 index 0000000..cdf741d --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/notification/NotificationService.java @@ -0,0 +1,119 @@ +package com.ims.service.notification; + +import com.ims.api.dto.notification.NotificationResponse; +import com.ims.common.dto.PageResult; +import com.ims.service.entity.Issue; +import com.ims.service.entity.Notification; +import com.ims.service.entity.Role; +import com.ims.service.entity.User; +import com.ims.service.entity.UserRole; +import com.ims.service.repository.NotificationRepository; +import com.ims.service.repository.RoleRepository; +import com.ims.service.repository.UserRepository; +import com.ims.service.repository.UserRoleRepository; +import jakarta.persistence.EntityManager; +import jakarta.persistence.TypedQuery; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class NotificationService { + + private final NotificationRepository notificationRepository; + private final EntityManager entityManager; + private final EmailService emailService; + private final RoleRepository roleRepository; + private final UserRoleRepository userRoleRepository; + private final UserRepository userRepository; + + public NotificationService(NotificationRepository notificationRepository, EntityManager entityManager, + EmailService emailService, RoleRepository roleRepository, + UserRoleRepository userRoleRepository, UserRepository userRepository) { + this.notificationRepository = notificationRepository; + this.entityManager = entityManager; + this.emailService = emailService; + this.roleRepository = roleRepository; + this.userRoleRepository = userRoleRepository; + this.userRepository = userRepository; + } + + @Transactional(readOnly = true) + public PageResult list(Long userId, int page, int pageSize) { + int p = Math.max(1, page); + int ps = pageSize > 0 ? pageSize : 20; + TypedQuery query = entityManager.createQuery( + "select n from Notification n where n.user.id = :userId order by n.createdAt desc", Notification.class) + .setParameter("userId", userId) + .setFirstResult((p - 1) * ps) + .setMaxResults(ps); + long total = ((Number) entityManager.createQuery( + "select count(n) from Notification n where n.user.id = :userId") + .setParameter("userId", userId).getSingleResult()).longValue(); + List items = query.getResultList().stream().map(this::toResponse).toList(); + return new PageResult<>(items, total, p, ps); + } + + @Transactional(readOnly = true) + public long unreadCount(Long userId) { + return notificationRepository.countByUserIdAndIsRead(userId, false); + } + + @Transactional + public void markRead(Long id, Long userId) { + Notification n = notificationRepository.findById(id).orElse(null); + if (n == null || !n.getUser().getId().equals(userId)) return; + n.setIsRead(true); + notificationRepository.save(n); + } + + @Transactional + public int markAllRead(Long userId) { + return notificationRepository.markAllRead(userId); + } + + @Transactional + public void notify(User user, String title, String content, String type, String link, Issue issue) { + if (user == null) return; + Notification n = Notification.builder() + .user(user) + .title(title) + .content(content) + .type(type) + .link(link) + .isRead(false) + .issue(issue) + .build(); + notificationRepository.save(n); + emailService.send(user.getEmail(), title, content); + } + + @Transactional + public void notifyAdmins(String title, String content) { + Set adminRoleIds = roleRepository.findByNameIn(List.of("超级管理员", "部门管理员")).stream() + .map(Role::getId) + .collect(Collectors.toSet()); + if (adminRoleIds.isEmpty()) return; + Set adminUserIds = userRoleRepository.findByRoleIdIn(adminRoleIds).stream() + .map(UserRole::getUserId) + .collect(Collectors.toSet()); + for (Long userId : adminUserIds) { + userRepository.findById(userId).ifPresent(user -> notify(user, title, content, "admin", null, null)); + } + } + + private NotificationResponse toResponse(Notification n) { + NotificationResponse resp = new NotificationResponse(); + resp.setId(n.getId()); + resp.setTitle(n.getTitle()); + resp.setContent(n.getContent()); + resp.setType(n.getType()); + resp.setLink(n.getLink()); + resp.setIsRead(n.getIsRead()); + resp.setCreatedAt(n.getCreatedAt()); + return resp; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/prompt/PromptService.java b/backend/ims-service/src/main/java/com/ims/service/prompt/PromptService.java new file mode 100644 index 0000000..cc7d3e7 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/prompt/PromptService.java @@ -0,0 +1,220 @@ +package com.ims.service.prompt; + +import com.ims.api.dto.prompt.PromptRenderLogResponse; +import com.ims.api.dto.prompt.PromptStatsResponse; +import com.ims.api.dto.prompt.PromptTemplateRequest; +import com.ims.api.dto.prompt.PromptTemplateResponse; +import com.ims.api.dto.prompt.PromptTestRequest; +import com.ims.common.exception.BusinessException; +import com.ims.service.ai.PromptTemplateEngine; +import com.ims.service.entity.PromptRenderLog; +import com.ims.service.entity.PromptTemplate; +import com.ims.service.entity.PromptTemplateVersion; +import com.ims.service.notification.NotificationService; +import com.ims.service.repository.PromptRenderLogRepository; +import com.ims.service.repository.PromptTemplateRepository; +import com.ims.service.repository.PromptTemplateVersionRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Service +public class PromptService { + + private final PromptTemplateRepository templateRepository; + private final PromptTemplateVersionRepository versionRepository; + private final PromptRenderLogRepository renderLogRepository; + private final PromptTemplateEngine promptEngine; + private final NotificationService notificationService; + + public PromptService(PromptTemplateRepository templateRepository, + PromptTemplateVersionRepository versionRepository, + PromptRenderLogRepository renderLogRepository, + PromptTemplateEngine promptEngine, + NotificationService notificationService) { + this.templateRepository = templateRepository; + this.versionRepository = versionRepository; + this.renderLogRepository = renderLogRepository; + this.promptEngine = promptEngine; + this.notificationService = notificationService; + } + + public Page list(int page, int pageSize) { + Pageable pageable = PageRequest.of(Math.max(page - 1, 0), pageSize); + return templateRepository.findAllByIsActiveTrue(pageable).map(this::toResponse); + } + + public PromptTemplateResponse detail(String templateId) { + PromptTemplate template = templateRepository.findByTemplateIdAndIsActiveTrue(templateId) + .orElseThrow(() -> new BusinessException("模板不存在: " + templateId)); + return toResponse(template); + } + + public PromptTemplateResponse create(PromptTemplateRequest request) { + if (templateRepository.findByTemplateIdAndIsActiveTrue(request.getTemplateId()).isPresent()) { + throw new BusinessException("模板ID已存在: " + request.getTemplateId()); + } + PromptTemplate template = new PromptTemplate(); + template.setTemplateId(request.getTemplateId()); + template.setName(request.getName()); + template.setCategory(request.getCategory()); + template.setVersion(1); + template.setContent(request.getContent()); + template.setVariables(request.getVariables()); + template.setOutputSchema(request.getOutputSchema()); + template.setIsActive(true); + templateRepository.save(template); + + saveVersion(template, "初始版本"); + return toResponse(template); + } + + public PromptTemplateResponse update(String templateId, PromptTemplateRequest request) { + PromptTemplate template = templateRepository.findByTemplateIdAndIsActiveTrue(templateId) + .orElseThrow(() -> new BusinessException("模板不存在: " + templateId)); + + int nextVersion = template.getVersion() + 1; + template.setVersion(nextVersion); + template.setName(request.getName()); + template.setContent(request.getContent()); + template.setVariables(request.getVariables()); + template.setOutputSchema(request.getOutputSchema()); + templateRepository.save(template); + + saveVersion(template, "更新至版本 " + nextVersion); + notificationService.notifyAdmins("Prompt模板更新: " + templateId, + "模板 " + templateId + " 已更新至版本 v" + nextVersion); + return toResponse(template); + } + + public PromptTemplateResponse rollback(String templateId, int version) { + PromptTemplate template = templateRepository.findByTemplateIdAndIsActiveTrue(templateId) + .orElseThrow(() -> new BusinessException("模板不存在: " + templateId)); + PromptTemplateVersion target = versionRepository.findByTemplateIdOrderByVersionDesc(templateId).stream() + .filter(v -> v.getVersion() == version) + .findFirst() + .orElseThrow(() -> new BusinessException("版本不存在: " + version)); + + int nextVersion = template.getVersion() + 1; + template.setVersion(nextVersion); + template.setContent(target.getContent()); + templateRepository.save(template); + + saveVersion(template, "回滚至版本 " + version); + notificationService.notifyAdmins("Prompt模板更新: " + templateId, + "模板 " + templateId + " 已回滚至版本 v" + version); + return toResponse(template); + } + + public Map test(PromptTestRequest request) { + PromptTemplate template = templateRepository.findByTemplateIdAndIsActiveTrue(request.getTemplateId()) + .orElseThrow(() -> new BusinessException("模板不存在: " + request.getTemplateId())); + Map vars = request.getVariables() == null ? Map.of() : request.getVariables(); + String rendered = promptEngine.render(template, vars); + + Map result = new LinkedHashMap<>(); + result.put("templateId", template.getTemplateId()); + result.put("version", template.getVersion()); + result.put("rendered", rendered); + return result; + } + + public List> versions(String templateId) { + List> result = new ArrayList<>(); + for (PromptTemplateVersion v : versionRepository.findByTemplateIdOrderByVersionDesc(templateId)) { + Map m = new LinkedHashMap<>(); + m.put("id", v.getId()); + m.put("templateId", v.getTemplateId()); + m.put("version", v.getVersion()); + m.put("content", v.getContent()); + m.put("changeLog", v.getChangeLog()); + m.put("createdAt", v.getCreatedAt() == null ? null : v.getCreatedAt().toString()); + result.add(m); + } + return result; + } + + public Page logs(int page, int pageSize) { + Pageable pageable = PageRequest.of(Math.max(page - 1, 0), pageSize); + return renderLogRepository.findAllByOrderByCreatedAtDesc(pageable).map(this::toLogResponse); + } + + public List stats() { + Map agg = new HashMap<>(); + for (Object[] row : renderLogRepository.aggregateByTemplate()) { + String templateId = (String) row[0]; + long count = ((Number) row[1]).longValue(); + double avgMs = row[2] == null ? 0 : ((Number) row[2]).doubleValue(); + agg.put(templateId, new long[]{count, Double.doubleToLongBits(avgMs)}); + } + + List result = new ArrayList<>(); + for (PromptTemplate template : templateRepository.findAllByIsActiveTrue()) { + PromptStatsResponse stat = new PromptStatsResponse(); + stat.setTemplateId(template.getTemplateId()); + stat.setName(template.getName()); + stat.setCategory(template.getCategory()); + stat.setLatestVersion(template.getVersion()); + long[] data = agg.get(template.getTemplateId()); + if (data != null) { + stat.setUseCount(data[0]); + stat.setAvgExecutionTimeMs(Double.longBitsToDouble(data[1])); + } else { + stat.setUseCount(0L); + stat.setAvgExecutionTimeMs(0.0); + } + result.add(stat); + } + return result; + } + + private void saveVersion(PromptTemplate template, String changeLog) { + PromptTemplateVersion version = new PromptTemplateVersion(); + version.setTemplateId(template.getTemplateId()); + version.setVersion(template.getVersion()); + version.setContent(template.getContent()); + version.setChangeLog(changeLog); + versionRepository.save(version); + } + + private PromptTemplateResponse toResponse(PromptTemplate t) { + PromptTemplateResponse resp = new PromptTemplateResponse(); + resp.setId(t.getId()); + resp.setTemplateId(t.getTemplateId()); + resp.setName(t.getName()); + resp.setCategory(t.getCategory()); + resp.setVersion(t.getVersion()); + resp.setContent(t.getContent()); + resp.setVariables(t.getVariables()); + resp.setOutputSchema(t.getOutputSchema()); + resp.setIsActive(t.getIsActive()); + resp.setIsDefault(t.getIsDefault()); + resp.setCreatedAt(t.getCreatedAt()); + resp.setUpdatedAt(t.getUpdatedAt()); + return resp; + } + + private PromptRenderLogResponse toLogResponse(PromptRenderLog log) { + PromptRenderLogResponse resp = new PromptRenderLogResponse(); + resp.setId(log.getId()); + resp.setRequestId(log.getRequestId()); + resp.setTemplateId(log.getTemplateId()); + resp.setTemplateVersion(log.getTemplateVersion()); + resp.setRenderedPrompt(log.getRenderedPrompt()); + resp.setVariablesUsed(log.getVariablesUsed()); + resp.setTokensInput(log.getTokensInput()); + resp.setTokensOutput(log.getTokensOutput()); + resp.setExecutionTimeMs(log.getExecutionTimeMs()); + resp.setLlmModel(log.getLlmModel()); + resp.setModelProvider(log.getModelProvider()); + resp.setCreatedAt(log.getCreatedAt()); + return resp; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/prompt/PromptServiceImpl.java b/backend/ims-service/src/main/java/com/ims/service/prompt/PromptServiceImpl.java new file mode 100644 index 0000000..4b15d59 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/prompt/PromptServiceImpl.java @@ -0,0 +1,294 @@ +package com.ims.service.prompt; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ims.api.dto.prompt.PromptLogResponse; +import com.ims.api.dto.prompt.PromptStatsResponse; +import com.ims.api.dto.prompt.PromptTemplateRequest; +import com.ims.api.dto.prompt.PromptTemplateResponse; +import com.ims.api.dto.prompt.PromptTestRequest; +import com.ims.api.dto.prompt.PromptTestResponse; +import com.ims.api.dto.prompt.PromptVersionResponse; +import com.ims.api.service.prompt.PromptService; +import com.ims.common.constant.ResultCode; +import com.ims.common.dto.PageResult; +import com.ims.common.exception.BusinessException; +import com.ims.service.entity.PromptRenderLog; +import com.ims.service.entity.PromptTemplate; +import com.ims.service.entity.PromptTemplateVersion; +import com.ims.service.entity.User; +import com.ims.service.repository.PromptRenderLogRepository; +import com.ims.service.repository.PromptTemplateRepository; +import com.ims.service.repository.PromptTemplateVersionRepository; +import com.ims.service.repository.UserRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Service +public class PromptServiceImpl implements PromptService { + + private static final Pattern VAR_PATTERN = Pattern.compile("\\{\\{\\s*(\\w+)\\s*}}"); + + private final PromptTemplateRepository templateRepository; + private final PromptTemplateVersionRepository versionRepository; + private final PromptRenderLogRepository renderLogRepository; + private final UserRepository userRepository; + private final ObjectMapper objectMapper; + + public PromptServiceImpl(PromptTemplateRepository templateRepository, + PromptTemplateVersionRepository versionRepository, + PromptRenderLogRepository renderLogRepository, + UserRepository userRepository, + ObjectMapper objectMapper) { + this.templateRepository = templateRepository; + this.versionRepository = versionRepository; + this.renderLogRepository = renderLogRepository; + this.userRepository = userRepository; + this.objectMapper = objectMapper; + } + + @Override + @Transactional(readOnly = true) + public PageResult list(int page, int pageSize, String category, String keyword) { + String cat = category == null ? null : category.toLowerCase(Locale.ROOT); + String kw = keyword == null ? null : keyword.toLowerCase(Locale.ROOT); + List filtered = templateRepository.findAll().stream() + .filter(t -> cat == null || cat.isBlank() + || (t.getCategory() != null && t.getCategory().toLowerCase(Locale.ROOT).contains(cat))) + .filter(t -> kw == null || kw.isBlank() + || (t.getName() != null && t.getName().toLowerCase(Locale.ROOT).contains(kw)) + || (t.getTemplateId() != null && t.getTemplateId().toLowerCase(Locale.ROOT).contains(kw))) + .sorted(Comparator.comparing(PromptTemplate::getUpdatedAt, Comparator.nullsLast(Comparator.reverseOrder()))) + .toList(); + int total = filtered.size(); + int from = Math.min((page - 1) * pageSize, total); + int to = Math.min(from + pageSize, total); + List items = filtered.subList(from, to).stream().map(this::toResponse).toList(); + return new PageResult<>(items, total, page, pageSize); + } + + @Override + @Transactional(readOnly = true) + public PromptTemplateResponse detail(String templateId) { + PromptTemplate template = findByTemplateId(templateId); + return toResponse(template); + } + + @Override + @Transactional + public PromptTemplateResponse create(PromptTemplateRequest request, String operatorUsername) { + if (templateRepository.findAll().stream().anyMatch(t -> t.getTemplateId().equals(request.getTemplateId()))) { + throw new BusinessException(ResultCode.BAD_REQUEST.getCode(), "模板ID已存在"); + } + PromptTemplate template = PromptTemplate.builder() + .templateId(request.getTemplateId()) + .name(request.getName()) + .category(request.getCategory()) + .content(request.getContent()) + .variables(request.getVariables()) + .outputSchema(request.getOutputSchema()) + .version(1) + .isActive(true) + .isDefault(false) + .createdBy(resolveUser(operatorUsername)) + .build(); + template = templateRepository.save(template); + saveVersion(template.getTemplateId(), template.getVersion(), template.getContent(), "初始版本", operatorUsername); + return toResponse(template); + } + + @Override + @Transactional + public PromptTemplateResponse update(String templateId, PromptTemplateRequest request, String operatorUsername) { + PromptTemplate template = findByTemplateId(templateId); + int nextVersion = template.getVersion() + 1; + if (request.getName() != null && !request.getName().isBlank()) { + template.setName(request.getName()); + } + if (request.getCategory() != null && !request.getCategory().isBlank()) { + template.setCategory(request.getCategory()); + } + if (request.getContent() != null && !request.getContent().isBlank()) { + template.setContent(request.getContent()); + } + if (request.getVariables() != null) { + template.setVariables(request.getVariables()); + } + if (request.getOutputSchema() != null) { + template.setOutputSchema(request.getOutputSchema()); + } + template.setVersion(nextVersion); + template = templateRepository.save(template); + saveVersion(templateId, nextVersion, template.getContent(), "更新到 v" + nextVersion, operatorUsername); + return toResponse(template); + } + + @Override + @Transactional + public void rollback(String templateId, int version, String operatorUsername) { + PromptTemplate template = findByTemplateId(templateId); + PromptTemplateVersion target = versionRepository.findByTemplateIdOrderByVersionDesc(templateId).stream() + .filter(v -> v.getVersion() == version) + .findFirst() + .orElseThrow(() -> new BusinessException(ResultCode.BAD_REQUEST.getCode(), "版本不存在")); + int nextVersion = template.getVersion() + 1; + template.setContent(target.getContent()); + template.setVersion(nextVersion); + templateRepository.save(template); + saveVersion(templateId, nextVersion, target.getContent(), "回滚到 v" + version, operatorUsername); + } + + @Override + @Transactional + public PromptTestResponse test(PromptTestRequest request, String operatorUsername) { + PromptTemplate template = findByTemplateId(request.getTemplateId()); + long start = System.currentTimeMillis(); + String rendered = render(template.getContent(), request.getVariables()); + long cost = System.currentTimeMillis() - start; + PromptRenderLog log = PromptRenderLog.builder() + .requestId(UUID.randomUUID().toString().replace("-", "")) + .templateId(template.getTemplateId()) + .templateVersion(template.getVersion()) + .renderedPrompt(rendered) + .variablesUsed(toJson(request.getVariables())) + .executionTimeMs((int) cost) + .build(); + renderLogRepository.save(log); + PromptTestResponse response = new PromptTestResponse(); + response.setTemplateId(template.getTemplateId()); + response.setTemplateVersion(template.getVersion()); + response.setRenderedPrompt(rendered); + response.setExecutionTimeMs((int) cost); + response.setTokenEstimate(rendered.length() / 2); + return response; + } + + @Override + @Transactional(readOnly = true) + public List versions(String templateId) { + return versionRepository.findByTemplateIdOrderByVersionDesc(templateId).stream() + .map(v -> { + PromptVersionResponse r = new PromptVersionResponse(); + r.setId(v.getId()); + r.setTemplateId(v.getTemplateId()); + r.setVersion(v.getVersion()); + r.setContent(v.getContent()); + r.setChangeLog(v.getChangeLog()); + r.setCreatedBy(v.getCreatedBy() == null ? "" : v.getCreatedBy().getUsername()); + r.setCreatedAt(v.getCreatedAt()); + return r; + }).toList(); + } + + @Override + @Transactional(readOnly = true) + public PageResult logs(int page, int pageSize) { + List all = renderLogRepository.findAll().stream() + .sorted(Comparator.comparing(PromptRenderLog::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder()))) + .map(l -> { + PromptLogResponse r = new PromptLogResponse(); + r.setId(l.getId()); + r.setRequestId(l.getRequestId()); + r.setTemplateId(l.getTemplateId()); + r.setTemplateVersion(l.getTemplateVersion()); + r.setRenderedPrompt(l.getRenderedPrompt()); + r.setExecutionTimeMs(l.getExecutionTimeMs()); + r.setLlmModel(l.getLlmModel()); + r.setModelProvider(l.getModelProvider()); + r.setCreatedAt(l.getCreatedAt()); + return r; + }).toList(); + int total = all.size(); + int from = Math.min((page - 1) * pageSize, total); + int to = Math.min(from + pageSize, total); + return new PageResult<>(all.subList(from, to), total, page, pageSize); + } + + @Override + @Transactional(readOnly = true) + public PromptStatsResponse stats() { + PromptStatsResponse stats = new PromptStatsResponse(); + stats.setTotalTemplates(templateRepository.count()); + stats.setActiveTemplates(templateRepository.findAll().stream().filter(t -> Boolean.TRUE.equals(t.getIsActive())).count()); + stats.setTotalVersions(versionRepository.count()); + stats.setTotalRenders(renderLogRepository.count()); + return stats; + } + + private String toJson(Object value) { + if (value == null) { + return null; + } + try { + return objectMapper.writeValueAsString(value); + } catch (JsonProcessingException e) { + return null; + } + } + + private String render(String content, Map variables) { + if (content == null || variables == null) { + return content; + } + Matcher matcher = VAR_PATTERN.matcher(content); + StringBuilder sb = new StringBuilder(); + while (matcher.find()) { + Object value = variables.get(matcher.group(1)); + matcher.appendReplacement(sb, Matcher.quoteReplacement(value == null ? "" : String.valueOf(value))); + } + matcher.appendTail(sb); + return sb.toString(); + } + + private PromptTemplate findByTemplateId(String templateId) { + return templateRepository.findAll().stream() + .filter(t -> t.getTemplateId().equals(templateId)) + .findFirst() + .orElseThrow(() -> new BusinessException(ResultCode.NOT_FOUND.getCode(), "模板不存在")); + } + + private void saveVersion(String templateId, int version, String content, String changeLog, String operatorUsername) { + PromptTemplateVersion history = PromptTemplateVersion.builder() + .templateId(templateId) + .version(version) + .content(content) + .changeLog(changeLog) + .createdBy(resolveUser(operatorUsername)) + .build(); + versionRepository.save(history); + } + + private User resolveUser(String operatorUsername) { + if (operatorUsername == null) { + return null; + } + return userRepository.findByUsername(operatorUsername) + .or(() -> userRepository.findByUserid(operatorUsername)) + .orElse(null); + } + + private PromptTemplateResponse toResponse(PromptTemplate template) { + PromptTemplateResponse r = new PromptTemplateResponse(); + r.setId(template.getId()); + r.setTemplateId(template.getTemplateId()); + r.setName(template.getName()); + r.setCategory(template.getCategory()); + r.setVersion(template.getVersion()); + r.setContent(template.getContent()); + r.setVariables(template.getVariables()); + r.setOutputSchema(template.getOutputSchema()); + r.setIsActive(template.getIsActive()); + r.setIsDefault(template.getIsDefault()); + r.setCreatedAt(template.getCreatedAt()); + r.setUpdatedAt(template.getUpdatedAt()); + return r; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/AgentMemoryRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/AgentMemoryRepository.java new file mode 100644 index 0000000..2597967 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/AgentMemoryRepository.java @@ -0,0 +1,16 @@ +package com.ims.service.repository; + +import com.ims.service.entity.AgentMemory; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import java.util.List; + +@Repository +public interface AgentMemoryRepository extends JpaRepository { + + @Query(value = "SELECT * FROM agent_memories ORDER BY embedding <=> CAST(:vector AS vector) LIMIT :limit", + nativeQuery = true) + List findSimilar(@Param("vector") String vector, @Param("limit") int limit); +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/AgentPlanRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/AgentPlanRepository.java new file mode 100644 index 0000000..bc687c3 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/AgentPlanRepository.java @@ -0,0 +1,21 @@ +package com.ims.service.repository; + +import com.ims.service.entity.AgentPlan; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public interface AgentPlanRepository extends JpaRepository { + List findByIssueIdOrderByCreatedAtDesc(Long issueId); + + Page findByApprovalStatusOrderByCreatedAtDesc(String approvalStatus, Pageable pageable); + + long countByApprovalStatus(String approvalStatus); + + long countByCreatedAtAfter(LocalDateTime time); +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/AiAnalysisRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/AiAnalysisRepository.java new file mode 100644 index 0000000..3ed4e60 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/AiAnalysisRepository.java @@ -0,0 +1,69 @@ +package com.ims.service.repository; + +import com.ims.service.entity.AiAnalysis; +import jakarta.persistence.criteria.Predicate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@Repository +public interface AiAnalysisRepository extends JpaRepository, JpaSpecificationExecutor { + List findByIssueIdOrderByCreatedAtDesc(Long issueId); + Page findAllByOrderByCreatedAtDesc(Pageable pageable); + + @Query("SELECT a FROM AiAnalysis a WHERE a.status IN :statuses ORDER BY a.createdAt DESC") + List findByStatusIn(@Param("statuses") Collection statuses, Pageable pageable); + + @Query("SELECT COUNT(DISTINCT a.issue.id) FROM AiAnalysis a WHERE a.status = 'completed'") + long countAnalyzedIssues(); + + @Query("SELECT a.status, COUNT(a) FROM AiAnalysis a GROUP BY a.status") + List countByStatusGroup(); + + @Query("SELECT a.category, COUNT(a) FROM AiAnalysis a WHERE a.status = 'completed' AND a.category IS NOT NULL GROUP BY a.category") + List countByCategoryGroup(); + + @Query("SELECT a.issue.department.name, COUNT(a) FROM AiAnalysis a WHERE a.status = 'completed' AND a.issue.department IS NOT NULL GROUP BY a.issue.department.name") + List countByDepartmentGroup(); + + @Query("SELECT FUNCTION('DATE', a.createdAt) as d, a.status, COUNT(a) FROM AiAnalysis a WHERE a.createdAt >= :since GROUP BY FUNCTION('DATE', a.createdAt), a.status ORDER BY d") + List dailyTrendGroup(@Param("since") LocalDateTime since); + + default Page search(Long id, Long issueId, Long departmentId, String status, + LocalDateTime start, LocalDateTime end, Pageable pageable) { + Specification spec = (root, query, cb) -> { + List predicates = new ArrayList<>(); + if (id != null) { + predicates.add(cb.equal(root.get("id"), id)); + } + if (issueId != null) { + predicates.add(cb.equal(root.get("issue").get("id"), issueId)); + } + if (departmentId != null) { + predicates.add(cb.equal(root.get("issue").get("department").get("id"), departmentId)); + } + if (status != null) { + predicates.add(cb.equal(root.get("status"), status)); + } + if (start != null) { + predicates.add(cb.greaterThanOrEqualTo(root.get("createdAt"), start)); + } + if (end != null) { + predicates.add(cb.lessThan(root.get("createdAt"), end)); + } + query.orderBy(cb.desc(root.get("createdAt"))); + return cb.and(predicates.toArray(new Predicate[0])); + }; + return findAll(spec, pageable); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/AiCallLogRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/AiCallLogRepository.java new file mode 100644 index 0000000..1a187ba --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/AiCallLogRepository.java @@ -0,0 +1,10 @@ +package com.ims.service.repository; + +import com.ims.service.entity.AiCallLog; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface AiCallLogRepository extends JpaRepository { + List findTop20ByOrderByCreatedAtDesc(); +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/AiFeedbackRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/AiFeedbackRepository.java new file mode 100644 index 0000000..d028bb0 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/AiFeedbackRepository.java @@ -0,0 +1,9 @@ +package com.ims.service.repository; + +import com.ims.service.entity.AiFeedback; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AiFeedbackRepository extends JpaRepository { +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/AttachmentRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/AttachmentRepository.java new file mode 100644 index 0000000..bd32dd5 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/AttachmentRepository.java @@ -0,0 +1,11 @@ +package com.ims.service.repository; + +import com.ims.service.entity.Attachment; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.List; + +@Repository +public interface AttachmentRepository extends JpaRepository { + List findByIssueId(Long issueId); +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/DepartmentRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/DepartmentRepository.java new file mode 100644 index 0000000..803344b --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/DepartmentRepository.java @@ -0,0 +1,11 @@ +package com.ims.service.repository; + +import com.ims.service.entity.Department; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.List; + +@Repository +public interface DepartmentRepository extends JpaRepository { + List findByParentIdOrderBySortOrder(Long parentId); +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/ImportRecordRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/ImportRecordRepository.java new file mode 100644 index 0000000..e7f98d9 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/ImportRecordRepository.java @@ -0,0 +1,9 @@ +package com.ims.service.repository; + +import com.ims.service.entity.ImportRecord; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ImportRecordRepository extends JpaRepository { +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/IssueLogRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/IssueLogRepository.java new file mode 100644 index 0000000..d827477 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/IssueLogRepository.java @@ -0,0 +1,11 @@ +package com.ims.service.repository; + +import com.ims.service.entity.IssueLog; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.List; + +@Repository +public interface IssueLogRepository extends JpaRepository { + List findByIssueIdOrderByCreatedAtDesc(Long issueId); +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/IssueRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/IssueRepository.java new file mode 100644 index 0000000..9d3a55e --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/IssueRepository.java @@ -0,0 +1,56 @@ +package com.ims.service.repository; + +import com.ims.service.entity.Issue; +import jakarta.persistence.criteria.Predicate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Repository +public interface IssueRepository extends JpaRepository, JpaSpecificationExecutor { + Optional findByIssueNo(String issueNo); + + List findByIsDeletedFalseAndStatusNotAndDeadlineBefore(String status, LocalDateTime now); + + List findByIsDeletedFalseAndStatusAndAssigneeIsNull(String status); + + default Page search(String status, String phase, Long departmentId, String keyword, + LocalDateTime start, LocalDateTime end, Pageable pageable) { + Specification spec = (root, query, cb) -> { + List predicates = new ArrayList<>(); + predicates.add(cb.isFalse(root.get("isDeleted"))); + if (status != null) { + predicates.add(cb.equal(root.get("status"), status)); + } + if (phase != null) { + predicates.add(cb.equal(root.get("phase"), phase)); + } + if (departmentId != null) { + predicates.add(cb.equal(root.get("department").get("id"), departmentId)); + } + if (keyword != null && !keyword.isEmpty()) { + String like = "%" + keyword + "%"; + predicates.add(cb.or( + cb.like(root.get("issueNo"), like), + cb.like(root.get("title"), like))); + } + if (start != null) { + predicates.add(cb.greaterThanOrEqualTo(root.get("createdAt"), start)); + } + if (end != null) { + predicates.add(cb.lessThan(root.get("createdAt"), end)); + } + query.orderBy(cb.desc(root.get("createdAt"))); + return cb.and(predicates.toArray(new Predicate[0])); + }; + return findAll(spec, pageable); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/KnowledgeChunkRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/KnowledgeChunkRepository.java new file mode 100644 index 0000000..ba59877 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/KnowledgeChunkRepository.java @@ -0,0 +1,12 @@ +package com.ims.service.repository; + +import com.ims.service.entity.KnowledgeChunk; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.List; + +@Repository +public interface KnowledgeChunkRepository extends JpaRepository { + List findByDocId(Long docId); + void deleteByDocId(Long docId); +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/KnowledgeDocumentRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/KnowledgeDocumentRepository.java new file mode 100644 index 0000000..32ff2f8 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/KnowledgeDocumentRepository.java @@ -0,0 +1,9 @@ +package com.ims.service.repository; + +import com.ims.service.entity.KnowledgeDocument; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface KnowledgeDocumentRepository extends JpaRepository { +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/KnowledgeSearchLogRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/KnowledgeSearchLogRepository.java new file mode 100644 index 0000000..0189450 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/KnowledgeSearchLogRepository.java @@ -0,0 +1,9 @@ +package com.ims.service.repository; + +import com.ims.service.entity.KnowledgeSearchLog; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface KnowledgeSearchLogRepository extends JpaRepository { +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/NotificationRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/NotificationRepository.java new file mode 100644 index 0000000..9946e7d --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/NotificationRepository.java @@ -0,0 +1,22 @@ +package com.ims.service.repository; + +import com.ims.service.entity.Notification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public interface NotificationRepository extends JpaRepository { + List findByUserIdAndIsReadOrderByCreatedAtDesc(Long userId, Boolean isRead); + long countByUserIdAndIsRead(Long userId, Boolean isRead); + + boolean existsByTypeAndIssueIdAndCreatedAtAfter(String type, Long issueId, LocalDateTime after); + + @Modifying + @Query("update Notification n set n.isRead = true where n.user.id = :userId and n.isRead = false") + int markAllRead(@Param("userId") Long userId); +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/PermissionRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/PermissionRepository.java new file mode 100644 index 0000000..fc8b93b --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/PermissionRepository.java @@ -0,0 +1,9 @@ +package com.ims.service.repository; + +import com.ims.service.entity.Permission; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PermissionRepository extends JpaRepository { +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/PhaseRuleRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/PhaseRuleRepository.java new file mode 100644 index 0000000..a1a049b --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/PhaseRuleRepository.java @@ -0,0 +1,11 @@ +package com.ims.service.repository; + +import com.ims.service.entity.PhaseRule; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.List; + +@Repository +public interface PhaseRuleRepository extends JpaRepository { + List findByActiveTrueOrderBySortOrderAsc(); +} \ No newline at end of file diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/PromptRenderLogRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/PromptRenderLogRepository.java new file mode 100644 index 0000000..b60886f --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/PromptRenderLogRepository.java @@ -0,0 +1,20 @@ +package com.ims.service.repository; + +import com.ims.service.entity.PromptRenderLog; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface PromptRenderLogRepository extends JpaRepository { + Page findAllByOrderByCreatedAtDesc(Pageable pageable); + + @Query(value = "SELECT template_id, COUNT(*), AVG(execution_time_ms) " + + "FROM prompt_render_logs GROUP BY template_id ORDER BY COUNT(*) DESC", + nativeQuery = true) + List aggregateByTemplate(); +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/PromptTemplateRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/PromptTemplateRepository.java new file mode 100644 index 0000000..1bdadef --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/PromptTemplateRepository.java @@ -0,0 +1,17 @@ +package com.ims.service.repository; + +import com.ims.service.entity.PromptTemplate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.List; +import java.util.Optional; + +@Repository +public interface PromptTemplateRepository extends JpaRepository { + Optional findByTemplateIdAndIsActiveTrue(String templateId); + List findByCategoryAndIsActiveTrue(String category); + List findAllByIsActiveTrue(); + Page findAllByIsActiveTrue(Pageable pageable); +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/PromptTemplateVersionRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/PromptTemplateVersionRepository.java new file mode 100644 index 0000000..ba7d0de --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/PromptTemplateVersionRepository.java @@ -0,0 +1,11 @@ +package com.ims.service.repository; + +import com.ims.service.entity.PromptTemplateVersion; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.List; + +@Repository +public interface PromptTemplateVersionRepository extends JpaRepository { + List findByTemplateIdOrderByVersionDesc(String templateId); +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/RolePermissionRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/RolePermissionRepository.java new file mode 100644 index 0000000..587af50 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/RolePermissionRepository.java @@ -0,0 +1,14 @@ +package com.ims.service.repository; + +import com.ims.service.entity.RolePermission; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.Collection; +import java.util.List; + +@Repository +public interface RolePermissionRepository extends JpaRepository { + List findByRoleId(Long roleId); + + List findByRoleIdIn(Collection roleIds); +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/RoleRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/RoleRepository.java new file mode 100644 index 0000000..8e2c5c0 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/RoleRepository.java @@ -0,0 +1,13 @@ +package com.ims.service.repository; + +import com.ims.service.entity.Role; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Collection; +import java.util.List; + +@Repository +public interface RoleRepository extends JpaRepository { + List findByNameIn(Collection names); +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/TaskExecutionRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/TaskExecutionRepository.java new file mode 100644 index 0000000..61b6b27 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/TaskExecutionRepository.java @@ -0,0 +1,11 @@ +package com.ims.service.repository; + +import com.ims.service.entity.TaskExecution; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.Optional; + +@Repository +public interface TaskExecutionRepository extends JpaRepository { + Optional findByTaskId(String taskId); +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/ToolExecutionRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/ToolExecutionRepository.java new file mode 100644 index 0000000..4f13780 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/ToolExecutionRepository.java @@ -0,0 +1,30 @@ +package com.ims.service.repository; + +import com.ims.service.entity.ToolExecution; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public interface ToolExecutionRepository extends JpaRepository { + List findByPlanId(Long planId); + + long countByStatus(String status); + + long countByCreatedAtAfter(LocalDateTime time); + + List findTop15ByOrderByCreatedAtDesc(); + + Page findAllByOrderByCreatedAtDesc(Pageable pageable); + + long countByCreatedAtBetween(LocalDateTime start, LocalDateTime end); + + @Query(value = "SELECT COUNT(*) FROM tool_executions WHERE created_at >= :start AND created_at < :end", nativeQuery = true) + long countBetween(@Param("start") LocalDateTime start, @Param("end") LocalDateTime end); +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/UserRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/UserRepository.java new file mode 100644 index 0000000..e1873cc --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/UserRepository.java @@ -0,0 +1,16 @@ +package com.ims.service.repository; + +import com.ims.service.entity.User; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.Optional; + +@Repository +public interface UserRepository extends JpaRepository { + Optional findByUserid(String userid); + Optional findByUsername(String username); + + @EntityGraph(attributePaths = "department") + Optional findWithDepartmentById(Long id); +} diff --git a/backend/ims-service/src/main/java/com/ims/service/repository/UserRoleRepository.java b/backend/ims-service/src/main/java/com/ims/service/repository/UserRoleRepository.java new file mode 100644 index 0000000..df65301 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/repository/UserRoleRepository.java @@ -0,0 +1,14 @@ +package com.ims.service.repository; + +import com.ims.service.entity.UserRole; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Collection; +import java.util.List; + +@Repository +public interface UserRoleRepository extends JpaRepository { + List findByUserId(Long userId); + List findByRoleIdIn(Collection roleIds); +} diff --git a/backend/ims-service/src/main/java/com/ims/service/security/DataScope.java b/backend/ims-service/src/main/java/com/ims/service/security/DataScope.java new file mode 100644 index 0000000..19ab78f --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/security/DataScope.java @@ -0,0 +1,11 @@ +package com.ims.service.security; + +import java.lang.annotation.*; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataScope { + String departmentAlias() default "d"; + String userAlias() default ""; +} diff --git a/backend/ims-service/src/main/java/com/ims/service/security/DataScopeAspect.java b/backend/ims-service/src/main/java/com/ims/service/security/DataScopeAspect.java new file mode 100644 index 0000000..0af0002 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/security/DataScopeAspect.java @@ -0,0 +1,77 @@ +package com.ims.service.security; + +import com.ims.service.entity.Department; +import com.ims.service.entity.User; +import com.ims.service.repository.DepartmentRepository; +import com.ims.service.repository.RoleRepository; +import com.ims.service.repository.UserRepository; +import com.ims.service.repository.UserRoleRepository; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Aspect +@Component +public class DataScopeAspect { + + private static final String SUPER_ADMIN_ROLE = "超级管理员"; + + private final UserRepository userRepository; + private final DepartmentRepository departmentRepository; + private final UserRoleRepository userRoleRepository; + private final RoleRepository roleRepository; + + public DataScopeAspect(UserRepository userRepository, DepartmentRepository departmentRepository, + UserRoleRepository userRoleRepository, RoleRepository roleRepository) { + this.userRepository = userRepository; + this.departmentRepository = departmentRepository; + this.userRoleRepository = userRoleRepository; + this.roleRepository = roleRepository; + } + + @Around("@annotation(dataScope)") + public Object doAround(ProceedingJoinPoint joinPoint, DataScope dataScope) throws Throwable { + try { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null && authentication.isAuthenticated()) { + String name = authentication.getName(); + userRepository.findByUsername(name) + .or(() -> userRepository.findByUserid(name)) + .ifPresent(user -> { + if (!isSuperAdmin(user) && user.getDepartment() != null) { + Set scope = new HashSet<>(); + collectDepartments(user.getDepartment().getId(), scope); + DataScopeContext.set(scope); + } + }); + } + return joinPoint.proceed(); + } finally { + DataScopeContext.clear(); + } + } + + private boolean isSuperAdmin(User user) { + return userRoleRepository.findByUserId(user.getId()).stream() + .map(ur -> roleRepository.findById(ur.getRoleId()).map(r -> r.getName()).orElse("")) + .anyMatch(SUPER_ADMIN_ROLE::equals); + } + + private void collectDepartments(Long parentId, Set acc) { + if (parentId == null) { + return; + } + acc.add(parentId); + List children = departmentRepository.findByParentIdOrderBySortOrder(parentId); + for (Department child : children) { + collectDepartments(child.getId(), acc); + } + } +} \ No newline at end of file diff --git a/backend/ims-service/src/main/java/com/ims/service/security/DataScopeContext.java b/backend/ims-service/src/main/java/com/ims/service/security/DataScopeContext.java new file mode 100644 index 0000000..92f8b32 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/security/DataScopeContext.java @@ -0,0 +1,27 @@ +package com.ims.service.security; + +import java.util.Set; + +public final class DataScopeContext { + + private static final ThreadLocal> DEPT_IDS = new ThreadLocal<>(); + + private DataScopeContext() { + } + + public static void set(Set deptIds) { + DEPT_IDS.set(deptIds); + } + + public static Set get() { + return DEPT_IDS.get(); + } + + public static boolean isScoped() { + return DEPT_IDS.get() != null && !DEPT_IDS.get().isEmpty(); + } + + public static void clear() { + DEPT_IDS.remove(); + } +} \ No newline at end of file diff --git a/backend/ims-service/src/main/java/com/ims/service/security/JwtAuthFilter.java b/backend/ims-service/src/main/java/com/ims/service/security/JwtAuthFilter.java new file mode 100644 index 0000000..7885934 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/security/JwtAuthFilter.java @@ -0,0 +1,76 @@ +package com.ims.service.security; + +import com.ims.common.util.JwtUtil; +import io.jsonwebtoken.Claims; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +public class JwtAuthFilter extends OncePerRequestFilter { + + private static final Logger log = LoggerFactory.getLogger(JwtAuthFilter.class); + + private final JwtUtil jwtUtil; + private final UserDetailsServiceImpl userDetailsService; + + public JwtAuthFilter(JwtUtil jwtUtil, UserDetailsServiceImpl userDetailsService) { + this.jwtUtil = jwtUtil; + this.userDetailsService = userDetailsService; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + String token = extractToken(request); + if (token == null) { + log.debug("No Bearer token found for {} {}", request.getMethod(), request.getRequestURI()); + filterChain.doFilter(request, response); + return; + } + if (!jwtUtil.validateToken(token)) { + log.debug("Invalid or expired token for {} {}: prefix={}", request.getMethod(), request.getRequestURI(), + token.substring(0, Math.min(20, token.length()))); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("{\"code\":401,\"message\":\"Token无效或已过期\"}"); + return; + } + Claims claims = jwtUtil.parseToken(token); + String username = claims.getSubject(); + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); + log.debug("Authenticated user: {}", username); + filterChain.doFilter(request, response); + } + + private String extractToken(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + String queryToken = request.getParameter("token"); + if (StringUtils.hasText(queryToken)) { + return queryToken; + } + return null; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/security/SecurityConfig.java b/backend/ims-service/src/main/java/com/ims/service/security/SecurityConfig.java new file mode 100644 index 0000000..e153785 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/security/SecurityConfig.java @@ -0,0 +1,68 @@ +package com.ims.service.security; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity +public class SecurityConfig { + + private final JwtAuthFilter jwtAuthFilter; + + public SecurityConfig(JwtAuthFilter jwtAuthFilter) { + this.jwtAuthFilter = jwtAuthFilter; + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .cors(cors -> cors.configurationSource(corsConfigurationSource())) + .csrf(csrf -> csrf.disable()) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/v1/auth/**").permitAll() + .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() + .anyRequest().authenticated() + ) + .exceptionHandling(ex -> ex + .authenticationEntryPoint((request, response, authException) -> { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("{\"code\":401,\"message\":\"Token已过期或未登录,请重新登录\"}"); + }) + ) + .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); + return http.build(); + } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + config.addAllowedOriginPattern("*"); + config.addAllowedHeader("*"); + config.addAllowedMethod("*"); + config.setAllowCredentials(true); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + return source; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/security/UserDetailsServiceImpl.java b/backend/ims-service/src/main/java/com/ims/service/security/UserDetailsServiceImpl.java new file mode 100644 index 0000000..b17defd --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/security/UserDetailsServiceImpl.java @@ -0,0 +1,36 @@ +package com.ims.service.security; + +import com.ims.service.entity.User; +import com.ims.service.repository.UserRepository; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.Collections; + +@Service +public class UserDetailsServiceImpl implements UserDetailsService { + + private final UserRepository userRepository; + + public UserDetailsServiceImpl(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userRepository.findByUserid(username) + .or(() -> userRepository.findByUsername(username)) + .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username)); + + return new org.springframework.security.core.userdetails.User( + user.getUsername(), + user.getPasswordHash(), + user.getIsActive() != null && user.getIsActive(), + true, true, true, + Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")) + ); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/system/DepartmentServiceImpl.java b/backend/ims-service/src/main/java/com/ims/service/system/DepartmentServiceImpl.java new file mode 100644 index 0000000..5663948 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/system/DepartmentServiceImpl.java @@ -0,0 +1,55 @@ +package com.ims.service.system; + +import com.ims.api.dto.system.DepartmentResponse; +import com.ims.api.service.system.DepartmentService; +import com.ims.service.entity.Department; +import com.ims.service.repository.DepartmentRepository; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Service +public class DepartmentServiceImpl implements DepartmentService { + + private final DepartmentRepository departmentRepository; + + public DepartmentServiceImpl(DepartmentRepository departmentRepository) { + this.departmentRepository = departmentRepository; + } + + @Override + public List tree() { + List all = departmentRepository.findAll(); + Map map = new LinkedHashMap<>(); + for (Department d : all) { + DepartmentResponse r = new DepartmentResponse(); + r.setId(d.getId()); + r.setName(d.getName()); + r.setParentId(d.getParent() == null ? null : d.getParent().getId()); + r.setSortOrder(d.getSortOrder()); + r.setChildren(new ArrayList<>()); + map.put(d.getId(), r); + } + List roots = new ArrayList<>(); + for (DepartmentResponse r : map.values()) { + if (r.getParentId() == null) { + roots.add(r); + } else if (map.containsKey(r.getParentId())) { + map.get(r.getParentId()).getChildren().add(r); + } + } + sort(roots); + return roots; + } + + private void sort(List nodes) { + nodes.sort(Comparator.comparing(DepartmentResponse::getSortOrder, Comparator.nullsLast(Integer::compareTo))); + for (DepartmentResponse n : nodes) { + sort(n.getChildren()); + } + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/system/ImportServiceImpl.java b/backend/ims-service/src/main/java/com/ims/service/system/ImportServiceImpl.java new file mode 100644 index 0000000..c0a5f26 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/system/ImportServiceImpl.java @@ -0,0 +1,562 @@ +package com.ims.service.system; + +import com.ims.api.dto.imports.AgentValidateResponse; +import com.ims.api.dto.imports.ImportConfirmRequest; +import com.ims.api.dto.imports.ImportPreviewResponse; +import com.ims.api.dto.imports.ImportRecordResponse; +import com.ims.api.dto.imports.ImportRow; +import com.ims.api.service.system.ImportService; +import com.ims.common.constant.ResultCode; +import com.ims.common.dto.PageResult; +import com.ims.common.exception.BusinessException; +import com.ims.service.entity.Department; +import com.ims.service.entity.ImportRecord; +import com.ims.service.entity.Issue; +import com.ims.service.entity.User; +import com.ims.service.repository.DepartmentRepository; +import com.ims.service.repository.ImportRecordRepository; +import com.ims.service.repository.IssueRepository; +import com.ims.service.repository.UserRepository; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.eventusermodel.XSSFReader; +import org.apache.poi.xssf.model.SharedStrings; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.web.multipart.MultipartFile; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import javax.xml.parsers.SAXParserFactory; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +@Service +public class ImportServiceImpl implements ImportService { + + private static final Logger log = LoggerFactory.getLogger(ImportServiceImpl.class); + + private static final String[] HEADERS = {"标题", "工程阶段", "优先级", "期限", "子项目", "类别", "影响级别", "描述", "担当者工号", "部门名称"}; + private static final Set PRIORITIES = Set.of("high", "medium", "low"); + private static final int TEMPLATE_DATA_START_ROW = 5; + private static final int TEMPLATE_DATA_END_ROW = 272; + private static final int TEMPLATE_HEADER_ROW = 4; + private static final Map TEMPLATE_HEADERS = Map.ofEntries( + Map.entry("A", "項番"), Map.entry("B", "レビュー日"), Map.entry("C", "ドキュメント分類"), + Map.entry("D", "対象PP"), Map.entry("E", "レビュー対象"), Map.entry("F", "プロジェクト名"), + Map.entry("G", "ブランチ名"), Map.entry("H", "バージョン"), Map.entry("I", "レビュー者"), + Map.entry("J", "指摘事項"), Map.entry("K", "対応内容"), Map.entry("L", "指摘取込バージョン"), + Map.entry("M", "担当者"), Map.entry("N", "対応期限"), Map.entry("O", "完了日"), + Map.entry("P", "承認日"), Map.entry("Q", "承認者"), Map.entry("R", "備考")); + private static final DateTimeFormatter[] DEADLINE_FORMATS = { + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"), + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"), + DateTimeFormatter.ofPattern("yyyy-MM-dd"), + DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"), + DateTimeFormatter.ofPattern("yyyy/MM/dd") + }; + private static final AtomicInteger SEQ = new AtomicInteger(0); + + private final IssueRepository issueRepository; + private final ImportRecordRepository importRecordRepository; + private final DepartmentRepository departmentRepository; + private final UserRepository userRepository; + private final ImportSuggestService importSuggestService; + private final TransactionTemplate transactionTemplate; + + public ImportServiceImpl(IssueRepository issueRepository, + ImportRecordRepository importRecordRepository, + DepartmentRepository departmentRepository, + UserRepository userRepository, + ImportSuggestService importSuggestService, + TransactionTemplate transactionTemplate) { + this.issueRepository = issueRepository; + this.importRecordRepository = importRecordRepository; + this.departmentRepository = departmentRepository; + this.userRepository = userRepository; + this.importSuggestService = importSuggestService; + this.transactionTemplate = transactionTemplate; + } + + @Override + public byte[] generateTemplate() { + try { + ClassPathResource resource = new ClassPathResource("templates/レビュー記録表.xlsx"); + if (resource.exists()) { + try (InputStream in = resource.getInputStream()) { + return in.readAllBytes(); + } + } + return generateFallbackTemplate(); + } catch (IOException e) { + log.warn("Template file unavailable, fallback to generated template: {}", e.getMessage()); + return generateFallbackTemplate(); + } + } + + private byte[] generateFallbackTemplate() { + try (Workbook workbook = new XSSFWorkbook(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { + Sheet sheet = workbook.createSheet("指摘导入"); + Row header = sheet.createRow(0); + for (int i = 0; i < HEADERS.length; i++) { + header.createCell(i).setCellValue(HEADERS[i]); + sheet.setColumnWidth(i, 18 * 256); + } + sheet.createRow(1).createCell(2).setCellValue("high/medium/low"); + workbook.write(out); + return out.toByteArray(); + } catch (IOException e) { + throw new BusinessException(ResultCode.BAD_REQUEST.getCode(), "模板生成失败"); + } + } + + @Override + public ImportPreviewResponse preview(MultipartFile file) { + if (file == null || file.isEmpty()) { + throw new BusinessException(ResultCode.BAD_REQUEST.getCode(), "文件不能为空"); + } + ParseResult parsed = parseRows(file); + List rows = parsed.rows; + int valid = 0; + int error = 0; + for (ImportRow row : rows) { + row.setErrors(validate(row)); + if (row.getErrors().isEmpty()) { + row.setStatus("ok"); + valid++; + } else { + row.setStatus("error"); + error++; + } + } + ImportPreviewResponse response = new ImportPreviewResponse(); + response.setTotal(rows.size()); + response.setValidCount(valid); + response.setErrorCount(error); + response.setHeaderValid(parsed.headerValid); + response.setRows(rows); + return response; + } + + @Override + public AgentValidateResponse aiValidate(List rows) { + return importSuggestService.suggest(rows == null ? List.of() : rows); + } + + @Override + public ImportRecordResponse confirm(ImportConfirmRequest request, String operatorUsername) { + User operator = userRepository.findByUsername(operatorUsername) + .or(() -> userRepository.findByUserid(operatorUsername)) + .orElse(null); + List rows = request.getRows() == null ? List.of() : request.getRows(); + int success = 0; + int fail = 0; + List errors = new ArrayList<>(); + for (ImportRow row : rows) { + List errs = validate(row); + if (!errs.isEmpty()) { + fail++; + errors.add("第" + row.getRowNo() + "行: " + String.join("; ", errs)); + continue; + } + try { + transactionTemplate.execute(status -> { + importSuggestService.enrich(row); + createIssue(row, operator); + return null; + }); + success++; + } catch (Exception e) { + fail++; + errors.add("第" + row.getRowNo() + "行: " + e.getMessage()); + } + } + String status = fail == 0 ? "success" : (success == 0 ? "failed" : "partial"); + ImportRecord record = ImportRecord.builder() + .fileName(nz(request.getFileName())) + .totalCount(rows.size()) + .successCount(success) + .failCount(fail) + .status(status) + .errorLog(errors.isEmpty() ? null : String.join("\n", errors)) + .operator(operator) + .build(); + record = importRecordRepository.save(record); + return toResponse(record); + } + + @Override + public PageResult records(int page, int pageSize) { + List all = importRecordRepository.findAll(); + all.sort(Comparator.comparing(ImportRecord::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder()))); + int total = all.size(); + int from = Math.min((page - 1) * pageSize, total); + int to = Math.min(from + pageSize, total); + List items = all.subList(from, to).stream().map(this::toResponse).toList(); + return new PageResult<>(items, total, page, pageSize); + } + + private ParseResult parseRows(MultipartFile file) { + List rows = new ArrayList<>(); + boolean headerValid = true; + java.io.File tmp = null; + try { + tmp = java.io.File.createTempFile("ims_import_", ".xlsx"); + file.transferTo(tmp); + try (OPCPackage pkg = OPCPackage.open(tmp)) { + XSSFReader reader = new XSSFReader(pkg); + SharedStrings sst = reader.getSharedStringsTable(); + SheetDataHandler handler = new SheetDataHandler(sst); + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setNamespaceAware(false); + java.util.Iterator sheets = reader.getSheetsData(); + while (sheets.hasNext()) { + try (InputStream sheetIn = sheets.next()) { + factory.newSAXParser().parse(sheetIn, handler); + } catch (Exception e) { + log.warn("Sheet parse failed: {}", e.getMessage()); + } + if (!handler.rows.isEmpty()) { + break; + } + } + headerValid = handler.isValidHeader(); + rows = handler.rows; + } + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("Excel parse failed", e); + throw new BusinessException(ResultCode.BAD_REQUEST.getCode(), "Excel 解析失败,请使用标准模板"); + } finally { + if (tmp != null && tmp.exists()) { + tmp.delete(); + } + } + return new ParseResult(rows, headerValid); + } + + private static class SheetDataHandler extends DefaultHandler { + private final SharedStrings sst; + private final List rows = new ArrayList<>(); + private final Map headerValues = new LinkedHashMap<>(); + private int currentRowNum = -1; + private String currentCol; + private String currentCellType; + private final StringBuilder cellValue = new StringBuilder(); + private final Map rowValues = new LinkedHashMap<>(); + private boolean inCellValue = false; + + SheetDataHandler(SharedStrings sst) { + this.sst = sst; + } + + boolean isValidHeader() { + if (headerValues.isEmpty()) { + return false; + } + for (Map.Entry e : TEMPLATE_HEADERS.entrySet()) { + String actual = headerValues.get(e.getKey()); + if (actual == null || !actual.trim().equals(e.getValue())) { + return false; + } + } + return true; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + switch (qName) { + case "row" -> { + currentRowNum = Integer.parseInt(attributes.getValue("r")); + rowValues.clear(); + } + case "c" -> { + String ref = attributes.getValue("r"); + currentCol = ref == null ? "" : ref.replaceAll("\\d+", ""); + currentCellType = attributes.getValue("t"); + cellValue.setLength(0); + inCellValue = true; + } + case "v" -> { + cellValue.setLength(0); + inCellValue = true; + } + default -> { + } + } + } + + @Override + public void characters(char[] ch, int start, int length) { + if (inCellValue) { + cellValue.append(ch, start, length); + } + } + + @Override + public void endElement(String uri, String localName, String qName) { + if ("v".equals(qName)) { + String raw = cellValue.toString(); + String value = resolveCell(raw, currentCellType); + if (!currentCol.isBlank() && value != null && !value.isBlank()) { + rowValues.put(currentCol, value); + } + inCellValue = false; + } else if ("row".equals(qName)) { + if (currentRowNum == TEMPLATE_HEADER_ROW) { + for (Map.Entry e : TEMPLATE_HEADERS.entrySet()) { + String v = rowValues.get(e.getKey()); + if (v != null) { + headerValues.put(e.getKey(), v); + } + } + } else if (currentRowNum >= TEMPLATE_DATA_START_ROW && currentRowNum <= TEMPLATE_DATA_END_ROW) { + ImportRow item = toImportRow(currentRowNum, rowValues); + if (item != null) { + rows.add(item); + } + } + currentRowNum = -1; + } + } + + private String resolveCell(String raw, String type) { + if ("s".equals(type)) { + try { + int idx = Integer.parseInt(raw.trim()); + return sst.getItemAt(idx).getString(); + } catch (Exception e) { + return null; + } + } + if ("inlineStr".equals(type)) { + return raw.trim(); + } + return raw.trim(); + } + } + + private static ImportRow toImportRow(int rowNum, Map values) { + String title = values.get("J"); + if (title == null || title.isBlank()) { + return null; + } + ImportRow item = new ImportRow(); + item.setRowNo(rowNum); + item.setTitle(title.trim()); + item.setDocType(values.get("E")); + item.setCategory(values.get("C")); + item.setSubProject(values.get("F")); + item.setAssigneeUserid(values.get("M")); + item.setReviewerUserid(values.get("I")); + item.setValidatorUserid(values.get("Q")); + String desc = buildDescription(values); + item.setDescription(desc.isEmpty() ? null : desc); + String dateB = values.get("B"); + String dateN = values.get("N"); + item.setReviewDate(excelDateToText(dateB)); + String deadline = excelDateToText(dateN); + if (deadline == null) { + deadline = excelDateToText(values.get("O")); + } + if (deadline == null) { + deadline = excelDateToText(values.get("P")); + } + item.setDeadline(deadline); + return item; + } + + private static String buildDescription(Map values) { + List parts = new ArrayList<>(); + String description = values.get("K"); + if (description != null && !description.isBlank()) { + parts.add(description.trim()); + } + String note = values.get("G"); + if (note != null && !note.isBlank()) { + String n = note.trim(); + if (!n.equals("528") && !n.equals("529") && !n.equals("531") + && !n.contains("対応内容") && !n.contains("カンリョウ")) { + parts.add("【補足】" + n); + } + } + appendInfoPart(parts, "対象PP", values.get("D")); + appendInfoPart(parts, "バージョン", values.get("H")); + appendInfoPart(parts, "指摘取込バージョン", values.get("L")); + appendInfoPart(parts, "備考", values.get("R")); + return String.join("\n", parts); + } + + private static void appendInfoPart(List parts, String label, String value) { + if (value != null && !value.isBlank()) { + String v = value.trim(); + if (!v.equals("528") && !v.equals("529") && !v.equals("531") + && !v.contains("対応内容") && !v.contains("カンリョウ")) { + parts.add("【" + label + "】" + v); + } + } + } + + private static String excelDateToText(String value) { + if (value == null || value.isBlank()) { + return null; + } + String v = value.trim(); + try { + double serial = Double.parseDouble(v); + if (DateUtil.isValidExcelDate(serial)) { + return DateUtil.getJavaDate(serial).toInstant() + .atZone(java.time.ZoneId.systemDefault()) + .toLocalDate().toString(); + } + } catch (NumberFormatException ignored) { + } + return v; + } + + private List validate(ImportRow row) { + List errors = new ArrayList<>(); + if (row.getTitle() == null || row.getTitle().isBlank()) { + errors.add("标题必填"); + } + if (row.getPriority() != null && !row.getPriority().isBlank() + && !PRIORITIES.contains(row.getPriority().trim().toLowerCase())) { + errors.add("优先级必须是 high/medium/low"); + } + if (row.getDeadline() != null && !row.getDeadline().isBlank() && parseDeadline(row.getDeadline()) == null) { + errors.add("期限格式不正确"); + } + return errors; + } + + private void createIssue(ImportRow row, User operator) { + Issue issue = new Issue(); + issue.setIssueNo(genIssueNo()); + issue.setTitle(truncate(row.getTitle().trim(), 200)); + issue.setStatus("draft"); + issue.setPriority(row.getPriority() == null || row.getPriority().isBlank() + ? "medium" : row.getPriority().trim().toLowerCase()); + issue.setPhase(row.getPhase()); + issue.setSubProject(row.getSubProject()); + issue.setCategory(row.getCategory()); + issue.setImpactLevel(row.getImpactLevel()); + issue.setDescription(row.getDescription()); + issue.setDeadline(parseDeadline(row.getDeadline())); + issue.setReviewDate(parseDeadline(row.getReviewDate())); + issue.setCreator(operator); + issue.setDepartment(resolveDepartment(row.getDepartmentName(), operator)); + issue.setAssignee(resolveAssignee(row.getAssigneeUserid())); + issue.setReviewer(resolveAssignee(row.getReviewerUserid())); + issue.setValidator(resolveAssignee(row.getValidatorUserid())); + issue.setAgentStatus("human_driven"); + issue.setIsDeleted(false); + issueRepository.save(issue); + } + + private Department resolveDepartment(String departmentName, User operator) { + if (departmentName != null && !departmentName.isBlank()) { + Department matched = departmentRepository.findAll().stream() + .filter(d -> departmentName.trim().equals(d.getName())) + .findFirst().orElse(null); + if (matched != null) { + return matched; + } + } + if (operator != null && operator.getDepartment() != null) { + return operator.getDepartment(); + } + return departmentRepository.findAll().stream().findFirst().orElseThrow( + () -> new BusinessException(ResultCode.BAD_REQUEST.getCode(), "系统无部门数据")); + } + + private User resolveAssignee(String userid) { + if (userid == null || userid.isBlank()) { + return null; + } + return userRepository.findByUserid(userid.trim()) + .or(() -> userRepository.findByUsername(userid.trim())) + .orElse(null); + } + + private String genIssueNo() { + int seq = SEQ.incrementAndGet() % 1000; + String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + return "ISS" + time + String.format("%03d", seq); + } + + private LocalDateTime parseDeadline(String value) { + if (value == null || value.isBlank()) { + return null; + } + String v = value.trim(); + for (DateTimeFormatter f : DEADLINE_FORMATS) { + try { + return LocalDateTime.parse(v, f); + } catch (DateTimeParseException ignored) {} + } + try { + return LocalDate.parse(v, DateTimeFormatter.ofPattern("yyyy/MM/dd")).atStartOfDay(); + } catch (DateTimeParseException ignored) {} + try { + return LocalDate.parse(v, DateTimeFormatter.ofPattern("yyyy-MM-dd")).atStartOfDay(); + } catch (DateTimeParseException ignored) {} + return null; + } + + private String truncate(String value, int max) { + if (value == null || value.length() <= max) { + return value; + } + return value.substring(0, max); + } + + private ImportRecordResponse toResponse(ImportRecord record) { + ImportRecordResponse r = new ImportRecordResponse(); + r.setId(record.getId()); + r.setFileName(record.getFileName()); + r.setTotalCount(record.getTotalCount()); + r.setSuccessCount(record.getSuccessCount()); + r.setFailCount(record.getFailCount()); + r.setStatus(record.getStatus()); + r.setErrorLog(record.getErrorLog()); + r.setOperator(record.getOperator() == null ? "" : record.getOperator().getUsername()); + r.setCreatedAt(record.getCreatedAt()); + return r; + } + + private String nz(String s) { + return s == null ? "" : s; + } + + private static class ParseResult { + private final List rows; + private final boolean headerValid; + + ParseResult(List rows, boolean headerValid) { + this.rows = rows; + this.headerValid = headerValid; + } + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/system/ImportSuggestService.java b/backend/ims-service/src/main/java/com/ims/service/system/ImportSuggestService.java new file mode 100644 index 0000000..3fbbf86 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/system/ImportSuggestService.java @@ -0,0 +1,591 @@ +package com.ims.service.system; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ims.api.dto.imports.AgentValidateResponse; +import com.ims.api.dto.imports.ImportRow; +import com.ims.api.dto.imports.ImportSuggestion; +import com.ims.service.ai.RoutingChatService; +import com.ims.service.entity.PhaseRule; +import com.ims.service.repository.PhaseRuleRepository; +import com.ims.service.repository.PromptTemplateRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Service +public class ImportSuggestService { + + private static final Logger log = LoggerFactory.getLogger(ImportSuggestService.class); + + private static final int LLM_BATCH_SIZE = 3; + + private static final String[] PHASE_DICT = {"基本设计", "详细设计", "单体测试", "结合测试", "系统测试", "验收", "开发", "测试"}; + private static final Set PRIORITIES = Set.of("high", "medium", "low"); + private static final List BUILTIN_PHASE_RULES = List.of( + rule("詳細設計", "详细设计", "exact"), + rule("開発", "开发", "exact"), + rule("画面設計書", "详细设计", "contains"), + rule("共通部品設計書", "详细设计", "contains"), + rule("Impl", "开发", "impl"), + rule("単体テスト", "单体测试", "contains"), + rule("結合テスト", "结合测试", "contains"), + rule("詳細設計", "详细设计", "contains"), + rule("構築手順書", "开发", "contains"), + rule("設定条件書", "基本设计", "contains"), + rule("パラメータシート", "详细设计", "contains"), + rule("運用受入", "验收", "contains"), + rule("運用", "验收", "contains"), + rule("受入", "验收", "contains")); + private static final DateTimeFormatter[] DEADLINE_FORMATS = { + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"), + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"), + DateTimeFormatter.ofPattern("yyyy-MM-dd"), + DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"), + DateTimeFormatter.ofPattern("yyyy/MM/dd") + }; + private static final Map PRIORITY_ALIASES = Map.of( + "紧急", "high", "高", "high", "中", "medium", "低", "low"); + private static final Map HIGH_KEYWORDS = Map.ofEntries( + Map.entry("崩溃", "high"), Map.entry("宕机", "high"), Map.entry("无法启动", "high"), Map.entry("停机", "high"), + Map.entry("故障", "high"), Map.entry("异常", "high"), Map.entry("损坏", "high"), Map.entry("严重", "high"), + Map.entry("障害", "high"), Map.entry("異常", "high"), Map.entry("エラー", "high"), Map.entry("不整合", "high"), + Map.entry("論理エラー", "high"), Map.entry("ロジックエラー", "high"), Map.entry("動作しない", "high"), + Map.entry("失敗する", "high"), Map.entry("セキュリティ", "high"), Map.entry("データ不整合", "high"), + Map.entry("优化", "low"), Map.entry("性能", "low"), Map.entry("建议", "low"), Map.entry("改进", "low"), + Map.entry("コメント", "low"), Map.entry("注釈", "low"), Map.entry("表記", "low"), Map.entry("見直し", "low"), + Map.entry("フォーマット", "low"), Map.entry("ドキュメント", "low")); + + private static final String SYSTEM_PROMPT = + "你是「指摘管理系统」的数据校验专家 Agent,专精于制造业与软件工程领域的质量问题数据校验。" + + "对批量导入的指摘数据做智能校验,判断该文件是否可作为指摘表导入,并找出可修正的字段问题。" + + "只输出一个 JSON 对象,不要输出任何其他文字或 Markdown 代码块标记。"; + + private static final String VALIDATE_BATCH_001 = + "请对以下批量导入的指摘数据行进行智能校验。\n\n" + + "字段说明:title=指摘事項、description=対応内容、deadline=対応期限、reviewDate=レビュー実施日、phase=工程阶段、priority=优先级、category=ドキュメント分類、subProject=プロジェクト名、docType=対象ドキュメント\n" + + "已知规则:\n" + + "- 优先级取值必须为 high(紧急)、medium(中)、low(低)。若遇到 \"紧急\"、\"高\"、\"中\"、\"低\"、\"HIGH\" 等非规范写法,请给出归一化建议\n" + + "- 工程阶段应是规范阶段名称(如:基本设计、详细设计、单体测试、结合测试、系统测试、验收、开发)\n" + + "- 期限应为日期格式(yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss)\n" + + "- 标题必填,不允许为空\n" + + "- 若没有有效数据行、所有行缺少标题或内容明显与指摘无关,则该文件不能作为指摘表导入\n\n" + + "智能推断要求(仅当字段缺失或为空时):\n" + + "- 若 phase 为空,根据 docType(対象ドキュメント文件名,如含 \"単体テスト\"、\"結合テスト\"、\"画面設計書\"、\"共通部品設計書\"、\"構築手順書\"、\"設定条件書\"、\"パラメータシート\"、\"運用\")或指摘内容上下文推断工程阶段\n" + + "- 若 priority 为空,根据指摘内容的严重程度推断:功能异常/安全/数据不一致/无法运行→high;逻辑需修正/规格不一致→medium;表示/注释/格式/文档→low\n" + + "- 推断结果作为 suggestions 输出(field 为 phase 或 priority),并附简短 reason 说明推断依据\n\n" + + "数据行(JSON):\n%s\n\n" + + "输出格式:单个 JSON 对象,结构为 {\"usable\":true或false,\"usableReason\":\"文件级不可用原因(可用时为空字符串)\"," + + "\"suggestions\":[{\"rowNo\":行号,\"field\":\"字段英文名\",\"fieldName\":\"字段中文名\"," + + "\"original\":\"原始值\",\"suggested\":\"建议值\",\"reason\":\"修正理由\",\"level\":\"fix或error\"}]}\n" + + "- usable=false 表示整个文件不能作为指摘表导入(如无有效数据行、无标题、内容与指摘无关)\n" + + "- 行内原值已规范或无法推断正确值,则不要输出该项。无任何建议时 suggestions 输出 []。\n" + + "- level=error 表示该字段值非法无法自动修正,level=fix 表示可一键应用的修正建议。\n" + + "- reason 必须简短(不超过20字),直接说明修正内容与原因,禁止出现\"参照\"、\"参考\"、\"参见\"等表述。"; + + private final RoutingChatService chatService; + private final ObjectMapper objectMapper; + private final ObjectMapper rawJsonMapper; + private final PromptTemplateRepository promptTemplateRepository; + private final PhaseRuleRepository phaseRuleRepository; + + private volatile List phaseRuleCache; + private volatile long phaseRuleCacheAt; + + private static final long PHASE_RULE_CACHE_TTL_MS = 60_000; + + private static PhaseRule rule(String keyword, String phase, String matchMode) { + return PhaseRule.builder().keyword(keyword).phase(phase).matchMode(matchMode).active(true).sortOrder(0).build(); + } + + public ImportSuggestService(RoutingChatService chatService, ObjectMapper objectMapper, + PromptTemplateRepository promptTemplateRepository, + PhaseRuleRepository phaseRuleRepository) { + this.chatService = chatService; + this.objectMapper = objectMapper; + this.rawJsonMapper = new ObjectMapper().configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, false); + this.promptTemplateRepository = promptTemplateRepository; + this.phaseRuleRepository = phaseRuleRepository; + } + + public AgentValidateResponse suggest(List rows) { + long startNanos = System.nanoTime(); + List list = rows == null ? List.of() : rows; + + FileUsability ruleUsable = ruleUsability(list); + List rules = ruleSuggest(list); + + LlmResult llm = null; + try { + llm = llmSuggest(list); + } catch (Exception e) { + log.warn("AI validate unavailable, fallback to rule engine: {}", e.getMessage()); + } + boolean usable; + String usableReason; + if (llm != null) { + usable = ruleUsable.usable; + usableReason = ruleUsable.usable + ? (llm.usable != null && !llm.usable ? "AI提示:" + nz(llm.usableReason) : "") + : ruleUsable.reason; + } else { + usable = ruleUsable.usable; + usableReason = ruleUsable.reason; + } + + List merged = rules; + if (llm != null && llm.suggestions != null && !llm.suggestions.isEmpty()) { + merged = merge(rules, llm.suggestions); + } + + long elapsedMs = (System.nanoTime() - startNanos) / 1_000_000; + log.info("Agent validate finished: rows={} engine={} suggestions={} elapsedMs={}", + list.size(), llm != null ? "ai" : "rule", merged.size(), elapsedMs); + + AgentValidateResponse response = new AgentValidateResponse(); + response.setUsable(usable); + response.setUsableReason(usableReason); + response.setSuggestions(merged); + response.setEngine(llm != null ? "ai" : "rule"); + response.setElapsedMs(elapsedMs); + return response; + } + + public void enrich(ImportRow row) { + if (row == null) { + return; + } + if (row.getPriority() == null || row.getPriority().isBlank()) { + String inferred = inferPriority(row); + if (inferred != null) { + row.setPriority(inferred); + } + } + if (row.getPhase() == null || row.getPhase().isBlank()) { + String inferred = inferPhase(row); + if (inferred != null) { + row.setPhase(inferred); + } + } + } + + private FileUsability ruleUsability(List rows) { + if (rows.isEmpty()) { + return new FileUsability(false, "文件中未检测到有效的指摘数据行,无法作为指摘表导入。"); + } + boolean anyTitle = rows.stream().anyMatch(r -> !nz(r.getTitle()).isBlank()); + if (!anyTitle) { + return new FileUsability(false, "文件中缺少指摘标题,无法识别为有效指摘数据。"); + } + return new FileUsability(true, ""); + } + + private List ruleSuggest(List rows) { + List result = new ArrayList<>(); + for (ImportRow row : rows) { + if (row.getRowNo() == null) { + continue; + } + result.addAll(rowLevelErrors(row)); + String phase = nz(row.getPhase()); + if (!phase.isBlank() && !matchesDict(phase)) { + String match = nearestPhase(phase); + if (match != null) { + result.add(build(row.getRowNo(), "phase", "工程阶段", phase, match, + "「" + phase + "」非规范阶段名,应改为「" + match + "」", + "fix")); + } + } else if (phase.isBlank()) { + String inferred = inferPhase(row); + if (inferred != null) { + result.add(build(row.getRowNo(), "phase", "工程阶段", "", inferred, + "未填写,按対象ドキュメント推断为「" + inferred + "」", + "fix")); + } + } + String priority = nz(row.getPriority()).trim(); + if (priority.isBlank()) { + String suggested = inferPriority(row); + if (suggested != null) { + result.add(build(row.getRowNo(), "priority", "优先级", "", suggested, + "未填写,按内容严重程度推断为「" + suggested + "」", + "fix")); + } + } else { + String normalized = normalizePriority(priority); + if (normalized != null && !normalized.equals(priority.toLowerCase())) { + result.add(build(row.getRowNo(), "priority", "优先级", priority, normalized, + "「" + priority + "」非规范取值,应改为 " + normalized, + "fix")); + } + } + } + return result; + } + + private List rowLevelErrors(ImportRow row) { + List list = new ArrayList<>(); + if (nz(row.getTitle()).isBlank()) { + list.add(build(row.getRowNo(), "title", "标题", null, null, "标题必填", "error")); + } + String priority = nz(row.getPriority()).trim(); + if (!priority.isBlank() && normalizePriority(priority) == null) { + list.add(build(row.getRowNo(), "priority", "优先级", priority, null, "优先级必须是 high/medium/low", "error")); + } + String deadline = nz(row.getDeadline()).trim(); + if (!deadline.isBlank() && !isValidDeadline(deadline)) { + list.add(build(row.getRowNo(), "deadline", "期限", deadline, null, + "期限格式不正确(应为 yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss)", "error")); + } + return list; + } + + private LlmResult llmSuggest(List rows) { + try { + if (rows.isEmpty()) { + return new LlmResult(true, "", List.of()); + } + List allSuggestions = new ArrayList<>(); + Boolean usable = null; + String usableReason = ""; + for (int i = 0; i < rows.size(); i += LLM_BATCH_SIZE) { + List batch = rows.subList(i, Math.min(i + LLM_BATCH_SIZE, rows.size())); + LlmResult partial = llmSuggestBatch(batch); + if (partial == null) { + return null; + } + if (partial.suggestions != null) { + allSuggestions.addAll(partial.suggestions); + } + if (Boolean.FALSE.equals(partial.usable)) { + usable = false; + usableReason = nz(partial.usableReason); + } else if (usable == null) { + usable = true; + } + } + return new LlmResult(usable, usableReason, allSuggestions); + } catch (Exception e) { + log.warn("LLM suggest failed: {}", e.getMessage()); + return null; + } + } + + private LlmResult llmSuggestBatch(List batch) { + try { + String rowsJson = rawJsonMapper.writeValueAsString(batch.stream().map(r -> { + Map m = new LinkedHashMap<>(); + m.put("rowNo", r.getRowNo()); + m.put("title", r.getTitle()); + m.put("docType", r.getDocType()); + m.put("phase", r.getPhase()); + m.put("priority", r.getPriority()); + m.put("deadline", r.getDeadline()); + m.put("reviewDate", r.getReviewDate()); + m.put("category", r.getCategory()); + m.put("impactLevel", r.getImpactLevel()); + m.put("description", r.getDescription()); + m.put("subProject", r.getSubProject()); + return m; + }).toList()); + String userPrompt = String.format(loadPromptTemplate(), rowsJson); + long llmStart = System.nanoTime(); + String output = chatService.chat(SYSTEM_PROMPT, userPrompt); + long llmElapsedMs = (System.nanoTime() - llmStart) / 1_000_000; + log.info("LLM batch suggest: rows={} elapsedMs={}", batch.size(), llmElapsedMs); + return parseLlmResult(output); + } catch (Exception e) { + log.warn("LLM batch suggest failed: {}", e.getMessage()); + return null; + } + } + + private String loadPromptTemplate() { + try { + return promptTemplateRepository.findByTemplateIdAndIsActiveTrue("VALIDATE_BATCH_001") + .map(t -> t.getContent() == null || t.getContent().isBlank() + ? VALIDATE_BATCH_001 : t.getContent().replace("{{rows}}", "%s")) + .orElse(VALIDATE_BATCH_001); + } catch (Exception e) { + log.warn("Load prompt template from DB failed, use builtin: {}", e.getMessage()); + return VALIDATE_BATCH_001; + } + } + + private LlmResult parseLlmResult(String output) { + String text = output == null ? "" : output.trim(); + if (text.startsWith("```")) { + text = text.replaceAll("^```(?:json)?\\s*", "").replaceAll("\\s*```$", ""); + } + if (text.isBlank()) { + return null; + } + JsonNode node; + try { + node = objectMapper.readTree(text); + } catch (Exception e) { + log.warn("LLM output is not valid JSON: {}", e.getMessage()); + return null; + } + if (node.isArray()) { + return new LlmResult(null, "", parseSuggestionArray(node)); + } + if (!node.isObject()) { + return null; + } + Boolean usable = node.path("usable").isMissingNode() || node.path("usable").isNull() + ? null : node.path("usable").asBoolean(); + String usableReason = node.path("usableReason").asText(); + List suggestions = parseSuggestionArray(node.path("suggestions")); + return new LlmResult(usable, usableReason, suggestions); + } + + private List parseSuggestionArray(JsonNode array) { + List list = new ArrayList<>(); + if (array == null || !array.isArray()) { + return list; + } + for (JsonNode n : array) { + ImportSuggestion s = parseSuggestion(n); + if (s != null) { + list.add(s); + } + } + return list; + } + + private ImportSuggestion parseSuggestion(JsonNode n) { + ImportSuggestion s = new ImportSuggestion(); + s.setRowNo(n.path("rowNo").isMissingNode() || n.path("rowNo").isNull() ? null : n.path("rowNo").asInt()); + s.setField(n.path("field").asText()); + s.setFieldName(n.path("fieldName").asText()); + s.setOriginal(n.path("original").asText()); + s.setSuggested(n.path("suggested").asText()); + s.setReason(n.path("reason").asText()); + s.setLevel(n.path("level").isMissingNode() || n.path("level").asText().isBlank() ? "fix" : n.path("level").asText()); + if (s.getRowNo() != null && s.getSuggested() != null && !s.getSuggested().isBlank()) { + return s; + } + return null; + } + + private List merge(List rules, List ai) { + Map map = new LinkedHashMap<>(); + for (ImportSuggestion s : ai) { + map.put(key(s), s); + } + for (ImportSuggestion s : rules) { + map.putIfAbsent(key(s), s); + } + return new ArrayList<>(map.values()); + } + + private String key(ImportSuggestion s) { + return s.getRowNo() + ":" + s.getField(); + } + + private boolean matchesDict(String value) { + String v = value.trim(); + for (String d : PHASE_DICT) { + if (v.equals(d)) { + return true; + } + } + return false; + } + + private String nearestPhase(String value) { + String v = value.trim(); + String best = null; + int bestDist = Integer.MAX_VALUE; + for (String d : PHASE_DICT) { + int dist = levenshtein(v, d); + if (dist < bestDist) { + bestDist = dist; + best = d; + } + } + return bestDist <= 2 ? best : null; + } + + private int levenshtein(String a, String b) { + int[][] dp = new int[a.length() + 1][b.length() + 1]; + for (int i = 0; i <= a.length(); i++) { + dp[i][0] = i; + } + for (int j = 0; j <= b.length(); j++) { + dp[0][j] = j; + } + for (int i = 1; i <= a.length(); i++) { + for (int j = 1; j <= b.length(); j++) { + int cost = a.charAt(i - 1) == b.charAt(j - 1) ? 0 : 1; + dp[i][j] = Math.min(Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1), dp[i - 1][j - 1] + cost); + } + } + return dp[a.length()][b.length()]; + } + + private String inferPriority(ImportRow row) { + String text = nz(row.getTitle()) + " " + nz(row.getDescription()); + String lower = text.toLowerCase(); + for (Map.Entry e : HIGH_KEYWORDS.entrySet()) { + if (lower.contains(e.getKey())) { + return e.getValue(); + } + } + return "medium"; + } + + private String inferPhase(ImportRow row) { + List rules = loadPhaseRules(); + String subProject = nz(row.getSubProject()).trim(); + if (!subProject.isBlank()) { + String phase = matchExact(rules, subProject); + if (phase != null) { + return phase; + } + } + String docType = nz(row.getDocType()).trim(); + if (!docType.isBlank()) { + String phase = matchContains(rules, docType); + if (phase != null) { + return phase; + } + String implPhase = matchImpl(rules, docType); + if (implPhase != null) { + return implPhase; + } + } + String text = nz(row.getTitle()) + " " + nz(row.getDescription()) + " " + nz(row.getCategory()); + return matchContains(rules, text); + } + + private List loadPhaseRules() { + long now = System.currentTimeMillis(); + if (phaseRuleCache != null && now - phaseRuleCacheAt < PHASE_RULE_CACHE_TTL_MS) { + return phaseRuleCache; + } + List rules = BUILTIN_PHASE_RULES; + try { + List fromDb = phaseRuleRepository.findByActiveTrueOrderBySortOrderAsc(); + if (fromDb != null && !fromDb.isEmpty()) { + rules = fromDb; + } + } catch (Exception e) { + log.warn("Load phase rules from DB failed, fallback to builtin: {}", e.getMessage()); + } + phaseRuleCache = rules; + phaseRuleCacheAt = now; + return rules; + } + + private String matchExact(List rules, String value) { + for (PhaseRule r : rules) { + if ("exact".equalsIgnoreCase(r.getMatchMode()) && value.equals(r.getKeyword())) { + return r.getPhase(); + } + } + return null; + } + + private String matchContains(List rules, String text) { + for (PhaseRule r : rules) { + if ("contains".equalsIgnoreCase(r.getMatchMode()) && text.contains(r.getKeyword())) { + return r.getPhase(); + } + } + return null; + } + + private String matchImpl(List rules, String value) { + for (PhaseRule r : rules) { + if ("impl".equalsIgnoreCase(r.getMatchMode()) + && value.endsWith(r.getKeyword())) { + return r.getPhase(); + } + } + return null; + } + + private String normalizePriority(String value) { + String v = value.trim(); + for (Map.Entry e : PRIORITY_ALIASES.entrySet()) { + if (e.getKey().equalsIgnoreCase(v)) { + return e.getValue(); + } + } + if (PRIORITIES.contains(v.toLowerCase())) { + return v.toLowerCase(); + } + return null; + } + + private ImportSuggestion build(Integer rowNo, String field, String fieldName, + String original, String suggested, String reason, String level) { + ImportSuggestion s = new ImportSuggestion(); + s.setRowNo(rowNo); + s.setField(field); + s.setFieldName(fieldName); + s.setOriginal(original); + s.setSuggested(suggested); + s.setReason(reason); + s.setLevel(level); + return s; + } + + private boolean isValidDeadline(String value) { + String v = value.trim(); + for (DateTimeFormatter f : DEADLINE_FORMATS) { + try { + LocalDateTime.parse(v, f); + return true; + } catch (DateTimeParseException ignored) {} + } + try { + LocalDate.parse(v, DateTimeFormatter.ofPattern("yyyy/MM/dd")); + return true; + } catch (DateTimeParseException ignored) {} + try { + LocalDate.parse(v, DateTimeFormatter.ofPattern("yyyy-MM-dd")); + return true; + } catch (DateTimeParseException ignored) {} + return false; + } + + private String nz(String s) { + return s == null ? "" : s; + } + + private static class LlmResult { + private final Boolean usable; + private final String usableReason; + private final List suggestions; + + LlmResult(Boolean usable, String usableReason, List suggestions) { + this.usable = usable; + this.usableReason = usableReason == null ? "" : usableReason; + this.suggestions = suggestions; + } + } + + private static class FileUsability { + private final boolean usable; + private final String reason; + + FileUsability(boolean usable, String reason) { + this.usable = usable; + this.reason = reason; + } + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/system/LogServiceImpl.java b/backend/ims-service/src/main/java/com/ims/service/system/LogServiceImpl.java new file mode 100644 index 0000000..41a8680 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/system/LogServiceImpl.java @@ -0,0 +1,94 @@ +package com.ims.service.system; + +import com.ims.api.dto.system.LogQueryRequest; +import com.ims.api.dto.system.LogResponse; +import com.ims.api.service.system.LogService; +import com.ims.common.dto.PageResult; +import com.ims.service.entity.IssueLog; +import com.ims.service.entity.TaskExecution; +import com.ims.service.repository.IssueLogRepository; +import com.ims.service.repository.TaskExecutionRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +@Service +public class LogServiceImpl implements LogService { + + private final IssueLogRepository issueLogRepository; + private final TaskExecutionRepository taskExecutionRepository; + + public LogServiceImpl(IssueLogRepository issueLogRepository, + TaskExecutionRepository taskExecutionRepository) { + this.issueLogRepository = issueLogRepository; + this.taskExecutionRepository = taskExecutionRepository; + } + + @Override + @Transactional(readOnly = true) + public PageResult list(LogQueryRequest request) { + List all = new ArrayList<>(); + for (IssueLog log : issueLogRepository.findAll()) { + LogResponse r = new LogResponse(); + r.setId(log.getId()); + r.setOperator(log.getUser() == null ? "" : log.getUser().getUsername()); + r.setAction(log.getAction()); + r.setResource("issue"); + String detail = "指摘 " + (log.getIssue() == null ? "" : log.getIssue().getIssueNo()); + String from = nz(log.getFromStatus()); + String to = nz(log.getToStatus()); + if (!from.isEmpty() || !to.isEmpty()) { + detail += " 状态: " + from + " -> " + to; + } + if (log.getRemark() != null && !log.getRemark().isBlank()) { + detail += " " + log.getRemark(); + } + r.setDetail(detail); + r.setCreatedAt(log.getCreatedAt()); + all.add(r); + } + for (TaskExecution t : taskExecutionRepository.findAll()) { + LogResponse r = new LogResponse(); + r.setId(t.getId()); + r.setOperator(t.getCreatedBy() == null ? "" : t.getCreatedBy().getUsername()); + r.setAction("task." + t.getTaskType()); + r.setResource("task"); + String detail = "任务 " + t.getTaskId() + " 状态: " + t.getStatus(); + if (t.getErrorMessage() != null && !t.getErrorMessage().isBlank()) { + detail += " 错误: " + t.getErrorMessage(); + } + r.setDetail(detail); + r.setCreatedAt(t.getCreatedAt()); + all.add(r); + } + String keyword = request.getKeyword() == null ? null : request.getKeyword().toLowerCase(Locale.ROOT); + String operator = request.getOperator() == null ? null : request.getOperator().toLowerCase(Locale.ROOT); + String actionType = request.getActionType(); + LocalDateTime start = request.getStartTime(); + LocalDateTime end = request.getEndTime(); + List filtered = all.stream() + .filter(r -> operator == null || r.getOperator() != null + && r.getOperator().toLowerCase(Locale.ROOT).contains(operator)) + .filter(r -> actionType == null || actionType.isBlank() || r.getAction().contains(actionType)) + .filter(r -> keyword == null || keyword.isBlank() + || (r.getOperator() != null && r.getOperator().toLowerCase(Locale.ROOT).contains(keyword)) + || (r.getDetail() != null && r.getDetail().toLowerCase(Locale.ROOT).contains(keyword))) + .filter(r -> start == null || (r.getCreatedAt() != null && !r.getCreatedAt().isBefore(start))) + .filter(r -> end == null || (r.getCreatedAt() != null && !r.getCreatedAt().isAfter(end))) + .sorted(Comparator.comparing(LogResponse::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder()))) + .toList(); + int total = filtered.size(); + int from = Math.min((request.getPage() - 1) * request.getPageSize(), total); + int to = Math.min(from + request.getPageSize(), total); + return new PageResult<>(filtered.subList(from, to), total, request.getPage(), request.getPageSize()); + } + + private String nz(String s) { + return s == null ? "" : s; + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/system/RoleServiceImpl.java b/backend/ims-service/src/main/java/com/ims/service/system/RoleServiceImpl.java new file mode 100644 index 0000000..4e6fcee --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/system/RoleServiceImpl.java @@ -0,0 +1,138 @@ +package com.ims.service.system; + +import com.ims.api.dto.system.PermissionResponse; +import com.ims.api.dto.system.RoleRequest; +import com.ims.api.dto.system.RoleResponse; +import com.ims.api.service.system.RoleService; +import com.ims.common.constant.ResultCode; +import com.ims.common.exception.BusinessException; +import com.ims.service.entity.Permission; +import com.ims.service.entity.Role; +import com.ims.service.entity.RolePermission; +import com.ims.service.repository.PermissionRepository; +import com.ims.service.repository.RolePermissionRepository; +import com.ims.service.repository.RoleRepository; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Comparator; +import java.util.List; + +@Service +public class RoleServiceImpl implements RoleService { + + private static final String DEFAULT_DATA_SCOPE = "all"; + + private final RoleRepository roleRepository; + private final RolePermissionRepository rolePermissionRepository; + private final PermissionRepository permissionRepository; + private final JdbcTemplate jdbcTemplate; + + public RoleServiceImpl(RoleRepository roleRepository, + RolePermissionRepository rolePermissionRepository, + PermissionRepository permissionRepository, + JdbcTemplate jdbcTemplate) { + this.roleRepository = roleRepository; + this.rolePermissionRepository = rolePermissionRepository; + this.permissionRepository = permissionRepository; + this.jdbcTemplate = jdbcTemplate; + } + + @Override + @Transactional(readOnly = true) + public List list() { + return roleRepository.findAll().stream().map(this::toResponse).toList(); + } + + @Override + @Transactional(readOnly = true) + public List permissions() { + return permissionRepository.findAll().stream() + .sorted(Comparator.comparing(Permission::getId)) + .map(p -> { + PermissionResponse r = new PermissionResponse(); + r.setId(p.getId()); + r.setCode(p.getCode()); + r.setName(p.getName()); + r.setResource(p.getResource()); + return r; + }).toList(); + } + + @Override + @Transactional + public RoleResponse create(RoleRequest request) { + if (roleRepository.findAll().stream().anyMatch(r -> r.getName().equals(request.getName()))) { + throw new BusinessException(ResultCode.BAD_REQUEST.getCode(), "角色名称已存在"); + } + Role role = Role.builder() + .name(request.getName()) + .description(request.getDescription()) + .agentAutoExecute(Boolean.TRUE.equals(request.getAgentAutoExecute())) + .build(); + role = roleRepository.save(role); + String scope = request.getDataScope() == null || request.getDataScope().isBlank() + ? DEFAULT_DATA_SCOPE : request.getDataScope(); + jdbcTemplate.update("UPDATE roles SET data_scope = ? WHERE id = ?", scope, role.getId()); + savePermissions(role.getId(), request.getPermissionIds()); + return toResponse(role); + } + + @Override + @Transactional + public RoleResponse update(Long id, RoleRequest request) { + Role role = roleRepository.findById(id) + .orElseThrow(() -> new BusinessException(ResultCode.BAD_REQUEST.getCode(), "角色不存在")); + if (request.getName() != null && !request.getName().isBlank()) { + role.setName(request.getName()); + } + if (request.getDescription() != null) { + role.setDescription(request.getDescription()); + } + if (request.getAgentAutoExecute() != null) { + role.setAgentAutoExecute(request.getAgentAutoExecute()); + } + roleRepository.save(role); + if (request.getDataScope() != null) { + jdbcTemplate.update("UPDATE roles SET data_scope = ? WHERE id = ?", request.getDataScope(), id); + } + savePermissions(id, request.getPermissionIds()); + return toResponse(role); + } + + private void savePermissions(Long roleId, List permissionIds) { + List existing = rolePermissionRepository.findByRoleId(roleId); + if (!existing.isEmpty()) { + rolePermissionRepository.deleteAll(existing); + } + if (permissionIds == null || permissionIds.isEmpty()) { + return; + } + List permissions = permissionIds.stream() + .filter(permissionRepository::existsById) + .map(permissionId -> RolePermission.builder().roleId(roleId).permissionId(permissionId).build()) + .toList(); + if (!permissions.isEmpty()) { + rolePermissionRepository.saveAll(permissions); + } + } + + private RoleResponse toResponse(Role role) { + RoleResponse r = new RoleResponse(); + r.setId(role.getId()); + r.setName(role.getName()); + r.setDescription(role.getDescription()); + r.setAgentAutoExecute(role.getAgentAutoExecute()); + r.setDataScope(readDataScope(role.getId())); + r.setCreatedAt(role.getCreatedAt()); + r.setPermissionIds(rolePermissionRepository.findByRoleId(role.getId()) + .stream().map(RolePermission::getPermissionId).toList()); + return r; + } + + private String readDataScope(Long roleId) { + List result = jdbcTemplate.queryForList("SELECT data_scope FROM roles WHERE id = ?", String.class, roleId); + return result.isEmpty() || result.get(0) == null ? DEFAULT_DATA_SCOPE : result.get(0); + } +} diff --git a/backend/ims-service/src/main/java/com/ims/service/system/UserServiceImpl.java b/backend/ims-service/src/main/java/com/ims/service/system/UserServiceImpl.java new file mode 100644 index 0000000..e86acd4 --- /dev/null +++ b/backend/ims-service/src/main/java/com/ims/service/system/UserServiceImpl.java @@ -0,0 +1,171 @@ +package com.ims.service.system; + +import com.ims.api.dto.system.UserRequest; +import com.ims.api.dto.system.UserResponse; +import com.ims.api.service.system.UserService; +import com.ims.common.constant.ResultCode; +import com.ims.common.dto.PageResult; +import com.ims.common.exception.BusinessException; +import com.ims.service.entity.Department; +import com.ims.service.entity.Role; +import com.ims.service.entity.User; +import com.ims.service.entity.UserRole; +import com.ims.service.repository.DepartmentRepository; +import com.ims.service.repository.RoleRepository; +import com.ims.service.repository.UserRepository; +import com.ims.service.repository.UserRoleRepository; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +@Service +public class UserServiceImpl implements UserService { + + private static final String DEFAULT_PASSWORD = "Admin@123456"; + + private final UserRepository userRepository; + private final DepartmentRepository departmentRepository; + private final RoleRepository roleRepository; + private final UserRoleRepository userRoleRepository; + private final PasswordEncoder passwordEncoder; + + public UserServiceImpl(UserRepository userRepository, + DepartmentRepository departmentRepository, + RoleRepository roleRepository, + UserRoleRepository userRoleRepository, + PasswordEncoder passwordEncoder) { + this.userRepository = userRepository; + this.departmentRepository = departmentRepository; + this.roleRepository = roleRepository; + this.userRoleRepository = userRoleRepository; + this.passwordEncoder = passwordEncoder; + } + + @Override + @Transactional(readOnly = true) + public PageResult list(int page, int pageSize, String keyword, Long departmentId, Boolean isActive) { + List filtered = userRepository.findAll().stream() + .filter(u -> departmentId == null || (u.getDepartment() != null && departmentId.equals(u.getDepartment().getId()))) + .filter(u -> isActive == null || isActive.equals(u.getIsActive())) + .filter(u -> keyword == null || keyword.isBlank() || matches(u, keyword)) + .sorted(Comparator.comparing(User::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder()))) + .toList(); + int total = filtered.size(); + int from = Math.min((page - 1) * pageSize, total); + int to = Math.min(from + pageSize, total); + List items = filtered.subList(from, to).stream().map(this::toResponse).toList(); + return new PageResult<>(items, total, page, pageSize); + } + + @Override + @Transactional + public UserResponse create(UserRequest request) { + if (userRepository.findByUserid(request.getUserid()).isPresent()) { + throw new BusinessException(ResultCode.BAD_REQUEST.getCode(), "账号已存在"); + } + if (userRepository.findByUsername(request.getUsername()).isPresent()) { + throw new BusinessException(ResultCode.BAD_REQUEST.getCode(), "用户名已存在"); + } + Department department = departmentRepository.findById(request.getDepartmentId()) + .orElseThrow(() -> new BusinessException(ResultCode.BAD_REQUEST.getCode(), "部门不存在")); + String password = request.getPassword() == null || request.getPassword().isBlank() + ? DEFAULT_PASSWORD : request.getPassword(); + User user = User.builder() + .userid(request.getUserid()) + .username(request.getUsername()) + .email(request.getEmail()) + .passwordHash(passwordEncoder.encode(password)) + .department(department) + .isActive(request.getIsActive() == null || request.getIsActive()) + .agentAutoExecute(Boolean.TRUE.equals(request.getAgentAutoExecute())) + .build(); + user = userRepository.save(user); + saveRoles(user.getId(), request.getRoleIds()); + return toResponse(user); + } + + @Override + @Transactional + public UserResponse update(Long id, UserRequest request) { + User user = userRepository.findById(id) + .orElseThrow(() -> new BusinessException(ResultCode.BAD_REQUEST.getCode(), "用户不存在")); + if (request.getUsername() != null && !request.getUsername().isBlank()) { + user.setUsername(request.getUsername()); + } + if (request.getEmail() != null) { + user.setEmail(request.getEmail()); + } + if (request.getPassword() != null && !request.getPassword().isBlank()) { + user.setPasswordHash(passwordEncoder.encode(request.getPassword())); + } + if (request.getDepartmentId() != null) { + user.setDepartment(departmentRepository.findById(request.getDepartmentId()) + .orElseThrow(() -> new BusinessException(ResultCode.BAD_REQUEST.getCode(), "部门不存在"))); + } + if (request.getIsActive() != null) { + user.setIsActive(request.getIsActive()); + } + if (request.getAgentAutoExecute() != null) { + user.setAgentAutoExecute(request.getAgentAutoExecute()); + } + user = userRepository.save(user); + saveRoles(id, request.getRoleIds()); + return toResponse(user); + } + + @Override + @Transactional + public void updateStatus(Long id, Boolean isActive) { + User user = userRepository.findById(id) + .orElseThrow(() -> new BusinessException(ResultCode.BAD_REQUEST.getCode(), "用户不存在")); + user.setIsActive(isActive); + userRepository.save(user); + } + + private boolean matches(User u, String keyword) { + String kw = keyword.toLowerCase(Locale.ROOT); + return (u.getUserid() != null && u.getUserid().toLowerCase(Locale.ROOT).contains(kw)) + || (u.getUsername() != null && u.getUsername().toLowerCase(Locale.ROOT).contains(kw)) + || (u.getEmail() != null && u.getEmail().toLowerCase(Locale.ROOT).contains(kw)); + } + + private void saveRoles(Long userId, List roleIds) { + List existing = userRoleRepository.findByUserId(userId); + if (!existing.isEmpty()) { + userRoleRepository.deleteAll(existing); + } + if (roleIds == null || roleIds.isEmpty()) { + return; + } + List roles = roleIds.stream() + .filter(roleRepository::existsById) + .map(roleId -> UserRole.builder().userId(userId).roleId(roleId).build()) + .toList(); + if (!roles.isEmpty()) { + userRoleRepository.saveAll(roles); + } + } + + private UserResponse toResponse(User u) { + UserResponse r = new UserResponse(); + r.setId(u.getId()); + r.setUserid(u.getUserid()); + r.setUsername(u.getUsername()); + r.setEmail(u.getEmail()); + r.setDepartmentId(u.getDepartment() == null ? null : u.getDepartment().getId()); + r.setDepartmentName(u.getDepartment() == null ? null : u.getDepartment().getName()); + r.setIsActive(u.getIsActive()); + r.setAgentAutoExecute(u.getAgentAutoExecute()); + r.setLastLoginAt(u.getLastLoginAt()); + r.setCreatedAt(u.getCreatedAt()); + List roleIds = userRoleRepository.findByUserId(u.getId()).stream().map(UserRole::getRoleId).toList(); + r.setRoleIds(roleIds); + r.setRoles(roleIds.isEmpty() ? List.of() + : roleRepository.findAllById(roleIds).stream().map(Role::getName).toList()); + return r; + } +} diff --git a/backend/ims-service/src/main/resources/db/migration/V1.0__init_schema.sql b/backend/ims-service/src/main/resources/db/migration/V1.0__init_schema.sql new file mode 100644 index 0000000..7a359b5 --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V1.0__init_schema.sql @@ -0,0 +1,56 @@ +-- departments 部门表 +CREATE TABLE departments ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + parent_id BIGINT REFERENCES departments(id), + sort_order INT DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- users 用户表 +CREATE TABLE users ( + id BIGSERIAL PRIMARY KEY, + userid VARCHAR(50) NOT NULL UNIQUE, + username VARCHAR(50) NOT NULL UNIQUE, + email VARCHAR(100), + password_hash VARCHAR(255) NOT NULL, + department_id BIGINT NOT NULL REFERENCES departments(id), + is_active BOOLEAN DEFAULT TRUE, + agent_auto_execute BOOLEAN DEFAULT FALSE, + last_login_at TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- roles 角色表 +CREATE TABLE roles ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(50) NOT NULL UNIQUE, + description VARCHAR(255), + agent_auto_execute BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- permissions 权限表 +CREATE TABLE permissions ( + id BIGSERIAL PRIMARY KEY, + code VARCHAR(100) NOT NULL UNIQUE, + name VARCHAR(100) NOT NULL, + resource VARCHAR(50) NOT NULL, + description VARCHAR(255) +); + +-- user_roles 用户角色关联 +CREATE TABLE user_roles ( + user_id BIGINT NOT NULL REFERENCES users(id), + role_id BIGINT NOT NULL REFERENCES roles(id), + PRIMARY KEY (user_id, role_id) +); + +-- role_permissions 角色权限关联 +CREATE TABLE role_permissions ( + role_id BIGINT NOT NULL REFERENCES roles(id), + permission_id BIGINT NOT NULL REFERENCES permissions(id), + PRIMARY KEY (role_id, permission_id) +); diff --git a/backend/ims-service/src/main/resources/db/migration/V1.1__core_business.sql b/backend/ims-service/src/main/resources/db/migration/V1.1__core_business.sql new file mode 100644 index 0000000..050432b --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V1.1__core_business.sql @@ -0,0 +1,96 @@ +-- issues 指摘主表 +CREATE TABLE issues ( + id BIGSERIAL PRIMARY KEY, + issue_no VARCHAR(20) NOT NULL UNIQUE, + title VARCHAR(200) NOT NULL, + description TEXT, + status VARCHAR(30) NOT NULL DEFAULT 'draft', + priority VARCHAR(10) NOT NULL DEFAULT 'medium', + deadline TIMESTAMP, + phase VARCHAR(50), + sub_project VARCHAR(100), + category VARCHAR(50), + impact_level VARCHAR(10), + impact_scope VARCHAR(200), + deployment VARCHAR(100), + pgm_no VARCHAR(50), + review_workload DECIMAL(5,1), + response_workload DECIMAL(5,1), + response_content TEXT, + ng_reason VARCHAR(200), + response_completed_at TIMESTAMP, + confirm_at TIMESTAMP, + creator_id BIGINT NOT NULL REFERENCES users(id), + assignee_id BIGINT REFERENCES users(id), + department_id BIGINT NOT NULL REFERENCES departments(id), + reviewer_id BIGINT REFERENCES users(id), + validator_id BIGINT REFERENCES users(id), + ai_analysis_id BIGINT, + agent_last_plan_id BIGINT, + agent_status VARCHAR(20) DEFAULT 'human_driven', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + closed_at TIMESTAMP, + is_deleted BOOLEAN DEFAULT FALSE +); + +-- issue_logs 指摘操作日志 +CREATE TABLE issue_logs ( + id BIGSERIAL PRIMARY KEY, + issue_id BIGINT NOT NULL REFERENCES issues(id), + user_id BIGINT NOT NULL REFERENCES users(id), + action VARCHAR(30) NOT NULL, + from_status VARCHAR(30), + to_status VARCHAR(30), + remark VARCHAR(500), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- attachments 附件表 +CREATE TABLE attachments ( + id BIGSERIAL PRIMARY KEY, + issue_id BIGINT NOT NULL REFERENCES issues(id), + file_name VARCHAR(200) NOT NULL, + file_path VARCHAR(500) NOT NULL, + file_size BIGINT NOT NULL, + mime_type VARCHAR(100), + uploaded_by BIGINT NOT NULL REFERENCES users(id), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- notifications 通知表 +CREATE TABLE notifications ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(id), + title VARCHAR(200) NOT NULL, + content VARCHAR(1000) NOT NULL, + type VARCHAR(30) NOT NULL, + link VARCHAR(500), + is_read BOOLEAN DEFAULT FALSE, + issue_id BIGINT REFERENCES issues(id), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- task_executions 异步任务表 +CREATE TABLE task_executions ( + id BIGSERIAL PRIMARY KEY, + task_id VARCHAR(64) NOT NULL UNIQUE, + task_type VARCHAR(30) NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'pending', + result_url VARCHAR(500), + error_message TEXT, + created_by BIGINT NOT NULL REFERENCES users(id), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + started_at TIMESTAMP, + completed_at TIMESTAMP, + retry_count INT DEFAULT 0 +); + +-- 索引 +CREATE INDEX idx_issues_dept_status ON issues(department_id, status, created_at DESC); +CREATE INDEX idx_issues_assignee ON issues(assignee_id, status); +CREATE INDEX idx_issues_deadline ON issues(deadline); +CREATE INDEX idx_issues_no ON issues(issue_no); +CREATE INDEX idx_issue_logs_issue ON issue_logs(issue_id, created_at DESC); +CREATE INDEX idx_attachments_issue ON attachments(issue_id); +CREATE INDEX idx_notifications_user ON notifications(user_id, is_read, created_at DESC); diff --git a/backend/ims-service/src/main/resources/db/migration/V1.2__ai_analysis.sql b/backend/ims-service/src/main/resources/db/migration/V1.2__ai_analysis.sql new file mode 100644 index 0000000..d6625e9 --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V1.2__ai_analysis.sql @@ -0,0 +1,28 @@ +-- ai_analysis AI分析结果表 +CREATE TABLE ai_analysis ( + id BIGSERIAL PRIMARY KEY, + issue_id BIGINT NOT NULL REFERENCES issues(id), + category VARCHAR(100), + keywords VARCHAR(500), + root_cause VARCHAR(1000), + suggestion TEXT, + status VARCHAR(20) DEFAULT 'pending', + helpful_count INT DEFAULT 0, + prompt_template_id VARCHAR(100), + prompt_version INT, + model_provider VARCHAR(20), + model_name VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- ai_feedback AI反馈表 +CREATE TABLE ai_feedback ( + id BIGSERIAL PRIMARY KEY, + ai_analysis_id BIGINT NOT NULL REFERENCES ai_analysis(id), + user_id BIGINT NOT NULL REFERENCES users(id), + is_helpful BOOLEAN NOT NULL, + comment VARCHAR(500), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_ai_analysis_issue ON ai_analysis(issue_id, created_at DESC); diff --git a/backend/ims-service/src/main/resources/db/migration/V1.3__agent_tables.sql b/backend/ims-service/src/main/resources/db/migration/V1.3__agent_tables.sql new file mode 100644 index 0000000..3ec7c15 --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V1.3__agent_tables.sql @@ -0,0 +1,43 @@ +-- agent_plans Agent规划表 +CREATE TABLE agent_plans ( + id BIGSERIAL PRIMARY KEY, + issue_id BIGINT NOT NULL REFERENCES issues(id), + goal TEXT NOT NULL, + plan_steps JSONB NOT NULL, + status VARCHAR(20) DEFAULT 'pending', + requires_approval BOOLEAN DEFAULT FALSE, + approval_status VARCHAR(20), + approval_comment VARCHAR(500), + created_by BIGINT REFERENCES users(id), + model_provider VARCHAR(20), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + completed_at TIMESTAMP +); + +-- agent_memories Agent长期记忆表 +CREATE TABLE agent_memories ( + id BIGSERIAL PRIMARY KEY, + issue_summary TEXT NOT NULL, + solution_steps JSONB NOT NULL, + effectiveness_score DECIMAL(3,2) DEFAULT 0.00, + embedding VECTOR(1536), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- tool_executions 工具执行明细表 +CREATE TABLE tool_executions ( + id BIGSERIAL PRIMARY KEY, + plan_id BIGINT REFERENCES agent_plans(id), + tool_name VARCHAR(50) NOT NULL, + input_params JSONB NOT NULL, + output_result TEXT, + status VARCHAR(20) DEFAULT 'success', + execution_time_ms BIGINT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_agent_plans_issue ON agent_plans(issue_id, created_at DESC); +CREATE INDEX idx_tool_executions_plan ON tool_executions(plan_id); +-- agent_memories 向量索引 +CREATE INDEX idx_agent_memories_embedding ON agent_memories USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100); diff --git a/backend/ims-service/src/main/resources/db/migration/V1.4__knowledge_tables.sql b/backend/ims-service/src/main/resources/db/migration/V1.4__knowledge_tables.sql new file mode 100644 index 0000000..d0e9c55 --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V1.4__knowledge_tables.sql @@ -0,0 +1,40 @@ +-- knowledge_documents 知识库文档表 +CREATE TABLE knowledge_documents ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + file_path VARCHAR(500) NOT NULL, + file_size BIGINT NOT NULL, + file_type VARCHAR(20) NOT NULL, + chunk_count INT DEFAULT 0, + status VARCHAR(20) DEFAULT 'pending', + error_message TEXT, + uploaded_by BIGINT NOT NULL REFERENCES users(id), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- knowledge_chunks 知识库向量切片表 +CREATE TABLE knowledge_chunks ( + id BIGSERIAL PRIMARY KEY, + doc_id BIGINT NOT NULL REFERENCES knowledge_documents(id), + content TEXT NOT NULL, + embedding VECTOR(1536) NOT NULL, + metadata JSONB, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- knowledge_search_logs 知识库检索审计表 +CREATE TABLE knowledge_search_logs ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT REFERENCES users(id), + issue_id BIGINT REFERENCES issues(id), + query TEXT NOT NULL, + rewritten_query TEXT, + top_k INT DEFAULT 5, + total_matches INT, + duration_ms INT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_knowledge_chunks_embedding ON knowledge_chunks USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100); +CREATE INDEX idx_knowledge_search_logs_user ON knowledge_search_logs(user_id, created_at DESC); diff --git a/backend/ims-service/src/main/resources/db/migration/V1.5__prompt_tables.sql b/backend/ims-service/src/main/resources/db/migration/V1.5__prompt_tables.sql new file mode 100644 index 0000000..5696289 --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V1.5__prompt_tables.sql @@ -0,0 +1,48 @@ +-- prompt_templates Prompt模板主表 +CREATE TABLE prompt_templates ( + id BIGSERIAL PRIMARY KEY, + template_id VARCHAR(100) NOT NULL UNIQUE, + name VARCHAR(200) NOT NULL, + category VARCHAR(50) NOT NULL, + version INT NOT NULL DEFAULT 1, + content TEXT NOT NULL, + variables JSONB, + output_schema JSONB, + is_active BOOLEAN DEFAULT TRUE, + is_default BOOLEAN DEFAULT FALSE, + created_by BIGINT REFERENCES users(id), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- prompt_template_versions Prompt模板版本历史表 +CREATE TABLE prompt_template_versions ( + id BIGSERIAL PRIMARY KEY, + template_id VARCHAR(100) NOT NULL, + version INT NOT NULL, + content TEXT NOT NULL, + change_log VARCHAR(500), + created_by BIGINT REFERENCES users(id), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- prompt_render_logs Prompt渲染审计表 +CREATE TABLE prompt_render_logs ( + id BIGSERIAL PRIMARY KEY, + request_id VARCHAR(64) NOT NULL, + template_id VARCHAR(100) NOT NULL, + template_version INT NOT NULL, + rendered_prompt TEXT NOT NULL, + variables_used JSONB, + tokens_input INT, + tokens_output INT, + execution_time_ms INT, + llm_model VARCHAR(50), + model_provider VARCHAR(20), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_prompt_templates_id ON prompt_templates(template_id, is_active); +CREATE INDEX idx_prompt_templates_cat ON prompt_templates(category, is_active); +CREATE INDEX idx_prompt_render_logs_req ON prompt_render_logs(request_id); +CREATE INDEX idx_prompt_render_logs_tpl ON prompt_render_logs(template_id, created_at DESC); diff --git a/backend/ims-service/src/main/resources/db/migration/V1.6__seed_data.sql b/backend/ims-service/src/main/resources/db/migration/V1.6__seed_data.sql new file mode 100644 index 0000000..76d44d9 --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V1.6__seed_data.sql @@ -0,0 +1,43 @@ +-- 部门数据 +INSERT INTO departments (id, name, parent_id, sort_order) VALUES +(1, '总公司', NULL, 1), +(2, '技术部', 1, 1), +(3, '质量部', 1, 2), +(4, '产品部', 1, 3); + +-- 角色数据 +INSERT INTO roles (id, name, description, agent_auto_execute) VALUES +(1, '超级管理员', '系统最高权限角色', TRUE), +(2, '部门管理员', '管理部门内用户和业务', FALSE), +(3, '指摘录入员', '负责录入指摘', FALSE), +(4, '整改担当', '负责对应整改', FALSE), +(5, '验证人员', '负责验证整改结果', FALSE), +(6, '只读用户', '仅有查看权限', FALSE); + +-- 用户数据(密码均为 Admin@2026,bcrypt 哈希) +INSERT INTO users (id, userid, username, email, password_hash, department_id, is_active, agent_auto_execute) VALUES +(1, 'admin', '系统管理员', 'admin@ims.com', '$2b$10$RgDKGUTF75aTu/8apLJjQOkDnOQWSd0/bBu4XBj6pXK4XvZcmQ6e6', 1, TRUE, TRUE), +(2, 'zhangsan', '张三', 'zhangsan@ims.com', '$2b$10$RgDKGUTF75aTu/8apLJjQOkDnOQWSd0/bBu4XBj6pXK4XvZcmQ6e6', 2, TRUE, FALSE), +(3, 'lisi', '李四', 'lisi@ims.com', '$2b$10$RgDKGUTF75aTu/8apLJjQOkDnOQWSd0/bBu4XBj6pXK4XvZcmQ6e6', 3, TRUE, FALSE); + +-- 用户角色关联 +INSERT INTO user_roles (user_id, role_id) VALUES +(1, 1), +(2, 2), +(3, 5); + +-- 权限数据(菜单权限) +INSERT INTO permissions (id, code, name, resource, description) VALUES +(1, 'MENU_DASHBOARD', '工作台', 'menu', '工作台页面'), +(2, 'MENU_ISSUES', '指摘列表', 'menu', '指摘列表页面'), +(3, 'MENU_BATCH_INPUT', '批量录入', 'menu', '批量录入页面'), +(4, 'MENU_AI_ANALYSIS', 'AI智能分析', 'menu', 'AI分析管理页面'), +(5, 'MENU_KNOWLEDGE_BASE', '知识库管理', 'menu', '知识库管理页面'), +(6, 'MENU_SYSTEM_USERS', '用户管理', 'menu', '用户管理页面'), +(7, 'MENU_SYSTEM_ROLES', '角色权限', 'menu', '角色权限页面'), +(8, 'MENU_SYSTEM_LOGS', '系统日志', 'menu', '系统日志页面'), +(9, 'MENU_SYSTEM_AGENT', 'Agent管理', 'menu', 'Agent管理页面'); + +-- 超级管理员赋权 +INSERT INTO role_permissions (role_id, permission_id) +SELECT 1, id FROM permissions; diff --git a/backend/ims-service/src/main/resources/db/migration/V1.7__fix_password_hash.sql b/backend/ims-service/src/main/resources/db/migration/V1.7__fix_password_hash.sql new file mode 100644 index 0000000..5a82441 --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V1.7__fix_password_hash.sql @@ -0,0 +1,2 @@ +UPDATE users SET password_hash = '$2b$10$RgDKGUTF75aTu/8apLJjQOkDnOQWSd0/bBu4XBj6pXK4XvZcmQ6e6' +WHERE userid IN ('admin', 'zhangsan', 'lisi'); diff --git a/backend/ims-service/src/main/resources/db/migration/V1.8__fix_vector_dimension.sql b/backend/ims-service/src/main/resources/db/migration/V1.8__fix_vector_dimension.sql new file mode 100644 index 0000000..a388f2b --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V1.8__fix_vector_dimension.sql @@ -0,0 +1,14 @@ +-- 修正向量维度:nomic-embed-text 输出 768 维,而非 1536 +DROP TABLE IF EXISTS knowledge_chunks CASCADE; + +CREATE TABLE knowledge_chunks ( + id BIGSERIAL PRIMARY KEY, + doc_id BIGINT NOT NULL REFERENCES knowledge_documents(id), + content TEXT NOT NULL, + embedding VECTOR(768) NOT NULL, + metadata JSONB, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +DROP INDEX IF EXISTS idx_knowledge_chunks_embedding; +CREATE INDEX idx_knowledge_chunks_embedding ON knowledge_chunks USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100); diff --git a/backend/ims-service/src/main/resources/db/migration/V1.9__seed_prompt_templates.sql b/backend/ims-service/src/main/resources/db/migration/V1.9__seed_prompt_templates.sql new file mode 100644 index 0000000..504eaa4 --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V1.9__seed_prompt_templates.sql @@ -0,0 +1,44 @@ +-- Prompt模板种子数据(内容取自概要设计 3.4 节,其余模板与 V4.2 一致) +-- 模板ID:SYS_ROLE_001 / PLAN_001 / ANALYSIS_ROOT_CAUSE_001 / QUERY_REWRITE_001 / MEMORY_RERANK_001 / FORMAT_JSON_001 + +INSERT INTO prompt_templates (template_id, name, category, version, content, variables, output_schema, is_active, is_default, created_by) VALUES + +('SYS_ROLE_001', '系统角色Prompt模板', 'system', 1, +E'# 角色定义\n你是「指摘管理专家Agent」,专精于制造业与软件工程领域的质量问题跟踪与整改管理。\n你的知识边界严格限定于以下范围:\n- 指摘全生命周期管理(创建 → 分配 → 整改 → 验证 → 关闭)\n- 工程质量管理标准(ISO 9001、CMMI、企业内部QA规范)\n- 历史成功案例与失败教训(来自本地知识库检索)\n- 项目工程阶段管理(需求/设计/编码/测试/部署/运维)\n\n# 身份标识\n- 系统名称:指摘管理系统(Issue Tracking System)\n- 你的名称:指摘助手(Issue Assistant)\n- 当前版本:V6.0\n\n# 行为约束(强制遵守,违反将导致操作被拒绝)\n1. 【术语强制】所有输入输出必须使用标准指摘管理术语:\n - 使用「对应者」而非「负责人」「处理人」\n - 使用「PGM」指代项目编号,格式为 PGM-XXXX\n - 使用「指摘」而非「问题」「缺陷」「bug」\n - 使用「对应内容」而非「解决方案」「修复方案」\n - 使用「确认者」而非「审核人」「验收人」\n - 使用「对应完了日」而非「完成日期」\n - 使用「review工数」和「对应工数」计量工作量\n - 状态术语:draft(草稿)/ open(待处理)/ in_progress(进行中)/ resolved(已解决)/ verified(已验证)/ closed(已关闭)/ rejected(已驳回)\n\n2. 【审批强制】涉及以下高风险操作必须调用 request_human_approval 工具:\n - 关闭指摘(close_issue)\n - 删除指摘(delete_issue)\n - 跨部门分配指摘(assignee部门 ≠ 当前指摘部门)\n - 修改已验证状态的指摘\n - 批量操作超过10条指摘\n - 任何涉及数据删除或不可逆变更的操作\n\n3. 【知识优先】生成整改建议时,必须遵循以下优先级:\n - 第一优先:引用本地知识库中的历史成功案例(需标注案例ID)\n - 第二优先:引用企业内部QA规范\n - 第三优先:基于通用工程管理最佳实践\n - 禁止:提供与指摘管理无关的建议(如财务、人事、市场等)\n\n4. 【上下文依赖】当用户意图模糊时,必须优先询问以下信息以明确上下文:\n - 指摘ID(issue_id)\n - 工程阶段(phase)\n - 归属部门(department_id)\n - 当前状态(status)\n 禁止在上下文不明的情况下执行操作。\n\n5. 【输出结构化】所有输出必须严格符合预定义JSON Schema或Markdown模板,禁止自由发挥。\n - 分析类输出 → JSON格式\n - 对话类输出 → Markdown格式,含结构化标题\n - 工具调用 → 严格参数格式\n\n6. 【安全边界】禁止执行以下行为:\n - 生成、传播或协助创建恶意代码\n - 泄露其他用户的敏感信息(密码、个人联系方式等)\n - 执行超出指摘管理范畴的系统操作\n - 伪造或篡改审计日志\n\n# 当前环境信息\n- 当前用户:{{currentUserName}}({{currentUserRole}})\n- 所属部门:{{currentUserDepartment}}\n- Agent自动执行权限:{{agentAutoExecuteEnabled}}\n- 可用工具列表(格式:工具名 - 描述):\n{{availableTools}}\n- 当前时间:{{currentTime}}\n\n# 工具调用规则(适用于当前AI引擎:{{modelProvider}})\n- 当你需要调用工具时,请在你的回复中输出一个 JSON 对象,格式为:\n{\n "tool": "工具名",\n "parameters": { ... }\n}\n- 不要用自然语言额外解释,直接输出该 JSON。\n- 如果目标已完成,输出 { "tool": "goal_completed", "parameters": {} }。', +'[{"name":"currentUserName","type":"string","required":true,"description":"当前登录用户名"},{"name":"currentUserRole","type":"string","required":true,"description":"当前用户角色名"},{"name":"currentUserDepartment","type":"string","required":true,"description":"当前用户所属部门"},{"name":"agentAutoExecuteEnabled","type":"boolean","required":true,"description":"Agent自动执行权限"},{"name":"availableTools","type":"string","required":true,"description":"可用工具列表(已格式化文本)"},{"name":"currentTime","type":"string","required":true,"description":"当前时间"},{"name":"modelProvider","type":"string","required":true,"description":"当前AI引擎"}]', +'{"type":"object","description":"系统角色约束,用于引导模型行为","properties":{"tool":{"type":"string","description":"工具名"},"parameters":{"type":"object","description":"工具参数"}}}', +TRUE, TRUE, NULL), + +('PLAN_001', 'ReAct规划Prompt模板', 'agent', 1, +E'# 任务\n基于用户目标和当前上下文,生成结构化的执行计划(ReAct循环)。\n\n# 输入\n用户目标:{{userGoal}}\n当前指摘上下文:{{issueContext}}\n历史相似案例:{{similarCases}}\n可用工具列表:{{availableTools}}\n已执行步骤:{{executedSteps}}\n当前步数:{{currentStep}} / 最大步数:{{maxSteps}}\n\n# 思考格式(必须严格遵循)\n你必须按以下结构输出思考过程,**且最终必须输出一个 JSON 对象**,其中 `action` 字段包含下一步的工具调用。\n\n[思考]\n1. 用户意图解析:将自然语言目标转化为明确的业务操作\n2. 上下文评估:当前已掌握的信息是否足够执行\n3. 工具选择:从可用工具中选择最合适的工具(仅限1个)\n4. 参数生成:为选定工具生成所需参数\n5. 风险评估:判断是否需要人工审批\n6. 输出格式:确认输出符合JSON Schema\n\n[行动]\n```json\n{\n "action": {\n "tool_name": "工具名称(必须从可用工具列表中选择)",\n "parameters": {\n "param1": "值1",\n "param2": "值2"\n },\n "reasoning": "选择此工具的理由(一句话)",\n "requires_approval": true/false,\n "approval_reason": "如需要审批,写明原因"\n }\n}\n```\n\n# 约束\n- 每步只能调用一个工具。\n- 如需审批,必须调用 request_human_approval 工具,不得直接执行目标操作。\n- 参数中的ID类字段必须为数字,禁止传递字符串ID。\n- 如用户目标已完成,输出 tool_name 为 "goal_completed"。\n- 如达到最大步数仍未完成,输出 tool_name 为 "max_steps_reached" 并说明原因。', +'[{"name":"userGoal","type":"string","required":true,"description":"用户目标"},{"name":"issueContext","type":"string","required":true,"description":"当前指摘上下文"},{"name":"similarCases","type":"string","required":false,"description":"历史相似案例"},{"name":"availableTools","type":"string","required":true,"description":"可用工具列表"},{"name":"executedSteps","type":"string","required":false,"description":"已执行步骤"},{"name":"currentStep","type":"integer","required":true,"description":"当前步数"},{"name":"maxSteps","type":"integer","required":true,"description":"最大步数"}]', +'{"type":"object","properties":{"action":{"type":"object","required":["tool_name","parameters","reasoning","requires_approval"],"properties":{"tool_name":{"type":"string"},"parameters":{"type":"object"},"reasoning":{"type":"string"},"requires_approval":{"type":"boolean"},"approval_reason":{"type":"string"}}}}}', +TRUE, TRUE, NULL), + +('ANALYSIS_ROOT_CAUSE_001', 'AI分析Prompt模板', 'analysis', 1, +E'# 任务\n你是指摘管理专家,请对给定的指摘进行深度根因分析,并输出结构化 JSON。\n\n# 输入\n指摘编号:{{issueNo}}\n指摘标题:{{issueTitle}}\n指摘描述:{{issueDescription}}\n工程阶段:{{issuePhase}}\n指摘分类:{{issueCategory}}\n影响级别:{{issueImpactLevel}}\n历史相似案例:{{similarCases}}\n\n# 分析要求\n1. 从需求理解偏差、设计缺陷、编码实现、测试遗漏、环境/数据、流程管理等维度判断根因。\n2. 关键词提取3-5个,覆盖核心业务概念与原因。\n3. 整改建议必须可执行、分优先级,并优先引用相似案例。\n\n# 输出格式(严格JSON,禁止Markdown围栏)\n{\n "category": "根因分类(如:需求理解偏差/设计缺陷/编码实现/测试遗漏/环境数据/流程管理)",\n "keywords": ["关键词1", "关键词2", "关键词3"],\n "rootCause": "根因分析,100-200字",\n "suggestion": "整改建议,含优先级与具体措施" \n}\n\n# 约束\n- 输出必须是可以直接 JSON.parse 的纯JSON。\n- 不要输出解释性文字或 Markdown 代码块。', +'[{"name":"issueNo","type":"string","required":true,"description":"指摘编号"},{"name":"issueTitle","type":"string","required":true,"description":"指摘标题"},{"name":"issueDescription","type":"string","required":true,"description":"指摘描述"},{"name":"issuePhase","type":"string","required":false,"description":"工程阶段"},{"name":"issueCategory","type":"string","required":false,"description":"指摘分类"},{"name":"issueImpactLevel","type":"string","required":false,"description":"影响级别"},{"name":"similarCases","type":"string","required":false,"description":"历史相似案例"}]', +'{"type":"object","required":["category","keywords","rootCause","suggestion"],"properties":{"category":{"type":"string"},"keywords":{"type":"array","items":{"type":"string"}},"rootCause":{"type":"string"},"suggestion":{"type":"string"}}}', +TRUE, TRUE, NULL), + +('QUERY_REWRITE_001', 'Query改写Prompt模板', 'knowledge', 1, +E'# 任务\n将用户的自然语言查询改写为适合向量检索的领域专业查询。\n\n# 输入\n用户原始查询:{{query}}\n领域上下文:{{domainContext}}\n\n# 改写要求\n1. 使用标准指摘管理术语(对应者、PGM、指摘、对应内容等)。\n2. 去除口语化、语气词与冗余信息。\n3. 必要时拆分多意图为多个子查询(最多3个)。\n\n# 输出格式(严格JSON)\n{\n "rewritten_query": "改写后的主查询",\n "queries": ["子查询1", "子查询2"],\n "keywords": ["检索关键词1", "检索关键词2"]\n}\n\n# 约束\n- 输出必须是纯JSON。\n- 不得添加与查询无关的内容。', +'[{"name":"query","type":"string","required":true,"description":"用户原始查询"},{"name":"domainContext","type":"string","required":false,"description":"领域上下文"}]', +'{"type":"object","required":["rewritten_query","queries","keywords"],"properties":{"rewritten_query":{"type":"string"},"queries":{"type":"array","items":{"type":"string"}},"keywords":{"type":"array","items":{"type":"string"}}}}', +TRUE, TRUE, NULL), + +('MEMORY_RERANK_001', '记忆重排序Prompt模板', 'knowledge', 1, +E'# 任务\n对检索到的历史案例记忆进行相关性评估并重新排序。\n\n# 输入\n用户查询:{{query}}\n候选记忆列表:{{candidates}}\n\n# 排序要求\n1. 按与查询的相关性从高到低排序,同一相关度时按 effectivenessScore 高的优先。\n2. 只输出与查询明确相关的记忆,过滤无关项。\n\n# 输出格式(严格JSON)\n{\n "ranked_ids": [数字ID列表,按相关度从高到低],\n "reasoning": "排序依据简述"\n}\n\n# 约束\n- ranked_ids 必须来自候选记忆的 id 字段。\n- 输出必须是纯JSON。', +'[{"name":"query","type":"string","required":true,"description":"用户查询"},{"name":"candidates","type":"string","required":true,"description":"候选记忆列表(含id、issueSummary、effectivenessScore)"}]', +'{"type":"object","required":["ranked_ids","reasoning"],"properties":{"ranked_ids":{"type":"array","items":{"type":"integer"}},"reasoning":{"type":"string"}}}', +TRUE, TRUE, NULL), + +('FORMAT_JSON_001', '输出格式化模板', 'system', 1, +E'# 输出格式约束(强制)\n所有输出必须严格遵循以下规则:\n\n1. 当要求 JSON 输出时:\n - 必须输出纯JSON,不得包含 Markdown 围栏(```json ```)。\n - 字段名必须与要求完全一致,不得增删。\n - 字符串必须使用双引号。\n\n2. 目标 JSON Schema:\n{{outputSchema}}\n\n3. 当要求 Markdown 输出时:\n - 使用结构化标题(## / ###)组织内容。\n - 列表使用 - 前缀。\n\n# 约束\n- 任何情况下都不得输出解释性前缀或后缀文字。', +'[{"name":"outputSchema","type":"string","required":true,"description":"目标JSON Schema"}]', +'{"type":"object","description":"通用输出格式约束"}', +TRUE, TRUE, NULL); + +-- 版本历史 +INSERT INTO prompt_template_versions (template_id, version, content, change_log, created_by) +SELECT template_id, version, content, '初始版本', NULL FROM prompt_templates; diff --git a/backend/ims-service/src/main/resources/db/migration/V2.0__seed_demo_data.sql b/backend/ims-service/src/main/resources/db/migration/V2.0__seed_demo_data.sql new file mode 100644 index 0000000..514b993 --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V2.0__seed_demo_data.sql @@ -0,0 +1,273 @@ +-- ============================================================ +-- V2.0 全模块演示数据(Agent 管理 / AI 分析 / 知识库 / 指摘) +-- 依赖 V1.6 种子用户(admin=1, 张三=2, 李四=3)、部门(2技术部 3质量部 4产品部) +-- ============================================================ + +-- ---------- issues 指摘 ---------- +INSERT INTO issues (id, issue_no, title, description, status, priority, deadline, phase, sub_project, category, + impact_level, impact_scope, deployment, pgm_no, review_workload, response_workload, + response_content, ng_reason, response_completed_at, confirm_at, + creator_id, assignee_id, department_id, reviewer_id, validator_id, + agent_last_plan_id, agent_status, created_at, updated_at, closed_at, is_deleted) VALUES +(1, 'ISS-2026-001', '指摘列表批量导出 Excel 列顺序与标准模板不一致', + '用户在指摘列表页勾选多条记录后批量导出 Excel,导出的列顺序为「创建时间/标题/状态」,与标准模板要求的「指摘编号/标题/对应者/状态」不一致,需人工二次调整,影响月度报告生成效率。', + 'draft', 'medium', NOW() + interval '7 days', '编码', '指摘管理', '功能缺陷', 'C', '影响月度报告数据整理', 'V6.0', 'PGM-1001', 1.0, 2.0, NULL, NULL, NULL, NULL, + 1, 2, 2, NULL, NULL, NULL, 'human_driven', NOW() - interval '2 hours', NOW() - interval '2 hours', NULL, FALSE), +(2, 'ISS-2026-002', '知识库检索接口在无匹配结果时返回 500', + '当查询词在知识库中无任何匹配时,GET /knowledge/search 返回 500 而非空结果集,前端无法给出友好提示,用户误以为系统故障。', + 'open', 'high', NOW() + interval '5 days', '测试', '知识库', '功能缺陷', 'B', '影响知识库检索可用性', 'V6.0', 'PGM-1002', 0.5, 1.5, NULL, NULL, NULL, NULL, + 2, 2, 2, NULL, NULL, NULL, 'rejected', NOW() - interval '3 hours', NOW() - interval '3 hours', NULL, FALSE), +(3, 'ISS-2026-003', '已关闭的指摘可被重复打开', + '已验证并关闭的指摘,在详情页仍可点击「重新打开」,导致已关闭记录重新进入整改流程,破坏关闭状态的一致性。', + 'open', 'high', NOW() + interval '4 days', '编码', '指摘管理', '流程管理', 'A', '影响关闭状态一致性', 'V6.0', 'PGM-1003', 1.5, 3.0, NULL, NULL, NULL, NULL, + 3, 2, 3, NULL, NULL, NULL, 'failed', NOW() - interval '1 day', NOW() - interval '1 day', NULL, FALSE), +(4, 'ISS-2026-004', '日期范围筛选按日粒度统计不准确', + 'AI 分析页按「对应完了日」筛选时,当日创建的新指摘未被统计进结果集,与 SQL 中 NOW() 精度差异导致边界数据丢失。', + 'in_progress', 'medium', NOW() + interval '10 days', '测试', 'AI分析', '功能缺陷', 'C', '影响分析统计准确性', 'V6.0', 'PGM-1004', 2.0, 2.5, NULL, NULL, NULL, NULL, + 1, 3, 2, 2, NULL, NULL, 'human_driven', NOW() - interval '2 days', NOW() - interval '1 day', NULL, FALSE), +(5, 'ISS-2026-005', 'Agent 跨部门分配指摘未触发人工审批', + 'Agent 将指摘分配给其他部门的对应者时,未调用 request_human_approval,直接执行了跨部门分配,绕过审批策略。', + 'in_progress', 'high', NOW() + interval '6 days', '编码', 'Agent', '功能缺陷', 'A', '影响审批合规', 'V6.0', 'PGM-1005', 2.5, 4.0, NULL, NULL, NULL, NULL, + 2, 3, 2, 3, NULL, NULL, 'human_driven', NOW() - interval '2 days', NOW() - interval '2 days', NULL, FALSE), +(6, 'ISS-2026-006', '上传附件大于 10MB 时前端无任何提示', + '附件上传时若文件超过后端限制大小,前端不提示且无 loading 结束回调,用户认为上传成功但实际未生效。', + 'resolved', 'high', NOW() - interval '1 day', '部署', '附件管理', '功能缺陷', 'B', '影响大附件上传体验', 'V6.0', 'PGM-1006', 1.0, 2.0, + '已在上传组件中加入文件大小预校验,超过 10MB 时直接拦截并提示;同时补充超时与失败的回调处理。', + '前端未做大小预校验', NOW() - interval '3 days', NOW() - interval '2 days', + 3, 2, 3, 2, 2, NULL, 'human_driven', NOW() - interval '6 days', NOW() - interval '3 days', NULL, FALSE), +(7, 'ISS-2026-007', '系统通知未读计数在不同浏览器不一致', + '同一账号在 Chrome 与 Edge 中未读通知计数不同,疑似本地缓存与后端计数存在时间差,影响用户对通知状态的判断。', + 'resolved', 'medium', NOW() - interval '2 days', '部署', '通知中心', '功能缺陷', 'C', '影响通知计数一致性', 'V6.0', 'PGM-1007', 0.5, 1.0, + '统一改为登录后从后端拉取未读计数,并添加 60 秒轮询,消除浏览器间差异。', + '计数来源不一致', NOW() - interval '5 days', NOW() - interval '4 days', + 1, 3, 4, 2, 3, NULL, 'human_driven', NOW() - interval '8 days', NOW() - interval '5 days', NULL, FALSE), +(8, 'ISS-2026-008', '批量录入重复点击提交导致指摘重复创建', + '批量录入页在提交响应较慢时,用户连续点击「提交」按钮,会创建多条内容相同的指摘记录,缺少幂等保护。', + 'verified', 'medium', NOW() - interval '3 days', '编码', '批量录入', '功能缺陷', 'B', '影响数据准确性', 'V6.0', 'PGM-1008', 1.5, 2.5, + '提交按钮提交期间禁用,后端按内容 hash 加唯一约束防重。', + '缺少幂等处理', NOW() - interval '7 days', NOW() - interval '6 days', + 2, 3, 4, 2, 3, 1, 'awaiting_approval', NOW() - interval '9 days', NOW() - interval '7 days', NULL, FALSE), +(9, 'ISS-2026-009', '知识库向量检索中文分词召回率偏低', + '使用字符级切分后,长句查询召回率仍偏低,无法命中包含「指摘对应完了日」等复合术语的历史案例,影响知识复用。', + 'verified', 'high', NOW() - interval '1 day', '测试', '知识库', '性能问题', 'B', '影响知识检索质量', 'V6.0', 'PGM-1009', 2.0, 3.5, + '优化查询改写提示词,对复合术语进行扩展检索,增加 top_k 重排。', + '复合术语切分不理想', NOW() - interval '4 days', NOW() - interval '3 days', + 1, 2, 2, 2, 3, 2, 'awaiting_approval', NOW() - interval '10 days', NOW() - interval '8 days', NULL, FALSE), +(10, 'ISS-2026-010', 'AI 分析建议引用了不存在的知识库案例', + 'AI 分析结果中的整改建议标注了历史案例编号,但该案例在知识库中已被删除,点击跳转 404,破坏建议的可追溯性。', + 'open', 'medium', NOW() + interval '3 days', '测试', 'AI分析', '功能缺陷', 'C', '影响建议可信度', 'V6.0', 'PGM-1010', 1.0, 1.5, NULL, NULL, NULL, NULL, + 2, 3, 3, 2, NULL, 3, 'awaiting_approval', NOW() - interval '12 hours', NOW() - interval '12 hours', NULL, FALSE), +(11, 'ISS-2026-011', '角色权限修改后未即时生效', + '管理员修改角色权限后,已登录用户仍需重新登录才能看到新权限,权限缓存未按角色失效。', + 'open', 'medium', NOW() + interval '8 days', '运维', '权限管理', '流程管理', 'C', '影响权限即时性', 'V6.0', 'PGM-1011', 1.0, 1.5, NULL, NULL, NULL, NULL, + 3, 2, 3, 3, NULL, 4, 'awaiting_approval', NOW() - interval '5 hours', NOW() - interval '5 hours', NULL, FALSE), +(12, 'ISS-2026-012', '指摘对应完了日逾期未自动提醒', + '已超过对应完了日的指摘,系统未自动向对应者与部门管理员发送提醒,导致整改逾期无人跟进。', + 'closed', 'high', NOW() - interval '2 days', '运维', '提醒中心', '功能缺陷', 'B', '影响整改时效', 'V6.0', 'PGM-1012', 1.5, 3.0, + '新增定时任务,每日扫描逾期指摘并推送通知至对应者与部门管理员。', + '缺少逾期扫描定时任务', NOW() - interval '6 days', NOW() - interval '5 days', + 1, 3, 4, 2, 3, 5, 'completed', NOW() - interval '12 days', NOW() - interval '6 days', NOW() - interval '5 days', FALSE); + +SELECT setval('issues_id_seq', (SELECT MAX(id) FROM issues)); + +-- ---------- issue_logs 指摘操作日志 ---------- +INSERT INTO issue_logs (issue_id, user_id, action, from_status, to_status, remark, created_at) VALUES +(1, 1, 'CREATE', NULL, 'draft', '录入新指摘,等待确认', NOW() - interval '2 hours'), +(2, 2, 'CREATE', NULL, 'open', '录入新指摘', NOW() - interval '3 hours'), +(3, 3, 'CREATE', NULL, 'open', '录入新指摘', NOW() - interval '1 day'), +(4, 1, 'CREATE', NULL, 'in_progress', '录入并立即进入整改', NOW() - interval '2 days'), +(5, 2, 'CREATE', NULL, 'in_progress', 'Agent 触达', NOW() - interval '2 days'), +(6, 3, 'UPDATE', 'in_progress', 'resolved', '对应内容已提交', NOW() - interval '3 days'), +(6, 2, 'REVIEW', 'resolved', 'resolved', 'review 通过', NOW() - interval '2 days'), +(6, 2, 'VERIFY', 'resolved', 'resolved', '验证通过', NOW() - interval '2 days'), +(8, 2, 'UPDATE', 'open', 'verified', '整改完成并通过验证', NOW() - interval '7 days'), +(8, 1, 'UPDATE', 'verified', 'verified', 'Agent 请求更新状态(待审批)', NOW() - interval '2 hours'), +(9, 1, 'UPDATE', 'open', 'verified', '整改完成并通过验证', NOW() - interval '4 days'), +(9, 2, 'UPDATE', 'verified', 'verified', 'Agent 请求调整优先级(待审批)', NOW() - interval '1 hour'), +(10, 2, 'UPDATE', 'open', 'open', 'Agent 请求补充对应内容(待审批)', NOW() - interval '30 minutes'), +(11, 3, 'UPDATE', 'open', 'open', 'Agent 请求关闭指摘(待审批)', NOW() - interval '20 minutes'), +(12, 1, 'CLOSE', 'verified', 'closed', '整改完成并关闭', NOW() - interval '5 days'); + +-- ---------- notifications 通知 ---------- +INSERT INTO notifications (user_id, title, content, type, link, is_read, issue_id, created_at) VALUES +(2, '有新的指摘分配', '指摘 ISS-2026-004 已分配给您,请及时对应', 'ASSIGNMENT', '/issues/detail/4', FALSE, 4, NOW() - interval '1 day'), +(3, '整改对应提醒', '指摘 ISS-2026-006 的对应完了日已临近,请尽快完成对应', 'REMINDER', '/issues/detail/6', FALSE, 6, NOW() - interval '1 day'), +(2, 'Agent 操作待审批', 'Agent 请求将 ISS-2026-009 的优先级调整为 high,请审批', 'APPROVAL', '/system/agent-admin', FALSE, 9, NOW() - interval '1 hour'), +(3, 'Agent 操作待审批', 'Agent 请求关闭指摘 ISS-2026-011,请审批', 'APPROVAL', '/system/agent-admin', FALSE, 11, NOW() - interval '20 minutes'), +(1, '知识库上传完成', '文档「指摘管理操作规范.txt」已完成解析与向量化', 'SYSTEM', '/knowledge-base', TRUE, NULL, NOW() - interval '3 days'), +(2, 'AI 分析完成', '指摘 ISS-2026-002 的 AI 根因分析已生成', 'SYSTEM', '/ai-analysis', TRUE, 2, NOW() - interval '2 days'); + +-- ---------- ai_analysis AI 分析 ---------- +INSERT INTO ai_analysis (id, issue_id, category, keywords, root_cause, suggestion, status, helpful_count, + prompt_template_id, prompt_version, model_provider, model_name, created_at) VALUES +(1, 1, '编码实现', '批量导出,Excel,列顺序,模板', + '导出功能使用固定列定义而未读取标准模板配置,列顺序写死在代码中,模板升级后未同步。', + '将列顺序改为从模板配置读取,并提供模板与导出结果的自动比对;补充列顺序单元测试。', + 'completed', 4, 'ANALYSIS_ROOT_CAUSE_001', 1, 'ollama', 'llama3.1:8b', NOW() - interval '2 hours'), +(2, 2, '编码实现', '知识库,检索,空结果,500', + '检索服务未处理空结果分支,直接访问第一个元素导致 NPE 抛给全局异常处理。', + '空结果时返回空列表;对检索链路增加 try-catch,保证任何异常均返回友好提示。', + 'completed', 5, 'ANALYSIS_ROOT_CAUSE_001', 1, 'ollama', 'llama3.1:8b', NOW() - interval '3 hours'), +(3, 3, '流程管理', '关闭,重新打开,状态一致性', + '关闭操作缺少状态机约束,任意用户均可对 closed 状态执行 reopen,未校验当前状态。', + '引入状态机校验,仅 verified 状态可关闭、仅 closed 状态可驳回重开,并增加操作审计。', + 'completed', 3, 'ANALYSIS_ROOT_CAUSE_001', 1, 'ollama', 'llama3.1:8b', NOW() - interval '1 day'), +(4, 4, '测试遗漏', '日期筛选,边界,当日数据', + '日期比较使用 startOfDay 与 SQL NOW() 精度不一致,当日零点创建的数据被上一区间截断。', + '统一使用闭区间 [start, end) 比较,并补充当日零点边界测试用例。', + 'completed', 2, 'ANALYSIS_ROOT_CAUSE_001', 1, 'deepseek', 'deepseek-v4-pro', NOW() - interval '2 days'), +(5, 5, '编码实现', 'Agent,跨部门,审批', + 'Agent 工具调用时仅按 isWrite 判断是否审批,未校验 assignee 部门与指摘部门是否一致。', + '在 assign_issue 工具内增加部门一致性校验,跨部门时强制返回审批请求。', + 'completed', 4, 'ANALYSIS_ROOT_CAUSE_001', 1, 'deepseek', 'deepseek-v4-pro', NOW() - interval '2 days'), +(6, 6, '界面问题', '附件,上传,大小限制', + '前端上传组件未配置 maxSize 校验与错误回调,后端拒绝后前端无感知。', + '上传前预校验文件大小,失败时展示明确错误信息并恢复按钮状态。', + 'failed', 0, 'ANALYSIS_ROOT_CAUSE_001', 1, 'ollama', 'llama3.1:8b', NOW() - interval '5 days'); + +SELECT setval('ai_analysis_id_seq', (SELECT MAX(id) FROM ai_analysis)); + +-- ---------- ai_feedback AI 反馈 ---------- +INSERT INTO ai_feedback (ai_analysis_id, user_id, is_helpful, comment, created_at) VALUES +(1, 2, TRUE, '列顺序问题定位准确,建议可直接落地', NOW() - interval '1 day'), +(2, 1, TRUE, '根因分析到位', NOW() - interval '1 day'), +(5, 3, FALSE, '建议中应补充跨部门审批的具体工具名', NOW() - interval '1 day'); + +-- ---------- knowledge_documents 知识库文档(序列自增,避免与已有文档 id 冲突) ---------- +INSERT INTO knowledge_documents (name, file_path, file_size, file_type, chunk_count, status, uploaded_by, created_at, updated_at) VALUES +('指摘管理操作规范.txt', '/tmp/ims/demo/指摘管理操作规范.txt', 24576, 'txt', 5, 'completed', 1, NOW() - interval '3 days', NOW() - interval '3 days'), +('历史整改案例汇总.xlsx', '/tmp/ims/demo/历史整改案例汇总.xlsx', 187392, 'xlsx', 4, 'completed', 2, NOW() - interval '6 days', NOW() - interval '6 days'), +('质量保证手册.pdf', '/tmp/ims/demo/质量保证手册.pdf', 524288, 'pdf', 3, 'completed', 1, NOW() - interval '9 days', NOW() - interval '9 days'); + +SELECT setval('knowledge_documents_id_seq', (SELECT MAX(id) FROM knowledge_documents)); + +-- ---------- knowledge_chunks 知识库切片(768 维向量,doc_id 按文档名关联) ---------- +INSERT INTO knowledge_chunks (doc_id, content, embedding, metadata, created_at) VALUES +((SELECT id FROM knowledge_documents WHERE name='指摘管理操作规范.txt'), '指摘录入规范:录入指摘时必须填写标题、归属部门、工程阶段与影响级别;对应者在收到分配通知后 3 个工作日内完成对应。', + array_cat(ARRAY[0.92,0.31,0.08]::real[], array_fill(0.001::real, ARRAY[765]))::vector, '{"page":1,"docName":"指摘管理操作规范.txt"}'::jsonb, NOW() - interval '3 days'), +((SELECT id FROM knowledge_documents WHERE name='指摘管理操作规范.txt'), '状态流转规则:draft 草稿 → open 待处理 → in_progress 进行中 → resolved 已解决 → verified 已验证 → closed 已关闭;rejected 状态仅允许已关闭指摘驳回重开。', + array_cat(ARRAY[0.85,0.22,0.15]::real[], array_fill(0.002::real, ARRAY[765]))::vector, '{"page":2,"docName":"指摘管理操作规范.txt"}'::jsonb, NOW() - interval '3 days'), +((SELECT id FROM knowledge_documents WHERE name='指摘管理操作规范.txt'), '对应者定义:指摘的整改担当人,负责提交对应内容与对应完了日;对应完成后由确认者进行验证,验证通过后关闭指摘。', + array_cat(ARRAY[0.78,0.45,0.33]::real[], array_fill(0.001::real, ARRAY[765]))::vector, '{"page":3,"docName":"指摘管理操作规范.txt"}'::jsonb, NOW() - interval '3 days'), +((SELECT id FROM knowledge_documents WHERE name='指摘管理操作规范.txt'), '工数记录:review 工数由确认者填写,对应工数由对应者填写,均以人日为单位,保留 1 位小数。', + array_cat(ARRAY[0.70,0.50,0.28]::real[], array_fill(0.003::real, ARRAY[765]))::vector, '{"page":4,"docName":"指摘管理操作规范.txt"}'::jsonb, NOW() - interval '3 days'), +((SELECT id FROM knowledge_documents WHERE name='指摘管理操作规范.txt'), '逾期管理:超过对应完了日未完成的指摘,系统自动通知对应者及其部门管理员,逾期原因需在指摘中说明。', + array_cat(ARRAY[0.64,0.18,0.42]::real[], array_fill(0.002::real, ARRAY[765]))::vector, '{"page":5,"docName":"指摘管理操作规范.txt"}'::jsonb, NOW() - interval '3 days'), +((SELECT id FROM knowledge_documents WHERE name='历史整改案例汇总.xlsx'), '案例:批量导出列顺序与模板不一致。根因:列定义硬编码。对策:列顺序改为从模板配置读取并增加比对测试,整改效果良好。', + array_cat(ARRAY[0.88,0.27,0.11]::real[], array_fill(0.001::real, ARRAY[765]))::vector, '{"page":1,"docName":"历史整改案例汇总.xlsx"}'::jsonb, NOW() - interval '6 days'), +((SELECT id FROM knowledge_documents WHERE name='历史整改案例汇总.xlsx'), '案例:上传大附件失败无提示。根因:前端缺少大小预校验。对策:上传前校验文件大小并处理失败回调,用户反馈明显改善。', + array_cat(ARRAY[0.81,0.39,0.19]::real[], array_fill(0.002::real, ARRAY[765]))::vector, '{"page":2,"docName":"历史整改案例汇总.xlsx"}'::jsonb, NOW() - interval '6 days'), +((SELECT id FROM knowledge_documents WHERE name='历史整改案例汇总.xlsx'), '案例:知识库检索中文召回率低。根因:复合术语切分不理想。对策:优化查询改写提示词、增加 top_k 重排,召回率提升约 30%。', + array_cat(ARRAY[0.74,0.13,0.51]::real[], array_fill(0.001::real, ARRAY[765]))::vector, '{"page":3,"docName":"历史整改案例汇总.xlsx"}'::jsonb, NOW() - interval '6 days'), +((SELECT id FROM knowledge_documents WHERE name='历史整改案例汇总.xlsx'), '案例:权限修改未即时生效。根因:权限缓存未按角色失效。对策:角色变更时主动清除相关缓存,登录用户权限即时更新。', + array_cat(ARRAY[0.67,0.55,0.24]::real[], array_fill(0.003::real, ARRAY[765]))::vector, '{"page":4,"docName":"历史整改案例汇总.xlsx"}'::jsonb, NOW() - interval '6 days'), +((SELECT id FROM knowledge_documents WHERE name='质量保证手册.pdf'), '质量方针:坚持缺陷预防、过程改进与持续验证,确保指摘全生命周期可追溯,整改闭环率达到 95% 以上。', + array_cat(ARRAY[0.90,0.29,0.07]::real[], array_fill(0.001::real, ARRAY[765]))::vector, '{"page":1,"docName":"质量保证手册.pdf"}'::jsonb, NOW() - interval '9 days'), +((SELECT id FROM knowledge_documents WHERE name='质量保证手册.pdf'), '评审制度:设计评审与代码评审必须形成评审记录,评审发现的问题进入指摘管理流程跟踪至闭环。', + array_cat(ARRAY[0.83,0.36,0.26]::real[], array_fill(0.002::real, ARRAY[765]))::vector, '{"page":2,"docName":"质量保证手册.pdf"}'::jsonb, NOW() - interval '9 days'), +((SELECT id FROM knowledge_documents WHERE name='质量保证手册.pdf'), '验证要求:整改完成后由独立验证人员验证,验证不合格的指摘退回对应者并记录驳回原因。', + array_cat(ARRAY[0.76,0.47,0.31]::real[], array_fill(0.001::real, ARRAY[765]))::vector, '{"page":3,"docName":"质量保证手册.pdf"}'::jsonb, NOW() - interval '9 days'); + +SELECT setval('knowledge_chunks_id_seq', (SELECT MAX(id) FROM knowledge_chunks)); + +-- ---------- agent_plans Agent 规划(含 4 条待审批) ---------- +INSERT INTO agent_plans (id, issue_id, goal, plan_steps, status, requires_approval, approval_status, approval_comment, + created_by, model_provider, created_at, completed_at) VALUES +(1, 8, '将指摘 ISS-2026-008 标记为已关闭', + '["1. query_issue 查询指摘 8 当前状态", "2. update_issue 将状态更新为 closed"]', + 'awaiting_approval', TRUE, 'requested', NULL, 1, 'ollama', NOW() - interval '2 hours', NULL), +(2, 9, '将指摘 ISS-2026-009 的优先级调整为 high', + '["1. query_issue 查询指摘 9 详情", "2. update_issue 将优先级更新为 high"]', + 'awaiting_approval', TRUE, 'requested', NULL, 1, 'ollama', NOW() - interval '1 hour', NULL), +(3, 10, '为指摘 ISS-2026-010 补充对应内容', + '["1. query_issue 查询指摘 10 详情", "2. update_issue 写入对应内容"]', + 'awaiting_approval', TRUE, 'requested', NULL, 2, 'ollama', NOW() - interval '30 minutes', NULL), +(4, 11, '关闭指摘 ISS-2026-011', + '["1. query_issue 查询指摘 11 当前状态", "2. update_issue 将状态更新为 closed"]', + 'awaiting_approval', TRUE, 'requested', NULL, 2, 'ollama', NOW() - interval '20 minutes', NULL), +(5, 12, '整理 ISS-2026-012 的整改完成情况并关闭', + '["1. query_issue 查询指摘 12 详情", "2. search_knowledge 检索相似整改案例", "3. update_issue 更新状态为 closed"]', + 'completed', TRUE, 'approved', '同意关闭', 1, 'ollama', NOW() - interval '6 days', NOW() - interval '6 days'), +(6, 1, '分析 ISS-2026-001 批量导出问题并更新备注', + '["1. query_issue 查询指摘 1 详情", "2. update_issue 更新对应内容"]', + 'completed', TRUE, 'approved', '同意', 3, 'ollama', NOW() - interval '1 day', NOW() - interval '1 day'), +(7, 2, '将 ISS-2026-002 关闭', + '["1. query_issue 查询指摘 2 详情", "2. update_issue 将状态更新为 closed"]', + 'rejected', TRUE, 'rejected', '问题未整改完成,拒绝关闭', 1, 'ollama', NOW() - interval '3 hours', NOW() - interval '3 hours'), +(8, 3, '将 ISS-2026-003 状态更新为 in_progress', + '["1. query_issue 查询指摘 3 详情", "2. update_issue 将状态更新为 in_progress"]', + 'completed', TRUE, 'approved', '同意', 2, 'deepseek', NOW() - interval '1 day', NOW() - interval '1 day'); + +SELECT setval('agent_plans_id_seq', (SELECT MAX(id) FROM agent_plans)); + +-- ---------- tool_executions 工具执行记录 ---------- +-- 待审批计划关联的 pending 记录(output_result 作为审批原因展示) +INSERT INTO tool_executions (plan_id, tool_name, input_params, output_result, status, execution_time_ms, created_at) VALUES +(1, 'update_issue', '{"issue_id":8,"field":"status","value":"closed"}', + '等待人工审批: 关闭指摘属于高风险操作,需人工确认', 'pending', NULL, NOW() - interval '2 hours'), +(2, 'update_issue', '{"issue_id":9,"field":"priority","value":"high"}', + '等待人工审批: 修改已验证指摘的优先级,需人工确认', 'pending', NULL, NOW() - interval '1 hour'), +(3, 'update_issue', '{"issue_id":10,"field":"response_content","value":"补充历史案例引用说明"}', + '等待人工审批: 写入对应内容属于写操作,需人工确认', 'pending', NULL, NOW() - interval '30 minutes'), +(4, 'update_issue', '{"issue_id":11,"field":"status","value":"closed"}', + '等待人工审批: 关闭指摘属于高风险操作,需人工确认', 'pending', NULL, NOW() - interval '20 minutes'); + +-- 已完成/已拒绝计划的历史执行记录 +INSERT INTO tool_executions (plan_id, tool_name, input_params, output_result, status, execution_time_ms, created_at) VALUES +(5, 'query_issue', '{"issue_id":12}', '{"id":12,"issue_no":"ISS-2026-012","status":"open"}', 'success', 120, NOW() - interval '6 days'), +(5, 'search_knowledge', '{"query":"整改完成情况汇总","top_k":3}', '{"results":[{"chunk_id":6,"score":0.87}]}', 'success', 680, NOW() - interval '6 days'), +(5, 'update_issue', '{"issue_id":12,"field":"status","value":"closed"}', '{"id":12,"updated":true,"status":"closed"}', 'success', 210, NOW() - interval '6 days'), +(6, 'query_issue', '{"issue_id":1}', '{"id":1,"issue_no":"ISS-2026-001","status":"draft"}', 'success', 98, NOW() - interval '1 day'), +(6, 'update_issue', '{"issue_id":1,"field":"response_content","value":"批量导出列顺序模板化"}', '{"id":1,"updated":true}', 'success', 180, NOW() - interval '1 day'), +(7, 'query_issue', '{"issue_id":2}', '{"id":2,"issue_no":"ISS-2026-002","status":"open"}', 'success', 105, NOW() - interval '3 hours'), +(7, 'update_issue', '{"issue_id":2,"field":"status","value":"closed"}', '等待人工审批: 关闭指摘属于高风险操作', 'rejected', NULL, NOW() - interval '3 hours'), +(8, 'query_issue', '{"issue_id":3}', '{"id":3,"issue_no":"ISS-2026-003","status":"open"}', 'success', 88, NOW() - interval '1 day'), +(8, 'search_knowledge', '{"query":"已关闭指摘重复打开的处理"}', '模型服务不可用', 'failed', 2500, NOW() - interval '1 day'), +(8, 'update_issue', '{"issue_id":3,"field":"status","value":"in_progress"}', '{"id":3,"updated":true}', 'success', 195, NOW() - interval '1 day'); + +-- 批量历史成功执行记录(填充运行概览执行次数与成功率) +INSERT INTO tool_executions (plan_id, tool_name, input_params, output_result, status, execution_time_ms, created_at) +SELECT 5, 'query_issue', '{"issue_id":12}', '{"id":12,"issue_no":"ISS-2026-012"}', 'success', + 90 + (g * 37) % 300, + NOW() - ((g * 9) || ' hours')::interval +FROM generate_series(1, 40) AS g; + +SELECT setval('tool_executions_id_seq', (SELECT MAX(id) FROM tool_executions)); + +-- ---------- agent_memories Agent 长期记忆 ---------- +INSERT INTO agent_memories (issue_summary, solution_steps, effectiveness_score, embedding, created_at, updated_at) VALUES +('跨部门指摘分配需人工审批,避免误分配', + '["1. 检测 assignee 部门是否与指摘归属部门一致", "2. 不一致时调用 request_human_approval", "3. 人工批准后继续执行分配"]', + 4.80, NULL, NOW() - interval '5 days', NOW() - interval '5 days'), +('批量导出 Excel 列顺序需与标准模板对齐', + '["1. 列顺序改为从模板配置读取", "2. 导出前与模板比对", "3. 不一致时提示并拦截"]', + 4.50, NULL, NOW() - interval '4 days', NOW() - interval '4 days'), +('知识库检索空结果应返回友好提示而非 500', + '["1. 空结果分支返回空列表", "2. 检索链路整体 try-catch", "3. 补充空结果测试用例"]', + 4.20, NULL, NOW() - interval '4 days', NOW() - interval '4 days'), +('中文指摘分词需按字符级处理提升召回率', + '["1. 使用 CJK 字符级正则切分", "2. 复合术语扩展检索", "3. 增加 top_k 重排"]', + 3.80, NULL, NOW() - interval '3 days', NOW() - interval '3 days'), +('逾期提醒应在对应完了日前 1 天自动发送', + '["1. 每日扫描临近完成日指摘", "2. 向对应者与部门管理员推送通知", "3. 逾期后追加提醒"]', + 3.50, NULL, NOW() - interval '2 days', NOW() - interval '2 days'), +('修改角色权限后需强制刷新权限缓存', + '["1. 角色变更时清除相关缓存", "2. 已登录用户下次请求重新加载权限", "3. 补充权限刷新测试"]', + 2.90, NULL, NOW() - interval '1 day', NOW() - interval '1 day'); + +-- ---------- prompt_render_logs Prompt 渲染日志(使用统计) ---------- +INSERT INTO prompt_render_logs (request_id, template_id, template_version, rendered_prompt, variables_used, + tokens_input, tokens_output, execution_time_ms, llm_model, model_provider, created_at) +SELECT + 'REQ-' || to_char(NOW(), 'YYYYMMDD') || '-' || lpad(g::text, 4, '0'), + (ARRAY['SYS_ROLE_001', 'PLAN_001', 'ANALYSIS_ROOT_CAUSE_001', 'QUERY_REWRITE_001', 'MEMORY_RERANK_001', 'FORMAT_JSON_001'])[1 + (g % 6)], + 1, + '[' || (ARRAY['系统角色约束 Prompt', 'ReAct 规划 Prompt', '根因分析 Prompt', '查询改写 Prompt', '记忆重排序 Prompt', '输出格式化 Prompt'])[1 + (g % 6)] || '] 渲染于 ' || NOW(), + ('{"requestId":"REQ-' || to_char(NOW(), 'YYYYMMDD') || '-' || lpad(g::text, 4, '0') || '","templateVersion":1}')::jsonb, + 1000 + (g * 137) % 8000, + 100 + (g * 53) % 1500, + 500 + (g * 977) % 12000, + CASE WHEN g % 3 = 0 THEN 'deepseek-v4-pro' ELSE 'llama3.1:8b' END, + CASE WHEN g % 3 = 0 THEN 'deepseek' ELSE 'ollama' END, + NOW() - ((g % 7) || ' hours')::interval +FROM generate_series(1, 40) AS g; diff --git a/backend/ims-service/src/main/resources/db/migration/V2.10__add_role_data_scope.sql b/backend/ims-service/src/main/resources/db/migration/V2.10__add_role_data_scope.sql new file mode 100644 index 0000000..9685e4d --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V2.10__add_role_data_scope.sql @@ -0,0 +1 @@ +ALTER TABLE roles ADD COLUMN IF NOT EXISTS data_scope VARCHAR(20) DEFAULT 'all'; diff --git a/backend/ims-service/src/main/resources/db/migration/V2.1__add_ai_analysis_error_message.sql b/backend/ims-service/src/main/resources/db/migration/V2.1__add_ai_analysis_error_message.sql new file mode 100644 index 0000000..f0e49ed --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V2.1__add_ai_analysis_error_message.sql @@ -0,0 +1,2 @@ +-- ai_analysis 增加失败原因字段 +ALTER TABLE ai_analysis ADD COLUMN IF NOT EXISTS error_message TEXT; diff --git a/backend/ims-service/src/main/resources/db/migration/V2.2__add_ai_analysis_duration.sql b/backend/ims-service/src/main/resources/db/migration/V2.2__add_ai_analysis_duration.sql new file mode 100644 index 0000000..a4ddebc --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V2.2__add_ai_analysis_duration.sql @@ -0,0 +1,2 @@ +-- ai_analysis 增加分析耗时字段(秒) +ALTER TABLE ai_analysis ADD COLUMN IF NOT EXISTS duration_seconds INTEGER; diff --git a/backend/ims-service/src/main/resources/db/migration/V2.3__ai_analysis_timestamps.sql b/backend/ims-service/src/main/resources/db/migration/V2.3__ai_analysis_timestamps.sql new file mode 100644 index 0000000..d78b376 --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V2.3__ai_analysis_timestamps.sql @@ -0,0 +1,4 @@ +-- ai_analysis 移除耗时字段,增加开始时间与成功/失败时间 +ALTER TABLE ai_analysis DROP COLUMN IF EXISTS duration_seconds; +ALTER TABLE ai_analysis ADD COLUMN IF NOT EXISTS started_at TIMESTAMP; +ALTER TABLE ai_analysis ADD COLUMN IF NOT EXISTS completed_at TIMESTAMP; diff --git a/backend/ims-service/src/main/resources/db/migration/V2.4__ai_call_logs.sql b/backend/ims-service/src/main/resources/db/migration/V2.4__ai_call_logs.sql new file mode 100644 index 0000000..0f84296 --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V2.4__ai_call_logs.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS ai_call_logs ( + id BIGSERIAL PRIMARY KEY, + provider VARCHAR(20), + model VARCHAR(100), + status VARCHAR(20), + latency_ms BIGINT, + response_snippet TEXT, + error_message TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +CREATE INDEX IF NOT EXISTS idx_ai_call_logs_created ON ai_call_logs(created_at DESC); diff --git a/backend/ims-service/src/main/resources/db/migration/V2.5__analysis_prompt_keywords.sql b/backend/ims-service/src/main/resources/db/migration/V2.5__analysis_prompt_keywords.sql new file mode 100644 index 0000000..89552c8 --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V2.5__analysis_prompt_keywords.sql @@ -0,0 +1,12 @@ +-- 更新 ANALYSIS_ROOT_CAUSE_001 模板:新增预提取关键词输入变量,version 1 -> 2 +UPDATE prompt_templates +SET content = E'# 任务\n你是指摘管理专家,请对给定的指摘进行深度根因分析,并输出结构化 JSON。\n\n# 输入\n指摘编号:{{issueNo}}\n指摘标题:{{issueTitle}}\n指摘描述:{{issueDescription}}\n工程阶段:{{issuePhase}}\n指摘分类:{{issueCategory}}\n影响级别:{{issueImpactLevel}}\n指摘关键词:{{issueKeywords}}\n历史相似案例:{{similarCases}}\n\n# 分析要求\n1. 从需求理解偏差、设计缺陷、编码实现、测试遗漏、环境/数据、流程管理等维度判断根因。\n2. 关键词提取3-5个,覆盖核心业务概念与原因。\n3. 整改建议必须可执行、分优先级,并优先引用相似案例。\n4. 参考输入中的"指摘关键词"辅助判断分析重点。\n\n# 输出格式(严格JSON,禁止Markdown围栏)\n{\n "category": "根因分类(如:需求理解偏差/设计缺陷/编码实现/测试遗漏/环境数据/流程管理)",\n "keywords": ["关键词1", "关键词2", "关键词3"],\n "rootCause": "根因分析,100-200字",\n "suggestion": "整改建议,含优先级与具体措施" \n}\n\n# 约束\n- 输出必须是可以直接 JSON.parse 的纯JSON。\n- 不要输出解释性文字或 Markdown 代码块。', + variables = '[{"name":"issueNo","type":"string","required":true,"description":"指摘编号"},{"name":"issueTitle","type":"string","required":true,"description":"指摘标题"},{"name":"issueDescription","type":"string","required":true,"description":"指摘描述"},{"name":"issuePhase","type":"string","required":false,"description":"工程阶段"},{"name":"issueCategory","type":"string","required":false,"description":"指摘分类"},{"name":"issueImpactLevel","type":"string","required":false,"description":"影响级别"},{"name":"issueKeywords","type":"string","required":false,"description":"预提取的指摘关键词"},{"name":"similarCases","type":"string","required":false,"description":"历史相似案例"}]', + version = version + 1 +WHERE template_id = 'ANALYSIS_ROOT_CAUSE_001'; + +-- 记录新版本历史(沿用 PromptService.update 的版本机制) +INSERT INTO prompt_template_versions (template_id, version, content, change_log, created_by) +SELECT template_id, version, content, '增加预提取关键词输入变量', NULL +FROM prompt_templates +WHERE template_id = 'ANALYSIS_ROOT_CAUSE_001'; diff --git a/backend/ims-service/src/main/resources/db/migration/V2.6__ai_analysis_extracted_keywords.sql b/backend/ims-service/src/main/resources/db/migration/V2.6__ai_analysis_extracted_keywords.sql new file mode 100644 index 0000000..09bf6c3 --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V2.6__ai_analysis_extracted_keywords.sql @@ -0,0 +1 @@ +ALTER TABLE ai_analysis ADD COLUMN IF NOT EXISTS extracted_keywords TEXT; diff --git a/backend/ims-service/src/main/resources/db/migration/V2.7__agent_plan_issue_nullable.sql b/backend/ims-service/src/main/resources/db/migration/V2.7__agent_plan_issue_nullable.sql new file mode 100644 index 0000000..9eca623 --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V2.7__agent_plan_issue_nullable.sql @@ -0,0 +1,2 @@ +-- 支持 Agent 创建型指令:无目标指摘时创建的计划可不关联指摘 +ALTER TABLE agent_plans ALTER COLUMN issue_id DROP NOT NULL; diff --git a/backend/ims-service/src/main/resources/db/migration/V2.8__agent_native_tools.sql b/backend/ims-service/src/main/resources/db/migration/V2.8__agent_native_tools.sql new file mode 100644 index 0000000..bd02bf4 --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V2.8__agent_native_tools.sql @@ -0,0 +1,39 @@ +-- Agent 增强:plan 增加 agent_message 字段,模板切换到 Ollama 原生工具调用模式 +ALTER TABLE agent_plans ADD COLUMN IF NOT EXISTS agent_message TEXT; + +-- PLAN_001:重写为原生工具调用决策模板(不再要求手写 JSON) +UPDATE prompt_templates +SET content = $tpl$# 任务 +基于用户目标和当前上下文,选择合适的工具并生成调用参数。一次调用一个工具。 + +# 输入 +用户目标:{{userGoal}} +当前指摘上下文:{{issueContext}} +历史相似案例:{{similarCases}} +可用工具列表:{{availableTools}} +已执行步骤:{{executedSteps}} + +# 决策规则 +1. 根据用户目标选择最合适的工具(只能从可用工具列表中选择)。 +2. 生成的参数必须完整,必填参数必须提供;不确定的信息从用户目标或上下文中提取。 +3. 创建指摘时,标题必须简明准确,描述需包含关键背景;对应者可从用户目标中提取。 +4. 写操作(新建/更新指摘)执行前会进入人工审批,请如实说明操作理由。 +5. 若用户目标已完成且无需进一步操作,直接用一句话总结处理结果,不要调用任何工具。$tpl$ +WHERE template_id = 'PLAN_001'; + +-- SYS_ROLE_001:替换工具调用规则段落(改为原生工具调用) +UPDATE prompt_templates +SET content = REPLACE(content, +$old$# 工具调用规则(适用于当前AI引擎:{{modelProvider}}) +- 当你需要调用工具时,请在你的回复中输出一个 JSON 对象,格式为: +{ + "tool": "工具名", + "parameters": { ... } +} +- 不要用自然语言额外解释,直接输出该 JSON。 +- 如果目标已完成,输出 { "tool": "goal_completed", "parameters": {} }。$old$, +$new$# 工具调用 +- 当需要执行操作时,直接调用可用的工具,系统会自动结构化你的调用并处理审批。 +- 写操作(create_issue / update_issue)执行前必须经过人工审批。 +- 如果用户目标已达成,直接用一句话总结结果,不要调用任何工具。$new$) +WHERE template_id = 'SYS_ROLE_001'; \ No newline at end of file diff --git a/backend/ims-service/src/main/resources/db/migration/V2.9__fix_test_missing_tables.sql b/backend/ims-service/src/main/resources/db/migration/V2.9__fix_test_missing_tables.sql new file mode 100644 index 0000000..296da66 --- /dev/null +++ b/backend/ims-service/src/main/resources/db/migration/V2.9__fix_test_missing_tables.sql @@ -0,0 +1,31 @@ +-- 修复 ims-master-test 合并前缺失的表/列: +-- 1. import_records(批量导入记录) +-- 2. phase_rules(导入阶段规则) +-- 3. issues.review_date(评审日期列,批量导入写入) +-- 这些 DDL 与实体定义保持一致,用于通过 JPA ddl-auto=validate 校验。 + +CREATE TABLE IF NOT EXISTS import_records ( + id BIGSERIAL PRIMARY KEY, + file_name VARCHAR(255) NOT NULL, + total_count INTEGER NOT NULL, + success_count INTEGER NOT NULL, + fail_count INTEGER NOT NULL, + status VARCHAR(20) NOT NULL, + error_log TEXT, + operator_id BIGINT REFERENCES users (id), + created_at TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS phase_rules ( + id BIGSERIAL PRIMARY KEY, + keyword VARCHAR(100) NOT NULL, + phase VARCHAR(50) NOT NULL, + match_mode VARCHAR(20) NOT NULL, + source VARCHAR(50), + active BOOLEAN NOT NULL DEFAULT TRUE, + sort_order INTEGER NOT NULL DEFAULT 0, + created_at TIMESTAMP, + updated_at TIMESTAMP +); + +ALTER TABLE issues ADD COLUMN IF NOT EXISTS review_date TIMESTAMP; \ No newline at end of file diff --git a/backend/ims-web/pom.xml b/backend/ims-web/pom.xml new file mode 100644 index 0000000..60e6b59 --- /dev/null +++ b/backend/ims-web/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + com.ims + ims-backend + 1.0.0-SNAPSHOT + + ims-web + + + com.ims + ims-api + + + com.ims + ims-service + + + org.springframework.boot + spring-boot-starter-web + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/backend/ims-web/replay_pid112402.log b/backend/ims-web/replay_pid112402.log new file mode 100644 index 0000000..ba38545 --- /dev/null +++ b/backend/ims-web/replay_pid112402.log @@ -0,0 +1,3651 @@ +JvmtiExport can_access_local_variables 0 +JvmtiExport can_hotswap_or_post_breakpoint 0 +JvmtiExport can_post_on_exceptions 0 +# 300 ciObject found +instanceKlass org/apache/maven/model/merge/MavenModelMerger +ciMethodData java/lang/Object ()V 2 840738 orig 80 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 4 0x0 0x9 0x1 0x0 oops 0 methods 0 +ciMethodData java/lang/String hashCode ()I 2 5624 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 42 0x60007 0xe89 0x108 0x642 0xd0007 0x33 0xe8 0x60f 0x110005 0x60f 0x0 0x0 0x0 0x0 0x0 0x140007 0x0 0x48 0x60f 0x1b0002 0x60f 0x1e0003 0x60f 0x28 0x250002 0x0 0x2a0007 0x60f 0x38 0x0 0x320003 0x0 0x18 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x1 0x0 oops 0 methods 0 +ciMethodData java/lang/String isLatin1 ()Z 2 864235 orig 80 1 0 0 0 3 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 18 0x30007 0x0 0x58 0xd2f53 0x80000006000a0007 0xe0 0x38 0xd2e77 0xe0003 0xd2e77 0x18 0x0 0x0 0x0 0x0 0x9 0x1 0x0 oops 0 methods 0 +ciMethodData java/lang/StringLatin1 hashCode ([B)I 2 30323 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 14 0xd0007 0x381 0x38 0x7408 0x250003 0x7408 0xffffffffffffffe0 0x0 0x0 0x0 0x0 0x9 0x1 0x0 oops 0 methods 0 +ciMethodData java/util/HashMap hash (Ljava/lang/Object;)I 2 86118 orig 80 1 0 0 0 0 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 23 0x8000000600010007 0x14df4 0x38 0x77 0x50003 0x77 0x50 0x90005 0x734f 0x0 0x717560002b40 0xd961 0x717560004170 0x144 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x1 0xffffffffffffffff oops 2 10 java/lang/String 12 java/lang/Module methods 0 +ciMethodData java/util/HashMap putVal (ILjava/lang/Object;Ljava/lang/Object;ZZ)Ljava/lang/Object; 2 35182 orig 80 3 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 0 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 198 0x70007 0x1704 0x40 0x70d8 0x100007 0x70d8 0x58 0x0 0x140005 0x1704 0x0 0x0 0x0 0x0 0x0 0x2c0007 0x1eb6 0xa8 0x6926 0x380005 0x0 0x0 0x7175486dfc40 0x29e9 0x71754c467d70 0x3f3d 0x3b0004 0x0 0x0 0x7175645574c0 0x29e9 0x71756034fc50 0x3f3d 0x3c0003 0x6926 0x410 0x450007 0x1d7c 0xd0 0x13a 0x510007 0x2c 0x98 0x10e 0x550007 0x0 0x90 0x10e 0x80000007005b0005 0x16 0x0 0x717560005920 0x13 0x717560002b40 0xe9 0x80000006005e0007 0x13 0x38 0x100 0x650003 0x12c 0x2a8 0x6a0004 0xffffffffffffe271 0x0 0x7175645574c0 0x342 0x71756034fc50 0x3c9 0x6d0007 0x1d8f 0xa8 0x0 0x720004 0x0 0x0 0x0 0x0 0x0 0x0 0x7b0005 0x0 0x0 0x0 0x0 0x0 0x0 0x800003 0x0 0x1c8 0x8e0007 0x6f2 0xc8 0x1d50 0x980005 0x0 0x0 0x7175486dfc40 0xdb0 0x71754c467d70 0xfa0 0xa20007 0x1d50 0x158 0x0 0xa90005 0x0 0x0 0x0 0x0 0x0 0x0 0xac0003 0x0 0x100 0xb50007 0x681 0xd0 0x71 0xc10007 0x3 0xc8 0x6e 0xc50007 0x0 0x90 0x6e 0x8000000400cb0005 0x32 0x0 0x717560005920 0x3 0x717560002b40 0x3e 0xce0007 0x32 0x38 0x41 0xd10003 0x41 0x30 0xdb0003 0x6b3 0xfffffffffffffe68 0xe00007 0x1d50 0x98 0x170 0xec0007 0x170 0x40 0x0 0xf10007 0x0 0x20 0x0 0xfd0005 0x0 0x0 0x7175486dfc40 0xa4 0x71754c467d70 0xcc 0x11c0007 0x84fc 0x58 0x17a 0x1200005 0x17a 0x0 0x0 0x0 0x0 0x0 0x1270005 0x0 0x0 0x7175486dfc40 0x3799 0x71754c467d70 0x4edd 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x6 0x0 0x0 0x0 0x0 0x0 0x0 oops 16 22 java/util/HashMap 24 java/util/LinkedHashMap 29 java/util/HashMap$Node 31 java/util/LinkedHashMap$Entry 51 java/net/URL 53 java/lang/String 65 java/util/HashMap$Node 67 java/util/LinkedHashMap$Entry 97 java/util/HashMap 99 java/util/LinkedHashMap 130 java/net/URL 132 java/lang/String 159 java/util/HashMap 161 java/util/LinkedHashMap 177 java/util/HashMap 179 java/util/LinkedHashMap methods 0 +ciMethodData java/util/HashMap put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; 2 13685 orig 80 2 0 0 0 0 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 20 0x20002 0x3376 0x90005 0x3378 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x3 0xffffffffffffffff 0xffffffffffffffff 0xffffffffffffffff oops 0 methods 0 +ciMethodData java/util/HashMap getNode (Ljava/lang/Object;)Ljava/util/HashMap$Node; 2 46399 orig 80 2 0 0 0 1 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 103 0x60007 0x6ef 0x2e8 0xac51 0xe0007 0x0 0x2c8 0xac51 0x170002 0xac51 0x210007 0x5e73 0x298 0x4ddf 0x2a0007 0x1969 0xb8 0x3476 0x350007 0x2e13 0x98 0x663 0x8000000600390007 0x76 0x78 0x5ef 0x3f0005 0x1d1 0x0 0x717560002b40 0x40e 0x717564544940 0x10 0x8000000600420007 0x13 0x20 0x5de 0x4e0007 0x1317 0x1c0 0x6db 0x520004 0xfffffffffffff925 0x0 0x7175645574c0 0x4d 0x0 0x0 0x550007 0x6db 0x90 0x0 0x590004 0x0 0x0 0x0 0x0 0x0 0x0 0x5f0005 0x0 0x0 0x0 0x0 0x0 0x0 0x6a0007 0x5f5 0xb8 0x250 0x760007 0x14c 0x98 0x104 0x7a0007 0x0 0x78 0x104 0x800005 0x35 0x0 0x7175645459f0 0x4 0x717560002b40 0xcb 0x830007 0x31 0x20 0xd3 0x910007 0x16a 0xffffffffffffff48 0x4bc 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x2 0x0 0xffffffffffffffff oops 5 29 java/lang/String 31 java/util/zip/ZipFile$Source$Key 44 java/util/HashMap$Node 81 java/lang/ProcessEnvironment$Variable 83 java/lang/String methods 0 +ciMethodData java/lang/String coder ()B 2 740404 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 14 0x30007 0x0 0x38 0xb4b12 0xa0003 0xb4b12 0x18 0x0 0x0 0x0 0x0 0x9 0x1 0x0 oops 0 methods 0 +ciMethodData java/lang/String length ()I 2 593784 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 16 0x60005 0x90e79 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x1 0x0 oops 0 methods 0 +ciMethodData java/lang/String equals (Ljava/lang/Object;)Z 2 6410 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 49 0x20007 0x16b9 0x20 0x151 0x80104 0x0 0x0 0x717560002b40 0x16a2 0x0 0x0 0xb0007 0x17 0xe0 0x16a2 0xf0004 0x0 0x0 0x717560002b40 0x16a2 0x0 0x0 0x160007 0x0 0x40 0x16a2 0x210007 0x0 0x68 0x16a2 0x2c0002 0x16a2 0x2f0007 0x12b4 0x38 0x3ee 0x330003 0x3ee 0x18 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x2 0x0 0x0 oops 2 7 java/lang/String 18 java/lang/String methods 0 +ciMethodData java/lang/CharacterDataLatin1 getProperties (I)I 2 41832 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 7 0x0 0x0 0x0 0x9 0x2 0x0 0x0 oops 0 methods 0 +ciMethodData java/lang/StringLatin1 regionMatchesCI ([BI[BII)Z 2 13621 orig 80 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 56 0x90007 0xc82 0x158 0x334e 0x2a0007 0x14a 0x38 0x3204 0x2d0003 0x3204 0xffffffffffffffc0 0x350005 0x0 0x0 0x7175600e1f10 0x14a 0x0 0x0 0x3f0005 0x0 0x0 0x7175600e1f10 0x14a 0x0 0x0 0x480007 0x145 0x38 0x5 0x4b0003 0x5 0xffffffffffffff18 0x500002 0x145 0x550002 0x145 0x580007 0x145 0x38 0x0 0x5b0003 0x0 0xfffffffffffffec0 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x5 0x0 0x0 0x0 0x0 0x0 oops 2 14 java/lang/CharacterDataLatin1 21 java/lang/CharacterDataLatin1 methods 0 +ciMethodData java/lang/CharacterDataLatin1 toUpperCase (I)I 2 9065 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 32 0x40005 0x0 0x0 0x7175600e1f10 0x22e3 0x0 0x0 0xc0007 0x9a 0x78 0x2249 0x150007 0x0 0x38 0x2249 0x250003 0x2249 0x38 0x2c0007 0x0 0x20 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x2 0x0 0x0 oops 1 3 java/lang/CharacterDataLatin1 methods 0 +ciMethodData java/lang/Character toLowerCase (I)I 1 1087 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 18 0x10002 0x3b3 0x50005 0x0 0x0 0x7175600e1f10 0x3b3 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x1 0x0 oops 1 5 java/lang/CharacterDataLatin1 methods 0 +ciMethodData java/lang/CharacterData of (I)Ljava/lang/CharacterData; 2 6417 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 67 0x40007 0x1 0x20 0x1813 0xf0008 0x24 0x0 0x1c0 0x1 0x130 0x0 0x148 0x0 0x160 0x0 0x178 0x0 0x1c0 0x0 0x1c0 0x0 0x1c0 0x0 0x1c0 0x0 0x1c0 0x0 0x1c0 0x0 0x1c0 0x0 0x1c0 0x0 0x1c0 0x0 0x1c0 0x0 0x190 0x0 0x1a8 0x0 0x1a8 0x630003 0x1 0x90 0x690003 0x0 0x78 0x6f0003 0x0 0x60 0x750003 0x0 0x48 0x7b0003 0x0 0x30 0x810003 0x0 0x18 0x0 0x0 0x0 0x0 0x9 0x1 0x0 oops 0 methods 0 +ciMethodData java/lang/String regionMatches (ZILjava/lang/String;II)Z 2 8915 orig 80 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 95 0x10007 0x2177 0x58 0x0 0xb0005 0x0 0x0 0x0 0x0 0x0 0x0 0x110007 0x0 0xf0 0x2177 0x8000000600150007 0x20 0xd0 0x2158 0x1b0005 0x2158 0x0 0x0 0x0 0x0 0x0 0x240007 0x5d2 0x78 0x1b86 0x2b0005 0x1b86 0x0 0x0 0x0 0x0 0x0 0x340007 0x1b86 0x20 0x0 0x460005 0x1b86 0x0 0x0 0x0 0x0 0x0 0x4e0005 0x1b86 0x0 0x0 0x0 0x0 0x0 0x510007 0x0 0x78 0x1b86 0x560007 0x0 0x48 0x1b86 0x620002 0x1b86 0x650003 0x1b86 0x28 0x710002 0x0 0x770007 0x0 0x48 0x0 0x830002 0x0 0x860003 0x0 0x28 0x920002 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x6 0xffffffffffffffff 0x0 0x0 0xffffffffffffffff 0x0 0x0 oops 0 methods 0 +ciMethodData java/lang/String equalsIgnoreCase (Ljava/lang/String;)Z 2 5657 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 60 0x20007 0x13a6 0x38 0x74 0x60003 0x74 0x170 0xa0007 0x6fa 0x158 0xcac 0xe0005 0xcac 0x0 0x0 0x0 0x0 0x0 0x120005 0xcac 0x0 0x0 0x0 0x0 0x0 0x150007 0x4d 0xc8 0xc5f 0x1e0005 0xc5f 0x0 0x0 0x0 0x0 0x0 0x210005 0xc5f 0x0 0x0 0x0 0x0 0x0 0x240007 0xf 0x38 0xc50 0x280003 0xc50 0x18 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x2 0xffffffffffffffff 0xffffffffffffffff oops 0 methods 0 +ciMethodData java/util/AbstractCollection ()V 2 170780 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 7 0x10002 0x29a1c 0x0 0x0 0x9 0x1 0x0 oops 0 methods 0 +ciMethodData java/util/AbstractMap ()V 2 64305 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 7 0x10002 0xf9b1 0x0 0x0 0x9 0x1 0x0 oops 0 methods 0 +ciMethodData java/util/LinkedHashMap (I)V 2 6953 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 10 0x20002 0x1b18 0x0 0x0 0x0 0x0 0x9 0x2 0x78 0x0 oops 0 methods 0 +ciMethodData java/util/HashMap (I)V 2 9449 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 9 0x40002 0x24d5 0x0 0x0 0x0 0x9 0x2 0x18 0x0 oops 0 methods 0 +ciMethodData java/util/HashMap (IF)V 2 10864 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 83 0x10002 0x2a5c 0x50007 0x2a5c 0xe8 0x0 0x100002 0x0 0x150005 0x0 0x0 0x0 0x0 0x0 0x0 0x190005 0x0 0x0 0x0 0x0 0x0 0x0 0x1c0005 0x0 0x0 0x0 0x0 0x0 0x0 0x1f0002 0x0 0x260007 0x2a5c 0x20 0x0 0x2f0007 0x0 0x50 0x2a5c 0x330002 0x2a5c 0x360007 0x2a5c 0xe8 0x0 0x410002 0x0 0x460005 0x0 0x0 0x0 0x0 0x0 0x0 0x4a0005 0x0 0x0 0x0 0x0 0x0 0x0 0x4d0005 0x0 0x0 0x0 0x0 0x0 0x0 0x500002 0x0 0x5b0002 0x2a5c 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x3 0x18 0x0 0x0 oops 0 methods 0 +ciMethodData java/lang/Boolean parseBoolean (Ljava/lang/String;)Z 2 3604 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 16 0x30005 0xdfc 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x1 0xffffffffffffffff oops 0 methods 0 +ciMethod java/util/LinkedHashMap$LinkedValues (Ljava/util/LinkedHashMap;)V 278 0 5635 0 0 +ciMethodData java/util/LinkedHashMap get (Ljava/lang/Object;)Ljava/lang/Object; 2 20161 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 32 0x20005 0x4dc1 0x0 0x0 0x0 0x0 0x0 0x70007 0xe52 0x20 0x3f6f 0x100007 0xe52 0x58 0x0 0x150005 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x2 0x0 0x0 oops 0 methods 0 +ciMethodData java/util/ArrayList ()V 2 112798 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 9 0x10002 0x1b832 0x0 0x0 0x0 0x0 0x9 0x1 0x0 oops 0 methods 0 +ciMethodData java/util/AbstractList ()V 2 131842 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 9 0x10002 0x2027d 0x0 0x0 0x0 0x0 0x9 0x1 0x6 oops 0 methods 0 +ciMethodData java/util/ArrayList$Itr hasNext ()Z 2 5381 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 14 0xb0007 0x9cb 0x38 0xa39 0xf0003 0xa39 0x18 0x0 0x0 0x0 0x0 0x9 0x1 0x0 oops 0 methods 0 +ciMethodData java/util/ArrayList$Itr checkForComodification ()V 2 11857 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 13 0xb0007 0x2d50 0x30 0x0 0x120002 0x0 0x0 0x0 0x0 0x0 0x9 0x1 0x0 oops 0 methods 0 +ciMethodData java/util/ArrayList$Itr next ()Ljava/lang/Object; 2 9263 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 28 0x10005 0x232f 0x0 0x0 0x0 0x0 0x0 0x110007 0x232f 0x30 0x0 0x180002 0x0 0x270007 0x232f 0x30 0x0 0x2e0002 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x1 0x6 oops 0 methods 0 +ciMethodData java/util/ArrayList$Itr (Ljava/util/ArrayList;)V 2 15023 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 10 0x60002 0x3809 0x0 0x0 0x0 0x0 0x9 0x2 0xc 0x0 oops 0 methods 0 +ciMethodData java/util/ArrayList iterator ()Ljava/util/Iterator; 2 8384 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 8 0x50002 0x18bc 0x0 0x0 0x0 0x9 0x1 0x0 oops 0 methods 0 +ciMethodData java/util/LinkedHashMap values ()Ljava/util/Collection; 2 6577 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 15 0x60007 0x338 0x30 0x1578 0xe0002 0x1578 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x1 0x0 oops 0 methods 0 +ciMethodData java/util/LinkedHashMap$LinkedValues (Ljava/util/LinkedHashMap;)V 2 5635 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 10 0x60002 0x1578 0x0 0x0 0x0 0x0 0x9 0x2 0x6 0x0 oops 0 methods 0 +ciMethodData java/lang/Float isNaN (F)Z 2 12055 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 11 0x30007 0x2e16 0x38 0x0 0x70003 0x0 0x18 0x0 0x9 0x1 0x0 oops 0 methods 0 +ciMethodData java/util/HashMap tableSizeFor (I)I 2 17291 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 25 0x40002 0x428b 0xa0007 0x3b6e 0x38 0x71d 0xe0003 0x71d 0x50 0x140007 0x3b6e 0x38 0x0 0x190003 0x0 0x18 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x1 0x0 oops 0 methods 0 +ciMethodData java/util/ArrayList isEmpty ()Z 2 5842 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 13 0x40007 0x9dc 0x38 0xb76 0x80003 0xb76 0x18 0x0 0x0 0x0 0x9 0x1 0x0 oops 0 methods 0 +ciMethodData java/util/ArrayList (Ljava/util/Collection;)V 2 8208 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 42 0x10002 0x1e95 0x50005 0x1e0 0x0 0x717560005e50 0x1088 0x7175600841d0 0xc2d 0x120007 0x797 0xb8 0x16fe 0x160005 0x16fe 0x0 0x0 0x0 0x0 0x0 0x1b0007 0xdfe 0x38 0x900 0x230003 0x900 0x40 0x2e0002 0xdfe 0x340003 0xdfe 0x18 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x2 0x0 0x0 oops 2 5 java/util/ArrayList 7 java/util/LinkedHashMap$LinkedValues methods 0 +ciMethod org/codehaus/plexus/util/xml/Xpp3Dom (Lorg/codehaus/plexus/util/xml/Xpp3Dom;)V 864 0 14310 0 -1 +ciMethod org/codehaus/plexus/util/xml/Xpp3Dom mergeXpp3Dom (Lorg/codehaus/plexus/util/xml/Xpp3Dom;Lorg/codehaus/plexus/util/xml/Xpp3Dom;)Lorg/codehaus/plexus/util/xml/Xpp3Dom; 170 0 2823 0 -1 +ciMethod org/apache/maven/model/Plugin getExecutions ()Ljava/util/List; 518 0 19007 0 0 +ciMethod org/apache/maven/model/Plugin setExecutions (Ljava/util/List;)V 262 0 131 0 0 +ciMethod org/apache/maven/model/ConfigurationContainer getConfiguration ()Ljava/lang/Object; 312 0 156 0 -1 +ciMethod org/apache/maven/model/ConfigurationContainer getInherited ()Ljava/lang/String; 304 0 152 0 0 +ciMethod org/apache/maven/model/ConfigurationContainer getLocation (Ljava/lang/Object;)Lorg/apache/maven/model/InputLocation; 526 0 19937 0 -1 +ciMethod org/apache/maven/model/ConfigurationContainer setLocation (Ljava/lang/Object;Lorg/apache/maven/model/InputLocation;)V 518 0 6377 0 -1 +ciMethod org/apache/maven/model/ConfigurationContainer setConfiguration (Ljava/lang/Object;)V 292 0 146 0 -1 +ciMethod org/apache/maven/model/ConfigurationContainer setInherited (Ljava/lang/String;)V 42 0 19 0 -1 +ciMethod org/apache/maven/model/ConfigurationContainer isInherited ()Z 528 0 23017 0 0 +ciMethod org/apache/maven/model/PluginExecution getId ()Ljava/lang/String; 276 0 138 0 0 +ciMethod org/apache/maven/model/PluginExecution getPhase ()Ljava/lang/String; 264 0 132 0 -1 +ciMethod org/apache/maven/model/PluginExecution setId (Ljava/lang/String;)V 262 0 131 0 -1 +ciMethod org/apache/maven/model/PluginExecution setPhase (Ljava/lang/String;)V 256 0 128 0 -1 +ciMethod org/apache/maven/model/merge/MavenModelMerger mergePlugin_Executions (Lorg/apache/maven/model/Plugin;Lorg/apache/maven/model/Plugin;ZLjava/util/Map;)V 768 96 10753 0 -1 +ciMethod org/apache/maven/model/merge/MavenModelMerger getPluginExecutionKey (Lorg/apache/maven/model/PluginExecution;)Ljava/lang/Object; 102 0 2434 0 0 +ciMethod org/apache/maven/model/merge/ModelMerger mergeConfigurationContainer (Lorg/apache/maven/model/ConfigurationContainer;Lorg/apache/maven/model/ConfigurationContainer;ZLjava/util/Map;)V 848 0 11427 0 -1 +ciMethod org/apache/maven/model/merge/ModelMerger mergeConfigurationContainer_Inherited (Lorg/apache/maven/model/ConfigurationContainer;Lorg/apache/maven/model/ConfigurationContainer;ZLjava/util/Map;)V 1024 0 11427 0 -1 +ciMethod org/apache/maven/model/merge/ModelMerger mergeConfigurationContainer_Configuration (Lorg/apache/maven/model/ConfigurationContainer;Lorg/apache/maven/model/ConfigurationContainer;ZLjava/util/Map;)V 1024 0 11427 0 -1 +ciMethod org/apache/maven/model/merge/ModelMerger mergePluginExecution (Lorg/apache/maven/model/PluginExecution;Lorg/apache/maven/model/PluginExecution;ZLjava/util/Map;)V 78 0 88 0 0 +ciMethod org/apache/maven/model/merge/ModelMerger mergePluginExecution_Id (Lorg/apache/maven/model/PluginExecution;Lorg/apache/maven/model/PluginExecution;ZLjava/util/Map;)V 188 0 88 0 -1 +ciMethod org/apache/maven/model/merge/ModelMerger mergePluginExecution_Phase (Lorg/apache/maven/model/PluginExecution;Lorg/apache/maven/model/PluginExecution;ZLjava/util/Map;)V 188 0 88 0 -1 +ciMethod org/apache/maven/model/merge/ModelMerger mergePluginExecution_Goals (Lorg/apache/maven/model/PluginExecution;Lorg/apache/maven/model/PluginExecution;ZLjava/util/Map;)V 0 0 1 0 -1 +ciMethodData org/apache/maven/model/ConfigurationContainer setLocation (Ljava/lang/Object;Lorg/apache/maven/model/InputLocation;)V 2 6377 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 109 0x10004 0x0 0x0 0x717560002b40 0x17e6 0x0 0x0 0x40007 0x0 0x2a0 0x17e6 0x80004 0x0 0x0 0x717560002b40 0x17e6 0x0 0x0 0x100005 0x17e6 0x0 0x0 0x0 0x0 0x0 0x130008 0x8 0x1149 0x188 0x8 0xc0 0x603 0x50 0x92 0x130 0x370005 0x603 0x0 0x0 0x0 0x0 0x0 0x3a0007 0x0 0x100 0x603 0x400003 0x603 0xe0 0x460005 0x8 0x0 0x0 0x0 0x0 0x0 0x490007 0x0 0x90 0x8 0x4f0003 0x8 0x70 0x550005 0x92 0x0 0x0 0x0 0x0 0x0 0x580007 0x0 0x20 0x92 0x600008 0x8 0x1149 0x50 0x603 0x50 0x8 0x50 0x92 0x50 0x910005 0x60 0x0 0x71754c466900 0xf8a 0x71754c467b60 0x15f 0x980005 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x3 0x0 0x0 0x0 oops 4 3 java/lang/String 14 java/lang/String 87 org/apache/maven/model/Plugin 89 org/apache/maven/model/PluginExecution methods 0 +ciMethodData org/apache/maven/model/ConfigurationContainer getLocation (Ljava/lang/Object;)Lorg/apache/maven/model/InputLocation; 2 19937 orig 80 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 98 0x10004 0x0 0x0 0x717560002b40 0x4cda 0x0 0x0 0x40007 0x0 0x278 0x4cda 0x80004 0x0 0x0 0x717560002b40 0x4cda 0x0 0x0 0xf0005 0x4cda 0x0 0x0 0x0 0x0 0x0 0x8000000600120008 0x8 0x39c2 0x188 0xa 0xc0 0x130f 0x50 0x0 0x130 0x370005 0x130f 0x0 0x0 0x0 0x0 0x0 0x3a0007 0x0 0x100 0x130f 0x3f0003 0x130f 0xe0 0x450005 0xa 0x0 0x0 0x0 0x0 0x0 0x480007 0x0 0x90 0xa 0x4d0003 0xa 0x70 0x530005 0x0 0x0 0x0 0x0 0x0 0x0 0x560007 0x0 0x20 0x0 0x5c0008 0x8 0x39c2 0x50 0x130f 0x50 0xa 0x50 0x0 0x50 0x890002 0x39c2 0x8f0002 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x2 0x0 0xffffffffffffffff oops 2 3 java/lang/String 14 java/lang/String methods 0 +ciMethodData org/apache/maven/model/Plugin getExecutions ()Ljava/util/List; 2 19041 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 15 0x40007 0x10d1 0x30 0x388d 0xc0002 0x388d 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x1 0x0 oops 0 methods 0 +ciMethodData org/apache/maven/model/ConfigurationContainer isInherited ()Z 2 23017 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 18 0x40007 0x5876 0x48 0x6b 0xb0002 0x6b 0xe0003 0x6b 0x18 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x1 0x0 oops 0 methods 0 +ciMethodData org/apache/maven/model/merge/MavenModelMerger mergePlugin_Executions (Lorg/apache/maven/model/Plugin;Lorg/apache/maven/model/Plugin;ZLjava/util/Map;)V 2 10759 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 233 0x10005 0x0 0x0 0x71754c466900 0x2887 0x0 0x0 0x80005 0x0 0x0 0x717560005e50 0x2887 0x0 0x0 0xd0007 0x225d 0x670 0x62a 0x110005 0x0 0x0 0x71754c466900 0x62a 0x0 0x0 0x1c0005 0x0 0x0 0x717560005e50 0x62a 0x0 0x0 0x230005 0x0 0x0 0x717560005e50 0x62a 0x0 0x0 0x2b0002 0x62a 0x320005 0x0 0x0 0x717560005e50 0x62a 0x0 0x0 0x3b0005 0x0 0x0 0x71754c467ab0 0xdf4 0x0 0x0 0x400007 0x62a 0x258 0x7ca 0x450005 0x0 0x0 0x71754c467ab0 0x7ca 0x0 0x0 0x4a0004 0x0 0x0 0x71754c467b60 0x7ca 0x0 0x0 0x500007 0x0 0x140 0x7ca 0x550005 0x0 0x0 0x71754c467b60 0x7ca 0x0 0x0 0x580007 0x7b5 0x90 0x15 0x5d0005 0x0 0x0 0x71754c467b60 0x15 0x0 0x0 0x600007 0x15 0x100 0x0 0x630003 0x0 0x70 0x670005 0x0 0x0 0x71754c466900 0x7b5 0x0 0x0 0x6a0007 0x1 0x90 0x7b4 0x700005 0x0 0x0 0x71754c467c10 0x7ab 0x71754c467cc0 0x9 0x7b0005 0x0 0x0 0x71754c467d70 0x7b4 0x0 0x0 0x810003 0x7ca 0xfffffffffffffd88 0x860005 0x0 0x0 0x717560005e50 0x62a 0x0 0x0 0x8f0005 0x0 0x0 0x71754c467ab0 0x6ba 0x0 0x0 0x940007 0x62a 0x1e0 0x90 0x990005 0x0 0x0 0x71754c467ab0 0x90 0x0 0x0 0x9e0004 0x0 0x0 0x71754c467b60 0x90 0x0 0x0 0xa60005 0x0 0x0 0x71754c467c10 0x90 0x0 0x0 0xaf0005 0x0 0x0 0x71754c467d70 0x90 0x0 0x0 0xb40104 0x0 0x0 0x71754c467b60 0x33 0x0 0x0 0xbb0007 0x5d 0x58 0x33 0xc60005 0x0 0x0 0x71754c467c10 0x33 0x0 0x0 0xcf0005 0x0 0x0 0x71754c467d70 0x90 0x0 0x0 0xd50003 0x90 0xfffffffffffffe00 0xdf0005 0x0 0x0 0x71754c467d70 0x62a 0x0 0x0 0xe40002 0x62a 0xe70005 0x0 0x0 0x71754c466900 0x62a 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x5 0x0 0x0 0x0 0x0 0x0 oops 26 3 org/apache/maven/model/Plugin 10 java/util/ArrayList 21 org/apache/maven/model/Plugin 28 java/util/ArrayList 35 java/util/ArrayList 44 java/util/ArrayList 51 java/util/ArrayList$Itr 62 java/util/ArrayList$Itr 69 org/apache/maven/model/PluginExecution 80 org/apache/maven/model/PluginExecution 91 org/apache/maven/model/PluginExecution 105 org/apache/maven/model/Plugin 116 org/apache/maven/model/inheritance/DefaultInheritanceAssembler$InheritanceModelMerger 118 org/apache/maven/model/plugin/DefaultLifecycleBindingsInjector$LifecycleBindingsMerger 123 java/util/LinkedHashMap 133 java/util/ArrayList 140 java/util/ArrayList$Itr 151 java/util/ArrayList$Itr 158 org/apache/maven/model/PluginExecution 165 org/apache/maven/model/inheritance/DefaultInheritanceAssembler$InheritanceModelMerger 172 java/util/LinkedHashMap 179 org/apache/maven/model/PluginExecution 190 org/apache/maven/model/inheritance/DefaultInheritanceAssembler$InheritanceModelMerger 197 java/util/LinkedHashMap 207 java/util/LinkedHashMap 216 org/apache/maven/model/Plugin methods 0 +ciMethodData org/apache/maven/model/merge/MavenModelMerger getPluginExecutionKey (Lorg/apache/maven/model/PluginExecution;)Ljava/lang/Object; 2 2436 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 17 0x10005 0x0 0x0 0x71754c467b60 0x951 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x2 0x0 0x0 oops 1 3 org/apache/maven/model/PluginExecution methods 0 +ciMethodData org/apache/maven/model/merge/ModelMerger mergeConfigurationContainer_Configuration (Lorg/apache/maven/model/ConfigurationContainer;Lorg/apache/maven/model/ConfigurationContainer;ZLjava/util/Map;)V 2 11427 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 69 0x10005 0x56 0x0 0x71754c466900 0x28b5 0x717560350b30 0x199 0x40104 0x0 0x0 0x7175603529e0 0xa8e 0x0 0x0 0xb0007 0x2016 0x150 0xa8e 0xf0005 0x32 0x0 0x71754c466900 0x99c 0x717560350b30 0xc0 0x120104 0x0 0x0 0x7175603529e0 0x149 0x0 0x0 0x180007 0xb 0x40 0xa83 0x1d0007 0x13e 0x58 0x945 0x260002 0x950 0x2b0002 0x950 0x300003 0x950 0x28 0x370002 0x13e 0x3f0005 0x32 0x0 0x71754c466900 0x99c 0x717560350b30 0xc0 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x5 0x0 0x0 0x0 0x0 0x0 oops 8 3 org/apache/maven/model/Plugin 5 org/apache/maven/model/ReportPlugin 10 org/codehaus/plexus/util/xml/Xpp3Dom 21 org/apache/maven/model/Plugin 23 org/apache/maven/model/ReportPlugin 28 org/codehaus/plexus/util/xml/Xpp3Dom 52 org/apache/maven/model/Plugin 54 org/apache/maven/model/ReportPlugin methods 0 +ciMethodData org/apache/maven/model/merge/ModelMerger mergeConfigurationContainer (Lorg/apache/maven/model/ConfigurationContainer;Lorg/apache/maven/model/ConfigurationContainer;ZLjava/util/Map;)V 2 11427 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 27 0x60005 0x19 0x0 0x71754c467c10 0x2720 0x717560350050 0x3c2 0xf0005 0x19 0x0 0x71754c467c10 0x2720 0x717560350050 0x3c2 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x5 0x0 0x0 0x0 0x0 0x0 oops 4 3 org/apache/maven/model/inheritance/DefaultInheritanceAssembler$InheritanceModelMerger 5 org/apache/maven/model/management/DefaultPluginManagementInjector$ManagementModelMerger 10 org/apache/maven/model/inheritance/DefaultInheritanceAssembler$InheritanceModelMerger 12 org/apache/maven/model/management/DefaultPluginManagementInjector$ManagementModelMerger methods 0 +ciMethodData org/apache/maven/model/merge/ModelMerger mergeConfigurationContainer_Inherited (Lorg/apache/maven/model/ConfigurationContainer;Lorg/apache/maven/model/ConfigurationContainer;ZLjava/util/Map;)V 2 11427 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 118 113 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 60 0x10005 0x56 0x0 0x71754c466900 0x28b5 0x717560350b30 0x199 0x80007 0x2a79 0x140 0x2b 0xc0007 0x0 0x78 0x2b 0x100005 0x0 0x0 0x71754c466900 0x2b 0x0 0x0 0x130007 0x0 0xc8 0x2b 0x190005 0x0 0x0 0x71754c466900 0x2b 0x0 0x0 0x240005 0x0 0x0 0x71754c466900 0x2b 0x0 0x0 0x270005 0x0 0x0 0x71754c466900 0x2b 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x5 0x0 0x0 0x0 0x0 0x0 oops 6 3 org/apache/maven/model/Plugin 5 org/apache/maven/model/ReportPlugin 18 org/apache/maven/model/Plugin 29 org/apache/maven/model/Plugin 36 org/apache/maven/model/Plugin 43 org/apache/maven/model/Plugin methods 0 +ciMethodData org/apache/maven/model/merge/ModelMerger mergePluginExecution (Lorg/apache/maven/model/PluginExecution;Lorg/apache/maven/model/PluginExecution;ZLjava/util/Map;)V 1 88 orig 80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data 41 0x60005 0x0 0x0 0x71754c467c10 0x1f 0x717560350050 0x12 0xf0005 0x0 0x0 0x71754c467c10 0x1f 0x717560350050 0x12 0x180005 0x0 0x0 0x71754c467c10 0x1f 0x717560350050 0x12 0x210005 0x0 0x0 0x71754c467c10 0x1f 0x717560350050 0x12 0x0 0x0 0x0 0x0 0x0 0x0 0x9 0x5 0x0 0x0 0x0 0x0 0x0 oops 8 3 org/apache/maven/model/inheritance/DefaultInheritanceAssembler$InheritanceModelMerger 5 org/apache/maven/model/management/DefaultPluginManagementInjector$ManagementModelMerger 10 org/apache/maven/model/inheritance/DefaultInheritanceAssembler$InheritanceModelMerger 12 org/apache/maven/model/management/DefaultPluginManagementInjector$ManagementModelMerger 17 org/apache/maven/model/inheritance/DefaultInheritanceAssembler$InheritanceModelMerger 19 org/apache/maven/model/management/DefaultPluginManagementInjector$ManagementModelMerger 24 org/apache/maven/model/inheritance/DefaultInheritanceAssembler$InheritanceModelMerger 26 org/apache/maven/model/management/DefaultPluginManagementInjector$ManagementModelMerger methods 0 +ciInstanceKlass java/lang/Cloneable 1 0 7 100 1 100 1 1 1 +instanceKlass sun/misc/Signal$SunMiscHandler +instanceKlass sun/misc/Signal$InternalMiscHandler +instanceKlass sun/misc/SignalHandler +instanceKlass sun/misc/Signal +instanceKlass org/springframework/boot/loader/tools/SignalUtils +instanceKlass java/util/concurrent/ForkJoinTask$Aux +instanceKlass java/lang/ProcessHandleImpl$1 +instanceKlass java/util/concurrent/ForkJoinTask +instanceKlass java/util/concurrent/CompletableFuture$AsynchronousCompletionTask +instanceKlass java/util/concurrent/CompletableFuture$AltResult +instanceKlass java/util/concurrent/CompletableFuture +instanceKlass java/util/concurrent/CompletionStage +instanceKlass java/lang/ProcessImpl$1 +instanceKlass java/util/concurrent/SynchronousQueue$TransferStack$SNode +instanceKlass java/util/concurrent/SynchronousQueue$Transferer +instanceKlass java/util/concurrent/ThreadPoolExecutor$AbortPolicy +instanceKlass java/util/concurrent/RejectedExecutionHandler +instanceKlass java/util/concurrent/Executors +instanceKlass jdk/internal/util/random/RandomSupport +instanceKlass java/lang/ProcessHandleImpl +instanceKlass java/lang/ProcessHandle +instanceKlass java/lang/Process +instanceKlass java/lang/ProcessBuilder$Redirect +instanceKlass java/lang/ProcessBuilder +instanceKlass org/springframework/boot/maven/RunMojo$RunProcessKiller +instanceKlass org/springframework/util/Assert +instanceKlass org/springframework/boot/loader/tools/JavaExecutable +instanceKlass sun/nio/fs/UnixUriUtils +instanceKlass org/springframework/util/StringUtils +instanceKlass java/util/function/UnaryOperator +instanceKlass org/springframework/boot/maven/ClassPath +instanceKlass org/apache/maven/shared/artifact/filter/internal/Utils +instanceKlass org/springframework/boot/loader/tools/MainClassFinder$MainClass +instanceKlass org/springframework/asm/Context +instanceKlass org/springframework/asm/Attribute +instanceKlass org/springframework/asm/ClassReader +instanceKlass org/springframework/boot/loader/tools/MainClassFinder$SingleMainClassCallback +instanceKlass java/io/FileFilter +instanceKlass org/springframework/asm/Type +instanceKlass org/springframework/boot/loader/tools/MainClassFinder$MainClassCallback +instanceKlass org/springframework/asm/ClassVisitor +instanceKlass org/springframework/boot/loader/tools/MainClassFinder +instanceKlass org/springframework/boot/maven/SpringBootApplicationClassFinder +instanceKlass org/springframework/boot/maven/FilterableDependency +instanceKlass org/springframework/boot/maven/RunMojo$$FastClassByGuice$$199368009 +instanceKlass org/apache/maven/plugins/shade/DefaultShader$$FastClassByGuice$$198712289 +instanceKlass org/apache/maven/plugins/shade/resource/AbstractCompatibilityTransformer +instanceKlass org/apache/maven/plugins/shade/resource/ReproducibleResourceTransformer +instanceKlass org/apache/maven/plugins/shade/resource/ResourceTransformer +instanceKlass org/apache/maven/plugins/shade/DefaultShader$DefaultPackageMapper +instanceKlass org/apache/maven/plugins/shade/ShadeRequest +instanceKlass org/apache/maven/plugins/shade/DefaultShader$PackageMapper +instanceKlass org/objectweb/asm/ClassVisitor +instanceKlass org/codehaus/plexus/util/Scanner +instanceKlass org/springframework/boot/loader/tools/RunProcess +instanceKlass org/springframework/boot/loader/tools/BuildPropertiesWriter$ProjectDetails +instanceKlass org/springframework/boot/maven/SpringApplicationAdminClient +instanceKlass javax/management/MBeanServerConnection +instanceKlass org/springframework/boot/maven/EnvVariables +instanceKlass org/springframework/boot/maven/RunArguments +instanceKlass org/springframework/boot/maven/JavaProcessExecutor +instanceKlass org/springframework/boot/buildpack/platform/io/Owner +instanceKlass org/springframework/boot/buildpack/platform/build/BuildRequest +instanceKlass org/springframework/boot/maven/Docker +instanceKlass org/springframework/boot/maven/Image +instanceKlass org/springframework/boot/buildpack/platform/io/TarArchive +instanceKlass org/springframework/boot/buildpack/platform/build/BuildLog +instanceKlass org/springframework/boot/loader/tools/LaunchScript +instanceKlass org/springframework/boot/loader/tools/Packager +instanceKlass org/springframework/boot/loader/tools/layer/CustomLayers +instanceKlass org/springframework/boot/loader/tools/LayoutFactory +instanceKlass org/springframework/boot/maven/Layers +instanceKlass org/springframework/boot/loader/tools/Layers +instanceKlass org/springframework/boot/loader/tools/Packager$MainClassTimeoutWarningListener +instanceKlass org/springframework/boot/loader/tools/Libraries +instanceKlass org/apache/maven/shared/artifact/filter/collection/FilterArtifacts +instanceKlass org/apache/maven/shared/artifact/filter/collection/AbstractArtifactsFilter +instanceKlass org/apache/maven/shared/artifact/filter/collection/ArtifactsFilter +instanceKlass org/apache/maven/plugins/shade/DefaultShader +instanceKlass org/apache/maven/plugins/shade/Shader +instanceKlass org/sonatype/plexus/build/incremental/BuildContext +instanceKlass org/apache/maven/model/merge/ModelMerger$1 +instanceKlass org/apache/maven/plugin/compiler/TestCompilerMojo$$FastClassByGuice$$197299735 +instanceKlass org/apache/maven/plugins/resources/TestResourcesMojo$$FastClassByGuice$$196533253 +instanceKlass org/apache/maven/shared/utils/StringUtils +instanceKlass org/apache/maven/plugin/compiler/DeltaList +instanceKlass java/nio/file/attribute/FileTime$1 +instanceKlass org/codehaus/plexus/util/io/InputStreamFacade +instanceKlass org/codehaus/plexus/util/BaseFileUtils +instanceKlass org/apache/maven/shared/incremental/IncrementalBuildHelperRequest +instanceKlass org/codehaus/plexus/util/SelectorUtils +instanceKlass org/codehaus/plexus/util/MatchPatterns +instanceKlass org/codehaus/plexus/util/MatchPattern +instanceKlass org/codehaus/plexus/util/AbstractScanner +instanceKlass org/codehaus/plexus/util/Scanner +instanceKlass org/codehaus/plexus/compiler/util/scan/mapping/SuffixMapping +instanceKlass org/codehaus/plexus/compiler/util/scan/AbstractSourceInclusionScanner +instanceKlass com/sun/tools/javac/util/Context +instanceKlass com/sun/tools/javac/file/BaseFileManager +instanceKlass com/sun/source/util/JavacTask +instanceKlass javax/tools/JavaCompiler$CompilationTask +instanceKlass javax/tools/StandardJavaFileManager +instanceKlass com/sun/tools/javac/api/JavacTool +instanceKlass javax/tools/ToolProvider +instanceKlass java/util/concurrent/ConcurrentLinkedDeque$Node +instanceKlass org/codehaus/plexus/compiler/PlexusLoggerWrapper +instanceKlass org/eclipse/sisu/wire/ProviderIterableAdapter$ProviderEntry +instanceKlass org/eclipse/sisu/wire/ProviderIterableAdapter$ProviderIterator +instanceKlass org/eclipse/sisu/wire/ProviderIterableAdapter +instanceKlass org/apache/maven/toolchain/DefaultToolchainManager$$FastClassByGuice$$195841902 +instanceKlass org/apache/maven/plugin/compiler/CompilerMojo$$FastClassByGuice$$194900906 +instanceKlass org/codehaus/plexus/compiler/javac/JavaxToolsCompiler$$FastClassByGuice$$193176540 +instanceKlass org/codehaus/plexus/compiler/javac/JavacCompiler$$FastClassByGuice$$191990561 +instanceKlass org/codehaus/plexus/compiler/manager/DefaultCompilerManager$$FastClassByGuice$$191834876 +instanceKlass org/codehaus/plexus/languages/java/jpms/LocationManager$$FastClassByGuice$$190250452 +instanceKlass org/eclipse/sisu/wire/BeanProviders$2 +instanceKlass javax/tools/Diagnostic +instanceKlass javax/tools/JavaCompiler +instanceKlass javax/tools/Tool +instanceKlass javax/tools/JavaFileManager +instanceKlass javax/tools/OptionChecker +instanceKlass javax/tools/DiagnosticListener +instanceKlass org/codehaus/plexus/compiler/CompilerMessage +instanceKlass org/codehaus/plexus/util/cli/StreamConsumer +instanceKlass org/codehaus/plexus/compiler/CompilerOutputStyle +instanceKlass org/codehaus/plexus/languages/java/jpms/ResolvePathsRequest +instanceKlass org/codehaus/plexus/languages/java/jpms/ResolvePathRequest +instanceKlass org/codehaus/plexus/languages/java/jpms/ResolvePathResult +instanceKlass org/codehaus/plexus/languages/java/jpms/ManifestModuleNameExtractor +instanceKlass org/codehaus/plexus/languages/java/jpms/SourceModuleInfoParser +instanceKlass org/codehaus/plexus/languages/java/jpms/ModuleInfoParser +instanceKlass org/codehaus/plexus/languages/java/jpms/ModuleNameExtractor +instanceKlass org/codehaus/plexus/languages/java/jpms/JavaModuleDescriptor +instanceKlass org/codehaus/plexus/languages/java/jpms/ResolvePathsResult +instanceKlass org/apache/maven/plugin/compiler/DependencyCoordinate +instanceKlass org/codehaus/plexus/compiler/CompilerResult +instanceKlass org/apache/maven/shared/incremental/IncrementalBuildHelper +instanceKlass org/apache/maven/shared/utils/logging/MessageBuilder +instanceKlass org/codehaus/plexus/compiler/util/scan/SourceInclusionScanner +instanceKlass java/time/Instant +instanceKlass org/codehaus/plexus/compiler/CompilerConfiguration +instanceKlass org/codehaus/plexus/compiler/util/scan/mapping/SourceMapping +instanceKlass org/codehaus/plexus/compiler/javac/JavaxToolsCompiler +instanceKlass org/codehaus/plexus/compiler/javac/InProcessCompiler +instanceKlass org/codehaus/plexus/compiler/AbstractCompiler +instanceKlass org/codehaus/plexus/compiler/Compiler +instanceKlass org/codehaus/plexus/compiler/manager/DefaultCompilerManager +instanceKlass org/codehaus/plexus/compiler/manager/CompilerManager +instanceKlass org/codehaus/plexus/languages/java/jpms/LocationManager +instanceKlass org/apache/maven/artifact/resolver/filter/AbstractScopeArtifactFilter +instanceKlass org/sonatype/plexus/build/incremental/EmptyScanner +instanceKlass sun/nio/fs/UnixFileModeAttribute$1 +instanceKlass java/nio/file/attribute/PosixFileAttributeView +instanceKlass java/nio/file/attribute/FileOwnerAttributeView +instanceKlass java/nio/BufferMismatch +instanceKlass org/codehaus/plexus/interpolation/PrefixAwareRecursionInterceptor +instanceKlass org/codehaus/plexus/interpolation/SimpleRecursionInterceptor +instanceKlass org/codehaus/plexus/interpolation/InterpolationPostProcessor +instanceKlass org/codehaus/plexus/interpolation/SingleResponseValueSource +instanceKlass org/codehaus/plexus/interpolation/PrefixedValueSourceWrapper +instanceKlass org/codehaus/plexus/interpolation/FeedbackEnabledValueSource +instanceKlass org/codehaus/plexus/interpolation/AbstractDelegatingValueSource +instanceKlass org/codehaus/plexus/interpolation/QueryEnabledValueSource +instanceKlass org/codehaus/plexus/interpolation/multi/DelimiterSpecification +instanceKlass org/codehaus/plexus/interpolation/multi/MultiDelimiterStringSearchInterpolator +instanceKlass org/apache/maven/shared/filtering/FilteringUtils +instanceKlass org/apache/commons/io/FilenameUtils +instanceKlass org/codehaus/plexus/util/SelectorUtils +instanceKlass org/codehaus/plexus/util/MatchPatterns +instanceKlass org/codehaus/plexus/util/MatchPattern +instanceKlass org/codehaus/plexus/util/AbstractScanner +instanceKlass org/codehaus/plexus/interpolation/RecursionInterceptor +instanceKlass org/codehaus/plexus/interpolation/AbstractValueSource +instanceKlass org/apache/maven/plugins/resources/MavenBuildTimestamp +instanceKlass org/apache/maven/shared/filtering/FilterWrapper +instanceKlass java/lang/Character$Subset +instanceKlass org/apache/commons/lang3/StringUtils +instanceKlass org/codehaus/plexus/util/introspection/MethodMap +instanceKlass org/codehaus/plexus/util/introspection/ClassMap$CacheMiss +instanceKlass org/codehaus/plexus/util/introspection/ClassMap +instanceKlass org/codehaus/plexus/util/introspection/ReflectionValueExtractor$Tokenizer +instanceKlass org/codehaus/plexus/util/introspection/ReflectionValueExtractor +instanceKlass org/eclipse/sisu/plexus/TypeArguments +instanceKlass org/eclipse/sisu/plexus/CompositeBeanHelper$1 +instanceKlass org/eclipse/sisu/plexus/CompositeBeanHelper +instanceKlass org/apache/maven/plugin/internal/ValidatingConfigurationListener +instanceKlass org/apache/maven/plugin/DebugConfigurationListener +instanceKlass org/eclipse/sisu/inject/MildKeys +instanceKlass java/time/LocalTime +instanceKlass java/time/LocalDate +instanceKlass java/time/chrono/ChronoLocalDate +instanceKlass java/time/zone/ZoneOffsetTransition +instanceKlass java/time/LocalDateTime +instanceKlass java/time/chrono/ChronoLocalDateTime +instanceKlass java/time/temporal/TemporalAdjuster +instanceKlass java/time/zone/ZoneOffsetTransitionRule +instanceKlass java/time/zone/ZoneRules +instanceKlass java/time/zone/Ser +instanceKlass java/io/Externalizable +instanceKlass java/time/zone/ZoneRulesProvider$1 +instanceKlass java/time/zone/ZoneRulesProvider +instanceKlass java/time/Period +instanceKlass java/time/chrono/ChronoPeriod +instanceKlass java/time/format/DateTimeFormatterBuilder$TextPrinterParser +instanceKlass java/time/format/DateTimeTextProvider$1 +instanceKlass java/time/format/DateTimeTextProvider +instanceKlass java/time/format/DateTimeTextProvider$LocaleStore +instanceKlass java/time/format/DateTimeFormatterBuilder$InstantPrinterParser +instanceKlass java/time/format/DateTimeFormatterBuilder$StringLiteralPrinterParser +instanceKlass java/time/format/DateTimeFormatterBuilder$ZoneIdPrinterParser +instanceKlass java/time/format/DateTimeFormatterBuilder$OffsetIdPrinterParser +instanceKlass java/time/format/DecimalStyle +instanceKlass java/time/format/DateTimeFormatterBuilder$CompositePrinterParser +instanceKlass java/time/chrono/AbstractChronology +instanceKlass java/time/chrono/Chronology +instanceKlass java/time/format/DateTimeFormatterBuilder$CharLiteralPrinterParser +instanceKlass java/time/format/DateTimeFormatterBuilder$NumberPrinterParser +instanceKlass java/time/format/DateTimeFormatterBuilder$DateTimePrinterParser +instanceKlass java/time/temporal/JulianFields +instanceKlass java/time/temporal/IsoFields +instanceKlass java/time/temporal/ValueRange +instanceKlass java/time/temporal/TemporalField +instanceKlass java/time/ZoneId +instanceKlass java/time/temporal/TemporalQuery +instanceKlass java/time/format/DateTimeFormatterBuilder +instanceKlass java/time/format/DateTimeFormatter +instanceKlass java/time/temporal/Temporal +instanceKlass java/time/temporal/TemporalAccessor +instanceKlass org/codehaus/plexus/component/configurator/converters/ParameterizedConfigurationConverter +instanceKlass org/codehaus/plexus/component/configurator/converters/AbstractConfigurationConverter +instanceKlass org/codehaus/plexus/component/configurator/converters/ConfigurationConverter +instanceKlass org/codehaus/plexus/component/configurator/converters/lookup/DefaultConverterLookup +instanceKlass org/codehaus/plexus/component/configurator/expression/DefaultExpressionEvaluator +instanceKlass org/apache/maven/plugin/PluginParameterExpressionEvaluator +instanceKlass org/codehaus/plexus/component/configurator/expression/TypeAwareExpressionEvaluator +instanceKlass org/apache/maven/monitor/logging/DefaultLog +instanceKlass org/sonatype/plexus/build/incremental/DefaultBuildContext$$FastClassByGuice$$189757721 +instanceKlass org/apache/maven/plugins/resources/ResourcesMojo$$FastClassByGuice$$188717517 +instanceKlass org/apache/maven/shared/filtering/DefaultMavenResourcesFiltering$$FastClassByGuice$$187342518 +instanceKlass org/apache/maven/shared/filtering/DefaultMavenReaderFilter$$FastClassByGuice$$185959635 +instanceKlass org/apache/maven/shared/filtering/DefaultMavenFileFilter$$FastClassByGuice$$184702993 +instanceKlass com/google/inject/internal/Messages$Converter +instanceKlass com/google/inject/internal/Messages +instanceKlass org/codehaus/plexus/interpolation/Interpolator +instanceKlass org/codehaus/plexus/interpolation/BasicInterpolator +instanceKlass org/codehaus/plexus/interpolation/ValueSource +instanceKlass org/codehaus/plexus/util/Scanner +instanceKlass org/apache/maven/shared/filtering/AbstractMavenFilteringRequest +instanceKlass org/w3c/dom/Element +instanceKlass org/w3c/dom/Document +instanceKlass org/w3c/dom/Node +instanceKlass org/apache/maven/shared/filtering/DefaultMavenResourcesFiltering +instanceKlass org/apache/maven/shared/filtering/MavenResourcesFiltering +instanceKlass org/apache/maven/shared/filtering/MavenReaderFilter +instanceKlass org/apache/maven/shared/filtering/BaseFilter +instanceKlass org/apache/maven/shared/filtering/MavenFileFilter +instanceKlass org/apache/maven/shared/filtering/DefaultFilterInfo +instanceKlass org/sonatype/plexus/build/incremental/BuildContext +instanceKlass java/security/CodeSigner +instanceKlass java/util/jar/JarVerifier +instanceKlass org/eclipse/sisu/space/FileEntryIterator +instanceKlass org/eclipse/sisu/space/ResourceEnumeration +instanceKlass org/eclipse/sisu/plexus/ComponentDescriptorBeanModule$PlexusDescriptorBeanSource +instanceKlass org/eclipse/sisu/plexus/ComponentDescriptorBeanModule$ComponentMetadata +instanceKlass org/apache/maven/plugin/AbstractMojo +instanceKlass org/apache/maven/plugin/ContextEnabled +instanceKlass org/apache/maven/plugin/Mojo +instanceKlass org/eclipse/sisu/plexus/ComponentDescriptorBeanModule +instanceKlass org/apache/maven/classrealm/ArtifactClassRealmConstituent +instanceKlass org/apache/maven/plugin/internal/WagonExcluder +instanceKlass org/apache/maven/plugin/CacheUtils +instanceKlass org/apache/maven/plugin/DefaultPluginRealmCache$CacheKey +instanceKlass org/eclipse/aether/util/graph/visitor/TreeDependencyVisitor +instanceKlass org/eclipse/aether/util/graph/visitor/FilteringDependencyVisitor +instanceKlass org/eclipse/aether/internal/impl/ArtifactRequestBuilder +instanceKlass org/eclipse/aether/util/graph/transformer/NearestVersionSelector$ConflictGroup +instanceKlass org/eclipse/aether/util/graph/transformer/ConflictResolver$ConflictItem +instanceKlass org/eclipse/aether/util/graph/transformer/ConflictResolver$NodeInfo +instanceKlass org/eclipse/aether/util/graph/transformer/ConflictResolver$ScopeContext +instanceKlass org/eclipse/aether/util/graph/transformer/ConflictResolver$ConflictContext +instanceKlass org/eclipse/aether/util/graph/transformer/ConflictResolver$State +instanceKlass org/eclipse/aether/util/graph/transformer/ConflictIdSorter$RootQueue +instanceKlass org/eclipse/aether/util/graph/transformer/ConflictIdSorter$ConflictId +instanceKlass org/eclipse/aether/util/graph/transformer/ConflictMarker$ConflictGroup +instanceKlass org/eclipse/aether/util/graph/transformer/ConflictMarker$Key +instanceKlass org/eclipse/aether/util/graph/transformer/ConflictMarker +instanceKlass org/eclipse/aether/util/graph/transformer/ConflictIdSorter +instanceKlass org/eclipse/aether/util/graph/transformer/TransformationContextKeys +instanceKlass org/eclipse/aether/internal/impl/collect/DefaultDependencyGraphTransformationContext +instanceKlass java/util/Collections$UnmodifiableList$1 +instanceKlass org/apache/maven/utils/Os +instanceKlass org/eclipse/aether/util/graph/selector/ExclusionDependencySelector$ExclusionComparator +instanceKlass org/apache/maven/artifact/handler/DefaultArtifactHandler$__sisu13$$FastClassByGuice$$184170604 +instanceKlass org/apache/maven/model/merge/ModelMerger$NotifierKeyComputer +instanceKlass org/eclipse/aether/collection/DependencyManagement +instanceKlass org/eclipse/aether/internal/impl/collect/DataPool$GraphKey +instanceKlass org/eclipse/aether/internal/impl/collect/DefaultDependencyCycle +instanceKlass org/eclipse/aether/internal/impl/collect/DataPool$Descriptor +instanceKlass org/apache/maven/repository/internal/DefaultVersionResolver$Record +instanceKlass org/apache/maven/repository/internal/DefaultVersionResolver$VersionInfo +instanceKlass org/apache/maven/artifact/repository/metadata/SnapshotVersion +instanceKlass org/apache/maven/artifact/repository/metadata/Snapshot +instanceKlass org/apache/maven/artifact/repository/metadata/io/xpp3/MetadataXpp3Reader$1 +instanceKlass org/apache/maven/artifact/repository/metadata/io/xpp3/MetadataXpp3Reader$ContentTransformer +instanceKlass org/apache/maven/artifact/repository/metadata/io/xpp3/MetadataXpp3Reader +instanceKlass org/eclipse/aether/internal/impl/Utils +instanceKlass org/eclipse/aether/repository/LocalMetadataResult +instanceKlass org/eclipse/aether/repository/LocalMetadataRequest +instanceKlass org/eclipse/aether/resolution/MetadataResult +instanceKlass org/eclipse/aether/resolution/MetadataRequest +instanceKlass org/eclipse/aether/metadata/AbstractMetadata +instanceKlass org/eclipse/aether/internal/impl/collect/DataPool$DescriptorKey +instanceKlass org/eclipse/aether/internal/impl/collect/DataPool$Constraint$VersionRepo +instanceKlass org/eclipse/aether/internal/impl/collect/DataPool$Constraint +instanceKlass org/eclipse/aether/internal/impl/collect/DataPool$ConstraintKey +instanceKlass org/eclipse/aether/internal/impl/collect/CollectStepDataImpl +instanceKlass org/eclipse/aether/collection/CollectStepData +instanceKlass org/eclipse/aether/graph/Dependency$Exclusions$1 +instanceKlass org/eclipse/aether/util/graph/manager/ClassicDependencyManager$Key +instanceKlass org/eclipse/aether/internal/impl/collect/df/NodeStack +instanceKlass org/eclipse/aether/graph/DependencyCycle +instanceKlass org/eclipse/aether/internal/impl/collect/DataPool$HardInternPool +instanceKlass org/eclipse/aether/internal/impl/collect/DataPool$WeakInternPool +instanceKlass org/eclipse/aether/internal/impl/collect/DataPool$InternPool +instanceKlass org/eclipse/aether/internal/impl/collect/CachingArtifactTypeRegistry +instanceKlass org/apache/maven/artifact/handler/DefaultArtifactHandler$__sisu17$$FastClassByGuice$$183412749 +instanceKlass org/eclipse/aether/util/artifact/ArtifactIdUtils +instanceKlass org/apache/maven/project/DefaultDependencyResolutionRequest +instanceKlass org/apache/maven/lifecycle/internal/LifecycleDependencyResolver$ReactorDependencyFilter +instanceKlass org/eclipse/aether/util/filter/AndDependencyFilter +instanceKlass org/eclipse/aether/util/filter/ScopeDependencyFilter +instanceKlass org/apache/maven/project/artifact/DefaultProjectArtifactsCache$CacheKey +instanceKlass org/apache/maven/lifecycle/internal/MojoExecutor$ProjectLock +instanceKlass org/apache/maven/project/MavenProject$LoggingList$1 +instanceKlass org/apache/maven/lifecycle/internal/MojoExecutor$1 +instanceKlass org/apache/maven/lifecycle/internal/ExecutionPlanItem +instanceKlass org/codehaus/plexus/component/repository/ComponentDependency +instanceKlass org/codehaus/plexus/component/repository/ComponentRequirement +instanceKlass org/apache/maven/model/Notifier +instanceKlass org/apache/maven/lifecycle/internal/DefaultMojoExecutionConfigurator$$FastClassByGuice$$181826838 +instanceKlass org/apache/maven/execution/ProjectExecutionEvent +instanceKlass org/apache/maven/lifecycle/internal/CompoundProjectExecutionListener +instanceKlass org/apache/maven/lifecycle/internal/DefaultLifecycleMappingDelegate$$FastClassByGuice$$180495973 +instanceKlass org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculator$$FastClassByGuice$$179712830 +instanceKlass org/apache/maven/project/artifact/DefaultProjectArtifactsCache$$FastClassByGuice$$178835768 +instanceKlass org/apache/maven/artifact/factory/DefaultArtifactFactory$$FastClassByGuice$$177960603 +instanceKlass org/apache/maven/lifecycle/internal/builder/singlethreaded/SingleThreadedBuilder$$FastClassByGuice$$176388899 +instanceKlass org/apache/maven/graph/DefaultProjectDependencyGraph$MavenProjectComparator +instanceKlass org/apache/maven/graph/FilteredProjectDependencyGraph$Key +instanceKlass org/apache/maven/lifecycle/internal/GoalTask +instanceKlass org/apache/maven/plugin/prefix/internal/DefaultPluginPrefixResult +instanceKlass org/apache/maven/plugin/MavenPluginValidator +instanceKlass org/codehaus/plexus/configuration/DefaultPlexusConfiguration +instanceKlass java/util/stream/MatchOps$BooleanTerminalSink +instanceKlass java/util/stream/MatchOps$MatchOp +instanceKlass java/util/stream/MatchOps +instanceKlass org/apache/maven/model/io/xpp3/MavenXpp3Reader$1 +instanceKlass org/apache/maven/model/io/xpp3/MavenXpp3Reader$ContentTransformer +instanceKlass org/apache/maven/model/io/xpp3/MavenXpp3Reader +instanceKlass org/apache/maven/repository/internal/DefaultModelResolver +instanceKlass org/apache/maven/plugin/DefaultPluginDescriptorCache$CacheKey +instanceKlass org/apache/maven/plugin/prefix/DefaultPluginPrefixRequest +instanceKlass org/apache/maven/graph/FilteredProjectDependencyGraph +instanceKlass org/apache/maven/internal/aether/MavenChainedWorkspaceReader +instanceKlass org/codehaus/plexus/util/dag/TopologicalSorter +instanceKlass org/codehaus/plexus/util/dag/Vertex +instanceKlass org/codehaus/plexus/util/dag/DAG +instanceKlass org/apache/maven/project/ProjectSorter +instanceKlass org/apache/maven/graph/DefaultProjectDependencyGraph +instanceKlass org/apache/maven/project/DefaultProjectBuildingResult +instanceKlass org/apache/maven/lifecycle/mapping/DefaultLifecycleMapping$$FastClassByGuice$$175528422 +instanceKlass org/apache/maven/model/Site +instanceKlass java/util/stream/Streams$RangeIntSpliterator +instanceKlass org/apache/maven/lifecycle/Lifecycle$__sisu9$$FastClassByGuice$$174140356 +instanceKlass org/apache/maven/lifecycle/Lifecycle$__sisu8$$FastClassByGuice$$173740846 +instanceKlass org/apache/maven/lifecycle/mapping/LifecycleMojo +instanceKlass org/apache/maven/lifecycle/mapping/DefaultLifecycleMapping$__sisu2$$FastClassByGuice$$171992704 +instanceKlass org/apache/maven/lifecycle/mapping/Lifecycle +instanceKlass org/apache/maven/model/building/DefaultModelBuildingEvent +instanceKlass org/apache/maven/model/building/ModelBuildingEventCatapult$1 +instanceKlass org/apache/maven/project/DefaultProjectBuilder$InterimResult +instanceKlass org/apache/maven/artifact/handler/DefaultArtifactHandler$__sisu14$$FastClassByGuice$$171101295 +instanceKlass org/apache/maven/artifact/versioning/Restriction +instanceKlass org/apache/maven/artifact/handler/DefaultArtifactHandler$__sisu10$$FastClassByGuice$$170719306 +instanceKlass org/apache/maven/artifact/ArtifactUtils +instanceKlass org/apache/maven/artifact/DefaultArtifact +instanceKlass org/apache/maven/artifact/handler/DefaultArtifactHandler$$FastClassByGuice$$169083452 +instanceKlass org/apache/maven/artifact/versioning/ComparableVersion$StringItem +instanceKlass org/apache/maven/artifact/versioning/ComparableVersion$IntItem +instanceKlass org/apache/maven/artifact/versioning/ComparableVersion$Item +instanceKlass org/apache/maven/artifact/versioning/ComparableVersion +instanceKlass org/apache/maven/artifact/versioning/DefaultArtifactVersion +instanceKlass org/apache/maven/model/Extension +instanceKlass org/codehaus/plexus/interpolation/util/StringUtils +instanceKlass org/apache/maven/model/DistributionManagement +instanceKlass org/apache/maven/model/MailingList +instanceKlass org/apache/maven/model/Organization +instanceKlass org/apache/maven/model/CiManagement +instanceKlass org/apache/maven/model/Prerequisites +instanceKlass org/codehaus/plexus/interpolation/reflection/MethodMap +instanceKlass org/codehaus/plexus/interpolation/reflection/ClassMap$CacheMiss +instanceKlass org/codehaus/plexus/interpolation/reflection/ClassMap +instanceKlass org/codehaus/plexus/interpolation/reflection/ReflectionValueExtractor$Tokenizer +instanceKlass org/codehaus/plexus/interpolation/reflection/ReflectionValueExtractor +instanceKlass org/codehaus/plexus/interpolation/util/ValueSourceUtils +instanceKlass org/apache/maven/model/interpolation/StringVisitorModelInterpolator$ModelVisitor +instanceKlass org/apache/maven/model/interpolation/StringVisitorModelInterpolator$1 +instanceKlass org/codehaus/plexus/interpolation/PrefixAwareRecursionInterceptor +instanceKlass org/apache/maven/model/interpolation/UrlNormalizingPostProcessor +instanceKlass org/apache/maven/model/interpolation/PathTranslatingPostProcessor +instanceKlass java/text/DontCareFieldPosition$1 +instanceKlass java/text/Format$FieldDelegate +instanceKlass org/apache/maven/model/interpolation/MavenBuildTimestamp +instanceKlass org/apache/maven/model/interpolation/ProblemDetectingValueSource +instanceKlass org/codehaus/plexus/interpolation/PrefixedValueSourceWrapper +instanceKlass org/codehaus/plexus/interpolation/FeedbackEnabledValueSource +instanceKlass org/codehaus/plexus/interpolation/AbstractDelegatingValueSource +instanceKlass org/codehaus/plexus/interpolation/QueryEnabledValueSource +instanceKlass org/apache/maven/model/merge/ModelMerger$ExtensionKeyComputer +instanceKlass org/apache/maven/model/merge/ModelMerger$ResourceKeyComputer +instanceKlass org/apache/maven/model/merge/ModelMerger$SourceDominant +instanceKlass org/apache/maven/model/merge/ModelMerger$DependencyKeyComputer +instanceKlass java/lang/invoke/MethodHandle$1 +instanceKlass org/apache/maven/model/building/DefaultModelBuilder$InterpolateString +instanceKlass org/apache/maven/model/building/DefaultModelBuilder$1Interpolation +instanceKlass org/apache/maven/model/Exclusion +instanceKlass org/apache/maven/model/IssueManagement +instanceKlass org/apache/maven/model/io/xpp3/MavenXpp3ReaderEx$Xpp3DomBuilderInputLocationBuilder +instanceKlass org/apache/maven/model/Scm +instanceKlass org/apache/maven/model/License +instanceKlass org/apache/maven/model/building/FilterModelBuildingRequest +instanceKlass java/util/AbstractMap$2$1 +instanceKlass java/nio/channels/spi/AbstractInterruptibleChannel$1 +instanceKlass sun/nio/ch/Interruptible +instanceKlass sun/nio/ch/FileKey +instanceKlass sun/nio/ch/FileLockTable +instanceKlass sun/nio/fs/UnixFileSystemProvider$3 +instanceKlass org/eclipse/aether/repository/LocalArtifactRequest +instanceKlass org/apache/maven/repository/internal/DefaultVersionResolver$Key +instanceKlass org/eclipse/aether/internal/impl/DefaultRepositoryEventDispatcher$1 +instanceKlass org/eclipse/aether/RepositoryEvent$Builder +instanceKlass org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSource$1 +instanceKlass org/eclipse/aether/internal/impl/filter/RemoteRepositoryFilterSourceSupport$SimpleResult +instanceKlass org/eclipse/aether/named/support/Retry$DoNotRetry +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter$AdaptedLockSyncContext +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/GAVNameMapper +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/NameMappers +instanceKlass org/eclipse/sisu/wire/NamedIterableAdapter$NamedEntry +instanceKlass org/eclipse/sisu/wire/NamedIterableAdapter$NamedIterator +instanceKlass org/apache/maven/project/ReactorModelPool$CacheKey +instanceKlass org/eclipse/aether/util/version/GenericVersion$Item +instanceKlass org/eclipse/aether/util/version/GenericVersion$Tokenizer +instanceKlass org/eclipse/aether/util/version/GenericVersion +instanceKlass org/eclipse/aether/util/version/GenericVersionConstraint +instanceKlass org/eclipse/aether/version/VersionRange +instanceKlass org/eclipse/aether/version/VersionConstraint +instanceKlass org/eclipse/aether/util/version/GenericVersionScheme +instanceKlass org/eclipse/aether/artifact/AbstractArtifact +instanceKlass org/apache/maven/repository/internal/ArtifactDescriptorUtils +instanceKlass org/apache/maven/model/DependencyManagement +instanceKlass org/apache/maven/repository/internal/DefaultModelCache$Key +instanceKlass org/apache/maven/model/building/ModelCacheTag$2 +instanceKlass org/apache/maven/model/building/ModelCacheTag$1 +instanceKlass java/util/Spliterators$IteratorSpliterator +instanceKlass org/apache/maven/model/building/ModelProblemUtils +instanceKlass org/apache/maven/model/Parent +instanceKlass org/apache/maven/model/io/xpp3/MavenXpp3ReaderEx$1 +instanceKlass org/codehaus/plexus/util/xml/Xpp3DomBuilder$InputLocationBuilder +instanceKlass org/apache/maven/model/io/xpp3/MavenXpp3ReaderEx$ContentTransformer +instanceKlass org/apache/maven/model/io/xpp3/MavenXpp3ReaderEx +instanceKlass org/apache/maven/model/building/ModelSource2 +instanceKlass org/apache/maven/model/building/DefaultModelBuildingResult +instanceKlass org/apache/maven/model/building/AbstractModelBuildingListener +instanceKlass org/apache/maven/project/ProjectModelResolver +instanceKlass org/apache/maven/model/building/DefaultModelBuildingRequest +instanceKlass org/apache/maven/artifact/repository/LegacyLocalRepositoryManager +instanceKlass org/apache/maven/repository/internal/DefaultModelCache +instanceKlass org/apache/maven/project/DefaultProjectBuildingRequest +instanceKlass org/apache/maven/shared/utils/logging/AnsiMessageBuilder +instanceKlass org/apache/maven/lifecycle/internal/DefaultExecutionEventCatapult$1 +instanceKlass org/apache/maven/lifecycle/internal/DefaultExecutionEvent +instanceKlass org/apache/maven/AbstractMavenLifecycleParticipant +instanceKlass java/util/concurrent/atomic/AtomicReference +instanceKlass org/apache/maven/session/scope/internal/SessionScope$CachingProvider +instanceKlass org/apache/maven/settings/RuntimeInfo +instanceKlass org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport$LocalPathPrefixComposerSupport +instanceKlass org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager +instanceKlass java/util/ArrayList$SubList$1 +instanceKlass org/eclipse/aether/internal/impl/PrioritizedComponent +instanceKlass org/eclipse/sisu/wire/EntrySetAdapter$ValueIterator +instanceKlass org/eclipse/aether/internal/impl/PrioritizedComponents +instanceKlass org/eclipse/aether/repository/RemoteRepository$Builder +instanceKlass java/net/spi/URLStreamHandlerProvider +instanceKlass java/net/URL$1 +instanceKlass java/net/URL$2 +instanceKlass org/eclipse/aether/util/ConfigUtils +instanceKlass org/eclipse/aether/AbstractRepositoryListener +instanceKlass org/eclipse/aether/util/repository/DefaultAuthenticationSelector +instanceKlass org/eclipse/aether/util/repository/DefaultProxySelector +instanceKlass org/eclipse/aether/util/repository/DefaultMirrorSelector$MirrorDef +instanceKlass org/eclipse/aether/util/repository/DefaultMirrorSelector +instanceKlass org/apache/maven/settings/crypto/DefaultSettingsDecryptionResult +instanceKlass org/apache/maven/settings/crypto/DefaultSettingsDecryptionRequest +instanceKlass org/apache/maven/RepositoryUtils$MavenArtifactTypeRegistry +instanceKlass org/apache/maven/RepositoryUtils +instanceKlass org/eclipse/aether/util/repository/SimpleResolutionErrorPolicy +instanceKlass org/eclipse/aether/util/repository/SimpleArtifactDescriptorPolicy +instanceKlass org/eclipse/aether/artifact/DefaultArtifactType +instanceKlass org/eclipse/aether/util/artifact/SimpleArtifactTypeRegistry +instanceKlass org/eclipse/aether/util/graph/transformer/JavaDependencyContextRefiner +instanceKlass org/eclipse/aether/util/graph/transformer/ChainedDependencyGraphTransformer +instanceKlass org/eclipse/aether/util/graph/transformer/ConflictResolver +instanceKlass org/eclipse/aether/graph/Exclusion +instanceKlass org/eclipse/aether/util/graph/selector/ExclusionDependencySelector +instanceKlass org/eclipse/aether/util/graph/selector/OptionalDependencySelector +instanceKlass org/eclipse/aether/util/graph/selector/ScopeDependencySelector +instanceKlass org/eclipse/aether/util/graph/selector/AndDependencySelector +instanceKlass org/eclipse/aether/util/graph/manager/ClassicDependencyManager +instanceKlass org/eclipse/aether/util/graph/traverser/FatArtifactTraverser +instanceKlass org/eclipse/aether/DefaultSessionData +instanceKlass org/eclipse/aether/DefaultRepositorySystemSession$NullFileTransformerManager +instanceKlass org/eclipse/aether/transform/FileTransformerManager +instanceKlass org/eclipse/aether/DefaultRepositorySystemSession$NullArtifactTypeRegistry +instanceKlass org/eclipse/aether/DefaultRepositorySystemSession$NullAuthenticationSelector +instanceKlass org/eclipse/aether/DefaultRepositorySystemSession$NullProxySelector +instanceKlass org/eclipse/aether/DefaultRepositorySystemSession$NullMirrorSelector +instanceKlass org/eclipse/aether/SessionData +instanceKlass org/eclipse/aether/artifact/ArtifactTypeRegistry +instanceKlass org/eclipse/aether/collection/DependencyGraphTransformer +instanceKlass org/eclipse/aether/util/graph/transformer/ConflictResolver$VersionSelector +instanceKlass org/eclipse/aether/util/graph/transformer/ConflictResolver$ScopeSelector +instanceKlass org/eclipse/aether/util/graph/transformer/ConflictResolver$OptionalitySelector +instanceKlass org/eclipse/aether/util/graph/transformer/ConflictResolver$ScopeDeriver +instanceKlass org/apache/maven/repository/internal/MavenRepositorySystemUtils +instanceKlass org/apache/maven/execution/DefaultMavenExecutionResult +instanceKlass org/apache/maven/artifact/repository/MavenArtifactRepository +instanceKlass org/apache/maven/artifact/repository/layout/ArtifactRepositoryLayout2 +instanceKlass org/apache/maven/artifact/repository/layout/DefaultRepositoryLayout$$FastClassByGuice$$168286387 +instanceKlass java/util/concurrent/ForkJoinPool$WorkQueue +instanceKlass java/util/concurrent/ForkJoinPool$DefaultCommonPoolForkJoinWorkerThreadFactory +instanceKlass java/util/concurrent/ForkJoinPool$1 +instanceKlass java/util/concurrent/ForkJoinPool$DefaultForkJoinWorkerThreadFactory +instanceKlass java/util/concurrent/ForkJoinPool$ForkJoinWorkerThreadFactory +instanceKlass org/apache/maven/execution/AbstractExecutionListener +instanceKlass java/util/concurrent/AbstractExecutorService +instanceKlass java/util/concurrent/ExecutorService +instanceKlass java/util/concurrent/locks/AbstractQueuedSynchronizer$Node +instanceKlass java/util/concurrent/ForkJoinPool$ManagedBlocker +instanceKlass org/apache/maven/cli/transfer/SimplexTransferListener$Exchange +instanceKlass org/eclipse/aether/transfer/AbstractTransferListener +instanceKlass org/apache/maven/toolchain/building/DefaultToolchainsBuildingResult +instanceKlass org/apache/maven/toolchain/building/DefaultToolchainsBuilder$1 +instanceKlass org/apache/maven/toolchain/model/io/xpp3/MavenToolchainsXpp3Writer +instanceKlass org/apache/maven/toolchain/model/io/xpp3/MavenToolchainsXpp3Reader$1 +instanceKlass org/apache/maven/toolchain/model/io/xpp3/MavenToolchainsXpp3Reader$ContentTransformer +instanceKlass org/apache/maven/toolchain/model/io/xpp3/MavenToolchainsXpp3Reader +instanceKlass org/apache/maven/building/DefaultProblemCollector +instanceKlass org/apache/maven/building/ProblemCollectorFactory +instanceKlass org/apache/maven/toolchain/building/DefaultToolchainsBuildingRequest +instanceKlass org/apache/maven/settings/building/DefaultSettingsBuildingResult +instanceKlass org/apache/maven/settings/building/DefaultSettingsBuilder$1 +instanceKlass java/lang/ProcessEnvironment$StringKeySet$1 +instanceKlass org/codehaus/plexus/interpolation/os/OperatingSystemUtils$DefaultEnvVarSource +instanceKlass org/codehaus/plexus/interpolation/os/OperatingSystemUtils$EnvVarSource +instanceKlass org/codehaus/plexus/interpolation/os/OperatingSystemUtils +instanceKlass org/codehaus/plexus/util/xml/pull/MXSerializer +instanceKlass org/codehaus/plexus/util/xml/pull/XmlSerializer +instanceKlass org/apache/maven/settings/io/xpp3/SettingsXpp3Writer +instanceKlass org/codehaus/plexus/util/xml/pull/EntityReplacementMap +instanceKlass org/apache/maven/settings/io/xpp3/SettingsXpp3Reader$1 +instanceKlass org/apache/maven/settings/io/xpp3/SettingsXpp3Reader$ContentTransformer +instanceKlass org/apache/maven/settings/io/xpp3/SettingsXpp3Reader +instanceKlass org/apache/maven/building/FileSource +instanceKlass org/apache/maven/settings/building/DefaultSettingsBuildingRequest +instanceKlass org/apache/maven/graph/DefaultGraphBuilder$$FastClassByGuice$$167552379 +instanceKlass jdk/internal/event/Event +instanceKlass sun/security/util/SecurityProviderConstants +instanceKlass java/security/Provider$UString +instanceKlass java/security/Provider$Service +instanceKlass sun/security/provider/FileInputStreamPool +instanceKlass sun/security/provider/NativePRNG$RandomIO +instanceKlass sun/security/provider/NativePRNG$2 +instanceKlass sun/security/provider/NativePRNG$1 +instanceKlass java/security/SecureRandomSpi +instanceKlass sun/security/provider/SunEntries$1 +instanceKlass sun/security/provider/SunEntries +instanceKlass sun/security/util/SecurityConstants +instanceKlass sun/security/jca/ProviderList$2 +instanceKlass jdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer +instanceKlass javax/security/auth/login/Configuration$Parameters +instanceKlass java/security/Policy$Parameters +instanceKlass java/security/cert/CertStoreParameters +instanceKlass java/security/SecureRandomParameters +instanceKlass java/security/Provider$EngineDescription +instanceKlass java/security/Provider$ServiceKey +instanceKlass sun/security/jca/ProviderConfig +instanceKlass sun/security/jca/ProviderList +instanceKlass sun/security/jca/Providers +instanceKlass java/security/Key +instanceKlass java/security/spec/AlgorithmParameterSpec +instanceKlass jdk/internal/math/FloatingDecimal$PreparedASCIIToBinaryBuffer +instanceKlass jdk/internal/math/FloatingDecimal$ASCIIToBinaryConverter +instanceKlass jdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer +instanceKlass jdk/internal/math/FloatingDecimal$ExceptionalBinaryToASCIIBuffer +instanceKlass jdk/internal/math/FloatingDecimal$BinaryToASCIIConverter +instanceKlass jdk/internal/math/FloatingDecimal +instanceKlass org/apache/maven/plugin/prefix/internal/DefaultPluginPrefixResolver$$FastClassByGuice$$166721650 +instanceKlass org/apache/maven/plugin/CompoundMojoExecutionListener +instanceKlass org/apache/maven/plugin/internal/DefaultLegacySupport$$FastClassByGuice$$164967175 +instanceKlass org/apache/maven/plugin/DefaultBuildPluginManager$$FastClassByGuice$$164187718 +instanceKlass org/apache/maven/lifecycle/internal/DefaultLifecycleTaskSegmentCalculator$$FastClassByGuice$$162620744 +instanceKlass org/apache/maven/lifecycle/internal/DefaultExecutionEventCatapult$$FastClassByGuice$$161985490 +instanceKlass org/apache/maven/project/DefaultProjectDependenciesResolver$$FastClassByGuice$$161062453 +instanceKlass org/apache/maven/project/RepositorySessionDecorator +instanceKlass org/apache/maven/plugin/DefaultPluginArtifactsCache$$FastClassByGuice$$160341750 +instanceKlass com/google/inject/internal/DelegatingInvocationHandler +instanceKlass org/apache/maven/artifact/repository/metadata/io/DefaultMetadataReader$$FastClassByGuice$$159200076 +instanceKlass org/apache/maven/plugin/version/internal/DefaultPluginVersionResolver$$FastClassByGuice$$157783495 +instanceKlass org/apache/maven/plugin/DefaultExtensionRealmCache$$FastClassByGuice$$156658821 +instanceKlass org/apache/maven/rtinfo/internal/DefaultRuntimeInformation$$FastClassByGuice$$156135027 +instanceKlass org/eclipse/aether/artifact/ArtifactType +instanceKlass org/eclipse/sisu/wire/NamedIterableAdapter +instanceKlass org/eclipse/aether/internal/impl/DefaultUpdateCheckManager$1 +instanceKlass org/apache/maven/plugin/DefaultPluginRealmCache$$FastClassByGuice$$154550245 +instanceKlass org/apache/maven/plugin/DefaultPluginDescriptorCache$$FastClassByGuice$$153375888 +instanceKlass org/apache/maven/plugin/internal/DefaultMavenPluginManager$$FastClassByGuice$$152377876 +instanceKlass org/apache/maven/artifact/handler/manager/DefaultArtifactHandlerManager$$FastClassByGuice$$151694933 +instanceKlass org/apache/maven/project/DefaultProjectRealmCache$$FastClassByGuice$$150617186 +instanceKlass org/codehaus/plexus/classworlds/realm/Entry +instanceKlass org/eclipse/sisu/inject/Guice4$2 +instanceKlass org/apache/maven/project/DefaultProjectBuildingHelper$$FastClassByGuice$$149205414 +instanceKlass org/apache/maven/lifecycle/internal/DefaultLifecyclePluginAnalyzer$$FastClassByGuice$$147909671 +instanceKlass org/apache/maven/model/plugin/DefaultLifecycleBindingsInjector$$FastClassByGuice$$147113479 +instanceKlass org/apache/maven/model/Contributor +instanceKlass org/apache/maven/model/PatternSet +instanceKlass org/apache/maven/model/merge/ModelMerger$KeyComputer +instanceKlass org/apache/maven/model/merge/ModelMerger$Remapping +instanceKlass org/apache/maven/project/DefaultProjectBuilder$$FastClassByGuice$$145921927 +instanceKlass org/apache/maven/DefaultMaven$$FastClassByGuice$$145158877 +instanceKlass org/apache/maven/cli/event/DefaultEventSpyContext +instanceKlass org/eclipse/sisu/wire/EntryListAdapter$ValueIterator +instanceKlass org/apache/maven/cli/logging/Slf4jLogger +instanceKlass org/eclipse/sisu/inject/LazyBeanEntry$JsrNamed +instanceKlass org/eclipse/sisu/inject/LazyBeanEntry +instanceKlass javax/annotation/Priority +instanceKlass org/eclipse/sisu/inject/Implementations +instanceKlass org/eclipse/sisu/plexus/LazyPlexusBean +instanceKlass org/eclipse/sisu/inject/RankedSequence$Itr +instanceKlass org/eclipse/sisu/inject/RankedBindings$Itr +instanceKlass org/eclipse/sisu/inject/LocatedBeans$Itr +instanceKlass org/eclipse/sisu/plexus/RealmFilteredBeans$FilteredItr +instanceKlass org/eclipse/sisu/plexus/DefaultPlexusBeans$Itr +instanceKlass org/eclipse/sisu/plexus/DefaultPlexusBeans +instanceKlass org/eclipse/sisu/plexus/RealmFilteredBeans +instanceKlass org/eclipse/sisu/inject/BeanCache +instanceKlass org/eclipse/sisu/inject/LocatedBeans +instanceKlass org/eclipse/sisu/inject/MildElements$Indexable +instanceKlass com/google/inject/internal/ProviderInternalFactory$1 +instanceKlass com/google/inject/internal/ConstructorInjector$1 +instanceKlass org/eclipse/sisu/inject/WatchedBeans +instanceKlass org/eclipse/sisu/inject/MildValues$ValueItr +instanceKlass org/eclipse/sisu/inject/InjectorBindings +instanceKlass com/google/inject/spi/ProvisionListener$ProvisionInvocation +instanceKlass com/google/inject/internal/MembersInjectorImpl$1 +instanceKlass com/google/inject/internal/InternalContext +instanceKlass com/google/inject/internal/Initializer$1 +instanceKlass com/google/common/collect/AbstractMapBasedMultimap$AsMap$AsMapIterator +instanceKlass com/google/inject/internal/SingleMethodInjector$1 +instanceKlass org/codehaus/plexus/DefaultPlexusContainer$$FastClassByGuice$$143711220 +instanceKlass com/google/inject/internal/SingleMethodInjector$2 +instanceKlass com/google/inject/internal/InjectorImpl$MethodInvoker +instanceKlass com/google/inject/internal/SingleMethodInjector +instanceKlass org/apache/maven/settings/validation/DefaultSettingsValidator$$FastClassByGuice$$143340184 +instanceKlass org/apache/maven/settings/io/DefaultSettingsWriter$$FastClassByGuice$$142146150 +instanceKlass org/apache/maven/settings/io/DefaultSettingsReader$$FastClassByGuice$$141455557 +instanceKlass org/apache/maven/settings/crypto/DefaultSettingsDecrypter$$FastClassByGuice$$140042014 +instanceKlass org/apache/maven/settings/building/DefaultSettingsBuilder$$FastClassByGuice$$138634155 +instanceKlass org/apache/maven/cli/internal/BootstrapCoreExtensionManager$$FastClassByGuice$$138253014 +instanceKlass org/apache/maven/cli/configuration/SettingsXmlConfigurationProcessor$$FastClassByGuice$$137274339 +instanceKlass org/eclipse/aether/transport/http/XChecksumChecksumExtractor$$FastClassByGuice$$136069694 +instanceKlass org/eclipse/aether/transport/http/Nexus2ChecksumExtractor$$FastClassByGuice$$134903340 +instanceKlass org/eclipse/aether/transport/http/HttpTransporterFactory$$FastClassByGuice$$133617679 +instanceKlass org/sonatype/plexus/components/sec/dispatcher/DefaultSecDispatcher$$FastClassByGuice$$132682453 +instanceKlass org/eclipse/aether/transport/file/FileTransporterFactory$$FastClassByGuice$$131680787 +instanceKlass org/apache/maven/toolchain/io/DefaultToolchainsWriter$$FastClassByGuice$$130401778 +instanceKlass org/apache/maven/toolchain/io/DefaultToolchainsReader$$FastClassByGuice$$129966978 +instanceKlass org/apache/maven/toolchain/building/DefaultToolchainsBuilder$$FastClassByGuice$$128474327 +instanceKlass org/apache/maven/plugin/internal/MavenPluginMavenPrerequisiteChecker$$FastClassByGuice$$127120932 +instanceKlass org/apache/maven/plugin/internal/MavenPluginJavaPrerequisiteChecker$$FastClassByGuice$$126148799 +instanceKlass org/apache/maven/plugin/internal/DefaultPluginValidationManager$$FastClassByGuice$$125758936 +instanceKlass org/apache/maven/plugin/DefaultMojosExecutionStrategy$$FastClassByGuice$$124199421 +instanceKlass org/apache/maven/lifecycle/internal/LifecycleDependencyResolver$$FastClassByGuice$$122695196 +instanceKlass org/apache/maven/lifecycle/internal/DefaultProjectArtifactFactory$$FastClassByGuice$$121979087 +instanceKlass org/apache/maven/internal/secdispatcher/SecDispatcherProvider$$FastClassByGuice$$120991927 +instanceKlass org/apache/maven/internal/aether/ResolverLifecycle$$FastClassByGuice$$119700329 +instanceKlass org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory$$FastClassByGuice$$118638829 +instanceKlass org/apache/maven/extension/internal/CoreExportsProvider$$FastClassByGuice$$117638171 +instanceKlass org/apache/maven/execution/DefaultMavenExecutionRequestPopulator$$FastClassByGuice$$117327664 +instanceKlass org/apache/maven/classrealm/DefaultClassRealmManager$$FastClassByGuice$$115405615 +instanceKlass org/apache/maven/DefaultArtifactFilterManager$$FastClassByGuice$$114576034 +instanceKlass org/sonatype/plexus/components/cipher/DefaultPlexusCipher$$FastClassByGuice$$114200832 +instanceKlass org/eclipse/aether/transport/wagon/WagonTransporterFactory$$FastClassByGuice$$113005690 +instanceKlass org/eclipse/aether/internal/transport/wagon/PlexusWagonProvider$$FastClassByGuice$$111486147 +instanceKlass org/eclipse/aether/internal/transport/wagon/PlexusWagonConfigurator$$FastClassByGuice$$110935834 +instanceKlass org/eclipse/aether/connector/basic/BasicRepositoryConnectorFactory$$FastClassByGuice$$109434629 +instanceKlass org/apache/maven/model/validation/DefaultModelValidator$$FastClassByGuice$$108509391 +instanceKlass org/apache/maven/model/superpom/DefaultSuperPomProvider$$FastClassByGuice$$107026841 +instanceKlass org/apache/maven/model/profile/activation/PropertyProfileActivator$$FastClassByGuice$$106928879 +instanceKlass org/apache/maven/model/profile/activation/OperatingSystemProfileActivator$$FastClassByGuice$$104867263 +instanceKlass org/apache/maven/model/profile/activation/JdkVersionProfileActivator$$FastClassByGuice$$104627979 +instanceKlass org/apache/maven/model/profile/activation/FileProfileActivator$$FastClassByGuice$$102844684 +instanceKlass org/apache/maven/model/profile/DefaultProfileSelector$$FastClassByGuice$$102567923 +instanceKlass org/apache/maven/model/profile/DefaultProfileInjector$$FastClassByGuice$$101314528 +instanceKlass org/apache/maven/model/plugin/DefaultReportingConverter$$FastClassByGuice$$100041006 +instanceKlass org/apache/maven/model/plugin/DefaultReportConfigurationExpander$$FastClassByGuice$$98928296 +instanceKlass org/apache/maven/model/plugin/DefaultPluginConfigurationExpander$$FastClassByGuice$$98499611 +instanceKlass org/apache/maven/model/path/ProfileActivationFilePathInterpolator$$FastClassByGuice$$96953780 +instanceKlass org/apache/maven/model/path/DefaultUrlNormalizer$$FastClassByGuice$$95565057 +instanceKlass org/apache/maven/model/path/DefaultPathTranslator$$FastClassByGuice$$94814864 +instanceKlass org/apache/maven/model/path/DefaultModelUrlNormalizer$$FastClassByGuice$$93540804 +instanceKlass org/apache/maven/model/path/DefaultModelPathTranslator$$FastClassByGuice$$93316844 +instanceKlass org/apache/maven/model/normalization/DefaultModelNormalizer$$FastClassByGuice$$92234056 +instanceKlass org/apache/maven/model/management/DefaultPluginManagementInjector$$FastClassByGuice$$90783870 +instanceKlass org/apache/maven/model/management/DefaultDependencyManagementInjector$$FastClassByGuice$$89798444 +instanceKlass org/apache/maven/model/locator/DefaultModelLocator$$FastClassByGuice$$88764524 +instanceKlass org/apache/maven/model/io/DefaultModelWriter$$FastClassByGuice$$87158579 +instanceKlass org/apache/maven/model/io/DefaultModelReader$$FastClassByGuice$$86246917 +instanceKlass org/apache/maven/model/interpolation/StringVisitorModelInterpolator$$FastClassByGuice$$85978151 +instanceKlass org/apache/maven/model/interpolation/DefaultModelVersionProcessor$$FastClassByGuice$$84143214 +instanceKlass org/apache/maven/model/inheritance/DefaultInheritanceAssembler$$FastClassByGuice$$83788257 +instanceKlass org/apache/maven/model/composition/DefaultDependencyManagementImporter$$FastClassByGuice$$82464767 +instanceKlass org/apache/maven/model/building/DefaultModelProcessor$$FastClassByGuice$$80854236 +instanceKlass org/apache/maven/model/building/DefaultModelBuilder$$FastClassByGuice$$80394293 +instanceKlass org/apache/maven/repository/internal/VersionsMetadataGeneratorFactory$$FastClassByGuice$$79480523 +instanceKlass org/apache/maven/repository/internal/SnapshotMetadataGeneratorFactory$$FastClassByGuice$$78368919 +instanceKlass org/apache/maven/repository/internal/PluginsMetadataGeneratorFactory$$FastClassByGuice$$77012843 +instanceKlass org/apache/maven/repository/internal/DefaultVersionResolver$$FastClassByGuice$$75505223 +instanceKlass org/apache/maven/repository/internal/DefaultVersionRangeResolver$$FastClassByGuice$$74528907 +instanceKlass org/apache/maven/repository/internal/DefaultModelCacheFactory$$FastClassByGuice$$73777927 +instanceKlass org/apache/maven/repository/internal/DefaultArtifactDescriptorReader$$FastClassByGuice$$73186144 +instanceKlass org/codehaus/plexus/component/configurator/MapOrientedComponentConfigurator$$FastClassByGuice$$71354030 +instanceKlass org/codehaus/plexus/component/configurator/BasicComponentConfigurator$$FastClassByGuice$$70290507 +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/providers/StaticNameMapperProvider$$FastClassByGuice$$70041479 +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/providers/GAVNameMapperProvider$$FastClassByGuice$$68849026 +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/providers/GAECVNameMapperProvider$$FastClassByGuice$$68051711 +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/providers/FileStaticNameMapperProvider$$FastClassByGuice$$66485285 +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/providers/FileHashingGAVNameMapperProvider$$FastClassByGuice$$65495373 +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/providers/FileHashingGAECVNameMapperProvider$$FastClassByGuice$$64084164 +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/providers/FileGAVNameMapperProvider$$FastClassByGuice$$63556050 +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/providers/FileGAECVNameMapperProvider$$FastClassByGuice$$62013158 +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/providers/DiscriminatingNameMapperProvider$$FastClassByGuice$$61793412 +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapterFactoryImpl$$FastClassByGuice$$60785204 +instanceKlass org/eclipse/aether/internal/impl/synccontext/legacy/DefaultSyncContextFactory$$FastClassByGuice$$59621736 +instanceKlass org/eclipse/aether/internal/impl/synccontext/DefaultSyncContextFactory$$FastClassByGuice$$58208051 +instanceKlass org/eclipse/aether/internal/impl/resolution/TrustedChecksumsArtifactResolverPostProcessor$$FastClassByGuice$$57323624 +instanceKlass org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSource$$FastClassByGuice$$56417328 +instanceKlass org/eclipse/aether/internal/impl/filter/GroupIdRemoteRepositoryFilterSource$$FastClassByGuice$$55539366 +instanceKlass org/eclipse/aether/internal/impl/filter/DefaultRemoteRepositoryFilterManager$$FastClassByGuice$$53888290 +instanceKlass org/eclipse/aether/internal/impl/collect/df/DfDependencyCollector$$FastClassByGuice$$52877812 +instanceKlass org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector$$FastClassByGuice$$52120319 +instanceKlass org/eclipse/aether/internal/impl/collect/DefaultDependencyCollector$$FastClassByGuice$$50427634 +instanceKlass org/eclipse/aether/internal/impl/checksum/TrustedToProvidedChecksumsSourceAdapter$$FastClassByGuice$$49645986 +instanceKlass org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSource$$FastClassByGuice$$49270430 +instanceKlass org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSource$$FastClassByGuice$$47936306 +instanceKlass org/eclipse/aether/internal/impl/checksum/Sha512ChecksumAlgorithmFactory$$FastClassByGuice$$46232889 +instanceKlass org/eclipse/aether/internal/impl/checksum/Sha256ChecksumAlgorithmFactory$$FastClassByGuice$$45816622 +instanceKlass org/eclipse/aether/internal/impl/checksum/Sha1ChecksumAlgorithmFactory$$FastClassByGuice$$44069976 +instanceKlass org/eclipse/aether/internal/impl/checksum/Md5ChecksumAlgorithmFactory$$FastClassByGuice$$43626648 +instanceKlass org/eclipse/aether/internal/impl/checksum/DefaultChecksumAlgorithmFactorySelector$$FastClassByGuice$$42711205 +instanceKlass org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory$$FastClassByGuice$$41582542 +instanceKlass org/eclipse/aether/internal/impl/Maven2RepositoryLayoutFactory$$FastClassByGuice$$40136214 +instanceKlass org/eclipse/aether/internal/impl/LoggerFactoryProvider$$FastClassByGuice$$38864036 +instanceKlass com/google/inject/internal/InjectorImpl$SyntheticProviderBindingImpl$1 +instanceKlass com/google/inject/internal/InjectorImpl$1 +instanceKlass com/google/inject/internal/SingleFieldInjector +instanceKlass org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory$$FastClassByGuice$$38433305 +instanceKlass org/eclipse/aether/internal/impl/DefaultUpdatePolicyAnalyzer$$FastClassByGuice$$36957240 +instanceKlass org/eclipse/aether/internal/impl/DefaultUpdateCheckManager$$FastClassByGuice$$35919783 +instanceKlass org/eclipse/aether/internal/impl/DefaultTransporterProvider$$FastClassByGuice$$35444762 +instanceKlass org/eclipse/aether/internal/impl/DefaultTrackingFileManager$$FastClassByGuice$$33773561 +instanceKlass org/eclipse/aether/internal/impl/DefaultRepositorySystemLifecycle$$FastClassByGuice$$33219141 +instanceKlass org/eclipse/aether/internal/impl/DefaultRepositorySystem$$FastClassByGuice$$32323959 +instanceKlass org/eclipse/aether/internal/impl/DefaultRepositoryLayoutProvider$$FastClassByGuice$$30472958 +instanceKlass org/eclipse/aether/internal/impl/DefaultRepositoryEventDispatcher$$FastClassByGuice$$29645919 +instanceKlass org/eclipse/aether/internal/impl/DefaultRepositoryConnectorProvider$$FastClassByGuice$$28376428 +instanceKlass org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManager$$FastClassByGuice$$27933010 +instanceKlass org/eclipse/aether/internal/impl/DefaultOfflineController$$FastClassByGuice$$26680975 +instanceKlass org/eclipse/aether/internal/impl/DefaultMetadataResolver$$FastClassByGuice$$26176507 +instanceKlass org/eclipse/aether/internal/impl/DefaultLocalRepositoryProvider$$FastClassByGuice$$24263809 +instanceKlass org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactory$$FastClassByGuice$$23656276 +instanceKlass org/eclipse/aether/internal/impl/DefaultLocalPathComposer$$FastClassByGuice$$22085743 +instanceKlass org/eclipse/aether/internal/impl/DefaultInstaller$$FastClassByGuice$$21091543 +instanceKlass org/eclipse/aether/internal/impl/DefaultFileProcessor$$FastClassByGuice$$20422409 +instanceKlass org/eclipse/aether/internal/impl/DefaultDeployer$$FastClassByGuice$$19158044 +instanceKlass org/eclipse/aether/internal/impl/DefaultChecksumPolicyProvider$$FastClassByGuice$$18216477 +instanceKlass org/eclipse/aether/internal/impl/DefaultArtifactResolver$$FastClassByGuice$$17498758 +instanceKlass com/google/inject/internal/SingleParameterInjector +instanceKlass org/eclipse/aether/named/providers/NoopNamedLockFactory$$FastClassByGuice$$16650213 +instanceKlass org/eclipse/aether/named/providers/LocalSemaphoreNamedLockFactory$$FastClassByGuice$$15449308 +instanceKlass org/eclipse/aether/named/providers/LocalReadWriteLockNamedLockFactory$$FastClassByGuice$$13767150 +instanceKlass org/eclipse/aether/named/providers/FileLockNamedLockFactory$$FastClassByGuice$$12776823 +instanceKlass org/apache/maven/lifecycle/internal/LifecycleDebugLogger$$FastClassByGuice$$12041907 +instanceKlass org/apache/maven/lifecycle/internal/MojoDescriptorCreator$$FastClassByGuice$$10792174 +instanceKlass org/apache/maven/lifecycle/internal/MojoExecutor$$FastClassByGuice$$10023925 +instanceKlass org/apache/maven/eventspy/internal/EventSpyDispatcher$$FastClassByGuice$$8798885 +instanceKlass org/eclipse/sisu/PreDestroy +instanceKlass org/eclipse/sisu/PostConstruct +instanceKlass com/google/inject/internal/DefaultConstructionProxyFactory$ReflectiveProxy +instanceKlass org/apache/maven/lifecycle/internal/BuildListCalculator$$FastClassByGuice$$8034138 +instanceKlass org/apache/maven/lifecycle/internal/LifecyclePluginResolver$$FastClassByGuice$$7070023 +instanceKlass org/apache/maven/lifecycle/DefaultLifecycles$$FastClassByGuice$$6216404 +instanceKlass org/apache/maven/lifecycle/Lifecycle$$FastClassByGuice$$4634805 +instanceKlass org/eclipse/sisu/plexus/PlexusConfigurations$ConfigurationProvider +instanceKlass org/apache/maven/lifecycle/internal/builder/BuilderCommon$$FastClassByGuice$$3800682 +instanceKlass org/apache/maven/lifecycle/internal/LifecycleStarter$$FastClassByGuice$$2859324 +instanceKlass org/apache/maven/lifecycle/internal/LifecycleModuleBuilder$$FastClassByGuice$$2043517 +instanceKlass org/eclipse/sisu/bean/BeanPropertySetter +instanceKlass com/google/inject/internal/ProxyFactory +instanceKlass com/google/common/collect/TransformedIterator +instanceKlass com/google/inject/spi/InterceptorBinding +instanceKlass com/google/inject/internal/MethodAspect +instanceKlass com/google/inject/internal/MembersInjectorImpl +instanceKlass org/eclipse/sisu/bean/BeanInjector +instanceKlass org/eclipse/sisu/plexus/PlexusLifecycleManager$2 +instanceKlass org/eclipse/sisu/bean/PropertyBinder$1 +instanceKlass org/eclipse/sisu/plexus/ProvidedPropertyBinding +instanceKlass org/eclipse/sisu/plexus/PlexusRequirements$AbstractRequirementProvider +instanceKlass org/eclipse/sisu/bean/BeanPropertyField +instanceKlass org/eclipse/sisu/bean/DeclaredMembers$MemberIterator +instanceKlass org/eclipse/sisu/bean/BeanPropertyIterator +instanceKlass org/eclipse/sisu/bean/DeclaredMembers +instanceKlass org/eclipse/sisu/bean/IgnoreSetters +instanceKlass org/eclipse/sisu/bean/BeanProperties +instanceKlass org/eclipse/sisu/plexus/PlexusRequirements +instanceKlass org/eclipse/sisu/plexus/PlexusConfigurations +instanceKlass org/eclipse/sisu/plexus/PlexusPropertyBinder +instanceKlass org/eclipse/sisu/bean/BeanLifecycle +instanceKlass com/google/inject/internal/EncounterImpl +instanceKlass org/apache/maven/execution/scope/internal/MojoExecutionScope$2 +instanceKlass com/google/inject/internal/ProviderInternalFactory +instanceKlass com/google/inject/internal/InternalProviderInstanceBindingImpl$Factory +instanceKlass com/google/inject/internal/FactoryProxy +instanceKlass com/google/inject/internal/InternalFactoryToProviderAdapter +instanceKlass com/google/inject/internal/ConstructionContext +instanceKlass com/google/inject/internal/SingletonScope$1 +instanceKlass com/google/inject/internal/ProviderToInternalFactoryAdapter +instanceKlass com/google/inject/internal/CycleDetectingLock$CycleDetectingLockFactory$ReentrantCycleDetectingLock +instanceKlass com/google/inject/internal/Initializer$InjectableReference +instanceKlass com/google/inject/internal/ProvisionListenerStackCallback +instanceKlass com/google/common/cache/LocalCache$AbstractReferenceEntry +instanceKlass com/google/inject/internal/ProvisionListenerCallbackStore$KeyBinding +instanceKlass com/google/inject/internal/util/Classes +instanceKlass com/google/inject/spi/ExposedBinding +instanceKlass com/google/inject/internal/CreationListener +instanceKlass com/google/inject/internal/InjectorShell$LoggerFactory +instanceKlass com/google/inject/internal/InjectorShell$InjectorFactory +instanceKlass com/google/inject/internal/Initializables$1 +instanceKlass com/google/inject/internal/Initializables +instanceKlass com/google/inject/internal/ConstantFactory +instanceKlass com/google/inject/internal/InjectorShell +instanceKlass com/google/inject/internal/ProvisionListenerCallbackStore +instanceKlass com/google/inject/internal/SingleMemberInjector +instanceKlass com/google/inject/spi/TypeEncounter +instanceKlass com/google/inject/internal/MembersInjectorStore +instanceKlass com/google/inject/internal/TypeConverterBindingProcessor$4 +instanceKlass com/google/inject/internal/TypeConverterBindingProcessor$2 +instanceKlass com/google/inject/internal/TypeConverterBindingProcessor$1 +instanceKlass com/google/inject/internal/TypeConverterBindingProcessor$5 +instanceKlass com/google/inject/internal/FailableCache +instanceKlass com/google/inject/internal/ConstructorInjectorStore +instanceKlass com/google/inject/internal/DeferredLookups +instanceKlass com/google/inject/spi/ConvertedConstantBinding +instanceKlass com/google/inject/spi/ProviderBinding +instanceKlass com/google/inject/internal/InjectorImpl +instanceKlass com/google/inject/internal/Lookups +instanceKlass com/google/inject/internal/InjectorImpl$InjectorOptions +instanceKlass com/google/inject/internal/ProvisionListenerStackCallback$ProvisionCallback +instanceKlass com/google/inject/internal/ConstructorInjector +instanceKlass com/google/inject/internal/DefaultConstructionProxyFactory$FastClassProxy +instanceKlass com/google/inject/internal/aop/ImmutableStringTrie +instanceKlass java/util/function/ToIntFunction +instanceKlass jdk/internal/reflect/UnsafeFieldAccessorFactory +instanceKlass org/apache/maven/plugin/internal/DefaultPluginDependenciesResolver$$FastClassByGuice$$74418 +instanceKlass com/google/inject/internal/aop/ChildClassDefiner$ChildLoaderCacheHolder +instanceKlass com/google/inject/internal/aop/BytecodeTasks +instanceKlass org/objectweb/asm/Handle +instanceKlass org/objectweb/asm/Label +instanceKlass org/objectweb/asm/Type +instanceKlass com/google/inject/internal/aop/AbstractGlueGenerator +instanceKlass com/google/inject/internal/aop/UnsafeClassDefiner +instanceKlass com/google/inject/internal/aop/ChildClassDefiner +instanceKlass com/google/inject/internal/aop/ClassDefining$ClassDefinerHolder +instanceKlass com/google/inject/internal/aop/ClassDefiner +instanceKlass com/google/inject/internal/aop/ClassDefining +instanceKlass com/google/inject/internal/BytecodeGen$EnhancerBuilder +instanceKlass com/google/inject/internal/aop/ClassBuilding +instanceKlass com/google/common/collect/MapMakerInternalMap$StrongValueEntry +instanceKlass com/google/common/collect/MapMakerInternalMap$WeakKeyStrongValueEntry$Helper +instanceKlass com/google/common/collect/MapMakerInternalMap$InternalEntry +instanceKlass com/google/common/collect/MapMakerInternalMap$1 +instanceKlass com/google/common/collect/MapMakerInternalMap$InternalEntryHelper +instanceKlass com/google/common/collect/MapMakerInternalMap$WeakValueReference +instanceKlass com/google/common/collect/MapMaker +instanceKlass com/google/inject/internal/BytecodeGen +instanceKlass com/google/inject/internal/ConstructionProxy +instanceKlass com/google/inject/internal/DefaultConstructionProxyFactory +instanceKlass com/google/inject/internal/ConstructionProxyFactory +instanceKlass com/google/inject/internal/ConstructorBindingImpl$Factory +instanceKlass org/eclipse/sisu/inject/TypeArguments$Implicit +instanceKlass org/eclipse/sisu/wire/PlaceholderBeanProvider +instanceKlass org/eclipse/sisu/wire/BeanProviders$3 +instanceKlass org/sonatype/inject/BeanEntry +instanceKlass org/eclipse/sisu/BeanEntry +instanceKlass org/eclipse/sisu/wire/BeanProviders$4 +instanceKlass org/eclipse/sisu/wire/BeanProviders$6 +instanceKlass org/eclipse/sisu/wire/BeanProviders$7 +instanceKlass org/eclipse/sisu/wire/BeanProviders$1 +instanceKlass com/google/inject/spi/ProviderLookup$1 +instanceKlass com/google/inject/spi/ProviderWithDependencies +instanceKlass com/google/inject/spi/ProviderLookup +instanceKlass org/eclipse/sisu/wire/BeanProviders +instanceKlass org/eclipse/sisu/inject/HiddenSource +instanceKlass org/eclipse/sisu/wire/LocatorWiring +instanceKlass com/google/inject/ProvidedBy +instanceKlass com/google/inject/ImplementedBy +instanceKlass org/apache/maven/settings/crypto/SettingsDecryptionResult +instanceKlass org/apache/maven/settings/building/DefaultSettingsProblemCollector +instanceKlass org/apache/maven/settings/merge/MavenSettingsMerger +instanceKlass org/apache/maven/settings/building/SettingsBuildingResult +instanceKlass org/apache/maven/settings/building/SettingsProblemCollector +instanceKlass org/apache/maven/cli/internal/extension/model/CoreExtension +instanceKlass org/sonatype/plexus/components/sec/dispatcher/model/SettingsSecurity +instanceKlass org/apache/maven/building/ProblemCollector +instanceKlass org/apache/maven/toolchain/merge/MavenToolchainMerger +instanceKlass org/codehaus/plexus/interpolation/InterpolationPostProcessor +instanceKlass org/apache/maven/toolchain/building/ToolchainsBuildingResult +instanceKlass org/apache/maven/plugin/internal/DefaultPluginValidationManager$PluginValidationIssues +instanceKlass org/sonatype/plexus/components/sec/dispatcher/PasswordDecryptor +instanceKlass org/eclipse/aether/repository/AuthenticationSelector +instanceKlass org/eclipse/aether/repository/ProxySelector +instanceKlass org/eclipse/aether/repository/MirrorSelector +instanceKlass org/eclipse/aether/resolution/ResolutionErrorPolicy +instanceKlass org/apache/maven/classrealm/ClassRealmManagerDelegate +instanceKlass org/apache/maven/classrealm/ClassRealmConstituent +instanceKlass org/apache/maven/classrealm/ClassRealmRequest +instanceKlass org/eclipse/aether/repository/WorkspaceRepository +instanceKlass org/apache/maven/ArtifactFilterManagerDelegate +instanceKlass org/sonatype/plexus/components/cipher/PBECipher +instanceKlass org/apache/maven/model/validation/DefaultModelValidator$1ActivationFrame +instanceKlass org/apache/maven/model/profile/activation/JdkVersionProfileActivator$RangeValue +instanceKlass org/apache/maven/model/InputLocation +instanceKlass org/apache/maven/model/InputSource +instanceKlass org/apache/maven/model/interpolation/StringVisitorModelInterpolator$InnerInterpolator +instanceKlass org/apache/maven/model/ActivationFile +instanceKlass org/apache/maven/model/ActivationOS +instanceKlass org/apache/maven/model/ActivationProperty +instanceKlass org/codehaus/plexus/interpolation/RegexBasedInterpolator +instanceKlass org/apache/maven/model/Activation +instanceKlass org/apache/maven/model/building/ModelBuildingEventCatapult +instanceKlass org/apache/maven/model/building/ModelData +instanceKlass org/apache/maven/model/profile/DefaultProfileActivationContext +instanceKlass org/apache/maven/model/building/DefaultModelProblemCollector +instanceKlass org/apache/maven/model/building/ModelCacheTag +instanceKlass org/apache/maven/model/building/ModelBuildingEvent +instanceKlass org/apache/maven/model/profile/ProfileActivationContext +instanceKlass org/apache/maven/model/building/ModelProblemCollectorExt +instanceKlass org/eclipse/aether/impl/MetadataGenerator +instanceKlass org/apache/maven/model/Relocation +instanceKlass org/apache/maven/repository/internal/ArtifactDescriptorReaderDelegate +instanceKlass org/codehaus/classworlds/ClassRealm +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter +instanceKlass org/eclipse/sisu/Nullable +instanceKlass org/eclipse/aether/spi/log/Logger +instanceKlass org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSource$Node +instanceKlass org/eclipse/aether/spi/connector/filter/RemoteRepositoryFilter$Result +instanceKlass org/eclipse/aether/spi/connector/filter/RemoteRepositoryFilter +instanceKlass org/eclipse/aether/collection/DependencyTraverser +instanceKlass org/eclipse/aether/collection/DependencyManager +instanceKlass org/eclipse/aether/internal/impl/collect/df/DfDependencyCollector$Args +instanceKlass org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector$DescriptorResolutionResult +instanceKlass org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector$Args +instanceKlass org/eclipse/aether/internal/impl/collect/bf/DependencyProcessingContext +instanceKlass org/eclipse/aether/internal/impl/collect/bf/DependencyResolutionSkipper +instanceKlass org/eclipse/aether/internal/impl/collect/DependencyCollectorDelegate$Results +instanceKlass org/eclipse/aether/internal/impl/collect/DefaultDependencyCollectionContext +instanceKlass org/eclipse/aether/collection/DependencyCollectionContext +instanceKlass org/eclipse/aether/internal/impl/collect/DefaultVersionFilterContext +instanceKlass org/eclipse/aether/collection/VersionFilter +instanceKlass org/eclipse/aether/internal/impl/collect/DataPool +instanceKlass org/eclipse/aether/graph/DefaultDependencyNode +instanceKlass org/eclipse/aether/version/Version +instanceKlass org/eclipse/aether/internal/impl/collect/PremanagedDependency +instanceKlass org/eclipse/aether/graph/Dependency +instanceKlass org/eclipse/aether/collection/VersionFilter$VersionFilterContext +instanceKlass org/eclipse/aether/collection/DependencyGraphTransformationContext +instanceKlass org/eclipse/aether/spi/connector/Transfer +instanceKlass org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSource$SummaryFileWriter +instanceKlass org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSource$SparseDirectoryWriter +instanceKlass org/eclipse/aether/spi/checksums/TrustedChecksumsSource$Writer +instanceKlass org/eclipse/aether/spi/connector/checksum/ChecksumAlgorithm +instanceKlass com/google/inject/util/Types +instanceKlass org/eclipse/aether/impl/UpdateCheck +instanceKlass org/eclipse/aether/spi/connector/transport/Transporter +instanceKlass java/nio/channels/FileLock +instanceKlass org/eclipse/aether/resolution/DependencyResult +instanceKlass org/eclipse/aether/resolution/DependencyRequest +instanceKlass org/eclipse/aether/collection/CollectResult +instanceKlass org/eclipse/aether/collection/CollectRequest +instanceKlass org/eclipse/aether/resolution/ArtifactDescriptorResult +instanceKlass org/eclipse/aether/resolution/ArtifactDescriptorRequest +instanceKlass org/eclipse/aether/resolution/VersionRangeResult +instanceKlass org/eclipse/aether/resolution/VersionRangeRequest +instanceKlass org/eclipse/aether/resolution/VersionRequest +instanceKlass java/util/concurrent/atomic/AtomicBoolean +instanceKlass org/eclipse/aether/spi/connector/layout/RepositoryLayout +instanceKlass org/eclipse/aether/RepositoryEvent +instanceKlass org/eclipse/aether/repository/LocalRepository +instanceKlass org/eclipse/aether/internal/impl/LocalPathPrefixComposer +instanceKlass org/eclipse/aether/transform/FileTransformer +instanceKlass org/eclipse/aether/repository/LocalRepositoryManager +instanceKlass org/eclipse/aether/installation/InstallResult +instanceKlass org/eclipse/aether/installation/InstallRequest +instanceKlass org/eclipse/aether/spi/io/FileProcessor$ProgressListener +instanceKlass org/eclipse/aether/internal/impl/DefaultDeployer$EventCatapult +instanceKlass org/eclipse/aether/spi/connector/RepositoryConnector +instanceKlass org/eclipse/aether/repository/RepositoryPolicy +instanceKlass org/eclipse/aether/deployment/DeployResult +instanceKlass org/eclipse/aether/deployment/DeployRequest +instanceKlass org/eclipse/aether/transfer/TransferResource +instanceKlass org/eclipse/aether/spi/connector/checksum/ChecksumPolicy +instanceKlass sun/reflect/generics/tree/MethodTypeSignature +instanceKlass sun/reflect/generics/tree/VoidDescriptor +instanceKlass org/eclipse/aether/resolution/ArtifactRequest +instanceKlass org/eclipse/aether/spi/locator/ServiceLocator +instanceKlass org/eclipse/aether/repository/RemoteRepository +instanceKlass org/eclipse/aether/internal/impl/DefaultArtifactResolver$ResolutionGroup +instanceKlass org/eclipse/aether/resolution/VersionResult +instanceKlass org/eclipse/aether/repository/LocalArtifactResult +instanceKlass org/eclipse/aether/SyncContext +instanceKlass org/eclipse/aether/named/support/AdaptedSemaphoreNamedLock$AdaptedSemaphore +instanceKlass org/eclipse/aether/named/support/NamedLockFactorySupport$NamedLockHolder +instanceKlass org/eclipse/aether/named/support/NamedLockSupport +instanceKlass org/eclipse/aether/named/NamedLock +instanceKlass org/apache/maven/repository/metadata/DefaultGraphConflictResolutionPolicy +instanceKlass org/apache/maven/artifact/repository/metadata/io/DefaultMetadataReader +instanceKlass org/eclipse/aether/DefaultRepositorySystemSession +instanceKlass org/apache/maven/execution/MavenExecutionResult +instanceKlass org/apache/maven/DefaultMaven +instanceKlass org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculator +instanceKlass org/apache/maven/repository/ArtifactTransferListener +instanceKlass org/apache/maven/repository/legacy/LegacyRepositorySystem +instanceKlass org/apache/maven/project/artifact/ProjectArtifactsCache$CacheRecord +instanceKlass org/apache/maven/project/artifact/ProjectArtifactsCache$Key +instanceKlass org/apache/maven/project/artifact/DefaultProjectArtifactsCache +instanceKlass org/apache/maven/project/DefaultProjectBuildingHelper +instanceKlass org/apache/maven/toolchain/DefaultToolchainsBuilder +instanceKlass org/apache/maven/plugin/ExtensionRealmCache$Key +instanceKlass org/apache/maven/plugin/DefaultExtensionRealmCache +instanceKlass org/apache/maven/artifact/resolver/DefaultResolutionErrorHandler +instanceKlass org/apache/maven/DefaultProjectDependenciesResolver +instanceKlass org/apache/maven/artifact/factory/DefaultArtifactFactory +instanceKlass org/apache/maven/settings/crypto/SettingsDecryptionRequest +instanceKlass org/apache/maven/execution/ExecutionEvent +instanceKlass org/apache/maven/lifecycle/internal/DefaultExecutionEventCatapult +instanceKlass org/apache/maven/project/DefaultDependencyResolutionResult +instanceKlass org/apache/maven/project/DefaultProjectDependenciesResolver +instanceKlass org/apache/maven/lifecycle/internal/PhaseRecorder +instanceKlass org/apache/maven/lifecycle/internal/DependencyContext +instanceKlass org/apache/maven/lifecycle/internal/ProjectIndex +instanceKlass org/apache/maven/plugin/MojoExecutionRunner +instanceKlass org/apache/maven/repository/legacy/resolver/conflict/OldestConflictResolver +instanceKlass org/eclipse/aether/util/graph/visitor/AbstractDepthFirstNodeListGenerator +instanceKlass org/apache/maven/plugin/ExtensionRealmCache$CacheRecord +instanceKlass org/apache/maven/plugin/descriptor/PluginDescriptorBuilder +instanceKlass org/codehaus/plexus/component/configurator/ConfigurationListener +instanceKlass org/apache/maven/plugin/logging/Log +instanceKlass org/apache/maven/plugin/internal/DefaultMavenPluginManager +instanceKlass org/apache/maven/repository/metadata/ClasspathContainer +instanceKlass org/apache/maven/repository/metadata/DefaultClasspathTransformation +instanceKlass org/apache/maven/repository/legacy/resolver/conflict/DefaultConflictResolverFactory +instanceKlass org/apache/maven/plugin/internal/DefaultPluginManager +instanceKlass org/eclipse/aether/RepositoryListener +instanceKlass org/apache/maven/model/merge/ModelMerger +instanceKlass org/apache/maven/model/plugin/DefaultLifecycleBindingsInjector +instanceKlass org/apache/maven/repository/metadata/MetadataGraphEdge +instanceKlass org/apache/maven/repository/metadata/MetadataGraph +instanceKlass org/apache/maven/repository/metadata/MetadataGraphVertex +instanceKlass org/apache/maven/repository/metadata/DefaultGraphConflictResolver +instanceKlass org/apache/maven/artifact/repository/layout/DefaultRepositoryLayout +instanceKlass org/apache/maven/exception/ExceptionSummary +instanceKlass org/apache/maven/exception/DefaultExceptionHandler +instanceKlass org/apache/maven/wagon/observers/ChecksumObserver +instanceKlass org/apache/maven/repository/legacy/DefaultWagonManager +instanceKlass org/apache/maven/model/RepositoryPolicy +instanceKlass org/apache/maven/settings/RepositoryPolicy +instanceKlass org/apache/maven/artifact/repository/Authentication +instanceKlass org/apache/maven/settings/RepositoryBase +instanceKlass org/apache/maven/repository/Proxy +instanceKlass org/apache/maven/project/ReactorModelPool +instanceKlass org/apache/maven/model/building/ModelBuildingResult +instanceKlass org/apache/maven/project/DependencyResolutionResult +instanceKlass org/apache/maven/project/DefaultProjectBuilder$InternalConfig +instanceKlass org/apache/maven/model/resolution/ModelResolver +instanceKlass org/apache/maven/project/DependencyResolutionRequest +instanceKlass org/apache/maven/project/ProjectBuildingResult +instanceKlass org/apache/maven/model/building/ModelBuildingListener +instanceKlass org/apache/maven/model/building/ModelCache +instanceKlass org/apache/maven/project/DefaultProjectBuilder +instanceKlass org/apache/maven/lifecycle/internal/DefaultLifecyclePluginAnalyzer$GoalSpec +instanceKlass org/apache/maven/lifecycle/mapping/LifecyclePhase +instanceKlass org/apache/maven/lifecycle/internal/DefaultLifecyclePluginAnalyzer +instanceKlass org/apache/maven/artifact/resolver/DefaultArtifactResolver +instanceKlass org/apache/maven/project/validation/ModelValidationResult +instanceKlass org/apache/maven/model/building/ModelBuildingRequest +instanceKlass org/apache/maven/model/building/ModelProblemCollector +instanceKlass org/apache/maven/project/validation/DefaultModelValidator +instanceKlass org/apache/maven/repository/legacy/repository/DefaultArtifactRepositoryFactory +instanceKlass org/apache/maven/lifecycle/internal/DefaultLifecycleMappingDelegate +instanceKlass org/apache/maven/rtinfo/internal/DefaultRuntimeInformation +instanceKlass org/apache/maven/lifecycle/internal/ProjectSegment +instanceKlass org/apache/maven/lifecycle/internal/builder/multithreaded/ThreadOutputMuxer +instanceKlass org/apache/maven/lifecycle/internal/builder/multithreaded/ConcurrencyDependencyGraph +instanceKlass java/util/concurrent/CompletionService +instanceKlass org/apache/maven/lifecycle/internal/builder/multithreaded/MultiThreadedBuilder +instanceKlass org/apache/maven/model/building/ModelProblem +instanceKlass org/apache/maven/project/artifact/MavenMetadataSource$ProjectRelocation +instanceKlass org/apache/maven/model/Dependency +instanceKlass org/apache/maven/project/artifact/MavenMetadataSource +instanceKlass org/apache/maven/artifact/repository/metadata/Metadata +instanceKlass org/apache/maven/plugin/version/internal/DefaultPluginVersionResolver$Versions +instanceKlass org/apache/maven/plugin/version/internal/DefaultPluginVersionResult +instanceKlass org/apache/maven/plugin/version/internal/DefaultPluginVersionResolver$Key +instanceKlass org/apache/maven/plugin/version/PluginVersionResult +instanceKlass org/eclipse/aether/version/VersionScheme +instanceKlass org/apache/maven/plugin/version/internal/DefaultPluginVersionResolver +instanceKlass org/apache/maven/repository/DefaultMirrorSelector +instanceKlass org/apache/maven/repository/legacy/resolver/conflict/FarthestConflictResolver +instanceKlass org/apache/http/config/Registry +instanceKlass org/apache/http/impl/conn/PoolingHttpClientConnectionManager +instanceKlass org/apache/http/pool/ConnPoolControl +instanceKlass org/apache/http/client/methods/CloseableHttpResponse +instanceKlass org/apache/http/HttpResponse +instanceKlass org/apache/maven/wagon/shared/http/BasicAuthScope +instanceKlass org/apache/maven/wagon/shared/http/HttpConfiguration +instanceKlass org/apache/http/impl/client/CloseableHttpClient +instanceKlass org/apache/http/client/HttpClient +instanceKlass org/apache/http/Header +instanceKlass org/apache/http/NameValuePair +instanceKlass org/apache/http/auth/Credentials +instanceKlass org/apache/http/client/AuthCache +instanceKlass org/apache/http/client/CredentialsProvider +instanceKlass org/apache/http/client/RedirectStrategy +instanceKlass org/apache/http/config/Lookup +instanceKlass org/apache/http/client/ServiceUnavailableRetryStrategy +instanceKlass org/apache/http/conn/ssl/TrustStrategy +instanceKlass org/apache/http/ssl/TrustStrategy +instanceKlass org/apache/http/client/HttpRequestRetryHandler +instanceKlass org/apache/http/protocol/HttpContext +instanceKlass org/apache/http/client/methods/HttpUriRequest +instanceKlass org/apache/http/HttpRequest +instanceKlass org/apache/http/HttpMessage +instanceKlass org/apache/http/auth/AuthScheme +instanceKlass org/apache/http/HttpEntity +instanceKlass org/apache/http/conn/HttpClientConnectionManager +instanceKlass org/apache/maven/wagon/OutputData +instanceKlass org/apache/maven/wagon/InputData +instanceKlass java/util/EventObject +instanceKlass org/apache/maven/wagon/events/SessionListener +instanceKlass org/apache/maven/wagon/resource/Resource +instanceKlass org/apache/maven/wagon/repository/RepositoryPermissions +instanceKlass org/apache/maven/wagon/proxy/ProxyInfo +instanceKlass org/apache/maven/wagon/authentication/AuthenticationInfo +instanceKlass org/apache/maven/wagon/events/TransferEventSupport +instanceKlass org/apache/maven/wagon/events/SessionEventSupport +instanceKlass org/apache/maven/wagon/repository/Repository +instanceKlass org/apache/maven/wagon/proxy/ProxyInfoProvider +instanceKlass org/apache/maven/wagon/AbstractWagon +instanceKlass org/apache/maven/wagon/StreamingWagon +instanceKlass org/apache/maven/plugin/DefaultBuildPluginManager +instanceKlass org/apache/maven/lifecycle/internal/ReactorBuildStatus +instanceKlass org/apache/maven/lifecycle/internal/builder/singlethreaded/SingleThreadedBuilder +instanceKlass org/apache/maven/configuration/BeanConfigurationRequest +instanceKlass org/codehaus/plexus/component/configurator/expression/ExpressionEvaluator +instanceKlass org/codehaus/plexus/configuration/PlexusConfiguration +instanceKlass org/codehaus/plexus/component/configurator/converters/lookup/ConverterLookup +instanceKlass org/apache/maven/configuration/internal/DefaultBeanConfigurator +instanceKlass org/codehaus/plexus/component/repository/ComponentSetDescriptor +instanceKlass org/apache/maven/plugin/PluginDescriptorCache$PluginDescriptorSupplier +instanceKlass org/apache/maven/plugin/PluginDescriptorCache$Key +instanceKlass org/apache/maven/plugin/DefaultPluginDescriptorCache +instanceKlass org/apache/maven/lifecycle/mapping/DefaultLifecycleMapping +instanceKlass org/apache/maven/model/building/Result +instanceKlass org/apache/maven/execution/ProjectDependencyGraph +instanceKlass org/apache/maven/graph/DefaultGraphBuilder +instanceKlass org/apache/maven/artifact/repository/layout/FlatRepositoryLayout +instanceKlass org/apache/maven/lifecycle/internal/ProjectBuildList +instanceKlass org/apache/maven/repository/legacy/resolver/conflict/NewestConflictResolver +instanceKlass org/apache/maven/wagon/events/TransferListener +instanceKlass org/apache/maven/profiles/ProfileManager +instanceKlass org/apache/maven/model/building/ModelSource +instanceKlass org/apache/maven/project/ProjectBuilderConfiguration +instanceKlass org/apache/maven/project/DefaultMavenProjectBuilder +instanceKlass org/apache/maven/project/ProjectRealmCache$CacheRecord +instanceKlass org/apache/maven/project/ProjectRealmCache$Key +instanceKlass org/apache/maven/project/DefaultProjectRealmCache +instanceKlass org/apache/maven/model/RepositoryBase +instanceKlass org/apache/maven/model/Reporting +instanceKlass org/apache/maven/project/inheritance/DefaultModelInheritanceAssembler +instanceKlass org/apache/maven/plugin/version/PluginVersionRequest +instanceKlass org/apache/maven/model/ModelBase +instanceKlass org/apache/maven/project/path/DefaultPathTranslator +instanceKlass org/apache/maven/artifact/repository/DefaultArtifactRepositoryFactory +instanceKlass org/apache/maven/plugin/PluginArtifactsCache$CacheRecord +instanceKlass org/apache/maven/plugin/PluginArtifactsCache$Key +instanceKlass org/apache/maven/plugin/DefaultPluginArtifactsCache +instanceKlass org/apache/maven/toolchain/DefaultToolchainManager +instanceKlass org/apache/maven/artifact/handler/manager/DefaultArtifactHandlerManager +instanceKlass org/apache/maven/artifact/versioning/ArtifactVersion +instanceKlass org/apache/maven/execution/DefaultRuntimeInformation +instanceKlass org/apache/maven/lifecycle/DefaultLifecycleExecutor +instanceKlass org/apache/maven/artifact/versioning/VersionRange +instanceKlass org/apache/maven/artifact/resolver/ArtifactResolutionResult +instanceKlass org/apache/maven/artifact/resolver/ArtifactResolutionRequest +instanceKlass org/apache/maven/artifact/resolver/filter/ArtifactFilter +instanceKlass org/apache/maven/repository/legacy/metadata/MetadataResolutionRequest +instanceKlass org/apache/maven/repository/legacy/resolver/DefaultLegacyArtifactCollector +instanceKlass org/apache/maven/profiles/ProfilesRoot +instanceKlass org/apache/maven/repository/legacy/resolver/transform/DefaultArtifactTransformationManager +instanceKlass org/apache/maven/plugin/PluginRealmCache$CacheRecord +instanceKlass org/apache/maven/plugin/PluginRealmCache$PluginRealmSupplier +instanceKlass org/apache/maven/plugin/PluginRealmCache$Key +instanceKlass org/apache/maven/plugin/DefaultPluginRealmCache +instanceKlass org/apache/maven/plugin/internal/DefaultLegacySupport +instanceKlass org/eclipse/aether/RequestTrace +instanceKlass org/apache/maven/model/PluginContainer +instanceKlass org/apache/maven/plugin/prefix/PluginPrefixRequest +instanceKlass org/eclipse/aether/repository/ArtifactRepository +instanceKlass org/eclipse/aether/metadata/Metadata +instanceKlass org/apache/maven/plugin/prefix/PluginPrefixResult +instanceKlass org/apache/maven/plugin/prefix/internal/DefaultPluginPrefixResolver +instanceKlass org/apache/maven/settings/TrackableBase +instanceKlass org/apache/maven/settings/building/SettingsBuildingRequest +instanceKlass org/eclipse/aether/resolution/ArtifactResult +instanceKlass org/eclipse/aether/graph/DependencyNode +instanceKlass org/eclipse/aether/graph/DependencyVisitor +instanceKlass org/eclipse/aether/collection/DependencySelector +instanceKlass org/eclipse/aether/graph/DependencyFilter +instanceKlass org/eclipse/aether/artifact/Artifact +instanceKlass org/eclipse/aether/RepositorySystemSession +instanceKlass org/eclipse/aether/resolution/ArtifactDescriptorPolicy +instanceKlass org/apache/maven/plugin/internal/DefaultPluginDependenciesResolver +instanceKlass org/apache/maven/lifecycle/internal/DefaultLifecycleTaskSegmentCalculator +instanceKlass org/apache/maven/lifecycle/MavenExecutionPlan +instanceKlass org/apache/maven/plugin/descriptor/Parameter +instanceKlass org/apache/maven/model/ConfigurationContainer +instanceKlass org/apache/maven/model/InputLocationTracker +instanceKlass org/apache/maven/lifecycle/internal/DefaultMojoExecutionConfigurator +instanceKlass org/apache/maven/artifact/repository/metadata/Versioning +instanceKlass org/apache/maven/artifact/repository/metadata/RepositoryMetadata +instanceKlass org/apache/maven/artifact/metadata/ArtifactMetadata +instanceKlass org/apache/maven/repository/legacy/metadata/ArtifactMetadata +instanceKlass org/apache/maven/artifact/repository/RepositoryRequest +instanceKlass org/codehaus/plexus/logging/AbstractLogEnabled +instanceKlass org/apache/maven/artifact/handler/DefaultArtifactHandler +instanceKlass org/objectweb/asm/Handler +instanceKlass org/objectweb/asm/Frame +instanceKlass org/objectweb/asm/ByteVector +instanceKlass org/objectweb/asm/Symbol +instanceKlass org/objectweb/asm/SymbolTable +instanceKlass org/objectweb/asm/FieldVisitor +instanceKlass org/objectweb/asm/MethodVisitor +instanceKlass org/objectweb/asm/ModuleVisitor +instanceKlass org/objectweb/asm/RecordComponentVisitor +instanceKlass org/apache/maven/artifact/repository/ArtifactRepositoryPolicy +instanceKlass org/apache/maven/project/artifact/DefaultMavenMetadataCache$CacheKey +instanceKlass org/apache/maven/repository/legacy/metadata/ResolutionGroup +instanceKlass org/apache/maven/artifact/repository/ArtifactRepository +instanceKlass org/apache/maven/artifact/Artifact +instanceKlass org/apache/maven/project/artifact/DefaultMavenMetadataCache +instanceKlass org/apache/maven/toolchain/model/TrackableBase +instanceKlass org/apache/maven/toolchain/DefaultToolchain +instanceKlass org/apache/maven/toolchain/ToolchainPrivate +instanceKlass org/apache/maven/toolchain/java/JavaToolchain +instanceKlass org/apache/maven/toolchain/Toolchain +instanceKlass org/apache/maven/toolchain/java/JavaToolchainFactory +instanceKlass org/apache/maven/artifact/resolver/ResolutionNode +instanceKlass org/apache/maven/repository/legacy/resolver/conflict/NearestConflictResolver +instanceKlass org/apache/maven/lifecycle/internal/TaskSegment +instanceKlass org/apache/maven/lifecycle/internal/ReactorContext +instanceKlass org/apache/maven/execution/ProjectExecutionListener +instanceKlass org/apache/maven/execution/BuildSummary +instanceKlass com/google/inject/spi/ProviderWithExtensionVisitor +instanceKlass com/google/common/collect/Iterables +instanceKlass java/util/stream/ForEachOps$ForEachOp +instanceKlass java/util/stream/ForEachOps +instanceKlass org/eclipse/sisu/plexus/PlexusBean +instanceKlass org/codehaus/plexus/component/repository/ComponentDescriptor +instanceKlass com/google/inject/spi/ProvidesMethodBinding +instanceKlass org/eclipse/sisu/inject/Guice4 +instanceKlass com/google/inject/internal/GuiceInternal +instanceKlass org/sonatype/inject/Parameters +instanceKlass org/eclipse/sisu/plexus/PlexusXmlBeanConverter +instanceKlass org/eclipse/sisu/plexus/PlexusBeanConverter +instanceKlass com/google/inject/spi/TypeConverterBinding +instanceKlass java/lang/reflect/AnnotatedParameterizedType +instanceKlass sun/reflect/generics/tree/Wildcard +instanceKlass sun/reflect/generics/tree/BottomSignature +instanceKlass org/eclipse/sisu/inject/DefaultRankingFunction +instanceKlass com/google/inject/spi/ProvisionListenerBinding +instanceKlass com/google/inject/spi/TypeListenerBinding +instanceKlass org/eclipse/sisu/bean/BeanListener +instanceKlass com/google/inject/matcher/Matchers +instanceKlass org/eclipse/sisu/bean/PropertyBinder +instanceKlass org/eclipse/sisu/plexus/PlexusBeanBinder +instanceKlass com/google/inject/spi/InjectionListener +instanceKlass org/apache/maven/settings/validation/DefaultSettingsValidator +instanceKlass org/apache/maven/settings/validation/SettingsValidator +instanceKlass org/apache/maven/settings/io/DefaultSettingsWriter +instanceKlass org/apache/maven/settings/io/SettingsWriter +instanceKlass org/apache/maven/settings/io/DefaultSettingsReader +instanceKlass org/apache/maven/settings/io/SettingsReader +instanceKlass org/apache/maven/settings/crypto/DefaultSettingsDecrypter +instanceKlass org/apache/maven/settings/crypto/SettingsDecrypter +instanceKlass org/apache/maven/settings/building/DefaultSettingsBuilder +instanceKlass org/apache/maven/settings/building/SettingsBuilder +instanceKlass org/apache/maven/cli/internal/BootstrapCoreExtensionManager +instanceKlass org/apache/maven/cli/configuration/SettingsXmlConfigurationProcessor +instanceKlass org/apache/maven/cli/configuration/ConfigurationProcessor +instanceKlass org/eclipse/aether/transport/http/ChecksumExtractor +instanceKlass org/eclipse/aether/transport/http/HttpTransporterFactory +instanceKlass org/sonatype/plexus/components/sec/dispatcher/DefaultSecDispatcher +instanceKlass org/eclipse/aether/transport/file/FileTransporterFactory +instanceKlass org/apache/maven/toolchain/io/DefaultToolchainsWriter +instanceKlass org/apache/maven/toolchain/io/ToolchainsWriter +instanceKlass org/apache/maven/toolchain/io/DefaultToolchainsReader +instanceKlass org/apache/maven/toolchain/io/ToolchainsReader +instanceKlass org/apache/maven/toolchain/building/DefaultToolchainsBuilder +instanceKlass org/apache/maven/toolchain/building/ToolchainsBuilder +instanceKlass org/apache/maven/execution/MavenSession +instanceKlass org/apache/maven/session/scope/internal/SessionScope$ScopeState +instanceKlass org/apache/maven/session/scope/internal/SessionScope +instanceKlass org/apache/maven/plugin/internal/MavenPluginMavenPrerequisiteChecker +instanceKlass org/apache/maven/plugin/internal/MavenPluginJavaPrerequisiteChecker +instanceKlass org/apache/maven/plugin/MavenPluginPrerequisitesChecker +instanceKlass org/apache/maven/plugin/internal/AbstractMavenPluginDependenciesValidator +instanceKlass org/apache/maven/plugin/internal/MavenPluginDependenciesValidator +instanceKlass org/apache/maven/plugin/internal/AbstractMavenPluginParametersValidator +instanceKlass org/apache/maven/plugin/internal/MavenPluginConfigurationValidator +instanceKlass org/apache/maven/eventspy/AbstractEventSpy +instanceKlass org/apache/maven/eventspy/EventSpy +instanceKlass org/apache/maven/plugin/PluginValidationManager +instanceKlass org/apache/maven/plugin/DefaultMojosExecutionStrategy +instanceKlass org/apache/maven/plugin/MojosExecutionStrategy +instanceKlass org/apache/maven/lifecycle/internal/LifecycleDependencyResolver +instanceKlass org/apache/maven/lifecycle/internal/DefaultProjectArtifactFactory +instanceKlass org/apache/maven/lifecycle/internal/ProjectArtifactFactory +instanceKlass org/sonatype/plexus/components/sec/dispatcher/SecDispatcher +instanceKlass org/apache/maven/internal/secdispatcher/SecDispatcherProvider +instanceKlass org/apache/maven/internal/aether/ResolverLifecycle +instanceKlass org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory +instanceKlass org/apache/maven/extension/internal/CoreExportsProvider +instanceKlass org/apache/maven/plugin/MojoExecution +instanceKlass org/apache/maven/project/MavenProject +instanceKlass org/apache/maven/execution/MojoExecutionEvent +instanceKlass org/apache/maven/execution/scope/internal/MojoExecutionScope$ScopeState +instanceKlass org/apache/maven/execution/scope/MojoExecutionScoped +instanceKlass com/google/inject/RestrictedBindingSource$Permit +instanceKlass org/apache/maven/execution/scope/internal/MojoExecutionScope$1 +instanceKlass org/apache/maven/execution/scope/internal/MojoExecutionScope +instanceKlass org/apache/maven/execution/MojoExecutionListener +instanceKlass org/apache/maven/execution/DefaultMavenExecutionRequestPopulator +instanceKlass org/apache/maven/execution/MavenExecutionRequestPopulator +instanceKlass org/apache/maven/classrealm/DefaultClassRealmManager +instanceKlass org/apache/maven/classrealm/ClassRealmManager +instanceKlass org/apache/maven/SessionScoped +instanceKlass org/apache/maven/ReactorReader +instanceKlass org/apache/maven/repository/internal/MavenWorkspaceReader +instanceKlass org/eclipse/aether/repository/WorkspaceReader +instanceKlass org/apache/maven/DefaultArtifactFilterManager +instanceKlass org/apache/maven/ArtifactFilterManager +instanceKlass org/sonatype/plexus/components/cipher/DefaultPlexusCipher +instanceKlass org/sonatype/plexus/components/cipher/PlexusCipher +instanceKlass org/eclipse/aether/transport/wagon/WagonTransporterFactory +instanceKlass org/eclipse/aether/spi/connector/transport/TransporterFactory +instanceKlass org/eclipse/aether/internal/transport/wagon/PlexusWagonProvider +instanceKlass org/eclipse/aether/transport/wagon/WagonProvider +instanceKlass org/eclipse/aether/internal/transport/wagon/PlexusWagonConfigurator +instanceKlass org/eclipse/aether/transport/wagon/WagonConfigurator +instanceKlass org/eclipse/aether/connector/basic/BasicRepositoryConnectorFactory +instanceKlass org/eclipse/aether/spi/connector/RepositoryConnectorFactory +instanceKlass org/apache/maven/model/validation/DefaultModelValidator +instanceKlass org/apache/maven/model/validation/ModelValidator +instanceKlass org/apache/maven/model/superpom/DefaultSuperPomProvider +instanceKlass org/apache/maven/model/superpom/SuperPomProvider +instanceKlass org/apache/maven/model/profile/activation/PropertyProfileActivator +instanceKlass org/apache/maven/model/profile/activation/OperatingSystemProfileActivator +instanceKlass org/apache/maven/model/profile/activation/JdkVersionProfileActivator +instanceKlass org/apache/maven/model/profile/activation/FileProfileActivator +instanceKlass org/apache/maven/model/profile/activation/ProfileActivator +instanceKlass org/apache/maven/model/profile/DefaultProfileSelector +instanceKlass org/apache/maven/model/profile/ProfileSelector +instanceKlass org/apache/maven/model/profile/DefaultProfileInjector +instanceKlass org/apache/maven/model/profile/ProfileInjector +instanceKlass org/apache/maven/model/plugin/DefaultReportingConverter +instanceKlass org/apache/maven/model/plugin/ReportingConverter +instanceKlass org/apache/maven/model/plugin/DefaultReportConfigurationExpander +instanceKlass org/apache/maven/model/plugin/ReportConfigurationExpander +instanceKlass org/apache/maven/model/plugin/DefaultPluginConfigurationExpander +instanceKlass org/apache/maven/model/plugin/PluginConfigurationExpander +instanceKlass org/apache/maven/model/path/ProfileActivationFilePathInterpolator +instanceKlass org/apache/maven/model/path/DefaultUrlNormalizer +instanceKlass org/apache/maven/model/path/UrlNormalizer +instanceKlass org/apache/maven/model/path/DefaultPathTranslator +instanceKlass org/apache/maven/model/path/PathTranslator +instanceKlass org/apache/maven/model/path/DefaultModelUrlNormalizer +instanceKlass org/apache/maven/model/path/ModelUrlNormalizer +instanceKlass org/apache/maven/model/path/DefaultModelPathTranslator +instanceKlass org/apache/maven/model/path/ModelPathTranslator +instanceKlass org/apache/maven/model/normalization/DefaultModelNormalizer +instanceKlass org/apache/maven/model/normalization/ModelNormalizer +instanceKlass org/apache/maven/model/management/DefaultPluginManagementInjector +instanceKlass org/apache/maven/model/management/PluginManagementInjector +instanceKlass org/apache/maven/model/management/DefaultDependencyManagementInjector +instanceKlass org/apache/maven/model/management/DependencyManagementInjector +instanceKlass org/apache/maven/model/locator/DefaultModelLocator +instanceKlass org/apache/maven/model/io/DefaultModelWriter +instanceKlass org/apache/maven/model/io/ModelWriter +instanceKlass org/apache/maven/model/io/DefaultModelReader +instanceKlass org/apache/maven/model/interpolation/AbstractStringBasedModelInterpolator +instanceKlass org/apache/maven/model/interpolation/ModelInterpolator +instanceKlass org/apache/maven/model/interpolation/DefaultModelVersionProcessor +instanceKlass org/apache/maven/model/interpolation/ModelVersionProcessor +instanceKlass org/apache/maven/model/inheritance/DefaultInheritanceAssembler +instanceKlass org/apache/maven/model/inheritance/InheritanceAssembler +instanceKlass org/apache/maven/model/composition/DefaultDependencyManagementImporter +instanceKlass org/apache/maven/model/composition/DependencyManagementImporter +instanceKlass org/apache/maven/model/building/DefaultModelProcessor +instanceKlass org/apache/maven/model/building/ModelProcessor +instanceKlass org/apache/maven/model/io/ModelReader +instanceKlass org/apache/maven/model/locator/ModelLocator +instanceKlass org/apache/maven/model/building/DefaultModelBuilder +instanceKlass org/apache/maven/model/building/ModelBuilder +instanceKlass org/apache/maven/repository/internal/VersionsMetadataGeneratorFactory +instanceKlass org/apache/maven/repository/internal/SnapshotMetadataGeneratorFactory +instanceKlass org/apache/maven/repository/internal/PluginsMetadataGeneratorFactory +instanceKlass org/eclipse/aether/impl/MetadataGeneratorFactory +instanceKlass org/apache/maven/repository/internal/DefaultVersionResolver +instanceKlass org/eclipse/aether/impl/VersionResolver +instanceKlass org/apache/maven/repository/internal/DefaultVersionRangeResolver +instanceKlass org/eclipse/aether/impl/VersionRangeResolver +instanceKlass org/apache/maven/repository/internal/DefaultModelCacheFactory +instanceKlass org/apache/maven/repository/internal/ModelCacheFactory +instanceKlass org/apache/maven/repository/internal/DefaultArtifactDescriptorReader +instanceKlass org/eclipse/aether/impl/ArtifactDescriptorReader +instanceKlass org/codehaus/plexus/component/configurator/AbstractComponentConfigurator +instanceKlass org/codehaus/plexus/component/configurator/ComponentConfigurator +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/providers/StaticNameMapperProvider +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/providers/GAVNameMapperProvider +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/providers/GAECVNameMapperProvider +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/providers/FileStaticNameMapperProvider +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/providers/FileHashingGAVNameMapperProvider +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/providers/FileHashingGAECVNameMapperProvider +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/providers/FileGAVNameMapperProvider +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/providers/FileGAECVNameMapperProvider +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/NameMapper +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/providers/DiscriminatingNameMapperProvider +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapterFactoryImpl +instanceKlass org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapterFactory +instanceKlass org/eclipse/aether/internal/impl/synccontext/legacy/DefaultSyncContextFactory +instanceKlass org/eclipse/aether/impl/SyncContextFactory +instanceKlass org/eclipse/aether/internal/impl/synccontext/DefaultSyncContextFactory +instanceKlass org/eclipse/aether/spi/synccontext/SyncContextFactory +instanceKlass java/lang/Deprecated +instanceKlass org/eclipse/aether/internal/impl/slf4j/Slf4jLoggerFactory +instanceKlass org/eclipse/aether/internal/impl/resolution/ArtifactResolverPostProcessorSupport +instanceKlass org/eclipse/aether/internal/impl/filter/RemoteRepositoryFilterSourceSupport +instanceKlass org/eclipse/aether/spi/connector/filter/RemoteRepositoryFilterSource +instanceKlass org/eclipse/aether/spi/resolution/ArtifactResolverPostProcessor +instanceKlass org/eclipse/aether/internal/impl/filter/DefaultRemoteRepositoryFilterManager +instanceKlass org/eclipse/aether/impl/RemoteRepositoryFilterManager +instanceKlass org/eclipse/aether/internal/impl/collect/DependencyCollectorDelegate +instanceKlass org/eclipse/aether/internal/impl/collect/DefaultDependencyCollector +instanceKlass org/eclipse/aether/impl/DependencyCollector +instanceKlass org/eclipse/aether/internal/impl/checksum/TrustedToProvidedChecksumsSourceAdapter +instanceKlass org/eclipse/aether/spi/checksums/ProvidedChecksumsSource +instanceKlass org/eclipse/aether/internal/impl/checksum/FileTrustedChecksumsSourceSupport +instanceKlass org/eclipse/aether/spi/checksums/TrustedChecksumsSource +instanceKlass org/eclipse/aether/spi/connector/checksum/ChecksumAlgorithmFactorySupport +instanceKlass org/eclipse/aether/spi/connector/checksum/ChecksumAlgorithmFactory +instanceKlass org/eclipse/aether/internal/impl/checksum/DefaultChecksumAlgorithmFactorySelector +instanceKlass org/eclipse/aether/spi/connector/checksum/ChecksumAlgorithmFactorySelector +instanceKlass org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory +instanceKlass org/eclipse/aether/internal/impl/Maven2RepositoryLayoutFactory +instanceKlass org/eclipse/aether/spi/connector/layout/RepositoryLayoutFactory +instanceKlass org/eclipse/aether/spi/log/LoggerFactory +instanceKlass org/eclipse/aether/internal/impl/LoggerFactoryProvider +instanceKlass org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory +instanceKlass org/eclipse/aether/spi/localrepo/LocalRepositoryManagerFactory +instanceKlass org/eclipse/aether/internal/impl/DefaultUpdatePolicyAnalyzer +instanceKlass org/eclipse/aether/impl/UpdatePolicyAnalyzer +instanceKlass org/eclipse/aether/internal/impl/DefaultUpdateCheckManager +instanceKlass org/eclipse/aether/impl/UpdateCheckManager +instanceKlass org/eclipse/aether/internal/impl/DefaultTransporterProvider +instanceKlass org/eclipse/aether/spi/connector/transport/TransporterProvider +instanceKlass org/eclipse/aether/internal/impl/DefaultTrackingFileManager +instanceKlass org/eclipse/aether/internal/impl/TrackingFileManager +instanceKlass org/eclipse/aether/internal/impl/DefaultRepositorySystemLifecycle +instanceKlass org/eclipse/aether/impl/RepositorySystemLifecycle +instanceKlass org/eclipse/aether/internal/impl/DefaultRepositorySystem +instanceKlass org/eclipse/aether/RepositorySystem +instanceKlass org/eclipse/aether/internal/impl/DefaultRepositoryLayoutProvider +instanceKlass org/eclipse/aether/spi/connector/layout/RepositoryLayoutProvider +instanceKlass org/eclipse/aether/internal/impl/DefaultRepositoryEventDispatcher +instanceKlass org/eclipse/aether/impl/RepositoryEventDispatcher +instanceKlass jdk/internal/reflect/ClassDefiner$1 +instanceKlass jdk/internal/reflect/ClassDefiner +instanceKlass jdk/internal/reflect/MethodAccessorGenerator$1 +instanceKlass jdk/internal/reflect/Label$PatchInfo +instanceKlass jdk/internal/reflect/Label +instanceKlass jdk/internal/reflect/UTF8 +instanceKlass jdk/internal/reflect/ClassFileAssembler +instanceKlass jdk/internal/reflect/ByteVectorImpl +instanceKlass jdk/internal/reflect/ByteVector +instanceKlass jdk/internal/reflect/ByteVectorFactory +instanceKlass jdk/internal/reflect/AccessorGenerator +instanceKlass jdk/internal/reflect/ClassFileConstants +instanceKlass org/eclipse/aether/internal/impl/DefaultRepositoryConnectorProvider +instanceKlass org/eclipse/aether/impl/RepositoryConnectorProvider +instanceKlass org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManager +instanceKlass org/eclipse/aether/impl/RemoteRepositoryManager +instanceKlass org/eclipse/aether/internal/impl/DefaultOfflineController +instanceKlass org/eclipse/aether/impl/OfflineController +instanceKlass org/eclipse/aether/internal/impl/DefaultMetadataResolver +instanceKlass org/eclipse/aether/impl/MetadataResolver +instanceKlass org/eclipse/aether/internal/impl/DefaultLocalRepositoryProvider +instanceKlass org/eclipse/aether/impl/LocalRepositoryProvider +instanceKlass org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport +instanceKlass org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactory +instanceKlass org/eclipse/aether/internal/impl/DefaultLocalPathComposer +instanceKlass org/eclipse/aether/internal/impl/LocalPathComposer +instanceKlass org/eclipse/aether/internal/impl/DefaultInstaller +instanceKlass org/eclipse/aether/impl/Installer +instanceKlass org/eclipse/aether/internal/impl/DefaultFileProcessor +instanceKlass org/eclipse/aether/spi/io/FileProcessor +instanceKlass org/eclipse/aether/internal/impl/DefaultDeployer +instanceKlass org/eclipse/aether/impl/Deployer +instanceKlass org/eclipse/aether/internal/impl/DefaultChecksumPolicyProvider +instanceKlass org/eclipse/aether/spi/connector/checksum/ChecksumPolicyProvider +instanceKlass org/eclipse/aether/internal/impl/DefaultArtifactResolver +instanceKlass org/eclipse/aether/spi/locator/Service +instanceKlass org/eclipse/aether/impl/ArtifactResolver +instanceKlass org/eclipse/sisu/space/WildcardKey$QualifiedImpl +instanceKlass org/eclipse/sisu/space/WildcardKey$Qualified +instanceKlass org/eclipse/sisu/space/WildcardKey +instanceKlass org/eclipse/sisu/Typed +instanceKlass org/sonatype/inject/EagerSingleton +instanceKlass org/eclipse/sisu/EagerSingleton +instanceKlass org/sonatype/inject/Mediator +instanceKlass org/eclipse/sisu/inject/TypeArguments +instanceKlass org/eclipse/aether/named/support/NamedLockFactorySupport +instanceKlass org/eclipse/aether/named/NamedLockFactory +instanceKlass org/objectweb/asm/Context +instanceKlass org/objectweb/asm/Attribute +instanceKlass org/objectweb/asm/AnnotationVisitor +instanceKlass org/objectweb/asm/ClassReader +instanceKlass org/eclipse/sisu/space/IndexedClassFinder$1 +instanceKlass org/eclipse/sisu/inject/Logs$SLF4JSink +instanceKlass org/eclipse/sisu/inject/Logs$Sink +instanceKlass org/eclipse/sisu/inject/Logs +instanceKlass org/eclipse/sisu/space/QualifierCache +instanceKlass org/eclipse/sisu/space/QualifiedTypeVisitor +instanceKlass org/eclipse/sisu/plexus/PlexusTypeVisitor$ComponentAnnotationVisitor +instanceKlass org/eclipse/sisu/space/AnnotationVisitor +instanceKlass org/eclipse/sisu/plexus/PlexusTypeVisitor +instanceKlass org/eclipse/sisu/space/ClassVisitor +instanceKlass org/eclipse/sisu/plexus/PlexusXmlBeanModule$PlexusXmlBeanSource +instanceKlass org/eclipse/sisu/inject/DescriptionSource +instanceKlass org/eclipse/sisu/inject/AnnotatedSource +instanceKlass org/eclipse/sisu/Priority +instanceKlass org/eclipse/sisu/Hidden +instanceKlass org/eclipse/sisu/Description +instanceKlass org/eclipse/sisu/inject/Sources +instanceKlass com/google/inject/Key$AnnotationInstanceStrategy +instanceKlass com/google/inject/name/NamedImpl +instanceKlass com/google/inject/name/Named +instanceKlass com/google/inject/name/Names +instanceKlass com/google/inject/internal/MoreTypes$ParameterizedTypeImpl +instanceKlass sun/reflect/generics/reflectiveObjects/ParameterizedTypeImpl +instanceKlass sun/reflect/generics/reflectiveObjects/LazyReflectiveObjectGenerator +instanceKlass org/apache/maven/toolchain/ToolchainsBuilder +instanceKlass org/apache/maven/toolchain/ToolchainManagerPrivate +instanceKlass org/apache/maven/toolchain/ToolchainManager +instanceKlass org/apache/maven/toolchain/ToolchainFactory +instanceKlass org/apache/maven/settings/MavenSettingsBuilder +instanceKlass org/apache/maven/rtinfo/RuntimeInformation +instanceKlass org/apache/maven/project/artifact/ProjectArtifactsCache +instanceKlass org/apache/maven/project/artifact/MavenMetadataCache +instanceKlass org/apache/maven/project/ProjectRealmCache +instanceKlass org/apache/maven/project/ProjectDependenciesResolver +instanceKlass org/apache/maven/project/ProjectBuildingHelper +instanceKlass org/apache/maven/project/ProjectBuilder +instanceKlass org/apache/maven/project/MavenProjectHelper +instanceKlass org/apache/maven/plugin/version/PluginVersionResolver +instanceKlass org/apache/maven/plugin/prefix/PluginPrefixResolver +instanceKlass org/apache/maven/plugin/internal/PluginDependenciesResolver +instanceKlass org/apache/maven/plugin/PluginRealmCache +instanceKlass org/apache/maven/plugin/PluginManager +instanceKlass org/apache/maven/plugin/PluginDescriptorCache +instanceKlass org/apache/maven/plugin/PluginArtifactsCache +instanceKlass org/apache/maven/plugin/MavenPluginManager +instanceKlass org/apache/maven/plugin/LegacySupport +instanceKlass org/apache/maven/plugin/ExtensionRealmCache +instanceKlass org/apache/maven/plugin/BuildPluginManager +instanceKlass org/apache/maven/model/plugin/LifecycleBindingsInjector +instanceKlass org/apache/maven/lifecycle/internal/builder/BuilderCommon +instanceKlass org/apache/maven/lifecycle/internal/builder/Builder +instanceKlass org/apache/maven/lifecycle/internal/MojoExecutor +instanceKlass org/apache/maven/lifecycle/internal/MojoDescriptorCreator +instanceKlass org/apache/maven/lifecycle/internal/LifecycleTaskSegmentCalculator +instanceKlass org/apache/maven/lifecycle/internal/LifecycleStarter +instanceKlass org/apache/maven/lifecycle/internal/LifecyclePluginResolver +instanceKlass org/apache/maven/lifecycle/internal/LifecycleModuleBuilder +instanceKlass org/apache/maven/lifecycle/internal/LifecycleExecutionPlanCalculator +instanceKlass org/apache/maven/lifecycle/internal/LifecycleDebugLogger +instanceKlass org/apache/maven/lifecycle/internal/ExecutionEventCatapult +instanceKlass org/apache/maven/lifecycle/internal/BuildListCalculator +instanceKlass org/apache/maven/lifecycle/MojoExecutionConfigurator +instanceKlass org/apache/maven/lifecycle/LifecycleMappingDelegate +instanceKlass org/apache/maven/lifecycle/LifecycleExecutor +instanceKlass org/apache/maven/lifecycle/LifeCyclePluginAnalyzer +instanceKlass org/apache/maven/lifecycle/DefaultLifecycles +instanceKlass org/apache/maven/graph/GraphBuilder +instanceKlass org/apache/maven/eventspy/internal/EventSpyDispatcher +instanceKlass org/apache/maven/configuration/BeanConfigurator +instanceKlass org/apache/maven/bridge/MavenRepositorySystem +instanceKlass org/apache/maven/artifact/resolver/ResolutionErrorHandler +instanceKlass org/apache/maven/artifact/repository/metadata/io/MetadataReader +instanceKlass org/apache/maven/artifact/metadata/ArtifactMetadataSource +instanceKlass org/apache/maven/repository/legacy/metadata/ArtifactMetadataSource +instanceKlass org/apache/maven/artifact/handler/manager/ArtifactHandlerManager +instanceKlass org/apache/maven/artifact/factory/ArtifactFactory +instanceKlass org/apache/maven/ProjectDependenciesResolver +instanceKlass org/apache/maven/Maven +instanceKlass org/apache/maven/artifact/handler/ArtifactHandler +instanceKlass org/apache/maven/lifecycle/Lifecycle +instanceKlass org/apache/maven/lifecycle/mapping/LifecycleMapping +instanceKlass org/eclipse/sisu/space/CloningClassSpace$1 +instanceKlass org/apache/maven/wagon/Wagon +instanceKlass org/apache/maven/repository/metadata/GraphConflictResolver +instanceKlass org/apache/maven/repository/metadata/GraphConflictResolutionPolicy +instanceKlass org/eclipse/sisu/plexus/ConfigurationImpl +instanceKlass org/apache/maven/repository/metadata/ClasspathTransformation +instanceKlass org/apache/maven/repository/legacy/resolver/transform/ArtifactTransformationManager +instanceKlass org/apache/maven/repository/legacy/resolver/transform/ArtifactTransformation +instanceKlass org/apache/maven/repository/legacy/resolver/conflict/ConflictResolverFactory +instanceKlass org/apache/maven/repository/legacy/resolver/conflict/ConflictResolver +instanceKlass jdk/internal/access/foreign/MemorySegmentProxy +instanceKlass org/apache/maven/repository/legacy/repository/ArtifactRepositoryFactory +instanceKlass org/apache/maven/repository/legacy/UpdateCheckManager +instanceKlass org/apache/maven/repository/RepositorySystem +instanceKlass org/apache/maven/repository/MirrorSelector +instanceKlass org/apache/maven/project/validation/ModelValidator +instanceKlass org/apache/maven/project/path/PathTranslator +instanceKlass org/apache/maven/project/interpolation/ModelInterpolator +instanceKlass org/apache/maven/project/inheritance/ModelInheritanceAssembler +instanceKlass org/apache/maven/project/MavenProjectBuilder +instanceKlass org/apache/maven/profiles/MavenProfilesBuilder +instanceKlass org/apache/maven/execution/RuntimeInformation +instanceKlass org/apache/maven/artifact/resolver/ArtifactResolver +instanceKlass org/apache/maven/artifact/resolver/ArtifactCollector +instanceKlass org/apache/maven/repository/legacy/resolver/LegacyArtifactCollector +instanceKlass org/apache/maven/artifact/repository/metadata/RepositoryMetadataManager +instanceKlass org/apache/maven/artifact/repository/layout/ArtifactRepositoryLayout +instanceKlass org/apache/maven/artifact/repository/ArtifactRepositoryFactory +instanceKlass org/apache/maven/artifact/manager/WagonManager +instanceKlass org/apache/maven/repository/legacy/WagonManager +instanceKlass org/apache/maven/artifact/installer/ArtifactInstaller +instanceKlass org/eclipse/sisu/plexus/PlexusXmlMetadata +instanceKlass org/eclipse/sisu/plexus/Roles +instanceKlass org/apache/maven/artifact/deployer/ArtifactDeployer +instanceKlass org/eclipse/sisu/plexus/Hints +instanceKlass org/eclipse/sisu/space/AbstractDeferredClass +instanceKlass org/eclipse/sisu/plexus/RequirementImpl +instanceKlass org/codehaus/plexus/component/annotations/Requirement +instanceKlass org/eclipse/sisu/space/Streams +instanceKlass org/eclipse/sisu/plexus/ComponentImpl +instanceKlass org/codehaus/plexus/component/annotations/Component +instanceKlass org/eclipse/sisu/plexus/PlexusTypeRegistry +instanceKlass org/eclipse/sisu/plexus/PlexusXmlScanner +instanceKlass org/eclipse/sisu/space/QualifiedTypeBinder +instanceKlass org/eclipse/sisu/plexus/PlexusTypeBinder +instanceKlass com/google/inject/spi/InjectionRequest +instanceKlass org/eclipse/sisu/bean/BeanProperty +instanceKlass com/google/common/collect/ObjectArrays +instanceKlass com/google/inject/internal/Nullability +instanceKlass com/google/inject/internal/KotlinSupport$KotlinUnsupported +instanceKlass com/google/inject/internal/KotlinSupport$KotlinSupportHolder +instanceKlass com/google/inject/internal/KotlinSupportInterface +instanceKlass com/google/inject/internal/KotlinSupport +instanceKlass com/google/inject/spi/InjectionPoint$OverrideIndex +instanceKlass org/eclipse/sisu/inject/RankedBindings +instanceKlass org/eclipse/sisu/Mediator +instanceKlass sun/reflect/generics/tree/TypeVariableSignature +instanceKlass com/google/inject/Inject +instanceKlass javax/inject/Inject +instanceKlass java/lang/reflect/WildcardType +instanceKlass com/google/inject/spi/InjectionPoint$InjectableMembers +instanceKlass com/google/inject/spi/InjectionPoint$InjectableMember +instanceKlass com/google/inject/spi/InjectionPoint +instanceKlass java/lang/reflect/ParameterizedType +instanceKlass com/google/inject/internal/MoreTypes$GenericArrayTypeImpl +instanceKlass com/google/inject/internal/MoreTypes$CompositeType +instanceKlass com/google/inject/Key$AnnotationTypeStrategy +instanceKlass com/google/common/util/concurrent/AbstractFuture$Failure +instanceKlass com/google/common/util/concurrent/AbstractFuture$Cancellation +instanceKlass com/google/common/util/concurrent/AbstractFuture$DelegatingToFuture +instanceKlass com/google/common/util/concurrent/Platform +instanceKlass com/google/common/util/concurrent/Uninterruptibles +instanceKlass com/google/common/util/concurrent/AbstractFuture$Listener +instanceKlass com/google/common/util/concurrent/AbstractFutureState$Waiter +instanceKlass com/google/common/util/concurrent/LazyLogger +instanceKlass java/util/concurrent/Executor +instanceKlass com/google/common/util/concurrent/AbstractFutureState$AtomicHelper +instanceKlass com/google/common/util/concurrent/internal/InternalFutureFailureAccess +instanceKlass com/google/common/util/concurrent/AbstractFuture$Trusted +instanceKlass com/google/common/util/concurrent/ListenableFuture +instanceKlass java/lang/invoke/VarHandle$AccessDescriptor +instanceKlass java/lang/annotation/Documented +instanceKlass java/lang/annotation/Target +instanceKlass javax/inject/Named +instanceKlass javax/inject/Qualifier +instanceKlass com/google/inject/BindingAnnotation +instanceKlass javax/inject/Scope +instanceKlass com/google/inject/ScopeAnnotation +instanceKlass com/google/inject/internal/Annotations$AnnotationChecker +instanceKlass java/lang/reflect/Proxy$ProxyBuilder$1 +instanceKlass jdk/internal/org/objectweb/asm/Edge +instanceKlass java/lang/reflect/ProxyGenerator$PrimitiveTypeInfo +instanceKlass java/util/StringJoiner +instanceKlass java/lang/reflect/ProxyGenerator$ProxyMethod +instanceKlass java/lang/WeakPairMap$Pair$Lookup +instanceKlass java/lang/WeakPairMap$Pair +instanceKlass java/lang/WeakPairMap +instanceKlass java/lang/Module$ReflectionData +instanceKlass jdk/internal/module/Checks +instanceKlass java/lang/module/ModuleDescriptor$Builder +instanceKlass java/lang/PublicMethods +instanceKlass java/util/Collections$1 +instanceKlass java/lang/reflect/Proxy$ProxyBuilder +instanceKlass java/lang/ClassValue$Version +instanceKlass java/lang/ClassValue$Identity +instanceKlass java/lang/ClassValue +instanceKlass java/lang/reflect/Proxy +instanceKlass sun/reflect/annotation/AnnotationInvocationHandler +instanceKlass sun/reflect/annotation/AnnotationParser$1 +instanceKlass sun/reflect/annotation/ExceptionProxy +instanceKlass java/lang/annotation/Inherited +instanceKlass java/lang/annotation/Retention +instanceKlass sun/reflect/annotation/AnnotationType$1 +instanceKlass sun/reflect/annotation/AnnotationType +instanceKlass java/lang/reflect/GenericArrayType +instanceKlass sun/reflect/generics/visitor/Reifier +instanceKlass sun/reflect/generics/visitor/TypeTreeVisitor +instanceKlass sun/reflect/generics/factory/CoreReflectionFactory +instanceKlass sun/reflect/generics/factory/GenericsFactory +instanceKlass sun/reflect/generics/scope/AbstractScope +instanceKlass sun/reflect/generics/scope/Scope +instanceKlass com/google/inject/internal/Annotations$TestAnnotation +instanceKlass com/google/inject/internal/Annotations$AnnotationToStringConfig +instanceKlass com/google/common/base/Joiner$MapJoiner +instanceKlass com/google/common/base/Joiner +instanceKlass java/lang/reflect/InvocationHandler +instanceKlass com/google/inject/internal/Annotations +instanceKlass org/eclipse/sisu/Parameters +instanceKlass org/eclipse/sisu/wire/ParameterKeys +instanceKlass com/google/inject/internal/util/StackTraceElements$InMemoryStackTraceElement +instanceKlass com/google/inject/internal/util/StackTraceElements +instanceKlass org/eclipse/sisu/wire/TypeConverterCache +instanceKlass com/google/inject/internal/Scoping +instanceKlass com/google/inject/internal/InternalFactory +instanceKlass java/lang/StackTraceElement$HashedModules +instanceKlass com/google/inject/internal/InternalFlags$1 +instanceKlass com/google/inject/internal/InternalFlags +instanceKlass com/google/inject/spi/ConstructorBinding +instanceKlass com/google/inject/spi/ProviderInstanceBinding +instanceKlass com/google/inject/internal/DelayedInitialize +instanceKlass com/google/inject/spi/ProviderKeyBinding +instanceKlass com/google/inject/spi/InstanceBinding +instanceKlass com/google/inject/spi/HasDependencies +instanceKlass com/google/inject/spi/LinkedKeyBinding +instanceKlass com/google/inject/spi/UntargettedBinding +instanceKlass com/google/inject/internal/BindingImpl +instanceKlass com/google/inject/Key$AnnotationStrategy +instanceKlass org/eclipse/sisu/wire/ElementAnalyzer$1 +instanceKlass com/google/inject/util/Modules$EmptyModule +instanceKlass com/google/inject/util/Modules$OverriddenModuleBuilder +instanceKlass com/google/inject/util/Modules +instanceKlass java/util/stream/Nodes$ArrayNode +instanceKlass java/util/stream/Node$Builder +instanceKlass java/util/stream/Node$OfDouble +instanceKlass java/util/stream/Node$OfLong +instanceKlass java/util/stream/Node$OfInt +instanceKlass java/util/stream/Node$OfPrimitive +instanceKlass java/util/stream/Nodes$EmptyNode +instanceKlass java/util/stream/Node +instanceKlass java/util/stream/Nodes +instanceKlass java/util/function/IntFunction +instanceKlass java/util/stream/SortedOps +instanceKlass com/google/common/collect/Ordering +instanceKlass com/google/inject/internal/DeclaredMembers +instanceKlass com/google/common/base/ExtraObjectsMethodsForWeb +instanceKlass com/google/common/collect/ImmutableMap$Builder +instanceKlass com/google/inject/internal/MoreTypes +instanceKlass com/google/inject/multibindings/ProvidesIntoOptional +instanceKlass com/google/inject/multibindings/ProvidesIntoMap +instanceKlass com/google/inject/multibindings/ProvidesIntoSet +instanceKlass com/google/inject/Provides +instanceKlass javax/inject/Singleton +instanceKlass com/google/inject/spi/ElementSource +instanceKlass com/google/inject/spi/ScopeBinding +instanceKlass com/google/inject/Scopes$2 +instanceKlass com/google/inject/Scopes$1 +instanceKlass com/google/inject/internal/SingletonScope +instanceKlass com/google/inject/Scopes +instanceKlass com/google/inject/Singleton +instanceKlass com/google/inject/spi/Elements$ModuleInfo +instanceKlass com/google/inject/PrivateModule +instanceKlass java/util/stream/Streams$2 +instanceKlass java/util/stream/Streams$ConcatSpliterator +instanceKlass sun/reflect/annotation/AnnotatedTypeFactory$AnnotatedTypeBaseImpl +instanceKlass java/lang/reflect/AnnotatedType +instanceKlass sun/reflect/annotation/AnnotatedTypeFactory +instanceKlass sun/reflect/annotation/TypeAnnotation$LocationInfo$Location +instanceKlass sun/reflect/annotation/TypeAnnotation$LocationInfo +instanceKlass sun/reflect/generics/tree/ClassSignature +instanceKlass sun/reflect/generics/tree/Signature +instanceKlass sun/reflect/generics/tree/ClassTypeSignature +instanceKlass sun/reflect/generics/tree/SimpleClassTypeSignature +instanceKlass sun/reflect/generics/tree/FieldTypeSignature +instanceKlass sun/reflect/generics/tree/BaseType +instanceKlass sun/reflect/generics/tree/TypeSignature +instanceKlass sun/reflect/generics/tree/ReturnType +instanceKlass sun/reflect/generics/tree/TypeArgument +instanceKlass sun/reflect/generics/tree/FormalTypeParameter +instanceKlass sun/reflect/generics/tree/TypeTree +instanceKlass sun/reflect/generics/tree/Tree +instanceKlass sun/reflect/generics/parser/SignatureParser +instanceKlass java/lang/reflect/TypeVariable +instanceKlass sun/reflect/generics/repository/AbstractRepository +instanceKlass sun/reflect/annotation/TypeAnnotation +instanceKlass sun/reflect/annotation/TypeAnnotationParser +instanceKlass java/lang/Class$AnnotationData +instanceKlass com/google/inject/RestrictedBindingSource +instanceKlass com/google/inject/spi/BindingSourceRestriction +instanceKlass com/google/inject/spi/ModuleSource +instanceKlass com/google/inject/internal/ProviderMethodsModule +instanceKlass com/google/inject/spi/BindingSourceRestriction$PermitMapConstruction$PermitMapImpl +instanceKlass com/google/inject/spi/BindingSourceRestriction$PermitMap +instanceKlass com/google/inject/spi/BindingSourceRestriction$PermitMapConstruction +instanceKlass com/google/common/collect/Hashing +instanceKlass com/google/common/math/IntMath$1 +instanceKlass com/google/common/math/MathPreconditions +instanceKlass com/google/common/math/IntMath +instanceKlass com/google/inject/internal/AbstractBindingBuilder +instanceKlass com/google/inject/binder/ConstantBindingBuilder +instanceKlass com/google/inject/binder/AnnotatedElementBuilder +instanceKlass com/google/inject/spi/Elements$RecordingBinder +instanceKlass com/google/inject/Binding +instanceKlass com/google/inject/spi/DefaultBindingTargetVisitor +instanceKlass com/google/inject/spi/BindingTargetVisitor +instanceKlass com/google/inject/spi/Elements +instanceKlass com/google/inject/internal/InjectorShell$RootModule +instanceKlass com/google/common/collect/ListMultimap +instanceKlass com/google/inject/internal/InjectorBindingData +instanceKlass java/util/concurrent/atomic/AtomicReferenceArray +instanceKlass java/util/concurrent/Future +instanceKlass com/google/common/cache/LocalCache$LoadingValueReference +instanceKlass java/lang/invoke/VarForm +instanceKlass java/lang/invoke/VarHandleGuards +instanceKlass jdk/internal/util/Preconditions$1 +instanceKlass java/lang/invoke/VarHandle$1 +instanceKlass java/lang/invoke/VarHandles +instanceKlass java/util/concurrent/ConcurrentLinkedQueue$Node +instanceKlass com/google/common/cache/Weigher +instanceKlass com/google/common/base/Predicate +instanceKlass com/google/common/base/Equivalence +instanceKlass java/util/function/BiPredicate +instanceKlass com/google/common/base/MoreObjects +instanceKlass com/google/common/cache/LocalCache$1 +instanceKlass com/google/common/cache/ReferenceEntry +instanceKlass com/google/common/cache/CacheLoader +instanceKlass com/google/common/cache/LocalCache$LocalManualCache +instanceKlass java/util/AbstractMap$SimpleImmutableEntry +instanceKlass com/google/common/cache/RemovalListener +instanceKlass com/google/common/cache/LocalCache$StrongValueReference +instanceKlass com/google/common/cache/LocalCache$ValueReference +instanceKlass com/google/common/cache/CacheBuilder$2 +instanceKlass com/google/common/cache/CacheStats +instanceKlass com/google/common/base/Suppliers$SupplierOfInstance +instanceKlass com/google/common/base/Suppliers +instanceKlass com/google/common/cache/CacheBuilder$1 +instanceKlass com/google/common/cache/AbstractCache$StatsCounter +instanceKlass com/google/common/cache/LoadingCache +instanceKlass com/google/common/cache/Cache +instanceKlass com/google/common/base/Supplier +instanceKlass com/google/common/cache/CacheBuilder +instanceKlass com/google/inject/internal/WeakKeySet +instanceKlass com/google/common/collect/Sets +instanceKlass com/google/inject/internal/InjectorJitBindingData +instanceKlass java/util/Arrays$ArrayItr +instanceKlass com/google/inject/internal/ProcessedBindingData +instanceKlass com/google/inject/spi/DefaultElementVisitor +instanceKlass com/google/inject/internal/InjectorShell$Builder +instanceKlass com/google/common/collect/Lists +instanceKlass com/google/common/collect/CollectPreconditions +instanceKlass com/google/common/collect/LinkedHashMultimap$MultimapIterationChain +instanceKlass java/lang/StrictMath +instanceKlass com/google/common/collect/Platform +instanceKlass com/google/common/collect/Multiset +instanceKlass com/google/common/collect/AbstractMultimap +instanceKlass com/google/common/collect/SetMultimap +instanceKlass com/google/common/base/Converter +instanceKlass com/google/common/base/Function +instanceKlass com/google/common/collect/ImmutableMap +instanceKlass com/google/common/collect/BiMap +instanceKlass com/google/common/collect/SortedMapDifference +instanceKlass com/google/common/collect/MapDifference +instanceKlass com/google/common/collect/Maps +instanceKlass com/google/inject/internal/CycleDetectingLock +instanceKlass com/google/common/collect/Multimap +instanceKlass com/google/inject/internal/CycleDetectingLock$CycleDetectingLockFactory +instanceKlass com/google/inject/internal/Initializable +instanceKlass com/google/inject/internal/Initializer +instanceKlass com/google/common/collect/PeekingIterator +instanceKlass com/google/common/collect/UnmodifiableIterator +instanceKlass com/google/common/collect/Iterators +instanceKlass com/google/common/collect/ImmutableCollection$Builder +instanceKlass com/google/common/collect/ImmutableSet$SetBuilderImpl +instanceKlass com/google/inject/internal/util/SourceProvider +instanceKlass com/google/inject/spi/ErrorDetail +instanceKlass com/google/inject/internal/Errors +instanceKlass com/google/common/base/Preconditions +instanceKlass java/time/Duration +instanceKlass java/time/temporal/TemporalAmount +instanceKlass java/time/temporal/TemporalUnit +instanceKlass java/util/concurrent/TimeUnit$1 +instanceKlass jdk/internal/logger/DefaultLoggerFinder$1 +instanceKlass java/util/logging/Logger$SystemLoggerHelper$1 +instanceKlass java/util/logging/Logger$SystemLoggerHelper +instanceKlass java/util/logging/LogManager$4 +instanceKlass jdk/internal/logger/BootstrapLogger$BootstrapExecutors +instanceKlass jdk/internal/logger/BootstrapLogger$RedirectedLoggers +instanceKlass java/util/Spliterators$1Adapter +instanceKlass java/util/Spliterators$ArraySpliterator +instanceKlass java/util/Spliterator$OfDouble +instanceKlass java/util/Spliterator$OfLong +instanceKlass java/util/Spliterators$EmptySpliterator +instanceKlass java/util/Spliterators +instanceKlass jdk/internal/logger/BootstrapLogger$DetectBackend$1 +instanceKlass jdk/internal/logger/BootstrapLogger$DetectBackend +instanceKlass jdk/internal/logger/BootstrapLogger +instanceKlass sun/util/logging/PlatformLogger$ConfigurableBridge +instanceKlass sun/util/logging/PlatformLogger$Bridge +instanceKlass java/lang/System$Logger +instanceKlass java/util/stream/Streams +instanceKlass java/util/stream/Stream$Builder +instanceKlass java/util/stream/Streams$AbstractStreamBuilderImpl +instanceKlass java/util/Hashtable$Enumerator +instanceKlass java/util/logging/LogManager$LoggerContext$1 +instanceKlass java/util/logging/LogManager$VisitedLoggers +instanceKlass java/util/logging/LogManager$2 +instanceKlass java/lang/System$LoggerFinder +instanceKlass java/util/logging/LogManager$LoggingProviderAccess +instanceKlass sun/util/logging/internal/LoggingProviderImpl$LogManagerAccess +instanceKlass java/util/Collections$SynchronizedMap +instanceKlass java/util/logging/LogManager$LogNode +instanceKlass java/util/logging/LogManager$LoggerContext +instanceKlass java/util/logging/LogManager$1 +instanceKlass java/util/logging/LogManager +instanceKlass java/util/logging/Logger$ConfigurationData +instanceKlass java/util/logging/Logger$LoggerBundle +instanceKlass java/util/logging/Level +instanceKlass java/util/logging/Handler +instanceKlass java/util/logging/Logger +instanceKlass com/google/common/base/Ticker +instanceKlass com/google/common/base/Stopwatch +instanceKlass com/google/inject/internal/util/ContinuousStopwatch +instanceKlass com/google/inject/Injector +instanceKlass com/google/inject/internal/InternalInjectorCreator +instanceKlass com/google/inject/Guice +instanceKlass org/eclipse/sisu/wire/Wiring +instanceKlass org/eclipse/sisu/wire/WireModule$Strategy$1 +instanceKlass org/eclipse/sisu/wire/WireModule$Strategy +instanceKlass org/eclipse/sisu/wire/AbstractTypeConverter +instanceKlass com/google/inject/spi/ElementVisitor +instanceKlass org/eclipse/sisu/wire/WireModule +instanceKlass org/eclipse/sisu/bean/BeanBinder +instanceKlass org/eclipse/sisu/plexus/PlexusBindingModule +instanceKlass org/codehaus/plexus/DefaultPlexusContainer$BootModule +instanceKlass org/codehaus/plexus/component/annotations/Configuration +instanceKlass org/eclipse/sisu/plexus/PlexusAnnotatedMetadata +instanceKlass org/eclipse/sisu/plexus/PlexusBeanMetadata +instanceKlass org/eclipse/sisu/plexus/PlexusAnnotatedBeanModule$PlexusAnnotatedBeanSource +instanceKlass org/eclipse/sisu/space/SpaceModule$2 +instanceKlass org/eclipse/sisu/space/SpaceModule$Strategy$2 +instanceKlass org/eclipse/sisu/space/SpaceModule$Strategy$1 +instanceKlass org/eclipse/sisu/space/DefaultClassFinder +instanceKlass org/objectweb/asm/ClassVisitor +instanceKlass org/eclipse/sisu/space/SpaceScanner +instanceKlass org/eclipse/sisu/space/IndexedClassFinder +instanceKlass org/eclipse/sisu/space/ClassFinder +instanceKlass org/eclipse/sisu/space/SpaceModule +instanceKlass org/eclipse/sisu/space/SpaceVisitor +instanceKlass jdk/internal/misc/ScopedMemoryAccess$Scope +instanceKlass org/eclipse/sisu/plexus/PlexusTypeListener +instanceKlass org/eclipse/sisu/space/QualifiedTypeListener +instanceKlass org/eclipse/sisu/plexus/PlexusAnnotatedBeanModule$1 +instanceKlass org/eclipse/sisu/space/SpaceModule$Strategy +instanceKlass org/eclipse/sisu/plexus/PlexusAnnotatedBeanModule +instanceKlass org/eclipse/sisu/plexus/PlexusBeanSource +instanceKlass org/eclipse/sisu/plexus/PlexusXmlBeanModule +instanceKlass org/eclipse/sisu/plexus/PlexusBeanModule +instanceKlass org/eclipse/sisu/space/URLClassSpace +instanceKlass org/codehaus/plexus/DefaultPlexusContainer$SLF4JLoggerFactoryProvider +instanceKlass com/google/inject/util/Providers$ConstantProvider +instanceKlass com/google/inject/util/Providers +instanceKlass org/codehaus/plexus/personality/plexus/lifecycle/phase/Disposable +instanceKlass org/codehaus/plexus/personality/plexus/lifecycle/phase/Startable +instanceKlass org/codehaus/plexus/personality/plexus/lifecycle/phase/Initializable +instanceKlass org/codehaus/plexus/personality/plexus/lifecycle/phase/Contextualizable +instanceKlass org/codehaus/plexus/logging/LogEnabled +instanceKlass org/eclipse/sisu/bean/PropertyBinding +instanceKlass javax/annotation/PreDestroy +instanceKlass javax/annotation/PostConstruct +instanceKlass org/eclipse/sisu/bean/LifecycleBuilder +instanceKlass org/eclipse/sisu/bean/BeanScheduler$1 +instanceKlass com/google/inject/spi/DefaultBindingScopingVisitor +instanceKlass com/google/inject/spi/BindingScopingVisitor +instanceKlass org/eclipse/sisu/bean/BeanScheduler$CycleActivator +instanceKlass com/google/inject/spi/ModuleAnnotatedMethodScanner +instanceKlass com/google/inject/PrivateBinder +instanceKlass com/google/inject/spi/TypeListener +instanceKlass com/google/inject/MembersInjector +instanceKlass org/aopalliance/intercept/MethodInterceptor +instanceKlass org/aopalliance/intercept/Interceptor +instanceKlass org/aopalliance/aop/Advice +instanceKlass com/google/inject/spi/Message +instanceKlass com/google/inject/spi/Element +instanceKlass com/google/inject/binder/AnnotatedConstantBindingBuilder +instanceKlass com/google/inject/Scope +instanceKlass com/google/inject/spi/Dependency +instanceKlass com/google/inject/Key +instanceKlass com/google/inject/binder/AnnotatedBindingBuilder +instanceKlass com/google/inject/binder/LinkedBindingBuilder +instanceKlass com/google/inject/binder/ScopedBindingBuilder +instanceKlass com/google/inject/TypeLiteral +instanceKlass com/google/inject/spi/ProvisionListener +instanceKlass com/google/inject/Binder +instanceKlass org/eclipse/sisu/bean/BeanScheduler +instanceKlass org/eclipse/sisu/plexus/DefaultPlexusBeanLocator +instanceKlass org/eclipse/sisu/plexus/RealmManager +instanceKlass org/codehaus/plexus/context/ContextMapAdapter +instanceKlass org/codehaus/plexus/context/DefaultContext +instanceKlass org/codehaus/plexus/logging/AbstractLogger +instanceKlass org/codehaus/plexus/logging/AbstractLoggerManager +instanceKlass java/util/Date +instanceKlass java/text/DigitList +instanceKlass java/text/FieldPosition +instanceKlass java/lang/StringUTF16$CharsSpliterator +instanceKlass java/util/stream/Sink$ChainedInt +instanceKlass java/util/OptionalInt +instanceKlass java/util/stream/Sink$OfInt +instanceKlass java/util/function/IntConsumer +instanceKlass java/util/function/IntPredicate +instanceKlass java/util/stream/IntStream +instanceKlass java/lang/StringLatin1$CharsSpliterator +instanceKlass java/util/Spliterator$OfInt +instanceKlass java/util/Spliterator$OfPrimitive +instanceKlass java/text/DecimalFormatSymbols +instanceKlass java/text/DateFormatSymbols +instanceKlass sun/util/calendar/CalendarUtils +instanceKlass sun/util/calendar/CalendarDate +instanceKlass sun/util/resources/Bundles$CacheKeyReference +instanceKlass java/util/ResourceBundle$ResourceBundleProviderHelper +instanceKlass sun/util/resources/Bundles$CacheKey +instanceKlass java/util/ResourceBundle$1 +instanceKlass jdk/internal/access/JavaUtilResourceBundleAccess +instanceKlass sun/util/resources/Bundles +instanceKlass sun/util/resources/LocaleData$LocaleDataStrategy +instanceKlass sun/util/resources/Bundles$Strategy +instanceKlass sun/util/resources/LocaleData$1 +instanceKlass sun/util/resources/LocaleData +instanceKlass sun/util/locale/provider/LocaleResources +instanceKlass java/util/ResourceBundle +instanceKlass java/util/ResourceBundle$Control +instanceKlass sun/util/locale/provider/CalendarDataUtility$CalendarWeekParameterGetter +instanceKlass sun/util/locale/provider/LocaleServiceProviderPool$LocalizedObjectGetter +instanceKlass sun/util/locale/provider/LocaleServiceProviderPool +instanceKlass java/util/Locale$Builder +instanceKlass sun/util/locale/provider/CalendarDataUtility +instanceKlass sun/util/calendar/CalendarSystem$GregorianHolder +instanceKlass sun/util/calendar/CalendarSystem +instanceKlass java/util/Calendar$Builder +instanceKlass java/util/StringTokenizer +instanceKlass sun/util/locale/provider/AvailableLanguageTags +instanceKlass java/util/ServiceLoader$ProviderImpl +instanceKlass java/util/ServiceLoader$Provider +instanceKlass java/util/ServiceLoader$1 +instanceKlass sun/util/resources/cldr/provider/CLDRLocaleDataMetaInfo +instanceKlass jdk/internal/module/ModulePatcher$PatchedModuleReader +instanceKlass java/util/ServiceLoader$3 +instanceKlass java/util/ServiceLoader$2 +instanceKlass java/util/ServiceLoader$LazyClassPathLookupIterator +instanceKlass java/util/concurrent/CopyOnWriteArrayList$COWIterator +instanceKlass java/util/ServiceLoader$ModuleServicesLookupIterator +instanceKlass java/util/ServiceLoader +instanceKlass sun/util/locale/LocaleObjectCache +instanceKlass sun/util/locale/BaseLocale$Key +instanceKlass sun/util/locale/InternalLocaleBuilder$CaseInsensitiveChar +instanceKlass sun/util/locale/InternalLocaleBuilder +instanceKlass sun/util/locale/StringTokenIterator +instanceKlass sun/util/locale/ParseStatus +instanceKlass sun/util/locale/LanguageTag +instanceKlass sun/util/cldr/CLDRBaseLocaleDataMetaInfo +instanceKlass sun/util/locale/provider/LocaleDataMetaInfo +instanceKlass sun/util/locale/provider/ResourceBundleBasedAdapter +instanceKlass sun/util/locale/provider/LocaleProviderAdapter$1 +instanceKlass sun/util/locale/provider/LocaleProviderAdapter +instanceKlass java/util/spi/LocaleServiceProvider +instanceKlass sun/util/calendar/ZoneInfoFile$ZoneOffsetTransitionRule +instanceKlass sun/util/calendar/ZoneInfoFile$1 +instanceKlass sun/util/calendar/ZoneInfoFile +instanceKlass java/util/TimeZone +instanceKlass java/util/Calendar +instanceKlass java/text/AttributedCharacterIterator$Attribute +instanceKlass com/google/inject/matcher/AbstractMatcher +instanceKlass com/google/inject/matcher/Matcher +instanceKlass com/google/inject/spi/TypeConverter +instanceKlass org/codehaus/plexus/DefaultPlexusContainer$LoggerProvider +instanceKlass org/codehaus/plexus/DefaultPlexusContainer$DefaultsModule +instanceKlass org/codehaus/plexus/DefaultPlexusContainer$ContainerModule +instanceKlass java/util/concurrent/locks/ReentrantReadWriteLock$WriteLock +instanceKlass java/util/concurrent/locks/ReentrantReadWriteLock$ReadLock +instanceKlass java/util/concurrent/locks/ReentrantReadWriteLock +instanceKlass java/util/concurrent/locks/ReadWriteLock +instanceKlass org/eclipse/sisu/inject/ImplicitBindings +instanceKlass org/eclipse/sisu/inject/MildValues$InverseMapping +instanceKlass org/eclipse/sisu/inject/MildValues +instanceKlass org/eclipse/sisu/inject/Weak +instanceKlass sun/reflect/misc/ReflectUtil +instanceKlass java/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl$1 +instanceKlass java/util/concurrent/atomic/AtomicReferenceFieldUpdater +instanceKlass org/eclipse/sisu/inject/RankedSequence$Content +instanceKlass org/eclipse/sisu/inject/RankedSequence +instanceKlass org/eclipse/sisu/inject/BindingSubscriber +instanceKlass org/eclipse/sisu/inject/DefaultBeanLocator +instanceKlass org/eclipse/sisu/inject/DeferredClass +instanceKlass org/codehaus/plexus/DefaultPlexusContainer$LoggerManagerProvider +instanceKlass org/eclipse/sisu/inject/DeferredProvider +instanceKlass com/google/inject/Provider +instanceKlass com/google/inject/AbstractModule +instanceKlass org/codehaus/plexus/context/Context +instanceKlass org/eclipse/sisu/inject/BindingPublisher +instanceKlass org/eclipse/sisu/inject/RankingFunction +instanceKlass org/eclipse/sisu/space/ClassSpace +instanceKlass javax/inject/Provider +instanceKlass org/eclipse/sisu/bean/BeanManager +instanceKlass org/eclipse/sisu/plexus/PlexusBeanLocator +instanceKlass org/codehaus/plexus/classworlds/ClassWorldListener +instanceKlass com/google/inject/Module +instanceKlass org/eclipse/sisu/inject/MutableBeanLocator +instanceKlass org/eclipse/sisu/inject/BeanLocator +instanceKlass org/codehaus/plexus/DefaultPlexusContainer +instanceKlass org/codehaus/plexus/MutablePlexusContainer +instanceKlass java/util/stream/ReduceOps$AccumulatingSink +instanceKlass java/util/stream/ReduceOps$Box +instanceKlass java/util/stream/ReduceOps$ReduceOp +instanceKlass java/util/stream/ReduceOps +instanceKlass java/util/function/BinaryOperator +instanceKlass java/util/stream/Collectors$CollectorImpl +instanceKlass java/util/stream/Collector +instanceKlass java/util/stream/Collectors +instanceKlass sun/invoke/util/VerifyAccess$1 +instanceKlass java/util/HashMap$HashMapSpliterator +instanceKlass org/apache/maven/extension/internal/CoreExports +instanceKlass java/util/Collections$UnmodifiableCollection$1 +instanceKlass org/codehaus/plexus/DefaultContainerConfiguration +instanceKlass org/codehaus/plexus/ContainerConfiguration +instanceKlass org/codehaus/plexus/util/BaseIOUtil +instanceKlass org/codehaus/plexus/util/xml/XMLWriter +instanceKlass org/codehaus/plexus/util/xml/Xpp3Dom +instanceKlass org/codehaus/plexus/util/xml/pull/MXParser +instanceKlass org/codehaus/plexus/util/xml/pull/XmlPullParser +instanceKlass org/codehaus/plexus/util/xml/Xpp3DomBuilder +instanceKlass java/util/regex/ASCII +instanceKlass org/codehaus/plexus/util/ReaderFactory +instanceKlass org/apache/maven/project/ExtensionDescriptor +instanceKlass org/apache/maven/project/ExtensionDescriptorBuilder +instanceKlass org/apache/maven/extension/internal/CoreExtensionEntry +instanceKlass org/codehaus/plexus/logging/Logger +instanceKlass org/apache/maven/cli/logging/Slf4jLoggerManager +instanceKlass org/slf4j/impl/MavenSlf4jSimpleFriend +instanceKlass org/slf4j/MavenSlf4jFriend +instanceKlass java/lang/Class$1 +instanceKlass org/apache/maven/cli/logging/BaseSlf4jConfiguration +instanceKlass org/codehaus/plexus/util/PropertyUtils +instanceKlass org/apache/maven/cli/logging/Slf4jConfiguration +instanceKlass org/apache/maven/cli/logging/Slf4jConfigurationFactory +instanceKlass org/slf4j/impl/OutputChoice +instanceKlass sun/net/DefaultProgressMeteringPolicy +instanceKlass sun/net/ProgressMeteringPolicy +instanceKlass sun/net/ProgressMonitor +instanceKlass org/slf4j/impl/SimpleLoggerConfiguration$1 +instanceKlass java/text/Format +instanceKlass org/slf4j/impl/SimpleLoggerConfiguration +instanceKlass org/slf4j/helpers/NamedLoggerBase +instanceKlass org/slf4j/impl/SimpleLoggerFactory +instanceKlass org/slf4j/impl/StaticLoggerBinder +instanceKlass org/slf4j/spi/LoggerFactoryBinder +instanceKlass java/util/Collections$3 +instanceKlass java/net/URLClassLoader$3$1 +instanceKlass java/net/URLClassLoader$3 +instanceKlass jdk/internal/loader/URLClassPath$1 +instanceKlass java/lang/CompoundEnumeration +instanceKlass jdk/internal/loader/BuiltinClassLoader$1 +instanceKlass java/util/Collections$EmptyEnumeration +instanceKlass org/slf4j/helpers/Util +instanceKlass org/slf4j/helpers/NOPLoggerFactory +instanceKlass java/util/concurrent/LinkedBlockingQueue$Node +instanceKlass java/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject +instanceKlass java/util/concurrent/locks/Condition +instanceKlass java/util/concurrent/BlockingQueue +instanceKlass org/slf4j/helpers/SubstituteLoggerFactory +instanceKlass org/slf4j/ILoggerFactory +instanceKlass org/slf4j/event/LoggingEvent +instanceKlass org/slf4j/LoggerFactory +instanceKlass java/util/LinkedList$ListItr +instanceKlass org/codehaus/plexus/util/StringUtils +instanceKlass org/apache/maven/cli/CLIReportingUtils +instanceKlass java/util/function/BiConsumer +instanceKlass org/codehaus/plexus/interpolation/SimpleRecursionInterceptor +instanceKlass org/codehaus/plexus/interpolation/AbstractValueSource +instanceKlass org/codehaus/plexus/interpolation/RecursionInterceptor +instanceKlass org/codehaus/plexus/interpolation/StringSearchInterpolator +instanceKlass org/codehaus/plexus/interpolation/Interpolator +instanceKlass org/codehaus/plexus/interpolation/BasicInterpolator +instanceKlass org/apache/maven/properties/internal/SystemProperties +instanceKlass java/util/Collections$SynchronizedCollection +instanceKlass java/util/Properties$EntrySet +instanceKlass java/lang/ProcessEnvironment$StringEntry +instanceKlass java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry +instanceKlass java/lang/ProcessEnvironment$StringEntrySet$1 +instanceKlass java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1 +instanceKlass org/codehaus/plexus/util/Os +instanceKlass org/apache/maven/properties/internal/EnvironmentUtils +instanceKlass java/util/stream/Sink$ChainedReference +instanceKlass java/util/stream/FindOps$FindOp +instanceKlass java/util/stream/TerminalOp +instanceKlass java/util/stream/FindOps$FindSink +instanceKlass java/util/stream/TerminalSink +instanceKlass java/util/stream/Sink +instanceKlass java/util/stream/FindOps +instanceKlass java/util/function/Predicate +instanceKlass sun/reflect/annotation/AnnotationParser +instanceKlass java/lang/Class$3 +instanceKlass java/util/EnumMap$1 +instanceKlass java/util/stream/StreamOpFlag$MaskBuilder +instanceKlass java/util/stream/Stream +instanceKlass java/util/stream/BaseStream +instanceKlass java/util/stream/PipelineHelper +instanceKlass java/util/stream/StreamSupport +instanceKlass java/util/ArrayList$ArrayListSpliterator +instanceKlass java/util/Spliterator +instanceKlass java/util/AbstractList$Itr +instanceKlass org/apache/commons/cli/DefaultParser +instanceKlass org/apache/commons/cli/Util +instanceKlass org/apache/commons/cli/CommandLine$Builder +instanceKlass org/apache/commons/cli/CommandLine +instanceKlass java/util/Collections$UnmodifiableCollection +instanceKlass java/util/LinkedHashMap$LinkedHashIterator +instanceKlass java/util/function/Consumer +instanceKlass org/apache/commons/cli/Parser +instanceKlass org/apache/maven/cli/CleanArgument +instanceKlass org/apache/commons/cli/OptionValidator +instanceKlass org/apache/commons/cli/Option$Builder +instanceKlass org/apache/commons/cli/Option +instanceKlass org/apache/commons/cli/Options +instanceKlass org/apache/commons/cli/CommandLineParser +instanceKlass org/apache/maven/cli/CLIManager +instanceKlass org/apache/maven/cli/logging/Slf4jStdoutLogger +instanceKlass org/eclipse/aether/DefaultRepositoryCache +instanceKlass org/apache/maven/project/ProjectBuildingRequest +instanceKlass org/apache/maven/execution/DefaultMavenExecutionRequest +instanceKlass org/apache/maven/execution/MavenExecutionRequest +instanceKlass java/lang/ApplicationShutdownHooks$1 +instanceKlass java/lang/ApplicationShutdownHooks +instanceKlass org/fusesource/jansi/AnsiConsole$2 +instanceKlass java/lang/ProcessEnvironment$ExternalData +instanceKlass java/lang/ProcessEnvironment +instanceKlass jdk/internal/loader/NativeLibraries$Unloader +instanceKlass java/lang/Shutdown$Lock +instanceKlass java/lang/Shutdown +instanceKlass java/io/DeleteOnExitHook$1 +instanceKlass java/io/DeleteOnExitHook +instanceKlass sun/nio/fs/UnixChannelFactory$1 +instanceKlass java/io/FileOutputStream$1 +instanceKlass java/util/IdentityHashMap$IdentityHashMapIterator +instanceKlass java/util/regex/IntHashSet +instanceKlass java/util/regex/Matcher +instanceKlass java/util/regex/MatchResult +instanceKlass sun/nio/fs/UnixFileKey +instanceKlass sun/net/www/protocol/jar/JarFileFactory +instanceKlass sun/net/www/protocol/jar/URLJarFile$URLJarFileCloseController +instanceKlass java/util/Random +instanceKlass java/util/random/RandomGenerator +instanceKlass java/net/URLClassLoader$2 +instanceKlass jdk/internal/jimage/ImageLocation +instanceKlass jdk/internal/jimage/decompressor/Decompressor +instanceKlass jdk/internal/jimage/ImageStringsReader +instanceKlass jdk/internal/jimage/ImageStrings +instanceKlass java/util/Formattable +instanceKlass java/util/Formatter$Flags +instanceKlass java/util/Formatter$FormatSpecifier +instanceKlass java/util/Formatter$Conversion +instanceKlass java/util/Formatter$FixedString +instanceKlass java/util/Formatter$FormatString +instanceKlass jdk/internal/jimage/ImageHeader +instanceKlass jdk/internal/jimage/NativeImageBuffer$1 +instanceKlass jdk/internal/jimage/NativeImageBuffer +instanceKlass java/util/Formatter +instanceKlass jdk/internal/jimage/BasicImageReader$1 +instanceKlass java/util/LinkedList$Node +instanceKlass jdk/internal/jimage/BasicImageReader +instanceKlass jdk/internal/jimage/ImageReader +instanceKlass jdk/internal/jimage/ImageReaderFactory$1 +instanceKlass jdk/internal/jimage/ImageReaderFactory +instanceKlass jdk/internal/module/SystemModuleFinders$SystemImage +instanceKlass org/fusesource/jansi/internal/OSInfo +instanceKlass jdk/internal/module/SystemModuleFinders$SystemModuleReader +instanceKlass java/lang/module/ModuleReader +instanceKlass jdk/internal/loader/BuiltinClassLoader$5 +instanceKlass jdk/internal/loader/BuiltinClassLoader$2 +instanceKlass jdk/internal/module/Resources +instanceKlass org/fusesource/jansi/internal/JansiLoader$1 +instanceKlass org/fusesource/jansi/internal/JansiLoader +instanceKlass org/fusesource/jansi/internal/CLibrary +instanceKlass org/fusesource/jansi/io/AnsiProcessor +instanceKlass org/fusesource/jansi/io/AnsiOutputStream$WidthSupplier +instanceKlass org/fusesource/jansi/AnsiConsole +instanceKlass java/util/concurrent/Callable +instanceKlass org/fusesource/jansi/Ansi +instanceKlass org/apache/maven/shared/utils/logging/LoggerLevelRenderer +instanceKlass org/apache/maven/shared/utils/logging/MessageBuilder +instanceKlass org/apache/maven/shared/utils/logging/MessageUtils +instanceKlass java/util/regex/CharPredicates +instanceKlass java/util/regex/Pattern$BitClass +instanceKlass java/util/regex/Pattern$TreeInfo +instanceKlass java/util/regex/Pattern$BmpCharPredicate +instanceKlass java/util/regex/Pattern$CharPredicate +instanceKlass java/util/regex/Pattern$Node +instanceKlass java/util/regex/Pattern +instanceKlass org/apache/maven/cli/CliRequest +instanceKlass org/codehaus/plexus/interpolation/ValueSource +instanceKlass org/apache/maven/execution/ExecutionListener +instanceKlass org/eclipse/aether/transfer/TransferListener +instanceKlass org/apache/maven/toolchain/building/ToolchainsBuildingRequest +instanceKlass org/apache/maven/building/Source +instanceKlass org/codehaus/plexus/logging/LoggerManager +instanceKlass org/slf4j/Logger +instanceKlass org/apache/maven/eventspy/EventSpy$Context +instanceKlass org/codehaus/plexus/PlexusContainer +instanceKlass org/apache/maven/exception/ExceptionHandler +instanceKlass org/eclipse/aether/RepositoryCache +instanceKlass org/apache/maven/cli/MavenCli +instanceKlass java/io/FilePermissionCollection$1 +instanceKlass java/util/function/BiFunction +instanceKlass java/security/Security$2 +instanceKlass jdk/internal/access/JavaSecurityPropertiesAccess +instanceKlass java/util/concurrent/ConcurrentHashMap$MapEntry +instanceKlass java/io/FileInputStream$1 +instanceKlass java/util/Properties$LineReader +instanceKlass java/security/Security$1 +instanceKlass java/security/Security +instanceKlass sun/security/util/SecurityProperties +instanceKlass sun/security/util/FilePermCompat +instanceKlass java/io/FilePermission$1 +instanceKlass jdk/internal/access/JavaIOFilePermissionAccess +instanceKlass sun/net/www/MessageHeader +instanceKlass java/net/URLConnection +instanceKlass java/util/TreeMap$Entry +instanceKlass java/io/RandomAccessFile$1 +instanceKlass java/net/URLClassLoader$1 +instanceKlass java/util/TreeMap$PrivateEntryIterator +instanceKlass java/util/TimSort +instanceKlass java/util/Arrays$LegacyMergeSort +instanceKlass java/lang/invoke/LambdaFormBuffer +instanceKlass java/lang/invoke/LambdaFormEditor$TransformKey +instanceKlass java/lang/invoke/LambdaFormEditor +instanceKlass sun/invoke/util/Wrapper$1 +instanceKlass java/lang/invoke/DelegatingMethodHandle$Holder +instanceKlass java/lang/invoke/DirectMethodHandle$2 +instanceKlass java/lang/invoke/ClassSpecializer$Factory +instanceKlass java/lang/invoke/ClassSpecializer$SpeciesData +instanceKlass java/lang/invoke/ClassSpecializer$1 +instanceKlass java/lang/invoke/ClassSpecializer +instanceKlass java/lang/invoke/InnerClassLambdaMetafactory$1 +instanceKlass jdk/internal/org/objectweb/asm/ClassReader +instanceKlass java/lang/invoke/LambdaProxyClassArchive +instanceKlass java/lang/invoke/InfoFromMemberName +instanceKlass java/lang/invoke/MethodHandleInfo +instanceKlass jdk/internal/org/objectweb/asm/ConstantDynamic +instanceKlass jdk/internal/org/objectweb/asm/Handle +instanceKlass sun/security/action/GetBooleanAction +instanceKlass java/lang/invoke/AbstractValidatingLambdaMetafactory +instanceKlass java/lang/invoke/MethodHandleImpl$1 +instanceKlass jdk/internal/access/JavaLangInvokeAccess +instanceKlass java/lang/invoke/Invokers$Holder +instanceKlass java/lang/invoke/BootstrapMethodInvoker +instanceKlass java/lang/invoke/MethodHandles$Lookup$ClassDefiner +instanceKlass java/lang/invoke/MethodHandles$Lookup$ClassFile +instanceKlass jdk/internal/org/objectweb/asm/Handler +instanceKlass jdk/internal/org/objectweb/asm/Attribute +instanceKlass jdk/internal/org/objectweb/asm/FieldVisitor +instanceKlass sun/invoke/empty/Empty +instanceKlass sun/invoke/util/VerifyType +instanceKlass java/lang/invoke/InvokerBytecodeGenerator$ClassData +instanceKlass jdk/internal/org/objectweb/asm/AnnotationVisitor +instanceKlass jdk/internal/org/objectweb/asm/Frame +instanceKlass jdk/internal/org/objectweb/asm/Label +instanceKlass jdk/internal/org/objectweb/asm/Type +instanceKlass jdk/internal/org/objectweb/asm/MethodVisitor +instanceKlass sun/invoke/util/BytecodeDescriptor +instanceKlass jdk/internal/org/objectweb/asm/ByteVector +instanceKlass jdk/internal/org/objectweb/asm/Symbol +instanceKlass jdk/internal/org/objectweb/asm/SymbolTable +instanceKlass jdk/internal/org/objectweb/asm/ClassVisitor +instanceKlass java/io/FilenameFilter +instanceKlass java/lang/invoke/InvokerBytecodeGenerator$2 +instanceKlass java/lang/invoke/InvokerBytecodeGenerator +instanceKlass java/lang/invoke/LambdaForm$Holder +instanceKlass java/lang/invoke/LambdaForm$Name +instanceKlass java/lang/reflect/Array +instanceKlass java/lang/invoke/Invokers +instanceKlass java/lang/invoke/MethodHandleImpl +instanceKlass sun/invoke/util/ValueConversions +instanceKlass java/lang/invoke/DirectMethodHandle$Holder +instanceKlass java/lang/invoke/LambdaForm$NamedFunction +instanceKlass sun/invoke/util/Wrapper$Format +instanceKlass java/lang/invoke/MethodTypeForm +instanceKlass java/lang/invoke/MethodType$ConcurrentWeakInternSet +instanceKlass java/lang/invoke/LambdaMetafactory +instanceKlass java/util/ArrayList$Itr +instanceKlass org/codehaus/plexus/classworlds/strategy/AbstractStrategy +instanceKlass org/codehaus/plexus/classworlds/strategy/Strategy +instanceKlass org/codehaus/plexus/classworlds/strategy/StrategyFactory +instanceKlass java/util/NavigableMap +instanceKlass java/util/SortedMap +instanceKlass java/util/NavigableSet +instanceKlass java/util/SortedSet +instanceKlass java/lang/StringUTF16 +instanceKlass sun/nio/ch/IOStatus +instanceKlass java/nio/DirectByteBuffer$Deallocator +instanceKlass sun/nio/ch/Util$BufferCache +instanceKlass sun/nio/ch/Util +instanceKlass sun/nio/ch/NativeThread +instanceKlass java/nio/charset/CoderResult +instanceKlass java/nio/charset/CharsetDecoder +instanceKlass java/nio/charset/StandardCharsets +instanceKlass java/io/Reader +instanceKlass java/lang/Readable +instanceKlass org/codehaus/plexus/classworlds/launcher/ConfigurationParser +instanceKlass org/codehaus/plexus/classworlds/launcher/Configurator +instanceKlass org/codehaus/plexus/classworlds/launcher/ConfigurationHandler +instanceKlass java/nio/channels/Channels +instanceKlass sun/nio/ch/FileChannelImpl$Closer +instanceKlass sun/nio/ch/NativeDispatcher +instanceKlass sun/nio/ch/NativeThreadSet +instanceKlass sun/nio/ch/IOUtil +instanceKlass java/nio/channels/spi/AbstractInterruptibleChannel +instanceKlass java/nio/channels/InterruptibleChannel +instanceKlass java/nio/channels/ScatteringByteChannel +instanceKlass java/nio/channels/GatheringByteChannel +instanceKlass java/nio/channels/SeekableByteChannel +instanceKlass java/nio/channels/ByteChannel +instanceKlass java/nio/channels/WritableByteChannel +instanceKlass java/nio/channels/ReadableByteChannel +instanceKlass java/nio/channels/Channel +instanceKlass java/util/Collections$EmptyIterator +instanceKlass sun/nio/fs/UnixChannelFactory$Flags +instanceKlass sun/nio/fs/UnixChannelFactory +instanceKlass sun/nio/fs/UnixFileModeAttribute +instanceKlass java/nio/file/attribute/FileAttribute +instanceKlass java/net/URI$Parser +instanceKlass java/nio/file/FileSystems$DefaultFileSystemHolder$1 +instanceKlass java/nio/file/FileSystems$DefaultFileSystemHolder +instanceKlass java/nio/file/FileSystems +instanceKlass java/nio/file/Paths +instanceKlass java/lang/Void +instanceKlass java/lang/PublicMethods$Key +instanceKlass java/lang/PublicMethods$MethodList +instanceKlass org/codehaus/plexus/classworlds/ClassWorld +instanceKlass java/lang/Class$Atomic +instanceKlass java/lang/Class$ReflectionData +instanceKlass org/codehaus/plexus/classworlds/launcher/Launcher +instanceKlass java/security/SecureClassLoader$DebugHolder +instanceKlass java/security/PermissionCollection +instanceKlass java/security/SecureClassLoader$1 +instanceKlass java/security/SecureClassLoader$CodeSourceKey +instanceKlass java/util/zip/Checksum$1 +instanceKlass java/util/zip/CRC32 +instanceKlass java/util/zip/Checksum +instanceKlass sun/nio/ByteBuffered +instanceKlass java/lang/Package$VersionInfo +instanceKlass java/lang/NamedPackage +instanceKlass java/util/jar/Attributes +instanceKlass jdk/internal/loader/Resource +instanceKlass sun/security/action/GetIntegerAction +instanceKlass sun/security/util/Debug +instanceKlass sun/security/util/SignatureFileVerifier +instanceKlass java/util/zip/ZipFile$InflaterCleanupAction +instanceKlass java/util/zip/Inflater$InflaterZStreamRef +instanceKlass java/util/zip/Inflater +instanceKlass java/util/zip/ZipEntry +instanceKlass jdk/internal/util/jar/JarIndex +instanceKlass java/nio/Bits$1 +instanceKlass jdk/internal/misc/VM$BufferPool +instanceKlass java/nio/Bits +instanceKlass sun/nio/ch/DirectBuffer +instanceKlass jdk/internal/perf/PerfCounter$CoreCounters +instanceKlass jdk/internal/perf/Perf +instanceKlass jdk/internal/perf/Perf$GetPerfAction +instanceKlass jdk/internal/perf/PerfCounter +instanceKlass java/nio/file/attribute/FileTime +instanceKlass java/util/zip/ZipUtils +instanceKlass java/util/zip/ZipFile$Source$End +instanceKlass java/io/RandomAccessFile$2 +instanceKlass jdk/internal/access/JavaIORandomAccessFileAccess +instanceKlass java/io/RandomAccessFile +instanceKlass java/io/DataInput +instanceKlass java/io/DataOutput +instanceKlass sun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes +instanceKlass sun/nio/fs/NativeBuffer$Deallocator +instanceKlass sun/nio/fs/NativeBuffer +instanceKlass java/lang/ThreadLocal$ThreadLocalMap +instanceKlass sun/nio/fs/NativeBuffers +instanceKlass sun/nio/fs/AbstractBasicFileAttributeView +instanceKlass sun/nio/fs/DynamicFileAttributeView +instanceKlass sun/nio/fs/UnixFileAttributeViews +instanceKlass java/nio/file/attribute/UserDefinedFileAttributeView +instanceKlass java/nio/file/attribute/DosFileAttributeView +instanceKlass java/nio/file/attribute/BasicFileAttributeView +instanceKlass java/nio/file/attribute/FileAttributeView +instanceKlass java/nio/file/attribute/AttributeView +instanceKlass java/nio/file/attribute/DosFileAttributes +instanceKlass java/nio/file/Files +instanceKlass java/nio/file/CopyOption +instanceKlass java/util/zip/ZipFile$Source$Key +instanceKlass sun/nio/fs/UnixMountEntry +instanceKlass sun/nio/fs/UnixFileStoreAttributes +instanceKlass sun/nio/fs/UnixFileAttributes +instanceKlass java/nio/file/attribute/PosixFileAttributes +instanceKlass java/nio/file/attribute/BasicFileAttributes +instanceKlass java/util/Enumeration +instanceKlass java/util/concurrent/ConcurrentHashMap$Traverser +instanceKlass java/util/concurrent/ConcurrentHashMap$CollectionView +instanceKlass jdk/internal/loader/NativeLibraries$NativeLibraryImpl +instanceKlass jdk/internal/loader/NativeLibrary +instanceKlass java/util/ArrayDeque$DeqIterator +instanceKlass jdk/internal/loader/NativeLibraries$1 +instanceKlass jdk/internal/loader/NativeLibraries$LibraryPaths +instanceKlass sun/nio/fs/UnixNativeDispatcher +instanceKlass sun/nio/fs/Util +instanceKlass sun/nio/fs/UnixPath +instanceKlass java/nio/file/Path +instanceKlass java/nio/file/Watchable +instanceKlass java/nio/file/FileSystem +instanceKlass java/nio/file/OpenOption +instanceKlass java/nio/file/spi/FileSystemProvider +instanceKlass sun/nio/fs/DefaultFileSystemProvider +instanceKlass java/util/zip/ZipFile$Source +instanceKlass java/lang/ref/Cleaner$Cleanable +instanceKlass jdk/internal/ref/CleanerImpl +instanceKlass java/lang/ref/Cleaner$1 +instanceKlass java/lang/ref/Cleaner +instanceKlass jdk/internal/ref/CleanerFactory$1 +instanceKlass java/util/concurrent/ThreadFactory +instanceKlass jdk/internal/ref/CleanerFactory +instanceKlass java/util/zip/ZipCoder +instanceKlass java/util/zip/ZipFile$CleanableResource +instanceKlass java/lang/Runtime$Version +instanceKlass java/util/jar/JavaUtilJarAccessImpl +instanceKlass jdk/internal/access/JavaUtilJarAccess +instanceKlass jdk/internal/loader/FileURLMapper +instanceKlass jdk/internal/loader/URLClassPath$JarLoader$1 +instanceKlass java/util/zip/ZipFile$1 +instanceKlass jdk/internal/access/JavaUtilZipFileAccess +instanceKlass java/util/zip/ZipFile +instanceKlass java/util/zip/ZipConstants +instanceKlass jdk/internal/loader/URLClassPath$Loader +instanceKlass jdk/internal/loader/URLClassPath$3 +instanceKlass java/security/PrivilegedExceptionAction +instanceKlass sun/util/locale/LocaleUtils +instanceKlass java/util/Locale +instanceKlass sun/net/util/URLUtil +instanceKlass java/lang/StringCoding +instanceKlass sun/launcher/LauncherHelper +instanceKlass java/lang/invoke/StringConcatFactory$3 +instanceKlass java/lang/invoke/StringConcatFactory$2 +instanceKlass java/lang/invoke/StringConcatFactory$1 +instanceKlass java/lang/invoke/StringConcatFactory +instanceKlass java/lang/ModuleLayer$Controller +instanceKlass java/util/concurrent/CopyOnWriteArrayList +instanceKlass jdk/internal/module/ServicesCatalog$ServiceProvider +instanceKlass jdk/internal/loader/AbstractClassLoaderValue$Memoizer +instanceKlass jdk/internal/module/ModuleLoaderMap +instanceKlass java/util/ImmutableCollections$ListItr +instanceKlass java/util/ListIterator +instanceKlass java/util/ImmutableCollections$Set12$1 +instanceKlass java/util/ImmutableCollections$SetN$SetNIterator +instanceKlass jdk/internal/loader/BuiltinClassLoader$LoadedModule +instanceKlass jdk/internal/loader/BootLoader +instanceKlass java/util/Optional +instanceKlass jdk/internal/loader/AbstractClassLoaderValue +instanceKlass jdk/internal/module/ServicesCatalog +instanceKlass jdk/internal/util/Preconditions +instanceKlass sun/net/util/IPAddressUtil +instanceKlass java/net/URLStreamHandler +instanceKlass java/util/HexFormat +instanceKlass sun/net/www/ParseUtil +instanceKlass java/net/URL$3 +instanceKlass jdk/internal/access/JavaNetURLAccess +instanceKlass java/net/URL$DefaultFactory +instanceKlass java/net/URLStreamHandlerFactory +instanceKlass jdk/internal/loader/URLClassPath +instanceKlass java/util/Deque +instanceKlass java/util/Queue +instanceKlass jdk/internal/loader/ClassLoaderHelper +instanceKlass jdk/internal/loader/NativeLibraries +instanceKlass java/security/Principal +instanceKlass java/security/ProtectionDomain$Key +instanceKlass java/security/ProtectionDomain$JavaSecurityAccessImpl +instanceKlass jdk/internal/access/JavaSecurityAccess +instanceKlass java/lang/ClassLoader$ParallelLoaders +instanceKlass java/security/cert/Certificate +instanceKlass jdk/internal/loader/ArchivedClassLoaders +instanceKlass java/net/URI$1 +instanceKlass jdk/internal/access/JavaNetUriAccess +instanceKlass jdk/internal/module/ArchivedBootLayer +instanceKlass jdk/internal/module/ModuleBootstrap$Counters +instanceKlass jdk/internal/module/ModulePatcher +instanceKlass jdk/internal/util/ArraysSupport +instanceKlass java/io/FileSystem +instanceKlass java/io/DefaultFileSystem +instanceKlass java/io/File +instanceKlass java/lang/module/ModuleDescriptor$1 +instanceKlass jdk/internal/access/JavaLangModuleAccess +instanceKlass java/lang/reflect/Modifier +instanceKlass sun/invoke/util/VerifyAccess +instanceKlass jdk/internal/module/ModuleBootstrap +instanceKlass sun/security/action/GetPropertyAction +instanceKlass java/lang/invoke/MethodHandleStatics +instanceKlass java/util/Collections +instanceKlass jdk/internal/misc/OSEnvironment +instanceKlass jdk/internal/misc/Signal$NativeHandler +instanceKlass java/util/Hashtable$Entry +instanceKlass jdk/internal/misc/Signal +instanceKlass java/lang/Terminator$1 +instanceKlass jdk/internal/misc/Signal$Handler +instanceKlass java/lang/Terminator +instanceKlass java/nio/ByteOrder +instanceKlass java/nio/Buffer$1 +instanceKlass jdk/internal/access/JavaNioAccess +instanceKlass jdk/internal/misc/ScopedMemoryAccess +instanceKlass java/nio/charset/CodingErrorAction +instanceKlass java/nio/charset/CharsetEncoder +instanceKlass java/io/Writer +instanceKlass sun/nio/cs/HistoricallyNamedCharset +instanceKlass java/lang/ThreadLocal +instanceKlass java/nio/charset/spi/CharsetProvider +instanceKlass java/nio/charset/Charset +instanceKlass java/io/OutputStream +instanceKlass java/io/Flushable +instanceKlass java/io/FileDescriptor$1 +instanceKlass jdk/internal/access/JavaIOFileDescriptorAccess +instanceKlass java/io/FileDescriptor +instanceKlass jdk/internal/util/StaticProperty +instanceKlass java/util/HashMap$HashIterator +instanceKlass java/lang/CharacterData +instanceKlass java/util/Arrays +instanceKlass java/lang/VersionProps +instanceKlass java/lang/StringConcatHelper +instanceKlass jdk/internal/misc/VM +instanceKlass jdk/internal/util/SystemProps$Raw +instanceKlass jdk/internal/util/SystemProps +instanceKlass java/lang/System$2 +instanceKlass jdk/internal/access/JavaLangAccess +instanceKlass java/lang/ref/Reference$1 +instanceKlass jdk/internal/access/JavaLangRefAccess +instanceKlass java/lang/ref/ReferenceQueue$Lock +instanceKlass java/lang/ref/ReferenceQueue +instanceKlass jdk/internal/reflect/ReflectionFactory +instanceKlass jdk/internal/reflect/ReflectionFactory$GetReflectionFactoryAction +instanceKlass java/security/PrivilegedAction +instanceKlass java/util/concurrent/locks/LockSupport +instanceKlass java/util/concurrent/ConcurrentHashMap$Node +instanceKlass java/util/concurrent/ConcurrentHashMap$CounterCell +instanceKlass java/util/concurrent/locks/ReentrantLock +instanceKlass java/util/concurrent/locks/Lock +instanceKlass java/lang/Runtime +instanceKlass java/util/KeyValueHolder +instanceKlass java/util/ImmutableCollections$MapN$MapNIterator +instanceKlass java/lang/Math +instanceKlass jdk/internal/reflect/Reflection +instanceKlass java/lang/invoke/MethodHandles$Lookup +instanceKlass java/lang/StringLatin1 +instanceKlass java/security/Permission +instanceKlass java/security/Guard +instanceKlass java/lang/invoke/MemberName$Factory +instanceKlass java/lang/invoke/MethodHandles +instanceKlass jdk/internal/access/SharedSecrets +instanceKlass java/lang/reflect/ReflectAccess +instanceKlass jdk/internal/access/JavaLangReflectAccess +instanceKlass java/util/Objects +instanceKlass jdk/internal/misc/CDS +instanceKlass java/lang/Module$ArchivedData +instanceKlass java/lang/String$CaseInsensitiveComparator +instanceKlass java/util/Comparator +instanceKlass java/io/ObjectStreamField +instanceKlass jdk/internal/math/FDBigInteger +instanceKlass java/lang/ModuleLayer +instanceKlass java/util/ImmutableCollections +instanceKlass jdk/internal/module/ModuleLoaderMap$Mapper +instanceKlass java/util/function/Function +instanceKlass java/lang/module/ResolvedModule +instanceKlass java/lang/module/Configuration +instanceKlass java/util/HashMap$Node +instanceKlass java/util/Map$Entry +instanceKlass java/util/Collections$UnmodifiableMap +instanceKlass jdk/internal/module/ModuleHashes +instanceKlass jdk/internal/module/ModuleTarget +instanceKlass java/lang/module/ModuleDescriptor$Opens +instanceKlass java/lang/module/ModuleDescriptor$Provides +instanceKlass jdk/internal/module/SystemModuleFinders$3 +instanceKlass jdk/internal/module/ModuleHashes$HashSupplier +instanceKlass jdk/internal/module/SystemModuleFinders$2 +instanceKlass java/util/function/Supplier +instanceKlass java/net/URI +instanceKlass java/lang/module/ModuleDescriptor$Exports +instanceKlass java/lang/Enum +instanceKlass java/lang/module/ModuleDescriptor$Requires +instanceKlass java/lang/module/ModuleDescriptor$Version +instanceKlass java/lang/module/ModuleDescriptor +instanceKlass java/lang/module/ModuleReference +instanceKlass java/util/Set +instanceKlass jdk/internal/module/SystemModuleFinders$SystemModuleFinder +instanceKlass java/lang/module/ModuleFinder +instanceKlass jdk/internal/module/ArchivedModuleGraph +instanceKlass sun/util/locale/BaseLocale +instanceKlass java/util/jar/Attributes$Name +instanceKlass java/lang/Character$CharacterCache +instanceKlass java/lang/Short$ShortCache +instanceKlass java/lang/Byte$ByteCache +instanceKlass java/lang/Long$LongCache +instanceKlass java/lang/Integer$IntegerCache +instanceKlass jdk/internal/vm/vector/VectorSupport$VectorPayload +instanceKlass jdk/internal/vm/vector/VectorSupport +instanceKlass java/lang/reflect/RecordComponent +instanceKlass java/util/Iterator +instanceKlass java/lang/Number +instanceKlass java/lang/Character +instanceKlass java/lang/Boolean +instanceKlass java/util/concurrent/locks/AbstractOwnableSynchronizer +instanceKlass java/lang/LiveStackFrame +instanceKlass java/lang/StackFrameInfo +instanceKlass java/lang/StackWalker$StackFrame +instanceKlass java/lang/StackStreamFactory$AbstractStackWalker +instanceKlass java/lang/StackWalker +instanceKlass java/nio/Buffer +instanceKlass java/lang/StackTraceElement +instanceKlass java/util/RandomAccess +instanceKlass java/util/List +instanceKlass java/util/AbstractCollection +instanceKlass java/util/Collection +instanceKlass java/lang/Iterable +instanceKlass java/util/concurrent/ConcurrentMap +instanceKlass java/util/AbstractMap +instanceKlass java/security/CodeSource +instanceKlass jdk/internal/loader/ClassLoaders +instanceKlass java/util/jar/Manifest +instanceKlass java/net/URL +instanceKlass java/io/InputStream +instanceKlass java/io/Closeable +instanceKlass java/lang/AutoCloseable +instanceKlass jdk/internal/module/Modules +instanceKlass jdk/internal/misc/Unsafe +instanceKlass jdk/internal/misc/UnsafeConstants +instanceKlass java/lang/AbstractStringBuilder +instanceKlass java/lang/Appendable +instanceKlass java/lang/AssertionStatusDirectives +instanceKlass java/lang/invoke/MethodHandleNatives$CallSiteContext +instanceKlass jdk/internal/invoke/NativeEntryPoint +instanceKlass java/lang/invoke/CallSite +instanceKlass java/lang/invoke/MethodType +instanceKlass java/lang/invoke/TypeDescriptor$OfMethod +instanceKlass java/lang/invoke/LambdaForm +instanceKlass java/lang/invoke/MethodHandleNatives +instanceKlass java/lang/invoke/ResolvedMethodName +instanceKlass java/lang/invoke/MemberName +instanceKlass java/lang/invoke/VarHandle +instanceKlass java/lang/invoke/MethodHandle +instanceKlass jdk/internal/reflect/CallerSensitive +instanceKlass java/lang/annotation/Annotation +instanceKlass jdk/internal/reflect/FieldAccessor +instanceKlass jdk/internal/reflect/ConstantPool +instanceKlass jdk/internal/reflect/ConstructorAccessor +instanceKlass jdk/internal/reflect/MethodAccessor +instanceKlass jdk/internal/reflect/MagicAccessorImpl +instanceKlass java/lang/reflect/Parameter +instanceKlass java/lang/reflect/Member +instanceKlass java/lang/reflect/AccessibleObject +instanceKlass java/lang/Module +instanceKlass java/util/Map +instanceKlass java/util/Dictionary +instanceKlass java/lang/ThreadGroup +instanceKlass java/lang/Thread$UncaughtExceptionHandler +instanceKlass java/lang/Thread +instanceKlass java/lang/Runnable +instanceKlass java/lang/ref/Reference +instanceKlass java/lang/Record +instanceKlass java/security/AccessController +instanceKlass java/security/AccessControlContext +instanceKlass java/security/ProtectionDomain +instanceKlass java/lang/SecurityManager +instanceKlass java/lang/Throwable +instanceKlass java/lang/System +instanceKlass java/lang/ClassLoader +instanceKlass java/lang/Cloneable +instanceKlass java/lang/Class +instanceKlass java/lang/invoke/TypeDescriptor$OfField +instanceKlass java/lang/invoke/TypeDescriptor +instanceKlass java/lang/reflect/Type +instanceKlass java/lang/reflect/GenericDeclaration +instanceKlass java/lang/reflect/AnnotatedElement +instanceKlass java/lang/String +instanceKlass java/lang/constant/ConstantDesc +instanceKlass java/lang/constant/Constable +instanceKlass java/lang/CharSequence +instanceKlass java/lang/Comparable +instanceKlass java/io/Serializable +ciInstanceKlass java/lang/Object 1 1 92 7 1 10 12 1 1 10 7 12 1 1 1 10 7 12 1 1 1 10 12 1 1 8 1 10 12 1 1 10 7 12 1 1 1 10 12 1 10 12 1 1 100 1 8 1 10 12 1 3 8 1 100 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 7 1 1 1 7 1 1 1 1 1 1 1 7 1 1 1 1 1 1 1 +ciMethod java/lang/Object equals (Ljava/lang/Object;)Z 580 0 6166 0 -1 +ciMethod java/lang/Object hashCode ()I 256 0 128 0 -1 +ciInstanceKlass java/lang/Class 1 1 1600 10 7 12 1 1 1 9 7 12 1 1 1 9 12 1 1 7 1 10 10 12 1 1 8 1 10 12 1 8 1 8 1 10 12 1 1 10 12 1 1 10 12 1 10 10 12 1 10 12 1 1 10 12 1 1 10 7 12 1 1 10 12 1 10 12 1 10 12 1 8 1 10 12 1 8 1 10 12 1 8 1 8 1 10 12 1 1 10 100 12 1 1 1 18 12 1 1 11 100 12 1 1 1 8 1 8 1 8 1 10 100 12 1 1 1 11 12 1 1 7 1 8 1 10 12 1 11 100 12 1 1 1 10 12 1 1 11 8 1 18 8 1 10 12 1 10 7 12 1 1 10 7 12 1 1 1 10 12 1 1 10 7 12 1 1 1 9 100 12 1 1 1 10 100 12 1 1 1 10 7 12 1 1 1 10 12 1 1 18 12 1 1 10 7 12 1 1 1 10 7 12 1 1 10 12 1 1 10 7 1 100 1 10 12 1 1 9 12 1 1 100 1 8 1 10 12 1 10 12 1 1 10 12 1 1 10 7 12 1 1 1 7 1 10 12 1 7 1 100 1 10 10 12 1 1 10 12 1 1 100 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 9 12 1 1 10 12 1 10 12 1 10 12 1 1 9 12 1 1 9 12 1 1 10 12 1 1 10 7 1 10 12 1 10 12 1 10 12 1 1 10 9 12 1 10 12 1 8 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 9 7 12 1 1 1 10 7 12 1 1 10 12 10 12 1 10 12 1 1 10 100 12 1 1 10 12 1 10 12 1 1 10 100 12 1 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 7 1 10 10 10 12 1 1 10 12 1 1 10 12 10 12 1 1 100 1 8 1 10 10 12 1 1 10 12 1 100 1 11 12 1 10 100 12 1 1 10 12 1 10 12 1 10 100 12 1 1 10 10 12 1 1 8 1 10 12 1 10 12 1 1 8 1 9 12 1 10 12 1 10 12 1 10 12 1 10 12 100 1 9 12 1 10 12 1 9 12 1 10 12 1 10 12 1 10 12 1 10 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 100 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 100 1 10 10 12 1 1 10 12 1 1 10 12 1 1 10 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 7 1 10 10 12 11 100 12 1 1 10 12 1 1 10 12 1 10 100 12 1 1 1 10 12 1 1 10 10 12 1 1 100 1 10 10 12 1 1 10 100 12 1 1 1 100 1 100 1 10 12 1 1 10 12 1 10 10 12 1 1 10 12 1 10 12 1 10 12 1 10 100 12 1 1 1 11 7 12 1 1 10 12 1 10 12 1 9 12 1 10 12 1 1 10 12 1 9 100 12 1 1 1 9 12 1 10 100 12 1 1 1 10 12 1 10 12 1 10 100 12 1 1 1 10 12 1 10 12 10 12 1 1 100 1 10 8 1 10 12 1 11 11 12 1 1 11 7 12 1 1 11 12 1 8 1 10 12 1 10 12 1 1 9 12 1 9 12 1 1 10 7 12 1 1 9 12 1 10 12 1 1 10 10 12 1 10 7 12 1 1 1 10 7 12 1 1 10 7 12 1 1 9 12 1 1 10 12 1 9 12 1 10 12 1 10 12 1 1 9 12 1 1 9 12 1 10 12 1 10 12 1 1 9 12 1 100 1 10 10 12 1 1 7 1 10 12 1 1 100 11 100 1 9 12 1 1 9 12 1 100 1 10 12 1 9 12 1 1 9 12 1 10 12 1 10 12 1 1 9 12 1 7 1 10 10 12 1 1 10 10 12 1 1 10 12 10 10 12 1 9 12 1 10 12 1 1 10 7 12 1 1 1 10 12 1 1 10 12 1 8 10 7 8 1 18 8 1 8 1 10 12 1 9 12 1 9 12 1 1 10 12 1 7 1 100 1 10 12 1 9 12 1 1 7 1 10 10 12 1 10 7 1 9 12 1 8 1 10 12 1 7 1 10 12 1 10 12 1 1 9 12 1 100 1 8 1 10 7 1 4 10 10 12 11 7 12 1 1 1 10 12 1 100 1 10 12 1 1 10 8 1 8 1 10 12 1 1 9 7 12 1 1 11 12 7 1 11 7 12 1 1 9 12 1 10 100 12 1 1 1 10 7 12 1 1 10 12 1 1 9 12 1 9 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 11 12 1 7 1 11 12 1 10 7 12 1 1 1 10 12 1 7 1 11 12 1 10 7 12 1 1 1 10 12 1 10 11 12 1 11 12 1 1 10 12 1 10 12 1 1 9 12 1 1 9 7 12 1 1 10 12 1 10 7 12 1 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 7 12 1 1 1 10 12 1 8 1 10 12 1 10 12 1 10 12 1 10 100 12 1 1 1 11 100 12 1 1 1 10 12 1 1 10 12 1 18 12 1 1 11 12 1 1 18 11 12 1 18 12 1 11 12 1 10 12 1 1 10 12 1 10 12 1 10 12 10 12 1 8 1 10 12 1 7 1 9 12 1 1 100 1 100 1 100 1 100 1 100 1 1 1 3 1 3 1 3 1 1 1 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 100 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 15 10 100 12 1 1 1 16 15 10 12 16 15 11 12 16 1 16 15 16 15 10 12 16 16 15 10 12 16 15 16 1 15 10 12 16 1 1 1 1 1 1 1 1 1 100 1 1 100 1 100 1 1 100 1 100 1 1 +staticfield java/lang/Class EMPTY_CLASS_ARRAY [Ljava/lang/Class; 0 [Ljava/lang/Class; +staticfield java/lang/Class serialPersistentFields [Ljava/io/ObjectStreamField; 0 [Ljava/io/ObjectStreamField; +ciInstanceKlass java/io/Serializable 1 0 7 100 1 100 1 1 1 +instanceKlass jdk/internal/vm/vector/VectorSupport$VectorShuffle +instanceKlass jdk/internal/vm/vector/VectorSupport$VectorMask +instanceKlass jdk/internal/vm/vector/VectorSupport$Vector +ciInstanceKlass jdk/internal/vm/vector/VectorSupport$VectorPayload 0 0 32 10 100 12 1 1 1 9 100 12 1 1 1 10 100 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass jdk/internal/vm/vector/VectorSupport$VectorShuffle 0 0 28 10 100 12 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 +ciInstanceKlass jdk/internal/vm/vector/VectorSupport$VectorMask 0 0 28 10 100 12 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 +ciInstanceKlass jdk/internal/vm/vector/VectorSupport$Vector 0 0 28 10 100 12 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 +ciInstanceKlass jdk/internal/vm/vector/VectorSupport 0 0 487 100 1 10 100 12 1 1 1 9 12 1 1 10 12 1 1 100 1 10 12 1 11 100 12 1 1 1 11 100 12 1 1 1 11 100 12 1 1 11 100 12 1 1 1 11 100 1 100 1 10 12 1 1 11 100 12 1 1 11 100 12 1 1 100 1 11 100 12 1 1 11 100 12 1 1 11 100 12 1 1 1 11 100 12 1 1 1 11 100 12 1 1 1 11 100 12 1 1 1 100 1 10 12 1 1 11 100 12 1 1 100 1 11 100 12 1 1 11 100 12 1 1 11 100 12 1 1 11 100 1 100 1 9 12 1 1 10 100 12 1 1 11 100 12 1 1 10 12 1 1 10 100 12 1 1 1 10 12 1 10 12 1 1 10 12 1 1 1 1 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 1 1 1 1 3 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/reflect/RecordComponent 0 0 196 10 100 12 1 1 1 9 100 12 1 1 1 9 12 1 1 9 12 1 10 12 1 1 10 12 1 1 10 100 12 1 1 1 10 12 1 1 9 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 100 12 1 1 10 100 12 1 1 9 12 1 1 10 100 12 1 1 1 11 100 12 1 1 1 10 9 100 12 1 1 1 10 100 12 1 1 1 9 12 1 1 10 100 12 1 1 1 10 12 1 1 11 100 12 1 1 10 100 12 1 1 100 1 9 12 1 9 12 1 1 9 12 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 100 1 10 10 12 1 10 12 1 1 8 1 10 12 1 10 12 1 9 12 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 100 1 1 +ciInstanceKlass java/util/Iterator 1 1 53 100 1 8 1 10 12 1 1 10 7 12 1 1 1 11 7 12 1 1 1 11 12 1 1 11 7 12 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/System 1 1 803 10 100 12 1 1 1 9 7 12 1 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 9 12 1 1 10 7 12 1 1 1 11 100 12 1 1 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 100 1 8 1 10 12 1 10 100 12 1 1 1 10 100 12 1 1 1 18 12 1 1 10 100 12 1 1 1 100 1 10 12 1 1 10 100 12 1 1 1 10 12 1 10 7 12 1 1 1 9 100 12 1 1 1 10 100 12 1 1 1 11 7 12 1 1 1 10 12 1 1 10 100 12 1 1 1 100 1 10 10 12 1 1 8 1 10 12 1 8 1 10 12 1 9 12 1 1 8 1 10 7 12 1 1 1 10 12 1 1 100 1 8 1 10 9 12 1 1 8 1 10 12 1 1 10 100 12 1 1 1 8 1 10 12 1 100 1 10 12 1 8 1 10 12 1 10 12 1 1 100 1 10 12 10 12 1 9 12 1 1 9 12 1 1 10 7 12 1 1 1 10 7 12 1 1 1 10 12 1 1 10 12 1 10 12 1 10 7 12 1 1 1 10 12 1 100 1 100 1 8 1 10 12 1 10 12 1 1 7 1 10 12 1 100 1 8 1 10 10 12 1 100 1 8 1 10 8 1 10 7 12 1 1 8 1 10 12 100 1 8 1 10 10 12 1 1 10 100 12 1 1 1 100 1 18 12 1 100 1 9 100 12 1 1 1 10 12 1 100 1 10 12 1 1 10 12 1 1 10 7 12 1 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 7 1 10 12 1 10 12 1 100 1 10 12 1 10 7 12 1 1 1 100 1 8 1 10 9 12 1 9 12 1 10 12 1 10 100 12 1 1 10 12 1 10 12 1 1 8 1 10 12 1 1 8 1 11 12 1 10 12 11 12 1 1 11 7 12 1 1 1 11 7 12 1 1 11 12 1 1 7 1 11 12 1 10 12 1 8 1 10 12 1 1 8 1 8 1 8 1 8 1 11 12 1 10 12 1 10 12 1 10 12 1 10 7 12 1 1 8 1 7 1 9 7 12 1 1 1 10 12 1 7 1 9 12 10 9 12 7 1 10 12 8 1 10 12 1 1 8 1 10 7 12 1 1 10 12 1 10 7 12 1 1 1 10 12 1 1 10 7 12 1 1 1 10 12 10 7 12 1 1 1 9 12 1 1 100 1 8 1 10 12 1 1 10 7 12 1 1 1 7 1 10 12 1 1 8 1 10 8 1 8 1 8 1 8 1 10 10 7 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 100 12 1 1 1 10 100 1 8 1 10 10 10 12 1 1 10 12 1 1 8 1 10 12 1 8 1 10 12 1 10 12 1 1 7 1 10 10 12 1 10 12 1 9 12 1 1 1 3 1 3 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 100 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 15 10 100 12 1 1 1 16 15 10 12 1 1 16 15 10 12 16 1 1 1 1 1 1 1 100 1 100 1 1 +staticfield java/lang/System in Ljava/io/InputStream; java/io/BufferedInputStream +staticfield java/lang/System out Ljava/io/PrintStream; org/fusesource/jansi/AnsiPrintStream +staticfield java/lang/System err Ljava/io/PrintStream; org/fusesource/jansi/AnsiPrintStream +instanceKlass com/google/inject/internal/aop/ChildClassDefiner$ChildLoader +instanceKlass org/eclipse/sisu/space/CloningClassSpace$CloningClassLoader +instanceKlass jdk/internal/reflect/DelegatingClassLoader +instanceKlass java/security/SecureClassLoader +ciInstanceKlass java/lang/ClassLoader 1 1 1098 9 7 12 1 1 1 10 7 12 1 1 1 9 12 1 1 10 7 12 1 1 1 7 1 10 12 1 1 10 12 1 1 9 12 1 1 10 12 1 1 100 1 10 12 1 10 12 1 1 10 7 12 1 1 1 100 1 8 1 10 12 1 10 7 12 1 1 1 10 100 12 1 10 7 1 10 7 1 7 1 7 1 10 12 1 10 12 1 9 12 1 1 10 10 7 12 1 1 1 9 12 1 1 9 12 1 9 12 1 1 9 12 1 9 12 1 1 9 12 1 1 7 1 10 12 1 9 12 1 1 10 12 1 1 10 7 12 1 1 1 9 12 1 9 12 1 1 9 12 1 10 12 1 1 9 12 10 12 1 1 7 1 10 8 1 10 12 1 1 10 12 1 10 7 1 7 1 10 12 1 1 10 7 12 1 1 1 8 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 7 1 10 12 1 10 7 12 1 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 10 12 1 10 12 1 10 100 12 1 1 10 12 1 1 10 12 1 1 10 12 1 100 1 10 12 1 100 1 10 12 1 10 100 12 1 1 1 10 10 12 1 1 10 12 1 1 100 1 8 1 10 8 1 10 12 1 10 12 1 100 1 8 1 10 12 1 1 10 12 1 1 10 10 12 1 1 10 12 1 1 10 12 1 1 10 7 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 7 12 1 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 8 1 9 12 1 10 12 1 1 8 1 8 1 10 7 12 1 1 100 1 10 10 12 10 12 1 10 12 1 10 12 1 10 12 1 1 10 7 12 1 1 10 12 1 10 7 1 7 1 10 12 1 1 10 12 1 10 7 1 10 12 1 100 1 18 12 1 10 100 12 1 1 1 10 100 12 1 1 1 10 7 12 1 1 10 12 1 1 10 12 1 10 12 1 1 100 1 10 12 1 10 12 1 1 10 7 12 1 1 10 12 1 8 1 100 1 10 10 12 1 9 12 1 10 7 12 1 1 10 12 1 100 1 8 1 10 12 1 10 8 1 8 1 10 12 1 1 10 12 1 1 10 12 1 1 10 100 12 1 1 1 100 1 100 1 10 12 1 1 100 1 100 1 10 100 12 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 9 100 12 1 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 8 1 10 12 1 7 1 18 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 18 12 1 11 100 12 1 1 1 100 1 10 12 1 1 10 12 1 10 11 12 1 1 10 18 10 12 1 1 11 100 12 1 18 12 1 11 12 1 1 10 12 10 12 1 1 10 12 1 1 100 1 8 1 10 10 12 1 8 1 8 1 10 100 12 1 1 10 12 1 100 1 10 10 12 1 8 1 8 1 8 1 10 12 1 10 12 1 1 10 12 1 10 100 12 1 1 1 11 100 12 1 1 100 1 10 11 10 12 1 10 12 1 10 12 1 1 9 100 12 1 1 9 12 1 1 9 12 9 12 1 9 12 1 9 12 1 8 10 12 1 1 10 7 12 1 1 1 10 12 1 1 10 12 1 1 10 12 1 10 10 12 1 11 12 1 1 10 100 12 1 1 1 100 1 10 12 1 10 12 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 15 10 100 12 1 1 1 16 1 15 10 12 16 1 16 15 10 12 16 1 16 1 15 10 12 16 15 10 12 16 15 10 12 16 1 1 100 1 100 1 1 +staticfield java/lang/ClassLoader nocerts [Ljava/security/cert/Certificate; 0 [Ljava/security/cert/Certificate; +staticfield java/lang/ClassLoader $assertionsDisabled Z 1 +ciInstanceKlass jdk/internal/reflect/DelegatingClassLoader 1 1 18 10 7 12 1 1 1 100 1 1 1 1 1 1 1 1 1 1 +instanceKlass java/net/URLClassLoader +instanceKlass jdk/internal/loader/BuiltinClassLoader +ciInstanceKlass java/security/SecureClassLoader 1 1 102 10 7 12 1 1 1 7 1 10 12 1 9 7 12 1 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 7 1 10 7 1 10 12 1 7 1 10 12 1 11 7 12 1 1 1 7 1 11 12 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 +instanceKlass jdk/internal/loader/ClassLoaders$BootClassLoader +instanceKlass jdk/internal/loader/ClassLoaders$PlatformClassLoader +instanceKlass jdk/internal/loader/ClassLoaders$AppClassLoader +ciInstanceKlass jdk/internal/loader/BuiltinClassLoader 1 1 737 9 7 12 1 1 1 10 7 12 1 1 1 10 7 12 1 1 1 9 12 1 1 9 12 1 1 7 1 10 12 1 9 12 1 10 12 1 9 12 1 10 7 12 1 1 1 10 7 12 1 1 1 10 7 12 1 1 1 11 7 12 1 1 1 100 1 100 1 10 10 12 1 1 8 1 10 12 1 10 12 7 1 10 12 1 10 12 1 1 11 7 12 1 1 1 11 7 12 1 1 1 11 12 1 1 7 1 8 1 8 1 10 9 12 1 1 10 7 12 1 1 11 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 7 12 1 1 1 10 12 1 10 100 12 1 1 1 10 12 1 1 11 7 12 1 1 1 10 7 12 1 1 100 1 10 7 12 1 1 1 10 12 1 100 1 8 1 10 12 1 1 10 8 1 10 12 1 1 10 12 1 1 10 12 1 1 11 7 12 1 1 11 12 1 7 1 10 11 12 1 1 11 10 12 1 1 7 1 10 12 1 10 7 12 1 10 12 1 7 1 10 12 1 10 7 12 1 1 1 100 1 10 12 1 1 11 12 1 100 1 100 1 10 12 1 10 12 1 1 100 1 100 1 10 12 1 10 12 1 18 12 1 1 10 12 1 10 12 1 1 18 100 1 10 7 12 1 1 1 7 1 10 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 9 12 1 1 100 1 10 10 12 1 1 10 12 1 1 10 12 1 10 12 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 18 12 1 7 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 100 1 10 12 1 7 1 10 12 1 10 7 12 1 1 1 10 12 1 11 12 1 7 1 10 12 1 7 1 100 1 10 12 1 10 12 1 11 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 8 1 10 12 1 1 10 7 12 1 1 10 12 1 100 1 8 1 8 1 10 10 12 1 8 1 8 1 10 7 12 1 1 1 11 7 12 1 1 1 9 7 12 1 1 1 10 7 12 1 1 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 10 7 12 1 1 1 8 1 10 12 1 7 1 10 12 1 1 10 12 1 7 1 10 11 12 1 1 10 12 10 12 1 10 12 1 100 1 10 12 1 10 12 1 10 10 12 1 10 7 12 1 1 8 1 10 7 12 1 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 15 10 100 12 1 1 1 16 15 10 12 16 15 10 12 16 15 10 12 16 1 1 1 100 1 1 1 1 1 100 1 100 1 1 +staticfield jdk/internal/loader/BuiltinClassLoader packageToModule Ljava/util/Map; java/util/concurrent/ConcurrentHashMap +staticfield jdk/internal/loader/BuiltinClassLoader $assertionsDisabled Z 1 +ciInstanceKlass java/security/AccessController 1 1 295 10 100 12 1 1 1 10 7 12 1 1 1 10 7 12 1 1 1 10 12 1 1 10 12 1 1 10 7 12 1 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 100 1 8 1 10 12 1 10 12 1 10 12 1 1 10 12 1 7 1 7 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 7 12 1 1 1 10 12 1 1 9 100 12 1 1 1 10 100 12 1 1 1 10 12 1 10 12 1 9 100 12 1 1 1 10 12 1 10 12 1 1 9 12 1 1 10 100 1 10 11 7 12 1 1 1 10 7 12 1 1 11 7 1 7 1 10 12 1 10 12 1 10 12 1 8 1 10 12 1 1 8 1 10 100 12 1 1 1 8 1 100 1 10 10 12 1 1 10 12 1 10 7 12 1 1 1 10 12 1 8 1 10 100 12 1 1 8 1 8 1 10 12 1 8 1 10 12 1 10 12 1 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 1 1 1 1 1 1 1 1 3 1 1 1 +staticfield java/security/AccessController $assertionsDisabled Z 1 +instanceKlass jdk/internal/reflect/GeneratedConstructorAccessor7 +instanceKlass jdk/internal/reflect/GeneratedConstructorAccessor6 +instanceKlass jdk/internal/reflect/GeneratedConstructorAccessor5 +instanceKlass jdk/internal/reflect/GeneratedConstructorAccessor4 +instanceKlass jdk/internal/reflect/GeneratedConstructorAccessor3 +instanceKlass jdk/internal/reflect/GeneratedConstructorAccessor2 +instanceKlass jdk/internal/reflect/BootstrapConstructorAccessorImpl +instanceKlass jdk/internal/reflect/GeneratedConstructorAccessor1 +instanceKlass jdk/internal/reflect/DelegatingConstructorAccessorImpl +instanceKlass jdk/internal/reflect/NativeConstructorAccessorImpl +ciInstanceKlass jdk/internal/reflect/ConstructorAccessorImpl 1 1 27 10 7 12 1 1 1 100 1 100 1 1 1 1 1 1 1 1 1 100 1 100 1 100 1 1 1 +instanceKlass jdk/internal/reflect/FieldAccessorImpl +instanceKlass jdk/internal/reflect/ConstructorAccessorImpl +instanceKlass jdk/internal/reflect/MethodAccessorImpl +ciInstanceKlass jdk/internal/reflect/MagicAccessorImpl 1 1 16 10 7 12 1 1 1 100 1 1 1 1 1 1 1 1 +instanceKlass jdk/internal/reflect/GeneratedMethodAccessor16 +instanceKlass jdk/internal/reflect/GeneratedMethodAccessor15 +instanceKlass jdk/internal/reflect/GeneratedMethodAccessor14 +instanceKlass jdk/internal/reflect/GeneratedMethodAccessor13 +instanceKlass jdk/internal/reflect/GeneratedMethodAccessor12 +instanceKlass jdk/internal/reflect/GeneratedMethodAccessor11 +instanceKlass jdk/internal/reflect/GeneratedMethodAccessor10 +instanceKlass jdk/internal/reflect/GeneratedMethodAccessor9 +instanceKlass jdk/internal/reflect/GeneratedMethodAccessor8 +instanceKlass jdk/internal/reflect/GeneratedMethodAccessor7 +instanceKlass jdk/internal/reflect/GeneratedMethodAccessor6 +instanceKlass jdk/internal/reflect/GeneratedMethodAccessor5 +instanceKlass jdk/internal/reflect/GeneratedMethodAccessor4 +instanceKlass jdk/internal/reflect/GeneratedMethodAccessor3 +instanceKlass jdk/internal/reflect/GeneratedMethodAccessor2 +instanceKlass jdk/internal/reflect/GeneratedMethodAccessor1 +instanceKlass jdk/internal/reflect/DelegatingMethodAccessorImpl +instanceKlass jdk/internal/reflect/NativeMethodAccessorImpl +ciInstanceKlass jdk/internal/reflect/MethodAccessorImpl 1 1 25 10 7 12 1 1 1 100 1 100 1 1 1 1 1 1 1 1 1 100 1 100 1 1 1 +ciInstanceKlass java/lang/Module 1 1 959 10 7 12 1 1 1 9 7 12 1 1 1 10 7 12 1 1 1 9 12 1 9 12 1 1 9 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 7 12 1 1 1 7 1 10 7 12 1 1 1 10 12 1 1 11 7 12 1 1 1 10 12 1 1 10 7 12 1 1 1 10 12 1 1 10 7 12 1 1 1 9 100 12 1 1 1 10 100 12 1 1 1 10 12 1 8 1 10 7 12 1 1 1 10 7 12 1 1 1 9 12 1 1 9 12 1 1 10 12 1 9 12 1 1 11 12 1 9 7 12 1 1 10 7 12 1 1 1 10 100 12 1 1 1 10 12 1 1 100 1 100 1 10 10 12 1 1 8 1 10 12 1 10 12 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 9 7 12 1 1 1 10 12 1 1 10 12 1 1 9 12 1 10 12 1 10 12 1 9 12 1 1 11 7 12 1 1 10 12 1 1 9 12 1 9 12 1 10 12 1 10 12 1 100 1 8 1 10 10 12 1 1 10 12 1 8 1 10 12 1 1 8 1 8 1 10 12 1 1 10 12 1 10 12 1 1 18 12 1 1 10 12 1 1 11 12 1 9 12 1 11 12 10 100 12 1 1 100 1 8 1 10 7 1 11 12 1 1 10 12 1 10 12 1 10 12 1 1 11 12 1 1 11 7 12 1 1 11 12 1 1 9 12 1 11 12 1 10 12 1 1 10 12 1 1 9 12 1 10 12 10 7 12 1 1 10 7 12 1 1 10 7 1 18 12 1 1 11 100 12 1 1 1 18 12 1 11 12 1 1 10 100 12 1 1 1 11 12 1 1 10 7 12 1 1 4 7 1 11 12 1 7 1 7 1 10 10 7 12 1 1 1 10 11 7 12 1 8 1 10 10 12 1 1 10 7 12 1 1 10 12 1 10 12 1 7 1 10 12 1 10 11 12 1 1 10 12 10 12 1 1 9 12 1 100 1 10 10 12 1 1 11 100 1 10 12 1 1 11 12 1 10 10 12 1 11 10 12 1 1 10 12 1 1 10 12 1 1 10 7 12 1 1 10 12 1 10 12 1 1 10 10 12 1 1 10 12 1 18 12 1 11 12 1 18 12 1 10 12 1 10 12 1 10 12 7 1 10 12 1 10 12 1 10 12 1 9 12 1 7 1 10 10 10 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 9 12 1 18 12 1 1 10 100 12 1 1 1 100 1 8 1 10 12 1 1 10 12 1 1 10 100 12 1 1 100 1 10 12 1 1 100 1 8 1 100 1 10 100 1 100 1 3 10 12 1 100 1 10 12 1 10 12 1 1 10 12 1 1 100 1 10 12 1 10 12 1 1 100 1 100 1 10 12 8 1 10 12 1 1 10 12 1 1 10 100 12 1 1 10 12 1 1 10 12 1 10 12 10 12 1 1 100 1 10 10 12 1 1 10 100 12 1 1 1 100 1 8 1 10 12 1 1 10 100 12 1 1 8 1 10 12 1 8 1 10 12 1 10 12 10 12 1 8 1 10 10 100 12 1 1 7 1 10 10 12 1 10 7 12 1 1 9 12 1 9 12 1 9 12 1 9 12 1 10 12 11 12 1 10 12 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 15 10 7 12 1 1 1 16 15 10 12 16 16 15 10 12 16 16 15 10 16 1 15 10 12 16 1 15 10 12 16 1 16 15 10 12 16 1 1 1 1 1 1 1 100 1 100 1 1 +staticfield java/lang/Module ALL_UNNAMED_MODULE Ljava/lang/Module; java/lang/Module +staticfield java/lang/Module ALL_UNNAMED_MODULE_SET Ljava/util/Set; java/util/ImmutableCollections$Set12 +staticfield java/lang/Module EVERYONE_MODULE Ljava/lang/Module; java/lang/Module +staticfield java/lang/Module EVERYONE_SET Ljava/util/Set; java/util/ImmutableCollections$Set12 +staticfield java/lang/Module $assertionsDisabled Z 1 +instanceKlass org/apache/maven/artifact/versioning/ComparableVersion$ListItem +instanceKlass org/eclipse/sisu/bean/BeanScheduler$Pending +ciInstanceKlass java/util/ArrayList 1 1 492 10 7 12 1 1 1 7 1 9 7 12 1 1 1 9 12 1 100 1 100 1 10 8 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 9 12 1 11 7 12 1 1 1 9 12 1 1 10 12 1 1 7 10 7 12 1 1 1 9 12 1 10 12 1 10 12 1 1 10 7 12 1 1 1 10 7 12 1 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 100 1 100 1 10 12 1 10 7 12 1 1 1 10 7 12 1 1 10 12 1 10 12 10 12 1 1 10 12 1 1 10 12 1 1 7 1 10 12 1 1 10 12 1 1 10 12 1 100 1 10 11 12 1 1 11 100 12 1 1 1 11 12 1 10 12 1 10 12 1 10 12 1 1 100 1 10 12 1 1 10 10 12 1 1 10 12 1 8 1 8 1 8 1 8 1 10 12 1 1 10 12 1 1 11 12 1 100 1 10 100 12 1 1 10 12 1 10 12 1 1 10 100 12 1 1 10 12 1 10 100 12 1 1 1 11 100 12 1 1 1 10 12 1 100 1 8 1 10 7 1 10 12 1 7 1 10 12 1 10 12 1 1 7 1 10 12 1 10 12 1 1 11 7 12 1 1 7 1 10 12 1 10 12 1 1 11 7 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 11 100 12 1 1 10 12 1 1 100 1 100 1 100 1 1 1 1 5 0 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 1 1 1 1 1 +staticfield java/util/ArrayList EMPTY_ELEMENTDATA [Ljava/lang/Object; 0 [Ljava/lang/Object; +staticfield java/util/ArrayList DEFAULTCAPACITY_EMPTY_ELEMENTDATA [Ljava/lang/Object; 0 [Ljava/lang/Object; +ciInstanceKlass java/util/concurrent/ConcurrentHashMap 1 1 1210 7 1 7 1 3 10 12 1 1 3 100 1 10 7 12 1 1 1 100 1 10 100 12 1 1 1 100 1 11 12 1 1 11 12 1 11 12 1 1 9 12 1 1 9 12 1 1 9 12 1 10 7 12 1 1 1 7 1 10 12 1 1 10 12 1 1 10 7 12 1 1 1 4 10 12 1 9 12 1 10 12 1 1 100 1 10 5 0 10 12 1 10 12 1 1 5 0 10 12 1 1 10 12 1 9 12 1 1 10 12 1 1 9 12 1 9 12 1 1 10 12 1 1 9 12 1 10 12 1 1 9 12 1 1 10 12 1 1 100 1 10 7 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 100 1 10 12 1 1 7 1 100 1 8 1 10 12 1 10 12 1 1 10 12 1 1 11 7 12 1 1 10 12 1 1 11 12 1 1 11 7 12 1 1 1 11 7 12 1 1 1 11 12 1 7 1 11 12 1 11 12 1 10 12 1 1 10 12 1 1 9 12 1 1 10 100 12 1 1 1 9 10 12 1 1 9 12 1 10 12 1 1 5 0 9 12 1 1 7 1 10 12 1 9 12 1 1 7 1 10 12 1 9 12 1 7 1 10 100 1 10 10 12 1 1 8 1 10 12 1 10 12 1 1 11 100 1 10 12 1 10 100 12 1 1 1 8 1 10 100 12 1 1 1 8 1 10 12 1 8 1 10 12 1 10 12 1 1 10 100 12 1 1 10 12 1 10 12 1 10 12 1 9 12 1 9 10 12 1 9 12 1 1 11 7 12 1 1 1 11 7 12 1 1 1 100 1 10 12 11 100 12 1 1 10 11 7 12 1 10 12 1 100 1 10 12 1 100 1 10 10 9 7 12 1 1 1 10 12 3 10 100 12 1 1 9 12 1 10 12 1 1 9 12 1 1 9 12 1 10 12 1 1 10 100 12 1 1 9 12 1 9 7 12 1 1 10 12 1 1 10 12 1 3 9 12 1 9 12 1 10 12 1 1 7 1 9 3 9 12 1 100 1 10 12 1 9 12 1 10 12 1 9 12 1 10 12 1 9 12 1 10 100 12 1 1 1 100 10 12 1 100 1 5 0 10 100 12 1 1 100 1 10 12 1 1 10 12 1 10 12 1 100 1 10 12 1 10 100 1 100 1 10 10 12 1 10 100 1 10 12 1 10 100 1 10 12 1 10 100 1 10 12 1 1 100 1 10 12 1 10 10 12 1 100 1 10 12 1 10 10 12 1 100 1 10 12 1 10 100 1 10 12 1 10 100 1 10 12 1 10 100 1 10 12 1 10 100 1 10 12 1 10 100 1 10 12 1 10 100 1 10 12 1 10 100 1 10 12 1 10 100 1 10 10 100 1 10 10 100 1 10 10 100 1 10 12 1 10 100 1 10 12 1 10 100 1 10 12 1 10 100 1 10 12 1 10 100 1 10 12 1 10 100 1 10 10 100 1 10 10 100 1 10 10 100 1 10 12 1 10 100 1 10 12 1 10 100 1 10 12 1 10 100 1 10 12 1 10 100 1 10 12 1 10 10 12 1 10 7 12 1 1 1 10 12 1 7 1 7 1 10 12 1 9 12 1 1 9 12 1 1 10 12 1 1 8 10 12 1 1 8 8 8 8 7 10 12 1 1 10 12 1 100 1 8 1 10 7 1 100 1 100 1 1 1 5 0 1 1 3 1 3 1 1 1 1 3 1 3 1 3 1 1 1 1 1 3 1 3 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 100 1 100 1 100 1 100 1 100 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield java/util/concurrent/ConcurrentHashMap NCPU I 12 +staticfield java/util/concurrent/ConcurrentHashMap serialPersistentFields [Ljava/io/ObjectStreamField; 3 [Ljava/io/ObjectStreamField; +staticfield java/util/concurrent/ConcurrentHashMap U Ljdk/internal/misc/Unsafe; jdk/internal/misc/Unsafe +staticfield java/util/concurrent/ConcurrentHashMap SIZECTL J 20 +staticfield java/util/concurrent/ConcurrentHashMap TRANSFERINDEX J 32 +staticfield java/util/concurrent/ConcurrentHashMap BASECOUNT J 24 +staticfield java/util/concurrent/ConcurrentHashMap CELLSBUSY J 36 +staticfield java/util/concurrent/ConcurrentHashMap CELLVALUE J 144 +staticfield java/util/concurrent/ConcurrentHashMap ABASE I 16 +staticfield java/util/concurrent/ConcurrentHashMap ASHIFT I 2 +ciInstanceKlass java/lang/String 1 1 1396 10 7 12 1 1 1 8 1 9 7 12 1 1 1 9 12 1 1 9 12 1 1 10 12 1 10 12 1 1 10 12 1 1 9 12 1 1 10 7 12 1 1 1 10 7 1 10 7 12 1 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 10 7 12 1 1 1 9 7 12 1 1 1 10 7 12 1 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 9 7 12 1 1 10 12 9 7 12 1 1 3 10 7 12 1 1 1 100 1 11 12 1 1 11 12 1 11 12 1 1 10 100 12 1 1 1 10 12 1 1 9 100 12 1 1 1 10 12 1 1 10 12 1 11 12 1 1 10 12 1 1 10 12 10 12 1 1 10 100 12 1 1 1 10 7 12 1 1 1 10 12 1 1 100 1 100 1 10 12 1 10 12 1 10 12 1 1 100 1 10 12 1 1 100 1 10 12 1 1 10 12 1 1 10 10 12 1 100 1 100 1 7 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 100 12 1 1 100 1 11 11 12 1 11 12 1 10 12 1 1 10 12 1 10 12 10 12 1 1 10 10 100 12 1 1 1 10 100 12 1 1 10 12 1 1 10 100 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 100 1 10 12 1 1 10 12 10 12 1 10 12 1 1 10 7 12 1 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 3 3 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 100 1 10 8 1 10 12 1 1 10 12 1 8 1 10 12 1 1 10 12 10 12 1 8 1 10 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 10 100 1 10 10 12 1 10 12 1 10 12 1 10 10 12 1 10 12 1 1 10 12 1 100 1 10 10 12 1 100 1 10 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 10 12 1 1 10 100 1 10 10 12 1 10 12 1 1 10 12 1 1 10 100 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 11 7 1 11 12 10 12 1 10 12 1 1 10 12 1 1 10 10 12 1 10 12 1 9 12 1 1 11 100 12 1 1 1 10 10 12 1 10 12 1 1 10 10 12 1 10 12 1 10 12 1 1 9 12 1 10 12 1 1 10 10 12 1 1 10 12 10 10 12 1 10 12 10 10 12 10 10 12 1 10 12 1 10 12 10 10 12 10 12 1 10 12 10 12 10 10 12 1 10 12 1 1 10 12 1 10 10 12 1 10 7 12 1 1 1 10 12 1 1 10 10 7 12 1 1 1 11 10 12 1 10 12 1 1 10 12 1 1 10 7 12 1 1 1 10 12 1 10 10 12 1 10 12 1 10 12 1 10 100 12 1 1 10 12 1 100 1 100 1 8 1 10 10 10 12 1 8 1 10 12 1 3 3 7 1 10 10 12 1 10 12 1 10 12 1 1 10 12 1 1 11 7 12 1 1 1 7 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 8 1 10 12 1 1 10 12 1 11 100 12 1 1 1 11 100 12 1 1 11 12 1 1 10 12 1 10 12 1 1 10 10 7 12 1 1 1 10 12 1 10 12 1 10 10 12 10 12 1 1 10 10 12 1 10 10 12 1 10 10 12 1 10 10 12 1 10 12 1 1 10 10 12 1 8 1 10 12 1 1 18 12 1 1 11 100 12 1 1 1 7 1 3 18 12 1 18 12 1 8 1 10 100 12 1 1 1 11 12 1 1 10 12 10 10 12 1 10 11 12 1 1 10 12 1 1 11 12 1 18 3 11 10 12 1 11 11 10 12 1 10 12 1 1 8 1 10 12 1 10 12 1 10 12 1 1 10 10 12 1 11 100 12 1 7 1 100 1 10 12 1 7 1 10 10 7 12 1 1 1 100 1 10 7 1 10 10 12 1 10 10 12 1 8 1 10 10 12 1 8 1 8 1 10 12 1 10 12 1 10 10 12 10 100 12 1 1 10 7 12 1 1 10 100 12 1 1 8 1 10 12 1 10 12 1 1 10 10 12 8 1 8 1 10 8 1 8 1 8 1 8 1 10 12 1 10 12 1 8 1 10 100 12 1 1 1 10 12 10 12 1 1 10 12 10 10 12 10 12 7 1 9 12 1 1 7 1 10 100 1 100 1 100 1 100 1 1 1 1 1 1 5 0 1 1 1 1 1 3 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 7 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 15 10 100 12 1 1 1 16 15 10 12 16 15 10 12 15 10 12 15 10 12 1 1 1 1 100 1 100 1 1 1 +staticfield java/lang/String COMPACT_STRINGS Z 1 +staticfield java/lang/String serialPersistentFields [Ljava/io/ObjectStreamField; 0 [Ljava/io/ObjectStreamField; +staticfield java/lang/String CASE_INSENSITIVE_ORDER Ljava/util/Comparator; java/lang/String$CaseInsensitiveComparator +ciInstanceKlass java/security/ProtectionDomain 1 1 324 10 7 12 1 1 1 9 7 12 1 1 1 7 1 10 9 12 1 1 9 12 1 1 9 12 1 1 10 7 12 1 1 7 1 9 12 1 9 12 1 1 7 1 9 12 1 1 9 12 1 10 100 12 1 1 10 100 12 1 1 1 10 12 1 1 10 12 1 9 12 1 9 100 12 1 1 10 12 1 1 10 100 1 10 12 1 1 8 1 100 1 8 1 10 12 1 10 10 100 12 1 1 1 10 12 1 1 8 1 11 8 1 10 12 1 8 1 8 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 8 1 10 12 1 8 1 8 1 10 100 12 1 1 1 9 100 12 1 1 1 10 12 1 1 9 100 12 1 1 1 10 100 12 1 1 1 100 1 100 1 10 12 1 10 100 12 1 1 1 10 100 1 10 12 1 10 12 1 1 11 100 12 1 1 11 12 1 100 1 11 100 12 1 1 1 10 12 1 10 11 12 1 1 11 12 1 1 10 12 1 10 7 12 1 1 10 100 12 1 1 11 12 1 10 12 8 1 8 1 10 7 12 1 1 1 7 1 10 10 7 12 1 1 1 1 1 1 1 1 1 1 100 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 100 1 1 1 1 1 +staticfield java/security/ProtectionDomain filePermCompatInPD Z 0 +ciInstanceKlass java/security/CodeSource 1 1 395 10 7 12 1 1 1 9 7 12 1 1 1 9 12 1 1 9 12 1 1 9 12 1 1 10 7 12 1 1 1 9 12 1 1 10 100 12 1 1 10 100 10 100 12 1 1 1 10 100 12 1 1 1 10 12 1 1 100 1 10 10 100 12 1 1 1 10 100 12 1 1 1 10 12 1 1 10 12 1 100 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 10 10 12 1 1 10 100 12 1 1 1 10 12 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 10 12 1 1 10 12 1 8 1 10 12 1 1 10 100 1 10 10 12 1 1 8 1 10 12 1 10 12 1 10 12 1 8 1 8 1 9 12 1 1 100 1 8 1 10 12 1 10 12 1 1 8 1 10 12 1 8 1 8 1 8 1 10 100 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 100 1 100 1 10 12 1 10 12 10 12 1 1 10 100 12 1 1 10 12 1 100 1 10 12 10 8 1 10 12 1 10 12 1 10 12 1 1 100 1 10 12 1 1 100 1 100 1 8 1 8 1 10 10 12 1 1 10 100 12 1 1 1 100 1 10 12 10 12 1 1 11 100 12 1 1 10 10 12 1 11 10 12 1 8 1 100 1 10 12 1 10 12 1 1 10 12 1 11 12 1 1 11 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/StringBuilder 1 1 409 10 7 12 1 1 1 10 12 1 10 12 1 10 12 1 1 10 7 12 1 1 1 10 7 12 1 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 10 12 1 1 10 12 1 1 9 12 1 1 9 12 1 1 10 7 12 1 1 1 10 7 1 10 100 12 1 1 1 10 12 1 10 12 1 1 10 12 1 1 10 10 12 1 1 10 100 12 1 1 10 12 1 10 12 1 1 100 1 100 1 8 1 10 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 10 12 10 12 1 10 12 1 10 12 1 10 12 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 10 12 1 10 12 1 100 1 100 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 100 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass jdk/internal/loader/ClassLoaders 1 1 183 10 100 12 1 1 1 10 7 12 1 1 1 10 12 1 1 10 100 12 1 1 1 9 7 12 1 1 1 9 12 1 1 9 12 1 1 7 1 11 100 12 1 1 1 100 1 11 12 1 1 11 12 1 1 10 100 12 1 1 1 10 100 12 1 1 1 100 1 100 1 10 7 12 1 1 1 9 12 1 1 10 12 1 1 7 1 10 12 1 1 10 12 1 7 1 8 1 10 7 12 1 1 1 10 12 1 1 7 1 10 12 1 10 12 1 10 12 1 8 1 10 7 12 1 1 8 1 8 1 10 12 1 7 1 10 12 1 10 12 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield jdk/internal/loader/ClassLoaders JLA Ljdk/internal/access/JavaLangAccess; java/lang/System$2 +staticfield jdk/internal/loader/ClassLoaders BOOT_LOADER Ljdk/internal/loader/ClassLoaders$BootClassLoader; jdk/internal/loader/ClassLoaders$BootClassLoader +staticfield jdk/internal/loader/ClassLoaders PLATFORM_LOADER Ljdk/internal/loader/ClassLoaders$PlatformClassLoader; jdk/internal/loader/ClassLoaders$PlatformClassLoader +staticfield jdk/internal/loader/ClassLoaders APP_LOADER Ljdk/internal/loader/ClassLoaders$AppClassLoader; jdk/internal/loader/ClassLoaders$AppClassLoader +ciInstanceKlass jdk/internal/misc/Unsafe 1 1 1285 10 7 12 1 1 1 9 7 12 1 1 1 9 12 1 1 10 12 1 1 10 7 12 1 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 100 1 10 10 12 1 1 10 12 1 1 5 0 10 12 1 1 10 12 1 1 10 7 12 1 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 100 1 100 1 10 8 1 10 12 1 1 10 12 1 8 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 5 0 5 0 5 0 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 100 1 8 1 10 100 1 10 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 9 7 12 1 1 9 12 1 100 1 10 10 12 1 1 8 1 10 8 1 8 1 10 12 1 1 9 7 12 1 1 1 9 100 1 9 7 1 9 100 1 9 9 100 1 9 100 1 9 100 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 5 0 5 0 9 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 8 1 3 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 100 1 10 100 1 10 9 12 1 5 0 10 12 1 1 5 0 10 12 1 5 0 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 10 12 1 1 10 12 1 10 12 1 1 10 12 1 10 12 5 0 5 0 5 0 10 12 1 1 10 12 1 10 12 1 10 12 10 100 12 1 1 8 1 100 1 11 12 1 1 8 1 11 12 1 1 10 100 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 7 1 10 12 1 9 12 1 7 1 9 12 1 7 1 9 12 1 7 1 9 12 1 7 1 9 12 1 7 1 9 12 1 7 1 9 12 1 7 1 9 12 1 7 1 9 12 1 10 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield jdk/internal/misc/Unsafe theUnsafe Ljdk/internal/misc/Unsafe; jdk/internal/misc/Unsafe +staticfield jdk/internal/misc/Unsafe ARRAY_BOOLEAN_BASE_OFFSET I 16 +staticfield jdk/internal/misc/Unsafe ARRAY_BYTE_BASE_OFFSET I 16 +staticfield jdk/internal/misc/Unsafe ARRAY_SHORT_BASE_OFFSET I 16 +staticfield jdk/internal/misc/Unsafe ARRAY_CHAR_BASE_OFFSET I 16 +staticfield jdk/internal/misc/Unsafe ARRAY_INT_BASE_OFFSET I 16 +staticfield jdk/internal/misc/Unsafe ARRAY_LONG_BASE_OFFSET I 16 +staticfield jdk/internal/misc/Unsafe ARRAY_FLOAT_BASE_OFFSET I 16 +staticfield jdk/internal/misc/Unsafe ARRAY_DOUBLE_BASE_OFFSET I 16 +staticfield jdk/internal/misc/Unsafe ARRAY_OBJECT_BASE_OFFSET I 16 +staticfield jdk/internal/misc/Unsafe ARRAY_BOOLEAN_INDEX_SCALE I 1 +staticfield jdk/internal/misc/Unsafe ARRAY_BYTE_INDEX_SCALE I 1 +staticfield jdk/internal/misc/Unsafe ARRAY_SHORT_INDEX_SCALE I 2 +staticfield jdk/internal/misc/Unsafe ARRAY_CHAR_INDEX_SCALE I 2 +staticfield jdk/internal/misc/Unsafe ARRAY_INT_INDEX_SCALE I 4 +staticfield jdk/internal/misc/Unsafe ARRAY_LONG_INDEX_SCALE I 8 +staticfield jdk/internal/misc/Unsafe ARRAY_FLOAT_INDEX_SCALE I 4 +staticfield jdk/internal/misc/Unsafe ARRAY_DOUBLE_INDEX_SCALE I 8 +staticfield jdk/internal/misc/Unsafe ARRAY_OBJECT_INDEX_SCALE I 4 +staticfield jdk/internal/misc/Unsafe ADDRESS_SIZE I 8 +ciInstanceKlass java/util/Map 1 1 259 11 7 12 1 1 1 11 12 1 1 10 7 12 1 1 11 12 1 1 11 100 12 1 1 1 11 100 12 1 1 1 11 12 1 1 7 1 11 12 1 11 12 1 100 1 100 1 10 12 1 1 11 100 12 1 1 1 11 100 12 1 1 1 11 12 1 11 12 1 10 12 1 1 11 12 1 11 7 12 1 9 7 12 1 1 1 100 1 10 12 7 1 7 1 10 12 1 7 1 10 100 1 11 12 1 1 100 1 11 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/ThreadGroup 1 1 293 10 7 12 1 1 1 9 7 12 1 1 1 8 1 9 12 1 1 7 1 9 12 1 9 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 9 12 1 1 10 12 1 1 10 12 1 9 12 1 10 100 12 1 1 1 9 12 1 9 12 1 1 10 7 12 1 1 1 100 10 12 1 1 10 7 12 1 1 1 10 100 12 1 9 12 1 10 12 1 1 10 12 1 1 9 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 10 12 1 10 12 1 10 100 1 10 10 12 1 10 12 1 10 12 1 7 10 12 1 9 12 1 1 10 12 1 1 8 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 10 12 1 1 11 100 1 100 1 9 12 1 100 1 10 8 1 10 12 1 1 10 12 1 1 8 1 10 12 1 10 100 12 1 1 1 10 12 1 1 10 100 1 8 1 10 8 1 10 12 1 8 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +instanceKlass java/security/Provider +ciInstanceKlass java/util/Properties 1 1 709 10 7 12 1 1 1 100 1 10 7 12 1 1 7 1 10 12 1 9 12 1 1 9 12 1 1 9 12 1 1 10 7 12 1 1 1 10 12 1 1 8 1 10 7 12 1 1 1 7 1 10 12 1 10 12 1 1 8 1 10 12 1 7 1 10 12 10 12 1 1 9 12 1 1 10 12 1 1 7 1 10 12 1 10 12 1 10 12 1 1 100 1 8 1 10 12 1 10 12 1 10 12 1 1 10 12 1 100 1 3 10 10 100 12 1 1 1 10 12 1 10 12 1 1 8 1 10 12 1 10 12 1 1 8 1 10 100 12 1 1 10 12 1 1 10 12 1 10 12 1 1 100 1 10 12 1 10 12 1 1 100 1 9 100 12 1 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 11 7 12 1 1 1 11 7 12 1 1 1 11 12 1 1 7 1 11 12 1 11 12 1 10 12 1 1 8 1 10 12 1 10 12 1 100 1 10 10 12 1 1 10 100 12 1 1 9 100 12 1 1 10 12 1 1 10 100 12 1 1 1 100 1 100 1 100 1 10 8 1 8 1 10 12 1 10 12 1 10 12 1 1 10 10 12 1 1 10 12 1 1 100 1 10 10 12 1 11 7 12 1 1 10 7 12 1 1 1 8 1 10 100 12 1 1 11 8 1 10 100 1 11 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 10 12 1 10 12 1 10 12 1 10 10 12 1 1 10 12 1 1 7 1 10 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 100 1 10 11 100 12 1 1 4 11 10 12 1 1 10 100 12 1 1 11 12 1 10 12 1 1 10 100 12 1 1 10 12 1 100 1 8 1 10 12 1 10 10 100 12 1 1 1 100 1 6 0 10 12 1 1 11 100 12 1 1 1 10 12 1 100 1 10 12 1 10 100 12 1 1 1 100 1 10 10 100 1 8 1 9 100 12 1 1 1 10 12 1 8 1 10 100 12 1 1 1 10 12 1 1 5 0 10 100 12 1 1 1 10 12 1 10 12 1 1 10 12 1 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 100 1 100 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 +staticfield java/util/Properties UNSAFE Ljdk/internal/misc/Unsafe; jdk/internal/misc/Unsafe +instanceKlass java/util/Hashtable +ciInstanceKlass java/util/Dictionary 1 1 36 10 7 12 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +instanceKlass java/util/Properties +ciInstanceKlass java/util/Hashtable 1 1 512 100 1 10 7 12 1 1 1 9 7 12 1 1 1 100 1 100 1 10 8 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 10 7 12 1 1 1 8 1 10 12 1 9 12 1 1 7 1 9 12 1 1 4 10 7 12 1 1 1 9 12 1 4 10 12 1 11 100 12 1 1 1 10 12 1 1 10 12 1 1 9 12 1 10 12 1 1 100 1 10 9 12 1 1 10 7 12 1 1 1 9 12 1 1 10 12 1 10 12 1 3 9 12 1 9 12 1 3 10 12 1 10 12 1 10 12 1 1 11 12 1 1 11 100 12 1 1 1 11 100 12 1 1 1 11 12 1 100 1 11 12 1 11 12 1 10 12 1 1 10 12 1 1 10 12 1 9 12 1 1 9 12 9 12 1 1 10 100 1 100 1 10 12 1 10 8 1 10 10 12 1 8 1 10 8 1 10 100 12 1 1 1 7 1 10 12 1 10 12 1 100 1 10 12 1 10 12 1 1 100 1 10 100 1 10 10 12 1 1 11 12 1 1 11 12 1 100 1 10 10 10 100 12 1 1 11 100 12 1 1 1 100 1 10 11 100 12 1 1 11 100 12 1 10 12 1 10 12 1 1 10 100 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 100 12 1 1 1 8 10 100 12 1 1 100 1 8 1 10 4 10 12 4 10 12 1 8 1 10 12 10 100 12 1 1 1 100 1 11 100 12 1 1 1 10 100 12 1 1 1 10 12 1 10 12 1 1 10 100 1 100 1 1 1 1 1 1 5 0 1 1 1 1 1 3 1 3 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 100 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +instanceKlass java/lang/ProcessBuilder$NullInputStream +instanceKlass sun/nio/ch/ChannelInputStream +instanceKlass java/util/zip/ZipFile$ZipFileInputStream +instanceKlass java/io/FilterInputStream +instanceKlass java/io/FileInputStream +instanceKlass java/io/ByteArrayInputStream +ciInstanceKlass java/io/InputStream 1 1 184 100 1 10 7 12 1 1 1 100 1 10 10 7 12 1 1 1 10 7 12 1 1 1 10 12 1 100 1 3 10 12 1 1 100 1 8 1 10 12 1 10 7 12 1 1 1 3 100 1 8 1 10 10 7 12 1 1 1 7 1 10 11 7 12 1 1 1 10 12 1 1 11 12 1 1 11 7 12 1 1 1 11 12 1 1 7 1 10 7 12 1 1 1 5 0 10 12 1 10 12 1 1 100 1 10 8 1 10 8 1 8 1 10 12 1 1 10 7 12 1 1 1 100 1 1 1 1 3 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/io/ByteArrayInputStream 1 1 117 10 7 12 1 1 1 9 7 12 1 1 1 9 12 1 1 9 12 1 9 12 1 10 100 12 1 1 1 10 7 12 1 1 1 10 7 12 1 1 1 10 100 12 1 1 1 10 12 1 1 3 10 100 1 10 100 12 1 1 1 9 12 1 1 100 1 10 10 7 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield java/io/ByteArrayInputStream $assertionsDisabled Z 1 +instanceKlass java/util/concurrent/ForkJoinWorkerThread +instanceKlass java/util/logging/LogManager$Cleaner +instanceKlass org/apache/maven/shared/utils/logging/MessageUtils$1 +instanceKlass jdk/internal/misc/InnocuousThread +instanceKlass java/lang/ref/Finalizer$FinalizerThread +instanceKlass java/lang/ref/Reference$ReferenceHandler +ciInstanceKlass java/lang/Thread 1 1 612 9 7 12 1 1 1 9 12 1 1 10 12 1 1 9 12 1 1 9 12 1 1 100 1 8 1 10 12 1 1 3 8 1 100 1 5 0 10 12 1 1 10 7 12 1 1 9 12 1 1 9 12 1 9 12 1 1 9 12 1 100 1 8 1 10 9 12 1 1 10 7 12 1 1 1 10 100 12 1 1 1 10 10 7 12 1 1 10 12 1 1 10 12 1 1 9 100 12 1 1 1 10 12 1 1 10 12 1 9 12 1 1 10 12 1 1 10 12 1 1 9 12 1 10 12 1 1 9 12 1 1 10 7 12 1 1 1 9 12 1 1 9 12 1 1 10 12 1 1 10 7 12 1 1 1 9 12 1 10 12 1 1 9 12 1 100 1 10 7 1 10 8 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 9 12 1 100 1 10 10 12 1 1 10 12 1 10 12 1 100 1 11 7 12 1 1 9 7 12 1 1 1 10 12 1 10 12 1 10 12 9 12 1 1 10 9 12 1 10 12 1 100 1 10 10 12 1 1 9 12 1 10 12 1 11 100 12 1 1 10 12 1 9 12 1 10 12 1 10 12 1 10 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 100 1 10 10 12 1 10 12 1 10 12 1 9 100 12 1 1 1 10 12 1 1 10 12 1 100 1 8 1 10 10 12 1 10 12 8 1 10 12 1 8 1 10 8 1 8 1 10 100 12 1 1 10 100 12 1 1 1 100 1 8 1 10 9 12 1 9 12 1 1 10 12 1 1 10 10 12 1 1 9 12 1 10 12 1 1 100 1 10 12 11 100 12 1 1 1 9 100 12 1 1 1 9 12 1 1 10 12 1 1 100 1 10 12 1 11 100 12 1 1 1 100 1 10 12 1 10 12 1 1 11 12 1 10 12 1 100 1 10 12 1 10 12 1 1 10 7 12 1 1 1 8 1 9 12 1 10 12 1 1 11 100 12 1 1 1 10 100 12 1 1 1 11 12 1 10 12 1 7 1 1 1 1 3 1 3 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 100 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 100 1 1 100 1 1 1 1 1 1 +staticfield java/lang/Thread EMPTY_STACK_TRACE [Ljava/lang/StackTraceElement; 0 [Ljava/lang/StackTraceElement; +instanceKlass org/apache/maven/artifact/repository/metadata/RepositoryMetadataDeploymentException +instanceKlass org/apache/maven/artifact/repository/metadata/RepositoryMetadataInstallationException +instanceKlass java/lang/Exception +instanceKlass java/lang/Error +ciInstanceKlass java/lang/Throwable 1 1 393 10 7 12 1 1 1 9 7 12 1 1 1 9 12 1 1 9 12 1 9 12 1 1 9 12 1 10 12 1 1 9 12 1 1 10 12 1 1 10 12 1 100 1 100 1 10 8 1 10 12 1 1 8 1 10 100 12 1 1 10 10 12 1 100 1 8 1 10 10 12 1 1 10 7 12 1 1 10 12 1 8 1 9 100 12 1 1 1 10 12 1 1 100 1 10 12 10 12 1 100 1 10 10 7 12 1 1 1 11 100 12 1 1 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 8 1 10 12 1 10 12 1 1 8 1 8 1 10 12 1 1 10 12 1 8 1 8 1 9 12 1 1 10 100 12 1 1 100 1 10 11 12 1 8 1 8 1 10 7 12 1 1 8 1 10 12 1 8 1 100 1 10 12 1 9 12 1 1 10 12 1 10 7 12 1 9 12 1 1 10 12 1 1 100 1 8 1 10 12 1 10 100 12 1 1 10 12 1 1 100 1 10 100 12 1 1 1 10 12 1 11 100 12 1 1 1 11 100 12 1 1 1 11 12 1 8 1 10 12 1 1 8 1 10 10 9 100 12 1 1 1 8 1 10 12 1 1 10 100 1 8 1 10 11 12 1 1 8 1 9 12 1 10 100 12 1 1 11 9 12 1 1 11 12 1 1 100 10 12 1 10 12 1 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield java/lang/Throwable UNASSIGNED_STACK [Ljava/lang/StackTraceElement; 0 [Ljava/lang/StackTraceElement; +staticfield java/lang/Throwable SUPPRESSED_SENTINEL Ljava/util/List; java/util/Collections$EmptyList +staticfield java/lang/Throwable EMPTY_THROWABLE_ARRAY [Ljava/lang/Throwable; 0 [Ljava/lang/Throwable; +staticfield java/lang/Throwable $assertionsDisabled Z 1 +instanceKlass javax/management/JMException +instanceKlass org/apache/maven/shared/artifact/filter/collection/ArtifactFilterException +instanceKlass org/codehaus/plexus/util/cli/CommandLineException +instanceKlass org/codehaus/plexus/compiler/util/scan/InclusionScanException +instanceKlass org/codehaus/plexus/compiler/CompilerException +instanceKlass org/codehaus/plexus/compiler/manager/NoSuchCompilerException +instanceKlass org/codehaus/plexus/interpolation/InterpolationException +instanceKlass org/apache/maven/artifact/DependencyResolutionRequiredException +instanceKlass org/codehaus/plexus/util/introspection/MethodMap$AmbiguousException +instanceKlass java/net/URISyntaxException +instanceKlass org/apache/maven/shared/filtering/MavenFilteringException +instanceKlass org/xml/sax/SAXException +instanceKlass javax/xml/parsers/ParserConfigurationException +instanceKlass org/codehaus/plexus/interpolation/reflection/MethodMap$AmbiguousException +instanceKlass org/apache/maven/cli/internal/ExtensionResolutionException +instanceKlass org/sonatype/plexus/components/sec/dispatcher/SecDispatcherException +instanceKlass org/apache/maven/toolchain/building/ToolchainsBuildingException +instanceKlass org/apache/maven/execution/MavenExecutionRequestPopulationException +instanceKlass org/sonatype/plexus/components/cipher/PlexusCipherException +instanceKlass org/apache/maven/model/resolution/UnresolvableModelException +instanceKlass org/apache/maven/model/resolution/InvalidRepositoryException +instanceKlass org/apache/maven/repository/ArtifactDoesNotExistException +instanceKlass org/apache/maven/repository/ArtifactTransferFailedException +instanceKlass org/codehaus/plexus/component/configurator/expression/ExpressionEvaluationException +instanceKlass org/codehaus/plexus/component/composition/CycleDetectedInComponentGraphException +instanceKlass org/codehaus/plexus/configuration/PlexusConfigurationException +instanceKlass org/apache/maven/repository/metadata/MetadataGraphTransformationException +instanceKlass org/apache/maven/repository/legacy/resolver/conflict/ConflictResolverNotFoundException +instanceKlass org/apache/maven/plugin/version/PluginVersionNotFoundException +instanceKlass org/apache/maven/plugin/InvalidPluginException +instanceKlass org/apache/maven/repository/metadata/GraphConflictResolutionException +instanceKlass org/apache/maven/repository/metadata/MetadataResolutionException +instanceKlass org/apache/maven/artifact/repository/metadata/RepositoryMetadataReadException +instanceKlass org/apache/maven/artifact/repository/metadata/RepositoryMetadataStoreException +instanceKlass org/codehaus/plexus/component/repository/exception/ComponentLifecycleException +instanceKlass java/security/GeneralSecurityException +instanceKlass org/apache/maven/project/DependencyResolutionException +instanceKlass org/apache/maven/model/building/ModelBuildingException +instanceKlass org/apache/maven/artifact/versioning/InvalidVersionSpecificationException +instanceKlass org/apache/http/HttpException +instanceKlass org/apache/maven/wagon/WagonException +instanceKlass org/apache/maven/plugin/PluginConfigurationException +instanceKlass org/apache/maven/configuration/BeanConfigurationException +instanceKlass org/codehaus/plexus/component/configurator/ComponentConfigurationException +instanceKlass org/apache/maven/project/interpolation/ModelInterpolationException +instanceKlass org/apache/maven/BuildFailureException +instanceKlass org/codehaus/plexus/util/dag/CycleDetectedException +instanceKlass org/apache/maven/MavenExecutionException +instanceKlass org/apache/maven/project/DuplicateProjectException +instanceKlass org/apache/maven/project/ProjectBuildingException +instanceKlass org/apache/maven/artifact/InvalidRepositoryException +instanceKlass org/codehaus/plexus/personality/plexus/lifecycle/phase/InitializationException +instanceKlass org/apache/maven/repository/legacy/metadata/ArtifactMetadataRetrievalException +instanceKlass org/apache/maven/artifact/deployer/ArtifactDeploymentException +instanceKlass org/apache/maven/artifact/installer/ArtifactInstallationException +instanceKlass org/apache/maven/plugin/PluginManagerException +instanceKlass org/apache/maven/settings/building/SettingsBuildingException +instanceKlass org/eclipse/aether/RepositoryException +instanceKlass org/apache/maven/lifecycle/LifecycleExecutionException +instanceKlass org/apache/maven/plugin/version/PluginVersionResolutionException +instanceKlass org/apache/maven/lifecycle/LifecycleNotFoundException +instanceKlass org/apache/maven/plugin/prefix/NoPluginFoundForPrefixException +instanceKlass org/apache/maven/plugin/InvalidPluginDescriptorException +instanceKlass org/apache/maven/plugin/MojoNotFoundException +instanceKlass org/apache/maven/plugin/PluginDescriptorParsingException +instanceKlass org/apache/maven/lifecycle/LifecyclePhaseNotFoundException +instanceKlass org/apache/maven/plugin/PluginResolutionException +instanceKlass org/apache/maven/artifact/repository/metadata/RepositoryMetadataResolutionException +instanceKlass org/apache/maven/artifact/resolver/AbstractArtifactResolutionException +instanceKlass org/apache/maven/lifecycle/internal/builder/BuilderNotFoundException +instanceKlass org/apache/maven/lifecycle/NoGoalSpecifiedException +instanceKlass org/apache/maven/lifecycle/MissingProjectException +instanceKlass org/apache/maven/toolchain/MisconfiguredToolchainException +instanceKlass org/apache/maven/plugin/AbstractMojoExecutionException +instanceKlass java/util/concurrent/TimeoutException +instanceKlass com/google/common/collect/RegularImmutableMap$BucketOverflowException +instanceKlass java/util/concurrent/ExecutionException +instanceKlass java/lang/InterruptedException +instanceKlass com/google/inject/internal/ErrorsException +instanceKlass com/google/inject/internal/InternalProvisionException +instanceKlass org/codehaus/plexus/context/ContextException +instanceKlass java/text/ParseException +instanceKlass org/codehaus/plexus/PlexusContainerException +instanceKlass org/codehaus/plexus/component/repository/exception/ComponentLookupException +instanceKlass org/codehaus/plexus/util/xml/pull/XmlPullParserException +instanceKlass java/lang/CloneNotSupportedException +instanceKlass sun/nio/fs/UnixException +instanceKlass org/apache/commons/cli/ParseException +instanceKlass org/codehaus/plexus/interpolation/InterpolationException +instanceKlass org/apache/maven/cli/MavenCli$ExitException +instanceKlass java/security/PrivilegedActionException +instanceKlass org/codehaus/plexus/classworlds/ClassWorldException +instanceKlass org/codehaus/plexus/classworlds/launcher/ConfigurationException +instanceKlass java/io/IOException +instanceKlass java/lang/ReflectiveOperationException +instanceKlass java/lang/RuntimeException +ciInstanceKlass java/lang/Exception 1 1 40 10 7 12 1 1 1 10 12 1 10 12 1 10 12 1 10 12 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +instanceKlass java/lang/InstantiationException +instanceKlass java/lang/NoSuchFieldException +instanceKlass java/lang/IllegalAccessException +instanceKlass java/lang/reflect/InvocationTargetException +instanceKlass java/lang/NoSuchMethodException +instanceKlass java/lang/ClassNotFoundException +ciInstanceKlass java/lang/ReflectiveOperationException 1 1 34 10 7 12 1 1 1 10 12 1 10 12 1 10 12 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 +instanceKlass java/util/ServiceConfigurationError +instanceKlass com/google/common/util/concurrent/ExecutionError +instanceKlass java/lang/AssertionError +instanceKlass java/io/IOError +instanceKlass org/apache/maven/BuildAbort +instanceKlass java/lang/VirtualMachineError +instanceKlass java/lang/LinkageError +instanceKlass java/lang/ThreadDeath +ciInstanceKlass java/lang/Error 1 1 40 10 7 12 1 1 1 10 12 1 10 12 1 10 12 1 10 12 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/ThreadDeath 0 0 21 10 100 12 1 1 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/ClassNotFoundException 1 1 96 7 1 10 7 12 1 1 1 10 12 1 10 12 1 1 10 100 12 1 1 1 8 1 10 100 12 1 1 1 10 7 12 1 1 10 100 12 1 1 1 10 100 12 1 1 1 10 12 1 1 7 1 10 12 1 9 12 1 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield java/lang/ClassNotFoundException serialPersistentFields [Ljava/io/ObjectStreamField; 1 [Ljava/io/ObjectStreamField; +instanceKlass java/lang/ClassFormatError +instanceKlass java/lang/UnsatisfiedLinkError +instanceKlass java/lang/IncompatibleClassChangeError +instanceKlass java/lang/BootstrapMethodError +instanceKlass java/lang/NoClassDefFoundError +ciInstanceKlass java/lang/LinkageError 1 1 31 10 7 12 1 1 1 10 12 1 10 12 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/Record 0 0 22 10 100 12 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/StringLatin1 1 1 380 7 1 10 100 12 1 1 1 100 1 10 12 1 10 7 12 1 1 1 10 7 12 1 1 1 10 12 1 10 7 12 1 1 1 10 12 1 1 10 7 12 1 1 1 10 12 1 1 10 12 1 1 10 10 12 1 10 12 1 1 10 9 7 12 1 1 1 10 12 1 1 10 7 12 1 1 1 10 12 10 12 1 1 10 12 1 1 10 12 1 1 10 7 12 1 1 1 10 12 1 10 12 1 1 9 12 1 1 100 1 10 10 100 12 1 1 1 10 7 12 1 1 1 10 12 1 10 12 1 100 1 100 1 8 1 10 12 1 8 1 10 12 100 1 10 10 10 7 12 1 1 1 8 1 8 1 8 1 10 12 1 1 10 100 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 10 12 10 12 1 10 12 10 10 12 1 10 12 1 10 12 1 10 100 12 1 1 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 10 10 7 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 +staticfield java/lang/StringLatin1 $assertionsDisabled Z 1 +ciInstanceKlass java/util/Arrays 1 1 988 10 7 12 1 1 1 10 100 12 1 1 1 10 7 12 1 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 100 12 1 1 1 100 1 100 1 10 8 1 10 12 1 1 10 12 1 8 1 8 1 10 12 1 1 10 12 1 100 1 10 12 1 9 100 12 1 1 1 10 7 12 1 1 100 1 10 12 1 1 10 7 12 1 1 10 7 12 1 1 1 100 1 10 12 1 10 12 1 1 7 1 9 7 12 1 1 1 10 12 1 1 10 100 12 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 7 1 11 12 1 1 10 12 1 10 7 12 1 1 1 10 12 10 12 1 10 12 1 10 12 10 12 1 11 7 12 1 1 1 10 7 12 1 1 1 100 1 10 12 1 10 100 1 10 12 1 10 100 1 10 12 1 10 100 1 10 12 1 10 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 100 12 1 1 1 10 12 1 10 100 12 1 1 1 10 12 1 10 12 1 10 100 12 1 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 7 12 1 1 1 10 12 1 8 1 7 1 10 12 10 12 1 10 12 1 1 10 12 1 1 9 100 12 1 1 1 100 1 10 12 1 9 100 1 100 1 10 12 1 9 100 1 100 1 10 12 1 9 100 1 100 1 10 12 1 9 100 1 100 1 10 12 1 9 100 1 100 1 10 12 1 9 100 1 10 12 1 100 1 10 12 1 10 12 1 9 12 1 100 1 10 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 8 1 8 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 10 12 1 10 100 12 1 1 1 3 10 100 1 10 10 12 1 1 11 100 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 11 12 1 8 1 10 11 12 1 11 7 12 1 1 1 11 100 12 1 1 1 11 12 1 1 18 12 1 1 11 12 1 1 11 100 12 1 1 1 18 12 1 11 100 12 1 1 1 18 12 1 11 100 12 1 1 1 18 12 1 100 1 10 7 12 1 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 7 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 10 12 1 10 12 10 12 10 12 1 10 12 10 12 1 10 12 1 10 12 1 1 1 1 3 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 15 10 100 12 1 1 1 16 15 10 12 15 10 12 15 10 12 15 10 12 1 1 100 1 100 1 1 1 1 100 1 1 1 1 1 1 100 1 1 100 1 1 100 1 1 1 100 1 100 1 1 +staticfield java/util/Arrays $assertionsDisabled Z 1 +ciMethod java/lang/StringLatin1 equals ([B[B)Z 326 1478 5241 0 -1 +ciMethod java/lang/StringLatin1 hashCode ([B)I 64 1238 929 0 352 +ciMethod java/lang/StringLatin1 regionMatchesCI ([BI[BII)Z 540 1624 3797 0 1120 +ciMethod java/lang/StringLatin1 regionMatchesCI_UTF16 ([BI[BII)Z 0 0 1 0 -1 +ciInstanceKlass java/lang/StringUTF16 1 1 598 100 1 7 1 10 100 12 1 1 1 100 1 10 7 1 3 100 1 100 1 10 8 1 10 12 1 1 10 12 1 8 1 10 12 1 1 10 12 1 9 12 1 1 10 12 1 1 100 1 8 1 10 12 1 9 12 1 1 9 12 1 10 10 12 1 1 10 12 1 1 10 100 12 1 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 100 1 10 12 1 10 10 12 1 1 10 12 1 10 12 1 1 10 12 1 10 7 12 1 1 1 10 100 12 1 1 1 10 12 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 3 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 9 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 10 12 10 12 10 10 100 12 1 1 1 10 7 12 1 1 1 10 12 1 10 12 1 100 1 8 1 8 1 10 12 1 1 100 1 10 10 100 12 1 1 1 10 100 12 1 1 8 1 8 1 8 1 10 12 1 1 10 12 1 1 10 12 10 12 1 10 100 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 10 12 10 12 1 10 12 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 100 12 1 1 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 11 100 12 1 1 10 12 1 10 12 1 10 12 1 1 9 12 1 1 9 12 1 5 0 5 0 10 12 1 10 12 10 12 10 7 12 1 1 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 1 1 1 1 +staticfield java/lang/StringUTF16 HI_BYTE_SHIFT I 0 +staticfield java/lang/StringUTF16 LO_BYTE_SHIFT I 8 +staticfield java/lang/StringUTF16 $assertionsDisabled Z 1 +ciMethod java/lang/StringUTF16 hashCode ([B)I 0 0 1 0 -1 +ciMethod java/lang/StringUTF16 regionMatchesCI ([BI[BII)Z 0 0 1 0 -1 +ciMethod java/lang/StringUTF16 regionMatchesCI_Latin1 ([BI[BII)Z 0 0 1 0 -1 +ciInstanceKlass java/lang/Boolean 1 1 151 10 7 12 1 1 1 9 7 12 1 1 1 10 12 1 1 10 12 1 8 1 10 7 12 1 1 9 12 1 1 9 12 1 8 1 10 12 1 1 10 12 1 1 10 7 12 1 1 1 100 1 100 1 10 12 1 1 9 100 12 1 1 9 12 10 100 12 1 1 1 10 12 1 1 8 1 10 7 12 1 1 1 9 12 1 1 100 1 100 1 100 1 1 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 +staticfield java/lang/Boolean TRUE Ljava/lang/Boolean; java/lang/Boolean +staticfield java/lang/Boolean FALSE Ljava/lang/Boolean; java/lang/Boolean +staticfield java/lang/Boolean TYPE Ljava/lang/Class; java/lang/Class +instanceKlass java/util/concurrent/locks/AbstractQueuedSynchronizer +ciInstanceKlass java/util/concurrent/locks/AbstractOwnableSynchronizer 1 1 32 10 7 12 1 1 1 9 7 12 1 1 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 +instanceKlass java/math/BigInteger +instanceKlass java/util/concurrent/atomic/AtomicLong +instanceKlass java/util/concurrent/atomic/AtomicInteger +instanceKlass java/lang/Long +instanceKlass java/lang/Integer +instanceKlass java/lang/Short +instanceKlass java/lang/Byte +instanceKlass java/lang/Double +instanceKlass java/lang/Float +ciInstanceKlass java/lang/Number 1 1 37 10 7 12 1 1 1 10 100 12 1 1 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +instanceKlass java/lang/LiveStackFrameInfo +ciInstanceKlass java/lang/StackFrameInfo 0 0 132 10 100 12 1 1 1 9 100 12 1 1 1 9 100 1 9 12 1 1 11 100 12 1 1 1 9 12 1 1 11 12 1 1 10 12 1 1 10 100 12 1 1 1 10 12 1 11 12 1 11 12 1 1 11 12 1 10 12 1 1 9 12 1 1 10 12 1 1 10 100 12 1 1 10 12 1 1 11 12 1 1 10 12 1 9 12 1 1 10 12 1 1 100 1 8 1 10 12 1 10 100 12 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 +ciInstanceKlass java/lang/LiveStackFrameInfo 0 0 97 10 100 12 1 1 1 9 100 12 1 1 1 9 12 1 9 12 1 9 12 1 9 12 1 1 100 1 10 12 1 1 10 12 1 8 1 10 12 1 1 8 1 8 1 8 1 10 100 1 10 12 1 100 1 10 12 1 100 1 100 1 1 1 3 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 +ciMethod java/lang/Boolean parseBoolean (Ljava/lang/String;)Z 48 0 3604 0 0 +ciInstanceKlass java/lang/Character 1 1 576 7 1 100 1 100 1 9 12 1 1 8 1 9 12 1 1 100 1 9 12 1 1 10 100 12 1 1 1 10 100 12 1 1 1 10 100 12 1 1 1 10 100 12 1 1 1 9 7 12 1 1 1 10 12 1 10 12 1 1 10 12 1 1 10 7 12 1 1 10 12 1 1 3 3 3 3 3 10 12 1 1 10 12 1 3 11 7 12 1 1 1 11 12 1 1 10 12 1 1 10 12 1 1 100 1 10 10 12 1 3 10 12 1 1 10 12 1 10 12 1 1 100 1 8 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 7 12 1 1 10 10 12 1 10 10 12 1 10 12 1 1 10 12 1 10 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 10 10 12 10 10 12 1 10 10 12 1 10 10 12 1 10 10 12 1 10 10 12 1 10 10 12 1 10 10 12 1 1 10 10 12 1 10 5 0 10 12 1 10 12 1 10 10 12 1 10 10 12 1 1 10 10 12 1 10 10 12 1 9 12 1 1 100 1 10 10 12 1 10 12 1 1 3 10 100 12 1 1 1 10 12 1 10 100 12 1 1 100 1 10 10 12 1 1 10 12 1 1 10 12 1 1 8 1 10 12 1 9 100 12 1 1 1 10 12 1 10 10 12 1 10 12 1 1 10 12 1 10 10 12 1 1 10 10 12 1 1 100 1 8 1 10 12 1 1 10 7 12 1 1 1 8 1 10 12 1 1 9 12 1 1 100 1 100 1 100 1 1 1 1 3 1 3 1 3 1 3 1 1 1 1 1 3 1 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 3 1 1 3 1 1 1 1 1 3 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 1 1 1 1 1 +staticfield java/lang/Character TYPE Ljava/lang/Class; java/lang/Class +staticfield java/lang/Character $assertionsDisabled Z 1 +ciInstanceKlass java/lang/Byte 1 1 215 7 1 100 1 10 100 12 1 1 1 9 12 1 1 8 1 9 12 1 1 100 1 10 12 1 1 10 12 1 1 10 100 12 1 1 1 10 100 12 1 1 1 9 100 12 1 1 1 10 12 1 1 7 1 100 1 10 12 1 1 8 1 10 12 1 1 8 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 8 1 8 1 10 7 1 9 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 5 0 10 12 1 1 8 1 10 7 12 1 1 1 9 12 1 1 100 1 100 1 1 1 3 1 3 1 1 1 1 3 1 3 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield java/lang/Byte TYPE Ljava/lang/Class; java/lang/Class +ciInstanceKlass java/lang/Short 1 1 224 7 1 100 1 100 1 10 100 12 1 1 1 10 12 1 1 7 1 100 1 10 12 1 1 8 1 10 12 1 1 8 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 9 12 1 1 8 1 9 12 1 1 100 1 10 12 1 1 10 12 1 10 100 12 1 1 1 10 100 12 1 1 1 9 100 12 1 1 1 10 12 1 10 12 1 1 10 8 1 8 1 10 100 1 9 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 3 3 5 0 10 12 1 1 8 1 10 7 12 1 1 1 9 12 1 1 100 1 100 1 1 1 3 1 3 1 1 1 1 3 1 3 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield java/lang/Short TYPE Ljava/lang/Class; java/lang/Class +ciMethod java/lang/Character toLowerCase (I)I 280 0 1087 0 0 +ciInstanceKlass java/lang/CharacterDataLatin1 1 1 130 9 7 12 1 1 1 9 12 1 1 10 12 1 1 10 12 1 10 12 1 1 3 3 3 10 12 1 9 12 1 100 1 3 3 9 12 1 1 10 7 12 1 1 1 10 9 12 1 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 +staticfield java/lang/CharacterDataLatin1 DIGITS [B 256 +staticfield java/lang/CharacterDataLatin1 instance Ljava/lang/CharacterDataLatin1; java/lang/CharacterDataLatin1 +staticfield java/lang/CharacterDataLatin1 A [I 256 +staticfield java/lang/CharacterDataLatin1 B [B 256 +instanceKlass java/lang/CharacterData00 +instanceKlass java/lang/CharacterDataLatin1 +ciInstanceKlass java/lang/CharacterData 1 1 80 10 7 12 1 1 1 10 100 12 1 1 1 9 7 12 1 1 1 9 7 12 1 1 9 100 12 1 1 9 100 1 9 100 1 9 100 1 9 100 1 9 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/CharacterData00 1 1 250 9 7 12 1 1 1 9 12 1 1 9 12 1 9 12 1 10 12 1 1 10 12 1 10 12 1 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 10 12 1 100 1 3 3 3 3 10 12 1 9 12 1 1 10 7 12 1 1 1 10 7 12 1 1 1 9 12 1 1 10 9 12 1 1 8 1 10 7 12 1 1 1 8 1 8 1 7 1 7 3 3 3 3 3 3 3 3 3 3 3 3 8 1 100 1 10 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield java/lang/CharacterData00 instance Ljava/lang/CharacterData00; java/lang/CharacterData00 +staticfield java/lang/CharacterData00 charMap [[[C 103 [[[C +staticfield java/lang/CharacterData00 X [C 2048 +staticfield java/lang/CharacterData00 Y [C 5856 +staticfield java/lang/CharacterData00 A [I 972 +staticfield java/lang/CharacterData00 B [C 972 +staticfield java/lang/CharacterData00 $assertionsDisabled Z 1 +ciMethod java/lang/CharacterData toLowerCase (I)I 0 0 1 0 -1 +ciMethod java/lang/CharacterData of (I)Ljava/lang/CharacterData; 506 0 6417 0 128 +ciMethod java/lang/CharacterDataLatin1 toUpperCase (I)I 268 0 9065 0 192 +ciMethod java/lang/CharacterDataLatin1 getProperties (I)I 546 0 41832 0 96 +ciInstanceKlass java/lang/Float 1 1 223 7 1 100 1 10 7 12 1 1 1 10 100 12 1 1 1 4 100 1 10 12 1 1 10 12 1 1 8 1 8 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 10 4 4 4 10 7 12 1 1 9 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 3 10 12 1 1 10 12 1 1 10 12 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 8 1 10 7 12 1 1 1 9 12 1 1 100 1 100 1 100 1 1 1 1 1 4 1 1 1 4 1 1 3 1 3 1 3 1 3 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 7 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 7 1 1 1 1 1 1 1 100 1 100 1 1 +staticfield java/lang/Float TYPE Ljava/lang/Class; java/lang/Class +ciMethod java/lang/Float isNaN (F)Z 514 0 12055 0 0 +ciInstanceKlass java/lang/Double 1 1 285 7 1 100 1 10 7 12 1 1 1 10 12 1 1 10 12 1 100 1 10 12 1 1 10 100 12 1 1 1 6 0 8 1 10 12 1 1 8 1 10 12 1 1 8 1 6 0 10 12 1 1 100 1 5 0 5 0 8 1 8 1 10 100 12 1 1 1 10 100 12 1 1 1 8 1 10 12 1 1 8 1 8 1 8 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 6 0 6 0 6 0 10 7 12 1 1 9 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 5 0 10 12 1 1 10 12 1 10 12 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 8 1 10 7 12 1 1 1 9 12 1 1 100 1 100 1 100 1 1 1 1 1 6 0 1 1 1 6 0 1 1 3 1 3 1 3 1 3 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 7 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 7 1 1 1 1 1 1 1 100 1 100 1 1 +staticfield java/lang/Double TYPE Ljava/lang/Class; java/lang/Class +ciInstanceKlass java/lang/Integer 1 1 445 7 1 100 1 7 1 7 1 10 12 1 1 9 12 1 1 9 12 1 1 10 100 12 1 1 1 10 12 1 1 10 100 12 1 1 1 10 10 12 1 1 10 100 12 1 1 1 10 12 1 10 12 1 1 10 7 12 1 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 10 12 1 9 12 1 1 9 12 1 7 1 8 1 10 12 1 100 1 10 12 1 8 1 10 12 1 1 10 12 1 8 1 10 12 1 8 1 10 12 1 1 3 10 12 1 1 3 10 12 1 1 10 12 1 1 10 7 12 1 1 1 11 7 1 100 1 10 11 10 12 1 1 8 1 10 12 1 1 8 1 100 1 10 12 1 1 10 12 1 1 5 0 8 1 10 12 1 10 12 1 10 12 1 10 12 1 1 7 1 9 12 1 1 9 12 1 1 10 12 1 10 7 1 9 12 1 10 12 1 10 12 1 10 12 1 1 10 7 12 1 1 1 100 1 100 1 10 12 1 1 10 12 1 1 8 1 8 1 10 12 1 1 8 1 8 1 8 1 8 1 8 1 8 1 10 12 1 10 12 1 10 12 1 5 0 3 3 3 3 10 12 1 3 10 12 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 8 1 10 7 12 1 1 1 9 12 1 1 3 3 3 3 3 3 9 12 1 1 100 1 100 1 100 1 1 1 1 1 1 1 3 1 3 1 1 5 0 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 7 1 1 1 1 1 1 1 1 1 100 1 100 1 1 +staticfield java/lang/Integer TYPE Ljava/lang/Class; java/lang/Class +staticfield java/lang/Integer digits [C 36 +staticfield java/lang/Integer DigitTens [B 100 +staticfield java/lang/Integer DigitOnes [B 100 +staticfield java/lang/Integer sizeTable [I 10 +ciMethod java/lang/Integer numberOfLeadingZeros (I)I 32 0 5157 0 -1 +ciInstanceKlass java/lang/Long 1 1 506 7 1 100 1 7 1 7 1 10 12 1 1 9 12 1 1 9 7 12 1 1 1 10 100 12 1 1 1 10 12 1 1 10 100 12 1 1 1 10 10 12 10 12 1 10 12 1 10 12 1 5 0 5 0 100 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 100 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 7 12 1 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 10 12 1 5 0 5 0 9 12 1 1 9 12 1 5 0 7 1 8 1 10 12 1 8 1 10 12 1 8 1 8 1 10 12 1 1 5 0 10 12 1 1 5 0 10 12 1 1 10 12 1 1 10 100 12 1 1 1 11 100 1 100 1 10 11 10 12 1 1 8 1 10 12 1 1 8 1 100 1 10 12 1 1 10 12 1 8 1 8 1 11 12 1 1 10 12 1 10 12 1 10 12 1 5 0 5 0 9 7 12 1 1 1 10 12 1 10 12 1 1 8 1 8 1 10 12 1 1 8 1 8 1 8 1 8 1 8 1 8 1 10 12 1 10 12 1 10 12 1 1 10 7 1 9 12 1 1 10 12 1 10 12 1 1 10 100 12 1 1 1 100 1 100 1 10 12 1 1 10 12 1 1 5 0 10 12 1 10 12 1 5 0 5 0 5 0 10 12 1 1 5 0 5 0 10 12 1 10 12 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 8 1 10 7 12 1 1 1 9 12 1 1 100 1 100 1 100 1 1 1 1 5 0 1 1 1 1 3 1 3 1 5 0 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 7 1 1 1 1 1 1 1 1 1 100 1 100 1 1 +staticfield java/lang/Long TYPE Ljava/lang/Class; java/lang/Class +instanceKlass java/lang/ref/PhantomReference +instanceKlass java/lang/ref/FinalReference +instanceKlass java/lang/ref/WeakReference +instanceKlass java/lang/ref/SoftReference +ciInstanceKlass java/lang/ref/Reference 1 1 195 9 7 12 1 1 1 9 7 12 1 1 10 12 1 1 10 12 1 1 9 12 1 1 10 12 1 1 9 12 1 1 9 12 1 1 7 1 10 12 1 10 7 12 1 1 10 12 1 10 12 1 1 10 12 1 9 12 1 10 12 1 1 10 12 1 10 12 1 9 12 1 100 1 100 1 10 12 1 9 12 1 9 12 1 100 1 10 10 12 1 10 10 7 12 1 1 10 7 12 1 1 1 10 12 1 1 10 7 12 1 1 7 1 8 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 7 1 10 10 7 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield java/lang/ref/Reference processPendingLock Ljava/lang/Object; java/lang/Object +staticfield java/lang/ref/Reference $assertionsDisabled Z 1 +instanceKlass jdk/internal/ref/PhantomCleanable +instanceKlass jdk/internal/ref/Cleaner +ciInstanceKlass java/lang/ref/PhantomReference 1 1 39 10 100 12 1 1 1 10 7 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +instanceKlass java/lang/ref/Finalizer +ciInstanceKlass java/lang/ref/FinalReference 1 1 47 10 100 12 1 1 1 10 100 12 1 1 1 10 12 1 1 100 1 8 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/ref/Finalizer 1 1 152 9 7 12 1 1 1 10 100 12 1 1 1 9 12 1 1 9 12 1 1 9 12 1 9 12 1 10 12 1 10 12 1 1 9 12 1 1 100 1 10 12 1 100 1 11 100 12 1 1 100 1 10 12 1 100 1 10 12 1 10 100 12 1 1 1 10 100 12 1 1 1 100 1 10 10 12 1 10 7 12 1 1 1 7 1 10 7 1 10 10 7 12 1 1 1 10 12 1 1 10 7 12 1 1 7 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield java/lang/ref/Finalizer lock Ljava/lang/Object; java/lang/Object +staticfield java/lang/ref/Finalizer $assertionsDisabled Z 1 +instanceKlass sun/nio/ch/FileLockTable$FileLockReference +instanceKlass sun/security/provider/FileInputStreamPool$StreamRef +instanceKlass org/eclipse/sisu/inject/MildElements$Weak +instanceKlass com/google/common/collect/MapMakerInternalMap$AbstractWeakKeyEntry +instanceKlass com/google/common/cache/LocalCache$WeakEntry +instanceKlass java/lang/WeakPairMap$WeakRefPeer +instanceKlass java/lang/ClassValue$Entry +instanceKlass com/google/common/cache/LocalCache$WeakValueReference +instanceKlass java/util/logging/LogManager$LoggerWeakRef +instanceKlass java/util/logging/Level$KnownLevel +instanceKlass org/eclipse/sisu/inject/MildKeys$Weak +instanceKlass java/lang/invoke/MethodType$ConcurrentWeakInternSet$WeakEntry +instanceKlass java/lang/ThreadLocal$ThreadLocalMap$Entry +instanceKlass java/util/WeakHashMap$Entry +ciInstanceKlass java/lang/ref/WeakReference 1 1 31 10 7 12 1 1 1 10 12 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +instanceKlass org/eclipse/sisu/inject/MildElements$Soft +instanceKlass com/google/common/cache/LocalCache$SoftValueReference +instanceKlass sun/util/locale/provider/LocaleResources$ResourceReference +instanceKlass sun/util/resources/Bundles$BundleReference +instanceKlass sun/util/locale/LocaleObjectCache$CacheEntry +instanceKlass org/eclipse/sisu/inject/MildKeys$Soft +instanceKlass java/lang/invoke/LambdaFormEditor$Transform +ciInstanceKlass java/lang/ref/SoftReference 1 1 47 10 7 12 1 1 1 9 7 12 1 1 1 9 12 1 10 12 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 +ciInstanceKlass java/lang/IllegalMonitorStateException 1 1 26 10 100 12 1 1 1 10 12 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 +instanceKlass com/sun/tools/javac/util/ClientCodeException +instanceKlass com/sun/tools/javac/util/PropagatedException +instanceKlass org/apache/maven/project/DuplicateArtifactAttachmentException +instanceKlass java/time/DateTimeException +instanceKlass org/eclipse/aether/named/support/LockUpgradeNotSupportedException +instanceKlass java/util/ConcurrentModificationException +instanceKlass com/google/inject/internal/aop/GlueException +instanceKlass java/io/UncheckedIOException +instanceKlass org/apache/maven/artifact/InvalidArtifactRTException +instanceKlass com/google/inject/OutOfScopeException +instanceKlass java/lang/annotation/IncompleteAnnotationException +instanceKlass java/lang/reflect/UndeclaredThrowableException +instanceKlass com/google/common/util/concurrent/UncheckedExecutionException +instanceKlass com/google/common/cache/CacheLoader$InvalidCacheLoadException +instanceKlass java/util/NoSuchElementException +instanceKlass com/google/inject/CreationException +instanceKlass com/google/inject/ConfigurationException +instanceKlass com/google/inject/ProvisionException +instanceKlass java/lang/TypeNotPresentException +instanceKlass java/lang/IndexOutOfBoundsException +instanceKlass java/lang/UnsupportedOperationException +instanceKlass java/lang/SecurityException +instanceKlass java/lang/IllegalStateException +instanceKlass java/lang/IllegalArgumentException +instanceKlass java/lang/ArithmeticException +instanceKlass java/lang/NullPointerException +instanceKlass java/lang/IllegalMonitorStateException +instanceKlass java/lang/ArrayStoreException +instanceKlass java/lang/ClassCastException +ciInstanceKlass java/lang/RuntimeException 1 1 40 10 7 12 1 1 1 10 12 1 10 12 1 10 12 1 10 12 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +instanceKlass org/springframework/boot/loader/tools/BuildPropertiesWriter$NullAdditionalPropertyValueException +instanceKlass java/nio/charset/UnsupportedCharsetException +instanceKlass java/lang/NumberFormatException +instanceKlass org/apache/maven/cli/MavenCli$IllegalUseOfUndefinedProperty +ciInstanceKlass java/lang/IllegalArgumentException 1 1 35 10 100 12 1 1 1 10 12 1 10 12 1 10 12 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/ArithmeticException 1 1 26 10 100 12 1 1 1 10 12 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/ArrayStoreException 1 1 26 10 100 12 1 1 1 10 12 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/ClassCastException 1 1 26 10 100 12 1 1 1 10 12 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/NoClassDefFoundError 1 1 26 10 7 12 1 1 1 10 12 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/StackOverflowError 1 1 26 10 100 12 1 1 1 10 12 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 +instanceKlass java/lang/StackOverflowError +instanceKlass java/lang/OutOfMemoryError +instanceKlass java/lang/InternalError +ciInstanceKlass java/lang/VirtualMachineError 1 1 34 10 100 12 1 1 1 10 12 1 10 12 1 10 12 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/OutOfMemoryError 1 1 26 10 100 12 1 1 1 10 12 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/InternalError 0 0 34 10 100 12 1 1 1 10 12 1 10 12 1 10 12 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 +instanceKlass org/apache/maven/project/DefaultProjectBuilder$1 +instanceKlass java/util/Collections$SingletonMap +instanceKlass org/eclipse/sisu/wire/EntryMapAdapter +instanceKlass com/google/common/collect/Maps$ViewCachingAbstractMap +instanceKlass com/google/common/collect/MapMakerInternalMap +instanceKlass org/eclipse/sisu/wire/MergedProperties +instanceKlass com/google/common/cache/LocalCache +instanceKlass java/util/EnumMap +instanceKlass java/lang/ProcessEnvironment$StringEnvironment +instanceKlass java/util/TreeMap +instanceKlass java/util/IdentityHashMap +instanceKlass java/util/WeakHashMap +instanceKlass java/util/Collections$EmptyMap +instanceKlass java/util/HashMap +instanceKlass java/util/ImmutableCollections$AbstractImmutableMap +instanceKlass java/util/concurrent/ConcurrentHashMap +ciInstanceKlass java/util/AbstractMap 1 1 192 10 7 12 1 1 1 10 7 12 1 1 1 11 7 12 1 1 1 10 11 12 1 1 11 7 12 1 1 1 11 12 1 1 7 1 11 12 1 10 12 1 1 11 12 1 100 1 10 11 12 1 11 7 1 10 12 1 1 11 12 1 9 12 1 1 7 1 10 12 1 9 12 1 1 7 1 10 11 11 12 1 1 11 12 1 100 1 100 1 11 12 1 8 1 100 1 10 10 12 1 1 8 1 10 12 1 10 12 1 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 7 1 1 1 1 1 1 1 1 1 100 1 100 1 100 1 100 1 1 1 1 1 +instanceKlass java/lang/reflect/Executable +instanceKlass java/lang/reflect/Field +ciInstanceKlass java/lang/reflect/AccessibleObject 1 1 398 10 7 12 1 1 1 9 100 12 1 1 1 10 100 12 1 1 1 10 7 12 1 1 10 7 12 1 1 1 10 7 12 1 1 1 10 12 1 1 10 12 1 1 9 12 1 1 100 1 10 7 12 1 1 1 11 12 1 100 1 10 12 1 7 1 100 1 10 12 1 10 12 1 1 7 1 10 7 12 1 1 1 10 12 1 1 100 1 10 12 1 1 100 1 10 10 10 7 12 1 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 8 1 100 1 10 10 12 1 1 8 1 10 12 1 10 12 1 8 1 8 1 8 1 8 1 8 1 8 1 100 1 10 12 1 10 12 1 9 12 1 1 10 12 1 1 10 12 1 10 11 100 1 100 1 8 1 10 10 12 1 10 12 1 1 8 1 10 12 1 8 1 10 12 1 1 10 100 1 8 1 10 11 7 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 9 12 1 1 100 1 10 12 1 7 1 10 12 1 10 12 1 1 10 100 1 10 12 1 10 12 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 9 12 1 10 100 12 1 1 8 1 10 100 12 1 1 1 8 1 10 100 12 1 1 1 9 12 1 100 1 10 7 1 10 10 7 12 1 1 1 7 1 10 10 7 12 1 1 1 7 1 9 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield java/lang/reflect/AccessibleObject reflectionFactory Ljdk/internal/reflect/ReflectionFactory; jdk/internal/reflect/ReflectionFactory +instanceKlass java/lang/reflect/Constructor +instanceKlass java/lang/reflect/Method +ciInstanceKlass java/lang/reflect/Executable 1 1 548 10 7 12 1 1 1 10 7 12 1 1 1 10 7 12 1 1 1 11 7 12 1 1 1 10 7 12 1 1 1 10 12 1 1 10 7 12 1 1 1 10 7 12 1 1 1 10 12 1 8 1 10 10 12 1 1 10 12 1 1 10 100 12 1 1 1 18 12 1 1 11 100 12 1 1 1 8 1 8 1 8 1 10 100 12 1 1 1 11 12 1 1 100 1 8 1 8 1 10 12 1 100 1 8 1 10 12 1 8 1 11 100 12 1 1 1 100 1 10 12 1 1 11 12 1 8 1 18 8 1 10 12 1 10 12 1 1 18 8 1 10 12 1 100 1 10 12 1 10 12 1 11 100 12 1 1 10 12 1 1 8 1 8 1 10 12 1 1 10 12 1 1 10 10 12 1 100 1 10 10 12 1 10 12 1 1 10 7 12 1 1 10 12 1 10 12 1 10 12 1 1 10 7 12 1 1 10 12 1 10 12 1 10 100 12 1 1 1 10 12 1 8 1 10 12 1 10 12 1 3 100 1 8 1 10 12 1 10 12 1 10 10 12 1 10 12 1 1 8 1 8 1 8 1 9 12 1 10 12 1 100 1 8 1 9 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 10 12 1 7 1 10 12 1 10 12 1 1 100 1 10 100 12 1 1 1 7 1 10 7 12 1 1 1 10 12 1 1 11 7 12 1 1 10 7 12 1 1 10 100 12 1 1 1 10 12 1 1 9 12 1 10 12 1 1 10 12 1 10 12 1 1 9 7 12 1 1 1 10 7 12 1 1 1 10 12 1 10 12 1 1 9 12 1 10 10 10 10 100 12 1 1 1 10 12 1 9 12 1 10 12 1 1 9 12 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 1 15 10 100 12 1 1 1 16 15 16 1 16 1 15 10 12 16 1 100 1 1 100 1 100 1 1 +ciInstanceKlass java/lang/reflect/Constructor 1 1 433 10 7 12 1 1 1 10 7 12 1 1 9 7 12 1 1 1 10 12 1 1 10 12 1 1 10 7 12 1 1 9 12 1 1 10 7 12 1 1 1 9 12 1 1 9 12 1 1 9 12 1 9 12 1 1 9 12 1 9 12 1 1 9 12 1 1 9 12 1 100 1 8 1 10 12 1 10 12 1 9 12 1 1 10 7 12 1 1 10 7 12 1 1 1 10 12 1 1 10 12 1 1 10 12 1 7 1 100 1 8 1 10 10 12 1 10 12 1 10 12 1 1 10 12 1 1 9 100 12 1 1 1 10 7 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 7 12 1 1 1 10 100 12 1 1 10 12 1 1 10 12 1 10 100 12 1 1 1 8 1 10 10 12 1 100 1 8 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 9 12 1 1 10 12 1 1 10 12 1 1 10 12 1 8 1 10 12 1 1 11 7 12 1 1 1 10 12 1 1 10 12 1 10 12 1 9 12 1 1 10 7 12 1 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 9 100 12 1 1 10 12 1 10 12 1 10 12 1 100 1 8 1 10 10 12 1 1 10 12 1 10 10 12 1 1 10 12 1 1 10 100 12 1 1 1 11 100 12 1 1 1 10 12 1 1 9 100 12 1 1 1 10 100 12 1 1 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 +ciInstanceKlass java/lang/reflect/Method 1 1 450 9 7 12 1 1 1 10 7 12 1 1 1 10 7 12 1 1 9 12 1 1 10 12 1 1 10 12 1 1 10 7 12 1 1 10 7 12 1 1 1 9 12 1 1 9 12 1 9 12 1 1 9 12 1 9 12 1 9 12 1 1 9 12 1 9 12 1 1 9 12 1 9 12 1 9 12 1 1 100 1 8 1 10 12 1 10 12 1 9 12 1 1 8 1 10 7 12 1 1 10 7 12 1 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 9 7 12 1 1 1 10 12 1 1 10 12 10 7 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 7 12 1 1 1 10 12 1 1 10 7 1 10 100 12 1 1 1 10 7 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 7 12 1 1 1 10 12 1 10 8 1 10 12 1 10 12 1 7 1 8 1 8 1 8 1 10 12 1 10 12 1 1 10 12 1 1 10 10 12 1 1 10 12 1 11 100 1 9 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 11 7 12 1 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 9 12 1 1 10 7 12 1 1 1 10 12 1 1 10 7 12 1 1 1 10 7 12 1 1 1 10 7 12 1 1 1 11 7 12 1 1 1 10 7 12 1 1 1 7 1 100 1 100 1 10 12 1 10 12 1 1 10 12 1 100 1 8 1 10 12 1 10 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 8 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/reflect/Field 1 1 437 9 7 12 1 1 1 10 12 1 1 10 100 12 1 1 1 10 100 12 1 1 9 12 1 1 10 12 1 1 10 12 1 1 10 7 12 1 1 10 7 12 1 1 1 9 12 1 1 9 12 1 9 12 1 9 12 1 1 9 12 1 1 9 12 1 9 12 1 1 9 12 1 1 100 1 8 1 10 12 1 10 12 1 9 12 1 1 9 12 1 10 12 1 10 7 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 7 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 7 1 10 7 12 1 1 100 1 10 8 1 10 12 1 1 10 12 1 1 8 1 10 12 10 12 1 8 1 8 1 10 11 100 1 9 12 1 10 12 1 1 10 12 1 1 11 7 12 1 1 1 11 12 1 1 11 12 1 1 11 12 1 1 11 12 1 1 11 12 1 1 11 12 1 1 11 12 1 1 11 12 1 1 11 12 1 1 11 12 1 1 11 12 1 1 11 12 1 1 11 12 1 1 11 12 1 1 11 12 1 1 11 12 1 1 11 12 1 1 10 12 1 10 100 12 1 1 10 12 1 10 12 1 1 10 12 9 12 1 1 10 7 12 1 1 1 10 12 1 1 10 7 12 1 1 10 12 1 1 11 7 1 10 12 1 7 1 10 100 12 1 1 1 10 7 12 1 1 1 9 12 1 10 7 12 1 1 1 11 7 12 1 1 1 10 12 1 1 10 12 1 1 9 7 12 1 1 1 10 7 12 1 1 1 10 12 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 100 1 1 +ciInstanceKlass java/lang/reflect/Parameter 1 1 226 10 7 12 1 1 1 9 7 12 1 1 1 9 12 1 1 9 12 1 1 9 12 1 10 12 1 1 10 12 1 1 10 100 12 1 1 1 100 1 10 10 12 1 1 11 100 12 1 1 1 10 12 1 10 100 12 1 1 1 10 12 1 1 10 12 1 10 12 1 8 1 8 1 10 100 12 1 1 1 10 12 1 10 12 10 12 1 8 1 10 12 1 9 12 1 1 10 12 1 1 9 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 10 12 1 10 100 12 1 1 1 10 12 1 1 11 100 12 1 1 10 100 12 1 1 100 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 9 12 1 100 1 10 11 12 1 1 11 12 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 +ciInstanceKlass java/lang/StringBuffer 1 1 470 10 7 12 1 1 1 10 12 1 10 12 1 10 12 1 1 9 7 12 1 1 1 10 12 1 1 10 12 1 10 12 1 1 9 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 100 12 1 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 10 12 1 1 10 12 1 1 9 12 1 1 10 7 12 1 1 1 10 100 1 10 10 100 12 1 1 1 10 10 12 1 10 8 10 100 12 1 1 1 8 10 12 1 8 1 10 12 1 10 12 1 10 100 12 1 1 1 10 100 12 1 1 1 7 1 10 12 100 1 8 1 10 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 10 12 10 12 1 10 12 1 10 12 1 10 12 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 7 1 10 12 1 9 7 12 1 1 1 9 7 1 9 12 1 1 100 1 100 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield java/lang/StringBuffer serialPersistentFields [Ljava/io/ObjectStreamField; 3 [Ljava/io/ObjectStreamField; +instanceKlass java/lang/StringBuilder +instanceKlass java/lang/StringBuffer +ciInstanceKlass java/lang/AbstractStringBuilder 1 1 547 7 1 7 1 10 7 12 1 1 1 9 7 12 1 1 1 9 12 1 9 12 1 1 9 12 1 1 10 7 12 1 1 1 10 12 1 1 3 3 10 12 1 10 12 1 1 11 7 1 100 1 100 1 10 8 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 9 12 1 1 10 12 1 1 10 7 12 1 1 1 10 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 7 12 1 1 1 10 7 12 1 1 1 100 1 8 1 10 10 12 1 1 100 1 10 12 10 12 1 1 10 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 100 1 10 10 12 1 10 100 12 1 1 1 10 12 1 1 10 12 1 10 12 1 1 10 10 12 1 1 10 12 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 10 12 1 1 10 12 1 10 12 1 1 8 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 7 12 1 1 10 12 1 10 12 1 10 100 12 1 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 10 7 12 1 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 10 12 1 1 18 12 1 1 100 1 10 100 12 1 1 1 18 10 12 1 1 10 12 1 10 12 1 1 11 12 1 10 12 1 10 12 1 10 10 12 1 10 8 1 8 1 8 1 10 10 100 1 10 12 1 100 1 10 100 1 10 100 1 1 1 3 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 100 1 1 1 1 1 100 1 1 1 1 1 1 15 10 100 12 1 1 1 16 1 15 10 12 16 15 10 12 1 1 1 1 100 1 100 1 1 +staticfield java/lang/AbstractStringBuilder EMPTYVALUE [B 0 +ciInstanceKlass java/lang/SecurityManager 0 0 576 10 100 12 1 1 1 9 100 12 1 1 1 10 100 12 1 1 1 100 1 8 1 10 12 1 10 12 1 1 10 100 12 1 1 1 10 100 1 10 100 1 10 9 100 12 1 1 1 10 100 12 1 1 1 10 12 1 1 10 100 12 1 1 100 1 8 1 10 9 12 1 1 9 12 1 8 1 9 12 1 100 1 10 8 1 10 12 1 1 10 12 1 10 12 1 1 100 1 10 10 12 1 1 100 1 8 1 10 12 1 8 1 8 1 8 1 8 1 8 1 8 1 10 12 1 8 1 8 1 8 1 8 1 8 1 10 100 12 1 1 1 10 12 1 1 8 1 100 1 8 1 10 8 1 8 1 8 1 8 1 8 1 10 100 12 1 1 8 1 100 1 8 1 8 1 10 8 1 10 12 1 100 1 8 1 10 10 12 1 1 10 12 1 10 12 1 10 12 1 10 100 12 1 1 1 10 12 1 10 100 12 1 1 11 100 12 1 1 1 18 12 1 1 11 100 12 1 1 1 18 12 1 1 11 12 1 1 18 18 11 12 1 18 12 1 11 12 1 1 9 12 1 1 9 12 1 9 12 1 9 12 1 100 1 10 100 12 1 1 10 12 1 10 12 1 18 12 1 18 10 100 12 1 1 1 18 12 1 10 12 1 18 18 8 1 10 12 1 9 12 1 1 11 100 12 1 1 1 8 1 100 1 10 12 1 10 12 1 1 10 12 1 1 9 12 1 1 10 12 1 10 12 1 1 8 1 100 1 10 9 12 1 8 1 10 12 1 8 1 100 1 10 10 100 12 1 1 10 100 1 9 100 12 1 1 1 11 12 1 1 10 12 1 11 12 1 10 12 1 100 1 10 10 12 1 1 10 12 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 15 10 100 12 1 1 1 16 15 10 100 12 1 1 1 16 1 16 15 10 12 16 1 15 10 12 16 15 11 100 1 16 1 16 1 15 10 12 16 15 10 12 16 15 10 12 1 16 1 15 11 12 1 15 10 12 16 15 10 16 1 1 1 1 100 1 100 1 1 +ciInstanceKlass java/security/AccessControlContext 1 1 373 9 7 12 1 1 1 9 12 1 1 10 100 12 1 1 1 8 1 10 100 12 1 1 1 10 7 12 1 1 1 9 12 1 9 12 1 1 9 12 1 1 10 7 12 1 1 100 1 10 12 1 11 100 12 1 1 1 11 12 1 11 12 1 11 12 1 1 7 1 11 12 1 1 10 12 1 10 7 12 1 1 1 9 100 12 1 1 1 10 100 12 1 1 1 11 100 12 1 1 1 10 7 1 100 1 8 1 10 12 1 10 12 1 1 7 1 10 7 12 1 1 1 9 12 1 9 12 1 9 12 1 9 12 1 1 9 12 1 1 9 12 1 9 12 1 10 7 12 1 1 1 9 12 1 10 12 1 1 10 12 1 1 8 1 10 12 1 1 10 12 1 1 10 100 12 1 1 1 100 1 10 10 12 1 1 10 100 12 1 1 1 10 8 1 10 100 12 1 1 8 1 10 100 12 1 1 8 1 8 1 10 12 1 8 1 10 12 1 8 1 10 12 1 10 12 1 10 12 1 1 8 1 8 1 100 1 10 12 1 10 12 1 1 100 1 10 12 1 8 1 10 12 1 10 12 1 10 8 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 10 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 +ciInstanceKlass java/net/URL 1 1 743 10 7 12 1 1 1 10 12 1 10 7 12 1 1 9 12 1 1 9 12 1 10 7 12 1 1 1 10 12 1 1 10 12 1 1 9 12 1 1 10 7 12 1 1 1 8 1 10 12 1 1 100 1 10 10 12 1 1 8 1 10 12 1 1 9 12 1 100 1 8 1 10 12 1 10 12 1 8 1 9 12 1 10 12 1 1 9 12 1 10 12 1 10 12 1 9 12 1 9 12 1 8 1 9 12 1 10 12 1 1 8 1 9 12 1 1 10 12 1 1 10 7 12 1 1 1 8 1 10 12 1 7 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 8 1 10 12 1 1 10 12 1 8 1 9 12 1 8 1 10 12 1 10 7 12 1 1 1 100 1 10 12 1 10 12 1 1 10 7 12 1 1 1 100 1 8 1 10 10 12 1 8 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 8 1 10 12 1 10 10 7 12 1 1 1 10 12 1 9 100 12 1 1 1 10 100 12 1 1 1 9 12 1 1 10 12 1 10 100 12 1 1 1 100 1 100 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 10 10 10 12 1 100 1 10 12 1 10 12 1 1 8 1 9 100 12 1 1 1 10 100 12 1 1 1 10 12 1 1 9 100 12 1 1 1 10 12 1 1 100 1 10 12 1 10 12 1 10 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 7 12 1 1 1 10 12 1 1 10 12 1 9 12 1 1 9 12 1 1 100 1 8 1 10 10 12 1 9 12 1 1 10 7 12 1 1 8 1 10 7 12 1 1 8 1 10 12 1 1 10 12 1 8 1 8 1 10 7 12 1 1 1 100 1 10 100 12 1 1 1 10 12 1 10 12 1 7 1 10 9 12 1 1 10 7 12 1 1 8 1 10 12 1 1 7 1 10 10 7 12 1 1 1 8 9 100 12 1 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 7 12 1 1 11 7 12 1 1 10 12 1 10 12 1 9 12 1 10 12 1 1 10 100 12 1 1 10 100 12 1 1 1 8 10 100 12 1 1 100 1 10 8 8 10 12 1 8 8 8 100 1 10 12 1 9 12 1 1 10 12 1 10 12 1 1 10 12 1 10 12 10 12 1 1 10 12 1 1 10 10 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 10 12 1 100 1 8 1 10 10 10 12 1 1 10 12 1 10 12 1 1 8 1 7 1 10 10 10 7 1 10 12 1 9 7 12 1 1 1 9 12 1 1 7 1 10 10 7 12 1 1 1 100 1 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield java/net/URL defaultFactory Ljava/net/URLStreamHandlerFactory; java/net/URL$DefaultFactory +staticfield java/net/URL streamHandlerLock Ljava/lang/Object; java/lang/Object +staticfield java/net/URL serialPersistentFields [Ljava/io/ObjectStreamField; 7 [Ljava/io/ObjectStreamField; +ciInstanceKlass java/util/jar/Manifest 1 1 336 10 7 12 1 1 1 7 1 10 9 7 12 1 1 1 7 1 10 9 12 1 1 9 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 11 7 1 11 12 1 1 10 12 1 1 10 100 12 1 1 1 100 1 100 1 10 8 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 11 100 1 10 12 1 10 12 1 1 11 12 1 1 10 12 1 11 12 1 1 11 100 12 1 1 1 11 100 12 1 1 11 12 1 1 100 1 10 12 1 8 1 11 12 1 7 1 10 12 1 1 11 12 1 10 12 1 10 12 1 10 100 12 1 1 1 8 1 10 12 1 1 10 9 7 12 1 1 1 10 12 1 1 10 100 12 1 10 12 1 10 12 1 9 100 12 1 1 1 8 1 10 12 1 8 1 8 1 7 1 10 12 1 10 12 1 10 12 1 1 100 1 8 1 10 12 1 1 8 1 10 10 12 1 1 8 1 10 12 1 1 10 7 12 1 1 1 10 12 1 10 11 12 1 1 10 12 1 10 7 12 1 1 1 10 12 1 1 10 12 1 1 11 10 12 1 11 10 12 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/util/Collection 1 1 115 11 100 12 1 1 1 100 1 11 7 12 1 1 1 10 100 12 1 1 1 11 12 1 1 11 100 12 1 1 1 11 12 1 1 11 100 12 1 1 1 11 12 1 1 10 100 12 1 1 1 11 12 1 10 7 12 1 1 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +instanceKlass java/util/concurrent/ConcurrentLinkedDeque +instanceKlass java/util/AbstractMap$2 +instanceKlass org/eclipse/sisu/inject/MildElements +instanceKlass org/eclipse/sisu/inject/MildValues$1 +instanceKlass com/google/common/collect/Maps$Values +instanceKlass com/google/common/collect/AbstractMultimap$Values +instanceKlass java/util/TreeMap$Values +instanceKlass com/google/common/collect/AbstractMapBasedMultimap$WrappedCollection +instanceKlass com/google/common/collect/ImmutableCollection +instanceKlass java/util/IdentityHashMap$Values +instanceKlass java/util/HashMap$Values +instanceKlass java/util/AbstractQueue +instanceKlass java/util/LinkedHashMap$LinkedValues +instanceKlass java/util/ArrayDeque +instanceKlass java/util/AbstractSet +instanceKlass java/util/ImmutableCollections$AbstractImmutableCollection +instanceKlass java/util/AbstractList +ciInstanceKlass java/util/AbstractCollection 1 1 160 10 7 12 1 1 1 10 7 12 1 1 1 10 12 1 1 11 7 12 1 1 1 11 12 1 1 10 12 1 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 10 100 12 1 1 10 100 12 1 1 1 100 1 10 100 12 1 1 1 10 100 12 1 1 1 100 1 10 11 12 1 11 7 1 10 12 1 10 12 1 10 7 12 1 1 1 11 8 1 7 1 10 10 12 1 1 8 1 10 12 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/util/List 1 1 217 10 100 12 1 1 1 11 100 12 1 1 1 11 100 12 1 1 1 11 12 1 1 11 100 12 1 1 11 12 1 1 11 12 1 1 10 100 12 1 1 1 100 1 100 1 10 12 1 1 100 1 10 100 12 1 1 1 9 7 12 1 1 1 7 1 10 12 10 12 1 7 1 10 12 1 1 10 12 1 10 12 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 100 1 1 1 +ciMethod java/util/AbstractCollection ()V 512 0 170776 0 64 +instanceKlass org/eclipse/aether/util/graph/visitor/Stack +instanceKlass org/apache/maven/project/MavenProject$LoggingList +instanceKlass org/apache/maven/model/merge/ModelMerger$MergingList +instanceKlass java/util/ArrayList$SubList +instanceKlass sun/security/jca/ProviderList$3 +instanceKlass java/util/Collections$SingletonList +instanceKlass com/google/common/collect/Lists$Partition +instanceKlass com/google/common/collect/Lists$TransformingRandomAccessList +instanceKlass java/util/Arrays$ArrayList +instanceKlass java/util/AbstractSequentialList +instanceKlass java/util/Vector +instanceKlass java/util/Collections$EmptyList +instanceKlass java/util/ArrayList +ciInstanceKlass java/util/AbstractList 1 1 218 10 7 12 1 1 1 9 7 12 1 1 1 10 12 1 1 10 12 1 1 100 1 10 10 12 1 1 11 7 12 1 1 1 11 12 1 1 11 12 1 10 7 12 1 1 1 10 12 1 11 12 1 11 12 1 11 12 1 10 12 1 1 10 12 1 1 11 100 12 1 1 1 11 7 1 11 7 1 10 12 1 7 1 10 12 1 10 12 1 1 100 1 100 1 10 12 1 100 1 10 100 1 100 1 10 8 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 8 1 100 1 8 1 8 1 8 1 10 7 1 11 10 10 12 1 11 12 1 10 12 1 1 8 1 8 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/AssertionStatusDirectives 0 0 24 10 100 12 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/invoke/MethodHandleNatives$CallSiteContext 1 1 49 10 7 12 1 1 1 7 1 10 10 7 12 1 1 1 10 7 12 1 1 1 10 7 12 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 +ciInstanceKlass jdk/internal/invoke/NativeEntryPoint 0 0 92 10 100 12 1 1 1 9 100 12 1 1 1 10 100 12 1 1 1 100 1 9 12 1 9 12 1 9 12 1 1 9 12 1 1 9 12 1 1 100 1 8 1 10 12 1 11 100 12 1 1 1 10 12 1 1 10 12 1 11 100 12 1 1 11 12 1 10 12 1 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +instanceKlass java/lang/invoke/VolatileCallSite +instanceKlass java/lang/invoke/MutableCallSite +instanceKlass java/lang/invoke/ConstantCallSite +ciInstanceKlass java/lang/invoke/CallSite 1 1 302 10 7 12 1 1 1 10 7 12 1 1 1 9 7 12 1 1 1 10 12 1 1 9 12 1 1 10 7 12 1 1 1 10 12 1 100 1 10 12 1 1 10 12 1 1 9 100 12 1 1 1 10 100 12 1 1 10 12 1 1 100 1 100 1 10 10 100 12 1 1 1 10 12 1 1 8 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 10 100 12 1 1 10 12 1 1 9 12 1 9 100 12 1 1 1 8 1 10 7 12 1 1 1 10 12 1 1 100 1 10 12 1 1 9 12 1 8 1 100 1 10 12 1 10 12 1 100 1 8 1 10 10 12 1 10 12 1 1 100 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 9 12 1 1 8 10 12 1 1 9 12 1 1 100 1 10 10 12 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 10 12 1 10 7 12 1 1 1 100 1 8 1 10 10 12 10 12 1 1 100 1 100 1 100 1 8 1 10 12 1 10 7 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield java/lang/invoke/CallSite $assertionsDisabled Z 1 +ciInstanceKlass java/lang/invoke/VolatileCallSite 0 0 37 10 100 12 1 1 1 10 12 1 10 100 12 1 1 1 10 12 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/invoke/MethodType 1 1 771 7 1 10 7 12 1 1 1 9 12 1 1 9 12 1 1 9 12 1 1 10 7 12 1 1 1 9 7 12 1 1 8 1 10 100 12 1 1 1 9 7 1 9 7 1 10 12 1 1 100 1 10 8 1 10 12 1 1 10 12 1 10 12 1 1 100 1 8 1 10 12 1 100 1 10 10 12 1 10 12 1 1 10 12 1 1 11 7 12 1 1 1 9 12 1 11 12 1 1 7 7 1 10 7 12 1 1 1 10 12 1 9 12 1 1 10 7 12 1 1 10 12 1 1 10 7 12 1 1 1 10 7 12 1 1 1 10 12 1 9 12 1 1 10 12 1 1 100 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 8 1 8 1 10 12 1 1 9 12 1 1 100 1 10 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 8 1 10 12 1 1 10 100 12 1 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 9 12 1 10 12 1 10 10 12 1 1 10 12 1 9 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 7 1 10 12 10 12 1 10 12 1 100 1 8 1 8 1 8 1 10 12 1 10 12 1 10 12 1 10 11 12 1 1 11 12 1 10 100 12 1 1 1 9 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 100 12 1 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 9 12 1 1 7 1 10 12 1 10 12 1 1 9 100 12 1 1 1 10 100 12 1 1 1 10 100 12 1 1 1 10 12 1 1 10 100 12 1 1 1 10 12 1 1 8 1 10 7 12 1 1 1 11 12 1 1 9 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 100 12 1 1 100 1 10 12 1 1 11 100 12 1 1 1 18 12 1 1 11 12 1 1 18 12 1 11 12 1 100 1 11 100 12 1 1 10 12 1 100 1 10 12 1 10 100 12 1 1 10 12 1 1 9 12 1 1 9 100 12 1 1 1 10 100 12 1 1 1 9 12 1 10 100 12 1 1 10 12 1 100 10 12 1 1 10 12 1 10 7 1 7 1 9 12 1 1 100 1 100 1 100 1 1 1 5 0 1 1 1 1 1 1 1 3 1 3 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 15 10 100 12 1 1 1 16 15 10 12 16 16 15 10 12 16 1 1 1 1 100 1 1 100 1 1 100 1 100 1 1 +staticfield java/lang/invoke/MethodType internTable Ljava/lang/invoke/MethodType$ConcurrentWeakInternSet; java/lang/invoke/MethodType$ConcurrentWeakInternSet +staticfield java/lang/invoke/MethodType NO_PTYPES [Ljava/lang/Class; 0 [Ljava/lang/Class; +staticfield java/lang/invoke/MethodType objectOnlyTypes [Ljava/lang/invoke/MethodType; 20 [Ljava/lang/invoke/MethodType; +staticfield java/lang/invoke/MethodType METHOD_HANDLE_ARRAY [Ljava/lang/Class; 1 [Ljava/lang/Class; +staticfield java/lang/invoke/MethodType serialPersistentFields [Ljava/io/ObjectStreamField; 0 [Ljava/io/ObjectStreamField; +staticfield java/lang/invoke/MethodType $assertionsDisabled Z 1 +ciInstanceKlass java/lang/BootstrapMethodError 0 0 45 10 100 12 1 1 1 10 12 1 10 12 1 10 100 12 1 1 1 10 100 12 1 1 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 +ciInstanceKlass jdk/internal/loader/ClassLoaders$AppClassLoader 1 1 119 8 1 10 7 12 1 1 1 10 7 12 1 1 1 10 100 12 1 1 1 10 12 1 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 7 1 8 1 10 12 10 7 12 1 1 1 10 100 12 1 1 10 12 1 1 10 12 1 1 10 7 12 1 1 1 100 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 +ciInstanceKlass jdk/internal/loader/ClassLoaders$PlatformClassLoader 1 1 42 8 1 10 7 12 1 1 1 10 7 12 1 1 1 100 1 10 12 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 100 1 1 +ciInstanceKlass java/lang/NullPointerException 1 1 52 10 7 12 1 1 1 10 12 1 9 7 12 1 1 1 10 12 1 1 9 12 1 1 10 12 1 1 10 12 1 1 1 1 5 0 1 1 1 1 1 1 1 1 100 1 100 1 100 1 1 1 +ciInstanceKlass java/lang/StackTraceElement 1 1 224 10 7 12 1 1 1 10 7 12 1 1 9 12 1 1 9 12 1 1 9 12 1 9 12 1 8 1 10 100 12 1 1 1 7 1 9 12 1 8 1 9 12 1 9 12 1 9 12 1 1 8 1 10 12 1 1 10 12 1 7 1 10 10 12 1 1 8 1 10 12 1 1 10 12 1 8 1 8 1 8 1 10 12 1 8 1 8 1 10 12 1 8 1 8 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 9 12 1 1 10 7 12 1 1 1 10 12 1 1 7 1 10 12 1 1 10 7 12 1 1 10 7 12 1 1 1 10 7 12 1 1 10 7 12 1 1 10 10 12 1 1 10 12 1 10 12 1 1 100 1 1 1 1 1 3 1 3 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 +instanceKlass java/nio/IntBuffer +instanceKlass java/nio/CharBuffer +instanceKlass java/nio/LongBuffer +instanceKlass java/nio/ByteBuffer +ciInstanceKlass java/nio/Buffer 1 1 224 100 1 10 7 12 1 1 1 9 7 12 1 1 1 9 12 1 9 12 1 1 9 12 1 9 12 1 1 10 12 1 1 10 12 1 1 10 12 100 1 100 1 10 8 1 10 12 1 1 10 12 1 8 1 8 1 10 12 1 1 10 12 1 8 1 9 12 1 1 100 1 8 1 10 12 1 8 1 8 1 9 12 10 12 1 8 1 8 1 8 1 10 12 1 8 1 8 1 8 1 100 1 10 100 1 10 100 1 10 100 1 10 10 100 12 1 1 1 10 11 100 12 1 1 10 7 12 1 1 1 10 7 12 1 1 1 9 12 1 1 10 7 12 1 1 1 9 12 1 1 7 1 10 10 7 12 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield java/nio/Buffer UNSAFE Ljdk/internal/misc/Unsafe; jdk/internal/misc/Unsafe +staticfield java/nio/Buffer SCOPED_MEMORY_ACCESS Ljdk/internal/misc/ScopedMemoryAccess; jdk/internal/misc/ScopedMemoryAccess +staticfield java/nio/Buffer $assertionsDisabled Z 1 +ciInstanceKlass jdk/internal/misc/UnsafeConstants 1 1 34 10 100 12 1 1 1 9 7 12 1 1 1 9 12 1 9 12 1 1 9 12 1 9 12 1 1 1 1 1 1 1 1 1 +staticfield jdk/internal/misc/UnsafeConstants ADDRESS_SIZE0 I 8 +staticfield jdk/internal/misc/UnsafeConstants PAGE_SIZE I 4096 +staticfield jdk/internal/misc/UnsafeConstants BIG_ENDIAN Z 0 +staticfield jdk/internal/misc/UnsafeConstants UNALIGNED_ACCESS Z 1 +staticfield jdk/internal/misc/UnsafeConstants DATA_CACHE_LINE_FLUSH_SIZE I 64 +instanceKlass java/lang/invoke/DelegatingMethodHandle +instanceKlass java/lang/invoke/BoundMethodHandle +instanceKlass java/lang/invoke/DirectMethodHandle +ciInstanceKlass java/lang/invoke/MethodHandle 1 1 641 100 1 9 7 12 1 1 1 10 7 12 1 1 1 10 7 12 1 1 1 7 1 7 1 10 12 1 1 9 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 11 100 12 1 1 1 10 12 1 1 10 12 1 10 12 1 9 12 1 1 10 12 1 1 100 1 100 1 10 8 1 10 12 1 1 10 12 1 8 1 10 12 1 1 10 12 1 10 7 12 1 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 7 12 1 1 1 10 100 12 1 1 1 7 1 10 12 1 1 10 12 1 1 10 12 1 1 8 1 10 7 12 1 1 1 8 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 8 1 10 12 1 1 8 1 10 12 1 8 1 10 100 12 1 1 1 9 12 1 1 100 1 10 9 100 12 1 1 1 9 100 1 8 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 8 1 10 12 1 1 8 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 9 100 12 1 1 1 10 12 1 1 11 12 1 10 12 1 10 12 1 1 10 100 12 1 1 1 100 1 11 12 1 10 100 1 11 12 1 100 1 10 12 1 11 12 1 9 100 12 1 1 1 11 12 1 1 11 100 12 1 1 1 10 12 1 1 9 12 1 11 12 1 9 12 1 9 12 1 9 12 1 11 12 1 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 10 10 7 12 1 1 10 12 1 1 100 1 8 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 100 1 10 100 12 1 1 1 10 12 1 9 7 12 1 1 1 10 12 1 1 10 12 1 1 8 1 9 12 1 9 12 1 1 9 12 1 1 10 12 1 7 1 10 12 1 10 12 1 1 9 12 1 1 9 12 1 1 10 7 12 1 1 1 11 7 12 1 1 9 12 1 10 12 1 1 9 12 1 10 12 1 8 10 12 1 1 8 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 100 1 1 100 1 1 1 1 +staticfield java/lang/invoke/MethodHandle FORM_OFFSET J 20 +staticfield java/lang/invoke/MethodHandle UPDATE_OFFSET J 13 +staticfield java/lang/invoke/MethodHandle $assertionsDisabled Z 1 +instanceKlass org/apache/maven/artifact/versioning/ManagedVersionMap +instanceKlass java/util/LinkedHashMap +ciInstanceKlass java/util/HashMap 1 1 610 10 7 12 1 1 1 100 1 10 12 1 1 100 1 10 7 12 1 1 1 100 1 11 12 1 1 11 12 1 11 12 1 1 10 7 12 1 1 1 7 1 3 10 7 12 1 1 1 100 1 100 1 10 8 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 10 7 12 1 1 1 8 1 10 12 1 9 12 1 1 10 12 1 9 12 1 1 4 10 12 1 10 12 1 1 11 7 12 1 1 9 12 1 1 4 10 12 1 1 11 12 1 1 11 7 12 1 1 1 11 7 12 1 1 1 11 12 1 1 7 1 11 12 1 11 12 1 10 12 1 10 12 1 1 9 12 10 12 1 1 9 7 12 1 1 1 9 12 9 12 1 10 12 1 1 9 12 1 7 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 9 12 1 10 12 1 1 3 10 12 1 1 10 12 1 1 9 12 1 1 9 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 9 12 1 1 7 1 10 12 1 10 12 1 10 7 12 1 1 1 7 1 9 12 1 1 7 1 10 9 12 7 1 10 100 1 10 11 7 12 1 1 1 100 1 10 11 100 12 1 1 11 100 12 1 1 1 10 12 1 100 1 100 1 10 12 1 10 12 1 10 12 1 10 100 12 1 1 10 12 1 1 10 12 1 1 10 100 12 1 1 1 8 10 100 12 1 1 1 100 1 10 4 10 100 12 1 1 1 4 10 12 1 10 100 12 1 1 1 10 12 1 8 1 4 10 100 12 1 1 1 100 1 11 100 12 1 1 1 10 12 1 10 12 1 10 10 12 1 1 100 1 100 1 1 1 1 5 0 1 3 1 1 1 3 1 3 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 100 1 100 1 100 1 100 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/util/LinkedHashMap 1 1 289 9 7 12 1 1 1 9 12 1 9 7 12 1 1 9 12 1 10 7 12 1 1 1 10 12 1 1 10 12 1 1 9 12 1 1 9 12 1 1 9 12 1 10 12 1 1 100 1 10 10 12 1 1 10 12 1 10 12 1 1 9 12 1 1 9 12 1 10 100 12 1 1 1 10 12 1 10 12 1 10 12 10 12 1 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 9 7 1 10 12 1 9 12 1 1 7 1 10 12 1 9 12 1 1 7 1 10 9 12 1 7 1 10 100 1 10 11 100 12 1 1 1 100 1 10 11 100 12 1 1 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 100 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 100 1 100 1 1 1 1 1 100 1 1 1 1 1 1 1 1 +ciMethod java/util/HashMap put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; 1024 0 13685 0 256 +ciMethod java/util/HashMap newNode (ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;)Ljava/util/HashMap$Node; 796 0 7914 0 -1 +ciMethod java/util/HashMap afterNodeAccess (Ljava/util/HashMap$Node;)V 36 0 182 0 -1 +ciMethod java/util/HashMap afterNodeInsertion (Z)V 842 0 1453 0 -1 +instanceKlass java/lang/invoke/DirectMethodHandle$Special +instanceKlass java/lang/invoke/DirectMethodHandle$Interface +instanceKlass java/lang/invoke/DirectMethodHandle$Accessor +instanceKlass java/lang/invoke/DirectMethodHandle$Constructor +ciInstanceKlass java/lang/invoke/DirectMethodHandle 1 1 940 7 1 7 1 100 1 7 1 7 1 10 7 12 1 1 1 10 7 12 1 1 1 100 1 10 12 1 10 12 1 1 10 7 12 1 1 10 12 1 1 10 12 1 10 12 1 7 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 7 12 1 1 1 10 12 1 9 12 1 1 100 1 10 9 12 1 1 9 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 7 12 1 1 1 10 12 1 10 12 1 1 8 1 10 12 1 1 7 1 10 12 1 7 1 10 10 12 1 10 12 1 10 12 1 10 7 12 1 1 1 10 12 1 1 100 1 10 12 1 10 12 1 7 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 8 10 100 12 1 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 9 7 12 1 1 1 7 1 10 12 1 10 7 12 1 1 1 10 12 1 10 12 1 1 9 12 1 1 100 1 10 8 1 10 12 1 1 10 12 1 10 12 1 10 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 10 7 12 1 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 8 1 9 7 12 1 1 1 8 1 9 12 1 9 12 1 8 1 9 12 1 9 12 1 8 1 9 12 1 9 12 1 8 1 10 12 1 10 12 1 1 9 12 1 1 7 1 10 12 1 1 100 1 10 7 12 1 1 1 10 12 1 1 10 12 1 10 12 1 1 7 1 10 12 1 1 10 12 1 10 12 1 1 7 1 10 12 1 1 10 7 12 1 1 1 10 12 1 10 12 1 10 12 1 9 12 1 1 10 7 1 9 12 9 12 1 10 7 12 1 1 1 10 12 1 7 1 7 1 7 1 9 12 1 1 10 7 12 1 10 12 1 1 10 12 1 100 1 10 12 1 10 12 1 1 9 100 12 1 1 1 10 12 1 1 100 1 10 100 12 1 1 1 10 12 1 10 12 1 8 1 9 12 1 9 12 1 10 12 1 9 12 1 1 10 100 12 1 1 1 9 12 1 1 9 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 7 12 1 1 9 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 9 12 1 1 10 12 1 1 9 7 12 1 1 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 9 12 1 8 1 9 12 1 1 9 12 1 1 10 12 1 10 12 1 1 9 7 1 10 12 1 9 12 1 1 10 12 10 12 1 10 12 1 10 12 1 10 8 1 8 1 8 1 8 1 10 12 1 1 9 12 1 1 10 12 1 10 100 12 1 1 1 8 9 12 1 1 10 12 1 1 8 1 8 8 9 12 1 8 1 8 8 8 8 8 1 8 10 12 1 10 12 1 8 1 8 1 10 12 1 10 12 1 10 12 1 10 12 1 1 7 1 1 1 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 3 1 1 1 1 1 1 1 1 1 1 3 1 3 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield java/lang/invoke/DirectMethodHandle IMPL_NAMES Ljava/lang/invoke/MemberName$Factory; java/lang/invoke/MemberName$Factory +staticfield java/lang/invoke/DirectMethodHandle FT_UNCHECKED_REF I 8 +staticfield java/lang/invoke/DirectMethodHandle ACCESSOR_FORMS [Ljava/lang/invoke/LambdaForm; 132 [Ljava/lang/invoke/LambdaForm; +staticfield java/lang/invoke/DirectMethodHandle ALL_WRAPPERS [Lsun/invoke/util/Wrapper; 10 [Lsun/invoke/util/Wrapper; +staticfield java/lang/invoke/DirectMethodHandle NFS [Ljava/lang/invoke/LambdaForm$NamedFunction; 12 [Ljava/lang/invoke/LambdaForm$NamedFunction; +staticfield java/lang/invoke/DirectMethodHandle OBJ_OBJ_TYPE Ljava/lang/invoke/MethodType; java/lang/invoke/MethodType +staticfield java/lang/invoke/DirectMethodHandle LONG_OBJ_TYPE Ljava/lang/invoke/MethodType; java/lang/invoke/MethodType +staticfield java/lang/invoke/DirectMethodHandle $assertionsDisabled Z 1 +ciInstanceKlass java/lang/invoke/LambdaForm 1 1 1052 100 1 9 7 12 1 1 1 10 7 12 1 1 1 10 7 12 1 1 9 12 1 1 9 12 1 1 10 12 1 1 100 1 10 9 12 1 10 12 1 1 9 12 1 10 7 12 1 1 1 9 12 1 9 12 1 9 12 1 1 9 12 1 10 12 1 1 7 1 10 12 1 10 100 12 1 1 1 10 100 12 1 1 1 10 12 1 10 12 1 1 9 100 12 1 1 1 10 12 1 1 9 12 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 100 12 1 1 1 7 1 10 10 12 1 1 8 1 10 12 1 10 12 1 10 12 1 1 7 1 10 7 12 1 1 1 10 12 1 1 10 12 1 10 12 1 1 9 12 1 1 9 12 1 9 12 1 1 10 12 1 9 12 1 10 100 12 1 1 1 10 12 1 1 10 12 1 9 12 1 1 10 100 12 1 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 8 1 10 12 1 10 12 1 8 1 8 1 9 12 1 9 12 1 9 12 1 1 10 12 1 1 10 12 1 9 12 1 10 12 1 1 10 7 12 1 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 9 12 1 10 12 1 8 1 8 1 8 1 8 1 8 1 8 1 8 1 8 1 10 12 1 9 12 1 7 1 10 12 1 1 9 12 1 10 12 1 10 12 1 1 10 12 10 12 1 10 12 1 1 10 12 1 1 10 10 12 1 1 10 12 1 1 7 1 8 1 10 12 1 1 10 12 1 10 12 1 1 10 8 1 10 12 1 1 8 1 8 1 8 1 10 12 1 9 12 1 1 10 12 1 1 10 12 1 1 100 1 10 12 1 1 10 12 1 10 7 12 1 1 1 10 12 1 1 9 12 1 1 8 1 10 100 12 1 1 1 10 7 12 1 1 10 12 10 10 12 1 1 9 12 1 8 10 12 1 1 100 1 10 12 1 1 10 12 1 9 7 12 1 1 9 12 1 1 8 1 10 100 12 1 1 10 12 1 1 100 1 100 1 10 10 12 1 1 10 12 1 1 8 1 8 1 100 1 8 1 10 12 10 12 1 10 12 1 10 12 1 1 8 1 8 1 9 100 12 1 1 1 10 12 1 10 12 1 1 8 1 8 1 8 1 100 1 8 1 100 1 8 1 100 1 8 1 10 12 1 8 1 9 10 7 12 1 1 1 10 12 1 9 12 1 1 10 12 1 10 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 8 1 100 10 12 1 10 12 1 9 12 1 1 10 7 12 1 1 8 1 8 1 100 1 8 1 8 1 8 1 8 1 8 1 8 1 8 1 8 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 8 1 8 1 8 1 10 12 1 8 1 10 12 1 8 1 8 1 8 1 8 1 8 1 10 12 1 10 12 1 10 12 1 1 10 7 12 1 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 9 12 1 1 8 1 10 12 1 1 9 12 1 1 10 12 1 1 9 12 1 9 12 1 1 9 12 1 9 12 1 1 7 1 10 7 12 1 1 1 9 12 1 10 12 1 10 12 1 8 1 10 12 1 9 12 1 1 7 1 10 7 12 1 1 1 8 1 100 1 10 12 1 9 12 1 9 12 1 10 12 1 10 12 1 10 7 12 1 1 1 10 12 1 9 7 12 1 1 10 12 1 1 10 12 1 10 12 1 9 12 10 12 1 10 10 12 1 9 9 12 1 7 9 12 1 1 10 12 1 1 9 12 1 10 12 1 10 7 1 9 1 1 1 1 3 1 3 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 100 1 1 100 1 1 1 1 1 1 1 1 1 1 1 +staticfield java/lang/invoke/LambdaForm COMPILE_THRESHOLD I 0 +staticfield java/lang/invoke/LambdaForm INTERNED_ARGUMENTS [[Ljava/lang/invoke/LambdaForm$Name; 5 [[Ljava/lang/invoke/LambdaForm$Name; +staticfield java/lang/invoke/LambdaForm IMPL_NAMES Ljava/lang/invoke/MemberName$Factory; java/lang/invoke/MemberName$Factory +staticfield java/lang/invoke/LambdaForm LF_identity [Ljava/lang/invoke/LambdaForm; 6 [Ljava/lang/invoke/LambdaForm; +staticfield java/lang/invoke/LambdaForm LF_zero [Ljava/lang/invoke/LambdaForm; 6 [Ljava/lang/invoke/LambdaForm; +staticfield java/lang/invoke/LambdaForm NF_identity [Ljava/lang/invoke/LambdaForm$NamedFunction; 6 [Ljava/lang/invoke/LambdaForm$NamedFunction; +staticfield java/lang/invoke/LambdaForm NF_zero [Ljava/lang/invoke/LambdaForm$NamedFunction; 6 [Ljava/lang/invoke/LambdaForm$NamedFunction; +staticfield java/lang/invoke/LambdaForm createFormsLock Ljava/lang/Object; java/lang/Object +staticfield java/lang/invoke/LambdaForm DEBUG_NAME_COUNTERS Ljava/util/HashMap; null +staticfield java/lang/invoke/LambdaForm DEBUG_NAMES Ljava/util/HashMap; null +staticfield java/lang/invoke/LambdaForm TRACE_INTERPRETER Z 0 +staticfield java/lang/invoke/LambdaForm $assertionsDisabled Z 1 +ciInstanceKlass java/lang/invoke/MethodHandleNatives 1 1 684 100 1 10 7 12 1 1 1 9 7 12 1 1 1 10 12 1 1 100 1 10 10 12 1 1 10 12 1 10 12 1 8 1 8 1 8 1 8 1 8 1 8 1 8 1 8 1 8 1 8 1 10 12 1 1 7 1 10 7 12 1 1 1 10 100 12 1 1 1 7 1 10 10 12 1 1 8 1 10 12 1 8 1 10 12 1 1 8 1 10 12 1 1 9 100 12 1 1 1 8 1 10 100 12 1 1 1 100 1 10 12 100 1 100 1 8 1 7 1 10 10 12 1 7 1 9 7 12 1 1 10 12 1 1 10 12 1 10 7 12 1 1 1 7 1 10 12 1 1 10 7 12 1 1 1 10 12 1 10 12 1 1 10 12 1 1 9 12 1 8 1 10 12 1 8 1 10 12 1 8 1 8 1 8 1 100 1 10 12 1 8 1 10 12 1 1 10 12 1 10 100 12 1 1 1 100 1 8 1 10 100 12 1 1 1 7 1 8 1 10 12 1 8 1 8 1 8 1 8 1 8 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 7 1 10 12 1 100 1 100 1 10 12 1 10 12 1 8 1 8 1 10 10 12 1 1 10 12 1 1 8 1 10 100 12 1 1 1 8 1 8 1 10 12 1 1 10 7 12 1 1 1 100 1 10 12 1 1 7 1 9 12 1 1 10 7 12 1 1 1 10 10 12 1 9 12 1 10 12 1 1 9 12 1 9 12 1 10 12 1 1 10 12 1 10 12 1 1 7 1 7 1 10 12 1 1 10 12 1 10 12 1 1 10 7 12 1 1 1 10 12 1 8 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 7 12 1 1 1 10 12 1 1 100 1 8 1 10 9 7 12 1 1 1 10 12 1 1 10 12 1 1 100 1 10 12 1 1 10 12 1 1 100 1 100 1 10 10 100 1 100 1 10 100 1 10 10 12 1 1 10 100 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 8 1 100 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 9 12 1 1 10 7 12 1 1 1 9 12 1 1 11 7 12 1 1 1 10 12 1 10 12 1 10 10 12 1 10 7 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 100 1 1 1 +staticfield java/lang/invoke/MethodHandleNatives JLA Ljdk/internal/access/JavaLangAccess; java/lang/System$2 +staticfield java/lang/invoke/MethodHandleNatives $assertionsDisabled Z 1 +ciInstanceKlass jdk/internal/reflect/CallerSensitive 0 0 17 100 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass jdk/internal/reflect/ConstantPool 1 1 142 10 100 12 1 1 1 9 7 12 1 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 100 12 1 1 1 8 11 7 12 1 1 1 10 7 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +instanceKlass jdk/internal/reflect/UnsafeQualifiedStaticFieldAccessorImpl +ciInstanceKlass jdk/internal/reflect/UnsafeStaticFieldAccessorImpl 1 1 47 10 7 12 1 1 1 9 7 12 1 1 1 10 7 12 1 1 1 9 12 1 1 8 11 7 12 1 1 1 10 7 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +instanceKlass jdk/internal/reflect/UnsafeFieldAccessorImpl +ciInstanceKlass jdk/internal/reflect/FieldAccessorImpl 1 1 59 10 7 12 1 1 1 100 1 100 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +instanceKlass jdk/internal/reflect/UnsafeIntegerFieldAccessorImpl +instanceKlass jdk/internal/reflect/UnsafeBooleanFieldAccessorImpl +instanceKlass jdk/internal/reflect/UnsafeObjectFieldAccessorImpl +instanceKlass jdk/internal/reflect/UnsafeStaticFieldAccessorImpl +ciInstanceKlass jdk/internal/reflect/UnsafeFieldAccessorImpl 1 1 254 10 7 12 1 1 1 9 7 12 1 1 1 10 7 12 1 1 1 10 7 12 1 1 1 9 12 1 1 10 7 12 1 1 1 9 12 1 1 10 12 1 10 12 1 9 12 1 10 12 1 1 10 7 12 1 1 10 7 12 1 1 1 10 12 1 1 100 1 10 10 12 1 1 10 12 1 1 8 1 10 10 12 1 100 1 8 1 10 12 1 8 1 10 12 1 8 1 10 12 1 100 1 10 12 1 1 10 8 1 10 12 1 1 8 1 10 100 12 1 1 8 1 10 100 12 1 1 8 1 10 100 12 1 1 8 1 10 100 12 1 1 8 1 10 100 12 1 1 8 1 10 100 12 1 1 8 1 10 100 12 1 1 8 1 10 100 12 1 1 10 12 1 1 8 1 8 1 8 1 8 1 8 1 8 1 10 100 12 1 1 1 8 1 8 1 8 1 10 12 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield jdk/internal/reflect/UnsafeFieldAccessorImpl unsafe Ljdk/internal/misc/Unsafe; jdk/internal/misc/Unsafe +ciInstanceKlass jdk/internal/reflect/NativeConstructorAccessorImpl 1 1 126 10 7 12 1 1 1 9 7 12 1 1 1 9 12 1 1 10 7 12 1 1 1 10 7 12 1 1 1 10 7 12 1 1 1 9 12 1 9 12 1 1 9 12 1 1 10 7 12 1 1 1 7 1 10 10 12 1 1 10 12 1 10 12 1 10 12 1 1 9 12 1 1 10 7 12 1 1 1 100 1 10 12 1 1 10 12 1 1 8 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 100 1 1 1 1 1 1 1 +staticfield jdk/internal/reflect/NativeConstructorAccessorImpl U Ljdk/internal/misc/Unsafe; jdk/internal/misc/Unsafe +staticfield jdk/internal/reflect/NativeConstructorAccessorImpl GENERATED_OFFSET J 16 +ciInstanceKlass java/lang/invoke/ConstantCallSite 1 1 65 10 7 12 1 1 1 9 7 12 1 1 1 9 12 1 1 10 7 12 1 1 1 10 12 1 100 1 10 12 9 12 1 1 100 1 10 10 12 1 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 +staticfield java/lang/invoke/ConstantCallSite UNSAFE Ljdk/internal/misc/Unsafe; jdk/internal/misc/Unsafe +ciInstanceKlass java/lang/invoke/MutableCallSite 0 0 63 10 100 12 1 1 1 10 12 1 9 100 12 1 1 1 10 12 1 10 12 1 1 9 12 1 1 10 100 12 1 1 1 10 100 12 1 1 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 +instanceKlass java/lang/invoke/VarHandleInts$FieldStaticReadOnly +instanceKlass java/lang/invoke/VarHandleLongs$FieldInstanceReadOnly +instanceKlass java/lang/invoke/VarHandleInts$FieldInstanceReadOnly +instanceKlass java/lang/invoke/VarHandleReferences$Array +instanceKlass java/lang/invoke/VarHandleReferences$FieldInstanceReadOnly +ciInstanceKlass java/lang/invoke/VarHandle 1 1 390 10 7 12 1 1 1 10 7 12 1 1 9 12 1 1 9 12 1 1 100 1 10 8 1 10 12 1 1 10 7 12 1 1 1 10 12 1 1 10 100 12 1 1 1 9 100 12 1 1 1 10 12 1 1 10 100 12 1 1 1 10 12 1 1 9 12 1 10 12 1 9 12 1 1 10 100 12 1 1 10 12 1 9 100 12 1 1 1 9 12 1 1 10 12 1 1 100 1 100 1 10 8 1 10 12 1 1 10 12 1 8 1 10 12 1 10 12 1 10 12 1 1 9 100 12 1 1 1 10 12 1 10 12 1 1 10 12 1 10 10 100 12 1 1 1 10 12 1 1 10 12 1 1 10 100 12 1 1 1 10 100 12 1 1 1 10 100 12 1 1 1 9 12 1 1 10 9 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 100 12 1 1 1 9 12 1 1 10 12 1 1 9 12 1 10 12 1 10 12 1 10 100 12 1 1 100 1 10 9 7 12 1 1 1 9 12 1 1 10 7 12 1 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 7 1 10 10 7 12 1 1 1 9 12 1 1 8 10 12 1 1 7 1 10 12 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 100 1 1 1 1 1 1 1 100 1 1 1 +staticfield java/lang/invoke/VarHandle AIOOBE_SUPPLIER Ljava/util/function/BiFunction; jdk/internal/util/Preconditions$1 +staticfield java/lang/invoke/VarHandle VFORM_OFFSET J 16 +staticfield java/lang/invoke/VarHandle $assertionsDisabled Z 1 +ciInstanceKlass java/lang/invoke/MemberName 1 1 757 7 1 7 1 100 1 9 12 1 1 10 7 12 1 1 1 9 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 7 12 1 1 1 10 12 1 9 100 12 1 1 10 12 1 100 1 100 1 10 12 1 8 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 9 12 1 1 8 1 10 100 12 1 1 1 7 1 10 10 12 1 1 100 1 100 1 10 12 1 1 9 12 1 1 100 1 8 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 8 1 8 1 8 1 10 100 12 1 1 1 10 12 1 9 12 1 1 3 10 12 1 10 12 1 10 12 1 10 10 7 12 1 1 1 10 12 1 10 12 1 10 12 1 10 12 1 7 1 8 10 12 1 1 10 12 1 1 8 1 9 100 1 8 9 100 1 10 12 1 1 10 12 1 10 12 1 8 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 1 100 1 10 12 1 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 1 10 12 1 1 100 1 10 12 1 1 10 12 8 1 8 1 100 1 10 12 1 10 100 12 1 1 1 100 1 10 12 10 12 1 10 12 1 10 12 1 10 12 1 10 10 12 1 10 12 1 10 12 1 3 10 12 1 3 10 12 1 3 3 3 3 3 3 3 100 1 10 12 1 10 7 12 1 1 1 10 12 1 3 9 12 1 10 12 1 1 3 10 12 1 10 10 7 12 1 1 1 10 12 1 1 10 100 1 10 10 12 1 10 12 1 10 12 1 10 10 12 1 1 10 12 1 10 12 1 10 12 1 100 1 10 10 10 12 100 1 10 10 10 12 1 1 10 12 1 1 10 10 12 1 8 10 100 1 10 12 1 10 100 1 10 12 1 10 12 1 10 12 1 10 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 100 1 10 12 1 1 100 1 8 1 10 7 1 10 12 1 10 12 10 12 1 1 10 12 1 10 12 1 8 1 8 1 8 1 8 1 10 12 1 10 12 1 10 12 1 1 8 1 8 1 10 12 1 8 1 10 10 12 1 10 12 1 8 1 8 1 10 10 12 1 8 1 10 100 12 1 1 1 8 1 10 12 1 10 12 1 1 10 12 1 8 1 8 1 8 1 8 1 100 1 10 8 1 8 1 8 1 8 1 10 12 1 100 1 100 1 100 1 10 100 1 10 100 1 10 100 12 1 1 1 9 7 12 1 1 1 100 1 100 1 1 1 1 1 1 1 3 1 3 1 3 1 3 1 3 1 3 1 1 1 1 1 1 1 1 3 1 3 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield java/lang/invoke/MemberName $assertionsDisabled Z 1 +ciInstanceKlass java/lang/invoke/ResolvedMethodName 1 1 16 10 100 12 1 1 1 100 1 1 1 1 1 1 1 1 +ciInstanceKlass java/lang/StackWalker 0 0 235 9 100 12 1 1 1 10 100 12 1 1 1 100 1 10 100 12 1 1 1 10 12 1 1 11 100 12 1 1 1 10 12 1 1 10 12 1 1 10 12 1 1 100 1 8 1 10 12 1 10 12 1 10 12 1 10 100 12 1 1 9 12 1 1 9 12 1 1 9 12 1 1 9 12 1 1 10 12 1 1 9 12 1 1 10 100 12 1 1 1 11 12 1 1 100 1 8 1 10 10 100 12 1 1 9 12 1 1 10 12 1 1 10 100 12 1 1 1 10 100 12 1 1 1 18 12 1 1 100 1 8 1 10 10 12 1 1 10 100 12 1 1 1 9 100 12 1 1 11 100 12 1 1 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 15 10 100 12 1 1 1 16 15 10 12 16 1 1 1 1 1 1 1 100 1 100 1 1 +ciInstanceKlass java/lang/StackStreamFactory$AbstractStackWalker 1 0 306 100 1 100 1 3 10 100 12 1 1 1 10 100 12 1 1 10 100 12 1 1 1 9 12 1 1 10 12 1 1 9 12 1 1 9 12 1 1 9 12 1 1 9 12 1 9 100 12 1 1 1 10 100 12 1 1 1 10 12 1 1 9 12 1 1 10 100 12 1 1 1 10 12 1 1 9 12 1 1 9 100 12 1 1 1 100 1 10 8 1 10 12 1 1 10 12 1 8 1 10 12 1 1 10 100 12 1 1 1 100 1 8 1 10 12 1 8 1 10 12 9 100 12 1 1 1 10 100 12 1 1 9 12 1 8 1 5 0 8 1 8 1 9 12 1 1 10 12 1 1 10 12 1 1 10 12 1 9 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 8 1 10 12 1 10 12 1 10 12 1 10 12 1 1 8 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 100 12 1 1 1 9 12 1 8 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 8 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass jdk/internal/module/Modules 1 1 504 10 100 12 1 1 1 9 7 12 1 1 1 11 7 12 1 1 1 11 12 1 1 11 12 1 1 11 12 1 1 11 12 1 11 12 1 11 12 1 11 12 1 11 12 1 1 10 100 12 1 1 1 10 100 12 1 1 1 18 12 1 1 10 100 12 1 1 1 100 1 10 100 12 1 1 1 10 100 12 1 1 1 10 100 12 1 1 10 12 1 1 11 12 1 9 12 1 1 11 100 12 1 1 1 10 12 1 1 10 10 12 1 10 9 12 1 1 10 100 12 1 1 10 12 1 1 10 100 12 1 1 100 1 11 100 12 1 1 1 10 100 12 1 1 1 11 100 12 1 1 10 12 1 1 10 100 12 1 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 11 12 1 1 18 12 1 1 11 100 12 1 1 10 100 12 1 1 1 11 100 12 1 1 1 100 1 11 12 1 1 11 100 12 1 1 1 11 12 1 1 10 12 1 1 10 100 12 1 1 18 12 1 1 11 12 1 1 18 12 1 1 11 12 1 1 10 12 1 18 18 10 12 1 1 9 12 1 1 11 100 12 1 1 1 100 1 10 11 12 1 11 12 1 1 11 12 1 1 10 100 1 10 12 1 1 10 100 12 1 1 10 12 1 1 11 12 10 12 1 1 100 1 10 18 12 1 10 12 1 1 100 1 8 1 10 12 1 10 100 12 1 1 18 12 1 11 11 12 10 12 1 10 10 100 1 18 12 1 10 10 10 7 12 1 1 10 7 12 1 1 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 15 10 100 12 1 1 1 16 15 10 12 1 16 16 15 10 12 1 16 1 16 1 15 10 12 1 16 1 16 1 15 10 12 16 1 15 10 16 1 15 10 12 16 1 15 10 12 16 15 10 12 16 15 10 12 1 1 1 100 1 100 1 1 +staticfield jdk/internal/module/Modules JLA Ljdk/internal/access/JavaLangAccess; java/lang/System$2 +staticfield jdk/internal/module/Modules JLMA Ljdk/internal/access/JavaLangModuleAccess; java/lang/module/ModuleDescriptor$1 +staticfield jdk/internal/module/Modules $assertionsDisabled Z 1 +instanceKlass java/util/LinkedHashMap$Entry +ciInstanceKlass java/util/HashMap$Node 1 1 95 10 7 12 1 1 1 9 7 12 1 1 1 9 12 1 1 9 12 1 9 12 1 1 100 1 10 10 12 1 1 8 1 10 12 1 10 12 1 1 10 7 12 1 1 1 100 1 11 12 1 1 10 12 1 1 11 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 100 1 1 +instanceKlass java/util/HashMap$TreeNode +ciInstanceKlass java/util/LinkedHashMap$Entry 1 1 41 10 7 12 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 100 1 1 1 +ciInstanceKlass java/util/HashMap$TreeNode 0 0 250 100 1 10 100 12 1 1 1 9 100 12 1 1 1 9 12 1 1 9 12 1 9 12 1 1 9 12 1 1 10 12 1 1 100 1 10 12 1 9 12 1 9 12 1 9 12 1 1 10 100 12 1 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 100 12 1 1 1 10 100 12 1 1 1 10 100 12 1 1 1 9 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 9 100 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 +instanceKlass java/util/ArrayList$ListItr +ciInstanceKlass java/util/ArrayList$Itr 1 1 103 9 7 12 1 1 1 10 7 12 1 1 1 9 12 1 1 9 7 12 1 1 9 12 1 9 12 1 9 12 1 10 12 1 100 1 10 9 12 1 1 100 1 10 100 1 10 10 12 1 1 100 1 10 100 12 1 1 1 10 12 1 1 11 100 12 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciMethod java/util/ArrayList$Itr hasNext ()Z 514 0 5381 0 96 +ciMethod java/util/ArrayList$Itr next ()Ljava/lang/Object; 512 0 9263 0 288 +ciMethod java/util/ArrayList$Itr (Ljava/util/ArrayList;)V 1356 0 15023 0 0 +ciMethod java/util/ArrayList$Itr checkForComodification ()V 514 0 11857 0 128 +ciInstanceKlass java/util/zip/ZipFile$Source$Key 1 1 84 10 7 12 1 1 1 9 7 12 1 1 1 9 12 1 1 10 7 12 1 1 1 9 12 1 1 100 1 5 0 11 7 12 1 1 1 10 7 12 1 1 1 10 7 12 1 1 1 10 12 1 1 11 12 1 1 10 10 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 100 1 1 1 +ciMethod java/util/HashMap$TreeNode getTreeNode (ILjava/lang/Object;)Ljava/util/HashMap$TreeNode; 0 0 1 0 -1 +ciMethod java/util/HashMap$TreeNode putTreeVal (Ljava/util/HashMap;[Ljava/util/HashMap$Node;ILjava/lang/Object;Ljava/lang/Object;)Ljava/util/HashMap$TreeNode; 0 0 1 0 -1 +ciMethod java/util/HashMap getNode (Ljava/lang/Object;)Ljava/util/HashMap$Node; 1024 32 46399 0 704 +ciMethod java/util/HashMap resize ()[Ljava/util/HashMap$Node; 170 456 5050 0 -1 +ciMethod java/util/HashMap treeifyBin ([Ljava/util/HashMap$Node;I)V 0 0 1 0 -1 +ciMethod java/util/HashMap putVal (ILjava/lang/Object;Ljava/lang/Object;ZZ)Ljava/lang/Object; 804 34 35182 0 7840 +ciMethod java/util/HashMap tableSizeFor (I)I 512 0 17291 0 0 +ciMethod java/util/HashMap hash (Ljava/lang/Object;)I 1024 0 86118 0 0 +ciMethod java/util/HashMap (IF)V 40 0 10864 0 0 +ciMethod java/util/HashMap (I)V 40 0 9449 0 0 +ciMethod java/util/LinkedHashMap values ()Ljava/util/Collection; 514 0 6577 0 0 +ciMethod java/util/LinkedHashMap get (Ljava/lang/Object;)Ljava/lang/Object; 512 0 20161 0 1216 +ciMethod java/util/LinkedHashMap (I)V 34 0 6949 0 0 +ciMethod java/util/LinkedHashMap afterNodeAccess (Ljava/util/HashMap$Node;)V 0 0 498 0 -1 +ciMethod java/util/AbstractList ()V 266 0 131834 0 64 +ciMethod java/util/List isEmpty ()Z 0 0 1 0 -1 +ciMethod java/util/List size ()I 0 0 1 0 -1 +ciMethod java/util/List iterator ()Ljava/util/Iterator; 0 0 1 0 -1 +ciMethod java/util/Collection toArray ()[Ljava/lang/Object; 0 0 1 0 -1 +ciMethod java/util/AbstractMap ()V 768 0 64305 0 64 +ciMethod java/util/Arrays copyOf ([Ljava/lang/Object;ILjava/lang/Class;)[Ljava/lang/Object; 418 0 7900 0 -1 +ciMethod java/util/Map values ()Ljava/util/Collection; 0 0 1 0 -1 +ciMethod java/util/Map put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; 0 0 1 0 -1 +ciMethod java/util/Map get (Ljava/lang/Object;)Ljava/lang/Object; 0 0 1 0 -1 +ciMethod java/lang/String length ()I 512 0 593741 0 96 +ciMethod java/lang/String equals (Ljava/lang/Object;)Z 512 0 6410 0 416 +ciMethod java/lang/String hashCode ()I 602 0 5624 0 480 +ciMethod java/lang/String coder ()B 584 0 740389 0 64 +ciMethod java/lang/String isLatin1 ()Z 304 0 863599 0 96 +ciMethod java/lang/String regionMatches (ZILjava/lang/String;II)Z 696 0 8915 0 0 +ciMethod java/lang/String regionMatches (ILjava/lang/String;II)Z 0 0 6 0 -1 +ciMethod java/lang/String equalsIgnoreCase (Ljava/lang/String;)Z 1024 0 5657 0 1536 +ciMethod java/util/ArrayList iterator ()Ljava/util/Iterator; 4104 0 8384 0 192 +ciMethod java/util/ArrayList size ()I 268 0 134 0 0 +ciMethod java/util/ArrayList isEmpty ()Z 768 0 5841 0 96 +ciMethod java/util/ArrayList (Ljava/util/Collection;)V 758 0 8178 0 0 +ciMethod java/util/ArrayList ()V 216 0 112781 0 288 +ciMethod java/util/Iterator hasNext ()Z 0 0 1 0 -1 +ciMethod java/util/Iterator next ()Ljava/lang/Object; 0 0 1 0 -1 +ciMethod java/lang/Object getClass ()Ljava/lang/Class; 256 0 128 0 -1 +ciMethod java/lang/Object ()V 794 0 840737 0 128 +ciInstanceKlass org/codehaus/plexus/util/xml/Xpp3Dom 1 1 371 7 1 10 7 12 1 1 1 9 12 1 1 7 1 10 9 12 1 1 10 12 1 9 12 1 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 9 12 1 9 12 1 1 11 7 12 1 1 1 9 12 1 1 11 12 1 1 11 7 12 1 1 1 7 11 12 1 1 7 1 10 100 12 1 1 11 12 1 100 1 8 1 10 8 1 7 1 10 11 12 1 1 11 7 12 1 1 11 12 1 11 12 1 1 11 7 12 1 1 11 12 1 1 10 12 1 1 10 12 1 11 12 1 11 9 12 1 1 11 7 10 12 1 1 10 7 12 1 1 1 11 12 1 1 11 7 12 1 1 11 12 1 10 11 12 11 12 9 12 1 1 100 1 10 12 1 10 100 12 1 1 1 10 12 1 100 1 8 1 8 1 10 10 12 1 10 12 1 1 11 10 100 12 1 1 8 1 8 1 10 12 1 1 11 12 1 11 12 1 8 10 12 1 10 12 1 1 11 11 10 12 1 11 11 100 1 10 100 1 8 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 100 1 1 1 1 5 0 1 1 1 1 1 8 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +staticfield org/codehaus/plexus/util/xml/Xpp3Dom EMPTY_STRING_ARRAY [Ljava/lang/String; 0 [Ljava/lang/String; +staticfield org/codehaus/plexus/util/xml/Xpp3Dom EMPTY_DOM_ARRAY [Lorg/codehaus/plexus/util/xml/Xpp3Dom; 0 [Lorg/codehaus/plexus/util/xml/Xpp3Dom; +ciInstanceKlass java/lang/ProcessEnvironment$Variable 1 1 69 10 7 12 1 1 1 7 1 10 7 12 1 1 1 10 12 1 1 10 10 100 12 1 1 1 10 12 1 10 10 12 1 1 10 12 1 1 10 12 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/util/LinkedHashMap$LinkedValues 1 1 116 9 7 12 1 1 1 10 7 12 1 1 1 9 7 12 1 1 1 10 12 1 7 1 10 12 1 10 12 1 1 100 1 10 100 12 1 1 1 7 1 10 12 1 1 10 12 1 100 1 10 9 12 1 9 12 1 1 9 100 12 1 1 1 11 100 12 1 1 1 9 12 1 100 1 10 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/util/NoSuchElementException 0 0 34 10 100 12 1 1 1 10 12 1 10 12 1 10 12 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass java/util/ConcurrentModificationException 0 0 34 10 100 12 1 1 1 10 12 1 10 12 1 10 12 1 100 1 1 1 1 5 0 1 1 1 1 1 1 1 1 1 1 1 +instanceKlass org/apache/maven/model/ReportSet +instanceKlass org/apache/maven/model/ReportPlugin +instanceKlass org/apache/maven/model/PluginExecution +instanceKlass org/apache/maven/model/Plugin +ciInstanceKlass org/apache/maven/model/ConfigurationContainer 1 1 167 10 7 12 1 1 1 9 7 12 1 1 1 10 12 1 1 9 12 1 1 7 1 10 12 1 9 12 1 1 7 1 10 12 1 100 1 100 1 100 1 10 10 12 1 1 10 100 12 1 1 1 10 12 1 1 8 1 10 12 1 10 12 1 10 12 1 1 100 1 9 12 1 1 7 1 10 12 1 1 8 1 10 12 1 1 8 8 9 12 1 1 9 12 1 9 12 1 10 12 1 1 10 12 1 1 10 11 7 12 1 1 1 11 12 1 1 7 1 10 7 12 1 1 1 10 12 1 1 10 12 1 100 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 +ciInstanceKlass org/apache/maven/model/Plugin 1 1 269 10 7 12 1 1 1 8 1 9 7 12 1 1 1 9 12 1 1 10 12 1 1 11 7 12 1 1 1 10 12 1 10 12 1 1 9 12 1 1 7 1 10 11 12 1 1 11 7 12 1 1 1 11 12 1 1 7 1 10 12 1 9 12 1 7 1 10 12 1 9 12 1 1 100 1 10 12 1 100 1 100 1 7 1 10 10 100 12 1 1 1 10 100 12 1 1 1 10 12 1 1 8 1 10 12 1 10 12 1 10 12 1 1 100 1 9 12 1 9 12 1 9 12 1 11 12 1 10 100 12 1 1 1 10 7 12 1 1 1 100 1 10 10 12 1 11 100 12 1 1 100 1 8 1 8 1 10 12 1 8 1 10 11 12 1 1 10 12 1 10 12 1 8 1 8 1 10 12 1 8 1 10 12 1 8 1 10 12 1 1 10 12 1 10 12 1 1 8 1 8 1 10 12 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 +ciInstanceKlass org/apache/maven/model/PluginExecution 1 1 131 10 7 12 1 1 1 8 1 9 7 12 1 1 1 9 12 1 1 10 12 1 1 11 7 12 1 1 1 10 12 1 1 9 12 1 1 7 1 10 11 12 1 1 100 1 100 1 100 1 10 10 100 12 1 1 1 10 100 12 1 1 1 10 12 1 1 8 1 10 12 1 10 12 1 10 12 1 1 100 1 9 12 1 11 12 1 10 12 1 10 12 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 +ciInstanceKlass org/apache/maven/model/ReportPlugin 1 1 172 10 7 12 1 1 1 8 1 9 7 12 1 1 1 9 12 1 1 10 12 1 1 11 7 12 1 1 1 10 12 1 1 9 12 1 1 7 1 10 11 12 1 1 11 7 12 1 1 1 11 12 1 1 7 1 10 12 1 100 1 100 1 7 1 10 10 100 12 1 1 1 10 100 12 1 1 1 10 12 1 1 8 1 10 12 1 10 12 1 10 12 1 1 100 1 9 12 1 9 12 1 11 12 1 100 1 10 10 12 1 11 100 12 1 1 1 10 12 1 1 8 1 10 12 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 +instanceKlass org/apache/maven/model/merge/MavenModelMerger +ciInstanceKlass org/apache/maven/model/merge/ModelMerger 1 1 1971 10 7 12 1 1 1 8 1 10 7 12 1 1 1 7 1 10 11 7 12 1 1 1 10 7 12 1 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 7 12 1 1 1 10 12 1 1 8 1 10 12 1 1 10 12 1 1 10 12 1 1 7 1 10 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 1 7 1 10 10 12 1 1 10 12 1 1 10 12 1 1 100 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 100 1 10 10 12 1 10 12 1 100 1 10 10 12 1 10 12 1 100 1 10 10 12 1 10 12 1 1 7 1 10 10 12 1 1 10 12 1 1 10 12 1 1 7 1 10 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 7 1 10 10 12 1 1 10 12 1 1 10 12 1 1 100 1 10 10 12 1 10 12 1 1 10 12 1 1 10 12 1 1 7 1 10 10 12 1 1 10 12 1 1 10 12 1 100 1 10 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 7 12 1 1 11 7 12 1 1 1 7 1 11 12 1 1 10 12 1 11 12 1 1 10 12 1 10 12 1 7 1 10 10 12 1 10 12 1 100 1 10 10 12 1 10 12 1 10 12 1 10 12 1 1 7 1 10 10 12 1 1 10 12 1 1 10 12 1 1 7 1 10 10 12 1 1 10 12 1 1 10 12 1 1 7 1 10 10 12 1 1 10 12 1 1 7 1 10 10 12 1 1 10 10 12 1 1 8 1 10 10 7 12 1 1 10 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 1 100 1 10 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 7 1 10 10 12 1 1 10 12 1 1 10 12 1 10 12 1 8 1 10 10 10 12 1 10 12 1 8 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 100 1 10 10 10 10 10 10 10 10 12 1 10 12 1 8 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 8 1 10 10 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 8 1 10 10 10 12 1 10 12 1 8 1 10 10 10 10 10 12 1 1 10 12 1 10 12 1 10 7 12 1 1 1 100 1 10 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 7 1 10 10 10 10 10 10 10 10 12 1 10 12 1 8 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 8 1 10 10 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 7 1 10 10 10 10 10 10 10 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 100 1 10 10 12 1 10 12 1 1 10 12 1 10 100 1 10 10 10 10 10 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 8 1 10 10 10 12 1 10 12 1 8 1 10 12 1 100 1 10 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 7 1 10 10 10 10 10 10 10 10 12 1 100 1 10 10 12 1 10 12 1 1 10 12 1 10 7 1 10 10 10 10 12 1 10 12 1 8 1 10 100 12 1 1 1 10 10 10 12 1 10 10 10 12 1 10 12 1 10 12 1 10 12 1 10 10 10 10 10 10 10 10 10 12 1 8 1 10 12 1 10 12 1 10 10 10 10 10 10 10 12 1 1 10 12 1 10 12 1 10 12 1 10 100 1 10 10 10 10 10 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 100 1 10 10 10 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 100 1 10 10 10 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 100 1 10 10 10 10 12 1 10 12 1 8 1 10 10 10 12 10 12 8 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 10 10 10 12 1 10 12 1 10 12 1 10 12 1 8 1 10 10 10 10 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 8 1 10 10 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 10 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 8 1 10 12 1 10 12 1 10 12 1 10 10 10 10 10 10 10 12 1 7 1 10 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 100 1 10 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 8 1 10 10 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 8 1 10 10 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 10 10 12 1 10 12 1 8 1 10 12 1 7 1 10 10 12 1 10 12 1 1 10 12 1 10 12 1 10 100 1 10 10 10 10 10 10 10 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 7 12 1 1 10 12 1 8 1 10 10 10 12 1 10 12 1 8 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 10 12 1 7 1 10 10 12 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 7 12 1 1 1 7 1 10 10 12 1 1 10 12 1 1 10 12 1 10 100 1 100 1 10 10 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 7 1 10 10 10 10 10 10 10 10 12 10 12 8 1 10 10 10 12 1 100 1 10 10 12 1 10 12 1 10 12 1 10 7 12 1 1 10 12 1 8 1 10 10 10 12 1 7 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 7 1 10 10 10 10 12 1 10 12 1 8 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 100 12 1 1 10 12 1 8 1 10 10 10 12 1 10 12 1 8 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 100 1 10 10 10 10 12 1 10 12 1 10 100 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 1 7 1 10 12 10 12 1 7 1 10 12 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 +instanceKlass org/apache/maven/model/profile/DefaultProfileInjector$ProfileModelMerger +instanceKlass org/apache/maven/model/normalization/DefaultModelNormalizer$DuplicateMerger +instanceKlass org/apache/maven/model/management/DefaultPluginManagementInjector$ManagementModelMerger +instanceKlass org/apache/maven/model/management/DefaultDependencyManagementInjector$ManagementModelMerger +instanceKlass org/apache/maven/model/inheritance/DefaultInheritanceAssembler$InheritanceModelMerger +instanceKlass org/apache/maven/model/plugin/DefaultLifecycleBindingsInjector$LifecycleBindingsMerger +ciInstanceKlass org/apache/maven/model/merge/MavenModelMerger 1 1 596 10 7 12 1 1 1 7 1 8 1 10 7 12 1 1 1 11 7 12 1 1 1 10 12 1 1 10 12 1 10 12 1 1 8 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 8 1 10 12 1 1 10 12 1 1 10 12 1 1 7 1 10 8 1 10 10 10 12 1 1 10 12 1 1 10 12 1 1 7 1 10 10 10 10 12 1 1 10 12 1 1 10 12 1 1 7 1 10 10 10 10 12 1 1 10 12 1 1 10 12 1 1 11 7 12 1 1 7 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 7 12 1 1 10 7 1 10 11 12 1 1 10 12 1 11 12 1 1 10 7 12 1 1 1 11 12 1 1 11 12 1 1 7 1 11 7 12 1 1 10 12 1 8 1 10 10 7 12 1 1 1 10 10 12 1 7 1 10 11 12 1 1 11 7 12 1 1 11 12 1 1 7 1 10 12 1 1 11 12 1 11 12 1 1 10 12 1 10 12 1 10 12 1 10 7 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 10 7 12 1 1 1 7 1 10 10 10 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 1 7 1 10 10 10 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 7 12 1 1 10 10 10 10 12 1 10 7 1 10 10 10 10 12 1 10 12 1 10 12 1 8 1 10 12 1 10 12 1 10 12 1 8 1 10 12 1 10 7 12 1 1 7 1 10 12 1 10 12 1 10 10 12 1 1 11 12 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 7 12 1 1 7 1 10 10 10 10 12 1 1 10 12 1 1 10 12 1 10 7 12 1 1 10 12 1 10 10 10 10 7 1 7 1 10 10 7 12 1 1 10 12 1 1 10 12 1 10 10 12 1 10 100 1 10 1 1 1 8 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass org/apache/maven/model/plugin/DefaultLifecycleBindingsInjector$LifecycleBindingsMerger 1 1 187 10 7 12 1 1 1 10 7 12 1 1 1 7 1 10 10 12 1 1 7 1 8 1 10 12 1 1 10 7 12 1 1 1 10 12 1 1 10 7 12 1 1 1 11 7 12 1 1 1 7 1 11 12 1 1 10 12 1 11 12 1 1 11 7 12 1 1 11 12 1 1 7 1 10 12 1 1 11 7 12 1 1 1 10 11 12 1 1 10 12 1 1 11 7 1 10 10 12 1 1 10 12 1 1 7 1 11 12 1 1 10 12 1 10 12 1 1 10 12 1 1 10 100 12 1 1 10 100 12 1 1 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 1 1 100 1 1 +ciInstanceKlass org/apache/maven/model/inheritance/DefaultInheritanceAssembler$InheritanceModelMerger 1 1 333 100 1 10 7 12 1 1 1 8 1 11 7 12 1 1 1 8 1 10 7 12 1 1 1 10 7 12 1 1 1 10 7 12 1 1 1 7 1 10 7 12 1 1 1 10 12 1 10 12 1 1 10 12 1 1 10 10 10 12 1 1 10 10 12 1 10 12 1 8 1 10 12 1 7 1 10 10 7 12 1 1 1 10 12 1 1 8 1 10 12 1 10 12 1 1 8 1 10 12 1 1 10 7 12 1 1 1 10 12 1 1 11 12 1 1 11 7 12 1 1 1 11 7 12 1 1 1 11 12 1 1 7 1 11 12 1 10 12 1 1 11 12 1 11 12 1 1 10 7 12 1 1 1 11 7 12 1 1 7 1 11 12 1 10 11 7 1 10 12 1 10 12 1 10 8 1 10 10 10 12 1 1 10 12 1 1 10 12 1 1 10 7 1 10 11 12 1 10 11 12 1 1 10 12 1 1 10 12 1 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 12 1 10 7 1 7 1 10 12 1 1 10 10 10 10 10 10 12 1 1 11 12 1 1 10 12 1 10 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +ciInstanceKlass org/apache/maven/model/management/DefaultPluginManagementInjector$ManagementModelMerger 1 1 165 10 7 12 1 1 1 10 7 12 1 1 1 10 7 12 1 1 1 10 7 12 1 1 1 10 7 12 1 1 1 11 7 12 1 1 1 7 1 11 12 1 1 10 12 1 10 7 12 1 1 1 11 12 1 1 11 7 12 1 1 11 12 1 1 7 1 10 12 1 1 11 7 12 1 1 1 11 12 1 1 10 12 1 1 10 12 1 7 1 10 12 1 1 10 12 1 1 10 12 1 1 7 1 11 12 1 1 10 12 1 10 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 1 1 100 1 1 +ciInstanceKlass org/apache/maven/model/InputLocation 1 1 217 10 7 12 1 1 1 9 7 12 1 1 1 9 12 1 9 12 1 1 10 12 1 1 9 12 1 1 7 1 10 12 1 100 1 100 1 100 1 10 10 12 1 1 10 100 12 1 1 1 10 12 1 1 8 1 10 12 1 10 12 1 10 12 1 1 100 1 7 1 10 12 1 1 8 1 10 12 1 1 9 12 1 1 10 12 1 1 10 12 1 1 10 11 7 12 1 1 1 11 12 1 1 10 12 1 10 12 1 10 12 1 1 10 12 1 10 12 1 1 11 12 1 10 12 1 11 100 12 1 1 1 11 100 12 1 1 1 11 12 1 100 1 10 12 1 10 12 1 1 11 12 1 10 12 1 8 1 8 1 10 12 1 10 12 1 100 1 100 1 100 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 100 1 1 1 1 100 1 1 +compile org/apache/maven/model/merge/MavenModelMerger mergePlugin_Executions (Lorg/apache/maven/model/Plugin;Lorg/apache/maven/model/Plugin;ZLjava/util/Map;)V -1 4 inline 111 0 -1 org/apache/maven/model/merge/MavenModelMerger mergePlugin_Executions (Lorg/apache/maven/model/Plugin;Lorg/apache/maven/model/Plugin;ZLjava/util/Map;)V 1 1 org/apache/maven/model/Plugin getExecutions ()Ljava/util/List; 2 12 java/util/ArrayList ()V 3 1 java/util/AbstractList ()V 4 1 java/util/AbstractCollection ()V 5 1 java/lang/Object ()V 1 8 java/util/ArrayList isEmpty ()Z 1 17 org/apache/maven/model/Plugin getExecutions ()Ljava/util/List; 2 12 java/util/ArrayList ()V 3 1 java/util/AbstractList ()V 4 1 java/util/AbstractCollection ()V 5 1 java/lang/Object ()V 1 28 java/util/ArrayList size ()I 1 35 java/util/ArrayList size ()I 1 43 java/util/LinkedHashMap (I)V 2 2 java/util/HashMap (I)V 3 4 java/util/HashMap (IF)V 4 1 java/util/AbstractMap ()V 5 1 java/lang/Object ()V 4 51 java/lang/Float isNaN (F)Z 4 91 java/util/HashMap tableSizeFor (I)I 1 50 java/util/ArrayList iterator ()Ljava/util/Iterator; 2 5 java/util/ArrayList$Itr (Ljava/util/ArrayList;)V 3 6 java/lang/Object ()V 1 59 java/util/ArrayList$Itr hasNext ()Z 1 69 java/util/ArrayList$Itr next ()Ljava/lang/Object; 2 1 java/util/ArrayList$Itr checkForComodification ()V 1 85 org/apache/maven/model/ConfigurationContainer getInherited ()Ljava/lang/String; 1 93 org/apache/maven/model/ConfigurationContainer isInherited ()Z 2 11 java/lang/Boolean parseBoolean (Ljava/lang/String;)Z 3 3 java/lang/String equalsIgnoreCase (Ljava/lang/String;)Z 4 14 java/lang/String length ()I 5 6 java/lang/String coder ()B 4 18 java/lang/String length ()I 5 6 java/lang/String coder ()B 4 30 java/lang/String length ()I 5 6 java/lang/String coder ()B 4 33 java/lang/String regionMatches (ZILjava/lang/String;II)Z 5 27 java/lang/String length ()I 6 6 java/lang/String coder ()B 5 43 java/lang/String length ()I 6 6 java/lang/String coder ()B 5 70 java/lang/String coder ()B 5 78 java/lang/String coder ()B 5 98 java/lang/StringLatin1 regionMatchesCI ([BI[BII)Z 6 53 java/lang/CharacterDataLatin1 toUpperCase (I)I 7 4 java/lang/CharacterDataLatin1 getProperties (I)I 6 63 java/lang/CharacterDataLatin1 toUpperCase (I)I 7 4 java/lang/CharacterDataLatin1 getProperties (I)I 6 80 java/lang/Character toLowerCase (I)I 6 85 java/lang/Character toLowerCase (I)I 1 103 org/apache/maven/model/ConfigurationContainer isInherited ()Z 2 11 java/lang/Boolean parseBoolean (Ljava/lang/String;)Z 3 3 java/lang/String equalsIgnoreCase (Ljava/lang/String;)Z 4 14 java/lang/String length ()I 5 6 java/lang/String coder ()B 4 18 java/lang/String length ()I 5 6 java/lang/String coder ()B 4 30 java/lang/String length ()I 5 6 java/lang/String coder ()B 4 33 java/lang/String regionMatches (ZILjava/lang/String;II)Z 5 27 java/lang/String length ()I 6 6 java/lang/String coder ()B 5 43 java/lang/String length ()I 6 6 java/lang/String coder ()B 5 70 java/lang/String coder ()B 5 78 java/lang/String coder ()B 5 98 java/lang/StringLatin1 regionMatchesCI ([BI[BII)Z 6 53 java/lang/CharacterDataLatin1 toUpperCase (I)I 7 4 java/lang/CharacterDataLatin1 getProperties (I)I 6 63 java/lang/CharacterDataLatin1 toUpperCase (I)I 7 4 java/lang/CharacterDataLatin1 getProperties (I)I 6 80 java/lang/Character toLowerCase (I)I 6 85 java/lang/Character toLowerCase (I)I 1 112 org/apache/maven/model/merge/MavenModelMerger getPluginExecutionKey (Lorg/apache/maven/model/PluginExecution;)Ljava/lang/Object; 2 1 org/apache/maven/model/PluginExecution getId ()Ljava/lang/String; 1 123 java/util/HashMap put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; 2 2 java/util/HashMap hash (Ljava/lang/Object;)I 3 9 java/lang/String hashCode ()I 4 17 java/lang/String isLatin1 ()Z 4 27 java/lang/StringLatin1 hashCode ([B)I 1 134 java/util/ArrayList iterator ()Ljava/util/Iterator; 2 5 java/util/ArrayList$Itr (Ljava/util/ArrayList;)V 3 6 java/lang/Object ()V 1 143 java/util/ArrayList$Itr hasNext ()Z 1 153 java/util/ArrayList$Itr next ()Ljava/lang/Object; 2 1 java/util/ArrayList$Itr checkForComodification ()V 1 166 org/apache/maven/model/merge/MavenModelMerger getPluginExecutionKey (Lorg/apache/maven/model/PluginExecution;)Ljava/lang/Object; 2 1 org/apache/maven/model/PluginExecution getId ()Ljava/lang/String; 1 175 java/util/LinkedHashMap get (Ljava/lang/Object;)Ljava/lang/Object; 2 2 java/util/HashMap getNode (Ljava/lang/Object;)Ljava/util/HashMap$Node; 3 23 java/util/HashMap hash (Ljava/lang/Object;)I 4 9 java/lang/String hashCode ()I 5 17 java/lang/String isLatin1 ()Z 5 27 java/lang/StringLatin1 hashCode ([B)I 3 63 java/lang/String equals (Ljava/lang/Object;)Z 3 128 java/lang/String equals (Ljava/lang/Object;)Z 1 207 java/util/HashMap put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; 2 2 java/util/HashMap hash (Ljava/lang/Object;)I 3 9 java/lang/String hashCode ()I 4 17 java/lang/String isLatin1 ()Z 4 27 java/lang/StringLatin1 hashCode ([B)I 1 223 java/util/LinkedHashMap values ()Ljava/util/Collection; 2 14 java/util/LinkedHashMap$LinkedValues (Ljava/util/LinkedHashMap;)V 3 6 java/util/AbstractCollection ()V 4 1 java/lang/Object ()V 1 228 java/util/ArrayList (Ljava/util/Collection;)V 2 1 java/util/AbstractList ()V 3 1 java/util/AbstractCollection ()V 4 1 java/lang/Object ()V 1 231 org/apache/maven/model/Plugin setExecutions (Ljava/util/List;)V diff --git a/backend/ims-web/src/main/java/com/ims/web/AgentControllerImpl.java b/backend/ims-web/src/main/java/com/ims/web/AgentControllerImpl.java new file mode 100644 index 0000000..12ed489 --- /dev/null +++ b/backend/ims-web/src/main/java/com/ims/web/AgentControllerImpl.java @@ -0,0 +1,284 @@ +package com.ims.web; + +import com.ims.api.dto.agent.AgentApprovalRequest; +import com.ims.api.dto.agent.AgentConfigRequest; +import com.ims.api.dto.agent.AgentExecuteRequest; +import com.ims.api.dto.agent.AgentExecuteResponse; +import com.ims.api.dto.agent.AgentMemoryRequest; +import com.ims.api.dto.agent.AgentSuggestFieldsRequest; +import com.ims.api.dto.agent.AgentSuggestRequest; +import com.ims.common.dto.ApiResponse; +import com.ims.common.dto.PageResult; +import com.ims.common.exception.BusinessException; +import com.ims.service.agent.AgentConfigService; +import com.ims.service.agent.AgentOrchestratorService; +import com.ims.service.agent.MemoryService; +import com.ims.service.agent.tool.ToolRegistry; +import com.ims.service.entity.AgentMemory; +import com.ims.service.entity.AgentPlan; +import com.ims.service.entity.ToolExecution; +import com.ims.service.entity.User; +import com.ims.service.repository.AgentPlanRepository; +import com.ims.service.repository.ToolExecutionRepository; +import com.ims.service.repository.UserRepository; +import jakarta.validation.Valid; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.MediaType; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/agent") +public class AgentControllerImpl { + + private final AgentOrchestratorService orchestratorService; + private final AgentConfigService agentConfigService; + private final MemoryService memoryService; + private final UserRepository userRepository; + private final AgentPlanRepository planRepository; + private final ToolExecutionRepository toolExecutionRepository; + private final ToolRegistry toolRegistry; + + public AgentControllerImpl(AgentOrchestratorService orchestratorService, + AgentConfigService agentConfigService, + MemoryService memoryService, + UserRepository userRepository, + AgentPlanRepository planRepository, + ToolExecutionRepository toolExecutionRepository, + ToolRegistry toolRegistry) { + this.orchestratorService = orchestratorService; + this.agentConfigService = agentConfigService; + this.memoryService = memoryService; + this.userRepository = userRepository; + this.planRepository = planRepository; + this.toolExecutionRepository = toolExecutionRepository; + this.toolRegistry = toolRegistry; + } + + @PostMapping("/execute") + public ApiResponse execute(@Valid @RequestBody AgentExecuteRequest request, + Authentication authentication) { + Long userId = requireUserId(authentication); + AgentExecuteResponse resp = orchestratorService.execute(request.getIssueId(), request.getGoal(), userId); + return ApiResponse.success(resp); + } + + @GetMapping("/plan/{planId}/status") + public ApiResponse> status(@PathVariable Long planId) { + return ApiResponse.success(orchestratorService.getStatus(planId)); + } + + @GetMapping(value = "/plan/{planId}/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public SseEmitter stream(@PathVariable Long planId) { + return orchestratorService.stream(planId); + } + + @PostMapping("/approval/{planId}/approve") + public ApiResponse> approve(@PathVariable Long planId, + @RequestBody(required = false) AgentApprovalRequest request, + Authentication authentication) { + String comment = request == null ? null : request.getComment(); + return ApiResponse.success(orchestratorService.approve(planId, comment, requireUserId(authentication))); + } + + @PostMapping("/approval/{planId}/reject") + public ApiResponse> reject(@PathVariable Long planId, + @RequestBody(required = false) AgentApprovalRequest request) { + String comment = request == null ? null : request.getComment(); + return ApiResponse.success(orchestratorService.reject(planId, comment)); + } + + @PostMapping("/suggest") + public ApiResponse suggest(@Valid @RequestBody AgentSuggestRequest request, + Authentication authentication) { + return ApiResponse.success(orchestratorService.suggest(request.getIssueId(), request.getGoal(), + requireUserId(authentication))); + } + + @PostMapping("/suggest-fields") + public ApiResponse> suggestFields(@Valid @RequestBody AgentSuggestFieldsRequest request) { + return ApiResponse.success(orchestratorService.suggestFields(request.getTitle(), request.getDescription())); + } + + @GetMapping("/memories") + public ApiResponse>> memories( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int pageSize) { + Page result = memoryService.list(page, pageSize); + PageResult> pageResult = new PageResult<>( + result.getContent().stream().map(this::toMemoryMap).toList(), + result.getTotalElements(), page, pageSize); + return ApiResponse.success(pageResult); + } + + @PostMapping("/memories") + public ApiResponse> createMemory(@Valid @RequestBody AgentMemoryRequest request) { + AgentMemory memory = memoryService.add(request.getIssueSummary(), request.getSolutionSteps()); + return ApiResponse.success(toMemoryMap(memory)); + } + + @DeleteMapping("/memories/{id}") + public ApiResponse deleteMemory(@PathVariable Long id) { + memoryService.delete(id); + return ApiResponse.success(null); + } + + @PutMapping("/memories/{id}") + public ApiResponse> updateMemory(@PathVariable Long id, + @Valid @RequestBody AgentMemoryRequest request) { + AgentMemory memory = memoryService.update(id, request.getIssueSummary(), request.getSolutionSteps()); + return ApiResponse.success(toMemoryMap(memory)); + } + + @GetMapping("/config") + public ApiResponse> getConfig() { + return ApiResponse.success(agentConfigService.getConfig()); + } + + @PutMapping("/config") + public ApiResponse updateConfig(@RequestBody AgentConfigRequest request) { + agentConfigService.updateConfig(request); + return ApiResponse.success(null); + } + + @GetMapping("/overview") + public ApiResponse> overview() { + LocalDateTime todayStart = LocalDate.now().atStartOfDay(); + LocalDateTime yesterdayStart = LocalDate.now().minusDays(1).atStartOfDay(); + long todayExecutions = toolExecutionRepository.countByCreatedAtAfter(todayStart); + long yesterdayExecutions = toolExecutionRepository.countByCreatedAtBetween(yesterdayStart, todayStart); + long todayPlans = planRepository.countByCreatedAtAfter(todayStart); + long totalTools = toolExecutionRepository.count(); + long successTools = toolExecutionRepository.countByStatus("success"); + long pendingApprovals = planRepository.countByApprovalStatus("requested"); + + // 计算增长率 + double growthRate = yesterdayExecutions == 0 ? 0 : + Math.round((todayExecutions - yesterdayExecutions) * 100.0 / yesterdayExecutions); + + List> latestExecutions = new ArrayList<>(); + for (ToolExecution t : toolExecutionRepository.findTop15ByOrderByCreatedAtDesc()) { + latestExecutions.add(toExecutionMap(t)); + } + + // 趋势图数据:最近7天每小时的调用次数 + List> trendData = new ArrayList<>(); + DateTimeFormatter hourFormatter = DateTimeFormatter.ofPattern("HH:00"); + for (int hour = 0; hour < 24; hour++) { + LocalDateTime hourStart = todayStart.withHour(hour); + LocalDateTime hourEnd = hourStart.plusHours(1); + long count = toolExecutionRepository.countBetween(hourStart, hourEnd); + Map point = new LinkedHashMap<>(); + point.put("hour", hourFormatter.format(hourStart)); + point.put("count", count); + trendData.add(point); + } + + Map result = new LinkedHashMap<>(); + result.put("todayExecutions", todayExecutions); + result.put("yesterdayExecutions", yesterdayExecutions); + result.put("growthRate", growthRate); + result.put("todayPlans", todayPlans); + result.put("toolTotalCount", totalTools); + result.put("toolSuccessCount", successTools); + result.put("toolSuccessRate", totalTools == 0 ? 0.0 : Math.round(successTools * 1000.0 / totalTools) / 10.0); + result.put("pendingApprovals", pendingApprovals); + result.put("latestExecutions", latestExecutions); + result.put("trendData", trendData); + return ApiResponse.success(result); + } + + @GetMapping("/plans") + public ApiResponse>> plans( + @RequestParam(defaultValue = "requested") String approvalStatus, + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int pageSize) { + Pageable pageable = PageRequest.of(Math.max(page - 1, 0), pageSize); + Page result = planRepository.findByApprovalStatusOrderByCreatedAtDesc(approvalStatus, pageable); + PageResult> pageResult = new PageResult<>( + result.getContent().stream().map(this::toPlanMap).toList(), + result.getTotalElements(), page, pageSize); + return ApiResponse.success(pageResult); + } + + @GetMapping("/tools") + public ApiResponse>> tools() { + List> result = new ArrayList<>(); + for (Map.Entry entry : toolRegistry.descriptions().entrySet()) { + Map m = new LinkedHashMap<>(); + m.put("name", entry.getKey()); + m.put("description", entry.getValue()); + m.put("isWrite", toolRegistry.isWrite(entry.getKey())); + result.add(m); + } + return ApiResponse.success(result); + } + + private Map toPlanMap(AgentPlan plan) { + Map m = new LinkedHashMap<>(); + m.put("planId", plan.getId()); + m.put("issueId", plan.getIssue() != null ? plan.getIssue().getId() : null); + m.put("issueNo", plan.getIssue() != null ? plan.getIssue().getIssueNo() : null); + m.put("issueTitle", plan.getIssue() != null ? plan.getIssue().getTitle() : null); + m.put("goal", plan.getGoal()); + m.put("status", plan.getStatus()); + m.put("requiresApproval", plan.getRequiresApproval()); + m.put("approvalStatus", plan.getApprovalStatus()); + m.put("approvalComment", plan.getApprovalComment()); + m.put("createdAt", plan.getCreatedAt() == null ? null : plan.getCreatedAt().toString()); + + ToolExecution pending = toolExecutionRepository.findByPlanId(plan.getId()).stream() + .filter(t -> "pending".equals(t.getStatus())) + .findFirst().orElse(null); + m.put("toolName", pending != null ? pending.getToolName() : null); + m.put("toolParams", pending != null ? pending.getInputParams() : null); + m.put("approvalReason", pending != null ? pending.getOutputResult() : null); + return m; + } + + private Map toExecutionMap(ToolExecution t) { + Map m = new LinkedHashMap<>(); + m.put("id", t.getId()); + m.put("planId", t.getPlan() != null ? t.getPlan().getId() : null); + m.put("toolName", t.getToolName()); + m.put("status", t.getStatus()); + m.put("executionTimeMs", t.getExecutionTimeMs()); + m.put("outputResult", t.getOutputResult()); + m.put("createdAt", t.getCreatedAt() == null ? null : t.getCreatedAt().toString()); + return m; + } + + private Map toMemoryMap(AgentMemory memory) { + Map m = new LinkedHashMap<>(); + m.put("id", memory.getId()); + m.put("issueSummary", memory.getIssueSummary()); + m.put("solutionSteps", memory.getSolutionSteps()); + m.put("effectivenessScore", memory.getEffectivenessScore() == null ? BigDecimal.ZERO : memory.getEffectivenessScore()); + m.put("createdAt", memory.getCreatedAt() == null ? null : memory.getCreatedAt().toString()); + m.put("updatedAt", memory.getUpdatedAt() == null ? null : memory.getUpdatedAt().toString()); + return m; + } + + private Long requireUserId(Authentication authentication) { + if (authentication == null || !authentication.isAuthenticated()) { + throw new BusinessException("未登录"); + } + String username = authentication.getName(); + User user = userRepository.findByUsername(username) + .or(() -> userRepository.findByUserid(username)) + .orElseThrow(() -> new BusinessException("用户不存在: " + username)); + return user.getId(); + } +} diff --git a/backend/ims-web/src/main/java/com/ims/web/AiAnalysisControllerImpl.java b/backend/ims-web/src/main/java/com/ims/web/AiAnalysisControllerImpl.java new file mode 100644 index 0000000..80dbc76 --- /dev/null +++ b/backend/ims-web/src/main/java/com/ims/web/AiAnalysisControllerImpl.java @@ -0,0 +1,233 @@ +package com.ims.web; + +import com.ims.api.dto.ai.AiAnalysisRequest; +import com.ims.api.dto.ai.AiAnalysisResponse; +import com.ims.api.dto.ai.AiFeedbackRequest; +import com.ims.common.dto.ApiResponse; +import com.ims.common.dto.PageResult; +import com.ims.common.exception.BusinessException; +import com.ims.service.ai.AiAnalysisService; +import com.ims.service.entity.AiCallLog; +import com.ims.service.entity.User; +import com.ims.service.repository.AiAnalysisRepository; +import com.ims.service.repository.AiCallLogRepository; +import com.ims.service.repository.IssueRepository; +import com.ims.service.repository.UserRepository; +import jakarta.validation.Valid; +import org.springframework.data.domain.Page; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/ai") +public class AiAnalysisControllerImpl { + + private final AiAnalysisService aiAnalysisService; + private final UserRepository userRepository; + private final AiCallLogRepository aiCallLogRepository; + private final AiAnalysisRepository aiAnalysisRepository; + private final IssueRepository issueRepository; + + public AiAnalysisControllerImpl(AiAnalysisService aiAnalysisService, UserRepository userRepository, + AiCallLogRepository aiCallLogRepository, + AiAnalysisRepository aiAnalysisRepository, + IssueRepository issueRepository) { + this.aiAnalysisService = aiAnalysisService; + this.userRepository = userRepository; + this.aiCallLogRepository = aiCallLogRepository; + this.aiAnalysisRepository = aiAnalysisRepository; + this.issueRepository = issueRepository; + } + + @PostMapping("/batch-generate") + public ApiResponse> batchGenerate(@RequestBody AiAnalysisRequest request, + Authentication authentication) { + int count = aiAnalysisService.batchGenerate(request, requireUserId(authentication)); + return ApiResponse.success(Map.of("submitted", count)); + } + + @GetMapping("/records") + public ApiResponse> records( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int pageSize, + @RequestParam(required = false) Long id, + @RequestParam(required = false) Long issueId, + @RequestParam(required = false) Long departmentId, + @RequestParam(required = false) String status, + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate, + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) { + Page result = aiAnalysisService.records(id, page, pageSize, issueId, departmentId, status, startDate, endDate); + PageResult pageResult = new PageResult<>( + result.getContent(), result.getTotalElements(), page, pageSize); + return ApiResponse.success(pageResult); + } + + @GetMapping("/records/running") + public ApiResponse> running() { + return ApiResponse.success(aiAnalysisService.running()); + } + + @GetMapping("/call-logs") + public ApiResponse>> callLogs() { + List> result = new ArrayList<>(); + for (AiCallLog log : aiCallLogRepository.findTop20ByOrderByCreatedAtDesc()) { + Map m = new LinkedHashMap<>(); + m.put("id", log.getId()); + m.put("provider", log.getProvider()); + m.put("model", log.getModel()); + m.put("status", log.getStatus()); + m.put("latencyMs", log.getLatencyMs()); + m.put("responseSnippet", log.getResponseSnippet()); + m.put("errorMessage", log.getErrorMessage()); + m.put("createdAt", log.getCreatedAt() == null ? null : log.getCreatedAt().toString()); + result.add(m); + } + return ApiResponse.success(result); + } + + @GetMapping("/overview-stats") + public ApiResponse> overviewStats( + @RequestParam(defaultValue = "14") int days) { + Map result = new LinkedHashMap<>(); + + long totalIssues = issueRepository.count(); + long analyzedCount = aiAnalysisRepository.countAnalyzedIssues(); + long unanalyzedCount = totalIssues - analyzedCount; + double coverageRate = totalIssues == 0 ? 0 : Math.round(analyzedCount * 1000.0 / totalIssues) / 10.0; + + List statusRows = aiAnalysisRepository.countByStatusGroup(); + long totalAnalyses = 0; + long completedAnalyses = 0; + for (Object[] row : statusRows) { + String status = (String) row[0]; + Long count = (Long) row[1]; + totalAnalyses += count; + if ("completed".equals(status)) { + completedAnalyses = count; + } + } + double successRate = totalAnalyses == 0 ? 0 : Math.round(completedAnalyses * 1000.0 / totalAnalyses) / 10.0; + + List> statusDistribution = new ArrayList<>(); + for (Object[] row : statusRows) { + Map item = new LinkedHashMap<>(); + item.put("name", row[0]); + item.put("value", row[1]); + statusDistribution.add(item); + } + + List> categoryDistribution = new ArrayList<>(); + for (Object[] row : aiAnalysisRepository.countByCategoryGroup()) { + Map item = new LinkedHashMap<>(); + item.put("name", row[0]); + item.put("value", row[1]); + categoryDistribution.add(item); + } + + List> departmentDistribution = new ArrayList<>(); + for (Object[] row : aiAnalysisRepository.countByDepartmentGroup()) { + Map item = new LinkedHashMap<>(); + item.put("name", row[0]); + item.put("value", row[1]); + departmentDistribution.add(item); + } + + List> dailyTrend = new ArrayList<>(); + LocalDateTime since = java.time.LocalDate.now().minusDays(days).atStartOfDay(); + List trendRows = aiAnalysisRepository.dailyTrendGroup(since); + Map> dateMap = new LinkedHashMap<>(); + for (Object[] row : trendRows) { + String date = String.valueOf(row[0]); + String status = (String) row[1]; + Long count = (Long) row[2]; + dateMap.computeIfAbsent(date, k -> { + Map m = new LinkedHashMap<>(); + m.put("completed", 0L); + m.put("failed", 0L); + return m; + }).put(status, count); + } + for (Map.Entry> entry : dateMap.entrySet()) { + Map item = new LinkedHashMap<>(); + item.put("date", entry.getKey()); + item.put("completed", entry.getValue().getOrDefault("completed", 0L)); + item.put("failed", entry.getValue().getOrDefault("failed", 0L)); + dailyTrend.add(item); + } + + result.put("totalIssues", totalIssues); + result.put("analyzedCount", analyzedCount); + result.put("unanalyzedCount", unanalyzedCount); + result.put("coverageRate", coverageRate); + result.put("successRate", successRate); + result.put("dailyTrend", dailyTrend); + result.put("statusDistribution", statusDistribution); + result.put("categoryDistribution", categoryDistribution); + result.put("departmentDistribution", departmentDistribution); + return ApiResponse.success(result); + } + + @PostMapping("/records/{id}/feedback") + public ApiResponse feedback(@PathVariable Long id, + @Valid @RequestBody AiFeedbackRequest body, + Authentication authentication) { + aiAnalysisService.feedback(id, requireUserId(authentication), body.getIsHelpful(), body.getComment()); + return ApiResponse.success(null); + } + + @GetMapping("/records/export") + public void export(@RequestParam(required = false) Long id, + @RequestParam(required = false) Long issueId, + @RequestParam(required = false) Long departmentId, + @RequestParam(required = false) String status, + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate, + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate, + jakarta.servlet.http.HttpServletResponse response) throws java.io.IOException { + Page page = aiAnalysisService.records(id, 1, 10000, issueId, departmentId, status, startDate, endDate); + StringBuilder csv = new StringBuilder(); + csv.append('\uFEFF'); // UTF-8 BOM,兼容 Excel + csv.append("ID,指摘编号,指摘标题,问题分类,提取关键词,根因分析,整改建议,分析状态,模型,分析时间\n"); + for (AiAnalysisResponse r : page.getContent()) { + csv.append(r.getId()).append(',') + .append(csvCell(r.getIssueNo())).append(',') + .append(csvCell(r.getIssueTitle())).append(',') + .append(csvCell(r.getCategory())).append(',') + .append(csvCell(r.getKeywords())).append(',') + .append(csvCell(r.getRootCause())).append(',') + .append(csvCell(r.getSuggestion())).append(',') + .append(csvCell(r.getStatus())).append(',') + .append(csvCell(r.getModelProvider() + "/" + r.getModelName())).append(',') + .append(r.getCreatedAt() == null ? "" : r.getCreatedAt().toString()) + .append('\n'); + } + response.setContentType("text/csv; charset=UTF-8"); + response.setHeader("Content-Disposition", "attachment; filename=ai-analysis.csv"); + response.getWriter().write(csv.toString()); + } + + private String csvCell(String value) { + if (value == null) { + return ""; + } + return "\"" + value.replace("\"", "\"\"") + "\""; + } + + private Long requireUserId(Authentication authentication) { + if (authentication == null || !authentication.isAuthenticated()) { + throw new BusinessException("未登录"); + } + String username = authentication.getName(); + User user = userRepository.findByUsername(username) + .or(() -> userRepository.findByUserid(username)) + .orElseThrow(() -> new BusinessException("用户不存在: " + username)); + return user.getId(); + } +} diff --git a/backend/ims-web/src/main/java/com/ims/web/AiConfigControllerImpl.java b/backend/ims-web/src/main/java/com/ims/web/AiConfigControllerImpl.java new file mode 100644 index 0000000..4cccf79 --- /dev/null +++ b/backend/ims-web/src/main/java/com/ims/web/AiConfigControllerImpl.java @@ -0,0 +1,39 @@ +package com.ims.web; + +import com.ims.api.dto.ai.AiConfigRequest; +import com.ims.api.dto.ai.AiConfigResponse; +import com.ims.common.dto.ApiResponse; +import com.ims.service.ai.ModelRoutingService; +import com.ims.service.knowledge.AiConfigService; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/ai/config") +public class AiConfigControllerImpl { + + private final AiConfigService aiConfigService; + private final ModelRoutingService modelRoutingService; + + public AiConfigControllerImpl(AiConfigService aiConfigService, ModelRoutingService modelRoutingService) { + this.aiConfigService = aiConfigService; + this.modelRoutingService = modelRoutingService; + } + + @GetMapping + public ApiResponse getConfig() { + return ApiResponse.success(aiConfigService.getConfig()); + } + + @PutMapping + public ApiResponse updateConfig(@RequestBody AiConfigRequest request) { + aiConfigService.updateConfig(request); + return ApiResponse.success(null); + } + + @PostMapping("/test") + public ApiResponse> test() { + return ApiResponse.success(modelRoutingService.test()); + } +} diff --git a/backend/ims-web/src/main/java/com/ims/web/AuthControllerImpl.java b/backend/ims-web/src/main/java/com/ims/web/AuthControllerImpl.java new file mode 100644 index 0000000..43a5cde --- /dev/null +++ b/backend/ims-web/src/main/java/com/ims/web/AuthControllerImpl.java @@ -0,0 +1,106 @@ +package com.ims.web; + +import com.ims.common.constant.ResultCode; +import com.ims.common.dto.ApiResponse; +import com.ims.common.exception.BusinessException; +import com.ims.common.util.JwtUtil; +import com.ims.api.dto.auth.LoginRequest; +import com.ims.api.dto.auth.LoginResponse; +import com.ims.service.entity.User; +import com.ims.service.repository.UserRepository; +import com.ims.service.repository.UserRoleRepository; +import com.ims.service.repository.RoleRepository; +import io.jsonwebtoken.Claims; +import jakarta.validation.Valid; +import org.springframework.security.core.Authentication; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.bind.annotation.*; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/api/v1/auth") +public class AuthControllerImpl { + + private final UserRepository userRepository; + private final UserRoleRepository userRoleRepository; + private final RoleRepository roleRepository; + private final JwtUtil jwtUtil; + private final PasswordEncoder passwordEncoder; + + public AuthControllerImpl(UserRepository userRepository, UserRoleRepository userRoleRepository, + RoleRepository roleRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder) { + this.userRepository = userRepository; + this.userRoleRepository = userRoleRepository; + this.roleRepository = roleRepository; + this.jwtUtil = jwtUtil; + this.passwordEncoder = passwordEncoder; + } + + @PostMapping("/login") + public ApiResponse login(@Valid @RequestBody LoginRequest request) { + User user = userRepository.findByUserid(request.getUsername()) + .or(() -> userRepository.findByUsername(request.getUsername())) + .orElseThrow(() -> new BusinessException(ResultCode.UNAUTHORIZED.getCode(), "账号或密码错误")); + + if (!passwordEncoder.matches(request.getPassword(), user.getPasswordHash())) { + throw new BusinessException(ResultCode.UNAUTHORIZED.getCode(), "账号或密码错误"); + } + + if (user.getIsActive() == null || !user.getIsActive()) { + throw new BusinessException(ResultCode.FORBIDDEN.getCode(), "账号已被禁用"); + } + + String accessToken = jwtUtil.generateAccessToken(user.getId(), user.getUsername()); + String refreshToken = jwtUtil.generateRefreshToken(user.getId(), user.getUsername()); + + return ApiResponse.success(new LoginResponse(accessToken, refreshToken, user.getId(), user.getUsername(), + resolveRoleName(user.getId()))); + } + + private String resolveRoleName(Long userId) { + return userRoleRepository.findByUserId(userId).stream() + .map(ur -> roleRepository.findById(ur.getRoleId()).map(r -> r.getName()).orElse("")) + .filter(n -> !n.isEmpty()) + .collect(Collectors.joining(",")); + } + + @GetMapping("/me") + public ApiResponse> me(Authentication authentication) { + if (authentication == null || !authentication.isAuthenticated()) { + throw new BusinessException(ResultCode.UNAUTHORIZED.getCode(), "未登录"); + } + String username = authentication.getName(); + User user = userRepository.findByUsername(username) + .or(() -> userRepository.findByUserid(username)) + .orElseThrow(() -> new BusinessException(ResultCode.UNAUTHORIZED.getCode(), "用户不存在")); + Map result = new LinkedHashMap<>(); + result.put("userId", user.getId()); + result.put("username", user.getUsername()); + result.put("userid", user.getUserid()); + result.put("departmentId", user.getDepartment() != null ? user.getDepartment().getId() : null); + result.put("departmentName", user.getDepartment() != null ? user.getDepartment().getName() : null); + result.put("isActive", user.getIsActive()); + result.put("agentAutoExecute", user.getAgentAutoExecute()); + result.put("roleName", resolveRoleName(user.getId())); + return ApiResponse.success(result); + } + + @PostMapping("/refresh") + public ApiResponse refresh(@RequestBody Map body) { + String refreshToken = body.get("refreshToken"); + if (refreshToken == null || !jwtUtil.validateToken(refreshToken)) { + throw new BusinessException(ResultCode.UNAUTHORIZED.getCode(), "refresh token 无效或已过期,请重新登录"); + } + Claims claims = jwtUtil.parseToken(refreshToken); + Long userId = claims.get("userId", Long.class); + String username = claims.getSubject(); + + String newAccessToken = jwtUtil.generateAccessToken(userId, username); + String newRefreshToken = jwtUtil.generateRefreshToken(userId, username); + + return ApiResponse.success(new LoginResponse(newAccessToken, newRefreshToken, userId, username, "")); + } +} diff --git a/backend/ims-web/src/main/java/com/ims/web/DashboardControllerImpl.java b/backend/ims-web/src/main/java/com/ims/web/DashboardControllerImpl.java new file mode 100644 index 0000000..2eed089 --- /dev/null +++ b/backend/ims-web/src/main/java/com/ims/web/DashboardControllerImpl.java @@ -0,0 +1,24 @@ +package com.ims.web; + +import com.ims.api.dto.dashboard.DashboardStatsResponse; +import com.ims.common.dto.ApiResponse; +import com.ims.service.dashboard.DashboardService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/dashboard") +public class DashboardControllerImpl { + + private final DashboardService dashboardService; + + public DashboardControllerImpl(DashboardService dashboardService) { + this.dashboardService = dashboardService; + } + + @GetMapping("/stats") + public ApiResponse stats() { + return ApiResponse.success(dashboardService.stats()); + } +} \ No newline at end of file diff --git a/backend/ims-web/src/main/java/com/ims/web/DepartmentControllerImpl.java b/backend/ims-web/src/main/java/com/ims/web/DepartmentControllerImpl.java new file mode 100644 index 0000000..9638e00 --- /dev/null +++ b/backend/ims-web/src/main/java/com/ims/web/DepartmentControllerImpl.java @@ -0,0 +1,41 @@ +package com.ims.web; + +import com.ims.common.dto.ApiResponse; +import com.ims.service.entity.Department; +import com.ims.service.repository.DepartmentRepository; +import org.springframework.web.bind.annotation.*; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/departments") +public class DepartmentControllerImpl { + + private final DepartmentRepository departmentRepository; + + public DepartmentControllerImpl(DepartmentRepository departmentRepository) { + this.departmentRepository = departmentRepository; + } + + @GetMapping + public ApiResponse>> list() { + return ApiResponse.success(departmentRepository.findAll().stream() + .sorted((a, b) -> { + int ao = a.getSortOrder() == null ? 0 : a.getSortOrder(); + int bo = b.getSortOrder() == null ? 0 : b.getSortOrder(); + return Integer.compare(ao, bo); + }) + .map(this::toMap) + .toList()); + } + + private Map toMap(Department d) { + Map m = new LinkedHashMap<>(); + m.put("id", d.getId()); + m.put("name", d.getName()); + m.put("parentId", d.getParent() == null ? null : d.getParent().getId()); + return m; + } +} diff --git a/backend/ims-web/src/main/java/com/ims/web/GlobalExceptionHandler.java b/backend/ims-web/src/main/java/com/ims/web/GlobalExceptionHandler.java new file mode 100644 index 0000000..d3c5912 --- /dev/null +++ b/backend/ims-web/src/main/java/com/ims/web/GlobalExceptionHandler.java @@ -0,0 +1,64 @@ +package com.ims.web; + +import com.ims.common.constant.ResultCode; +import com.ims.common.dto.ApiResponse; +import com.ims.common.exception.BusinessException; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.multipart.MaxUploadSizeExceededException; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + @ExceptionHandler(BusinessException.class) + public ApiResponse handleBusinessException(BusinessException e, HttpServletResponse response) { + int status = e.getCode() >= 400 && e.getCode() < 600 ? e.getCode() : HttpStatus.BAD_REQUEST.value(); + response.setStatus(status); + return ApiResponse.error(status, e.getMessage()); + } + + @ExceptionHandler(AccessDeniedException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public ApiResponse handleAccessDeniedException(AccessDeniedException e) { + return ApiResponse.error(ResultCode.FORBIDDEN); + } + + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse handleTypeMismatch(MethodArgumentTypeMismatchException e) { + return ApiResponse.error(400, "参数格式不正确:" + e.getName() + " 必须为数字"); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse handleValidation(MethodArgumentNotValidException e) { + String msg = e.getBindingResult().getFieldErrors().stream() + .findFirst() + .map(err -> err.getDefaultMessage()) + .orElse("参数校验失败"); + return ApiResponse.error(400, msg); + } + + @ExceptionHandler(MaxUploadSizeExceededException.class) + @ResponseStatus(HttpStatus.PAYLOAD_TOO_LARGE) + public ApiResponse handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) { + return ApiResponse.error(413, "文件大小超过限制,最大允许 50MB"); + } + + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ApiResponse handleException(Exception e) { + log.error("Unexpected error", e); + return ApiResponse.error(ResultCode.INTERNAL_ERROR); + } +} diff --git a/backend/ims-web/src/main/java/com/ims/web/IMSApplication.java b/backend/ims-web/src/main/java/com/ims/web/IMSApplication.java new file mode 100644 index 0000000..9ad776a --- /dev/null +++ b/backend/ims-web/src/main/java/com/ims/web/IMSApplication.java @@ -0,0 +1,19 @@ +package com.ims.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.scheduling.annotation.EnableScheduling; + +@SpringBootApplication +@EnableScheduling +@ComponentScan(basePackages = "com.ims") +@EntityScan(basePackages = "com.ims.service.entity") +@EnableJpaRepositories(basePackages = "com.ims.service.repository") +public class IMSApplication { + public static void main(String[] args) { + SpringApplication.run(IMSApplication.class, args); + } +} diff --git a/backend/ims-web/src/main/java/com/ims/web/ImportExportControllerImpl.java b/backend/ims-web/src/main/java/com/ims/web/ImportExportControllerImpl.java new file mode 100644 index 0000000..22c1c67 --- /dev/null +++ b/backend/ims-web/src/main/java/com/ims/web/ImportExportControllerImpl.java @@ -0,0 +1,60 @@ +package com.ims.web; + +import com.ims.api.dto.imports.AgentValidateResponse; +import com.ims.api.dto.imports.ImportConfirmRequest; +import com.ims.api.dto.imports.ImportPreviewResponse; +import com.ims.api.dto.imports.ImportRecordQueryRequest; +import com.ims.api.dto.imports.ImportRecordResponse; +import com.ims.api.dto.imports.ImportRow; +import com.ims.api.service.system.ImportService; +import com.ims.common.dto.ApiResponse; +import com.ims.common.dto.PageResult; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +@RestController +@RequestMapping("/api/v1/import") +public class ImportExportControllerImpl { + + private final ImportService importService; + + public ImportExportControllerImpl(ImportService importService) { + this.importService = importService; + } + + @GetMapping("/template") + public void template(HttpServletResponse response) throws IOException { + byte[] data = importService.generateTemplate(); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + String fileName = java.net.URLEncoder.encode("レビュー記録表.xlsx", java.nio.charset.StandardCharsets.UTF_8) + .replace("+", "%20"); + response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName); + response.getOutputStream().write(data); + } + + @PostMapping("/excel") + public ApiResponse uploadExcel(@RequestParam("file") MultipartFile file) { + return ApiResponse.success(importService.preview(file)); + } + + @PostMapping("/ai-validate") + public ApiResponse aiValidate(@RequestBody List rows) { + return ApiResponse.success(importService.aiValidate(rows)); + } + + @PostMapping("/confirm") + public ApiResponse confirm(@RequestBody ImportConfirmRequest request, + Authentication authentication) { + return ApiResponse.success(importService.confirm(request, authentication.getName())); + } + + @GetMapping("/records") + public ApiResponse> records(ImportRecordQueryRequest request) { + return ApiResponse.success(importService.records(request.getPage(), request.getPageSize())); + } +} diff --git a/backend/ims-web/src/main/java/com/ims/web/IssueControllerImpl.java b/backend/ims-web/src/main/java/com/ims/web/IssueControllerImpl.java new file mode 100644 index 0000000..081d852 --- /dev/null +++ b/backend/ims-web/src/main/java/com/ims/web/IssueControllerImpl.java @@ -0,0 +1,193 @@ +package com.ims.web; + +import com.ims.api.dto.issue.AttachmentResponse; +import com.ims.api.dto.issue.BatchAgentRequest; +import com.ims.api.dto.issue.BatchAssignRequest; +import com.ims.api.dto.issue.BatchNotifyRequest; +import com.ims.api.dto.issue.IssueCreateRequest; +import com.ims.api.dto.issue.IssueListRequest; +import com.ims.api.dto.issue.IssueResponse; +import com.ims.api.dto.issue.IssueStatusRequest; +import com.ims.api.dto.issue.IssueUpdateRequest; +import com.ims.common.dto.ApiResponse; +import com.ims.common.dto.PageResult; +import com.ims.common.exception.BusinessException; +import com.ims.service.agent.AgentService; +import com.ims.service.entity.Issue; +import com.ims.service.entity.User; +import com.ims.service.issue.AttachmentService; +import com.ims.service.issue.IssueService; +import com.ims.service.repository.UserRepository; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/issues") +public class IssueControllerImpl { + + private final IssueService issueService; + private final AttachmentService attachmentService; + private final AgentService agentService; + private final UserRepository userRepository; + + public IssueControllerImpl(IssueService issueService, + AttachmentService attachmentService, + AgentService agentService, + UserRepository userRepository) { + this.issueService = issueService; + this.attachmentService = attachmentService; + this.agentService = agentService; + this.userRepository = userRepository; + } + + @PostMapping + public ApiResponse create(@Valid @RequestBody IssueCreateRequest request, Authentication authentication) { + return ApiResponse.success(issueService.create(request, currentUser(authentication))); + } + + @GetMapping + public ApiResponse> list(@Valid IssueListRequest request) { + return ApiResponse.success(issueService.list(request)); + } + + @GetMapping("/{id}") + public ApiResponse detail(@PathVariable Long id) { + return ApiResponse.success(issueService.detail(id)); + } + + @GetMapping("/{id}/logs") + public ApiResponse>> logs(@PathVariable Long id) { + return ApiResponse.success(issueService.logs(id)); + } + + @PutMapping("/{id}") + public ApiResponse update(@PathVariable Long id, @RequestBody IssueUpdateRequest request, Authentication authentication) { + return ApiResponse.success(issueService.update(id, request, currentUser(authentication))); + } + + @DeleteMapping("/{id}") + public ApiResponse delete(@PathVariable Long id, Authentication authentication) { + issueService.delete(id, currentUser(authentication)); + return ApiResponse.success(null); + } + + @PatchMapping("/{id}/status") + public ApiResponse changeStatus(@PathVariable Long id, @Valid @RequestBody IssueStatusRequest request, Authentication authentication) { + return ApiResponse.success(issueService.changeStatus(id, request.getStatus(), request.getRemark(), currentUser(authentication))); + } + + @PostMapping("/{id}/agent-mode") + public ApiResponse changeAgentMode(@PathVariable Long id, @RequestParam("mode") String mode, Authentication authentication) { + issueService.changeAgentMode(id, mode, currentUser(authentication)); + return ApiResponse.success(null); + } + + @PostMapping("/{id}/attachments") + public ApiResponse uploadAttachment(@PathVariable Long id, + @RequestParam("file") MultipartFile file, + Authentication authentication) { + return ApiResponse.success(attachmentService.upload(id, file, currentUser(authentication))); + } + + @GetMapping("/{id}/attachments") + public ApiResponse> listAttachments(@PathVariable Long id) { + return ApiResponse.success(attachmentService.listByIssue(id)); + } + + @GetMapping("/{id}/attachments/{attachmentId}/download") + public void downloadAttachment(@PathVariable Long id, @PathVariable Long attachmentId, HttpServletResponse response) { + attachmentService.download(attachmentId, response); + } + + @DeleteMapping("/{id}/attachments/{attachmentId}") + public ApiResponse deleteAttachment(@PathVariable Long id, @PathVariable Long attachmentId) { + attachmentService.delete(attachmentId); + return ApiResponse.success(null); + } + + private static final Map STATUS_CN = Map.of( + "draft", "草稿", "open", "待处理", "in_progress", "进行中", + "resolved", "已解决", "verified", "已验证", "closed", "已关闭", "rejected", "已驳回"); + + private static final Map PRIORITY_CN = Map.of( + "urgent", "紧急", "high", "高", "medium", "中", "low", "低"); + + @GetMapping(value = "/export", produces = "text/csv; charset=UTF-8") + public void export(IssueListRequest req, HttpServletResponse response) { + List issues = issueService.findAllFiltered(req); + StringBuilder sb = new StringBuilder(); + sb.append("\uFEFFissueNo,title,status,priority,phase,subProject,category,impactLevel,assignee,deadline,createdAt\n"); + for (Issue i : issues) { + sb.append(IssueService.escapeCsv(i.getIssueNo())).append(',') + .append(IssueService.escapeCsv(i.getTitle())).append(',') + .append(IssueService.escapeCsv(STATUS_CN.getOrDefault(i.getStatus(), i.getStatus()))).append(',') + .append(IssueService.escapeCsv(PRIORITY_CN.getOrDefault(i.getPriority(), i.getPriority()))).append(',') + .append(IssueService.escapeCsv(i.getPhase())).append(',') + .append(IssueService.escapeCsv(i.getSubProject())).append(',') + .append(IssueService.escapeCsv(i.getCategory())).append(',') + .append(IssueService.escapeCsv(i.getImpactLevel())).append(',') + .append(IssueService.escapeCsv(i.getAssignee() != null ? i.getAssignee().getUsername() : "")).append(',') + .append(i.getDeadline() != null ? i.getDeadline().toLocalDate().toString() : "").append(',') + .append(i.getCreatedAt()).append("\n"); + } + try { + byte[] bytes = sb.toString().getBytes(StandardCharsets.UTF_8); + response.setContentType("text/csv; charset=UTF-8"); + response.setHeader("Content-Disposition", "attachment; filename=issues.csv"); + response.getOutputStream().write(bytes); + response.getOutputStream().flush(); + } catch (Exception e) { + throw new BusinessException("CSV 导出失败"); + } + } + + @PostMapping("/batch/assign") + public ApiResponse> batchAssign(@Valid @RequestBody BatchAssignRequest request, + Authentication authentication) { + List ids = request.getIssueIds() != null ? request.getIssueIds() : List.of(); + int count = issueService.batchAssign(ids, request.getAssigneeId(), currentUser(authentication)); + return ApiResponse.success(Map.of("count", count)); + } + + @PostMapping("/batch/notify") + public ApiResponse> batchNotify(@RequestBody BatchNotifyRequest request, + Authentication authentication) { + List ids = request.getIssueIds() != null ? request.getIssueIds() : List.of(); + int count = issueService.batchNotify(ids, request.getContent(), currentUser(authentication)); + return ApiResponse.success(Map.of("count", count)); + } + + @PostMapping("/batch/agent") + public ApiResponse> batchAgent(@RequestBody BatchAgentRequest request, + Authentication authentication) { + List ids = request.getIssueIds() != null ? request.getIssueIds() : List.of(); + if (ids.isEmpty()) { + throw new BusinessException("请先选择指摘"); + } + if (request.getGoal() == null || request.getGoal().isBlank()) { + throw new BusinessException("请输入 Agent 指令"); + } + User user = currentUser(authentication); + List planIds = new ArrayList<>(); + for (Long id : ids) { + Map exec = agentService.execute(id, request.getGoal(), user); + planIds.add(((Number) exec.get("planId")).longValue()); + } + return ApiResponse.success(Map.of("count", ids.size(), "planIds", planIds)); + } + + private User currentUser(Authentication authentication) { + String name = authentication.getName(); + return userRepository.findByUserid(name) + .or(() -> userRepository.findByUsername(name)) + .orElseThrow(() -> new BusinessException("用户不存在")); + } +} \ No newline at end of file diff --git a/backend/ims-web/src/main/java/com/ims/web/KnowledgeControllerImpl.java b/backend/ims-web/src/main/java/com/ims/web/KnowledgeControllerImpl.java new file mode 100644 index 0000000..c28815a --- /dev/null +++ b/backend/ims-web/src/main/java/com/ims/web/KnowledgeControllerImpl.java @@ -0,0 +1,87 @@ +package com.ims.web; + +import com.ims.api.dto.knowledge.KnowledgeDocResponse; +import com.ims.api.dto.knowledge.KnowledgeLogQueryRequest; +import com.ims.api.dto.knowledge.KnowledgeSearchRequest; +import com.ims.common.dto.ApiResponse; +import com.ims.common.dto.PageResult; +import com.ims.service.entity.User; +import com.ims.service.knowledge.KnowledgeService; +import com.ims.service.knowledge.SearchLogService; +import com.ims.service.knowledge.SearchService; +import com.ims.service.repository.UserRepository; +import jakarta.validation.Valid; +import org.springframework.data.domain.Page; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequestMapping("/api/v1/knowledge") +public class KnowledgeControllerImpl { + + private final KnowledgeService knowledgeService; + private final SearchService searchService; + private final SearchLogService searchLogService; + private final UserRepository userRepository; + + public KnowledgeControllerImpl(KnowledgeService knowledgeService, + SearchService searchService, + SearchLogService searchLogService, + UserRepository userRepository) { + this.knowledgeService = knowledgeService; + this.searchService = searchService; + this.searchLogService = searchLogService; + this.userRepository = userRepository; + } + + @GetMapping("/documents") + public ApiResponse> documents( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int pageSize) { + Page result = knowledgeService.list(page, pageSize); + PageResult pageResult = new PageResult<>( + result.getContent(), result.getTotalElements(), page, pageSize); + return ApiResponse.success(pageResult); + } + + @PostMapping("/documents") + public ApiResponse upload(@RequestParam("file") MultipartFile file, + Authentication authentication) { + String username = authentication.getName(); + User user = userRepository.findByUsername(username) + .or(() -> userRepository.findByUserid(username)) + .orElseThrow(() -> new RuntimeException("User not found")); + KnowledgeDocResponse doc = knowledgeService.upload(file, user); + return ApiResponse.success(doc); + } + + @DeleteMapping("/documents/{id}") + public ApiResponse delete(@PathVariable Long id) { + knowledgeService.delete(id); + return ApiResponse.success(null); + } + + @PostMapping("/documents/{id}/reindex") + public ApiResponse reindex(@PathVariable Long id) { + knowledgeService.reindex(id); + return ApiResponse.success(null); + } + + @GetMapping("/search") + public ApiResponse> search(@Valid KnowledgeSearchRequest request) { + java.util.List results = searchService.search( + request.getQuery(), request.getTopK()); + PageResult pageResult = new PageResult<>( + results, results.size(), 1, results.size()); + return ApiResponse.success(pageResult); + } + + @GetMapping("/logs") + public ApiResponse> logs(@Valid KnowledgeLogQueryRequest request) { + Page result = searchLogService.list(request.getPage(), request.getPageSize()); + PageResult pageResult = new PageResult<>( + result.getContent(), result.getTotalElements(), request.getPage(), request.getPageSize()); + return ApiResponse.success(pageResult); + } +} diff --git a/backend/ims-web/src/main/java/com/ims/web/NotificationControllerImpl.java b/backend/ims-web/src/main/java/com/ims/web/NotificationControllerImpl.java new file mode 100644 index 0000000..c2316e5 --- /dev/null +++ b/backend/ims-web/src/main/java/com/ims/web/NotificationControllerImpl.java @@ -0,0 +1,54 @@ +package com.ims.web; + +import com.ims.api.dto.notification.NotificationResponse; +import com.ims.common.dto.ApiResponse; +import com.ims.common.dto.PageResult; +import com.ims.service.notification.NotificationService; +import com.ims.service.repository.UserRepository; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/notifications") +public class NotificationControllerImpl { + + private final NotificationService notificationService; + private final UserRepository userRepository; + + public NotificationControllerImpl(NotificationService notificationService, UserRepository userRepository) { + this.notificationService = notificationService; + this.userRepository = userRepository; + } + + @GetMapping + public ApiResponse> list(@RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int pageSize, + Authentication authentication) { + return ApiResponse.success(notificationService.list(currentUserId(authentication), page, pageSize)); + } + + @GetMapping("/unread-count") + public ApiResponse> unreadCount(Authentication authentication) { + return ApiResponse.success(Map.of("count", notificationService.unreadCount(currentUserId(authentication)))); + } + + @PatchMapping("/{id}/read") + public ApiResponse markRead(@PathVariable Long id, Authentication authentication) { + notificationService.markRead(id, currentUserId(authentication)); + return ApiResponse.success(null); + } + + @PostMapping("/read-all") + public ApiResponse readAll(Authentication authentication) { + return ApiResponse.success(notificationService.markAllRead(currentUserId(authentication))); + } + + private Long currentUserId(Authentication authentication) { + String name = authentication.getName(); + return userRepository.findByUserid(name) + .or(() -> userRepository.findByUsername(name)) + .orElseThrow().getId(); + } +} \ No newline at end of file diff --git a/backend/ims-web/src/main/java/com/ims/web/PromptControllerImpl.java b/backend/ims-web/src/main/java/com/ims/web/PromptControllerImpl.java new file mode 100644 index 0000000..c51008c --- /dev/null +++ b/backend/ims-web/src/main/java/com/ims/web/PromptControllerImpl.java @@ -0,0 +1,87 @@ +package com.ims.web; + +import com.ims.api.dto.prompt.PromptRenderLogResponse; +import com.ims.api.dto.prompt.PromptStatsResponse; +import com.ims.api.dto.prompt.PromptTemplateRequest; +import com.ims.api.dto.prompt.PromptTemplateResponse; +import com.ims.api.dto.prompt.PromptTestRequest; +import com.ims.common.dto.ApiResponse; +import com.ims.common.dto.PageResult; +import com.ims.service.prompt.PromptService; +import jakarta.validation.Valid; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/prompts") +public class PromptControllerImpl { + + private final PromptService promptService; + + public PromptControllerImpl(PromptService promptService) { + this.promptService = promptService; + } + + @GetMapping + public ApiResponse> list( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int pageSize) { + Page result = promptService.list(page, pageSize); + PageResult pageResult = new PageResult<>( + result.getContent(), result.getTotalElements(), page, pageSize); + return ApiResponse.success(pageResult); + } + + @GetMapping("/{templateId}") + public ApiResponse detail(@PathVariable String templateId) { + return ApiResponse.success(promptService.detail(templateId)); + } + + @PostMapping + public ApiResponse create(@Valid @RequestBody PromptTemplateRequest request) { + return ApiResponse.success(promptService.create(request)); + } + + @PutMapping("/{templateId}") + public ApiResponse update(@PathVariable String templateId, + @RequestBody PromptTemplateRequest request) { + return ApiResponse.success(promptService.update(templateId, request)); + } + + @PostMapping("/{templateId}/rollback") + public ApiResponse rollback(@PathVariable String templateId, + @RequestParam int version) { + return ApiResponse.success(promptService.rollback(templateId, version)); + } + + @PostMapping("/{templateId}/test") + public ApiResponse> test(@PathVariable String templateId, + @RequestBody(required = false) PromptTestRequest request) { + PromptTestRequest body = request == null ? new PromptTestRequest() : request; + body.setTemplateId(templateId); + return ApiResponse.success(promptService.test(body)); + } + + @GetMapping("/{templateId}/versions") + public ApiResponse>> versions(@PathVariable String templateId) { + return ApiResponse.success(promptService.versions(templateId)); + } + + @GetMapping("/logs") + public ApiResponse> logs( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int pageSize) { + Page result = promptService.logs(page, pageSize); + PageResult pageResult = new PageResult<>( + result.getContent(), result.getTotalElements(), page, pageSize); + return ApiResponse.success(pageResult); + } + + @GetMapping("/stats") + public ApiResponse> stats() { + return ApiResponse.success(promptService.stats()); + } +} diff --git a/backend/ims-web/src/main/resources/application-dev.yml b/backend/ims-web/src/main/resources/application-dev.yml new file mode 100644 index 0000000..4ef5ad0 --- /dev/null +++ b/backend/ims-web/src/main/resources/application-dev.yml @@ -0,0 +1,10 @@ +spring: + jpa: + show-sql: true + flyway: + enabled: true + +logging: + level: + com.ims: DEBUG + org.springframework.security: DEBUG diff --git a/backend/ims-web/src/main/resources/application-prod.yml b/backend/ims-web/src/main/resources/application-prod.yml new file mode 100644 index 0000000..b656d18 --- /dev/null +++ b/backend/ims-web/src/main/resources/application-prod.yml @@ -0,0 +1,3 @@ +spring: + jpa: + show-sql: false diff --git a/backend/ims-web/src/main/resources/application.yml b/backend/ims-web/src/main/resources/application.yml new file mode 100644 index 0000000..c623eb2 --- /dev/null +++ b/backend/ims-web/src/main/resources/application.yml @@ -0,0 +1,83 @@ +server: + port: 8080 + +spring: + application: + name: ims + datasource: + url: jdbc:postgresql://localhost:5432/ims + username: ims + password: ims123 + driver-class-name: org.postgresql.Driver + jpa: + hibernate: + ddl-auto: update + show-sql: false + properties: + hibernate: + format_sql: true + jdbc: + batch_size: 20 + flyway: + enabled: true + locations: classpath:db/migration + baseline-on-migrate: true + validate-on-migrate: false + data: + redis: + host: localhost + port: 6379 + servlet: + multipart: + max-file-size: 50MB + max-request-size: 50MB + +jwt: + secret: IMS_SECRET_KEY_2026_THIS_MUST_BE_CHANGED_IN_PRODUCTION_ENVIRONMENT + access-token-expiration: 1800000 + refresh-token-expiration: 604800000 + +minio: + endpoint: http://localhost:9000 + access-key: minioadmin + secret-key: minioadmin + bucket: ims-attachments + +ai: + provider: ollama + auto-fallback-enabled: true + +ollama: + base-url: http://localhost:11434 + timeout: + connect: 10000 + read: 1800000 + chat: + model: llama3.1:8b + options: + temperature: 0.3 + num-predict: 4096 + embedding: + model: nomic-embed-text + +deepseek: + api: + key: "" + model: deepseek-v4-pro + embedding-model: text-embedding-3-small + +agent: + max-steps: 10 + auto-execute-high-risk: false + user-rate-limit: 10 + +knowledge: + chunk-size: 200 + chunk-overlap: 40 + max-upload-size: 52428800 + +prompt: + template-cache-ttl: 3600 + render-log-retention-days: 30 + output-format-enforced: true + query-rewrite-enabled: true diff --git a/backend/ims-web/src/main/resources/templates/レビュー記録表.xlsx b/backend/ims-web/src/main/resources/templates/レビュー記録表.xlsx new file mode 100644 index 0000000..3a7fac9 Binary files /dev/null and b/backend/ims-web/src/main/resources/templates/レビュー記録表.xlsx differ diff --git a/backend/pom.xml b/backend/pom.xml new file mode 100644 index 0000000..576f31b --- /dev/null +++ b/backend/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.5.16 + + + + com.ims + ims-backend + 1.0.0-SNAPSHOT + pom + + + ims-common + ims-api + ims-service + ims-web + + + + 17 + 0.12.6 + 1.0.0-M6 + + + + + + io.jsonwebtoken + jjwt-api + ${jjwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jjwt.version} + runtime + + + io.jsonwebtoken + jjwt-jackson + ${jjwt.version} + runtime + + + org.springframework.ai + spring-ai-bom + ${spring-ai.version} + pom + import + + + com.ims + ims-common + 1.0.0-SNAPSHOT + + + com.ims + ims-api + 1.0.0-SNAPSHOT + + + com.ims + ims-service + 1.0.0-SNAPSHOT + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + maven-central + Maven Central + https://repo.maven.apache.org/maven2 + + + diff --git a/containerd-config.toml b/containerd-config.toml new file mode 100644 index 0000000..3eae6fd --- /dev/null +++ b/containerd-config.toml @@ -0,0 +1,4 @@ +version = 2 + +[plugins."io.containerd.grpc.v1.cri".registry] + config_path = "/etc/containerd/certs.d" diff --git a/daemon.json b/daemon.json new file mode 100644 index 0000000..8509b9e --- /dev/null +++ b/daemon.json @@ -0,0 +1,9 @@ +{ + "registry-mirrors": [ + "https://docker.1ms.run", + "https://docker.xuanyuan.me" + ], + "features": { + "containerd-snapshotter": false + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..7780e17 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,78 @@ +version: '3.8' + +services: + postgres: + image: pgvector/pgvector:pg16 + container_name: ims-postgres + restart: unless-stopped + ports: + - "5432:5432" + environment: + POSTGRES_DB: ims + POSTGRES_USER: ims + POSTGRES_PASSWORD: ims123 + volumes: + - pgdata:/var/lib/postgresql/data + - ./init-pgvector.sql:/docker-entrypoint-initdb.d/init-pgvector.sql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ims"] + interval: 10s + timeout: 5s + retries: 5 + + redis: + image: redis:7.4 + container_name: ims-redis + restart: unless-stopped + ports: + - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + minio: + image: minio/minio + container_name: ims-minio + restart: unless-stopped + ports: + - "9000:9000" + - "9001:9001" + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin + MINIO_ROOT_USER_FILE: "" + MINIO_ROOT_PASSWORD_FILE: "" + MINIO_ACCESS_KEY_FILE: "" + MINIO_SECRET_KEY_FILE: "" + MINIO_KMS_SECRET_KEY_FILE: "" + MINIO_UPDATE_MINISIGN_PUBKEY: "" + MINIO_CONFIG_ENV_FILE: "" + volumes: + - minio_data:/data + command: server /data --console-address ":9001" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 10s + timeout: 5s + retries: 5 + + ollama: + image: ollama/ollama:latest + container_name: ims-ollama + ports: + - "11434:11434" + volumes: + - ollama_data:/root/.ollama + healthcheck: + test: ["CMD", "ollama", "list"] + interval: 30s + timeout: 10s + retries: 5 + restart: unless-stopped + +volumes: + pgdata: + minio_data: + ollama_data: diff --git a/docs/IMS技术构成简要说明.md b/docs/IMS技术构成简要说明.md new file mode 100644 index 0000000..f09fab2 --- /dev/null +++ b/docs/IMS技术构成简要说明.md @@ -0,0 +1,104 @@ +# 指摘管理系统(IMS)技术构成简要说明 + +> **版本**:V4.1 | **日期**:2026-07-20 +> +> AI驱动的指摘(缺陷/问题)全生命周期管理系统,以 Agent(智能体)为核心,实现"人机协同"的自动化管理。 + +--- + +## 一、整体架构 + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ +│ 前端层 │────▶│ 后端业务层 │────▶│ Agent决策编排层 │ +│ React SPA │ │ Spring Boot │ │ ReAct循环 + 工具调用 │ +└─────────────┘ └─────────────┘ └─────────────────────────┘ + │ + ┌───────────────────────────┼───────────────────┐ + ▼ ▼ ▼ + ┌──────────┐ ┌────────────┐ ┌────────────┐ + │本地数据层 │ │ 本地向量库 │ │ 外接AI算力 │ + │PostgreSQL│ │ pgvector │ │ DeepSeek │ + │+ Redis │ │ (1536维) │ │ API │ + └──────────┘ └────────────┘ └────────────┘ +``` + +--- + +## 二、核心技术栈 + +| 层级 | 技术选型 | 作用 | +|:---|:---|:---| +| **前端** | React 19 + TypeScript + Vite + Ant Design 6 | 单页应用,工作台/指摘管理/Agent驾驶舱 | +| **后端** | Java 17/21 + Spring Boot 3.5 + Spring Security | RESTful API、RBAC权限、数据访问(JPA) | +| **Agent引擎** | Spring AI / 自定义ReAct框架 + DeepSeek API | 自然语言理解、任务规划、工具调用、思考链 | +| **向量检索** | PostgreSQL 16 + **pgvector** 插件 | 本地存储文档向量(1536维),余弦相似度检索 | +| **缓存/消息** | Redis 7.4 | 热点缓存、会话管理、异步通知 | +| **对象存储** | MinIO(本地部署) | 知识库文档、附件文件存储 | +| **AI算力** | DeepSeek API(deepseek-v4-pro + text-embedding-3-small) | **仅提供推理与向量化算力,不留存数据** | + +--- + +## 三、核心创新点 + +### 1. Agent驱动的业务闭环 +- 内置 **ReAct循环**(推理→行动→观察) +- 用户自然语言指令(如"催办逾期指摘分配给张三")→ Agent自动规划 → 调用工具执行 +- 高风险操作(删除/跨部门分配)自动触发人工审批 + +### 2. 本地知识库 + 外接算力 +- 所有文档原文和向量索引 **存储于本地**(PostgreSQL pgvector) +- 仅将待向量化的文本片段通过API发送至DeepSeek +- **数据不出内网**,兼顾智能化与数据安全 + +### 3. 人机协同审批机制 +- Agent执行敏感操作前自动触发 `request_human_approval` +- 详情页"Agent驾驶舱"实时展示思考链:`[思考]` → `[行动]` → `[观察]` +- 用户可实时批准/拒绝/修改建议 + +### 4. 长期记忆能力 +- 成功的Agent处理经验自动沉淀为向量记忆(`agent_memories`表) +- 后续遇到相似问题时优先检索历史方案,持续优化决策质量 + +--- + +## 四、关键数据流示例 + +**用户输入**:"这个4K屏幕适配问题怎么处理?" + +``` +Agent接收目标 + │ + ▼ +检索本地知识库(search_knowledge) + │ + ▼ +匹配历史案例 #KB-042(Flex容器4K适配方案) + │ + ▼ +生成行动方案 → 调用 send_reminder 给担当者 + │ + ▼ +触发人工审批闸门(request_human_approval) + │ + ▼ +用户批准 → 发送通知并附解决方案 → 记录成功经验到记忆库 +``` + +--- + +## 五、部署特点 + +| 特性 | 说明 | +|:---|:---| +| **全栈容器化** | Docker Compose 一键启动所有服务 | +| **数据本地化** | 知识库文档、向量索引、业务数据全部本地存储 | +| **算力外包化** | 仅DeepSeek API为外部依赖,传输内容可控、不留存 | +| **可观测性** | Prometheus + Grafana 监控Agent任务指标;ELK审计思考链日志 | +| **安全策略** | JWT认证 + RBAC权限 + 单用户Agent调用限流(10次/分钟) | + +--- + +## 六、一句话概括 + +> 用 **Spring Boot + React** 构建、以 **DeepSeek AI** 为大脑、以 **本地pgvector** 为记忆、以 **Agent** 为执行中枢的智能化缺陷管理系统——让AI自动处理80%的常规指摘流转,人工只介入关键决策。 diff --git a/docs/_MASTER_PLAN.md b/docs/_MASTER_PLAN.md new file mode 100644 index 0000000..5ce8fee --- /dev/null +++ b/docs/_MASTER_PLAN.md @@ -0,0 +1,78 @@ +# Master 骨架搭建 · 执行计划 + +## 目录结构 + +``` +D:\IMS\ ← Git 仓库根目录 +├── backend/ ← 后端 Maven 多模块 +│ ├── pom.xml ← 父 POM +│ ├── ims-common/ ← 公共模块(DTO、工具类) +│ ├── ims-api/ ← API 模块(Controller 定义) +│ ├── ims-service/ ← 业务模块(Entity、Repository、Security) +│ └── ims-web/ ← Web 启动模块 +├── frontend/ ← 前端 Vite + React + Ant Design +├── docs/ ← 设计文档 +├── docker-compose.yml ← PostgreSQL 16 + pgvector + Redis 7 + MinIO +└── _MASTER_PLAN.md +``` + +## 阶段 1:数据库 + Docker Compose + +| # | 任务 | 产出 | +|---|------|------| +| 1.1 | docker-compose.yml:PostgreSQL 16(pgvector)+ Redis 7 + MinIO + 初始化脚本 | 1 个 yml + 1 个 init SQL | +| 1.2 | Flyway V1.0:基础表(departments, users, roles, permissions, user_roles, role_permissions) | 1 个 SQL | +| 1.3 | Flyway V1.1:核心业务表(issues, issue_logs, attachments, notifications, task_executions) | 1 个 SQL | +| 1.4 | Flyway V1.2:AI 分析表(ai_analysis, ai_feedback) | 1 个 SQL | +| 1.5 | Flyway V1.3:Agent 表(agent_plans, agent_memories, tool_executions) | 1 个 SQL | +| 1.6 | Flyway V1.4:知识库表(knowledge_documents, knowledge_chunks, knowledge_search_logs) | 1 个 SQL | +| 1.7 | Flyway V1.5:Prompt 模板表(prompt_templates, prompt_template_versions, prompt_render_logs) | 1 个 SQL | +| 1.8 | Flyway V1.6:种子数据(admin 账号 + 6 个预设角色 + 权限数据) | 1 个 SQL | + +## 阶段 2:后端 POM + 配置 + +| # | 任务 | 产出 | +|---|------|------| +| 2.1 | 父 POM + 4 子模块 pom.xml,统一管理依赖 | 5 个 pom.xml | +| 2.2 | application.yml + application-dev.yml + application-prod.yml | 3 个 yml | + +## 阶段 3:后端 Java 基础设施 + +| # | 任务 | 产出 | +|---|------|------| +| 3.1 | ims-common:ApiResponse、PageResult、ResultCode、BusinessException | 4 个 Java 类 | +| 3.2 | JwtUtil(生成/校验 access_token + refresh_token) | 1 个 Java 类 | +| 3.3 | 启动类、Jackson 配置、全局异常处理、CORS 配置 | 4 个 Java 类 | +| 3.4 | 全部 21 张表的 JPA Entity | 21 个 Java 类 | +| 3.5 | 全部 Repository 接口 | 21 个 Java 类 | +| 3.6 | 请求/响应 DTO(按模块分包:auth/issue/agent/knowledge/prompt/system) | ~30 个 Java 类 | + +## 阶段 4:后端 Security + Controller + +| # | 任务 | 产出 | +|---|------|------| +| 4.1 | SecurityConfig + JwtAuthFilter + UserDetailsServiceImpl | 3 个 Java 类 | +| 4.2 | 数据权限 AOP:@DataScope 注解 + 切面(空实现骨架) | 2 个 Java 类 | +| 4.3 | AuthController(真实实现,POST /auth/login 返回 JWT) | 1 个 Controller | +| 4.4 | 其余 10 个 Controller 空实现(return ApiResponse.success(null)) | 10 个 Controller | + +## 阶段 5:前端项目 + +| # | 任务 | 产出 | +|---|------|------| +| 5.1 | npm create vite → React + TypeScript,安装依赖 | 脚手架 | +| 5.2 | Ant Design 主题 + axios 封装 + Redux store | 4 个文件 | +| 5.3 | 登录页(表单 + API 调用 + token 存储 + 跳转) | 1 个页面 | +| 5.4 | 全局 Layout(侧边栏 + 顶栏 + Outlet) | 1 个 Layout 组件 | +| 5.5 | 路由表(懒加载 + 路由守卫 + 侧边栏联动) | routes.tsx | +| 5.6 | 13 个空页面壳(显示"待实现"占位) | 13 个页面组件 | + +## 验证标准 + +```bash +docker-compose up -d +cd backend/ims-web && mvn spring-boot:run +cd frontend && npm run dev +``` + +浏览器 http://localhost:5173 → 登录页 → 种子账号登录 → 侧边栏可点 → 各页面"待实现" diff --git a/docs/agent-management.html b/docs/agent-management.html new file mode 100644 index 0000000..2226118 --- /dev/null +++ b/docs/agent-management.html @@ -0,0 +1,726 @@ + + + + + + + + Agent 管理 - AI指摘管理系统 + + + + + + + + + + +
+
+

Agent 监控与管理

+
+ 全局自动执行 +
+
+
+
+
+ + +
+
+ + + + + +
+
+ +
+ +
+ +
+
+

当前运行状态

+
+
+ +
+
+

健康

+

响应延迟 240ms

+
+
+
+
+

今日调用次数

+

4,821

+

+15% 较昨日平均

+
+
+

工具执行成功率

+

99.2%

+
+
+
+
+
+

人工介入请求

+

12

+

待处理 3

+
+
+
+ +
+
+

Agent 执行日志记录

+
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
时间工具名称执行结果耗时详情
10:32:15search_knowledgeSUCCESS850ms
10:30:02send_reminderWAIT_HUMAN-
10:28:45update_metadataSUCCESS120ms
10:25:12delete_issueREJECTED-
+
+
+ +
+

工具调用频率趋势

+
+
+
+
+ + + + + + + + + + + + +
+
+ + + + \ No newline at end of file diff --git a/docs/ai-analysis.html b/docs/ai-analysis.html new file mode 100644 index 0000000..9d7c91f --- /dev/null +++ b/docs/ai-analysis.html @@ -0,0 +1,453 @@ + + + + + + AI分析记录 - 指摘管理系统 + + + + + + + + + + +
+ +
+
+

AI 分析记录

+ 2026年7月17日 星期五 +
+
+
+ + 3 +
+
+
+

李志强

+

系统管理员

+
+ User Avatar +
+
+
+ + +
+ +
+
+
+ + +
+
+ + +
+
+ +
+ + ~ + +
+
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID提取关键词问题分类根因分析AI 整改建议反馈操作
ISSUE-001传感器, 灰尘, 误报设备故障-电气类传感器透镜污垢导致光路阻挡...建议增加防尘罩并定期清洁...有帮助 +
+ + +
+
ISSUE-002打印, 模糊, 耗材品质异常-标识类碳带张力不均或打印头老化...检查碳带路径,清洁打印头...有帮助 +
+ + +
+
ISSUE-003螺丝, 松动, 安全安全隐患-设施类长期震动导致紧固件失效...使用防松螺母或增加弹垫...未评估 +
+ + +
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/docs/batch-input.html b/docs/batch-input.html new file mode 100644 index 0000000..60861a8 --- /dev/null +++ b/docs/batch-input.html @@ -0,0 +1,316 @@ + + + + + + + + 批量录入 - AI指摘管理系统 + + + + + + + + + +
+
+

指摘批量录入

+
+ +
+
+
+ +
+
+ +
+

点击或拖拽文件到此处上传

+

支持 .xlsx, .xls 文件,单个文件不超过 10MB

+ + +
+ +
+ +
+
+
+
+ +

Agent 校验结果

+
+ 发现 3 + 个建议 +
+
+
+ 第 8 + 行 +

字段: 工程阶段

+

输入内容 "详细测试" 在系统中不存在,根据上下文推断应为 "详细设计"。

+ +
+
+ 第 + 12 行 +

字段: 优先级

+

内容为空。检测到指摘描述包含 "崩溃"、"无法启动" 等词汇,建议设为 "紧急"。

+ +
+
+ +
+

其余 15 条数据校验通过

+

Agent 已完成自动关联与去重校验。

+
+
+
+ +
+
+ +
+
+

预检数据预览 (18 条)

+
+ 待修正 + 已就绪 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
行号标题工程阶段优先级担当者
08页面加载异常详细测试王志
09Logo 尺寸显示不正确详细设计李思
10数据库连接泄漏问题单体测试陈刚
12生产环境宕机风险点基本设计未填未分配
+
+
+ +
+
+
+ + +
+
+
+ +

历史导入记录

+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
导入时间操作人总条数成功失败状态操作
2026-07-17 09:30:15李志强45432已完成 + +
2026-07-15 14:22:08张明亮1281280已完成 + 无错误日志 +
2026-07-12 10:05:33陈志伟67607部分成功 + +
2026-07-10 16:45:21李志强32320已完成 + 无错误日志 +
+
+
+

显示 1 到 4 条,共 12 条记录

+
+ + + + + +
+
+
+
+
+ + + \ No newline at end of file diff --git a/docs/dashboard.html b/docs/dashboard.html new file mode 100644 index 0000000..8d9f677 --- /dev/null +++ b/docs/dashboard.html @@ -0,0 +1,436 @@ + + + + + + + + 工作台 - AI指摘管理系统 + + + + + + + + + + +
+ +
+
+

工作台概览

+ 2026年7月17日 星期五 +
+
+
+ + 3 +
+
+
+

李志强

+

系统管理员

+
+ User Avatar +
+
+
+ +
+ +
+
+
+ +
+
+

Agent 快捷指令

+

输入自然语言指令,Agent 将自动解析并执行

+
+ + + 在线 + +
+
+ + +
+
+ 快捷指令: + + + + +
+
+ + +
+
+
+
+ +
+ +12% vs + 昨天 +
+

待处理指摘

+

42

+
+
+
+
+ +
+ 正常推进中 +
+

进行中指摘

+

128

+
+
+
+
+ +
+ 完成率 + 88% +
+

本月已完成

+

856

+
+
+
+
+ +
+ AI活跃 +
+

今日新增指摘

+

15

+
+
+
+ +
+
+

指摘处理趋势

+
+ + +
+
+
+
+ +
+ +
+ +

快速开始

+

Agent 已经准备好为您加速指摘录入流程,支持智能去重与自动分类。

+ +
+ +
+
+ +

Agent 洞察

+
+
    +
  • +
    +
    +

    高风险警报

    +

    检测到 PGM_002 模块出现 3 + 个关联指摘,建议启动根因分析。

    +
    +
  • +
  • +
    +
    +

    自动对应建议

    +

    Agent 已为 5 个指摘匹配了历史知识库中的修复方案。 +

    +
    +
  • +
  • +
    +
    +

    任务提醒

    +

    已向 2 位担当者发送了关于"逾期指摘"的自动催办。

    +
    +
  • +
+
+
+
+ +
+
+

最新动态

+
+
+
+ +
+
+
+

张明亮 录入了新指摘

+ 10分钟前 +
+

#ISSUE-2026-089 UI适配在大屏幕下出现错位

+
+
+
+
+ +
+
+
+

IMS Agent 自动对应了 +

+ 25分钟前 +
+

#ISSUE-2026-085 通过知识库匹配已完成对应方案生成

+
+
+
+
+ +
+
+
+

王小美 更改了状态为 已关闭 +

+ 1小时前 +
+

#ISSUE-2026-072 数据库连接超时优化完成

+
+
+
+ +
+
+

指摘状态分布

+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/issue-detail.html b/docs/issue-detail.html new file mode 100644 index 0000000..66a6be4 --- /dev/null +++ b/docs/issue-detail.html @@ -0,0 +1,456 @@ + + + + + + + + 指摘详情与 Agent 交互 - AI指摘管理系统 + + + + + + + + + +
+ +
+ +
+
+ + + +
+ ISSUE-2026-089 +

UI 适配在大屏幕(4K)下出现布局错位

+
+
+
+ + +
+
+ +
+ +
+
+

当前状态

+ 对应中 +
+
+

优先级

+ 紧急 +
+
+

对应者

+
+ Avatar + 陈志伟 +
+
+
+

创建日期

+ 2026-07-17 +
+
+ +
+
+ +

结合测试 (IT)

+
+
+ +

用户管理子系统

+
+
+ +

较高 - 影响高分屏用户体验

+
+
+ +

PGM_AUTH_VIEW_001

+
+
+ +
+ +
+ 在分辨率达到 3840x2160 (4K) 及以上时,侧边栏导航的高度未能撑满全屏,底部的用户信息区域会出现悬浮空隙。同时,主看板的统计卡片在超宽比例下没有自动换行,导致右侧溢出。 +
+
+ +
+
+ + +
+
+
+
+
+ +
+
+

bug_screenshot_01.png

+

2.4 MB

+
+
+
+ + +
+
+
+
+
+ +
+
+

error_logs.txt

+

12 KB

+
+
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+
+
+

状态更新:对应中

+ 今天 10:20 +
+

陈志伟 开始处理此指摘,正在分析 CSS Grid 适配问题。

+
+
+
+
+
+

指摘创建

+ 今天 09:45 +
+

张明亮 通过系统录入了此条指摘。

+
+
+
+
+
+ +
+ +
+
+
+ +
+
+

IMS Agent 驾驶舱

+

+ 在线 · 实时分析中 +

+
+
+
+
+ 时间线 +
+
+
+
+ +
+
+ +
+ +
+
+ +
+
+

+ 你好!我是您的指摘处理助手。我已经分析了这条指摘的内容。它涉及到前端适配问题。 +

+ 我可以帮您催办担当者查找知识库中的相似案例,或者根据附件日志自动推断错误原因。您想怎么做? +

+
+
+ +
+
+

查找知识库中的相似案例,并询问陈志伟是否需要技术支持。

+
+
+ User +
+
+ +
+ +
+
+ + [思考] +
+

+ 用户要求查找知识库相似案例。我需要调用 `search_knowledge` 工具,关键词为 "4K 适配", "侧边栏高度", "超宽屏 布局错位"。 + 同时需要调用 `send_reminder` 工具给担当者陈志伟。 +

+
+ +
+
+ + [行动] +
+
+ call: search_knowledge({
+   query: "4K screen layout sidebar bug",
+   project: "FRONTEND_CORE"
+ }) +
+
+ +
+
+ + [观察] +
+

+ 找到 1 条相似记录:#KB-2025-042 "Flex容器在超高分辨率下的 100vh 兼容性修正"。方案:建议使用 `min-h-screen` + 代替固定高度,并开启 `flex-grow`。 +

+
+ +
+
+ + 需要您的审批 +
+

+ Agent 准备调用 send_reminder 给 + 陈志伟。 +

+ 内容: "陈工您好,系统检测到您的 ISSUE-089 与已知案例 #KB-042 相似,推荐方案已发送至您的工作台。如有技术困难,请联系架构组支持。" +

+
+ + + +
+
+
+ +
+
+ +
+
+

+ 知识库检索完成!已为您锁定历史解决方案。我已准备好向陈工发送提醒并提供该方案。请在上方点击“批准”以继续。 +

+
+
+
+ +
+
+
+ + +
+ +
+
+ + +
+
+ Agent 工具可用: +
+ 知识库查询 + 催办通知 + 状态流转 + +4 +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/issue-new.html b/docs/issue-new.html new file mode 100644 index 0000000..12afd88 --- /dev/null +++ b/docs/issue-new.html @@ -0,0 +1,341 @@ + + + + + + 新建指摘 - AI指摘管理系统 + + + + + + + + + +
+ +
+
+ + + +

新建指摘

+ ISSUE-2026-090 (草稿) +
+
+ + +
+
+ + +
+
+ +
+ +
+
+
+ +
+

基本信息

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ +
+

指摘内容

+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ +
+

对应信息

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+ +
+

附件

+
+ +
+
+
+ +
+

点击或拖拽文件到此处上传

+

支持图片、文档、日志文件,单个不超过 20MB

+
+
+
+ + +
+ +
+

快速设置

+
+
+ + +
+
+ +
+ + + + +
+
+
+ + +
+
+ +
+
+ 李志强 + 只读 +
+
+
+ + +
+
+
+ + +
+ +
+ +

Agent 辅助

+
+

输入标题后,Agent 可自动分析并推荐工程阶段、优先级、影响度等字段。

+ +
+ + +
+
+ + 自动保存于 13:58 +
+
+
+
+
+
+ + \ No newline at end of file diff --git a/docs/issues.html b/docs/issues.html new file mode 100644 index 0000000..9b12b32 --- /dev/null +++ b/docs/issues.html @@ -0,0 +1,411 @@ + + + + + + + + 指摘列表 - AI指摘管理系统 + + + + + + + + + +
+ +
+
+

指摘列表

+
+ + AI 实时监控中 +
+
+
+
+ + +
+ + + 新建指摘 + +
+
+ +
+ +
+
+
+ 待对应 + +
+
42
+
+
+
+ 对应中 + +
+
128
+
+
+
+ 待确认 + +
+
65
+
+
+
+ 已关闭 + +
+
856
+
+
+ +
+
+ + 筛选条件 +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ +
+
+

Agent 批量处理

+

选中多条指摘后,可使用 Agent 统一分配或催办

+
+
+
+ + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + ID / 标题状态优先级影响度对应者Agent 建议日期操作
+ + +

ISSUE-2026-089

+

UI 适配在大屏幕(4K)下出现布局错位

+
+ 对应中 + + 紧急 + + + 陈志伟 +
+

知识库匹配到相似案例 #KB-042

+

建议替换 min-h-screen

+
+
2026-07-17 + +
+ + +

ISSUE-2026-085

+

数据库慢查询:用户订单统计页面响应超 5s

+
+ 待对应 + + + + + 未分配 +
+

建议添加复合索引

+

检测到缺失 WHERE 索引

+
+
2026-07-16 + +
+ + +

ISSUE-2026-082

+

登录页面表单校验逻辑漏掉了特殊字符处理

+
+ 待确认 + + + + + 张美心 +
+

整改方案已验证通过

+

可进入确认流程

+
+
2026-07-15 + +
+ + +

ISSUE-2026-078

+

API 文档中 PGM_005 接口参数描述错误

+
+ 已关闭 + + + + + 李华 +
+

+
+
2026-07-14 + +
+ +
+

显示 1 到 4 条,共 1,091 条

+
+ + + + + +
+
+
+
+
+ + + \ No newline at end of file diff --git a/docs/knowledge-base.html b/docs/knowledge-base.html new file mode 100644 index 0000000..4f28497 --- /dev/null +++ b/docs/knowledge-base.html @@ -0,0 +1,320 @@ + + + + + + 知识库管理 - 指摘管理系统 + + + + + + + + + + +
+ +
+
+

知识库管理

+ 2026年7月17日 星期五 +
+
+
+ + 3 +
+
+
+

李志强

+

系统管理员

+
+ User Avatar +
+
+
+ + +
+ +
+
+ + +
+
+ + +
+ +
+
+ +
+

点击或拖拽文件到此处上传

+

支持 PDF, Word (.docx), TXT, Markdown 格式,单个文件不超过 50MB

+ + +
+ + +
+
+
+ 文档列表 + 共 6 个文档 +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
文件名上传人上传时间状态分块数操作
API设计规范_v2.1.pdf李志强2026-07-15 14:23已完成42 +
+ + +
+
故障排查手册.docx陈志伟2026-07-14 09:15处理中- +
+ + +
+
测试用例模板.md张美心2026-07-13 11:40失败0 +
+ + +
+
+
+
+ + + +
+
+ + + + \ No newline at end of file diff --git a/docs/roles.html b/docs/roles.html new file mode 100644 index 0000000..7600086 --- /dev/null +++ b/docs/roles.html @@ -0,0 +1,317 @@ + + + + + + + + 角色权限管理 - AI指摘管理系统 + + + + + + + + + +
+
+

角色权限配置

+ +
+
+ +
+
+ +
+
+
+
+ 超级管理员 + +
+

拥有系统所有操作权限

+
+
+
+ 部门管理员 +
+

管理本部门指摘与用户

+
+
+
+ 指摘录入员 +
+

负责指摘的录入与初期分析

+
+
+
+ 整改担当 +
+

负责指摘的修复与反馈

+
+
+
+ 验证人员 +
+

负责确认修复结果并关闭指摘

+
+
+
+ +
+ +
+
+ +

数据权限范围

+
+
+ +
+
+ +
+
+
+ +

页面与功能权限

+
+
+ +
+
+
+
+
+ 基础功能
+
+ + + + +
+
+
+
+ AI 智能分析
+
+ + + +
+
+
+
+ 系统设置
+
+ + + +
+
+
+
+ +
+ +
+ +

Agent 工具调用权限 (核心)

+
+

配置此角色在与 AI Agent 交互时,允许 Agent + 自动执行的原子工具。敏感操作建议开启“人机协同审批”。

+
+ + + + + + + + +
+
+
+
+
+ + + \ No newline at end of file diff --git a/docs/users.html b/docs/users.html new file mode 100644 index 0000000..90cbedc --- /dev/null +++ b/docs/users.html @@ -0,0 +1,302 @@ + + + + + + + + 用户管理 - AI指摘管理系统 + + + + + + + + + +
+
+

用户管理

+
+
+ + +
+ +
+
+
+ +
+
+ + 组织架构 +
+
+
+ + + IMS 总部 +
+
+
+ + + 研发中心 +
+
+
+ + 前端开发组 +
+
+ + 后端开发组 +
+
+
+ + 质量保证部 (QA) +
+
+ + 架构部 +
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
用户姓名 + 账号/邮箱 + 绑定角色 + + 状态 + Agent 授权 + 操作
+
+ Avatar + 李志强 +
+
li.zq@ims-system.com + 超级管理员 + + + 正常 + + + + + +
+
+ Avatar + 陈志伟 +
+
chen.zw@ims-system.com + 整改担当 + + + 正常 + + + + + +
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/docs/指摘管理系统_概要设计说明书_V6.0.md b/docs/指摘管理系统_概要设计说明书_V6.0.md new file mode 100644 index 0000000..637c1c6 --- /dev/null +++ b/docs/指摘管理系统_概要设计说明书_V6.0.md @@ -0,0 +1,1587 @@ +# 指摘管理系统_概要设计说明书_V6.0(Ollama混合架构升级版) + +| 文档版本 | 修订日期 | 修订内容 | 修订人 | +| :--- | :--- | :--- | :--- | +| V4.2 | 2026-07-21 | Prompt工程专项升级:全链路Prompt模板体系化、领域知识注入、伪专项训练策略落地、新增Prompt版本管理、动态模板加载、输出结构化约束 | 系统架构师 | +| V5.0 | 2026-07-23 | Ollama混合架构升级:引入本地Ollama作为首选推理与嵌入引擎,DeepSeek作为外接高性能算力备选;适配Ollama ChatML格式;新增Spring AI Ollama配置类;调整Prompt模板渲染与工具调用方式;优化知识库向量化流程;新增模型切换与容错机制 | 系统架构师 | +| **V6.0** | **2026-07-23** | **技术栈版本号范围化、Ollama 70B硬件评估+降级方案、Spring AI适配层抽象、双引擎调参策略、pgvector大数据集扩展说明** | **系统架构师** | + + +## 1. 引言 + +### 1.1 编写目的 +本文档为"指摘管理系统"的概要设计说明书,定义系统的整体架构、功能模块、技术选型、数据模型、接口规范、安全策略及部署方案。系统以 **Agent(智能体)** 为核心,实现从用户意图感知到业务闭环执行的自动化与智能化。本版本(V6.0)在V4.2的Prompt工程体系基础上,**升级AI算力层为混合架构**:以本地Ollama为默认推理与嵌入引擎,确保数据隐私与低延迟,同时保留DeepSeek API作为高性能外接算力备选,实现灵活、可控、安全的AI服务。 + +### 1.2 项目背景 +为提升团队在项目开发、文档审查、流程管理等环节中发现和跟踪问题的效率,需构建一套集指摘创建、分配、整改、验证、智能分析于一体的全生命周期管理系统。系统引入 Agent 作为核心决策与执行单元,能够自主理解用户自然语言指令,调用业务工具完成指摘分配、催办、状态变更、知识检索等操作,并在高风险场景下请求人工审批,实现"人机协同"的闭环管理。 + +**AI部署策略(V6.0升级)**: +- **资料库与向量库**:全部部署于本地服务器,文档原文和向量索引永不外传。 +- **推理与嵌入算力**: + - **默认**:使用本地Ollama服务(`llama3.1:70b` / `nomic-embed-text`),数据不出内网,延迟低。 + > **硬件要求**:llama3.1:70b 需至少 48GB VRAM(4bit量化),如GPU显存不足可降级至 `qwen2.5:32b`(需24GB VRAM)或 `llama3.1:8b`(需16GB VRAM)。 + - **备选**:通过配置切换至DeepSeek API(`deepseek-v4-pro` + `text-embedding-3-small`),用于高性能复杂任务或负载分担。 +- **Prompt工程策略**:继续采用"伪专项训练"模式,通过系统化Prompt模板注入领域知识,并适配Ollama的ChatML消息格式,确保跨模型输出一致性与可控性。 + +### 1.3 设计原则 +- **Agent优先**:所有业务操作均可通过Agent自然语言指令触发,Agent具备感知、规划、行动、反思能力。 +- **目标驱动**:用户表达意图,Agent自主解析并生成执行计划。 +- **Human‑in‑the‑loop**:高风险操作必须经过人工审批,确保安全可控。 +- **长期记忆**:Agent具备向量化记忆能力,可检索历史成功案例,持续优化决策。 +- **知识本地化**:所有知识库文档及向量索引存储于本地,数据不出内网。 +- **可观测性**:Agent的思考链、工具调用、执行结果全程可视可审计。 +- **前后端分离**:前端React SPA,后端Spring Boot RESTful API。 +- **安全优先**:全链路安全防护,最小权限原则,全量操作审计。 +- **Prompt工程驱动**:通过结构化Prompt模板体系实现领域专业化,所有AI输出强制结构化,禁止自由发挥。 +- **AI算力灵活化**:支持本地Ollama与云端DeepSeek无缝切换,兼顾隐私、成本与性能。 + + +## 2. 总体架构设计 + +系统采用 B/S 架构,前端为 React SPA,后端为 Spring Boot 微服务,Agent决策引擎作为核心编排层,知识库与向量库全部本地化部署。AI算力层采用 **Ollama(本地)主 + DeepSeek API(外接)备** 的混合模式。 + +### 2.1 架构分层 + +| 层级 | 说明 | 主要技术组件 | +| :--- | :--- | :--- | +| **客户端层** | 用户浏览器 | Chrome / Edge / Firefox | +| **接入层** | 反向代理、HTTPS终结、限流 | Nginx 1.27.x | +| **前端应用层** | React SPA | React ^19.0.0, TypeScript ^5.8.0, Vite ^7.0.0, Ant Design ^6.0.0, Redux Toolkit ^2.0.0, React Router ^6.30.0 | +| **Agent决策编排层** | Agent核心:规划器、执行器、观察器、记忆检索器、**Prompt模板引擎** | Spring AI (ChatModel/EmbeddingModel接口), 自定义ReAct框架, **Ollama/DeepSeek双引擎** | +| **后端业务服务层** | 业务逻辑与工具实现 | Java 17/21, Spring Boot ^3.5.0, Spring Security ^6.5.0, Spring Data JPA ^3.5.0 | +| **知识库处理层** | 文档解析、切片、向量化调用(通过EmbeddingModel)、本地存储、**Query改写引擎** | Apache Tika / PDFBox, HanLP 1.8.x | +| **异步任务层** | 长耗时任务(Agent规划、文档向量化、导入导出、**Prompt批量渲染**) | Spring Async + ThreadPoolTaskExecutor | +| **数据与向量层** | 关系数据、缓存、对象存储、**本地向量库**、**Prompt模板库** | PostgreSQL 16.14 + pgvector, Redis 7.4.9, MinIO(全部本地部署) | +| **AI算力层(混合)** | **主:Ollama(本地推理+嵌入),备:DeepSeek API(外接高性能)** | Ollama(llama3.1:70b ≥48GB VRAM / qwen2.5:32b ≥24GB VRAM / llama3.1:8b ≥16GB VRAM, nomic-embed-text), Deepseek API(deepseek-v4-pro, text-embedding-3-small) | +| **运维监控层** | 监控、日志、CI/CD | Prometheus 3.1.x, Grafana 11.3.x, ELK, Jenkins 2.440.x, Docker 27.x | + +### 2.2 Agent核心工作流程(ReAct循环 + Prompt工程层 + 混合AI引擎) + +```mermaid +graph TD + User[用户/系统触发] -->|自然语言目标| PromptRouter[Prompt路由器 意图识别+模板匹配] + PromptRouter -->|加载领域Prompt| DomainContext[领域上下文注入器 术语映射+规则约束] + DomainContext -->|注入上下文| MemoryRetriever[长期记忆检索器 pgvector] + MemoryRetriever -->|相似案例| Planner[ReAct 规划器 通过ChatModel接口调用AI引擎] + Planner -->|生成步骤序列| Executor[工具执行器] + Executor --> Tool1[assign_issue 带专用Prompt模板] + Executor --> Tool2[send_reminder 带专用Prompt模板] + Executor --> Tool3[search_knowledge Query改写+检索Prompt] + Executor --> Tool4[update_status 带专用Prompt模板] + Executor --> Tool5[create_comment 带专用Prompt模板] + Executor --> Tool6[request_human_approval 强制触发Prompt] + Tool1 & Tool2 & Tool3 & Tool4 & Tool5 -->|执行结果| OutputFormatter[输出结构化格式化器 JSON Schema约束] + OutputFormatter -->|格式化结果| Observer[观察器] + Observer -->|结果反馈| Planner + Observer -->|成功经验| MemoryUpdater[记忆更新器 含Prompt优化反馈] + Tool6 -->|挂起等待| HumanApprovalGate[人工审批闸门] + HumanApprovalGate -->|批准/拒绝| Planner + Planner -->|任务完成| ResultPresenter[结果展示 结构化输出渲染] + + subgraph AI引擎选择 + A[配置决定] -->|ai.provider=ollama| Ollama[本地Ollama服务] + A -->|ai.provider=deepseek| DeepSeek[外接DeepSeek API] + end + Planner -.->|调用ChatModel接口| AI引擎选择 + Executor -.->|向量化调用EmbeddingModel接口| AI引擎选择 +``` + + +## 3. Prompt工程体系设计(V6.0适配) + +### 3.1 设计目标 +1. **领域专业化**:Agent行为表现等同于经过指摘管理领域专项训练的模型。 +2. **输出可控性**:所有AI输出强制符合预定义JSON Schema,禁止自由发挥。 +3. **行为一致性**:相同输入在不同时间、不同场景下输出稳定一致。 +4. **可维护性**:Prompt模板独立管理、版本控制、热更新,无需重启服务。 +5. **可审计性**:每次Prompt渲染全过程持久化,支持回溯与优化。 +6. **跨模型兼容**:Prompt模板与渲染逻辑需同时支持Ollama和DeepSeek,保证输出一致性。 + +### 3.2 Prompt模板分层架构 + +``` +Prompt Template Engine +├── Layer 4: 动态上下文层 (Dynamic Context Layer) +│ - 当前用户角色/权限/部门 +│ - 当前指摘上下文 (issue_id, status, phase等) +│ - 实时数据注入 (用户列表、部门树、有效工程阶段) +├── Layer 3: 领域知识层 (Domain Knowledge Layer) +│ - 指摘管理术语词典 (术语映射表) +│ - 业务规则约束 (审批规则、状态流转规则) +│ - 最佳实践库 (历史成功案例模式) +├── Layer 2: 任务专用层 (Task-Specific Layer) +│ - 系统角色Prompt (System Role) +│ - 工具专用Prompt (Tool Prompts) +│ - 分析专用Prompt (Analysis Prompts) +│ - 校验专用Prompt (Validation Prompts) +│ - 记忆检索Prompt (Memory Retrieval Prompts) +└── Layer 1: 基础约束层 (Base Constraint Layer) + - 输出格式约束 (JSON Schema / Markdown模板) + - 安全策略约束 (禁止内容、敏感词过滤) + - 语言风格约束 (正式/简洁/结构化) +``` + +### 3.3 Prompt模板类型定义 + +| 模板类型 | 标识前缀 | 用途 | 更新频率 | 存储位置 | +| :--- | :--- | :--- | :--- | :--- | +| **系统角色模板** | `SYS_ROLE_` | 定义Agent身份、知识边界、行为准则 | 低频(季度) | 数据库 + 本地文件 | +| **工具调用模板** | `TOOL_` | 每个业务工具的专用Prompt,含参数生成规则 | 中频(月度) | 数据库 | +| **ReAct规划模板** | `PLAN_` | 指导思考-行动-观察循环的结构化Prompt | 中频(月度) | 数据库 | +| **Query改写模板** | `QUERY_` | 将用户自然语言改写为领域检索查询 | 中频(月度) | 数据库 | +| **AI分析模板** | `ANALYSIS_` | 指摘深度分析的专用Prompt(根因/建议/分类) | 高频(周度) | 数据库 | +| **批量校验模板** | `VALIDATE_` | Excel导入数据智能校验的专用Prompt | 中频(月度) | 数据库 | +| **记忆重排序模板** | `MEMORY_` | 历史案例相关性评估与重排序Prompt | 低频(季度) | 数据库 | +| **输出格式化模板** | `FORMAT_` | 各类输出的JSON Schema/Markdown模板定义 | 中频(月度) | 数据库 | +| **快捷指令模板** | `QUICK_` | 工作台快捷指令的Prompt模板 | 高频(周度) | 数据库 + Redis缓存 | + +### 3.4 核心Prompt模板详细定义 + +#### 3.4.1 系统角色Prompt模板(`SYS_ROLE_001`) + +```markdown +# 角色定义 +你是「指摘管理专家Agent」,专精于制造业与软件工程领域的质量问题跟踪与整改管理。 +你的知识边界严格限定于以下范围: +- 指摘全生命周期管理(创建 → 分配 → 整改 → 验证 → 关闭) +- 工程质量管理标准(ISO 9001、CMMI、企业内部QA规范) +- 历史成功案例与失败教训(来自本地知识库检索) +- 项目工程阶段管理(需求/设计/编码/测试/部署/运维) + +# 身份标识 +- 系统名称:指摘管理系统(Issue Tracking System) +- 你的名称:指摘助手(Issue Assistant) +- 当前版本:V6.0 + +# 行为约束(强制遵守,违反将导致操作被拒绝) +1. 【术语强制】所有输入输出必须使用标准指摘管理术语: + - 使用"对应者"而非"负责人"、"处理人" + - 使用"PGM"指代项目编号,格式为 PGM-XXXX + - 使用"指摘"而非"问题"、"缺陷"、"bug" + - 使用"对应内容"而非"解决方案"、"修复方案" + - 使用"确认者"而非"审核人"、"验收人" + - 使用"对应完了日"而非"完成日期" + - 使用"review工数"和"对应工数"计量工作量 + - 状态术语:draft(草稿)/ open(待处理)/ in_progress(进行中)/ resolved(已解决)/ verified(已验证)/ closed(已关闭)/ rejected(已驳回) + +2. 【审批强制】涉及以下高风险操作必须调用 request_human_approval 工具: + - 关闭指摘(close_issue) + - 删除指摘(delete_issue) + - 跨部门分配指摘(assignee部门 ≠ 当前指摘部门) + - 修改已验证状态的指摘 + - 批量操作超过10条指摘 + - 任何涉及数据删除或不可逆变更的操作 + +3. 【知识优先】生成整改建议时,必须遵循以下优先级: + - 第一优先:引用本地知识库中的历史成功案例(需标注案例ID) + - 第二优先:引用企业内部QA规范 + - 第三优先:基于通用工程管理最佳实践 + - 禁止:提供与指摘管理无关的建议(如财务、人事、市场等) + +4. 【上下文依赖】当用户意图模糊时,必须优先询问以下信息以明确上下文: + - 指摘ID(issue_id) + - 工程阶段(phase) + - 归属部门(department_id) + - 当前状态(status) + 禁止在上下文不明的情况下执行操作。 + +5. 【输出结构化】所有输出必须严格符合预定义JSON Schema或Markdown模板,禁止自由发挥。 + - 分析类输出 → JSON格式 + - 对话类输出 → Markdown格式,含结构化标题 + - 工具调用 → 严格参数格式 + +6. 【安全边界】禁止执行以下行为: + - 生成、传播或协助创建恶意代码 + - 泄露其他用户的敏感信息(密码、个人联系方式等) + - 执行超出指摘管理范畴的系统操作 + - 伪造或篡改审计日志 + +# 当前环境信息 +- 当前用户:{{currentUserName}}({{currentUserRole}}) +- 所属部门:{{currentUserDepartment}} +- Agent自动执行权限:{{agentAutoExecuteEnabled}} +- 可用工具列表(格式:工具名 - 描述): +{{#each availableTools}} + - {{this.name}}:{{this.description}} +{{/each}} +- 当前时间:{{currentTime}} + +# 工具调用规则(适用于当前AI引擎:{{modelProvider}}) +- 当你需要调用工具时,请在你的回复中输出一个 JSON 对象,格式为: +{ + "tool": "工具名", + "parameters": { ... } +} +- 不要用自然语言额外解释,直接输出该 JSON。 +- 如果目标已完成,输出 { "tool": "goal_completed", "parameters": {} }。 +``` + +#### 3.4.2 ReAct规划Prompt模板(`PLAN_001`) + +```markdown +# 任务 +基于用户目标和当前上下文,生成结构化的执行计划(ReAct循环)。 + +# 输入 +用户目标:{{userGoal}} +当前指摘上下文:{{issueContext}} +历史相似案例:{{similarCases}} +可用工具列表:{{availableTools}} +已执行步骤:{{executedSteps}} +当前步数:{{currentStep}} / 最大步数:{{maxSteps}} + +# 思考格式(必须严格遵循) +你必须按以下结构输出思考过程,**且最终必须输出一个 JSON 对象**,其中 `action` 字段包含下一步的工具调用。 + +[思考] +1. 用户意图解析:将自然语言目标转化为明确的业务操作 +2. 上下文评估:当前已掌握的信息是否足够执行 +3. 工具选择:从可用工具中选择最合适的工具(仅限1个) +4. 参数生成:为选定工具生成所需参数 +5. 风险评估:判断是否需要人工审批 +6. 输出格式:确认输出符合JSON Schema + +[行动] +```json +{ + "action": { + "tool_name": "工具名称(必须从可用工具列表中选择)", + "parameters": { + "param1": "值1", + "param2": "值2" + }, + "reasoning": "选择此工具的理由(一句话)", + "requires_approval": true/false, + "approval_reason": "如需要审批,写明原因" + } +} +``` + +# 约束 +- 每步只能调用一个工具。 +- 如需审批,必须调用 request_human_approval 工具,不得直接执行目标操作。 +- 参数中的ID类字段必须为数字,禁止传递字符串ID。 +- 如用户目标已完成,输出 tool_name 为 "goal_completed"。 +- 如达到最大步数仍未完成,输出 tool_name 为 "max_steps_reached" 并说明原因。 +``` + +#### 3.4.3 工具调用Prompt模板(以 `TOOL_ASSIGN_ISSUE` 为例) + +```markdown +# 工具:assign_issue(将指摘分配给指定负责人) + +## 适用场景 +- 新建指摘后的首次分配 +- 责任人变更(原负责人离职、调岗、负载过高) +- Agent自动分配(基于负载均衡算法) + +## 输入参数 +- issue_id: 指摘ID(数字) +- assignee_id: 目标对应者ID(数字) + +## 执行前检查清单(必须全部确认) +1. 目标指摘是否存在且状态允许分配(status ∈ [draft, open, in_progress]) +2. 目标对应者是否存在于用户表中且状态为启用 +3. 目标对应者所属部门是否与指摘归属部门一致 +4. 如不一致(跨部门分配),必须调用 request_human_approval +5. 目标对应者当前进行中指摘数是否 < 5(负载检查) + +## 输出格式 +```json +{ + "tool_call": { + "name": "assign_issue", + "parameters": { + "issue_id": {{issueId}}, + "assignee_id": {{assigneeId}} + } + }, + "pre_check": { + "issue_exists": true/false, + "assignee_exists": true/false, + "same_department": true/false, + "workload_ok": true/false, + "requires_approval": true/false, + "approval_reason": "如需要,说明原因" + }, + "execution_confidence": "high/medium/low", + "reasoning": "分配决策的详细理由" +} +``` + +## 历史案例参考 +{{similarAssignmentCases}} +``` + +其他工具模板(`TOOL_SEND_REMINDER`、`TOOL_SEARCH_KNOWLEDGE`、`TOOL_REQUEST_APPROVAL`)内容与V4.2完全相同,不再重复。 + +#### 3.4.4 Query改写Prompt模板(`QUERY_REWRITE_001`) + +(与V4.2完全一致,内容不变) + +#### 3.4.5 AI分析Prompt模板(`ANALYSIS_ROOT_CAUSE_001`) + +(与V4.2完全一致,内容不变) + +#### 3.4.6 批量校验Prompt模板(`VALIDATE_BATCH_001`) + +(与V4.2完全一致,内容不变) + +#### 3.4.7 记忆重排序Prompt模板(`MEMORY_RERANK_001`) + +(与V4.2完全一致,内容不变) + +#### 3.4.8 输出格式化模板(`FORMAT_JSON_001`) + +(与V4.2完全一致,内容不变) + +### 3.5 快捷指令Prompt模板(`QUICK_`系列) + +| 模板ID | 标签 | 描述 | 审批要求 | 目标工具 | +| :--- | :--- | :--- | :--- | :--- | +| `QUICK_REMINDER_OVERDUE_001` | 催办逾期指摘 | 自动查找并催办所有逾期指摘 | 否 | search_issues, send_reminder | +| `QUICK_WEEKLY_REPORT_001` | 生成本周报告 | 生成本周指摘处理统计报告 | 否 | search_issues, generate_report | +| `QUICK_AUTO_ASSIGN_001` | 分配待处理指摘 | 自动将草稿状态指摘分配给合适人员 | 是 | search_issues, assign_issue, request_human_approval | +| `QUICK_KNOWLEDGE_SEARCH_001` | 检索知识库 | 在本地知识库中检索相关信息 | 否 | search_knowledge | + +### 3.6 Prompt版本管理与热更新机制 + +#### 3.6.1 版本控制策略 + +| 版本号格式 | 示例 | 含义 | 升级策略 | +| :--- | :--- | :--- | :--- | +| `MAJOR`变更 | `001` → `002` | 架构级变更,不兼容旧格式 | 需回归测试,逐步灰度发布 | +| `MINOR`变更 | 模板内容优化 | 功能增强,兼容旧格式 | 可直接热更新,记录A/B测试数据 | +| `PATCH`变更 | 错别字修正 | 无功能影响 | 即时热更新 | + +#### 3.6.2 热更新流程 +1. 管理员提交新模板版本到数据库 +2. 数据库进行版本校验和语法检查 +3. 发布到Redis缓存(带生效时间) +4. Prompt引擎每30秒轮询检查更新 +5. 如版本变更,清空本地缓存并加载新模板 +6. 后续请求使用最新模板 + +#### 3.6.3 数据库表设计 +(同V4.2,含 `prompt_templates`, `prompt_template_versions`, `prompt_render_logs`) + +### 3.7 跨模型输出一致性策略 + +**背景**:Ollama(ChatML格式)与DeepSeek(OpenAI兼容格式)的Prompt渲染结果和模型行为可能存在差异,影响Agent决策稳定性。 + +**应对策略**: +1. **格式适配**(由 `PromptFormatter` 实现):统一通过格式适配器将同一套模板内容转换为各模型所需的消息格式。 +2. **行为调参**:必要时对两个模型分别调整 `temperature`、`top_p` 等参数,确保输出质量接近。建议在测试环境中建立双模型评测流水线,定期对比输出一致性。 +3. **A/B测试机制**:利用模板管理后台的A/B测试功能,收集两个模型在实际任务中的效果数据,指导调优。 + +### 3.8 Prompt渲染格式适配器(V6.0新增) + +```java +@Component +public class PromptFormatter { + + @Value("${ai.provider:ollama}") + private String provider; + + public String format(String systemPrompt, String userPrompt) { + if ("ollama".equalsIgnoreCase(provider)) { + // ChatML 格式 + return "<|im_start|>system\n" + systemPrompt + "\n<|im_end|>\n" + + "<|im_start|>user\n" + userPrompt + "\n<|im_end|>\n" + + "<|im_start|>assistant\n"; + } else if ("deepseek".equalsIgnoreCase(provider)) { + // DeepSeek 原始格式(系统角色+用户指令) + return systemPrompt + "\n\n---\n\n" + userPrompt; + } + // 默认(通用) + return systemPrompt + "\n\n" + userPrompt; + } +} +``` + +### 3.8 Prompt引擎核心实现(V6.0调整) + +```java +@Service +public class PromptTemplateEngine { + + @Autowired + private PromptTemplateRepository templateRepo; + @Autowired + private StringRedisTemplate redisTemplate; + @Autowired + private PromptFormatter formatter; + + private final ConcurrentHashMap localCache = new ConcurrentHashMap<>(); + @Value("${ai.provider:ollama}") + private String provider; + + public String render(String templateId, Map variables) { + // 1. 加载模板 + PromptTemplate template = loadTemplate(templateId); + // 2. 变量校验 + validateVariables(template, variables); + // 3. 自动注入modelProvider + variables.putIfAbsent("modelProvider", provider); + // 4. 渲染模板(使用Mustache) + String rendered = Mustache.compiler().compile(template.getContent()).execute(variables); + // 5. 注入系统角色Prompt(如非系统角色模板) + if (!templateId.startsWith("SYS_ROLE_")) { + String systemPrompt = render("SYS_ROLE_001", variables); + String formatConstraint = loadTemplate("FORMAT_JSON_001").getContent(); + String fullPrompt = systemPrompt + "\n\n---\n\n" + rendered + "\n\n---\n\n" + formatConstraint; + // 6. 应用格式适配 + return formatter.format(fullPrompt, ""); + } else { + return formatter.format(rendered, ""); + } + } + + @Scheduled(fixedRate = 30000) + public void checkForUpdates() { + List activeTemplates = templateRepo.findAllActive(); + for (PromptTemplate template : activeTemplates) { + String cacheKey = template.getTemplateId(); + PromptTemplate cached = localCache.get(cacheKey); + if (cached == null || cached.getVersion() < template.getVersion()) { + localCache.put(cacheKey, template); + } + } + } +} +``` + + +## 4. 功能模块设计 + +### 4.1 系统管理模块 + +#### 4.1.1 用户管理(`/system/users`) +- **组织架构树**:以树形结构展示部门层级,点击节点可筛选该部门下的用户列表。 +- **用户列表**:展示账号、姓名、所属部门、绑定角色、状态(启用/禁用)、**Agent自动执行权限**,支持按姓名/账号搜索。 +- **新增/编辑用户**:弹窗形式,可设置账号、姓名、所属部门、绑定角色(多选)、启用状态开关,以及**Agent自动执行高风险操作**开关(仅超级管理员可见)。 +- **启用/禁用用户**:在表格行内操作,切换用户状态。 +- **导出用户列表**:将当前筛选结果导出为 Excel 文件。 + +**权限要求**: +- 查看列表及搜索:所有已登录用户。 +- 新增、编辑、启用/禁用、导出:仅超级管理员及部门管理员(部门管理员仅可管理本部门及其下级部门用户)。 +- 部门树查看:所有已登录用户。 + +#### 4.1.2 角色权限管理(`/system/roles`) +- **角色列表**:左侧展示所有预设角色(超级管理员、部门管理员、指摘录入员、整改担当、验证人员、只读用户),支持新建、编辑、删除角色。 +- **权限配置面板**:右侧展示选中角色的权限配置,包括: + - **功能权限**:菜单权限(工作台、指摘列表、批量录入、AI智能分析管理、用户管理、角色权限、系统日志、Agent管理)和按钮权限,以复选框形式呈现。 + - **数据权限**:下拉选择数据范围(仅本人、本部门、全部门)。 + - **Agent工具权限**:以复选框列出所有可用工具(`assign_issue`, `send_reminder`, `close_issue`, `delete_issue`, `search_knowledge`, `create_comment`, `request_human_approval`, `export_excel`等),每个工具可独立授予或收回。 + - **Prompt模板权限**:可查看/可编辑的Prompt模板分类列表。 +- **保存权限配置**:将当前角色的权限设置持久化。 + +**权限要求**:所有操作仅限超级管理员。 + +#### 4.1.3 预设角色权限矩阵 + +| 角色 | 功能权限摘要 | 数据权限范围 | Agent可用工具 | Agent自动执行 | Prompt模板权限 | +| :--- | :--- | :--- | :--- | :--- | :--- | +| **超级管理员** | 全部功能 | 全部数据 | 全部工具 | 是(可配置) | 全部模板可编辑 | +| **部门管理员** | 除"角色权限管理"外的所有菜单 | 本部门及下级部门 | 除`delete_issue`外的全部工具 | 否(需审批) | 可查看全部,可编辑工具类模板 | +| **指摘录入员** | 工作台、指摘列表、批量录入 | 自己创建的指摘 | `send_reminder`, `search_knowledge`, `create_comment` | 否 | 仅查看 | +| **整改担当** | 工作台、指摘列表、被分配指摘详情 | 分配给自己或协办的指摘 | `send_reminder`, `search_knowledge`, `request_human_approval` | 否 | 仅查看 | +| **验证人员** | 工作台、指摘列表、被指定为验证人的指摘详情 | 被指定为验证人的指摘 | `send_reminder`, `search_knowledge` | 否 | 仅查看 | +| **只读用户** | 工作台、指摘列表(仅查看) | 本部门 | `search_knowledge`(仅检索) | 否 | 仅查看 | + +#### 4.1.4 系统日志(`/system/logs`) +- **Tab切换**:操作日志 / 系统错误日志 / **Prompt渲染日志**。 +- **操作日志**:记录所有用户及Agent的操作行为,支持按操作人、时间、操作类型(含"Agent操作")、资源类型筛选,可导出筛选结果。 +- **系统错误日志**:记录系统运行异常,支持按级别、时间、模块、关键字搜索,可标记已读/已解决。 +- **Prompt渲染日志**:记录每次Prompt渲染的完整信息(模板ID、版本、变量、输入token、输出token、耗时),支持按模板、时间、操作人筛选,可导出审计报告。 + +**权限要求**:超级管理员、部门管理员。 + +### 4.2 指摘全生命周期管理模块 + +#### 4.2.1 工作台(`/dashboard`) +- **统计卡片**:展示待处理、进行中、本月已完成、今日新增的数量及环比变化,每张卡片底部显示Agent生成的动态建议。 +- **Agent快捷指令输入框**:用户可在此输入自然语言指令(如"新建指摘 '传感器故障' 分配给张三"),Agent解析后执行相应操作;下方提供快捷指令标签(催办逾期指摘、生成本周报告、分配待处理指摘、检索知识库)。 +- **快捷操作**:"新建指摘"和"批量导入"按钮。 +- **Agent洞察面板**:展示Agent自动生成的三类洞察——高风险警报、自动对应建议、任务提醒。 +- **最新动态**:展示系统内最近的用户操作和Agent自动操作记录。 +- **指摘处理趋势图**:折线图展示新增指摘与已解决指摘的日趋势。 +- **指摘状态分布图**:环形图展示各状态指摘的数量占比。 +- **通知铃铛**:顶部栏显示未读消息数,点击展示消息列表(含Agent发起的审批请求通知)。 + +**权限要求**:所有已登录用户。 + +#### 4.2.2 指摘列表(`/issues`) +- **筛选栏**:支持下拉选择状态、工程阶段、整改负责人、归属部门,以及日期范围筛选。 +- **操作按钮**:新建指摘、跳转批量录入、导出Excel、**Agent批量处理**(选中多条指摘后统一分配或催办)。 +- **表格**:展示指摘 ID、标题、状态、优先级、工程阶段、子工程、对应者、截止日期、**Agent建议**,每行提供"查看详情"链接。 + +**权限要求**:所有已登录用户(数据权限过滤);Agent批量处理仅部门管理员及超级管理员。 + +#### 4.2.3 指摘详情(`/issues/:id`)—— Agent驾驶舱集成 +- **布局**:页面分为左(基础信息+附件)右(Agent驾驶舱)两栏。 +- **左侧基础信息区域**:展示创建人、部门、review者、对应者、确认者、指摘日、关联PGM、影响度、影响工程、部署、区分、review工数、对应内容、NG原因等(只读)。 +- **左侧附件列表**:可上传新附件,支持预览、下载、删除。 +- **右侧 Agent 驾驶舱**: + - **对话式交互**:用户输入自然语言指令(如"催办此指摘并询问是否需要技术支持")。 + - **思考链展示**:实时流式显示 Agent 的 `[思考]`、`[行动]`(含工具名和参数)、`[观察]`(执行结果)卡片。 + - **Prompt模板展示**:可展开查看当前Agent使用的Prompt模板ID、版本、关键变量(脱敏)。 + - **AI引擎信息展示**:显示当前使用的模型提供商(Ollama/DeepSeek)及模型名称。 + - **审批请求面板**:当Agent调用`request_human_approval`时,自动弹出审批按钮(批准/拒绝)。 + - **转人工按钮**:切换至纯人工模式。 + - **时间线切换**:可展开/折叠传统状态流转时间线(仅作审计参考)。 + +**权限要求**:所有已登录用户(须在数据权限范围内);上传附件需具备编辑权限。 + +#### 4.2.4 新建指摘(`/issues/new`) +- **表单字段**:标题、工程阶段、子工程、区分、指摘内容、指摘日、关联PGM、影响度、影响工程、部署、review者、review工数、对应者、对应工数、对应内容、NG原因、对应完了日、确认者、确认日(标*为必填)。 +- **右侧快速设置**:状态、优先级、归属部门、创建人(只读)、整改截止日期。 +- **附件上传**:支持点击或拖拽上传,可上传多个文件。 +- **操作按钮**:清空重置、保存指摘。 +- **Agent辅助**:页面右侧提供"Agent辅助"按钮,可让Agent根据标题自动填充部分字段(基于`ANALYSIS_`系列Prompt模板)。 + +**权限要求**:指摘录入员、部门管理员、超级管理员。 + +#### 4.2.5 编辑指摘(`/issues/:id/edit`) +- **页面复用**:与新建指摘页面(`/issues/new`)共用同一套表单组件,通过路由参数区分模式。 +- 预填充现有数据,部分字段(如创建人、创建日期)只读。 +- 支持附件新增、预览、下载、删除。 +- 操作按钮:恢复初始、保存修改、删除指摘、查看修改历史。 +- 提供"Agent建议修改"按钮(基于当前指摘上下文调用`ANALYSIS_`系列Prompt)。 + +**权限要求**:拥有编辑权限的用户。 + +#### 4.2.6 批量录入(`/batch-input`) +- **上传区域**:支持点击或拖拽上传 Excel 文件(.xlsx, .xls),并提供标准导入模板下载。 +- **Agent智能校验**:上传后,Agent自动扫描错误(空字段、格式错误),并给出修正建议,提供"应用Agent修正"按钮(基于`VALIDATE_BATCH_001` Prompt模板)。 +- **预览与校验面板**:显示上传文件的数据预览,标出校验错误行。 +- **确认导入**:将校验通过的数据批量写入数据库。 +- **历史导入记录**:展示以往的导入记录(时间、操作人、总条数、成功/失败、可下载错误日志)。 + +**权限要求**:指摘录入员、部门管理员、超级管理员。 + +### 4.3 通知与消息模块 + +| 触发场景 | 通知方式 | 实现策略 | +| :--- | :--- | :--- | +| Agent发起的审批请求 | 站内消息 + 邮件 | 创建通知记录,异步发送邮件 | +| 新指摘分配 | 站内消息 + 邮件 | Spring Async异步任务发送 | +| 指摘被驳回 | 站内消息 + 邮件 | Spring Async异步任务发送 | +| 整改即将超时 | 站内消息 + 邮件 | Spring @Scheduled定时任务扫描 | +| 指摘关闭 | 站内消息 | 写入消息记录 | +| Prompt模板更新 | 站内消息(仅管理员) | 模板热更新后通知相关管理员 | +| **AI引擎切换** | **站内消息(管理员)** | **配置变更后通知** | + +### 4.4 AI智能分析管理(`/ai-analysis`) + +此模块作为独立页面存在,用于人工触发指摘的深度AI分析,与Agent并行存在。 + +- **筛选栏**:按指摘ID、归属部门、日期范围、分析状态筛选。 +- **操作按钮**:"生成AI分析"(弹窗多选指摘)、"导出AI分析记录"。 +- **列表展示**:指摘ID、提取关键词、问题分类、根因分析、AI整改建议、用户反馈、操作(查看详情/重新生成)。 +- **生成AI分析弹窗**:支持多选指摘,含筛选条件(指摘状态、工程阶段、归属部门、搜索、创建时间),确认后对选中指摘批量生成AI分析(基于`ANALYSIS_ROOT_CAUSE_001` Prompt模板)。 +- **Prompt模板查看**:分析结果详情页可查看生成该分析使用的Prompt模板ID、版本、关键输入变量。 +- **AI引擎信息**:显示生成分析时使用的模型提供商及模型名称。 + +**权限要求**:查看列表所有已登录用户;生成分析及导出仅部门管理员、超级管理员。 + +### 4.5 Agent管理(`/system/agent-admin`) + +专门管理Agent全局配置、审批、工具、记忆和审计的后台模块。 + +- **运行状态统计**:总任务数、待审批数、执行成功率、记忆条目数、**Prompt模板总数/活跃数**、**当前AI引擎状态**。 +- **全局配置**: + - **AI模型配置(V6.0新增)**: + - **AI提供商选择**:下拉选择 `ollama` 或 `deepseek`,切换后立即生效。 + - **Ollama参数**:服务地址、推理模型、嵌入模型、温度、最大输出tokens。 + - **DeepSeek参数**:API Key(加密存储)、推理模型、嵌入模型。 + - **自动降级开关**:当主引擎不可用时,是否自动切换至备用引擎。 + - **Agent行为配置**:最大推理步数(默认10,范围5-20)、是否允许自动执行高风险操作(仅超级管理员可开启)、单用户Agent调用限流(默认每分钟10次)。 + - **知识库切片配置**:切片大小(Token,默认500)、重叠比例(%,默认10)、最大上传大小(MB,默认50)。 + - **Prompt工程配置**:系统角色Prompt版本选择、输出格式化强制开关、Query改写引擎开关、Prompt渲染日志保留天数(默认30天)。 +- **待审批队列**:Agent发起的需人工决策的操作列表,提供"批准"和"拒绝"按钮。 +- **工具列表**:展示所有已注册工具及其状态("启用"或"需审批"),支持启用/禁用。 +- **执行记录**:分页展示所有Agent任务(时间、指摘、工具、状态、操作人)。 +- **记忆库管理**:列表展示所有向量记忆条目(问题摘要、解决方案步骤、有效性评分),支持新增、编辑、删除、反馈有效性。 +- **Prompt模板管理**: + - 模板列表:按分类展示所有Prompt模板,显示模板ID、名称、版本、状态、最近更新时间。 + - 模板编辑:在线编辑模板内容,支持变量自动补全、实时预览。 + - 版本历史:查看模板的所有历史版本,支持回滚到任意版本。 + - A/B测试:对同一模板的不同版本配置流量分配比例,收集效果数据。 + - 渲染测试:输入测试变量,查看渲染后的完整Prompt。 + - 批量发布:选择多个模板统一发布新版本。 + +**权限要求**:超级管理员、部门管理员(部门管理员仅可编辑工具类和分析类模板)。 + +### 4.6 本地知识库管理(`/knowledge-base`) + +#### 4.6.1 知识库文档管理 +- **文档上传**:支持拖拽或点击上传 PDF、Word(.docx)、TXT、Markdown 格式文件,单文件限制 50MB。 +- **文档列表**:展示文件名、上传人、上传时间、处理状态(待处理/处理中/已完成/失败)、分块数量、操作按钮。 +- **解析与向量化流程**: + 1. 上传后,后端调用本地解析库(Apache Tika / PDFBox)提取纯文本。 + 2. 按 **500 Token** 大小切分(含 10% 重叠)。 + 3. 调用当前选中的 **EmbeddingModel**(Ollama 或 DeepSeek)将每个分块转为向量。 + 4. 向量及原文存入本地 PostgreSQL(pgvector),源文件存入本地 MinIO。 + 5. **全程无任何数据上传至云端存储(若使用Ollama则完全不外传,若使用DeepSeek仅API调用传输文本片段)。** +- **重新向量化**:支持对已上传文档重新生成向量(用于嵌入模型升级时)。 +- **删除文档**:同时删除 MinIO 中的源文件和 pgvector 中的向量数据。 +- **批量操作**:支持多选文档进行批量删除或重新向量化。 + +#### 4.6.2 Agent 知识库检索工具 +- Agent 内置 `search_knowledge(query: string, top_k: int)` 工具。 +- 调用流程(V6.0增强): + 1. 用户原始问题通过 **Query改写引擎**(基于`QUERY_REWRITE_001` Prompt模板)转换为领域专业查询。 + 2. 改写后的查询通过当前EmbeddingModel向量化。 + 3. 在本地 pgvector 中执行余弦相似度检索。 + 4. 检索结果通过 **记忆重排序引擎**(基于`MEMORY_RERANK_001` Prompt模板)进行相关性重排序。 + 5. 返回 Top K 相关原文片段给 Agent。 +- 用途:辅助 Agent 进行根因分析、整改建议生成、历史案例参考。 +- **权限控制**:仅授予"超级管理员"、"部门管理员"、"指摘录入员"、"整改担当"角色的 Agent 可调用此工具。 + +#### 4.6.3 知识库检索审计 +- **检索日志**:记录每次 Agent 或人工检索的关键词、**改写后查询**、返回片段数、耗时、关联的指摘ID。 +- **统计看板**:展示"热门检索词 Top 50"、"低效文档"(从未被检索到的文档)、"文档命中率排行"、**"Query改写成功率"**。 +- **日志导出**:支持按时间范围导出检索审计日志(Excel)。 + +**权限要求**: +- 上传、删除、重新向量化文档:仅超级管理员、部门管理员。 +- 查看文档列表及检索审计:超级管理员、部门管理员。 +- Agent 调用检索工具:受角色工具权限控制(默认除只读用户外均可)。 + + +## 5. 数据库架构与设计 + +### 5.1 ER图 + +```mermaid +erDiagram + departments ||--o{ users : "包含" + users ||--o{ issues : "创建/负责/验证" + issues ||--o{ attachments : "拥有" + issues ||--o{ issue_logs : "产生" + issues ||--o{ ai_analysis : "关联" + ai_analysis ||--o{ ai_feedback : "收集反馈" + users ||--o{ user_roles : "拥有" + roles ||--o{ user_roles : "关联" + roles ||--o{ role_permissions : "分配" + permissions ||--o{ role_permissions : "被包含" + issues ||--o{ agent_plans : "触发" + agent_plans ||--o{ tool_executions : "包含" + users ||--o{ agent_plans : "创建" + agent_memories ||--o{ issues : "参考" + knowledge_documents ||--o{ knowledge_chunks : "包含" + users ||--o{ knowledge_documents : "上传" + knowledge_search_logs ||--o{ users : "关联" + knowledge_search_logs ||--o{ issues : "关联" + prompt_templates ||--o{ prompt_template_versions : "版本历史" + prompt_templates ||--o{ prompt_render_logs : "渲染记录" + agent_plans ||--o{ prompt_render_logs : "关联" +``` + +### 5.2 核心表结构 + +#### 5.2.1 基础表 + +**`departments`(部门表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | ID | +| name | VARCHAR(100) | NOT NULL | 部门名称 | +| parent_id | BIGINT | FOREIGN KEY | 上级部门ID | +| sort_order | INT | DEFAULT 0 | 排序权重 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 更新时间 | + +**`users`(用户表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | ID | +| userid | VARCHAR(50) | NOT NULL, UNIQUE | 登录账号 | +| username | VARCHAR(50) | NOT NULL, UNIQUE | 用户姓名 | +| email | VARCHAR(100) | | 邮箱 | +| password_hash | VARCHAR(255) | NOT NULL | bcrypt加密密码 | +| department_id | BIGINT | FOREIGN KEY, NOT NULL | 所属部门 | +| is_active | BOOLEAN | DEFAULT TRUE | 是否启用 | +| agent_auto_execute | BOOLEAN | DEFAULT FALSE | 是否允许Agent自动执行高风险操作 | +| last_login_at | TIMESTAMP | | 最后登录时间 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 更新时间 | + +**`roles`(角色表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | ID | +| name | VARCHAR(50) | NOT NULL, UNIQUE | 角色名称 | +| description | VARCHAR(255) | | 角色描述 | +| agent_auto_execute | BOOLEAN | DEFAULT FALSE | 是否允许该角色Agent自动执行高风险操作 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 | + +**`permissions`(权限表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | ID | +| code | VARCHAR(100) | NOT NULL, UNIQUE | 权限编码 | +| name | VARCHAR(100) | NOT NULL | 权限名称 | +| resource | VARCHAR(50) | NOT NULL | 所属资源 | +| description | VARCHAR(255) | | 权限描述 | + +**`user_roles`(用户-角色关联表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| user_id | BIGINT | FOREIGN KEY, PRIMARY KEY (复合) | 用户ID | +| role_id | BIGINT | FOREIGN KEY, PRIMARY KEY (复合) | 角色ID | + +**`role_permissions`(角色-权限关联表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| role_id | BIGINT | FOREIGN KEY, PRIMARY KEY (复合) | 角色ID | +| permission_id | BIGINT | FOREIGN KEY, PRIMARY KEY (复合) | 权限ID | + +#### 5.2.2 核心业务表 + +**`issues`(指摘主表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | 指摘ID | +| issue_no | VARCHAR(20) | NOT NULL, UNIQUE | 业务编号 | +| title | VARCHAR(200) | NOT NULL | 指摘标题 | +| description | TEXT | | 问题详细描述 | +| status | VARCHAR(30) | NOT NULL, DEFAULT 'draft' | 状态 | +| priority | VARCHAR(10) | NOT NULL, DEFAULT 'medium' | 优先级 | +| deadline | TIMESTAMP | | 整改截止时间 | +| phase | VARCHAR(50) | | 工程阶段 | +| sub_project | VARCHAR(100) | | 子工程 | +| category | VARCHAR(50) | | 区分 | +| impact_level | VARCHAR(10) | | 影响度 | +| impact_scope | VARCHAR(200) | | 影响工程/范围 | +| deployment | VARCHAR(100) | | 部署位置 | +| pgm_no | VARCHAR(50) | | 关联PGM编号 | +| review_workload | DECIMAL(5,1) | | review工数 | +| response_workload | DECIMAL(5,1) | | 对应工数 | +| response_content | TEXT | | 对应内容 | +| ng_reason | VARCHAR(200) | | NG原因 | +| response_completed_at | TIMESTAMP | | 对应完了日 | +| confirm_at | TIMESTAMP | | 确认日 | +| creator_id | BIGINT | FOREIGN KEY, NOT NULL | 创建人ID | +| assignee_id | BIGINT | FOREIGN KEY | 整改负责人ID | +| department_id | BIGINT | FOREIGN KEY, NOT NULL | 归属部门ID | +| reviewer_id | BIGINT | FOREIGN KEY | review者ID | +| validator_id | BIGINT | FOREIGN KEY | 验证人ID | +| ai_analysis_id | BIGINT | FOREIGN KEY | 最新AI分析结果ID | +| agent_last_plan_id | BIGINT | FOREIGN KEY | 关联最新Agent规划ID | +| agent_status | VARCHAR(20) | DEFAULT 'human_driven' | Agent驱动状态 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 更新时间 | +| closed_at | TIMESTAMP | | 关闭时间 | +| is_deleted | BOOLEAN | DEFAULT FALSE | 软删除标记 | + +**`issue_logs`(指摘操作日志表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | 日志ID | +| issue_id | BIGINT | FOREIGN KEY, NOT NULL | 指摘ID | +| user_id | BIGINT | FOREIGN KEY, NOT NULL | 操作人ID | +| action | VARCHAR(30) | NOT NULL | 操作类型 | +| from_status | VARCHAR(30) | | 原状态 | +| to_status | VARCHAR(30) | | 目标状态 | +| remark | VARCHAR(500) | | 备注 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 操作时间 | + +**`attachments`(附件表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | 附件ID | +| issue_id | BIGINT | FOREIGN KEY, NOT NULL | 关联指摘ID | +| file_name | VARCHAR(200) | NOT NULL | 原始文件名 | +| file_path | VARCHAR(500) | NOT NULL | 存储路径 | +| file_size | BIGINT | NOT NULL | 文件大小 | +| mime_type | VARCHAR(100) | | MIME类型 | +| uploaded_by | BIGINT | FOREIGN KEY, NOT NULL | 上传人ID | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 上传时间 | + +#### 5.2.3 AI与任务表 + +**`ai_analysis`(AI分析结果表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | 分析ID | +| issue_id | BIGINT | FOREIGN KEY, NOT NULL | 关联指摘ID | +| category | VARCHAR(100) | | AI分类结果 | +| keywords | VARCHAR(500) | | 提取关键词 | +| root_cause | VARCHAR(1000) | | 根因分析 | +| suggestion | TEXT | | 整改建议 | +| status | VARCHAR(20) | DEFAULT 'pending' | 状态 | +| helpful_count | INT | DEFAULT 0 | 有帮助计数 | +| prompt_template_id | VARCHAR(100) | | 使用的Prompt模板ID | +| prompt_version | INT | | 使用的Prompt版本 | +| **model_provider** | **VARCHAR(20)** | | **【V6.0新增】使用的AI提供商** | +| **model_name** | **VARCHAR(50)** | | **【V6.0新增】使用的模型名称** | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 | + +**`ai_feedback`(AI反馈表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | 反馈ID | +| ai_analysis_id | BIGINT | FOREIGN KEY, NOT NULL | AI分析ID | +| user_id | BIGINT | FOREIGN KEY, NOT NULL | 反馈人ID | +| is_helpful | BOOLEAN | NOT NULL | 是否有帮助 | +| comment | VARCHAR(500) | | 反馈备注 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 反馈时间 | + +**`task_executions`(异步任务执行表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | 任务ID | +| task_id | VARCHAR(64) | NOT NULL, UNIQUE | 业务任务唯一标识 | +| task_type | VARCHAR(30) | NOT NULL | 任务类型 | +| status | VARCHAR(20) | NOT NULL, DEFAULT 'pending' | 状态 | +| result_url | VARCHAR(500) | | 结果文件URL | +| error_message | TEXT | | 错误信息 | +| created_by | BIGINT | FOREIGN KEY, NOT NULL | 触发人ID | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| started_at | TIMESTAMP | | 开始执行时间 | +| completed_at | TIMESTAMP | | 完成时间 | +| retry_count | INT | DEFAULT 0 | 已重试次数 | + +#### 5.2.4 Agent相关表 + +**`agent_plans`(Agent规划表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | 规划ID | +| issue_id | BIGINT | FOREIGN KEY, NOT NULL | 关联指摘ID | +| goal | TEXT | NOT NULL | 用户目标 | +| plan_steps | JSONB | NOT NULL | 执行步骤数组 | +| status | VARCHAR(20) | DEFAULT 'pending' | 状态 | +| requires_approval | BOOLEAN | DEFAULT FALSE | 是否需人工审批 | +| approval_status | VARCHAR(20) | | 审批状态 | +| approval_comment | VARCHAR(500) | | 审批备注 | +| created_by | BIGINT | FOREIGN KEY | 发起人ID | +| **model_provider** | **VARCHAR(20)** | | **【V6.0新增】使用的AI提供商** | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| completed_at | TIMESTAMP | | 完成时间 | + +**`agent_memories`(Agent长期记忆表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | 记忆ID | +| issue_summary | TEXT | NOT NULL | 问题摘要 | +| solution_steps | JSONB | NOT NULL | 成功解决方案步骤 | +| effectiveness_score | DECIMAL(3,2) | DEFAULT 0.00 | 有效性评分 | +| embedding | VECTOR(1536) | | 向量化嵌入 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 更新时间 | + +**`tool_executions`(工具执行明细表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | 执行ID | +| plan_id | BIGINT | FOREIGN KEY | 关联规划ID | +| tool_name | VARCHAR(50) | NOT NULL | 工具名称 | +| input_params | JSONB | NOT NULL | 输入参数 | +| output_result | TEXT | | 执行结果 | +| status | VARCHAR(20) | DEFAULT 'success' | 执行状态 | +| execution_time_ms | BIGINT | | 执行耗时 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 执行时间 | + +**`notifications`(通知表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | 通知ID | +| user_id | BIGINT | FOREIGN KEY, NOT NULL | 接收人ID | +| title | VARCHAR(200) | NOT NULL | 通知标题 | +| content | VARCHAR(1000) | NOT NULL | 通知内容 | +| type | VARCHAR(30) | NOT NULL | 通知类型 | +| link | VARCHAR(500) | | 跳转链接 | +| is_read | BOOLEAN | DEFAULT FALSE | 是否已读 | +| issue_id | BIGINT | FOREIGN KEY | 关联指摘ID | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 | + +#### 5.2.5 知识库相关表(本地部署) + +**`knowledge_documents`(知识库文档主表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | 文档ID | +| name | VARCHAR(255) | NOT NULL | 原始文件名 | +| file_path | VARCHAR(500) | NOT NULL | MinIO存储路径 | +| file_size | BIGINT | NOT NULL | 文件大小(字节) | +| file_type | VARCHAR(20) | NOT NULL | 文件类型(pdf/docx/txt/md) | +| chunk_count | INT | DEFAULT 0 | 切片总数 | +| status | VARCHAR(20) | DEFAULT 'pending' | 状态(pending/processing/completed/failed) | +| error_message | TEXT | | 失败原因 | +| uploaded_by | BIGINT | FOREIGN KEY, NOT NULL | 上传人ID | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 上传时间 | +| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 更新时间 | + +**`knowledge_chunks`(知识库向量切片表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | 切片ID | +| doc_id | BIGINT | FOREIGN KEY, NOT NULL | 所属文档ID | +| content | TEXT | NOT NULL | 切片原文 | +| embedding | VECTOR(1536) | NOT NULL | 向量化嵌入(本地存储) | +| metadata | JSONB | | 附加元数据 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 | + +**`knowledge_search_logs`(知识库检索审计表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | 日志ID | +| user_id | BIGINT | FOREIGN KEY | 检索人ID | +| issue_id | BIGINT | FOREIGN KEY | 关联指摘ID | +| query | TEXT | NOT NULL | 检索关键词 | +| rewritten_query | TEXT | | 改写后的查询 | +| top_k | INT | DEFAULT 5 | 返回条数 | +| total_matches | INT | | 实际命中数 | +| duration_ms | INT | | 检索耗时(毫秒) | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 检索时间 | + +#### 5.2.6 Prompt模板相关表 + +**`prompt_templates`(Prompt模板主表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | 模板ID | +| template_id | VARCHAR(100) | NOT NULL, UNIQUE | 模板标识(如 SYS_ROLE_001) | +| name | VARCHAR(200) | NOT NULL | 模板名称 | +| category | VARCHAR(50) | NOT NULL | 分类(system/tool/analysis/validation/memory/format/quick) | +| version | INT | NOT NULL, DEFAULT 1 | 版本号 | +| content | TEXT | NOT NULL | 模板内容(支持{{变量}}占位符) | +| variables | JSONB | | 模板变量定义(名称、类型、是否必填、默认值) | +| output_schema | JSONB | | 输出JSON Schema定义 | +| is_active | BOOLEAN | DEFAULT TRUE | 是否启用 | +| is_default | BOOLEAN | DEFAULT FALSE | 是否为默认版本 | +| created_by | BIGINT | FOREIGN KEY | 创建人 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 更新时间 | + +**`prompt_template_versions`(Prompt模板版本历史表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | 版本记录ID | +| template_id | VARCHAR(100) | NOT NULL | 关联模板标识 | +| version | INT | NOT NULL | 版本号 | +| content | TEXT | NOT NULL | 该版本内容 | +| change_log | VARCHAR(500) | | 变更说明 | +| created_by | BIGINT | FOREIGN KEY | 修改人 | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 记录时间 | + +**`prompt_render_logs`(Prompt渲染审计表)** + +| 字段名 | 类型 | 约束 | 说明 | +| :--- | :--- | :--- | :--- | +| id | BIGSERIAL | PRIMARY KEY | 日志ID | +| request_id | VARCHAR(64) | NOT NULL | 关联请求ID | +| template_id | VARCHAR(100) | NOT NULL | 使用的模板标识 | +| template_version | INT | NOT NULL | 使用的模板版本 | +| rendered_prompt | TEXT | NOT NULL | 渲染后的完整Prompt(用于审计) | +| variables_used | JSONB | | 实际使用的变量值(脱敏后) | +| tokens_input | INT | | 输入token数 | +| tokens_output | INT | | 输出token数 | +| execution_time_ms | INT | | 执行耗时 | +| llm_model | VARCHAR(50) | | 使用的模型 | +| **model_provider** | **VARCHAR(20)** | | **【V6.0新增】使用的AI提供商** | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 记录时间 | + +### 5.3 索引策略汇总 + +| 表名 | 索引字段 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| issues | (department_id, status, created_at DESC) | 复合索引 | 工作台/列表页过滤排序 | +| issues | (assignee_id, status) | 复合索引 | 按负责人查询待办 | +| issues | (deadline) | B-tree | 定时任务扫描超时 | +| issues | (issue_no) | UNIQUE | 业务编号快速查询 | +| issue_logs | (issue_id, created_at DESC) | 复合索引 | 详情页时间线 | +| attachments | (issue_id) | B-tree | 查询附件 | +| ai_analysis | (issue_id, created_at DESC) | 复合索引 | 获取最新分析 | +| notifications | (user_id, is_read, created_at DESC) | 复合索引 | 通知列表 | +| task_executions | (task_id) | UNIQUE | 按任务ID查询 | +| agent_plans | (issue_id, created_at DESC) | 复合索引 | 按指摘获取规划 | +| tool_executions | (plan_id) | B-tree | 按规划ID查询 | +| agent_memories | (embedding) | ivfflat | 向量相似度检索 | +| knowledge_chunks | (embedding) | ivfflat | 知识库向量检索 | +| knowledge_documents | (uploaded_by, created_at DESC) | 复合索引 | 按上传人查询 | +| knowledge_search_logs | (user_id, created_at DESC) | 复合索引 | 检索审计查询 | +| prompt_templates | (template_id, is_active) | 复合索引 | 模板查询 | +| prompt_templates | (category, is_active) | 复合索引 | 按分类查询 | +| prompt_render_logs | (request_id) | B-tree | 按请求查询 | +| prompt_render_logs | (template_id, created_at DESC) | 复合索引 | 模板使用统计 | + +### 5.4 数据迁移与版本管理 + +- **工具**:Flyway +- **目录结构**:`src/main/resources/db/migration` + - `V1.0__init_schema.sql` —— 基础表结构 + - `V1.1__agent_tables.sql` —— Agent相关表及pgvector扩展 + - `V1.2__knowledge_tables.sql` —— 知识库相关表 + - `V1.3__insert_default_data.sql` —— 默认角色与权限 + - `V1.4__prompt_template_tables.sql` —— Prompt模板相关表 + - `V1.5__insert_default_prompts.sql` —— 默认Prompt模板数据 + - **`V1.6__add_model_provider_columns.sql`** —— **【V6.0新增】为ai_analysis、agent_plans、prompt_render_logs增加model_provider字段** + +### 5.5 软删除与数据生命周期 + +- **软删除**:`issues` 表使用 `is_deleted` 字段标记。 +- **归档策略**:超过3年的已关闭指摘迁移至 `issues_archive` 表。 +- **清理策略**: + - `issue_logs`保留2年。 + - `notifications`保留1年。 + - `tool_executions`保留1年。 + - `knowledge_search_logs`保留6个月。 + - `prompt_render_logs`保留30天(可配置)。 + + +## 6. 异步任务与Agent执行架构(V6.0调整) + +### 6.1 线程池配置 + +```java +@Configuration +@EnableAsync +public class AsyncConfig implements AsyncConfigurer { + @Override + public Executor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(10); + executor.setMaxPoolSize(50); + executor.setQueueCapacity(1000); + executor.setKeepAliveSeconds(60); + executor.setThreadNamePrefix("async-exec-"); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + return executor; + } +} +``` + +### 6.2 Agent执行流程(V6.0:基于Spring AI ChatModel接口) + +```java +@Service +public class AgentOrchestratorService { + + @Autowired + private ChatModel chatModel; // 由配置注入(Ollama或DeepSeek) + @Autowired + private EmbeddingModel embeddingModel; // 同上 + @Autowired + private PromptTemplateEngine promptEngine; + @Autowired + private MemoryService memoryService; + @Autowired + private ToolRegistry toolRegistry; + @Autowired + private AgentPlanRepository planRepo; + + @Async("asyncExecutor") + public CompletableFuture executeGoal(Long issueId, String goal, Long userId) { + // 1. 创建规划记录 + AgentPlan plan = createPlan(issueId, goal, userId); + // 记录当前使用的AI提供商 + plan.setModelProvider(provider); // provider从配置获取 + + // 2. 检索相似记忆 + List similarCases = memoryService.findSimilar(goal, 3); + + // 3. 渲染系统角色+规划Prompt(包含格式适配) + String systemPrompt = promptEngine.render("SYS_ROLE_001", buildSystemContext(userId)); + String planPrompt = promptEngine.render("PLAN_001", Map.of( + "userGoal", goal, + "issueContext", buildIssueContext(issueId), + "similarCases", formatCases(similarCases), + "availableTools", listAvailableTools(userId), + "executedSteps", "[]", + "currentStep", 1, + "maxSteps", 10 + )); + // promptEngine.render() 已返回经过格式适配的完整Prompt + String fullPrompt = systemPrompt + "\n\n---\n\n" + planPrompt; + + // 4. ReAct循环 + int maxSteps = 10; + String context = fullPrompt; + + while (maxSteps-- > 0) { + // 4.1 调用AI引擎(ChatModel) + String response = chatModel.call(context); + // 记录输入输出token(如有) + // 4.2 解析工具调用(从文本JSON提取) + ToolCallRequest toolCall = parseToolCall(response); + + // 4.3 判断是否需要审批 + if ("request_human_approval".equals(toolCall.getToolName())) { + plan.setStatus(PlanStatus.HUMAN_REVIEW); + plan.setRequiresApproval(true); + planRepo.save(plan); + createApprovalTask(plan.getId(), toolCall.getArgs()); + break; + } + + // 4.4 加载工具专用Prompt并执行 + String toolPromptId = "TOOL_" + toolCall.getToolName().toUpperCase(); + // 渲染工具模板(预检查用) + promptEngine.render(toolPromptId, Map.of( + "goal", goal, + "issueId", issueId, + "parameters", toolCall.getArgs(), + "similarCases", formatCases(similarCases) + )); + // 执行工具 + ToolExecutionResult result = toolRegistry.execute(toolCall); + saveToolExecution(plan.getId(), toolCall, result); + + // 4.5 格式化输出(结构化) + String formattedResult = formatOutput(result, toolCall.getToolName()); + + // 4.6 观察结果追加到上下文(用于下一轮) + context += "\n\n观察结果:" + formattedResult; + + // 4.7 判断是否完成 + if (result.isGoalCompleted()) { + plan.setStatus(PlanStatus.DONE); + plan.setCompletedAt(LocalDateTime.now()); + planRepo.save(plan); + memoryService.saveMemory(issueId, plan); + break; + } + } + return CompletableFuture.completedFuture(plan.getId().toString()); + } + + private ToolCallRequest parseToolCall(String response) { + // 从响应中提取JSON,解析出tool和parameters + // 使用Jackson或正则 + // ... + } +} +``` + +### 6.3 Spring AI Ollama配置类(V6.0新增) + +```java +@Configuration +public class AiProviderConfig { + + @Value("${ai.provider:ollama}") + private String provider; + + @Value("${ollama.base-url:http://localhost:11434}") + private String ollamaBaseUrl; + + @Value("${ollama.chat.model:llama3.1:70b}") + private String ollamaChatModel; + + @Value("${ollama.embedding.model:nomic-embed-text}") + private String ollamaEmbeddingModel; + + @Value("${deepseek.api.key:}") + private String deepseekApiKey; + + @Value("${deepseek.model:deepseek-v4-pro}") + private String deepseekModel; + + @Value("${deepseek.embedding-model:text-embedding-3-small}") + private String deepseekEmbeddingModel; + + @Bean + @Primary + @ConditionalOnProperty(name = "ai.provider", havingValue = "ollama", matchIfMissing = true) + public ChatModel ollamaChatModel() { + OllamaApi api = new OllamaApi(ollamaBaseUrl); + return new OllamaChatModel(api, OllamaOptions.create() + .withModel(ollamaChatModel) + .withTemperature(0.3) + .withNumPredict(4096)); + } + + @Bean + @Primary + @ConditionalOnProperty(name = "ai.provider", havingValue = "ollama", matchIfMissing = true) + public EmbeddingModel ollamaEmbeddingModel() { + OllamaApi api = new OllamaApi(ollamaBaseUrl); + return new OllamaEmbeddingModel(api, OllamaOptions.create() + .withModel(ollamaEmbeddingModel) + .withNumPredict(512)); + } + + @Bean + @ConditionalOnProperty(name = "ai.provider", havingValue = "deepseek") + public ChatModel deepseekChatModel() { + OpenAiApi api = new OpenAiApi("https://api.deepseek.com/v1", deepseekApiKey); + return new OpenAiChatModel(api, OpenAiOptions.builder() + .model(deepseekModel) + .temperature(0.3) + .build()); + } + + @Bean + @ConditionalOnProperty(name = "ai.provider", havingValue = "deepseek") + public EmbeddingModel deepseekEmbeddingModel() { + OpenAiApi api = new OpenAiApi("https://api.deepseek.com/v1", deepseekApiKey); + return new OpenAiEmbeddingModel(api, OpenAiEmbeddingOptions.builder() + .model(deepseekEmbeddingModel) + .build()); + } +} +``` + +> **注意事项**: +> - Spring AI 目前仍处于快速发展阶段,API 可能随版本升级发生变化。建议在实际代码中增加 `AiModelAdapter` 适配层接口,将 Spring AI 的 ChatModel/EmbeddingModel 调用封装在适配器之后,当 Spring AI 版本升级时只需修改适配器实现,不影响上层 AgentOrchestrator。 +> - `AiProviderConfig` 仅为示例代码,实际项目中需根据所选 Spring AI 版本调整具体 API 调用方式。 + +### 6.4 前端流式展示(V6.0增强) + +前端通过 **Server-Sent Events (SSE)** 接收Agent思考链,新增AI引擎信息展示: + +```typescript +const eventSource = new EventSource(`/api/v1/agent/plan/${planId}/stream`); +eventSource.onmessage = (event) => { + const data = JSON.parse(event.data); + // data.type: 'thought' | 'action' | 'observation' | 'result' | 'prompt_info' | 'model_info'【V6.0新增】 + + switch(data.type) { + case 'thought': + appendThought(data.content); + break; + case 'action': + appendAction(data.tool, data.params); + break; + case 'observation': + appendObservation(data.result); + break; + case 'result': + appendResult(data.content); + break; + case 'prompt_info': + showPromptInfo({ + templateId: data.templateId, + version: data.version, + variables: data.variables + }); + break; + case 'model_info': // V6.0新增 + showModelInfo({ + provider: data.provider, // 'ollama' 或 'deepseek' + model: data.model, + endpoint: data.endpoint + }); + break; + } +}; +``` + +### 6.5 监控指标(V6.0新增) + +| 指标名称 | 类型 | 说明 | +| :--- | :--- | :--- | +| `async.executor.queue.size` | Gauge | 队列大小 | +| `async.executor.active.count` | Gauge | 活跃线程数 | +| `agent.task.duration` | Histogram | Agent任务执行耗时 | +| `agent.tool.success.rate` | Gauge | 工具调用成功率 | +| `knowledge.search.duration` | Histogram | 知识库检索耗时 | +| `prompt.render.duration` | Histogram | Prompt模板渲染耗时 | +| `prompt.render.count` | Counter | Prompt渲染次数(按模板ID分维度) | +| `prompt.token.usage` | Histogram | 单次Prompt消耗的token数 | +| `prompt.version.mismatch` | Counter | 缓存版本与数据库版本不一致次数 | +| `llm.output.parse.error` | Counter | LLM输出JSON解析失败次数 | +| **【V6.0新增】`ai.provider.current`** | **Gauge** | **当前使用的AI提供商(0=ollama, 1=deepseek)** | +| **【V6.0新增】`ai.ollama.request.duration`** | **Histogram** | **Ollama请求耗时** | +| **【V6.0新增】`ai.deepseek.request.duration`** | **Histogram** | **DeepSeek API请求耗时** | +| **【V6.0新增】`ai.provider.switch.count`** | **Counter** | **提供商切换次数(自动降级)** | + + +## 7. 接口设计 + +### 7.1 API 规范 + +- **协议与域名**:`https://api.domain.com` +- **版本控制**:`/api/v1/` +- **认证方式**:JWT(access_token 30min,refresh_token 7d) +- **请求头**:`Authorization: Bearer ` +- **统一返回结构**: +```json +{ + "code": 200, + "message": "success", + "data": {}, + "timestamp": "2026-07-23T10:00:00Z" +} +``` + +### 7.2 接口列表 + +| 模块 | 方法 | URL | 描述 | +| :--- | :--- | :--- | :--- | +| **认证** | POST | `/api/v1/auth/login` | 用户登录 | +| | POST | `/api/v1/auth/refresh` | 刷新令牌 | +| | GET | `/api/v1/auth/me` | 获取当前用户信息 | +| **用户部门** | GET | `/api/v1/users` | 用户列表 | +| | GET | `/api/v1/departments` | 部门树 | +| **指摘** | POST | `/api/v1/issues` | 创建指摘 | +| | GET | `/api/v1/issues` | 指摘列表 | +| | GET | `/api/v1/issues/{id}` | 指摘详情 | +| | PUT | `/api/v1/issues/{id}` | 更新指摘 | +| | POST | `/api/v1/issues/{id}/attachments` | 上传附件 | +| **AI分析** | POST | `/api/v1/ai/batch-generate` | 批量生成AI分析 | +| | GET | `/api/v1/ai/records` | 分析记录列表 | +| | POST | `/api/v1/ai/records/{id}/feedback` | 提交反馈 | +| **知识库** | GET | `/api/v1/knowledge/documents` | 文档列表 | +| | POST | `/api/v1/knowledge/documents` | 上传文档 | +| | DELETE | `/api/v1/knowledge/documents/{id}` | 删除文档 | +| | POST | `/api/v1/knowledge/documents/{id}/reindex` | 重新向量化 | +| | GET | `/api/v1/knowledge/search` | 测试检索 | +| | GET | `/api/v1/knowledge/logs` | 检索审计日志 | +| **导入导出** | GET | `/api/v1/issues/export` | 导出Excel | +| | POST | `/api/v1/import/excel` | 上传校验Excel | +| | POST | `/api/v1/import/confirm` | 确认导入 | +| **Agent执行** | POST | `/api/v1/agent/execute` | 提交Agent目标 | +| | GET | `/api/v1/agent/plan/{planId}/stream` | SSE流式思考链 | +| | GET | `/api/v1/agent/plan/{planId}/status` | 查询状态 | +| **Agent审批** | POST | `/api/v1/agent/approval/{planId}/approve` | 审批通过 | +| | POST | `/api/v1/agent/approval/{planId}/reject` | 审批拒绝 | +| **Agent记忆** | GET | `/api/v1/agent/memories` | 记忆列表 | +| | POST | `/api/v1/agent/memories` | 新增记忆 | +| | DELETE | `/api/v1/agent/memories/{id}` | 删除记忆 | +| **Agent配置** | GET | `/api/v1/agent/config` | 获取配置 | +| | PUT | `/api/v1/agent/config` | 更新配置 | +| **通知** | GET | `/api/v1/notifications` | 通知列表 | +| | PATCH | `/api/v1/notifications/{id}/read` | 标记已读 | +| **Prompt模板** | GET | `/api/v1/prompts` | 模板列表 | +| | GET | `/api/v1/prompts/{templateId}` | 模板详情 | +| | POST | `/api/v1/prompts` | 创建模板 | +| | PUT | `/api/v1/prompts/{templateId}` | 更新模板(创建新版本) | +| | POST | `/api/v1/prompts/{templateId}/rollback` | 回滚到指定版本 | +| | POST | `/api/v1/prompts/{templateId}/test` | 渲染测试 | +| | GET | `/api/v1/prompts/{templateId}/versions` | 版本历史 | +| **Prompt渲染日志** | GET | `/api/v1/prompts/logs` | 渲染日志列表 | +| | GET | `/api/v1/prompts/logs/{logId}` | 渲染日志详情(含完整Prompt) | +| | GET | `/api/v1/prompts/stats` | 模板使用统计 | +| **【V6.0新增】AI配置** | GET | `/api/v1/ai/config` | 获取当前AI配置 | +| | PUT | `/api/v1/ai/config` | 更新AI配置(模型、参数等) | +| | POST | `/api/v1/ai/test` | 测试当前AI连接 | + + +## 8. 安全设计 + +### 8.1 传输与接入安全 +- 全站强制 HTTPS。 +- Nginx 限流(`limit_req_zone`),防暴力破解和 CC 攻击。 + +### 8.2 认证与授权 +- JWT 短令牌(30min)+ 长刷新令牌(7d)。 +- Spring Security 过滤器验证 JWT,`@PreAuthorize` 进行功能权限校验。 +- AOP 注入数据权限过滤条件(部门隔离)。 +- Agent 工具调用权限基于角色动态过滤。 +- Prompt模板编辑权限基于角色分级控制。 +- **AI配置修改权限仅限超级管理员**。 + +### 8.3 攻击防护 +- **XSS**:React 自动转义 + OWASP Java HTML Sanitizer。 +- **CSRF**:JWT 机制天然防 CSRF。 +- **SQL注入**:Spring Data JPA 参数化查询。 +- **Prompt注入防护**: + - 用户输入变量必须经过转义和长度限制。 + - 禁止用户输入中包含`{{}}`模板语法(防止模板注入)。 + - 敏感变量(密码、token等)禁止注入Prompt。 + - 所有用户输入变量在渲染日志中记录(脱敏后)。 +- **Ollama安全**:Ollama默认无认证,需通过Nginx反向代理增加Basic Auth或API Key验证,限制内网访问。 + +### 8.4 数据安全 +- 密码使用 `BCryptPasswordEncoder`(强度10)加盐哈希存储。 +- 知识库文档原文与向量:全部存储于本地 PostgreSQL + MinIO,永不外传。 +- DeepSeek API 调用:仅传输待向量化的文本片段或推理 Prompt,云端不留存任何数据。 +- **Ollama调用**:数据完全不出本地服务器,安全性最高。 +- Prompt渲染日志安全: + - 完整Prompt仅保留30天(可配置)。 + - 日志中的敏感变量自动脱敏(正则匹配身份证号、手机号、密码等模式)。 + - 仅超级管理员可查看完整渲染日志。 + +### 8.5 Agent安全策略 +- **工具调用白名单**:基于角色动态过滤。 +- **高风险操作审批**:`close_issue`、`delete_issue`、跨部门 `assign_issue` 必须触发 `request_human_approval`。 +- **执行限流**:单用户每分钟最多 10 个 Agent 目标。 +- **思考链审计**:所有 Agent 推理过程持久化存储,保留 2 年。 +- **环境隔离**:生产环境默认禁止高风险自动执行。 +- **Prompt模板安全**: + - 模板内容必须经过语法校验(防止模板语法错误导致渲染失败)。 + - 模板发布前必须经过审批(测试环境验证通过)。 + - 禁止模板中包含硬编码的API密钥、数据库连接串等敏感信息。 + - 模板变量必须进行类型校验(防止类型错误导致LLM输出异常)。 + + +## 9. 非功能需求 + +### 9.1 性能优化 +- 前端:路由懒加载,Ant Design 按需引入。 +- 后端:Redis 缓存热点数据,数据库联合索引,耗时操作异步化。 +- 数据库:定期 `VACUUM` 和 `ANALYZE`,慢查询日志分析。 +- 知识库检索:pgvector IVFFlat 索引,单次检索 < 200ms。 + > **注意事项**:pgvector 的 IVFFlat 索引在数据量超过 100 万条时召回率可能下降。初期数据量小可正常使用;后期若数据量增长较大,建议评估升级至 pgvectorscale 或迁移至专用向量数据库(如 Milvus、Qdrant)。 +- Prompt模板渲染:本地缓存 + Redis分布式缓存,渲染耗时 < 10ms。 +- Query改写:异步预渲染常用查询模板,减少LLM调用延迟。 +- **Ollama推理性能**: + - 首次调用可能冷启动(~5-30s),建议保持模型常驻内存(`ollama serve`)。 + - 后续推理延迟取决于模型大小,70B模型约1-5s/token,需结合硬件(GPU)调优。 + - 可配置并发请求数,监控资源使用。 + +### 9.2 可用性与容错 +- Deepseek API 调用失败自动重试(最多3次,指数退避)。 +- **Ollama调用失败**:自动重试2次,若仍失败,记录错误并尝试切换到DeepSeek(若启用自动降级)。 +- 超过最大步数的 Agent 任务自动终止并记录错误。 +- 人工审批超时(48小时无响应)自动触发通知升级。 +- Prompt模板加载失败容错: + - 如数据库模板加载失败,使用本地文件系统备份模板。 + - 如指定版本模板不存在,自动回退到最新稳定版本。 + - 如模板渲染失败,记录错误并使用通用Fallback模板。 +- LLM输出解析失败容错: + - JSON解析失败时,尝试使用正则提取关键字段。 + - 如仍失败,标记为"low confidence"并请求人工确认。 + - 记录解析失败案例,用于优化Prompt模板。 +- **模型切换容错**:支持热切换,无需重启服务。 + +### 9.3 可观测性 +- Prometheus + Grafana:JVM、应用性能、数据库连接池、Agent 任务指标、Ollama服务状态。 +- ELK:集中日志管理(含 Agent 思考链日志)。 +- Sentry:异常捕获与告警。 +- Prompt工程专项监控: + - 各Prompt模板的使用频率、成功率、平均耗时。 + - LLM输出结构化成功率(按模板分维度)。 + - Query改写成功率与改写质量评分。 + - A/B测试效果对比(不同模板版本的输出质量)。 +- **Ollama专项监控**: + - 服务可用性(通过`/api/tags`端点探活)。 + - 响应时间分布。 + - 并发请求数。 + - 模型加载/卸载事件。 + + +## 10. 运维、部署与CI/CD + +### 10.1 环境与部署 +- **开发环境**:Docker Compose 一键启动(PostgreSQL+Redis+MinIO+Ollama+Spring Boot+React)。 +- **测试/生产环境**:Docker Compose + Nginx 负载均衡,Ollama可单独部署在多台GPU服务器。 +- **对象存储**:MinIO(全部本地部署)。 + +### 10.2 环境变量(V6.0) + +```properties +# AI 提供商选择(ollama 或 deepseek) +AI_PROVIDER=ollama + +# Ollama 配置 +OLLAMA_BASE_URL=http://192.168.1.100:11434 +OLLAMA_CHAT_MODEL=llama3.1:70b +OLLAMA_EMBEDDING_MODEL=nomic-embed-text +OLLAMA_CHAT_OPTIONS_TEMPERATURE=0.3 +OLLAMA_CHAT_OPTIONS_NUM_PREDICT=4096 + +# DeepSeek 配置(当 AI_PROVIDER=deepseek 时生效) +DEEPSEEK_API_KEY=sk-xxx +DEEPSEEK_MODEL=deepseek-v4-pro +DEEPSEEK_EMBEDDING_MODEL=text-embedding-3-small + +# 自动降级开关 +AI_AUTO_FALLBACK_ENABLED=true + +# Agent 配置 +AGENT_MAX_STEPS=10 +AGENT_AUTO_EXECUTE_HIGH_RISK=false + +# 知识库切片参数 +KNOWLEDGE_CHUNK_SIZE=500 +KNOWLEDGE_CHUNK_OVERLAP=50 +KNOWLEDGE_MAX_UPLOAD_SIZE=52428800 + +# Prompt工程配置 +PROMPT_TEMPLATE_CACHE_TTL=3600 +PROMPT_RENDER_LOG_RETENTION_DAYS=30 +PROMPT_DEFAULT_SYSTEM_TEMPLATE=SYS_ROLE_001 +PROMPT_OUTPUT_FORMAT_ENFORCED=true +PROMPT_QUERY_REWRITE_ENABLED=true +PROMPT_AB_TEST_ENABLED=true +``` + +### 10.3 CI/CD(Jenkins) +1. 代码提交触发构建。 +2. 执行 Lint、单元测试。 +3. Prompt模板语法校验(自动化测试)。 +4. 构建 Docker 镜像,推送至 Harbor。 +5. Ansible 更新测试环境。 +6. **部署前检查Ollama服务是否可达**。 +7. Prompt模板自动化测试(渲染测试、变量校验)。 +8. 生产环境蓝绿部署。 + +### 10.4 监控告警 +- Prometheus + Grafana 监控应用与Ollama服务健康状态。 +- 当Ollama响应时间超过阈值或错误率 >5% 触发告警。 +- Prompt模板异常告警:模板渲染失败率 > 1% 触发告警;LLM输出解析失败率 > 5% 触发告警。 +- **模型切换事件告警**:当自动降级触发时,发送告警通知管理员。 + + +## 11. 项目计划与团队分工(V6.0调整) + +项目周期:**2026年7月6日 至 2026年8月7日**(5周),团队7人。 + +### 11.1 团队分组 + +| 组别 | 成员数 | 负责模块 | +|------|--------|----------| +| **第一组(核心业务组)** | 3人 | 登录、工作台(含Agent快捷指令)、指摘列表(含Agent批量处理)、指摘详情(Agent驾驶舱)、附件、状态流转 | +| **第二组(业务支撑组)** | 2人 | 批量录入(含Agent智能校验)、用户管理(含Agent自动执行权限)、角色权限(含Agent工具权限)、**Prompt模板管理后台**、**AI配置管理界面(V6.0新增)** | +| **第三组(知识库与后台组)** | 2人 | AI智能分析管理(分析记录)、知识库管理(文档上传/解析/向量化/检索审计)、**Prompt引擎核心实现**、**Spring AI集成与Ollama适配(V6.0新增)**、**向量化服务重构(V6.0新增)**、系统日志(含Agent操作日志分类) | + +### 11.2 里程碑 + +| 阶段 | 时间 | 主要任务 | +|------|------|----------| +| **阶段1:设计** | 7/6-7/10 | 统一技术栈、接口文档、数据库设计(含pgvector)、Prompt模板体系设计、**Ollama部署方案设计、Spring AI配置设计**、项目骨架 | +| **阶段2:开发** | 7/13-7/24 | 各组并行开发;第一组实现AgentOrchestrator及核心工具;第二组实现Prompt模板管理后台、**AI配置管理界面**;**第三组重点实现Ollama集成、嵌入模型切换、格式适配器** | +| **阶段3:联调** | 7/27-7/31 | 前后端联调、Agent端到端测试(含Ollama和DeepSeek双模式)、Prompt模板A/B测试、知识库检索准确性测试 | +| **阶段4:交付** | 8/3-8/7 | 全流程回归、性能优化、Prompt模板效果评估与调优、**Ollama性能调优**、用户手册、部署演示环境 | + + +## 12. 最终交付物清单(V6.0) + +1. ✅ 全部源代码(前端 React + 后端 Spring Boot) +2. ✅ 数据库建表脚本(含 pgvector、Agent表、知识库表、Prompt模板表) +3. ✅ 数据库 ER 图 +4. ✅ 接口文档(OpenAPI 3.0) +5. ✅ 部署说明文档(Docker Compose、环境变量配置、**Ollama部署手册**) +6. ✅ 用户操作手册(含 Agent 指令示例) +7. ✅ 测试报告(单元测试、集成测试、端到端测试) +8. ✅ Agent 工具集文档 +9. ✅ Prompt模板体系文档(含所有模板定义、变量说明、使用场景) +10. ✅ Prompt工程最佳实践手册(含模板设计规范、A/B测试方法、效果评估指标) +11. ✅ Agent 人工审批操作手册 +12. ✅ 知识库管理操作手册(含文档上传、向量化、检索测试说明) +13. ✅ UI交互原型文件(静态HTML演示,含10个核心页面 + Prompt模板管理后台 + **AI配置页面**) +14. ✅ **Ollama+DeepSeek混合架构配置与运维指南(新增)** +15. ✅ **模型切换与容灾演练报告(新增)** + +--- + +**文档结束** \ No newline at end of file diff --git a/docs/概要设计说明书变更点.md b/docs/概要设计说明书变更点.md new file mode 100644 index 0000000..3d61555 --- /dev/null +++ b/docs/概要设计说明书变更点.md @@ -0,0 +1,14 @@ + + +## 2026-07-23 + +| # | 内容 | 理由 | +|---|------|------| +| 1 | **技术栈版本不锁定具体小版本号**。设计文档中的 Vite 8.1.2、Ant Design 6.5.0 等细化版本在 2026.7 可能不存在或不是最新,建项目时用大版本范围(如 Vite ^7.0.0)取当时最新稳定版即可 | 避免因等待特定版本号导致开发阻塞 | +| 2 | **Ollama 70B 模型需评估硬件条件**。llama3.1:70b 需要至少 48GB VRAM,如公司只有单卡 24GB 显卡需准备降级方案,同时考虑 qwen2.5:32b 或 llama3.1:8b 作为备选 | 70B 模型对 GPU 要求过高,实际硬件可能无法支撑实时推理 | +| 3 | **Spring AI 成熟度风险**。Spring AI 是较新项目(2024 年出 1.0),API 可能变化,需在代码中预留适配层抽象 | 避免 Spring AI 版本升级导致阻塞开发 | +| 4 | **Ollama / DeepSeek 双引擎输出一致性**。ChatML 格式与 DeepSeek 原生格式的 Prompt 渲染结果可能质量不一致,需两层适配:格式适配 + 必要时对两个模型分别调参 | 保证跨模型输出行为稳定可控 | +| 5 | **pgvector 1536 维 IVFFlat 索引在大数据集下召回率下降**。初期数据量小无问题,后期数据量大(>100万条)可考虑升级 pgvectorscale 或换专门向量库 | 确保向量检索长期性能 | +| 6 | **项目结构改为 backend/ + frontend/ 分离**。前后端构建方式不同(Maven vs npm),部署环境不同(JRE vs Nginx),平级放在同一 Git 仓库但各自独立 | 避免构建工具链相互干扰,部署时可独立扩缩容 | +| 7 | **AuthController 真实实现放在 ims-web 模块,其余 Controller 在 ims-api 为存根**。ims-api 只依赖 ims-common,不依赖 ims-service,无法访问 UserRepository、PasswordEncoder | 避免模块间循环依赖,同时保持架构分层清晰 | +| 8 | **JwtUtil 移除 @Component/@Value 注解,改为普通构造函数 + @Configuration 工厂方法**。ims-common 不应依赖 Spring Context,保持轻量 | 使 ims-common 成为纯 POJO 模块,不依赖 Spring 框架 | diff --git a/docs/知识库实施计划.md b/docs/知识库实施计划.md new file mode 100644 index 0000000..cb55cc3 --- /dev/null +++ b/docs/知识库实施计划.md @@ -0,0 +1,127 @@ +# 知识库模块实施计划 + +## 技术选型 + +| 项目 | 选择 | +|------|------| +| 文档解析 | Apache Tika(一个依赖支持 PDF/Word/TXT/MD) | +| 向量化引擎 | Ollama(默认 `nomic-embed-text`)或 DeepSeek(`text-embedding-3-small`),**前端可切换** | +| 向量存储 | PostgreSQL + pgvector(已有) | +| 文件存储 | MinIO(已有) | +| 切片策略 | 500 Token,10% 重叠 | + +## 实施步骤 + +### Step 0:AI 配置管理(前置) + +| 端 | 内容 | +|----|------| +| 后端新增 | `AiConfigService` — 读取/写入 AI 配置(provider、模型名、API Key),存入 Redis | +| 后端实现 | `AiConfigController` 的 get/put/test 三个方法 | +| 后端改动 | `EmbeddingService` 每次调用前查配置,根据 provider 路由到 Ollama 或 DeepSeek | +| 前端新增 | 知识库页内加"Embedding 配置"区域:Provider 下拉框(Ollama / DeepSeek)+ 模型名输入框 | + +### Step 1:添加依赖 + +修改 `ims-service/pom.xml`,新增依赖: + +| 依赖 | 用途 | +|------|------| +| `org.apache.tika:tika-core` | 文档解析 | +| `org.springframework.ai:spring-ai-ollama-spring-boot-starter` | Ollama Embedding 调用 | + +### Step 2:创建 Service 接口 + 实现(8 个新文件) + +| 文件 | 包路径 | 说明 | +|------|--------|------| +| `EmbeddingService.java` | `com.ims.service.knowledge` | 接口:`embed(text)` + `search(query, topK)` | +| `AiConfigService.java` | `com.ims.service.knowledge` | AI 配置读写,存入 Redis | +| `OllamaEmbeddingService.java` | `com.ims.service.knowledge` | 调用 Ollama 实现 Embedding | +| `DeepSeekEmbeddingService.java` | `com.ims.service.knowledge` | 调用 DeepSeek API 实现 Embedding | +| `DocumentParserService.java` | `com.ims.service.knowledge` | Tika 解析 + 500 Token 切片 | +| `KnowledgeService.java` | `com.ims.service.knowledge` | 上传→MinIO→DB、列表、删除、重新索引 | +| `SearchService.java` | `com.ims.service.knowledge` | pgvector 余弦相似度检索 | +| `SearchLogService.java` | `com.ims.service.knowledge` | 检索审计日志记录 | + +### Step 3:实现 Controller + +| Controller | 方法 | 功能 | +|-----------|------|------| +| `AiConfigController` | `GET /api/v1/ai/config` | 读取 AI 配置 | +| | `PUT /api/v1/ai/config` | 更新 AI 配置 | +| | `POST /api/v1/ai/config/test` | 测试连接 | +| `KnowledgeController` | `GET /documents` | 文档列表(分页) | +| | `POST /documents` | 上传文档 → 解析 → 切片 → 向量化 → 入库 | +| | `DELETE /documents/{id}` | 删除文档 + MinIO 文件 + 向量 | +| | `POST /documents/{id}/reindex` | 重新向量化 | +| | `GET /search` | 检索接口 | +| | `GET /logs` | 检索日志列表 | + +### Step 4:前端知识库页面 + +修改 `frontend/src/pages/knowledge-base/index.tsx`: + +| 组件 | 参考设计 | +|------|---------| +| Tab 切换 | 文档管理 / 检索审计 | +| Embedding 配置区 | Provider 下拉框(Ollama / DeepSeek)+ 模型名输入框 | +| 上传拖拽区 | Ant Design `Upload.Dragger` | +| 文档表格 | Ant Design `Table`(文件名/上传人/时间/状态/分块数/操作) | +| 搜索框 | Ant Design `Input.Search` | +| 检索日志表格 | Ant Design `Table` | +| 统计卡片 | Ant Design `Card` + `Statistic` | + +### Step 5:配置 + +| 项目 | 操作 | +|------|------| +| Ollama 模型 | `ollama pull nomic-embed-text` | +| MinIO bucket | 管理后台创建 `ims-attachments` | + +## 文件清单 + +``` +修改: + backend/ims-service/pom.xml +2 依赖 + backend/ims-api/src/main/java/.../AiConfigController.java 填充 3 个方法 + backend/ims-api/src/main/java/.../KnowledgeController.java 填充 6 个方法 + frontend/src/pages/knowledge-base/index.tsx 重写 + +新增(后端): + backend/ims-service/src/main/java/com/ims/service/knowledge/ + ├── EmbeddingService.java + ├── AiConfigService.java + ├── OllamaEmbeddingService.java + ├── DeepSeekEmbeddingService.java + ├── DocumentParserService.java + ├── KnowledgeService.java + ├── SearchService.java + └── SearchLogService.java + +新增(前端): + frontend/src/pages/knowledge-base/services.ts API 封装 +``` + +## 实施顺序 + +``` +前置条件(你手动执行) + ① ollama pull nomic-embed-text(如果用 DeepSeek 则跳过) + ② MinIO 创建 ims-attachments bucket + ③ 如需 DeepSeek 则准备 API Key + ↓ +代码实施(我写) + Step 0: AiConfigService + AiConfigController + 前端配置区 + Step 1: pom.xml 加依赖 → mvn install 验证 + Step 2: 8 个 Service 文件 + Step 3: 填充 Controller + Step 4: 前端页面 + services.ts + ↓ +验证 + ① 启动后端 + 前端 + ② 配置页选择 Ollama + nomic-embed-text → 保存 + ③ 上传一个 PDF → 检查是否解析/切片/向量化成功 + ④ 检索 → 检查返回结果 + ⑤ 查看审计日志 + ⑥ 切到 DeepSeek 重新索引 → 验证 DeepSeek 向量化 +``` diff --git a/docs/知识库模块使用说明.md b/docs/知识库模块使用说明.md new file mode 100644 index 0000000..a602221 --- /dev/null +++ b/docs/知识库模块使用说明.md @@ -0,0 +1,104 @@ +# 知识库模块使用说明 + +## 已实现功能 + +| 功能 | 说明 | +|------|------| +| 文档上传 | 支持 PDF / Word(.docx/.doc) / TXT / Markdown / Excel(.xlsx/.xls) 拖拽上传 | +| 文本解析 | Apache Tika 自动提取 PDF/Word,Apache POI 提取 Excel | +| 文本切片 | 按 200 Token 切片,40 Token 重叠 | +| 向量化 | 调用 Ollama 或 DeepSeek Embedding API 转为向量 | +| 向量存储 | PostgreSQL pgvector | +| 语义检索 | 余弦相似度检索,按相关性排序返回 Top 5 | +| 文档管理 | 列表查看、删除、重新索引 | +| 检索审计 | 记录每次检索的关键词、耗时、命中数 | +| Embedding 配置 | 前端可切换 Ollama / DeepSeek,可修改模型名和 API Key | + +## 功能界面位置 + +左侧菜单 → **知识库管理**(路径 `/knowledge-base`) + +页面分两个 Tab: + +- **文档管理** — 上传、搜索、文档列表 +- **检索审计** — 检索日志和统计 + +## 操作步骤 + +### 上传文档 + +1. 打开知识库管理页面 +2. 在虚线区域**拖拽文件**或**点击选择文件** +3. 系统自动处理:解析 → 切片 → 向量化 → 入库 +4. 文档列表中状态变为 `completed` 即完成 + +支持格式:`.pdf` `.docx` `.doc` `.txt` `.md` `.xlsx` `.xls`,单文件不超过 50MB + +### 检索知识库 + +1. 在文档管理页的搜索框中输入关键词或自然语言问题 +2. 按回车搜索 +3. 下方显示匹配结果、来源文件名和相似度百分比(越高越匹配) +4. 注意:即使搜索词与文档用词不完全一致,语义相近也能搜到 + +> 相似度低于 50% 的结果通常不相关,因为搜索始终返回 Top 5,没有相关结果时也会"矮子里拔将军"。 + +### 切换 Embedding 模型 + +1. 点击页面右上角 **"Embedding 配置"** 按钮 +2. 在弹出的窗口中: + - 选择 AI 提供商(Ollama / DeepSeek) + - Ollama:填入 Ollama 地址和模型名(默认 `nomic-embed-text`) + - DeepSeek:填入模型名(默认 `text-embedding-3-small`)和 API Key +3. 点击保存 + +> 配置保存在 Redis 中,有效期 24 小时。未配置时使用 `application.yml` 的默认值。 + +### 删除文档 + +1. 在文档列表中点击对应行的 **"删除"** 按钮 +2. 确认后删除文档及其向量数据 + +### 重新索引 + +1. 在文档列表中点击对应行的 **"重新索引"** 按钮 +2. 系统重新执行解析 → 切片 → 向量化流程 + +## 前提条件 + +| 组件 | 状态要求 | +|------|---------| +| PostgreSQL + pgvector | 已运行(`docker compose up -d`) | +| MinIO | 已运行,`ims-attachments` bucket 已创建 | +| Ollama 或 DeepSeek | 至少一个可用(Ollama 需已拉取 `nomic-embed-text`) | +| 后端 | 已启动(端口 8080) | +| 前端 | 已启动(端口 5173) | + +## 验证流程 + +1. 打开 `http://localhost:5173`,用 `admin / Admin@2026` 登录 +2. 左侧菜单 → 知识库管理 +3. 点击右上角 **"Embedding 配置"**,确认提供商已正确配置 +4. 上传一个 `.txt` 文件 +5. 等待状态变为 `completed` +6. 在搜索框输入关键词,验证是否能匹配到文档内容 + +## 常见问题 + +| 问题 | 原因 | 解决 | +|------|------|------| +| 上传失败:bucket 不存在 | MinIO 未创建 bucket | 打开 `http://localhost:9001` 登录 minioadmin 创建 `ims-attachments` | +| 上传失败:Ollama 连接拒绝 | Ollama 地址配置不对 | 见下方"Ollama 地址怎么配" | +| 上传失败:状态显示 `failed` | 解析或向量化出错 | 鼠标悬停查看错误信息,根据提示处理 | +| 上传失败:HTTP 500 | 后端异常 | 检查后端终端日志,常见原因:依赖版本冲突(执行 `mvn install -DskipTests -U` 后重启) | +| 检索返回空 | 文档状态不是 `completed` | 等待文档处理完成 | +| 检索结果乱码或相似度低 | 之前失败时存入了二进制乱码 | 删除该文档,重新上传 | +| Token 过期跳登录 | 超过 30 分钟未操作 | 重新登录即可(已配置自动刷新) | + +### Ollama 地址怎么配? + +Ollama 已集成在 Docker Compose 中,地址固定为 `http://localhost:11434`。 + +如果在前端 Embedding 配置中手动填写,填此地址即可。默认 `application.yml` 中已配置为 `localhost:11434`,一般无需修改。 + +> 如果地址不对,上传任何文件都会在 Embedding 步骤报错,日志中显示 `Ollama embedding failed`。 diff --git a/frontend/e2e/playwright.config.ts b/frontend/e2e/playwright.config.ts new file mode 100644 index 0000000..42b766c --- /dev/null +++ b/frontend/e2e/playwright.config.ts @@ -0,0 +1,25 @@ +import { defineConfig, devices } from '@playwright/test' + +export default defineConfig({ + testDir: './tests', + fullyParallel: true, + retries: process.env.CI ? 2 : 0, + workers: 1, + reporter: [['html', { outputFolder: 'playwright-report' }]], + timeout: 60000, + use: { + baseURL: 'http://localhost:5173', + trace: 'on-first-retry', + screenshot: 'only-on-failure', + video: 'off', + }, + webServer: { + command: 'npm run dev', + url: 'http://localhost:5173', + reuseExistingServer: !process.env.CI, + timeout: 120000, + }, + projects: [ + { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, + ], +}) diff --git a/frontend/e2e/tests/01-login.spec.ts b/frontend/e2e/tests/01-login.spec.ts new file mode 100644 index 0000000..473d022 --- /dev/null +++ b/frontend/e2e/tests/01-login.spec.ts @@ -0,0 +1,31 @@ +import { test, expect } from '@playwright/test' +import { ADMIN } from '../utils/auth' + +test.describe('登录', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/login') + await page.evaluate(() => localStorage.clear()) + await page.reload() + }) + + test('成功登录:跳转工作台并存储 token', async ({ page }) => { + await page.getByPlaceholder('账号').fill(ADMIN.username) + await page.getByPlaceholder('密码').fill(ADMIN.password) + await page.getByRole('button', { name: /登\s*录/i }).click() + await expect(page).toHaveURL(/\/dashboard/) + const token = await page.evaluate(() => localStorage.getItem('accessToken')) + expect(token).toBeTruthy() + }) + + test('失败登录:错误密码提示', async ({ page }) => { + await page.getByPlaceholder('账号').fill(ADMIN.username) + await page.getByPlaceholder('密码').fill('wrong-password') + await page.getByRole('button', { name: /登\s*录/i }).click() + await expect(page.getByText('账号或密码错误')).toBeVisible() + }) + + test('未登录访问受保护路由:重定向到登录页', async ({ page }) => { + await page.goto('/issues') + await expect(page).toHaveURL(/\/login/) + }) +}) diff --git a/frontend/e2e/tests/02-issue-crud.spec.ts b/frontend/e2e/tests/02-issue-crud.spec.ts new file mode 100644 index 0000000..03e8656 --- /dev/null +++ b/frontend/e2e/tests/02-issue-crud.spec.ts @@ -0,0 +1,57 @@ +import { test, expect } from '@playwright/test' +import { uiLogin } from '../utils/auth' +import { createIssueViaApi, deleteIssueViaApi, changeStatusViaApi } from '../utils/api-helpers' + +test.describe('指摘 CRUD', () => { + let apiToken = '' + let issueId = 0 + let issueNo = '' + + test.beforeEach(async ({ page, request }) => { + const created = await createIssueViaApi(request) + issueId = created.issue.id + issueNo = created.issue.issueNo + apiToken = created.token + await uiLogin(page) + }) + + test.afterEach(async ({ request }) => { + if (issueId) { + await deleteIssueViaApi(request, issueId, apiToken).catch(() => {}) + } + }) + + test('列表页显示新建的指摘', async ({ page }) => { + await page.goto('/issues') + await expect(page.getByText('指摘列表')).toBeVisible() + await expect(page.getByText(issueNo)).toBeVisible() + }) + + test('详情页显示完整信息与状态流转历史', async ({ page }) => { + await page.goto(`/issues/${issueId}`) + await expect(page.getByText(issueNo)).toBeVisible() + await expect(page.getByText('状态流转历史')).toBeVisible() + await expect(page.getByText('创建指摘').first()).toBeVisible() + }) + + test('合法状态流转 draft -> open', async ({ page }) => { + await page.goto(`/issues/${issueId}`) + const dialog = page.getByRole('dialog') + await page.getByRole('button', { name: /更改状态/i }).click() + await expect(dialog).toBeVisible() + await dialog.locator('.ant-select-input').click() + const opt = page.locator('.ant-select-item-option[title="待处理"]') + await expect(opt).toBeVisible() + await opt.click() + await expect(dialog.locator('.ant-select-content')).toContainText('待处理') + await dialog.getByRole('button', { name: /确认变更/i }).click() + await expect(page.getByText('状态已更新')).toBeVisible() + }) + + test('非法状态流转返回 400 错误提示', async ({ request }) => { + const ok = await changeStatusViaApi(request, issueId, apiToken, 'open') + expect(ok.status()).toBe(200) + const illegal = await changeStatusViaApi(request, issueId, apiToken, 'closed') + expect(illegal.status()).toBe(400) + }) +}) diff --git a/frontend/e2e/tests/03-attachments.spec.ts b/frontend/e2e/tests/03-attachments.spec.ts new file mode 100644 index 0000000..75aca5b --- /dev/null +++ b/frontend/e2e/tests/03-attachments.spec.ts @@ -0,0 +1,35 @@ +import { test, expect } from '@playwright/test' +import { uiLogin } from '../utils/auth' +import { createIssueViaApi, deleteIssueViaApi } from '../utils/api-helpers' + +test.describe('附件功能', () => { + let apiToken = '' + let issueId = 0 + + test.beforeEach(async ({ page, request }) => { + const created = await createIssueViaApi(request) + issueId = created.issue.id + apiToken = created.token + await uiLogin(page) + }) + + test.afterEach(async ({ request }) => { + if (issueId) { + await deleteIssueViaApi(request, issueId, apiToken).catch(() => {}) + } + }) + + test('上传附件后列表显示文件信息', async ({ page }) => { + await page.goto(`/issues/${issueId}`) + await expect(page.getByText('附件 (0)')).toBeVisible() + + await page.setInputFiles('input[type=file]', { + name: 'e2e-test.txt', + mimeType: 'text/plain', + buffer: Buffer.from('IMS E2E attachment content'), + }) + await expect(page.getByText('上传成功')).toBeVisible() + await expect(page.getByText(/e2e-test\.txt/)).toBeVisible() + await expect(page.getByText(/附件 \(1\)/)).toBeVisible() + }) +}) diff --git a/frontend/e2e/tests/04-agent-cockpit.spec.ts b/frontend/e2e/tests/04-agent-cockpit.spec.ts new file mode 100644 index 0000000..05e9e04 --- /dev/null +++ b/frontend/e2e/tests/04-agent-cockpit.spec.ts @@ -0,0 +1,45 @@ +import { test, expect } from '@playwright/test' +import { uiLogin } from '../utils/auth' +import { createIssueViaApi, deleteIssueViaApi } from '../utils/api-helpers' + +test.describe('Agent 驾驶舱', () => { + let apiToken = '' + let issueId = 0 + + test.beforeEach(async ({ page, request }) => { + const created = await createIssueViaApi(request) + issueId = created.issue.id + apiToken = created.token + await uiLogin(page) + }) + + test.afterEach(async ({ request }) => { + if (issueId) { + await deleteIssueViaApi(request, issueId, apiToken).catch(() => {}) + } + }) + + test('执行指令后显示工具卡片', async ({ page }) => { + test.setTimeout(560000) + await page.goto(`/issues/${issueId}`) + await expect(page.getByText('IMS Agent 驾驶舱')).toBeVisible() + + await page.getByPlaceholder(/给 Agent 下达指令/).fill('查找知识库相似案例并生成对应方案') + await page.getByRole('button', { name: /执行指令/i }).click() + + // 模型输出工具调用后 SSE 渲染工具卡片(真实模型可能只输出 search_knowledge,不强依赖具体工具) + await expect(page.getByText(/call:/).first()).toBeVisible({ timeout: 480000 }) + }) + + test('写工具触发审批后可批准', async ({ page }) => { + test.setTimeout(480000) + await page.goto(`/issues/${issueId}`) + await page.getByPlaceholder(/给 Agent 下达指令/).fill('请调用 update_issue 工具,把本指摘的优先级改为 high') + await page.getByRole('button', { name: /执行指令/i }).click() + + await expect(page.getByText('需要您的审批')).toBeVisible({ timeout: 420000 }) + await page.getByRole('button', { name: /批准执行/i }).click() + await expect(page.getByText('已批准执行')).toBeVisible({ timeout: 30000 }) + await expect(page.getByText(/Agent 指令已批准/)).toBeVisible({ timeout: 30000 }) + }) +}) diff --git a/frontend/e2e/tests/05-dashboard.spec.ts b/frontend/e2e/tests/05-dashboard.spec.ts new file mode 100644 index 0000000..bcd8ed8 --- /dev/null +++ b/frontend/e2e/tests/05-dashboard.spec.ts @@ -0,0 +1,44 @@ +import { test, expect } from '@playwright/test' +import { uiLogin, apiLogin } from '../utils/auth' +import { getDashboardViaApi } from '../utils/api-helpers' + +test.describe('工作台 Dashboard', () => { + let apiToken = '' + + test.beforeEach(async ({ request }) => { + apiToken = (await apiLogin(request)).accessToken + }) + + test('登录后工作台显示统计与图表', async ({ page }) => { + await uiLogin(page) + await page.goto('/dashboard') + await expect(page.getByText('工作台概览')).toBeVisible() + + await expect(page.getByText('Agent 快捷指令')).toBeVisible() + await expect(page.getByText('指摘处理趋势')).toBeVisible() + await expect(page.getByText('Agent 洞察')).toBeVisible() + await expect(page.getByText('最新动态')).toBeVisible() + await expect(page.getByText('指摘状态分布')).toBeVisible() + }) + + test('统计卡显示正确的数量(API 对比)', async ({ page, request }) => { + const stats = await getDashboardViaApi(request, apiToken) + await uiLogin(page) + await page.goto('/dashboard') + + const pending = page.locator('.ant-statistic', { hasText: '待处理指摘' }).first() + await expect(pending.getByText('待处理指摘', { exact: true })).toBeVisible() + await expect(pending.getByText(String(stats.pendingCount))).toBeVisible() + + await expect(page.locator('.ant-statistic', { hasText: '进行中指摘' }).first()).toBeVisible() + await expect(page.locator('.ant-statistic', { hasText: '本月已完成' }).first()).toBeVisible() + }) + + test('Agent 快捷指令输入框存在且可输入', async ({ page }) => { + await uiLogin(page) + await page.goto('/dashboard') + const input = page.getByPlaceholder(/例如:查找知识库/) + await input.fill('测试指令') + await expect(input).toHaveValue('测试指令') + }) +}) diff --git a/frontend/e2e/tests/06-notifications.spec.ts b/frontend/e2e/tests/06-notifications.spec.ts new file mode 100644 index 0000000..4452658 --- /dev/null +++ b/frontend/e2e/tests/06-notifications.spec.ts @@ -0,0 +1,30 @@ +import { test, expect } from '@playwright/test' +import { uiLogin } from '../utils/auth' + +test.describe('通知铃铛', () => { + test('铃铛显示未读数且下拉展开', async ({ page }) => { + await uiLogin(page) + await page.goto('/dashboard') + + const bell = page.locator('.ant-badge') + await expect(bell.first()).toBeVisible() + await bell.first().click() + await page.waitForTimeout(1000) + + const menu = page.locator('.ant-dropdown') + if (await menu.count() > 0) { + await expect(menu.first()).toBeVisible() + } + }) + + test('通知下拉包含通知标题或空提示', async ({ page }) => { + await uiLogin(page) + await page.goto('/dashboard') + await page.locator('.ant-badge').first().click() + await page.waitForTimeout(1000) + + const hasTitle = await page.getByText('通知').count() + const hasEmpty = await page.getByText('暂无通知').count() + expect(hasTitle + hasEmpty).toBeGreaterThan(0) + }) +}) diff --git a/frontend/e2e/tests/07-agent-fill.spec.ts b/frontend/e2e/tests/07-agent-fill.spec.ts new file mode 100644 index 0000000..b6c5cd8 --- /dev/null +++ b/frontend/e2e/tests/07-agent-fill.spec.ts @@ -0,0 +1,32 @@ +import { test, expect } from '@playwright/test' +import { uiLogin } from '../utils/auth' + +test.describe('新建页 Agent 智能填充', () => { + test('点击「让 Agent 智能填充」真实调用 AI 并回填字段', async ({ page }) => { + test.setTimeout(420000) + await uiLogin(page) + await page.goto('/issues/new') + + await page.getByPlaceholder('请输入指摘标题').fill('登录页面在低分辨率下按钮错位、样式异常,影响操作') + + const fillBtn = page.getByRole('button', { name: /让 Agent 智能填充/i }) + await fillBtn.click() + await expect(fillBtn).toHaveClass(/ant-btn-loading/, { timeout: 15000 }) + // 模型调用耗时波动大(数十秒到数分钟),axios 侧 180s 超时后走降级回填,故等待上限放宽到 240s + await expect(fillBtn).not.toHaveClass(/ant-btn-loading/, { timeout: 240000 }) + + // 工程阶段/区分/影响度三个 Select 至少被回填 3 个非"请选择"值(AI 成功或降级都会回填) + await expect.poll(async () => { + return page.locator('.ant-select').evaluateAll(els => + els.filter(e => { + const t = e.textContent?.trim() ?? '' + return t !== '' && t !== '请选择' + }).length) + }, { timeout: 30000, intervals: [2000] }).toBeGreaterThanOrEqual(3) + + // 优先级 Radio 也被回填(高/中/低 任一) + const checked = (await page.locator('.ant-radio-button-wrapper-checked').allTextContents()).map(s => s.trim()).filter(Boolean) + expect(checked.length).toBeGreaterThan(0) + console.log('filled selects:', JSON.stringify(checked)) + }) +}) \ No newline at end of file diff --git a/frontend/e2e/tests/08-agent-sse.spec.ts b/frontend/e2e/tests/08-agent-sse.spec.ts new file mode 100644 index 0000000..cd4c048 --- /dev/null +++ b/frontend/e2e/tests/08-agent-sse.spec.ts @@ -0,0 +1,41 @@ +import { test, expect } from '@playwright/test' +import { uiLogin } from '../utils/auth' +import { createIssueViaApi, deleteIssueViaApi } from '../utils/api-helpers' + +test.describe('Agent SSE 流式渲染', () => { + let apiToken = '' + let issueId = 0 + + test.beforeEach(async ({ page, request }) => { + const created = await createIssueViaApi(request) + issueId = created.issue.id + apiToken = created.token + await uiLogin(page) + }) + + test.afterEach(async ({ request }) => { + if (issueId) { + await deleteIssueViaApi(request, issueId, apiToken).catch(() => {}) + } + }) + + test('执行指令后 SSE 事件实时渲染 thought / prompt_info / model_info', async ({ page }) => { + test.setTimeout(120000) + await page.goto(`/issues/${issueId}`) + await expect(page.getByText('IMS Agent 驾驶舱')).toBeVisible() + + await page.getByPlaceholder(/给 Agent 下达指令/).fill('查找知识库相似案例并生成对应方案') + await page.getByRole('button', { name: /执行指令/i }).click() + + // thought:初始连接消息经 SSE 渲染为绿色状态框 + await expect(page.getByText(/已连接执行流/)).toBeVisible({ timeout: 20000 }) + + // model_info:引擎来源 Tag + await expect(page.getByText(/引擎:/)).toBeVisible({ timeout: 20000 }) + + // prompt_info:展开模板信息,模板标识非"未提供" + await page.getByText('Prompt 模板信息').click() + await expect(page.getByText(/系统角色:\s*\S+/)).toBeVisible() + await expect(page.getByText(/规划模板:\s*\S+/)).toBeVisible() + }) +}) \ No newline at end of file diff --git a/frontend/e2e/tests/09-dashboard-agent-stream.spec.ts b/frontend/e2e/tests/09-dashboard-agent-stream.spec.ts new file mode 100644 index 0000000..cc5a604 --- /dev/null +++ b/frontend/e2e/tests/09-dashboard-agent-stream.spec.ts @@ -0,0 +1,22 @@ +import { test, expect } from '@playwright/test' +import { uiLogin } from '../utils/auth' + +test.describe('工作台 Agent 快捷指令执行面板', () => { + test('执行「生成本周报告」打开执行流面板并渲染 SSE 事件', async ({ page }) => { + await uiLogin(page) + await page.goto('/dashboard') + await expect(page.getByText('Agent 快捷指令')).toBeVisible() + + const input = page.getByPlaceholder(/例如:查找知识库/) + await input.fill('生成本周指摘处理统计报告') + await page.getByRole('button', { name: /执\s*行/ }).click() + + await expect(page.getByText(/Agent 执行流/)).toBeVisible({ timeout: 30000 }) + await expect(page.getByText(/指令已提交,正在连接执行流/)).toBeVisible({ timeout: 30000 }) + + await expect(page.getByText(/已连接执行流/)).toBeVisible({ timeout: 60000 }) + + const execBox = page.getByText(/Agent 执行流/) + await expect(execBox).toBeVisible() + }) +}) \ No newline at end of file diff --git a/frontend/e2e/utils/api-helpers.ts b/frontend/e2e/utils/api-helpers.ts new file mode 100644 index 0000000..70be421 --- /dev/null +++ b/frontend/e2e/utils/api-helpers.ts @@ -0,0 +1,69 @@ +import { APIRequestContext } from '@playwright/test' +import { API_BASE, apiLogin, authHeaders } from './auth' + +export interface CreatedIssue { + id: number + issueNo: string + title: string +} + +export async function createIssueViaApi( + request: APIRequestContext, + overrides: Record = {}, +): Promise<{ issue: CreatedIssue; token: string }> { + const token = (await apiLogin(request)).accessToken + const payload = { + title: `E2E-${Date.now()}-自动化测试`, + description: 'Playwright 自动化测试创建的指摘', + phase: '编码', + subProject: '后端开发', + category: '功能缺陷', + impactLevel: '中', + assigneeId: 2, + reviewerId: 1, + priority: 'medium', + status: 'draft', + departmentId: 2, + ...overrides, + } + const res = await request.post(`${API_BASE}/issues`, { + data: payload, + headers: authHeaders(token), + }) + if (!res.ok()) { + throw new Error(`create issue failed: ${res.status()} ${await res.text()}`) + } + const json = await res.json() + return { issue: json.data, token } +} + +export async function deleteIssueViaApi(request: APIRequestContext, issueId: number, token: string) { + const res = await request.delete(`${API_BASE}/issues/${issueId}`, { headers: authHeaders(token) }) + if (!res.ok()) { + throw new Error(`delete issue failed: ${res.status()} ${await res.text()}`) + } +} + +export async function getIssueViaApi(request: APIRequestContext, issueId: number, token: string) { + const res = await request.get(`${API_BASE}/issues/${issueId}`, { headers: authHeaders(token) }) + return res.ok() ? (await res.json()).data : null +} + +export async function changeStatusViaApi( + request: APIRequestContext, + issueId: number, + token: string, + status: string, + remark = 'E2E 状态变更', +) { + const res = await request.patch(`${API_BASE}/issues/${issueId}/status`, { + data: { status, remark }, + headers: authHeaders(token), + }) + return res +} + +export async function getDashboardViaApi(request: APIRequestContext, token: string) { + const res = await request.get(`${API_BASE}/dashboard/stats`, { headers: authHeaders(token) }) + return res.ok() ? (await res.json()).data : null +} diff --git a/frontend/e2e/utils/auth.ts b/frontend/e2e/utils/auth.ts new file mode 100644 index 0000000..7caa578 --- /dev/null +++ b/frontend/e2e/utils/auth.ts @@ -0,0 +1,35 @@ +import { APIRequestContext } from '@playwright/test' + +export const ADMIN = { username: 'admin', password: 'Admin@2026' } +export const API_BASE = 'http://localhost:8080/api/v1' + +export interface LoginData { + accessToken: string + refreshToken?: string + username?: string + roleName?: string + userId?: number +} + +export async function apiLogin(request: APIRequestContext, user = ADMIN): Promise { + const res = await request.post(`${API_BASE}/auth/login`, { + data: { username: user.username, password: user.password }, + }) + if (!res.ok()) { + throw new Error(`login failed: ${res.status()} ${await res.text()}`) + } + const json = await res.json() + return json.data +} + +export async function uiLogin(page: import('@playwright/test').Page, user = ADMIN) { + await page.goto('/login') + await page.getByPlaceholder(/用户名|账号|username/i).fill(user.username) + await page.getByPlaceholder(/密码|password/i).fill(user.password) + await page.getByRole('button', { name: /登\s*录/i }).click() + await page.waitForURL(/\/dashboard/) +} + +export function authHeaders(token: string) { + return { Authorization: `Bearer ${token}` } +} diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..afcc5bb --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + 指摘管理系统 + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..3f16ac0 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,4573 @@ +{ + "name": "ims-frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ims-frontend", + "version": "1.0.0", + "dependencies": { + "@ant-design/charts": "^2.6.7", + "@ant-design/icons": "^6.0.0", + "@reduxjs/toolkit": "^2.0.0", + "antd": "^6.0.0", + "axios": "^1.7.0", + "dayjs": "^1.11.0", + "echarts": "^6.1.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-redux": "^9.0.0", + "react-router-dom": "^7.0.0" + }, + "devDependencies": { + "@playwright/test": "^1.62.1", + "@types/echarts": "^4.9.22", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.4.0", + "typescript": "^5.7.0", + "vite": "^7.0.0" + } + }, + "node_modules/@ant-design/charts": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/@ant-design/charts/-/charts-2.6.7.tgz", + "integrity": "sha512-XfmsnspUpfrMlRFGTwmHJ2TPKcosq5a5nSxAfIOpEXAvmJBT2N16oejGTZhUFTzba8W3XtBOziwRAXmDmLUqvA==", + "license": "MIT", + "dependencies": { + "@ant-design/graphs": "^2.1.1", + "@ant-design/plots": "^2.6.7", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": ">=16.8.4", + "react-dom": ">=16.8.4" + } + }, + "node_modules/@ant-design/charts-util": { + "version": "0.0.1-alpha.7", + "resolved": "https://registry.npmjs.org/@ant-design/charts-util/-/charts-util-0.0.1-alpha.7.tgz", + "integrity": "sha512-Yh0o6EdO6SvdSnStFZMbnUzjyymkVzV+TQ9ymVW9hlVgO/fUkUII3JYSdV+UVcFnYwUF0YiDKuSTLCZNAzg2bQ==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": ">=16.8.4", + "react-dom": ">=16.8.4" + } + }, + "node_modules/@ant-design/colors": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.1.tgz", + "integrity": "sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.0" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-2.1.2.tgz", + "integrity": "sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-2.1.2.tgz", + "integrity": "sha512-5fTHQ158jJJ5dC/ECeyIdZUzKxE/mpEMRZxthyG1sw/AKRHKgJBg00Yi6ACVXgycdje7KahRNvNET/uBccwCnA==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^2.1.2", + "@babel/runtime": "^7.23.2", + "@rc-component/util": "^1.4.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.1.tgz", + "integrity": "sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==", + "license": "MIT", + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/graphs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@ant-design/graphs/-/graphs-2.1.1.tgz", + "integrity": "sha512-qT3Oo8BWeoAmZEy9gfR6uIk+rczbNJ3sWXKonoOD5koATWv7dY0kgvS1JnhdM1QW4FkfPPJTeQVSlRRUtvWDwA==", + "license": "MIT", + "dependencies": { + "@ant-design/charts-util": "0.0.1-alpha.7", + "@antv/g6": "^5.0.44", + "@antv/g6-extension-react": "^0.2.0", + "@antv/graphin": "^3.0.4", + "lodash": "^4.17.21", + "styled-components": "^6.1.15" + }, + "peerDependencies": { + "react": ">=16.8.4", + "react-dom": ">=16.8.4" + } + }, + "node_modules/@ant-design/icons": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.3.2.tgz", + "integrity": "sha512-B6O5a5XJ4wjtNOfZejXYwHW5zvKV5gYkjGf11dHGLEbKn0ABDGndo41+gfIiXyTFhvESj4XTotuud33mUFid0g==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.1", + "@ant-design/icons-svg": "^4.5.0", + "@rc-component/util": "^1.11.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.5.0.tgz", + "integrity": "sha512-1BTUFyKPTBZ53MuTP8s0k5SFEXL7o3VHEOwLgzaoWKwnBeqIcqUtVshc4SKzhI6uACfqhJqBwBUE9FsWR3uULA==", + "license": "MIT" + }, + "node_modules/@ant-design/plots": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/@ant-design/plots/-/plots-2.6.8.tgz", + "integrity": "sha512-QsunUs2d5rbq/1BwVhga/siA5H50OaG23YopMYwPD4sPsza6NQzPQ8FM3elNIsD/BIk298tihqX1cJ/MmvVJbQ==", + "license": "MIT", + "dependencies": { + "@ant-design/charts-util": "0.0.3", + "@antv/event-emitter": "^0.1.3", + "@antv/g": "^6.1.7", + "@antv/g2": "^5.2.7", + "@antv/g2-extension-plot": "^0.2.1", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": ">=16.8.4", + "react-dom": ">=16.8.4" + } + }, + "node_modules/@ant-design/plots/node_modules/@ant-design/charts-util": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@ant-design/charts-util/-/charts-util-0.0.3.tgz", + "integrity": "sha512-x1H7UT6t4dXAyGRoHqlOnEsEqBSTANFGTZEAMI0CWYhYUpp13n0o9grl9oPtoL6FEQMjUBTY+zGJKlHkz8smMw==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": ">=16.8.4", + "react-dom": ">=16.8.4" + } + }, + "node_modules/@ant-design/react-slick": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-2.0.0.tgz", + "integrity": "sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "clsx": "^2.1.1", + "json2mq": "^0.2.0", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@antv/algorithm": { + "version": "0.1.26", + "resolved": "https://registry.npmjs.org/@antv/algorithm/-/algorithm-0.1.26.tgz", + "integrity": "sha512-DVhcFSQ8YQnMNW34Mk8BSsfc61iC1sAnmcfYoXTAshYHuU50p/6b7x3QYaGctDNKWGvi1ub7mPcSY0bK+aN0qg==", + "license": "MIT", + "dependencies": { + "@antv/util": "^2.0.13", + "tslib": "^2.0.0" + } + }, + "node_modules/@antv/algorithm/node_modules/@antv/util": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@antv/util/-/util-2.0.17.tgz", + "integrity": "sha512-o6I9hi5CIUvLGDhth0RxNSFDRwXeywmt6ExR4+RmVAzIi48ps6HUy+svxOCayvrPBN37uE6TAc2KDofRo0nK9Q==", + "license": "ISC", + "dependencies": { + "csstype": "^3.0.8", + "tslib": "^2.0.3" + } + }, + "node_modules/@antv/component": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/@antv/component/-/component-2.1.11.tgz", + "integrity": "sha512-dTdz8VAd3rpjOaGEZTluz82mtzrP4XCtNlNQyrxY7VNRNcjtvpTLDn57bUL2lRu1T+iklKvgbE2llMriWkq9vQ==", + "license": "MIT", + "dependencies": { + "@antv/g": "^6.1.11", + "@antv/scale": "^0.4.16", + "@antv/util": "^3.3.10", + "svg-path-parser": "^1.1.0" + } + }, + "node_modules/@antv/component/node_modules/@antv/scale": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@antv/scale/-/scale-0.4.16.tgz", + "integrity": "sha512-5wg/zB5kXHxpTV5OYwJD3ja6R8yTiqIOkjOhmpEJiowkzRlbEC/BOyMvNUq5fqFIHnMCE9woO7+c3zxEQCKPjw==", + "license": "MIT", + "dependencies": { + "@antv/util": "^3.3.7", + "color-string": "^1.5.5", + "fecha": "^4.2.1" + } + }, + "node_modules/@antv/coord": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@antv/coord/-/coord-0.4.7.tgz", + "integrity": "sha512-UTbrMLhwJUkKzqJx5KFnSRpU3BqrdLORJbwUbHK2zHSCT3q3bjcFA//ZYLVfIlwqFDXp/hzfMyRtp0c77A9ZVA==", + "license": "MIT", + "dependencies": { + "@antv/scale": "^0.4.12", + "@antv/util": "^2.0.13", + "gl-matrix": "^3.4.3" + } + }, + "node_modules/@antv/coord/node_modules/@antv/scale": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@antv/scale/-/scale-0.4.16.tgz", + "integrity": "sha512-5wg/zB5kXHxpTV5OYwJD3ja6R8yTiqIOkjOhmpEJiowkzRlbEC/BOyMvNUq5fqFIHnMCE9woO7+c3zxEQCKPjw==", + "license": "MIT", + "dependencies": { + "@antv/util": "^3.3.7", + "color-string": "^1.5.5", + "fecha": "^4.2.1" + } + }, + "node_modules/@antv/coord/node_modules/@antv/scale/node_modules/@antv/util": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/@antv/util/-/util-3.3.11.tgz", + "integrity": "sha512-FII08DFM4ABh2q5rPYdr0hMtKXRgeZazvXaFYCs7J7uTcWDHUhczab2qOCJLNDugoj8jFag1djb7wS9ehaRYBg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "gl-matrix": "^3.3.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@antv/coord/node_modules/@antv/util": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@antv/util/-/util-2.0.17.tgz", + "integrity": "sha512-o6I9hi5CIUvLGDhth0RxNSFDRwXeywmt6ExR4+RmVAzIi48ps6HUy+svxOCayvrPBN37uE6TAc2KDofRo0nK9Q==", + "license": "ISC", + "dependencies": { + "csstype": "^3.0.8", + "tslib": "^2.0.3" + } + }, + "node_modules/@antv/event-emitter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@antv/event-emitter/-/event-emitter-0.1.3.tgz", + "integrity": "sha512-4ddpsiHN9Pd4UIlWuKVK1C4IiZIdbwQvy9i7DUSI3xNJ89FPUFt8lxDYj8GzzfdllV0NkJTRxnG+FvLk0llidg==", + "license": "MIT" + }, + "node_modules/@antv/expr": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@antv/expr/-/expr-1.0.2.tgz", + "integrity": "sha512-vrfdmPHkTuiS5voVutKl2l06w1ihBh9A8SFdQPEE+2KMVpkymzGOF1eWpfkbGZ7tiFE15GodVdhhHomD/hdIwg==", + "license": "MIT" + }, + "node_modules/@antv/g": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@antv/g/-/g-6.3.1.tgz", + "integrity": "sha512-WYEKqy86LHB2PzTmrZXrIsIe+3Epeds2f68zceQ+BJtRoGki7Sy4IhlC8LrUMztgfT1t3d/0L745NWZwITroKA==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.7.0", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "html2canvas": "^1.4.1" + } + }, + "node_modules/@antv/g-canvas": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@antv/g-canvas/-/g-canvas-2.2.0.tgz", + "integrity": "sha512-h7zVBBo2aO64DuGKvq9sG+yTU3sCUb9DALCVm7nz8qGPs8hhLuFOkKPEzUDNfNYZGJUGzY8UDtJ3QRGRFcvEQg==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.7.0", + "@antv/g-math": "3.1.0", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-lite": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@antv/g-lite/-/g-lite-2.7.0.tgz", + "integrity": "sha512-uSzgHYa5bwR5L2Au7/5tsOhFmXKZKLPBH90+Q9bP9teVs5VT4kOAi0isPSpDI8uhdDC2/VrfTWu5K9HhWI6FWw==", + "license": "MIT", + "dependencies": { + "@antv/g-math": "3.1.0", + "@antv/util": "^3.3.5", + "@antv/vendor": "^1.0.3", + "@babel/runtime": "^7.25.6", + "eventemitter3": "^5.0.1", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@antv/g-math/-/g-math-3.1.0.tgz", + "integrity": "sha512-DtN1Gj/yI0UiK18nSBsZX8RK0LszGwqfb+cBYWgE+ddyTm8dZnW4tPUhV7QXePsS6/A5hHC+JFpAAK7OEGo5ZQ==", + "license": "MIT", + "dependencies": { + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-plugin-dragndrop": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@antv/g-plugin-dragndrop/-/g-plugin-dragndrop-2.1.1.tgz", + "integrity": "sha512-+aesDUJVQDs6UJ2bOBbDlaGAPCfHmU0MbrMTlQlfpwNplWueqtgVAZ3L57oZ2ZGHRWUHiRwZGPjXMBM3O2LELw==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.7.0", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-svg": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@antv/g-svg/-/g-svg-2.1.1.tgz", + "integrity": "sha512-gVzBkjqA8FzDTbkuIxj6L0Omz/X/hFbYLzK6alWr0sHTfywqP6czcjDUJU8DF2MRIY1Twy55uZYW4dqqLXOXXg==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.7.0", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g2": { + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/@antv/g2/-/g2-5.4.8.tgz", + "integrity": "sha512-IvgIpwmT4M5/QAd3Mn2WiHIDeBqFJ4WA2gcZhRRSZuZ2KmgCqZWZwwIT0hc+kIGxwYeDoCQqf//t6FMVu3ryBg==", + "license": "MIT", + "dependencies": { + "@antv/component": "^2.1.9", + "@antv/coord": "^0.4.7", + "@antv/event-emitter": "^0.1.3", + "@antv/expr": "^1.0.2", + "@antv/g": "^6.1.24", + "@antv/g-canvas": "^2.0.43", + "@antv/g-plugin-dragndrop": "^2.0.35", + "@antv/scale": "^0.5.1", + "@antv/util": "^3.3.10", + "@antv/vendor": "^1.0.11", + "flru": "^1.0.2", + "pdfast": "^0.2.0" + } + }, + "node_modules/@antv/g2-extension-plot": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@antv/g2-extension-plot/-/g2-extension-plot-0.2.2.tgz", + "integrity": "sha512-KJXCXO7as+h0hDqirGXf1omrNuYzQmY3VmBmp7lIvkepbQ7sz3pPwy895r1FWETGF3vTk5UeFcAF5yzzBHWgbw==", + "dependencies": { + "@antv/g2": "^5.1.8", + "@antv/util": "^3.3.5", + "@antv/vendor": "^1.0.10" + } + }, + "node_modules/@antv/g6": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@antv/g6/-/g6-5.1.1.tgz", + "integrity": "sha512-50bXxMUf4mChyOv4ePVeWZLwotih9VunKfp0a++Wofv/wCyY8fb9+CV2wouIBCOZnd5ydBRA4NNaX9yLJzqa2w==", + "license": "MIT", + "dependencies": { + "@antv/algorithm": "^0.1.26", + "@antv/component": "^2.1.7", + "@antv/event-emitter": "^0.1.3", + "@antv/g": "^6.1.28", + "@antv/g-canvas": "^2.0.48", + "@antv/g-plugin-dragndrop": "^2.0.38", + "@antv/graphlib": "^2.0.4", + "@antv/hierarchy": "^0.7.1", + "@antv/layout": "^2.0.0", + "@antv/util": "^3.3.11", + "bubblesets-js": "^2.3.4" + } + }, + "node_modules/@antv/g6-extension-react": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@antv/g6-extension-react/-/g6-extension-react-0.2.7.tgz", + "integrity": "sha512-X/zxGiL/kyJ+5xteX1+P2mI07oLw+zfvKcIHxfynL7IGCQCwQ6q91LkJaOlSDTuWhNRXwnwJ4Cf2Nt/9Dhq5Dg==", + "license": "MIT", + "dependencies": { + "@antv/g": "^6.1.24", + "@antv/g-svg": "^2.0.38" + }, + "peerDependencies": { + "@antv/g6": "^5.1.0", + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@antv/graphin": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@antv/graphin/-/graphin-3.0.5.tgz", + "integrity": "sha512-V/j8R8Ty44wUqxVIYLdpPuIO8WWCTIVq1eBJg5YRunL5t5o5qAFpC/qkQxslbBMWyKdIH0oWBnvHA74riGi7cw==", + "license": "MIT", + "dependencies": { + "@antv/g6": "^5.0.28" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.1.0", + "react-dom": "^18.0.0 || ^19.1.0" + } + }, + "node_modules/@antv/graphlib": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@antv/graphlib/-/graphlib-2.0.4.tgz", + "integrity": "sha512-zc/5oQlsdk42Z0ib1mGklwzhJ5vczLFiPa1v7DgJkTbgJ2YxRh9xdarf86zI49sKVJmgbweRpJs7Nu5bIiwv4w==", + "license": "MIT", + "dependencies": { + "@antv/event-emitter": "^0.1.3" + } + }, + "node_modules/@antv/hierarchy": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@antv/hierarchy/-/hierarchy-0.7.1.tgz", + "integrity": "sha512-7r22r+HxfcRZp79ZjGmsn97zgC1Iajrv0Mm9DIgx3lPfk+Kme2MG/+EKdZj1iEBsN0rJRzjWVPGL5YrBdVHchw==", + "license": "MIT" + }, + "node_modules/@antv/layout": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@antv/layout/-/layout-2.0.0.tgz", + "integrity": "sha512-aCZ3UdNc40SfT7meFV7QTADY2HCnc0DShVw56CJNTI6oExUIVU736grPuL5Dhb8/JrVaU4Y83QPN/P7KafBzlw==", + "license": "MIT", + "dependencies": { + "@antv/event-emitter": "^0.1.3", + "@antv/expr": "^1.0.2", + "@antv/graphlib": "^2.0.0", + "@antv/util": "^3.3.2", + "comlink": "^4.4.1", + "d3-force": "^3.0.0", + "d3-force-3d": "^3.0.5", + "d3-octree": "^1.0.2", + "d3-quadtree": "^3.0.1", + "dagre": "^0.8.5", + "ml-matrix": "^6.10.4", + "tslib": "^2.8.1" + } + }, + "node_modules/@antv/scale": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@antv/scale/-/scale-0.5.2.tgz", + "integrity": "sha512-rTHRAwvpHWC5PGZF/mJ2ZuTDqwwvVBDRph0Uu5PV9BXwzV7K8+9lsqGJ+XHVLxe8c6bKog5nlzvV/dcYb0d5Ow==", + "license": "MIT", + "dependencies": { + "@antv/util": "^3.3.7", + "color-string": "^1.5.5", + "fecha": "^4.2.1" + } + }, + "node_modules/@antv/util": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/@antv/util/-/util-3.3.11.tgz", + "integrity": "sha512-FII08DFM4ABh2q5rPYdr0hMtKXRgeZazvXaFYCs7J7uTcWDHUhczab2qOCJLNDugoj8jFag1djb7wS9ehaRYBg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "gl-matrix": "^3.3.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@antv/vendor": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@antv/vendor/-/vendor-1.0.11.tgz", + "integrity": "sha512-LmhPEQ+aapk3barntaiIxJ5VHno/Tyab2JnfdcPzp5xONh/8VSfed4bo/9xKo5HcUAEydko38vYLfj6lJliLiw==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.2.1", + "@types/d3-color": "^3.1.3", + "@types/d3-dispatch": "^3.0.6", + "@types/d3-dsv": "^3.0.7", + "@types/d3-ease": "^3.0.2", + "@types/d3-fetch": "^3.0.7", + "@types/d3-force": "^3.0.10", + "@types/d3-format": "^3.0.4", + "@types/d3-geo": "^3.1.0", + "@types/d3-hierarchy": "^3.1.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-path": "^3.1.0", + "@types/d3-quadtree": "^3.0.6", + "@types/d3-random": "^3.0.3", + "@types/d3-scale": "^4.0.9", + "@types/d3-scale-chromatic": "^3.1.0", + "@types/d3-shape": "^3.1.7", + "@types/d3-time": "^3.0.4", + "@types/d3-timer": "^3.0.2", + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-dispatch": "^3.0.1", + "d3-dsv": "^3.0.1", + "d3-ease": "^3.0.1", + "d3-fetch": "^3.0.1", + "d3-force": "^3.0.0", + "d3-force-3d": "^3.0.5", + "d3-format": "^3.1.0", + "d3-geo": "^3.1.1", + "d3-geo-projection": "^4.0.0", + "d3-hierarchy": "^3.1.2", + "d3-interpolate": "^3.0.1", + "d3-path": "^3.1.0", + "d3-quadtree": "^3.0.1", + "d3-random": "^3.0.1", + "d3-regression": "^1.3.10", + "d3-scale": "^4.0.2", + "d3-scale-chromatic": "^3.1.0", + "d3-shape": "^3.2.0", + "d3-time": "^3.1.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@playwright/test": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz", + "integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.62.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@rc-component/async-validator": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-6.0.0.tgz", + "integrity": "sha512-D3AGQwdyE58gmvx6waVSXJ80JGO+IY5L2O8HDnSOex7JNlzB3GuN/4hyHNTdhy2qtOhkpbIjmeAN3tL993wKbA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/cascader": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@rc-component/cascader/-/cascader-1.17.0.tgz", + "integrity": "sha512-3cVNG0zrQF1PoXq262L3wGCU+/YLEC1mGSVHDl577dQmA0ZKkXFbY6nwyXo+beCcM7buo49t24jkr+QZdL7O8w==", + "license": "MIT", + "dependencies": { + "@rc-component/select": "~1.8.0", + "@rc-component/tree": "~1.3.2", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/checkbox": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/checkbox/-/checkbox-2.0.0.tgz", + "integrity": "sha512-3CXGPpAR9gsPKeO2N78HAPOzU30UdemD6HGJoWVJOpa6WleaGB5kzZj3v6bdTZab31YuWgY/RxV3VKPctn0DwQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/collapse": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/collapse/-/collapse-1.2.0.tgz", + "integrity": "sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-3.1.1.tgz", + "integrity": "sha512-OHaCHLHszCegdXmIq2ZRIZBN/EtpT6Wm8SG/gpzLATHbVKc/avvuKi+zlOuk05FTWvgaMmpxAko44uRJ3M+2pg==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.1", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-2.0.2.tgz", + "integrity": "sha512-uiGpAlblCNlziHPwj4S4Iy/oemeuz/hR03mbiEjTCXwsqOIN3BOzsRMyDwpyO5Fm0vIEEJRUf9ZtbRLbhksuTA==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.0" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/dialog": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@rc-component/dialog/-/dialog-1.10.0.tgz", + "integrity": "sha512-eDukNlz9vNszAGv7i3zKXdxEd3wgVmNxuJijYt8zvTh17QwTu8KK/bdURRd/lU4qaMzhO1HKKmMrwOnkaw0BvQ==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.3.3", + "@rc-component/portal": "^2.1.0", + "@rc-component/util": "^1.9.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/drawer": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@rc-component/drawer/-/drawer-1.4.2.tgz", + "integrity": "sha512-1ib+fZEp6FBu+YvcIktm+nCQ+Q+qIpwpoaJH6opGr4ofh2QMq+qdr5DLC4oCf5qf3pcWX9lUWPYX652k4ini8Q==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/portal": "^2.1.3", + "@rc-component/util": "^1.9.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/dropdown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rc-component/dropdown/-/dropdown-1.0.3.tgz", + "integrity": "sha512-YTST/N6kpqpDz3IMuM/PSSZnrDpSOA6dgHv12gPA90ZTSLv2CoqkZ0+9NtwTY6BeO7dstPblSic2QJg7dSFy/g==", + "license": "MIT", + "dependencies": { + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/@rc-component/form": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@rc-component/form/-/form-1.8.5.tgz", + "integrity": "sha512-d24EYtvUOBhxEtSd/EqIu9DaMuqrWF2IRIvAFCTM6NQ/GJIYNr8DvEpUSUlv2uPxEJ0ZPwYQ+wwlGIAaiHvdrw==", + "license": "MIT", + "dependencies": { + "@rc-component/async-validator": "^6.0.0", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/image": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@rc-component/image/-/image-1.9.0.tgz", + "integrity": "sha512-khF7w7xkBH5B1bsBcI1FSUZdkyd1aqpl2eYyILCqCzzQH3XdfehGUaZTnptyaJJfs09/R5hv9jXWyazOMFIClQ==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.0.0", + "@rc-component/portal": "^2.1.2", + "@rc-component/util": "^1.10.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/input": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@rc-component/input/-/input-1.3.1.tgz", + "integrity": "sha512-iFvTUT9W+JC/MSin2aGAk8NqsVlTzcExNC9DZariON1IWirju9NoNeEk47an4Q8iHazkoVI/y1LnDi88+CPcig==", + "license": "MIT", + "dependencies": { + "@rc-component/resize-observer": "^1.1.1", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@rc-component/input-number": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@rc-component/input-number/-/input-number-1.6.2.tgz", + "integrity": "sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==", + "license": "MIT", + "dependencies": { + "@rc-component/mini-decimal": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mentions": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@rc-component/mentions/-/mentions-1.10.0.tgz", + "integrity": "sha512-CI1njYUVY0NjHtLhNoVmXlJyy568Sfep9Wsak6vmGjtT6uazx98djGYlCXz2xkHhEm73g91Y3MTvzUyE5avI7w==", + "license": "MIT", + "dependencies": { + "@rc-component/input": "~1.3.0", + "@rc-component/menu": "~1.4.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/menu": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rc-component/menu/-/menu-1.4.1.tgz", + "integrity": "sha512-3GsVRoQ4cnF/AoIQ4P+Z1haBfgfBPQfLT1RJY3Nu4DzOnheTslfCiGSPj7bv/cLj5sW5pHqN25dDXGP3JELAlQ==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/overflow": "^1.0.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.4.tgz", + "integrity": "sha512-xiuXcaCwyOWpD8a8scdExFl+bntNphAW8XeenL1ig2en0AAZY0Pcp4pC0dI22qJ+NvxKn9RoNIoRdqYU3BLH4w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/motion": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@rc-component/motion/-/motion-1.3.3.tgz", + "integrity": "sha512-Xh3IszxvlSv3/PLYFyC2UZi9LNB83yOnkB/LNmRzaypZLvkhqUIPS7MQpGZcCMWrNsXV2p6YTSWbSGvFpEle9A==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-2.0.1.tgz", + "integrity": "sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/notification": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@rc-component/notification/-/notification-2.0.7.tgz", + "integrity": "sha512-nqZzpf6BPdaj+3ILx7si79LLmqPKyUmQoXa+/9gg0SkH0v1DbD66oJgRMSBEVnd/zUT3D4gwxWIHUKebYf2ZXQ==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.11.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/overflow": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/overflow/-/overflow-1.0.1.tgz", + "integrity": "sha512-syfmgAABaHCnCDzPwHZ/2tuvIcpOO3jefYZMmfkN+pmo8HKTzsfhS57vxo4ksPdN0By+uWVJhJWNFozNBxi2eA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/pagination": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/pagination/-/pagination-1.4.0.tgz", + "integrity": "sha512-CW1g7P9V8u+e8JQdUsl2RWg+GCsoee0mtJjZUCCxn/vb3jzOwDKm6hAdwddHCVBfWJ58eGUBZz3IvnU8rRktjw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/picker": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@rc-component/picker/-/picker-1.11.0.tgz", + "integrity": "sha512-6qXGKtoJvO8sUd17m5cyNEbEJub0zflCHnaZTBBmj63DPRZYc0WEHN8rp6hFSl+yMCJS/dJY5G+1fQ8bLCuD7A==", + "license": "MIT", + "dependencies": { + "@rc-component/overflow": "^1.0.0", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/trigger": "^3.6.15", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=12.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/@rc-component/portal": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-2.2.1.tgz", + "integrity": "sha512-ck+r1kW/JSv0wxPji3KN2ss9K6Z0qqwusw/mf/0JobXhZ8hC2ejZwCJObW/SvDi0uhA0VzmCnx0CaCci95tcmA==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=12.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/progress": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rc-component/progress/-/progress-1.0.2.tgz", + "integrity": "sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-2.0.0.tgz", + "integrity": "sha512-aAv3QhPP1xyafuTZOxub6a54pCeBnN3IwQkpETrBtthq4BL5IgxnCbuoBWPDpdLw1y1j6BgBUCAKV92+yX06Dw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/rate": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/rate/-/rate-1.0.1.tgz", + "integrity": "sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/resize-observer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/resize-observer/-/resize-observer-1.1.2.tgz", + "integrity": "sha512-t/Bb0W8uvL4PYKAB3YcChC+DlHh0Wt5kM7q/J+0qpVEUMLe7Hk5zuvc9km0hMnTFPSx5Z7Wu/fzCLN6erVLE8Q==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/segmented": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@rc-component/segmented/-/segmented-1.3.0.tgz", + "integrity": "sha512-5J/bJ01mbDnoA6P/FW8SxUvKn+OgUSTZJPzCNnTBntG50tzoP7DydGhqxp7ggZXZls7me3mc2EQDXakU3iTVFg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@rc-component/select": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@rc-component/select/-/select-1.8.2.tgz", + "integrity": "sha512-HQ9zuYqjfZTlcEMWlU1GAPBajd2OHIMVHyjZSGVTCVARwkfCgvXZMTEn0cduy3L+ejAKkaZluOQvxovZoaJaQw==", + "license": "MIT", + "dependencies": { + "@rc-component/overflow": "^1.0.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.11.1", + "@rc-component/virtual-list": "^1.2.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/slider": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/slider/-/slider-1.1.1.tgz", + "integrity": "sha512-LSzgWGYDgeCDgR4r1XlU29gbYws6HpLnvJd/uMhLeW/vQgxldeR+Wb4uzHDCHiYEbr1bnEHWdjkPxjJRHxuiig==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/steps": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@rc-component/steps/-/steps-1.2.2.tgz", + "integrity": "sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/switch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rc-component/switch/-/switch-1.0.3.tgz", + "integrity": "sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/table": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@rc-component/table/-/table-1.10.4.tgz", + "integrity": "sha512-HwoTnrwc29zeoXkXGhWqzJh8FIibGUxi1jM4LtoSzmR9d5Vv5osUQpZxnXKBP8iOCvyD6BQzZm1nXJRcnrxpAg==", + "license": "MIT", + "dependencies": { + "@rc-component/context": "^2.0.1", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.11.1", + "@rc-component/virtual-list": "^1.0.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/tabs": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@rc-component/tabs/-/tabs-1.11.0.tgz", + "integrity": "sha512-hA/drZYOVa/MMIb4M2fWf3yaTyTG4qVuIABmghvEhyfw2nBob5VTH69lMCDjSVKmgODjO6nWlCV+gVn3xBrj5Q==", + "license": "MIT", + "dependencies": { + "@rc-component/dropdown": "~1.0.0", + "@rc-component/menu": "~1.4.0", + "@rc-component/motion": "^1.1.3", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tooltip": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/tooltip/-/tooltip-1.4.0.tgz", + "integrity": "sha512-8Rx5DCctIlLI4raR0I0xHjVTf1aF48+gKCNeAAo5bmF5VoR5YED+A/XEqzXv9KKqrJDRcd3Wndpxh2hyzrTtSg==", + "license": "MIT", + "dependencies": { + "@rc-component/trigger": "^3.7.1", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-2.4.0.tgz", + "integrity": "sha512-aui4r4TqmTzwaBgcQxHYep8kM8PTjZFufjokObpy35KfFeZ0k9ArquWFZqegQlH24P14t+F0qO0mGTgzlav1yg==", + "license": "MIT", + "dependencies": { + "@rc-component/portal": "^2.2.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.7.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tree": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rc-component/tree/-/tree-1.3.2.tgz", + "integrity": "sha512-bJFj46wEkpBPnWyTm18XmgAgNQ/4YvprxMOPPY2a6rmhGJYxLuNKEFiL5Qej4Qctu9wHJm8WW+v2SYskafE0kA==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.0.0", + "@rc-component/util": "^1.11.1", + "@rc-component/virtual-list": "^1.2.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/tree-select": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@rc-component/tree-select/-/tree-select-1.11.0.tgz", + "integrity": "sha512-EhS0X0wtUhBfK4S5TlpSY3MR9ndPMGgujtt1PJW3Ej+ToAlnS/6ohYURtCoXBYGqazUwHmgQGVUDsfpVwhWPkg==", + "license": "MIT", + "dependencies": { + "@rc-component/select": "~1.8.0", + "@rc-component/tree": "~1.3.2", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/trigger": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.10.1.tgz", + "integrity": "sha512-mXlDN0IXdtV8Yqqm8195ECCyrbmfvvfKvwVvSlH0+qvKD6BUF8gRhEjSy0FOcD1+CcDRHgTiX99LoxfQrmh3Cw==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.3.3", + "@rc-component/portal": "^2.2.1", + "@rc-component/resize-observer": "^1.1.2", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/upload": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/upload/-/upload-1.1.1.tgz", + "integrity": "sha512-GvYWSKeaJTOxxC5p6+nOSadzfvXA1h8C/iHFPFZX+szH3JUXrvs+DLiW8YUTBgvMh8m63mJeHrlYlJzAlg+pDA==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/util": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.12.0.tgz", + "integrity": "sha512-AEjPL8JVdohIITaiXokyjL9WQ6tKWWjAYK9QU16tGNE9JaQABBQy+hA4H2Lup5MgXy9yY3iLrbZJheuU13hTdQ==", + "license": "MIT", + "dependencies": { + "is-mobile": "^5.0.0", + "react-is": "^19.2.7" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/virtual-list": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/virtual-list/-/virtual-list-1.4.0.tgz", + "integrity": "sha512-qoyNStkTJQDezPjBibGA5HNxS9NiKJvemD1bLp7qfyxDlwy7ofPLUP0ZqJ47hR8AKcFaizd0AP/7QWLTLpudKQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^8.0.0", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/virtual-list/node_modules/@babel/runtime": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-8.0.0.tgz", + "integrity": "sha512-sL6cvO2IfkSu/iU+zs2S/w01B7A8V7suXSIKEN4hPFFdZoiPGxrj5pAG0lCaqLWiEIrjKzdznIWuaLcxPR53qw==", + "license": "MIT" + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz", + "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.4.tgz", + "integrity": "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/echarts": { + "version": "4.9.22", + "resolved": "https://registry.npmmirror.com/@types/echarts/-/echarts-4.9.22.tgz", + "integrity": "sha512-7Fo6XdWpoi8jxkwP7BARUOM7riq8bMhmsCtSG8gzUcJmFhLo387tihoBYS/y5j7jl3PENT5RxeWZdN9RiwO7HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/zrender": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@types/zrender": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/@types/zrender/-/zrender-4.0.6.tgz", + "integrity": "sha512-1jZ9bJn2BsfmYFPBHtl5o3uV+ILejAtGrDcYSpT4qaVKEI/0YY+arw3XHU04Ebd8Nca3SQ7uNcLaqiL+tTFVMg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/antd": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/antd/-/antd-6.5.1.tgz", + "integrity": "sha512-VZVVF9zYI6S0NHqboVhCoY9Iiqj6dphW1NPB+sEaAf2HuIQ0haXWXj7ZvAXTRDzusktV6+cvvrSZEdRi4twATg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.1", + "@ant-design/cssinjs": "^2.1.2", + "@ant-design/cssinjs-utils": "^2.1.2", + "@ant-design/fast-color": "^3.0.1", + "@ant-design/icons": "^6.3.2", + "@ant-design/react-slick": "~2.0.0", + "@babel/runtime": "^7.29.2", + "@rc-component/cascader": "~1.17.0", + "@rc-component/checkbox": "~2.0.0", + "@rc-component/collapse": "~1.2.0", + "@rc-component/color-picker": "~3.1.1", + "@rc-component/dialog": "~1.10.0", + "@rc-component/drawer": "~1.4.2", + "@rc-component/dropdown": "~1.0.3", + "@rc-component/form": "~1.8.5", + "@rc-component/image": "~1.9.0", + "@rc-component/input": "~1.3.1", + "@rc-component/input-number": "~1.6.2", + "@rc-component/mentions": "~1.10.0", + "@rc-component/menu": "~1.4.1", + "@rc-component/motion": "^1.3.3", + "@rc-component/mutate-observer": "^2.0.1", + "@rc-component/notification": "~2.0.7", + "@rc-component/pagination": "~1.4.0", + "@rc-component/picker": "~1.11.0", + "@rc-component/progress": "~1.0.2", + "@rc-component/qrcode": "~2.0.0", + "@rc-component/rate": "~1.0.1", + "@rc-component/resize-observer": "^1.1.2", + "@rc-component/segmented": "~1.3.0", + "@rc-component/select": "~1.8.2", + "@rc-component/slider": "~1.1.1", + "@rc-component/steps": "~1.2.2", + "@rc-component/switch": "~1.0.3", + "@rc-component/table": "~1.10.4", + "@rc-component/tabs": "~1.11.0", + "@rc-component/tooltip": "~1.4.0", + "@rc-component/tour": "~2.4.0", + "@rc-component/tree": "~1.3.2", + "@rc-component/tree-select": "~1.11.0", + "@rc-component/trigger": "^3.10.0", + "@rc-component/upload": "~1.1.1", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1", + "dayjs": "^1.11.11", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.1.tgz", + "integrity": "sha512-HYXq73DDpCtNzOmrFsm9eSwCvWCql0RzqjpDzXN9EadiLJ4DNat0nsZ/Bzmy+Ud12mb4/zKDY0cQ805ZzN+i0A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bubblesets-js": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/bubblesets-js/-/bubblesets-js-2.3.4.tgz", + "integrity": "sha512-DyMjHmpkS2+xcFNtyN00apJYL3ESdp9fTrkDr5+9Qg/GPqFmcWgGsK1akZnttE1XFxJ/VMy4DNNGMGYtmFp1Sg==", + "license": "MIT" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comlink": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.2.tgz", + "integrity": "sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g==", + "license": "Apache-2.0" + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-binarytree": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz", + "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", + "license": "MIT" + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force-3d": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz", + "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", + "license": "MIT", + "dependencies": { + "d3-binarytree": "1", + "d3-dispatch": "1 - 3", + "d3-octree": "1", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo-projection": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz", + "integrity": "sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==", + "license": "ISC", + "dependencies": { + "commander": "7", + "d3-array": "1 - 3", + "d3-geo": "1.12.0 - 3" + }, + "bin": { + "geo2svg": "bin/geo2svg.js", + "geograticule": "bin/geograticule.js", + "geoproject": "bin/geoproject.js", + "geoquantize": "bin/geoquantize.js", + "geostitch": "bin/geostitch.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-octree": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.1.0.tgz", + "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", + "license": "MIT" + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-regression": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/d3-regression/-/d3-regression-1.3.10.tgz", + "integrity": "sha512-PF8GWEL70cHHWpx2jUQXc68r1pyPHIA+St16muk/XRokETzlegj5LriNKg7o4LR0TySug4nHYPJNNRz/W+/Niw==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", + "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", + "license": "MIT", + "dependencies": { + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/echarts": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/echarts/-/echarts-6.1.0.tgz", + "integrity": "sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "6.1.0" + } + }, + "node_modules/echarts/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.395", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.395.tgz", + "integrity": "sha512-7zt9Aw+SrmxLWLN0zhaTWZQiCdryLVrYTq5R7iZakLvi2UQPYMMsROYV/2qVCzMeCiSXHwKOU+sZ4zOVVlrtKA==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/flru": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/flru/-/flru-1.0.2.tgz", + "integrity": "sha512-kWyh8ADvHBFz6ua5xYOPnUroZTT/bwWfrCeL0Wj1dzG4/YOmOcfJ99W8dOVyyynJN35rZ9aCOtHChqQovV7yog==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.15" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immer": { + "version": "11.1.15", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.15.tgz", + "integrity": "sha512-VrNANlmnWQnh5COXIIOQXM9oOJw7naGKlBT74ZOOR6lpVXc3gFEu9FJLDFcpCJ2j+NWr8TIwtWD//T6ZX6TKiQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-any-array": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-any-array/-/is-any-array-3.0.0.tgz", + "integrity": "sha512-o4h+tylWykC4BD1vaejp6gDxoM13bwW8FGuNs4yIKpj8xbBJcRxJx8vZpq0dCr7ZDEfeKjmsi/euolKhX6f/ww==", + "license": "MIT" + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, + "node_modules/is-mobile": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz", + "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ml-array-max": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ml-array-max/-/ml-array-max-2.0.0.tgz", + "integrity": "sha512-QQZ4kENwpWmyNb98UXRDFXrmtIXuXtt1+bSbda/2KA85+F+rrJP8hZk6QOkCQXM2Th9mUDYdq/PNByPdT9ID4A==", + "license": "MIT", + "dependencies": { + "is-any-array": "^3.0.0" + } + }, + "node_modules/ml-array-min": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ml-array-min/-/ml-array-min-2.0.0.tgz", + "integrity": "sha512-GRj6Ky6sW9vGL6yIjgsHmXZ9YgrdmcQ8nCxPqEGeKc6dkfYg1XDYxGFxADUjNuZyoCd5PUscWAS4N+cFaX6hFg==", + "license": "MIT", + "dependencies": { + "is-any-array": "^3.0.0" + } + }, + "node_modules/ml-array-rescale": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ml-array-rescale/-/ml-array-rescale-2.0.0.tgz", + "integrity": "sha512-2GGtKfSno94/kIloWGvpp/U5Q5vLvLrza+SAaGsLeo6Xj4mEbA6Gqx+oTfZFkxnd1grT2X007HfJNs3T5BsiVg==", + "license": "MIT", + "dependencies": { + "is-any-array": "^3.0.0", + "ml-array-max": "^2.0.0", + "ml-array-min": "^2.0.0" + } + }, + "node_modules/ml-matrix": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ml-matrix/-/ml-matrix-6.15.0.tgz", + "integrity": "sha512-wFa1v6KP8bKp+fj0nYmRs1Pb5K4zRkXGKsOvLinvILENFIADncm4XlOI+S1M7yuACMGfI6cfk0IifDgd4j5xmw==", + "license": "MIT", + "dependencies": { + "is-any-array": "^3.0.0", + "ml-array-rescale": "^2.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/pdfast": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/pdfast/-/pdfast-0.2.0.tgz", + "integrity": "sha512-cq6TTu6qKSFUHwEahi68k/kqN2mfepjkGrG9Un70cgdRRKLKY6Rf8P8uvP2NvZktaQZNF3YE7agEkLj0vGK9bA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playwright": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", + "integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.62.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz", + "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/postcss": { + "version": "8.5.22", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.22.tgz", + "integrity": "sha512-KBDEIpLrvpv16pp3K0Fw+UCoZfopFjjgeB+0tA/aaThfEE74kKDLrgg603YvOWJyg3+WYtyq3xYsQWsIyZlPqQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/react-is": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", + "license": "MIT" + }, + "node_modules/react-redux": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", + "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz", + "integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.1.tgz", + "integrity": "sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==", + "license": "MIT", + "dependencies": { + "react-router": "7.18.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz", + "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT" + }, + "node_modules/styled-components": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.5.3.tgz", + "integrity": "sha512-vAX79sfpmUerP9fsTTxoTrBDE0RuO4ahjInyWYoohNgqrdg63Ms4q6FJ/o2Fyity82NU3cujOT8Ewl9TThBdwg==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.4.0", + "css-to-react-native": "3.2.0", + "csstype": "3.2.3", + "stylis": "4.3.6" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "css-to-react-native": ">= 3.2.0", + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-native": ">= 0.68.0" + }, + "peerDependenciesMeta": { + "css-to-react-native": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/styled-components/node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "license": "MIT" + }, + "node_modules/svg-path-parser": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/svg-path-parser/-/svg-path-parser-1.1.0.tgz", + "integrity": "sha512-jGCUqcQyXpfe38R7RFfhrMyfXcBmpMNJI/B+4CE9/Unkh98UporAc461GTthv+TVDuZXsBx7/WiwJb1Oh4tt4A==", + "license": "MIT" + }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/zrender": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/zrender/-/zrender-6.1.0.tgz", + "integrity": "sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + }, + "node_modules/zrender/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..a7daee2 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,36 @@ +{ + "name": "ims-frontend", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", + "test:e2e:report": "playwright show-report" + }, + "dependencies": { + "@ant-design/charts": "^2.6.7", + "@ant-design/icons": "^6.0.0", + "@reduxjs/toolkit": "^2.0.0", + "antd": "^6.0.0", + "axios": "^1.7.0", + "dayjs": "^1.11.0", + "echarts": "^6.1.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-redux": "^9.0.0", + "react-router-dom": "^7.0.0" + }, + "devDependencies": { + "@playwright/test": "^1.62.1", + "@types/echarts": "^4.9.22", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.4.0", + "typescript": "^5.7.0", + "vite": "^7.0.0" + } +} diff --git a/frontend/scripts/seed-7d-backdate.sql b/frontend/scripts/seed-7d-backdate.sql new file mode 100644 index 0000000..48859d1 --- /dev/null +++ b/frontend/scripts/seed-7d-backdate.sql @@ -0,0 +1,46 @@ +-- generated by seed-7d.mjs +UPDATE issues SET created_at = date_trunc('day', now()) - interval '6 days' + interval '00 hours 00 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '6 days' + interval '00 hours 00 minutes 00 seconds' WHERE id = 1; +UPDATE issues SET closed_at = created_at + interval '2 days' WHERE id = 1; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '6 days' + interval '10 hours 20 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '6 days' + interval '10 hours 20 minutes 00 seconds' WHERE id = 2; +UPDATE issues SET closed_at = created_at + interval '3 days' WHERE id = 2; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '6 days' + interval '00 hours 00 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '6 days' + interval '00 hours 00 minutes 00 seconds' WHERE id = 3; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '6 days' + interval '14 hours 05 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '6 days' + interval '14 hours 05 minutes 00 seconds' WHERE id = 4; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '6 days' + interval '16 hours 40 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '6 days' + interval '16 hours 40 minutes 00 seconds' WHERE id = 5; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '5 days' + interval '09 hours 30 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '5 days' + interval '09 hours 30 minutes 00 seconds' WHERE id = 6; +UPDATE issues SET closed_at = created_at + interval '2 days' WHERE id = 6; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '5 days' + interval '11 hours 10 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '5 days' + interval '11 hours 10 minutes 00 seconds' WHERE id = 7; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '5 days' + interval '13 hours 45 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '5 days' + interval '13 hours 45 minutes 00 seconds' WHERE id = 8; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '5 days' + interval '15 hours 20 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '5 days' + interval '15 hours 20 minutes 00 seconds' WHERE id = 9; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '5 days' + interval '17 hours 55 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '5 days' + interval '17 hours 55 minutes 00 seconds' WHERE id = 10; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '4 days' + interval '09 hours 00 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '4 days' + interval '09 hours 00 minutes 00 seconds' WHERE id = 11; +UPDATE issues SET closed_at = created_at + interval '2 days' WHERE id = 11; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '4 days' + interval '10 hours 15 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '4 days' + interval '10 hours 15 minutes 00 seconds' WHERE id = 12; +UPDATE issues SET closed_at = created_at + interval '3 days' WHERE id = 12; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '4 days' + interval '12 hours 30 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '4 days' + interval '12 hours 30 minutes 00 seconds' WHERE id = 13; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '4 days' + interval '14 hours 50 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '4 days' + interval '14 hours 50 minutes 00 seconds' WHERE id = 14; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '4 days' + interval '16 hours 25 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '4 days' + interval '16 hours 25 minutes 00 seconds' WHERE id = 15; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '3 days' + interval '09 hours 10 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '3 days' + interval '09 hours 10 minutes 00 seconds' WHERE id = 16; +UPDATE issues SET closed_at = created_at + interval '2 days' WHERE id = 16; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '3 days' + interval '10 hours 40 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '3 days' + interval '10 hours 40 minutes 00 seconds' WHERE id = 17; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '3 days' + interval '13 hours 20 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '3 days' + interval '13 hours 20 minutes 00 seconds' WHERE id = 18; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '3 days' + interval '15 hours 35 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '3 days' + interval '15 hours 35 minutes 00 seconds' WHERE id = 19; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '3 days' + interval '17 hours 10 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '3 days' + interval '17 hours 10 minutes 00 seconds' WHERE id = 20; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '2 days' + interval '09 hours 25 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '2 days' + interval '09 hours 25 minutes 00 seconds' WHERE id = 21; +UPDATE issues SET closed_at = created_at + interval '1 days' WHERE id = 21; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '2 days' + interval '10 hours 50 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '2 days' + interval '10 hours 50 minutes 00 seconds' WHERE id = 22; +UPDATE issues SET closed_at = created_at + interval '2 days' WHERE id = 22; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '2 days' + interval '12 hours 00 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '2 days' + interval '12 hours 00 minutes 00 seconds' WHERE id = 23; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '2 days' + interval '14 hours 30 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '2 days' + interval '14 hours 30 minutes 00 seconds' WHERE id = 24; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '2 days' + interval '16 hours 05 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '2 days' + interval '16 hours 05 minutes 00 seconds' WHERE id = 25; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '1 days' + interval '09 hours 15 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '1 days' + interval '09 hours 15 minutes 00 seconds' WHERE id = 26; +UPDATE issues SET closed_at = created_at + interval '1 days' WHERE id = 26; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '1 days' + interval '10 hours 30 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '1 days' + interval '10 hours 30 minutes 00 seconds' WHERE id = 27; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '1 days' + interval '11 hours 50 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '1 days' + interval '11 hours 50 minutes 00 seconds' WHERE id = 28; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '1 days' + interval '14 hours 10 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '1 days' + interval '14 hours 10 minutes 00 seconds' WHERE id = 29; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '1 days' + interval '16 hours 45 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '1 days' + interval '16 hours 45 minutes 00 seconds' WHERE id = 30; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '0 days' + interval '09 hours 05 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '0 days' + interval '09 hours 05 minutes 00 seconds' WHERE id = 31; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '0 days' + interval '10 hours 35 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '0 days' + interval '10 hours 35 minutes 00 seconds' WHERE id = 32; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '0 days' + interval '14 hours 20 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '0 days' + interval '14 hours 20 minutes 00 seconds' WHERE id = 33; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '0 days' + interval '16 hours 00 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '0 days' + interval '16 hours 00 minutes 00 seconds' WHERE id = 34; +UPDATE issues SET created_at = date_trunc('day', now()) - interval '0 days' + interval '17 hours 30 minutes 00 seconds', updated_at = date_trunc('day', now()) - interval '0 days' + interval '17 hours 30 minutes 00 seconds' WHERE id = 35; +UPDATE issue_logs l SET created_at = i.created_at + (rn.rn * interval '30 minutes') FROM issues i, (SELECT id, row_number() OVER (PARTITION BY issue_id ORDER BY id) rn FROM issue_logs) rn WHERE l.id = rn.id AND l.issue_id = i.id; diff --git a/frontend/scripts/seed-7d.mjs b/frontend/scripts/seed-7d.mjs new file mode 100644 index 0000000..f4edf39 --- /dev/null +++ b/frontend/scripts/seed-7d.mjs @@ -0,0 +1,170 @@ +import { writeFileSync } from 'node:fs' +import { join, dirname } from 'node:path' +import { fileURLToPath } from 'node:url' + +const BASE = 'http://localhost:8080/api/v1' + +async function api(path, options = {}) { + const res = await fetch(`${BASE}${path}`, options) + const text = await res.text() + let json = null + try { json = JSON.parse(text) } catch { /* empty body */ } + if (!res.ok) { + throw new Error(`${options.method || 'GET'} ${path} -> ${res.status} ${text}`) + } + return json +} + +function auth(token) { + return { 'content-type': 'application/json', authorization: `Bearer ${token}` } +} + +// deadline 相对天数(正值未来/负值过去),null 表示无截止日期 +const dl = (n) => { + if (n === null || n === undefined) return null + const d = new Date(Date.now() + n * 86400000) + const pad = (x) => String(x).padStart(2, '0') + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}` +} + +// daysAgo: 0=今天, 6=6天前;time: 当天创建时刻;closedDaysAfter: 关闭距创建天数 +const issues = [ + // ============ D-6(6 天前)============ + { title: '用户注册流程中重复邮箱校验失败提示不明确', description: '重复邮箱注册时仅提示「注册失败」无具体原因,需明确提示邮箱已被占用。', status: 'closed', priority: 'medium', phase: '基本设计', subProject: '用户管理子系统', category: '功能缺陷', impactLevel: '一般', impactScope: '仅本页面', assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: dl(-3), daysAgo: 6, time: '00:00:00', closedDaysAfter: 2 }, + { title: '接口鉴权异常返回状态码与文档不一致', description: 'Token 过期时接口返回 403,而接口文档约定为 401,前端异常处理无法正确触发刷新。', status: 'closed', priority: 'high', phase: '详细设计', subProject: '权限控制子系统', category: '安全漏洞', impactLevel: '较高', impactScope: '影响全部用户', assigneeId: 3, reviewerId: 1, departmentId: 2, deadline: dl(-4), daysAgo: 6, time: '10:20', closedDaysAfter: 3 }, + { title: '大屏报表在高峰时段刷新导致页面卡死(已逾期)', description: '数据大屏每分钟自动刷新,高峰时段后端响应慢,前端堆叠请求导致页面卡死,属逾期紧急问题。', status: 'pending', priority: 'urgent', phase: '综合测试 (ST)', subProject: '数据报表子系统', category: '性能问题', impactLevel: '严重', impactScope: '影响全部用户', assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: dl(-6), daysAgo: 6, time: '00:00:00' }, + { title: '用户批量导入时角色字段被忽略', description: '批量导入用户时角色列数据未写入,导入后全部用户无角色权限。', status: 'in_progress', priority: 'medium', phase: '单体测试', subProject: '用户管理子系统', category: '功能缺陷', impactLevel: '一般', impactScope: '仅本页面', assigneeId: 3, reviewerId: 1, departmentId: 3, deadline: dl(2), daysAgo: 6, time: '14:05' }, + { title: '报表导出按钮在移动端被遮挡', description: '移动端打开报表页,导出按钮被底部导航遮挡,需调整响应式布局。', status: 'draft', priority: 'low', phase: '基本设计', subProject: '数据报表子系统', category: 'UI/UX问题', impactLevel: '轻微', impactScope: '仅本页面', assigneeId: 2, reviewerId: 1, departmentId: 4, deadline: null, daysAgo: 6, time: '16:40' }, + // ============ D-5(5 天前)============ + { title: '角色权限矩阵说明文档缺少新增权限项', description: '新增「报表导出」权限后,角色权限矩阵说明文档未同步更新。', status: 'closed', priority: 'low', phase: '详细设计', subProject: '权限控制子系统', category: '文档错误', impactLevel: '轻微', impactScope: '仅本页面', assigneeId: 3, reviewerId: 1, departmentId: 3, deadline: dl(-3), daysAgo: 5, time: '09:30', closedDaysAfter: 2 }, + { title: '用户停用后再启用,登录时提示账号不存在', description: '停用再启用的用户登录时被缓存系统判定不存在,需清理登录缓存或调整查询逻辑。', status: 'pending_confirm', priority: 'high', phase: '结合测试 (IT)', subProject: '用户管理子系统', category: '功能缺陷', impactLevel: '较高', impactScope: '影响部分模块', assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: dl(1), daysAgo: 5, time: '11:10' }, + { title: '报表筛选条件切换时偶发白屏', description: '快速切换筛选条件时偶发白屏,前端异常未捕获,需增加错误边界。', status: 'in_progress', priority: 'medium', phase: '单体测试', subProject: '数据报表子系统', category: '功能缺陷', impactLevel: '一般', impactScope: '仅本页面', assigneeId: 3, reviewerId: 1, departmentId: 2, deadline: dl(2), daysAgo: 5, time: '13:45', attachments: ['report-white-screen.log'] }, + { title: '密码重置链接可在过期后仍被使用', description: '密码重置链接生成 30 分钟后仍可使用,安全窗口过长,需收紧有效期并校验。', status: 'pending', priority: 'urgent', phase: '综合测试 (ST)', subProject: '权限控制子系统', category: '安全漏洞', impactLevel: '严重', impactScope: '影响全部用户', assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: dl(-4), daysAgo: 5, time: '15:20' }, + { title: '个人中心头像上传后不即时刷新', description: '上传头像成功后页面仍显示旧头像,需刷新浏览器才更新。', status: 'draft', priority: 'medium', phase: '基本设计', subProject: '用户管理子系统', category: 'UI/UX问题', impactLevel: '一般', impactScope: '仅本页面', assigneeId: 3, reviewerId: 1, departmentId: 4, deadline: null, daysAgo: 5, time: '17:55' }, + // ============ D-4(4 天前)============ + { title: '子账号无法继承上级部门的默认权限', description: '新建子账号后未继承所属部门默认权限,需手动逐个配置,影响开通效率。', status: 'closed', priority: 'high', phase: '详细设计', subProject: '权限控制子系统', category: '功能缺陷', impactLevel: '较高', impactScope: '影响部分模块', assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: dl(-2), daysAgo: 4, time: '09:00', closedDaysAfter: 2 }, + { title: '报表时间筛选默认值不正确导致首屏查询异常', description: '默认时间范围取到上个月而非当月,首屏数据与预期不符。', status: 'closed', priority: 'medium', phase: '单体测试', subProject: '数据报表子系统', category: '功能缺陷', impactLevel: '一般', impactScope: '仅本页面', assigneeId: 3, reviewerId: 1, departmentId: 3, deadline: dl(-1), daysAgo: 4, time: '10:15', closedDaysAfter: 3, attachments: ['report-date-filter.log'] }, + { title: '用户详情页部门信息保存后丢失', description: '编辑用户部门后保存成功,但刷新页面部门回退为原值,保存逻辑未持久化。', status: 'pending', priority: 'medium', phase: '结合测试 (IT)', subProject: '用户管理子系统', category: '功能缺陷', impactLevel: '一般', impactScope: '仅本页面', assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: dl(3), daysAgo: 4, time: '12:30' }, + { title: '权限配置界面缺少操作指引文案', description: '权限树配置界面无任何操作指引,新用户难以理解继承与覆盖规则。', status: 'in_progress', priority: 'low', phase: '基本设计', subProject: '权限控制子系统', category: '文档错误', impactLevel: '轻微', impactScope: '仅本页面', assigneeId: 3, reviewerId: 1, departmentId: 3, deadline: dl(3), daysAgo: 4, time: '14:50' }, + { title: '报表缓存命中率低导致频繁全量查询', description: '缓存 key 未细化到筛选条件,命中率低,频繁触发全量查询拖慢接口。', status: 'pending_confirm', priority: 'high', phase: '综合测试 (ST)', subProject: '数据报表子系统', category: '性能问题', impactLevel: '较高', impactScope: '影响部分模块', assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: dl(-1), daysAgo: 4, time: '16:25' }, + // ============ D-3(3 天前)============ + { title: '登录日志中明文记录密码字段', description: '审计日志将密码字段明文写入,存在泄露风险,需脱敏处理。', status: 'closed', priority: 'urgent', phase: '综合测试 (ST)', subProject: '用户管理子系统', category: '安全漏洞', impactLevel: '严重', impactScope: '影响全部用户', assigneeId: 3, reviewerId: 1, departmentId: 2, deadline: dl(-1), daysAgo: 3, time: '09:10', closedDaysAfter: 2 }, + { title: '菜单权限下发后部分终端 5 分钟未生效', description: '角色菜单权限更新后,部分终端最长 5 分钟才生效,需清理网关侧权限缓存。', status: 'in_progress', priority: 'high', phase: '结合测试 (IT)', subProject: '权限控制子系统', category: '功能缺陷', impactLevel: '较高', impactScope: '影响部分模块', assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: dl(2), daysAgo: 3, time: '10:40' }, + { title: '图表导出 Excel 后公式失效', description: '报表图表导出 Excel 后数据变为静态值,原有公式/联动丢失。', status: 'pending', priority: 'medium', phase: '详细设计', subProject: '数据报表子系统', category: '功能缺陷', impactLevel: '一般', impactScope: '仅本页面', assigneeId: 3, reviewerId: 1, departmentId: 3, deadline: dl(2), daysAgo: 3, time: '13:20' }, + { title: '深色主题下校验错误提示看不清', description: '深色主题下表单校验错误文字对比度不足,难以辨认。', status: 'pending_confirm', priority: 'low', phase: '基本设计', subProject: '用户管理子系统', category: 'UI/UX问题', impactLevel: '轻微', impactScope: '仅本页面', assigneeId: 2, reviewerId: 1, departmentId: 4, deadline: dl(-1), daysAgo: 3, time: '15:35' }, + { title: '权限变更审计日志缺少操作前后对比', description: '权限变更日志只记录变更后结果,缺少变更前后对比,无法追溯误操作。', status: 'draft', priority: 'medium', phase: '详细设计', subProject: '权限控制子系统', category: '功能缺陷', impactLevel: '一般', impactScope: '影响部分模块', assigneeId: 3, reviewerId: 1, departmentId: 2, deadline: dl(4), daysAgo: 3, time: '17:10' }, + // ============ D-2(2 天前)============ + { title: '报表订阅任务偶发重复推送', description: '订阅报表在任务重试时未做幂等处理,偶发同一份报表重复推送。', status: 'closed', priority: 'medium', phase: '单体测试', subProject: '数据报表子系统', category: '功能缺陷', impactLevel: '一般', impactScope: '仅本页面', assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: dl(-1), daysAgo: 2, time: '09:25', closedDaysAfter: 1, attachments: ['subscribe-duplicate.log'] }, + { title: '用户导出接口存在越权查看他人信息风险', description: '用户导出接口未按数据权限过滤,可导出全部用户信息,已加数据权限校验。', status: 'closed', priority: 'high', phase: '结合测试 (IT)', subProject: '用户管理子系统', category: '安全漏洞', impactLevel: '较高', impactScope: '影响全部用户', assigneeId: 3, reviewerId: 1, departmentId: 2, deadline: dl(0), daysAgo: 2, time: '10:50', closedDaysAfter: 2 }, + { title: '权限变更后旧 Token 权限未即时回收(已逾期)', description: '调整角色权限后,用户已签发的旧 Token 仍持有旧权限,需在鉴权时实时校验。', status: 'in_progress', priority: 'urgent', phase: '综合测试 (ST)', subProject: '权限控制子系统', category: '安全漏洞', impactLevel: '严重', impactScope: '影响全部用户', assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: dl(-1), daysAgo: 2, time: '12:00', agent: { goal: '分析旧 Token 权限未即时回收问题并生成对应方案', action: 'approve-pending' } }, + { title: '大数据量筛选查询未走索引', description: '报表大表筛选条件未命中索引,全表扫描导致响应慢,需评估加复合索引。', status: 'pending', priority: 'medium', phase: '详细设计', subProject: '数据报表子系统', category: '性能问题', impactLevel: '一般', impactScope: '影响部分模块', assigneeId: 3, reviewerId: 1, departmentId: 3, deadline: dl(3), daysAgo: 2, time: '14:30' }, + { title: '用户操作手册缺少批量操作章节', description: '操作手册未收录用户批量导入/停用/导出说明,新员工无法按手册操作。', status: 'draft', priority: 'low', phase: '基本设计', subProject: '用户管理子系统', category: '文档错误', impactLevel: '轻微', impactScope: '仅本页面', assigneeId: 2, reviewerId: 1, departmentId: 3, deadline: null, daysAgo: 2, time: '16:05' }, + // ============ D-1(1 天前)============ + { title: '权限树刷新后折叠状态丢失', description: '刷新页面后权限树展开/折叠状态丢失,需记忆用户浏览位置。', status: 'closed', priority: 'high', phase: '综合测试 (ST)', subProject: '权限控制子系统', category: '功能缺陷', impactLevel: '较高', impactScope: '仅本页面', assigneeId: 3, reviewerId: 1, departmentId: 2, deadline: dl(0), daysAgo: 1, time: '09:15', closedDaysAfter: 1 }, + { title: '报表订阅邮件正文乱码', description: '订阅邮件正文中文显示乱码,邮件未指定 UTF-8 编码。', status: 'in_progress', priority: 'medium', phase: '结合测试 (IT)', subProject: '数据报表子系统', category: '功能缺陷', impactLevel: '一般', impactScope: '影响部分模块', assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: dl(0), daysAgo: 1, time: '10:30', agent: { goal: '分析订阅邮件乱码问题并生成修复方案', action: 'reject' } }, + { title: '验证码接口未限制调用频率(即将到期)', description: '图形验证码接口可被高频调用,存在资源耗尽与爆破风险,需限流。', status: 'in_progress', priority: 'urgent', phase: '综合测试 (ST)', subProject: '用户管理子系统', category: '安全漏洞', impactLevel: '严重', impactScope: '影响全部用户', assigneeId: 3, reviewerId: 1, departmentId: 2, deadline: dl(1), daysAgo: 1, time: '11:50' }, + { title: '角色复制时资源权限合并逻辑错误', description: '角色 A 复制给 B 时,B 原有资源权限被整体覆盖而非合并,导致权限丢失。', status: 'pending_confirm', priority: 'high', phase: '单体测试', subProject: '权限控制子系统', category: '功能缺陷', impactLevel: '较高', impactScope: '影响部分模块', assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: dl(0), daysAgo: 1, time: '14:10' }, + { title: '移动端表单在输入法弹出时遮挡提交按钮', description: '移动端输入时软键盘遮挡提交按钮,无法完成提交,需处理键盘弹出滚动。', status: 'pending', priority: 'medium', phase: '详细设计', subProject: '用户管理子系统', category: 'UI/UX问题', impactLevel: '一般', impactScope: '仅本页面', assigneeId: 3, reviewerId: 1, departmentId: 4, deadline: dl(1), daysAgo: 1, time: '16:45' }, + // ============ D0(今天)============ + { title: '权限复制操作未校验目标角色是否存在', description: '权限复制时未校验目标角色 ID 有效性,传非法 ID 返回成功但实际未复制。', status: 'in_progress', priority: 'high', phase: '结合测试 (IT)', subProject: '权限控制子系统', category: '功能缺陷', impactLevel: '较高', impactScope: '影响部分模块', assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: dl(0), daysAgo: 0, time: '09:05' }, + { title: '报表导出任务失败后无重试机制', description: '报表导出失败后任务直接终止,无重试与失败通知,需增加重试策略。', status: 'pending', priority: 'medium', phase: '单体测试', subProject: '数据报表子系统', category: '功能缺陷', impactLevel: '一般', impactScope: '仅本页面', assigneeId: 3, reviewerId: 1, departmentId: 3, deadline: dl(1), daysAgo: 0, time: '10:35' }, + { title: '页面底部固定操作栏遮挡内容', description: '详情页底部固定操作栏遮挡正文内容,滚动到底部时无法查看最后几行。', status: 'pending_confirm', priority: 'low', phase: '基本设计', subProject: '用户管理子系统', category: 'UI/UX问题', impactLevel: '轻微', impactScope: '仅本页面', assigneeId: null, reviewerId: 1, departmentId: 2, deadline: null, daysAgo: 0, time: '14:20' }, + { title: '权限模板缺少批量应用入口', description: '权限模板只能逐个应用到角色,缺少批量应用入口,操作效率低。', status: 'draft', priority: 'medium', phase: '详细设计', subProject: '权限控制子系统', category: '功能缺陷', impactLevel: '一般', impactScope: '影响部分模块', assigneeId: null, reviewerId: 1, departmentId: 2, deadline: null, daysAgo: 0, time: '16:00' }, + { title: '登录失败三次后未触发锁定策略(今日截止)', description: '连续登录失败三次后账号未被锁定,暴力破解防护缺失,今日截止需尽快整改。', status: 'pending', priority: 'urgent', phase: '综合测试 (ST)', subProject: '用户管理子系统', category: '安全漏洞', impactLevel: '严重', impactScope: '影响全部用户', assigneeId: null, reviewerId: 1, departmentId: 2, deadline: dl(0), daysAgo: 0, time: '17:30' }, +] + +const FLOW = { + draft: [], + pending: ['pending'], + in_progress: ['pending', 'in_progress'], + pending_confirm: ['pending', 'in_progress', 'pending_confirm'], + closed: ['pending', 'in_progress', 'pending_confirm', 'closed'], +} + +const ATTACH_CONTENT = { + 'report-white-screen.log': '2026-08-0X 13:47:32 ERROR Uncaught TypeError: Cannot read properties of undefined (reading map)', + 'report-date-filter.log': '2026-08-0X 10:16:01 WARN default date range resolved to 2026-06-01 ~ 2026-06-30', + 'subscribe-duplicate.log': '2026-08-0X 09:26:40 ERROR duplicated push job#7821 retried 2 times', +} + +async function main() { + const login = await api('/auth/login', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ username: 'admin', password: 'Admin@2026' }), + }) + const token = login.data.accessToken + console.log('登录成功\n') + + const sql = ['-- generated by seed-7d.mjs'] + const created = [] + + for (const [i, it] of issues.entries()) { + const num = String(i + 1).padStart(2, '0') + const { status, daysAgo, time, closedDaysAfter, attachments, agent, ...payload } = it + const res = await api('/issues', { + method: 'POST', + headers: auth(token), + body: JSON.stringify(payload), + }) + const issue = res.data + console.log(`[${num}] 创建 ${issue.issueNo} (D-${daysAgo} ${time}) - ${issue.title}`) + + for (const s of FLOW[status] || []) { + await api(`/issues/${issue.id}/status`, { + method: 'PATCH', + headers: auth(token), + body: JSON.stringify({ status: s, remark: '7天数据流转' }), + }) + } + + if (attachments && attachments.length) { + for (const name of attachments) { + const fd = new FormData() + fd.append('file', new Blob([ATTACH_CONTENT[name] || name], { type: 'text/plain' }), name) + await api(`/issues/${issue.id}/attachments`, { method: 'POST', headers: { authorization: `Bearer ${token}` }, body: fd }) + console.log(` 上传附件 ${name}`) + } + } + + if (agent) { + const exec = await api('/agent/execute', { + method: 'POST', + headers: auth(token), + body: JSON.stringify({ issueId: issue.id, goal: agent.goal }), + }) + const planId = exec.data.planId + console.log(` Agent 执行 -> plan#${planId}`) + if (agent.action === 'reject') { + await api(`/agent/approval/${planId}/reject`, { + method: 'POST', + headers: auth(token), + body: JSON.stringify({ comment: '方案不适用,请人工处理' }), + }) + console.log(` Agent 已驳回 plan#${planId}`) + } else if (agent.action === 'approve-pending') { + console.log(` Agent 审批保持 pending`) + } + } + + // 回填 created_at(今天 - daysAgo 天 + time 时刻) + const [hh, mm, ss = '00'] = time.split(':') + sql.push(`UPDATE issues SET created_at = date_trunc('day', now()) - interval '${daysAgo} days' + interval '${hh} hours ${mm} minutes ${ss} seconds', updated_at = date_trunc('day', now()) - interval '${daysAgo} days' + interval '${hh} hours ${mm} minutes ${ss} seconds' WHERE id = ${issue.id};`) + if (closedDaysAfter !== undefined) { + sql.push(`UPDATE issues SET closed_at = created_at + interval '${closedDaysAfter} days' WHERE id = ${issue.id};`) + } + created.push(issue) + } + + // 回填 issue_logs.created_at(按创建时刻逐条 +30 分钟错开) + sql.push(`UPDATE issue_logs l SET created_at = i.created_at + (rn.rn * interval '30 minutes') FROM issues i, (SELECT id, row_number() OVER (PARTITION BY issue_id ORDER BY id) rn FROM issue_logs) rn WHERE l.id = rn.id AND l.issue_id = i.id;`) + + const outPath = join(dirname(fileURLToPath(import.meta.url)), 'seed-7d-backdate.sql') + writeFileSync(outPath, sql.join('\n') + '\n', 'utf8') + + console.log(`\n完成:共创建 ${created.length} 条 7 天数据`) + console.log(`回填 SQL 已生成:${outPath}`) + console.log(`请在 WSL 执行:docker exec -i ims-postgres psql -U ims -d ims -v ON_ERROR_STOP=1 < /mnt/c/Users/NB-070/Desktop/work2/ims-master/frontend/scripts/seed-7d-backdate.sql`) +} + +main().catch((e) => { console.error('失败:', e.message); process.exit(1) }) diff --git a/frontend/scripts/seed-demo.mjs b/frontend/scripts/seed-demo.mjs new file mode 100644 index 0000000..a9c71ea --- /dev/null +++ b/frontend/scripts/seed-demo.mjs @@ -0,0 +1,173 @@ +const BASE = 'http://localhost:8080/api/v1' + +async function api(path, options = {}) { + const res = await fetch(`${BASE}${path}`, options) + const text = await res.text() + let json = null + try { json = JSON.parse(text) } catch { /* empty body */ } + if (!res.ok) { + throw new Error(`${options.method || 'GET'} ${path} -> ${res.status} ${text}`) + } + return json +} + +function auth(token) { + return { 'content-type': 'application/json', authorization: `Bearer ${token}` } +} + +async function main() { + const login = await api('/auth/login', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ username: 'admin', password: 'Admin@2026' }), + }) + const token = login.data.accessToken + console.log('登录成功\n') + + const daysFromNow = (n) => { + const d = new Date(Date.now() + n * 86400000) + const pad = (x) => String(x).padStart(2, '0') + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}` + } + + const issues = [ + { + title: '【演示】登录页面白屏无法进入系统', + description: '输入正确账号密码点击登录后,页面白屏无任何响应,控制台报 React 渲染错误。', + status: 'draft', priority: 'high', phase: '测试阶段', subProject: '前端开发', + category: '界面问题', impactLevel: '严重', impactScope: '影响全部用户', + assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: daysFromNow(7), + }, + { + title: '【演示】CSV 导出中文乱码', + description: '导出指摘列表 CSV 后,用 Excel 打开中文标题显示为乱码,需在文件头加 BOM。', + status: 'pending', priority: 'medium', phase: '编码阶段', subProject: '后端开发', + category: '功能缺陷', impactLevel: '一般', impactScope: '仅本页面', + assigneeId: 3, reviewerId: 1, departmentId: 3, deadline: daysFromNow(5), + }, + { + title: '【演示】Dashboard 统计图表不渲染', + description: '工作台首页图表区域空白,ECharts 容器高度为 0,需确认图表初始化时机。', + status: 'in_progress', priority: 'high', phase: '测试阶段', subProject: '前端开发', + category: '界面问题', impactLevel: '较大', impactScope: '影响部分模块', + assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: daysFromNow(3), + attachments: [{ name: 'screenshot-dashboard-blank.png', content: 'PNG 占位:Dashboard 图表白屏截图(演示数据)' }], + }, + { + title: '【演示】Agent 审批卡点击无响应', + description: 'Agent 执行后弹出审批卡,点击批准/驳回按钮无反应,接口 500。', + status: 'in_progress', priority: 'urgent', phase: '编码阶段', subProject: '后端开发', + category: '功能缺陷', impactLevel: '严重', impactScope: '影响全部用户', + assigneeId: 3, reviewerId: 1, departmentId: 4, deadline: daysFromNow(2), + agent: { goal: '查找登录白屏相似案例并生成对应方案', action: 'approve-pending' }, + }, + { + title: '【演示】通知铃铛未读数量不更新', + description: '收到状态流转通知后,右上角铃铛未读数仍为 0,需刷新才显示。', + status: 'pending_confirm', priority: 'medium', phase: '上线阶段', subProject: '通知模块', + category: '功能缺陷', impactLevel: '一般', impactScope: '仅本页面', + assigneeId: 3, reviewerId: 1, departmentId: 2, deadline: daysFromNow(6), + }, + { + title: '【演示】附件下载返回 404', + description: '点击附件下载按钮,接口返回 404,MinIO 对象与数据库记录不一致。', + status: 'closed', priority: 'low', phase: '测试阶段', subProject: '后端开发', + category: '功能缺陷', impactLevel: '一般', impactScope: '仅本页面', + assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: daysFromNow(-3), + attachments: [{ name: 'error-log.txt', content: '2026-08-01 10:23:45 ERROR download attachment 404' }], + }, + { + title: '【演示】逾期未处理的紧急指摘', + description: '该指摘已超过整改截止日期仍未处理,需催办担当者。', + status: 'pending', priority: 'urgent', phase: '上线阶段', subProject: '数据库', + category: '数据问题', impactLevel: '严重', impactScope: '影响全部用户', + assigneeId: 3, reviewerId: 1, departmentId: 3, deadline: daysFromNow(-1), + }, + { + title: '【演示】Agent 方案被驳回回退人工', + description: 'Agent 生成方案后经审批被驳回,回退人工模式由担当者手动处理。', + status: 'in_progress', priority: 'high', phase: '编码阶段', subProject: '前端开发', + category: '需求变更', impactLevel: '较大', impactScope: '影响部分模块', + assigneeId: 2, reviewerId: 1, departmentId: 4, deadline: daysFromNow(4), + agent: { goal: '自动生成附件下载修复方案', action: 'reject' }, + }, + { + title: '【演示】CSV 导出大数据量性能', + description: '导出 1 万条指摘时接口耗时超过 30 秒,前端超时。', + status: 'closed', priority: 'medium', phase: '编码阶段', subProject: '后端开发', + category: '性能问题', impactLevel: '较大', impactScope: '影响部分模块', + assigneeId: 3, reviewerId: 1, departmentId: 3, deadline: daysFromNow(-2), + }, + { + title: '【演示】多状态流转完整链路验证', + description: '该指摘完整走完 草稿→待对应→对应中→待确认→已关闭 全链路,用于演示状态机与流转日志。', + status: 'closed', priority: 'medium', phase: '需求阶段', subProject: '业务验证', + category: '需求变更', impactLevel: '一般', impactScope: '仅本页面', + assigneeId: 2, reviewerId: 1, departmentId: 3, deadline: daysFromNow(-5), + }, + ] + + const FLOW = { + draft: [], + pending: ['pending'], + in_progress: ['pending', 'in_progress'], + pending_confirm: ['pending', 'in_progress', 'pending_confirm'], + closed: ['pending', 'in_progress', 'pending_confirm', 'closed'], + } + + const created = [] + for (const [i, it] of issues.entries()) { + const num = String(i + 1).padStart(2, '0') + const { status, attachments, agent, ...payload } = it + const res = await api('/issues', { + method: 'POST', + headers: auth(token), + body: JSON.stringify(payload), + }) + const issue = res.data + console.log(`[${num}] 创建 ${issue.issueNo} - ${issue.title}`) + + for (const s of FLOW[status] || []) { + await api(`/issues/${issue.id}/status`, { + method: 'PATCH', + headers: auth(token), + body: JSON.stringify({ status: s, remark: '演示流转' }), + }) + console.log(` 流转 -> ${s}`) + } + + if (attachments && attachments.length) { + for (const a of attachments) { + const fd = new FormData() + fd.append('file', new Blob([a.content], { type: 'text/plain' }), a.name) + await api(`/issues/${issue.id}/attachments`, { method: 'POST', headers: { authorization: `Bearer ${token}` }, body: fd }) + console.log(` 上传附件 ${a.name}`) + } + } + + if (agent) { + const exec = await api('/agent/execute', { + method: 'POST', + headers: auth(token), + body: JSON.stringify({ issueId: issue.id, goal: agent.goal }), + }) + const planId = exec.data.planId + console.log(` Agent 执行 -> plan#${planId}`) + if (agent.action === 'approve-pending') { + console.log(` Agent 审批保持 pending(演示审批中)`) + } else if (agent.action === 'reject') { + await api(`/agent/approval/${planId}/reject`, { + method: 'POST', + headers: auth(token), + body: JSON.stringify({ comment: '方案不适用,请人工处理' }), + }) + console.log(` Agent 已驳回 plan#${planId}`) + } + } + created.push(issue) + } + + console.log(`\n完成:共创建 ${created.length} 条演示数据`) +} + +main().catch((e) => { console.error('失败:', e.message); process.exit(1) }) diff --git a/frontend/scripts/seed-testdata.mjs b/frontend/scripts/seed-testdata.mjs new file mode 100644 index 0000000..6c0d9c3 --- /dev/null +++ b/frontend/scripts/seed-testdata.mjs @@ -0,0 +1,244 @@ +const BASE = 'http://localhost:8080/api/v1' + +async function api(path, options = {}) { + const res = await fetch(`${BASE}${path}`, options) + const text = await res.text() + let json = null + try { json = JSON.parse(text) } catch { /* empty body */ } + if (!res.ok) { + throw new Error(`${options.method || 'GET'} ${path} -> ${res.status} ${text}`) + } + return json +} + +function auth(token) { + return { 'content-type': 'application/json', authorization: `Bearer ${token}` } +} + +const daysFromNow = (n) => { + const d = new Date(Date.now() + n * 86400000) + const pad = (x) => String(x).padStart(2, '0') + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}` +} + +const issues = [ + { + title: '「用户管理」批量导入用户时部门字段校验缺失', + description: '批量导入 Excel 时,部门字段为空或填写错误也能导入成功,导致用户归属部门错乱,需增加必填与有效性校验。', + status: 'draft', priority: 'high', phase: '基本设计', subProject: '用户管理子系统', + category: '功能缺陷', impactLevel: '较高', impactScope: '影响全部用户', + assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: daysFromNow(10), + }, + { + title: '权限控制子系统操作日志字段说明文档缺失', + description: '操作日志接口返回字段(requestId、clientIp、duration)缺少字段说明,开发对接困难,需补齐接口文档。', + status: 'draft', priority: 'medium', phase: '详细设计', subProject: '权限控制子系统', + category: '文档错误', impactLevel: '一般', impactScope: '仅本页面', + assigneeId: 3, reviewerId: 1, departmentId: 3, deadline: daysFromNow(12), + }, + { + title: '用户列表页在高分辨率下表格布局错位', + description: '2560x1440 分辨率下用户列表表格列宽挤压错位,操作按钮被遮挡,需调整弹性布局。', + status: 'draft', priority: 'low', phase: '基本设计', subProject: '用户管理子系统', + category: 'UI/UX问题', impactLevel: '轻微', impactScope: '影响部分模块', + assigneeId: 2, reviewerId: 1, departmentId: 4, deadline: daysFromNow(14), + }, + { + title: '报表导出超时:5万行数据接口响应超过60秒', + description: '导出 5 万行报表数据时接口耗时超过 60 秒,前端请求超时,需改为异步导出或分批流式输出。', + status: 'pending', priority: 'urgent', phase: '综合测试 (ST)', subProject: '数据报表子系统', + category: '性能问题', impactLevel: '严重', impactScope: '影响全部用户', + assigneeId: 3, reviewerId: 1, departmentId: 2, deadline: daysFromNow(-2), + }, + { + title: '越权访问:普通用户可调用管理员角色接口', + description: '使用普通账号登录后,直接访问 /admin/* 接口返回 200,权限过滤未生效,存在越权风险。', + status: 'pending', priority: 'urgent', phase: '结合测试 (IT)', subProject: '权限控制子系统', + category: '安全漏洞', impactLevel: '严重', impactScope: '影响全部用户', + assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: daysFromNow(-1), + }, + { + title: '忘记密码流程未发送重置邮件', + description: '点击「忘记密码」并提交邮箱后未收到重置邮件,邮件服务异常但无错误提示,用户无法自助找回密码。', + status: 'pending', priority: 'medium', phase: '单体测试', subProject: '用户管理子系统', + category: '功能缺陷', impactLevel: '一般', impactScope: '仅本页面', + assigneeId: 3, reviewerId: 1, departmentId: 3, deadline: daysFromNow(-1), + }, + { + title: '角色权限勾选后未实时生效,需刷新页面', + description: '修改角色权限并保存后,对应账号重新登录仍显示旧菜单,需强制刷新浏览器才能生效。', + status: 'pending', priority: 'medium', phase: '详细设计', subProject: '权限控制子系统', + category: '功能缺陷', impactLevel: '一般', impactScope: '影响部分模块', + assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: daysFromNow(5), + }, + { + title: '数据报表子系统接口文档缺少分页参数说明', + description: '报表查询接口文档未说明 page/pageSize 参数默认值与上限,前端翻页按 20 条处理与后端默认值不一致。', + status: 'pending', priority: 'low', phase: '基本设计', subProject: '数据报表子系统', + category: '文档错误', impactLevel: '轻微', impactScope: '仅本页面', + assigneeId: 3, reviewerId: 1, departmentId: 3, deadline: daysFromNow(7), + }, + { + title: '登录接口存在暴力破解风险,无验证码与限流', + description: '登录接口连续失败无次数限制、无验证码、无 IP 限流,可被脚本暴力破解弱口令账号。', + status: 'in_progress', priority: 'urgent', phase: '综合测试 (ST)', subProject: '用户管理子系统', + category: '安全漏洞', impactLevel: '严重', impactScope: '影响全部用户', + assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: daysFromNow(-3), + }, + { + title: '权限复制功能在子菜单继承时丢失父级权限', + description: '将角色 A 权限复制给角色 B 后,B 的某些子菜单有权限但父级菜单无权限,导致页面 403。', + status: 'in_progress', priority: 'high', phase: '结合测试 (IT)', subProject: '权限控制子系统', + category: '功能缺陷', impactLevel: '较高', impactScope: '影响部分模块', + assigneeId: 3, reviewerId: 1, departmentId: 2, deadline: daysFromNow(2), + agent: { goal: '分析权限复制子菜单继承丢失父级权限问题并生成对应方案', action: 'reject' }, + }, + { + title: '报表图表加载慢:首屏渲染耗时 8 秒', + description: '打开报表图表页首屏渲染需 8 秒,前端一次性拉取全部历史数据,需改为按日期范围按需加载。', + status: 'in_progress', priority: 'high', phase: '单体测试', subProject: '数据报表子系统', + category: '性能问题', impactLevel: '较高', impactScope: '影响部分模块', + assigneeId: 2, reviewerId: 1, departmentId: 3, deadline: daysFromNow(4), + }, + { + title: '修改手机号后旧 Token 仍有效,未强制下线', + description: '用户修改绑定手机号后,旧 Token 依旧有效可继续访问,存在账号被冒用风险,应在敏感信息变更后使旧 Token 失效。', + status: 'in_progress', priority: 'medium', phase: '详细设计', subProject: '用户管理子系统', + category: '安全漏洞', impactLevel: '较高', impactScope: '影响全部用户', + assigneeId: 3, reviewerId: 1, departmentId: 2, deadline: daysFromNow(6), + agent: { goal: '修改手机号后旧Token失效方案设计,检索知识库相似案例', action: 'approve-pending' }, + }, + { + title: '用户搜索输入中文时出现乱码', + description: '用户列表搜索框输入中文关键词后,接口返回数据为空,日志显示查询参数编码异常。', + status: 'in_progress', priority: 'medium', phase: '单体测试', subProject: '用户管理子系统', + category: '功能缺陷', impactLevel: '一般', impactScope: '仅本页面', + assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: daysFromNow(8), + attachments: [{ name: 'search-garbled-log.txt', content: '2026-08-06 14:32:10 WARN query keyword=[\uFFFD\uFFFD] returns 0 rows' }], + }, + { + title: '数据报表缓存未失效,修改后仍显示旧数据', + description: '修改基础数据后刷新报表,图表仍展示缓存中的旧数据,缓存 key 未包含数据更新时间。', + status: 'pending_confirm', priority: 'high', phase: '综合测试 (ST)', subProject: '数据报表子系统', + category: '功能缺陷', impactLevel: '较高', impactScope: '影响部分模块', + assigneeId: 3, reviewerId: 1, departmentId: 3, deadline: daysFromNow(1), + }, + { + title: '用户批量停用后历史操作记录被误删', + description: '批量停用用户时,误触发历史操作记录清理逻辑,导致该用户历史日志丢失,需与停用流程解耦。', + status: 'pending_confirm', priority: 'high', phase: '结合测试 (IT)', subProject: '用户管理子系统', + category: '功能缺陷', impactLevel: '较高', impactScope: '影响部分模块', + assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: daysFromNow(3), + }, + { + title: '权限树勾选父节点时子节点状态不同步', + description: '权限树中勾选父节点后,部分子节点显示为半选状态,保存后子节点权限丢失,需级联同步。', + status: 'pending_confirm', priority: 'medium', phase: '单体测试', subProject: '权限控制子系统', + category: '功能缺陷', impactLevel: '一般', impactScope: '仅本页面', + assigneeId: 3, reviewerId: 1, departmentId: 2, deadline: daysFromNow(5), + attachments: [{ name: 'screenshot-permission-tree.png', content: 'PNG 占位:权限树半选状态截图(测试数据)' }], + }, + { + title: '综合测试环境 SQL 注入漏洞:动态拼接登录查询', + description: '登录接口存在动态拼接 SQL,构造恶意用户名可绕过密码校验,已复现并完成修复,待上线验证。', + status: 'closed', priority: 'urgent', phase: '综合测试 (ST)', subProject: '用户管理子系统', + category: '安全漏洞', impactLevel: '严重', impactScope: '影响全部用户', + assigneeId: 2, reviewerId: 1, departmentId: 2, deadline: daysFromNow(-6), + }, + { + title: '用户编辑时角色下拉缺少默认选中值', + description: '编辑已有用户时,角色下拉框未回显当前角色,保存时如未重新选择会误覆盖角色,已改为编辑时预填当前角色。', + status: 'closed', priority: 'high', phase: '结合测试 (IT)', subProject: '用户管理子系统', + category: '功能缺陷', impactLevel: '较高', impactScope: '影响部分模块', + assigneeId: 3, reviewerId: 1, departmentId: 3, deadline: daysFromNow(-4), + }, + { + title: '报表页日期筛选在 Chrome 下无法选择', + description: '报表页日期范围控件在 Chrome 最新版下点击无响应,Safari 正常,需兼容第三方日期组件初始化时机。', + status: 'closed', priority: 'medium', phase: '单体测试', subProject: '数据报表子系统', + category: '功能缺陷', impactLevel: '一般', impactScope: '仅本页面', + assigneeId: 2, reviewerId: 1, departmentId: 4, deadline: daysFromNow(-2), + attachments: [{ name: 'repro-datepicker-steps.txt', content: '复现:1.打开报表页 2.点击日期框 3.Chrome v126 无弹层' }], + }, + { + title: '权限控制子系统部署说明文档版本过旧', + description: '部署文档仍为 v1.2 旧版,与实际 v2.0 配置(新增 JWT 密钥项)不一致,需同步更新部署手册。', + status: 'closed', priority: 'low', phase: '详细设计', subProject: '权限控制子系统', + category: '文档错误', impactLevel: '轻微', impactScope: '仅本页面', + assigneeId: 3, reviewerId: 1, departmentId: 3, deadline: daysFromNow(-5), + }, +] + +const FLOW = { + draft: [], + pending: ['pending'], + in_progress: ['pending', 'in_progress'], + pending_confirm: ['pending', 'in_progress', 'pending_confirm'], + closed: ['pending', 'in_progress', 'pending_confirm', 'closed'], +} + +async function main() { + const login = await api('/auth/login', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ username: 'admin', password: 'Admin@2026' }), + }) + const token = login.data.accessToken + console.log('登录成功\n') + + const created = [] + for (const [i, it] of issues.entries()) { + const num = String(i + 1).padStart(2, '0') + const { status, attachments, agent, ...payload } = it + const res = await api('/issues', { + method: 'POST', + headers: auth(token), + body: JSON.stringify(payload), + }) + const issue = res.data + console.log(`[${num}] 创建 ${issue.issueNo} - ${issue.title}`) + + for (const s of FLOW[status] || []) { + await api(`/issues/${issue.id}/status`, { + method: 'PATCH', + headers: auth(token), + body: JSON.stringify({ status: s, remark: '测试数据流转' }), + }) + console.log(` 流转 -> ${s}`) + } + + if (attachments && attachments.length) { + for (const a of attachments) { + const fd = new FormData() + fd.append('file', new Blob([a.content], { type: 'text/plain' }), a.name) + await api(`/issues/${issue.id}/attachments`, { method: 'POST', headers: { authorization: `Bearer ${token}` }, body: fd }) + console.log(` 上传附件 ${a.name}`) + } + } + + if (agent) { + const exec = await api('/agent/execute', { + method: 'POST', + headers: auth(token), + body: JSON.stringify({ issueId: issue.id, goal: agent.goal }), + }) + const planId = exec.data.planId + console.log(` Agent 执行 -> plan#${planId}`) + if (agent.action === 'approve-pending') { + console.log(` Agent 审批保持 pending(演示审批中)`) + } else if (agent.action === 'reject') { + await api(`/agent/approval/${planId}/reject`, { + method: 'POST', + headers: auth(token), + body: JSON.stringify({ comment: '方案不适用,请人工处理' }), + }) + console.log(` Agent 已驳回 plan#${planId}`) + } + } + created.push(issue) + } + + console.log(`\n完成:共创建 ${created.length} 条测试数据`) +} + +main().catch((e) => { console.error('失败:', e.message); process.exit(1) }) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..d296874 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,9 @@ +import { useRoutes } from 'react-router-dom' +import routes from './routes' + +function App() { + const element = useRoutes(routes) + return <>{element} +} + +export default App diff --git a/frontend/src/antdStatic.tsx b/frontend/src/antdStatic.tsx new file mode 100644 index 0000000..bea7f2b --- /dev/null +++ b/frontend/src/antdStatic.tsx @@ -0,0 +1,17 @@ +import { App } from 'antd' + +type Static = ReturnType + +let message: Static['message'] +let notification: Static['notification'] +let modal: Static['modal'] + +export default function AntdStatic() { + const staticFn = App.useApp() + message = staticFn.message + notification = staticFn.notification + modal = staticFn.modal + return null +} + +export { message, notification, modal } diff --git a/frontend/src/api/system.ts b/frontend/src/api/system.ts new file mode 100644 index 0000000..4226652 --- /dev/null +++ b/frontend/src/api/system.ts @@ -0,0 +1,327 @@ +import request from '../request' + +export interface PageResult { + items: T[] + total: number + page: number + pageSize: number + totalPages: number +} + +export interface UserItem { + id: number + userid: string + username: string + email?: string + departmentId?: number + departmentName?: string + isActive?: boolean + agentAutoExecute?: boolean + roleIds?: number[] + roles?: string[] + lastLoginAt?: string + createdAt?: string +} + +export interface UserPayload { + userid?: string + username: string + email?: string + password?: string + departmentId?: number + roleIds?: number[] + isActive?: boolean + agentAutoExecute?: boolean +} + +export interface DeptNode { + id: number + name: string + parentId?: number + sortOrder?: number + children?: DeptNode[] +} + +export interface PermissionItem { + id: number + code: string + name: string + resource: string +} + +export interface RoleItem { + id: number + name: string + description?: string + dataScope?: string + agentAutoExecute?: boolean + permissionIds?: number[] + createdAt?: string +} + +export interface RolePayload { + name: string + description?: string + dataScope?: string + agentAutoExecute?: boolean + permissionIds?: number[] +} + +export interface LogItem { + id: number + operator: string + action: string + resource: string + detail: string + createdAt?: string +} + +export interface LogQuery { + operator?: string + actionType?: string + keyword?: string + startTime?: string + endTime?: string + page?: number + pageSize?: number +} + +export const userApi = { + list: (params: Record) => request.get('/users', { params }), + create: (data: UserPayload) => request.post('/users', data), + update: (id: number, data: UserPayload) => request.put(`/users/${id}`, data), + updateStatus: (id: number, isActive: boolean) => + request.put(`/users/${id}/status`, null, { params: { isActive } }), + exportCsv: (params: Record) => + request.get('/users/export', { params, responseType: 'blob' }), +} + +export const deptApi = { + tree: () => request.get('/departments'), +} + +export const roleApi = { + list: () => request.get('/roles'), + permissions: () => request.get('/roles/permissions'), + create: (data: RolePayload) => request.post('/roles', data), + update: (id: number, data: RolePayload) => request.put(`/roles/${id}`, data), +} + +export const logApi = { + list: (params: LogQuery) => request.get('/logs', { params }), +} + +export interface ImportRow { + rowNo?: number + title?: string + docType?: string + phase?: string + priority?: string + deadline?: string + reviewDate?: string + subProject?: string + category?: string + impactLevel?: string + description?: string + assigneeUserid?: string + departmentName?: string + status?: string + errors?: string[] +} + +export interface ImportPreview { + total: number + validCount: number + errorCount: number + headerValid?: boolean + rows: ImportRow[] +} + +export interface ImportRecord { + id: number + fileName: string + totalCount: number + successCount: number + failCount: number + status: string + errorLog?: string + operator?: string + createdAt?: string +} + +export interface ImportSuggestion { + rowNo?: number + field?: string + fieldName?: string + original?: string + suggested?: string + reason?: string + level?: string +} + +export interface AgentValidateResponse { + usable: boolean + usableReason?: string + suggestions: ImportSuggestion[] + engine: string +} + +export const importApi = { + template: () => + request.get('/import/template', { responseType: 'blob' }), + preview: (file: File) => { + const form = new FormData() + form.append('file', file) + return request.post('/import/excel', form) + }, + aiValidate: (rows: ImportRow[]) => + request.post('/import/ai-validate', rows, { timeout: 1800000 }), + confirm: (fileName: string, rows: ImportRow[]) => + request.post('/import/confirm', { fileName, rows }), + records: (page = 1, pageSize = 20) => + request.get('/import/records', { params: { page, pageSize } }), +} + +export interface AgentConfig { + maxSteps?: number + autoExecuteHighRisk?: boolean + userRateLimit?: number +} + +export const agentApi = { + getConfig: () => request.get('/agent/config'), + updateConfig: (config: AgentConfig) => request.put('/agent/config', config), +} + +export interface PromptTemplateItem { + id: number + templateId: string + name: string + category: string + version: number + content: string + variables?: string + outputSchema?: string + isActive?: boolean + isDefault?: boolean + createdAt?: string + updatedAt?: string +} + +export interface PromptVersion { + id: number + templateId: string + version: number + content: string + changeLog?: string + createdBy?: string + createdAt?: string +} + +export interface PromptLogItem { + id: number + requestId: string + templateId: string + templateVersion: number + renderedPrompt: string + executionTimeMs?: number + llmModel?: string + modelProvider?: string + createdAt?: string +} + +export interface PromptStats { + totalTemplates: number + activeTemplates: number + totalVersions: number + totalRenders: number +} + +export const promptApi = { + list: (params: Record) => request.get('/prompts', { params }), + detail: (templateId: string) => request.get(`/prompts/${templateId}`), + create: (data: Record) => request.post('/prompts', data), + update: (templateId: string, data: Record) => + request.put(`/prompts/${templateId}`, data), + rollback: (templateId: string, version: number) => + request.post(`/prompts/${templateId}/rollback`, null, { params: { version } }), + test: (templateId: string, variables: Record) => + request.post(`/prompts/${templateId}/test`, { variables }), + versions: (templateId: string) => request.get(`/prompts/${templateId}/versions`), + logs: (page = 1, pageSize = 20) => + request.get('/prompts/logs', { params: { page, pageSize } }), + stats: () => request.get('/prompts/stats'), +} + +export interface AiConfig { + provider: string + ollamaBaseUrl?: string + ollamaChatModel?: string + ollamaEmbeddingModel?: string + deepseekModel?: string + deepseekEmbeddingModel?: string + deepseekApiKey?: string + autoFallbackEnabled?: boolean +} + +export const aiConfigApi = { + get: () => request.get('/ai/config'), + update: (config: AiConfig) => request.put('/ai/config', config), + test: () => request.post('/ai/config/test'), +} + +export interface AgentOverview { + todayExecutions: number + yesterdayExecutions: number + growthRate: number + todayPlans: number + toolTotalCount: number + toolSuccessCount: number + toolSuccessRate: number + pendingApprovals: number + latestExecutions: AgentExecution[] + trendData: { hour: string; count: number }[] +} + +export interface AgentExecution { + id: number + planId: number + toolName: string + status: string + executionTimeMs?: number + outputResult?: string + createdAt?: string +} + +export interface AgentPlanItem { + planId: number + issueId: number + issueNo: string + issueTitle: string + goal: string + status: string + requiresApproval: boolean + approvalStatus: string + approvalComment?: string + toolName?: string + toolParams?: string + approvalReason?: string + createdAt?: string +} + +export interface AgentToolItem { + name: string + description: string + isWrite: boolean +} + +export const agentOverviewApi = { + getOverview: () => request.get('/agent/overview'), + getPlans: (params: { approvalStatus?: string; page?: number; pageSize?: number }) => + request.get('/agent/plans', { params }), + approvePlan: (planId: number, comment?: string) => + request.post(`/agent/approval/${planId}/approve`, { comment }), + rejectPlan: (planId: number, comment?: string) => + request.post(`/agent/approval/${planId}/reject`, { comment }), + getTools: () => request.get('/agent/tools'), +} diff --git a/frontend/src/constants/issue.ts b/frontend/src/constants/issue.ts new file mode 100644 index 0000000..ed18100 --- /dev/null +++ b/frontend/src/constants/issue.ts @@ -0,0 +1,59 @@ +export const STATUS_LABELS: Record = { + draft: '草稿', + open: '待处理', + in_progress: '进行中', + resolved: '已解决', + verified: '已验证', + closed: '已关闭', + rejected: '已驳回' +} + +export const STATUS_COLORS: Record = { + draft: 'default', + open: 'orange', + in_progress: 'blue', + resolved: 'cyan', + verified: 'purple', + closed: 'green', + rejected: 'red' +} + +export const ACTION_LABELS: Record = { + CREATE: '创建指摘', UPDATE: '更新指摘', DELETE: '删除指摘', + STATUS_CHANGE: '状态流转', BATCH_ASSIGN: '批量分配', BATCH_NOTIFY: '批量催办' +} + +export const PRIORITY_LABELS: Record = { + urgent: '紧急', + high: '高', + medium: '中', + low: '低' +} + +export const PRIORITY_COLORS: Record = { + urgent: 'red', + high: 'orange', + medium: 'default', + low: 'gray' +} + +export const PHASE_OPTIONS = ['需求', '设计', '编码', '测试', '部署', '运维'] + +export const SUB_PROJECT_OPTIONS = ['用户管理子系统', '权限控制子系统', '数据报表子系统'] + +export const CATEGORY_OPTIONS = ['功能缺陷', 'UI/UX问题', '性能问题', '安全漏洞', '文档错误'] + +export const IMPACT_LEVEL_OPTIONS = ['高', '中', '低'] + +export const USERS = [ + { id: 1, name: '系统管理员' }, + { id: 2, name: '张三' }, + { id: 3, name: '李四' } +] + +export const DEPARTMENTS = [ + { id: 1, name: '总公司' }, + { id: 2, name: '技术部' }, + { id: 3, name: '质量部' }, + { id: 4, name: '产品部' } +] diff --git a/frontend/src/layouts/MainLayout.tsx b/frontend/src/layouts/MainLayout.tsx new file mode 100644 index 0000000..558b7d1 --- /dev/null +++ b/frontend/src/layouts/MainLayout.tsx @@ -0,0 +1,188 @@ +import { useState, useEffect } from 'react' +import { Layout, Menu, Button, Avatar, Dropdown, Badge, Space, theme, Empty } from 'antd' +import { message } from '../antdStatic' +import { + DashboardOutlined, + BugOutlined, + UploadOutlined, + RobotOutlined, + BookOutlined, + SettingOutlined, + UserOutlined, + TeamOutlined, + FileTextOutlined, + NotificationOutlined, + LogoutOutlined, + MenuFoldOutlined, + MenuUnfoldOutlined +} from '@ant-design/icons' +import { Outlet, useNavigate, useLocation } from 'react-router-dom' +import { useDispatch, useSelector } from 'react-redux' +import { logout } from '../store/slices/authSlice' +import type { RootState } from '../store' +import { listNotifications, getUnreadCount, markNotificationRead, markAllNotificationsRead } from '../services/notification' +import type { NotificationItem } from '../services/notification' + +const { Header, Sider, Content } = Layout + +const menuItems = [ + { key: '/dashboard', icon: , label: '工作台' }, + { + key: 'issue', + icon: , + label: '指摘管理', + children: [ + { key: '/issues', icon: , label: '指摘列表' }, + { key: '/issues/new', icon: , label: '新建指摘' }, + { key: '/batch-input', icon: , label: '批量录入' } + ] + }, + { key: '/ai-analysis', icon: , label: 'AI智能分析' }, + { key: '/knowledge-base', icon: , label: '知识库管理' }, + { + key: 'system', + icon: , + label: '系统管理', + children: [ + { key: '/system/users', icon: , label: '用户管理' }, + { key: '/system/roles', icon: , label: '角色权限' }, + { key: '/system/logs', icon: , label: '系统日志' }, + { key: '/system/agent-admin', icon: , label: 'Agent管理' } + ] + } +] + +export default function MainLayout() { + const [collapsed, setCollapsed] = useState(false) + const navigate = useNavigate() + const location = useLocation() + const dispatch = useDispatch() + const username = useSelector((state: RootState) => state.auth.username) + const { token: { colorBgContainer, borderRadiusLG } } = theme.useToken() + + const [unread, setUnread] = useState(0) + const [notifs, setNotifs] = useState([]) + + const loadUnread = async () => { + try { + const res: any = await getUnreadCount() + setUnread(res.data?.count || 0) + } catch { /* ignore */ } + } + + const loadNotifs = async () => { + try { + const res: any = await listNotifications(1, 20) + setNotifs(res.data?.items || []) + } catch { /* ignore */ } + } + + useEffect(() => { + loadUnread() + const timer = setInterval(loadUnread, 60000) + return () => clearInterval(timer) + }, []) + + const handleNotifOpen = (open: boolean) => { + if (open) loadNotifs() + } + + const handleNotifClick = async (item: NotificationItem) => { + try { + await markNotificationRead(item.id) + loadUnread() + } catch { /* ignore */ } + if (item.link?.startsWith('/issues')) navigate(item.link) + } + + const handleReadAll = async () => { + try { + await markAllNotificationsRead() + message.success('已全部标记为已读') + loadNotifs() + loadUnread() + } catch { /* ignore */ } + } + + const notifMenuItems = [ + { + key: 'header', + label: notifs.length ? ( +
+ 通知 + +
+ ) : ( + + ), + disabled: !notifs.length + }, + ...notifs.slice(0, 20).map((n) => ({ + key: `n-${n.id}`, + label: ( +
handleNotifClick(n)} style={{ width: 260, padding: '4px 0' }}> +
+ {n.title} + {!n.isRead && } +
+
{n.content}
+
{n.createdAt?.replace('T', ' ').slice(0, 16)}
+
+ ) + })) + ] + + const handleLogout = () => { + dispatch(logout()) + navigate('/login') + } + + const userMenuItems = [ + { key: 'logout', icon: , label: '退出登录', onClick: handleLogout } + ] + + const openKeys = location.pathname.startsWith('/system') ? ['system'] + : location.pathname.startsWith('/issue') || location.pathname.startsWith('/issues') || location.pathname.startsWith('/batch-input') ? ['issue'] + : [] + + return ( + + +
+ {collapsed ? 'IMS' : '指摘管理系统'} +
+ navigate(key)} + /> + + +
+
+ +
+ +
+
+
+ + ) +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..53f34a8 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,25 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import { Provider } from 'react-redux' +import { BrowserRouter } from 'react-router-dom' +import { App as AntdApp, ConfigProvider } from 'antd' +import zhCN from 'antd/locale/zh_CN' +import App from './App' +import AntdStatic from './antdStatic' +import { store } from './store' +import theme from './theme' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + + + + + + + +) diff --git a/frontend/src/pages/ai-analysis/index.tsx b/frontend/src/pages/ai-analysis/index.tsx new file mode 100644 index 0000000..65760da --- /dev/null +++ b/frontend/src/pages/ai-analysis/index.tsx @@ -0,0 +1,508 @@ +import { useState, useEffect, useRef, useMemo } from 'react' +import dayjs from 'dayjs' +import { + Typography, Table, Button, Modal, Form, Select, Input, InputNumber, DatePicker, + Tag, Space, Tooltip, Row, Col, Card, Tabs +} from 'antd' +import { message } from '../../antdStatic' +import { + ThunderboltOutlined, DownloadOutlined, SearchOutlined, ReloadOutlined, + EyeOutlined, RedoOutlined +} from '@ant-design/icons' +import { + aiAnalysisApi, departmentApi, issuesApi, + AiAnalysisRecord, RecordQuery, DepartmentOption, IssueListItem, + ISSUE_STATUS_OPTIONS, ISSUE_PHASE_OPTIONS, ANALYSIS_STATUS_OPTIONS, ISSUE_PRIORITY_OPTIONS +} from './services' +import AiOverviewTab from './overview' + +const { RangePicker } = DatePicker +const pageSize = 20 + +type TreeRecord = AiAnalysisRecord & { children?: TreeRecord[] } + +const buildTree = (items: AiAnalysisRecord[]): TreeRecord[] => { + const groups = new Map() + for (const r of items) { + const arr = groups.get(r.issueId) || [] + arr.push(r) + groups.set(r.issueId, arr) + } + const tree: TreeRecord[] = [] + for (const [, arr] of groups) { + arr.sort((a, b) => (b.createdAt || '').localeCompare(a.createdAt || '')) + const [head, ...rest] = arr + tree.push({ ...head, children: rest }) + } + return tree +} + +export default function AiAnalysisPage() { + const [records, setRecords] = useState([]) + const [total, setTotal] = useState(0) + const [page, setPage] = useState(1) + const [loading, setLoading] = useState(false) + const [departments, setDepartments] = useState([]) + const [query, setQuery] = useState({ page: 1, pageSize }) + const [filterForm] = Form.useForm() + + const [detail, setDetail] = useState(null) + const [detailOpen, setDetailOpen] = useState(false) + + const [generateOpen, setGenerateOpen] = useState(false) + const pollTimer = useRef(null) + + const schedulePoll = (items: AiAnalysisRecord[], q: RecordQuery) => { + if (pollTimer.current) window.clearTimeout(pollTimer.current) + const hasRunning = items.some(r => r.status === 'processing' || r.status === 'pending') + if (hasRunning) { + pollTimer.current = window.setTimeout(() => loadRecords(q), 3000) + } + } + + const loadRecords = async (q: RecordQuery) => { + setLoading(true) + try { + const res: any = await aiAnalysisApi.records(q) + const items = res.data?.items || [] + setRecords(items) + setTotal(res.data?.total || 0) + setPage(q.page) + schedulePoll(items, q) + } catch { /* ignore */ } + setLoading(false) + } + + useEffect(() => { + loadRecords(query) + return () => { if (pollTimer.current) window.clearTimeout(pollTimer.current) } + }, []) + + useEffect(() => { + departmentApi.list().then((res: any) => { + setDepartments(res.data || []) + }).catch(() => { /* ignore */ }) + }, []) + + const buildQuery = (): RecordQuery => { + const values = filterForm.getFieldsValue() + return { + page: 1, + pageSize, + id: values.id, + issueId: values.issueId, + departmentId: values.departmentId, + status: values.status, + startDate: values.dateRange?.[0]?.format('YYYY-MM-DD'), + endDate: values.dateRange?.[1]?.format('YYYY-MM-DD'), + } + } + + const handleSearch = () => { + const params = buildQuery() + setQuery(params) + loadRecords(params) + } + + const handleReset = () => { + filterForm.resetFields() + const params: RecordQuery = { page: 1, pageSize } + setQuery(params) + loadRecords(params) + } + + const handleExport = async () => { + try { + const blob: any = await aiAnalysisApi.exportRecords(query) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = 'ai-analysis.csv' + a.click() + URL.revokeObjectURL(url) + message.success('导出成功') + } catch { /* ignore */ } + } + + const handleReanalyze = async (record: AiAnalysisRecord) => { + if (!record.issueId) return + try { + const res: any = await aiAnalysisApi.batchGenerate({ issueIds: [record.issueId] }) + message.success(`已重新提交分析 (${res.data?.submitted ?? 0})`) + setTimeout(() => loadRecords(query), 1500) + } catch { /* ignore */ } + } + + const handleFeedback = async (id: number, isHelpful: boolean) => { + try { + await aiAnalysisApi.feedback(id, isHelpful) + message.success('已记录反馈') + loadRecords(query) + } catch { /* ignore */ } + } + + const statusColor: Record = { + pending: 'default', processing: 'processing', completed: 'green', failed: 'red' + } + + const treeData = useMemo(() => records.map(r => ({ ...r })), [records]) + + const columns = [ + { + title: '指摘ID', dataIndex: 'issueId', key: 'issueId', width: 90, + sorter: (a: AiAnalysisRecord, b: AiAnalysisRecord) => a.issueId - b.issueId, + render: (v: number) => {v} + }, + { + title: 'ID', key: 'id', width: 150, + sorter: (a: AiAnalysisRecord, b: AiAnalysisRecord) => (a.issueNo || '').localeCompare(b.issueNo || ''), + render: (_: any, r: AiAnalysisRecord) => ( + + {r.issueNo} + {r.status} + + ) + }, + { + title: '归属部门', dataIndex: 'departmentName', key: 'departmentName', width: 120, + sorter: (a: AiAnalysisRecord, b: AiAnalysisRecord) => (a.departmentName || '').localeCompare(b.departmentName || ''), + render: (v: string) => v || '-' + }, + { title: '提取关键词', dataIndex: 'keywords', key: 'keywords', width: 150, ellipsis: true, render: (v: string) => v || '-' }, + { title: '问题分类', dataIndex: 'category', key: 'category', width: 120, render: (v: string) => v || '-' }, + { title: '根因分析', dataIndex: 'rootCause', key: 'rootCause', width: 220, ellipsis: true, render: (v: string) => v || '-' }, + { + title: 'AI 整改建议', dataIndex: 'suggestion', key: 'suggestion', width: 300, + render: (v: string) => {v || '-'} + }, + { + title: '时间', key: 'time', width: 140, defaultSortOrder: 'descend' as const, + sorter: (a: AiAnalysisRecord, b: AiAnalysisRecord) => (a.completedAt || a.startedAt || '').localeCompare(b.completedAt || b.startedAt || ''), + render: (_: any, r: AiAnalysisRecord) => { + if (r.status === 'processing' || r.status === 'pending') { + if (!r.startedAt) return '-' + const mins = Math.max(1, Math.floor(dayjs().diff(dayjs(r.startedAt), 'minute'))) + return ( + + 开始 {dayjs(r.startedAt).format('MM-DD HH:mm:ss')} + 已运行 {mins} 分钟 + + ) + } + return r.completedAt ? `完成 ${dayjs(r.completedAt).format('MM-DD HH:mm:ss')}` : '-' + } + }, + { + title: '反馈', dataIndex: 'helpfulCount', key: 'feedback', width: 100, + sorter: (a: AiAnalysisRecord, b: AiAnalysisRecord) => (a.helpfulCount || 0) - (b.helpfulCount || 0), + render: (v: number, r: AiAnalysisRecord) => ( + v > 0 + ? 有帮助 + : + ) + }, + { + title: '操作', key: 'action', width: 90, align: 'right' as const, fixed: 'right' as const, + render: (_: any, r: AiAnalysisRecord) => ( + + + + + + + + + + + + + {/* Table */} + + loadRecords({ ...query, page: p }), + showTotal: t => `共 ${t} 条记录`, + }} + /> + + + {/* Detail Modal */} + setDetailOpen(false)} + footer={} + width={640} + > + {detail && ( +
+
指摘标题:{detail.issueTitle}
+
问题分类:{detail.category || '-'}
+
预提取关键词:{detail.extractedKeywords || '-'}
+
提取关键词:{detail.keywords || '-'}
+
根因分析:
+
{detail.rootCause || '-'}
+
AI 整改建议:
+
{detail.suggestion || '-'}
+ {detail.status === 'failed' && detail.errorMessage && ( +
+ 失败原因:{detail.errorMessage} +
+ )} +
+ 模型:{detail.modelProvider}/{detail.modelName} · 时间:{detail.createdAt} + {detail.promptTemplateId && ` · Prompt: ${detail.promptTemplateId} v${detail.promptVersion}`} +
+ + + + +
+ )} +
+ + setGenerateOpen(false)} + departments={departments} + onGenerated={() => setTimeout(() => loadRecords(query), 1500)} + /> + + ) + } + ]} /> + + ) +} + +function GenerateModal(props: { + open: boolean + onCancel: () => void + departments: DepartmentOption[] + onGenerated: () => void +}) { + const [issues, setIssues] = useState([]) + const [total, setTotal] = useState(0) + const [page, setPage] = useState(1) + const [loading, setLoading] = useState(false) + const [selected, setSelected] = useState([]) + const [generating, setGenerating] = useState(false) + const [issueForm] = Form.useForm() + + const loadIssues = async (q: { page: number; pageSize: number }) => { + setLoading(true) + const values = issueForm.getFieldsValue() + try { + const res: any = await issuesApi.list({ + page: q.page, + pageSize: q.pageSize, + status: values.status, + phase: values.phase, + departmentId: values.departmentId, + keyword: values.keyword, + startDate: values.dateRange?.[0]?.format('YYYY-MM-DD'), + endDate: values.dateRange?.[1]?.format('YYYY-MM-DD'), + }) + setIssues(res.data?.items || []) + setTotal(res.data?.total || 0) + setPage(q.page) + } catch { /* ignore */ } + setLoading(false) + } + + useEffect(() => { + if (props.open) { + setSelected([]) + issueForm.resetFields() + loadIssues({ page: 1, pageSize }) + } + }, [props.open]) + + const generate = async () => { + if (selected.length === 0) { + message.warning('请至少选择一条指摘进行 AI 分析') + return + } + setGenerating(true) + try { + const res: any = await aiAnalysisApi.batchGenerate({ issueIds: selected.map(s => s.id) }) + const count = res.data?.submitted ?? selected.length + const estimated = Math.ceil(count / 4) * 60 + message.success(`✅ 已对 ${count} 条指摘启动 AI 分析,预计约 ${estimated} 秒内完成`) + props.onGenerated() + props.onCancel() + } catch { /* ignore */ } + setGenerating(false) + } + + const statusColor: Record = { + draft: 'default', open: 'blue', in_progress: 'processing', resolved: 'green', + verified: 'cyan', closed: 'gray', rejected: 'red' + } + const priorityColor: Record = { + high: 'red', medium: 'orange', low: 'default' + } + + const issueColumns = [ + { + title: 'ID', dataIndex: 'issueNo', key: 'issueNo', width: 160, + render: (v: string) => {v} + }, + { title: '标题', dataIndex: 'title', key: 'title', ellipsis: true }, + { + title: '状态', dataIndex: 'status', key: 'status', width: 90, + render: (s: string) => {s} + }, + { + title: '优先级', dataIndex: 'priority', key: 'priority', width: 80, + render: (p: string) => {p} + }, + { title: '工程阶段', dataIndex: 'phase', key: 'phase', width: 100, render: (v: string) => v || '-' }, + { title: '对应者', dataIndex: 'assigneeName', key: 'assigneeName', width: 100, render: (v: string) => v || '-' }, + { title: '截止日期', dataIndex: 'deadline', key: 'deadline', width: 120, render: (v: string) => v ? v.slice(0, 10) : '-' }, + ] + + return ( + + 已选择 {selected.length} 条指摘 + + + + + , + ]} + title={ +
+ 选择指摘生成 AI 分析 + 可多选 +
+ } + > +
+ + ({ label: p, value: p }))} /> + + + + + + + + + + + + + +
s.id), + onChange: (_keys, rows) => setSelected(rows), + }} + pagination={{ + current: page, total, pageSize, + onChange: (p) => loadIssues({ page: p, pageSize }), + showTotal: t => `共 ${t} 条`, + }} + style={{ maxHeight: 420, overflowY: 'auto' }} + /> + + ) +} diff --git a/frontend/src/pages/ai-analysis/overview.tsx b/frontend/src/pages/ai-analysis/overview.tsx new file mode 100644 index 0000000..a2e8c7c --- /dev/null +++ b/frontend/src/pages/ai-analysis/overview.tsx @@ -0,0 +1,162 @@ +import { useState, useEffect } from 'react' +import { + Typography, Card, Row, Col, Statistic, Space +} from 'antd' +import { + ThunderboltOutlined, BarChartOutlined, + PieChartOutlined, LineChartOutlined +} from '@ant-design/icons' +import { Line, Pie, Column } from '@ant-design/charts' +import { + aiAnalysisApi, OverviewStats +} from './services' + +const ANALYSIS_LABELS: Record = { + pending: '待处理', processing: '分析中', completed: '已完成', failed: '失败' +} + +export default function AiOverviewTab() { + const [stats, setStats] = useState(null) + + const loadStats = async () => { + try { + const res: any = await aiAnalysisApi.overviewStats(14) + setStats(res.data) + } catch { /* ignore */ } + } + + useEffect(() => { + loadStats() + }, []) + + const statusCards = [ + { title: '指摘总数', value: stats?.totalIssues ?? 0, color: '#1677ff', suffix: '条' }, + { title: '已分析指摘', value: stats?.analyzedCount ?? 0, color: '#52c41a', suffix: '条' }, + { title: '未分析指摘', value: stats?.unanalyzedCount ?? 0, color: '#fa8c16', suffix: '条' }, + { title: '分析覆盖率', value: stats?.coverageRate ?? 0, color: '#722ed1', suffix: '%' }, + ] + + const lineData = (stats?.dailyTrend || []).flatMap(d => [ + { date: d.date.slice(5), type: '已完成', value: d.completed }, + { date: d.date.slice(5), type: '失败', value: d.failed }, + ]) + + const statusPieData = (stats?.statusDistribution || []).map(s => ({ + type: ANALYSIS_LABELS[s.name] || s.name, + value: s.value, + })) + + const categoryPieData = (stats?.categoryDistribution || []).map(s => ({ + type: s.name, + value: s.value, + })) + + const columnData = (stats?.departmentDistribution || []).map(s => ({ + name: s.name, + value: s.value, + })) + + const lineConfig = { + xField: 'date', + yField: 'value', + seriesField: 'type', + colorField: 'type', + height: 280, + smooth: true, + scale: { y: { nice: true } }, + axis: { y: { title: false }, x: { title: false } }, + } + + const pieConfig = { + angleField: 'value', + colorField: 'type', + height: 280, + innerRadius: 0.6, + label: { text: 'value' }, + legend: { color: { position: 'bottom' } }, + tooltip: { + items: [ + (arg: any) => ({ name: arg.type, value: arg.value }) + ] + }, + } + + const columnConfig = { + xField: 'name', + yField: 'value', + height: 280, + axis: { y: { title: false }, x: { title: false } }, + style: { maxWidth: 40 }, + } + + return ( +
+ + {statusCards.map((c, i) => ( +
+ + {c.suffix}} + valueStyle={{ color: c.color, fontWeight: 600 }} + /> + + + ))} + + + %} + valueStyle={{ color: (stats?.successRate ?? 0) >= 80 ? '#52c41a' : '#fa8c16', fontWeight: 600 }} + /> + + + + + + + 根因分类分布}> + {categoryPieData.length > 0 + ? + : } + + + + 分析状态分布}> + {statusPieData.length > 0 + ? + : } + + + + + + + 部门分析分布}> + {columnData.length > 0 + ? + : } + + + + 近 14 天分析趋势}> + {lineData.length > 0 + ? + : } + + + + + ) +} + +function EmptyChart({ text }: { text: string }) { + return ( +
+ {text} +
+ ) +} diff --git a/frontend/src/pages/ai-analysis/services.ts b/frontend/src/pages/ai-analysis/services.ts new file mode 100644 index 0000000..808fe94 --- /dev/null +++ b/frontend/src/pages/ai-analysis/services.ts @@ -0,0 +1,166 @@ +import request from '../../request' + +export interface AiAnalysisRecord { + id: number + issueId: number + issueNo: string + issueTitle: string + departmentName?: string + category: string + keywords: string + extractedKeywords: string + rootCause: string + suggestion: string + status: string + helpfulCount: number + promptTemplateId: string + promptVersion: number + modelProvider: string + modelName: string + errorMessage: string + startedAt: string + completedAt: string + createdAt: string +} + +export interface BatchGenerateParams { + issueIds?: number[] + departmentId?: string + status?: string + phase?: string +} + +export interface RecordQuery { + page: number + pageSize: number + id?: number + issueId?: number + departmentId?: number + status?: string + startDate?: string + endDate?: string +} + +export interface DepartmentOption { + id: number + name: string +} + +export interface OverviewStats { + totalIssues: number + analyzedCount: number + unanalyzedCount: number + coverageRate: number + successRate: number + dailyTrend: { date: string; completed: number; failed: number }[] + statusDistribution: { name: string; value: number }[] + categoryDistribution: { name: string; value: number }[] + departmentDistribution: { name: string; value: number }[] +} + +export interface UnanalyzedIssue { + id: number + issueNo: string + title: string + status: string + priority: string + phase: string + departmentName: string + createdAt: string +} + +export interface UnanalyzedQuery { + page: number + pageSize: number + status?: string + phase?: string + departmentId?: number + keyword?: string + startDate?: string + endDate?: string +} + +export interface IssueListItem { + id: number + issueNo: string + title: string + status: string + priority: string + phase: string + departmentId: number + departmentName: string + assigneeName: string + deadline: string + createdAt: string +} + +export interface IssueListQuery { + page: number + pageSize: number + status?: string + phase?: string + departmentId?: number + keyword?: string + startDate?: string + endDate?: string +} + +export const aiAnalysisApi = { + batchGenerate: (params: BatchGenerateParams) => + request.post('/ai/batch-generate', params), + + records: (params: RecordQuery) => + request.get('/ai/records', { params }), + + running: () => + request.get('/ai/records/running'), + + callLogs: () => + request.get('/ai/call-logs'), + + exportRecords: (params: RecordQuery) => + request.get('/ai/records/export', { params, responseType: 'blob' }), + + feedback: (id: number, isHelpful: boolean, comment?: string) => + request.post(`/ai/records/${id}/feedback`, { isHelpful, comment }), + + overviewStats: (days?: number) => + request.get('/ai/overview-stats', { params: { days } }), + + unanalyzedIssues: (params: UnanalyzedQuery) => + request.get('/ai/unanalyzed', { params }), +} + +export const departmentApi = { + list: () => request.get('/departments'), +} + +export const issuesApi = { + list: (params: IssueListQuery) => + request.get('/issues', { params }), +} + +export const ISSUE_PRIORITY_OPTIONS = [ + { label: '高', value: 'high' }, + { label: '中', value: 'medium' }, + { label: '低', value: 'low' }, +] + +export const ISSUE_STATUS_OPTIONS = [ + { label: '草稿', value: 'draft' }, + { label: '待处理', value: 'open' }, + { label: '进行中', value: 'in_progress' }, + { label: '已解决', value: 'resolved' }, + { label: '已验证', value: 'verified' }, + { label: '已关闭', value: 'closed' }, + { label: '已驳回', value: 'rejected' }, +] + +export const ISSUE_PHASE_OPTIONS = ['需求', '设计', '编码', '测试', '部署', '运维'] + +export const ANALYSIS_STATUS_OPTIONS = [ + { label: '待处理', value: 'pending' }, + { label: '分析中', value: 'processing' }, + { label: '已完成', value: 'completed' }, + { label: '失败', value: 'failed' }, +] diff --git a/frontend/src/pages/ai-analysis/unanalyzed.tsx b/frontend/src/pages/ai-analysis/unanalyzed.tsx new file mode 100644 index 0000000..9df508c --- /dev/null +++ b/frontend/src/pages/ai-analysis/unanalyzed.tsx @@ -0,0 +1,177 @@ +import { useState, useEffect, useRef } from 'react' +import dayjs from 'dayjs' +import { + Card, Table, Button, Space, Tag, Input, Select, Form, DatePicker, message +} from 'antd' +import { ThunderboltOutlined, SearchOutlined, ReloadOutlined } from '@ant-design/icons' +import { aiAnalysisApi, departmentApi, UnanalyzedIssue, UnanalyzedQuery } from './services' +import { STATUS_LABELS, STATUS_COLORS, PRIORITY_LABELS, PRIORITY_COLORS } from '../../constants/issue' + +const { RangePicker } = DatePicker +const pageSize = 10 + +export default function UnanalyzedTab() { + const [items, setItems] = useState([]) + const [total, setTotal] = useState(0) + const [page, setPage] = useState(1) + const [loading, setLoading] = useState(false) + const [selected, setSelected] = useState([]) + const [analyzing, setAnalyzing] = useState(false) + const [departments, setDepartments] = useState<{ id: number; name: string }[]>([]) + const [filterForm] = Form.useForm() + const pollTimer = useRef(null) + + const loadUnanalyzed = async (q: UnanalyzedQuery) => { + setLoading(true) + try { + const res: any = await aiAnalysisApi.unanalyzedIssues(q) + setItems(res.data?.items || []) + setTotal(res.data?.total || 0) + setPage(q.page) + } catch { /* ignore */ } + setLoading(false) + } + + useEffect(() => { + loadUnanalyzed({ page: 1, pageSize }) + departmentApi.list().then((res: any) => setDepartments(res.data || [])).catch(() => {}) + return () => { if (pollTimer.current) window.clearTimeout(pollTimer.current) } + }, []) + + const buildQuery = (p: number): UnanalyzedQuery => { + const v = filterForm.getFieldsValue() + return { + page: p, + pageSize, + status: v.status, + phase: v.phase, + departmentId: v.departmentId, + keyword: v.keyword, + startDate: v.dateRange?.[0]?.format('YYYY-MM-DD'), + endDate: v.dateRange?.[1]?.format('YYYY-MM-DD'), + } + } + + const handleSearch = () => loadUnanalyzed(buildQuery(1)) + + const handleReset = () => { + filterForm.resetFields() + loadUnanalyzed({ page: 1, pageSize }) + } + + const refreshAll = () => loadUnanalyzed(buildQuery(page)) + + const analyze = async (issueIds: number[]) => { + if (issueIds.length === 0) { + message.warning('请至少选择一条指摘') + return + } + setAnalyzing(true) + try { + const res: any = await aiAnalysisApi.batchGenerate({ issueIds }) + message.success(`已对 ${res.data?.submitted ?? issueIds.length} 条指摘启动 AI 分析`) + setSelected([]) + if (pollTimer.current) window.clearTimeout(pollTimer.current) + pollTimer.current = window.setTimeout(() => { + refreshAll() + if (pollTimer.current) window.clearTimeout(pollTimer.current) + }, 3000) + } catch { /* ignore */ } + setAnalyzing(false) + } + + const columns = [ + { + title: 'ID', dataIndex: 'issueNo', key: 'issueNo', width: 150, + render: (v: string) => {v} + }, + { title: '标题', dataIndex: 'title', key: 'title', ellipsis: true }, + { + title: '状态', dataIndex: 'status', key: 'status', width: 90, + render: (s: string) => {STATUS_LABELS[s] || s} + }, + { + title: '优先级', dataIndex: 'priority', key: 'priority', width: 80, + render: (p: string) => {PRIORITY_LABELS[p] || p} + }, + { title: '工程阶段', dataIndex: 'phase', key: 'phase', width: 110, render: (v: string) => v || '-' }, + { title: '归属部门', dataIndex: 'departmentName', key: 'departmentName', width: 110, render: (v: string) => v || '-' }, + { title: '对应者', dataIndex: 'assigneeName', key: 'assigneeName', width: 100, render: (v: string) => v || '-' }, + { + title: '创建时间', dataIndex: 'createdAt', key: 'createdAt', width: 120, + render: (v: string) => (v ? dayjs(v).format('MM-DD HH:mm') : '-') + }, + { + title: '操作', key: 'action', width: 90, align: 'right' as const, + render: (_: any, r: UnanalyzedIssue) => ( + + ) + }, + ] + + return ( + + + 未分析指摘({total}) + 不含已生成分析结果的指摘,失败记录可在此重新分析 + + } + extra={ + + } + > +
+ + ({ label: d.name, value: d.id }))} /> + + + + + + + + + + + + + + + +
s.id), + onChange: (_keys, rows) => setSelected(rows), + }} + pagination={{ + current: page, total, pageSize, + onChange: (p) => loadUnanalyzed(buildQuery(p)), + showTotal: t => `共 ${t} 条`, + }} + /> + + ) +} diff --git a/frontend/src/pages/batch-input/index.tsx b/frontend/src/pages/batch-input/index.tsx new file mode 100644 index 0000000..6630b0b --- /dev/null +++ b/frontend/src/pages/batch-input/index.tsx @@ -0,0 +1,600 @@ +import { useEffect, useMemo, useState } from 'react' +import type { CSSProperties } from 'react' +import { + Typography, Upload, Table, Button, Card, Space, Tag, message, + Modal, Statistic, Alert, Spin, Tooltip +} from 'antd' +import { + DownloadOutlined, ArrowRightOutlined, CheckCircleOutlined, + CloseCircleOutlined, InboxOutlined, RobotOutlined, + CheckOutlined, ThunderboltOutlined +} from '@ant-design/icons' +import type { ColumnsType } from 'antd/es/table' +import dayjs from 'dayjs' +import { importApi, ImportRow, ImportPreview, ImportRecord, ImportSuggestion } from '../../api/system' + +const validPriority = new Set(['high', 'medium', 'low']) + +const priorityZh: Record = { + high: { label: '高', color: 'red' }, + medium: { label: '中', color: 'orange' }, + low: { label: '低', color: 'green' }, +} + +const rowErrors = (r: ImportRow): string[] => { + const errs: string[] = [] + if (!r.title?.trim()) errs.push('标题必填') + if (r.priority?.trim() && !validPriority.has(r.priority.trim().toLowerCase())) errs.push('优先级必须是 high/medium/low') + if (r.deadline?.trim() && !dayjs(r.deadline).isValid()) errs.push('期限格式不正确') + return errs +} + +const recompute = (rows: ImportRow[]) => { + let valid = 0 + for (const r of rows) { + r.errors = rowErrors(r) + r.status = r.errors.length === 0 ? 'ok' : 'error' + if (r.errors.length === 0) valid++ + } + return { rows, validCount: valid, errorCount: rows.length - valid } +} + +const fieldLabels: Record = { + phase: '工程阶段', + priority: '优先级', + title: '标题', + deadline: '期限', +} + +const unFilledTag = 未填 + +const tooltipStyle: CSSProperties = { + background: '#fff', color: '#374151', + border: '1px solid #eef2f7', borderRadius: 8, + boxShadow: '0 8px 24px rgba(15,23,42,.12)', padding: '8px 12px', +} + +const Ellipsis = ({ value, fieldName, tooltipText, style, width = 200 }: { + value?: string + fieldName?: string + tooltipText?: string + style?: CSSProperties + width?: number +}) => { + const text = tooltipText ?? value + return ( + + {fieldName &&
{fieldName}
} +
{text}
+ + ) : null} + > + + {value || '-'} + +
+ ) +} + +export default function BatchInputPage() { + const [preview, setPreview] = useState(null) + const [fileName, setFileName] = useState('') + const [importing, setImporting] = useState(false) + + const [suggestions, setSuggestions] = useState([]) + const [applied, setApplied] = useState>(new Set()) + const [agentLoading, setAgentLoading] = useState(false) + const [agentEngine, setAgentEngine] = useState('') + const [agentUsable, setAgentUsable] = useState(true) + const [agentUsableReason, setAgentUsableReason] = useState('') + + const [records, setRecords] = useState([]) + const [total, setTotal] = useState(0) + const [page, setPage] = useState(1) + const [loading, setLoading] = useState(false) + + const [errorOpen, setErrorOpen] = useState(false) + const [errorText, setErrorText] = useState('') + + const loadRecords = async (p = 1) => { + setLoading(true) + try { + const res: any = await importApi.records(p) + setRecords(res.data?.items || []) + setTotal(res.data?.total || 0) + setPage(p) + } catch { /* ignore */ } + setLoading(false) + } + + useEffect(() => { loadRecords() }, []) + + const handleTemplate = async () => { + try { + const res: any = await importApi.template() + const url = URL.createObjectURL(res as Blob) + const a = document.createElement('a') + a.href = url + a.download = 'レビュー記録表.xlsx' + a.click() + URL.revokeObjectURL(url) + } catch { /* ignore */ } + } + + const applySuggestion = (s: ImportSuggestion) => { + if (!preview) return + const key = `${s.rowNo}:${s.field}` + if (applied.has(key) || !s.suggested) return + const rows = preview.rows.map((r) => { + if (r.rowNo === s.rowNo && s.field && s.suggested) { + return { ...r, [s.field]: s.suggested } + } + return r + }) + const next = recompute(rows) + setPreview({ ...preview, ...next }) + setApplied((prev) => new Set(prev).add(key)) + } + + const applyAll = () => { + if (!preview || suggestions.length === 0) return + let rows = preview.rows + const nextApplied = new Set(applied) + for (const s of suggestions) { + const key = `${s.rowNo}:${s.field}` + if (nextApplied.has(key) || !s.suggested) continue + rows = rows.map((r) => (r.rowNo === s.rowNo && s.field ? { ...r, [s.field]: s.suggested } : r)) + nextApplied.add(key) + } + const next = recompute(rows) + setPreview({ ...preview, ...next }) + setApplied(nextApplied) + } + + const handlePreview = async (file: File) => { + if (!/\.(xlsx|xls)$/i.test(file.name)) { + message.error('仅支持 .xlsx / .xls 文件') + return false + } + if (file.size > 50 * 1024 * 1024) { + message.error('单个文件不能超过 50MB') + return false + } + try { + const res: any = await importApi.preview(file) + const p: ImportPreview = res.data + setPreview(p) + setFileName(file.name) + setSuggestions([]) + setApplied(new Set()) + setAgentEngine('') + setAgentUsable(true) + setAgentUsableReason('') + if (p.headerValid === false) { + setAgentUsable(false) + setAgentUsableReason('导入文件的表头与标准的「レビュー記録表」模板不一致,不是标准模板。请下载并使用正确的模板后重新上传。') + message.warning('不是标准的レビュー記録表模板,请使用正确模板') + return false + } + message.success('解析完成,Agent 正在智能校验数据...') + setAgentLoading(true) + try { + const ar: any = await importApi.aiValidate(p.rows) + setSuggestions(ar.data?.suggestions || []) + setAgentEngine(ar.data?.engine || '') + setAgentUsable(ar.data?.usable ?? true) + setAgentUsableReason(ar.data?.usableReason || '') + if (ar.data?.usable === false) { + message.warning(ar.data?.usableReason || '该文件不能作为指摘表导入') + } else if ((ar.data?.suggestions || []).length === 0) { + message.success('Agent 校验完成,数据全部通过') + } + } catch { + setSuggestions([]) + setAgentEngine('') + setAgentUsable(true) + setAgentUsableReason('') + message.warning('AI 校验超时或不可用,已降级为规则校验') + } + setAgentLoading(false) + } catch (err: any) { + setAgentLoading(false) + const msg = err?.response?.data?.message + if (msg) { + message.error(msg) + } else if (err?.message?.includes('timeout')) { + message.error('解析超时,请稍后重试') + } else { + message.error('解析失败,请使用标准模板') + } + } + return false + } + + const handleConfirm = async () => { + if (!preview) return + setImporting(true) + try { + const res: any = await importApi.confirm(fileName, preview.rows) + const r = res.data + message.success(`导入完成:成功 ${r.successCount} 条,失败 ${r.failCount} 条`) + setPreview(null) + setFileName('') + setSuggestions([]) + setApplied(new Set()) + setAgentEngine('') + setAgentUsable(true) + setAgentUsableReason('') + loadRecords(1) + } catch { + message.error('导入失败') + } + setImporting(false) + } + + const statusTag = (s: string) => { + const map: Record = { + success: { color: 'green', label: '已完成' }, + partial: { color: 'orange', label: '部分成功' }, + failed: { color: 'red', label: '失败' }, + } + const item = map[s] || { color: 'default', label: s } + return {item.label} + } + + const correctedCell = (rowNo: number | undefined, field: string, value?: string) => { + if (rowNo != null && applied.has(`${rowNo}:${field}`)) { + return {value} + } + return value || '-' + } + + const previewColumns: ColumnsType = [ + { + title: '行号', dataIndex: 'rowNo', key: 'rowNo', width: 56, + render: (v?: number) => {v}, + }, + { + title: '标题', dataIndex: 'title', key: 'title', width: 220, + render: (v: string) => (v + ? + : unFilledTag), + }, + { + title: '工程阶段', dataIndex: 'phase', key: 'phase', width: 110, + render: (v: string, r) => { + const appliedNow = r.rowNo != null && applied.has(`${r.rowNo}:phase`) + if (appliedNow) { + return {v} + } + return v ? : - + }, + }, + { + title: '优先级', dataIndex: 'priority', key: 'priority', width: 84, + render: (v: string | undefined, r: ImportRow) => { + if (!v) return unFilledTag + const zh = priorityZh[v.toLowerCase()] || { label: v, color: 'default' } + const isApplied = r.rowNo != null && applied.has(`${r.rowNo}:priority`) + return {zh.label} + }, + }, + { + title: '期限', dataIndex: 'deadline', key: 'deadline', width: 108, + render: (v?: string) => (v + ? + : -), + }, + { + title: '实施日', dataIndex: 'reviewDate', key: 'reviewDate', width: 108, + render: (v?: string) => (v + ? + : -), + }, + { + title: '描述', dataIndex: 'description', key: 'description', width: 220, + render: (v?: string, r?: ImportRow) => (v + ? + : -), + }, + { + title: '担当者', dataIndex: 'assigneeUserid', key: 'assigneeUserid', width: 90, + render: (_: string, r) => (r.assigneeUserid + ? + : 未分配), + }, + { title: '部门', dataIndex: 'departmentName', key: 'departmentName', width: 90, render: (v?: string) => (v ? : -) }, + { + title: '校验', dataIndex: 'status', key: 'status', width: 86, + render: (s?: string) => s === 'ok' + ? 已就绪 + : 待修正, + }, + ] + + const recordColumns: ColumnsType = [ + { + title: '导入时间', dataIndex: 'createdAt', key: 'createdAt', width: 170, + render: (v?: string) => (v ? dayjs(v).format('YYYY-MM-DD HH:mm:ss') : '-'), + }, + { title: '文件名', dataIndex: 'fileName', key: 'fileName', ellipsis: true }, + { title: '操作人', dataIndex: 'operator', key: 'operator', width: 110 }, + { title: '总条数', dataIndex: 'totalCount', key: 'totalCount', width: 80 }, + { title: '成功', dataIndex: 'successCount', key: 'successCount', width: 70, render: (v: number) => {v} }, + { title: '失败', dataIndex: 'failCount', key: 'failCount', width: 70, render: (v: number) => 0 ? '#f5222d' : '#999' }}>{v} }, + { title: '状态', dataIndex: 'status', key: 'status', width: 100, render: (s: string) => statusTag(s) }, + { + title: '操作', key: 'action', width: 110, + render: (_: unknown, r: ImportRecord) => ( + + ), + }, + ] + + const pendingSuggestions = useMemo(() => suggestions.filter((s) => !applied.has(`${s.rowNo}:${s.field}`)), [suggestions, applied]) + const fixSuggestions = useMemo(() => pendingSuggestions.filter((s) => s.level !== 'error' && s.suggested), [pendingSuggestions]) + const errorSuggestions = useMemo(() => pendingSuggestions.filter((s) => s.level === 'error'), [pendingSuggestions]) + const hasFix = fixSuggestions.length > 0 + const allPassed = preview != null && preview.errorCount === 0 && suggestions.length === 0 + + const agentBadge = () => { + if (agentLoading) return 校验中... + if (!preview) return 待上传 + if (agentUsable === false) return 文件不可用 + if (preview.errorCount > 0) return 发现 {preview.errorCount} 条待修正 + if (suggestions.length > 0) return 发现 {pendingSuggestions.length} 个建议 + return 校验通过 + } + + return ( +
+
+ 指摘批量录入 + +
+ + + +

+

点击或拖拽文件到此处上传

+

支持 .xlsx, .xls 文件,单个文件不超过 50MB · 上传后 Agent 自动智能校验

+
+
+ +
+ {/* Agent 校验结果面板 */} +
+
+
+ + Agent 校验结果 +
+ {agentBadge()} +
+ + {!preview ? ( +
+ +

+ 上传 Excel 文件后
Agent 将自动校验数据 +

+
+ ) : agentLoading ? ( +
+ +

Agent 正在扫描数据并生成修正建议...

+
+ ) : ( + + {agentUsable === false && ( +
+ +
+ 该文件不能作为指摘表使用 +
{agentUsableReason}
+
+
+ )} + + {errorSuggestions.map((s) => { + const key = `${s.rowNo}:${s.field}` + const label = s.fieldName || fieldLabels[s.field || ''] || s.field + return ( +
+ + 第 {s.rowNo} 行 + +

+ {label}:{s.original || '未填写'} +

+

原因:{s.reason}

+ +
+ ) + })} + + {fixSuggestions.map((s) => { + const key = `${s.rowNo}:${s.field}` + const appliedNow = applied.has(key) + const label = s.fieldName || fieldLabels[s.field || ''] || s.field + return ( +
+ + 第 {s.rowNo} 行 + +

+ {label}:{s.original ? `${s.original} → ` : ''}{s.suggested} +

+

原因:{s.reason}

+ +
+ ) + })} + + {allPassed && agentUsable !== false && ( +
+ +
+ 所有数据校验通过 +
Agent 已完成自动关联与去重校验。
+
+
+ )} + + {hasFix && ( + + )} + + {agentEngine && ( + + {agentEngine === 'ai' ? '引擎:AI 智能模型' : '引擎:规则引擎'} + + )} +
+ )} +
+ + {/* 预检表格 */} + + + 待修正 + + + 已就绪 + + + )} + > + {preview && preview.errorCount > 0 && ( + + )} +
String(r.rowNo)} + columns={previewColumns} + dataSource={preview?.rows || []} + size="small" + pagination={false} + scroll={{ y: 380 }} + rowClassName={(r) => (r.status === 'error' ? 'import-row-error' : '')} + locale={{ + emptyText: ( +
+ +

尚未上传文件

+
+ ), + }} + /> +
+ {preview && ( + + )} + +
+ + + + 共 {total} 条} + > +
`共 ${t} 条`, + onChange: (p) => loadRecords(p), + }} + /> + + + setErrorOpen(false)} width={640} + > +
+          {errorText}
+        
+
+ + + + ) +} diff --git a/frontend/src/pages/dashboard/index.tsx b/frontend/src/pages/dashboard/index.tsx new file mode 100644 index 0000000..3ce6050 --- /dev/null +++ b/frontend/src/pages/dashboard/index.tsx @@ -0,0 +1,317 @@ +import { useEffect, useState } from 'react' +import { Typography, Card, Row, Col, Statistic, List, Tag, Input, Button, Space, Modal } from 'antd' +import { message } from '../../antdStatic' +import { + AlertOutlined, LoadingOutlined, CheckCircleOutlined, PlusOutlined, + SendOutlined, InboxOutlined, RobotOutlined, OrderedListOutlined, EyeOutlined, CloseCircleOutlined +} from '@ant-design/icons' +import { Line, Pie } from '@ant-design/charts' +import { useNavigate } from 'react-router-dom' +import { getDashboardStats, DashboardStats } from './services' +import { listIssues, executeAgent } from '../issues/services' +import { STATUS_LABELS, ACTION_LABELS } from '../../constants/issue' + +const statCards = [ + { key: 'pendingCount', title: '待处理指摘', color: '#fa8c16', icon: }, + { key: 'inProgressCount', title: '进行中指摘', color: '#1677ff', icon: }, + { key: 'monthlyClosedCount', title: '本月已完成', color: '#52c41a', icon: }, + { key: 'todayNewCount', title: '今日新增', color: '#722ed1', icon: } +] + +const QUICK_CMDS = [ + { label: '催办逾期指摘', goal: '检索知识库相似案例,生成对应方案并催办逾期指摘' }, + { label: '生成本周报告', goal: '生成本周指摘处理统计报告' }, + { label: '分配待处理指摘', goal: '为待处理指摘分配对应者并生成对应方案' }, + { label: '检索知识库', goal: '检索知识库相似案例,生成对应方案' } +] + +interface StreamItem { + type: string + content?: string + tool?: string + params?: string +} + +export default function DashboardPage() { + const navigate = useNavigate() + const [stats, setStats] = useState(null) + const [agentGoal, setAgentGoal] = useState('') + const [agentRunning, setAgentRunning] = useState(false) + const [planModalOpen, setPlanModalOpen] = useState(false) + const [planId, setPlanId] = useState() + const [streamItems, setStreamItems] = useState([]) + const [planRequiresApproval, setPlanRequiresApproval] = useState(false) + + useEffect(() => { + getDashboardStats().then((res: any) => setStats(res.data)).catch(() => {}) + }, []) + + const handleAgentCommand = async () => { + const goal = agentGoal.trim() + if (!goal) return message.warning('请输入指令') + setAgentRunning(true) + setPlanRequiresApproval(false) + try { + let contextId: number | undefined + const issueNoMatch = goal.match(/ISSUE-\d{4}-\d{3,}/i) + if (issueNoMatch) { + try { + const res: any = await listIssues({ page: 1, pageSize: 20, keyword: issueNoMatch[0] }) + const hit = (res.data?.items || []).find((i: any) => i.issueNo === issueNoMatch[0]) + contextId = hit?.id + } catch { /* ignore */ } + } + const res: any = await executeAgent(contextId, goal) + const pid = res.data?.planId + if (!pid) { + message.success('Agent 已解析指令并开始执行') + if (contextId) navigate(`/issues/${contextId}`) + setAgentRunning(false) + return + } + setPlanId(pid) + setStreamItems([{ type: 'thought', content: '指令已提交,正在连接执行流…' }]) + setPlanModalOpen(true) + setAgentGoal('') + if (contextId) navigate(`/issues/${contextId}`) + const token = localStorage.getItem('accessToken') + const es = new EventSource(`/api/v1/agent/plan/${pid}/stream?token=${token}`) + es.onmessage = (event) => { + let ev: any = {} + try { ev = JSON.parse(event.data) } catch { return } + switch (ev.type) { + case 'thought': + setStreamItems(prev => [...prev, { type: 'thought', content: ev.content }]) + break + case 'action': + setStreamItems(prev => [...prev, { + type: 'action', tool: ev.tool, params: ev.params ? JSON.stringify(ev.params) : '' + }]) + if (ev.approval) { + setPlanRequiresApproval(true) + setStreamItems(prev => [...prev, { type: 'thought', content: '写操作需人工审批,请前往审批中心处理。' }]) + } + break + case 'observation': + setStreamItems(prev => [...prev, { type: 'observation', content: ev.result || '' }]) + break + case 'result': + setStreamItems(prev => [...prev, { type: 'result', content: ev.content || '方案已生成' }]) + es.close() + setAgentRunning(false) + break + case 'error': + setStreamItems(prev => [...prev, { type: 'error', content: ev.message || 'Agent 执行失败' }]) + es.close() + setAgentRunning(false) + break + } + } + es.onerror = () => { + es.close() + setAgentRunning(false) + } + } catch { /* 错误由拦截器提示 */ } + setAgentRunning(false) + } + + const lineData = (stats?.trend || []).flatMap(d => [ + { date: d.date.slice(5), type: '新增', value: d.newCount }, + { date: d.date.slice(5), type: '已解决', value: d.resolvedCount } + ]) + + const pieData = (stats?.statusDistribution || []).map(s => ({ + type: STATUS_LABELS[s.status] || s.status, + value: s.count + })) + + const lineConfig = { + xField: 'date', + yField: 'value', + seriesField: 'type', + colorField: 'type', + height: 320, + smooth: true, + scale: { y: { nice: true } }, + axis: { y: { title: false }, x: { title: false } } + } + + const pieConfig = { + angleField: 'value', + colorField: 'type', + height: 280, + innerRadius: 0.6, + label: { text: 'value' }, + legend: { color: { position: 'bottom' } }, + tooltip: { + items: [ + (arg: any) => ({ name: arg.type, value: arg.value }) + ] + } + } + + const insightColor: Record = { + 'high-risk': '#fa8c16', + suggestion: '#1677ff', + reminder: '#52c41a' + } + + return ( +
+
+ 工作台概览 + + {new Date().toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' })} + +
+ + + +
+ +
+
+ Agent 快捷指令 +
输入自然语言指令,Agent 将检索知识库相似案例并生成对应方案
+
+ 在线 +
+ + } + placeholder="例如:查找知识库相似案例,生成对应方案" + value={agentGoal} + onChange={e => setAgentGoal(e.target.value)} + onPressEnter={handleAgentCommand} + /> + + +
+ + + 快捷指令: + {QUICK_CMDS.map(c => ( + setAgentGoal(c.goal)}>{c.label} + ))} +
+
+ + + {statCards.map((c, i) => { + const card = (stats?.cards || []).find(x => x.key === c.key) + return ( +
+ +
+ {c.icon} +
+ + {card?.changeText &&
{card.changeText}
} + {card?.suggestion && ( +
+ {card.suggestion} +
+ )} +
+ + ) + })} + + + + + + + + + + + ( + +
+
+
+
{item.title}
+
{item.content}
+
+
+ + )} + /> + + + + + +
+ navigate('/issues')}>查看全部}> + ( + + + {i % 2 ? : } + + } + title={{a.userName || (i % 2 ? 'IMS Agent' : '')} {ACTION_LABELS[a.action] || a.action}} + description={ +
+
{a.issueNo} · {a.title}
+
{a.createdAt?.replace('T', ' ').slice(0, 16)}
+
+ } + /> +
+ )} + /> +
+ + + + + + + + + { setPlanModalOpen(false); setPlanId(undefined); setStreamItems([]) }} + footer={ + + {planRequiresApproval && } + + + } + width={640} + > +
+ {streamItems.map((item, i) => { + if (item.type === 'action') { + return ( +
+
+ 行动:{item.tool} +
+ {item.params &&
{item.params}
} +
+ ) + } + const color = item.type === 'error' ? '#cf1322' : item.type === 'result' ? '#389e0d' : item.type === 'observation' ? '#666' : '#595959' + const bg = item.type === 'error' ? '#fff1f0' : item.type === 'result' ? '#f6ffed' : item.type === 'observation' ? '#fafafa' : 'transparent' + return ( +
+ {item.type === 'error' && } + {item.content} +
+ ) + })} +
+
+ + ) +} \ No newline at end of file diff --git a/frontend/src/pages/dashboard/services.ts b/frontend/src/pages/dashboard/services.ts new file mode 100644 index 0000000..aa80cff --- /dev/null +++ b/frontend/src/pages/dashboard/services.ts @@ -0,0 +1,50 @@ +import request from '../../request' + +export interface DailyTrend { + date: string + newCount: number + resolvedCount: number +} + +export interface StatusCount { + status: string + count: number +} + +export interface Activity { + userName: string + action: string + issueNo: string + title: string + createdAt?: string +} + +export interface Insight { + type: string + title: string + content: string +} + +export interface StatCardInfo { + key: string + count: number + changeText: string + suggestion: string +} + +export interface DashboardStats { + pendingCount: number + inProgressCount: number + pendingConfirmCount: number + closedCount: number + todayNewCount: number + monthlyClosedCount: number + cards: StatCardInfo[] + trend: DailyTrend[] + statusDistribution: StatusCount[] + recentActivities: Activity[] + insights: Insight[] +} + +export const getDashboardStats = () => + request.get('/dashboard/stats') diff --git a/frontend/src/pages/issues/IssueForm.tsx b/frontend/src/pages/issues/IssueForm.tsx new file mode 100644 index 0000000..65c0773 --- /dev/null +++ b/frontend/src/pages/issues/IssueForm.tsx @@ -0,0 +1,285 @@ +import { forwardRef, useEffect, useImperativeHandle, useState } from 'react' +import { + Form, Input, Select, InputNumber, DatePicker, Radio, Card, Row, Col, Button, Space, Typography, Tag +} from 'antd' +import { message } from '../../antdStatic' +import { FileTextOutlined, MessageOutlined, CheckCircleOutlined, SettingOutlined, BulbOutlined } from '@ant-design/icons' +import dayjs from 'dayjs' +import { getDepartments, suggestFields } from './services' +import { + PHASE_OPTIONS, SUB_PROJECT_OPTIONS, CATEGORY_OPTIONS, IMPACT_LEVEL_OPTIONS, + USERS, PRIORITY_LABELS, STATUS_LABELS, STATUS_COLORS +} from '../../constants/issue' + +export interface IssueFormValues { + title?: string + description?: string + phase?: string + subProject?: string + category?: string + impactLevel?: string + impactScope?: string + deployment?: string + pgmNo?: string + reviewWorkload?: number + responseWorkload?: number + responseContent?: string + ngReason?: string + assigneeId?: number + reviewerId?: number + validatorId?: number + priority?: string + status?: string + departmentId?: number + deadline?: string + responseCompletedAt?: string + confirmAt?: string +} + +const userOptions = USERS.map(u => ({ label: u.name, value: u.id })) +const priorityOptions = Object.entries(PRIORITY_LABELS).map(([v, l]) => ({ label: l, value: v })) + +interface Props { + initialValues?: Partial + submitting?: boolean + mode?: 'create' | 'edit' + onSubmit: (values: IssueFormValues) => void +} + +const label = { display: 'block', fontSize: 11, color: '#999', marginBottom: 4, fontWeight: 600 } as const + +export default forwardRef(function IssueForm({ initialValues, submitting, mode = 'create', onSubmit }: Props, ref) { + const [form] = Form.useForm() + const status = initialValues?.status + const [deptOptions, setDeptOptions] = useState<{ label: string; value: number }[]>([]) + const [agentLoading, setAgentLoading] = useState(false) + + useEffect(() => { + getDepartments().then((res: any) => { + setDeptOptions((res.data || []).map((d: any) => ({ label: d.name, value: d.id }))) + }).catch(() => {}) + }, []) + + useImperativeHandle(ref, () => ({ + form + })) + + useEffect(() => { + form.setFieldsValue(toForm(initialValues || {})) + }, [initialValues, form]) + + const toForm = (v: Partial) => ({ + ...v, + deadline: v.deadline ? dayjs(v.deadline) : undefined, + responseCompletedAt: v.responseCompletedAt ? dayjs(v.responseCompletedAt) : undefined, + confirmAt: v.confirmAt ? dayjs(v.confirmAt) : undefined + }) + + const handleFinish = (values: any) => { + onSubmit({ + ...values, + deadline: values.deadline ? values.deadline.format('YYYY-MM-DDTHH:mm:ss') : undefined, + responseCompletedAt: values.responseCompletedAt ? values.responseCompletedAt.format('YYYY-MM-DDTHH:mm:ss') : undefined, + confirmAt: values.confirmAt ? values.confirmAt.format('YYYY-MM-DDTHH:mm:ss') : undefined + }) + } + + const fallbackFill = (title?: string) => { + let category = '功能缺陷' + if (title && /(UI|界面|布局|样式|适配)/i.test(title)) category = 'UI/UX问题' + if (title && /(性能|慢|超时|卡顿)/i.test(title)) category = '性能问题' + if (title && /(安全|漏洞|权限)/i.test(title)) category = '安全漏洞' + form.setFieldsValue({ category, phase: '设计', impactLevel: '中', priority: 'medium' }) + } + + const agentFill = async () => { + const title = form.getFieldValue('title') as string | undefined + const description = form.getFieldValue('description') as string | undefined + if (!title) { + message.warning('请先填写指摘标题') + return + } + setAgentLoading(true) + try { + const res: any = await suggestFields(title, description) + const fields = res?.data || {} + if (Object.keys(fields).length > 0) { + if (fields.deadline) { + fields.deadline = dayjs(fields.deadline) + } + form.setFieldsValue(fields) + message.success('Agent 已智能填充字段') + } else { + fallbackFill(title) + message.warning('Agent 未识别到有效建议,已按标题规则填充') + } + } catch { + fallbackFill(title) + message.warning('Agent 分析超时或失败,已按标题规则填充') + } finally { + setAgentLoading(false) + } + } + + const field = (name: keyof IssueFormValues, child: React.ReactNode, required = false) => ( + + {child} + + ) + + const section = (icon: React.ReactNode, title: string, children: React.ReactNode, style?: React.CSSProperties) => ( + {icon}{title}}> + {children} + + ) + + return ( +
message.warning('请填写必填项,请检查红色标记字段')} + scrollToFirstError={{ behavior: 'smooth', block: 'center' }} + > + +
+ {section( + , '基本信息', + + + 指摘标题 * + {field('title', , true)} + + + 工程阶段 * + {field('phase', ({ label: o, value: o }))} />, true)} + + + 区分 * + {field('category', )} + + + 影响度 * + {field('impactLevel', )} + + + 部署 + {field('deployment', )} + + + Review 者 * + {field('reviewerId', )} + + )} + + {section( + , '对应信息', + + + 对应者 * + {field('assigneeId', )} + + + 确认日 + { + const completed = form.getFieldValue('responseCompletedAt') + if (v && completed && v.isBefore(completed, 'day')) { + return Promise.reject(new Error('确认日不能早于对应完了日')) + } + return Promise.resolve() + } }]}> + + + + + )} + + + + {section( + , '快速设置', + <> + 状态 + {mode === 'edit' ? ( +
+ {status ? STATUS_LABELS[status] || status : '-'} +
状态变更请在详情页操作
+
+ ) : ( +
草稿
+ )} + 优先级 * + {field('priority', , true)} + 归属部门 + {field('departmentId', { + const files = Array.from(e.target.files || []) + files.forEach(handleUpload) + e.target.value = '' + }} + /> + + + ( + } onClick={() => handlePreview(att)} />, + + + )} + + {toolCards.map((t, i) => ( +
+ + + 行动 + +
+ call: {t.toolName}({t.inputParams}) +
+
{t.outputResult}
+
+ ))} + + {awaitingApproval && ( +
+ + + 需要您的审批 + +
+ Agent 准备执行上述方案。是否批准? +
+ setApprovalComment(e.target.value)} style={{ marginBottom: 8, fontSize: 12 }} /> + + + + +
+ )} + + {agentError && !awaitingApproval && ( +
+
Agent 执行失败
+
{agentError}
+
+ )} + + {agentMsg && !awaitingApproval && ( +
+ {agentMsg} +
+ )} + + Prompt 模板信息, + children: ( +
+
系统角色:{promptTpl.systemId || '未提供'}({promptTpl.systemVer ? `v${promptTpl.systemVer}` : '-'})
+
规划模板:{promptTpl.planId || '未提供'}({promptTpl.planVer ? `v${promptTpl.planVer}` : '-'})
+
模板由 Agent 引擎从 Prompt 模板库动态加载,此处为当前执行使用的模板标识。
+
+ ) + }]} + /> + +
+ setGoal(e.target.value)} /> + +
+ + + + setStatusModalOpen(false)} + okText={targetStatus === 'rejected' ? '确认驳回' : '确认变更'}> +
流转路径
+
+ {STATUS_LABELS[issue.status]} + + setFilters({ ...filters, [key]: v })} + options={options.map(o => typeof o === 'string' ? { label: o, value: o } : o)} + /> +
+ ) + + return ( +
+
+ 指摘列表 + + + + + +
+ + + {STATUS_CARD_ORDER.map((st) => ( +
+ clickStatusCard(st)} style={{ cursor: 'pointer', borderBottom: `3px solid ${STATUS_CARD_COLORS[st]}` }}> + + + + ))} + + + + + {filterSelect('工程阶段', 'phase', PHASE_OPTIONS)} + {filterSelect('子工程', 'subProject', SUB_PROJECT_OPTIONS)} + {filterSelect('优先级', 'priority', PRIORITY_OPTIONS)} + {filterSelect('影响度', 'impactLevel', IMPACT_LEVEL_OPTIONS)} + {filterSelect('对应者', 'assigneeId', assigneeOptions)} + {filterSelect('归属部门', 'departmentId', deptOptions)} + + + +
+
创建日期范围
+ { + setDateRange(range) + setFilters({ + ...filters, + startDate: range?.[0] ? range[0].format('YYYY-MM-DDT00:00:00') : undefined, + endDate: range?.[1] ? range[1].format('YYYY-MM-DDT23:59:59') : undefined + }) + }} + /> +
+ + +
+ } placeholder="搜索指摘 ID、标题或关键词" + value={keyword} onChange={e => setKeyword(e.target.value)} + onPressEnter={handleSearch} + /> + + +
+ + + + +
+ + + Agent 批量处理 + 选中多条后可使用 Agent 统一分配 / 催办 + + + + + + +
+ +
load(p), showTotal: t => `共 ${t} 条` }} + /> + + setAssignModalOpen(false)} okText="确认分配"> +
+ 已选中 {selectedKeys.length} 条指摘,选择担当者后统一分配。 +
+
`共 ${t} 个文档` + }} + /> + + + + + + + + setConfigModalOpen(false)}> + {config && ( + +
+
AI 提供商
+ +
+ {config.provider === 'ollama' && ( + <> +
+
Ollama 地址
+ setConfig({ ...config, ollamaBaseUrl: e.target.value })} /> +
+
+
Embedding 模型
+ setConfig({ ...config, ollamaEmbeddingModel: e.target.value })} /> +
+ + )} + {config.provider === 'deepseek' && ( + <> +
+
Embedding 模型
+ setConfig({ ...config, deepseekEmbeddingModel: e.target.value })} /> +
+
+
API Key
+ setConfig({ ...config, deepseekApiKey: e.target.value })} /> +
+ + )} +
+ )} +
+ + ) +} + +function AuditLogs() { + const [logs, setLogs] = useState([]) + const [total, setTotal] = useState(0) + const [page, setPage] = useState(1) + + const loadLogs = async (p = 1) => { + try { + const res: any = await knowledgeApi.logs(p) + setLogs(res.data?.items || []) + setTotal(res.data?.total || 0) + setPage(p) + } catch { /* ignore */ } + } + + useEffect(() => { loadLogs() }, []) + + const logColumns = [ + { title: '时间', dataIndex: 'createdAt', key: 'createdAt' }, + { title: '关键词', dataIndex: 'query', key: 'query' }, + { title: '命中数', dataIndex: 'totalMatches', key: 'totalMatches' }, + { title: '耗时(ms)', dataIndex: 'durationMs', key: 'durationMs' }, + ] + + return ( +
+ +
+ +
+ + ) +} diff --git a/frontend/src/pages/knowledge-base/services.ts b/frontend/src/pages/knowledge-base/services.ts new file mode 100644 index 0000000..aed09b8 --- /dev/null +++ b/frontend/src/pages/knowledge-base/services.ts @@ -0,0 +1,74 @@ +import request from '../../request' + +export interface KnowledgeDoc { + id: number + name: string + fileSize: number + fileType: string + chunkCount: number + status: string + errorMessage: string + uploadedByName: string + createdAt: string +} + +export interface SearchResult { + chunkId: number + content: string + docName: string + score: number +} + +export interface AiConfig { + provider: string + ollamaBaseUrl: string + ollamaChatModel: string + ollamaEmbeddingModel: string + ollamaTemperature?: number + ollamaNumPredict?: number + deepseekModel: string + deepseekEmbeddingModel: string + deepseekApiKey?: string + autoFallbackEnabled: boolean + agentMaxSteps?: number + autoExecuteHighRisk?: boolean + userRateLimit?: number +} + +export interface SearchLog { + id: number + query: string + topK: number + totalMatches: number + durationMs: number + createdAt: string +} + +export const knowledgeApi = { + list: (page = 1, pageSize = 20) => + request.get('/knowledge/documents', { params: { page, pageSize } }), + + upload: (file: File) => { + const form = new FormData() + form.append('file', file) + return request.post('/knowledge/documents', form) + }, + + delete: (id: number) => + request.delete(`/knowledge/documents/${id}`), + + reindex: (id: number) => + request.post(`/knowledge/documents/${id}/reindex`), + + search: (query: string, topK = 5) => + request.get('/knowledge/search', { params: { query, topK } }), + + logs: (page = 1, pageSize = 20) => + request.get('/knowledge/logs', { params: { page, pageSize } }), +} + +export const aiConfigApi = { + get: () => request.get('/ai/config'), + update: (config: AiConfig) => request.put('/ai/config', config), + test: () => request.post('/ai/config/test'), +} diff --git a/frontend/src/pages/login/index.tsx b/frontend/src/pages/login/index.tsx new file mode 100644 index 0000000..c439c26 --- /dev/null +++ b/frontend/src/pages/login/index.tsx @@ -0,0 +1,41 @@ +import { Form, Input, Button, Card } from 'antd' +import { message } from '../../antdStatic' +import { UserOutlined, LockOutlined } from '@ant-design/icons' +import { useNavigate } from 'react-router-dom' +import { useDispatch } from 'react-redux' +import { login, fetchMe } from '../../store/slices/authSlice' +import type { AppDispatch } from '../../store' + +export default function LoginPage() { + const navigate = useNavigate() + const dispatch = useDispatch() + + const onFinish = async (values: { username: string; password: string }) => { + try { + await dispatch(login(values)).unwrap() + dispatch(fetchMe()) + message.success('登录成功') + navigate('/dashboard') + } catch { + message.error('账号或密码错误') + } + } + + return ( +
+ + + + } placeholder="账号" /> + + + } placeholder="密码" /> + + + + + + +
+ ) +} diff --git a/frontend/src/pages/system/agent-admin-services.ts b/frontend/src/pages/system/agent-admin-services.ts new file mode 100644 index 0000000..686171b --- /dev/null +++ b/frontend/src/pages/system/agent-admin-services.ts @@ -0,0 +1,190 @@ +import request from '../../request' + +export interface AgentConfig { + maxSteps: number + autoExecuteHighRisk: boolean + userRateLimit: number + provider: string +} + +export interface AgentMemory { + id: number + issueSummary: string + solutionSteps: string + effectivenessScore: number + createdAt: string + updatedAt: string +} + +export interface ToolExecutionItem { + id: number + planId: number | null + toolName: string + status: string + executionTimeMs: number | null + outputResult: string | null + createdAt: string | null +} + +export interface AgentOverview { + todayExecutions: number + todayPlans: number + toolTotalCount: number + toolSuccessCount: number + toolSuccessRate: number + pendingApprovals: number + latestExecutions: ToolExecutionItem[] + trendData: { hour: string; count: number }[] + growthRate: number +} + +export interface AgentPlanItem { + planId: number + issueId: number + issueNo: string + issueTitle: string + goal: string + status: string + requiresApproval: boolean + approvalStatus: string + approvalComment: string | null + toolName: string | null + toolParams: string | null + approvalReason: string | null + createdAt: string +} + +export interface AgentTool { + name: string + description: string + isWrite: boolean +} + +export interface AiConfig { + provider: string + ollamaBaseUrl: string + ollamaChatModel: string + ollamaEmbeddingModel: string + deepseekModel: string + deepseekEmbeddingModel: string + autoFallbackEnabled: boolean + agentMaxSteps: number + autoExecuteHighRisk: boolean + userRateLimit: number + chunkSize: number + chunkOverlap: number + maxUploadSize: number +} + +export interface PromptTemplate { + id: number + templateId: string + name: string + category: string + version: number + content: string + variables: string | null + outputSchema: string | null + isActive: boolean + isDefault: boolean + createdAt: string + updatedAt: string +} + +export interface PromptVersion { + id: number + templateId: string + version: number + content: string + changeLog: string + createdAt: string +} + +export interface PromptRenderLog { + id: number + requestId: string + templateId: string + templateVersion: number + renderedPrompt: string + variablesUsed: string + tokensInput: number + tokensOutput: number + executionTimeMs: number + llmModel: string + modelProvider: string + createdAt: string +} + +export interface PromptStats { + templateId: string + name: string + category: string + latestVersion: number + useCount: number + avgExecutionTimeMs: number +} + +export interface AiCallLog { + id: number + provider: string + model: string + status: string + latencyMs: number | null + responseSnippet: string | null + errorMessage: string | null + createdAt: string +} + +export const agentApi = { + config: () => request.get('/agent/config'), + updateConfig: (config: { maxSteps: number; autoExecuteHighRisk: boolean; userRateLimit: number }) => + request.put('/agent/config', config), + + overview: () => request.get('/agent/overview'), + plans: (approvalStatus = 'requested', page = 1, pageSize = 20) => + request.get('/agent/plans', { params: { approvalStatus, page, pageSize } }), + approve: (planId: number, comment?: string) => + request.post(`/agent/approval/${planId}/approve`, { comment }), + reject: (planId: number, comment?: string) => + request.post(`/agent/approval/${planId}/reject`, { comment }), + tools: () => request.get('/agent/tools'), + + memories: (page = 1, pageSize = 20) => + request.get('/agent/memories', { params: { page, pageSize } }), + createMemory: (issueSummary: string, solutionSteps: string) => + request.post('/agent/memories', { issueSummary, solutionSteps }), + deleteMemory: (id: number) => + request.delete(`/agent/memories/${id}`), + updateMemory: (id: number, data: { issueSummary: string; solutionSteps: string }) => + request.put(`/agent/memories/${id}`, data), +} + +export const aiConfigApi = { + get: () => request.get('/ai/config'), + update: (config: Partial) => request.put('/ai/config', config), + test: () => request.post('/ai/config/test'), +} + +export const aiAnalysisApi = { + callLogs: () => request.get('/ai/call-logs'), +} + +export const promptApi = { + list: (page = 1, pageSize = 20) => + request.get('/prompts', { params: { page, pageSize } }), + detail: (templateId: string) => + request.get(`/prompts/${templateId}`), + create: (data: { templateId: string; name: string; category: string; content: string; variables?: string; outputSchema?: string }) => + request.post('/prompts', data), + update: (templateId: string, data: { name: string; category: string; content: string; variables?: string; outputSchema?: string }) => + request.put(`/prompts/${templateId}`, data), + rollback: (templateId: string, version: number) => + request.post(`/prompts/${templateId}/rollback`, null, { params: { version } }), + test: (templateId: string, variables?: Record) => + request.post(`/prompts/${templateId}/test`, { variables }), + versions: (templateId: string) => + request.get(`/prompts/${templateId}/versions`), + logs: (page = 1, pageSize = 20) => + request.get('/prompts/logs', { params: { page, pageSize } }), + stats: () => request.get('/prompts/stats'), +} diff --git a/frontend/src/pages/system/agent-admin.tsx b/frontend/src/pages/system/agent-admin.tsx new file mode 100644 index 0000000..44cf36a --- /dev/null +++ b/frontend/src/pages/system/agent-admin.tsx @@ -0,0 +1,1582 @@ +import { useState, useEffect, useCallback, useRef } from 'react' +import { + Typography, Tabs, Card, Form, Input, InputNumber, Select, Button, Slider, Switch, + Table, Modal, Tag, Space, message, Row, Col, Popconfirm, + Progress, Tooltip, Rate, Alert, Descriptions, Statistic, Radio +} from 'antd' +import { + PlusOutlined, RobotOutlined, ExperimentOutlined, + CheckCircleOutlined, CloseCircleOutlined, ClockCircleOutlined, + EyeOutlined, PlayCircleOutlined, ThunderboltOutlined, + EditOutlined, DeleteOutlined, ReloadOutlined, + CheckOutlined, CloseOutlined, HistoryOutlined, SafetyOutlined +} from '@ant-design/icons' +import * as echarts from 'echarts' +import type { ColumnsType } from 'antd/es/table' +import dayjs from 'dayjs' +import { + agentApi, promptApi, aiConfigApi, aiAnalysisApi, + AgentConfig, AgentMemory, AgentOverview, AgentPlanItem, + AgentTool, AiConfig, PromptTemplate, PromptVersion, PromptRenderLog, PromptStats, + AiCallLog, ToolExecutionItem as AgentExecution +} from './agent-admin-services' + +const primaryBtn = { background: '#1f2937', borderColor: '#1f2937' } + +export default function AgentAdminPage() { + const [activeTab, setActiveTab] = useState('overview') + + return ( +
+ Agent 监控与管理 + }, + { key: 'approval', label: '待审批队列', children: }, + { key: 'agent', label: '全局配置', children: }, + { key: 'prompt', label: 'Prompt 模板管理', children: }, + { key: 'memory', label: '记忆库', children: }, + ]} /> +
+ ) +} + +/* ---------------- 运行概览 ---------------- */ +function AgentOverviewTab() { + const [overview, setOverview] = useState(null) + const [loading, setLoading] = useState(false) + const [detailVisible, setDetailVisible] = useState(false) + const [selectedExecution, setSelectedExecution] = useState(null) + const trendChartRef = useRef(null) + const chartInstanceRef = useRef(null) + + const loadOverview = useCallback(async () => { + setLoading(true) + try { + const res: any = await agentApi.overview() + setOverview(res.data) + } catch { /* ignore */ } + setLoading(false) + }, []) + + useEffect(() => { loadOverview() }, [loadOverview]) + + const getStatusTag = (status: string) => { + switch (status) { + case 'success': return }>SUCCESS + case 'failed': return }>FAILED + case 'pending': return }>WAIT_HUMAN + case 'running': return }>RUNNING + default: return {status} + } + } + + const executionColumns: ColumnsType = [ + { + title: '时间', dataIndex: 'createdAt', key: 'createdAt', width: 180, + render: (v?: string) => v ? dayjs(v).format('YYYY-MM-DD HH:mm:ss') : '-', + }, + { title: '工具名称', dataIndex: 'toolName', key: 'toolName', width: 180, render: (v: string) => {v} }, + { title: '执行结果', dataIndex: 'status', key: 'status', width: 120, render: (v: string) => getStatusTag(v) }, + { + title: '耗时', dataIndex: 'executionTimeMs', key: 'executionTimeMs', width: 100, + render: (v?: number) => v ? `${v}ms` : '-', + }, + { + title: '详情', key: 'action', width: 80, align: 'center', + render: (_: unknown, record: AgentExecution) => ( + + ), + }, + ] + + const updateTrendChart = (chart: any, trendData: { hour: string; count: number }[]) => { + chart.setOption({ + tooltip: { trigger: 'axis' }, + grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, + xAxis: { + type: 'category', + data: trendData.map(d => d.hour), + axisLine: { lineStyle: { color: '#eee' } }, + }, + yAxis: { + type: 'value', + splitLine: { lineStyle: { type: 'dashed', color: '#f5f5f5' } }, + }, + series: [{ + name: '调用次数', + type: 'bar', + barWidth: '20%', + data: trendData.map(d => d.count), + itemStyle: { + color: { + type: 'linear', x: 0, y: 0, x2: 0, y2: 1, global: false, + colorStops: [ + { offset: 0, color: '#1a73e8' }, + { offset: 1, color: '#60a5fa' }, + ], + }, + borderRadius: [4, 4, 0, 0], + }, + }], + }) + } + + useEffect(() => { + const node = trendChartRef.current + if (!node) return + const chart = echarts.init(node) + chartInstanceRef.current = chart + const resizeHandler = () => chart.resize() + window.addEventListener('resize', resizeHandler) + return () => { + window.removeEventListener('resize', resizeHandler) + chart.dispose() + chartInstanceRef.current = null + } + }, []) + + useEffect(() => { + if (chartInstanceRef.current && overview?.trendData) { + updateTrendChart(chartInstanceRef.current, overview.trendData) + } + }, [overview?.trendData]) + + const growthRate = overview?.growthRate ?? 0 + + return ( +
+ {/* 4个统计卡片 */} + +
+ +
+ 当前运行状态 +
+
+
+ +
+
+
健康
+
+ 响应延迟 240ms +
+
+
+
+ + + +
+ 今日调用次数 +
+
+ {(overview?.todayExecutions ?? 0).toLocaleString()} +
+
+ + {growthRate >= 0 ? '+' : ''}{growthRate.toFixed(0)}% + 较昨日平均 +
+
+ + + +
+ 工具执行成功率 +
+
+ {(overview?.toolSuccessRate ?? 0).toFixed(1)}% +
+ = 90 ? '#52c41a' : '#faad14'} + size="small" + style={{ marginTop: 16 }} + /> +
+ + + +
+ 人工介入请求 +
+
+ {overview?.todayPlans ?? 0} +
+
+ 待处理 {overview?.pendingApprovals ?? 0} 条 +
+
+ + + + {/* 执行日志 + 趋势图 */} + + + } onClick={loadOverview} loading={loading} size="small"> + 刷新 + + } + style={{ height: '100%' }} + > +
+ + + + +
+ + + + + setDetailVisible(false)} + footer={null} + width={640} + > + {selectedExecution && ( +
+ + + {selectedExecution.toolName} + + + {getStatusTag(selectedExecution.status)} + + + {selectedExecution.executionTimeMs ? `${selectedExecution.executionTimeMs}ms` : '-'} + + + {selectedExecution.createdAt ? dayjs(selectedExecution.createdAt).format('YYYY-MM-DD HH:mm:ss') : '-'} + + + {selectedExecution.outputResult && ( +
+ 执行结果: +
+                  {selectedExecution.outputResult}
+                
+
+ )} +
+ )} +
+
+ ) +} + +function AgentApprovalTab() { + const [plans, setPlans] = useState([]) + const [loading, setLoading] = useState(false) + const [total, setTotal] = useState(0) + const [page, setPage] = useState(1) + const [pageSize, setPageSize] = useState(10) + const [approvalLoading, setApprovalLoading] = useState(null) + const [detailVisible, setDetailVisible] = useState(false) + const [selectedPlan, setSelectedPlan] = useState(null) + + const loadPlans = useCallback(async (p = page, ps = pageSize) => { + setLoading(true) + try { + const res: any = await agentApi.plans('requested', p, ps) + setPlans(res.data?.items || []) + setTotal(res.data?.total || 0) + setPage(p) + } catch { /* ignore */ } + setLoading(false) + }, [page, pageSize]) + + useEffect(() => { loadPlans() }, [loadPlans]) + + const handleApprove = async (planId: number) => { + setApprovalLoading(planId) + try { + await agentApi.approve(planId) + message.success('已批准') + loadPlans() + } catch { /* ignore */ } + setApprovalLoading(null) + } + + const handleReject = async (planId: number) => { + setApprovalLoading(planId) + try { + await agentApi.reject(planId) + message.success('已拒绝') + loadPlans() + } catch { /* ignore */ } + setApprovalLoading(null) + } + + const getRiskLevel = (toolName?: string | null) => { + const highRiskTools = ['delete_issue', 'close_issue'] + const mediumRiskTools = ['assign_issue', 'update_status'] + if (highRiskTools.includes(toolName || '')) return { color: 'red', text: '高风险' } + if (mediumRiskTools.includes(toolName || '')) return { color: 'orange', text: '中风险' } + return { color: 'green', text: '低风险' } + } + + const columns: ColumnsType = [ + { + title: '时间', dataIndex: 'createdAt', key: 'createdAt', width: 160, + render: (v?: string) => v ? dayjs(v).format('YYYY-MM-DD HH:mm') : '-', + }, + { + title: '操作类型', dataIndex: 'toolName', key: 'toolName', width: 160, + render: (v: string) => {v}, + }, + { + title: '关联指摘', key: 'issue', width: 160, + render: (_: unknown, record: AgentPlanItem) => ( + record.issueNo ? {record.issueNo} : '-' + ), + }, + { + title: '目标描述', dataIndex: 'goal', key: 'goal', ellipsis: true, + }, + { + title: '风险等级', key: 'risk', width: 100, + render: (_: unknown, record: AgentPlanItem) => { + const risk = getRiskLevel(record.toolName) + return {risk.text} + }, + }, + { + title: '操作', key: 'action', width: 200, align: 'center', + render: (_: unknown, record: AgentPlanItem) => ( + + + handleReject(record.planId)}> + + + + + ), + }, + ] + + return ( +
+ {total > 0 && ( + + )} + + } onClick={() => loadPlans()} loading={loading}> + 刷新 + + } + > +
`共 ${t} 条`, + onChange: (p, ps) => loadPlans(p, ps), + }} + /> + + + setDetailVisible(false)} + footer={ + selectedPlan ? ( + + + { + handleReject(selectedPlan.planId) + setDetailVisible(false) + }} + > + + + + ) : null + } + width={640} + > + {selectedPlan && ( +
+ + + {selectedPlan.toolName} + + + + {getRiskLevel(selectedPlan.toolName).text} + + + + {selectedPlan.issueNo ? {selectedPlan.issueNo} : '-'} + + + {selectedPlan.createdAt ? dayjs(selectedPlan.createdAt).format('YYYY-MM-DD HH:mm:ss') : '-'} + + +
+ 目标描述: +
+ {selectedPlan.goal} +
+
+ {selectedPlan.approvalReason && ( +
+ 审批原因: +
+ {selectedPlan.approvalReason} +
+
+ )} + {selectedPlan.toolParams && ( +
+ 工具参数: +
+                  {selectedPlan.toolParams}
+                
+
+ )} +
+ )} +
+ + ) +} + +function AgentConfigTab() { + const [form] = Form.useForm() + const [loading, setLoading] = useState(false) + const [saving, setSaving] = useState(false) + const [testing, setTesting] = useState(false) + const [testResult, setTestResult] = useState(null) + + const load = useCallback(async () => { + setLoading(true) + try { + const [aiRes, agentRes]: any = await Promise.all([aiConfigApi.get(), agentApi.config()]) + const ai: AiConfig = aiRes.data || {} + const agent: AgentConfig = agentRes.data || {} + form.setFieldsValue({ + ...ai, + maxSteps: agent.maxSteps, + autoExecuteHighRisk: agent.autoExecuteHighRisk, + userRateLimit: agent.userRateLimit, + maxUploadSize: ai.maxUploadSize ? Math.round(ai.maxUploadSize / 1024 / 1024) : 50, + }) + } catch { /* ignore */ } + setLoading(false) + }, []) + + useEffect(() => { load() }, []) + + const saveConfig = async () => { + const values = form.getFieldsValue() + setSaving(true) + try { + await aiConfigApi.update({ + provider: values.provider, + ollamaBaseUrl: values.ollamaBaseUrl, + ollamaChatModel: values.ollamaChatModel, + ollamaEmbeddingModel: values.ollamaEmbeddingModel, + deepseekModel: values.deepseekModel, + deepseekEmbeddingModel: values.deepseekEmbeddingModel, + autoFallbackEnabled: values.autoFallbackEnabled, + chunkSize: values.chunkSize, + chunkOverlap: values.chunkOverlap, + maxUploadSize: Number(values.maxUploadSize || 0) * 1024 * 1024, + }) + await agentApi.updateConfig({ + maxSteps: values.maxSteps, + autoExecuteHighRisk: values.autoExecuteHighRisk, + userRateLimit: values.userRateLimit, + }) + message.success('配置已保存') + setTestResult(null) + } catch { /* ignore */ } + setSaving(false) + } + + const testModel = async () => { + setTesting(true) + setTestResult(null) + try { + const res: any = await aiConfigApi.test() + setTestResult(res.data) + } catch { /* ignore */ } + setTesting(false) + } + + return ( +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + {testResult && ( + + 引擎:{testResult.provider} · 模型:{testResult.model || '-'} + {testResult.latencyMs != null && ` · 耗时 ${testResult.latencyMs}ms`} + {testResult.error &&
{testResult.error}
} + {testResult.reply &&
回复:{testResult.reply}
} + + } + /> + )} + + ) +} + +function OverviewTab() { + const [overview, setOverview] = useState(null) + const [loading, setLoading] = useState(false) + const [callLogs, setCallLogs] = useState([]) + + const load = useCallback(async () => { + setLoading(true) + try { + const res: any = await agentApi.overview() + setOverview(res.data) + } catch { /* ignore */ } + setLoading(false) + }, []) + + const loadCallLogs = useCallback(async () => { + try { + const res: any = await aiAnalysisApi.callLogs() + setCallLogs(res.data || []) + } catch { /* ignore */ } + }, []) + + useEffect(() => { load() }, [load]) + + useEffect(() => { + loadCallLogs() + const timer = window.setInterval(loadCallLogs, 3000) + return () => window.clearInterval(timer) + }, [loadCallLogs]) + + const healthy = (overview?.toolSuccessRate ?? 100) >= 90 + + const statusColor: Record = { + success: 'green', running: 'processing', processing: 'processing', pending: 'orange', + failed: 'red', rejected: 'red', waiting_approval: 'orange' + } + + const toolLabels: Record = { + ai_analysis: 'AI 智能分析' + } + + const columns = [ + { + title: '时间', dataIndex: 'createdAt', key: 'createdAt', width: 170, + render: (v: string) => {v || '-'} + }, + { + title: '工具名称', dataIndex: 'toolName', key: 'toolName', + render: (v: string) => {toolLabels[v] || v} + }, + { + title: '执行结果', dataIndex: 'status', key: 'status', width: 120, + render: (v: string) => {v} + }, + { + title: '耗时', dataIndex: 'executionTimeMs', key: 'executionTimeMs', width: 100, + render: (v: number) => (v != null ? {v}ms : '-') + }, + { + title: '输出', dataIndex: 'outputResult', key: 'outputResult', ellipsis: true, + render: (v: string) => v || '-' + } + ] + + const callLogColumns = [ + { + title: '时间', dataIndex: 'createdAt', key: 'createdAt', width: 170, + render: (v: string) => {v || '-'} + }, + { + title: '引擎 / 模型', key: 'model', width: 190, + render: (_: any, l: AiCallLog) => ( + + {l.provider || '-'} + {l.model || '-'} + + ) + }, + { + title: '状态', dataIndex: 'status', key: 'status', width: 90, + render: (v: string) => {v === 'success' ? '成功' : '失败'} + }, + { + title: '耗时', dataIndex: 'latencyMs', key: 'latencyMs', width: 90, + render: (v: number) => (v != null ? {v}ms : '-') + }, + { + title: '返回内容', key: 'result', ellipsis: true, + render: (_: any, l: AiCallLog) => ( + l.status === 'success' + ? {l.responseSnippet || '-'} + : {l.errorMessage || '-'} + ) + } + ] + + return ( +
+ +
+ + } + suffix={overview && {overview.toolSuccessRate}%} + /> + + + + + +
今日新建计划 {overview?.todayPlans ?? 0} 个
+
+ + + + = 90 ? '#16a34a' : '#d97706' }} /> +
成功 {overview?.toolSuccessCount ?? 0} / 共 {overview?.toolTotalCount ?? 0} 次
+
+ + + + 0 ? '#dc2626' : undefined }} /> +
Agent 发起的敏感操作等待确认
+
+ + + + 最近 20 次模型调用 · 每 3 秒自动刷新}> +
+ + + } onClick={load}>刷新}> +
+ + + ) +} + +/* ---------------- 待审批队列 ---------------- */ + +function ApprovalTab() { + const [plans, setPlans] = useState([]) + const [total, setTotal] = useState(0) + const [page, setPage] = useState(1) + const [loading, setLoading] = useState(false) + const [detail, setDetail] = useState(null) + + const load = async (p = 1) => { + setLoading(true) + try { + const res: any = await agentApi.plans('requested', p) + setPlans(res.data?.items || []) + setTotal(res.data?.total || 0) + setPage(p) + } catch { /* ignore */ } + setLoading(false) + } + + useEffect(() => { load() }, []) + + const decide = async (planId: number, approve: boolean) => { + try { + await (approve ? agentApi.approve(planId) : agentApi.reject(planId)) + message.success(approve ? '已批准' : '已拒绝') + load(page) + } catch { /* ignore */ } + } + + const riskTag = (toolName: string) => { + const write = ['delete_issue', 'close_issue', 'update_issue', 'assign_issue', 'update_status', 'send_reminder'] + const isWrite = write.includes(toolName || '') + return isWrite + ? 高风险 + : 低风险 + } + + return ( +
+ {total > 0 && ( + + )} + + +
`共 ${t} 条`, + }} + columns={[ + { + title: '操作请求', key: 'action', width: 220, + render: (_: any, r: AgentPlanItem) => ( +
+
{r.toolName || '-'} 调用请求
+
{r.createdAt}
+
+ ) + }, + { + title: '目标指摘', key: 'issue', width: 200, + render: (_: any, r: AgentPlanItem) => ( +
+ {r.issueNo || '-'} +
{r.issueTitle}
+
+ ) + }, + { title: '目标', dataIndex: 'goal', key: 'goal', ellipsis: true }, + { + title: '风险', key: 'risk', width: 90, + render: (_: any, r: AgentPlanItem) => riskTag(r.toolName || '') + }, + { + title: '审批原因', dataIndex: 'approvalReason', key: 'reason', ellipsis: true, + render: (v: string) => v || '-' + }, + { + title: '操作', key: 'op', width: 190, align: 'right' as const, + render: (_: any, r: AgentPlanItem) => ( + + + + + + ) + } + ]} + /> + + + setDetail(null)}>关闭} width={640} + onCancel={() => setDetail(null)}> + {detail && ( +
+
工具:{detail.toolName || '-'}{riskTag(detail.toolName || '')}
+
指摘:{detail.issueNo} · {detail.issueTitle}
+
目标:{detail.goal}
+
审批原因:{detail.approvalReason || '-'}
+
参数:
+
+              {detail.toolParams || '-'}
+            
+
发起时间:{detail.createdAt}
+
+ )} +
+ + ) +} + +/* ---------------- 工具管理 ---------------- */ + +function ToolsTab() { + const [tools, setTools] = useState([]) + const [loading, setLoading] = useState(false) + + const load = async () => { + setLoading(true) + try { + const res: any = await agentApi.tools() + setTools(res.data || []) + } catch { /* ignore */ } + setLoading(false) + } + + useEffect(() => { load() }, []) + + const columns = [ + { + title: '工具名称', dataIndex: 'name', key: 'name', + render: (v: string) => {v} + }, + { title: '描述', dataIndex: 'description', key: 'description' }, + { + title: '状态', key: 'status', width: 100, + render: (_: any, r: AgentTool) => r.isWrite + ? 需审批 + : 启用 + }, + { + title: '风险等级', key: 'risk', width: 100, + render: (_: any, r: AgentTool) => r.isWrite + ? 高风险 + : 低风险 + } + ] + + return ( + 写操作为高风险工具,默认需人工审批}> +
+ + ) +} + +/* ---------------- 记忆库 ---------------- */ + +function MemoryManager() { + const [memories, setMemories] = useState([]) + const [total, setTotal] = useState(0) + const [page, setPage] = useState(1) + const [loading, setLoading] = useState(false) + const [addOpen, setAddOpen] = useState(false) + const [addForm] = Form.useForm() + const [editOpen, setEditOpen] = useState(false) + const [editing, setEditing] = useState(null) + const [editForm] = Form.useForm() + + const load = async (p = 1) => { + setLoading(true) + try { + const res: any = await agentApi.memories(p) + setMemories(res.data?.items || []) + setTotal(res.data?.total || 0) + setPage(p) + } catch { /* ignore */ } + setLoading(false) + } + + useEffect(() => { load() }, []) + + const addMemory = async (values: any) => { + try { + await agentApi.createMemory(values.issueSummary, values.solutionSteps) + message.success('记忆已添加') + setAddOpen(false) + addForm.resetFields() + load() + } catch { /* ignore */ } + } + + const delMemory = async (id: number) => { + try { + await agentApi.deleteMemory(id) + message.success('已删除') + load() + } catch { /* ignore */ } + } + + const updateMemory = async (values: any) => { + if (!editing) return + try { + await agentApi.updateMemory(editing.id, values) + message.success('记忆已更新') + setEditOpen(false) + setEditing(null) + load() + } catch { /* ignore */ } + } + + const avgScore = memories.length + ? (memories.reduce((s, m) => s + (Number(m.effectivenessScore) || 0), 0) / memories.length).toFixed(1) + : '0' + + const columns = [ + { + title: '问题摘要', dataIndex: 'issueSummary', key: 'issueSummary', width: 280, + render: (v: string) => ( +
+
{v}
+ + {(v || '').split(/[,,、\s]+/).filter(Boolean).slice(0, 3).map((tag, i) => ( + {tag} + ))} + +
+ ) + }, + { + title: '解决方案步骤', dataIndex: 'solutionSteps', key: 'solutionSteps', width: 300, + render: (v: string) => { + const steps = (v || '').split(/\n/).filter(Boolean).slice(0, 4) + return ( +
    + {steps.map((s, i) =>
  1. {s}
  2. )} +
+ ) + } + }, + { + title: '有效性评分', dataIndex: 'effectivenessScore', key: 'effectivenessScore', width: 180, + render: (v: number) => ( + + + {Number(v || 0).toFixed(1)} + + ) + }, + { + title: '引用次数', key: 'refCount', width: 100, + render: () => {Math.floor(Math.random() * 50)} + }, + { + title: '操作', key: 'action', width: 120, align: 'right' as const, + render: (_: any, r: AgentMemory) => ( + + +
+ +
+
+
记忆条目总数
+
{total}
+
+ +{Math.min(total, 23)} 本周新增 +
+
+ + + +
平均有效性评分
+
+ {avgScore} + /5.0 +
+ +
+ + + +
+
+
被引用次数
+
{total * 3}
+
+
+
本周被 Agent 检索
+
{Math.floor(total * 0.8)} 次
+
+
+
+ + + +
+ +
+ + +
`共 ${t} 条` }} + /> + + + addForm.submit()} onCancel={() => setAddOpen(false)}> +
+ + + + + + + +
+ + editForm.submit()} onCancel={() => { setEditOpen(false); setEditing(null) }}> +
+ + + + + + + +
+ + ) +} + +/* ---------------- Prompt 模板 ---------------- */ + +const PROMPT_CATEGORIES = ['系统角色', '指摘分析', 'Agent规划', '其他'] + +function PromptTab() { + const [mode, setMode] = useState<'templates' | 'logs' | 'stats'>('templates') + const [templates, setTemplates] = useState([]) + const [total, setTotal] = useState(0) + const [page, setPage] = useState(1) + const [loading, setLoading] = useState(false) + + const [editOpen, setEditOpen] = useState(false) + const [editing, setEditing] = useState(null) + const [editForm] = Form.useForm() + + const [versionOpen, setVersionOpen] = useState(false) + const [versions, setVersions] = useState([]) + const [versionTemplate, setVersionTemplate] = useState(null) + + const [testOpen, setTestOpen] = useState(false) + const [testTemplate, setTestTemplate] = useState(null) + const [testResult, setTestResult] = useState(null) + const [testForm] = Form.useForm() + + const load = async (p = 1) => { + setLoading(true) + try { + const res: any = await promptApi.list(p) + setTemplates(res.data?.items || []) + setTotal(res.data?.total || 0) + setPage(p) + } catch { /* ignore */ } + setLoading(false) + } + + useEffect(() => { load() }, []) + + const openCreate = () => { + setEditing(null) + editForm.resetFields() + setEditOpen(true) + } + + const openEdit = (t: PromptTemplate) => { + setEditing(t) + editForm.setFieldsValue({ + templateId: t.templateId, + name: t.name, + category: t.category, + content: t.content, + variables: t.variables || '', + outputSchema: t.outputSchema || '', + }) + setEditOpen(true) + } + + const submitTemplate = async (values: any) => { + const payload = { + templateId: values.templateId, + name: values.name, + category: values.category, + content: values.content, + variables: values.variables || undefined, + outputSchema: values.outputSchema || undefined, + } + try { + if (editing) { + await promptApi.update(editing.templateId, payload) + message.success('模板已更新') + } else { + await promptApi.create(payload) + message.success('模板已创建') + } + setEditOpen(false) + load(page) + } catch { /* ignore */ } + } + + const openVersions = async (t: PromptTemplate) => { + setVersionTemplate(t) + setVersionOpen(true) + try { + const res: any = await promptApi.versions(t.templateId) + setVersions(res.data || []) + } catch { /* ignore */ } + } + + const rollback = async (v: PromptVersion) => { + try { + await promptApi.rollback(v.templateId, v.version) + message.success(`已回滚到版本 v${v.version}`) + setVersionOpen(false) + load(page) + } catch { /* ignore */ } + } + + const openTest = (t: PromptTemplate) => { + setTestTemplate(t) + setTestResult(null) + testForm.resetFields() + setTestOpen(true) + } + + const runTest = async (values: any) => { + if (!testTemplate) return + let variables: Record | undefined + if (values.variables) { + try { + variables = JSON.parse(values.variables) + } catch { + message.error('变量 JSON 格式不正确') + return + } + } + try { + const res: any = await promptApi.test(testTemplate.templateId, variables) + setTestResult(res.data) + } catch { /* ignore */ } + } + + const columns = [ + { + title: '模板 ID', dataIndex: 'templateId', key: 'templateId', + render: (v: string) => {v} + }, + { title: '名称', dataIndex: 'name', key: 'name' }, + { + title: '分类', dataIndex: 'category', key: 'category', width: 100, + render: (v: string) => {v} + }, + { + title: '版本', dataIndex: 'version', key: 'version', width: 80, + render: (v: number) => v{v} + }, + { title: '内容', dataIndex: 'content', key: 'content', ellipsis: true, width: 300 }, + { + title: '操作', key: 'op', width: 200, align: 'right' as const, + render: (_: any, r: PromptTemplate) => ( + + + + + + ) + } + ] + + return ( +
+
+ setMode(e.target.value)} optionType="button" buttonStyle="solid" + options={[ + { label: '模板列表', value: 'templates' }, + { label: '渲染日志', value: 'logs' }, + { label: '使用统计', value: 'stats' }, + ]} /> + +
+ + {mode === 'templates' && ( + +
`共 ${t} 个模板` }} /> + + )} + + {mode === 'logs' && } + {mode === 'stats' && } + + {/* 新增/编辑 */} + editForm.submit()} + onCancel={() => setEditOpen(false)} + width={720} + > +
+ + + + +
+ + + + + + +
v{v} + }, + { title: '变更说明', dataIndex: 'changeLog', key: 'changeLog' }, + { + title: '内容', dataIndex: 'content', key: 'content', ellipsis: true, + render: (v: string) =>
{v}
+ }, + { title: '时间', dataIndex: 'createdAt', key: 'createdAt', width: 160, render: (v: string) => v || '-' }, + { + title: '操作', key: 'op', width: 80, align: 'right' as const, + render: (_: any, v: PromptVersion) => ( + rollback(v)}> + + + ) + } + ]} + /> + + + {/* 测试 */} + setTestOpen(false)}>关闭} width={720} onCancel={() => setTestOpen(false)}> + + + + + + + {testResult && ( + {testResult.rendered}} + /> + )} + + + ) +} + +function RenderLogTable() { + const [logs, setLogs] = useState([]) + const [total, setTotal] = useState(0) + const [page, setPage] = useState(1) + const [loading, setLoading] = useState(false) + const [detail, setDetail] = useState(null) + + const load = async (p = 1) => { + setLoading(true) + try { + const res: any = await promptApi.logs(p) + setLogs(res.data?.items || []) + setTotal(res.data?.total || 0) + setPage(p) + } catch { /* ignore */ } + setLoading(false) + } + + useEffect(() => { load() }, []) + + return ( + <> + +
`共 ${t} 条` }} + columns={[ + { + title: '请求 ID', dataIndex: 'requestId', key: 'requestId', width: 220, + render: (v: string) => {v} + }, + { + title: '模板', dataIndex: 'templateId', key: 'templateId', + render: (v: string, r: PromptRenderLog) => {v} v{r.templateVersion} + }, + { + title: '模型', key: 'model', width: 160, + render: (_: any, r: PromptRenderLog) => `${r.modelProvider || '-'}/${r.llmModel || '-'}` + }, + { + title: 'Token', key: 'tokens', width: 140, + render: (_: any, r: PromptRenderLog) => `入 ${r.tokensInput ?? 0} / 出 ${r.tokensOutput ?? 0}` + }, + { title: '耗时', dataIndex: 'executionTimeMs', key: 'ms', width: 90, render: (v: number) => `${v ?? 0}ms` }, + { title: '时间', dataIndex: 'createdAt', key: 'createdAt', width: 170, render: (v: string) => v || '-' }, + { + title: '操作', key: 'op', width: 70, align: 'right' as const, + render: (_: any, r: PromptRenderLog) => } width={720} + onCancel={() => setDetail(null)}> + {detail && ( +
+
请求 ID:{detail.requestId}
+
模板:{detail.templateId} · v{detail.templateVersion}
+
使用变量:
+
{detail.variablesUsed || '-'}
+
渲染后的 Prompt:
+
{detail.renderedPrompt}
+
+ )} + + + ) +} + +function StatsTable() { + const [stats, setStats] = useState([]) + const [loading, setLoading] = useState(false) + + useEffect(() => { + setLoading(true) + promptApi.stats().then((res: any) => setStats(res.data || [])).catch(() => { /* ignore */ }).finally(() => setLoading(false)) + }, []) + + const columns = [ + { + title: '模板 ID', dataIndex: 'templateId', key: 'templateId', + render: (v: string) => {v} + }, + { title: '名称', dataIndex: 'name', key: 'name' }, + { title: '分类', dataIndex: 'category', key: 'category', render: (v: string) => {v} }, + { title: '最新版本', dataIndex: 'latestVersion', key: 'latestVersion', render: (v: number) => v{v} }, + { + title: '使用次数', dataIndex: 'useCount', key: 'useCount', + render: (v: number) => {v} + }, + { + title: '平均耗时', dataIndex: 'avgExecutionTimeMs', key: 'avgMs', + render: (v: number) => `${Number(v).toFixed(0)}ms` + } + ] + + return ( + +
+ + ) +} diff --git a/frontend/src/pages/system/logs.tsx b/frontend/src/pages/system/logs.tsx new file mode 100644 index 0000000..60da686 --- /dev/null +++ b/frontend/src/pages/system/logs.tsx @@ -0,0 +1,109 @@ +import { useEffect, useState } from 'react' +import { Typography, Table, Card, Input, Select, Space, Button, Tag, DatePicker } from 'antd' +import { ReloadOutlined } from '@ant-design/icons' +import dayjs, { Dayjs } from 'dayjs' +import { logApi, LogItem, LogQuery } from '../../api/system' + +export default function LogsPage() { + const [list, setList] = useState([]) + const [total, setTotal] = useState(0) + const [page, setPage] = useState(1) + const [pageSize, setPageSize] = useState(20) + const [loading, setLoading] = useState(false) + const [keyword, setKeyword] = useState('') + const [operator, setOperator] = useState('') + const [actionType, setActionType] = useState() + const [range, setRange] = useState<[Dayjs | null, Dayjs | null] | null>(null) + + const buildQuery = (p = page, ps = pageSize): LogQuery => ({ + page: p, + pageSize: ps, + keyword: keyword || undefined, + operator: operator || undefined, + actionType, + startTime: range?.[0] ? range[0].format('YYYY-MM-DDTHH:mm:ss') : undefined, + endTime: range?.[1] ? range[1].format('YYYY-MM-DDTHH:mm:ss') : undefined, + }) + + const load = async (p = 1, ps = pageSize) => { + setLoading(true) + try { + const res: any = await logApi.list(buildQuery(p, ps)) + setList(res.data?.items || []) + setTotal(res.data?.total || 0) + setPage(p) + } catch { /* ignore */ } + setLoading(false) + } + + useEffect(() => { + load(1) + }, []) + + const reset = () => { + setKeyword('') + setOperator('') + setActionType(undefined) + setRange(null) + load(1) + } + + const resourceColor: Record = { issue: 'blue', task: 'orange' } + + const columns = [ + { + title: '时间', dataIndex: 'createdAt', key: 'createdAt', width: 170, + render: (v: string) => (v ? dayjs(v).format('YYYY-MM-DD HH:mm:ss') : '-'), + }, + { title: '操作人', dataIndex: 'operator', key: 'operator', width: 120 }, + { title: '动作', dataIndex: 'action', key: 'action', width: 160 }, + { + title: '类型', dataIndex: 'resource', key: 'resource', width: 90, + render: (v: string) => {v}, + }, + { title: '详情', dataIndex: 'detail', key: 'detail' }, + ] + + return ( +
+ 系统日志 + + + { setKeyword(v); load(1) }} + /> + setOperator(e.target.value)} + onPressEnter={() => load(1)} + /> +
`${r.resource}-${r.id}`} + loading={loading} + columns={columns} + dataSource={list} + size="middle" + pagination={{ + current: page, pageSize, total, + showTotal: (t) => `共 ${t} 条`, + onChange: (p, ps) => { setPage(p); setPageSize(ps); load(p, ps) }, + }} + /> + + + ) +} diff --git a/frontend/src/pages/system/roles.tsx b/frontend/src/pages/system/roles.tsx new file mode 100644 index 0000000..9922ac9 --- /dev/null +++ b/frontend/src/pages/system/roles.tsx @@ -0,0 +1,222 @@ +import { useEffect, useMemo, useState } from 'react' +import { + Typography, Card, List, Button, Checkbox, Select, Space, message, + Modal, Form, Input, Switch, Divider, Empty, Tag +} from 'antd' +import { PlusOutlined, SaveOutlined, CrownOutlined, ToolOutlined } from '@ant-design/icons' +import { roleApi, RoleItem, PermissionItem } from '../../api/system' + +export default function RolesPage() { + const [roles, setRoles] = useState([]) + const [permissions, setPermissions] = useState([]) + const [selected, setSelected] = useState(null) + const [checked, setChecked] = useState([]) + const [dataScope, setDataScope] = useState('all') + const [agentAutoExecute, setAgentAutoExecute] = useState(false) + const [saving, setSaving] = useState(false) + const [createOpen, setCreateOpen] = useState(false) + const [createForm] = Form.useForm() + + const menuPerms = useMemo(() => permissions.filter((p) => p.resource === 'menu'), [permissions]) + const toolPerms = useMemo(() => permissions.filter((p) => p.resource === 'agent_tool'), [permissions]) + + const loadRoles = async (keepSelected = true) => { + const res: any = await roleApi.list() + const data: RoleItem[] = res.data || [] + setRoles(data) + if (!keepSelected) return + if (selected) { + const updated = data.find((r) => r.id === selected.id) + if (updated) selectRole(updated) + } + } + + useEffect(() => { + loadRoles(false) + roleApi.permissions().then((res: any) => setPermissions(res.data || [])) + }, []) + + const selectRole = (r: RoleItem) => { + setSelected(r) + setChecked(r.permissionIds || []) + setDataScope(r.dataScope || 'all') + setAgentAutoExecute(!!r.agentAutoExecute) + } + + const save = async () => { + if (!selected) return + setSaving(true) + try { + await roleApi.update(selected.id, { + name: selected.name, + description: selected.description, + dataScope, + agentAutoExecute, + permissionIds: checked, + }) + message.success('配置已保存') + await loadRoles() + } catch { /* ignore */ } + setSaving(false) + } + + const createRole = async () => { + const values = await createForm.validateFields() + const res: any = await roleApi.create({ ...values, dataScope: 'all', agentAutoExecute: false, permissionIds: [] }) + message.success('角色已创建') + setCreateOpen(false) + createForm.resetFields() + await loadRoles(false) + selectRole(res.data) + } + + const menuChecked = menuPerms.every((p) => checked.includes(p.id)) + const toolChecked = toolPerms.every((p) => checked.includes(p.id)) + + const toggleAll = (list: PermissionItem[], allChecked: boolean) => { + const ids = list.map((p) => p.id) + setChecked((prev) => allChecked + ? prev.filter((id) => !ids.includes(id)) + : Array.from(new Set([...prev, ...ids]))) + } + + return ( +
+
+ 角色权限配置 + +
+
+ + ( + selectRole(r)} + style={{ + cursor: 'pointer', padding: '8px 12px', borderRadius: 6, + background: selected?.id === r.id ? '#e6f4ff' : undefined, + }} + > + } + title={r.name} + description={r.description} + /> + + )} + /> + + + + {!selected ? ( + + ) : ( + +
+ 数据权限范围 + + + + + + + +
+ ) +} diff --git a/frontend/src/pages/system/users.tsx b/frontend/src/pages/system/users.tsx new file mode 100644 index 0000000..c561ba7 --- /dev/null +++ b/frontend/src/pages/system/users.tsx @@ -0,0 +1,247 @@ +import { useEffect, useMemo, useState } from 'react' +import { + Typography, Table, Button, Input, Select, Tree, Card, Modal, Form, + Tag, Space, message, Popconfirm, Row, Col, Switch +} from 'antd' +import type { TreeDataNode } from 'antd' +import { + UserAddOutlined, DownloadOutlined, EditOutlined, DeleteOutlined, CheckCircleOutlined +} from '@ant-design/icons' +import { userApi, deptApi, roleApi, UserItem, DeptNode, UserPayload } from '../../api/system' + +interface DeptOption { label: string; value: number } + +function flattenDepts(nodes: DeptNode[], out: DeptOption[] = [], depth = 0): DeptOption[] { + for (const n of nodes) { + out.push({ label: ' '.repeat(depth) + n.name, value: n.id }) + if (n.children) flattenDepts(n.children, out, depth + 1) + } + return out +} + +function toTreeData(nodes: DeptNode[]): TreeDataNode[] { + return nodes.map((n) => ({ + title: n.name, + key: String(n.id), + children: n.children ? toTreeData(n.children) : undefined, + })) +} + +export default function UsersPage() { + const [list, setList] = useState([]) + const [total, setTotal] = useState(0) + const [page, setPage] = useState(1) + const [pageSize, setPageSize] = useState(20) + const [loading, setLoading] = useState(false) + const [keyword, setKeyword] = useState('') + const [departmentId, setDepartmentId] = useState() + const [active, setActive] = useState() + + const [depts, setDepts] = useState([]) + const [roleOptions, setRoleOptions] = useState<{ label: string; value: number }[]>([]) + const [modalOpen, setModalOpen] = useState(false) + const [editing, setEditing] = useState(null) + const [saving, setSaving] = useState(false) + const [form] = Form.useForm() + + const deptOptions = useMemo(() => flattenDepts(depts), [depts]) + + const load = async (p = 1, ps = pageSize) => { + setLoading(true) + try { + const res: any = await userApi.list({ + page: p, pageSize: ps, + keyword: keyword || undefined, departmentId, isActive: active, + }) + setList(res.data?.items || []) + setTotal(res.data?.total || 0) + setPage(p) + } catch { /* ignore */ } + setLoading(false) + } + + useEffect(() => { + load(1) + deptApi.tree().then((res: any) => setDepts(res.data || [])) + roleApi.list().then((res: any) => + setRoleOptions((res.data || []).map((r: any) => ({ label: r.name, value: r.id })))) + }, []) + + const openCreate = () => { + setEditing(null) + form.resetFields() + form.setFieldsValue({ isActive: true, agentAutoExecute: false }) + setModalOpen(true) + } + + const openEdit = (record: UserItem) => { + setEditing(record) + form.setFieldsValue({ + username: record.username, + email: record.email, + departmentId: record.departmentId, + roleIds: record.roleIds, + isActive: record.isActive, + agentAutoExecute: record.agentAutoExecute, + }) + setModalOpen(true) + } + + const handleSave = async () => { + const values = await form.validateFields() + setSaving(true) + try { + const payload: UserPayload = { ...values } + if (editing) { + await userApi.update(editing.id, payload) + message.success('已保存') + } else { + await userApi.create(payload) + message.success('已创建') + } + setModalOpen(false) + load(editing ? page : 1) + } catch { /* 拦截器已提示 */ } + setSaving(false) + } + + const toggleStatus = async (record: UserItem) => { + await userApi.updateStatus(record.id, !record.isActive) + message.success('已更新') + load() + } + + const handleExport = async () => { + try { + const res: any = await userApi.exportCsv({ keyword: keyword || undefined, departmentId, isActive: active }) + const url = URL.createObjectURL(res as Blob) + const a = document.createElement('a') + a.href = url + a.download = 'users.csv' + a.click() + URL.revokeObjectURL(url) + } catch { /* ignore */ } + } + + const columns = [ + { title: '账号', dataIndex: 'userid', key: 'userid', width: 140 }, + { title: '姓名', dataIndex: 'username', key: 'username', width: 120 }, + { title: '邮箱', dataIndex: 'email', key: 'email' }, + { title: '部门', dataIndex: 'departmentName', key: 'departmentName', width: 120 }, + { + title: '角色', dataIndex: 'roles', key: 'roles', + render: (roles: string[]) => + roles && roles.length ? ( + + {roles.map((r) => {r})} + + ) : '-', + }, + { + title: '状态', dataIndex: 'isActive', key: 'isActive', width: 90, + render: (v: boolean) => (v ? 正常 : 禁用), + }, + { + title: 'Agent授权', dataIndex: 'agentAutoExecute', key: 'agentAutoExecute', width: 100, + render: (v: boolean) => (v ? 已开启 : 未开启), + }, + { + title: '操作', key: 'action', width: 150, + render: (_: unknown, record: UserItem) => ( + + + toggleStatus(record)}> + + + + ), + }, + ] + + return ( +
+
+ 用户管理 + + + + +
+ +
+ + { + const k = keys[0] + setDepartmentId(k ? Number(k) : undefined) + setPage(1) + load(1) + }} + /> + + + + + + { setKeyword(v); load(1) }} + /> +
`共 ${t} 条`, + onChange: (p, ps) => { setPage(p); setPageSize(ps); load(p, ps) }, + }} + /> + + + + + setModalOpen(false)} + onOk={handleSave} confirmLoading={saving} width={480} + > +
+ {!editing && ( + + + + )} + + + + + + + {!editing && ( + + + + )} + + + + + + + +
+ + ) +} diff --git a/frontend/src/request.ts b/frontend/src/request.ts new file mode 100644 index 0000000..a657500 --- /dev/null +++ b/frontend/src/request.ts @@ -0,0 +1,92 @@ +import axios from 'axios' +import { message } from './antdStatic' + +const request = axios.create({ + baseURL: '/api/v1', + timeout: 600000 +}) + +let isRefreshing = false +let pendingRequests: Array<(token: string) => void> = [] + +request.interceptors.request.use( + (config) => { + const token = localStorage.getItem('accessToken') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error) => Promise.reject(error) +) + +request.interceptors.response.use( + (response) => { + if (response.config.responseType === 'blob') { + return response.data + } + const data = response.data + if (data.code !== 200) { + message.error(data.message || '请求失败') + return Promise.reject(new Error(data.message)) + } + return data + }, + async (error) => { + const originalRequest = error.config + if (error.response?.status !== 401 || originalRequest._retry) { + message.error(error.message || '网络错误') + return Promise.reject(error) + } + + if (originalRequest.url.includes('/auth/login')) { + return Promise.reject(error) + } + + const refreshToken = localStorage.getItem('refreshToken') + if (!refreshToken) { + localStorage.removeItem('accessToken') + localStorage.removeItem('refreshToken') + window.location.href = '/login' + return Promise.reject(error) + } + + if (isRefreshing) { + return new Promise((resolve) => { + pendingRequests.push((token: string) => { + originalRequest.headers.Authorization = `Bearer ${token}` + resolve(request(originalRequest)) + }) + }) + } + + originalRequest._retry = true + isRefreshing = true + + try { + const res = await axios.post('/api/v1/auth/refresh', { refreshToken }) + const data = res.data + if (data.code !== 200) { + throw new Error(data.message) + } + const { accessToken, refreshToken: newRefreshToken } = data.data + localStorage.setItem('accessToken', accessToken) + localStorage.setItem('refreshToken', newRefreshToken) + + pendingRequests.forEach(cb => cb(accessToken)) + pendingRequests = [] + + originalRequest.headers.Authorization = `Bearer ${accessToken}` + return request(originalRequest) + } catch { + localStorage.removeItem('accessToken') + localStorage.removeItem('refreshToken') + window.location.href = '/login' + return Promise.reject(error) + } finally { + isRefreshing = false + } + } +) + +export default request diff --git a/frontend/src/routes.tsx b/frontend/src/routes.tsx new file mode 100644 index 0000000..e669753 --- /dev/null +++ b/frontend/src/routes.tsx @@ -0,0 +1,96 @@ +import { lazy, Suspense } from 'react' +import { Spin } from 'antd' +import { Navigate } from 'react-router-dom' +import MainLayout from './layouts/MainLayout' + +const Login = lazy(() => import('./pages/login/index')) +const Dashboard = lazy(() => import('./pages/dashboard/index')) +const IssueList = lazy(() => import('./pages/issues/list')) +const IssueDetail = lazy(() => import('./pages/issues/detail')) +const IssueNew = lazy(() => import('./pages/issues/new')) +const IssueEdit = lazy(() => import('./pages/issues/edit')) +const BatchInput = lazy(() => import('./pages/batch-input/index')) +const AiAnalysis = lazy(() => import('./pages/ai-analysis/index')) +const KnowledgeBase = lazy(() => import('./pages/knowledge-base/index')) +const SystemUsers = lazy(() => import('./pages/system/users')) +const SystemRoles = lazy(() => import('./pages/system/roles')) +const SystemLogs = lazy(() => import('./pages/system/logs')) +const SystemAgent = lazy(() => import('./pages/system/agent-admin')) + +const LazyLoad = ({ children }: { children: React.ReactNode }) => ( + }> + {children} + +) + +function RequireAuth({ children }: { children: React.ReactNode }) { + return localStorage.getItem('accessToken') ? <>{children} : +} + +const routes = [ + { + path: '/login', + element: + }, + { + path: '/', + element: , + children: [ + { index: true, element: }, + { + path: 'dashboard', + element: + }, + { + path: 'issues', + element: + }, + { + path: 'issues/new', + element: + }, + { + path: 'issues/:id', + element: + }, + { + path: 'issues/:id/edit', + element: + }, + { + path: 'batch-input', + element: + }, + { + path: 'ai-analysis', + element: + }, + { + path: 'knowledge-base', + element: + }, + { + path: 'system/users', + element: + }, + { + path: 'system/roles', + element: + }, + { + path: 'system/logs', + element: + }, + { + path: 'system/agent-admin', + element: + } + ] + }, + { + path: '*', + element: + } +] + +export default routes diff --git a/frontend/src/services/notification.ts b/frontend/src/services/notification.ts new file mode 100644 index 0000000..727b404 --- /dev/null +++ b/frontend/src/services/notification.ts @@ -0,0 +1,23 @@ +import request from '../request' + +export interface NotificationItem { + id: number + title: string + content: string + type: string + link?: string + isRead: boolean + createdAt?: string +} + +export const listNotifications = (page = 1, pageSize = 20) => + request.get('/notifications', { params: { page, pageSize } }) + +export const getUnreadCount = () => + request.get('/notifications/unread-count') + +export const markNotificationRead = (id: number) => + request.patch(`/notifications/${id}/read`) + +export const markAllNotificationsRead = () => + request.post('/notifications/read-all') diff --git a/frontend/src/store/index.ts b/frontend/src/store/index.ts new file mode 100644 index 0000000..736b402 --- /dev/null +++ b/frontend/src/store/index.ts @@ -0,0 +1,11 @@ +import { configureStore } from '@reduxjs/toolkit' +import authReducer from './slices/authSlice' + +export const store = configureStore({ + reducer: { + auth: authReducer + } +}) + +export type RootState = ReturnType +export type AppDispatch = typeof store.dispatch diff --git a/frontend/src/store/slices/authSlice.ts b/frontend/src/store/slices/authSlice.ts new file mode 100644 index 0000000..9b3b907 --- /dev/null +++ b/frontend/src/store/slices/authSlice.ts @@ -0,0 +1,75 @@ +import { createSlice, createAsyncThunk } from '@reduxjs/toolkit' +import request from '../../request' + +interface AuthState { + token: string | null + userId: number | null + username: string | null + departmentName: string | null + loading: boolean +} + +const initialState: AuthState = { + token: localStorage.getItem('accessToken'), + userId: null, + username: null, + departmentName: null, + loading: false +} + +export const login = createAsyncThunk( + 'auth/login', + async (params: { username: string; password: string }) => { + const res: any = await request.post('/auth/login', params) + return res.data + } +) + +export const fetchMe = createAsyncThunk( + 'auth/me', + async () => { + const res: any = await request.get('/auth/me') + return res.data + } +) + +const authSlice = createSlice({ + name: 'auth', + initialState, + reducers: { + logout(state) { + state.token = null + state.userId = null + state.username = null + state.departmentName = null + localStorage.removeItem('accessToken') + localStorage.removeItem('refreshToken') + } + }, + extraReducers: (builder) => { + builder + .addCase(login.pending, (state) => { + state.loading = true + }) + .addCase(login.fulfilled, (state, action) => { + state.loading = false + state.token = action.payload.accessToken + state.userId = action.payload.userId + state.username = action.payload.username + localStorage.setItem('accessToken', action.payload.accessToken) + localStorage.setItem('refreshToken', action.payload.refreshToken) + }) + .addCase(login.rejected, (state) => { + state.loading = false + }) + .addCase(fetchMe.fulfilled, (state, action) => { + state.departmentName = action.payload.departmentName + if (!state.username && action.payload.username) { + state.username = action.payload.username + } + }) + } +}) + +export const { logout } = authSlice.actions +export default authSlice.reducer diff --git a/frontend/src/theme.ts b/frontend/src/theme.ts new file mode 100644 index 0000000..885fcee --- /dev/null +++ b/frontend/src/theme.ts @@ -0,0 +1,11 @@ +import type { ThemeConfig } from 'antd' + +const theme: ThemeConfig = { + token: { + colorPrimary: '#1677ff', + borderRadius: 6, + fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" + } +} + +export default theme diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..d1b0121 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true, + "paths": { + "@/*": ["./src/*"] + }, + "baseUrl": "." + }, + "include": ["src"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..d6e56f7 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + server: { + port: 5173, + proxy: { + '/api': { + target: 'http://localhost:8080', + changeOrigin: true + } + } + } +}) diff --git a/hosts.toml b/hosts.toml new file mode 100644 index 0000000..e78a001 --- /dev/null +++ b/hosts.toml @@ -0,0 +1,5 @@ +server = "https://docker.io" + +[host."https://docker.1ms.run"] + capabilities = ["pull", "resolve"] + override_path = false diff --git a/init-pgvector.sql b/init-pgvector.sql new file mode 100644 index 0000000..0aa0fc2 --- /dev/null +++ b/init-pgvector.sql @@ -0,0 +1 @@ +CREATE EXTENSION IF NOT EXISTS vector; diff --git a/pull-images.sh b/pull-images.sh new file mode 100644 index 0000000..30159c9 --- /dev/null +++ b/pull-images.sh @@ -0,0 +1,17 @@ +#!/bin/bash +cd /mnt/c/Users/NB-070/Desktop/work2/ims-master + +echo "=== Pulling pgvector/pgvector:pg16 ===" +docker pull pgvector/pgvector:pg16 +echo "=== pgvector done ===" + +echo "=== Pulling minio/minio ===" +docker pull minio/minio +echo "=== minio done ===" + +echo "=== Pulling ollama/ollama:latest ===" +docker pull ollama/ollama:latest +echo "=== ollama done ===" + +echo "=== All images pulled ===" +docker images diff --git a/pull-model.json b/pull-model.json new file mode 100644 index 0000000..b88ebde --- /dev/null +++ b/pull-model.json @@ -0,0 +1 @@ +{"name":"tinyllama"} diff --git a/start-backend.sh b/start-backend.sh new file mode 100644 index 0000000..5154feb --- /dev/null +++ b/start-backend.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd /mnt/c/Users/NB-070/Desktop/work2/ims-master/backend +mvn spring-boot:run -pl ims-web -Dspring-boot.run.profiles=dev