Ver código fonte

refactor: 统一配置文件为 application.properties 格式

- 新增 backend/application.properties(Java后端环境变量配置)
- 新增 python-services/ner-service/application.properties(Python NER服务配置)
- 新增 backend/lingyue-starter/src/main/resources/application.properties(替代 application.yml)
- 删除 backend/lingyue-starter/src/main/resources/application.yml
- start.sh: 从 backend/application.properties 加载环境变量
- Python config.py: env_file 改为读取 ner-service/application.properties
- docx_parser.py: 修复表格合并单元格重复渲染问题
- Login.vue: 添加 autocomplete 属性支持 Chrome 密码记录
何文松 18 horas atrás
pai
commit
306db586e9

+ 101 - 0
backend/application.properties

@@ -0,0 +1,101 @@
+# ==================== 服务器配置 ====================
+# 服务监听端口
+SERVER_PORT=8001
+# 服务监听地址
+SERVER_ADDRESS=127.0.0.1
+# 文件上传单个文件大小限制
+SERVER_MULTIPART_MAX_FILE_SIZE=100MB
+# 文件上传请求总大小限制
+SERVER_MULTIPART_MAX_REQUEST_SIZE=100MB
+# Tomcat最大工作线程数
+SERVER_TOMCAT_THREADS_MAX=200
+# Tomcat最小空闲线程数
+SERVER_TOMCAT_THREADS_MIN_SPARE=10
+
+# ==================== 数据库配置 (PostgreSQL) ====================
+# 数据库主机地址
+DB_HOST=127.0.0.1
+# 数据库端口号
+DB_PORT=5432
+# 数据库名称
+DB_NAME=lingyue_zhibao
+# 数据库用户名
+DB_USERNAME=postgres
+# 数据库密码
+DB_PASSWORD=postgres
+# HikariCP 连接池最小空闲连接数
+DB_POOL_MIN_IDLE=5
+# HikariCP 连接池最大连接数
+DB_POOL_MAX_SIZE=20
+# HikariCP 空闲连接超时时间(毫秒)
+DB_POOL_IDLE_TIMEOUT=30000
+# HikariCP 连接最大生命周期(毫秒)
+DB_POOL_MAX_LIFETIME=1800000
+# HikariCP 获取连接超时时间(毫秒)
+DB_POOL_CONNECTION_TIMEOUT=30000
+
+# ==================== Redis缓存配置 ====================
+# Redis服务器主机地址
+REDIS_HOST=127.0.0.1
+# Redis服务器端口号,默认6379
+REDIS_PORT=6379
+# Redis访问密码
+REDIS_PASSWORD=123123
+# Redis数据库索引(0-15)
+REDIS_DATABASE=0
+# Redis连接超时时间
+REDIS_TIMEOUT=10s
+# Lettuce连接池最小空闲连接数
+REDIS_POOL_MIN_IDLE=0
+# Lettuce连接池最大空闲连接数
+REDIS_POOL_MAX_IDLE=8
+# Lettuce连接池最大连接数
+REDIS_POOL_MAX_ACTIVE=8
+# Lettuce连接池最大阻塞等待时间(负值表示无限制)
+REDIS_POOL_MAX_WAIT=-1ms
+
+# ==================== JWT配置 ====================
+# JWT密钥(生产环境请务必修改,长度至少32位)
+JWT_SECRET=lingyue-zhibao-jwt-secret-key-must-be-at-least-32-characters
+# access token 过期时间(秒),默认24小时
+JWT_ACCESS_TOKEN_EXPIRE=86400
+# refresh token 过期时间(秒),默认7天
+JWT_REFRESH_TOKEN_EXPIRE=604800
+
+# ==================== 文件存储配置 ====================
+# 存储类型:local(本地磁盘)
+FILE_STORAGE_TYPE=local
+# 本地存储根路径
+FILE_LOCAL_PATH=/data/lingyue/files
+
+# ==================== 异步任务线程池配置 ====================
+# 核心线程数
+TASK_CORE_POOL_SIZE=5
+# 最大线程数
+TASK_MAX_POOL_SIZE=20
+# 任务队列容量
+TASK_QUEUE_CAPACITY=100
+
+# ==================== Python NER 服务地址 ====================
+# NER 服务对外地址(Java后端通过此地址调用Python服务)
+NER_SERVICE_URL=http://127.0.0.1:8002
+# NER 请求超时时间(毫秒)
+NER_TIMEOUT=30000
+
+# ==================== DeepSeek LLM 配置(Java后端) ====================
+# DeepSeek API 基础地址
+LLM_API_URL=https://api.deepseek.com/v1
+# DeepSeek API 密钥(必填)
+LLM_API_KEY=
+# 使用的模型名称
+LLM_MODEL=deepseek-chat
+# 最大生成令牌数
+LLM_MAX_TOKENS=4096
+# 温度参数(控制随机性,0-1,值越低输出越稳定)
+LLM_TEMPERATURE=0.7
+
+# ==================== 日志配置 ====================
+# 业务代码日志级别
+LOG_LEVEL_APP=debug
+# Spring框架日志级别
+LOG_LEVEL_SPRING=warn

+ 78 - 0
backend/lingyue-starter/src/main/resources/application.properties

@@ -0,0 +1,78 @@
+# ==================== 服务器配置 ====================
+server.port=${SERVER_PORT:8001}
+server.address=${SERVER_ADDRESS:127.0.0.1}
+server.servlet.multipart.max-file-size=${SERVER_MULTIPART_MAX_FILE_SIZE:100MB}
+server.servlet.multipart.max-request-size=${SERVER_MULTIPART_MAX_REQUEST_SIZE:100MB}
+server.tomcat.threads.max=${SERVER_TOMCAT_THREADS_MAX:200}
+server.tomcat.threads.min-spare=${SERVER_TOMCAT_THREADS_MIN_SPARE:10}
+
+# ==================== 应用基本信息 ====================
+spring.application.name=lingyue-zhibao
+spring.cloud.nacos.discovery.enabled=false
+spring.cloud.nacos.config.enabled=false
+spring.cloud.nacos.config.import-check.enabled=false
+
+# ==================== 数据库配置 (PostgreSQL) ====================
+spring.datasource.url=jdbc:postgresql://${DB_HOST:127.0.0.1}:${DB_PORT:5432}/${DB_NAME:lingyue_zhibao}
+spring.datasource.username=${DB_USERNAME:postgres}
+spring.datasource.password=${DB_PASSWORD:postgres}
+spring.datasource.driver-class-name=org.postgresql.Driver
+spring.datasource.hikari.minimum-idle=${DB_POOL_MIN_IDLE:5}
+spring.datasource.hikari.maximum-pool-size=${DB_POOL_MAX_SIZE:20}
+spring.datasource.hikari.idle-timeout=${DB_POOL_IDLE_TIMEOUT:30000}
+spring.datasource.hikari.max-lifetime=${DB_POOL_MAX_LIFETIME:1800000}
+spring.datasource.hikari.connection-timeout=${DB_POOL_CONNECTION_TIMEOUT:30000}
+
+# ==================== Redis缓存配置 ====================
+spring.data.redis.host=${REDIS_HOST:127.0.0.1}
+spring.data.redis.port=${REDIS_PORT:6379}
+spring.data.redis.password=${REDIS_PASSWORD:123123}
+spring.data.redis.database=${REDIS_DATABASE:0}
+spring.data.redis.timeout=${REDIS_TIMEOUT:10s}
+spring.data.redis.lettuce.pool.min-idle=${REDIS_POOL_MIN_IDLE:0}
+spring.data.redis.lettuce.pool.max-idle=${REDIS_POOL_MAX_IDLE:8}
+spring.data.redis.lettuce.pool.max-active=${REDIS_POOL_MAX_ACTIVE:8}
+spring.data.redis.lettuce.pool.max-wait=${REDIS_POOL_MAX_WAIT:-1ms}
+
+# ==================== MyBatis Plus ====================
+mybatis-plus.mapper-locations=classpath*:/mapper/**/*.xml
+mybatis-plus.configuration.map-underscore-to-camel-case=true
+mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.slf4j.Slf4jImpl
+mybatis-plus.global-config.db-config.id-type=auto
+mybatis-plus.global-config.db-config.logic-delete-field=deleted
+mybatis-plus.global-config.db-config.logic-delete-value=1
+mybatis-plus.global-config.db-config.logic-not-delete-value=0
+
+# ==================== JWT配置 ====================
+jwt.secret=${JWT_SECRET:lingyue-zhibao-jwt-secret-key-must-be-at-least-32-characters}
+jwt.access-token-expire=${JWT_ACCESS_TOKEN_EXPIRE:86400}
+jwt.refresh-token-expire=${JWT_REFRESH_TOKEN_EXPIRE:604800}
+
+# ==================== 文件存储配置 ====================
+file.storage.type=${FILE_STORAGE_TYPE:local}
+file.storage.local.base-path=${FILE_LOCAL_PATH:/data/lingyue/files}
+
+# ==================== 异步任务配置 ====================
+task.async.core-pool-size=${TASK_CORE_POOL_SIZE:5}
+task.async.max-pool-size=${TASK_MAX_POOL_SIZE:20}
+task.async.queue-capacity=${TASK_QUEUE_CAPACITY:100}
+
+# ==================== AI / NER 配置 ====================
+ai.ner.service-url=${NER_SERVICE_URL:http://127.0.0.1:8002}
+ai.ner.timeout=${NER_TIMEOUT:30000}
+ai.llm.api-url=${LLM_API_URL:https://api.deepseek.com/v1}
+ai.llm.api-key=${LLM_API_KEY:}
+ai.llm.model=${LLM_MODEL:deepseek-chat}
+ai.llm.max-tokens=${LLM_MAX_TOKENS:4096}
+ai.llm.temperature=${LLM_TEMPERATURE:0.7}
+
+# ==================== Knife4j API文档 ====================
+knife4j.enable=true
+knife4j.setting.language=zh_cn
+
+# ==================== 日志配置 ====================
+logging.level.com.lingyue=${LOG_LEVEL_APP:debug}
+logging.level.org.springframework=${LOG_LEVEL_SPRING:warn}
+
+# ==================== Actuator ====================
+management.endpoints.web.exposure.include=mappings,beans

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

@@ -1,90 +0,0 @@
-server:
-  port: 8001
-
-spring:
-  servlet:
-    multipart:
-      max-file-size: 100MB
-      max-request-size: 100MB
-  application:
-    name: lingyue-zhibao
-  cloud:
-    nacos:
-      discovery:
-        enabled: false
-      config:
-        enabled: false
-        import-check:
-          enabled: false
-  datasource:
-    url: jdbc:postgresql://${DB_HOST:127.0.0.1}:${DB_PORT:5432}/${DB_NAME:lingyue_zhibao}
-    username: ${DB_USERNAME:postgres}
-    password: ${DB_PASSWORD:postgres}
-    driver-class-name: org.postgresql.Driver
-    hikari:
-      minimum-idle: 5
-      maximum-pool-size: 20
-      idle-timeout: 30000
-      max-lifetime: 1800000
-      connection-timeout: 30000
-  data:
-    redis:
-      host: ${REDIS_HOST:127.0.0.1}
-      port: ${REDIS_PORT:6379}
-      password: ${REDIS_PASSWORD:123123}
-      database: 0
-
-mybatis-plus:
-  mapper-locations: classpath*:/mapper/**/*.xml
-  configuration:
-    map-underscore-to-camel-case: true
-    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
-  global-config:
-    db-config:
-      id-type: auto
-      logic-delete-field: deleted
-      logic-delete-value: 1
-      logic-not-delete-value: 0
-
-jwt:
-  secret: ${JWT_SECRET:lingyue-zhibao-jwt-secret-key-must-be-at-least-32-characters}
-  access-token-expire: 86400
-  refresh-token-expire: 604800
-
-file:
-  storage:
-    type: ${FILE_STORAGE_TYPE:local}
-    local:
-      base-path: ${FILE_LOCAL_PATH:/data/lingyue/files}
-
-ai:
-  ner:
-    service-url: ${NER_SERVICE_URL:http://127.0.0.1:5000}
-    timeout: 30000
-  llm:
-    api-url: ${LLM_API_URL:https://api.deepseek.com/v1}
-    api-key: ${LLM_API_KEY:}
-    model: ${LLM_MODEL:deepseek-chat}
-    max-tokens: 4096
-    temperature: 0.7
-
-task:
-  async:
-    core-pool-size: 5
-    max-pool-size: 20
-    queue-capacity: 100
-
-knife4j:
-  enable: true
-  setting:
-    language: zh_cn
-
-logging:
-  level:
-    com.lingyue: debug
-
-management:
-  endpoints:
-    web:
-      exposure:
-        include: mappings,beans

+ 2 - 0
frontend/vue-demo/src/views/Login.vue

@@ -29,6 +29,7 @@
               v-model="form.usernameOrEmail"
               placeholder="用户名或邮箱"
               :prefix-icon="User"
+              autocomplete="username"
               clearable
             />
           </el-form-item>
@@ -39,6 +40,7 @@
               type="password"
               placeholder="密码"
               :prefix-icon="Lock"
+              autocomplete="current-password"
               show-password
               @keyup.enter="handleLogin"
             />

+ 2 - 38
python-services/ner-service/.env.example

@@ -1,38 +1,2 @@
-# NER 服务环境配置
-
-# ============================================
-# NER 模型配置
-# ============================================
-# 可选值: rule / ollama
-# - rule: 基于规则的简单 NER(开发测试用,速度快但准确率低)
-# - ollama: 使用本地 Ollama LLM(准确率高,需要算力)
-NER_MODEL=ollama
-
-# ============================================
-# Ollama 配置
-# ============================================
-# Ollama 服务地址
-OLLAMA_URL=http://localhost:11434
-
-# 使用的模型
-# - hoangquan456/qwen3-nothink:4b(推荐,无思考模式直接输出)
-# - qwen3:8b(更强,需要更多内存)
-# - qwen2.5:7b(备选)
-OLLAMA_MODEL=hoangquan456/qwen3-nothink:4b
-
-# 请求超时时间(秒,CPU 模式需要更长)
-OLLAMA_TIMEOUT=180
-
-# ============================================
-# 文本分块配置(长文本处理)
-# ============================================
-CHUNK_SIZE=2000
-CHUNK_OVERLAP=200
-
-# ============================================
-# 日志和服务配置
-# ============================================
-LOG_LEVEL=INFO
-HOST=0.0.0.0
-PORT=8001
-DEBUG=false
+# 此文件已废弃,NER 服务配置已统一到项目根目录的 application.properties
+# 请直接修改:lingyue-zhibao/application.properties

+ 8 - 3
python-services/ner-service/app/config.py

@@ -2,9 +2,13 @@
 NER 服务配置
 """
 import os
+from pathlib import Path
 from pydantic_settings import BaseSettings
 from typing import Optional, List
 
+# Python 服务自身的 application.properties
+_APP_PROPS = Path(__file__).parents[2] / "application.properties"
+
 
 class Settings(BaseSettings):
     """应用配置"""
@@ -14,7 +18,7 @@ class Settings(BaseSettings):
     app_version: str = "1.0.0"
     debug: bool = False
     host: str = "0.0.0.0"
-    port: int = 8001
+    port: int = 8002
     
     # NER 模型配置
     # rule: 基于规则的简单 NER(开发测试用)
@@ -38,7 +42,7 @@ class Settings(BaseSettings):
     chunk_overlap: int = 200  # 分块重叠字符数
     
     # DeepSeek API 配置(阿里云百炼平台)
-    deepseek_api_key: str = "sk-14a0d0b9eda54b3bb6f0e55a7b8df084"
+    deepseek_api_key: str = ""
     deepseek_base_url: str = "https://dashscope.aliyuncs.com/compatible-mode"
     deepseek_model: str = "deepseek-v3.2-exp"
     deepseek_timeout: int = 600
@@ -67,8 +71,9 @@ class Settings(BaseSettings):
     log_level: str = "INFO"
     
     class Config:
-        env_file = ".env"
+        env_file = str(_APP_PROPS)
         env_file_encoding = "utf-8"
+        case_sensitive = False
 
 
 settings = Settings()

+ 14 - 2
python-services/ner-service/app/services/docx_parser.py

@@ -273,13 +273,25 @@ def _parse_paragraph(paragraph, block_id: int, images_map: Dict) -> Dict:
 
 
 def _parse_table(table, block_id: int) -> Dict:
-    """解析表格"""
+    """解析表格
+    
+    注意:python-docx 对于水平合并单元格,row.cells 会返回重复的 cell 对象引用。
+    例如:A 单元格合并了 3 列,row.cells 会返回 [A, A, A, B, C, ...]
+    需要通过跟踪 cell._tc 元素来去重,避免重复添加同一个单元格。
+    """
     rows_data = []
     for row in table.rows:
         cells_data = []
+        seen_tc = set()  # 跟踪已处理的单元格元素,避免重复
+        
         for cell in row.cells:
-            cell_text = cell.text.strip()
             tc = cell._tc
+            # 如果这个单元格元素已经处理过,跳过(合并单元格的重复引用)
+            if id(tc) in seen_tc:
+                continue
+            seen_tc.add(id(tc))
+            
+            cell_text = cell.text.strip()
             grid_span = tc.find(qn('w:tcPr'))
             colspan = 1
             if grid_span is not None:

+ 35 - 0
python-services/ner-service/application.properties

@@ -0,0 +1,35 @@
+# ==================== 服务端口 ====================
+# Python NER 服务端口
+PORT=8002
+
+# ==================== NER 模型配置 ====================
+# NER模式:rule(规则,快速)/ ollama(本地LLM)/ deepseek(推荐)
+NER_MODEL=deepseek
+
+# ==================== Ollama配置(NER_MODEL=ollama 时使用) ====================
+# Ollama服务地址
+OLLAMA_URL=http://localhost:11434
+# 使用的模型(推荐:无思考模式,速度快)
+OLLAMA_MODEL=hoangquan456/qwen3-nothink:4b
+# 请求超时时间(秒,CPU模式建议180+)
+OLLAMA_TIMEOUT=180
+# 文本分块大小(字符数)
+CHUNK_SIZE=2000
+# 分块重叠大小(字符数)
+CHUNK_OVERLAP=200
+
+# ==================== DeepSeek配置(NER_MODEL=deepseek 时使用) ====================
+# 阿里云百炼平台 API 密钥(必填)
+DEEPSEEK_API_KEY=
+# 百炼平台兼容模式地址
+DEEPSEEK_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode
+# NER 使用的 DeepSeek 模型
+DEEPSEEK_MODEL=deepseek-v3.2-exp
+# 请求超时时间(秒)
+DEEPSEEK_TIMEOUT=600
+# 最大重试次数
+DEEPSEEK_MAX_RETRIES=3
+
+# ==================== 日志配置 ====================
+LOG_LEVEL=INFO
+DEBUG=false

+ 27 - 2
start.sh

@@ -17,6 +17,7 @@ NC='\033[0m'
 PROJECT_DIR="/home/hws/workspace/GitLab/ay/lingyue-zhibao"
 BACKEND_DIR="${PROJECT_DIR}/backend"
 JAR_FILE="${BACKEND_DIR}/lingyue-starter/target/lingyue-starter.jar"
+APP_PROPS="${PROJECT_DIR}/backend/application.properties"
 
 log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
 log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
@@ -91,6 +92,28 @@ build_project() {
     fi
 }
 
+# 从 application.properties 加载环境变量
+load_app_props() {
+    if [ -f "$APP_PROPS" ]; then
+        log_info "加载配置: $APP_PROPS"
+        # 过滤注释行和空行,导出为环境变量
+        while IFS='=' read -r key value; do
+            # 跳过注释和空行
+            [[ "$key" =~ ^[[:space:]]*# ]] && continue
+            [[ -z "$key" ]] && continue
+            # 去除首尾空格
+            key=$(echo "$key" | xargs)
+            value=$(echo "$value" | xargs)
+            # 只导出大写字母开头的变量(环境变量风格,如 DB_HOST)
+            if [[ "$key" =~ ^[A-Z] ]]; then
+                export "$key=$value"
+            fi
+        done < "$APP_PROPS"
+    else
+        log_warn "未找到 application.properties,使用默认配置"
+    fi
+}
+
 # 启动应用
 start_app() {
     log_title "启动灵越智报"
@@ -108,7 +131,7 @@ start_app() {
     echo ""
     echo -e "${GREEN}================================================${NC}"
     echo -e "${GREEN}  灵越智报 2.0${NC}"
-    echo -e "${GREEN}  访问地址: http://localhost:8000${NC}"
+    echo -e "${GREEN}  访问地址: http://localhost:8001${NC}"
     echo -e "${GREEN}  按 Ctrl+C 停止应用${NC}"
     echo -e "${GREEN}================================================${NC}"
     echo ""
@@ -137,7 +160,7 @@ start_background() {
     if pgrep -f "lingyue-starter.jar" > /dev/null; then
         log_info "应用启动成功 ✓"
         log_info "日志文件: /tmp/lingyue.log"
-        log_info "访问地址: http://localhost:8000"
+        log_info "访问地址: http://localhost:8001"
     else
         log_error "应用启动失败,请查看日志: /tmp/lingyue.log"
     fi
@@ -235,6 +258,7 @@ EOF
 main() {
     case "${1:-}" in
         -d|daemon)
+            load_app_props
             check_dependencies
             check_ollama
             build_project
@@ -262,6 +286,7 @@ main() {
             show_help
             ;;
         "")
+            load_app_props
             check_dependencies
             check_ollama
             build_project