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
|
application-prod.yml
|
||||||
bootstrap.yml
|
bootstrap.yml
|
||||||
|
|
||||||
|
# conf 目录下的实际配置文件(敏感信息)
|
||||||
|
# 开发阶段:src/main/resources/conf/
|
||||||
|
# 部署阶段:conf/(打包后)
|
||||||
|
src/main/resources/conf/env.properties
|
||||||
|
src/main/resources/conf/service.properties
|
||||||
|
|
||||||
# 数据库备份
|
# 数据库备份
|
||||||
*.sql.backup
|
*.sql.backup
|
||||||
db_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
|
```text
|
||||||
worklog-api/
|
worklog-api/
|
||||||
├── src/main/java/com/company/worklog/
|
├── src/main/java/com/wjbl/worklog/
|
||||||
│ ├── WorklogApplication.java # 启动类
|
│ ├── WorklogApplication.java # 启动类
|
||||||
│ ├── config/ # 配置类
|
│ ├── config/ # 配置类
|
||||||
│ │ ├── SecurityConfig.java
|
│ │ ├── 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
|
```text
|
||||||
src/main/resources/
|
src/main/resources/
|
||||||
├── application.yml # 主配置文件
|
├── application.yml # 主配置文件
|
||||||
├── application-dev.yml # 开发环境配置
|
|
||||||
├── application-test.yml # 测试环境配置
|
|
||||||
├── application-prod.yml # 生产环境配置
|
|
||||||
├── bootstrap.yml # 启动配置(Nacos)
|
├── bootstrap.yml # 启动配置(Nacos)
|
||||||
└── logback-spring.xml # 日志配置
|
└── logback-spring.xml # 日志配置
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**注意**:开发环境不使用多环境配置分离(application-dev.yml、application-test.yml 等),统一使用 application.yml。
|
||||||
|
|
||||||
**配置优先级**:
|
**配置优先级**:
|
||||||
1. bootstrap.yml(最先加载,用于 Nacos 配置中心)
|
1. bootstrap.yml(最先加载,用于 Nacos 配置中心,默认禁用)
|
||||||
2. application.yml(通用配置)
|
2. application.yml(通用配置,开发环境使用)
|
||||||
3. application-{profile}.yml(环境特定配置,覆盖通用配置)
|
3. 生产环境通过 conf/env.properties 和 conf/service.properties 配置
|
||||||
|
|
||||||
**激活开发环境**:
|
**激活开发环境**:
|
||||||
|
|
||||||
```yaml
|
开发环境直接使用 application.yml,无需配置环境标识。
|
||||||
# application.yml
|
|
||||||
spring:
|
|
||||||
profiles:
|
|
||||||
active: dev
|
|
||||||
```
|
|
||||||
|
|
||||||
或通过启动参数:
|
生产环境通过启动参数激活:
|
||||||
|
|
||||||
```bash
|
```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. **密码安全**:
|
1. **密码安全**:
|
||||||
- 开发环境配置文件不应提交到代码仓库
|
- 实际配置文件(包含敏感信息)不应提交到代码仓库
|
||||||
- 使用 `.gitignore` 忽略 `application-*.yml` 文件
|
- 使用 [`.gitignore`](file:///home/along/MyCode/wanjiabuluo/worklog/.gitignore) 忽略实际配置文件
|
||||||
- 敏感信息建议使用环境变量或配置中心管理
|
- 敏感信息使用环境变量或配置中心管理
|
||||||
|
|
||||||
2. **配置示例**:
|
2. **配置示例**:
|
||||||
```gitignore
|
```gitignore
|
||||||
# .gitignore
|
# .gitignore 配置
|
||||||
application-dev.yml
|
application.yml
|
||||||
application-test.yml
|
bootstrap.yml
|
||||||
application-prod.yml
|
src/main/resources/conf/env.properties
|
||||||
|
src/main/resources/conf/service.properties
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **配置模板**:
|
3. **配置模板**:
|
||||||
- 提供 `application-dev.yml.example` 作为模板
|
- 提供 `env.properties.example` 和 `service.properties.example` 作为模板
|
||||||
- 开发人员复制并修改为 `application-dev.yml`
|
- 开发人员/运维人员复制模板并修改为实际配置文件
|
||||||
- 填入本地实际配置信息
|
- 填入实际配置信息(数据库密码、Redis 密码等)
|
||||||
|
|
||||||
|
4. **配置文件权限**(生产环境):
|
||||||
|
```bash
|
||||||
|
# 限制配置文件访问权限
|
||||||
|
chmod 600 conf/env.properties
|
||||||
|
chmod 600 conf/service.properties
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -580,32 +580,81 @@ project-root/
|
|||||||
- `env.properties`:环境级通用配置(注册中心、缓存、限流、文件上传、日志等)。
|
- `env.properties`:环境级通用配置(注册中心、缓存、限流、文件上传、日志等)。
|
||||||
- 各服务 `service.properties`:服务名、实例名、多租户路由、端口等个性化配置。
|
- 各服务 `service.properties`:服务名、实例名、多租户路由、端口等个性化配置。
|
||||||
|
|
||||||
统一配置示例(节选):
|
统一配置示例:
|
||||||
|
|
||||||
```properties
|
```properties
|
||||||
# Nacos / 注册中心
|
# ==================== 数据库配置 ====================
|
||||||
NACOS_SERVER_ADDR=localhost:8848
|
DB_HOST=localhost
|
||||||
NACOS_NAMESPACE=project-namespace
|
DB_PORT=3306
|
||||||
NACOS_GROUP=DEFAULT_GROUP
|
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_HOST=localhost
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
REDIS_PASSWORD=your-password
|
REDIS_PASSWORD=zjf@123456
|
||||||
|
REDIS_DATABASE=0
|
||||||
|
REDIS_TIMEOUT=5000
|
||||||
|
|
||||||
# 网关限流
|
# Redis 连接池配置
|
||||||
GATEWAY_RATE_LIMIT_REPLENISH_RATE=100
|
REDIS_POOL_MAX_ACTIVE=8
|
||||||
GATEWAY_RATE_LIMIT_BURST_CAPACITY=200
|
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_SIZE=50MB
|
||||||
FILE_UPLOAD_MAX_REQUEST_SIZE=100MB
|
FILE_UPLOAD_MAX_REQUEST_SIZE=100MB
|
||||||
FILE_STORAGE_PATH=./uploads
|
FILE_STORAGE_PATH=./uploads
|
||||||
|
|
||||||
# 日志
|
# ==================== 日志配置 ====================
|
||||||
LOG_PATH=/var/logs/apps
|
# 日志路径(扁平目录结构,无子目录)
|
||||||
|
LOG_PATH=./logs
|
||||||
|
|
||||||
|
# 日志级别
|
||||||
LOG_LEVEL_ROOT=INFO
|
LOG_LEVEL_ROOT=INFO
|
||||||
LOG_LEVEL_APP=DEBUG
|
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=
|
TENANT_ID=
|
||||||
|
|
||||||
|
# ==================== 服务端口配置 ====================
|
||||||
# 服务端口(可覆盖 application.yml 中配置)
|
# 服务端口(可覆盖 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 配置加载顺序
|
#### 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>
|
<artifactId>spring-boot-starter-aop</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Security Crypto (用于密码加密) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-crypto</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- MyBatis-Plus -->
|
<!-- MyBatis-Plus -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.baomidou</groupId>
|
<groupId>com.baomidou</groupId>
|
||||||
@ -96,12 +102,6 @@
|
|||||||
<version>${commonmark.version}</version>
|
<version>${commonmark.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- BCrypt 密码加密 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.security</groupId>
|
|
||||||
<artifactId>spring-security-crypto</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Spring Boot Test -->
|
<!-- Spring Boot Test -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<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 {
|
public class WebMvcConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
private final TraceInterceptor traceInterceptor;
|
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
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
// 注册链路追踪拦截器
|
// 注册链路追踪拦截器(所有请求)
|
||||||
registry.addInterceptor(traceInterceptor)
|
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"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<configuration>
|
<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">
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
<encoder>
|
<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>
|
<charset>UTF-8</charset>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<!-- 应用日志文件输出 -->
|
<!-- 应用日志文件输出 - 直接输出到 logs/ 目录 -->
|
||||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
<file>${LOG_PATH}/app.log</file>
|
<file>${LOG_PATH}/app.log</file>
|
||||||
<encoder>
|
<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>
|
<charset>UTF-8</charset>
|
||||||
</encoder>
|
</encoder>
|
||||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
@ -27,11 +33,11 @@
|
|||||||
</rollingPolicy>
|
</rollingPolicy>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<!-- SQL日志文件输出 -->
|
<!-- SQL日志文件输出 - MyBatis-Plus SQL 日志独立输出 -->
|
||||||
<appender name="SQL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
<appender name="SQL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
<file>${LOG_PATH}/sql.log</file>
|
<file>${LOG_PATH}/sql.log</file>
|
||||||
<encoder>
|
<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>
|
<charset>UTF-8</charset>
|
||||||
</encoder>
|
</encoder>
|
||||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
@ -49,8 +55,14 @@
|
|||||||
<appender-ref ref="CONSOLE"/>
|
<appender-ref ref="CONSOLE"/>
|
||||||
</logger>
|
</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="CONSOLE"/>
|
||||||
<appender-ref ref="FILE"/>
|
<appender-ref ref="FILE"/>
|
||||||
</root>
|
</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