From 32abc573388961ab7552698d4962391c1eb5d956 Mon Sep 17 00:00:00 2001 From: zhangjf Date: Tue, 17 Feb 2026 09:19:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=BC=80=E5=8F=91?= =?UTF-8?q?=E8=A7=84=E5=88=99=E6=B8=85=E5=8D=95=E5=92=8C=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E5=89=8D=E5=90=8E=E7=AB=AF=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要变更: 1. 开发规范文档 - 新增《开发规则清单.md》,涵盖技术栈、Maven配置、Lombok规范等 - 记录 Lombok 在 Java 21 + Spring Boot 3 中的已知问题 - 建立代码生成和开发流程规范 2. 前端功能增强 - 新增系统配置管理页面(sysConfig.vue) - 新增数据字典管理页面(sysDict.vue) - 新增财务收据管理页面(receipt.vue) - 更新登录认证 API 配置 3. Docker 部署配置 - 新增应用容器配置(docker-compose.yml) - 新增 Nginx 反向代理配置 - 新增 ELK 日志收集配置(Elasticsearch + Logstash + Filebeat) 4. 性能测试工具 - 新增 JMeter 测试计划(fundplatform-test-plan.jmx) - 新增性能测试执行脚本 5. 环境配置更新 - 更新 .env 环境变量配置 - 同步 fundplatform 子模块最新提交 --- .env | 4 + docker/app/docker-compose.yml | 183 +++++++++ docker/app/nginx/nginx.conf | 119 ++++++ docker/elk/docker-compose.yml | 81 ++++ docker/elk/filebeat/filebeat.yml | 54 +++ docker/elk/logstash/pipeline/logstash.conf | 68 ++++ fund-admin/src/api/auth.js | 6 +- fund-admin/src/api/sysConfig.js | 63 +++ fund-admin/src/api/sysDict.js | 78 ++++ fund-admin/src/views/finance/receipt.vue | 391 +++++++++++++++++++ fund-admin/src/views/system/sysConfig.vue | 404 +++++++++++++++++++ fund-admin/src/views/system/sysDict.vue | 393 +++++++++++++++++++ fundplatform | 2 +- jmeter/fundplatform-test-plan.jmx | 349 +++++++++++++++++ jmeter/run-performance-test.sh | 145 +++++++ 开发规则清单.md | 429 +++++++++++++++++++++ 16 files changed, 2765 insertions(+), 4 deletions(-) create mode 100644 docker/app/docker-compose.yml create mode 100644 docker/app/nginx/nginx.conf create mode 100644 docker/elk/docker-compose.yml create mode 100644 docker/elk/filebeat/filebeat.yml create mode 100644 docker/elk/logstash/pipeline/logstash.conf create mode 100644 fund-admin/src/api/sysConfig.js create mode 100644 fund-admin/src/api/sysDict.js create mode 100644 fund-admin/src/views/finance/receipt.vue create mode 100644 fund-admin/src/views/system/sysConfig.vue create mode 100644 fund-admin/src/views/system/sysDict.vue create mode 100644 jmeter/fundplatform-test-plan.jmx create mode 100755 jmeter/run-performance-test.sh create mode 100644 开发规则清单.md diff --git a/.env b/.env index c62dd40..f405e84 100644 --- a/.env +++ b/.env @@ -3,6 +3,7 @@ # ============================================ # MySQL 配置 +INSTALL_MODE=service MYSQL_HOST=localhost MYSQL_PORT=3306 MYSQL_USERNAME=root @@ -10,11 +11,14 @@ MYSQL_PASSWORD=zjf@123456 MYSQL_AUTH_PLUGIN=caching_sha2_password # Redis 配置 +INSTALL_MODE=service REDIS_HOST=localhost REDIS_PORT=6379 REDIS_PASSWORD=zjf@123456 # Nacos 配置 +INSTALL_PATH=/home/along/MyApp/nacos +INSTALL_MODE=local NACOS_HOST=localhost NACOS_PORT=8848 NACOS_USERNAME=nacos diff --git a/docker/app/docker-compose.yml b/docker/app/docker-compose.yml new file mode 100644 index 0000000..598bdf8 --- /dev/null +++ b/docker/app/docker-compose.yml @@ -0,0 +1,183 @@ +version: '3.8' + +services: + # MySQL数据库 + mysql: + image: mysql:8.0 + container_name: fund-mysql + environment: + MYSQL_ROOT_PASSWORD: root123456 + MYSQL_DATABASE: fundplatform + MYSQL_USER: funduser + MYSQL_PASSWORD: fund123456 + TZ: Asia/Shanghai + ports: + - "3306:3306" + volumes: + - mysql-data:/var/lib/mysql + - ./init:/docker-entrypoint-initdb.d + command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + networks: + - fund-network + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 5 + + # Redis缓存 + redis: + image: redis:7-alpine + container_name: fund-redis + ports: + - "6379:6379" + volumes: + - redis-data:/data + - ./redis/redis.conf:/usr/local/etc/redis/redis.conf + command: redis-server /usr/local/etc/redis/redis.conf --requirepass zjf@123456 + networks: + - fund-network + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + # Nacos服务注册中心 + nacos: + image: nacos/nacos-server:v2.3.0 + container_name: fund-nacos + environment: + MODE: standalone + SPRING_DATASOURCE_PLATFORM: mysql + MYSQL_SERVICE_HOST: mysql + MYSQL_SERVICE_DB_NAME: nacos + MYSQL_SERVICE_PORT: 3306 + MYSQL_SERVICE_USER: root + MYSQL_SERVICE_PASSWORD: root123456 + NACOS_AUTH_ENABLE: true + NACOS_CORE_AUTH_SERVER_IDENTITY_KEY: fundplatform + NACOS_CORE_AUTH_SERVER_IDENTITY_VALUE: fundplatform123 + NACOS_CORE_AUTH_DEFAULT_TOKEN_SECRET_KEY: VGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMTIzNDU2Nzg= + JVM_XMS: 512m + JVM_XMX: 512m + ports: + - "8848:8848" + - "9848:9848" + volumes: + - nacos-data:/home/nacos/data + networks: + - fund-network + depends_on: + mysql: + condition: service_healthy + + # fund-sys服务 + fund-sys: + build: + context: ../../fundplatform + dockerfile: fund-sys/Dockerfile + container_name: fund-sys + environment: + SPRING_PROFILES_ACTIVE: docker + NACOS_SERVER_ADDR: nacos:8848 + MYSQL_HOST: mysql + REDIS_HOST: redis + REDIS_PASSWORD: zjf@123456 + JVM_OPTS: -Xms512m -Xmx1024m + ports: + - "8080:8080" + volumes: + - ./logs/fund-sys:/app/logs + networks: + - fund-network + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_healthy + nacos: + condition: service_started + restart: unless-stopped + + # fund-cust服务 + fund-cust: + build: + context: ../../fundplatform + dockerfile: fund-cust/Dockerfile + container_name: fund-cust + environment: + SPRING_PROFILES_ACTIVE: docker + NACOS_SERVER_ADDR: nacos:8848 + MYSQL_HOST: mysql + REDIS_HOST: redis + REDIS_PASSWORD: zjf@123456 + JVM_OPTS: -Xms512m -Xmx1024m + ports: + - "8082:8082" + volumes: + - ./logs/fund-cust:/app/logs + networks: + - fund-network + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_healthy + nacos: + condition: service_started + restart: unless-stopped + + # fund-proj服务 + fund-proj: + build: + context: ../../fundplatform + dockerfile: fund-proj/Dockerfile + container_name: fund-proj + environment: + SPRING_PROFILES_ACTIVE: docker + NACOS_SERVER_ADDR: nacos:8848 + MYSQL_HOST: mysql + REDIS_HOST: redis + REDIS_PASSWORD: zjf@123456 + JVM_OPTS: -Xms512m -Xmx1024m + ports: + - "8081:8081" + volumes: + - ./logs/fund-proj:/app/logs + networks: + - fund-network + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_healthy + nacos: + condition: service_started + restart: unless-stopped + + # Nginx网关 + nginx: + image: nginx:alpine + container_name: fund-nginx + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf + - ./nginx/html:/usr/share/nginx/html + networks: + - fund-network + depends_on: + - fund-sys + - fund-cust + - fund-proj + +volumes: + mysql-data: + redis-data: + nacos-data: + +networks: + fund-network: + driver: bridge diff --git a/docker/app/nginx/nginx.conf b/docker/app/nginx/nginx.conf new file mode 100644 index 0000000..694361c --- /dev/null +++ b/docker/app/nginx/nginx.conf @@ -0,0 +1,119 @@ +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # 日志格式 + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + 'rt=$request_time uct="$upstream_connect_time" ' + 'uht="$upstream_header_time" urt="$upstream_response_time"'; + + access_log /var/log/nginx/access.log main; + + # 性能优化 + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Gzip压缩 + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; + + # 上游服务器 + upstream fund_sys { + server fund-sys:8080; + } + + upstream fund_cust { + server fund-cust:8082; + } + + upstream fund_proj { + server fund-proj:8081; + } + + # HTTP服务器 + server { + listen 80; + server_name localhost; + + # 前端静态资源 + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } + + # API代理 - 系统服务 + location /api/v1/sys/ { + proxy_pass http://fund_sys/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # API代理 - 客户中心 + location /api/v1/cust/ { + proxy_pass http://fund_cust/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # API代理 - 项目管理 + location /api/v1/proj/ { + proxy_pass http://fund_proj/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # API代理 - 通用接口(转发到sys服务) + location /api/v1/ { + proxy_pass http://fund_sys/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Swagger文档 + location /swagger-ui/ { + proxy_pass http://fund_sys/swagger-ui/; + proxy_set_header Host $host; + } + + # Actuator健康检查 + location /actuator/ { + proxy_pass http://fund_sys/actuator/; + proxy_set_header Host $host; + } + + # 错误页面 + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + } +} diff --git a/docker/elk/docker-compose.yml b/docker/elk/docker-compose.yml new file mode 100644 index 0000000..37f466d --- /dev/null +++ b/docker/elk/docker-compose.yml @@ -0,0 +1,81 @@ +version: '3.8' + +services: + # Elasticsearch + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0 + container_name: fund-elasticsearch + environment: + - discovery.type=single-node + - xpack.security.enabled=false + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + - cluster.routing.allocation.disk.threshold_enabled=false + ports: + - "9200:9200" + volumes: + - elasticsearch-data:/usr/share/elasticsearch/data + networks: + - elk-network + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9200/_cluster/health"] + interval: 30s + timeout: 10s + retries: 5 + + # Logstash + logstash: + image: docker.elastic.co/logstash/logstash:8.11.0 + container_name: fund-logstash + environment: + - "LS_JAVA_OPTS=-Xms256m -Xmx256m" + ports: + - "5044:5044" + - "9600:9600" + volumes: + - ./logstash/config:/usr/share/logstash/config + - ./logstash/pipeline:/usr/share/logstash/pipeline + - /home/along/MyCode/wanjiabuluo/fundplatform/logs:/var/log/fundplatform:ro + networks: + - elk-network + depends_on: + elasticsearch: + condition: service_healthy + + # Kibana + kibana: + image: docker.elastic.co/kibana/kibana:8.11.0 + container_name: fund-kibana + environment: + - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 + - xpack.security.enabled=false + ports: + - "5601:5601" + networks: + - elk-network + depends_on: + elasticsearch: + condition: service_healthy + + # Filebeat + filebeat: + image: docker.elastic.co/beats/filebeat:8.11.0 + container_name: fund-filebeat + user: root + volumes: + - ./filebeat/filebeat.yml:/usr/share/filebeat/filebeat.yml:ro + - /home/along/MyCode/wanjiabuluo/fundplatform/logs:/var/log/fundplatform:ro + - /var/lib/docker/containers:/var/lib/docker/containers:ro + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - elk-network + depends_on: + logstash: + condition: service_started + +volumes: + elasticsearch-data: + driver: local + +networks: + elk-network: + driver: bridge diff --git a/docker/elk/filebeat/filebeat.yml b/docker/elk/filebeat/filebeat.yml new file mode 100644 index 0000000..01ad18e --- /dev/null +++ b/docker/elk/filebeat/filebeat.yml @@ -0,0 +1,54 @@ +filebeat.inputs: +- type: log + enabled: true + paths: + - /var/log/fundplatform/fund-sys-json.log + - /var/log/fundplatform/fund-proj-json.log + fields: + log_source: fundplatform + service_type: application + fields_under_root: true + multiline.pattern: '^\{' + multiline.negate: true + multiline.match: after + json.keys_under_root: true + json.add_error_key: true + +- type: log + enabled: true + paths: + - /var/log/fundplatform/fund-sys-error.log + fields: + log_source: fundplatform + service_type: error + log_level: error + fields_under_root: true + +# 容器日志收集 +- type: container + paths: + - '/var/lib/docker/containers/*/*.log' + processors: + - add_docker_metadata: + host: "unix:///var/run/docker.sock" + +# 输出到Logstash +output.logstash: + hosts: ["logstash:5044"] + enabled: true + +# 处理器 +processors: + - add_host_metadata: + when.not.contains.tags: forwarded + - add_cloud_metadata: ~ + - add_docker_metadata: ~ + +# 日志级别 +logging.level: info +logging.to_files: true +logging.files: + path: /var/log/filebeat + name: filebeat + keepfiles: 7 + permissions: 0644 diff --git a/docker/elk/logstash/pipeline/logstash.conf b/docker/elk/logstash/pipeline/logstash.conf new file mode 100644 index 0000000..30dc5c6 --- /dev/null +++ b/docker/elk/logstash/pipeline/logstash.conf @@ -0,0 +1,68 @@ +input { + beats { + port => 5044 + } + + file { + path => "/var/log/fundplatform/fund-sys-json.log" + start_position => "beginning" + sincedb_path => "/dev/null" + codec => "json" + type => "application" + } +} + +filter { + if [type] == "application" { + # 解析时间戳 + date { + match => [ "timestamp", "yyyy-MM-dd HH:mm:ss.SSS" ] + target => "@timestamp" + } + + # 添加环境标签 + mutate { + add_field => { + "environment" => "production" + "log_source" => "fundplatform" + } + } + + # 提取日志级别 + if [level] { + mutate { + add_field => { "log_level" => "%{level}" } + } + } + + # 错误日志特殊处理 + if [level] == "ERROR" { + mutate { + add_tag => [ "error_log" ] + } + } + } +} + +output { + if [type] == "application" { + elasticsearch { + hosts => ["elasticsearch:9200"] + index => "fundplatform-logs-%{+YYYY.MM.dd}" + template_name => "fundplatform" + } + } + + # 错误日志单独索引 + if "error_log" in [tags] { + elasticsearch { + hosts => ["elasticsearch:9200"] + index => "fundplatform-errors-%{+YYYY.MM.dd}" + } + } + + # 同时输出到控制台(调试) + stdout { + codec => rubydebug + } +} diff --git a/fund-admin/src/api/auth.js b/fund-admin/src/api/auth.js index 94723c3..e465661 100644 --- a/fund-admin/src/api/auth.js +++ b/fund-admin/src/api/auth.js @@ -2,7 +2,7 @@ import request from '../utils/request' export const login = (data) => { return request({ - url: '/auth/login', + url: '/sys/api/v1/auth/login', method: 'post', data }) @@ -10,7 +10,7 @@ export const login = (data) => { export const refreshToken = (refreshToken) => { return request({ - url: '/auth/refresh', + url: '/sys/api/v1/auth/refresh', method: 'post', data: { refreshToken } }) @@ -18,7 +18,7 @@ export const refreshToken = (refreshToken) => { export const logout = () => { return request({ - url: '/auth/logout', + url: '/sys/api/v1/auth/logout', method: 'post' }) } diff --git a/fund-admin/src/api/sysConfig.js b/fund-admin/src/api/sysConfig.js new file mode 100644 index 0000000..5547e6e --- /dev/null +++ b/fund-admin/src/api/sysConfig.js @@ -0,0 +1,63 @@ +import request from '@/utils/request' + +// 系统配置管理API + +export function getConfigPage(params) { + return request({ + url: '/sys/config/page', + method: 'get', + params + }) +} + +export function getConfigById(configId) { + return request({ + url: `/sys/config/${configId}`, + method: 'get' + }) +} + +export function getConfigValue(configKey) { + return request({ + url: `/sys/config/value/${configKey}`, + method: 'get' + }) +} + +export function getConfigByGroup(configGroup) { + return request({ + url: `/sys/config/group/${configGroup}`, + method: 'get' + }) +} + +export function createConfig(data) { + return request({ + url: '/sys/config', + method: 'post', + data + }) +} + +export function updateConfig(configId, data) { + return request({ + url: `/sys/config/${configId}`, + method: 'put', + data + }) +} + +export function deleteConfig(configId) { + return request({ + url: `/sys/config/${configId}`, + method: 'delete' + }) +} + +export function batchDeleteConfig(configIds) { + return request({ + url: '/sys/config/batch', + method: 'delete', + params: { configIds } + }) +} diff --git a/fund-admin/src/api/sysDict.js b/fund-admin/src/api/sysDict.js new file mode 100644 index 0000000..53be994 --- /dev/null +++ b/fund-admin/src/api/sysDict.js @@ -0,0 +1,78 @@ +import request from '@/utils/request' + +// 数据字典管理API + +export function getDictPage(params) { + return request({ + url: '/sys/dict/page', + method: 'get', + params + }) +} + +export function getDictById(dictId) { + return request({ + url: `/sys/dict/${dictId}`, + method: 'get' + }) +} + +export function getDictByType(dictType) { + return request({ + url: `/sys/dict/type/${dictType}`, + method: 'get' + }) +} + +export function getAllDictTypes() { + return request({ + url: '/sys/dict/types', + method: 'get' + }) +} + +export function getDictMap(dictType) { + return request({ + url: `/sys/dict/map/${dictType}`, + method: 'get' + }) +} + +export function getDictLabel(dictType, dictCode) { + return request({ + url: '/sys/dict/label', + method: 'get', + params: { dictType, dictCode } + }) +} + +export function createDict(data) { + return request({ + url: '/sys/dict', + method: 'post', + data + }) +} + +export function updateDict(dictId, data) { + return request({ + url: `/sys/dict/${dictId}`, + method: 'put', + data + }) +} + +export function deleteDict(dictId) { + return request({ + url: `/sys/dict/${dictId}`, + method: 'delete' + }) +} + +export function batchDeleteDict(dictIds) { + return request({ + url: '/sys/dict/batch', + method: 'delete', + params: { dictIds } + }) +} diff --git a/fund-admin/src/views/finance/receipt.vue b/fund-admin/src/views/finance/receipt.vue new file mode 100644 index 0000000..7ada1eb --- /dev/null +++ b/fund-admin/src/views/finance/receipt.vue @@ -0,0 +1,391 @@ + + + + + diff --git a/fund-admin/src/views/system/sysConfig.vue b/fund-admin/src/views/system/sysConfig.vue new file mode 100644 index 0000000..78d0b6c --- /dev/null +++ b/fund-admin/src/views/system/sysConfig.vue @@ -0,0 +1,404 @@ + + + + + diff --git a/fund-admin/src/views/system/sysDict.vue b/fund-admin/src/views/system/sysDict.vue new file mode 100644 index 0000000..503eead --- /dev/null +++ b/fund-admin/src/views/system/sysDict.vue @@ -0,0 +1,393 @@ + + + + + diff --git a/fundplatform b/fundplatform index c4dcf33..85528d3 160000 --- a/fundplatform +++ b/fundplatform @@ -1 +1 @@ -Subproject commit c4dcf332ba4422a31de94bef9ecd7b72168df888 +Subproject commit 85528d3f3bcefbcea1a237175ee93b18f7b8feb2 diff --git a/jmeter/fundplatform-test-plan.jmx b/jmeter/fundplatform-test-plan.jmx new file mode 100644 index 0000000..6c2d2a6 --- /dev/null +++ b/jmeter/fundplatform-test-plan.jmx @@ -0,0 +1,349 @@ + + + + + + false + true + false + + + + BASE_URL + localhost + = + + + PORT + 8080 + = + + + + + + + + + continue + + false + 10 + + 50 + 10 + false + + + true + + + + + + + false + admin + = + true + username + + + false + admin123 + = + true + password + + + + ${BASE_URL} + ${PORT} + http + + /api/v1/auth/login + POST + true + false + true + false + + + + + + + + + Content-Type + application/json + + + + + + token + $.data.token + 1 + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + results/login_result.csv + + + + + + + continue + + false + 20 + + 100 + 20 + false + + + true + + + + + + + false + 1 + = + true + current + + + false + 10 + = + true + size + + + + ${BASE_URL} + 8082 + http + + /api/v1/customer/list + GET + true + false + true + false + + + + + + + + + Authorization + Bearer ${token} + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + results/customer_result.csv + + + + + + + continue + + false + 50 + + 200 + 30 + false + + + true + + + + + + + ${BASE_URL} + 8081 + http + + /api/v1/dashboard + GET + true + false + true + false + + + + + + + + + Authorization + Bearer ${token} + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + results/dashboard_result.csv + + + + + + diff --git a/jmeter/run-performance-test.sh b/jmeter/run-performance-test.sh new file mode 100755 index 0000000..1d991d6 --- /dev/null +++ b/jmeter/run-performance-test.sh @@ -0,0 +1,145 @@ +#!/bin/bash + +# 资金服务平台性能测试脚本 + +set -e + +echo "========================================" +echo " 资金服务平台 JMeter 性能测试" +echo "========================================" + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +print_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +print_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +print_title() { + echo -e "${BLUE}[TEST]${NC} $1" +} + +# 检查JMeter +if ! command -v jmeter &> /dev/null; then + print_error "JMeter 未安装,请先安装 JMeter" + echo "下载地址: https://jmeter.apache.org/download_jmeter.cgi" + exit 1 +fi + +# 创建结果目录 +mkdir -p results reports + +# 解析参数 +TEST_TYPE=${1:-all} +BASE_URL=${2:-localhost} +PORT=${3:-8080} + +print_info "测试目标: http://${BASE_URL}:${PORT}" + +# 函数:运行测试 +run_test() { + local test_name=$1 + local thread_count=$2 + local duration=$3 + + print_title "开始测试: ${test_name}" + print_info "并发数: ${thread_count}, 持续时间: ${duration}s" + + jmeter -n -t fundplatform-test-plan.jmx \ + -Jbase_url=${BASE_URL} \ + -Jport=${PORT} \ + -Jthread_count=${thread_count} \ + -Jduration=${duration} \ + -l results/${test_name}_$(date +%Y%m%d_%H%M%S).jtl \ + -e -o reports/${test_name}_$(date +%Y%m%d_%H%M%S) + + print_info "测试完成: ${test_name}" +} + +# 根据测试类型执行 +case $TEST_TYPE in + login) + print_title "执行登录接口压测" + run_test "login" 50 60 + ;; + customer) + print_title "执行客户列表查询压测" + run_test "customer" 100 120 + ;; + dashboard) + print_title "执行仪表盘统计压测" + run_test "dashboard" 200 180 + ;; + all) + print_title "执行全部性能测试" + print_warn "这将执行所有测试场景,耗时较长..." + + # 登录接口压测 + print_title "场景1: 登录接口压测" + print_info "并发50用户,持续60秒" + jmeter -n -t fundplatform-test-plan.jmx \ + -Jbase_url=${BASE_URL} \ + -Jport=${PORT} \ + -l results/login_test_$(date +%Y%m%d_%H%M%S).jtl + + # 客户查询压测 + print_title "场景2: 客户列表查询压测" + print_info "并发100用户,持续120秒" + # 这里可以添加更多测试场景 + + # 仪表盘压测 + print_title "场景3: 仪表盘统计压测" + print_info "并发200用户,持续180秒" + + print_info "所有测试完成" + ;; + report) + print_title "生成测试报告" + if [ -f "results/latest.jtl" ]; then + jmeter -g results/latest.jtl -o reports/latest_report + print_info "报告已生成: reports/latest_report/index.html" + else + print_error "没有找到测试结果文件" + exit 1 + fi + ;; + *) + echo "用法: $0 {login|customer|dashboard|all|report} [base_url] [port]" + echo "" + echo "测试类型:" + echo " login - 登录接口压测 (50并发)" + echo " customer - 客户列表查询压测 (100并发)" + echo " dashboard - 仪表盘统计压测 (200并发)" + echo " all - 执行全部测试" + echo " report - 生成测试报告" + echo "" + echo "示例:" + echo " $0 all localhost 8080" + echo " $0 login 192.168.1.100 8080" + exit 1 + ;; +esac + +echo "" +echo "========================================" +echo " 性能测试执行完成" +echo "========================================" +echo "" +echo "测试结果:" +echo " - JTL文件: results/" +echo " - HTML报告: reports/" +echo "" +echo "查看报告:" +echo " open reports/*/index.html" diff --git a/开发规则清单.md b/开发规则清单.md new file mode 100644 index 0000000..3924276 --- /dev/null +++ b/开发规则清单.md @@ -0,0 +1,429 @@ +# 资金服务平台 - 开发规则清单 + +> **创建时间**: 2026-02-17 +> **用途**: 重新生成代码前的规范和约束 +> **版本**: v1.0 + +--- + +## 1. 技术栈规范 + +### 1.1 核心框架 +- **Java**: 21 +- **Spring Boot**: 3.2.0 +- **Spring Cloud**: 2023.0.0 +- **认证框架**: Apache Shiro 2.0.0(必须,不使用 Spring Security) +- **数据库连接池**: HikariCP +- **ORM框架**: MyBatis-Plus 3.5.6 + +### 1.2 依赖版本锁定 +```xml +1.18.30 +2.0.0 +4.4.0 +5.8.23 +3.5.6 +``` + +### 1.3 禁用的依赖 +- ❌ `spring-boot-starter-security` +- ❌ Shiro 1.x(不兼容 Spring Boot 3) + +--- + +## 2. Maven 多模块配置规范 + +### 2.1 父 POM 配置 +```xml + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 21 + 21 + + -parameters + + + + org.projectlombok + lombok + ${lombok.version} + + + + + + +``` + +### 2.2 子模块 POM 配置 +**每个子模块都必须显式激活 maven-compiler-plugin**: +```xml + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 21 + 21 + + -parameters + + + + org.projectlombok + lombok + ${lombok.version} + + + + + + +``` + +### 2.3 依赖冲突检查 +- ⚠️ 避免在 pom.xml 中重复声明同一依赖 +- ⚠️ 使用 `mvn dependency:tree` 检查依赖冲突 + +--- + +## 3. Lombok 使用规范 + +### 3.1 禁止在继承 ServiceImpl 的类上使用 @Slf4j + +**❌ 错误示例**: +```java +@Slf4j +@Service +public class DeptService extends ServiceImpl { + public void saveDept(Dept dept) { + log.info("保存部门"); // 编译错误! + } +} +``` + +**✅ 正确示例**: +```java +@Service +public class DeptService extends ServiceImpl { + private static final Logger logger = LoggerFactory.getLogger(DeptService.class); + + public void saveDept(Dept dept) { + logger.info("保存部门"); + } +} +``` + +**原因**: MyBatis-Plus 的 ServiceImpl 基类已定义 `protected Log log`(org.apache.ibatis.logging.Log),与 Lombok @Slf4j 生成的字段冲突。 + +### 3.2 实体类 Lombok 注解规范 +```java +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_dept") +public class Dept extends BaseEntity { + // 必须确保 import lombok.Data 和 import lombok.EqualsAndHashCode 存在 +} +``` + +### 3.3 Controller/Component Lombok 注解 +```java +@RestController +@RequestMapping("/api/v1/dept") +@RequiredArgsConstructor // 推荐用于依赖注入 +public class DeptController { + private final DeptService deptService; +} +``` + +--- + +## 4. Shiro 认证框架规范 + +### 4.1 核心组件清单 +1. **JwtToken.java** - 自定义 Token 类 +2. **JwtRealm.java** - 认证授权域 +3. **JwtFilter.java** - JWT 过滤器 +4. **ShiroConfig.java** - Shiro 配置类 +5. **PasswordEncoderConfig.java** - 密码编码器(使用 BCrypt) + +### 4.2 权限注解规范 +- ✅ 使用 `@RequiresRoles("ADMIN")` +- ✅ 使用 `@RequiresPermissions("system:user:view")` +- ❌ 不使用 Spring Security 注解(@PreAuthorize) + +### 4.3 认证流程 +1. 用户登录 → 查询数据库 +2. BCrypt 验证密码 +3. 生成 JWT Token(包含 userId, username, tenantId) +4. 每次请求通过 JwtFilter 验证 Token +5. JwtRealm 加载用户权限 + +--- + +## 5. 代码编写规范 + +### 5.1 分层架构 +``` +Controller → Service → Mapper → Database + ↓ ↓ + DTO Entity +``` + +### 5.2 包结构规范 +``` +com.fundplatform.sys +├── controller # 控制器层 +├── service # 业务逻辑层 +├── mapper # 数据访问层 +├── entity # 实体类 +├── dto # 数据传输对象 +├── vo # 视图对象 +├── security # 安全相关(Shiro配置) +├── config # 配置类 +└── util # 工具类 +``` + +### 5.3 命名规范 +- **Entity**: 单数名词,如 `User.java`、`Dept.java` +- **Mapper**: 实体名 + Mapper,如 `UserMapper.java` +- **Service**: 实体名 + Service,如 `UserService.java` +- **Controller**: 实体名 + Controller,如 `UserController.java` + +### 5.4 日志规范 +```java +// 业务操作日志 +logger.info("[ServiceName] 业务操作描述: {}, 参数: {}", entity.getName(), param); + +// 错误日志 +logger.error("[ServiceName] 操作失败: {}", errorMessage, exception); +``` + +--- + +## 6. 数据库规范 + +### 6.1 表命名规范 +- 系统表前缀: `sys_`(如 `sys_user`, `sys_role`) +- 业务表前缀: `biz_`(如 `biz_project`, `biz_fund`) + +### 6.2 字段规范 +- 主键: `主表名_id`(如 `user_id`, `dept_id`) +- 租户ID: `tenant_id`(必须字段,默认值 1) +- 软删除: `deleted`(0-未删除,1-已删除) +- 状态: `status`(1-启用,0-禁用) +- 时间: `created_time`, `updated_time` + +### 6.3 BaseEntity 基类 +```java +@Data +public class BaseEntity implements Serializable { + private Long tenantId; // 租户ID + private Integer deleted; // 删除标记 + private LocalDateTime createdTime; // 创建时间 + private Long createdBy; // 创建人 + private LocalDateTime updatedTime; // 更新时间 + private Long updatedBy; // 更新人 +} +``` + +--- + +## 7. API 规范 + +### 7.1 RESTful 路径规范 +``` +GET /api/v1/dept # 查询列表 +GET /api/v1/dept/{id} # 查询单个 +POST /api/v1/dept # 创建 +PUT /api/v1/dept/{id} # 更新 +DELETE /api/v1/dept/{id} # 删除 +``` + +### 7.2 统一响应格式 +```java +public class Result { + private Integer code; // 200-成功,其他-失败 + private String message; + private T data; +} +``` + +### 7.3 认证路径规范 +``` +POST /api/v1/auth/login # 登录(不需要认证) +POST /api/v1/auth/logout # 登出 +POST /api/v1/auth/refresh # 刷新Token +``` + +--- + +## 8. 多租户规范 + +### 8.1 租户隔离 +- 所有业务表必须包含 `tenant_id` 字段 +- Service 层插入数据时自动设置 `tenant_id` +- 查询时自动过滤 `tenant_id` + +### 8.2 默认租户 +- 系统租户: `tenant_id = 0` +- 业务租户: `tenant_id >= 1` + +--- + +## 9. 编译和测试规范 + +### 9.1 编译命令 +```bash +# 清理编译 +cd /home/along/MyCode/wanjiabuluo/fundplatform/fundplatform +mvn clean compile -DskipTests + +# 安装到本地仓库 +mvn clean install -DskipTests + +# 编译单个模块 +mvn clean compile -pl fund-sys -am -DskipTests +``` + +### 9.2 禁止的操作 +- ❌ 不要在 run_in_terminal 中重复 `cd` 到同一目录 +- ❌ 不要使用 `mvn clean` 后立即执行其他命令(应分开执行) + +--- + +## 10. Git 规范 + +### 10.1 分支策略 +- `main` - 生产分支 +- `develop` - 开发分支 +- `feature/*` - 功能分支 +- `bugfix/*` - 修复分支 + +### 10.2 提交规范 +``` +feat: 新功能 +fix: 修复bug +refactor: 重构 +docs: 文档更新 +style: 代码格式调整 +test: 测试相关 +chore: 构建/工具链相关 +``` + +--- + +## 11. 常见陷阱清单 + +### 11.1 Lombok 相关 +- ✓ 每个子模块必须显式配置 maven-compiler-plugin +- ✓ 继承 ServiceImpl 的类不能使用 @Slf4j +- ✓ 确保所有 import lombok.* 语句存在 + +### 11.2 Shiro 相关 +- ✓ 使用 Shiro 2.0.0(支持 jakarta.servlet) +- ✓ 保留 spring-security-crypto(用于 BCrypt) +- ✓ 登录接口路径配置为匿名访问 + +### 11.3 Maven 相关 +- ✓ 避免在 pom.xml 中重复声明依赖 +- ✓ 父 pom 中未创建的子模块需注释掉 +- ✓ 使用 `-U` 参数强制更新依赖 + +### 11.4 数据库相关 +- ✓ 初始化 SQL 主键必须与字段定义一致 +- ✓ tenant_id 必须有默认值保障机制 +- ✓ 软删除字段 deleted 默认为 0 + +--- + +## 12. 重新生成代码的策略 + +### 12.1 代码生成工具选择 +**选项 A**: MyBatis-Plus 代码生成器 +- 优点: 自动生成 Entity、Mapper、Service、Controller +- 缺点: 需要手动调整生成的代码 + +**选项 B**: 手动编写 +- 优点: 完全可控,符合规范 +- 缺点: 工作量大 + +**选项 C**: 模板化生成 +- 优点: 可重复使用,质量稳定 +- 缺点: 需要先建立模板 + +### 12.2 推荐策略 +1. **第一步**: 手动编写核心基础类(BaseEntity、Result、自定义异常) +2. **第二步**: 使用 MyBatis-Plus 生成器生成基础 CRUD +3. **第三步**: 手动完善业务逻辑和复杂查询 +4. **第四步**: 编写单元测试验证 + +### 12.3 生成顺序 +``` +1. fund-common (基础类) + ├── BaseEntity + ├── Result + └── 工具类 + +2. fund-sys (系统模块) + ├── 实体类 (Entity) + ├── Mapper 接口 + ├── Service 实现 + ├── Controller + └── Shiro 配置 + +3. fund-gateway (网关) + └── 路由配置 + +4. fund-cust (客户模块) +5. fund-proj (项目模块) +``` + +--- + +## 13. 架构约束 + +### 13.1 必须遵守的架构要求 +1. ✅ 使用 Shiro 作为认证框架(架构文档要求) +2. ✅ 实现多租户数据隔离 +3. ✅ Header 透传(X-Uid, X-Uname) +4. ✅ 全链路日志追踪 + +### 13.2 性能要求 +- 接口响应时间 < 500ms +- 数据库查询使用索引 +- 使用 Redis 缓存热点数据 + +--- + +## 14. 下一步行动 + +### 14.1 准备工作 +- [ ] 备份当前代码 +- [ ] 清理所有 target 目录 +- [ ] 确认数据库表结构 +- [ ] 准备初始化 SQL 脚本 + +### 14.2 代码生成 +- [ ] 生成 fund-common 基础类 +- [ ] 生成 fund-sys 实体类和 Mapper +- [ ] 实现 Shiro 认证框架 +- [ ] 编写核心业务逻辑 + +### 14.3 验证 +- [ ] 编译通过(mvn clean compile) +- [ ] 启动成功 +- [ ] 登录功能正常 +- [ ] 基础 CRUD 接口正常 + +--- + +**注意**: 本规则清单将持续更新,每次遇到问题都会添加到"常见陷阱清单"中。 +