Compare commits

..

10 Commits

Author SHA1 Message Date
16f9a325d7 fix: 修复附件配置属性构造器冲突 2026-05-20 00:11:37 +08:00
e68150ad02 feat: 建立Vue3前端框架 2026-05-20 00:11:37 +08:00
zhiye.sun
bc225d3557 refactor: 统一RAG接口查询请求与返回体 2026-05-20 00:11:37 +08:00
zhiye.sun
fc97c3998d refactor: 统一系统枚举接口入参与返回体 2026-05-20 00:11:37 +08:00
zhiye.sun
603c006c49 refactor: 收紧附件上传接口 2026-05-20 00:11:37 +08:00
zhiye.sun
067b098aa1 refactor: 调整通用领域模型包结构 2026-05-20 00:11:37 +08:00
b4e8324d60 ft:加入测试文件 2026-05-20 00:11:37 +08:00
d7dcf4c8e8 ft:引入提示文件AGENT.md 2026-05-20 00:11:37 +08:00
852cb7e7a6 feat:调整展示路径 2026-05-18 23:42:38 +08:00
f553409544 docs: 增加DTO与统一响应改造计划 2026-05-18 22:45:31 +08:00
63 changed files with 5169 additions and 87 deletions

185
AGENT.md Normal file
View File

@@ -0,0 +1,185 @@
# 通用 Agent 平台设计草案
## 1. 项目定位
`common_agent` 的目标是构建一个基于 Java、Spring Boot、Spring AI 的通用 Agent 平台,支持:
- 多 Agent / 单 Agent 运行
- 工具调用与流程编排
- 会话上下文管理
- RAG 知识库接入
- 文件上传与附件管理
- 前后端统一的管理控制台
当前阶段以“先搭平台骨架,再逐步补智能能力”为主,优先保证工程结构、接口规范、知识库链路和可扩展性。
## 2. 总体设计思路
平台整体按“接入层 - 应用层 - 领域层 - 基础设施层”拆分:
- 接入层
提供 REST API、后续可扩展 WebSocket / SSE用于前端控制台和外部系统接入。
- 应用层
负责请求编排、DTO 转换、统一返回体、会话协调和 Agent 调度入口。
- 领域层
承载核心业务对象如系统枚举、附件、知识库、知识文档、Agent 配置、任务执行记录等。
- 基础设施层
负责数据库访问、文件存储、模型调用、向量检索、日志、缓存和第三方工具适配。
## 3. 核心模块规划
### 3.1 系统基础模块
用于支撑整个平台的通用能力:
- `sys_enum`:系统枚举配置
- `sys_attachment`:附件与文件上传
- 统一 DTO / `RequestResult`
- 通用状态枚举、启用禁用枚举
- 后续可补用户、权限、审计等基础能力
### 3.2 RAG 知识库模块
当前已经有初步表设计与 Java 骨架:
- `rag_store`:知识库主表
- `rag_document`:知识库文档表
后续计划继续扩展:
- 文档切片
- 向量化
- 检索召回
- 索引任务
当前设计原则:
- 文件物理信息放在 `sys_attachment`
- 业务归属关系通过 `source_type``source_id` 或文档关联字段承接
- RAG 领域代码独立放在 `com.bruce.rag`
### 3.3 Agent 运行模块
后续平台重点能力,建议逐步补齐:
- Agent 定义
- Prompt 模板
- 工具注册与调用
- 会话上下文与记忆
- 执行日志与任务状态
- 多步骤编排
建议未来增加的核心对象:
- `agent_definition`
- `agent_session`
- `agent_message`
- `agent_task`
- `agent_tool`
### 3.4 管理控制台模块
后续需要一个前端控制台,至少覆盖:
- 枚举管理
- 附件管理
- 知识库管理
- 文档上传与状态查看
- Agent 调试页
- 执行日志查看
## 4. 当前接口设计原则
项目后续统一遵循这些规则:
1. `controller` 不直接暴露实体类
所有请求和响应优先走 DTO。
2. `service` 尽量以 DTO 为边界
持久化实体只在内部流转,不直接穿透到外层接口。
3. 查询条件不直接使用多个裸参数
尽量改成 `QueryRequest` / `SaveRequest` / `Response` 形式。
4. 统一返回体
使用 `RequestResult<T>` 作为标准响应包装。
5. 基础枚举统一化
通用状态、启用禁用、RAG 解析/索引状态等统一管理。
## 5. 数据与存储设计
### 5.1 关系型数据库
当前主数据库使用 PostgreSQL。
已确定的方向:
- 业务表采用 PostgreSQL 规范 SQL
- 主键使用 MyBatis-Plus `ASSIGN_ID`
- 通用字段沉淀到 `BaseEntity`
### 5.2 向量能力
知识库检索阶段优先使用 PostgreSQL + `pgvector`
- 统一技术栈
- 降低部署复杂度
- 便于关系数据与向量数据联动
适合项目当前阶段的中小规模知识库场景。
### 5.3 文件存储
当前先走本地文件存储,后续可抽象成:
- 本地文件系统
- MinIO / S3
- 其他对象存储
## 6. 当前阶段开发优先级
建议优先顺序如下:
1. 统一接口层规范
DTO、返回体、基础校验、通用异常处理
2. 收紧基础模块
`sys_enum``sys_attachment`
3. 补全 RAG 基础业务闭环
`rag_store``rag_document`、文件归属、文档状态
4. 接入 Spring AI
5. 建立 Agent 运行时骨架
6. 补前端控制台
## 7. 下一步建议
结合当前代码状态,接下来建议重点做:
- 完成现有三块接口 DTO 化改造
- 建立统一异常处理和错误码规范
- 完善 `rag_store` / `rag_document` 的增删改查
- 增加知识库文档上传并自动关联附件
- 为后续切片与向量化预留任务入口
## 8. 文档用途说明
本文件是项目级的 Agent 平台设计入口文档,当前用于:
- 对齐项目方向
- 固定基础架构思路
- 作为后续详细设计和模块拆分的上层说明
后续可继续拆成:
- `agent-runtime.md`
- `rag-design.md`
- `api-style.md`
- `frontend-console.md`

View File

@@ -87,6 +87,22 @@ spring:
当前阶段还没有加入 Web 服务依赖或常驻任务,所以应用可能启动成功后立即退出。
启动前端:
```powershell
cd frontend
npm install
npm run dev
```
前端检查:
```powershell
cd frontend
npm run type-check
npm run build
```
## 规划模块
- `agent-core`Agent 执行模型、工具注册、记忆和编排能力。
@@ -106,4 +122,3 @@ spring:
- [Spring AI Reference](https://docs.spring.io/spring-ai/reference/)
- [Spring AI RAG Reference](https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html)
- [MyBatis-Plus](https://baomidou.com/)

View File

@@ -0,0 +1,414 @@
# DTO And RequestResult Refactor Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 将现有枚举、附件、RAG 三块接口统一改造成 DTO 入参与 DTO 返回,并引入 `RequestResult` 作为统一响应包装。
**Architecture:** `controller``service``mapper` 三层都尽量以 DTO 作为边界对象,实体类仅用于 MyBatis-Plus 持久化与表映射。控制层统一返回 `RequestResult<T>`,查询条件走 request/query DTO列表和详情都返回 response DTO避免继续直接暴露实体和零散参数。
**Tech Stack:** Java 21、Spring Boot 4、MyBatis-Plus、Spring MVC、JUnit 5
---
### Task 1: 建立统一响应体和 DTO 包结构
**Files:**
- Create: `src/main/java/com/bruce/common/dto/RequestResult.java`
- Create: `src/main/java/com/bruce/common/dto/request/`
- Create: `src/main/java/com/bruce/common/dto/response/`
- Create: `src/main/java/com/bruce/rag/dto/request/`
- Create: `src/main/java/com/bruce/rag/dto/response/`
- Modify: `src/test/java/com/bruce/common/enumconfig/SysEnumComponentStructureTests.java`
- Modify: `src/test/java/com/bruce/rag/RagComponentStructureTests.java`
- [ ] **Step 1: 写失败测试,固定统一返回体存在且控制层不再暴露裸实体**
在结构测试中增加如下断言思路:
```java
Method saveOrUpdateMethod = SysEnumController.class.getMethod("saveOrUpdate", SysEnumSaveRequest.class);
assertEquals(RequestResult.class, saveOrUpdateMethod.getReturnType());
```
```java
Method listMethod = RagStoreController.class.getMethod("list", RagStoreQueryRequest.class);
assertEquals(RequestResult.class, listMethod.getReturnType());
```
- [ ] **Step 2: 运行测试并确认失败**
Run: `.\mvnw.cmd "-Dtest=SysEnumComponentStructureTests,RagComponentStructureTests" test`
Expected: FAIL提示 DTO 或 `RequestResult` 类型不存在,或控制器方法签名不匹配。
- [ ] **Step 3: 最小化实现统一响应体和基础 DTO 目录**
`RequestResult.java` 采用如下结构:
```java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RequestResult<T> {
private boolean success;
private String code;
private String message;
private T data;
public static <T> RequestResult<T> success(T data) {
return new RequestResult<>(true, "SUCCESS", "操作成功", data);
}
public static <T> RequestResult<T> success(String message, T data) {
return new RequestResult<>(true, "SUCCESS", message, data);
}
public static <T> RequestResult<T> failure(String code, String message) {
return new RequestResult<>(false, code, message, null);
}
}
```
- [ ] **Step 4: 运行测试并确认通过**
Run: `.\mvnw.cmd "-Dtest=SysEnumComponentStructureTests,RagComponentStructureTests" test`
Expected: PASS 或只剩下后续控制器签名相关失败。
- [ ] **Step 5: Commit**
```bash
git add src/main/java/com/bruce/common/dto src/main/java/com/bruce/rag/dto src/test/java/com/bruce/common/enumconfig/SysEnumComponentStructureTests.java src/test/java/com/bruce/rag/RagComponentStructureTests.java
git commit -m "refactor: 增加统一响应体与DTO结构"
```
### Task 2: 重构 sys_enum 模块为 DTO 入参与 DTO 返回
**Files:**
- Create: `src/main/java/com/bruce/common/dto/request/SysEnumQueryRequest.java`
- Create: `src/main/java/com/bruce/common/dto/request/SysEnumSaveRequest.java`
- Create: `src/main/java/com/bruce/common/dto/response/SysEnumResponse.java`
- Modify: `src/main/java/com/bruce/common/controller/SysEnumController.java`
- Modify: `src/main/java/com/bruce/common/service/ISysEnumService.java`
- Modify: `src/main/java/com/bruce/common/service/impl/SysEnumServiceImpl.java`
- Modify: `src/main/java/com/bruce/common/mapper/SysEnumMapper.java`
- Modify: `src/test/java/com/bruce/common/enumconfig/SysEnumComponentStructureTests.java`
- [ ] **Step 1: 写失败测试,固定 sys_enum 控制器和服务都使用 DTO**
在 `SysEnumComponentStructureTests` 中增加断言:
```java
Method queryMethod = SysEnumController.class.getMethod("queryByCatalogAndType", SysEnumQueryRequest.class);
Method saveMethod = SysEnumController.class.getMethod("saveOrUpdate", SysEnumSaveRequest.class);
Method serviceMethod = ISysEnumService.class.getMethod("listByCatalogAndType", SysEnumQueryRequest.class);
```
- [ ] **Step 2: 运行测试并确认失败**
Run: `.\mvnw.cmd "-Dtest=SysEnumComponentStructureTests" test`
Expected: FAIL提示方法签名仍然是 `String` 或 `SysEnum`。
- [ ] **Step 3: 最小化实现 request/response DTO**
`SysEnumQueryRequest.java`
```java
@Data
public class SysEnumQueryRequest {
private String catalog;
private String type;
}
```
`SysEnumSaveRequest.java`
```java
@Data
public class SysEnumSaveRequest {
private Long id;
private String catalog;
private String type;
private String name;
private Integer value;
private String strvalue;
private Integer sort;
private String remark;
}
```
`SysEnumResponse.java`
```java
@Data
public class SysEnumResponse {
private Long id;
private String catalog;
private String type;
private String name;
private Integer value;
private String strvalue;
private Integer sort;
private String remark;
}
```
- [ ] **Step 4: 修改 mapper/service/controller**
- `ISysEnumService` 返回 `List<SysEnumResponse>`,保存返回 `SysEnumResponse`
- `SysEnumServiceImpl` 新增 DTO 与实体互转私有方法
- `SysEnumMapper` 保留 MP 基础能力;如需自定义查询,新增 DTO 查询方法签名
- `SysEnumController` 所有接口返回 `RequestResult<?>`
控制器目标形态:
```java
@PostMapping("/query")
public RequestResult<List<SysEnumResponse>> queryByCatalogAndType(@RequestBody SysEnumQueryRequest request) {
return RequestResult.success(sysEnumService.listByCatalogAndType(request));
}
```
- [ ] **Step 5: 运行测试并确认通过**
Run: `.\mvnw.cmd "-Dtest=SysEnumComponentStructureTests" test`
Expected: PASS
- [ ] **Step 6: Commit**
```bash
git add src/main/java/com/bruce/common/controller/SysEnumController.java src/main/java/com/bruce/common/service/ISysEnumService.java src/main/java/com/bruce/common/service/impl/SysEnumServiceImpl.java src/main/java/com/bruce/common/dto/request/SysEnumQueryRequest.java src/main/java/com/bruce/common/dto/request/SysEnumSaveRequest.java src/main/java/com/bruce/common/dto/response/SysEnumResponse.java src/test/java/com/bruce/common/enumconfig/SysEnumComponentStructureTests.java
git commit -m "refactor: 调整sys_enum接口为DTO模式"
```
### Task 3: 重构 sys_attachment 模块为 DTO 入参与 DTO 返回
**Files:**
- Create: `src/main/java/com/bruce/common/dto/request/SysAttachmentUploadRequest.java`
- Create: `src/main/java/com/bruce/common/dto/request/SysAttachmentQueryRequest.java`
- Create: `src/main/java/com/bruce/common/dto/response/SysAttachmentResponse.java`
- Modify: `src/main/java/com/bruce/common/controller/SysAttachmentController.java`
- Modify: `src/main/java/com/bruce/common/service/ISysAttachmentService.java`
- Modify: `src/main/java/com/bruce/common/service/impl/SysAttachmentServiceImpl.java`
- Modify: `src/test/java/com/bruce/common/attachment/SysAttachmentComponentStructureTests.java`
- [ ] **Step 1: 写失败测试,固定附件接口返回 `RequestResult` 且 service 返回 DTO**
示例断言:
```java
Method uploadMethod = SysAttachmentController.class.getMethod("upload", MultipartFile.class, SysAttachmentUploadRequest.class);
assertEquals(RequestResult.class, uploadMethod.getReturnType());
```
- [ ] **Step 2: 运行测试并确认失败**
Run: `.\mvnw.cmd "-Dtest=SysAttachmentComponentStructureTests" test`
Expected: FAIL提示控制器或服务方法签名不匹配。
- [ ] **Step 3: 新增附件 DTO**
`SysAttachmentUploadRequest.java`
```java
@Data
public class SysAttachmentUploadRequest {
private String sourceType;
private Long sourceId;
}
```
`SysAttachmentQueryRequest.java`
```java
@Data
public class SysAttachmentQueryRequest {
private String sourceType;
private Long sourceId;
}
```
`SysAttachmentResponse.java`
```java
@Data
public class SysAttachmentResponse {
private Long id;
private String sourceType;
private Long sourceId;
private String originalName;
private String fileName;
private String fileSuffix;
private String contentType;
private Long fileSize;
private String storageType;
private String filePath;
private String fileUrl;
private String remark;
}
```
- [ ] **Step 4: 修改附件控制器和服务**
- `ISysAttachmentService.upload` 返回 `SysAttachmentResponse`
- 控制器上传接口返回 `RequestResult<SysAttachmentResponse>`
- 如补充列表查询,也走 `SysAttachmentQueryRequest`
- [ ] **Step 5: 运行测试并确认通过**
Run: `.\mvnw.cmd "-Dtest=SysAttachmentComponentStructureTests" test`
Expected: PASS
- [ ] **Step 6: Commit**
```bash
git add src/main/java/com/bruce/common/controller/SysAttachmentController.java src/main/java/com/bruce/common/service/ISysAttachmentService.java src/main/java/com/bruce/common/service/impl/SysAttachmentServiceImpl.java src/main/java/com/bruce/common/dto/request/SysAttachmentUploadRequest.java src/main/java/com/bruce/common/dto/request/SysAttachmentQueryRequest.java src/main/java/com/bruce/common/dto/response/SysAttachmentResponse.java src/test/java/com/bruce/common/attachment/SysAttachmentComponentStructureTests.java
git commit -m "refactor: 调整附件接口为DTO模式"
```
### Task 4: 重构 rag_store 与 rag_document 模块为 DTO 入参与 DTO 返回
**Files:**
- Create: `src/main/java/com/bruce/rag/dto/request/RagStoreQueryRequest.java`
- Create: `src/main/java/com/bruce/rag/dto/request/RagStoreSaveRequest.java`
- Create: `src/main/java/com/bruce/rag/dto/request/RagDocumentQueryRequest.java`
- Create: `src/main/java/com/bruce/rag/dto/request/RagDocumentSaveRequest.java`
- Create: `src/main/java/com/bruce/rag/dto/response/RagStoreResponse.java`
- Create: `src/main/java/com/bruce/rag/dto/response/RagDocumentResponse.java`
- Modify: `src/main/java/com/bruce/rag/controller/RagStoreController.java`
- Modify: `src/main/java/com/bruce/rag/controller/RagDocumentController.java`
- Modify: `src/main/java/com/bruce/rag/service/IRagStoreService.java`
- Modify: `src/main/java/com/bruce/rag/service/IRagDocumentService.java`
- Modify: `src/main/java/com/bruce/rag/service/impl/RagStoreServiceImpl.java`
- Modify: `src/main/java/com/bruce/rag/service/impl/RagDocumentServiceImpl.java`
- Modify: `src/test/java/com/bruce/rag/RagComponentStructureTests.java`
- [ ] **Step 1: 写失败测试,固定 RAG 控制器和服务都使用 DTO**
示例断言:
```java
Method storeListMethod = RagStoreController.class.getMethod("list", RagStoreQueryRequest.class);
Method documentListMethod = RagDocumentController.class.getMethod("list", RagDocumentQueryRequest.class);
assertEquals(RequestResult.class, storeListMethod.getReturnType());
assertEquals(RequestResult.class, documentListMethod.getReturnType());
```
- [ ] **Step 2: 运行测试并确认失败**
Run: `.\mvnw.cmd "-Dtest=RagComponentStructureTests" test`
Expected: FAIL提示控制器和 service 仍然暴露实体或无 DTO。
- [ ] **Step 3: 新增 RAG DTO**
`RagStoreQueryRequest.java`
```java
@Data
public class RagStoreQueryRequest {
private String storeCode;
private String storeName;
private String status;
}
```
`RagStoreResponse.java`
```java
@Data
public class RagStoreResponse {
private Long id;
private String storeCode;
private String storeName;
private String description;
private String status;
private String remark;
}
```
`RagDocumentQueryRequest.java`
```java
@Data
public class RagDocumentQueryRequest {
private Long storeId;
private Long attachmentId;
private String parseStatus;
private String indexStatus;
private Boolean enabled;
}
```
`RagDocumentResponse.java`
```java
@Data
public class RagDocumentResponse {
private Long id;
private Long storeId;
private Long attachmentId;
private String documentTitle;
private String documentSummary;
private String parseStatus;
private String indexStatus;
private Boolean enabled;
private String errorMessage;
private String remark;
}
```
- [ ] **Step 4: 修改 RAG service/controller**
- `IRagStoreService`、`IRagDocumentService` 返回 DTO
- `RagStoreController` 和 `RagDocumentController` 返回 `RequestResult`
- 查询接口改为 `@PostMapping("/query")` + `@RequestBody QueryRequest`
- [ ] **Step 5: 运行测试并确认通过**
Run: `.\mvnw.cmd "-Dtest=RagComponentStructureTests" test`
Expected: PASS
- [ ] **Step 6: Commit**
```bash
git add src/main/java/com/bruce/rag/controller/RagStoreController.java src/main/java/com/bruce/rag/controller/RagDocumentController.java src/main/java/com/bruce/rag/service/IRagStoreService.java src/main/java/com/bruce/rag/service/IRagDocumentService.java src/main/java/com/bruce/rag/service/impl/RagStoreServiceImpl.java src/main/java/com/bruce/rag/service/impl/RagDocumentServiceImpl.java src/main/java/com/bruce/rag/dto/request src/main/java/com/bruce/rag/dto/response src/test/java/com/bruce/rag/RagComponentStructureTests.java
git commit -m "refactor: 调整RAG接口为DTO模式"
```
### Task 5: 全量验证与整理
**Files:**
- Modify: `src/test/java/com/bruce/common/attachment/SysAttachmentComponentStructureTests.java`
- Modify: `src/test/java/com/bruce/common/enumconfig/SysEnumComponentStructureTests.java`
- Modify: `src/test/java/com/bruce/rag/RagComponentStructureTests.java`
- [ ] **Step 1: 运行定向测试,确认三块 DTO 改造都覆盖到**
Run: `.\mvnw.cmd "-Dtest=SysAttachmentComponentStructureTests,SysEnumComponentStructureTests,RagComponentStructureTests" test`
Expected: PASS
- [ ] **Step 2: 运行全量测试**
Run: `.\mvnw.cmd test`
Expected: `BUILD SUCCESS`
- [ ] **Step 3: 检查工作区**
Run: `git status --short`
Expected: 仅显示本次预期文件,或为空(若已提交)。
- [ ] **Step 4: Commit**
```bash
git add src/test/java/com/bruce/common/attachment/SysAttachmentComponentStructureTests.java src/test/java/com/bruce/common/enumconfig/SysEnumComponentStructureTests.java src/test/java/com/bruce/rag/RagComponentStructureTests.java
git commit -m "test: 校验DTO接口改造结构"
```

5
frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
node_modules/
dist/
*.log
*.local

1
frontend/env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

12
frontend/index.html Normal file
View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Common Agent</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

3345
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

32
frontend/package.json Normal file
View File

@@ -0,0 +1,32 @@
{
"name": "common-agent-frontend",
"version": "0.0.1",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --host 0.0.0.0",
"build": "vue-tsc -b && vite build",
"preview": "vite preview --host 0.0.0.0",
"test:unit": "vitest run",
"type-check": "vue-tsc -b"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"axios": "^1.13.2",
"element-plus": "^2.11.8",
"pinia": "^3.0.4",
"vue": "^3.5.24",
"vue-router": "^4.6.3"
},
"devDependencies": {
"@vue/tsconfig": "^0.8.1",
"@vitejs/plugin-vue": "^6.0.2",
"@types/node": "^24.10.2",
"@vue/test-utils": "^2.4.6",
"jsdom": "^27.2.0",
"typescript": "~5.9.3",
"vite": "^7.2.7",
"vitest": "^4.0.15",
"vue-tsc": "^3.1.8"
}
}

3
frontend/src/App.vue Normal file
View File

@@ -0,0 +1,3 @@
<template>
<RouterView />
</template>

View File

@@ -0,0 +1,9 @@
import { describe, expect, it } from 'vitest';
import { request } from '../request';
describe('request client', () => {
it('uses the backend api prefix', () => {
expect(request.defaults.baseURL).toBe('/api');
});
});

View File

@@ -0,0 +1,21 @@
import axios from 'axios';
import type { AxiosRequestConfig } from 'axios';
export interface RequestResult<T> {
code: number;
message: string;
data: T;
}
export const request = axios.create({
baseURL: '/api',
timeout: 15000,
});
export function get<T>(url: string, config?: AxiosRequestConfig) {
return request.get<RequestResult<T>>(url, config).then((response) => response.data);
}
export function post<T, D = unknown>(url: string, data?: D, config?: AxiosRequestConfig) {
return request.post<RequestResult<T>>(url, data, config).then((response) => response.data);
}

View File

@@ -0,0 +1,62 @@
<script setup lang="ts">
import {
Box,
Collection,
DataBoard,
Document,
Files,
Grid,
} from '@element-plus/icons-vue';
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { useAppStore } from '@/stores/app';
const route = useRoute();
const appStore = useAppStore();
const pageTitle = computed(() => String(route.meta.title ?? 'Common Agent'));
const menuItems = [
{ path: '/dashboard', label: '工作台', icon: DataBoard },
{ path: '/system/enums', label: '系统枚举', icon: Grid },
{ path: '/system/attachments', label: '附件管理', icon: Files },
{ path: '/rag/stores', label: '知识库', icon: Collection },
{ path: '/rag/documents', label: '知识文档', icon: Document },
];
</script>
<template>
<el-container class="admin-layout">
<el-aside class="admin-sidebar" width="232px">
<div class="brand">
<el-icon :size="24">
<Box />
</el-icon>
<span>Common Agent</span>
</div>
<el-menu class="side-menu" :default-active="$route.path" router>
<el-menu-item v-for="item in menuItems" :key="item.path" :index="item.path">
<el-icon>
<component :is="item.icon" />
</el-icon>
<span>{{ item.label }}</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-container>
<el-header class="admin-header">
<div>
<h1>{{ pageTitle }}</h1>
<p>{{ appStore.environmentName }}</p>
</div>
</el-header>
<el-main class="admin-main">
<RouterView />
</el-main>
</el-container>
</el-container>
</template>

17
frontend/src/main.ts Normal file
View File

@@ -0,0 +1,17 @@
import 'element-plus/dist/index.css';
import './styles/global.css';
import ElementPlus from 'element-plus';
import { createPinia } from 'pinia';
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
const app = createApp(App);
app.use(createPinia());
app.use(router);
app.use(ElementPlus);
app.mount('#app');

View File

@@ -0,0 +1,8 @@
<template>
<section class="page-panel">
<div class="page-panel__header">
<h2>工作台</h2>
<span>Console</span>
</div>
</section>
</template>

View File

@@ -0,0 +1,6 @@
<template>
<main class="not-found">
<h1>404</h1>
<RouterLink to="/dashboard">返回工作台</RouterLink>
</main>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<section class="page-panel">
<div class="page-panel__header">
<h2>知识文档</h2>
<span>Documents</span>
</div>
</section>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<section class="page-panel">
<div class="page-panel__header">
<h2>知识库</h2>
<span>RAG</span>
</div>
</section>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<section class="page-panel">
<div class="page-panel__header">
<h2>附件管理</h2>
<span>Files</span>
</div>
</section>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<section class="page-panel">
<div class="page-panel__header">
<h2>系统枚举</h2>
<span>System</span>
</div>
</section>
</template>

View File

@@ -0,0 +1,17 @@
import { describe, expect, it } from 'vitest';
import { routes } from '../index';
describe('router', () => {
it('defines the admin shell routes', () => {
const paths = routes.map((route) => route.path);
expect(paths).toContain('/');
expect(paths).toContain('/dashboard');
expect(paths).toContain('/system/enums');
expect(paths).toContain('/system/attachments');
expect(paths).toContain('/rag/stores');
expect(paths).toContain('/rag/documents');
expect(paths).toContain('/:pathMatch(.*)*');
});
});

View File

@@ -0,0 +1,109 @@
import type { RouteRecordRaw } from 'vue-router';
import { createRouter, createWebHistory } from 'vue-router';
import DashboardPage from '@/pages/DashboardPage.vue';
import NotFoundPage from '@/pages/NotFoundPage.vue';
import RagDocumentsPage from '@/pages/RagDocumentsPage.vue';
import RagStoresPage from '@/pages/RagStoresPage.vue';
import SystemAttachmentsPage from '@/pages/SystemAttachmentsPage.vue';
import SystemEnumsPage from '@/pages/SystemEnumsPage.vue';
import AdminLayout from '@/layouts/AdminLayout.vue';
export const routes: RouteRecordRaw[] = [
{
path: '/',
redirect: '/dashboard',
},
{
path: '/dashboard',
name: 'dashboard',
component: DashboardPage,
meta: { title: '工作台' },
},
{
path: '/system/enums',
name: 'system-enums',
component: SystemEnumsPage,
meta: { title: '系统枚举' },
},
{
path: '/system/attachments',
name: 'system-attachments',
component: SystemAttachmentsPage,
meta: { title: '附件管理' },
},
{
path: '/rag/stores',
name: 'rag-stores',
component: RagStoresPage,
meta: { title: '知识库' },
},
{
path: '/rag/documents',
name: 'rag-documents',
component: RagDocumentsPage,
meta: { title: '知识文档' },
},
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: NotFoundPage,
meta: { title: '页面不存在' },
},
];
const routerRoutes: RouteRecordRaw[] = [
{
path: '/',
redirect: '/dashboard',
},
{
path: '/',
component: AdminLayout,
children: [
{
path: 'dashboard',
name: 'dashboard',
component: DashboardPage,
meta: { title: '工作台' },
},
{
path: 'system/enums',
name: 'system-enums',
component: SystemEnumsPage,
meta: { title: '系统枚举' },
},
{
path: 'system/attachments',
name: 'system-attachments',
component: SystemAttachmentsPage,
meta: { title: '附件管理' },
},
{
path: 'rag/stores',
name: 'rag-stores',
component: RagStoresPage,
meta: { title: '知识库' },
},
{
path: 'rag/documents',
name: 'rag-documents',
component: RagDocumentsPage,
meta: { title: '知识文档' },
},
],
},
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: NotFoundPage,
meta: { title: '页面不存在' },
},
];
const router = createRouter({
history: createWebHistory(),
routes: routerRoutes,
});
export default router;

View File

@@ -0,0 +1,7 @@
import { defineStore } from 'pinia';
export const useAppStore = defineStore('app', {
state: () => ({
environmentName: '本地开发',
}),
});

View File

@@ -0,0 +1,128 @@
:root {
color: #1f2937;
background: #f4f6f8;
font-family:
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
sans-serif;
font-synthesis: none;
text-rendering: optimizeLegibility;
}
* {
box-sizing: border-box;
}
body {
min-width: 320px;
margin: 0;
}
a {
color: inherit;
}
#app {
min-height: 100vh;
}
.admin-layout {
min-height: 100vh;
}
.admin-sidebar {
border-right: 1px solid #d9dee7;
background: #ffffff;
}
.brand {
display: flex;
align-items: center;
gap: 10px;
height: 64px;
padding: 0 18px;
border-bottom: 1px solid #e5e9f0;
color: #182433;
font-size: 17px;
font-weight: 700;
}
.side-menu {
border-right: 0;
padding: 10px 8px;
}
.side-menu .el-menu-item {
height: 42px;
border-radius: 6px;
margin-bottom: 4px;
}
.admin-header {
display: flex;
align-items: center;
height: 64px;
border-bottom: 1px solid #d9dee7;
background: #ffffff;
}
.admin-header h1 {
margin: 0;
color: #172033;
font-size: 18px;
font-weight: 700;
letter-spacing: 0;
}
.admin-header p {
margin: 4px 0 0;
color: #667085;
font-size: 12px;
}
.admin-main {
padding: 20px;
background: #f4f6f8;
}
.page-panel {
min-height: calc(100vh - 104px);
border: 1px solid #d9dee7;
border-radius: 6px;
background: #ffffff;
}
.page-panel__header {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 56px;
padding: 0 18px;
border-bottom: 1px solid #edf0f4;
}
.page-panel__header h2 {
margin: 0;
color: #1f2937;
font-size: 16px;
font-weight: 700;
letter-spacing: 0;
}
.page-panel__header span {
color: #768195;
font-size: 12px;
}
.not-found {
display: grid;
min-height: 100vh;
place-content: center;
gap: 16px;
text-align: center;
}
.not-found h1 {
margin: 0;
font-size: 48px;
letter-spacing: 0;
}

View File

@@ -0,0 +1,12 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"types": ["vite/client"],
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"]
}

11
frontend/tsconfig.json Normal file
View File

@@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.node.json"
}
]
}

View File

@@ -0,0 +1,17 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2023",
"lib": ["ES2023"],
"module": "ESNext",
"types": ["node"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"strict": true
},
"include": ["vite.config.ts"]
}

25
frontend/vite.config.ts Normal file
View File

@@ -0,0 +1,25 @@
import { fileURLToPath, URL } from 'node:url';
import vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
},
test: {
environment: 'jsdom',
},
});

View File

@@ -0,0 +1,41 @@
中国城市信息测试文档
北京是中国的首都,也是全国政治、文化、国际交往和科技创新中心。北京拥有故宫、天坛、颐和园等历史文化资源,同时聚集了大量高校、科研机构和互联网企业。
上海位于长江入海口,是中国重要的国际经济、金融、贸易和航运中心。上海拥有陆家嘴金融区、外滩、张江科学城等代表性区域,现代服务业和先进制造业都很发达。
广州是广东省省会,地处珠江三角洲北缘,是国家中心城市之一。广州商贸传统深厚,广交会长期在这里举办,汽车、电子、食品和现代服务业发展较快。
深圳位于广东省南部,毗邻香港,是中国改革开放后快速发展起来的创新型城市。深圳聚集了大量科技企业,在电子信息、金融科技、人工智能和机器人产业方面具有优势。
杭州是浙江省省会,位于长江三角洲南翼。杭州以西湖闻名,也因数字经济、电商平台、云计算和文化旅游产业而受到关注。
成都是四川省省会,是中国西部重要的经济、科技和交通中心。成都生活服务业、电子信息产业、游戏产业和美食文化都很有代表性。
武汉是湖北省省会,位于长江和汉江交汇处,是中部地区重要的交通枢纽和科教城市。武汉拥有光电子信息、生物医药、汽车制造等产业基础。
西安是陕西省省会,也是中国历史文化名城。西安拥有兵马俑、大雁塔、古城墙等文化资源,同时在航空航天、电子信息和高等教育方面实力较强。
南京是江苏省省会,位于长江下游地区,是长三角重要中心城市。南京高校资源丰富,软件和信息服务业、智能电网、生物医药等产业具有优势。
重庆是中国直辖市之一,位于长江上游地区。重庆山地城市特征明显,汽车制造、电子信息、装备制造和火锅餐饮文化都很有代表性。
天津是中国直辖市之一,位于渤海湾西岸,是北方重要港口城市。天津拥有港口物流、装备制造、航空航天和现代服务业基础。
苏州位于江苏省东南部,是长三角重要制造业城市。苏州以园林文化闻名,同时在电子信息、生物医药、精密制造和外向型经济方面实力突出。
郑州是河南省省会,位于中国中部地区,是重要铁路和物流枢纽。郑州在食品加工、装备制造、汽车和商贸物流方面具有较强基础。
青岛位于山东半岛南部,是重要的沿海开放城市。青岛拥有港口、海洋经济、家电制造、啤酒产业和旅游资源,城市品牌辨识度较高。
大连位于辽宁省南部,是东北地区重要的港口、工业和旅游城市。大连在船舶制造、软件服务、海洋工程和现代物流方面有一定基础。
厦门位于福建省东南沿海,是经济特区和海滨旅游城市。厦门港航服务、电子信息、文化旅游和对外贸易较为活跃。
福州是福建省省会,位于闽江下游。福州拥有三坊七巷等历史街区,在数字经济、新能源材料、海洋经济和纺织鞋服相关产业方面持续发展。
长沙是湖南省省会,位于湘江下游,是中部地区重要的文化和消费城市。长沙的工程机械、传媒娱乐、食品消费和教育科技产业较有特色。
昆明是云南省省会,因气候温和被称为春城。昆明是面向南亚东南亚的重要门户城市,花卉、旅游、高原特色农业和生物资源产业具有代表性。
贵阳是贵州省省会,位于云贵高原东部。贵阳近年来在大数据产业、政务云服务、生态旅游和现代山地特色产业方面发展较快。

View File

@@ -0,0 +1,22 @@
公司信息测试文档
北辰智能制造有限公司位于北京主营工业机器人控制系统和产线数字化改造创办于2014年目前未上市。
海澜云数科技股份有限公司总部在上海主要做企业数据中台、云迁移服务和安全运维成立于2012年已在科创板上市。
珠江冷链物流集团设在广州主营生鲜冷链运输、城市仓配和供应链金融创办年份为2008年暂未上市。
前海量化信息技术有限公司位于深圳业务集中在金融风控模型、智能投研工具和合规数据服务2016年创办未上市。
西湖语义计算科技有限公司在杭州运营主营自然语言处理平台、知识库问答和企业搜索成立于2019年目前没有上市。
蓉城互动娱乐有限公司来自成都主要开发移动游戏、互动剧情产品和游戏美术外包创办于2015年未上市。
楚天光电仪器股份有限公司坐落于武汉主营激光传感器、光通信检测设备创办于2006年已在创业板上市。
长安微能科技有限公司位于西安做储能管理系统、充电桩控制器和新能源运维平台成立于2018年尚未上市。
金陵软件服务有限公司总部在南京主营政企软件定制、数据治理和低代码平台创办于2011年未上市。
山城智行汽车科技有限公司位于重庆主营车联网终端、智能座舱软件和售后数据分析成立于2017年未上市。
津门港航设备股份有限公司在天津主要生产港口自动化设备、船岸通信设备创办于2004年已在北交所上市。
苏园生物医药有限公司位于苏州主营细胞培养试剂、实验室耗材和生物样本管理系统成立于2013年未上市。
中原粮食科技集团总部在郑州主营粮食加工设备、仓储温控系统和农产品追溯创办于2001年已在主板上市。
琴岛海洋食品有限公司位于青岛主要经营海产品深加工、预制菜和跨境电商销售成立于2010年未上市。
星海船舶工程股份有限公司设在大连主营船舶维修、海工装备配套和港口工程服务创办于1998年已在港交所上市。
鹭岛航运服务有限公司位于厦门主营国际货代、船务代理和航线数据服务成立于2009年未上市。
榕城新能源材料有限公司总部在福州主营锂电隔膜、储能材料检测和电池回收咨询创办于2016年未上市。
星沙教育科技有限公司位于长沙主营在线职业教育、题库系统和教学数据分析成立于2014年未上市。
春城农业科技有限公司来自昆明主营高原特色农产品育种、智慧温室和冷链销售创办于2007年未上市。
黔云数据服务有限公司位于贵阳主营政务云运维、大数据治理和数据资产评估成立于2015年已在新三板挂牌。

View File

@@ -0,0 +1,52 @@
酒店信息测试文档
北京云栖庭酒店位于北京东城区,酒店主打胡同院落体验,招牌菜是北京烤鸭和炸酱面。
上海浦江悦榕酒店坐落在上海黄浦区,特色是江景客房,餐厅推荐本帮红烧肉和响油鳝糊。
广州花城雅居酒店在广州天河区,适合商务差旅,特色菜品包括白切鸡、艇仔粥。
深圳湾海景酒店位于深圳南山区,靠近科技园和海岸线,主厨菜是客家酿豆腐和潮汕牛肉丸。
杭州西溪书院酒店在杭州西湖区,环境清静,招牌菜为西湖醋鱼、龙井虾仁。
成都锦里小院酒店位于成都武侯区,带川西院落风格,特色菜是麻婆豆腐和夫妻肺片。
武汉江汉云舍酒店坐落于武汉江汉区,交通方便,餐厅供应热干面和武昌鱼。
西安雁塔唐风酒店位于西安雁塔区,主打唐风装饰,特色菜品是羊肉泡馍和凉皮。
南京秦淮水岸酒店在南京秦淮区,临近夫子庙,推荐盐水鸭和鸭血粉丝汤。
重庆山城观景酒店位于重庆渝中区,拥有江景房,特色菜是重庆火锅和辣子鸡。
天津海河悦庭酒店位于天津和平区,临近海河,招牌菜为煎饼果子和锅巴菜。
苏州园林逸居酒店在苏州姑苏区,庭院精致,餐厅主推松鼠桂鱼和碧螺虾仁。
郑州中原商务酒店位于郑州金水区,适合会议住宿,特色菜是烩面和胡辣汤。
青岛栈桥海风酒店在青岛市南区,靠近海边,推荐辣炒蛤蜊和鲅鱼水饺。
大连星海湾酒店位于大连沙河口区,海景视野好,特色菜是海胆蒸蛋和海鲜焖子。
厦门鼓浪海韵酒店位于厦门思明区,适合休闲度假,餐厅提供沙茶面和海蛎煎。
福州三坊雅舍酒店在福州鼓楼区,临近三坊七巷,特色菜为佛跳墙和荔枝肉。
长沙橘子洲酒店位于长沙岳麓区,客房看江景,主打剁椒鱼头和臭豆腐。
昆明滇池花园酒店在昆明西山区,临近滇池,特色菜是过桥米线和汽锅鸡。
贵阳黔灵山居酒店位于贵阳云岩区,环境清爽,推荐酸汤鱼和丝娃娃。
南宁青秀湖酒店位于南宁青秀区,适合家庭出行,特色菜是老友粉和柠檬鸭。
海口骑楼海景酒店在海口龙华区,靠近骑楼老街,餐厅供应海南鸡饭和清补凉。
哈尔滨中央大街酒店位于哈尔滨道里区,冬季游客多,招牌菜是锅包肉和红肠拼盘。
长春净月湖酒店位于长春南关区,适合会务活动,特色菜有雪衣豆沙和酱骨头。
太原晋阳湖酒店在太原晋源区,主打湖景房,餐厅推荐刀削面和过油肉。
呼和浩特草原迎宾酒店位于呼和浩特赛罕区,特色是草原主题宴会,菜品有手把肉和奶茶。
兰州黄河岸酒店在兰州城关区,临近黄河风情线,特色菜是兰州牛肉面和手抓羊肉。
银川贺兰山酒店位于银川金凤区,适合自驾游客,主打烩小吃和羊肉臊子面。
乌鲁木齐天山宾馆在乌鲁木齐天山区,交通便利,特色菜是大盘鸡和烤包子。
西宁青唐驿酒店位于西宁城西区,适合高原旅行中转,特色菜为手抓羊肉和酸奶。
拉萨布达拉观景酒店在拉萨城关区,拥有观景露台,推荐藏面和酥油茶。
宁波东钱湖酒店位于宁波鄞州区,湖景安静,特色菜是宁波汤圆和雪菜黄鱼。
无锡太湖雅居酒店在无锡滨湖区,靠近太湖,招牌菜为酱排骨和太湖三白。
佛山岭南庭院酒店位于佛山禅城区,主打岭南建筑风格,特色菜是顺德双皮奶和均安蒸猪。
东莞松山湖酒店在东莞松山湖片区,适合科技企业接待,菜品有烧鹅和莞香鸡。
珠海情侣路酒店位于珠海香洲区,海岸景观明显,特色菜是横琴蚝和白灼虾。
泉州刺桐客栈在泉州鲤城区,临近古城街巷,推荐面线糊和姜母鸭。
南昌滕王阁酒店位于南昌东湖区,江景开阔,特色菜是瓦罐汤和藜蒿炒腊肉。
合肥包河商务酒店在合肥包河区,适合短途商务,主打臭鳜鱼和庐州烤鸭。
石家庄燕赵酒店位于石家庄长安区,会议设施齐全,特色菜是金毛狮子鱼和驴肉火烧。
洛阳牡丹庭酒店在洛阳老城区,春季游客多,特色菜是洛阳水席和浆面条。
济南泉城人家酒店位于济南历下区,靠近泉水景区,推荐九转大肠和甜沫。
烟台海岸葡园酒店在烟台芝罘区,适合海滨休闲,特色菜是鲅鱼饺子和葱烧海参。
沈阳盛京公馆酒店位于沈阳沈河区,装饰带东北民俗元素,菜品有锅包肉和熏肉大饼。
襄阳古城驿酒店在襄阳襄城区,临近古城墙,特色菜是襄阳牛肉面和夹沙肉。
宝鸡陈仓驿酒店位于宝鸡渭滨区,适合秦岭旅行中转,推荐臊子面和擀面皮。
绵阳科技城酒店在绵阳涪城区,服务科技园商务客,特色菜是绵阳米粉和江油肥肠。
岳阳洞庭湖酒店位于岳阳岳阳楼区,临近洞庭湖,主打洞庭银鱼和姜辣蛇。
大理洱海风月酒店在大理古城附近,适合度假,特色菜是砂锅鱼和乳扇。
遵义红城酒店位于遵义红花岗区,红色旅游客源多,招牌菜是豆花面和羊肉粉。

View File

@@ -0,0 +1,108 @@
人物信息测试文档
张伟34岁目前在北京一家智能制造企业担任算法工程师祖籍河北石家庄。
李娜今年29岁是上海某跨境电商公司的产品经理籍贯江苏南京。
王强45岁现任广州一家物流集团运营总监老家在广东佛山。
赵敏31岁深圳金融科技公司的数据分析师籍贯湖南长沙。
陈磊38岁在杭州从事云计算架构工作出生于浙江宁波。
刘洋27岁成都游戏公司的角色原画师籍贯四川绵阳。
杨帆今年42岁武汉光电企业研发主管祖籍湖北襄阳。
黄静36岁西安高校讲师籍贯陕西宝鸡。
周涛33岁在南京一家软件公司担任后端开发工程师老家安徽合肥。
吴倩28岁重庆文旅公司市场策划籍贯重庆万州。
徐明51岁天津港口企业安全负责人祖籍山东济南。
孙悦今年30岁苏州生物医药企业注册专员籍贯江苏扬州。
胡斌39岁郑州食品加工厂生产经理老家河南洛阳。
朱琳26岁在青岛做品牌设计师籍贯山东烟台。
高峰44岁大连船舶企业项目经理祖籍辽宁沈阳。
林晓32岁厦门互联网公司的测试工程师籍贯福建泉州。
何俊37岁福州新能源公司采购主管老家江西南昌。
郭婷35岁长沙教育科技公司教研负责人籍贯湖南岳阳。
马超41岁昆明农业科技企业销售总监祖籍云南大理。
罗兰今年29岁贵阳大数据企业运维工程师籍贯贵州遵义。
梁晨46岁南宁建筑设计院结构工程师老家广西桂林。
宋佳25岁海口酒店集团客户经理籍贯海南三亚。
唐凯40岁哈尔滨装备制造企业工艺工程师祖籍黑龙江齐齐哈尔。
冯雪33岁长春汽车零部件公司质量经理籍贯吉林吉林市。
韩旭48岁太原煤机企业技术顾问老家山西大同。
曹阳31岁呼和浩特乳制品企业渠道主管籍贯内蒙古包头。
彭敏27岁兰州医疗器械公司售后工程师祖籍甘肃天水。
曾辉43岁银川葡萄酒企业酿造师籍贯宁夏吴忠。
邓洁30岁乌鲁木齐贸易公司外贸专员老家新疆喀什。
薛亮36岁西宁环保企业项目负责人籍贯青海海东。
魏然52岁拉萨旅游服务公司总经理祖籍四川成都。
蒋悦24岁宁波港航企业单证员籍贯浙江绍兴。
沈航34岁无锡物联网企业嵌入式工程师老家江苏常州。
任洁29岁佛山家电企业用户研究员籍贯广东中山。
姚坤47岁东莞电子制造企业厂长祖籍广东梅州。
卢欣31岁珠海医药企业法务专员籍贯广东湛江。
蔡敏39岁泉州鞋服企业供应链经理老家福建莆田。
丁一28岁南昌传媒公司视频编导籍贯江西九江。
潘越35岁合肥新能源车企系统工程师祖籍安徽芜湖。
邱宁42岁石家庄制药企业质量负责人籍贯河北保定。
袁浩26岁洛阳机器人公司机械设计师老家河南开封。
程雪37岁济南软件园人力资源经理籍贯山东潍坊。
莫凡33岁烟台海洋食品企业电商运营祖籍山东威海。
贺琳45岁沈阳航空企业采购经理籍贯辽宁鞍山。
叶晨30岁襄阳汽车零部件企业工艺员老家湖北宜昌。
孔明49岁宝鸡钛材企业设备主管祖籍陕西咸阳。
孟琪27岁绵阳电子信息企业硬件工程师籍贯四川德阳。
秦川41岁岳阳石化企业安全工程师老家湖南常德。
邵雨32岁大理民宿品牌运营经理籍贯云南丽江。
白露25岁遵义茶叶公司新媒体专员祖籍贵州安顺。
夏然38岁桂林旅游平台产品运营籍贯广西柳州。
陆鸣44岁三亚度假酒店餐饮总监老家海南海口。
范哲36岁齐齐哈尔装备厂电气工程师祖籍黑龙江牡丹江。
田野29岁吉林市化工企业实验员籍贯吉林四平。
于洋50岁大同能源企业矿山工程师老家山西忻州。
段明35岁包头稀土材料企业销售经理祖籍内蒙古赤峰。
康宁31岁天水农产品公司渠道专员籍贯甘肃平凉。
钱峰43岁吴忠食品企业财务经理老家宁夏中卫。
石磊34岁喀什贸易公司仓储主管籍贯新疆伊宁。
乔安28岁海东教育培训机构课程顾问祖籍青海西宁。
赖青39岁绍兴纺织企业生产计划员籍贯浙江嘉兴。
熊浩46岁常州工程机械公司售前经理老家江苏镇江。
庞宇33岁中山照明企业工业设计师祖籍广东江门。
纪辰40岁湛江水产企业品控经理籍贯广东茂名。
向晴27岁莆田鞋业公司视觉设计师老家福建福州。
杜航35岁九江港口企业调度员祖籍江西景德镇。
穆清48岁芜湖汽车企业总装车间主任籍贯安徽马鞍山。
童乐26岁保定新能源材料企业实验助理老家河北唐山。
毛宁37岁开封文创公司策展人祖籍河南郑州。
欧阳洁32岁潍坊农业装备公司行政主管籍贯山东临沂。
施展45岁威海海工企业项目负责人老家山东青岛。
严妍29岁鞍山钢铁企业环保专员祖籍辽宁大连。
祝凯41岁宜昌化工企业物流经理籍贯湖北荆州。
颜晨30岁咸阳食品公司品牌经理老家陕西西安。
舒曼36岁德阳装备制造企业采购工程师祖籍四川成都。
梅婷28岁常德农旅公司内容运营籍贯湖南长沙。
费翔49岁丽江旅行社线路设计师老家云南昆明。
毕然34岁安顺航空配套企业质量工程师祖籍贵州贵阳。
蓝心31岁柳州汽车公司成本分析师籍贯广西南宁。
左宁42岁牡丹江林产品公司销售总监老家黑龙江哈尔滨。
米兰27岁四平食品企业直播运营祖籍吉林长春。
卜凡52岁忻州煤化工企业设备顾问籍贯山西太原。
焦阳38岁赤峰农牧企业牧场经理老家内蒙古呼和浩特。
邢云30岁平凉制药企业研发助理祖籍甘肃兰州。
仲夏35岁中卫新能源企业运维主管籍贯宁夏银川。
欧文44岁伊宁跨境贸易公司商务经理老家新疆乌鲁木齐。
单晴26岁嘉兴服装企业买手助理祖籍浙江杭州。
甄浩33岁镇江材料企业工艺工程师籍贯江苏南京。
解语29岁江门摩托车企业市场专员老家广东广州。
卫东47岁茂名石化企业设备经理祖籍广东深圳。
游佳31岁景德镇陶瓷品牌设计师籍贯江西南昌。
宁川40岁马鞍山钢材企业计划经理老家安徽合肥。
房琪28岁唐山陶瓷公司电商客服主管祖籍河北石家庄。
艾明36岁临沂商贸企业物流调度籍贯山东济南。
楚然32岁荆州文化传媒公司编导老家湖北武汉。
褚航39岁江门智能装备公司售后经理祖籍广东佛山。
苗雨25岁镇江香醋品牌新媒体运营籍贯江苏苏州。
谷峰46岁长沙工程咨询公司项目总监老家湖南湘潭。
贝宁34岁宁波跨境电商企业采购经理祖籍浙江温州。
桑榆37岁厦门航运服务公司客服主管籍贯福建漳州。
路遥43岁重庆汽车公司供应链总监老家四川南充。
景甜30岁北京文化科技公司交互设计师祖籍天津。
辛宇41岁上海医疗信息企业解决方案架构师籍贯江苏无锡。
温岚27岁广州食品科技公司营养师老家广东汕头。
霍然35岁深圳机器人公司视觉算法工程师祖籍湖北武汉。
简宁48岁杭州数字经济园区招商经理籍贯浙江金华。

View File

@@ -1,19 +1,16 @@
package com.bruce.common.config;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@Data
@NoArgsConstructor
@ConfigurationProperties(prefix = "common.attachment")
public class AttachmentProperties {
private String basePath = "data/attachments";
public String getBasePath() {
return basePath;
}
public void setBasePath(String basePath) {
this.basePath = basePath;
}
}

View File

@@ -1,31 +1,28 @@
package com.bruce.common.controller;
import com.bruce.common.entity.SysAttachment;
import com.bruce.common.domain.entity.SysAttachment;
import com.bruce.common.domain.model.RequestResult;
import com.bruce.common.dto.request.SysAttachmentUploadRequest;
import com.bruce.common.service.ISysAttachmentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@Tag(name = "系统附件管理")
@RestController
@RequestMapping("/api/attachments")
public class SysAttachmentController {
private final ISysAttachmentService sysAttachmentService;
public SysAttachmentController(ISysAttachmentService sysAttachmentService) {
this.sysAttachmentService = sysAttachmentService;
}
@Autowired
private ISysAttachmentService sysAttachmentService;
@Operation(summary = "上传附件")
@PostMapping("/upload")
public SysAttachment upload(@RequestParam("file") MultipartFile file,
@RequestParam String sourceType,
@RequestParam(required = false) Long sourceId) {
return sysAttachmentService.upload(file, sourceType, sourceId);
public RequestResult<SysAttachment> upload(@ModelAttribute SysAttachmentUploadRequest request) {
return RequestResult.success(sysAttachmentService.upload(request));
}
}

View File

@@ -1,16 +1,19 @@
package com.bruce.common.controller;
import com.bruce.common.entity.SysEnum;
import com.bruce.common.domain.entity.SysEnum;
import com.bruce.common.domain.model.RequestResult;
import com.bruce.common.dto.request.SysEnumQueryRequest;
import com.bruce.common.dto.request.SysEnumSaveRequest;
import com.bruce.common.service.ISysEnumService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@@ -20,33 +23,30 @@ import java.util.List;
@RequestMapping("/api/sys-enums")
public class SysEnumController {
private final ISysEnumService sysEnumService;
public SysEnumController(ISysEnumService sysEnumService) {
this.sysEnumService = sysEnumService;
}
@Autowired
private ISysEnumService sysEnumService;
@Operation(summary = "查询全部系统枚举")
@GetMapping
public List<SysEnum> list() {
return sysEnumService.list();
public RequestResult<List<SysEnum>> list() {
return RequestResult.success(sysEnumService.list());
}
@Operation(summary = "根据模块和类型查询系统枚举")
@GetMapping("/query")
public List<SysEnum> queryByCatalogAndType(@RequestParam String catalog, @RequestParam String type) {
return sysEnumService.listByCatalogAndType(catalog, type);
@PostMapping("/query")
public RequestResult<List<SysEnum>> queryByCatalogAndType(@RequestBody SysEnumQueryRequest request) {
return RequestResult.success(sysEnumService.listByCatalogAndType(request));
}
@Operation(summary = "新增或修改系统枚举")
@PostMapping
public boolean saveOrUpdate(@RequestBody SysEnum sysEnum) {
return sysEnumService.saveOrUpdate(sysEnum);
public RequestResult<Boolean> saveOrUpdate(@RequestBody SysEnumSaveRequest request) {
return RequestResult.success(sysEnumService.saveOrUpdate(request));
}
@Operation(summary = "删除系统枚举")
@DeleteMapping("/{id}")
public boolean deleteById(@PathVariable Long id) {
return sysEnumService.removeById(id);
public RequestResult<Boolean> deleteById(@PathVariable Long id) {
return RequestResult.success(sysEnumService.removeById(id));
}
}

View File

@@ -1,7 +1,8 @@
package com.bruce.common.entity;
package com.bruce.common.domain.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.bruce.common.domain.model.BaseEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;

View File

@@ -1,6 +1,7 @@
package com.bruce.common.entity;
package com.bruce.common.domain.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.bruce.common.domain.model.BaseEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;

View File

@@ -1,4 +1,4 @@
package com.bruce.common.entity;
package com.bruce.common.domain.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;

View File

@@ -0,0 +1,59 @@
package com.bruce.common.domain.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RequestResult<T> {
public static final String FAIL_CODE = "-1";
public static final String SUCCESS_CODE = "0";
@Schema(description = "错误消息")
private String message;
@Schema(description = "消息代码")
private String resultcode;
@Schema(description = "数据")
private T data;
public RequestResult(T data) {
this.resultcode = SUCCESS_CODE;
this.data = data;
}
public static <T> RequestResult<T> success() {
return build(SUCCESS_CODE, null, null);
}
public static <T> RequestResult<T> success(T data) {
return build(SUCCESS_CODE, null, data);
}
public static <T> RequestResult<T> success(String message, T data) {
return build(SUCCESS_CODE, message, data);
}
public static <T> RequestResult<T> fail(String message) {
return build(FAIL_CODE, message, null);
}
public static <T> RequestResult<T> fail(String resultcode, String message) {
return build(resultcode, message, null);
}
private static <T> RequestResult<T> build(String resultcode, String message, T data) {
RequestResult<T> result = new RequestResult<>();
result.setResultcode(resultcode);
result.setMessage(message);
result.setData(data);
return result;
}
}

View File

@@ -0,0 +1,19 @@
package com.bruce.common.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
@Data
@Schema(description = "附件上传请求")
public class SysAttachmentUploadRequest {
@Schema(description = "上传文件")
private MultipartFile file;
@Schema(description = "来源类型")
private String sourceType;
@Schema(description = "来源业务ID")
private Long sourceId;
}

View File

@@ -0,0 +1,15 @@
package com.bruce.common.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "系统枚举查询请求")
public class SysEnumQueryRequest {
@Schema(description = "模块目录")
private String catalog;
@Schema(description = "枚举类型")
private String type;
}

View File

@@ -0,0 +1,33 @@
package com.bruce.common.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "系统枚举保存请求")
public class SysEnumSaveRequest {
@Schema(description = "主键ID")
private Long id;
@Schema(description = "模块目录")
private String catalog;
@Schema(description = "枚举类型")
private String type;
@Schema(description = "枚举名称")
private String name;
@Schema(description = "整型值")
private Integer value;
@Schema(description = "字符串值")
private String strvalue;
@Schema(description = "排序")
private Integer sort;
@Schema(description = "备注")
private String remark;
}

View File

@@ -1,7 +1,7 @@
package com.bruce.common.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.bruce.common.entity.SysAttachment;
import com.bruce.common.domain.entity.SysAttachment;
import org.apache.ibatis.annotations.Mapper;
@Mapper

View File

@@ -1,7 +1,7 @@
package com.bruce.common.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.bruce.common.entity.SysEnum;
import com.bruce.common.domain.entity.SysEnum;
import org.apache.ibatis.annotations.Mapper;
@Mapper

View File

@@ -1,10 +1,10 @@
package com.bruce.common.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bruce.common.entity.SysAttachment;
import org.springframework.web.multipart.MultipartFile;
import com.bruce.common.domain.entity.SysAttachment;
import com.bruce.common.dto.request.SysAttachmentUploadRequest;
public interface ISysAttachmentService extends IService<SysAttachment> {
SysAttachment upload(MultipartFile file, String sourceType, Long sourceId);
SysAttachment upload(SysAttachmentUploadRequest request);
}

View File

@@ -1,11 +1,15 @@
package com.bruce.common.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bruce.common.entity.SysEnum;
import com.bruce.common.domain.entity.SysEnum;
import com.bruce.common.dto.request.SysEnumQueryRequest;
import com.bruce.common.dto.request.SysEnumSaveRequest;
import java.util.List;
public interface ISysEnumService extends IService<SysEnum> {
List<SysEnum> listByCatalogAndType(String catalog, String type);
List<SysEnum> listByCatalogAndType(SysEnumQueryRequest request);
boolean saveOrUpdate(SysEnumSaveRequest request);
}

View File

@@ -2,9 +2,11 @@ package com.bruce.common.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bruce.common.config.AttachmentProperties;
import com.bruce.common.entity.SysAttachment;
import com.bruce.common.domain.entity.SysAttachment;
import com.bruce.common.dto.request.SysAttachmentUploadRequest;
import com.bruce.common.mapper.SysAttachmentMapper;
import com.bruce.common.service.ISysAttachmentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
@@ -22,17 +24,19 @@ public class SysAttachmentServiceImpl extends ServiceImpl<SysAttachmentMapper, S
private static final String STORAGE_TYPE_LOCAL = "LOCAL";
private final AttachmentProperties attachmentProperties;
public SysAttachmentServiceImpl(AttachmentProperties attachmentProperties) {
this.attachmentProperties = attachmentProperties;
}
@Autowired
private AttachmentProperties attachmentProperties;
@Override
public SysAttachment upload(MultipartFile file, String sourceType, Long sourceId) {
public SysAttachment upload(SysAttachmentUploadRequest request) {
if (request == null) {
throw new IllegalArgumentException("上传请求不能为空");
}
MultipartFile file = request.getFile();
if (file == null || file.isEmpty()) {
throw new IllegalArgumentException("上传文件不能为空");
}
String sourceType = request.getSourceType();
if (!StringUtils.hasText(sourceType)) {
throw new IllegalArgumentException("sourceType不能为空");
}
@@ -54,7 +58,7 @@ public class SysAttachmentServiceImpl extends ServiceImpl<SysAttachmentMapper, S
SysAttachment attachment = new SysAttachment();
attachment.setSourceType(sourceType);
attachment.setSourceId(sourceId);
attachment.setSourceId(request.getSourceId());
attachment.setOriginalName(originalName);
attachment.setFileName(storedFileName);
attachment.setFileSuffix(suffix);

View File

@@ -1,10 +1,13 @@
package com.bruce.common.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bruce.common.entity.SysEnum;
import com.bruce.common.domain.entity.SysEnum;
import com.bruce.common.dto.request.SysEnumQueryRequest;
import com.bruce.common.dto.request.SysEnumSaveRequest;
import com.bruce.common.mapper.SysEnumMapper;
import com.bruce.common.service.ISysEnumService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.List;
@@ -12,11 +15,32 @@ import java.util.List;
public class SysEnumServiceImpl extends ServiceImpl<SysEnumMapper, SysEnum> implements ISysEnumService {
@Override
public List<SysEnum> listByCatalogAndType(String catalog, String type) {
public List<SysEnum> listByCatalogAndType(SysEnumQueryRequest request) {
if (request == null) {
throw new IllegalArgumentException("查询请求不能为空");
}
return lambdaQuery()
.eq(SysEnum::getCatalog, catalog)
.eq(SysEnum::getType, type)
.eq(StringUtils.hasText(request.getCatalog()), SysEnum::getCatalog, request.getCatalog())
.eq(StringUtils.hasText(request.getType()), SysEnum::getType, request.getType())
.orderByAsc(SysEnum::getSort)
.list();
}
@Override
public boolean saveOrUpdate(SysEnumSaveRequest request) {
if (request == null) {
throw new IllegalArgumentException("保存请求不能为空");
}
SysEnum sysEnum = new SysEnum();
sysEnum.setId(request.getId());
sysEnum.setCatalog(request.getCatalog());
sysEnum.setType(request.getType());
sysEnum.setName(request.getName());
sysEnum.setValue(request.getValue());
sysEnum.setStrvalue(request.getStrvalue());
sysEnum.setSort(request.getSort());
sysEnum.setRemark(request.getRemark());
return super.saveOrUpdate(sysEnum);
}
}

View File

@@ -1,10 +1,15 @@
package com.bruce.rag.controller;
import com.bruce.common.domain.model.RequestResult;
import com.bruce.rag.dto.request.RagDocumentQueryRequest;
import com.bruce.rag.entity.RagDocument;
import com.bruce.rag.service.IRagDocumentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -15,15 +20,18 @@ import java.util.List;
@RequestMapping("/api/rag/documents")
public class RagDocumentController {
private final IRagDocumentService ragDocumentService;
public RagDocumentController(IRagDocumentService ragDocumentService) {
this.ragDocumentService = ragDocumentService;
}
@Autowired
private IRagDocumentService ragDocumentService;
@Operation(summary = "查询全部知识库文档")
@GetMapping
public List<RagDocument> list() {
return ragDocumentService.list();
public RequestResult<List<RagDocument>> list() {
return RequestResult.success(ragDocumentService.list());
}
@Operation(summary = "按条件查询知识库文档")
@PostMapping("/query")
public RequestResult<List<RagDocument>> query(@RequestBody RagDocumentQueryRequest request) {
return RequestResult.success(ragDocumentService.query(request));
}
}

View File

@@ -1,10 +1,15 @@
package com.bruce.rag.controller;
import com.bruce.common.domain.model.RequestResult;
import com.bruce.rag.dto.request.RagStoreQueryRequest;
import com.bruce.rag.entity.RagStore;
import com.bruce.rag.service.IRagStoreService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -15,15 +20,18 @@ import java.util.List;
@RequestMapping("/api/rag/stores")
public class RagStoreController {
private final IRagStoreService ragStoreService;
public RagStoreController(IRagStoreService ragStoreService) {
this.ragStoreService = ragStoreService;
}
@Autowired
private IRagStoreService ragStoreService;
@Operation(summary = "查询全部知识库")
@GetMapping
public List<RagStore> list() {
return ragStoreService.list();
public RequestResult<List<RagStore>> list() {
return RequestResult.success(ragStoreService.list());
}
@Operation(summary = "按条件查询知识库")
@PostMapping("/query")
public RequestResult<List<RagStore>> query(@RequestBody RagStoreQueryRequest request) {
return RequestResult.success(ragStoreService.query(request));
}
}

View File

@@ -0,0 +1,24 @@
package com.bruce.rag.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "RAG知识库文档查询请求")
public class RagDocumentQueryRequest {
@Schema(description = "知识库ID")
private Long storeId;
@Schema(description = "附件ID")
private Long attachmentId;
@Schema(description = "解析状态")
private String parseStatus;
@Schema(description = "索引状态")
private String indexStatus;
@Schema(description = "是否启用")
private Boolean enabled;
}

View File

@@ -0,0 +1,18 @@
package com.bruce.rag.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "RAG知识库查询请求")
public class RagStoreQueryRequest {
@Schema(description = "知识库编码")
private String storeCode;
@Schema(description = "知识库名称")
private String storeName;
@Schema(description = "状态")
private String status;
}

View File

@@ -2,7 +2,7 @@ package com.bruce.rag.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.bruce.common.entity.BaseEntity;
import com.bruce.common.domain.model.BaseEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;

View File

@@ -1,7 +1,7 @@
package com.bruce.rag.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.bruce.common.entity.BaseEntity;
import com.bruce.common.domain.model.BaseEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;

View File

@@ -1,7 +1,12 @@
package com.bruce.rag.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bruce.rag.dto.request.RagDocumentQueryRequest;
import com.bruce.rag.entity.RagDocument;
import java.util.List;
public interface IRagDocumentService extends IService<RagDocument> {
List<RagDocument> query(RagDocumentQueryRequest request);
}

View File

@@ -1,7 +1,12 @@
package com.bruce.rag.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bruce.rag.dto.request.RagStoreQueryRequest;
import com.bruce.rag.entity.RagStore;
import java.util.List;
public interface IRagStoreService extends IService<RagStore> {
List<RagStore> query(RagStoreQueryRequest request);
}

View File

@@ -1,11 +1,29 @@
package com.bruce.rag.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bruce.rag.dto.request.RagDocumentQueryRequest;
import com.bruce.rag.entity.RagDocument;
import com.bruce.rag.mapper.RagDocumentMapper;
import com.bruce.rag.service.IRagDocumentService;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class RagDocumentServiceImpl extends ServiceImpl<RagDocumentMapper, RagDocument> implements IRagDocumentService {
@Override
public List<RagDocument> query(RagDocumentQueryRequest request) {
if (request == null) {
throw new IllegalArgumentException("查询请求不能为空");
}
return lambdaQuery()
.eq(request.getStoreId() != null, RagDocument::getStoreId, request.getStoreId())
.eq(request.getAttachmentId() != null, RagDocument::getAttachmentId, request.getAttachmentId())
.eq(request.getParseStatus() != null, RagDocument::getParseStatus, request.getParseStatus())
.eq(request.getIndexStatus() != null, RagDocument::getIndexStatus, request.getIndexStatus())
.eq(request.getEnabled() != null, RagDocument::getEnabled, request.getEnabled())
.orderByDesc(RagDocument::getId)
.list();
}
}

View File

@@ -1,11 +1,28 @@
package com.bruce.rag.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bruce.rag.dto.request.RagStoreQueryRequest;
import com.bruce.rag.entity.RagStore;
import com.bruce.rag.mapper.RagStoreMapper;
import com.bruce.rag.service.IRagStoreService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.List;
@Service
public class RagStoreServiceImpl extends ServiceImpl<RagStoreMapper, RagStore> implements IRagStoreService {
@Override
public List<RagStore> query(RagStoreQueryRequest request) {
if (request == null) {
throw new IllegalArgumentException("查询请求不能为空");
}
return lambdaQuery()
.eq(StringUtils.hasText(request.getStoreCode()), RagStore::getStoreCode, request.getStoreCode())
.like(StringUtils.hasText(request.getStoreName()), RagStore::getStoreName, request.getStoreName())
.eq(StringUtils.hasText(request.getStatus()), RagStore::getStatus, request.getStatus())
.orderByAsc(RagStore::getStoreCode)
.list();
}
}

View File

@@ -3,16 +3,19 @@ package com.bruce.common.attachment;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bruce.common.config.AttachmentProperties;
import com.bruce.common.controller.SysAttachmentController;
import com.bruce.common.entity.SysAttachment;
import com.bruce.common.domain.model.RequestResult;
import com.bruce.common.dto.request.SysAttachmentUploadRequest;
import com.bruce.common.domain.entity.SysAttachment;
import com.bruce.common.mapper.SysAttachmentMapper;
import com.bruce.common.service.ISysAttachmentService;
import com.bruce.common.service.impl.SysAttachmentServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.web.multipart.MultipartFile;
import java.lang.reflect.Method;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -31,18 +34,27 @@ class SysAttachmentComponentStructureTests {
void sysAttachmentShouldExposeUploadMethods() throws NoSuchMethodException {
Method serviceMethod = ISysAttachmentService.class.getMethod(
"upload",
MultipartFile.class,
String.class,
Long.class
SysAttachmentUploadRequest.class
);
Method controllerMethod = SysAttachmentController.class.getMethod(
"upload",
MultipartFile.class,
String.class,
Long.class
SysAttachmentUploadRequest.class
);
assertNotNull(serviceMethod);
assertNotNull(controllerMethod);
assertEquals(SysAttachment.class, serviceMethod.getReturnType());
assertEquals(RequestResult.class, controllerMethod.getReturnType());
}
@Test
void attachmentPropertiesShouldExposeBindableBasePath() {
AttachmentProperties properties = new AttachmentProperties();
assertEquals("data/attachments", properties.getBasePath());
properties.setBasePath("custom/attachments");
assertEquals("custom/attachments", properties.getBasePath());
}
}

View File

@@ -3,6 +3,9 @@ package com.bruce.common.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.Version;
import com.bruce.common.domain.model.BaseEntity;
import com.bruce.common.domain.entity.SysAttachment;
import com.bruce.common.domain.entity.SysEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import org.junit.jupiter.api.Test;

View File

@@ -4,14 +4,19 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bruce.common.controller.SysEnumController;
import com.bruce.common.entity.SysEnum;
import com.bruce.common.domain.entity.SysEnum;
import com.bruce.common.domain.model.RequestResult;
import com.bruce.common.dto.request.SysEnumQueryRequest;
import com.bruce.common.dto.request.SysEnumSaveRequest;
import com.bruce.common.mapper.SysEnumMapper;
import com.bruce.common.service.ISysEnumService;
import com.bruce.common.service.impl.SysEnumServiceImpl;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -28,20 +33,30 @@ class SysEnumComponentStructureTests {
@Test
void sysEnumShouldExposeQueryMethodForCatalogAndType() throws NoSuchMethodException {
Method serviceMethod = ISysEnumService.class.getMethod("listByCatalogAndType", String.class, String.class);
Method controllerMethod = SysEnumController.class.getMethod("queryByCatalogAndType", String.class, String.class);
Method serviceMethod = ISysEnumService.class.getMethod("listByCatalogAndType", SysEnumQueryRequest.class);
Method controllerMethod = SysEnumController.class.getMethod("queryByCatalogAndType", SysEnumQueryRequest.class);
assertNotNull(serviceMethod);
assertNotNull(controllerMethod);
assertEquals(List.class, serviceMethod.getReturnType());
assertEquals(RequestResult.class, controllerMethod.getReturnType());
}
@Test
void sysEnumShouldExposeSaveOrUpdateAndDeleteInterfaces() throws NoSuchMethodException {
Method saveOrUpdateMethod = SysEnumController.class.getMethod("saveOrUpdate", SysEnum.class);
Method saveOrUpdateMethod = ISysEnumService.class.getMethod("saveOrUpdate", SysEnumSaveRequest.class);
Method controllerSaveOrUpdateMethod = SysEnumController.class.getMethod("saveOrUpdate", SysEnumSaveRequest.class);
Method deleteMethod = SysEnumController.class.getMethod("deleteById", Long.class);
Method listMethod = SysEnumController.class.getMethod("list");
assertNotNull(saveOrUpdateMethod);
assertNotNull(controllerSaveOrUpdateMethod);
assertNotNull(deleteMethod);
assertNotNull(listMethod);
assertEquals(boolean.class, saveOrUpdateMethod.getReturnType());
assertEquals(RequestResult.class, controllerSaveOrUpdateMethod.getReturnType());
assertEquals(RequestResult.class, deleteMethod.getReturnType());
assertEquals(RequestResult.class, listMethod.getReturnType());
}
@Test

View File

@@ -1,7 +1,7 @@
package com.bruce.common.enumconfig;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bruce.common.entity.SysEnum;
import com.bruce.common.domain.entity.SysEnum;
import com.bruce.common.enums.CommonStatusEnum;
import com.bruce.common.enums.EnableStatusEnum;
import com.bruce.common.service.ISysEnumService;

View File

@@ -0,0 +1,55 @@
package com.bruce.common.model;
import com.bruce.common.domain.model.RequestResult;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
class RequestResultStructureTests {
@Test
void dataConstructorShouldDefaultToSuccessCode() {
RequestResult<String> result = new RequestResult<>("payload");
assertEquals(RequestResult.SUCCESS_CODE, result.getResultcode());
assertNull(result.getMessage());
assertEquals("payload", result.getData());
}
@Test
void successFactoryShouldBuildSuccessfulResult() {
RequestResult<String> result = RequestResult.success("payload");
assertEquals(RequestResult.SUCCESS_CODE, result.getResultcode());
assertNull(result.getMessage());
assertEquals("payload", result.getData());
}
@Test
void successFactoryWithMessageShouldKeepCustomMessage() {
RequestResult<String> result = RequestResult.success("操作成功", "payload");
assertEquals(RequestResult.SUCCESS_CODE, result.getResultcode());
assertEquals("操作成功", result.getMessage());
assertEquals("payload", result.getData());
}
@Test
void failFactoryShouldBuildFailureResult() {
RequestResult<Void> result = RequestResult.fail("校验失败");
assertEquals(RequestResult.FAIL_CODE, result.getResultcode());
assertEquals("校验失败", result.getMessage());
assertNull(result.getData());
}
@Test
void failFactoryWithCustomCodeShouldKeepCustomCode() {
RequestResult<Void> result = RequestResult.fail("VALIDATE_ERROR", "校验失败");
assertEquals("VALIDATE_ERROR", result.getResultcode());
assertEquals("校验失败", result.getMessage());
assertNull(result.getData());
}
}

View File

@@ -3,9 +3,12 @@ package com.bruce.rag;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bruce.common.domain.model.RequestResult;
import com.bruce.rag.constant.RagSystemConstants;
import com.bruce.rag.controller.RagDocumentController;
import com.bruce.rag.controller.RagStoreController;
import com.bruce.rag.dto.request.RagDocumentQueryRequest;
import com.bruce.rag.dto.request.RagStoreQueryRequest;
import com.bruce.rag.entity.RagDocument;
import com.bruce.rag.entity.RagStore;
import com.bruce.rag.mapper.RagDocumentMapper;
@@ -17,6 +20,8 @@ import com.bruce.rag.service.impl.RagStoreServiceImpl;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -33,6 +38,25 @@ class RagComponentStructureTests {
assertTrue(ServiceImpl.class.isAssignableFrom(RagDocumentServiceImpl.class));
}
@Test
void ragControllersShouldExposeRequestResultAndQueryDtoMethods() throws NoSuchMethodException {
Method storeListMethod = RagStoreController.class.getMethod("list");
Method storeQueryMethod = RagStoreController.class.getMethod("query", RagStoreQueryRequest.class);
Method storeServiceQueryMethod = IRagStoreService.class.getMethod("query", RagStoreQueryRequest.class);
Method documentListMethod = RagDocumentController.class.getMethod("list");
Method documentQueryMethod = RagDocumentController.class.getMethod("query", RagDocumentQueryRequest.class);
Method documentServiceQueryMethod = IRagDocumentService.class.getMethod("query", RagDocumentQueryRequest.class);
assertEquals(RequestResult.class, storeListMethod.getReturnType());
assertEquals(RequestResult.class, storeQueryMethod.getReturnType());
assertEquals(List.class, storeServiceQueryMethod.getReturnType());
assertEquals(RequestResult.class, documentListMethod.getReturnType());
assertEquals(RequestResult.class, documentQueryMethod.getReturnType());
assertEquals(List.class, documentServiceQueryMethod.getReturnType());
}
@Test
void ragSourceTypesAndDocumentRelationShouldExist() throws NoSuchFieldException {
Field storeIdField = RagDocument.class.getDeclaredField("storeId");