chore: 更新Docker和CI配置
- 更新Woodpecker CI配置 - 更新Docker Compose配置 - 更新应用主类配置 - 更新网关路由服务 - 更新审计日志相关代码
This commit is contained in:
+121
-67
@@ -1,99 +1,153 @@
|
||||
# Woodpecker CI/CD 流水线配置
|
||||
# TDD工作流规范 - 质量门禁配置
|
||||
# Woodpecker CI/CD 流水线配置 - 企业级质量门禁
|
||||
# 基于Docker化部署的完整CI/CD流水线
|
||||
|
||||
pipeline:
|
||||
# 后端单元测试和集成测试
|
||||
# 代码质量检查阶段
|
||||
code-quality:
|
||||
group: 质量检查
|
||||
image: maven:3.9-openjdk-21
|
||||
commands:
|
||||
- echo "🔍 开始代码质量检查..."
|
||||
- cd novalon-manage-api
|
||||
- echo "📊 运行静态代码分析..."
|
||||
- mvn spotbugs:check
|
||||
- echo "📏 检查代码规范..."
|
||||
- mvn checkstyle:check
|
||||
- echo "📈 生成代码质量报告..."
|
||||
- mvn pmd:check
|
||||
- echo "✅ 代码质量检查完成"
|
||||
when:
|
||||
event: [push, pull_request]
|
||||
|
||||
# 后端测试阶段
|
||||
test-backend:
|
||||
group: 后端测试
|
||||
image: maven:3.9-openjdk-21
|
||||
commands:
|
||||
- echo "🚀 开始后端测试..."
|
||||
- cd novalon-manage-api
|
||||
- echo "🧪 运行单元测试..."
|
||||
- mvn clean test jacoco:report
|
||||
- echo "✅ 后端测试完成,生成覆盖率报告"
|
||||
- echo "📊 生成测试覆盖率报告..."
|
||||
- mvn jacoco:check
|
||||
- echo "✅ 后端测试完成,覆盖率: $(cat target/site/jacoco/jacoco.xml | grep -oP 'lineCoverage=\"\K[0-9.]+')%"
|
||||
when:
|
||||
event: [push, pull_request]
|
||||
|
||||
# 构建后端JAR文件(用于E2E测试)
|
||||
build-backend-jar:
|
||||
image: maven:3.9-openjdk-21
|
||||
# 前端测试阶段
|
||||
test-frontend:
|
||||
group: 前端测试
|
||||
image: node:20
|
||||
commands:
|
||||
- echo "📦 构建后端JAR文件..."
|
||||
- cd novalon-manage-api/manage-app
|
||||
- mvn clean package -DskipTests
|
||||
- echo "✅ JAR文件构建完成: target/manage-app-1.0.0.jar"
|
||||
when:
|
||||
event: [push, pull_request]
|
||||
|
||||
# 前端单元测试
|
||||
test-frontend-unit:
|
||||
image: node:18
|
||||
commands:
|
||||
- echo "🚀 开始前端单元测试..."
|
||||
- echo "🚀 开始前端测试..."
|
||||
- cd novalon-manage-web
|
||||
- echo "📦 安装依赖..."
|
||||
- npm ci
|
||||
- echo "🧪 运行单元测试..."
|
||||
- npm run test:unit
|
||||
- echo "✅ 前端单元测试完成"
|
||||
- echo "📏 检查代码规范..."
|
||||
- npm run lint
|
||||
- echo "✅ 前端测试完成"
|
||||
when:
|
||||
event: [push, pull_request]
|
||||
|
||||
# 前端E2E测试
|
||||
test-frontend-e2e:
|
||||
image: mcr.microsoft.com/playwright:v1.40.0-jammy
|
||||
environment:
|
||||
- DISPLAY=:99
|
||||
# Docker化构建阶段
|
||||
docker-build:
|
||||
group: 容器化构建
|
||||
image: docker:24
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
commands:
|
||||
- echo "🚀 开始前端E2E测试..."
|
||||
- echo "🐳 开始Docker化构建..."
|
||||
- echo "📦 构建后端镜像..."
|
||||
- docker build -t novalon/backend:${CI_COMMIT_SHA:0:8} -f novalon-manage-api/Dockerfile ./novalon-manage-api
|
||||
- echo "🌐 构建前端镜像..."
|
||||
- docker build -t novalon/frontend:${CI_COMMIT_SHA:0:8} -f novalon-manage-web/Dockerfile ./novalon-manage-web
|
||||
- echo "✅ Docker镜像构建完成"
|
||||
when:
|
||||
event: [push]
|
||||
branch: [main, develop]
|
||||
|
||||
# 集成测试阶段(使用Docker Compose)
|
||||
integration-test:
|
||||
group: 集成测试
|
||||
image: docker:24
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
commands:
|
||||
- echo "🧪 开始集成测试..."
|
||||
- echo "🐳 启动测试环境..."
|
||||
- docker-compose -f docker-compose.test.yml up -d
|
||||
- echo "⏳ 等待服务就绪..."
|
||||
- sleep 60
|
||||
- echo "🔍 检查服务健康状态..."
|
||||
- curl -f http://localhost:8085/actuator/health || (docker-compose -f docker-compose.test.yml logs && exit 1)
|
||||
- curl -f http://localhost:3002 || (docker-compose -f docker-compose.test.yml logs && exit 1)
|
||||
- echo "✅ 集成测试环境就绪"
|
||||
when:
|
||||
event: [push]
|
||||
branch: [main, develop]
|
||||
|
||||
# E2E测试阶段
|
||||
e2e-test:
|
||||
group: E2E测试
|
||||
image: mcr.microsoft.com/playwright:v1.58.2-jammy
|
||||
commands:
|
||||
- echo "🎭 开始E2E测试..."
|
||||
- cd novalon-manage-web
|
||||
- echo "📦 安装依赖..."
|
||||
- npm ci
|
||||
- echo "🔧 安装浏览器..."
|
||||
- npx playwright install --with-deps chromium
|
||||
|
||||
- echo "📦 启动后端服务..."
|
||||
- cd ../novalon-manage-api/manage-app
|
||||
- java -jar target/manage-app-1.0.0.jar --spring.profiles.active=test &
|
||||
- BACKEND_PID=$!
|
||||
- cd ../../novalon-manage-web
|
||||
|
||||
- echo "⏳ 等待后端服务就绪..."
|
||||
- |
|
||||
for i in {1..60}; do
|
||||
if curl -f http://localhost:8084/actuator/health > /dev/null 2>&1; then
|
||||
echo "✅ 后端服务就绪"
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
- echo "🎭 运行Playwright测试..."
|
||||
- npx playwright test --project=chromium
|
||||
|
||||
- echo "🛑 停止后端服务..."
|
||||
- kill $BACKEND_PID || true
|
||||
|
||||
- echo "🧪 运行E2E测试..."
|
||||
- npx playwright test --project=journeys --reporter=html,json,junit
|
||||
- echo "✅ E2E测试完成"
|
||||
when:
|
||||
event: [push, pull_request]
|
||||
event: [push]
|
||||
branch: [main, develop]
|
||||
|
||||
# 质量门禁检查
|
||||
quality-gates:
|
||||
image: maven:3.9-openjdk-21
|
||||
# 安全扫描阶段
|
||||
security-scan:
|
||||
group: 安全扫描
|
||||
image: aquasec/trivy:latest
|
||||
commands:
|
||||
- echo "🔍 开始质量门禁检查..."
|
||||
- cd novalon-manage-api
|
||||
- mvn jacoco:check
|
||||
- echo "✅ 测试覆盖率检查通过"
|
||||
- echo "✅ 所有测试用例通过"
|
||||
- echo "✅ 代码规范检查通过"
|
||||
- echo "🔒 开始安全扫描..."
|
||||
- echo "📊 扫描后端镜像..."
|
||||
- trivy image novalon/backend:${CI_COMMIT_SHA:0:8}
|
||||
- echo "📊 扫描前端镜像..."
|
||||
- trivy image novalon/frontend:${CI_COMMIT_SHA:0:8}
|
||||
- echo "✅ 安全扫描完成"
|
||||
when:
|
||||
event: [pull_request]
|
||||
event: [push]
|
||||
branch: [main, develop]
|
||||
|
||||
# 构建阶段
|
||||
build:
|
||||
image: maven:3.9-openjdk-21
|
||||
# 部署阶段
|
||||
deploy:
|
||||
group: 部署
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- echo "📦 开始构建..."
|
||||
- cd novalon-manage-api
|
||||
- mvn clean package -DskipTests
|
||||
- echo "✅ 构建成功"
|
||||
- echo "🚀 开始部署..."
|
||||
- echo "📦 推送镜像到仓库..."
|
||||
- docker tag novalon/backend:${CI_COMMIT_SHA:0:8} ${DOCKER_REGISTRY}/novalon/backend:${CI_COMMIT_SHA:0:8}
|
||||
- docker tag novalon/frontend:${CI_COMMIT_SHA:0:8} ${DOCKER_REGISTRY}/novalon/frontend:${CI_COMMIT_SHA:0:8}
|
||||
- docker push ${DOCKER_REGISTRY}/novalon/backend:${CI_COMMIT_SHA:0:8}
|
||||
- docker push ${DOCKER_REGISTRY}/novalon/frontend:${CI_COMMIT_SHA:0:8}
|
||||
- echo "✅ 部署完成"
|
||||
when:
|
||||
event: [push]
|
||||
branch: [main]
|
||||
|
||||
# 清理阶段
|
||||
cleanup:
|
||||
group: 清理
|
||||
image: docker:24
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
commands:
|
||||
- echo "🧹 开始清理..."
|
||||
- docker-compose -f docker-compose.test.yml down -v
|
||||
- docker system prune -f
|
||||
- echo "✅ 清理完成"
|
||||
when:
|
||||
event: [push]
|
||||
branch: [main, develop]
|
||||
|
||||
+83
-89
@@ -1,118 +1,109 @@
|
||||
version: '3.8'
|
||||
|
||||
x-common-env: &common-env
|
||||
TZ: Asia/Shanghai
|
||||
LANG: zh_CN.UTF-8
|
||||
|
||||
services:
|
||||
# PostgreSQL 数据库(用于测试)
|
||||
# PostgreSQL数据库服务(测试环境)
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: novalon-test-db
|
||||
container_name: novalon-postgres-test
|
||||
environment:
|
||||
POSTGRES_DB: novalon_test
|
||||
POSTGRES_USER: novalon
|
||||
POSTGRES_PASSWORD: novalon123
|
||||
<<: *common-env
|
||||
POSTGRES_DB: manage_system_test
|
||||
POSTGRES_USER: novalon_test
|
||||
POSTGRES_PASSWORD: novalon_test123
|
||||
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=zh_CN.UTF-8"
|
||||
ports:
|
||||
- "5432:5432"
|
||||
- "55433:5432"
|
||||
volumes:
|
||||
- postgres_test_data:/var/lib/postgresql/data
|
||||
- ./novalon-manage-api/manage-db/src/main/resources/db/migration:/docker-entrypoint-initdb.d
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U novalon -d novalon_test"]
|
||||
test: ["CMD-SHELL", "pg_isready -U novalon_test -d manage_system_test"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- novalon-test-network
|
||||
|
||||
# Redis 缓存(可选)
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: novalon-test-redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- novalon-test-network
|
||||
|
||||
# 后端服务
|
||||
backend:
|
||||
build:
|
||||
context: ./novalon-manage-api
|
||||
dockerfile: Dockerfile
|
||||
container_name: novalon-test-backend
|
||||
environment:
|
||||
SPRING_PROFILES_ACTIVE: test
|
||||
SPRING_R2DBC_URL: r2dbc:postgresql://postgres:5432/novalon_test
|
||||
SPRING_R2DBC_USERNAME: novalon
|
||||
SPRING_R2DBC_PASSWORD: novalon123
|
||||
SPRING_FLYWAY_URL: jdbc:postgresql://postgres:5432/novalon_test
|
||||
SPRING_FLYWAY_USER: novalon
|
||||
SPRING_FLYWAY_PASSWORD: novalon123
|
||||
SPRING_DATA_REDIS_HOST: redis
|
||||
SPRING_DATA_REDIS_PORT: 6379
|
||||
ports:
|
||||
- "8084:8084"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8084/actuator/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
networks:
|
||||
- novalon-test-network
|
||||
|
||||
# 网关服务
|
||||
gateway:
|
||||
build:
|
||||
context: ./novalon-manage-api/manage-gateway
|
||||
dockerfile: Dockerfile
|
||||
container_name: novalon-test-gateway
|
||||
environment:
|
||||
SPRING_PROFILES_ACTIVE: test
|
||||
BACKEND_SERVICE_URL: http://backend:8084
|
||||
SPRING_DATA_REDIS_HOST: redis
|
||||
SPRING_DATA_REDIS_PORT: 6379
|
||||
ports:
|
||||
- "8080:8080"
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
networks:
|
||||
- novalon-test-network
|
||||
|
||||
# 前端服务(开发模式)
|
||||
# 后端API服务(测试环境)
|
||||
backend:
|
||||
build:
|
||||
context: ./novalon-manage-api
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- BUILD_VERSION=test
|
||||
container_name: novalon-backend-test
|
||||
environment:
|
||||
<<: *common-env
|
||||
SPRING_PROFILES_ACTIVE: test
|
||||
SPRING_R2DBC_URL: r2dbc:postgresql://postgres:5432/manage_system_test
|
||||
SPRING_R2DBC_USERNAME: novalon_test
|
||||
SPRING_R2DBC_PASSWORD: novalon_test123
|
||||
SPRING_JACKSON_TIME_ZONE: Asia/Shanghai
|
||||
MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE: health,info,metrics
|
||||
ports:
|
||||
- "8085:8084"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8084/actuator/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
networks:
|
||||
- novalon-test-network
|
||||
|
||||
# 前端Web服务(测试环境)
|
||||
frontend:
|
||||
build:
|
||||
context: ./novalon-manage-web
|
||||
dockerfile: Dockerfile.dev
|
||||
container_name: novalon-test-frontend
|
||||
environment:
|
||||
VITE_API_BASE_URL: http://gateway:8080
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- BUILD_VERSION=test
|
||||
container_name: novalon-frontend-test
|
||||
ports:
|
||||
- "3002:3002"
|
||||
volumes:
|
||||
- ./novalon-manage-web:/app
|
||||
- /app/node_modules
|
||||
- "3002:80"
|
||||
depends_on:
|
||||
gateway:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
<<: *common-env
|
||||
- VITE_API_BASE_URL=http://backend:8084
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:80"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
networks:
|
||||
- novalon-test-network
|
||||
|
||||
# E2E测试服务
|
||||
e2e-tests:
|
||||
build:
|
||||
context: ./novalon-manage-web
|
||||
dockerfile: Dockerfile.playwright
|
||||
container_name: novalon-e2e-tests
|
||||
depends_on:
|
||||
frontend:
|
||||
condition: service_healthy
|
||||
backend:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
<<: *common-env
|
||||
TEST_BASE_URL: http://frontend:80
|
||||
PLAYWRIGHT_HEADLESS: "true"
|
||||
CI: "true"
|
||||
networks:
|
||||
- novalon-test-network
|
||||
command: npm run test:e2e:journeys
|
||||
|
||||
volumes:
|
||||
postgres_test_data:
|
||||
driver: local
|
||||
@@ -120,3 +111,6 @@ volumes:
|
||||
networks:
|
||||
novalon-test-network:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.21.0.0/16
|
||||
+56
-2
@@ -1,37 +1,51 @@
|
||||
version: '3.8'
|
||||
|
||||
x-common-env: &common-env
|
||||
TZ: Asia/Shanghai
|
||||
LANG: zh_CN.UTF-8
|
||||
|
||||
services:
|
||||
# PostgreSQL数据库服务
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: novalon-postgres
|
||||
environment:
|
||||
<<: *common-env
|
||||
POSTGRES_DB: manage_system
|
||||
POSTGRES_USER: novalon
|
||||
POSTGRES_PASSWORD: novalon123
|
||||
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=zh_CN.UTF-8"
|
||||
ports:
|
||||
- "55432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./novalon-manage-api/manage-db/src/main/resources/db/migration:/docker-entrypoint-initdb.d
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U novalon -d manage_system"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
networks:
|
||||
- novalon-network
|
||||
restart: unless-stopped
|
||||
|
||||
# 后端API服务
|
||||
backend:
|
||||
build:
|
||||
context: ./novalon-manage-api
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- BUILD_VERSION=${BUILD_VERSION:-latest}
|
||||
container_name: novalon-backend
|
||||
environment:
|
||||
<<: *common-env
|
||||
SPRING_PROFILES_ACTIVE: docker
|
||||
SPRING_R2DBC_URL: r2dbc:postgresql://postgres:5432/manage_system
|
||||
SPRING_R2DBC_USERNAME: novalon
|
||||
SPRING_R2DBC_PASSWORD: novalon123
|
||||
SPRING_JACKSON_TIME_ZONE: Asia/Shanghai
|
||||
MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE: health,info,metrics
|
||||
ports:
|
||||
- "8084:8084"
|
||||
depends_on:
|
||||
@@ -42,15 +56,23 @@ services:
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
start_period: 60s
|
||||
networks:
|
||||
- novalon-network
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# 前端Web服务
|
||||
frontend:
|
||||
build:
|
||||
context: ./novalon-manage-web
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- BUILD_VERSION=${BUILD_VERSION:-latest}
|
||||
container_name: novalon-frontend
|
||||
ports:
|
||||
- "3001:80"
|
||||
@@ -58,7 +80,8 @@ services:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- VITE_API_BASE_URL=http://backend:8084
|
||||
<<: *common-env
|
||||
VITE_API_BASE_URL: http://backend:8084
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:80"]
|
||||
interval: 30s
|
||||
@@ -67,11 +90,42 @@ services:
|
||||
start_period: 40s
|
||||
networks:
|
||||
- novalon-network
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# Redis缓存服务(可选)
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: novalon-redis
|
||||
environment:
|
||||
<<: *common-env
|
||||
ports:
|
||||
- "6379:6379"
|
||||
command: redis-server --appendonly yes --requirepass novalon123
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
networks:
|
||||
- novalon-network
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
driver: local
|
||||
redis_data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
novalon-network:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.20.0.0/16
|
||||
@@ -1,22 +1,49 @@
|
||||
FROM maven:3.9-eclipse-temurin-17 AS builder
|
||||
# 多阶段构建优化Dockerfile
|
||||
FROM maven:3.9-eclipse-temurin-21 AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 复制Maven配置文件和源码
|
||||
COPY pom.xml .
|
||||
COPY mvnw .
|
||||
COPY mvnw.cmd .
|
||||
COPY .mvn .mvn
|
||||
COPY src ./src
|
||||
|
||||
RUN chmod +x mvnw
|
||||
# 下载依赖(利用Docker缓存层)
|
||||
RUN ./mvnw dependency:go-offline -B
|
||||
|
||||
# 复制源码并构建
|
||||
COPY src ./src
|
||||
RUN ./mvnw clean package -DskipTests
|
||||
|
||||
FROM openjdk:17-slim
|
||||
# 运行时镜像
|
||||
FROM eclipse-temurin:21-jre-jammy
|
||||
|
||||
# 设置时区和语言环境
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 创建非root用户运行应用
|
||||
RUN groupadd -r novalon && useradd -r -g novalon novalon
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/target/*.jar app.jar
|
||||
# 复制构建产物
|
||||
COPY --from=builder --chown=novalon:novalon /app/target/*.jar app.jar
|
||||
|
||||
# 设置JVM参数优化
|
||||
ENV JAVA_OPTS="-Xmx512m -Xms256m -XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:+UseContainerSupport -Djava.security.egd=file:/dev/./urandom"
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 8084
|
||||
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
# 切换用户
|
||||
USER novalon
|
||||
|
||||
# 健康检查
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
CMD curl -f http://localhost:8084/actuator/health || exit 1
|
||||
|
||||
# 启动命令
|
||||
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
|
||||
+3
-3
@@ -9,9 +9,7 @@ import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
|
||||
|
||||
@SpringBootApplication(exclude = {ReactiveUserDetailsServiceAutoConfiguration.class})
|
||||
@ConfigurationPropertiesScan(basePackages = "cn.novalon.manage")
|
||||
@ComponentScan(basePackages = "cn.novalon.manage")
|
||||
@SpringBootApplication(scanBasePackages = "cn.novalon.manage", exclude = {ReactiveUserDetailsServiceAutoConfiguration.class})
|
||||
@EnableR2dbcRepositories(basePackages = {"cn.novalon.manage.db.dao", "cn.novalon.manage.sys.audit.repository"})
|
||||
public class ManageApplication {
|
||||
|
||||
@@ -20,6 +18,8 @@ public class ManageApplication {
|
||||
public static void main(String[] args) {
|
||||
logger.info("应用程序启动中...");
|
||||
logger.info("包扫描路径: cn.novalon.manage");
|
||||
|
||||
// 使用简单的启动方式,避免自动配置问题
|
||||
SpringApplication.run(ManageApplication.class, args);
|
||||
logger.info("应用程序启动完成");
|
||||
}
|
||||
|
||||
@@ -20,7 +20,11 @@ spring:
|
||||
password: ${DB_PASSWORD:postgres}
|
||||
driver-class-name: org.postgresql.Driver
|
||||
flyway:
|
||||
enabled: false
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
baseline-on-migrate: true
|
||||
baseline-version: 0
|
||||
validate-on-migrate: true
|
||||
security:
|
||||
user:
|
||||
name: disabled
|
||||
|
||||
+9
-15
@@ -74,11 +74,9 @@ public class DynamicRouteService implements IDynamicRouteService {
|
||||
logger.info("Adding route: {}", routeId);
|
||||
|
||||
return routeDefinitionWriter.save(Mono.just(routeDefinition))
|
||||
.then(Mono.fromRunnable(() -> {
|
||||
routeCache.put(routeId, routeDefinition);
|
||||
refreshRoutes();
|
||||
logger.info("Route added successfully: {}", routeId);
|
||||
}))
|
||||
.then(Mono.fromRunnable(() -> routeCache.put(routeId, routeDefinition)))
|
||||
.then(refreshRoutes())
|
||||
.then(Mono.fromRunnable(() -> logger.info("Route added successfully: {}", routeId)))
|
||||
.thenReturn(true)
|
||||
.onErrorResume(error -> {
|
||||
logger.error("Failed to add route: {}", routeId, error);
|
||||
@@ -104,11 +102,9 @@ public class DynamicRouteService implements IDynamicRouteService {
|
||||
|
||||
return routeDefinitionWriter.delete(Mono.just(routeId))
|
||||
.then(routeDefinitionWriter.save(Mono.just(routeDefinition)))
|
||||
.then(Mono.fromRunnable(() -> {
|
||||
routeCache.put(routeId, routeDefinition);
|
||||
refreshRoutes();
|
||||
logger.info("Route updated successfully: {}", routeId);
|
||||
}))
|
||||
.then(Mono.fromRunnable(() -> routeCache.put(routeId, routeDefinition)))
|
||||
.then(refreshRoutes())
|
||||
.then(Mono.fromRunnable(() -> logger.info("Route updated successfully: {}", routeId)))
|
||||
.thenReturn(true)
|
||||
.onErrorResume(error -> {
|
||||
logger.error("Failed to update route: {}", routeId, error);
|
||||
@@ -131,11 +127,9 @@ public class DynamicRouteService implements IDynamicRouteService {
|
||||
logger.info("Deleting route: {}", routeId);
|
||||
|
||||
return routeDefinitionWriter.delete(Mono.just(routeId))
|
||||
.then(Mono.fromRunnable(() -> {
|
||||
routeCache.remove(routeId);
|
||||
refreshRoutes();
|
||||
logger.info("Route deleted successfully: {}", routeId);
|
||||
}))
|
||||
.then(Mono.fromRunnable(() -> routeCache.remove(routeId)))
|
||||
.then(refreshRoutes())
|
||||
.then(Mono.fromRunnable(() -> logger.info("Route deleted successfully: {}", routeId)))
|
||||
.thenReturn(true)
|
||||
.onErrorResume(error -> {
|
||||
logger.error("Failed to delete route: {}", routeId, error);
|
||||
|
||||
+15
-4
@@ -1,5 +1,6 @@
|
||||
package cn.novalon.manage.gateway.route;
|
||||
|
||||
import cn.novalon.manage.gateway.service.impl.DynamicRouteService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -25,7 +26,7 @@ import static org.mockito.Mockito.*;
|
||||
* 涉及业务:路由增删改查、路由刷新
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-26
|
||||
* @date 2026-04-14
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DynamicRouteServiceTest {
|
||||
@@ -78,7 +79,15 @@ class DynamicRouteServiceTest {
|
||||
@Test
|
||||
void testDeleteRoute_Success() {
|
||||
String routeId = "test-route";
|
||||
RouteDefinition routeDefinition = createRouteDefinition(routeId);
|
||||
|
||||
// 先添加路由到缓存中
|
||||
when(routeDefinitionWriter.save(any())).thenReturn(Mono.empty());
|
||||
StepVerifier.create(dynamicRouteService.addRoute(routeDefinition))
|
||||
.expectNext(true)
|
||||
.verifyComplete();
|
||||
|
||||
// 然后删除路由
|
||||
when(routeDefinitionWriter.delete(any())).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(dynamicRouteService.deleteRoute(routeId))
|
||||
@@ -86,7 +95,7 @@ class DynamicRouteServiceTest {
|
||||
.verifyComplete();
|
||||
|
||||
verify(routeDefinitionWriter).delete(any());
|
||||
verify(publisher).publishEvent(any(RefreshRoutesEvent.class));
|
||||
verify(publisher, times(2)).publishEvent(any(RefreshRoutesEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -113,7 +122,7 @@ class DynamicRouteServiceTest {
|
||||
.expectNext(true)
|
||||
.verifyComplete();
|
||||
|
||||
StepVerifier.create(dynamicRouteService.getAllRoutes().collectList())
|
||||
StepVerifier.create(dynamicRouteService.getRoutes().collectList())
|
||||
.assertNext(routes -> {
|
||||
assertNotNull(routes);
|
||||
assertTrue(routes.size() >= 2);
|
||||
@@ -131,7 +140,9 @@ class DynamicRouteServiceTest {
|
||||
.expectNext(true)
|
||||
.verifyComplete();
|
||||
|
||||
assertTrue(dynamicRouteService.getRouteCount() >= 1);
|
||||
StepVerifier.create(dynamicRouteService.getRouteCount())
|
||||
.assertNext(count -> assertTrue(count >= 1))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
private RouteDefinition createRouteDefinition(String id) {
|
||||
|
||||
+4
-8
@@ -48,8 +48,7 @@ public class AuditLogController {
|
||||
if (request.getEntityType() != null && request.getEntityId() != null) {
|
||||
return auditLogService.findByEntityTypeAndEntityId(
|
||||
request.getEntityType(),
|
||||
request.getEntityId()
|
||||
);
|
||||
request.getEntityId());
|
||||
} else if (request.getEntityType() != null) {
|
||||
return auditLogService.findByEntityType(request.getEntityType());
|
||||
} else if (request.getOperator() != null) {
|
||||
@@ -59,8 +58,7 @@ public class AuditLogController {
|
||||
} else if (request.getStartTime() != null && request.getEndTime() != null) {
|
||||
return auditLogService.findByOperationTimeBetween(
|
||||
request.getStartTime(),
|
||||
request.getEndTime()
|
||||
);
|
||||
request.getEndTime());
|
||||
}
|
||||
|
||||
return Flux.empty();
|
||||
@@ -97,10 +95,8 @@ public class AuditLogController {
|
||||
@GetMapping("/time-range")
|
||||
@Operation(summary = "按时间范围查询", description = "根据时间范围查询审计日志")
|
||||
public Flux<AuditLog> findByTimeRange(
|
||||
@Parameter(description = "开始时间")
|
||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startTime,
|
||||
@Parameter(description = "结束时间")
|
||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endTime) {
|
||||
@Parameter(description = "开始时间") @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startTime,
|
||||
@Parameter(description = "结束时间") @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endTime) {
|
||||
return auditLogService.findByOperationTimeBetween(startTime, endTime);
|
||||
}
|
||||
|
||||
|
||||
+26
@@ -138,4 +138,30 @@ public class AuditLog extends BaseDomain {
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
AuditLog auditLog = (AuditLog) o;
|
||||
return java.util.Objects.equals(entityType, auditLog.entityType) &&
|
||||
java.util.Objects.equals(entityId, auditLog.entityId) &&
|
||||
java.util.Objects.equals(operationType, auditLog.operationType) &&
|
||||
java.util.Objects.equals(operator, auditLog.operator) &&
|
||||
java.util.Objects.equals(operationTime, auditLog.operationTime) &&
|
||||
java.util.Objects.equals(beforeData, auditLog.beforeData) &&
|
||||
java.util.Objects.equals(afterData, auditLog.afterData) &&
|
||||
java.util.Arrays.equals(changedFields, auditLog.changedFields) &&
|
||||
java.util.Objects.equals(ipAddress, auditLog.ipAddress) &&
|
||||
java.util.Objects.equals(userAgent, auditLog.userAgent) &&
|
||||
java.util.Objects.equals(description, auditLog.description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return java.util.Objects.hash(super.hashCode(), entityType, entityId, operationType, operator,
|
||||
operationTime, beforeData, afterData, java.util.Arrays.hashCode(changedFields),
|
||||
ipAddress, userAgent, description);
|
||||
}
|
||||
}
|
||||
|
||||
+34
@@ -100,4 +100,38 @@ public class AuditLogQueryRequest {
|
||||
public void setSize(Integer size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
AuditLogQueryRequest that = (AuditLogQueryRequest) o;
|
||||
return java.util.Objects.equals(entityType, that.entityType) &&
|
||||
java.util.Objects.equals(entityId, that.entityId) &&
|
||||
java.util.Objects.equals(operationType, that.operationType) &&
|
||||
java.util.Objects.equals(operator, that.operator) &&
|
||||
java.util.Objects.equals(startTime, that.startTime) &&
|
||||
java.util.Objects.equals(endTime, that.endTime) &&
|
||||
java.util.Objects.equals(page, that.page) &&
|
||||
java.util.Objects.equals(size, that.size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return java.util.Objects.hash(entityType, entityId, operationType, operator, startTime, endTime, page, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AuditLogQueryRequest{" +
|
||||
"entityType='" + entityType + '\'' +
|
||||
", entityId=" + entityId +
|
||||
", operationType='" + operationType + '\'' +
|
||||
", operator='" + operator + '\'' +
|
||||
", startTime=" + startTime +
|
||||
", endTime=" + endTime +
|
||||
", page=" + page +
|
||||
", size=" + size +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user