feat: 完成后端核心业务模块开发
阶段二:认证授权模块 - User实体类、Mapper、DataService - Token服务(Redis存储)、密码加密(BCrypt) - 认证拦截器、UserContext上下文 - 登录/登出接口 阶段三:核心业务模块 - 用户管理:CRUD、状态管理、密码重置 - 模板管理:CRUD、状态管理 - 工作日志:CRUD、权限控制 配置分离架构 - env.properties(环境敏感配置) - service.properties(服务配置) - logback-spring.xml更新 部署脚本 - deploy/目录(Nginx配置、启停脚本、备份脚本) 单元测试:29个测试全部通过
This commit is contained in:
parent
ae33bd4d6a
commit
dbcc06edbc
6
.gitignore
vendored
6
.gitignore
vendored
@ -48,6 +48,12 @@ application-test.yml
|
||||
application-prod.yml
|
||||
bootstrap.yml
|
||||
|
||||
# conf 目录下的实际配置文件(敏感信息)
|
||||
# 开发阶段:src/main/resources/conf/
|
||||
# 部署阶段:conf/(打包后)
|
||||
src/main/resources/conf/env.properties
|
||||
src/main/resources/conf/service.properties
|
||||
|
||||
# 数据库备份
|
||||
*.sql.backup
|
||||
db_backup/
|
||||
|
||||
411
deploy/DEPLOY.md
Normal file
411
deploy/DEPLOY.md
Normal file
@ -0,0 +1,411 @@
|
||||
# 工作日志服务平台 - 部署指南
|
||||
|
||||
## 目录说明
|
||||
|
||||
```
|
||||
deploy/
|
||||
├── nginx/
|
||||
│ └── worklog.conf # Nginx 配置文件
|
||||
└── scripts/
|
||||
├── backup.sh # 数据库备份脚本
|
||||
├── start.sh # 应用启动脚本
|
||||
├── stop.sh # 应用停止脚本
|
||||
└── restart.sh # 应用重启脚本
|
||||
```
|
||||
|
||||
## 一、服务器环境准备
|
||||
|
||||
### 1.1 系统要求
|
||||
|
||||
- 操作系统: Linux (Ubuntu 20.04+ / CentOS 7+)
|
||||
- JDK: 21+
|
||||
- MySQL: 8.0+
|
||||
- Redis: 7.x
|
||||
- Nginx: 1.18+
|
||||
|
||||
### 1.2 创建部署目录
|
||||
|
||||
```bash
|
||||
# 创建应用目录
|
||||
sudo mkdir -p /opt/worklog/worklog-api
|
||||
sudo mkdir -p /opt/worklog/worklog-admin
|
||||
sudo mkdir -p /opt/worklog/worklog-mobile
|
||||
|
||||
# 创建备份目录
|
||||
sudo mkdir -p /backup/mysql
|
||||
|
||||
# 设置权限
|
||||
sudo chown -R $USER:$USER /opt/worklog
|
||||
sudo chown -R $USER:$USER /backup/mysql
|
||||
```
|
||||
|
||||
## 二、应用部署
|
||||
|
||||
### 2.1 部署后端应用
|
||||
|
||||
1. **编译打包**
|
||||
|
||||
```bash
|
||||
cd worklog-api
|
||||
mvn clean package -DskipTests
|
||||
```
|
||||
|
||||
2. **上传 JAR 文件**
|
||||
|
||||
```bash
|
||||
scp target/worklog-api-1.0.0.jar user@server:/opt/worklog/worklog-api/
|
||||
```
|
||||
|
||||
3. **上传部署脚本**
|
||||
|
||||
```bash
|
||||
scp -r deploy/scripts user@server:/opt/worklog/worklog-api/
|
||||
```
|
||||
|
||||
4. **配置 application.yml**
|
||||
|
||||
```bash
|
||||
# 在服务器上创建配置文件
|
||||
cd /opt/worklog/worklog-api
|
||||
vi application.yml
|
||||
|
||||
# 复制 application.yml.example 内容并修改为生产环境配置
|
||||
# 重点修改:
|
||||
# - 数据库连接信息
|
||||
# - Redis 连接信息
|
||||
# - 日志路径
|
||||
# - 文件上传路径
|
||||
```
|
||||
|
||||
5. **启动应用**
|
||||
|
||||
```bash
|
||||
cd /opt/worklog/worklog-api
|
||||
./scripts/start.sh
|
||||
```
|
||||
|
||||
6. **查看日志**
|
||||
|
||||
```bash
|
||||
tail -f /opt/worklog/worklog-api/logs/console.log
|
||||
tail -f /opt/worklog/worklog-api/logs/app.log
|
||||
```
|
||||
|
||||
### 2.2 应用管理命令
|
||||
|
||||
```bash
|
||||
# 启动应用
|
||||
./scripts/start.sh
|
||||
|
||||
# 停止应用
|
||||
./scripts/stop.sh
|
||||
|
||||
# 重启应用
|
||||
./scripts/restart.sh
|
||||
|
||||
# 查看运行状态
|
||||
ps aux | grep worklog-api
|
||||
```
|
||||
|
||||
## 三、Nginx 配置
|
||||
|
||||
### 3.1 安装 Nginx
|
||||
|
||||
```bash
|
||||
# Ubuntu
|
||||
sudo apt update
|
||||
sudo apt install nginx
|
||||
|
||||
# CentOS
|
||||
sudo yum install nginx
|
||||
```
|
||||
|
||||
### 3.2 配置 Nginx
|
||||
|
||||
1. **复制配置文件**
|
||||
|
||||
```bash
|
||||
sudo cp deploy/nginx/worklog.conf /etc/nginx/sites-available/
|
||||
sudo ln -s /etc/nginx/sites-available/worklog.conf /etc/nginx/sites-enabled/
|
||||
```
|
||||
|
||||
2. **修改配置**
|
||||
|
||||
编辑 `/etc/nginx/sites-available/worklog.conf`,修改以下内容:
|
||||
- `server_name`: 修改为实际域名
|
||||
- 静态文件路径: 根据实际部署路径修改
|
||||
|
||||
3. **测试配置**
|
||||
|
||||
```bash
|
||||
sudo nginx -t
|
||||
```
|
||||
|
||||
4. **重启 Nginx**
|
||||
|
||||
```bash
|
||||
sudo systemctl restart nginx
|
||||
sudo systemctl enable nginx # 开机自启
|
||||
```
|
||||
|
||||
### 3.3 HTTPS 配置(推荐)
|
||||
|
||||
1. **申请 SSL 证书**(使用 Let's Encrypt 免费证书)
|
||||
|
||||
```bash
|
||||
sudo apt install certbot python3-certbot-nginx
|
||||
sudo certbot --nginx -d worklog.example.com
|
||||
```
|
||||
|
||||
2. **自动续期**
|
||||
|
||||
```bash
|
||||
sudo certbot renew --dry-run
|
||||
```
|
||||
|
||||
## 四、数据库备份
|
||||
|
||||
### 4.1 配置备份脚本
|
||||
|
||||
1. **修改配置**
|
||||
|
||||
编辑 `scripts/backup.sh`,确认以下配置:
|
||||
- 数据库连接信息
|
||||
- 备份目录路径
|
||||
- 保留策略
|
||||
|
||||
2. **测试备份**
|
||||
|
||||
```bash
|
||||
cd /opt/worklog/worklog-api
|
||||
./scripts/backup.sh full
|
||||
```
|
||||
|
||||
### 4.2 配置定时备份
|
||||
|
||||
```bash
|
||||
# 编辑 crontab
|
||||
crontab -e
|
||||
|
||||
# 添加以下内容:
|
||||
# 每日凌晨 2:00 全量备份
|
||||
0 2 * * * /opt/worklog/worklog-api/scripts/backup.sh full
|
||||
|
||||
# 每周日凌晨 1:00 全量备份(保留更久)
|
||||
0 1 * * 0 /opt/worklog/worklog-api/scripts/backup.sh full
|
||||
```
|
||||
|
||||
### 4.3 恢复数据库
|
||||
|
||||
```bash
|
||||
./scripts/backup.sh restore /backup/mysql/worklog/full/full_20260224_020000.sql.gz
|
||||
```
|
||||
|
||||
## 五、监控与日志
|
||||
|
||||
### 5.1 日志文件位置
|
||||
|
||||
```
|
||||
/opt/worklog/worklog-api/logs/
|
||||
├── console.log # 控制台输出
|
||||
├── app.log # 应用日志
|
||||
├── sql.log # SQL 日志
|
||||
└── gc.log # GC 日志
|
||||
```
|
||||
|
||||
### 5.2 查看日志
|
||||
|
||||
```bash
|
||||
# 实时查看应用日志
|
||||
tail -f /opt/worklog/worklog-api/logs/app.log
|
||||
|
||||
# 查看 SQL 日志
|
||||
tail -f /opt/worklog/worklog-api/logs/sql.log
|
||||
|
||||
# 查看错误日志
|
||||
grep ERROR /opt/worklog/worklog-api/logs/app.log
|
||||
|
||||
# 查看最近 100 行
|
||||
tail -n 100 /opt/worklog/worklog-api/logs/app.log
|
||||
```
|
||||
|
||||
### 5.3 日志清理
|
||||
|
||||
```bash
|
||||
# 清理 7 天前的日志
|
||||
find /opt/worklog/worklog-api/logs -name "*.log" -mtime +7 -delete
|
||||
```
|
||||
|
||||
建议配置 logrotate 自动清理日志。
|
||||
|
||||
## 六、性能优化
|
||||
|
||||
### 6.1 JVM 参数调优
|
||||
|
||||
编辑 `scripts/start.sh` 中的 `JVM_OPTS`:
|
||||
|
||||
```bash
|
||||
# 根据服务器内存调整堆大小
|
||||
JVM_OPTS="-Xms1g -Xmx2g"
|
||||
|
||||
# 使用 G1 垃圾回收器
|
||||
JVM_OPTS="${JVM_OPTS} -XX:+UseG1GC"
|
||||
|
||||
# GC 日志
|
||||
JVM_OPTS="${JVM_OPTS} -Xloggc:${APP_HOME}/logs/gc.log"
|
||||
```
|
||||
|
||||
### 6.2 数据库优化
|
||||
|
||||
1. 配置连接池参数(application.yml)
|
||||
2. 添加合适的索引
|
||||
3. 定期分析慢查询日志
|
||||
|
||||
### 6.3 Redis 优化
|
||||
|
||||
1. 配置持久化策略
|
||||
2. 设置最大内存限制
|
||||
3. 配置淘汰策略
|
||||
|
||||
## 七、故障排查
|
||||
|
||||
### 7.1 应用无法启动
|
||||
|
||||
1. 检查 JDK 版本: `java -version`
|
||||
2. 检查端口占用: `netstat -tuln | grep 8080`
|
||||
3. 查看启动日志: `tail -f logs/console.log`
|
||||
4. 检查配置文件: `cat application.yml`
|
||||
|
||||
### 7.2 数据库连接失败
|
||||
|
||||
1. 检查 MySQL 服务: `systemctl status mysql`
|
||||
2. 检查防火墙: `sudo ufw status`
|
||||
3. 测试连接: `mysql -h localhost -u worklog -p`
|
||||
4. 检查数据库用户权限
|
||||
|
||||
### 7.3 Redis 连接失败
|
||||
|
||||
1. 检查 Redis 服务: `systemctl status redis`
|
||||
2. 测试连接: `redis-cli -h localhost -p 6379 -a password`
|
||||
3. 检查配置文件: `/etc/redis/redis.conf`
|
||||
|
||||
### 7.4 Nginx 502 错误
|
||||
|
||||
1. 检查后端应用是否运行: `ps aux | grep worklog`
|
||||
2. 检查端口: `netstat -tuln | grep 8080`
|
||||
3. 查看 Nginx 错误日志: `tail -f /var/log/nginx/error.log`
|
||||
|
||||
## 八、安全加固
|
||||
|
||||
### 8.1 防火墙配置
|
||||
|
||||
```bash
|
||||
# 只开放必要端口
|
||||
sudo ufw allow 80/tcp # HTTP
|
||||
sudo ufw allow 443/tcp # HTTPS
|
||||
sudo ufw allow 22/tcp # SSH
|
||||
sudo ufw enable
|
||||
```
|
||||
|
||||
### 8.2 修改默认密码
|
||||
|
||||
1. MySQL root 密码
|
||||
2. Redis 密码
|
||||
3. 应用数据库用户密码
|
||||
4. 管理员默认账号密码
|
||||
|
||||
### 8.3 限制 Swagger 访问
|
||||
|
||||
在生产环境建议:
|
||||
1. 关闭 Swagger: `springdoc.swagger-ui.enabled=false`
|
||||
2. 或通过 Nginx 限制内网访问
|
||||
|
||||
## 九、升级部署
|
||||
|
||||
### 9.1 升级步骤
|
||||
|
||||
1. 备份数据库
|
||||
|
||||
```bash
|
||||
./scripts/backup.sh full
|
||||
```
|
||||
|
||||
2. 停止应用
|
||||
|
||||
```bash
|
||||
./scripts/stop.sh
|
||||
```
|
||||
|
||||
3. 备份当前版本
|
||||
|
||||
```bash
|
||||
cp worklog-api-1.0.0.jar worklog-api-1.0.0.jar.bak
|
||||
```
|
||||
|
||||
4. 上传新版本
|
||||
|
||||
```bash
|
||||
scp target/worklog-api-1.1.0.jar user@server:/opt/worklog/worklog-api/
|
||||
```
|
||||
|
||||
5. 启动应用
|
||||
|
||||
```bash
|
||||
# 修改 start.sh 中的 JAR 文件名
|
||||
vi scripts/start.sh
|
||||
./scripts/start.sh
|
||||
```
|
||||
|
||||
6. 验证升级
|
||||
|
||||
```bash
|
||||
# 检查健康状态
|
||||
curl http://localhost:8080/api/v1/health
|
||||
|
||||
# 查看日志
|
||||
tail -f logs/app.log
|
||||
```
|
||||
|
||||
### 9.2 回滚方案
|
||||
|
||||
如果升级失败,执行回滚:
|
||||
|
||||
```bash
|
||||
./scripts/stop.sh
|
||||
cp worklog-api-1.0.0.jar.bak worklog-api-1.0.0.jar
|
||||
./scripts/start.sh
|
||||
```
|
||||
|
||||
## 十、常用命令速查
|
||||
|
||||
```bash
|
||||
# 应用管理
|
||||
./scripts/start.sh # 启动
|
||||
./scripts/stop.sh # 停止
|
||||
./scripts/restart.sh # 重启
|
||||
|
||||
# 数据库备份
|
||||
./scripts/backup.sh full # 全量备份
|
||||
./scripts/backup.sh restore <file> # 恢复
|
||||
|
||||
# 日志查看
|
||||
tail -f logs/app.log # 应用日志
|
||||
tail -f logs/sql.log # SQL 日志
|
||||
grep ERROR logs/app.log # 错误日志
|
||||
|
||||
# Nginx
|
||||
sudo nginx -t # 测试配置
|
||||
sudo systemctl restart nginx # 重启
|
||||
sudo systemctl status nginx # 状态
|
||||
|
||||
# 系统状态
|
||||
ps aux | grep worklog # 进程状态
|
||||
netstat -tuln | grep 8080 # 端口监听
|
||||
df -h # 磁盘空间
|
||||
free -h # 内存使用
|
||||
top # 系统负载
|
||||
```
|
||||
|
||||
## 联系方式
|
||||
|
||||
如有问题,请联系运维团队。
|
||||
129
deploy/nginx/worklog.conf
Normal file
129
deploy/nginx/worklog.conf
Normal file
@ -0,0 +1,129 @@
|
||||
# ====================================================
|
||||
# 工作日志服务平台 - Nginx 配置
|
||||
# ====================================================
|
||||
# 说明:
|
||||
# 1. 此配置用于生产环境部署
|
||||
# 2. 需根据实际域名和路径修改配置
|
||||
# 3. 建议启用 HTTPS(本配置为 HTTP 示例)
|
||||
# ====================================================
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name worklog.example.com; # 修改为实际域名
|
||||
|
||||
# 字符集
|
||||
charset utf-8;
|
||||
|
||||
# 访问日志
|
||||
access_log /var/log/nginx/worklog_access.log;
|
||||
error_log /var/log/nginx/worklog_error.log;
|
||||
|
||||
# Gzip 压缩
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
|
||||
|
||||
# 管理后台
|
||||
location /admin/ {
|
||||
alias /opt/worklog/worklog-admin/dist/;
|
||||
try_files $uri $uri/ /admin/index.html;
|
||||
index index.html;
|
||||
|
||||
# 静态资源缓存
|
||||
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
|
||||
# 移动端 H5
|
||||
location /mobile/ {
|
||||
alias /opt/worklog/worklog-mobile/dist/;
|
||||
try_files $uri $uri/ /mobile/index.html;
|
||||
index index.html;
|
||||
|
||||
# 静态资源缓存
|
||||
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
|
||||
# 后端 API 代理
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
|
||||
# 代理头设置
|
||||
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;
|
||||
|
||||
# 缓冲设置
|
||||
proxy_buffering on;
|
||||
proxy_buffer_size 4k;
|
||||
proxy_buffers 8 4k;
|
||||
proxy_busy_buffers_size 8k;
|
||||
|
||||
# WebSocket 支持(如需要)
|
||||
# proxy_http_version 1.1;
|
||||
# proxy_set_header Upgrade $http_upgrade;
|
||||
# proxy_set_header Connection "upgrade";
|
||||
}
|
||||
|
||||
# Swagger API 文档(生产环境建议关闭或限制访问)
|
||||
location /swagger-ui.html {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# 限制访问(可选)
|
||||
# allow 192.168.1.0/24; # 只允许内网访问
|
||||
# deny all;
|
||||
}
|
||||
|
||||
# 健康检查接口
|
||||
location /api/v1/health {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header Host $host;
|
||||
access_log off; # 健康检查不记录日志
|
||||
}
|
||||
|
||||
# 禁止访问隐藏文件
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTPS 配置示例(需要 SSL 证书)
|
||||
# server {
|
||||
# listen 443 ssl http2;
|
||||
# server_name worklog.example.com;
|
||||
#
|
||||
# # SSL 证书配置
|
||||
# ssl_certificate /path/to/cert.pem;
|
||||
# ssl_certificate_key /path/to/cert.key;
|
||||
# ssl_protocols TLSv1.2 TLSv1.3;
|
||||
# ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
# ssl_prefer_server_ciphers on;
|
||||
# ssl_session_cache shared:SSL:10m;
|
||||
# ssl_session_timeout 10m;
|
||||
#
|
||||
# # 其他配置同上...
|
||||
# }
|
||||
#
|
||||
# # HTTP 重定向到 HTTPS
|
||||
# server {
|
||||
# listen 80;
|
||||
# server_name worklog.example.com;
|
||||
# return 301 https://$server_name$request_uri;
|
||||
# }
|
||||
198
deploy/scripts/backup.sh
Executable file
198
deploy/scripts/backup.sh
Executable file
@ -0,0 +1,198 @@
|
||||
#!/bin/bash
|
||||
# ====================================================
|
||||
# 工作日志服务平台 - 数据库备份脚本
|
||||
# ====================================================
|
||||
# 说明:
|
||||
# 1. 支持全量备份和增量备份
|
||||
# 2. 自动压缩备份文件
|
||||
# 3. 自动清理过期备份
|
||||
# 4. 建议配置到 crontab 定时执行
|
||||
# ====================================================
|
||||
|
||||
# ==================== 配置参数 ====================
|
||||
# 数据库配置
|
||||
DB_HOST="localhost"
|
||||
DB_PORT="3306"
|
||||
DB_NAME="worklog"
|
||||
DB_USER="worklog"
|
||||
DB_PASSWORD="Wlog@123"
|
||||
|
||||
# 备份目录配置
|
||||
BACKUP_ROOT="/backup/mysql"
|
||||
BACKUP_DIR="${BACKUP_ROOT}/${DB_NAME}"
|
||||
FULL_BACKUP_DIR="${BACKUP_DIR}/full"
|
||||
INCR_BACKUP_DIR="${BACKUP_DIR}/incr"
|
||||
|
||||
# 保留策略(天数)
|
||||
FULL_KEEP_DAYS=28 # 全量备份保留 4 周
|
||||
INCR_KEEP_DAYS=7 # 增量备份保留 7 天
|
||||
|
||||
# 日志文件
|
||||
LOG_FILE="${BACKUP_DIR}/backup.log"
|
||||
|
||||
# ==================== 初始化 ====================
|
||||
# 创建备份目录
|
||||
mkdir -p "${FULL_BACKUP_DIR}"
|
||||
mkdir -p "${INCR_BACKUP_DIR}"
|
||||
mkdir -p "$(dirname ${LOG_FILE})"
|
||||
|
||||
# 日志函数
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "${LOG_FILE}"
|
||||
}
|
||||
|
||||
# ==================== 全量备份 ====================
|
||||
full_backup() {
|
||||
log "========== 开始全量备份 =========="
|
||||
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="${FULL_BACKUP_DIR}/full_${DATE}.sql"
|
||||
|
||||
log "备份文件: ${BACKUP_FILE}"
|
||||
|
||||
# 执行备份
|
||||
mysqldump \
|
||||
-h"${DB_HOST}" \
|
||||
-P"${DB_PORT}" \
|
||||
-u"${DB_USER}" \
|
||||
-p"${DB_PASSWORD}" \
|
||||
--single-transaction \
|
||||
--routines \
|
||||
--triggers \
|
||||
--events \
|
||||
--hex-blob \
|
||||
--default-character-set=utf8mb4 \
|
||||
"${DB_NAME}" > "${BACKUP_FILE}" 2>> "${LOG_FILE}"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
log "数据库备份成功"
|
||||
|
||||
# 压缩备份文件
|
||||
log "开始压缩备份文件..."
|
||||
gzip "${BACKUP_FILE}"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
BACKUP_SIZE=$(du -h "${BACKUP_FILE}.gz" | awk '{print $1}')
|
||||
log "压缩完成,文件大小: ${BACKUP_SIZE}"
|
||||
log "备份文件路径: ${BACKUP_FILE}.gz"
|
||||
else
|
||||
log "ERROR: 压缩失败"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log "ERROR: 数据库备份失败"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "========== 全量备份完成 =========="
|
||||
return 0
|
||||
}
|
||||
|
||||
# ==================== 增量备份 ====================
|
||||
# 注意:MySQL 增量备份需要启用 binlog
|
||||
# 本脚本暂不实现增量备份,可根据需要扩展
|
||||
incr_backup() {
|
||||
log "========== 增量备份 =========="
|
||||
log "INFO: 增量备份功能暂未实现,建议每日执行全量备份"
|
||||
log "如需增量备份,请启用 MySQL binlog 并配置 mysqlbinlog 工具"
|
||||
}
|
||||
|
||||
# ==================== 清理过期备份 ====================
|
||||
cleanup_old_backups() {
|
||||
log "========== 开始清理过期备份 =========="
|
||||
|
||||
# 清理过期全量备份
|
||||
log "清理 ${FULL_KEEP_DAYS} 天前的全量备份..."
|
||||
find "${FULL_BACKUP_DIR}" -name "full_*.sql.gz" -type f -mtime +${FULL_KEEP_DAYS} -delete
|
||||
FULL_COUNT=$(find "${FULL_BACKUP_DIR}" -name "full_*.sql.gz" -type f | wc -l)
|
||||
log "当前保留全量备份文件数: ${FULL_COUNT}"
|
||||
|
||||
# 清理过期增量备份
|
||||
log "清理 ${INCR_KEEP_DAYS} 天前的增量备份..."
|
||||
find "${INCR_BACKUP_DIR}" -name "incr_*.sql.gz" -type f -mtime +${INCR_KEEP_DAYS} -delete
|
||||
INCR_COUNT=$(find "${INCR_BACKUP_DIR}" -name "incr_*.sql.gz" -type f | wc -l)
|
||||
log "当前保留增量备份文件数: ${INCR_COUNT}"
|
||||
|
||||
log "========== 清理完成 =========="
|
||||
}
|
||||
|
||||
# ==================== 恢复脚本 ====================
|
||||
restore_backup() {
|
||||
BACKUP_FILE=$1
|
||||
|
||||
if [ -z "${BACKUP_FILE}" ]; then
|
||||
log "ERROR: 请指定要恢复的备份文件"
|
||||
echo "用法: $0 restore <backup_file.sql.gz>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "${BACKUP_FILE}" ]; then
|
||||
log "ERROR: 备份文件不存在: ${BACKUP_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "========== 开始恢复数据库 =========="
|
||||
log "WARNING: 此操作将覆盖当前数据库,请确认!"
|
||||
read -p "确认恢复? (yes/no): " CONFIRM
|
||||
|
||||
if [ "${CONFIRM}" != "yes" ]; then
|
||||
log "取消恢复操作"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "正在恢复备份文件: ${BACKUP_FILE}"
|
||||
|
||||
# 解压并恢复
|
||||
gunzip -c "${BACKUP_FILE}" | mysql \
|
||||
-h"${DB_HOST}" \
|
||||
-P"${DB_PORT}" \
|
||||
-u"${DB_USER}" \
|
||||
-p"${DB_PASSWORD}" \
|
||||
"${DB_NAME}" 2>> "${LOG_FILE}"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
log "数据库恢复成功"
|
||||
else
|
||||
log "ERROR: 数据库恢复失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "========== 恢复完成 =========="
|
||||
}
|
||||
|
||||
# ==================== 主函数 ====================
|
||||
main() {
|
||||
case "$1" in
|
||||
full)
|
||||
full_backup
|
||||
cleanup_old_backups
|
||||
;;
|
||||
incr)
|
||||
incr_backup
|
||||
cleanup_old_backups
|
||||
;;
|
||||
restore)
|
||||
restore_backup "$2"
|
||||
;;
|
||||
cleanup)
|
||||
cleanup_old_backups
|
||||
;;
|
||||
*)
|
||||
echo "用法: $0 {full|incr|restore|cleanup}"
|
||||
echo ""
|
||||
echo "命令说明:"
|
||||
echo " full - 执行全量备份"
|
||||
echo " incr - 执行增量备份(暂未实现)"
|
||||
echo " restore - 恢复备份(需指定备份文件)"
|
||||
echo " cleanup - 清理过期备份"
|
||||
echo ""
|
||||
echo "示例:"
|
||||
echo " $0 full # 全量备份"
|
||||
echo " $0 restore /backup/mysql/worklog/full/full_20260224_020000.sql.gz"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
101
deploy/scripts/init_db.sh
Executable file
101
deploy/scripts/init_db.sh
Executable file
@ -0,0 +1,101 @@
|
||||
#!/bin/bash
|
||||
# ====================================================
|
||||
# 工作日志服务平台 - 数据库初始化执行脚本
|
||||
# ====================================================
|
||||
# 使用说明:
|
||||
# 1. 确保MySQL服务正在运行
|
||||
# 2. 准备好MySQL root密码
|
||||
# 3. 执行此脚本:./init_db.sh
|
||||
# ====================================================
|
||||
|
||||
set -e # 遇到错误立即退出
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
SQL_DIR="${PROJECT_ROOT}/sql"
|
||||
|
||||
echo "========================================"
|
||||
echo "工作日志服务平台 - 数据库初始化"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# 检查SQL文件是否存在
|
||||
if [ ! -f "${SQL_DIR}/create_user.sql" ]; then
|
||||
echo "错误:找不到文件 ${SQL_DIR}/create_user.sql"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "${SQL_DIR}/init_database.sql" ]; then
|
||||
echo "错误:找不到文件 ${SQL_DIR}/init_database.sql"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "步骤1:创建数据库用户"
|
||||
echo "----------------------------------------"
|
||||
echo "将创建以下用户:"
|
||||
echo " 用户名:worklog"
|
||||
echo " 密码:Wlog@123"
|
||||
echo " 权限:worklog数据库的所有权限"
|
||||
echo ""
|
||||
read -p "是否继续?(y/n): " confirm
|
||||
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
|
||||
echo "已取消操作"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "执行 create_user.sql ..."
|
||||
mysql -u root -p < "${SQL_DIR}/create_user.sql"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ 数据库用户创建成功"
|
||||
else
|
||||
echo "✗ 数据库用户创建失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "步骤2:创建数据库和表结构"
|
||||
echo "----------------------------------------"
|
||||
echo "将创建以下内容:"
|
||||
echo " 数据库:worklog"
|
||||
echo " 表:sys_user, log_template, work_log"
|
||||
echo " 初始数据:默认管理员账号和日志模板"
|
||||
echo ""
|
||||
read -p "是否继续?(y/n): " confirm
|
||||
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
|
||||
echo "已取消操作"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "执行 init_database.sql ..."
|
||||
mysql -u root -p < "${SQL_DIR}/init_database.sql"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ 数据库和表结构创建成功"
|
||||
else
|
||||
echo "✗ 数据库和表结构创建失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "数据库初始化完成!"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "数据库信息:"
|
||||
echo " 数据库名:worklog"
|
||||
echo " 用户名:worklog"
|
||||
echo " 密码:Wlog@123"
|
||||
echo " 字符集:utf8mb4"
|
||||
echo ""
|
||||
echo "默认管理员账号:"
|
||||
echo " 用户名:admin"
|
||||
echo " 密码:admin123(需要通过程序重置)"
|
||||
echo ""
|
||||
echo "验证连接:"
|
||||
echo " mysql -u worklog -p worklog"
|
||||
echo " 输入密码:Wlog@123"
|
||||
echo ""
|
||||
echo "========================================"
|
||||
34
deploy/scripts/restart.sh
Executable file
34
deploy/scripts/restart.sh
Executable file
@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
# ====================================================
|
||||
# 工作日志服务平台 - 应用重启脚本
|
||||
# ====================================================
|
||||
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
|
||||
echo "========================================="
|
||||
echo "重启应用"
|
||||
echo "========================================="
|
||||
|
||||
# 停止应用
|
||||
bash "${SCRIPT_DIR}/stop.sh"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: 停止应用失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 等待 3 秒
|
||||
echo "等待 3 秒..."
|
||||
sleep 3
|
||||
|
||||
# 启动应用
|
||||
bash "${SCRIPT_DIR}/start.sh"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: 启动应用失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "========================================="
|
||||
echo "应用重启成功!"
|
||||
echo "========================================="
|
||||
110
deploy/scripts/start.sh
Executable file
110
deploy/scripts/start.sh
Executable file
@ -0,0 +1,110 @@
|
||||
#!/bin/bash
|
||||
# ====================================================
|
||||
# 工作日志服务平台 - 应用启动脚本
|
||||
# ====================================================
|
||||
|
||||
# 应用配置
|
||||
APP_NAME="worklog-api"
|
||||
APP_JAR="worklog-api-1.0.0.jar"
|
||||
APP_HOME="/opt/worklog/${APP_NAME}"
|
||||
APP_JAR_PATH="${APP_HOME}/${APP_JAR}"
|
||||
|
||||
# 配置文件加载函数
|
||||
load_properties() {
|
||||
local file=$1
|
||||
if [ -f "${file}" ]; then
|
||||
echo "加载配置文件: ${file}"
|
||||
while IFS='=' read -r key value; do
|
||||
# 跳过注释和空行
|
||||
[[ ${key} =~ ^#.*$ ]] && continue
|
||||
[[ -z ${key} ]] && continue
|
||||
# 去除前后空格
|
||||
key=$(echo ${key} | xargs)
|
||||
value=$(echo ${value} | xargs)
|
||||
# 导出环境变量
|
||||
if [ -n "${key}" ] && [ -n "${value}" ]; then
|
||||
export "${key}=${value}"
|
||||
fi
|
||||
done < "${file}"
|
||||
else
|
||||
echo "警告: 配置文件不存在 ${file}"
|
||||
fi
|
||||
}
|
||||
|
||||
# 1. 加载统一环境配置
|
||||
load_properties "${APP_HOME}/conf/env.properties"
|
||||
|
||||
# 2. 加载服务个性化配置(覆盖同名参数)
|
||||
load_properties "${APP_HOME}/conf/service.properties"
|
||||
|
||||
# JVM 配置(从环境变量读取,如果未设置则使用默认值)
|
||||
JVM_XMS=${JVM_XMS:-512m}
|
||||
JVM_XMX=${JVM_XMX:-1024m}
|
||||
JVM_METASPACE_SIZE=${JVM_METASPACE_SIZE:-128m}
|
||||
JVM_MAX_METASPACE_SIZE=${JVM_MAX_METASPACE_SIZE:-256m}
|
||||
JVM_GC_TYPE=${JVM_GC_TYPE:-G1GC}
|
||||
JVM_MAX_GC_PAUSE_MILLIS=${JVM_MAX_GC_PAUSE_MILLIS:-200}
|
||||
|
||||
JVM_OPTS="-Xms${JVM_XMS} -Xmx${JVM_XMX} -XX:MetaspaceSize=${JVM_METASPACE_SIZE} -XX:MaxMetaspaceSize=${JVM_MAX_METASPACE_SIZE}"
|
||||
JVM_OPTS="${JVM_OPTS} -XX:+Use${JVM_GC_TYPE} -XX:MaxGCPauseMillis=${JVM_MAX_GC_PAUSE_MILLIS}"
|
||||
JVM_OPTS="${JVM_OPTS} -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${APP_HOME}/logs/heapdump.hprof"
|
||||
JVM_OPTS="${JVM_OPTS} -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:${APP_HOME}/logs/gc.log"
|
||||
|
||||
# Spring Boot 配置(从环境变量读取)
|
||||
SPRING_PROFILE=${SPRING_PROFILES_ACTIVE:-prod}
|
||||
SPRING_OPTS="--spring.profiles.active=${SPRING_PROFILE}"
|
||||
|
||||
# 日志文件
|
||||
LOG_DIR="${APP_HOME}/logs"
|
||||
CONSOLE_LOG="${LOG_DIR}/console.log"
|
||||
PID_FILE="${APP_HOME}/${APP_NAME}.pid"
|
||||
|
||||
# 创建日志目录
|
||||
mkdir -p "${LOG_DIR}"
|
||||
|
||||
# 检查应用是否已运行
|
||||
if [ -f "${PID_FILE}" ]; then
|
||||
PID=$(cat "${PID_FILE}")
|
||||
if ps -p ${PID} > /dev/null 2>&1; then
|
||||
echo "应用已在运行中,PID: ${PID}"
|
||||
exit 1
|
||||
else
|
||||
echo "发现残留 PID 文件,正在清理..."
|
||||
rm -f "${PID_FILE}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 检查 JAR 文件是否存在
|
||||
if [ ! -f "${APP_JAR_PATH}" ]; then
|
||||
echo "ERROR: 找不到应用 JAR 文件: ${APP_JAR_PATH}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 启动应用
|
||||
echo "========================================="
|
||||
echo "启动应用: ${APP_NAME}"
|
||||
echo "JAR 文件: ${APP_JAR_PATH}"
|
||||
echo "========================================="
|
||||
|
||||
nohup java ${JVM_OPTS} -jar "${APP_JAR_PATH}" ${SPRING_OPTS} \
|
||||
> "${CONSOLE_LOG}" 2>&1 &
|
||||
|
||||
PID=$!
|
||||
echo ${PID} > "${PID_FILE}"
|
||||
|
||||
echo "应用启动中,PID: ${PID}"
|
||||
echo "日志文件: ${CONSOLE_LOG}"
|
||||
|
||||
# 等待应用启动
|
||||
sleep 5
|
||||
|
||||
# 检查应用是否启动成功
|
||||
if ps -p ${PID} > /dev/null 2>&1; then
|
||||
echo "应用启动成功!"
|
||||
echo "查看日志: tail -f ${CONSOLE_LOG}"
|
||||
exit 0
|
||||
else
|
||||
echo "ERROR: 应用启动失败,请查看日志"
|
||||
rm -f "${PID_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
74
deploy/scripts/status.sh
Executable file
74
deploy/scripts/status.sh
Executable file
@ -0,0 +1,74 @@
|
||||
#!/bin/bash
|
||||
# ====================================================
|
||||
# 工作日志服务平台 - 应用状态查看脚本
|
||||
# ====================================================
|
||||
|
||||
# 应用配置
|
||||
APP_NAME="worklog-api"
|
||||
APP_HOME="/opt/worklog/${APP_NAME}"
|
||||
PID_FILE="${APP_HOME}/${APP_NAME}.pid"
|
||||
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo "========================================"
|
||||
echo "应用状态查看: ${APP_NAME}"
|
||||
echo "========================================"
|
||||
|
||||
# 检查 PID 文件是否存在
|
||||
if [ ! -f "${PID_FILE}" ]; then
|
||||
echo -e "${RED}[未运行]${NC} 未找到 PID 文件,应用可能未启动"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 读取 PID
|
||||
PID=$(cat "${PID_FILE}")
|
||||
|
||||
# 检查进程是否存在
|
||||
if ! ps -p ${PID} > /dev/null 2>&1; then
|
||||
echo -e "${RED}[异常]${NC} PID 文件存在但进程不存在 (PID: ${PID})"
|
||||
echo "建议:执行 stop.sh 清理残留文件后重新启动"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 应用正在运行,显示详细信息
|
||||
echo -e "${GREEN}[运行中]${NC} 应用正在运行"
|
||||
echo ""
|
||||
echo "进程信息:"
|
||||
echo " PID: ${PID}"
|
||||
echo " 启动时间: $(ps -p ${PID} -o lstart=)"
|
||||
echo " 运行时长: $(ps -p ${PID} -o etime=)"
|
||||
echo " CPU 占用: $(ps -p ${PID} -o %cpu=)%"
|
||||
echo " 内存占用: $(ps -p ${PID} -o %mem=)%"
|
||||
echo ""
|
||||
|
||||
# 显示端口监听情况
|
||||
echo "端口监听:"
|
||||
netstat -tlnp 2>/dev/null | grep ${PID} | awk '{print " " $4}' || \
|
||||
ss -tlnp 2>/dev/null | grep ${PID} | awk '{print " " $5}'
|
||||
echo ""
|
||||
|
||||
# 显示最近日志
|
||||
LOG_DIR="${APP_HOME}/logs"
|
||||
if [ -d "${LOG_DIR}" ]; then
|
||||
echo "最近日志 (最后 10 行):"
|
||||
echo "----------------------------------------"
|
||||
if [ -f "${LOG_DIR}/app.log" ]; then
|
||||
tail -n 10 "${LOG_DIR}/app.log"
|
||||
else
|
||||
echo " 未找到日志文件"
|
||||
fi
|
||||
else
|
||||
echo "日志目录不存在: ${LOG_DIR}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "查看实时日志: tail -f ${LOG_DIR}/app.log"
|
||||
echo "查看 SQL 日志: tail -f ${LOG_DIR}/sql.log"
|
||||
echo "========================================"
|
||||
|
||||
exit 0
|
||||
60
deploy/scripts/stop.sh
Executable file
60
deploy/scripts/stop.sh
Executable file
@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
# ====================================================
|
||||
# 工作日志服务平台 - 应用停止脚本
|
||||
# ====================================================
|
||||
|
||||
# 应用配置
|
||||
APP_NAME="worklog-api"
|
||||
APP_HOME="/opt/worklog/${APP_NAME}"
|
||||
PID_FILE="${APP_HOME}/${APP_NAME}.pid"
|
||||
|
||||
# 检查 PID 文件是否存在
|
||||
if [ ! -f "${PID_FILE}" ]; then
|
||||
echo "应用未运行(找不到 PID 文件)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 读取 PID
|
||||
PID=$(cat "${PID_FILE}")
|
||||
|
||||
# 检查进程是否存在
|
||||
if ! ps -p ${PID} > /dev/null 2>&1; then
|
||||
echo "应用未运行(进程不存在)"
|
||||
rm -f "${PID_FILE}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "========================================="
|
||||
echo "停止应用: ${APP_NAME}"
|
||||
echo "PID: ${PID}"
|
||||
echo "========================================="
|
||||
|
||||
# 优雅停止(发送 SIGTERM)
|
||||
echo "发送停止信号..."
|
||||
kill ${PID}
|
||||
|
||||
# 等待进程结束(最多等待 30 秒)
|
||||
TIMEOUT=30
|
||||
COUNT=0
|
||||
while ps -p ${PID} > /dev/null 2>&1; do
|
||||
if [ ${COUNT} -ge ${TIMEOUT} ]; then
|
||||
echo "WARNING: 应用未在 ${TIMEOUT} 秒内停止,强制终止..."
|
||||
kill -9 ${PID}
|
||||
sleep 2
|
||||
break
|
||||
fi
|
||||
|
||||
echo "等待应用停止... (${COUNT}/${TIMEOUT})"
|
||||
sleep 1
|
||||
COUNT=$((COUNT + 1))
|
||||
done
|
||||
|
||||
# 确认进程已停止
|
||||
if ps -p ${PID} > /dev/null 2>&1; then
|
||||
echo "ERROR: 无法停止应用"
|
||||
exit 1
|
||||
else
|
||||
echo "应用已停止"
|
||||
rm -f "${PID_FILE}"
|
||||
exit 0
|
||||
fi
|
||||
243
doc/架构设计文档.md
243
doc/架构设计文档.md
@ -115,7 +115,7 @@ graph LR
|
||||
|
||||
```text
|
||||
worklog-api/
|
||||
├── src/main/java/com/company/worklog/
|
||||
├── src/main/java/com/wjbl/worklog/
|
||||
│ ├── WorklogApplication.java # 启动类
|
||||
│ ├── config/ # 配置类
|
||||
│ │ ├── SecurityConfig.java
|
||||
@ -915,61 +915,250 @@ tencent:
|
||||
- 开发环境使用测试存储桶
|
||||
- 生产环境需配置独立的生产存储桶
|
||||
|
||||
#### 9.4.5 环境配置文件管理
|
||||
#### 9.4.5 配置文件分离架构(遵循架构设计规范第7章)
|
||||
|
||||
本项目采用配置文件分离架构,将统一环境配置和服务个性化配置分离,提高配置管理的灵活性和可维护性。
|
||||
|
||||
##### 9.4.5.1 配置文件结构
|
||||
|
||||
**开发阶段**配置文件位于 `src/main/resources/conf/` 目录:
|
||||
|
||||
```text
|
||||
worklog-api/src/main/resources/
|
||||
├── application.yml # 主配置文件(Spring Boot 配置)
|
||||
├── bootstrap.yml # 启动配置(Nacos 配置中心,可选)
|
||||
├── logback-spring.xml # 日志配置(从环境变量读取参数)
|
||||
└── conf/ # 配置文件目录(开发阶段)
|
||||
├── env.properties # 统一环境配置
|
||||
├── service.properties # 服务个性化配置
|
||||
├── env.properties.example # 环境配置模板
|
||||
└── service.properties.example # 服务配置模板
|
||||
```
|
||||
|
||||
**生产部署阶段**配置文件组织结构(打包后):
|
||||
|
||||
```text
|
||||
worklog-api/
|
||||
├── bin/ # 脚本目录
|
||||
│ ├── start.sh # 启动脚本(含配置加载逻辑)
|
||||
│ ├── stop.sh # 停止脚本
|
||||
│ ├── restart.sh # 重启脚本
|
||||
│ └── status.sh # 状态查看脚本
|
||||
├── lib/ # JAR 包目录
|
||||
│ └── worklog-api.jar
|
||||
├── conf/ # 配置文件目录(部署阶段)
|
||||
│ ├── env.properties # 统一环境配置(所有服务共用)
|
||||
│ ├── service.properties # 服务个性化配置(当前服务独立)
|
||||
│ ├── env.properties.example # 环境配置模板
|
||||
│ └── service.properties.example # 服务配置模板
|
||||
└── logs/ # 日志目录(扁平结构)
|
||||
├── app.log # 应用主日志
|
||||
├── sql.log # SQL 日志
|
||||
└── console.log # 控制台日志
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 开发阶段:配置文件在 `src/main/resources/conf/` 目录,跟随源码管理
|
||||
- 部署阶段:通过 Maven Assembly 插件打包后,配置文件输出到 `conf/` 目录,位于应用根目录
|
||||
|
||||
开发环境配置文件结构:
|
||||
|
||||
```text
|
||||
src/main/resources/
|
||||
├── application.yml # 主配置文件(Spring Boot 配置)
|
||||
├── bootstrap.yml # 启动配置(Nacos 配置中心,可选)
|
||||
└── logback-spring.xml # 日志配置(从环境变量读取参数)
|
||||
```
|
||||
|
||||
##### 9.4.5.2 配置加载顺序
|
||||
|
||||
启动脚本 [`start.sh`](file:///home/along/MyCode/wanjiabuluo/worklog/deploy/scripts/start.sh) 按以下顺序加载配置(后加载覆盖先加载):
|
||||
|
||||
1. **加载统一环境配置** [`conf/env.properties`](file:///home/along/MyCode/wanjiabuluo/worklog/worklog-api/src/main/resources/conf/env.properties)
|
||||
2. **加载服务个性化配置** [`conf/service.properties`](file:///home/along/MyCode/wanjiabuluo/worklog/worklog-api/src/main/resources/conf/service.properties)
|
||||
3. **启动 Spring Boot 应用**,配置项通过环境变量传递
|
||||
|
||||
```bash
|
||||
# 配置加载逻辑(start.sh 中实现)
|
||||
load_properties "${APP_HOME}/conf/env.properties"
|
||||
load_properties "${APP_HOME}/conf/service.properties"
|
||||
```
|
||||
|
||||
##### 9.4.5.3 统一环境配置(env.properties)
|
||||
|
||||
统一环境配置包含所有服务共用的配置项,主要包括:
|
||||
|
||||
**数据库配置**:
|
||||
- `DB_HOST`:数据库主机地址(localhost)
|
||||
- `DB_PORT`:数据库端口(3306)
|
||||
- `DB_NAME`:数据库名称(worklog)
|
||||
- `DB_USERNAME`:数据库用户名(worklog)
|
||||
- `DB_PASSWORD`:数据库密码(Wlog@123)
|
||||
- 连接池参数:`DB_POOL_MIN_IDLE`、`DB_POOL_MAX_SIZE` 等
|
||||
|
||||
**Redis 配置**:
|
||||
- `REDIS_HOST`:Redis 主机地址(localhost)
|
||||
- `REDIS_PORT`:Redis 端口(6379)
|
||||
- `REDIS_PASSWORD`:Redis 密码(zjf@123456)
|
||||
- `REDIS_DATABASE`:数据库索引(0)
|
||||
- 连接池参数:`REDIS_POOL_MAX_ACTIVE`、`REDIS_POOL_MAX_IDLE` 等
|
||||
|
||||
**Nacos 配置**:
|
||||
- `NACOS_SERVER_ADDR`:Nacos 服务地址(localhost:8848)
|
||||
- `NACOS_NAMESPACE`:命名空间(worklog-dev)
|
||||
- `NACOS_GROUP`:分组(DEFAULT_GROUP)
|
||||
- `NACOS_USERNAME`、`NACOS_PASSWORD`:认证信息
|
||||
|
||||
**文件上传配置**:
|
||||
- `FILE_UPLOAD_MAX_SIZE`:单个文件最大大小(50MB)
|
||||
- `FILE_UPLOAD_MAX_REQUEST_SIZE`:请求最大大小(100MB)
|
||||
- `FILE_STORAGE_PATH`:文件存储路径(./uploads)
|
||||
|
||||
**日志配置**:
|
||||
- `LOG_PATH`:日志路径(./logs,扁平目录结构)
|
||||
- `LOG_LEVEL_ROOT`:根日志级别(INFO)
|
||||
- `LOG_LEVEL_APP`:应用日志级别(DEBUG)
|
||||
- `LOG_FILE_MAX_SIZE`:日志文件最大大小(100MB)
|
||||
- `LOG_FILE_MAX_HISTORY`:日志保留天数(30)
|
||||
|
||||
**JVM 配置**:
|
||||
- `JVM_XMS`、`JVM_XMX`:堆内存配置(512m/1024m)
|
||||
- `JVM_METASPACE_SIZE`、`JVM_MAX_METASPACE_SIZE`:元空间配置
|
||||
- `JVM_GC_TYPE`:GC 类型(G1GC)
|
||||
- `JVM_MAX_GC_PAUSE_MILLIS`:最大 GC 停顿时间(200ms)
|
||||
|
||||
**业务配置**:
|
||||
- `TOKEN_EXPIRE_TIME`:Token 有效期(86400秒,24小时)
|
||||
- `TOKEN_PREFIX`:Token 缓存前缀(auth:token:)
|
||||
- `WORKLOG_MAX_CONTENT_LENGTH`:日志内容最大长度(2000字符)
|
||||
- `UPLOAD_ALLOWED_EXTENSIONS`:允许上传的文件扩展名
|
||||
|
||||
配置示例详见:[`conf/env.properties.example`](file:///home/along/MyCode/wanjiabuluo/worklog/worklog-api/src/main/resources/conf/env.properties.example)
|
||||
|
||||
##### 9.4.5.4 服务个性化配置(service.properties)
|
||||
|
||||
服务个性化配置包含当前服务独立的配置项,主要包括:
|
||||
|
||||
**服务基本信息**:
|
||||
- `APP_NAME`:服务名称(worklog-api)
|
||||
- `INSTANCE_NAME`:实例名称,多租户场景使用,默认与 `APP_NAME` 相同
|
||||
- `TENANT_ID`:租户标识,多租户场景用于路由,单租户留空
|
||||
|
||||
**服务端口配置**:
|
||||
- `SERVER_PORT`:服务端口,可覆盖 application.yml 中的端口配置(8080)
|
||||
|
||||
**环境标识**:
|
||||
- `SPRING_PROFILES_ACTIVE`:运行环境(dev-开发,test-测试,prod-生产)
|
||||
|
||||
**个性化覆盖配置(可选)**:
|
||||
- `LOG_PATH`:如需使用不同的日志路径,可在此覆盖
|
||||
- `LOG_LEVEL_APP`:如需使用不同的日志级别,可在此覆盖
|
||||
|
||||
配置示例详见:[`conf/service.properties.example`](file:///home/along/MyCode/wanjiabuluo/worklog/worklog-api/src/main/resources/conf/service.properties.example)
|
||||
|
||||
##### 9.4.5.5 日志配置集中化
|
||||
|
||||
日志配置遵循架构设计规范第7.4节要求:
|
||||
|
||||
1. **日志文件命名**:
|
||||
- 主日志文件统一命名为 `app.log`
|
||||
- SQL 日志独立输出到 `sql.log`(强制要求)
|
||||
- 所有日志文件输出到 `logs/` 目录(扁平结构,无子目录)
|
||||
|
||||
2. **日志格式要求**:
|
||||
- 所有日志必须包含 `traceId` 和 `spanId`,便于链路追踪
|
||||
- 日志格式:`%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId:-}][%X{spanId:-}] %-5level %logger{50} - %msg%n`
|
||||
|
||||
3. **配置参数化**:
|
||||
- [`logback-spring.xml`](file:///home/along/MyCode/wanjiabuluo/worklog/worklog-api/src/main/resources/logback-spring.xml) 从环境变量读取关键参数
|
||||
- 支持的环境变量:`LOG_PATH`、`LOG_LEVEL_ROOT`、`LOG_LEVEL_APP`
|
||||
|
||||
##### 9.4.5.6 配置文件安全管理
|
||||
|
||||
**版本控制策略**:
|
||||
|
||||
- **不提交到仓库**:实际配置文件(包含敏感信息)
|
||||
- `src/main/resources/conf/env.properties`
|
||||
- `src/main/resources/conf/service.properties`
|
||||
- `src/main/resources/application.yml`
|
||||
- `src/main/resources/bootstrap.yml`
|
||||
|
||||
- **提交到仓库**:配置模板文件
|
||||
- `src/main/resources/conf/env.properties.example`
|
||||
- `src/main/resources/conf/service.properties.example`
|
||||
|
||||
**部署流程**:
|
||||
|
||||
1. 在服务器上复制模板文件为实际配置文件
|
||||
2. 根据实际环境修改配置参数
|
||||
3. 启动脚本自动加载配置并启动应用
|
||||
|
||||
```bash
|
||||
# 部署时操作
|
||||
cp conf/env.properties.example conf/env.properties
|
||||
cp conf/service.properties.example conf/service.properties
|
||||
vim conf/env.properties # 修改实际配置
|
||||
vim conf/service.properties # 修改实际配置
|
||||
./bin/start.sh # 启动应用
|
||||
```
|
||||
|
||||
#### 9.4.6 环境配置文件管理(开发环境)
|
||||
|
||||
开发环境配置文件建议按以下结构组织:
|
||||
|
||||
```text
|
||||
src/main/resources/
|
||||
├── application.yml # 主配置文件
|
||||
├── application-dev.yml # 开发环境配置
|
||||
├── application-test.yml # 测试环境配置
|
||||
├── application-prod.yml # 生产环境配置
|
||||
├── bootstrap.yml # 启动配置(Nacos)
|
||||
└── logback-spring.xml # 日志配置
|
||||
```
|
||||
|
||||
**注意**:开发环境不使用多环境配置分离(application-dev.yml、application-test.yml 等),统一使用 application.yml。
|
||||
|
||||
**配置优先级**:
|
||||
1. bootstrap.yml(最先加载,用于 Nacos 配置中心)
|
||||
2. application.yml(通用配置)
|
||||
3. application-{profile}.yml(环境特定配置,覆盖通用配置)
|
||||
1. bootstrap.yml(最先加载,用于 Nacos 配置中心,默认禁用)
|
||||
2. application.yml(通用配置,开发环境使用)
|
||||
3. 生产环境通过 conf/env.properties 和 conf/service.properties 配置
|
||||
|
||||
**激活开发环境**:
|
||||
|
||||
```yaml
|
||||
# application.yml
|
||||
spring:
|
||||
profiles:
|
||||
active: dev
|
||||
```
|
||||
开发环境直接使用 application.yml,无需配置环境标识。
|
||||
|
||||
或通过启动参数:
|
||||
生产环境通过启动参数激活:
|
||||
|
||||
```bash
|
||||
java -jar worklog-api.jar --spring.profiles.active=dev
|
||||
java -jar worklog-api.jar --spring.profiles.active=prod
|
||||
```
|
||||
|
||||
#### 9.4.6 安全注意事项
|
||||
#### 9.4.7 安全注意事项
|
||||
|
||||
⚠️ **重要提示**:
|
||||
|
||||
1. **密码安全**:
|
||||
- 开发环境配置文件不应提交到代码仓库
|
||||
- 使用 `.gitignore` 忽略 `application-*.yml` 文件
|
||||
- 敏感信息建议使用环境变量或配置中心管理
|
||||
- 实际配置文件(包含敏感信息)不应提交到代码仓库
|
||||
- 使用 [`.gitignore`](file:///home/along/MyCode/wanjiabuluo/worklog/.gitignore) 忽略实际配置文件
|
||||
- 敏感信息使用环境变量或配置中心管理
|
||||
|
||||
2. **配置示例**:
|
||||
```gitignore
|
||||
# .gitignore
|
||||
application-dev.yml
|
||||
application-test.yml
|
||||
application-prod.yml
|
||||
# .gitignore 配置
|
||||
application.yml
|
||||
bootstrap.yml
|
||||
src/main/resources/conf/env.properties
|
||||
src/main/resources/conf/service.properties
|
||||
```
|
||||
|
||||
3. **配置模板**:
|
||||
- 提供 `application-dev.yml.example` 作为模板
|
||||
- 开发人员复制并修改为 `application-dev.yml`
|
||||
- 填入本地实际配置信息
|
||||
- 提供 `env.properties.example` 和 `service.properties.example` 作为模板
|
||||
- 开发人员/运维人员复制模板并修改为实际配置文件
|
||||
- 填入实际配置信息(数据库密码、Redis 密码等)
|
||||
|
||||
4. **配置文件权限**(生产环境):
|
||||
```bash
|
||||
# 限制配置文件访问权限
|
||||
chmod 600 conf/env.properties
|
||||
chmod 600 conf/service.properties
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -580,32 +580,81 @@ project-root/
|
||||
- `env.properties`:环境级通用配置(注册中心、缓存、限流、文件上传、日志等)。
|
||||
- 各服务 `service.properties`:服务名、实例名、多租户路由、端口等个性化配置。
|
||||
|
||||
统一配置示例(节选):
|
||||
统一配置示例:
|
||||
|
||||
```properties
|
||||
# Nacos / 注册中心
|
||||
NACOS_SERVER_ADDR=localhost:8848
|
||||
NACOS_NAMESPACE=project-namespace
|
||||
NACOS_GROUP=DEFAULT_GROUP
|
||||
# ==================== 数据库配置 ====================
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_NAME=worklog
|
||||
DB_USERNAME=worklog
|
||||
DB_PASSWORD=Wlog@123
|
||||
DB_URL=jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
|
||||
|
||||
# Redis
|
||||
# 连接池配置
|
||||
DB_POOL_MIN_IDLE=5
|
||||
DB_POOL_MAX_SIZE=20
|
||||
DB_POOL_CONNECTION_TIMEOUT=30000
|
||||
DB_POOL_IDLE_TIMEOUT=600000
|
||||
DB_POOL_MAX_LIFETIME=1800000
|
||||
|
||||
# ==================== Redis 配置 ====================
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=your-password
|
||||
REDIS_PASSWORD=zjf@123456
|
||||
REDIS_DATABASE=0
|
||||
REDIS_TIMEOUT=5000
|
||||
|
||||
# 网关限流
|
||||
GATEWAY_RATE_LIMIT_REPLENISH_RATE=100
|
||||
GATEWAY_RATE_LIMIT_BURST_CAPACITY=200
|
||||
# Redis 连接池配置
|
||||
REDIS_POOL_MAX_ACTIVE=8
|
||||
REDIS_POOL_MAX_WAIT=-1
|
||||
REDIS_POOL_MAX_IDLE=8
|
||||
REDIS_POOL_MIN_IDLE=0
|
||||
|
||||
# 文件上传
|
||||
# ==================== Nacos 配置 ====================
|
||||
NACOS_SERVER_ADDR=localhost:8848
|
||||
NACOS_NAMESPACE=worklog-dev
|
||||
NACOS_GROUP=DEFAULT_GROUP
|
||||
NACOS_USERNAME=nacos
|
||||
NACOS_PASSWORD=nacos
|
||||
|
||||
# ==================== 文件上传配置 ====================
|
||||
FILE_UPLOAD_MAX_SIZE=50MB
|
||||
FILE_UPLOAD_MAX_REQUEST_SIZE=100MB
|
||||
FILE_STORAGE_PATH=./uploads
|
||||
|
||||
# 日志
|
||||
LOG_PATH=/var/logs/apps
|
||||
# ==================== 日志配置 ====================
|
||||
# 日志路径(扁平目录结构,无子目录)
|
||||
LOG_PATH=./logs
|
||||
|
||||
# 日志级别
|
||||
LOG_LEVEL_ROOT=INFO
|
||||
LOG_LEVEL_APP=DEBUG
|
||||
|
||||
# 日志文件配置
|
||||
LOG_FILE_MAX_SIZE=100MB
|
||||
LOG_FILE_MAX_HISTORY=30
|
||||
|
||||
# ==================== JVM 配置 ====================
|
||||
JVM_XMS=512m
|
||||
JVM_XMX=1024m
|
||||
JVM_METASPACE_SIZE=128m
|
||||
JVM_MAX_METASPACE_SIZE=256m
|
||||
|
||||
# GC 配置
|
||||
JVM_GC_TYPE=G1GC
|
||||
JVM_MAX_GC_PAUSE_MILLIS=200
|
||||
|
||||
# ==================== 业务配置 ====================
|
||||
# Token 配置
|
||||
TOKEN_EXPIRE_TIME=86400
|
||||
TOKEN_PREFIX=auth:token:
|
||||
|
||||
# 日志内容限制
|
||||
WORKLOG_MAX_CONTENT_LENGTH=2000
|
||||
|
||||
# 允许的上传文件扩展名
|
||||
UPLOAD_ALLOWED_EXTENSIONS=jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx
|
||||
```
|
||||
|
||||
个性化配置示例:
|
||||
@ -620,8 +669,20 @@ INSTANCE_NAME=${APP_NAME}
|
||||
# 租户标识(多租户场景使用,用于路由)
|
||||
TENANT_ID=
|
||||
|
||||
# ==================== 服务端口配置 ====================
|
||||
# 服务端口(可覆盖 application.yml 中配置)
|
||||
# SERVER_PORT=8100
|
||||
SERVER_PORT=8080
|
||||
|
||||
# ==================== 环境标识 ====================
|
||||
# 运行环境:dev-开发, test-测试, prod-生产
|
||||
SPRING_PROFILES_ACTIVE=prod
|
||||
|
||||
# ==================== 个性化覆盖配置(可选) ====================
|
||||
# 如果当前服务需要使用不同的日志路径,可在此覆盖
|
||||
# LOG_PATH=/var/logs/worklog-api
|
||||
|
||||
# 如果当前服务需要使用不同的日志级别,可在此覆盖
|
||||
# LOG_LEVEL_APP=INFO
|
||||
```
|
||||
|
||||
#### 7.2 配置加载顺序
|
||||
|
||||
286
doc/配置规范落地说明.md
Normal file
286
doc/配置规范落地说明.md
Normal file
@ -0,0 +1,286 @@
|
||||
# 配置与部署规范落地说明
|
||||
|
||||
## 变更概述
|
||||
|
||||
本次更新根据 [`架构设计规范.md 第7章`](file:///home/along/MyCode/wanjiabuluo/worklog/doc/规范/架构设计规范.md) 的要求,完善了配置文件分离架构和部署相关文件,提高了配置管理的灵活性和可维护性。
|
||||
|
||||
## 主要变更内容
|
||||
|
||||
### 1. 配置文件分离架构(7.1 & 7.2 规范)
|
||||
|
||||
**新增配置文件**:
|
||||
|
||||
- [`src/main/resources/conf/env.properties`](file:///home/along/MyCode/wanjiabuluo/worklog/worklog-api/src/main/resources/conf/env.properties) - 统一环境配置(所有服务共用)
|
||||
- [`src/main/resources/conf/service.properties`](file:///home/along/MyCode/wanjiabuluo/worklog/worklog-api/src/main/resources/conf/service.properties) - 服务个性化配置
|
||||
- [`src/main/resources/conf/env.properties.example`](file:///home/along/MyCode/wanjiabuluo/worklog/worklog-api/src/main/resources/conf/env.properties.example) - 环境配置模板
|
||||
- [`src/main/resources/conf/service.properties.example`](file:///home/along/MyCode/wanjiabuluo/worklog/worklog-api/src/main/resources/conf/service.properties.example) - 服务配置模板
|
||||
|
||||
**配置内容说明**:
|
||||
|
||||
- **env.properties** 包含:
|
||||
- 数据库配置(MySQL 连接信息、连接池参数)
|
||||
- Redis 配置(连接信息、连接池参数)
|
||||
- Nacos 配置(注册中心和配置中心)
|
||||
- 文件上传配置
|
||||
- 日志配置(路径、级别)
|
||||
- JVM 配置(堆内存、元空间、GC 策略)
|
||||
- 业务配置(Token、日志内容限制等)
|
||||
|
||||
- **service.properties** 包含:
|
||||
- 服务名称和实例名称
|
||||
- 服务端口
|
||||
- 环境标识(dev/test/prod)
|
||||
- 租户标识(多租户场景)
|
||||
- 个性化覆盖配置
|
||||
|
||||
### 2. 启动脚本增强(7.2 规范)
|
||||
|
||||
**更新文件**:[`deploy/scripts/start.sh`](file:///home/along/MyCode/wanjiabuluo/worklog/deploy/scripts/start.sh)
|
||||
|
||||
**主要变更**:
|
||||
|
||||
1. **新增配置加载函数** `load_properties()`:
|
||||
- 支持自动加载 properties 文件
|
||||
- 跳过注释和空行
|
||||
- 将配置项导出为环境变量
|
||||
|
||||
2. **配置加载顺序**(遵循 7.2 规范):
|
||||
```bash
|
||||
# 1. 加载统一环境配置
|
||||
load_properties "${APP_HOME}/conf/env.properties"
|
||||
|
||||
# 2. 加载服务个性化配置(覆盖同名参数)
|
||||
load_properties "${APP_HOME}/conf/service.properties"
|
||||
```
|
||||
|
||||
3. **参数化配置**:
|
||||
- JVM 参数从环境变量读取(如未设置则使用默认值)
|
||||
- Spring Boot 配置从环境变量读取
|
||||
|
||||
### 3. 状态查看脚本(7.3 规范)
|
||||
|
||||
**新增文件**:[`deploy/scripts/status.sh`](file:///home/along/MyCode/wanjiabuluo/worklog/deploy/scripts/status.sh)
|
||||
|
||||
**功能特性**:
|
||||
|
||||
- 检查应用运行状态
|
||||
- 显示进程详细信息(PID、启动时间、运行时长、CPU/内存占用)
|
||||
- 显示端口监听情况
|
||||
- 显示最近日志(最后 10 行)
|
||||
- 提供快捷命令提示
|
||||
|
||||
### 4. 日志配置集中化(7.4 规范)
|
||||
|
||||
**更新文件**:[`worklog-api/src/main/resources/logback-spring.xml`](file:///home/along/MyCode/wanjiabuluo/worklog/worklog-api/src/main/resources/logback-spring.xml)
|
||||
|
||||
**主要变更**:
|
||||
|
||||
1. **从环境变量读取配置**:
|
||||
```xml
|
||||
<springProperty scope="context" name="LOG_PATH" source="logging.file.path" defaultValue="./logs"/>
|
||||
<springProperty scope="context" name="LOG_LEVEL_ROOT" source="logging.level.root" defaultValue="INFO"/>
|
||||
<springProperty scope="context" name="LOG_LEVEL_APP" source="logging.level.app" defaultValue="DEBUG"/>
|
||||
```
|
||||
|
||||
2. **日志格式标准化**(强制包含 traceId 和 spanId):
|
||||
```xml
|
||||
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId:-}][%X{spanId:-}] %-5level %logger{50} - %msg%n"/>
|
||||
```
|
||||
|
||||
3. **日志文件命名**(遵循 7.4 规范):
|
||||
- 主日志:`app.log`(所有级别)
|
||||
- SQL 日志:`sql.log`(MyBatis-Plus 独立输出)
|
||||
- 所有日志输出到 `logs/` 目录(扁平结构,无子目录)
|
||||
|
||||
4. **新增 MyBatis-Plus 框架日志配置**:
|
||||
```xml
|
||||
<logger name="com.baomidou.mybatisplus" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="SQL_FILE"/>
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</logger>
|
||||
```
|
||||
|
||||
### 5. Bootstrap 配置文件(7.5 规范)
|
||||
|
||||
**新增文件**:[`worklog-api/src/main/resources/bootstrap.yml`](file:///home/along/MyCode/wanjiabuluo/worklog/worklog-api/src/main/resources/bootstrap.yml)
|
||||
|
||||
**功能说明**:
|
||||
|
||||
- 用于 Nacos 配置中心(可选,默认禁用)
|
||||
- 支持从环境变量读取 Nacos 配置
|
||||
- 最先加载,优先级高于 application.yml
|
||||
|
||||
### 6. 安全配置管理
|
||||
|
||||
**更新文件**:[`.gitignore`](file:///home/along/MyCode/wanjiabuluo/worklog/.gitignore)
|
||||
|
||||
**新增忽略项**:
|
||||
|
||||
```gitignore
|
||||
# conf 目录下的实际配置文件(敏感信息)
|
||||
worklog-api/src/main/resources/conf/env.properties
|
||||
worklog-api/src/main/resources/conf/service.properties
|
||||
```
|
||||
|
||||
**安全策略**:
|
||||
|
||||
- 实际配置文件(包含敏感信息)不提交到仓库
|
||||
- 仅提交配置模板文件(.example)
|
||||
- 部署时复制模板并修改实际配置
|
||||
|
||||
### 7. 架构设计文档更新
|
||||
|
||||
**更新文件**:[`doc/架构设计文档.md`](file:///home/along/MyCode/wanjiabuluo/worklog/doc/架构设计文档.md)
|
||||
|
||||
**新增章节**:
|
||||
|
||||
- **9.4.5 配置文件分离架构**(新增,遵循架构设计规范第7章)
|
||||
- 9.4.5.1 配置文件结构
|
||||
- 9.4.5.2 配置加载顺序
|
||||
- 9.4.5.3 统一环境配置(env.properties)
|
||||
- 9.4.5.4 服务个性化配置(service.properties)
|
||||
- 9.4.5.5 日志配置集中化
|
||||
- 9.4.5.6 配置文件安全管理
|
||||
|
||||
- **9.4.6 环境配置文件管理**(更新,简化为单配置文件策略)
|
||||
- **9.4.7 安全注意事项**(更新,补充配置文件权限管理)
|
||||
|
||||
## 目录结构变化
|
||||
|
||||
### 新增目录和文件
|
||||
|
||||
**开发阶段**:
|
||||
|
||||
```text
|
||||
worklog-api/src/main/resources/
|
||||
├── conf/ # 配置文件目录(开发阶段)
|
||||
│ ├── env.properties # 统一环境配置
|
||||
│ ├── service.properties # 服务个性化配置
|
||||
│ ├── env.properties.example # 环境配置模板
|
||||
│ └── service.properties.example # 服务配置模板
|
||||
├── bootstrap.yml # 启动配置(新增)
|
||||
└── logback-spring.xml # 更新:增强日志配置
|
||||
|
||||
deploy/ # 项目根目录
|
||||
└── scripts/
|
||||
└── status.sh # 新增:状态查看脚本
|
||||
```
|
||||
|
||||
**部署阶段**(打包后):
|
||||
|
||||
### 部署目录结构(生产环境)
|
||||
|
||||
```text
|
||||
/opt/worklog/worklog-api/
|
||||
├── bin/ # 脚本目录
|
||||
│ ├── start.sh # 启动脚本(已增强)
|
||||
│ ├── stop.sh # 停止脚本
|
||||
│ ├── restart.sh # 重启脚本
|
||||
│ └── status.sh # 状态查看脚本(新增)
|
||||
├── lib/ # JAR 包目录
|
||||
│ └── worklog-api.jar
|
||||
├── conf/ # 配置文件目录(新增)
|
||||
│ ├── env.properties # 统一环境配置
|
||||
│ └── service.properties # 服务个性化配置
|
||||
└── logs/ # 日志目录(扁平结构)
|
||||
├── app.log # 应用主日志
|
||||
├── sql.log # SQL 日志
|
||||
├── console.log # 控制台日志
|
||||
└── gc.log # GC 日志
|
||||
```
|
||||
|
||||
## 配置规范对照
|
||||
|
||||
| 规范章节 | 规范要求 | 落地实现 | 状态 |
|
||||
|---------|---------|---------|------|
|
||||
| 7.1 配置文件分离 | env.properties + service.properties | ✅ 已实现 | ✅ |
|
||||
| 7.2 配置加载顺序 | 先统一配置,后个性化配置 | ✅ start.sh 中实现 | ✅ |
|
||||
| 7.3 打包目录结构 | bin/ lib/ conf/ | ✅ 已创建 | ✅ |
|
||||
| 7.3 脚本完整性 | start/stop/restart/status | ✅ 已补充 status.sh | ✅ |
|
||||
| 7.4 日志文件命名 | app.log 主日志 | ✅ 已配置 | ✅ |
|
||||
| 7.4 SQL 日志独立 | sql.log 独立输出 | ✅ 已配置 | ✅ |
|
||||
| 7.4 日志目录结构 | logs/ 扁平结构 | ✅ 已配置 | ✅ |
|
||||
| 7.4 traceId/spanId | 强制包含 | ✅ 已配置 | ✅ |
|
||||
| 7.4 配置参数化 | 从环境变量读取 | ✅ logback-spring.xml | ✅ |
|
||||
| 7.5 Redis 配置 | 环境变量配置 | ✅ env.properties | ✅ |
|
||||
| 7.5 Nacos 配置 | 环境变量配置 | ✅ env.properties + bootstrap.yml | ✅ |
|
||||
|
||||
## 部署指南
|
||||
|
||||
### 开发环境
|
||||
|
||||
1. 直接使用 [`application.yml`](file:///home/along/MyCode/wanjiabuluo/worklog/worklog-api/src/main/resources/application.yml) 配置
|
||||
2. 无需配置 env.properties 和 service.properties
|
||||
|
||||
### 生产环境
|
||||
|
||||
1. **复制配置模板**:
|
||||
```bash
|
||||
cd /opt/worklog/worklog-api
|
||||
cp conf/env.properties.example conf/env.properties
|
||||
cp conf/service.properties.example conf/service.properties
|
||||
```
|
||||
|
||||
2. **修改实际配置**:
|
||||
```bash
|
||||
vim conf/env.properties # 修改数据库、Redis 等配置
|
||||
vim conf/service.properties # 修改服务名称、端口等
|
||||
```
|
||||
|
||||
3. **设置文件权限**:
|
||||
```bash
|
||||
chmod 600 conf/env.properties
|
||||
chmod 600 conf/service.properties
|
||||
```
|
||||
|
||||
4. **启动应用**:
|
||||
```bash
|
||||
./bin/start.sh
|
||||
```
|
||||
|
||||
5. **查看状态**:
|
||||
```bash
|
||||
./bin/status.sh
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **配置文件安全**:
|
||||
- 实际配置文件包含敏感信息(数据库密码、Redis 密码等)
|
||||
- 已在 .gitignore 中配置忽略,请勿提交到代码仓库
|
||||
- 生产环境需设置文件权限为 600
|
||||
|
||||
2. **配置加载顺序**:
|
||||
- env.properties → service.properties → application.yml
|
||||
- 后加载的配置会覆盖前面的同名配置
|
||||
|
||||
3. **日志文件**:
|
||||
- 主日志:app.log(所有级别)
|
||||
- SQL 日志:sql.log(MyBatis-Plus 独立输出)
|
||||
- 所有日志包含 traceId 和 spanId,便于链路追踪
|
||||
|
||||
4. **环境变量支持**:
|
||||
- 启动脚本会自动将 properties 文件的配置导出为环境变量
|
||||
- Spring Boot 和 Logback 可通过环境变量或 ${} 占位符读取配置
|
||||
|
||||
## 后续计划
|
||||
|
||||
根据架构设计规范,后续可继续完善:
|
||||
|
||||
1. **Maven Assembly 打包配置**(7.3 规范):
|
||||
- 配置 assembly 插件
|
||||
- 自动打包为标准目录结构(bin/ lib/ conf/)
|
||||
|
||||
2. **多环境配置优化**:
|
||||
- 如需支持多环境,可创建多套 env.properties(env-dev.properties、env-prod.properties)
|
||||
- 启动时根据环境参数选择加载不同配置文件
|
||||
|
||||
3. **配置中心集成**(可选):
|
||||
- 启用 Nacos 配置中心
|
||||
- 将敏感配置迁移到配置中心管理
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [`架构设计规范.md`](file:///home/along/MyCode/wanjiabuluo/worklog/doc/规范/架构设计规范.md) - 第7章:配置与部署规范
|
||||
- [`架构设计文档.md`](file:///home/along/MyCode/wanjiabuluo/worklog/doc/架构设计文档.md) - 第9章:部署配置
|
||||
- [`DEPLOY.md`](file:///home/along/MyCode/wanjiabuluo/worklog/deploy/DEPLOY.md) - 部署指南
|
||||
@ -54,6 +54,12 @@
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Security Crypto (用于密码加密) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-crypto</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis-Plus -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
@ -96,12 +102,6 @@
|
||||
<version>${commonmark.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- BCrypt 密码加密 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-crypto</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@ -0,0 +1,74 @@
|
||||
package com.wjbl.worklog.common.context;
|
||||
|
||||
/**
|
||||
* 用户上下文工具类
|
||||
* 使用 ThreadLocal 存储当前用户信息
|
||||
*/
|
||||
public class UserContext {
|
||||
|
||||
private static final ThreadLocal<UserInfo> CONTEXT = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 设置当前用户信息
|
||||
*
|
||||
* @param userInfo 用户信息
|
||||
*/
|
||||
public static void setUserInfo(UserInfo userInfo) {
|
||||
CONTEXT.set(userInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*
|
||||
* @return 用户信息
|
||||
*/
|
||||
public static UserInfo getUserInfo() {
|
||||
return CONTEXT.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户ID
|
||||
*
|
||||
* @return 用户ID
|
||||
*/
|
||||
public static String getUserId() {
|
||||
UserInfo userInfo = CONTEXT.get();
|
||||
return userInfo != null ? userInfo.getUserId() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户名
|
||||
*
|
||||
* @return 用户名
|
||||
*/
|
||||
public static String getUsername() {
|
||||
UserInfo userInfo = CONTEXT.get();
|
||||
return userInfo != null ? userInfo.getUsername() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户角色
|
||||
*
|
||||
* @return 角色
|
||||
*/
|
||||
public static String getRole() {
|
||||
UserInfo userInfo = CONTEXT.get();
|
||||
return userInfo != null ? userInfo.getRole() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前用户是否为管理员
|
||||
*
|
||||
* @return 是否为管理员
|
||||
*/
|
||||
public static boolean isAdmin() {
|
||||
return "ADMIN".equals(getRole());
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除上下文
|
||||
*/
|
||||
public static void clear() {
|
||||
CONTEXT.remove();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package com.wjbl.worklog.common.context;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 用户上下文信息
|
||||
* 存储当前登录用户的基本信息
|
||||
*/
|
||||
@Data
|
||||
public class UserInfo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 姓名
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 角色(USER-普通用户,ADMIN-管理员)
|
||||
*/
|
||||
private String role;
|
||||
|
||||
public UserInfo() {
|
||||
}
|
||||
|
||||
public UserInfo(String userId, String username, String name, String role) {
|
||||
this.userId = userId;
|
||||
this.username = username;
|
||||
this.name = name;
|
||||
this.role = role;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package com.wjbl.worklog.common.util;
|
||||
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
|
||||
/**
|
||||
* 密码工具类
|
||||
* 使用 BCrypt 算法加密密码
|
||||
*/
|
||||
public class PasswordUtil {
|
||||
|
||||
private static final BCryptPasswordEncoder ENCODER = new BCryptPasswordEncoder(10);
|
||||
|
||||
/**
|
||||
* 加密密码
|
||||
*
|
||||
* @param rawPassword 明文密码
|
||||
* @return 加密后的密码
|
||||
*/
|
||||
public static String encode(String rawPassword) {
|
||||
return ENCODER.encode(rawPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验密码
|
||||
*
|
||||
* @param rawPassword 明文密码
|
||||
* @param encodedPassword 加密后的密码
|
||||
* @return 是否匹配
|
||||
*/
|
||||
public static boolean matches(String rawPassword, String encodedPassword) {
|
||||
return ENCODER.matches(rawPassword, encodedPassword);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
package com.wjbl.worklog.config;
|
||||
|
||||
import com.wjbl.worklog.common.context.UserContext;
|
||||
import com.wjbl.worklog.common.context.UserInfo;
|
||||
import com.wjbl.worklog.service.TokenService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
/**
|
||||
* 认证拦截器
|
||||
* 校验 Token 并设置用户上下文
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AuthInterceptor implements HandlerInterceptor {
|
||||
|
||||
private final TokenService tokenService;
|
||||
|
||||
private static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
private static final String BEARER_PREFIX = "Bearer ";
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
// 从请求头获取 Token
|
||||
String token = extractToken(request);
|
||||
|
||||
if (!StringUtils.hasText(token)) {
|
||||
// 没有 Token,返回 401
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write("{\"code\":401,\"message\":\"未登录或登录已过期\",\"data\":null,\"success\":false}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
UserInfo userInfo = tokenService.getUserInfo(token);
|
||||
|
||||
if (userInfo == null) {
|
||||
// Token 无效或已过期
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write("{\"code\":401,\"message\":\"Token 无效或已过期\",\"data\":null,\"success\":false}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置用户上下文
|
||||
UserContext.setUserInfo(userInfo);
|
||||
|
||||
// 刷新 Token 有效期
|
||||
tokenService.refreshToken(token);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||
// 清除用户上下文
|
||||
UserContext.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求头提取 Token
|
||||
*/
|
||||
private String extractToken(HttpServletRequest request) {
|
||||
String authorization = request.getHeader(AUTHORIZATION_HEADER);
|
||||
|
||||
if (StringUtils.hasText(authorization) && authorization.startsWith(BEARER_PREFIX)) {
|
||||
return authorization.substring(BEARER_PREFIX.length());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -16,11 +16,32 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
private final TraceInterceptor traceInterceptor;
|
||||
private final AuthInterceptor authInterceptor;
|
||||
|
||||
/**
|
||||
* 白名单路径(不需要认证)
|
||||
*/
|
||||
private static final String[] WHITE_LIST = {
|
||||
"/api/v1/auth/login",
|
||||
"/api/v1/auth/logout",
|
||||
"/api/v1/auth/health",
|
||||
"/swagger-ui.html",
|
||||
"/swagger-ui/**",
|
||||
"/v3/api-docs/**",
|
||||
"/error"
|
||||
};
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 注册链路追踪拦截器
|
||||
// 注册链路追踪拦截器(所有请求)
|
||||
registry.addInterceptor(traceInterceptor)
|
||||
.addPathPatterns("/**");
|
||||
.addPathPatterns("/**")
|
||||
.order(1);
|
||||
|
||||
// 注册认证拦截器(排除白名单)
|
||||
registry.addInterceptor(authInterceptor)
|
||||
.addPathPatterns("/api/v1/**")
|
||||
.excludePathPatterns(WHITE_LIST)
|
||||
.order(2);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
package com.wjbl.worklog.controller;
|
||||
|
||||
import com.wjbl.worklog.common.Result;
|
||||
import com.wjbl.worklog.dto.LoginDTO;
|
||||
import com.wjbl.worklog.service.AuthService;
|
||||
import com.wjbl.worklog.vo.LoginVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 认证控制器
|
||||
* 处理登录、登出等认证相关请求
|
||||
*/
|
||||
@Tag(name = "认证管理", description = "登录、登出等认证相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/auth")
|
||||
@RequiredArgsConstructor
|
||||
public class AuthController {
|
||||
|
||||
private final AuthService authService;
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
@Operation(summary = "用户登录", description = "通过用户名密码登录,返回 Token 和用户信息")
|
||||
@PostMapping("/login")
|
||||
public Result<LoginVO> login(@Valid @RequestBody LoginDTO loginDTO) {
|
||||
LoginVO loginVO = authService.login(loginDTO);
|
||||
return Result.success(loginVO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登出
|
||||
*/
|
||||
@Operation(summary = "用户登出", description = "退出登录,使 Token 失效")
|
||||
@PostMapping("/logout")
|
||||
public Result<Void> logout(@RequestHeader(value = "Authorization", required = false) String authorization) {
|
||||
String token = null;
|
||||
if (authorization != null && authorization.startsWith("Bearer ")) {
|
||||
token = authorization.substring(7);
|
||||
}
|
||||
if (token != null) {
|
||||
authService.logout(token);
|
||||
}
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,120 @@
|
||||
package com.wjbl.worklog.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.wjbl.worklog.common.PageResult;
|
||||
import com.wjbl.worklog.common.Result;
|
||||
import com.wjbl.worklog.dto.LogCreateDTO;
|
||||
import com.wjbl.worklog.dto.LogUpdateDTO;
|
||||
import com.wjbl.worklog.service.LogService;
|
||||
import com.wjbl.worklog.vo.LogVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 日志控制器
|
||||
* 处理日志管理相关请求
|
||||
*/
|
||||
@Tag(name = "日志管理", description = "日志管理相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/log")
|
||||
@RequiredArgsConstructor
|
||||
public class LogController {
|
||||
|
||||
private final LogService logService;
|
||||
|
||||
/**
|
||||
* 分页查询我的日志
|
||||
*/
|
||||
@Operation(summary = "分页查询我的日志", description = "分页查询当前用户的工作日志")
|
||||
@GetMapping("/page")
|
||||
public Result<PageResult<LogVO>> pageMyLogs(
|
||||
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@Parameter(description = "每页大小") @RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@Parameter(description = "开始日期") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
|
||||
@Parameter(description = "结束日期") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) {
|
||||
|
||||
Page<LogVO> page = logService.pageMyLogs(pageNum, pageSize, startDate, endDate);
|
||||
|
||||
PageResult<LogVO> pageResult = new PageResult<>();
|
||||
pageResult.setPageNum(page.getCurrent());
|
||||
pageResult.setPageSize(page.getSize());
|
||||
pageResult.setTotal(page.getTotal());
|
||||
pageResult.setList(page.getRecords());
|
||||
|
||||
return Result.success(pageResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询所有日志(管理员)
|
||||
*/
|
||||
@Operation(summary = "分页查询所有日志", description = "管理员分页查询所有工作日志")
|
||||
@GetMapping("/page/all")
|
||||
public Result<PageResult<LogVO>> pageAllLogs(
|
||||
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@Parameter(description = "每页大小") @RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@Parameter(description = "用户ID") @RequestParam(required = false) String userId,
|
||||
@Parameter(description = "开始日期") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
|
||||
@Parameter(description = "结束日期") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) {
|
||||
|
||||
Page<LogVO> page = logService.pageAllLogs(pageNum, pageSize, userId, startDate, endDate);
|
||||
|
||||
PageResult<LogVO> pageResult = new PageResult<>();
|
||||
pageResult.setPageNum(page.getCurrent());
|
||||
pageResult.setPageSize(page.getSize());
|
||||
pageResult.setTotal(page.getTotal());
|
||||
pageResult.setList(page.getRecords());
|
||||
|
||||
return Result.success(pageResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取日志
|
||||
*/
|
||||
@Operation(summary = "获取日志详情", description = "根据日志ID获取日志详细信息")
|
||||
@GetMapping("/{id}")
|
||||
public Result<LogVO> getLogById(
|
||||
@Parameter(description = "日志ID") @PathVariable String id) {
|
||||
LogVO log = logService.getLogById(id);
|
||||
return Result.success(log);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建日志
|
||||
*/
|
||||
@Operation(summary = "创建日志", description = "创建新的工作日志")
|
||||
@PostMapping
|
||||
public Result<LogVO> createLog(@Valid @RequestBody LogCreateDTO dto) {
|
||||
LogVO log = logService.createLog(dto);
|
||||
return Result.success(log);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新日志
|
||||
*/
|
||||
@Operation(summary = "更新日志", description = "更新日志信息")
|
||||
@PutMapping("/{id}")
|
||||
public Result<LogVO> updateLog(
|
||||
@Parameter(description = "日志ID") @PathVariable String id,
|
||||
@Valid @RequestBody LogUpdateDTO dto) {
|
||||
LogVO log = logService.updateLog(id, dto);
|
||||
return Result.success(log);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除日志
|
||||
*/
|
||||
@Operation(summary = "删除日志", description = "删除指定日志")
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> deleteLog(
|
||||
@Parameter(description = "日志ID") @PathVariable String id) {
|
||||
logService.deleteLog(id);
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
package com.wjbl.worklog.controller;
|
||||
|
||||
import com.wjbl.worklog.common.Result;
|
||||
import com.wjbl.worklog.dto.TemplateCreateDTO;
|
||||
import com.wjbl.worklog.dto.TemplateUpdateDTO;
|
||||
import com.wjbl.worklog.dto.UserStatusDTO;
|
||||
import com.wjbl.worklog.service.TemplateService;
|
||||
import com.wjbl.worklog.vo.TemplateVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 模板控制器
|
||||
* 处理模板管理相关请求
|
||||
*/
|
||||
@Tag(name = "模板管理", description = "模板管理相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/template")
|
||||
@RequiredArgsConstructor
|
||||
public class TemplateController {
|
||||
|
||||
private final TemplateService templateService;
|
||||
|
||||
/**
|
||||
* 获取启用的模板列表
|
||||
*/
|
||||
@Operation(summary = "获取启用的模板列表", description = "获取所有启用状态的模板列表")
|
||||
@GetMapping("/list")
|
||||
public Result<List<TemplateVO>> listEnabledTemplates() {
|
||||
List<TemplateVO> templates = templateService.listEnabledTemplates();
|
||||
return Result.success(templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有模板列表(管理员用)
|
||||
*/
|
||||
@Operation(summary = "获取所有模板列表", description = "管理员获取所有模板列表")
|
||||
@GetMapping("/list/all")
|
||||
public Result<List<TemplateVO>> listAllTemplates() {
|
||||
List<TemplateVO> templates = templateService.listAllTemplates();
|
||||
return Result.success(templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取模板
|
||||
*/
|
||||
@Operation(summary = "获取模板详情", description = "根据模板ID获取模板详细信息")
|
||||
@GetMapping("/{id}")
|
||||
public Result<TemplateVO> getTemplateById(
|
||||
@Parameter(description = "模板ID") @PathVariable String id) {
|
||||
TemplateVO template = templateService.getTemplateById(id);
|
||||
return Result.success(template);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建模板
|
||||
*/
|
||||
@Operation(summary = "创建模板", description = "创建新模板")
|
||||
@PostMapping
|
||||
public Result<TemplateVO> createTemplate(@Valid @RequestBody TemplateCreateDTO dto) {
|
||||
TemplateVO template = templateService.createTemplate(dto);
|
||||
return Result.success(template);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新模板
|
||||
*/
|
||||
@Operation(summary = "更新模板", description = "更新模板信息")
|
||||
@PutMapping("/{id}")
|
||||
public Result<TemplateVO> updateTemplate(
|
||||
@Parameter(description = "模板ID") @PathVariable String id,
|
||||
@Valid @RequestBody TemplateUpdateDTO dto) {
|
||||
TemplateVO template = templateService.updateTemplate(id, dto);
|
||||
return Result.success(template);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新模板状态
|
||||
*/
|
||||
@Operation(summary = "更新模板状态", description = "启用或禁用模板")
|
||||
@PutMapping("/{id}/status")
|
||||
public Result<Void> updateStatus(
|
||||
@Parameter(description = "模板ID") @PathVariable String id,
|
||||
@Valid @RequestBody UserStatusDTO dto) {
|
||||
templateService.updateStatus(id, dto.getStatus());
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除模板
|
||||
*/
|
||||
@Operation(summary = "删除模板", description = "删除指定模板")
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> deleteTemplate(
|
||||
@Parameter(description = "模板ID") @PathVariable String id) {
|
||||
templateService.deleteTemplate(id);
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,120 @@
|
||||
package com.wjbl.worklog.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.wjbl.worklog.common.PageResult;
|
||||
import com.wjbl.worklog.common.Result;
|
||||
import com.wjbl.worklog.dto.UserCreateDTO;
|
||||
import com.wjbl.worklog.dto.UserStatusDTO;
|
||||
import com.wjbl.worklog.dto.UserUpdateDTO;
|
||||
import com.wjbl.worklog.service.UserService;
|
||||
import com.wjbl.worklog.vo.UserVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 用户控制器
|
||||
* 处理用户管理相关请求
|
||||
*/
|
||||
@Tag(name = "用户管理", description = "用户管理相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/user")
|
||||
@RequiredArgsConstructor
|
||||
public class UserController {
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
/**
|
||||
* 分页查询用户
|
||||
*/
|
||||
@Operation(summary = "分页查询用户", description = "分页查询用户列表,支持按姓名、账号、状态筛选")
|
||||
@GetMapping("/page")
|
||||
public Result<PageResult<UserVO>> pageUsers(
|
||||
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@Parameter(description = "每页大小") @RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@Parameter(description = "姓名关键字") @RequestParam(required = false) String name,
|
||||
@Parameter(description = "账号关键字") @RequestParam(required = false) String username,
|
||||
@Parameter(description = "状态") @RequestParam(required = false) Integer status) {
|
||||
|
||||
Page<UserVO> page = userService.pageUsers(pageNum, pageSize, name, username, status);
|
||||
|
||||
PageResult<UserVO> pageResult = new PageResult<>();
|
||||
pageResult.setPageNum(page.getCurrent());
|
||||
pageResult.setPageSize(page.getSize());
|
||||
pageResult.setTotal(page.getTotal());
|
||||
pageResult.setList(page.getRecords());
|
||||
|
||||
return Result.success(pageResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取用户
|
||||
*/
|
||||
@Operation(summary = "获取用户详情", description = "根据用户ID获取用户详细信息")
|
||||
@GetMapping("/{id}")
|
||||
public Result<UserVO> getUserById(
|
||||
@Parameter(description = "用户ID") @PathVariable String id) {
|
||||
UserVO user = userService.getUserById(id);
|
||||
return Result.success(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建用户
|
||||
*/
|
||||
@Operation(summary = "创建用户", description = "创建新用户")
|
||||
@PostMapping
|
||||
public Result<UserVO> createUser(@Valid @RequestBody UserCreateDTO dto) {
|
||||
UserVO user = userService.createUser(dto);
|
||||
return Result.success(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户
|
||||
*/
|
||||
@Operation(summary = "更新用户", description = "更新用户信息")
|
||||
@PutMapping("/{id}")
|
||||
public Result<UserVO> updateUser(
|
||||
@Parameter(description = "用户ID") @PathVariable String id,
|
||||
@Valid @RequestBody UserUpdateDTO dto) {
|
||||
UserVO user = userService.updateUser(id, dto);
|
||||
return Result.success(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户状态
|
||||
*/
|
||||
@Operation(summary = "更新用户状态", description = "启用或禁用用户")
|
||||
@PutMapping("/{id}/status")
|
||||
public Result<Void> updateStatus(
|
||||
@Parameter(description = "用户ID") @PathVariable String id,
|
||||
@Valid @RequestBody UserStatusDTO dto) {
|
||||
userService.updateStatus(id, dto.getStatus());
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*/
|
||||
@Operation(summary = "删除用户", description = "删除指定用户")
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> deleteUser(
|
||||
@Parameter(description = "用户ID") @PathVariable String id) {
|
||||
userService.deleteUser(id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置用户密码
|
||||
*/
|
||||
@Operation(summary = "重置密码", description = "重置用户密码")
|
||||
@PutMapping("/{id}/password")
|
||||
public Result<Void> resetPassword(
|
||||
@Parameter(description = "用户ID") @PathVariable String id,
|
||||
@Parameter(description = "新密码") @RequestParam String newPassword) {
|
||||
userService.resetPassword(id, newPassword);
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
package com.wjbl.worklog.data.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 工作日志模板实体类
|
||||
* 对应数据库表:log_template
|
||||
*/
|
||||
@Data
|
||||
@TableName("log_template")
|
||||
public class LogTemplate implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 模板ID(雪花算法生成,使用String类型)
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 模板名称
|
||||
*/
|
||||
private String templateName;
|
||||
|
||||
/**
|
||||
* 模板内容(Markdown格式)
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 状态(0-禁用,1-启用)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 创建人ID
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
/**
|
||||
* 更新人ID
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updatedBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedTime;
|
||||
|
||||
/**
|
||||
* 逻辑删除(0-未删除,1-已删除)
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
@ -0,0 +1,99 @@
|
||||
package com.wjbl.worklog.data.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 系统用户实体类
|
||||
* 对应数据库表:sys_user
|
||||
*/
|
||||
@Data
|
||||
@TableName("sys_user")
|
||||
public class User implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户ID(雪花算法生成,使用String类型)
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 登录账号
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 登录密码(BCrypt加密)
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 姓名
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 联系方式
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 电子邮箱
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 职位
|
||||
*/
|
||||
private String position;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 状态(0-禁用,1-启用)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 角色(USER-普通用户,ADMIN-管理员)
|
||||
*/
|
||||
private String role;
|
||||
|
||||
/**
|
||||
* 创建人ID
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
/**
|
||||
* 更新人ID
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updatedBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedTime;
|
||||
|
||||
/**
|
||||
* 逻辑删除(0-未删除,1-已删除)
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
package com.wjbl.worklog.data.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 工作日志实体类
|
||||
* 对应数据库表:work_log
|
||||
*/
|
||||
@Data
|
||||
@TableName("work_log")
|
||||
public class WorkLog implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 日志ID(雪花算法生成,使用String类型)
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 日志日期
|
||||
*/
|
||||
private LocalDate logDate;
|
||||
|
||||
/**
|
||||
* 日志标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 日志内容(Markdown格式)
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 模板ID
|
||||
*/
|
||||
private String templateId;
|
||||
|
||||
/**
|
||||
* 创建人ID
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
/**
|
||||
* 更新人ID
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updatedBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedTime;
|
||||
|
||||
/**
|
||||
* 逻辑删除(0-未删除,1-已删除)
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package com.wjbl.worklog.data.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.wjbl.worklog.data.entity.LogTemplate;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 日志模板 Mapper 接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface LogTemplateMapper extends BaseMapper<LogTemplate> {
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package com.wjbl.worklog.data.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.wjbl.worklog.data.entity.User;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 用户 Mapper 接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface UserMapper extends BaseMapper<User> {
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package com.wjbl.worklog.data.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.wjbl.worklog.data.entity.WorkLog;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 工作日志 Mapper 接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface WorkLogMapper extends BaseMapper<WorkLog> {
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.wjbl.worklog.data.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.wjbl.worklog.data.entity.LogTemplate;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 日志模板数据服务接口
|
||||
* 遵循架构设计规范:使用 XxxDataService 命名
|
||||
*/
|
||||
public interface LogTemplateDataService extends IService<LogTemplate> {
|
||||
|
||||
/**
|
||||
* 根据模板名称查询模板
|
||||
*
|
||||
* @param templateName 模板名称
|
||||
* @return 模板实体
|
||||
*/
|
||||
LogTemplate getByTemplateName(String templateName);
|
||||
|
||||
/**
|
||||
* 查询所有启用的模板
|
||||
*
|
||||
* @return 启用的模板列表
|
||||
*/
|
||||
List<LogTemplate> listEnabledTemplates();
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package com.wjbl.worklog.data.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.wjbl.worklog.data.entity.User;
|
||||
|
||||
/**
|
||||
* 用户数据服务接口
|
||||
* 遵循架构设计规范:使用 XxxDataService 命名
|
||||
*/
|
||||
public interface UserDataService extends IService<User> {
|
||||
|
||||
/**
|
||||
* 根据用户名查询用户
|
||||
*
|
||||
* @param username 用户名
|
||||
* @return 用户实体
|
||||
*/
|
||||
User getByUsername(String username);
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package com.wjbl.worklog.data.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.wjbl.worklog.data.entity.WorkLog;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 工作日志数据服务接口
|
||||
* 遵循架构设计规范:使用 XxxDataService 命名
|
||||
*/
|
||||
public interface WorkLogDataService extends IService<WorkLog> {
|
||||
|
||||
/**
|
||||
* 分页查询用户的工作日志
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param userId 用户ID
|
||||
* @param startDate 开始日期
|
||||
* @param endDate 结束日期
|
||||
* @return 分页结果
|
||||
*/
|
||||
Page<WorkLog> pageByUserId(Page<WorkLog> page, String userId, LocalDate startDate, LocalDate endDate);
|
||||
|
||||
/**
|
||||
* 分页查询所有工作日志(管理员)
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param userId 用户ID(可选)
|
||||
* @param startDate 开始日期
|
||||
* @param endDate 结束日期
|
||||
* @return 分页结果
|
||||
*/
|
||||
Page<WorkLog> pageAll(Page<WorkLog> page, String userId, LocalDate startDate, LocalDate endDate);
|
||||
|
||||
/**
|
||||
* 根据用户ID和日期查询日志
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param logDate 日志日期
|
||||
* @return 工作日志
|
||||
*/
|
||||
WorkLog getByUserIdAndLogDate(String userId, LocalDate logDate);
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package com.wjbl.worklog.data.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.wjbl.worklog.data.entity.LogTemplate;
|
||||
import com.wjbl.worklog.data.mapper.LogTemplateMapper;
|
||||
import com.wjbl.worklog.data.service.LogTemplateDataService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 日志模板数据服务实现类
|
||||
* 遵循架构设计规范:使用 XxxDataServiceImpl 命名
|
||||
*/
|
||||
@Service
|
||||
public class LogTemplateDataServiceImpl extends ServiceImpl<LogTemplateMapper, LogTemplate> implements LogTemplateDataService {
|
||||
|
||||
@Override
|
||||
public LogTemplate getByTemplateName(String templateName) {
|
||||
return lambdaQuery()
|
||||
.eq(LogTemplate::getTemplateName, templateName)
|
||||
.one();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LogTemplate> listEnabledTemplates() {
|
||||
return lambdaQuery()
|
||||
.eq(LogTemplate::getStatus, 1)
|
||||
.orderByDesc(LogTemplate::getCreatedTime)
|
||||
.list();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package com.wjbl.worklog.data.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.wjbl.worklog.data.entity.User;
|
||||
import com.wjbl.worklog.data.mapper.UserMapper;
|
||||
import com.wjbl.worklog.data.service.UserDataService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 用户数据服务实现类
|
||||
* 遵循架构设计规范:使用 XxxDataServiceImpl 命名
|
||||
*/
|
||||
@Service
|
||||
public class UserDataServiceImpl extends ServiceImpl<UserMapper, User> implements UserDataService {
|
||||
|
||||
@Override
|
||||
public User getByUsername(String username) {
|
||||
return lambdaQuery()
|
||||
.eq(User::getUsername, username)
|
||||
.one();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package com.wjbl.worklog.data.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.wjbl.worklog.data.entity.WorkLog;
|
||||
import com.wjbl.worklog.data.mapper.WorkLogMapper;
|
||||
import com.wjbl.worklog.data.service.WorkLogDataService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 工作日志数据服务实现类
|
||||
* 遵循架构设计规范:使用 XxxDataServiceImpl 命名
|
||||
*/
|
||||
@Service
|
||||
public class WorkLogDataServiceImpl extends ServiceImpl<WorkLogMapper, WorkLog> implements WorkLogDataService {
|
||||
|
||||
@Override
|
||||
public Page<WorkLog> pageByUserId(Page<WorkLog> page, String userId, LocalDate startDate, LocalDate endDate) {
|
||||
LambdaQueryWrapper<WorkLog> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(WorkLog::getUserId, userId)
|
||||
.ge(startDate != null, WorkLog::getLogDate, startDate)
|
||||
.le(endDate != null, WorkLog::getLogDate, endDate)
|
||||
.orderByDesc(WorkLog::getLogDate);
|
||||
|
||||
return page(page, wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<WorkLog> pageAll(Page<WorkLog> page, String userId, LocalDate startDate, LocalDate endDate) {
|
||||
LambdaQueryWrapper<WorkLog> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(userId != null, WorkLog::getUserId, userId)
|
||||
.ge(startDate != null, WorkLog::getLogDate, startDate)
|
||||
.le(endDate != null, WorkLog::getLogDate, endDate)
|
||||
.orderByDesc(WorkLog::getLogDate);
|
||||
|
||||
return page(page, wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorkLog getByUserIdAndLogDate(String userId, LocalDate logDate) {
|
||||
return lambdaQuery()
|
||||
.eq(WorkLog::getUserId, userId)
|
||||
.eq(WorkLog::getLogDate, logDate)
|
||||
.one();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package com.wjbl.worklog.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 日志创建请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "日志创建请求")
|
||||
public class LogCreateDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 日志日期
|
||||
*/
|
||||
@Schema(description = "日志日期,默认当天")
|
||||
private LocalDate logDate;
|
||||
|
||||
/**
|
||||
* 日志标题
|
||||
*/
|
||||
@NotBlank(message = "标题不能为空")
|
||||
@Schema(description = "日志标题", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 日志内容(Markdown格式)
|
||||
*/
|
||||
@Schema(description = "日志内容(Markdown格式)")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 模板ID
|
||||
*/
|
||||
@Schema(description = "模板ID")
|
||||
private String templateId;
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.wjbl.worklog.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 日志更新请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "日志更新请求")
|
||||
public class LogUpdateDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 日志标题
|
||||
*/
|
||||
@Schema(description = "日志标题")
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 日志内容(Markdown格式)
|
||||
*/
|
||||
@Schema(description = "日志内容(Markdown格式)")
|
||||
private String content;
|
||||
}
|
||||
31
worklog-api/src/main/java/com/wjbl/worklog/dto/LoginDTO.java
Normal file
31
worklog-api/src/main/java/com/wjbl/worklog/dto/LoginDTO.java
Normal file
@ -0,0 +1,31 @@
|
||||
package com.wjbl.worklog.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 登录请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "登录请求")
|
||||
public class LoginDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
@Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@NotBlank(message = "密码不能为空")
|
||||
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String password;
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.wjbl.worklog.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 模板创建请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "模板创建请求")
|
||||
public class TemplateCreateDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 模板名称
|
||||
*/
|
||||
@NotBlank(message = "模板名称不能为空")
|
||||
@Schema(description = "模板名称", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String templateName;
|
||||
|
||||
/**
|
||||
* 模板内容(Markdown格式)
|
||||
*/
|
||||
@Schema(description = "模板内容(Markdown格式)")
|
||||
private String content;
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.wjbl.worklog.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 模板更新请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "模板更新请求")
|
||||
public class TemplateUpdateDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 模板名称
|
||||
*/
|
||||
@Schema(description = "模板名称")
|
||||
private String templateName;
|
||||
|
||||
/**
|
||||
* 模板内容(Markdown格式)
|
||||
*/
|
||||
@Schema(description = "模板内容(Markdown格式)")
|
||||
private String content;
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
package com.wjbl.worklog.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 用户创建请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "用户创建请求")
|
||||
public class UserCreateDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 登录账号
|
||||
*/
|
||||
@NotBlank(message = "账号不能为空")
|
||||
@Schema(description = "登录账号", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 登录密码
|
||||
*/
|
||||
@NotBlank(message = "密码不能为空")
|
||||
@Schema(description = "登录密码", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 姓名
|
||||
*/
|
||||
@NotBlank(message = "姓名不能为空")
|
||||
@Schema(description = "姓名", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 联系方式
|
||||
*/
|
||||
@Schema(description = "联系方式")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 电子邮箱
|
||||
*/
|
||||
@Schema(description = "电子邮箱")
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 职位
|
||||
*/
|
||||
@Schema(description = "职位")
|
||||
private String position;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 角色(USER-普通用户,ADMIN-管理员)
|
||||
*/
|
||||
@Schema(description = "角色(USER-普通用户,ADMIN-管理员)")
|
||||
private String role;
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package com.wjbl.worklog.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 用户状态更新 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "用户状态更新请求")
|
||||
public class UserStatusDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 状态(0-禁用,1-启用)
|
||||
*/
|
||||
@NotNull(message = "状态不能为空")
|
||||
@Schema(description = "状态(0-禁用,1-启用)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Integer status;
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
package com.wjbl.worklog.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 用户更新请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "用户更新请求")
|
||||
public class UserUpdateDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 姓名
|
||||
*/
|
||||
@Schema(description = "姓名")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 联系方式
|
||||
*/
|
||||
@Schema(description = "联系方式")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 电子邮箱
|
||||
*/
|
||||
@Schema(description = "电子邮箱")
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 职位
|
||||
*/
|
||||
@Schema(description = "职位")
|
||||
private String position;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package com.wjbl.worklog.service;
|
||||
|
||||
import com.wjbl.worklog.dto.LoginDTO;
|
||||
import com.wjbl.worklog.vo.LoginVO;
|
||||
|
||||
/**
|
||||
* 认证服务接口
|
||||
*/
|
||||
public interface AuthService {
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*
|
||||
* @param loginDTO 登录请求
|
||||
* @return 登录响应(包含 Token 和用户信息)
|
||||
*/
|
||||
LoginVO login(LoginDTO loginDTO);
|
||||
|
||||
/**
|
||||
* 用户登出
|
||||
*
|
||||
* @param token Token
|
||||
*/
|
||||
void logout(String token);
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
package com.wjbl.worklog.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.wjbl.worklog.dto.LogCreateDTO;
|
||||
import com.wjbl.worklog.dto.LogUpdateDTO;
|
||||
import com.wjbl.worklog.vo.LogVO;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 日志服务接口
|
||||
*/
|
||||
public interface LogService {
|
||||
|
||||
/**
|
||||
* 创建日志
|
||||
*
|
||||
* @param dto 创建请求
|
||||
* @return 日志信息
|
||||
*/
|
||||
LogVO createLog(LogCreateDTO dto);
|
||||
|
||||
/**
|
||||
* 更新日志
|
||||
*
|
||||
* @param id 日志ID
|
||||
* @param dto 更新请求
|
||||
* @return 日志信息
|
||||
*/
|
||||
LogVO updateLog(String id, LogUpdateDTO dto);
|
||||
|
||||
/**
|
||||
* 删除日志
|
||||
*
|
||||
* @param id 日志ID
|
||||
*/
|
||||
void deleteLog(String id);
|
||||
|
||||
/**
|
||||
* 分页查询日志(普通用户只能查看自己的日志)
|
||||
*
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页大小
|
||||
* @param startDate 开始日期
|
||||
* @param endDate 结束日期
|
||||
* @return 分页结果
|
||||
*/
|
||||
Page<LogVO> pageMyLogs(Integer pageNum, Integer pageSize, LocalDate startDate, LocalDate endDate);
|
||||
|
||||
/**
|
||||
* 分页查询所有日志(管理员)
|
||||
*
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页大小
|
||||
* @param userId 用户ID
|
||||
* @param startDate 开始日期
|
||||
* @param endDate 结束日期
|
||||
* @return 分页结果
|
||||
*/
|
||||
Page<LogVO> pageAllLogs(Integer pageNum, Integer pageSize, String userId, LocalDate startDate, LocalDate endDate);
|
||||
|
||||
/**
|
||||
* 根据ID获取日志
|
||||
*
|
||||
* @param id 日志ID
|
||||
* @return 日志信息
|
||||
*/
|
||||
LogVO getLogById(String id);
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
package com.wjbl.worklog.service;
|
||||
|
||||
import com.wjbl.worklog.dto.TemplateCreateDTO;
|
||||
import com.wjbl.worklog.dto.TemplateUpdateDTO;
|
||||
import com.wjbl.worklog.vo.TemplateVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 模板服务接口
|
||||
*/
|
||||
public interface TemplateService {
|
||||
|
||||
/**
|
||||
* 创建模板
|
||||
*
|
||||
* @param dto 创建请求
|
||||
* @return 模板信息
|
||||
*/
|
||||
TemplateVO createTemplate(TemplateCreateDTO dto);
|
||||
|
||||
/**
|
||||
* 更新模板
|
||||
*
|
||||
* @param id 模板ID
|
||||
* @param dto 更新请求
|
||||
* @return 模板信息
|
||||
*/
|
||||
TemplateVO updateTemplate(String id, TemplateUpdateDTO dto);
|
||||
|
||||
/**
|
||||
* 删除模板
|
||||
*
|
||||
* @param id 模板ID
|
||||
*/
|
||||
void deleteTemplate(String id);
|
||||
|
||||
/**
|
||||
* 更新模板状态
|
||||
*
|
||||
* @param id 模板ID
|
||||
* @param status 状态(0-禁用,1-启用)
|
||||
*/
|
||||
void updateStatus(String id, Integer status);
|
||||
|
||||
/**
|
||||
* 获取所有启用的模板列表
|
||||
*
|
||||
* @return 模板列表
|
||||
*/
|
||||
List<TemplateVO> listEnabledTemplates();
|
||||
|
||||
/**
|
||||
* 获取所有模板列表(管理员用)
|
||||
*
|
||||
* @return 模板列表
|
||||
*/
|
||||
List<TemplateVO> listAllTemplates();
|
||||
|
||||
/**
|
||||
* 根据ID获取模板
|
||||
*
|
||||
* @param id 模板ID
|
||||
* @return 模板信息
|
||||
*/
|
||||
TemplateVO getTemplateById(String id);
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package com.wjbl.worklog.service;
|
||||
|
||||
import com.wjbl.worklog.common.context.UserInfo;
|
||||
|
||||
/**
|
||||
* Token 服务接口
|
||||
*/
|
||||
public interface TokenService {
|
||||
|
||||
/**
|
||||
* 生成 Token
|
||||
*
|
||||
* @param userInfo 用户信息
|
||||
* @return Token
|
||||
*/
|
||||
String generateToken(UserInfo userInfo);
|
||||
|
||||
/**
|
||||
* 根据 Token 获取用户信息
|
||||
*
|
||||
* @param token Token
|
||||
* @return 用户信息
|
||||
*/
|
||||
UserInfo getUserInfo(String token);
|
||||
|
||||
/**
|
||||
* 刷新 Token 有效期
|
||||
*
|
||||
* @param token Token
|
||||
*/
|
||||
void refreshToken(String token);
|
||||
|
||||
/**
|
||||
* 删除 Token(登出)
|
||||
*
|
||||
* @param token Token
|
||||
*/
|
||||
void removeToken(String token);
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
package com.wjbl.worklog.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.wjbl.worklog.dto.UserCreateDTO;
|
||||
import com.wjbl.worklog.dto.UserUpdateDTO;
|
||||
import com.wjbl.worklog.vo.UserVO;
|
||||
|
||||
/**
|
||||
* 用户服务接口
|
||||
* 处理用户相关的业务逻辑
|
||||
*/
|
||||
public interface UserService {
|
||||
|
||||
/**
|
||||
* 创建用户
|
||||
*
|
||||
* @param dto 创建请求
|
||||
* @return 用户信息
|
||||
*/
|
||||
UserVO createUser(UserCreateDTO dto);
|
||||
|
||||
/**
|
||||
* 更新用户
|
||||
*
|
||||
* @param id 用户ID
|
||||
* @param dto 更新请求
|
||||
* @return 用户信息
|
||||
*/
|
||||
UserVO updateUser(String id, UserUpdateDTO dto);
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*
|
||||
* @param id 用户ID
|
||||
*/
|
||||
void deleteUser(String id);
|
||||
|
||||
/**
|
||||
* 更新用户状态
|
||||
*
|
||||
* @param id 用户ID
|
||||
* @param status 状态(0-禁用,1-启用)
|
||||
*/
|
||||
void updateStatus(String id, Integer status);
|
||||
|
||||
/**
|
||||
* 分页查询用户
|
||||
*
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页大小
|
||||
* @param name 姓名关键字(可选)
|
||||
* @param username 账号关键字(可选)
|
||||
* @param status 状态(可选)
|
||||
* @return 分页结果
|
||||
*/
|
||||
Page<UserVO> pageUsers(Integer pageNum, Integer pageSize, String name, String username, Integer status);
|
||||
|
||||
/**
|
||||
* 根据ID获取用户
|
||||
*
|
||||
* @param id 用户ID
|
||||
* @return 用户信息
|
||||
*/
|
||||
UserVO getUserById(String id);
|
||||
|
||||
/**
|
||||
* 重置用户密码
|
||||
*
|
||||
* @param id 用户ID
|
||||
* @param newPassword 新密码
|
||||
*/
|
||||
void resetPassword(String id, String newPassword);
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
package com.wjbl.worklog.service.impl;
|
||||
|
||||
import com.wjbl.worklog.common.context.UserInfo;
|
||||
import com.wjbl.worklog.common.exception.BusinessException;
|
||||
import com.wjbl.worklog.common.util.PasswordUtil;
|
||||
import com.wjbl.worklog.data.entity.User;
|
||||
import com.wjbl.worklog.data.service.UserDataService;
|
||||
import com.wjbl.worklog.dto.LoginDTO;
|
||||
import com.wjbl.worklog.service.AuthService;
|
||||
import com.wjbl.worklog.service.TokenService;
|
||||
import com.wjbl.worklog.vo.LoginVO;
|
||||
import com.wjbl.worklog.vo.UserInfoVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 认证服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AuthServiceImpl implements AuthService {
|
||||
|
||||
private final UserDataService userDataService;
|
||||
private final TokenService tokenService;
|
||||
|
||||
@Override
|
||||
public LoginVO login(LoginDTO loginDTO) {
|
||||
// 查询用户
|
||||
User user = userDataService.getByUsername(loginDTO.getUsername());
|
||||
|
||||
if (user == null) {
|
||||
throw new BusinessException("用户名或密码错误");
|
||||
}
|
||||
|
||||
// 校验密码
|
||||
if (!PasswordUtil.matches(loginDTO.getPassword(), user.getPassword())) {
|
||||
throw new BusinessException("用户名或密码错误");
|
||||
}
|
||||
|
||||
// 校验状态
|
||||
if (user.getStatus() == null || user.getStatus() != 1) {
|
||||
throw new BusinessException("账号已被禁用");
|
||||
}
|
||||
|
||||
// 构建用户信息
|
||||
UserInfo userInfo = new UserInfo(
|
||||
user.getId(),
|
||||
user.getUsername(),
|
||||
user.getName(),
|
||||
user.getRole()
|
||||
);
|
||||
|
||||
// 生成 Token
|
||||
String token = tokenService.generateToken(userInfo);
|
||||
|
||||
// 构建返回结果
|
||||
LoginVO loginVO = new LoginVO();
|
||||
loginVO.setToken(token);
|
||||
|
||||
UserInfoVO userInfoVO = new UserInfoVO();
|
||||
userInfoVO.setId(user.getId());
|
||||
userInfoVO.setUsername(user.getUsername());
|
||||
userInfoVO.setName(user.getName());
|
||||
userInfoVO.setRole(user.getRole());
|
||||
loginVO.setUserInfo(userInfoVO);
|
||||
|
||||
log.info("用户登录成功:{}", user.getUsername());
|
||||
|
||||
return loginVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(String token) {
|
||||
tokenService.removeToken(token);
|
||||
log.info("用户登出成功");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,175 @@
|
||||
package com.wjbl.worklog.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.wjbl.worklog.common.context.UserContext;
|
||||
import com.wjbl.worklog.common.exception.BusinessException;
|
||||
import com.wjbl.worklog.data.entity.WorkLog;
|
||||
import com.wjbl.worklog.data.service.WorkLogDataService;
|
||||
import com.wjbl.worklog.dto.LogCreateDTO;
|
||||
import com.wjbl.worklog.dto.LogUpdateDTO;
|
||||
import com.wjbl.worklog.service.LogService;
|
||||
import com.wjbl.worklog.vo.LogVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 日志服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class LogServiceImpl implements LogService {
|
||||
|
||||
private final WorkLogDataService workLogDataService;
|
||||
|
||||
@Override
|
||||
public LogVO createLog(LogCreateDTO dto) {
|
||||
String currentUserId = UserContext.getUserId();
|
||||
LocalDate logDate = dto.getLogDate() != null ? dto.getLogDate() : LocalDate.now();
|
||||
|
||||
// 校验当天是否已有日志
|
||||
WorkLog existLog = workLogDataService.getByUserIdAndLogDate(currentUserId, logDate);
|
||||
if (existLog != null) {
|
||||
throw new BusinessException("当天已有工作日志,请编辑已有日志");
|
||||
}
|
||||
|
||||
// 创建日志
|
||||
WorkLog workLog = new WorkLog();
|
||||
workLog.setUserId(currentUserId);
|
||||
workLog.setLogDate(logDate);
|
||||
workLog.setTitle(dto.getTitle());
|
||||
workLog.setContent(dto.getContent());
|
||||
workLog.setTemplateId(dto.getTemplateId());
|
||||
workLog.setDeleted(0);
|
||||
|
||||
// 设置审计字段
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
workLog.setCreatedBy(currentUserId);
|
||||
workLog.setCreatedTime(now);
|
||||
workLog.setUpdatedBy(currentUserId);
|
||||
workLog.setUpdatedTime(now);
|
||||
|
||||
workLogDataService.save(workLog);
|
||||
|
||||
log.info("创建工作日志成功:用户={}, 日期={}", currentUserId, logDate);
|
||||
|
||||
return convertToVO(workLog);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogVO updateLog(String id, LogUpdateDTO dto) {
|
||||
String currentUserId = UserContext.getUserId();
|
||||
boolean isAdmin = UserContext.isAdmin();
|
||||
|
||||
WorkLog workLog = workLogDataService.getById(id);
|
||||
if (workLog == null) {
|
||||
throw new BusinessException("日志不存在");
|
||||
}
|
||||
|
||||
// 校验权限(只能编辑自己的日志,管理员除外)
|
||||
if (!isAdmin && !workLog.getUserId().equals(currentUserId)) {
|
||||
throw new BusinessException("无权编辑他人的日志");
|
||||
}
|
||||
|
||||
// 更新日志
|
||||
if (dto.getTitle() != null) {
|
||||
workLog.setTitle(dto.getTitle());
|
||||
}
|
||||
if (dto.getContent() != null) {
|
||||
workLog.setContent(dto.getContent());
|
||||
}
|
||||
|
||||
// 设置审计字段
|
||||
workLog.setUpdatedBy(currentUserId);
|
||||
workLog.setUpdatedTime(LocalDateTime.now());
|
||||
|
||||
workLogDataService.updateById(workLog);
|
||||
|
||||
log.info("更新工作日志成功:ID={}", id);
|
||||
|
||||
return convertToVO(workLog);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteLog(String id) {
|
||||
String currentUserId = UserContext.getUserId();
|
||||
boolean isAdmin = UserContext.isAdmin();
|
||||
|
||||
WorkLog workLog = workLogDataService.getById(id);
|
||||
if (workLog == null) {
|
||||
throw new BusinessException("日志不存在");
|
||||
}
|
||||
|
||||
// 校验权限(只能删除自己的日志,管理员除外)
|
||||
if (!isAdmin && !workLog.getUserId().equals(currentUserId)) {
|
||||
throw new BusinessException("无权删除他人的日志");
|
||||
}
|
||||
|
||||
workLogDataService.removeById(id);
|
||||
|
||||
log.info("删除工作日志成功:ID={}", id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<LogVO> pageMyLogs(Integer pageNum, Integer pageSize, LocalDate startDate, LocalDate endDate) {
|
||||
String currentUserId = UserContext.getUserId();
|
||||
Page<WorkLog> page = new Page<>(pageNum, pageSize);
|
||||
|
||||
Page<WorkLog> logPage = workLogDataService.pageByUserId(page, currentUserId, startDate, endDate);
|
||||
|
||||
// 转换为 VO
|
||||
Page<LogVO> voPage = new Page<>(logPage.getCurrent(), logPage.getSize(), logPage.getTotal());
|
||||
voPage.setRecords(logPage.getRecords().stream()
|
||||
.map(this::convertToVO)
|
||||
.toList());
|
||||
|
||||
return voPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<LogVO> pageAllLogs(Integer pageNum, Integer pageSize, String userId, LocalDate startDate, LocalDate endDate) {
|
||||
Page<WorkLog> page = new Page<>(pageNum, pageSize);
|
||||
|
||||
Page<WorkLog> logPage = workLogDataService.pageAll(page, userId, startDate, endDate);
|
||||
|
||||
// 转换为 VO
|
||||
Page<LogVO> voPage = new Page<>(logPage.getCurrent(), logPage.getSize(), logPage.getTotal());
|
||||
voPage.setRecords(logPage.getRecords().stream()
|
||||
.map(this::convertToVO)
|
||||
.toList());
|
||||
|
||||
return voPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogVO getLogById(String id) {
|
||||
String currentUserId = UserContext.getUserId();
|
||||
boolean isAdmin = UserContext.isAdmin();
|
||||
|
||||
WorkLog workLog = workLogDataService.getById(id);
|
||||
if (workLog == null) {
|
||||
throw new BusinessException("日志不存在");
|
||||
}
|
||||
|
||||
// 校验权限(只能查看自己的日志,管理员可查看所有)
|
||||
if (!isAdmin && !workLog.getUserId().equals(currentUserId)) {
|
||||
throw new BusinessException("无权查看他人的日志");
|
||||
}
|
||||
|
||||
return convertToVO(workLog);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 VO
|
||||
*/
|
||||
private LogVO convertToVO(WorkLog workLog) {
|
||||
LogVO vo = new LogVO();
|
||||
BeanUtils.copyProperties(workLog, vo);
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,148 @@
|
||||
package com.wjbl.worklog.service.impl;
|
||||
|
||||
import com.wjbl.worklog.common.context.UserContext;
|
||||
import com.wjbl.worklog.common.exception.BusinessException;
|
||||
import com.wjbl.worklog.data.entity.LogTemplate;
|
||||
import com.wjbl.worklog.data.service.LogTemplateDataService;
|
||||
import com.wjbl.worklog.dto.TemplateCreateDTO;
|
||||
import com.wjbl.worklog.dto.TemplateUpdateDTO;
|
||||
import com.wjbl.worklog.service.TemplateService;
|
||||
import com.wjbl.worklog.vo.TemplateVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 模板服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TemplateServiceImpl implements TemplateService {
|
||||
|
||||
private final LogTemplateDataService logTemplateDataService;
|
||||
|
||||
@Override
|
||||
public TemplateVO createTemplate(TemplateCreateDTO dto) {
|
||||
// 校验模板名称是否已存在
|
||||
LogTemplate existTemplate = logTemplateDataService.getByTemplateName(dto.getTemplateName());
|
||||
if (existTemplate != null) {
|
||||
throw new BusinessException("模板名称已存在");
|
||||
}
|
||||
|
||||
// 创建模板
|
||||
LogTemplate template = new LogTemplate();
|
||||
template.setTemplateName(dto.getTemplateName());
|
||||
template.setContent(dto.getContent());
|
||||
template.setStatus(1); // 默认启用
|
||||
template.setDeleted(0);
|
||||
|
||||
// 设置审计字段
|
||||
String currentUserId = UserContext.getUserId();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
template.setCreatedBy(currentUserId);
|
||||
template.setCreatedTime(now);
|
||||
template.setUpdatedBy(currentUserId);
|
||||
template.setUpdatedTime(now);
|
||||
|
||||
logTemplateDataService.save(template);
|
||||
|
||||
log.info("创建模板成功:{}", template.getTemplateName());
|
||||
|
||||
return convertToVO(template);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemplateVO updateTemplate(String id, TemplateUpdateDTO dto) {
|
||||
LogTemplate template = logTemplateDataService.getById(id);
|
||||
if (template == null) {
|
||||
throw new BusinessException("模板不存在");
|
||||
}
|
||||
|
||||
// 校验模板名称是否被其他模板占用
|
||||
if (dto.getTemplateName() != null && !dto.getTemplateName().equals(template.getTemplateName())) {
|
||||
LogTemplate existTemplate = logTemplateDataService.getByTemplateName(dto.getTemplateName());
|
||||
if (existTemplate != null) {
|
||||
throw new BusinessException("模板名称已存在");
|
||||
}
|
||||
template.setTemplateName(dto.getTemplateName());
|
||||
}
|
||||
|
||||
// 更新模板内容
|
||||
if (dto.getContent() != null) {
|
||||
template.setContent(dto.getContent());
|
||||
}
|
||||
|
||||
// 设置审计字段
|
||||
template.setUpdatedBy(UserContext.getUserId());
|
||||
template.setUpdatedTime(LocalDateTime.now());
|
||||
|
||||
logTemplateDataService.updateById(template);
|
||||
|
||||
log.info("更新模板成功:{}", template.getTemplateName());
|
||||
|
||||
return convertToVO(template);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteTemplate(String id) {
|
||||
LogTemplate template = logTemplateDataService.getById(id);
|
||||
if (template == null) {
|
||||
throw new BusinessException("模板不存在");
|
||||
}
|
||||
|
||||
logTemplateDataService.removeById(id);
|
||||
|
||||
log.info("删除模板成功:{}", template.getTemplateName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStatus(String id, Integer status) {
|
||||
LogTemplate template = logTemplateDataService.getById(id);
|
||||
if (template == null) {
|
||||
throw new BusinessException("模板不存在");
|
||||
}
|
||||
|
||||
template.setStatus(status);
|
||||
template.setUpdatedBy(UserContext.getUserId());
|
||||
template.setUpdatedTime(LocalDateTime.now());
|
||||
|
||||
logTemplateDataService.updateById(template);
|
||||
|
||||
log.info("更新模板状态成功:{} -> {}", template.getTemplateName(), status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TemplateVO> listEnabledTemplates() {
|
||||
List<LogTemplate> templates = logTemplateDataService.listEnabledTemplates();
|
||||
return templates.stream().map(this::convertToVO).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TemplateVO> listAllTemplates() {
|
||||
List<LogTemplate> templates = logTemplateDataService.list();
|
||||
return templates.stream().map(this::convertToVO).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemplateVO getTemplateById(String id) {
|
||||
LogTemplate template = logTemplateDataService.getById(id);
|
||||
if (template == null) {
|
||||
throw new BusinessException("模板不存在");
|
||||
}
|
||||
return convertToVO(template);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 VO
|
||||
*/
|
||||
private TemplateVO convertToVO(LogTemplate template) {
|
||||
TemplateVO vo = new TemplateVO();
|
||||
BeanUtils.copyProperties(template, vo);
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
package com.wjbl.worklog.service.impl;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.wjbl.worklog.common.context.UserInfo;
|
||||
import com.wjbl.worklog.service.TokenService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Token 服务实现类
|
||||
* Token 存储在 Redis 中,Key 格式:auth:token:{token}
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TokenServiceImpl implements TokenService {
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Value("${worklog.token.expire-time:86400}")
|
||||
private Long expireTime;
|
||||
|
||||
@Value("${worklog.token.prefix:auth:token:}")
|
||||
private String tokenPrefix;
|
||||
|
||||
@Override
|
||||
public String generateToken(UserInfo userInfo) {
|
||||
// 生成 UUID Token
|
||||
String token = IdUtil.fastSimpleUUID();
|
||||
|
||||
// 存储到 Redis
|
||||
String key = tokenPrefix + token;
|
||||
redisTemplate.opsForValue().set(key, userInfo, expireTime, TimeUnit.SECONDS);
|
||||
|
||||
log.info("生成 Token 成功,用户:{}", userInfo.getUsername());
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserInfo getUserInfo(String token) {
|
||||
if (token == null || token.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String key = tokenPrefix + token;
|
||||
Object value = redisTemplate.opsForValue().get(key);
|
||||
|
||||
if (value instanceof UserInfo) {
|
||||
return (UserInfo) value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshToken(String token) {
|
||||
if (token == null || token.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String key = tokenPrefix + token;
|
||||
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeToken(String token) {
|
||||
if (token == null || token.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String key = tokenPrefix + token;
|
||||
redisTemplate.delete(key);
|
||||
|
||||
log.info("删除 Token 成功");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,205 @@
|
||||
package com.wjbl.worklog.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.wjbl.worklog.common.context.UserContext;
|
||||
import com.wjbl.worklog.common.exception.BusinessException;
|
||||
import com.wjbl.worklog.common.util.PasswordUtil;
|
||||
import com.wjbl.worklog.data.entity.User;
|
||||
import com.wjbl.worklog.data.service.UserDataService;
|
||||
import com.wjbl.worklog.dto.UserCreateDTO;
|
||||
import com.wjbl.worklog.dto.UserUpdateDTO;
|
||||
import com.wjbl.worklog.service.UserService;
|
||||
import com.wjbl.worklog.vo.UserVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserServiceImpl implements UserService {
|
||||
|
||||
private final UserDataService userDataService;
|
||||
|
||||
@Override
|
||||
public UserVO createUser(UserCreateDTO dto) {
|
||||
// 校验用户名是否已存在
|
||||
User existUser = userDataService.getByUsername(dto.getUsername());
|
||||
if (existUser != null) {
|
||||
throw new BusinessException("账号已存在");
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
User user = new User();
|
||||
user.setUsername(dto.getUsername());
|
||||
user.setPassword(PasswordUtil.encode(dto.getPassword()));
|
||||
user.setName(dto.getName());
|
||||
user.setPhone(dto.getPhone());
|
||||
user.setEmail(dto.getEmail());
|
||||
user.setPosition(dto.getPosition());
|
||||
user.setDescription(dto.getDescription());
|
||||
user.setRole(StringUtils.hasText(dto.getRole()) ? dto.getRole() : "USER");
|
||||
user.setStatus(1); // 默认启用
|
||||
user.setDeleted(0);
|
||||
|
||||
// 设置审计字段
|
||||
String currentUserId = UserContext.getUserId();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
user.setCreatedBy(currentUserId);
|
||||
user.setCreatedTime(now);
|
||||
user.setUpdatedBy(currentUserId);
|
||||
user.setUpdatedTime(now);
|
||||
|
||||
userDataService.save(user);
|
||||
|
||||
log.info("创建用户成功:{}", user.getUsername());
|
||||
|
||||
return convertToVO(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserVO updateUser(String id, UserUpdateDTO dto) {
|
||||
User user = userDataService.getById(id);
|
||||
if (user == null) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
if (StringUtils.hasText(dto.getName())) {
|
||||
user.setName(dto.getName());
|
||||
}
|
||||
if (dto.getPhone() != null) {
|
||||
user.setPhone(dto.getPhone());
|
||||
}
|
||||
if (dto.getEmail() != null) {
|
||||
user.setEmail(dto.getEmail());
|
||||
}
|
||||
if (dto.getPosition() != null) {
|
||||
user.setPosition(dto.getPosition());
|
||||
}
|
||||
if (dto.getDescription() != null) {
|
||||
user.setDescription(dto.getDescription());
|
||||
}
|
||||
|
||||
// 设置审计字段
|
||||
user.setUpdatedBy(UserContext.getUserId());
|
||||
user.setUpdatedTime(LocalDateTime.now());
|
||||
|
||||
userDataService.updateById(user);
|
||||
|
||||
log.info("更新用户成功:{}", user.getUsername());
|
||||
|
||||
return convertToVO(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteUser(String id) {
|
||||
User user = userDataService.getById(id);
|
||||
if (user == null) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
|
||||
// 不能删除自己
|
||||
if (id.equals(UserContext.getUserId())) {
|
||||
throw new BusinessException("不能删除自己");
|
||||
}
|
||||
|
||||
// 不能删除管理员
|
||||
if ("ADMIN".equals(user.getRole())) {
|
||||
throw new BusinessException("不能删除管理员");
|
||||
}
|
||||
|
||||
userDataService.removeById(id);
|
||||
|
||||
log.info("删除用户成功:{}", user.getUsername());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStatus(String id, Integer status) {
|
||||
User user = userDataService.getById(id);
|
||||
if (user == null) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
|
||||
// 不能禁用自己
|
||||
if (id.equals(UserContext.getUserId())) {
|
||||
throw new BusinessException("不能禁用自己");
|
||||
}
|
||||
|
||||
// 不能禁用管理员
|
||||
if ("ADMIN".equals(user.getRole())) {
|
||||
throw new BusinessException("不能禁用管理员");
|
||||
}
|
||||
|
||||
user.setStatus(status);
|
||||
user.setUpdatedBy(UserContext.getUserId());
|
||||
user.setUpdatedTime(LocalDateTime.now());
|
||||
|
||||
userDataService.updateById(user);
|
||||
|
||||
log.info("更新用户状态成功:{} -> {}", user.getUsername(), status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<UserVO> pageUsers(Integer pageNum, Integer pageSize, String name, String username, Integer status) {
|
||||
Page<User> page = new Page<>(pageNum, pageSize);
|
||||
|
||||
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.like(StringUtils.hasText(name), User::getName, name)
|
||||
.like(StringUtils.hasText(username), User::getUsername, username)
|
||||
.eq(status != null, User::getStatus, status)
|
||||
.orderByDesc(User::getCreatedTime);
|
||||
|
||||
Page<User> userPage = userDataService.page(page, wrapper);
|
||||
|
||||
// 转换为 VO
|
||||
Page<UserVO> voPage = new Page<>(userPage.getCurrent(), userPage.getSize(), userPage.getTotal());
|
||||
voPage.setRecords(userPage.getRecords().stream()
|
||||
.map(this::convertToVO)
|
||||
.toList());
|
||||
|
||||
return voPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserVO getUserById(String id) {
|
||||
User user = userDataService.getById(id);
|
||||
if (user == null) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
return convertToVO(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetPassword(String id, String newPassword) {
|
||||
User user = userDataService.getById(id);
|
||||
if (user == null) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
|
||||
user.setPassword(PasswordUtil.encode(newPassword));
|
||||
user.setUpdatedBy(UserContext.getUserId());
|
||||
user.setUpdatedTime(LocalDateTime.now());
|
||||
|
||||
userDataService.updateById(user);
|
||||
|
||||
log.info("重置用户密码成功:{}", user.getUsername());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 VO
|
||||
*/
|
||||
private UserVO convertToVO(User user) {
|
||||
UserVO vo = new UserVO();
|
||||
BeanUtils.copyProperties(user, vo);
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
66
worklog-api/src/main/java/com/wjbl/worklog/vo/LogVO.java
Normal file
66
worklog-api/src/main/java/com/wjbl/worklog/vo/LogVO.java
Normal file
@ -0,0 +1,66 @@
|
||||
package com.wjbl.worklog.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 日志信息 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "日志信息")
|
||||
public class LogVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 日志ID
|
||||
*/
|
||||
@Schema(description = "日志ID")
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@Schema(description = "用户ID")
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 日志日期
|
||||
*/
|
||||
@Schema(description = "日志日期")
|
||||
private LocalDate logDate;
|
||||
|
||||
/**
|
||||
* 日志标题
|
||||
*/
|
||||
@Schema(description = "日志标题")
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 日志内容(Markdown格式)
|
||||
*/
|
||||
@Schema(description = "日志内容(Markdown格式)")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 模板ID
|
||||
*/
|
||||
@Schema(description = "模板ID")
|
||||
private String templateId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updatedTime;
|
||||
}
|
||||
28
worklog-api/src/main/java/com/wjbl/worklog/vo/LoginVO.java
Normal file
28
worklog-api/src/main/java/com/wjbl/worklog/vo/LoginVO.java
Normal file
@ -0,0 +1,28 @@
|
||||
package com.wjbl.worklog.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 登录响应 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "登录响应")
|
||||
public class LoginVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Token
|
||||
*/
|
||||
@Schema(description = "Token")
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
@Schema(description = "用户信息")
|
||||
private UserInfoVO userInfo;
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
package com.wjbl.worklog.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 模板信息 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "模板信息")
|
||||
public class TemplateVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 模板ID
|
||||
*/
|
||||
@Schema(description = "模板ID")
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 模板名称
|
||||
*/
|
||||
@Schema(description = "模板名称")
|
||||
private String templateName;
|
||||
|
||||
/**
|
||||
* 模板内容(Markdown格式)
|
||||
*/
|
||||
@Schema(description = "模板内容(Markdown格式)")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 状态(0-禁用,1-启用)
|
||||
*/
|
||||
@Schema(description = "状态(0-禁用,1-启用)")
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updatedTime;
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package com.wjbl.worklog.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 用户信息 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "用户信息")
|
||||
public class UserInfoVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@Schema(description = "用户ID")
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@Schema(description = "用户名")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 姓名
|
||||
*/
|
||||
@Schema(description = "姓名")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 角色(USER-普通用户,ADMIN-管理员)
|
||||
*/
|
||||
@Schema(description = "角色")
|
||||
private String role;
|
||||
}
|
||||
83
worklog-api/src/main/java/com/wjbl/worklog/vo/UserVO.java
Normal file
83
worklog-api/src/main/java/com/wjbl/worklog/vo/UserVO.java
Normal file
@ -0,0 +1,83 @@
|
||||
package com.wjbl.worklog.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户信息 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "用户信息")
|
||||
public class UserVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@Schema(description = "用户ID")
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 登录账号
|
||||
*/
|
||||
@Schema(description = "登录账号")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 姓名
|
||||
*/
|
||||
@Schema(description = "姓名")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 联系方式
|
||||
*/
|
||||
@Schema(description = "联系方式")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 电子邮箱
|
||||
*/
|
||||
@Schema(description = "电子邮箱")
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 职位
|
||||
*/
|
||||
@Schema(description = "职位")
|
||||
private String position;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 状态(0-禁用,1-启用)
|
||||
*/
|
||||
@Schema(description = "状态(0-禁用,1-启用)")
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 角色(USER-普通用户,ADMIN-管理员)
|
||||
*/
|
||||
@Schema(description = "角色")
|
||||
private String role;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updatedTime;
|
||||
}
|
||||
86
worklog-api/src/main/resources/env.properties
Normal file
86
worklog-api/src/main/resources/env.properties
Normal file
@ -0,0 +1,86 @@
|
||||
# ====================================================
|
||||
# 工作日志服务平台 - 统一环境配置
|
||||
# 说明:所有服务共用的环境配置,包括数据库、缓存、注册中心等
|
||||
# ====================================================
|
||||
|
||||
# ==================== 数据库配置 ====================
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_NAME=worklog
|
||||
DB_USERNAME=worklog
|
||||
DB_PASSWORD=Wlog@123
|
||||
DB_URL=jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
|
||||
|
||||
# 连接池配置
|
||||
DB_POOL_MIN_IDLE=5
|
||||
DB_POOL_MAX_SIZE=20
|
||||
DB_POOL_CONNECTION_TIMEOUT=30000
|
||||
DB_POOL_IDLE_TIMEOUT=600000
|
||||
DB_POOL_MAX_LIFETIME=1800000
|
||||
|
||||
# ==================== Redis 配置 ====================
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=zjf@123456
|
||||
REDIS_DATABASE=0
|
||||
REDIS_TIMEOUT=5000
|
||||
|
||||
# Redis 连接池配置
|
||||
REDIS_POOL_MAX_ACTIVE=8
|
||||
REDIS_POOL_MAX_WAIT=-1
|
||||
REDIS_POOL_MAX_IDLE=8
|
||||
REDIS_POOL_MIN_IDLE=0
|
||||
|
||||
# ==================== Nacos 配置 ====================
|
||||
NACOS_SERVER_ADDR=localhost:8848
|
||||
NACOS_NAMESPACE=worklog-dev
|
||||
NACOS_GROUP=DEFAULT_GROUP
|
||||
NACOS_USERNAME=nacos
|
||||
NACOS_PASSWORD=nacos
|
||||
|
||||
# ==================== 文件上传配置 ====================
|
||||
FILE_UPLOAD_MAX_SIZE=50MB
|
||||
FILE_UPLOAD_MAX_REQUEST_SIZE=100MB
|
||||
FILE_STORAGE_PATH=./uploads
|
||||
|
||||
# ==================== 腾讯云 COS 配置 ====================
|
||||
COS_ENABLED=false
|
||||
COS_APP_ID=1308258046
|
||||
COS_SECRET_ID=AKIDukKfkY5LK2SbU6QTM7csugCSSDjzyiDS
|
||||
COS_SECRET_KEY=0lHXYIn20jDRP7ZlhNnyub3GEwObZHjw
|
||||
COS_BUCKET_NAME=test-1308258046
|
||||
COS_BUCKET_HOST=https://test-1308258046.cos.ap-beijing.myqcloud.com
|
||||
COS_REGION=ap-beijing
|
||||
|
||||
# ==================== 日志配置 ====================
|
||||
# 日志路径(扁平目录结构,无子目录)
|
||||
LOG_PATH=./logs
|
||||
|
||||
# 日志级别
|
||||
LOG_LEVEL_ROOT=INFO
|
||||
LOG_LEVEL_APP=DEBUG
|
||||
|
||||
# 日志文件配置
|
||||
LOG_FILE_MAX_SIZE=100MB
|
||||
LOG_FILE_MAX_HISTORY=30
|
||||
|
||||
# ==================== JVM 配置 ====================
|
||||
JVM_XMS=512m
|
||||
JVM_XMX=1024m
|
||||
JVM_METASPACE_SIZE=128m
|
||||
JVM_MAX_METASPACE_SIZE=256m
|
||||
|
||||
# GC 配置
|
||||
JVM_GC_TYPE=G1GC
|
||||
JVM_MAX_GC_PAUSE_MILLIS=200
|
||||
|
||||
# ==================== 业务配置 ====================
|
||||
# Token 配置
|
||||
TOKEN_EXPIRE_TIME=86400
|
||||
TOKEN_PREFIX=auth:token:
|
||||
|
||||
# 日志内容限制
|
||||
WORKLOG_MAX_CONTENT_LENGTH=2000
|
||||
|
||||
# 允许的上传文件扩展名
|
||||
UPLOAD_ALLOWED_EXTENSIONS=jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx
|
||||
89
worklog-api/src/main/resources/env.properties.example
Normal file
89
worklog-api/src/main/resources/env.properties.example
Normal file
@ -0,0 +1,89 @@
|
||||
# ====================================================
|
||||
# 工作日志服务平台 - 统一环境配置示例
|
||||
# 说明:
|
||||
# 1. 复制本文件为 env.properties
|
||||
# 2. 根据实际环境修改配置参数
|
||||
# 3. 敏感信息请勿提交到代码仓库
|
||||
# ====================================================
|
||||
|
||||
# ==================== 数据库配置 ====================
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_NAME=worklog
|
||||
DB_USERNAME=your_username
|
||||
DB_PASSWORD=your_password
|
||||
DB_URL=jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
|
||||
|
||||
# 连接池配置
|
||||
DB_POOL_MIN_IDLE=5
|
||||
DB_POOL_MAX_SIZE=20
|
||||
DB_POOL_CONNECTION_TIMEOUT=30000
|
||||
DB_POOL_IDLE_TIMEOUT=600000
|
||||
DB_POOL_MAX_LIFETIME=1800000
|
||||
|
||||
# ==================== Redis 配置 ====================
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=your_redis_password
|
||||
REDIS_DATABASE=0
|
||||
REDIS_TIMEOUT=5000
|
||||
|
||||
# Redis 连接池配置
|
||||
REDIS_POOL_MAX_ACTIVE=8
|
||||
REDIS_POOL_MAX_WAIT=-1
|
||||
REDIS_POOL_MAX_IDLE=8
|
||||
REDIS_POOL_MIN_IDLE=0
|
||||
|
||||
# ==================== Nacos 配置 ====================
|
||||
NACOS_SERVER_ADDR=localhost:8848
|
||||
NACOS_NAMESPACE=worklog-dev
|
||||
NACOS_GROUP=DEFAULT_GROUP
|
||||
NACOS_USERNAME=nacos
|
||||
NACOS_PASSWORD=nacos
|
||||
|
||||
# ==================== 文件上传配置 ====================
|
||||
FILE_UPLOAD_MAX_SIZE=50MB
|
||||
FILE_UPLOAD_MAX_REQUEST_SIZE=100MB
|
||||
FILE_STORAGE_PATH=./uploads
|
||||
|
||||
# ==================== 腾讯云 COS 配置 ====================
|
||||
COS_ENABLED=false
|
||||
COS_APP_ID=your_app_id
|
||||
COS_SECRET_ID=your_secret_id
|
||||
COS_SECRET_KEY=your_secret_key
|
||||
COS_BUCKET_NAME=your_bucket_name
|
||||
COS_BUCKET_HOST=https://your_bucket.cos.region.myqcloud.com
|
||||
COS_REGION=ap-beijing
|
||||
|
||||
# ==================== 日志配置 ====================
|
||||
# 日志路径(扁平目录结构,无子目录)
|
||||
LOG_PATH=./logs
|
||||
|
||||
# 日志级别
|
||||
LOG_LEVEL_ROOT=INFO
|
||||
LOG_LEVEL_APP=DEBUG
|
||||
|
||||
# 日志文件配置
|
||||
LOG_FILE_MAX_SIZE=100MB
|
||||
LOG_FILE_MAX_HISTORY=30
|
||||
|
||||
# ==================== JVM 配置 ====================
|
||||
JVM_XMS=512m
|
||||
JVM_XMX=1024m
|
||||
JVM_METASPACE_SIZE=128m
|
||||
JVM_MAX_METASPACE_SIZE=256m
|
||||
|
||||
# GC 配置
|
||||
JVM_GC_TYPE=G1GC
|
||||
JVM_MAX_GC_PAUSE_MILLIS=200
|
||||
|
||||
# ==================== 业务配置 ====================
|
||||
# Token 配置
|
||||
TOKEN_EXPIRE_TIME=86400
|
||||
TOKEN_PREFIX=auth:token:
|
||||
|
||||
# 日志内容限制
|
||||
WORKLOG_MAX_CONTENT_LENGTH=2000
|
||||
|
||||
# 允许的上传文件扩展名
|
||||
UPLOAD_ALLOWED_EXTENSIONS=jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx
|
||||
@ -1,21 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<!-- 日志文件存储路径 -->
|
||||
<property name="LOG_PATH" value="logs"/>
|
||||
<!-- 从环境变量读取配置 -->
|
||||
<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="worklog-api"/>
|
||||
<springProperty scope="context" name="LOG_PATH" source="logging.file.path" defaultValue="./logs"/>
|
||||
<springProperty scope="context" name="LOG_LEVEL_ROOT" source="logging.level.root" defaultValue="INFO"/>
|
||||
<springProperty scope="context" name="LOG_LEVEL_APP" source="logging.level.app" defaultValue="DEBUG"/>
|
||||
|
||||
<!-- 日志格式:强制包含 traceId 和 spanId -->
|
||||
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId:-}][%X{spanId:-}] %-5level %logger{50} - %msg%n"/>
|
||||
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%X{spanId}] %-5level %logger{50} - %msg%n</pattern>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 应用日志文件输出 -->
|
||||
<!-- 应用日志文件输出 - 直接输出到 logs/ 目录 -->
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_PATH}/app.log</file>
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%X{spanId}] %-5level %logger{50} - %msg%n</pattern>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
@ -27,11 +33,11 @@
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
|
||||
<!-- SQL日志文件输出 -->
|
||||
<!-- SQL日志文件输出 - MyBatis-Plus SQL 日志独立输出 -->
|
||||
<appender name="SQL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_PATH}/sql.log</file>
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%X{spanId}] %-5level %logger{50} - %msg%n</pattern>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
@ -48,9 +54,15 @@
|
||||
<appender-ref ref="SQL_FILE"/>
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</logger>
|
||||
|
||||
<!-- MyBatis-Plus 框架日志 -->
|
||||
<logger name="com.baomidou.mybatisplus" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="SQL_FILE"/>
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</logger>
|
||||
|
||||
<!-- 根日志级别 -->
|
||||
<root level="INFO">
|
||||
<root level="${LOG_LEVEL_ROOT}">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="FILE"/>
|
||||
</root>
|
||||
|
||||
29
worklog-api/src/main/resources/service.properties
Normal file
29
worklog-api/src/main/resources/service.properties
Normal file
@ -0,0 +1,29 @@
|
||||
# ====================================================
|
||||
# 工作日志服务平台 - 服务个性化配置
|
||||
# 说明:当前服务独立的配置,可覆盖 env.properties 中的同名参数
|
||||
# ====================================================
|
||||
|
||||
# ==================== 服务基本信息 ====================
|
||||
# 服务名称
|
||||
APP_NAME=worklog-api
|
||||
|
||||
# 实例名称(多租户场景使用,默认与 APP_NAME 相同)
|
||||
INSTANCE_NAME=${APP_NAME}
|
||||
|
||||
# 租户标识(多租户场景使用,用于路由,单租户留空)
|
||||
TENANT_ID=
|
||||
|
||||
# ==================== 服务端口配置 ====================
|
||||
# 服务端口(可覆盖 application.yml 中配置)
|
||||
SERVER_PORT=8080
|
||||
|
||||
# ==================== 环境标识 ====================
|
||||
# 运行环境:dev-开发, test-测试, prod-生产
|
||||
SPRING_PROFILES_ACTIVE=prod
|
||||
|
||||
# ==================== 个性化覆盖配置(可选) ====================
|
||||
# 如果当前服务需要使用不同的日志路径,可在此覆盖
|
||||
# LOG_PATH=/var/logs/worklog-api
|
||||
|
||||
# 如果当前服务需要使用不同的日志级别,可在此覆盖
|
||||
# LOG_LEVEL_APP=INFO
|
||||
32
worklog-api/src/main/resources/service.properties.example
Normal file
32
worklog-api/src/main/resources/service.properties.example
Normal file
@ -0,0 +1,32 @@
|
||||
# ====================================================
|
||||
# 工作日志服务平台 - 服务个性化配置示例
|
||||
# 说明:
|
||||
# 1. 复制本文件为 service.properties
|
||||
# 2. 根据实际环境修改配置参数
|
||||
# 3. 可覆盖 env.properties 中的同名参数
|
||||
# ====================================================
|
||||
|
||||
# ==================== 服务基本信息 ====================
|
||||
# 服务名称
|
||||
APP_NAME=worklog-api
|
||||
|
||||
# 实例名称(多租户场景使用,默认与 APP_NAME 相同)
|
||||
INSTANCE_NAME=${APP_NAME}
|
||||
|
||||
# 租户标识(多租户场景使用,用于路由,单租户留空)
|
||||
TENANT_ID=
|
||||
|
||||
# ==================== 服务端口配置 ====================
|
||||
# 服务端口(可覆盖 application.yml 中配置)
|
||||
SERVER_PORT=8080
|
||||
|
||||
# ==================== 环境标识 ====================
|
||||
# 运行环境:dev-开发, test-测试, prod-生产
|
||||
SPRING_PROFILES_ACTIVE=prod
|
||||
|
||||
# ==================== 个性化覆盖配置(可选) ====================
|
||||
# 如果当前服务需要使用不同的日志路径,可在此覆盖
|
||||
# LOG_PATH=/var/logs/worklog-api
|
||||
|
||||
# 如果当前服务需要使用不同的日志级别,可在此覆盖
|
||||
# LOG_LEVEL_APP=INFO
|
||||
@ -0,0 +1,208 @@
|
||||
package com.wjbl.worklog.service;
|
||||
|
||||
import com.wjbl.worklog.common.context.UserContext;
|
||||
import com.wjbl.worklog.common.context.UserInfo;
|
||||
import com.wjbl.worklog.common.exception.BusinessException;
|
||||
import com.wjbl.worklog.data.entity.WorkLog;
|
||||
import com.wjbl.worklog.data.service.WorkLogDataService;
|
||||
import com.wjbl.worklog.dto.LogCreateDTO;
|
||||
import com.wjbl.worklog.dto.LogUpdateDTO;
|
||||
import com.wjbl.worklog.service.impl.LogServiceImpl;
|
||||
import com.wjbl.worklog.vo.LogVO;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* LogService 单元测试
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class LogServiceTest {
|
||||
|
||||
@Mock
|
||||
private WorkLogDataService workLogDataService;
|
||||
|
||||
@InjectMocks
|
||||
private LogServiceImpl logService;
|
||||
|
||||
private WorkLog testLog;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
testLog = new WorkLog();
|
||||
testLog.setId("log-id-123");
|
||||
testLog.setUserId("user-id-123");
|
||||
testLog.setLogDate(LocalDate.now());
|
||||
testLog.setTitle("今日工作日志");
|
||||
testLog.setContent("今天完成了xxx任务");
|
||||
testLog.setDeleted(0);
|
||||
|
||||
// 设置普通用户上下文
|
||||
UserInfo userInfo = new UserInfo("user-id-123", "testuser", "测试用户", "USER");
|
||||
UserContext.setUserInfo(userInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("创建日志 - 成功")
|
||||
void createLog_success() {
|
||||
// Given
|
||||
LogCreateDTO dto = new LogCreateDTO();
|
||||
dto.setTitle("新日志");
|
||||
dto.setContent("日志内容");
|
||||
dto.setLogDate(LocalDate.now());
|
||||
|
||||
when(workLogDataService.getByUserIdAndLogDate("user-id-123", LocalDate.now())).thenReturn(null);
|
||||
when(workLogDataService.save(any(WorkLog.class))).thenReturn(true);
|
||||
|
||||
// When
|
||||
LogVO result = logService.createLog(dto);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
assertEquals("新日志", result.getTitle());
|
||||
verify(workLogDataService).save(any(WorkLog.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("创建日志 - 当天已有日志")
|
||||
void createLog_alreadyExists() {
|
||||
// Given
|
||||
LogCreateDTO dto = new LogCreateDTO();
|
||||
dto.setTitle("新日志");
|
||||
dto.setContent("日志内容");
|
||||
dto.setLogDate(LocalDate.now());
|
||||
|
||||
when(workLogDataService.getByUserIdAndLogDate("user-id-123", LocalDate.now())).thenReturn(testLog);
|
||||
|
||||
// When & Then
|
||||
assertThrows(BusinessException.class, () -> logService.createLog(dto));
|
||||
verify(workLogDataService, never()).save(any(WorkLog.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("更新日志 - 成功(自己的日志)")
|
||||
void updateLog_success_ownLog() {
|
||||
// Given
|
||||
LogUpdateDTO dto = new LogUpdateDTO();
|
||||
dto.setTitle("更新后的标题");
|
||||
dto.setContent("更新后的内容");
|
||||
|
||||
when(workLogDataService.getById("log-id-123")).thenReturn(testLog);
|
||||
when(workLogDataService.updateById(any(WorkLog.class))).thenReturn(true);
|
||||
|
||||
// When
|
||||
LogVO result = logService.updateLog("log-id-123", dto);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
assertEquals("更新后的标题", result.getTitle());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("更新日志 - 无权编辑他人日志")
|
||||
void updateLog_forbidden_otherUserLog() {
|
||||
// Given
|
||||
WorkLog otherUserLog = new WorkLog();
|
||||
otherUserLog.setId("other-log-id");
|
||||
otherUserLog.setUserId("other-user-id"); // 其他用户的日志
|
||||
otherUserLog.setLogDate(LocalDate.now());
|
||||
|
||||
LogUpdateDTO dto = new LogUpdateDTO();
|
||||
dto.setTitle("更新后的标题");
|
||||
|
||||
when(workLogDataService.getById("other-log-id")).thenReturn(otherUserLog);
|
||||
|
||||
// When & Then
|
||||
assertThrows(BusinessException.class, () -> logService.updateLog("other-log-id", dto));
|
||||
verify(workLogDataService, never()).updateById(any(WorkLog.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("更新日志 - 管理员可编辑他人日志")
|
||||
void updateLog_success_adminCanEditOtherLog() {
|
||||
// Given - 设置为管理员
|
||||
UserInfo adminInfo = new UserInfo("admin-id", "admin", "管理员", "ADMIN");
|
||||
UserContext.setUserInfo(adminInfo);
|
||||
|
||||
WorkLog otherUserLog = new WorkLog();
|
||||
otherUserLog.setId("other-log-id");
|
||||
otherUserLog.setUserId("other-user-id");
|
||||
otherUserLog.setLogDate(LocalDate.now());
|
||||
|
||||
LogUpdateDTO dto = new LogUpdateDTO();
|
||||
dto.setTitle("管理员更新");
|
||||
|
||||
when(workLogDataService.getById("other-log-id")).thenReturn(otherUserLog);
|
||||
when(workLogDataService.updateById(any(WorkLog.class))).thenReturn(true);
|
||||
|
||||
// When
|
||||
LogVO result = logService.updateLog("other-log-id", dto);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
assertEquals("管理员更新", result.getTitle());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("删除日志 - 成功")
|
||||
void deleteLog_success() {
|
||||
// Given
|
||||
when(workLogDataService.getById("log-id-123")).thenReturn(testLog);
|
||||
when(workLogDataService.removeById("log-id-123")).thenReturn(true);
|
||||
|
||||
// When
|
||||
logService.deleteLog("log-id-123");
|
||||
|
||||
// Then
|
||||
verify(workLogDataService).removeById("log-id-123");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("删除日志 - 无权删除他人日志")
|
||||
void deleteLog_forbidden_otherUserLog() {
|
||||
// Given
|
||||
WorkLog otherUserLog = new WorkLog();
|
||||
otherUserLog.setId("other-log-id");
|
||||
otherUserLog.setUserId("other-user-id");
|
||||
|
||||
when(workLogDataService.getById("other-log-id")).thenReturn(otherUserLog);
|
||||
|
||||
// When & Then
|
||||
assertThrows(BusinessException.class, () -> logService.deleteLog("other-log-id"));
|
||||
verify(workLogDataService, never()).removeById(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("获取日志详情 - 成功")
|
||||
void getLogById_success() {
|
||||
// Given
|
||||
when(workLogDataService.getById("log-id-123")).thenReturn(testLog);
|
||||
|
||||
// When
|
||||
LogVO result = logService.getLogById("log-id-123");
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
assertEquals("今日工作日志", result.getTitle());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("获取日志详情 - 日志不存在")
|
||||
void getLogById_notFound() {
|
||||
// Given
|
||||
when(workLogDataService.getById("nonexistent")).thenReturn(null);
|
||||
|
||||
// When & Then
|
||||
assertThrows(BusinessException.class, () -> logService.getLogById("nonexistent"));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,210 @@
|
||||
package com.wjbl.worklog.service;
|
||||
|
||||
import com.wjbl.worklog.common.context.UserContext;
|
||||
import com.wjbl.worklog.common.context.UserInfo;
|
||||
import com.wjbl.worklog.common.exception.BusinessException;
|
||||
import com.wjbl.worklog.data.entity.LogTemplate;
|
||||
import com.wjbl.worklog.data.service.LogTemplateDataService;
|
||||
import com.wjbl.worklog.dto.TemplateCreateDTO;
|
||||
import com.wjbl.worklog.dto.TemplateUpdateDTO;
|
||||
import com.wjbl.worklog.service.impl.TemplateServiceImpl;
|
||||
import com.wjbl.worklog.vo.TemplateVO;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* TemplateService 单元测试
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TemplateServiceTest {
|
||||
|
||||
@Mock
|
||||
private LogTemplateDataService logTemplateDataService;
|
||||
|
||||
@InjectMocks
|
||||
private TemplateServiceImpl templateService;
|
||||
|
||||
private LogTemplate testTemplate;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
testTemplate = new LogTemplate();
|
||||
testTemplate.setId("template-id-123");
|
||||
testTemplate.setTemplateName("日报模板");
|
||||
testTemplate.setContent("# 今日工作\n\n## 完成事项\n\n## 明日计划");
|
||||
testTemplate.setStatus(1);
|
||||
testTemplate.setDeleted(0);
|
||||
|
||||
// 设置用户上下文
|
||||
UserInfo userInfo = new UserInfo("admin-id", "admin", "管理员", "ADMIN");
|
||||
UserContext.setUserInfo(userInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("创建模板 - 成功")
|
||||
void createTemplate_success() {
|
||||
// Given
|
||||
TemplateCreateDTO dto = new TemplateCreateDTO();
|
||||
dto.setTemplateName("周报模板");
|
||||
dto.setContent("# 本周工作\n\n## 完成事项\n\n## 下周计划");
|
||||
|
||||
when(logTemplateDataService.getByTemplateName("周报模板")).thenReturn(null);
|
||||
when(logTemplateDataService.save(any(LogTemplate.class))).thenReturn(true);
|
||||
|
||||
// When
|
||||
TemplateVO result = templateService.createTemplate(dto);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
assertEquals("周报模板", result.getTemplateName());
|
||||
verify(logTemplateDataService).save(any(LogTemplate.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("创建模板 - 模板名称已存在")
|
||||
void createTemplate_nameExists() {
|
||||
// Given
|
||||
TemplateCreateDTO dto = new TemplateCreateDTO();
|
||||
dto.setTemplateName("日报模板");
|
||||
dto.setContent("内容");
|
||||
|
||||
when(logTemplateDataService.getByTemplateName("日报模板")).thenReturn(testTemplate);
|
||||
|
||||
// When & Then
|
||||
assertThrows(BusinessException.class, () -> templateService.createTemplate(dto));
|
||||
verify(logTemplateDataService, never()).save(any(LogTemplate.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("更新模板 - 成功")
|
||||
void updateTemplate_success() {
|
||||
// Given
|
||||
TemplateUpdateDTO dto = new TemplateUpdateDTO();
|
||||
dto.setContent("更新后的内容");
|
||||
|
||||
when(logTemplateDataService.getById("template-id-123")).thenReturn(testTemplate);
|
||||
when(logTemplateDataService.updateById(any(LogTemplate.class))).thenReturn(true);
|
||||
|
||||
// When
|
||||
TemplateVO result = templateService.updateTemplate("template-id-123", dto);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
assertEquals("更新后的内容", result.getContent());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("更新模板 - 模板不存在")
|
||||
void updateTemplate_notFound() {
|
||||
// Given
|
||||
TemplateUpdateDTO dto = new TemplateUpdateDTO();
|
||||
dto.setContent("内容");
|
||||
|
||||
when(logTemplateDataService.getById("nonexistent")).thenReturn(null);
|
||||
|
||||
// When & Then
|
||||
assertThrows(BusinessException.class, () -> templateService.updateTemplate("nonexistent", dto));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("更新模板 - 新名称已被占用")
|
||||
void updateTemplate_nameConflict() {
|
||||
// Given
|
||||
TemplateUpdateDTO dto = new TemplateUpdateDTO();
|
||||
dto.setTemplateName("已存在的模板名");
|
||||
|
||||
LogTemplate existingTemplate = new LogTemplate();
|
||||
existingTemplate.setId("other-template-id");
|
||||
existingTemplate.setTemplateName("已存在的模板名");
|
||||
|
||||
when(logTemplateDataService.getById("template-id-123")).thenReturn(testTemplate);
|
||||
when(logTemplateDataService.getByTemplateName("已存在的模板名")).thenReturn(existingTemplate);
|
||||
|
||||
// When & Then
|
||||
assertThrows(BusinessException.class, () -> templateService.updateTemplate("template-id-123", dto));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("删除模板 - 成功")
|
||||
void deleteTemplate_success() {
|
||||
// Given
|
||||
when(logTemplateDataService.getById("template-id-123")).thenReturn(testTemplate);
|
||||
when(logTemplateDataService.removeById("template-id-123")).thenReturn(true);
|
||||
|
||||
// When
|
||||
templateService.deleteTemplate("template-id-123");
|
||||
|
||||
// Then
|
||||
verify(logTemplateDataService).removeById("template-id-123");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("删除模板 - 模板不存在")
|
||||
void deleteTemplate_notFound() {
|
||||
// Given
|
||||
when(logTemplateDataService.getById("nonexistent")).thenReturn(null);
|
||||
|
||||
// When & Then
|
||||
assertThrows(BusinessException.class, () -> templateService.deleteTemplate("nonexistent"));
|
||||
verify(logTemplateDataService, never()).removeById(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("更新模板状态 - 成功")
|
||||
void updateStatus_success() {
|
||||
// Given
|
||||
when(logTemplateDataService.getById("template-id-123")).thenReturn(testTemplate);
|
||||
when(logTemplateDataService.updateById(any(LogTemplate.class))).thenReturn(true);
|
||||
|
||||
// When
|
||||
templateService.updateStatus("template-id-123", 0);
|
||||
|
||||
// Then
|
||||
verify(logTemplateDataService).updateById(any(LogTemplate.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("获取启用的模板列表 - 成功")
|
||||
void listEnabledTemplates_success() {
|
||||
// Given
|
||||
when(logTemplateDataService.listEnabledTemplates()).thenReturn(List.of(testTemplate));
|
||||
|
||||
// When
|
||||
List<TemplateVO> result = templateService.listEnabledTemplates();
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
assertEquals(1, result.size());
|
||||
assertEquals("日报模板", result.get(0).getTemplateName());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("获取所有模板列表 - 成功")
|
||||
void listAllTemplates_success() {
|
||||
// Given
|
||||
LogTemplate disabledTemplate = new LogTemplate();
|
||||
disabledTemplate.setId("disabled-id");
|
||||
disabledTemplate.setTemplateName("禁用模板");
|
||||
disabledTemplate.setStatus(0);
|
||||
|
||||
when(logTemplateDataService.list()).thenReturn(List.of(testTemplate, disabledTemplate));
|
||||
|
||||
// When
|
||||
List<TemplateVO> result = templateService.listAllTemplates();
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
assertEquals(2, result.size());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,217 @@
|
||||
package com.wjbl.worklog.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.wjbl.worklog.common.context.UserContext;
|
||||
import com.wjbl.worklog.common.context.UserInfo;
|
||||
import com.wjbl.worklog.common.exception.BusinessException;
|
||||
import com.wjbl.worklog.data.entity.User;
|
||||
import com.wjbl.worklog.data.service.UserDataService;
|
||||
import com.wjbl.worklog.dto.UserCreateDTO;
|
||||
import com.wjbl.worklog.dto.UserUpdateDTO;
|
||||
import com.wjbl.worklog.service.impl.UserServiceImpl;
|
||||
import com.wjbl.worklog.vo.UserVO;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* UserService 单元测试
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class UserServiceTest {
|
||||
|
||||
@Mock
|
||||
private UserDataService userDataService;
|
||||
|
||||
@InjectMocks
|
||||
private UserServiceImpl userService;
|
||||
|
||||
private User testUser;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
testUser = new User();
|
||||
testUser.setId("1234567890");
|
||||
testUser.setUsername("testuser");
|
||||
testUser.setPassword("$2a$10$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
|
||||
testUser.setName("测试用户");
|
||||
testUser.setRole("USER");
|
||||
testUser.setStatus(1);
|
||||
testUser.setDeleted(0);
|
||||
|
||||
// 设置当前用户上下文
|
||||
UserInfo userInfo = new UserInfo("admin-id", "admin", "管理员", "ADMIN");
|
||||
UserContext.setUserInfo(userInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("创建用户 - 成功")
|
||||
void createUser_success() {
|
||||
// Given
|
||||
UserCreateDTO dto = new UserCreateDTO();
|
||||
dto.setUsername("newuser");
|
||||
dto.setPassword("password123");
|
||||
dto.setName("新用户");
|
||||
|
||||
when(userDataService.getByUsername("newuser")).thenReturn(null);
|
||||
when(userDataService.save(any(User.class))).thenReturn(true);
|
||||
|
||||
// When
|
||||
UserVO result = userService.createUser(dto);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
assertEquals("newuser", result.getUsername());
|
||||
assertEquals("新用户", result.getName());
|
||||
verify(userDataService).save(any(User.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("创建用户 - 用户名已存在")
|
||||
void createUser_usernameExists() {
|
||||
// Given
|
||||
UserCreateDTO dto = new UserCreateDTO();
|
||||
dto.setUsername("testuser");
|
||||
dto.setPassword("password123");
|
||||
dto.setName("新用户");
|
||||
|
||||
when(userDataService.getByUsername("testuser")).thenReturn(testUser);
|
||||
|
||||
// When & Then
|
||||
assertThrows(BusinessException.class, () -> userService.createUser(dto));
|
||||
verify(userDataService, never()).save(any(User.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("更新用户 - 成功")
|
||||
void updateUser_success() {
|
||||
// Given
|
||||
UserUpdateDTO dto = new UserUpdateDTO();
|
||||
dto.setName("更新后的名字");
|
||||
|
||||
when(userDataService.getById("1234567890")).thenReturn(testUser);
|
||||
when(userDataService.updateById(any(User.class))).thenReturn(true);
|
||||
|
||||
// When
|
||||
UserVO result = userService.updateUser("1234567890", dto);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
assertEquals("更新后的名字", result.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("更新用户 - 用户不存在")
|
||||
void updateUser_userNotFound() {
|
||||
// Given
|
||||
UserUpdateDTO dto = new UserUpdateDTO();
|
||||
dto.setName("更新后的名字");
|
||||
|
||||
when(userDataService.getById("nonexistent")).thenReturn(null);
|
||||
|
||||
// When & Then
|
||||
assertThrows(BusinessException.class, () -> userService.updateUser("nonexistent", dto));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("删除用户 - 成功")
|
||||
void deleteUser_success() {
|
||||
// Given
|
||||
User userToDelete = new User();
|
||||
userToDelete.setId("user-to-delete");
|
||||
userToDelete.setUsername("deleteuser");
|
||||
userToDelete.setRole("USER");
|
||||
|
||||
when(userDataService.getById("user-to-delete")).thenReturn(userToDelete);
|
||||
when(userDataService.removeById("user-to-delete")).thenReturn(true);
|
||||
|
||||
// When
|
||||
userService.deleteUser("user-to-delete");
|
||||
|
||||
// Then
|
||||
verify(userDataService).removeById("user-to-delete");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("删除用户 - 不能删除自己")
|
||||
void deleteUser_cannotDeleteSelf() {
|
||||
// Given
|
||||
User currentUser = new User();
|
||||
currentUser.setId("admin-id");
|
||||
currentUser.setUsername("admin");
|
||||
currentUser.setRole("ADMIN");
|
||||
|
||||
when(userDataService.getById("admin-id")).thenReturn(currentUser);
|
||||
|
||||
// When & Then
|
||||
assertThrows(BusinessException.class, () -> userService.deleteUser("admin-id"));
|
||||
verify(userDataService, never()).removeById(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("删除用户 - 不能删除管理员")
|
||||
void deleteUser_cannotDeleteAdmin() {
|
||||
// Given
|
||||
User adminUser = new User();
|
||||
adminUser.setId("other-admin");
|
||||
adminUser.setUsername("otheradmin");
|
||||
adminUser.setRole("ADMIN");
|
||||
|
||||
when(userDataService.getById("other-admin")).thenReturn(adminUser);
|
||||
|
||||
// When & Then
|
||||
assertThrows(BusinessException.class, () -> userService.deleteUser("other-admin"));
|
||||
verify(userDataService, never()).removeById(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("分页查询用户 - 成功")
|
||||
void pageUsers_success() {
|
||||
// Given
|
||||
Page<User> userPage = new Page<>(1, 10);
|
||||
userPage.setRecords(java.util.List.of(testUser));
|
||||
userPage.setTotal(1);
|
||||
|
||||
when(userDataService.page(any(Page.class), any())).thenReturn(userPage);
|
||||
|
||||
// When
|
||||
Page<UserVO> result = userService.pageUsers(1, 10, null, null, null);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
assertEquals(1, result.getTotal());
|
||||
assertEquals(1, result.getRecords().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("根据ID获取用户 - 成功")
|
||||
void getUserById_success() {
|
||||
// Given
|
||||
when(userDataService.getById("1234567890")).thenReturn(testUser);
|
||||
|
||||
// When
|
||||
UserVO result = userService.getUserById("1234567890");
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
assertEquals("testuser", result.getUsername());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("根据ID获取用户 - 用户不存在")
|
||||
void getUserById_userNotFound() {
|
||||
// Given
|
||||
when(userDataService.getById("nonexistent")).thenReturn(null);
|
||||
|
||||
// When & Then
|
||||
assertThrows(BusinessException.class, () -> userService.getUserById("nonexistent"));
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user