Forráskód Böngészése

更新 .gitignore 文件,添加 Java、Flutter 和 Vue 的完整忽略规则

- 添加 Java/Maven/Gradle 相关忽略规则
- 完善 Flutter/Dart 相关忽略规则
- 添加 Vue/Node.js/npm/yarn 相关忽略规则
- 添加项目特定的忽略规则(数据库文件、敏感配置等)
- 清理 frontend 构建产物文件
何文松 1 hónapja
szülő
commit
deab4b595a
100 módosított fájl, 6133 hozzáadás és 6 törlés
  1. 295 6
      .gitignore
  2. 145 0
      backend/PROJECT_CHECK.md
  3. 275 0
      backend/README.md
  4. 68 0
      backend/ai-service/pom.xml
  5. 18 0
      backend/ai-service/src/main/java/com/lingyue/ai/AIServiceApplication.java
  6. 24 0
      backend/ai-service/src/main/java/com/lingyue/ai/config/MyBatisPlusConfig.java
  7. 41 0
      backend/ai-service/src/main/java/com/lingyue/ai/controller/AIController.java
  8. 56 0
      backend/ai-service/src/main/java/com/lingyue/ai/entity/Annotation.java
  9. 60 0
      backend/ai-service/src/main/java/com/lingyue/ai/entity/Element.java
  10. 13 0
      backend/ai-service/src/main/java/com/lingyue/ai/repository/AnnotationRepository.java
  11. 13 0
      backend/ai-service/src/main/java/com/lingyue/ai/repository/ElementRepository.java
  12. 19 0
      backend/ai-service/src/main/java/com/lingyue/ai/service/AIService.java
  13. 60 0
      backend/ai-service/src/main/resources/application.yml
  14. 86 0
      backend/auth-service/pom.xml
  15. 18 0
      backend/auth-service/src/main/java/com/lingyue/auth/AuthServiceApplication.java
  16. 27 0
      backend/auth-service/src/main/java/com/lingyue/auth/config/MyBatisPlusConfig.java
  17. 33 0
      backend/auth-service/src/main/java/com/lingyue/auth/config/RedisConfig.java
  18. 65 0
      backend/auth-service/src/main/java/com/lingyue/auth/config/SecurityConfig.java
  19. 91 0
      backend/auth-service/src/main/java/com/lingyue/auth/controller/AuthController.java
  20. 46 0
      backend/auth-service/src/main/java/com/lingyue/auth/dto/AuthResponse.java
  21. 18 0
      backend/auth-service/src/main/java/com/lingyue/auth/dto/LoginRequest.java
  22. 32 0
      backend/auth-service/src/main/java/com/lingyue/auth/dto/RegisterRequest.java
  23. 48 0
      backend/auth-service/src/main/java/com/lingyue/auth/entity/Session.java
  24. 50 0
      backend/auth-service/src/main/java/com/lingyue/auth/entity/User.java
  25. 48 0
      backend/auth-service/src/main/java/com/lingyue/auth/repository/SessionRepository.java
  26. 13 0
      backend/auth-service/src/main/java/com/lingyue/auth/repository/UserRepository.java
  27. 229 0
      backend/auth-service/src/main/java/com/lingyue/auth/service/AuthService.java
  28. 71 0
      backend/auth-service/src/main/java/com/lingyue/auth/service/TokenService.java
  29. 43 0
      backend/auth-service/src/main/java/com/lingyue/auth/service/UserService.java
  30. 24 0
      backend/auth-service/src/main/resources/application-dev.yml
  31. 25 0
      backend/auth-service/src/main/resources/application-prod.yml
  32. 80 0
      backend/auth-service/src/main/resources/application.yml
  33. 102 0
      backend/common/pom.xml
  34. 94 0
      backend/common/src/main/java/com/lingyue/common/constant/HttpStatus.java
  35. 210 0
      backend/common/src/main/java/com/lingyue/common/domain/AjaxResult.java
  36. 27 0
      backend/common/src/main/java/com/lingyue/common/domain/entity/AssignUuidModel.java
  37. 48 0
      backend/common/src/main/java/com/lingyue/common/domain/entity/CreationModel.java
  38. 56 0
      backend/common/src/main/java/com/lingyue/common/domain/entity/SimpleModel.java
  39. 43 0
      backend/common/src/main/java/com/lingyue/common/dto/PageRequest.java
  40. 52 0
      backend/common/src/main/java/com/lingyue/common/dto/PageResponse.java
  41. 37 0
      backend/common/src/main/java/com/lingyue/common/exception/BusinessException.java
  42. 147 0
      backend/common/src/main/java/com/lingyue/common/exception/GlobalExceptionHandler.java
  43. 64 0
      backend/common/src/main/java/com/lingyue/common/exception/ServiceException.java
  44. 131 0
      backend/common/src/main/java/com/lingyue/common/util/JwtUtil.java
  45. 27 0
      backend/common/src/main/java/com/lingyue/common/util/PasswordUtil.java
  46. 55 0
      backend/common/src/main/java/com/lingyue/common/utils/StringUtils.java
  47. 68 0
      backend/document-service/pom.xml
  48. 18 0
      backend/document-service/src/main/java/com/lingyue/document/DocumentServiceApplication.java
  49. 24 0
      backend/document-service/src/main/java/com/lingyue/document/config/MyBatisPlusConfig.java
  50. 53 0
      backend/document-service/src/main/java/com/lingyue/document/controller/DocumentController.java
  51. 76 0
      backend/document-service/src/main/java/com/lingyue/document/entity/Document.java
  52. 13 0
      backend/document-service/src/main/java/com/lingyue/document/repository/DocumentRepository.java
  53. 58 0
      backend/document-service/src/main/java/com/lingyue/document/service/DocumentService.java
  54. 57 0
      backend/document-service/src/main/resources/application.yml
  55. 68 0
      backend/gateway-service/pom.xml
  56. 18 0
      backend/gateway-service/src/main/java/com/lingyue/gateway/GatewayApplication.java
  57. 43 0
      backend/gateway-service/src/main/java/com/lingyue/gateway/config/CorsConfig.java
  58. 110 0
      backend/gateway-service/src/main/java/com/lingyue/gateway/filter/JwtAuthenticationFilter.java
  59. 11 0
      backend/gateway-service/src/main/resources/application-dev.yml
  60. 14 0
      backend/gateway-service/src/main/resources/application-prod.yml
  61. 88 0
      backend/gateway-service/src/main/resources/application.yml
  62. 68 0
      backend/graph-service/pom.xml
  63. 18 0
      backend/graph-service/src/main/java/com/lingyue/graph/GraphServiceApplication.java
  64. 24 0
      backend/graph-service/src/main/java/com/lingyue/graph/config/MyBatisPlusConfig.java
  65. 44 0
      backend/graph-service/src/main/java/com/lingyue/graph/controller/GraphController.java
  66. 50 0
      backend/graph-service/src/main/java/com/lingyue/graph/entity/Graph.java
  67. 13 0
      backend/graph-service/src/main/java/com/lingyue/graph/repository/GraphRepository.java
  68. 47 0
      backend/graph-service/src/main/java/com/lingyue/graph/service/GraphService.java
  69. 54 0
      backend/graph-service/src/main/resources/application.yml
  70. 153 0
      backend/lingyue-starter/README.md
  71. 142 0
      backend/lingyue-starter/pom.xml
  72. 31 0
      backend/lingyue-starter/src/main/java/com/lingyue/LingyueApplication.java
  73. 17 0
      backend/lingyue-starter/src/main/java/com/lingyue/LingyueServletInitializer.java
  74. 47 0
      backend/lingyue-starter/src/main/java/com/lingyue/config/AppConfig.java
  75. 35 0
      backend/lingyue-starter/src/main/java/com/lingyue/config/RequestParameterLoggingConfig.java
  76. 109 0
      backend/lingyue-starter/src/main/java/com/lingyue/config/SpringDocConfig.java
  77. 33 0
      backend/lingyue-starter/src/main/java/com/lingyue/config/interceptor/RequestParameterLoggingInterceptor.java
  78. 32 0
      backend/lingyue-starter/src/main/resources/application-dev.yml
  79. 33 0
      backend/lingyue-starter/src/main/resources/application-prod.yml
  80. 193 0
      backend/lingyue-starter/src/main/resources/application.yml
  81. 10 0
      backend/lingyue-starter/src/main/resources/banner.txt
  82. 8 0
      backend/lingyue-starter/src/main/resources/i18n/messages.properties
  83. 77 0
      backend/lingyue-starter/src/main/resources/logback-spring.xml
  84. 20 0
      backend/lingyue-starter/src/main/resources/mybatis/mybatis-config.xml
  85. 62 0
      backend/notification-service/pom.xml
  86. 18 0
      backend/notification-service/src/main/java/com/lingyue/notification/NotificationServiceApplication.java
  87. 32 0
      backend/notification-service/src/main/java/com/lingyue/notification/config/WebSocketConfig.java
  88. 30 0
      backend/notification-service/src/main/java/com/lingyue/notification/controller/NotificationController.java
  89. 21 0
      backend/notification-service/src/main/resources/application.yml
  90. 80 0
      backend/parse-service/pom.xml
  91. 20 0
      backend/parse-service/src/main/java/com/lingyue/parse/ParseServiceApplication.java
  92. 24 0
      backend/parse-service/src/main/java/com/lingyue/parse/config/MyBatisPlusConfig.java
  93. 35 0
      backend/parse-service/src/main/java/com/lingyue/parse/controller/ParseController.java
  94. 52 0
      backend/parse-service/src/main/java/com/lingyue/parse/entity/ParseTask.java
  95. 20 0
      backend/parse-service/src/main/java/com/lingyue/parse/repository/ParseTaskRepository.java
  96. 49 0
      backend/parse-service/src/main/java/com/lingyue/parse/service/ParseService.java
  97. 66 0
      backend/parse-service/src/main/resources/application.yml
  98. 217 0
      backend/pom.xml
  99. 203 0
      backend/sql/init.sql
  100. BIN
      frontend/build/79dfa80770ef31b4122a7e6609b22b5c.cache.dill.track.dill

+ 295 - 6
.gitignore

@@ -3,6 +3,8 @@
 *.log
 *.pyc
 *.swp
+*.swo
+*~
 .DS_Store
 .atom/
 .buildlog/
@@ -10,16 +12,91 @@
 .svn/
 migrate_working_dir/
 
-# IntelliJ related
+# ============================================
+# Java / Maven / Gradle
+# ============================================
+
+# Compiled class files
+*.class
+
+# Log files
+*.log
+
+# Package Files
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# Maven
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+.mvn/wrapper/maven-wrapper.jar
+
+# Gradle
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+gradle-app.setting
+!gradle-wrapper.jar
+.gradletasknamecache
+
+# Eclipse
+.classpath
+.project
+.settings/
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.loadpath
+.recommenders
+
+# IntelliJ IDEA
 *.iml
 *.ipr
 *.iws
 .idea/
+out/
+*.iws
+*.iml
+*.ipr
+
+# NetBeans
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+# VS Code
+.vscode/
+*.code-workspace
+
+# Spring Boot
+spring-boot-*.log
+application-*.properties
+!application.yml
+!application.properties
 
-# The .vscode folder contains launch configuration and tasks you configure in
-# VS Code which you may wish to be included in version control, so this line
-# is commented out by default.
-#.vscode/
+# ============================================
+# Flutter / Dart
+# ============================================
 
 # Flutter/Dart/Pub related
 **/doc/api/
@@ -30,7 +107,11 @@ migrate_working_dir/
 .packages
 .pub-cache/
 .pub/
-/build/
+build/
+*.dart.js
+*.js_
+*.js.deps
+*.js.map
 
 # Symbolication related
 app.*.symbols
@@ -43,3 +124,211 @@ app.*.map.json
 /android/app/profile
 /android/app/release
 
+# iOS related
+**/ios/**/*.mode1v3
+**/ios/**/*.mode2v3
+**/ios/**/*.moved-aside
+**/ios/**/*.pbxuser
+**/ios/**/*.perspectivev3
+**/ios/**/*sync/
+**/ios/**/.sconsign.dblite
+**/ios/**/.tags*
+**/ios/**/.vagrant/
+**/ios/**/DerivedData/
+**/ios/**/Icon?
+**/ios/**/Pods/
+**/ios/**/.symlinks/
+**/ios/**/profile
+**/ios/**/xcuserdata
+**/ios/.generated/
+**/ios/Flutter/.last_build_id
+**/ios/Flutter/App.framework
+**/ios/Flutter/Flutter.framework
+**/ios/Flutter/Flutter.podspec
+**/ios/Flutter/Generated.xcconfig
+**/ios/Flutter/ephemeral
+**/ios/Flutter/app.flx
+**/ios/Flutter/app.zip
+**/ios/Flutter/flutter_assets/
+**/ios/Flutter/flutter_export_environment.sh
+**/ios/ServiceDefinitions.json
+**/ios/Runner/GeneratedPluginRegistrant.*
+
+# Web related
+**/web/**/main.dart.js
+**/web/**/main.dart.js.map
+**/web/**/flutter_service_worker.js
+**/web/**/flutter_bootstrap.js
+
+# Coverage
+coverage/
+*.lcov
+test/.test_coverage.dart
+
+# Exceptions to above rules.
+!**/ios/**/default.mode1v3
+!**/ios/**/default.mode2v3
+!**/ios/**/default.pbxuser
+!**/ios/**/default.perspectivev3
+
+# ============================================
+# Vue / Node.js / npm / yarn
+# ============================================
+
+# Dependencies
+node_modules/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage/
+*.lcov
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+# Vue specific
+*.local
+.DS_Store
+dist/
+dist-ssr/
+*.local
+
+# Vite
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*
+
+# ============================================
+# Project specific
+# ============================================
+
+# Project docs (temporary)
+a_docs/
+
+# Database
+*.db
+*.sqlite
+*.sqlite3
+
+# Configuration files with sensitive data
+application-local.yml
+application-local.properties
+*.secret
+*.key
+*.pem
+
+# Temporary files
+*.tmp
+*.temp
+*.swp
+*.swo
+*~

+ 145 - 0
backend/PROJECT_CHECK.md

@@ -0,0 +1,145 @@
+# 项目检查报告
+
+## 检查时间
+2024年
+
+## 项目状态概览
+
+### ✅ 已完成
+1. **项目结构**: 所有微服务模块已创建
+2. **依赖管理**: 父POM已配置所有依赖版本
+3. **公共模块**: common模块包含统一响应、异常处理、工具类
+4. **实体基类**: AssignUuidModel、CreationModel、SimpleModel已创建
+5. **Starter模块**: lingyue-starter单体应用启动器已创建
+6. **配置文件**: 各服务的application.yml已配置
+
+### ⚠️ 需要修复的问题
+
+#### 1. MyBatis Plus 依赖问题(高优先级)
+**问题**: 多个服务的MyBatisPlusConfig无法解析`PaginationInnerInterceptor`
+- `auth-service/src/main/java/com/lingyue/auth/config/MyBatisPlusConfig.java`
+- `document-service/src/main/java/com/lingyue/document/config/MyBatisPlusConfig.java`
+- `parse-service/src/main/java/com/lingyue/parse/config/MyBatisPlusConfig.java`
+- `ai-service/src/main/java/com/lingyue/ai/config/MyBatisPlusConfig.java`
+- `graph-service/src/main/java/com/lingyue/graph/config/MyBatisPlusConfig.java`
+
+**原因**: 
+- MyBatis Plus 3.5.11版本应该包含此类
+- 可能是IDE索引问题,需要重新编译项目
+
+**解决方案**:
+```bash
+cd backend
+mvn clean install -DskipTests
+```
+
+#### 2. 代码警告(低优先级)
+- 未使用的import语句(可自动清理)
+- 未使用的字段(Controller中的service字段,待实现功能时使用)
+- 泛型警告(AjaxResult的raw type使用)
+
+#### 3. 依赖版本问题(已修复)
+- ✅ lingyue-starter的pom.xml中已添加版本号
+- ✅ common模块已添加MyBatis Plus、Spring Security依赖
+
+## 模块依赖关系
+
+```
+lingyue-zhibao (父POM)
+├── common (公共模块)
+│   ├── MyBatis Plus (实体注解)
+│   ├── Spring Security (异常处理)
+│   └── JWT工具类
+├── auth-service
+│   ├── common
+│   └── MyBatis Plus
+├── document-service
+│   ├── common
+│   └── MyBatis Plus
+├── parse-service
+│   ├── common
+│   └── MyBatis Plus
+├── ai-service
+│   ├── common
+│   └── MyBatis Plus
+├── graph-service
+│   ├── common
+│   └── MyBatis Plus
+├── notification-service
+│   └── common
+└── lingyue-starter (单体应用)
+    ├── common
+    ├── auth-service
+    ├── document-service
+    ├── parse-service
+    ├── ai-service
+    ├── graph-service
+    └── notification-service
+```
+
+## 编译建议
+
+### 1. 清理并重新编译
+```bash
+cd backend
+mvn clean install -DskipTests
+```
+
+### 2. 如果MyBatis Plus问题仍然存在
+检查MyBatis Plus版本:
+```bash
+mvn dependency:tree | grep mybatis-plus
+```
+
+### 3. IDE刷新
+- IntelliJ IDEA: File -> Invalidate Caches / Restart
+- Eclipse: Project -> Clean
+
+## 待实现功能
+
+### 高优先级(P0)
+1. **文档服务**: 文件上传、OSS集成
+2. **解析服务**: PaddleOCR集成
+3. **AI服务**: DeepSeek API集成
+4. **关系网络服务**: 图计算逻辑
+
+### 中优先级(P1)
+1. WebSocket实时通知
+2. 文档预览功能
+3. 批注功能
+
+### 低优先级(P2)
+1. 全文搜索
+2. 批量处理
+3. 数据统计
+
+## 配置检查清单
+
+- [x] 父POM依赖版本管理
+- [x] 各服务pom.xml依赖配置
+- [x] application.yml配置文件
+- [x] MyBatis Plus配置类
+- [x] 全局异常处理器
+- [x] 统一响应格式(AjaxResult)
+- [x] 实体基类
+- [x] JWT工具类
+- [x] 密码工具类
+- [x] Starter模块配置
+- [ ] 数据库初始化脚本
+- [ ] 单元测试
+- [ ] 集成测试
+
+## 下一步行动
+
+1. **立即执行**: 运行`mvn clean install`重新编译项目
+2. **验证**: 检查MyBatis Plus依赖是否正确解析
+3. **开发**: 开始实现P0优先级功能
+4. **测试**: 编写单元测试和集成测试
+
+## 注意事项
+
+1. **JWT密钥**: 生产环境必须修改默认密钥
+2. **数据库密码**: 生产环境必须使用强密码
+3. **Nacos配置**: 单体应用模式下可禁用服务发现
+4. **日志级别**: 生产环境建议使用WARN级别
+

+ 275 - 0
backend/README.md

@@ -0,0 +1,275 @@
+# 灵越智报 v2.0 后端服务
+
+## 项目概述
+
+灵越智报是一个智能文档处理平台,通过 OCR 识别、版面理解、要素抽取和 AI 处理,提升文档处理效率。
+
+## 技术栈
+
+- **框架**: Spring Boot 3.2.x, Spring Cloud 2022.0.x
+- **服务注册与配置**: Nacos 2.3.x
+- **API 网关**: Spring Cloud Gateway
+- **数据库**: PostgreSQL 15+
+- **数据访问**: MyBatis Plus 3.5.11
+- **连接池**: Druid 1.2.23
+- **缓存**: Redis 7+
+- **消息队列**: RabbitMQ
+- **认证**: JWT (jjwt 0.11.5)
+- **工具类**: Hutool 5.8.35, FastJSON2 2.0.53
+- **Java版本**: 17+
+
+## 项目结构
+
+```
+backend/
+├── pom.xml                          # 父 POM
+├── common/                           # 公共模块
+│   ├── pom.xml
+│   └── src/main/java/com/lingyue/common/
+│       ├── config/                  # 公共配置
+│       ├── dto/                     # 数据传输对象
+│       ├── exception/               # 异常处理
+│       ├── response/                # 统一响应
+│       └── util/                    # 工具类
+├── gateway-service/                  # API 网关服务
+├── auth-service/                     # 用户认证服务
+├── document-service/                # 文档管理服务
+├── parse-service/                    # 解析服务
+├── ai-service/                       # AI 处理服务
+├── graph-service/                    # 关系网络服务
+├── notification-service/             # 通知服务
+└── sql/                              # 数据库脚本
+    └── init.sql
+```
+
+## 服务说明
+
+### 1. gateway-service (端口: 8080)
+API 网关服务,负责路由转发、JWT 认证、CORS 配置等。
+
+### 2. auth-service (端口: 8001)
+用户认证服务,提供注册、登录、Token 管理等功能。
+
+### 3. document-service (端口: 8002)
+文档管理服务,负责文档上传、存储、管理等功能。
+
+### 4. parse-service (端口: 8003)
+解析服务,负责 OCR 识别、文本提取、版面分析等功能。
+
+### 5. ai-service (端口: 8004)
+AI 处理服务,负责要素提取、文本处理、Prompt 生成等功能。
+
+### 6. graph-service (端口: 8005)
+关系网络服务,负责关系网络构建、逻辑计算等功能。
+
+### 7. notification-service (端口: 8006)
+通知服务,提供 WebSocket 实时通信功能。
+
+## 环境要求
+
+- JDK 17+
+- Maven 3.6+
+- PostgreSQL 15+
+- Redis 7+
+- RabbitMQ 3.12+
+- Nacos 2.3+
+
+## 快速开始
+
+### 1. 数据库初始化
+
+```bash
+# 创建数据库
+createdb lingyue_zhibao
+
+# 执行初始化脚本
+psql -U postgres -d lingyue_zhibao -f sql/init.sql
+```
+
+### 2. 启动基础设施
+
+```bash
+# 启动 Nacos(需要单独安装)
+# 启动 Redis
+redis-server
+
+# 启动 RabbitMQ(需要单独安装)
+rabbitmq-server
+```
+
+### 3. 配置环境变量
+
+各服务的配置文件位于 `{service}/src/main/resources/application.yml`,可根据实际情况修改:
+
+- 数据库连接信息
+- Redis 连接信息
+- RabbitMQ 连接信息
+- Nacos 服务地址
+- JWT 密钥(生产环境必须修改)
+
+### 4. 编译项目
+
+```bash
+cd backend
+mvn clean install
+```
+
+### 5. 启动服务
+
+按以下顺序启动服务:
+
+```bash
+# 1. 启动认证服务
+cd auth-service
+mvn spring-boot:run
+
+# 2. 启动其他微服务(可并行启动)
+cd document-service && mvn spring-boot:run
+cd parse-service && mvn spring-boot:run
+cd ai-service && mvn spring-boot:run
+cd graph-service && mvn spring-boot:run
+cd notification-service && mvn spring-boot:run
+
+# 3. 最后启动网关服务
+cd gateway-service
+mvn spring-boot:run
+```
+
+## API 接口
+
+### 认证接口
+
+- `POST /api/v1/auth/register` - 用户注册
+- `POST /api/v1/auth/login` - 用户登录
+- `POST /api/v1/auth/logout` - 用户登出
+- `POST /api/v1/auth/refresh` - 刷新 Token
+
+### 文档接口
+
+- `GET /api/v1/documents` - 获取文档列表
+- `GET /api/v1/documents/{id}` - 获取文档详情
+- `POST /api/v1/documents` - 上传文档
+- `DELETE /api/v1/documents/{id}` - 删除文档
+
+### 解析接口
+
+- `POST /api/v1/parse/start` - 启动解析
+- `GET /api/v1/parse/status/{documentId}` - 查询解析状态
+
+### AI 接口
+
+- `POST /api/v1/ai/extract-elements` - 提取要素
+- `POST /api/v1/ai/polish-text` - 文本润色
+- `POST /api/v1/ai/generate-prompt` - 生成 Prompt
+
+### 关系网络接口
+
+- `POST /api/v1/graphs` - 创建关系网络
+- `GET /api/v1/graphs/{id}` - 获取关系网络
+- `POST /api/v1/graphs/{id}/calculate` - 计算关系网络
+
+### WebSocket
+
+- `ws://localhost:8080/ws` - WebSocket 连接端点
+
+## 开发说明
+
+### 统一响应格式
+
+所有接口返回统一的响应格式(AjaxResult):
+
+```json
+{
+  "code": 200,
+  "msg": "操作成功",
+  "data": { ... }
+}
+```
+
+响应码说明:
+- `200`: 操作成功
+- `400`: 参数列表错误
+- `401`: 未授权
+- `403`: 访问受限
+- `404`: 资源未找到
+- `500`: 系统内部错误
+- `601`: 系统警告消息
+
+### JWT 认证
+
+- 请求头格式: `Authorization: Bearer {token}`
+- Token 有效期: 7 天
+- 刷新 Token 有效期: 30 天
+
+### 异常处理
+
+使用 `ServiceException` 抛出业务异常,全局异常处理器会自动转换为统一响应格式。
+
+### 数据库实体
+
+所有实体类使用 MyBatis Plus 注解,继承 `SimpleModel` 基类,支持自动创建和更新 `create_time`、`update_time` 字段。
+
+实体基类:
+- `AssignUuidModel`: UUID主键模型基类
+- `CreationModel`: 创建信息模型基类(包含创建者、创建时间)
+- `SimpleModel`: 简单模型基类(包含创建和更新信息)
+
+## 配置说明
+
+### 开发环境
+
+使用 `application-dev.yml` 配置文件,包含本地开发环境的配置。
+
+### 生产环境
+
+使用 `application-prod.yml` 配置文件,通过环境变量配置敏感信息:
+
+```bash
+export DB_USERNAME=postgres
+export DB_PASSWORD=your_password
+export JWT_SECRET=your_jwt_secret
+export NACOS_SERVER_ADDR=nacos-server:8848
+```
+
+### MyBatis Plus 配置
+
+每个服务都包含 `MyBatisPlusConfig` 配置类,配置了:
+- Mapper扫描路径
+- 分页插件(PostgreSQL)
+- ID生成策略(ASSIGN_UUID)
+
+### Druid 连接池配置
+
+所有服务使用 Druid 连接池,配置了:
+- 连接池大小(初始5,最小5,最大20)
+- 连接验证
+- SQL监控(stat, wall, slf4j)
+
+## 待实现功能
+
+当前版本为基础框架,以下功能待实现:
+
+1. **文档服务**: 文件上传、OSS 集成、文档预览
+2. **解析服务**: PaddleOCR 集成、文本提取、版面分析
+3. **AI 服务**: DeepSeek API 集成、要素提取、文本处理
+4. **关系网络服务**: 关系网络计算、逻辑运算
+5. **通知服务**: WebSocket 消息推送、解析进度通知
+
+## 注意事项
+
+1. **JWT 密钥**: 生产环境必须修改默认的 JWT 密钥
+2. **数据库密码**: 生产环境必须使用强密码
+3. **CORS 配置**: 生产环境应配置具体的允许域名
+4. **日志级别**: 生产环境建议使用 WARN 级别
+
+## 参考文档
+
+- [设计文档](../DESIGN_DOCUMENT.md)
+- [Spring Cloud 文档](https://spring.io/projects/spring-cloud)
+- [Nacos 文档](https://nacos.io/docs/latest/what-is-nacos/)
+- [PostgreSQL 文档](https://www.postgresql.org/docs/)
+
+## 许可证
+
+详见 [LICENSE](../LICENSE) 文件。
+

+ 68 - 0
backend/ai-service/pom.xml

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.lingyue</groupId>
+        <artifactId>lingyue-zhibao</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>ai-service</artifactId>
+    <packaging>jar</packaging>
+
+    <name>AI Service</name>
+    <description>AI处理服务 - 要素提取、文本处理、Prompt生成</description>
+
+    <dependencies>
+        <!-- Spring Boot Web -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        
+        <!-- MyBatis Plus -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+        
+        <!-- PostgreSQL Driver -->
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+        </dependency>
+        
+        <!-- Nacos Service Discovery -->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+        </dependency>
+        
+        <!-- Common Module -->
+        <dependency>
+            <groupId>com.lingyue</groupId>
+            <artifactId>common</artifactId>
+        </dependency>
+        
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
+

+ 18 - 0
backend/ai-service/src/main/java/com/lingyue/ai/AIServiceApplication.java

@@ -0,0 +1,18 @@
+package com.lingyue.ai;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+/**
+ * AI处理服务启动类
+ */
+@SpringBootApplication
+@EnableDiscoveryClient
+public class AIServiceApplication {
+    
+    public static void main(String[] args) {
+        SpringApplication.run(AIServiceApplication.class, args);
+    }
+}
+

+ 24 - 0
backend/ai-service/src/main/java/com/lingyue/ai/config/MyBatisPlusConfig.java

@@ -0,0 +1,24 @@
+package com.lingyue.ai.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * MyBatis Plus 配置
+ */
+@Configuration
+@MapperScan("com.lingyue.ai.repository")
+public class MyBatisPlusConfig {
+    
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
+        return interceptor;
+    }
+}
+

+ 41 - 0
backend/ai-service/src/main/java/com/lingyue/ai/controller/AIController.java

@@ -0,0 +1,41 @@
+package com.lingyue.ai.controller;
+
+import com.lingyue.common.domain.AjaxResult;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * AI控制器(基础框架)
+ */
+@RestController
+@RequestMapping("/ai")
+@RequiredArgsConstructor
+public class AIController {
+    
+    /**
+     * 提取要素(待实现)
+     */
+    @PostMapping("/extract-elements")
+    public AjaxResult<?> extractElements() {
+        // TODO: 实现要素提取
+        return AjaxResult.success("要素提取接口待实现");
+    }
+    
+    /**
+     * 文本润色(待实现)
+     */
+    @PostMapping("/polish-text")
+    public AjaxResult<?> polishText() {
+        // TODO: 实现文本润色
+        return AjaxResult.success("文本润色接口待实现");
+    }
+    
+    /**
+     * 生成Prompt(待实现)
+     */
+    @PostMapping("/generate-prompt")
+    public AjaxResult<?> generatePrompt() {
+        // TODO: 实现Prompt生成
+        return AjaxResult.success("Prompt生成接口待实现");
+    }
+}

+ 56 - 0
backend/ai-service/src/main/java/com/lingyue/ai/entity/Annotation.java

@@ -0,0 +1,56 @@
+package com.lingyue.ai.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.lingyue.common.domain.entity.SimpleModel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+
+/**
+ * 批注实体
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@TableName("annotations")
+@Schema(description = "批注实体")
+public class Annotation extends SimpleModel {
+    
+    @Schema(description = "文档ID")
+    @TableField("document_id")
+    private String documentId;
+    
+    @Schema(description = "用户ID")
+    @TableField("user_id")
+    private String userId;
+    
+    @Schema(description = "文本")
+    @TableField("text")
+    private String text;
+    
+    @Schema(description = "位置")
+    @TableField(value = "position", typeHandler = com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler.class)
+    private Object position; // {page, start: {x, y}, end: {x, y}}
+    
+    @Schema(description = "类型")
+    @TableField("type")
+    private String type; // highlight/strikethrough/suggestion
+    
+    @Schema(description = "建议")
+    @TableField("suggestion")
+    private String suggestion;
+    
+    @Schema(description = "AI生成")
+    @TableField("ai_generated")
+    private Boolean aiGenerated = false;
+    
+    @Schema(description = "置信度")
+    @TableField("confidence")
+    private BigDecimal confidence;
+    
+    @Schema(description = "状态")
+    @TableField("status")
+    private String status = "pending"; // pending/accepted/rejected
+}

+ 60 - 0
backend/ai-service/src/main/java/com/lingyue/ai/entity/Element.java

@@ -0,0 +1,60 @@
+package com.lingyue.ai.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.lingyue.common.domain.entity.SimpleModel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+
+/**
+ * 要素实体
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@TableName("elements")
+@Schema(description = "要素实体")
+public class Element extends SimpleModel {
+    
+    @Schema(description = "文档ID")
+    @TableField("document_id")
+    private String documentId;
+    
+    @Schema(description = "用户ID")
+    @TableField("user_id")
+    private String userId;
+    
+    @Schema(description = "类型")
+    @TableField("type")
+    private String type; // amount/company/person/location/date/other
+    
+    @Schema(description = "标签")
+    @TableField("label")
+    private String label;
+    
+    @Schema(description = "值")
+    @TableField("value")
+    private String value;
+    
+    @Schema(description = "位置")
+    @TableField(value = "position", typeHandler = com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler.class)
+    private Object position; // {page, x, y, width, height}
+    
+    @Schema(description = "置信度")
+    @TableField("confidence")
+    private BigDecimal confidence; // 0.00-1.00
+    
+    @Schema(description = "提取方法")
+    @TableField("extraction_method")
+    private String extractionMethod; // ai/regex/rule/manual
+    
+    @Schema(description = "图节点ID")
+    @TableField("graph_node_id")
+    private String graphNodeId;
+    
+    @Schema(description = "元数据")
+    @TableField(value = "metadata", typeHandler = com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler.class)
+    private Object metadata;
+}

+ 13 - 0
backend/ai-service/src/main/java/com/lingyue/ai/repository/AnnotationRepository.java

@@ -0,0 +1,13 @@
+package com.lingyue.ai.repository;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.lingyue.ai.entity.Annotation;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 批注Repository
+ */
+@Mapper
+public interface AnnotationRepository extends BaseMapper<Annotation> {
+    
+}

+ 13 - 0
backend/ai-service/src/main/java/com/lingyue/ai/repository/ElementRepository.java

@@ -0,0 +1,13 @@
+package com.lingyue.ai.repository;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.lingyue.ai.entity.Element;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 要素Repository
+ */
+@Mapper
+public interface ElementRepository extends BaseMapper<Element> {
+    
+}

+ 19 - 0
backend/ai-service/src/main/java/com/lingyue/ai/service/AIService.java

@@ -0,0 +1,19 @@
+package com.lingyue.ai.service;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * AI服务(基础框架)
+ */
+@Service
+@RequiredArgsConstructor
+public class AIService {
+    
+    // TODO: 实现DeepSeek API客户端
+    // TODO: 实现要素提取逻辑
+    // TODO: 实现文本润色逻辑
+    // TODO: 实现错别字修正逻辑
+    // TODO: 实现Prompt生成逻辑
+}
+

+ 60 - 0
backend/ai-service/src/main/resources/application.yml

@@ -0,0 +1,60 @@
+server:
+  port: 8004
+
+spring:
+  application:
+    name: ai-service
+  
+  # 数据库配置(Druid)
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    druid:
+      driver-class-name: org.postgresql.Driver
+      url: jdbc:postgresql://localhost:5432/lingyue_zhibao
+      username: ${DB_USERNAME:postgres}
+      password: ${DB_PASSWORD:postgres}
+      initial-size: 5
+      min-idle: 5
+      max-active: 20
+      max-wait: 60000
+      time-between-eviction-runs-millis: 60000
+      min-evictable-idle-time-millis: 300000
+      validation-query: SELECT 1
+      test-while-idle: true
+      test-on-borrow: false
+      test-on-return: false
+      pool-prepared-statements: true
+      max-pool-prepared-statement-per-connection-size: 20
+      filters: stat,wall,slf4j
+  
+  # MyBatis Plus配置
+  mybatis-plus:
+    mapper-locations: classpath*:mapper/**/*Mapper.xml
+    type-aliases-package: com.lingyue.ai.entity
+    configuration:
+      map-underscore-to-camel-case: true
+      log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
+    global-config:
+      db-config:
+        id-type: assign_uuid
+  
+  # Nacos配置
+  cloud:
+    nacos:
+      discovery:
+        server-addr: ${NACOS_SERVER_ADDR:localhost:8848}
+        namespace: public
+        group: DEFAULT_GROUP
+
+# DeepSeek API配置
+deepseek:
+  api-url: ${DEEPSEEK_API_URL:https://api.deepseek.com}
+  api-key: ${DEEPSEEK_API_KEY:}
+  timeout: 30000
+
+# 日志配置
+logging:
+  level:
+    root: INFO
+    com.lingyue: DEBUG
+

+ 86 - 0
backend/auth-service/pom.xml

@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.lingyue</groupId>
+        <artifactId>lingyue-zhibao</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>auth-service</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Auth Service</name>
+    <description>用户认证服务 - 注册、登录、Token管理</description>
+
+    <dependencies>
+        <!-- Spring Boot Web -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        
+        <!-- MyBatis Plus -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+        
+        <!-- Spring Boot Security -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+        
+        <!-- Spring Security Web (for AccessDeniedException) -->
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-web</artifactId>
+        </dependency>
+        
+        <!-- Spring Boot Data Redis -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        
+        <!-- PostgreSQL Driver -->
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+        </dependency>
+        
+        <!-- Nacos Service Discovery -->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+        </dependency>
+        
+        <!-- Common Module -->
+        <dependency>
+            <groupId>com.lingyue</groupId>
+            <artifactId>common</artifactId>
+        </dependency>
+        
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
+

+ 18 - 0
backend/auth-service/src/main/java/com/lingyue/auth/AuthServiceApplication.java

@@ -0,0 +1,18 @@
+package com.lingyue.auth;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+/**
+ * 用户认证服务启动类
+ */
+@SpringBootApplication
+@EnableDiscoveryClient
+public class AuthServiceApplication {
+    
+    public static void main(String[] args) {
+        SpringApplication.run(AuthServiceApplication.class, args);
+    }
+}
+

+ 27 - 0
backend/auth-service/src/main/java/com/lingyue/auth/config/MyBatisPlusConfig.java

@@ -0,0 +1,27 @@
+package com.lingyue.auth.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * MyBatis Plus 配置
+ */
+@Configuration
+@MapperScan("com.lingyue.auth.repository")
+public class MyBatisPlusConfig {
+    
+    /**
+     * 分页插件
+     */
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
+        return interceptor;
+    }
+}
+

+ 33 - 0
backend/auth-service/src/main/java/com/lingyue/auth/config/RedisConfig.java

@@ -0,0 +1,33 @@
+package com.lingyue.auth.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.RedisTemplate;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+/**
+ * Redis 配置
+ */
+@Configuration
+public class RedisConfig {
+    
+    @Bean
+    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+        
+        // 使用String序列化器作为key的序列化器
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setHashKeySerializer(new StringRedisSerializer());
+        
+        // 使用JSON序列化器作为value的序列化器
+        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
+        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
+        
+        template.afterPropertiesSet();
+        return template;
+    }
+}
+

+ 65 - 0
backend/auth-service/src/main/java/com/lingyue/auth/config/SecurityConfig.java

@@ -0,0 +1,65 @@
+package com.lingyue.auth.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+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.web.SecurityFilterChain;
+
+/**
+ * Spring Security 配置
+ *
+ * @author lingyue
+ */
+@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig {
+    
+    /**
+     * 安全过滤器链配置
+     */
+    @Bean
+    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
+        return httpSecurity
+                // CSRF禁用,因为不使用session
+                .csrf(csrf -> csrf.disable())
+                // 禁用HTTP响应标头
+                .headers((headersCustomizer) -> {
+                    headersCustomizer.cacheControl(cache -> cache.disable())
+                                     .frameOptions(options -> options.sameOrigin());
+                })
+                // 基于token,所以不需要session
+                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+                // 授权配置
+                .authorizeHttpRequests((requests) -> {
+                    // 对于登录、注册、刷新Token允许匿名访问
+                    requests.requestMatchers("/auth/register", "/auth/login", "/auth/refresh").permitAll()
+                            // 静态资源,可匿名访问
+                            .requestMatchers(HttpMethod.GET,
+                                             "/",
+                                             "/*.html",
+                                             "/**/*.html",
+                                             "/**/*.css",
+                                             "/**/*.js").permitAll()
+                            // 错误页面
+                            .requestMatchers("/error").permitAll()
+                            // 除上面外的所有请求全部需要鉴权认证
+                            .anyRequest().authenticated();
+                })
+                .build();
+    }
+    
+    /**
+     * 强散列哈希加密实现
+     */
+    @Bean
+    public BCryptPasswordEncoder bCryptPasswordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+}
+

+ 91 - 0
backend/auth-service/src/main/java/com/lingyue/auth/controller/AuthController.java

@@ -0,0 +1,91 @@
+package com.lingyue.auth.controller;
+
+import com.lingyue.auth.dto.AuthResponse;
+import com.lingyue.auth.dto.LoginRequest;
+import com.lingyue.auth.dto.RegisterRequest;
+import com.lingyue.auth.service.AuthService;
+import com.lingyue.common.domain.AjaxResult;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 认证控制器
+ */
+@RestController
+@RequestMapping("/auth")
+@RequiredArgsConstructor
+public class AuthController {
+    
+    private final AuthService authService;
+    
+    /**
+     * 用户注册
+     */
+    @PostMapping("/register")
+    @ResponseStatus(org.springframework.http.HttpStatus.CREATED)
+    public AjaxResult<?> register(@Valid @RequestBody RegisterRequest request) {
+        AuthResponse response = authService.register(request);
+        return AjaxResult.success("注册成功", response);
+    }
+    
+    /**
+     * 用户登录
+     */
+    @PostMapping("/login")
+    public AjaxResult<AuthResponse> login(
+            @Valid @RequestBody LoginRequest request,
+            HttpServletRequest httpRequest) {
+        String ipAddress = getClientIpAddress(httpRequest);
+        String userAgent = httpRequest.getHeader("User-Agent");
+        AuthResponse response = authService.login(request, ipAddress, userAgent);
+        return AjaxResult.success("登录成功", response);
+    }
+    
+    /**
+     * 用户登出
+     */
+    @PostMapping("/logout")
+    public AjaxResult<?> logout(@RequestHeader("Authorization") String authorization) {
+        String token = authorization.replace("Bearer ", "");
+        authService.logout(token);
+        return AjaxResult.success("登出成功");
+    }
+    
+    /**
+     * 刷新Token
+     */
+    @PostMapping("/refresh")
+    public AjaxResult<AuthResponse> refreshToken(@RequestBody RefreshTokenRequest request) {
+        AuthResponse response = authService.refreshToken(request.getRefreshToken());
+        return AjaxResult.success(response);
+    }
+    
+    /**
+     * 获取客户端IP地址
+     */
+    private String getClientIpAddress(HttpServletRequest request) {
+        String xForwardedFor = request.getHeader("X-Forwarded-For");
+        if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
+            return xForwardedFor.split(",")[0].trim();
+        }
+        String xRealIp = request.getHeader("X-Real-IP");
+        if (xRealIp != null && !xRealIp.isEmpty()) {
+            return xRealIp;
+        }
+        return request.getRemoteAddr();
+    }
+    
+    /**
+     * 刷新Token请求DTO
+     */
+    @lombok.Data
+    @lombok.NoArgsConstructor
+    @lombok.AllArgsConstructor
+    public static class RefreshTokenRequest {
+        private String refreshToken;
+    }
+}
+

+ 46 - 0
backend/auth-service/src/main/java/com/lingyue/auth/dto/AuthResponse.java

@@ -0,0 +1,46 @@
+package com.lingyue.auth.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+/**
+ * 认证响应DTO
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class AuthResponse {
+    
+    /**
+     * 访问Token
+     */
+    private String accessToken;
+    
+    /**
+     * 刷新Token
+     */
+    private String refreshToken;
+    
+    /**
+     * Token类型(Bearer)
+     */
+    private String tokenType = "Bearer";
+    
+    /**
+     * 过期时间(秒)
+     */
+    private Long expiresIn;
+    
+    /**
+     * 用户ID
+     */
+    private String userId;
+    
+    /**
+     * 用户名
+     */
+    private String username;
+}
+

+ 18 - 0
backend/auth-service/src/main/java/com/lingyue/auth/dto/LoginRequest.java

@@ -0,0 +1,18 @@
+package com.lingyue.auth.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+/**
+ * 登录请求DTO
+ */
+@Data
+public class LoginRequest {
+    
+    @NotBlank(message = "用户名或邮箱不能为空")
+    private String usernameOrEmail;
+    
+    @NotBlank(message = "密码不能为空")
+    private String password;
+}
+

+ 32 - 0
backend/auth-service/src/main/java/com/lingyue/auth/dto/RegisterRequest.java

@@ -0,0 +1,32 @@
+package com.lingyue.auth.dto;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+/**
+ * 注册请求DTO
+ */
+@Data
+public class RegisterRequest {
+    
+    @NotBlank(message = "用户名不能为空")
+    @Size(min = 3, max = 20, message = "用户名长度必须在3-20个字符之间")
+    @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
+    private String username;
+    
+    @NotBlank(message = "邮箱不能为空")
+    @Email(message = "邮箱格式不正确")
+    private String email;
+    
+    @NotBlank(message = "密码不能为空")
+    @Size(min = 8, max = 32, message = "密码长度必须在8-32个字符之间")
+    @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d).+$", message = "密码必须包含字母和数字")
+    private String password;
+    
+    @NotBlank(message = "确认密码不能为空")
+    private String confirmPassword;
+}
+

+ 48 - 0
backend/auth-service/src/main/java/com/lingyue/auth/entity/Session.java

@@ -0,0 +1,48 @@
+package com.lingyue.auth.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.lingyue.common.domain.entity.SimpleModel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 会话实体
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@TableName("sessions")
+@Schema(description = "会话实体")
+public class Session extends SimpleModel {
+    
+    @Schema(description = "用户ID")
+    @TableField("user_id")
+    private String userId;
+    
+    @Schema(description = "Token哈希")
+    @TableField("token_hash")
+    private String tokenHash;
+    
+    @Schema(description = "刷新Token哈希")
+    @TableField("refresh_token_hash")
+    private String refreshTokenHash;
+    
+    @Schema(description = "过期时间")
+    @TableField("expires_at")
+    private Date expiresAt;
+    
+    @Schema(description = "IP地址")
+    @TableField("ip_address")
+    private String ipAddress;
+    
+    @Schema(description = "用户代理")
+    @TableField("user_agent")
+    private String userAgent;
+    
+    @Schema(description = "最后使用时间")
+    @TableField("last_used_at")
+    private Date lastUsedAt;
+}

+ 50 - 0
backend/auth-service/src/main/java/com/lingyue/auth/entity/User.java

@@ -0,0 +1,50 @@
+package com.lingyue.auth.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.lingyue.common.domain.entity.SimpleModel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 用户实体
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@TableName("users")
+@Schema(description = "用户实体")
+public class User extends SimpleModel {
+    
+    @Schema(description = "用户名")
+    @TableField("username")
+    private String username;
+    
+    @Schema(description = "邮箱")
+    @TableField("email")
+    private String email;
+    
+    @Schema(description = "密码哈希")
+    @TableField("password_hash")
+    private String passwordHash;
+    
+    @Schema(description = "头像URL")
+    @TableField("avatar_url")
+    private String avatarUrl;
+    
+    @Schema(description = "角色")
+    @TableField("role")
+    private String role = "user"; // admin/user/guest
+    
+    @Schema(description = "偏好设置")
+    @TableField(value = "preferences", typeHandler = com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler.class)
+    private Object preferences;
+    
+    @Schema(description = "最后登录时间")
+    @TableField("last_login_at")
+    private Date lastLoginAt;
+}

+ 48 - 0
backend/auth-service/src/main/java/com/lingyue/auth/repository/SessionRepository.java

@@ -0,0 +1,48 @@
+package com.lingyue.auth.repository;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.lingyue.auth.entity.Session;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Delete;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 会话Repository
+ */
+@Mapper
+public interface SessionRepository extends BaseMapper<Session> {
+    
+    /**
+     * 根据Token哈希查找会话
+     */
+    @Select("SELECT * FROM sessions WHERE token_hash = #{tokenHash}")
+    Session findByTokenHash(@Param("tokenHash") String tokenHash);
+    
+    /**
+     * 根据刷新Token哈希查找会话
+     */
+    @Select("SELECT * FROM sessions WHERE refresh_token_hash = #{refreshTokenHash}")
+    Session findByRefreshTokenHash(@Param("refreshTokenHash") String refreshTokenHash);
+    
+    /**
+     * 根据用户ID查找所有会话
+     */
+    @Select("SELECT * FROM sessions WHERE user_id = #{userId}")
+    List<Session> findByUserId(@Param("userId") String userId);
+    
+    /**
+     * 删除过期的会话
+     */
+    @Delete("DELETE FROM sessions WHERE expires_at < #{now}")
+    void deleteExpiredSessions(@Param("now") Date now);
+    
+    /**
+     * 删除用户的所有会话
+     */
+    @Delete("DELETE FROM sessions WHERE user_id = #{userId}")
+    void deleteByUserId(@Param("userId") String userId);
+}

+ 13 - 0
backend/auth-service/src/main/java/com/lingyue/auth/repository/UserRepository.java

@@ -0,0 +1,13 @@
+package com.lingyue.auth.repository;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.lingyue.auth.entity.User;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 用户Repository
+ */
+@Mapper
+public interface UserRepository extends BaseMapper<User> {
+    
+}

+ 229 - 0
backend/auth-service/src/main/java/com/lingyue/auth/service/AuthService.java

@@ -0,0 +1,229 @@
+package com.lingyue.auth.service;
+
+import com.lingyue.auth.dto.AuthResponse;
+import com.lingyue.auth.dto.LoginRequest;
+import com.lingyue.auth.dto.RegisterRequest;
+import com.lingyue.auth.entity.Session;
+import com.lingyue.auth.entity.User;
+import com.lingyue.auth.repository.SessionRepository;
+import com.lingyue.auth.repository.UserRepository;
+import com.lingyue.common.exception.ServiceException;
+import com.lingyue.common.constant.HttpStatus;
+import com.lingyue.common.util.JwtUtil;
+import com.lingyue.common.util.PasswordUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.UUID;
+
+/**
+ * 认证服务
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class AuthService {
+    
+    private final UserRepository userRepository;
+    private final SessionRepository sessionRepository;
+    private final UserService userService;
+    
+    @Value("${jwt.secret:lingyue-zhibao-secret-key-2024-please-change-in-production}")
+    private String jwtSecret;
+    
+    @Value("${jwt.expiration:604800000}") // 7天,单位:毫秒
+    private long jwtExpiration;
+    
+    @Value("${jwt.refresh-expiration:2592000000}") // 30天,单位:毫秒
+    private long refreshExpiration;
+    
+    /**
+     * 用户注册
+     */
+    @Transactional
+    public AuthResponse register(RegisterRequest request) {
+        // 验证密码确认
+        if (!request.getPassword().equals(request.getConfirmPassword())) {
+            throw new ServiceException("密码和确认密码不一致", HttpStatus.BAD_REQUEST);
+        }
+        
+        // 检查用户名是否已存在
+        com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<User> usernameWrapper = 
+            new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
+        usernameWrapper.eq(User::getUsername, request.getUsername());
+        if (userRepository.selectCount(usernameWrapper) > 0) {
+            throw new ServiceException("用户名已存在", 422);
+        }
+        
+        // 检查邮箱是否已存在
+        com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<User> emailWrapper = 
+            new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
+        emailWrapper.eq(User::getEmail, request.getEmail());
+        if (userRepository.selectCount(emailWrapper) > 0) {
+            throw new ServiceException("邮箱已被注册", 422);
+        }
+        
+        // 创建用户
+        User user = new User();
+        user.setId(java.util.UUID.randomUUID().toString().replace("-", ""));
+        user.setUsername(request.getUsername());
+        user.setEmail(request.getEmail());
+        user.setPasswordHash(PasswordUtil.encode(request.getPassword()));
+        user.setRole("user");
+        user.setCreateTime(new java.util.Date());
+        userRepository.insert(user);
+        
+        log.info("User registered: {}", user.getUsername());
+        
+        // 生成Token并创建会话
+        return createAuthResponse(user);
+    }
+    
+    /**
+     * 用户登录
+     */
+    @Transactional
+    public AuthResponse login(LoginRequest request, String ipAddress, String userAgent) {
+        // 查找用户(支持用户名或邮箱登录)
+        com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<User> wrapper = 
+            new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
+        wrapper.eq(User::getUsername, request.getUsernameOrEmail())
+               .or()
+               .eq(User::getEmail, request.getUsernameOrEmail());
+        User user = userRepository.selectOne(wrapper);
+        
+        if (user == null) {
+            throw new ServiceException("用户名或密码错误", HttpStatus.UNAUTHORIZED);
+        }
+        
+        // 验证密码
+        if (!PasswordUtil.matches(request.getPassword(), user.getPasswordHash())) {
+            throw new ServiceException("用户名或密码错误", HttpStatus.UNAUTHORIZED);
+        }
+        
+        // 更新最后登录时间
+        user.setLastLoginAt(new java.util.Date());
+        userRepository.updateById(user);
+        
+        log.info("User logged in: {}", user.getUsername());
+        
+        // 生成Token并创建会话
+        AuthResponse response = createAuthResponse(user, ipAddress, userAgent);
+        
+        return response;
+    }
+    
+    /**
+     * 用户登出
+     */
+    @Transactional
+    public void logout(String token) {
+        String tokenHash = hashToken(token);
+        Session session = sessionRepository.findByTokenHash(tokenHash);
+        if (session != null) {
+            sessionRepository.deleteById(session.getId());
+        }
+        log.info("User logged out");
+    }
+    
+    /**
+     * 刷新Token
+     */
+    @Transactional
+    public AuthResponse refreshToken(String refreshToken) {
+        String refreshTokenHash = hashToken(refreshToken);
+        Session session = sessionRepository.findByRefreshTokenHash(refreshTokenHash);
+        
+        if (session == null) {
+            throw new ServiceException("刷新Token无效", HttpStatus.UNAUTHORIZED);
+        }
+        
+        // 检查Token是否过期
+        if (session.getExpiresAt().before(new java.util.Date())) {
+            sessionRepository.deleteById(session.getId());
+            throw new ServiceException("刷新Token已过期", HttpStatus.UNAUTHORIZED);
+        }
+        
+        // 获取用户
+        User user = userService.getUserById(session.getUserId());
+        if (user == null) {
+            throw new ServiceException("用户不存在", HttpStatus.NOT_FOUND);
+        }
+        
+        // 删除旧会话
+        sessionRepository.deleteById(session.getId());
+        
+        // 创建新会话
+        return createAuthResponse(user);
+    }
+    
+    /**
+     * 创建认证响应(包含Token和会话)
+     */
+    private AuthResponse createAuthResponse(User user) {
+        return createAuthResponse(user, null, null);
+    }
+    
+    /**
+     * 创建认证响应(包含Token和会话)
+     */
+    private AuthResponse createAuthResponse(User user, String ipAddress, String userAgent) {
+        // 生成Token
+        String userId = user.getId().toString();
+        String accessToken = JwtUtil.generateToken(userId, user.getUsername(), jwtSecret, jwtExpiration);
+        String refreshToken = JwtUtil.generateRefreshToken(userId, user.getUsername(), jwtSecret);
+        
+        // 创建会话
+        Session session = new Session();
+        session.setId(java.util.UUID.randomUUID().toString().replace("-", ""));
+        session.setUserId(user.getId());
+        session.setTokenHash(hashToken(accessToken));
+        session.setRefreshTokenHash(hashToken(refreshToken));
+        java.util.Calendar cal = java.util.Calendar.getInstance();
+        cal.add(java.util.Calendar.SECOND, (int)(jwtExpiration / 1000));
+        session.setExpiresAt(cal.getTime());
+        session.setIpAddress(ipAddress);
+        session.setUserAgent(userAgent);
+        session.setCreateTime(new java.util.Date());
+        session.setLastUsedAt(new java.util.Date());
+        sessionRepository.insert(session);
+        
+        // 构建响应
+        AuthResponse response = new AuthResponse();
+        response.setAccessToken(accessToken);
+        response.setRefreshToken(refreshToken);
+        response.setTokenType("Bearer");
+        response.setExpiresIn(jwtExpiration / 1000);
+        response.setUserId(user.getId());
+        response.setUsername(user.getUsername());
+        
+        return response;
+    }
+    
+    /**
+     * 哈希Token(用于存储)
+     */
+    private String hashToken(String token) {
+        try {
+            MessageDigest digest = MessageDigest.getInstance("SHA-256");
+            byte[] hash = digest.digest(token.getBytes(StandardCharsets.UTF_8));
+            StringBuilder hexString = new StringBuilder();
+            for (byte b : hash) {
+                String hex = Integer.toHexString(0xff & b);
+                if (hex.length() == 1) {
+                    hexString.append('0');
+                }
+                hexString.append(hex);
+            }
+            return hexString.toString();
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to hash token", e);
+        }
+    }
+}
+

+ 71 - 0
backend/auth-service/src/main/java/com/lingyue/auth/service/TokenService.java

@@ -0,0 +1,71 @@
+package com.lingyue.auth.service;
+
+import com.lingyue.auth.entity.Session;
+import com.lingyue.auth.repository.SessionRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+
+/**
+ * Token管理服务
+ */
+@Service
+@RequiredArgsConstructor
+public class TokenService {
+    
+    private final SessionRepository sessionRepository;
+    
+    /**
+     * 验证Token是否有效
+     */
+    public boolean validateToken(String token) {
+        String tokenHash = hashToken(token);
+        Session session = sessionRepository.findByTokenHash(tokenHash);
+        return session != null;
+    }
+    
+    /**
+     * 根据Token获取会话
+     */
+    public Session getSessionByToken(String token) {
+        String tokenHash = hashToken(token);
+        return sessionRepository.findByTokenHash(tokenHash);
+    }
+    
+    /**
+     * 删除会话
+     */
+    @Transactional
+    public void deleteSession(String token) {
+        String tokenHash = hashToken(token);
+        Session session = sessionRepository.findByTokenHash(tokenHash);
+        if (session != null) {
+            sessionRepository.deleteById(session.getId());
+        }
+    }
+    
+    /**
+     * 哈希Token(用于存储)
+     */
+    private String hashToken(String token) {
+        try {
+            MessageDigest digest = MessageDigest.getInstance("SHA-256");
+            byte[] hash = digest.digest(token.getBytes(StandardCharsets.UTF_8));
+            StringBuilder hexString = new StringBuilder();
+            for (byte b : hash) {
+                String hex = Integer.toHexString(0xff & b);
+                if (hex.length() == 1) {
+                    hexString.append('0');
+                }
+                hexString.append(hex);
+            }
+            return hexString.toString();
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to hash token", e);
+        }
+    }
+}
+

+ 43 - 0
backend/auth-service/src/main/java/com/lingyue/auth/service/UserService.java

@@ -0,0 +1,43 @@
+package com.lingyue.auth.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.lingyue.auth.entity.User;
+import com.lingyue.auth.repository.UserRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 用户服务
+ */
+@Service
+@RequiredArgsConstructor
+public class UserService {
+    
+    private final UserRepository userRepository;
+    
+    /**
+     * 根据ID获取用户
+     */
+    public User getUserById(String userId) {
+        return userRepository.selectById(userId);
+    }
+    
+    /**
+     * 根据用户名获取用户
+     */
+    public User getUserByUsername(String username) {
+        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(User::getUsername, username);
+        return userRepository.selectOne(wrapper);
+    }
+    
+    /**
+     * 根据邮箱获取用户
+     */
+    public User getUserByEmail(String email) {
+        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(User::getEmail, email);
+        return userRepository.selectOne(wrapper);
+    }
+}
+

+ 24 - 0
backend/auth-service/src/main/resources/application-dev.yml

@@ -0,0 +1,24 @@
+spring:
+  datasource:
+    url: jdbc:postgresql://localhost:5432/lingyue_zhibao
+    username: postgres
+    password: postgres
+  
+  data:
+    redis:
+      host: localhost
+      port: 6379
+  
+  cloud:
+    nacos:
+      discovery:
+        server-addr: localhost:8848
+
+jwt:
+  secret: lingyue-zhibao-secret-key-2024-please-change-in-production
+
+logging:
+  level:
+    root: INFO
+    com.lingyue: DEBUG
+

+ 25 - 0
backend/auth-service/src/main/resources/application-prod.yml

@@ -0,0 +1,25 @@
+spring:
+  datasource:
+    url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:lingyue_zhibao}
+    username: ${DB_USERNAME:postgres}
+    password: ${DB_PASSWORD:postgres}
+  
+  data:
+    redis:
+      host: ${REDIS_HOST:localhost}
+      port: ${REDIS_PORT:6379}
+      password: ${REDIS_PASSWORD:}
+  
+  cloud:
+    nacos:
+      discovery:
+        server-addr: ${NACOS_SERVER_ADDR:nacos-server:8848}
+
+jwt:
+  secret: ${JWT_SECRET:lingyue-zhibao-secret-key-2024-please-change-in-production}
+
+logging:
+  level:
+    root: WARN
+    com.lingyue: INFO
+

+ 80 - 0
backend/auth-service/src/main/resources/application.yml

@@ -0,0 +1,80 @@
+server:
+  port: 8001
+
+spring:
+  application:
+    name: auth-service
+  
+  # 数据库配置(Druid)
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    druid:
+      driver-class-name: org.postgresql.Driver
+      url: jdbc:postgresql://localhost:5432/lingyue_zhibao
+      username: ${DB_USERNAME:postgres}
+      password: ${DB_PASSWORD:postgres}
+      initial-size: 5
+      min-idle: 5
+      max-active: 20
+      max-wait: 60000
+      time-between-eviction-runs-millis: 60000
+      min-evictable-idle-time-millis: 300000
+      validation-query: SELECT 1
+      test-while-idle: true
+      test-on-borrow: false
+      test-on-return: false
+      pool-prepared-statements: true
+      max-pool-prepared-statement-per-connection-size: 20
+      filters: stat,wall,slf4j
+      connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
+  
+  # MyBatis Plus配置
+  mybatis-plus:
+    mapper-locations: classpath*:mapper/**/*Mapper.xml
+    type-aliases-package: com.lingyue.auth.entity
+    configuration:
+      map-underscore-to-camel-case: true
+      log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
+    global-config:
+      db-config:
+        id-type: assign_uuid
+        logic-delete-field: delFlag
+        logic-delete-value: 1
+        logic-not-delete-value: 0
+  
+  # Redis配置
+  data:
+    redis:
+      host: ${REDIS_HOST:localhost}
+      port: ${REDIS_PORT:6379}
+      password: ${REDIS_PASSWORD:}
+      database: 0
+      timeout: 3000
+      lettuce:
+        pool:
+          max-active: 8
+          max-idle: 8
+          min-idle: 0
+  
+  # Nacos配置
+  cloud:
+    nacos:
+      discovery:
+        server-addr: ${NACOS_SERVER_ADDR:localhost:8848}
+        namespace: public
+        group: DEFAULT_GROUP
+
+# JWT配置
+jwt:
+  secret: ${JWT_SECRET:lingyue-zhibao-secret-key-2024-please-change-in-production}
+  expiration: 604800000  # 7天,单位:毫秒
+  refresh-expiration: 2592000000  # 30天,单位:毫秒
+
+# 日志配置
+logging:
+  level:
+    root: INFO
+    com.lingyue: DEBUG
+  pattern:
+    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
+

+ 102 - 0
backend/common/pom.xml

@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.lingyue</groupId>
+        <artifactId>lingyue-zhibao</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>common</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Common Module</name>
+    <description>公共模块 - 统一响应、异常处理、工具类等</description>
+
+    <dependencies>
+        <!-- Spring Boot Web -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        
+        <!-- Spring Boot Validation -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        
+        <!-- Spring Security (for PasswordEncoder and AccessDeniedException) -->
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-crypto</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-web</artifactId>
+        </dependency>
+        
+        <!-- Spring Data (for DataAccessException) -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-tx</artifactId>
+        </dependency>
+        
+        <!-- MyBatis Plus (for entity annotations) -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+        
+        <!-- JWT -->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-impl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-jackson</artifactId>
+        </dependency>
+        
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+        
+        <!-- Jackson for JSON -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        
+        <!-- FastJSON2 -->
+        <dependency>
+            <groupId>com.alibaba.fastjson2</groupId>
+            <artifactId>fastjson2</artifactId>
+        </dependency>
+        
+        <!-- Hutool -->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
+        
+        <!-- Swagger/OpenAPI 注解 -->
+        <dependency>
+            <groupId>io.swagger.core.v3</groupId>
+            <artifactId>swagger-annotations</artifactId>
+            <version>2.2.22</version>
+        </dependency>
+    </dependencies>
+</project>
+

+ 94 - 0
backend/common/src/main/java/com/lingyue/common/constant/HttpStatus.java

@@ -0,0 +1,94 @@
+package com.lingyue.common.constant;
+
+/**
+ * 返回状态码
+ *
+ * @author lingyue
+ */
+public class HttpStatus {
+    /**
+     * 操作成功
+     */
+    public static final int SUCCESS = 200;
+
+    /**
+     * 对象创建成功
+     */
+    public static final int CREATED = 201;
+
+    /**
+     * 请求已经被接受
+     */
+    public static final int ACCEPTED = 202;
+
+    /**
+     * 操作已经执行成功,但是没有返回数据
+     */
+    public static final int NO_CONTENT = 204;
+
+    /**
+     * 资源已被移除
+     */
+    public static final int MOVED_PERM = 301;
+
+    /**
+     * 重定向
+     */
+    public static final int SEE_OTHER = 303;
+
+    /**
+     * 资源没有被修改
+     */
+    public static final int NOT_MODIFIED = 304;
+
+    /**
+     * 参数列表错误(缺少,格式不匹配)
+     */
+    public static final int BAD_REQUEST = 400;
+
+    /**
+     * 未授权
+     */
+    public static final int UNAUTHORIZED = 401;
+
+    /**
+     * 访问受限,授权过期
+     */
+    public static final int FORBIDDEN = 403;
+
+    /**
+     * 资源,服务未找到
+     */
+    public static final int NOT_FOUND = 404;
+
+    /**
+     * 不允许的http方法
+     */
+    public static final int BAD_METHOD = 405;
+
+    /**
+     * 资源冲突,或者资源被锁
+     */
+    public static final int CONFLICT = 409;
+
+    /**
+     * 不支持的数据,媒体类型
+     */
+    public static final int UNSUPPORTED_TYPE = 415;
+
+    /**
+     * 系统内部错误
+     */
+    public static final int ERROR = 500;
+
+    /**
+     * 接口未实现
+     */
+    public static final int NOT_IMPLEMENTED = 501;
+
+    /**
+     * 系统警告消息
+     */
+    public static final int WARN = 601;
+}
+

+ 210 - 0
backend/common/src/main/java/com/lingyue/common/domain/AjaxResult.java

@@ -0,0 +1,210 @@
+package com.lingyue.common.domain;
+
+import com.lingyue.common.constant.HttpStatus;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.util.HashMap;
+import java.util.Objects;
+
+/**
+ * 操作消息提醒
+ *
+ * @author lingyue
+ */
+@Data
+@Schema(description = "返回结果,带状态,消息和主数据")
+public class AjaxResult<T> extends HashMap<String, Object> {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 返回成功消息
+     *
+     * @return 成功消息
+     */
+    public static AjaxResult<?> success() {
+        return AjaxResult.success("操作成功");
+    }
+
+    /**
+     * 返回成功数据
+     *
+     * @return 成功消息
+     */
+    public static <T> AjaxResult<T> success(T data) {
+        return AjaxResult.success("操作成功", data);
+    }
+
+    /**
+     * 返回成功消息
+     *
+     * @param msg 返回内容
+     * @return 成功消息
+     */
+    public static AjaxResult<?> success(String msg) {
+        return AjaxResult.success(msg, null);
+    }
+
+    /**
+     * 返回成功消息
+     *
+     * @param msg  返回内容
+     * @param data 数据对象
+     * @return 成功消息
+     */
+    public static <T> AjaxResult<T> success(String msg, T data) {
+        return new AjaxResult<>(HttpStatus.SUCCESS, msg, data);
+    }
+
+    /**
+     * 返回警告消息
+     *
+     * @param msg 返回内容
+     * @return 警告消息
+     */
+    public static AjaxResult<?> warn(String msg) {
+        return AjaxResult.warn(msg, null);
+    }
+
+    /**
+     * 返回警告消息
+     *
+     * @param msg  返回内容
+     * @param data 数据对象
+     * @return 警告消息
+     */
+    public static <T> AjaxResult<T> warn(String msg, T data) {
+        return new AjaxResult<>(HttpStatus.WARN, msg, data);
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @return 错误消息
+     */
+    public static AjaxResult<?> error() {
+        return AjaxResult.error("操作失败");
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @param msg 返回内容
+     * @return 错误消息
+     */
+    public static AjaxResult<?> error(String msg) {
+        return AjaxResult.error(msg, null);
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @param msg  返回内容
+     * @param data 数据对象
+     * @return 错误消息
+     */
+    public static <T> AjaxResult<T> error(String msg, T data) {
+        return new AjaxResult<>(HttpStatus.ERROR, msg, data);
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @param code 状态码
+     * @param msg  返回内容
+     * @return 错误消息
+     */
+    public static AjaxResult<?> error(int code, String msg) {
+        return new AjaxResult<>(code, msg, null);
+    }
+
+    /**
+     * 状态码
+     */
+    public static final String CODE_TAG = "code";
+
+    /**
+     * 返回内容
+     */
+    public static final String MSG_TAG = "msg";
+
+    /**
+     * 数据对象
+     */
+    public static final String DATA_TAG = "data";
+
+    /**
+     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
+     */
+    public AjaxResult() {
+    }
+
+    /**
+     * 初始化一个新创建的 AjaxResult 对象
+     *
+     * @param code 状态码
+     * @param msg  返回内容
+     */
+    public AjaxResult(int code, String msg) {
+        super.put(CODE_TAG, code);
+        super.put(MSG_TAG, msg);
+    }
+
+    /**
+     * 初始化一个新创建的 AjaxResult 对象
+     *
+     * @param code 状态码
+     * @param msg  返回内容
+     * @param data 数据对象
+     */
+    public AjaxResult(int code, String msg, T data) {
+        super.put(CODE_TAG, code);
+        super.put(MSG_TAG, msg);
+        if (data != null) {
+            super.put(DATA_TAG, data);
+        }
+    }
+
+    /**
+     * 是否为成功消息
+     *
+     * @return 结果
+     */
+    public boolean isSuccess() {
+        return Objects.equals(HttpStatus.SUCCESS, super.get(CODE_TAG));
+    }
+
+    /**
+     * 是否为警告消息
+     *
+     * @return 结果
+     */
+    public boolean isWarn() {
+        return Objects.equals(HttpStatus.WARN, super.get(CODE_TAG));
+    }
+
+    /**
+     * 是否为错误消息
+     *
+     * @return 结果
+     */
+    public boolean isError() {
+        return Objects.equals(HttpStatus.ERROR, super.get(CODE_TAG));
+    }
+
+    public void setData(T data) {
+        if (data != null) {
+            super.put(DATA_TAG, data);
+        }
+    }
+
+    @Schema(description = "数据对象")
+    public T getData() {
+        return (T) super.get(DATA_TAG);
+    }
+
+}
+

+ 27 - 0
backend/common/src/main/java/com/lingyue/common/domain/entity/AssignUuidModel.java

@@ -0,0 +1,27 @@
+package com.lingyue.common.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * UUID主键模型基类
+ *
+ * @author lingyue
+ */
+@Data
+public abstract class AssignUuidModel implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "主键ID")
+    @TableId(type = IdType.ASSIGN_UUID)
+    private String id;
+
+}
+

+ 48 - 0
backend/common/src/main/java/com/lingyue/common/domain/entity/CreationModel.java

@@ -0,0 +1,48 @@
+package com.lingyue.common.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 创建信息模型基类
+ *
+ * @author lingyue
+ */
+@Data
+public abstract class CreationModel extends AssignUuidModel {
+
+    public static final String[] IGNORE_FIELDS = {"id", "createBy", "createTime", "createByUsername"};
+
+    public static void clearBaseInfo(CreationModel model) {
+        if (model != null) {
+            model.createBy = null;
+            model.createTime = null;
+            model.createByUsername = null;
+        }
+    }
+
+    /**
+     * 创建有关字段
+     */
+    @Schema(description = "创建者 id")
+    @TableField("create_by")
+    private String createBy; // 创建者id
+    
+    @Schema(description = "创建者 username 冗余字段")
+    @TableField("create_by_name")
+    private String createByUsername; // 创建者 username 冗余字段
+    
+    @TableField("create_time")
+    @Schema(description = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime; // 创建时间
+
+    @Schema(description = "创建者用户所属部门(关联查询)")
+    @TableField(exist = false)
+    private String createByDeptName;
+}
+

+ 56 - 0
backend/common/src/main/java/com/lingyue/common/domain/entity/SimpleModel.java

@@ -0,0 +1,56 @@
+package com.lingyue.common.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 简单模型基类(不支持逻辑删除)
+ *
+ * @author lingyue
+ */
+@Data
+public class SimpleModel extends CreationModel {
+
+    public static final String[] IGNORE_FIELDS = {"id",
+                                                  "createBy",
+                                                  "createTime",
+                                                  "createByUsername",
+                                                  "updateBy",
+                                                  "updateTime",
+                                                  "updateByUsername"};
+
+    public static void clearBaseInfo(SimpleModel model) {
+        CreationModel.clearBaseInfo(model);
+        if (model != null) {
+            model.updateBy = null;
+            model.updateTime = null;
+            model.updateByUsername = null;
+        }
+    }
+
+    /**
+     * 更新有关字段
+     */
+    @Schema(description = "更新者 id")
+    @TableField("update_by")
+    private String updateBy; // 更新者 id
+    
+    @Schema(description = "更新者 username 冗余字段")
+    @TableField("update_by_name")
+    private String updateByUsername; // 更新者 username 冗余字段
+    
+    @Schema(description = "更新时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField("update_time")
+    private Date updateTime; //更新时间
+
+    @Schema(description = "更新者所属部门(关联查询)")
+    @TableField(exist = false)
+    private String updateByDeptName;
+
+}
+

+ 43 - 0
backend/common/src/main/java/com/lingyue/common/dto/PageRequest.java

@@ -0,0 +1,43 @@
+package com.lingyue.common.dto;
+
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import lombok.Data;
+
+/**
+ * 分页请求
+ */
+@Data
+public class PageRequest {
+    
+    /**
+     * 页码(从1开始)
+     */
+    @Min(value = 1, message = "页码必须大于0")
+    private Integer page = 1;
+    
+    /**
+     * 每页大小
+     */
+    @Min(value = 1, message = "每页大小必须大于0")
+    @Max(value = 100, message = "每页大小不能超过100")
+    private Integer pageSize = 20;
+    
+    /**
+     * 排序字段
+     */
+    private String sortBy;
+    
+    /**
+     * 排序方向(ASC/DESC)
+     */
+    private String sortDirection = "DESC";
+    
+    /**
+     * 获取偏移量
+     */
+    public int getOffset() {
+        return (page - 1) * pageSize;
+    }
+}
+

+ 52 - 0
backend/common/src/main/java/com/lingyue/common/dto/PageResponse.java

@@ -0,0 +1,52 @@
+package com.lingyue.common.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 分页响应
+ *
+ * @param <T> 数据类型
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class PageResponse<T> {
+    
+    /**
+     * 数据列表
+     */
+    private List<T> items;
+    
+    /**
+     * 总记录数
+     */
+    private Long total;
+    
+    /**
+     * 页码
+     */
+    private Integer page;
+    
+    /**
+     * 每页大小
+     */
+    private Integer pageSize;
+    
+    /**
+     * 总页数
+     */
+    private Integer totalPages;
+    
+    /**
+     * 创建分页响应
+     */
+    public static <T> PageResponse<T> of(List<T> items, Long total, Integer page, Integer pageSize) {
+        int totalPages = (int) Math.ceil((double) total / pageSize);
+        return new PageResponse<>(items, total, page, pageSize, totalPages);
+    }
+}
+

+ 37 - 0
backend/common/src/main/java/com/lingyue/common/exception/BusinessException.java

@@ -0,0 +1,37 @@
+package com.lingyue.common.exception;
+
+import lombok.Getter;
+
+/**
+ * 业务异常(已废弃,请使用 ServiceException)
+ * @deprecated 使用 ServiceException 替代
+ */
+@Deprecated
+@Getter
+public class BusinessException extends RuntimeException {
+    
+    private static final long serialVersionUID = 1L;
+    
+    /**
+     * 响应码
+     */
+    private final Integer code;
+    
+    /**
+     * 错误详情
+     */
+    private final Object errorDetail;
+    
+    public BusinessException(Integer code, String message) {
+        super(message);
+        this.code = code;
+        this.errorDetail = null;
+    }
+    
+    public BusinessException(Integer code, String message, Object errorDetail) {
+        super(message);
+        this.code = code;
+        this.errorDetail = errorDetail;
+    }
+}
+

+ 147 - 0
backend/common/src/main/java/com/lingyue/common/exception/GlobalExceptionHandler.java

@@ -0,0 +1,147 @@
+package com.lingyue.common.exception;
+
+import com.lingyue.common.constant.HttpStatus;
+import com.lingyue.common.domain.AjaxResult;
+import com.lingyue.common.utils.StringUtils;
+import jakarta.servlet.http.HttpServletRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.validation.BindException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.MissingPathVariableException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
+
+import java.sql.SQLException;
+
+// 注意:MyBatis Plus异常处理需要添加mybatis-plus依赖
+// import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
+
+/**
+ * 全局异常处理器
+ *
+ * @author lingyue
+ */
+@RestControllerAdvice(basePackages = "com.lingyue")
+public class GlobalExceptionHandler {
+    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
+
+    /**
+     * 权限校验异常
+     */
+    @ExceptionHandler(org.springframework.security.access.AccessDeniedException.class)
+    public AjaxResult<?> handleAccessDeniedException(org.springframework.security.access.AccessDeniedException e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
+        return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");
+    }
+
+    /**
+     * 请求方式不支持
+     */
+    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+    public AjaxResult<?> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
+                                                          HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
+        return AjaxResult.error(e.getMessage());
+    }
+
+    /**
+     * SQL 错误
+     */
+    @ExceptionHandler(SQLException.class)
+    public AjaxResult<?> handleSQLException(SQLException e,
+                                         HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',发生SQL异常.", requestURI, e);
+        return AjaxResult.error("数据库访问错误,请联系管理员");
+    }
+
+    /**
+     * DAO 错误
+     */
+    @ExceptionHandler(org.springframework.dao.DataAccessException.class)
+    public AjaxResult<?> handleDataAccessException(org.springframework.dao.DataAccessException e,
+                                                HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',发生数据访问异常.", requestURI, e);
+        return AjaxResult.error("数据库访问错误,请联系管理员");
+    }
+
+    /**
+     * 业务异常
+     */
+    @ExceptionHandler(ServiceException.class)
+    public AjaxResult<?> handleServiceException(ServiceException e, HttpServletRequest request) {
+        log.error(e.getMessage(), e);
+        Integer code = e.getCode();
+        return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
+    }
+
+    /**
+     * 请求路径中缺少必需的路径变量
+     */
+    @ExceptionHandler(MissingPathVariableException.class)
+    public AjaxResult<?> handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e);
+        return AjaxResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName()));
+    }
+
+    /**
+     * 请求参数类型不匹配
+     */
+    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
+    public AjaxResult<?> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e,
+                                                                HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e);
+        return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'",
+                                              e.getName(),
+                                              e.getRequiredType().getName(),
+                                              e.getValue()));
+    }
+
+    /**
+     * 拦截未知的运行时异常
+     */
+    @ExceptionHandler(RuntimeException.class)
+    public AjaxResult<?> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',发生未知异常.", requestURI, e);
+        return AjaxResult.error(e.getMessage());
+    }
+
+    /**
+     * 系统异常
+     */
+    @ExceptionHandler(Exception.class)
+    public AjaxResult<?> handleException(Exception e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',发生系统异常.", requestURI, e);
+        return AjaxResult.error(e.getMessage());
+    }
+
+    /**
+     * 自定义验证异常
+     */
+    @ExceptionHandler(BindException.class)
+    public AjaxResult<?> handleBindException(BindException e) {
+        log.error(e.getMessage(), e);
+        String message = e.getAllErrors().get(0).getDefaultMessage();
+        return AjaxResult.error(message);
+    }
+
+    /**
+     * 自定义验证异常
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public AjaxResult<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
+        log.error(e.getMessage(), e);
+        String message = e.getBindingResult().getFieldError().getDefaultMessage();
+        return AjaxResult.error(message);
+    }
+}

+ 64 - 0
backend/common/src/main/java/com/lingyue/common/exception/ServiceException.java

@@ -0,0 +1,64 @@
+package com.lingyue.common.exception;
+
+/**
+ * 业务异常
+ *
+ * @author lingyue
+ */
+public final class ServiceException extends RuntimeException {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 错误码
+     */
+    private Integer code;
+
+    /**
+     * 错误提示
+     */
+    private String message;
+
+    /**
+     * 错误明细,内部调试错误
+     */
+    private String detailMessage;
+
+    /**
+     * 空构造方法,避免反序列化问题
+     */
+    public ServiceException() {
+    }
+
+    public ServiceException(String message) {
+        this.message = message;
+    }
+
+    public ServiceException(String message, Integer code) {
+        this.message = message;
+        this.code = code;
+    }
+
+    public String getDetailMessage() {
+        return detailMessage;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public ServiceException setMessage(String message) {
+        this.message = message;
+        return this;
+    }
+
+    public ServiceException setDetailMessage(String detailMessage) {
+        this.detailMessage = detailMessage;
+        return this;
+    }
+}
+

+ 131 - 0
backend/common/src/main/java/com/lingyue/common/util/JwtUtil.java

@@ -0,0 +1,131 @@
+package com.lingyue.common.util;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+
+/**
+ * JWT 工具类
+ */
+@Slf4j
+public class JwtUtil {
+    
+    /**
+     * 默认密钥(生产环境应从配置文件读取)
+     */
+    private static final String DEFAULT_SECRET = "lingyue-zhibao-secret-key-2024-please-change-in-production";
+    
+    /**
+     * 默认过期时间(7天)
+     */
+    private static final long DEFAULT_EXPIRATION = 7 * 24 * 60 * 60 * 1000L;
+    
+    /**
+     * 刷新Token过期时间(30天)
+     */
+    private static final long REFRESH_EXPIRATION = 30 * 24 * 60 * 60 * 1000L;
+    
+    /**
+     * 生成密钥
+     */
+    private static SecretKey getSecretKey(String secret) {
+        byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
+        return new SecretKeySpec(keyBytes, SignatureAlgorithm.HS256.getJcaName());
+    }
+    
+    /**
+     * 生成Token
+     */
+    public static String generateToken(String userId, String username, String secret, long expiration) {
+        Date now = new Date();
+        Date expiryDate = new Date(now.getTime() + expiration);
+        
+        return Jwts.builder()
+            .setSubject(userId)
+            .claim("username", username)
+            .setIssuedAt(now)
+            .setExpiration(expiryDate)
+            .signWith(getSecretKey(secret), SignatureAlgorithm.HS256)
+            .compact();
+    }
+    
+    /**
+     * 生成Token(使用默认配置)
+     */
+    public static String generateToken(String userId, String username) {
+        return generateToken(userId, username, DEFAULT_SECRET, DEFAULT_EXPIRATION);
+    }
+    
+    /**
+     * 生成刷新Token
+     */
+    public static String generateRefreshToken(String userId, String username, String secret) {
+        return generateToken(userId, username, secret, REFRESH_EXPIRATION);
+    }
+    
+    /**
+     * 生成刷新Token(使用默认配置)
+     */
+    public static String generateRefreshToken(String userId, String username) {
+        return generateRefreshToken(userId, username, DEFAULT_SECRET);
+    }
+    
+    /**
+     * 解析Token
+     */
+    public static Claims parseToken(String token, String secret) {
+        try {
+            return Jwts.parserBuilder()
+                .setSigningKey(getSecretKey(secret))
+                .build()
+                .parseClaimsJws(token)
+                .getBody();
+        } catch (Exception e) {
+            log.error("Failed to parse token: {}", e.getMessage());
+            return null;
+        }
+    }
+    
+    /**
+     * 解析Token(使用默认密钥)
+     */
+    public static Claims parseToken(String token) {
+        return parseToken(token, DEFAULT_SECRET);
+    }
+    
+    /**
+     * 验证Token是否有效
+     */
+    public static boolean validateToken(String token, String secret) {
+        Claims claims = parseToken(token, secret);
+        return claims != null && !claims.getExpiration().before(new Date());
+    }
+    
+    /**
+     * 验证Token是否有效(使用默认密钥)
+     */
+    public static boolean validateToken(String token) {
+        return validateToken(token, DEFAULT_SECRET);
+    }
+    
+    /**
+     * 从Token中获取用户ID
+     */
+    public static String getUserIdFromToken(String token, String secret) {
+        Claims claims = parseToken(token, secret);
+        return claims != null ? claims.getSubject() : null;
+    }
+    
+    /**
+     * 从Token中获取用户ID(使用默认密钥)
+     */
+    public static String getUserIdFromToken(String token) {
+        return getUserIdFromToken(token, DEFAULT_SECRET);
+    }
+}

+ 27 - 0
backend/common/src/main/java/com/lingyue/common/util/PasswordUtil.java

@@ -0,0 +1,27 @@
+package com.lingyue.common.util;
+
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+/**
+ * 密码工具类
+ */
+public class PasswordUtil {
+    
+    private static final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+    
+    /**
+     * 加密密码
+     */
+    public static String encode(String rawPassword) {
+        return passwordEncoder.encode(rawPassword);
+    }
+    
+    /**
+     * 验证密码
+     */
+    public static boolean matches(String rawPassword, String encodedPassword) {
+        return passwordEncoder.matches(rawPassword, encodedPassword);
+    }
+}
+

+ 55 - 0
backend/common/src/main/java/com/lingyue/common/utils/StringUtils.java

@@ -0,0 +1,55 @@
+package com.lingyue.common.utils;
+
+/**
+ * 字符串工具类
+ *
+ * @author lingyue
+ */
+public class StringUtils {
+    
+    /**
+     * 空字符串
+     */
+    private static final String NULLSTR = "";
+
+    /**
+     * 下划线
+     */
+    private static final char SEPARATOR = '_';
+
+    /**
+     * 判断对象是否为空
+     */
+    public static boolean isNull(Object object) {
+        return object == null;
+    }
+
+    /**
+     * 判断对象是否非空
+     */
+    public static boolean isNotNull(Object object) {
+        return !isNull(object);
+    }
+
+    /**
+     * 判断字符串是否为空
+     */
+    public static boolean isEmpty(String str) {
+        return isNull(str) || NULLSTR.equals(str.trim());
+    }
+
+    /**
+     * 判断字符串是否非空
+     */
+    public static boolean isNotEmpty(String str) {
+        return !isEmpty(str);
+    }
+
+    /**
+     * 判断字符串是否有文本内容(非空且非空白)
+     */
+    public static boolean hasText(String str) {
+        return isNotEmpty(str) && str.trim().length() > 0;
+    }
+}
+

+ 68 - 0
backend/document-service/pom.xml

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.lingyue</groupId>
+        <artifactId>lingyue-zhibao</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>document-service</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Document Service</name>
+    <description>文档管理服务 - 文档上传、存储、管理</description>
+
+    <dependencies>
+        <!-- Spring Boot Web -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        
+        <!-- MyBatis Plus -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+        
+        <!-- PostgreSQL Driver -->
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+        </dependency>
+        
+        <!-- Nacos Service Discovery -->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+        </dependency>
+        
+        <!-- Common Module -->
+        <dependency>
+            <groupId>com.lingyue</groupId>
+            <artifactId>common</artifactId>
+        </dependency>
+        
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
+

+ 18 - 0
backend/document-service/src/main/java/com/lingyue/document/DocumentServiceApplication.java

@@ -0,0 +1,18 @@
+package com.lingyue.document;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+/**
+ * 文档管理服务启动类
+ */
+@SpringBootApplication
+@EnableDiscoveryClient
+public class DocumentServiceApplication {
+    
+    public static void main(String[] args) {
+        SpringApplication.run(DocumentServiceApplication.class, args);
+    }
+}
+

+ 24 - 0
backend/document-service/src/main/java/com/lingyue/document/config/MyBatisPlusConfig.java

@@ -0,0 +1,24 @@
+package com.lingyue.document.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * MyBatis Plus 配置
+ */
+@Configuration
+@MapperScan("com.lingyue.document.repository")
+public class MyBatisPlusConfig {
+    
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
+        return interceptor;
+    }
+}
+

+ 53 - 0
backend/document-service/src/main/java/com/lingyue/document/controller/DocumentController.java

@@ -0,0 +1,53 @@
+package com.lingyue.document.controller;
+
+import com.lingyue.document.service.DocumentService;
+import com.lingyue.common.domain.AjaxResult;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 文档控制器(基础框架)
+ */
+@RestController
+@RequestMapping("/documents")
+@RequiredArgsConstructor
+public class DocumentController {
+    
+    private final DocumentService documentService;
+    
+    /**
+     * 获取文档列表(待实现)
+     */
+    @GetMapping
+    public AjaxResult<?> getDocuments() {
+        // TODO: 实现文档列表查询
+        return AjaxResult.success("文档列表接口待实现");
+    }
+    
+    /**
+     * 获取文档详情(待实现)
+     */
+    @GetMapping("/{documentId}")
+    public AjaxResult<?> getDocument(@PathVariable String documentId) {
+        // TODO: 实现文档详情查询
+        return AjaxResult.success("文档详情接口待实现");
+    }
+    
+    /**
+     * 上传文档(待实现)
+     */
+    @PostMapping
+    public AjaxResult<?> uploadDocument() {
+        // TODO: 实现文档上传
+        return AjaxResult.success("文档上传接口待实现");
+    }
+    
+    /**
+     * 删除文档(待实现)
+     */
+    @DeleteMapping("/{documentId}")
+    public AjaxResult<?> deleteDocument(@PathVariable String documentId) {
+        // TODO: 实现文档删除
+        return AjaxResult.success("文档删除接口待实现");
+    }
+}

+ 76 - 0
backend/document-service/src/main/java/com/lingyue/document/entity/Document.java

@@ -0,0 +1,76 @@
+package com.lingyue.document.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.lingyue.common.domain.entity.SimpleModel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 文档实体
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@TableName("documents")
+@Schema(description = "文档实体")
+public class Document extends SimpleModel {
+    
+    @Schema(description = "用户ID")
+    @TableField("user_id")
+    private String userId;
+    
+    @Schema(description = "文档名称")
+    @TableField("name")
+    private String name;
+    
+    @Schema(description = "文档类型")
+    @TableField("type")
+    private String type; // pdf/word/image/markdown/other
+    
+    @Schema(description = "状态")
+    @TableField("status")
+    private String status = "pending"; // pending/uploading/parsing/completed/failed
+    
+    @Schema(description = "文件大小")
+    @TableField("file_size")
+    private Long fileSize;
+    
+    @Schema(description = "文件URL")
+    @TableField("file_url")
+    private String fileUrl;
+    
+    @Schema(description = "缩略图URL")
+    @TableField("thumbnail_url")
+    private String thumbnailUrl;
+    
+    @Schema(description = "解析后的文本")
+    @TableField("parsed_text")
+    private String parsedText;
+    
+    @Schema(description = "解析状态")
+    @TableField("parse_status")
+    private String parseStatus; // pending/parsing/completed/failed
+    
+    @Schema(description = "解析进度")
+    @TableField("parse_progress")
+    private Integer parseProgress = 0; // 0-100
+    
+    @Schema(description = "解析错误")
+    @TableField("parse_error")
+    private String parseError;
+    
+    @Schema(description = "解析开始时间")
+    @TableField("parse_started_at")
+    private Date parseStartedAt;
+    
+    @Schema(description = "解析完成时间")
+    @TableField("parse_completed_at")
+    private Date parseCompletedAt;
+    
+    @Schema(description = "元数据")
+    @TableField(value = "metadata", typeHandler = com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler.class)
+    private Object metadata;
+}

+ 13 - 0
backend/document-service/src/main/java/com/lingyue/document/repository/DocumentRepository.java

@@ -0,0 +1,13 @@
+package com.lingyue.document.repository;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.lingyue.document.entity.Document;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 文档Repository
+ */
+@Mapper
+public interface DocumentRepository extends BaseMapper<Document> {
+    
+}

+ 58 - 0
backend/document-service/src/main/java/com/lingyue/document/service/DocumentService.java

@@ -0,0 +1,58 @@
+package com.lingyue.document.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.lingyue.document.entity.Document;
+import com.lingyue.document.repository.DocumentRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 文档服务(基础框架)
+ */
+@Service
+@RequiredArgsConstructor
+public class DocumentService {
+    
+    private final DocumentRepository documentRepository;
+    
+    /**
+     * 根据ID获取文档
+     */
+    public Document getDocumentById(String documentId) {
+        return documentRepository.selectById(documentId);
+    }
+    
+    /**
+     * 根据用户ID分页查询文档
+     */
+    public IPage<Document> getDocumentsByUserId(String userId, Page<Document> page) {
+        LambdaQueryWrapper<Document> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(Document::getUserId, userId);
+        wrapper.orderByDesc(Document::getCreateTime);
+        return documentRepository.selectPage(page, wrapper);
+    }
+    
+    /**
+     * 保存文档
+     */
+    public Document saveDocument(Document document) {
+        if (document.getId() == null) {
+            document.setId(java.util.UUID.randomUUID().toString().replace("-", ""));
+            document.setCreateTime(new java.util.Date());
+            documentRepository.insert(document);
+        } else {
+            document.setUpdateTime(new java.util.Date());
+            documentRepository.updateById(document);
+        }
+        return document;
+    }
+    
+    /**
+     * 删除文档
+     */
+    public void deleteDocument(String documentId) {
+        documentRepository.deleteById(documentId);
+    }
+}

+ 57 - 0
backend/document-service/src/main/resources/application.yml

@@ -0,0 +1,57 @@
+server:
+  port: 8002
+
+spring:
+  application:
+    name: document-service
+  
+  # 数据库配置(Druid)
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    druid:
+      driver-class-name: org.postgresql.Driver
+      url: jdbc:postgresql://localhost:5432/lingyue_zhibao
+      username: ${DB_USERNAME:postgres}
+      password: ${DB_PASSWORD:postgres}
+      initial-size: 5
+      min-idle: 5
+      max-active: 20
+      max-wait: 60000
+      time-between-eviction-runs-millis: 60000
+      min-evictable-idle-time-millis: 300000
+      validation-query: SELECT 1
+      test-while-idle: true
+      test-on-borrow: false
+      test-on-return: false
+      pool-prepared-statements: true
+      max-pool-prepared-statement-per-connection-size: 20
+      filters: stat,wall,slf4j
+  
+  # MyBatis Plus配置
+  mybatis-plus:
+    mapper-locations: classpath*:mapper/**/*Mapper.xml
+    type-aliases-package: com.lingyue.document.entity
+    configuration:
+      map-underscore-to-camel-case: true
+      log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
+    global-config:
+      db-config:
+        id-type: assign_uuid
+    properties:
+      hibernate:
+        dialect: org.hibernate.dialect.PostgreSQLDialect
+  
+  # Nacos配置
+  cloud:
+    nacos:
+      discovery:
+        server-addr: ${NACOS_SERVER_ADDR:localhost:8848}
+        namespace: public
+        group: DEFAULT_GROUP
+
+# 日志配置
+logging:
+  level:
+    root: INFO
+    com.lingyue: DEBUG
+

+ 68 - 0
backend/gateway-service/pom.xml

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.lingyue</groupId>
+        <artifactId>lingyue-zhibao</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>gateway-service</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Gateway Service</name>
+    <description>API 网关服务 - Spring Cloud Gateway</description>
+
+    <dependencies>
+        <!-- Spring Cloud Gateway -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-gateway</artifactId>
+        </dependency>
+        
+        <!-- Spring Cloud LoadBalancer -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
+        </dependency>
+        
+        <!-- Nacos Service Discovery -->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+        </dependency>
+        
+        <!-- Sentinel Gateway -->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
+        </dependency>
+        
+        <!-- Common Module -->
+        <dependency>
+            <groupId>com.lingyue</groupId>
+            <artifactId>common</artifactId>
+        </dependency>
+        
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
+

+ 18 - 0
backend/gateway-service/src/main/java/com/lingyue/gateway/GatewayApplication.java

@@ -0,0 +1,18 @@
+package com.lingyue.gateway;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+/**
+ * API 网关服务启动类
+ */
+@SpringBootApplication
+@EnableDiscoveryClient
+public class GatewayApplication {
+    
+    public static void main(String[] args) {
+        SpringApplication.run(GatewayApplication.class, args);
+    }
+}
+

+ 43 - 0
backend/gateway-service/src/main/java/com/lingyue/gateway/config/CorsConfig.java

@@ -0,0 +1,43 @@
+package com.lingyue.gateway.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.reactive.CorsWebFilter;
+import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * CORS 配置
+ */
+@Configuration
+public class CorsConfig {
+    
+    @Bean
+    public CorsWebFilter corsWebFilter() {
+        CorsConfiguration corsConfig = new CorsConfiguration();
+        
+        // 允许的源(生产环境应配置具体域名)
+        corsConfig.setAllowedOriginPatterns(List.of("*"));
+        
+        // 允许的HTTP方法
+        corsConfig.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
+        
+        // 允许的请求头
+        corsConfig.setAllowedHeaders(List.of("*"));
+        
+        // 允许携带凭证
+        corsConfig.setAllowCredentials(true);
+        
+        // 预检请求的缓存时间(秒)
+        corsConfig.setMaxAge(3600L);
+        
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        source.registerCorsConfiguration("/**", corsConfig);
+        
+        return new CorsWebFilter(source);
+    }
+}
+

+ 110 - 0
backend/gateway-service/src/main/java/com/lingyue/gateway/filter/JwtAuthenticationFilter.java

@@ -0,0 +1,110 @@
+package com.lingyue.gateway.filter;
+
+import com.lingyue.common.util.JwtUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * JWT 认证过滤器
+ */
+@Slf4j
+@Component
+public class JwtAuthenticationFilter implements GlobalFilter, Ordered {
+    
+    /**
+     * JWT 密钥(从配置读取)
+     */
+    @Value("${jwt.secret:lingyue-zhibao-secret-key-2024-please-change-in-production}")
+    private String jwtSecret;
+    
+    /**
+     * 不需要认证的路径
+     */
+    private static final List<String> EXCLUDED_PATHS = Arrays.asList(
+        "/api/v1/auth/register",
+        "/api/v1/auth/login",
+        "/api/v1/auth/refresh"
+    );
+    
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+        ServerHttpRequest request = exchange.getRequest();
+        String path = request.getURI().getPath();
+        
+        // 检查是否为不需要认证的路径
+        if (isExcludedPath(path)) {
+            return chain.filter(exchange);
+        }
+        
+        // 从请求头获取Token
+        String token = getTokenFromRequest(request);
+        
+        if (!StringUtils.hasText(token)) {
+            log.warn("Missing token for path: {}", path);
+            return unauthorizedResponse(exchange);
+        }
+        
+        // 验证Token
+        if (!JwtUtil.validateToken(token, jwtSecret)) {
+            log.warn("Invalid token for path: {}", path);
+            return unauthorizedResponse(exchange);
+        }
+        
+        // 从Token中获取用户ID并添加到请求头
+        String userId = JwtUtil.getUserIdFromToken(token, jwtSecret);
+        if (userId != null) {
+            ServerHttpRequest modifiedRequest = request.mutate()
+                .header("X-User-Id", userId)
+                .build();
+            exchange = exchange.mutate().request(modifiedRequest).build();
+        }
+        
+        return chain.filter(exchange);
+    }
+    
+    /**
+     * 检查是否为不需要认证的路径
+     */
+    private boolean isExcludedPath(String path) {
+        return EXCLUDED_PATHS.stream().anyMatch(path::startsWith);
+    }
+    
+    /**
+     * 从请求头获取Token
+     */
+    private String getTokenFromRequest(ServerHttpRequest request) {
+        String bearerToken = request.getHeaders().getFirst("Authorization");
+        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
+            return bearerToken.substring(7);
+        }
+        return null;
+    }
+    
+    /**
+     * 返回未授权响应
+     */
+    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange) {
+        ServerHttpResponse response = exchange.getResponse();
+        response.setStatusCode(HttpStatus.UNAUTHORIZED);
+        return response.setComplete();
+    }
+    
+    @Override
+    public int getOrder() {
+        return -100;
+    }
+}
+

+ 11 - 0
backend/gateway-service/src/main/resources/application-dev.yml

@@ -0,0 +1,11 @@
+spring:
+  cloud:
+    nacos:
+      discovery:
+        server-addr: localhost:8848
+
+logging:
+  level:
+    root: INFO
+    com.lingyue: DEBUG
+

+ 14 - 0
backend/gateway-service/src/main/resources/application-prod.yml

@@ -0,0 +1,14 @@
+spring:
+  cloud:
+    nacos:
+      discovery:
+        server-addr: ${NACOS_SERVER_ADDR:nacos-server:8848}
+
+jwt:
+  secret: ${JWT_SECRET:lingyue-zhibao-secret-key-2024-please-change-in-production}
+
+logging:
+  level:
+    root: WARN
+    com.lingyue: INFO
+

+ 88 - 0
backend/gateway-service/src/main/resources/application.yml

@@ -0,0 +1,88 @@
+server:
+  port: 8080
+
+spring:
+  application:
+    name: gateway-service
+  
+  cloud:
+    gateway:
+      routes:
+        # 用户认证服务
+        - id: auth-service
+          uri: lb://auth-service
+          predicates:
+            - Path=/api/v1/auth/**
+          filters:
+            - StripPrefix=1
+        
+        # 文档管理服务
+        - id: document-service
+          uri: lb://document-service
+          predicates:
+            - Path=/api/v1/documents/**
+          filters:
+            - StripPrefix=1
+        
+        # 解析服务
+        - id: parse-service
+          uri: lb://parse-service
+          predicates:
+            - Path=/api/v1/parse/**
+          filters:
+            - StripPrefix=1
+        
+        # AI处理服务
+        - id: ai-service
+          uri: lb://ai-service
+          predicates:
+            - Path=/api/v1/ai/**
+          filters:
+            - StripPrefix=1
+        
+        # 关系网络服务
+        - id: graph-service
+          uri: lb://graph-service
+          predicates:
+            - Path=/api/v1/graphs/**
+          filters:
+            - StripPrefix=1
+        
+        # WebSocket服务(通过通知服务)
+        - id: notification-service
+          uri: lb://notification-service
+          predicates:
+            - Path=/ws/**
+          filters:
+            - StripPrefix=0
+      
+      # 全局CORS配置
+      globalcors:
+        cors-configurations:
+          '[/**]':
+            allowedOriginPatterns: "*"
+            allowedMethods: "*"
+            allowedHeaders: "*"
+            allowCredentials: true
+            maxAge: 3600
+  
+  # Nacos配置
+  cloud:
+    nacos:
+      discovery:
+        server-addr: localhost:8848
+        namespace: public
+        group: DEFAULT_GROUP
+
+# JWT配置
+jwt:
+  secret: lingyue-zhibao-secret-key-2024-please-change-in-production
+
+# 日志配置
+logging:
+  level:
+    root: INFO
+    com.lingyue: DEBUG
+  pattern:
+    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
+

+ 68 - 0
backend/graph-service/pom.xml

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.lingyue</groupId>
+        <artifactId>lingyue-zhibao</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>graph-service</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Graph Service</name>
+    <description>关系网络服务 - 关系网络构建、逻辑计算</description>
+
+    <dependencies>
+        <!-- Spring Boot Web -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        
+        <!-- MyBatis Plus -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+        
+        <!-- PostgreSQL Driver -->
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+        </dependency>
+        
+        <!-- Nacos Service Discovery -->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+        </dependency>
+        
+        <!-- Common Module -->
+        <dependency>
+            <groupId>com.lingyue</groupId>
+            <artifactId>common</artifactId>
+        </dependency>
+        
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
+

+ 18 - 0
backend/graph-service/src/main/java/com/lingyue/graph/GraphServiceApplication.java

@@ -0,0 +1,18 @@
+package com.lingyue.graph;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+/**
+ * 关系网络服务启动类
+ */
+@SpringBootApplication
+@EnableDiscoveryClient
+public class GraphServiceApplication {
+    
+    public static void main(String[] args) {
+        SpringApplication.run(GraphServiceApplication.class, args);
+    }
+}
+

+ 24 - 0
backend/graph-service/src/main/java/com/lingyue/graph/config/MyBatisPlusConfig.java

@@ -0,0 +1,24 @@
+package com.lingyue.graph.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * MyBatis Plus 配置
+ */
+@Configuration
+@MapperScan("com.lingyue.graph.repository")
+public class MyBatisPlusConfig {
+    
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
+        return interceptor;
+    }
+}
+

+ 44 - 0
backend/graph-service/src/main/java/com/lingyue/graph/controller/GraphController.java

@@ -0,0 +1,44 @@
+package com.lingyue.graph.controller;
+
+import com.lingyue.graph.service.GraphService;
+import com.lingyue.common.domain.AjaxResult;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 关系网络控制器(基础框架)
+ */
+@RestController
+@RequestMapping("/graphs")
+@RequiredArgsConstructor
+public class GraphController {
+    
+    private final GraphService graphService;
+    
+    /**
+     * 创建关系网络(待实现)
+     */
+    @PostMapping
+    public AjaxResult<?> createGraph() {
+        // TODO: 实现关系网络创建
+        return AjaxResult.success("关系网络创建接口待实现");
+    }
+    
+    /**
+     * 获取关系网络(待实现)
+     */
+    @GetMapping("/{graphId}")
+    public AjaxResult<?> getGraph(@PathVariable String graphId) {
+        // TODO: 实现关系网络查询
+        return AjaxResult.success("关系网络查询接口待实现");
+    }
+    
+    /**
+     * 计算关系网络(待实现)
+     */
+    @PostMapping("/{graphId}/calculate")
+    public AjaxResult<?> calculateGraph(@PathVariable String graphId) {
+        // TODO: 实现关系网络计算
+        return AjaxResult.success("关系网络计算接口待实现");
+    }
+}

+ 50 - 0
backend/graph-service/src/main/java/com/lingyue/graph/entity/Graph.java

@@ -0,0 +1,50 @@
+package com.lingyue.graph.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.lingyue.common.domain.entity.SimpleModel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 关系网络实体
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@TableName("graphs")
+@Schema(description = "关系网络实体")
+public class Graph extends SimpleModel {
+    
+    @Schema(description = "文档ID")
+    @TableField("document_id")
+    private String documentId;
+    
+    @Schema(description = "用户ID")
+    @TableField("user_id")
+    private String userId;
+    
+    @Schema(description = "名称")
+    @TableField("name")
+    private String name;
+    
+    @Schema(description = "节点")
+    @TableField(value = "nodes", typeHandler = com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler.class)
+    private Object nodes; // GraphNode数组
+    
+    @Schema(description = "边")
+    @TableField(value = "edges", typeHandler = com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler.class)
+    private Object edges; // GraphEdge数组
+    
+    @Schema(description = "计算结果")
+    @TableField(value = "calculation_result", typeHandler = com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler.class)
+    private Object calculationResult;
+    
+    @Schema(description = "计算状态")
+    @TableField("calculation_status")
+    private String calculationStatus; // pending/completed/failed
+    
+    @Schema(description = "元数据")
+    @TableField(value = "metadata", typeHandler = com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler.class)
+    private Object metadata;
+}

+ 13 - 0
backend/graph-service/src/main/java/com/lingyue/graph/repository/GraphRepository.java

@@ -0,0 +1,13 @@
+package com.lingyue.graph.repository;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.lingyue.graph.entity.Graph;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 关系网络Repository
+ */
+@Mapper
+public interface GraphRepository extends BaseMapper<Graph> {
+    
+}

+ 47 - 0
backend/graph-service/src/main/java/com/lingyue/graph/service/GraphService.java

@@ -0,0 +1,47 @@
+package com.lingyue.graph.service;
+
+import com.lingyue.graph.entity.Graph;
+import com.lingyue.graph.repository.GraphRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 关系网络服务(基础框架)
+ */
+@Service
+@RequiredArgsConstructor
+public class GraphService {
+    
+    private final GraphRepository graphRepository;
+    
+    /**
+     * 根据ID获取关系网络
+     */
+    public Graph getGraphById(String graphId) {
+        return graphRepository.selectById(graphId);
+    }
+    
+    /**
+     * 保存关系网络
+     */
+    public Graph saveGraph(Graph graph) {
+        if (graph.getId() == null) {
+            graph.setId(java.util.UUID.randomUUID().toString().replace("-", ""));
+            graph.setCreateTime(new java.util.Date());
+            graphRepository.insert(graph);
+        } else {
+            graph.setUpdateTime(new java.util.Date());
+            graphRepository.updateById(graph);
+        }
+        return graph;
+    }
+    
+    /**
+     * 删除关系网络
+     */
+    public void deleteGraph(String graphId) {
+        graphRepository.deleteById(graphId);
+    }
+    
+    // TODO: 实现关系网络计算逻辑
+}

+ 54 - 0
backend/graph-service/src/main/resources/application.yml

@@ -0,0 +1,54 @@
+server:
+  port: 8005
+
+spring:
+  application:
+    name: graph-service
+  
+  # 数据库配置(Druid)
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    druid:
+      driver-class-name: org.postgresql.Driver
+      url: jdbc:postgresql://localhost:5432/lingyue_zhibao
+      username: ${DB_USERNAME:postgres}
+      password: ${DB_PASSWORD:postgres}
+      initial-size: 5
+      min-idle: 5
+      max-active: 20
+      max-wait: 60000
+      time-between-eviction-runs-millis: 60000
+      min-evictable-idle-time-millis: 300000
+      validation-query: SELECT 1
+      test-while-idle: true
+      test-on-borrow: false
+      test-on-return: false
+      pool-prepared-statements: true
+      max-pool-prepared-statement-per-connection-size: 20
+      filters: stat,wall,slf4j
+  
+  # MyBatis Plus配置
+  mybatis-plus:
+    mapper-locations: classpath*:mapper/**/*Mapper.xml
+    type-aliases-package: com.lingyue.graph.entity
+    configuration:
+      map-underscore-to-camel-case: true
+      log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
+    global-config:
+      db-config:
+        id-type: assign_uuid
+  
+  # Nacos配置
+  cloud:
+    nacos:
+      discovery:
+        server-addr: ${NACOS_SERVER_ADDR:localhost:8848}
+        namespace: public
+        group: DEFAULT_GROUP
+
+# 日志配置
+logging:
+  level:
+    root: INFO
+    com.lingyue: DEBUG
+

+ 153 - 0
backend/lingyue-starter/README.md

@@ -0,0 +1,153 @@
+# 灵越智报 Starter
+
+## 概述
+
+`lingyue-starter` 是灵越智报的单体应用启动器,整合了所有微服务模块为一个可独立运行的应用。
+
+## 功能特性
+
+- **单体应用架构**: 整合所有微服务模块(auth、document、parse、ai、graph、notification)
+- **统一配置**: 集中管理所有服务的配置
+- **API文档**: 集成 SpringDoc OpenAPI(Swagger UI)
+- **请求日志**: 自动记录HTTP请求参数
+- **热部署**: 支持Spring Boot DevTools热部署
+- **国际化**: 支持多语言消息
+
+## 项目结构
+
+```
+lingyue-starter/
+├── pom.xml
+└── src/main/
+    ├── java/com/lingyue/
+    │   ├── LingyueApplication.java      # 主启动类
+    │   └── config/                      # 配置类
+    │       ├── SpringDocConfig.java     # API文档配置
+    │       ├── RequestParameterLoggingConfig.java  # 请求日志配置
+    │       └── interceptor/
+    │           └── RequestParameterLoggingInterceptor.java
+    └── resources/
+        ├── application.yml              # 主配置文件
+        ├── application-dev.yml          # 开发环境配置
+        ├── application-prod.yml         # 生产环境配置
+        ├── mybatis/
+        │   └── mybatis-config.xml      # MyBatis配置
+        ├── i18n/
+        │   └── messages.properties      # 国际化资源
+        └── banner.txt                   # 启动Banner
+```
+
+## 快速开始
+
+### 1. 编译项目
+
+```bash
+cd backend
+mvn clean install
+```
+
+### 2. 启动应用
+
+```bash
+cd lingyue-starter
+mvn spring-boot:run
+```
+
+或者直接运行jar包:
+
+```bash
+java -jar lingyue-starter.jar
+```
+
+### 3. 访问应用
+
+- **应用地址**: http://localhost:8000
+- **API文档**: http://localhost:8000/swagger-ui.html
+- **Druid监控**: http://localhost:8000/druid/
+
+## 配置说明
+
+### 应用配置
+
+在 `application.yml` 中配置:
+
+```yaml
+app:
+  name: 灵越智报
+  version: 2.0.0
+  uploadBaseDir: /tmp/lingyue-zhibao
+```
+
+### 数据库配置
+
+```yaml
+spring:
+  datasource:
+    druid:
+      url: jdbc:postgresql://localhost:5432/lingyue_zhibao
+      username: postgres
+      password: postgres
+```
+
+### API文档配置
+
+开发环境默认启用API文档,生产环境自动禁用:
+
+```yaml
+springdoc:
+  api-docs:
+    enabled: true  # dev环境启用,prod环境禁用
+```
+
+## 环境配置
+
+### 开发环境
+
+使用 `application-dev.yml`,包含本地开发配置。
+
+### 生产环境
+
+使用 `application-prod.yml`,通过环境变量配置:
+
+```bash
+export DB_USERNAME=postgres
+export DB_PASSWORD=your_password
+export JWT_SECRET=your_jwt_secret
+export REDIS_HOST=localhost
+export REDIS_PORT=6379
+```
+
+## API分组
+
+API文档按服务分组:
+
+- **auth-api**: 认证服务 API
+- **document-api**: 文档管理 API
+- **parse-api**: 解析服务 API
+- **ai-api**: AI处理服务 API
+- **graph-api**: 关系网络服务 API
+
+## 注意事项
+
+1. **服务发现**: 单体应用默认禁用Nacos服务发现(`spring.cloud.nacos.discovery.enabled: false`)
+2. **端口**: 默认端口8000,可在配置文件中修改
+3. **数据库**: 确保PostgreSQL数据库已创建并执行初始化脚本
+4. **Redis**: 确保Redis服务已启动
+5. **RabbitMQ**: 确保RabbitMQ服务已启动(如使用消息队列功能)
+
+## 与微服务架构的区别
+
+| 特性 | 微服务架构 | 单体应用(Starter) |
+|------|-----------|-------------------|
+| 部署方式 | 多个独立服务 | 单个应用 |
+| 服务发现 | 需要Nacos | 不需要 |
+| API网关 | 需要Gateway | 不需要 |
+| 配置管理 | 分散在各服务 | 统一配置 |
+| 适用场景 | 大型项目、分布式 | 中小型项目、快速开发 |
+
+## 开发建议
+
+1. **开发阶段**: 使用 `lingyue-starter` 快速开发和调试
+2. **生产环境**: 根据实际需求选择单体应用或微服务架构
+3. **迁移**: 可以轻松从单体应用迁移到微服务架构(代码无需修改)
+

+ 142 - 0
backend/lingyue-starter/pom.xml

@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.lingyue</groupId>
+        <artifactId>lingyue-zhibao</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>lingyue-starter</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Lingyue Starter</name>
+    <description>灵越智报启动器 - 整合所有微服务为单体应用</description>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    </properties>
+
+    <dependencies>
+        <!-- SpringDoc OpenAPI 文档 -->
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+        </dependency>
+
+        <!-- spring-boot-devtools -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <!-- 所有微服务模块 -->
+        <dependency>
+            <groupId>com.lingyue</groupId>
+            <artifactId>common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        
+        <dependency>
+            <groupId>com.lingyue</groupId>
+            <artifactId>auth-service</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        
+        <dependency>
+            <groupId>com.lingyue</groupId>
+            <artifactId>document-service</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        
+        <dependency>
+            <groupId>com.lingyue</groupId>
+            <artifactId>parse-service</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        
+        <dependency>
+            <groupId>com.lingyue</groupId>
+            <artifactId>ai-service</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        
+        <dependency>
+            <groupId>com.lingyue</groupId>
+            <artifactId>graph-service</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        
+        <dependency>
+            <groupId>com.lingyue</groupId>
+            <artifactId>notification-service</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <!-- AOP -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+        <!-- 测试所需 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <excludes>
+                    <exclude>**/*.sample</exclude>
+                </excludes>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <fork>true</fork>
+                    <excludes>
+                        <exclude>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                        </exclude>
+                    </excludes>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>3.3.0</version>
+                <configuration>
+                    <excludes>
+                        <exclude>**/logback.xml</exclude>
+                        <exclude>**/application*.yml</exclude>
+                        <exclude>**/application*.properties</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+</project>
+

+ 31 - 0
backend/lingyue-starter/src/main/java/com/lingyue/LingyueApplication.java

@@ -0,0 +1,31 @@
+package com.lingyue;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+/**
+ * 启动程序
+ * 
+ * 单体应用启动器,整合所有微服务模块
+ *
+ * @author lingyue
+ */
+@SpringBootApplication(scanBasePackages = "com.lingyue")
+@EnableScheduling
+@EnableAsync
+@EnableDiscoveryClient
+@EnableFeignClients(basePackages = "com.lingyue")
+public class LingyueApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(LingyueApplication.class, args);
+        System.out.println("(♥◠‿◠)ノ゙  灵越智报启动成功   ლ(´ڡ`ლ)゙  \n");
+    }
+
+}
+

+ 17 - 0
backend/lingyue-starter/src/main/java/com/lingyue/LingyueServletInitializer.java

@@ -0,0 +1,17 @@
+package com.lingyue;
+
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+/**
+ * web容器中进行部署
+ *
+ * @author lingyue
+ */
+public class LingyueServletInitializer extends SpringBootServletInitializer {
+    @Override
+    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+        return application.sources(LingyueApplication.class);
+    }
+}
+

+ 47 - 0
backend/lingyue-starter/src/main/java/com/lingyue/config/AppConfig.java

@@ -0,0 +1,47 @@
+package com.lingyue.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 应用配置
+ *
+ * @author lingyue
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "app")
+public class AppConfig {
+    
+    /**
+     * 应用名称
+     */
+    private String name;
+    
+    /**
+     * 应用版本
+     */
+    private String version;
+    
+    /**
+     * 版权年份
+     */
+    private String copyrightYear;
+    
+    /**
+     * 文件上传基础目录
+     */
+    private String uploadBaseDir;
+    
+    /**
+     * 是否启用IP地址获取
+     */
+    private Boolean addressEnabled;
+    
+    /**
+     * 验证码类型
+     */
+    private String captchaType;
+}
+

+ 35 - 0
backend/lingyue-starter/src/main/java/com/lingyue/config/RequestParameterLoggingConfig.java

@@ -0,0 +1,35 @@
+package com.lingyue.config;
+
+import com.lingyue.config.interceptor.RequestParameterLoggingInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * HTTP请求参数日志记录配置
+ *
+ * @author lingyue
+ */
+@Configuration
+public class RequestParameterLoggingConfig implements WebMvcConfigurer {
+
+    @Autowired
+    private RequestParameterLoggingInterceptor requestParameterLoggingInterceptor;
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(requestParameterLoggingInterceptor)
+                .addPathPatterns("/**")
+                .excludePathPatterns(
+                        "/swagger-ui/**",
+                        "/api-docs/**",
+                        "/v3/api-docs/**",
+                        "/webjars/**",
+                        "/error",
+                        "/favicon.ico",
+                        "/ws/**"
+                );
+    }
+}
+

+ 109 - 0
backend/lingyue-starter/src/main/java/com/lingyue/config/SpringDocConfig.java

@@ -0,0 +1,109 @@
+package com.lingyue.config;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import org.springdoc.core.models.GroupedOpenApi;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+
+/**
+ * SpringDoc OpenAPI 配置
+ * ⚠️ 仅在 dev 环境启用(双重保障机制)
+ *
+ * @author lingyue
+ */
+@Configuration
+@Profile("dev")  // 第一层保障:只在 dev profile 时加载
+@ConditionalOnProperty(
+        name = "springdoc.api-docs.enabled",
+        havingValue = "true",
+        matchIfMissing = false  // 第二层保障:必须明确启用
+)
+public class SpringDocConfig {
+
+    @Autowired
+    private AppConfig appConfig;
+
+    /**
+     * 自定义 OpenAPI 信息
+     */
+    @Bean
+    public OpenAPI customOpenAPI() {
+        return new OpenAPI()
+                .info(new Info()
+                        .title("灵越智报 API 文档")
+                        .description("灵越智报 v2.0 - 智能文档处理平台 API 接口文档<br/>" +
+                                "版本: " + appConfig.getVersion() + "<br/>" +
+                                "版权所有 © " + appConfig.getCopyrightYear())
+                        .version(appConfig.getVersion())
+                        .contact(new Contact()
+                                .name(appConfig.getName())
+                                .email("")))
+                ;
+    }
+
+    /**
+     * 认证服务 API 分组
+     */
+    @Bean
+    public GroupedOpenApi authApi() {
+        return GroupedOpenApi.builder()
+                .group("auth-api")
+                .displayName("认证服务 API")
+                .pathsToMatch("/auth/**")
+                .build();
+    }
+
+    /**
+     * 文档管理 API 分组
+     */
+    @Bean
+    public GroupedOpenApi documentApi() {
+        return GroupedOpenApi.builder()
+                .group("document-api")
+                .displayName("文档管理 API")
+                .pathsToMatch("/documents/**")
+                .build();
+    }
+
+    /**
+     * 解析服务 API 分组
+     */
+    @Bean
+    public GroupedOpenApi parseApi() {
+        return GroupedOpenApi.builder()
+                .group("parse-api")
+                .displayName("解析服务 API")
+                .pathsToMatch("/parse/**")
+                .build();
+    }
+
+    /**
+     * AI处理服务 API 分组
+     */
+    @Bean
+    public GroupedOpenApi aiApi() {
+        return GroupedOpenApi.builder()
+                .group("ai-api")
+                .displayName("AI处理服务 API")
+                .pathsToMatch("/ai/**")
+                .build();
+    }
+
+    /**
+     * 关系网络服务 API 分组
+     */
+    @Bean
+    public GroupedOpenApi graphApi() {
+        return GroupedOpenApi.builder()
+                .group("graph-api")
+                .displayName("关系网络服务 API")
+                .pathsToMatch("/graphs/**")
+                .build();
+    }
+}
+

+ 33 - 0
backend/lingyue-starter/src/main/java/com/lingyue/config/interceptor/RequestParameterLoggingInterceptor.java

@@ -0,0 +1,33 @@
+package com.lingyue.config.interceptor;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+/**
+ * 请求参数日志记录拦截器
+ *
+ * @author lingyue
+ */
+@Slf4j
+@Component
+public class RequestParameterLoggingInterceptor implements HandlerInterceptor {
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
+        if (log.isDebugEnabled()) {
+            String method = request.getMethod();
+            String uri = request.getRequestURI();
+            String queryString = request.getQueryString();
+            
+            log.debug("Request: {} {}", method, uri);
+            if (queryString != null) {
+                log.debug("Query Parameters: {}", queryString);
+            }
+        }
+        return true;
+    }
+}
+

+ 32 - 0
backend/lingyue-starter/src/main/resources/application-dev.yml

@@ -0,0 +1,32 @@
+# 开发环境配置
+spring:
+  datasource:
+    druid:
+      url: jdbc:postgresql://localhost:5432/lingyue_zhibao
+      username: postgres
+      password: postgres
+  
+  data:
+    redis:
+      host: localhost
+      port: 6379
+  
+  rabbitmq:
+    host: localhost
+    port: 5672
+    username: guest
+    password: guest
+
+jwt:
+  secret: lingyue-zhibao-secret-key-2024-please-change-in-production
+
+logging:
+  level:
+    root: INFO
+    com.lingyue: DEBUG
+
+# SpringDoc 开发环境启用
+springdoc:
+  api-docs:
+    enabled: true
+

+ 33 - 0
backend/lingyue-starter/src/main/resources/application-prod.yml

@@ -0,0 +1,33 @@
+# 生产环境配置
+spring:
+  datasource:
+    druid:
+      url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:lingyue_zhibao}
+      username: ${DB_USERNAME:postgres}
+      password: ${DB_PASSWORD:postgres}
+  
+  data:
+    redis:
+      host: ${REDIS_HOST:localhost}
+      port: ${REDIS_PORT:6379}
+      password: ${REDIS_PASSWORD:}
+  
+  rabbitmq:
+    host: ${RABBITMQ_HOST:localhost}
+    port: ${RABBITMQ_PORT:5672}
+    username: ${RABBITMQ_USERNAME:guest}
+    password: ${RABBITMQ_PASSWORD:guest}
+
+jwt:
+  secret: ${JWT_SECRET:lingyue-zhibao-secret-key-2024-please-change-in-production}
+
+logging:
+  level:
+    root: WARN
+    com.lingyue: INFO
+
+# SpringDoc 生产环境禁用
+springdoc:
+  api-docs:
+    enabled: false
+

+ 193 - 0
backend/lingyue-starter/src/main/resources/application.yml

@@ -0,0 +1,193 @@
+# 项目相关配置
+app:
+  # 名称
+  name: 灵越智报
+  # 版本
+  version: 2.0.0
+  # 版权年份
+  copyrightYear: 2024
+  # 文件路径 示例( Windows配置 D:/lingyue/uploadPath,Linux配置 /home/lingyue/uploadPath)
+  uploadBaseDir: /tmp/lingyue-zhibao
+  # 获取ip地址开关
+  addressEnabled: false
+  # 验证码类型 math 数字计算 char 字符验证
+  captchaType: math
+
+# 开发环境配置
+server:
+  # 服务器的HTTP端口
+  port: 8000
+  servlet:
+    # 应用的访问路径
+    context-path: /
+  tomcat:
+    # tomcat的URI编码
+    uri-encoding: UTF-8
+    # 连接数满后的排队数
+    accept-count: 1000
+    threads:
+      # tomcat最大线程数
+      max: 800
+      # Tomcat启动初始化的线程数
+      min-spare: 10
+
+# 日志配置
+logging:
+  level:
+    com.lingyue: info
+    org.springframework: warn
+    org.springframework.web: info
+
+# 用户配置
+user:
+  password:
+    # 密码最大错误次数
+    maxRetryCount: 5
+    # 密码锁定时间(默认10分钟)
+    lockTime: 10
+
+spring:
+  profiles:
+    active: dev
+  # 资源信息
+  messages:
+    # 国际化资源文件路径
+    basename: i18n/messages
+  # 文件上传
+  servlet:
+    multipart:
+      # 单个文件大小
+      max-file-size: 20MB
+      # 设置总上传的文件大小
+      max-request-size: 100MB
+  mvc:
+    pathmatch:
+      matching-strategy: ant-path-matcher
+  # 服务模块
+  devtools:
+    restart:
+      # 热部署开关
+      enabled: true
+  # 数据库配置(Druid)
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    druid:
+      driver-class-name: org.postgresql.Driver
+      url: jdbc:postgresql://localhost:5432/lingyue_zhibao
+      username: ${DB_USERNAME:postgres}
+      password: ${DB_PASSWORD:postgres}
+      initial-size: 5
+      min-idle: 5
+      max-active: 20
+      max-wait: 60000
+      time-between-eviction-runs-millis: 60000
+      min-evictable-idle-time-millis: 300000
+      validation-query: SELECT 1
+      test-while-idle: true
+      test-on-borrow: false
+      test-on-return: false
+      pool-prepared-statements: true
+      max-pool-prepared-statement-per-connection-size: 20
+      filters: stat,wall,slf4j
+      connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
+      # Druid监控配置
+      stat-view-servlet:
+        enabled: true
+        url-pattern: /druid/*
+        login-username: admin
+        login-password: admin123
+      web-stat-filter:
+        enabled: true
+        url-pattern: /*
+  # Redis配置
+  data:
+    redis:
+      host: ${REDIS_HOST:localhost}
+      port: ${REDIS_PORT:6379}
+      password: ${REDIS_PASSWORD:}
+      database: 0
+      timeout: 3000
+      lettuce:
+        pool:
+          max-active: 8
+          max-idle: 8
+          min-idle: 0
+  # RabbitMQ配置
+  rabbitmq:
+    host: ${RABBITMQ_HOST:localhost}
+    port: ${RABBITMQ_PORT:5672}
+    username: ${RABBITMQ_USERNAME:guest}
+    password: ${RABBITMQ_PASSWORD:guest}
+  # Nacos配置(单体应用可选)
+  cloud:
+    nacos:
+      discovery:
+        server-addr: ${NACOS_SERVER_ADDR:localhost:8848}
+        namespace: public
+        group: DEFAULT_GROUP
+        enabled: false  # 单体应用默认禁用服务发现
+
+# JWT配置
+jwt:
+  secret: ${JWT_SECRET:lingyue-zhibao-secret-key-2024-please-change-in-production}
+  expiration: 604800000  # 7天,单位:毫秒
+  refresh-expiration: 2592000000  # 30天,单位:毫秒
+
+# Token配置
+token:
+  # 令牌自定义标识
+  header: Authorization
+  # 令牌密钥
+  secret: ${JWT_SECRET:lingyue-zhibao-secret-key-2024-please-change-in-production}
+  # 令牌有效期(默认7天,单位:秒)
+  expireTime: 604800
+
+# MyBatis Plus 配置
+mybatis-plus:
+  # 搜索指定包别名
+  type-aliases-package: com.lingyue.**.entity
+  # 配置mapper的扫描,找到所有的mapper.xml映射文件
+  mapper-locations: classpath*:mapper/**/*Mapper.xml
+  # 加载全局的配置文件
+  config-location: classpath:mybatis/mybatis-config.xml
+  configuration:
+    map-underscore-to-camel-case: true
+    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
+  global-config:
+    db-config:
+      id-type: assign_uuid
+
+# SpringDoc OpenAPI 配置
+springdoc:
+  api-docs:
+    enabled: true
+    path: /api-docs
+  swagger-ui:
+    enabled: true
+    path: /swagger-ui.html
+
+# WebSocket配置
+websocket:
+  enabled: true
+  path: /ws
+  allowedOrigins: "*"
+
+# PaddleOCR配置
+paddleocr:
+  server-url: ${PADDLEOCR_SERVER_URL:http://localhost:8866}
+  timeout: 30000
+
+# DeepSeek API配置
+deepseek:
+  api-url: ${DEEPSEEK_API_URL:https://api.deepseek.com}
+  api-key: ${DEEPSEEK_API_KEY:}
+  timeout: 30000
+
+# 防止XSS攻击
+xss:
+  # 过滤开关
+  enabled: true
+  # 排除链接(多个用逗号分隔)
+  excludes: /auth/register,/auth/login
+  # 匹配链接
+  urlPatterns: /documents/*,/parse/*,/ai/*,/graphs/*

+ 10 - 0
backend/lingyue-starter/src/main/resources/banner.txt

@@ -0,0 +1,10 @@
+  _      _                         _     _     _      _       
+ | |    (_)                       (_)   | |   (_)    | |      
+ | |     _ _ __   __ _ _   _  ___  _ ___| |__  _ ___| |_ ___ 
+ | |    | | '_ \ / _` | | | |/ _ \| / __| '_ \| / __| __/ _ \
+ | |____| | | | | (_| | |_| |  __/| \__ \ | | | \__ \ ||  __/
+ |______|_|_| |_|\__, |\__,_|\___|_|___/_| |_|_|___/\__\___|
+                  __/ |                                      
+                 |___/                                       
+ :: 灵越智报 v2.0 ::        (Spring Boot ${spring-boot.version})
+

+ 8 - 0
backend/lingyue-starter/src/main/resources/i18n/messages.properties

@@ -0,0 +1,8 @@
+# 国际化资源文件
+user.login.success=登录成功
+user.logout.success=登出成功
+user.register.success=注册成功
+user.password.retry.limit.exceed=密码输入错误次数过多,请稍后再试
+user.password.not.match=用户不存在/密码错误
+user.not.exists=用户不存在
+

+ 77 - 0
backend/lingyue-starter/src/main/resources/logback-spring.xml

@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <!-- 定义日志文件的存储地址 -->
+    <property name="LOG_HOME" value="${LOG_PATH:-./logs}"/>
+    <property name="APP_NAME" value="lingyue-zhibao"/>
+
+    <!-- 控制台输出 -->
+    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <!-- 按照每天生成日志文件 -->
+    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!--日志文件输出的文件名 -->
+            <FileNamePattern>${LOG_HOME}/${APP_NAME}-%d{yyyy-MM-dd}.log</FileNamePattern>
+            <!--日志文件保留天数 -->
+            <MaxHistory>30</MaxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <!--日志文件最大的大小 -->
+        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
+            <MaxFileSize>10MB</MaxFileSize>
+        </triggeringPolicy>
+    </appender>
+
+    <!-- 错误日志文件 -->
+    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>ERROR</level>
+        </filter>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <FileNamePattern>${LOG_HOME}/${APP_NAME}-error-%d{yyyy-MM-dd}.log</FileNamePattern>
+            <MaxHistory>30</MaxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <!-- 异步输出 -->
+    <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
+        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
+        <discardingThreshold>0</discardingThreshold>
+        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
+        <queueSize>512</queueSize>
+        <appender-ref ref="FILE"/>
+    </appender>
+
+    <!-- 异步输出错误日志 -->
+    <appender name="ASYNC_ERROR" class="ch.qos.logback.classic.AsyncAppender">
+        <discardingThreshold>0</discardingThreshold>
+        <queueSize>512</queueSize>
+        <appender-ref ref="ERROR_FILE"/>
+    </appender>
+
+    <!-- 日志输出级别 -->
+    <root level="INFO">
+        <appender-ref ref="CONSOLE"/>
+        <appender-ref ref="ASYNC"/>
+        <appender-ref ref="ASYNC_ERROR"/>
+    </root>
+
+    <!-- 指定包的日志级别 -->
+    <logger name="com.lingyue" level="DEBUG"/>
+    <logger name="org.springframework" level="WARN"/>
+    <logger name="org.mybatis" level="DEBUG"/>
+    <logger name="com.alibaba.druid" level="INFO"/>
+</configuration>
+

+ 20 - 0
backend/lingyue-starter/src/main/resources/mybatis/mybatis-config.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE configuration
+PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-config.dtd">
+<configuration>
+    <!-- 全局参数 -->
+    <settings>
+        <!-- 使全局的映射器启用或禁用缓存 -->
+        <setting name="cacheEnabled"             value="true"   />
+        <!-- 允许JDBC 支持自动生成主键 -->
+        <setting name="useGeneratedKeys"         value="true"   />
+        <!-- 配置默认的执行器.SIMPLE就是普通执行器;REUSE执行器会重用预处理语句(prepared statements);BATCH执行器将重用语句并执行批量更新 -->
+        <setting name="defaultExecutorType"      value="SIMPLE" />
+        <!-- 指定 MyBatis 所用日志的具体实现 -->
+        <setting name="logImpl"                  value="SLF4J"  />
+        <!-- 使用驼峰命名法转换字段 -->
+        <setting name="mapUnderscoreToCamelCase" value="true"/>
+    </settings>
+</configuration>
+

+ 62 - 0
backend/notification-service/pom.xml

@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.lingyue</groupId>
+        <artifactId>lingyue-zhibao</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>notification-service</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Notification Service</name>
+    <description>通知服务 - WebSocket实时通信</description>
+
+    <dependencies>
+        <!-- Spring Boot Web -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        
+        <!-- Spring Boot WebSocket -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+        
+        <!-- Nacos Service Discovery -->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+        </dependency>
+        
+        <!-- Common Module -->
+        <dependency>
+            <groupId>com.lingyue</groupId>
+            <artifactId>common</artifactId>
+        </dependency>
+        
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
+

+ 18 - 0
backend/notification-service/src/main/java/com/lingyue/notification/NotificationServiceApplication.java

@@ -0,0 +1,18 @@
+package com.lingyue.notification;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+/**
+ * 通知服务启动类
+ */
+@SpringBootApplication
+@EnableDiscoveryClient
+public class NotificationServiceApplication {
+    
+    public static void main(String[] args) {
+        SpringApplication.run(NotificationServiceApplication.class, args);
+    }
+}
+

+ 32 - 0
backend/notification-service/src/main/java/com/lingyue/notification/config/WebSocketConfig.java

@@ -0,0 +1,32 @@
+package com.lingyue.notification.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.simp.config.MessageBrokerRegistry;
+import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
+import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
+import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
+
+/**
+ * WebSocket配置
+ */
+@Configuration
+@EnableWebSocketMessageBroker
+public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
+    
+    @Override
+    public void configureMessageBroker(MessageBrokerRegistry config) {
+        // 启用简单的消息代理,用于向客户端发送消息
+        config.enableSimpleBroker("/topic", "/queue");
+        // 设置客户端发送消息的前缀
+        config.setApplicationDestinationPrefixes("/app");
+    }
+    
+    @Override
+    public void registerStompEndpoints(StompEndpointRegistry registry) {
+        // 注册WebSocket端点
+        registry.addEndpoint("/ws")
+            .setAllowedOriginPatterns("*")
+            .withSockJS();
+    }
+}
+

+ 30 - 0
backend/notification-service/src/main/java/com/lingyue/notification/controller/NotificationController.java

@@ -0,0 +1,30 @@
+package com.lingyue.notification.controller;
+
+import com.lingyue.common.domain.AjaxResult;
+import lombok.RequiredArgsConstructor;
+import org.springframework.messaging.handler.annotation.MessageMapping;
+import org.springframework.messaging.handler.annotation.SendTo;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 通知控制器(基础框架)
+ */
+@Controller
+@RequiredArgsConstructor
+public class NotificationController {
+    
+    /**
+     * 处理订阅消息(待实现)
+     */
+    @MessageMapping("/subscribe")
+    @SendTo("/topic/notifications")
+    public String handleSubscribe(String message) {
+        // TODO: 实现订阅逻辑
+        return "Subscribed: " + message;
+    }
+    
+    // TODO: 实现解析进度推送
+    // TODO: 实现通用通知推送
+}
+

+ 21 - 0
backend/notification-service/src/main/resources/application.yml

@@ -0,0 +1,21 @@
+server:
+  port: 8006
+
+spring:
+  application:
+    name: notification-service
+  
+  # Nacos配置
+  cloud:
+    nacos:
+      discovery:
+        server-addr: ${NACOS_SERVER_ADDR:localhost:8848}
+        namespace: public
+        group: DEFAULT_GROUP
+
+# 日志配置
+logging:
+  level:
+    root: INFO
+    com.lingyue: DEBUG
+

+ 80 - 0
backend/parse-service/pom.xml

@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.lingyue</groupId>
+        <artifactId>lingyue-zhibao</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>parse-service</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Parse Service</name>
+    <description>解析服务 - OCR识别、文本提取、版面分析</description>
+
+    <dependencies>
+        <!-- Spring Boot Web -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        
+        <!-- MyBatis Plus -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+        
+        <!-- Spring Boot AMQP (RabbitMQ) -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-amqp</artifactId>
+        </dependency>
+        
+        <!-- PostgreSQL Driver -->
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+        </dependency>
+        
+        <!-- Nacos Service Discovery -->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+        </dependency>
+        
+        <!-- OpenFeign -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+        
+        <!-- Common Module -->
+        <dependency>
+            <groupId>com.lingyue</groupId>
+            <artifactId>common</artifactId>
+        </dependency>
+        
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
+

+ 20 - 0
backend/parse-service/src/main/java/com/lingyue/parse/ParseServiceApplication.java

@@ -0,0 +1,20 @@
+package com.lingyue.parse;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+
+/**
+ * 解析服务启动类
+ */
+@SpringBootApplication
+@EnableDiscoveryClient
+@EnableFeignClients
+public class ParseServiceApplication {
+    
+    public static void main(String[] args) {
+        SpringApplication.run(ParseServiceApplication.class, args);
+    }
+}
+

+ 24 - 0
backend/parse-service/src/main/java/com/lingyue/parse/config/MyBatisPlusConfig.java

@@ -0,0 +1,24 @@
+package com.lingyue.parse.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * MyBatis Plus 配置
+ */
+@Configuration
+@MapperScan("com.lingyue.parse.repository")
+public class MyBatisPlusConfig {
+    
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
+        return interceptor;
+    }
+}
+

+ 35 - 0
backend/parse-service/src/main/java/com/lingyue/parse/controller/ParseController.java

@@ -0,0 +1,35 @@
+package com.lingyue.parse.controller;
+
+import com.lingyue.parse.service.ParseService;
+import com.lingyue.common.domain.AjaxResult;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 解析控制器(基础框架)
+ */
+@RestController
+@RequestMapping("/parse")
+@RequiredArgsConstructor
+public class ParseController {
+    
+    private final ParseService parseService;
+    
+    /**
+     * 启动解析(待实现)
+     */
+    @PostMapping("/start")
+    public AjaxResult<?> startParse() {
+        // TODO: 实现解析启动逻辑
+        return AjaxResult.success("解析启动接口待实现");
+    }
+    
+    /**
+     * 查询解析状态(待实现)
+     */
+    @GetMapping("/status/{documentId}")
+    public AjaxResult<?> getParseStatus(@PathVariable String documentId) {
+        // TODO: 实现解析状态查询
+        return AjaxResult.success("解析状态查询接口待实现");
+    }
+}

+ 52 - 0
backend/parse-service/src/main/java/com/lingyue/parse/entity/ParseTask.java

@@ -0,0 +1,52 @@
+package com.lingyue.parse.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.lingyue.common.domain.entity.SimpleModel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 解析任务实体
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@TableName("parse_tasks")
+@Schema(description = "解析任务实体")
+public class ParseTask extends SimpleModel {
+    
+    @Schema(description = "文档ID")
+    @TableField("document_id")
+    private String documentId;
+    
+    @Schema(description = "状态")
+    @TableField("status")
+    private String status = "pending"; // pending/processing/completed/failed
+    
+    @Schema(description = "进度")
+    @TableField("progress")
+    private Integer progress = 0; // 0-100
+    
+    @Schema(description = "当前步骤")
+    @TableField("current_step")
+    private String currentStep;
+    
+    @Schema(description = "错误消息")
+    @TableField("error_message")
+    private String errorMessage;
+    
+    @Schema(description = "选项")
+    @TableField(value = "options", typeHandler = com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler.class)
+    private Object options;
+    
+    @Schema(description = "开始时间")
+    @TableField("started_at")
+    private Date startedAt;
+    
+    @Schema(description = "完成时间")
+    @TableField("completed_at")
+    private Date completedAt;
+}

+ 20 - 0
backend/parse-service/src/main/java/com/lingyue/parse/repository/ParseTaskRepository.java

@@ -0,0 +1,20 @@
+package com.lingyue.parse.repository;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.lingyue.parse.entity.ParseTask;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+/**
+ * 解析任务Repository
+ */
+@Mapper
+public interface ParseTaskRepository extends BaseMapper<ParseTask> {
+    
+    /**
+     * 根据文档ID查找解析任务
+     */
+    @Select("SELECT * FROM parse_tasks WHERE document_id = #{documentId}")
+    ParseTask findByDocumentId(@Param("documentId") String documentId);
+}

+ 49 - 0
backend/parse-service/src/main/java/com/lingyue/parse/service/ParseService.java

@@ -0,0 +1,49 @@
+package com.lingyue.parse.service;
+
+import com.lingyue.parse.entity.ParseTask;
+import com.lingyue.parse.repository.ParseTaskRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 解析服务(基础框架)
+ */
+@Service
+@RequiredArgsConstructor
+public class ParseService {
+    
+    private final ParseTaskRepository parseTaskRepository;
+    
+    /**
+     * 根据ID获取解析任务
+     */
+    public ParseTask getParseTaskById(String taskId) {
+        return parseTaskRepository.selectById(taskId);
+    }
+    
+    /**
+     * 根据文档ID获取解析任务
+     */
+    public ParseTask getParseTaskByDocumentId(String documentId) {
+        return parseTaskRepository.findByDocumentId(documentId);
+    }
+    
+    /**
+     * 保存解析任务
+     */
+    public ParseTask saveParseTask(ParseTask parseTask) {
+        if (parseTask.getId() == null) {
+            parseTask.setId(java.util.UUID.randomUUID().toString().replace("-", ""));
+            parseTask.setCreateTime(new java.util.Date());
+            parseTaskRepository.insert(parseTask);
+        } else {
+            parseTask.setUpdateTime(new java.util.Date());
+            parseTaskRepository.updateById(parseTask);
+        }
+        return parseTask;
+    }
+    
+    // TODO: 实现PaddleOCR调用逻辑
+    // TODO: 实现文本提取和版面分析
+    // TODO: 实现异步任务处理
+}

+ 66 - 0
backend/parse-service/src/main/resources/application.yml

@@ -0,0 +1,66 @@
+server:
+  port: 8003
+
+spring:
+  application:
+    name: parse-service
+  
+  # 数据库配置(Druid)
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    druid:
+      driver-class-name: org.postgresql.Driver
+      url: jdbc:postgresql://localhost:5432/lingyue_zhibao
+      username: ${DB_USERNAME:postgres}
+      password: ${DB_PASSWORD:postgres}
+      initial-size: 5
+      min-idle: 5
+      max-active: 20
+      max-wait: 60000
+      time-between-eviction-runs-millis: 60000
+      min-evictable-idle-time-millis: 300000
+      validation-query: SELECT 1
+      test-while-idle: true
+      test-on-borrow: false
+      test-on-return: false
+      pool-prepared-statements: true
+      max-pool-prepared-statement-per-connection-size: 20
+      filters: stat,wall,slf4j
+  
+  # MyBatis Plus配置
+  mybatis-plus:
+    mapper-locations: classpath*:mapper/**/*Mapper.xml
+    type-aliases-package: com.lingyue.parse.entity
+    configuration:
+      map-underscore-to-camel-case: true
+      log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
+    global-config:
+      db-config:
+        id-type: assign_uuid
+  
+  # RabbitMQ配置
+  rabbitmq:
+    host: ${RABBITMQ_HOST:localhost}
+    port: ${RABBITMQ_PORT:5672}
+    username: ${RABBITMQ_USERNAME:guest}
+    password: ${RABBITMQ_PASSWORD:guest}
+  
+  # Nacos配置
+  cloud:
+    nacos:
+      discovery:
+        server-addr: ${NACOS_SERVER_ADDR:localhost:8848}
+        namespace: public
+        group: DEFAULT_GROUP
+
+# PaddleOCR配置
+paddleocr:
+  server-url: ${PADDLEOCR_SERVER_URL:http://localhost:8866}
+  timeout: 30000
+
+# 日志配置
+logging:
+  level:
+    root: INFO
+    com.lingyue: DEBUG
+

+ 217 - 0
backend/pom.xml

@@ -0,0 +1,217 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.lingyue</groupId>
+    <artifactId>lingyue-zhibao</artifactId>
+    <version>2.0.0</version>
+    <packaging>pom</packaging>
+
+    <name>Lingyue Zhibao</name>
+    <description>灵越智报 v2.0 - 智能文档处理平台</description>
+
+    <modules>
+        <module>common</module>
+        <module>gateway-service</module>
+        <module>auth-service</module>
+        <module>document-service</module>
+        <module>parse-service</module>
+        <module>ai-service</module>
+        <module>graph-service</module>
+        <module>notification-service</module>
+        <module>lingyue-starter</module>
+    </modules>
+
+    <properties>
+        <java.version>17</java.version>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        
+        <!-- Spring Boot & Cloud Versions -->
+        <spring-boot.version>3.2.0</spring-boot.version>
+        <spring-cloud.version>2022.0.4</spring-cloud.version>
+        <spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version>
+        
+        <!-- Database -->
+        <postgresql.version>42.7.1</postgresql.version>
+        <druid.version>1.2.23</druid.version>
+        <mybatis-plus.version>3.5.11</mybatis-plus.version>
+        
+        <!-- JWT -->
+        <jjwt.version>0.11.5</jjwt.version>
+        
+        <!-- Other -->
+        <lombok.version>1.18.30</lombok.version>
+        <mapstruct.version>1.5.5.Final</mapstruct.version>
+        <hutool.version>5.8.35</hutool.version>
+        <fastjson.version>2.0.53</fastjson.version>
+        <springdoc.version>2.7.0</springdoc.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <!-- Spring Boot Dependencies -->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            
+            <!-- Spring Cloud Dependencies -->
+            <dependency>
+                <groupId>org.springframework.cloud</groupId>
+                <artifactId>spring-cloud-dependencies</artifactId>
+                <version>${spring-cloud.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            
+            <!-- Spring Cloud Alibaba Dependencies -->
+            <dependency>
+                <groupId>com.alibaba.cloud</groupId>
+                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
+                <version>${spring-cloud-alibaba.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            
+            <!-- PostgreSQL Driver -->
+            <dependency>
+                <groupId>org.postgresql</groupId>
+                <artifactId>postgresql</artifactId>
+                <version>${postgresql.version}</version>
+            </dependency>
+            
+            <!-- Druid 连接池 -->
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>druid-spring-boot-3-starter</artifactId>
+                <version>${druid.version}</version>
+            </dependency>
+            
+            <!-- MyBatis Plus -->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-bom</artifactId>
+                <version>${mybatis-plus.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            
+            <!-- JWT -->
+            <dependency>
+                <groupId>io.jsonwebtoken</groupId>
+                <artifactId>jjwt-api</artifactId>
+                <version>${jjwt.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.jsonwebtoken</groupId>
+                <artifactId>jjwt-impl</artifactId>
+                <version>${jjwt.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.jsonwebtoken</groupId>
+                <artifactId>jjwt-jackson</artifactId>
+                <version>${jjwt.version}</version>
+            </dependency>
+            
+            <!-- Lombok -->
+            <dependency>
+                <groupId>org.projectlombok</groupId>
+                <artifactId>lombok</artifactId>
+                <version>${lombok.version}</version>
+                <optional>true</optional>
+            </dependency>
+            
+            <!-- MapStruct -->
+            <dependency>
+                <groupId>org.mapstruct</groupId>
+                <artifactId>mapstruct</artifactId>
+                <version>${mapstruct.version}</version>
+            </dependency>
+            
+            <!-- Hutool 工具类 -->
+            <dependency>
+                <groupId>cn.hutool</groupId>
+                <artifactId>hutool-all</artifactId>
+                <version>${hutool.version}</version>
+            </dependency>
+            
+            <!-- FastJSON2 -->
+            <dependency>
+                <groupId>com.alibaba.fastjson2</groupId>
+                <artifactId>fastjson2</artifactId>
+                <version>${fastjson.version}</version>
+            </dependency>
+            
+            <!-- SpringDoc OpenAPI -->
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-starter-common</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+            
+            <!-- Common Module -->
+            <dependency>
+                <groupId>com.lingyue</groupId>
+                <artifactId>common</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-maven-plugin</artifactId>
+                    <version>${spring-boot.version}</version>
+                    <configuration>
+                        <excludes>
+                            <exclude>
+                                <groupId>org.projectlombok</groupId>
+                                <artifactId>lombok</artifactId>
+                            </exclude>
+                        </excludes>
+                    </configuration>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-compiler-plugin</artifactId>
+                    <version>3.11.0</version>
+                    <configuration>
+                        <source>${java.version}</source>
+                        <target>${java.version}</target>
+                        <encoding>${project.build.sourceEncoding}</encoding>
+                        <annotationProcessorPaths>
+                            <path>
+                                <groupId>org.projectlombok</groupId>
+                                <artifactId>lombok</artifactId>
+                                <version>${lombok.version}</version>
+                            </path>
+                            <path>
+                                <groupId>org.mapstruct</groupId>
+                                <artifactId>mapstruct-processor</artifactId>
+                                <version>${mapstruct.version}</version>
+                            </path>
+                        </annotationProcessorPaths>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+    </build>
+</project>
+

+ 203 - 0
backend/sql/init.sql

@@ -0,0 +1,203 @@
+-- 灵越智报 v2.0 数据库初始化脚本
+-- PostgreSQL 15+
+
+-- 创建数据库(如果不存在)
+-- CREATE DATABASE lingyue_zhibao;
+
+-- 连接到数据库
+-- \c lingyue_zhibao;
+
+-- 启用UUID扩展
+CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
+
+-- ============================================
+-- 1. 用户表 (users)
+-- ============================================
+CREATE TABLE IF NOT EXISTS users (
+    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+    username VARCHAR(50) UNIQUE NOT NULL,
+    email VARCHAR(100) UNIQUE NOT NULL,
+    password_hash VARCHAR(255) NOT NULL,
+    avatar_url VARCHAR(500),
+    role VARCHAR(20) NOT NULL DEFAULT 'user', -- admin/user/guest
+    preferences JSONB DEFAULT '{}',
+    last_login_at TIMESTAMP,
+    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
+CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
+
+-- ============================================
+-- 2. 文档表 (documents)
+-- ============================================
+CREATE TABLE IF NOT EXISTS documents (
+    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+    name VARCHAR(255) NOT NULL,
+    type VARCHAR(20) NOT NULL, -- pdf/word/image/markdown/other
+    status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending/uploading/parsing/completed/failed
+    file_size BIGINT,
+    file_url VARCHAR(500),
+    thumbnail_url VARCHAR(500),
+    parsed_text TEXT,
+    parse_status VARCHAR(20), -- pending/parsing/completed/failed
+    parse_progress INTEGER DEFAULT 0, -- 0-100
+    parse_error TEXT,
+    parse_started_at TIMESTAMP,
+    parse_completed_at TIMESTAMP,
+    metadata JSONB DEFAULT '{}', -- pageCount, ocrConfidence, layoutStructure等
+    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX IF NOT EXISTS idx_documents_user_id ON documents(user_id);
+CREATE INDEX IF NOT EXISTS idx_documents_status ON documents(status);
+CREATE INDEX IF NOT EXISTS idx_documents_type ON documents(type);
+CREATE INDEX IF NOT EXISTS idx_documents_created_at ON documents(created_at DESC);
+CREATE INDEX IF NOT EXISTS idx_documents_metadata ON documents USING GIN(metadata);
+CREATE INDEX IF NOT EXISTS idx_documents_parsed_text ON documents USING GIN(to_tsvector('english', parsed_text)); -- 全文搜索
+
+-- ============================================
+-- 3. 要素表 (elements)
+-- ============================================
+CREATE TABLE IF NOT EXISTS elements (
+    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+    document_id UUID NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
+    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+    type VARCHAR(20) NOT NULL, -- amount/company/person/location/date/other
+    label VARCHAR(100) NOT NULL,
+    value TEXT NOT NULL,
+    position JSONB, -- {page, x, y, width, height}
+    confidence DECIMAL(3,2), -- 0.00-1.00
+    extraction_method VARCHAR(20), -- ai/regex/rule/manual
+    graph_node_id UUID, -- 关联的图节点ID
+    metadata JSONB DEFAULT '{}',
+    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX IF NOT EXISTS idx_elements_document_id ON elements(document_id);
+CREATE INDEX IF NOT EXISTS idx_elements_user_id ON elements(user_id);
+CREATE INDEX IF NOT EXISTS idx_elements_type ON elements(type);
+CREATE INDEX IF NOT EXISTS idx_elements_graph_node_id ON elements(graph_node_id);
+CREATE INDEX IF NOT EXISTS idx_elements_position ON elements USING GIN(position);
+
+-- ============================================
+-- 4. 批注表 (annotations)
+-- ============================================
+CREATE TABLE IF NOT EXISTS annotations (
+    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+    document_id UUID NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
+    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+    text TEXT NOT NULL,
+    position JSONB NOT NULL, -- {page, start: {x, y}, end: {x, y}}
+    type VARCHAR(20) NOT NULL, -- highlight/strikethrough/suggestion
+    suggestion TEXT,
+    ai_generated BOOLEAN DEFAULT FALSE,
+    confidence DECIMAL(3,2),
+    status VARCHAR(20) DEFAULT 'pending', -- pending/accepted/rejected
+    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX IF NOT EXISTS idx_annotations_document_id ON annotations(document_id);
+CREATE INDEX IF NOT EXISTS idx_annotations_user_id ON annotations(user_id);
+CREATE INDEX IF NOT EXISTS idx_annotations_type ON annotations(type);
+CREATE INDEX IF NOT EXISTS idx_annotations_status ON annotations(status);
+
+-- ============================================
+-- 5. 关系网络表 (graphs)
+-- ============================================
+CREATE TABLE IF NOT EXISTS graphs (
+    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+    document_id UUID NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
+    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+    name VARCHAR(255) NOT NULL,
+    nodes JSONB NOT NULL DEFAULT '[]', -- GraphNode数组
+    edges JSONB NOT NULL DEFAULT '[]', -- GraphEdge数组
+    calculation_result JSONB,
+    calculation_status VARCHAR(20), -- pending/completed/failed
+    metadata JSONB DEFAULT '{}',
+    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX IF NOT EXISTS idx_graphs_document_id ON graphs(document_id);
+CREATE INDEX IF NOT EXISTS idx_graphs_user_id ON graphs(user_id);
+CREATE INDEX IF NOT EXISTS idx_graphs_nodes ON graphs USING GIN(nodes);
+CREATE INDEX IF NOT EXISTS idx_graphs_edges ON graphs USING GIN(edges);
+
+-- ============================================
+-- 6. 解析任务表 (parse_tasks)
+-- ============================================
+CREATE TABLE IF NOT EXISTS parse_tasks (
+    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+    document_id UUID NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
+    status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending/processing/completed/failed
+    progress INTEGER DEFAULT 0, -- 0-100
+    current_step VARCHAR(100),
+    error_message TEXT,
+    options JSONB DEFAULT '{}', -- 解析选项
+    started_at TIMESTAMP,
+    completed_at TIMESTAMP,
+    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX IF NOT EXISTS idx_parse_tasks_document_id ON parse_tasks(document_id);
+CREATE INDEX IF NOT EXISTS idx_parse_tasks_status ON parse_tasks(status);
+
+-- ============================================
+-- 7. 会话表 (sessions)
+-- ============================================
+CREATE TABLE IF NOT EXISTS sessions (
+    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+    token_hash VARCHAR(255) NOT NULL UNIQUE,
+    refresh_token_hash VARCHAR(255) NOT NULL UNIQUE,
+    expires_at TIMESTAMP NOT NULL,
+    ip_address VARCHAR(45),
+    user_agent TEXT,
+    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    last_used_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
+CREATE INDEX IF NOT EXISTS idx_sessions_token_hash ON sessions(token_hash);
+CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);
+
+-- ============================================
+-- 创建更新时间触发器函数
+-- ============================================
+CREATE OR REPLACE FUNCTION update_updated_at_column()
+RETURNS TRIGGER AS $$
+BEGIN
+    NEW.updated_at = CURRENT_TIMESTAMP;
+    RETURN NEW;
+END;
+$$ language 'plpgsql';
+
+-- 为所有表创建更新时间触发器
+CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users
+    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_documents_updated_at BEFORE UPDATE ON documents
+    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_elements_updated_at BEFORE UPDATE ON elements
+    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_annotations_updated_at BEFORE UPDATE ON annotations
+    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_graphs_updated_at BEFORE UPDATE ON graphs
+    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_parse_tasks_updated_at BEFORE UPDATE ON parse_tasks
+    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_sessions_updated_at BEFORE UPDATE ON sessions
+    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+

BIN
frontend/build/79dfa80770ef31b4122a7e6609b22b5c.cache.dill.track.dill


Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott