chore: 更新Docker和CI配置
- 更新Woodpecker CI配置 - 更新Docker Compose配置 - 更新应用主类配置 - 更新网关路由服务 - 更新审计日志相关代码
This commit is contained in:
+125
-71
@@ -1,99 +1,153 @@
|
|||||||
# Woodpecker CI/CD 流水线配置
|
# Woodpecker CI/CD 流水线配置 - 企业级质量门禁
|
||||||
# TDD工作流规范 - 质量门禁配置
|
# 基于Docker化部署的完整CI/CD流水线
|
||||||
|
|
||||||
pipeline:
|
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:
|
test-backend:
|
||||||
|
group: 后端测试
|
||||||
image: maven:3.9-openjdk-21
|
image: maven:3.9-openjdk-21
|
||||||
commands:
|
commands:
|
||||||
- echo "🚀 开始后端测试..."
|
- echo "🚀 开始后端测试..."
|
||||||
- cd novalon-manage-api
|
- cd novalon-manage-api
|
||||||
|
- echo "🧪 运行单元测试..."
|
||||||
- mvn clean test jacoco:report
|
- mvn clean test jacoco:report
|
||||||
- echo "✅ 后端测试完成,生成覆盖率报告"
|
- echo "📊 生成测试覆盖率报告..."
|
||||||
|
- mvn jacoco:check
|
||||||
|
- echo "✅ 后端测试完成,覆盖率: $(cat target/site/jacoco/jacoco.xml | grep -oP 'lineCoverage=\"\K[0-9.]+')%"
|
||||||
when:
|
when:
|
||||||
event: [push, pull_request]
|
event: [push, pull_request]
|
||||||
|
|
||||||
# 构建后端JAR文件(用于E2E测试)
|
# 前端测试阶段
|
||||||
build-backend-jar:
|
test-frontend:
|
||||||
image: maven:3.9-openjdk-21
|
group: 前端测试
|
||||||
|
image: node:20
|
||||||
commands:
|
commands:
|
||||||
- echo "📦 构建后端JAR文件..."
|
- echo "🚀 开始前端测试..."
|
||||||
- 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 "🚀 开始前端单元测试..."
|
|
||||||
- cd novalon-manage-web
|
- cd novalon-manage-web
|
||||||
|
- echo "📦 安装依赖..."
|
||||||
- npm ci
|
- npm ci
|
||||||
|
- echo "🧪 运行单元测试..."
|
||||||
- npm run test:unit
|
- npm run test:unit
|
||||||
- echo "✅ 前端单元测试完成"
|
- echo "📏 检查代码规范..."
|
||||||
|
- npm run lint
|
||||||
|
- echo "✅ 前端测试完成"
|
||||||
when:
|
when:
|
||||||
event: [push, pull_request]
|
event: [push, pull_request]
|
||||||
|
|
||||||
# 前端E2E测试
|
# Docker化构建阶段
|
||||||
test-frontend-e2e:
|
docker-build:
|
||||||
image: mcr.microsoft.com/playwright:v1.40.0-jammy
|
group: 容器化构建
|
||||||
environment:
|
image: docker:24
|
||||||
- DISPLAY=:99
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
commands:
|
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
|
- cd novalon-manage-web
|
||||||
|
- echo "📦 安装依赖..."
|
||||||
- npm ci
|
- npm ci
|
||||||
|
- echo "🔧 安装浏览器..."
|
||||||
- npx playwright install --with-deps chromium
|
- npx playwright install --with-deps chromium
|
||||||
|
- echo "🧪 运行E2E测试..."
|
||||||
- echo "📦 启动后端服务..."
|
- npx playwright test --project=journeys --reporter=html,json,junit
|
||||||
- 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测试完成"
|
- echo "✅ E2E测试完成"
|
||||||
when:
|
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:
|
commands:
|
||||||
- echo "🔍 开始质量门禁检查..."
|
- echo "🔒 开始安全扫描..."
|
||||||
- cd novalon-manage-api
|
- echo "📊 扫描后端镜像..."
|
||||||
- mvn jacoco:check
|
- trivy image novalon/backend:${CI_COMMIT_SHA:0:8}
|
||||||
- echo "✅ 测试覆盖率检查通过"
|
- echo "📊 扫描前端镜像..."
|
||||||
- echo "✅ 所有测试用例通过"
|
- trivy image novalon/frontend:${CI_COMMIT_SHA:0:8}
|
||||||
- echo "✅ 代码规范检查通过"
|
- echo "✅ 安全扫描完成"
|
||||||
when:
|
when:
|
||||||
event: [pull_request]
|
event: [push]
|
||||||
|
branch: [main, develop]
|
||||||
# 构建阶段
|
|
||||||
build:
|
# 部署阶段
|
||||||
image: maven:3.9-openjdk-21
|
deploy:
|
||||||
|
group: 部署
|
||||||
|
image: alpine:latest
|
||||||
commands:
|
commands:
|
||||||
- echo "📦 开始构建..."
|
- echo "🚀 开始部署..."
|
||||||
- cd novalon-manage-api
|
- echo "📦 推送镜像到仓库..."
|
||||||
- mvn clean package -DskipTests
|
- docker tag novalon/backend:${CI_COMMIT_SHA:0:8} ${DOCKER_REGISTRY}/novalon/backend:${CI_COMMIT_SHA:0:8}
|
||||||
- echo "✅ 构建成功"
|
- 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:
|
when:
|
||||||
event: [push]
|
event: [push]
|
||||||
branch: [main, develop]
|
branch: [main, develop]
|
||||||
|
|||||||
+83
-89
@@ -1,118 +1,109 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
|
x-common-env: &common-env
|
||||||
|
TZ: Asia/Shanghai
|
||||||
|
LANG: zh_CN.UTF-8
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# PostgreSQL 数据库(用于测试)
|
# PostgreSQL数据库服务(测试环境)
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:15-alpine
|
image: postgres:15-alpine
|
||||||
container_name: novalon-test-db
|
container_name: novalon-postgres-test
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: novalon_test
|
<<: *common-env
|
||||||
POSTGRES_USER: novalon
|
POSTGRES_DB: manage_system_test
|
||||||
POSTGRES_PASSWORD: novalon123
|
POSTGRES_USER: novalon_test
|
||||||
|
POSTGRES_PASSWORD: novalon_test123
|
||||||
|
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=zh_CN.UTF-8"
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "55433:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_test_data:/var/lib/postgresql/data
|
- postgres_test_data:/var/lib/postgresql/data
|
||||||
|
- ./novalon-manage-api/manage-db/src/main/resources/db/migration:/docker-entrypoint-initdb.d
|
||||||
healthcheck:
|
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
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
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
|
start_period: 30s
|
||||||
networks:
|
networks:
|
||||||
- novalon-test-network
|
- 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:
|
frontend:
|
||||||
build:
|
build:
|
||||||
context: ./novalon-manage-web
|
context: ./novalon-manage-web
|
||||||
dockerfile: Dockerfile.dev
|
dockerfile: Dockerfile
|
||||||
container_name: novalon-test-frontend
|
args:
|
||||||
environment:
|
- BUILD_VERSION=test
|
||||||
VITE_API_BASE_URL: http://gateway:8080
|
container_name: novalon-frontend-test
|
||||||
ports:
|
ports:
|
||||||
- "3002:3002"
|
- "3002:80"
|
||||||
volumes:
|
|
||||||
- ./novalon-manage-web:/app
|
|
||||||
- /app/node_modules
|
|
||||||
depends_on:
|
depends_on:
|
||||||
gateway:
|
backend:
|
||||||
condition: service_healthy
|
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:
|
networks:
|
||||||
- novalon-test-network
|
- 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:
|
volumes:
|
||||||
postgres_test_data:
|
postgres_test_data:
|
||||||
driver: local
|
driver: local
|
||||||
@@ -120,3 +111,6 @@ volumes:
|
|||||||
networks:
|
networks:
|
||||||
novalon-test-network:
|
novalon-test-network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
- subnet: 172.21.0.0/16
|
||||||
+57
-3
@@ -1,37 +1,51 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
|
x-common-env: &common-env
|
||||||
|
TZ: Asia/Shanghai
|
||||||
|
LANG: zh_CN.UTF-8
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# PostgreSQL数据库服务
|
# PostgreSQL数据库服务
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:15-alpine
|
image: postgres:15-alpine
|
||||||
container_name: novalon-postgres
|
container_name: novalon-postgres
|
||||||
environment:
|
environment:
|
||||||
|
<<: *common-env
|
||||||
POSTGRES_DB: manage_system
|
POSTGRES_DB: manage_system
|
||||||
POSTGRES_USER: novalon
|
POSTGRES_USER: novalon
|
||||||
POSTGRES_PASSWORD: novalon123
|
POSTGRES_PASSWORD: novalon123
|
||||||
|
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=zh_CN.UTF-8"
|
||||||
ports:
|
ports:
|
||||||
- "55432:5432"
|
- "55432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
- ./novalon-manage-api/manage-db/src/main/resources/db/migration:/docker-entrypoint-initdb.d
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U novalon -d manage_system"]
|
test: ["CMD-SHELL", "pg_isready -U novalon -d manage_system"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
start_period: 30s
|
||||||
networks:
|
networks:
|
||||||
- novalon-network
|
- novalon-network
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
# 后端API服务
|
# 后端API服务
|
||||||
backend:
|
backend:
|
||||||
build:
|
build:
|
||||||
context: ./novalon-manage-api
|
context: ./novalon-manage-api
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
- BUILD_VERSION=${BUILD_VERSION:-latest}
|
||||||
container_name: novalon-backend
|
container_name: novalon-backend
|
||||||
environment:
|
environment:
|
||||||
|
<<: *common-env
|
||||||
SPRING_PROFILES_ACTIVE: docker
|
SPRING_PROFILES_ACTIVE: docker
|
||||||
SPRING_R2DBC_URL: r2dbc:postgresql://postgres:5432/manage_system
|
SPRING_R2DBC_URL: r2dbc:postgresql://postgres:5432/manage_system
|
||||||
SPRING_R2DBC_USERNAME: novalon
|
SPRING_R2DBC_USERNAME: novalon
|
||||||
SPRING_R2DBC_PASSWORD: novalon123
|
SPRING_R2DBC_PASSWORD: novalon123
|
||||||
|
SPRING_JACKSON_TIME_ZONE: Asia/Shanghai
|
||||||
|
MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE: health,info,metrics
|
||||||
ports:
|
ports:
|
||||||
- "8084:8084"
|
- "8084:8084"
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -42,15 +56,23 @@ services:
|
|||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 60s
|
||||||
networks:
|
networks:
|
||||||
- novalon-network
|
- novalon-network
|
||||||
|
restart: unless-stopped
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
# 前端Web服务
|
# 前端Web服务
|
||||||
frontend:
|
frontend:
|
||||||
build:
|
build:
|
||||||
context: ./novalon-manage-web
|
context: ./novalon-manage-web
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
- BUILD_VERSION=${BUILD_VERSION:-latest}
|
||||||
container_name: novalon-frontend
|
container_name: novalon-frontend
|
||||||
ports:
|
ports:
|
||||||
- "3001:80"
|
- "3001:80"
|
||||||
@@ -58,7 +80,8 @@ services:
|
|||||||
backend:
|
backend:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
- VITE_API_BASE_URL=http://backend:8084
|
<<: *common-env
|
||||||
|
VITE_API_BASE_URL: http://backend:8084
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:80"]
|
test: ["CMD", "curl", "-f", "http://localhost:80"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
@@ -67,11 +90,42 @@ services:
|
|||||||
start_period: 40s
|
start_period: 40s
|
||||||
networks:
|
networks:
|
||||||
- novalon-network
|
- 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:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
driver: local
|
driver: local
|
||||||
|
redis_data:
|
||||||
|
driver: local
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
novalon-network:
|
novalon-network:
|
||||||
driver: bridge
|
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
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 复制Maven配置文件和源码
|
||||||
COPY pom.xml .
|
COPY pom.xml .
|
||||||
COPY mvnw .
|
COPY mvnw .
|
||||||
COPY mvnw.cmd .
|
COPY mvnw.cmd .
|
||||||
COPY .mvn .mvn
|
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
|
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
|
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
|
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.context.annotation.ComponentScan;
|
||||||
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
|
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
|
||||||
|
|
||||||
@SpringBootApplication(exclude = {ReactiveUserDetailsServiceAutoConfiguration.class})
|
@SpringBootApplication(scanBasePackages = "cn.novalon.manage", exclude = {ReactiveUserDetailsServiceAutoConfiguration.class})
|
||||||
@ConfigurationPropertiesScan(basePackages = "cn.novalon.manage")
|
|
||||||
@ComponentScan(basePackages = "cn.novalon.manage")
|
|
||||||
@EnableR2dbcRepositories(basePackages = {"cn.novalon.manage.db.dao", "cn.novalon.manage.sys.audit.repository"})
|
@EnableR2dbcRepositories(basePackages = {"cn.novalon.manage.db.dao", "cn.novalon.manage.sys.audit.repository"})
|
||||||
public class ManageApplication {
|
public class ManageApplication {
|
||||||
|
|
||||||
@@ -20,6 +18,8 @@ public class ManageApplication {
|
|||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
logger.info("应用程序启动中...");
|
logger.info("应用程序启动中...");
|
||||||
logger.info("包扫描路径: cn.novalon.manage");
|
logger.info("包扫描路径: cn.novalon.manage");
|
||||||
|
|
||||||
|
// 使用简单的启动方式,避免自动配置问题
|
||||||
SpringApplication.run(ManageApplication.class, args);
|
SpringApplication.run(ManageApplication.class, args);
|
||||||
logger.info("应用程序启动完成");
|
logger.info("应用程序启动完成");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ spring:
|
|||||||
password: ${DB_PASSWORD:postgres}
|
password: ${DB_PASSWORD:postgres}
|
||||||
driver-class-name: org.postgresql.Driver
|
driver-class-name: org.postgresql.Driver
|
||||||
flyway:
|
flyway:
|
||||||
enabled: false
|
enabled: true
|
||||||
|
locations: classpath:db/migration
|
||||||
|
baseline-on-migrate: true
|
||||||
|
baseline-version: 0
|
||||||
|
validate-on-migrate: true
|
||||||
security:
|
security:
|
||||||
user:
|
user:
|
||||||
name: disabled
|
name: disabled
|
||||||
|
|||||||
+9
-15
@@ -74,11 +74,9 @@ public class DynamicRouteService implements IDynamicRouteService {
|
|||||||
logger.info("Adding route: {}", routeId);
|
logger.info("Adding route: {}", routeId);
|
||||||
|
|
||||||
return routeDefinitionWriter.save(Mono.just(routeDefinition))
|
return routeDefinitionWriter.save(Mono.just(routeDefinition))
|
||||||
.then(Mono.fromRunnable(() -> {
|
.then(Mono.fromRunnable(() -> routeCache.put(routeId, routeDefinition)))
|
||||||
routeCache.put(routeId, routeDefinition);
|
.then(refreshRoutes())
|
||||||
refreshRoutes();
|
.then(Mono.fromRunnable(() -> logger.info("Route added successfully: {}", routeId)))
|
||||||
logger.info("Route added successfully: {}", routeId);
|
|
||||||
}))
|
|
||||||
.thenReturn(true)
|
.thenReturn(true)
|
||||||
.onErrorResume(error -> {
|
.onErrorResume(error -> {
|
||||||
logger.error("Failed to add route: {}", routeId, error);
|
logger.error("Failed to add route: {}", routeId, error);
|
||||||
@@ -104,11 +102,9 @@ public class DynamicRouteService implements IDynamicRouteService {
|
|||||||
|
|
||||||
return routeDefinitionWriter.delete(Mono.just(routeId))
|
return routeDefinitionWriter.delete(Mono.just(routeId))
|
||||||
.then(routeDefinitionWriter.save(Mono.just(routeDefinition)))
|
.then(routeDefinitionWriter.save(Mono.just(routeDefinition)))
|
||||||
.then(Mono.fromRunnable(() -> {
|
.then(Mono.fromRunnable(() -> routeCache.put(routeId, routeDefinition)))
|
||||||
routeCache.put(routeId, routeDefinition);
|
.then(refreshRoutes())
|
||||||
refreshRoutes();
|
.then(Mono.fromRunnable(() -> logger.info("Route updated successfully: {}", routeId)))
|
||||||
logger.info("Route updated successfully: {}", routeId);
|
|
||||||
}))
|
|
||||||
.thenReturn(true)
|
.thenReturn(true)
|
||||||
.onErrorResume(error -> {
|
.onErrorResume(error -> {
|
||||||
logger.error("Failed to update route: {}", routeId, error);
|
logger.error("Failed to update route: {}", routeId, error);
|
||||||
@@ -131,11 +127,9 @@ public class DynamicRouteService implements IDynamicRouteService {
|
|||||||
logger.info("Deleting route: {}", routeId);
|
logger.info("Deleting route: {}", routeId);
|
||||||
|
|
||||||
return routeDefinitionWriter.delete(Mono.just(routeId))
|
return routeDefinitionWriter.delete(Mono.just(routeId))
|
||||||
.then(Mono.fromRunnable(() -> {
|
.then(Mono.fromRunnable(() -> routeCache.remove(routeId)))
|
||||||
routeCache.remove(routeId);
|
.then(refreshRoutes())
|
||||||
refreshRoutes();
|
.then(Mono.fromRunnable(() -> logger.info("Route deleted successfully: {}", routeId)))
|
||||||
logger.info("Route deleted successfully: {}", routeId);
|
|
||||||
}))
|
|
||||||
.thenReturn(true)
|
.thenReturn(true)
|
||||||
.onErrorResume(error -> {
|
.onErrorResume(error -> {
|
||||||
logger.error("Failed to delete route: {}", routeId, error);
|
logger.error("Failed to delete route: {}", routeId, error);
|
||||||
|
|||||||
+15
-4
@@ -1,5 +1,6 @@
|
|||||||
package cn.novalon.manage.gateway.route;
|
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.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
@@ -25,7 +26,7 @@ import static org.mockito.Mockito.*;
|
|||||||
* 涉及业务:路由增删改查、路由刷新
|
* 涉及业务:路由增删改查、路由刷新
|
||||||
*
|
*
|
||||||
* @author 张翔
|
* @author 张翔
|
||||||
* @date 2026-03-26
|
* @date 2026-04-14
|
||||||
*/
|
*/
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class DynamicRouteServiceTest {
|
class DynamicRouteServiceTest {
|
||||||
@@ -78,7 +79,15 @@ class DynamicRouteServiceTest {
|
|||||||
@Test
|
@Test
|
||||||
void testDeleteRoute_Success() {
|
void testDeleteRoute_Success() {
|
||||||
String routeId = "test-route";
|
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());
|
when(routeDefinitionWriter.delete(any())).thenReturn(Mono.empty());
|
||||||
|
|
||||||
StepVerifier.create(dynamicRouteService.deleteRoute(routeId))
|
StepVerifier.create(dynamicRouteService.deleteRoute(routeId))
|
||||||
@@ -86,7 +95,7 @@ class DynamicRouteServiceTest {
|
|||||||
.verifyComplete();
|
.verifyComplete();
|
||||||
|
|
||||||
verify(routeDefinitionWriter).delete(any());
|
verify(routeDefinitionWriter).delete(any());
|
||||||
verify(publisher).publishEvent(any(RefreshRoutesEvent.class));
|
verify(publisher, times(2)).publishEvent(any(RefreshRoutesEvent.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -113,7 +122,7 @@ class DynamicRouteServiceTest {
|
|||||||
.expectNext(true)
|
.expectNext(true)
|
||||||
.verifyComplete();
|
.verifyComplete();
|
||||||
|
|
||||||
StepVerifier.create(dynamicRouteService.getAllRoutes().collectList())
|
StepVerifier.create(dynamicRouteService.getRoutes().collectList())
|
||||||
.assertNext(routes -> {
|
.assertNext(routes -> {
|
||||||
assertNotNull(routes);
|
assertNotNull(routes);
|
||||||
assertTrue(routes.size() >= 2);
|
assertTrue(routes.size() >= 2);
|
||||||
@@ -131,7 +140,9 @@ class DynamicRouteServiceTest {
|
|||||||
.expectNext(true)
|
.expectNext(true)
|
||||||
.verifyComplete();
|
.verifyComplete();
|
||||||
|
|
||||||
assertTrue(dynamicRouteService.getRouteCount() >= 1);
|
StepVerifier.create(dynamicRouteService.getRouteCount())
|
||||||
|
.assertNext(count -> assertTrue(count >= 1))
|
||||||
|
.verifyComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private RouteDefinition createRouteDefinition(String id) {
|
private RouteDefinition createRouteDefinition(String id) {
|
||||||
|
|||||||
+8
-12
@@ -47,9 +47,8 @@ public class AuditLogController {
|
|||||||
public Flux<AuditLog> query(AuditLogQueryRequest request) {
|
public Flux<AuditLog> query(AuditLogQueryRequest request) {
|
||||||
if (request.getEntityType() != null && request.getEntityId() != null) {
|
if (request.getEntityType() != null && request.getEntityId() != null) {
|
||||||
return auditLogService.findByEntityTypeAndEntityId(
|
return auditLogService.findByEntityTypeAndEntityId(
|
||||||
request.getEntityType(),
|
request.getEntityType(),
|
||||||
request.getEntityId()
|
request.getEntityId());
|
||||||
);
|
|
||||||
} else if (request.getEntityType() != null) {
|
} else if (request.getEntityType() != null) {
|
||||||
return auditLogService.findByEntityType(request.getEntityType());
|
return auditLogService.findByEntityType(request.getEntityType());
|
||||||
} else if (request.getOperator() != null) {
|
} else if (request.getOperator() != null) {
|
||||||
@@ -58,11 +57,10 @@ public class AuditLogController {
|
|||||||
return auditLogService.findByOperationType(request.getOperationType());
|
return auditLogService.findByOperationType(request.getOperationType());
|
||||||
} else if (request.getStartTime() != null && request.getEndTime() != null) {
|
} else if (request.getStartTime() != null && request.getEndTime() != null) {
|
||||||
return auditLogService.findByOperationTimeBetween(
|
return auditLogService.findByOperationTimeBetween(
|
||||||
request.getStartTime(),
|
request.getStartTime(),
|
||||||
request.getEndTime()
|
request.getEndTime());
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Flux.empty();
|
return Flux.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,10 +95,8 @@ public class AuditLogController {
|
|||||||
@GetMapping("/time-range")
|
@GetMapping("/time-range")
|
||||||
@Operation(summary = "按时间范围查询", description = "根据时间范围查询审计日志")
|
@Operation(summary = "按时间范围查询", description = "根据时间范围查询审计日志")
|
||||||
public Flux<AuditLog> findByTimeRange(
|
public Flux<AuditLog> findByTimeRange(
|
||||||
@Parameter(description = "开始时间")
|
@Parameter(description = "开始时间") @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startTime,
|
||||||
@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 endTime) {
|
|
||||||
return auditLogService.findByOperationTimeBetween(startTime, endTime);
|
return auditLogService.findByOperationTimeBetween(startTime, endTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +104,7 @@ public class AuditLogController {
|
|||||||
@Operation(summary = "审计日志统计", description = "获取审计日志的统计信息")
|
@Operation(summary = "审计日志统计", description = "获取审计日志的统计信息")
|
||||||
public Mono<AuditLogStatistics> getStatistics() {
|
public Mono<AuditLogStatistics> getStatistics() {
|
||||||
AuditLogStatistics statistics = new AuditLogStatistics();
|
AuditLogStatistics statistics = new AuditLogStatistics();
|
||||||
|
|
||||||
return Mono.just(statistics);
|
return Mono.just(statistics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+26
@@ -138,4 +138,30 @@ public class AuditLog extends BaseDomain {
|
|||||||
public void setDescription(String description) {
|
public void setDescription(String description) {
|
||||||
this.description = 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) {
|
public void setSize(Integer size) {
|
||||||
this.size = 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