3 Commits
Author SHA1 Message Date
cat-shark e42b15600a chore: 移除Jenkins配置 2026-08-02 10:21:05 +08:00
cat-shark e32226d7d2 docs: 合并CLAUDE规范 2026-08-01 00:15:56 +08:00
cat-shark b61736eb2b docs: migrate .pi/SYSTEM.md to AGENTS.md 2026-08-01 00:13:05 +08:00
5 changed files with 129 additions and 337 deletions
-109
View File
@@ -1,109 +0,0 @@
# LPT 后端服务(lpt-be
> Java Spring Boot 后端,提供 REST API、数据库访问、认证鉴权
## Git 提交规范
- 提交信息使用**中文**,简洁描述变更内容
- 格式:`类型: 简述`,如 `feat: 新增用户登录接口``fix: 修复多租户拦截器空指针``docs: 补充API文档`
---
## 技术栈
- **框架**: Spring Boot 3.2.5
- **ORM**: MyBatis-Plus 3.5.5
- **数据库**: MySQL 8.0(端口 8109,服务器 39.105.149.197
- **认证**: Sa-Token 1.38.0Cookie 名 `satoken`
- **数据库迁移**: Flyway
- **对象映射**: MapStruct 1.5.5
- **连接池**: Druid 1.2.8
- **密码加密**: jBCrypt 0.4
- **API 文档**: Knife4j (OpenAPI 3)
---
## 项目结构
```
lpt-be/src/main/java/com/guo/learningprogresstracker/
├── controller/ → REST 接口层(6个控制器)
├── service/ → 业务逻辑层(12个服务接口 + impl)
├── mapper/ → MyBatis-Plus BaseMapper10个)
├── entity/ → 数据库实体(10个)
├── dto/ → 请求/响应 DTO
│ ├── request/ → 请求体
│ └── response/ → 响应体
├── config/ → Spring 配置
├── common/ → 全局异常处理、Ops
├── mapStruct/ → MapStruct Converter
├── enums/ → 枚举类
├── exception/ → 自定义异常
└── utils/ → 工具类
```
---
## API 端点总览(32 个)
| 模块 | 端点数 | 端点 |
|------|--------|------|
| 学习会话 | 9 | `POST /study-sessions/{taskNum}/start`, `PUT .../pause`, `PUT .../resume`, `PUT .../end`, `POST .../fragments`, `GET .../history`, `PUT .../expectation`, `GET .../expectation`, `GET .../report-draft` |
| 标准思维导图 | 6 | `GET /review/standard-mind-map/{taskNum}`, `POST .../regenerate`, `PUT ...`, `POST .../recall`, `POST .../find-node`, `GET .../recall-records` |
| 复习模块 | 6 | `GET /review/feed`, `GET /review/task/{taskNum}`, `GET /review/report/{id}`, `GET /review/fragment/{id}`, `GET /review/standard-mind-map/recall-records/{recordId}`, `GET /review/tasks` |
| 任务管理 | 6 | `GET /tasks`, `POST /tasks`, `PUT /tasks/{taskNum}`, `DELETE /tasks/{taskNum}`, `GET /tasks/priority-weights`, `PUT /tasks/priority-weights` |
| 应用场景 | 4 | `GET/POST/PUT/DELETE /tasks/{taskNum}/applications[/{id}]` |
| 工具 | 1 | `GET /utils/fetch-title?url=...` |
---
## 数据库(10 个核心表)
`users`, `tasks`, `study_sessions`, `study_reports`, `study_report_fragments`, `study_expectations`, `task_applications`, `review_standard_mind_maps`, `review_recall_records`, `user_priority_weights`
---
## 关键机制
- **多租户隔离**: `TenantLineInnerInterceptor` 自动注入 `WHERE created_by = #{当前用户}`,排除 `user`/`flyway_schema_history`
- **自动填充**: `MetaObjectHandler` 自动填充 `created_by`/`updated_by`/`created_time`/`updated_time`
- **认证鉴权**: Sa-Token 拦截 `/**` 排除 `/login`Cookie `satoken``@SaCheckPermission` 注解式权限
- **响应格式**: `CommonResult<T>` 统一包装 `{ code, message, data }`
- **CORS**: 可配置,local profile 允许所有来源
---
## 配置文件
| Profile | 文件 |
|---------|------|
| local | `application-local.yml` |
| dev | `application-dev.yml` |
| uat | `application-uat.yml` |
| prod | `application-prod.yml` |
| 公共 | `application.yml` |
---
## AI 服务依赖
- 配置: `lpt.ai-service.url=http://localhost:5199`
- 超时: 600s
- 不可用时自动降级到内置规则引擎(`BuiltinMindMapGenerator`
---
## 启动命令
```bash
mvn clean compile -DskipTests # 编译(需要 JDK 17
mvn spring-boot:run # 启动(默认 profile: local
```
---
## 关联项目
| 项目 | 路径 | 端口 | 说明 |
|------|------|------|------|
| lpt-fe | `../lpt-fe/` | 5158 | Vue 3 前端,通过 `/api` 代理调用本服务 |
| lpt-ai | `../lpt-ai/` | 5199 | AI 服务,本服务通过 HTTP 调用其异步任务接口 |
## 调用关系
```
浏览器 → lpt-fe (5158) ──/api──→ lpt-be (5157) ──HTTP──→ lpt-ai (5199)
└── MySQL (8109)
```
+129 -1
View File
@@ -1,4 +1,132 @@
# 项目规范 # LPT 后端服务(lpt-be
> Java Spring Boot 后端,提供 REST API、数据库访问、认证鉴权。
## Git 提交规范
- 提交信息必须简短且使用中文,不要使用英文长句。
- 格式:`类型: 简述`,例如 `feat: 新增用户登录接口``fix: 修复多租户拦截器空指针``docs: 补充API文档`
## 技术栈
- Java 17
- 框架:Spring Boot 3.2.5
- ORMMyBatis-Plus 3.5.5
- 数据库:MySQL 8.0
- 认证:Sa-Token 1.38.0Cookie 名 `satoken`
- 数据库迁移:Flyway
- 对象映射:MapStruct 1.5.5
- 连接池:Druid 1.2.8
- 密码加密:jBCrypt 0.4
- API 文档:Knife4j (OpenAPI 3)
- Lombok@Data, @Slf4j
## 项目结构
```text
lpt-be/src/main/java/com/guo/learningprogresstracker/
├── controller/ → REST 接口层
├── service/ → 业务逻辑层(接口 + impl)
├── mapper/ → MyBatis-Plus BaseMapper
├── entity/ → 数据库实体
├── dto/ → 请求/响应 DTO
├── config/ → Spring 配置
├── common/ → 全局异常处理、Ops
├── mapStruct/ → MapStruct Converter
├── enums/ → 枚举类
├── exception/ → 自定义异常
└── utils/ → 工具类
```
- controller 只做参数校验和路由。
- service/impl 承载业务逻辑。
- mapper 使用 MyBatis-Plus BaseMapper。
- entity 使用 `@TableName``@TableField` 映射数据库字段。
- mapStruct 承载 DTO 转换。
## API 端点总览(32 个)
| 模块 | 端点数 | 端点 |
|------|--------|------|
| 学习会话 | 9 | `POST /study-sessions/{taskNum}/start`, `PUT .../pause`, `PUT .../resume`, `PUT .../end`, `POST .../fragments`, `GET .../history`, `PUT .../expectation`, `GET .../expectation`, `GET .../report-draft` |
| 标准思维导图 | 6 | `GET /review/standard-mind-map/{taskNum}`, `POST .../regenerate`, `PUT ...`, `POST .../recall`, `POST .../find-node`, `GET .../recall-records` |
| 复习模块 | 6 | `GET /review/feed`, `GET /review/task/{taskNum}`, `GET /review/report/{id}`, `GET /review/fragment/{id}`, `GET /review/standard-mind-map/recall-records/{recordId}`, `GET /review/tasks` |
| 任务管理 | 6 | `GET /tasks`, `POST /tasks`, `PUT /tasks/{taskNum}`, `DELETE /tasks/{taskNum}`, `GET /tasks/priority-weights`, `PUT /tasks/priority-weights` |
| 应用场景 | 4 | `GET/POST/PUT/DELETE /tasks/{taskNum}/applications[/{id}]` |
| 工具 | 1 | `GET /utils/fetch-title?url=...` |
## 数据库(10 个核心表)
`users`, `tasks`, `study_sessions`, `study_reports`, `study_report_fragments`, `study_expectations`, `task_applications`, `review_standard_mind_maps`, `review_recall_records`, `user_priority_weights`
## 关键机制
- 多租户隔离:`TenantLineInnerInterceptor` 自动注入 `WHERE created_by = #{当前用户}`,排除 `user`/`flyway_schema_history` 表。
- 自动填充:`MetaObjectHandler` 自动填充 `created_by`/`updated_by`/`created_time`/`updated_time`
- 认证鉴权:Sa-Token 拦截 `/**` 排除 `/login`Cookie `satoken`,支持 `@SaCheckPermission` 注解式权限。
- 响应格式:`CommonResult<T>` 统一包装 `{ code, message, data }`
- CORS:可配置,local profile 允许所有来源。
## 响应规范
- 成功:`CommonResult.success(data)`code=200。
- 业务错误:`CommonResult.error(msg)`code=400HTTP 200。
- 未登录:`GlobalExceptionHandler.handleNotLogin()`HTTP 401 + code=401。
- 参数校验失败:`MethodArgumentNotValidException`code=400。
## DTO 转换
- MapStruct 编译期生成 `*ConvertImpl.java`,同名属性自动映射。
- 默认 `unmappedTargetPolicy = IGNORE`
- 自定义映射使用 `@Mapping(source, target)`
- 增删 DTO 字段后必须重新编译,否则生成代码不含新字段。
## CORS
-`CorsProperties` 读取各 profile 的 `cors.allowed-origins`
- `allowed-origins: "*"` 时自动切换为 `allowedOriginPatterns("*")`,兼容 `allowCredentials`
## 标题抓取
- `TitleFetcher`:静态工具类,支持 HTTP→HTTPS 重定向和宽松 SSL。
- `UtilsController``GET /utils/fetch-title?url=...` 代理端点。
## 运行环境
- 默认 profilelocal`application.yml``spring.profiles.active: local`)。
- 编译命令:`mvn clean compile -DskipTests`,需要 JDK 17。
## 配置文件
| Profile | 文件 |
|---------|------|
| local | `application-local.yml` |
| dev | `application-dev.yml` |
| uat | `application-uat.yml` |
| prod | `application-prod.yml` |
| 公共 | `application.yml` |
## AI 服务依赖
- 配置:`lpt.ai-service.url=http://localhost:5199`
- 超时:600s
- AI 服务不可用时自动降级到内置规则引擎(`BuiltinMindMapGenerator`
## 启动命令
```bash
mvn clean compile -DskipTests # 编译(需要 JDK 17
mvn spring-boot:run # 启动(默认 profile: local
```
## 关联项目
| 项目 | 路径 | 端口 | 说明 |
|------|------|------|------|
| lpt-fe | `../lpt-fe/` | 5158 | Vue 3 前端,通过 `/api` 代理调用本服务 |
| lpt-ai | `../lpt-ai/` | 5199 | AI 服务,本服务通过 HTTP 调用其异步任务接口 |
## 项目规范
## 数据库迁移(Flyway Migration ## 数据库迁移(Flyway Migration
-63
View File
@@ -1,63 +0,0 @@
# LPT 后端规范
## 技术栈
- Java 17, Spring Boot 3.2.5
- MyBatis-Plus 3.5.5 + MySQL
- MapStruct 1.5.5(编译期生成代码,DTO 增删字段后需重新编译)
- Sa-Token 1.38.0(认证/鉴权)
- Flyway(数据库迁移)
- Lombok@Data, @Slf4j
## 项目结构
```
controller/ → REST 接口,只做参数校验和路由
service/ → 业务逻辑
impl/
mapper/ → MyBatis-Plus BaseMapper
entity/ → 数据库实体(@TableName, @TableField
dto/ → 请求/响应 DTO
request/
response/
config/ → Spring 配置(WebMvcConfig, JacksonConfig 等)
utils/ → 工具类
common/ → GlobalExceptionHandler, Ops 等
mapStruct/ → MapStruct Converter 接口
```
## 响应规范
- 统一使用 `CommonResult<T>` 包装:`{ code, message, data }`
- 成功:`CommonResult.success(data)` → code=200
- 业务错误:`CommonResult.error(msg)` → code=400, HTTP 200
- 未登录:`GlobalExceptionHandler.handleNotLogin()` → HTTP 401 + code=401
- 参数校验失败走 `MethodArgumentNotValidException` → code=400
## 认证
- SaInterceptor 注册在 `WebMvcConfig`(无 @Profile 限制,所有环境生效)
- 拦截 `/**`,排除 `/login`
- `StpUtil.checkLogin()` 失败 → NotLoginException → GlobalExceptionHandler → 401
- Cookie 名 `satoken`,前端 axios 需 `withCredentials: true`
## 数据库变更
- Flyway 迁移文件:`src/main/resources/db/migration/V{日期}_{序号}__{描述}.sql`
- 文件名日期格式:`yyyyMMdd`
## DTO 转换
- MapStruct 编译期生成 `*ConvertImpl.java`target/generated-sources/
- 同名属性自动映射,`unmappedTargetPolicy = IGNORE`
- 如需自定义映射用 `@Mapping(source, target)`
- **增删 DTO 字段后必须重新编译**,否则 MapStruct 生成代码不含新字段
## CORS
-`CorsProperties` 读取各 profile 的 `cors.allowed-origins` 配置
- `allowed-origins: "*"` 时自动切换为 `allowedOriginPatterns("*")`(兼容 allowCredentials
- local profile`allow-credentials: true`, `allowed-origins: '*'`
## 标题抓取
- `TitleFetcher`:静态工具类,支持手动跟随 HTTP→HTTPS 重定向、宽松 SSL
- `UtilsController``GET /utils/fetch-title?url=...` 代理端点
## 运行
- 默认 profilelocalapplication.yml 中 `spring.profiles.active: local`
- IDEA JDK`C:/Users/cat-win/.jdks/ms-17.0.19`
- MavenIDEA 内置 `C:/Program Files/JetBrains/IntelliJ IDEA 2026.1.3/plugins/maven/lib/maven3/bin/mvn`
- 编译:`mvn clean compile -DskipTests`(须用 JDK 17
Vendored
-86
View File
@@ -1,86 +0,0 @@
pipeline {
agent none
environment {
IMAGE_NAME = "lpt-prod:${env.GIT_COMMIT?.take(8) ?: '0.0'}"
CONTAINER_NAME = 'LPT-prod'
CONTAINER_PORT = '8888'
}
stages {
stage('Build') {
agent {
docker {
image 'maven:3.9.6-eclipse-temurin-17'
args '-v /root/.m2:/root/.m2'
}
}
steps {
sh 'mvn -B -DskipTests clean package'
}
}
stage('Build Docker Image') {
agent any // 使用 Jenkins 默认节点(宿主机),前提是宿主有 docker 命令
steps {
sh 'docker build -t $IMAGE_NAME .'
}
}
stage('Run Docker Container') {
agent any
steps {
sh '''
docker network create traefik-public || true
docker rm -f $CONTAINER_NAME || true
docker run -d --name $CONTAINER_NAME \
--network traefik-public \
--restart=always \
-e SPRING_PROFILES_ACTIVE=prod \
--label "traefik.enable=true" \
--label "traefik.docker.network=traefik-public" \
--label 'traefik.http.routers.lpt-api.rule=Host(`lpt.cat-shark.xyz`) && PathPrefix(`/api`)' \
--label "traefik.http.routers.lpt-api.entrypoints=websecure" \
--label "traefik.http.routers.lpt-api.tls.certresolver=le" \
--label "traefik.http.routers.lpt-api.priority=100" \
--label "traefik.http.routers.lpt-api.service=lpt-api" \
--label "traefik.http.routers.lpt-api.middlewares=lpt-api-strip" \
--label "traefik.http.middlewares.lpt-api-strip.stripprefix.prefixes=/api" \
--label "traefik.http.services.lpt-api.loadbalancer.server.port=$CONTAINER_PORT" \
--log-driver=loki \
--log-opt loki-url="http://192.168.123.199:3100/loki/api/v1/push" \
$IMAGE_NAME
docker network connect mysql-prod_mysql-prod $CONTAINER_NAME || true
'''
}
}
stage('Check Docker Status') {
agent any
steps {
script {
def lastStatus = ''
timeout(time: 120, unit: 'SECONDS') {
waitUntil {
def status = sh(
script: "docker inspect -f '{{.State.Health.Status}}' $CONTAINER_NAME || echo 'unhealthy'",
returnStdout: true
).trim()
if (status != lastStatus) {
echo "Container health: ${status}"
lastStatus = status
}
return (status == 'healthy')
}
}
}
}
}
stage('清理旧镜像') {
agent any
steps {
sh 'docker image prune -af --filter "until=168h" || true'
}
}
}
}
-78
View File
@@ -1,78 +0,0 @@
pipeline {
agent none // 全局不指定,局部自己声明
environment {
IMAGE_NAME = 'lpt-dev:0.0'
CONTAINER_NAME = 'LPT-dev'
CONTAINER_PORT = '8888'
}
stages {
stage('Build') {
agent {
docker {
image 'maven:3.9.6-eclipse-temurin-17'
args '-v /root/.m2:/root/.m2'
}
}
steps {
sh 'mvn -B -DskipTests clean package'
}
}
stage('Build Docker Image') {
agent any // 使用 Jenkins 默认节点(宿主机),前提是宿主有 docker 命令
steps {
sh 'docker build -t $IMAGE_NAME .'
}
}
stage('Run Docker Container') {
agent any
steps {
sh '''
docker network create traefik-public || true
docker rm -f $CONTAINER_NAME || true
docker run -d --name $CONTAINER_NAME --network mysql_default \\
--restart=always \\
-e SPRING_PROFILES_ACTIVE=dev \\
--label "traefik.enable=true" \\
--label "traefik.docker.network=traefik-public" \\
--label 'traefik.http.routers.lpt-api-dev.rule=Host(`lpt-dev.cat-shark.xyz`) && PathPrefix(`/api`)' \\
--label "traefik.http.routers.lpt-api-dev.entrypoints=websecure" \\
--label "traefik.http.routers.lpt-api-dev.tls.certresolver=le" \\
--label "traefik.http.routers.lpt-api-dev.priority=100" \\
--label "traefik.http.routers.lpt-api-dev.service=lpt-api-dev" \\
--label "traefik.http.routers.lpt-api-dev.middlewares=lpt-api-dev-strip" \\
--label "traefik.http.middlewares.lpt-api-dev-strip.stripprefix.prefixes=/api" \\
--label "traefik.http.services.lpt-api-dev.loadbalancer.server.port=$CONTAINER_PORT" \\
--log-driver=loki \\
--log-opt loki-url="http://192.168.123.199:3100/loki/api/v1/push" \\
$IMAGE_NAME
docker network connect traefik-public $CONTAINER_NAME || true
'''
}
}
stage('Check Docker Status') {
agent any
steps {
script {
def lastStatus = ''
timeout(time: 60, unit: 'SECONDS') {
waitUntil {
def status = sh(
script: "docker inspect -f '{{.State.Health.Status}}' $CONTAINER_NAME || echo 'unhealthy'",
returnStdout: true
).trim()
if (status != lastStatus) {
echo "Container health: ${status}"
lastStatus = status
}
return (status == 'healthy')
}
}
}
}
}
}
}