初始提交:DesignHub设计师任务管理系统

This commit is contained in:
Your Name 2026-01-02 12:06:09 +08:00
commit 35e4f8f677
279 changed files with 77455 additions and 0 deletions

68
.gitignore vendored Normal file
View File

@ -0,0 +1,68 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Build outputs
dist/
dist-ssr/
build/
target/
*.jar
*.war
*.ear
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
.DS_Store
# Environment files
.env
.env.local
.env.*.local
*.log
# Database
*.sql~
*.db
# OS
Thumbs.db
ehthumbs.db
# Java
*.class
*.log
*.ctxt
.mtj.tmp/
hs_err_pid*
# Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
# Spring Boot
.springBeans
.sts4-cache/
# Redis
dump.rdb
# Logs
logs/
*.log

850
DEPLOYMENT_GUIDE.md Normal file
View File

@ -0,0 +1,850 @@
# 🚀 DesignHub 部署指南
本文档详细说明如何在本地开发环境和生产环境中部署 DesignHub 系统。
---
## 📋 目录
1. [环境要求](#环境要求)
2. [本地开发环境部署](#本地开发环境部署)
3. [生产环境部署](#生产环境部署)
4. [Docker部署](#docker部署)
5. [常见问题](#常见问题)
6. [维护与监控](#维护与监控)
---
## 🔧 环境要求
### 开发环境
| 组件 | 版本要求 | 说明 |
|------|---------|------|
| JDK | 17+ | 建议使用 OpenJDK 17 |
| Node.js | 18+ | 建议使用 LTS 版本 |
| MySQL | 8.0+ | 必须 |
| Redis | 6.0+ | 建议 7.0+ |
| Maven | 3.6+ | 后端构建工具 |
| pnpm | 8.0+ | 前端包管理器(可选npm/yarn) |
### 生产环境
| 组件 | 版本要求 | 配置建议 |
|------|---------|---------|
| 服务器 | Linux | CentOS 7+, Ubuntu 20.04+ |
| CPU | 4核+ | 推荐 8核 |
| 内存 | 8GB+ | 推荐 16GB |
| 磁盘 | 50GB+ | SSD推荐 |
| 带宽 | 10Mbps+ | 推荐 100Mbps |
---
## 💻 本地开发环境部署
### 1. 克隆项目
```bash
# 克隆代码仓库
cd /Users/lanmu/Desktop/qiangdanxitong
# 或使用 Git
# git clone <repository_url>
```
### 2. 配置数据库
#### 2.1 安装 MySQL 8.0
**macOS**:
```bash
brew install mysql@8.0
brew services start mysql@8.0
```
**Linux**:
```bash
sudo apt update
sudo apt install mysql-server
sudo systemctl start mysql
sudo systemctl enable mysql
```
**Windows**:
- 下载MySQL 8.0安装包
- 运行安装向导
#### 2.2 创建数据库
```sql
-- 登录MySQL
mysql -u root -p
-- 创建数据库
CREATE DATABASE design_hub CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 创建用户(可选)
CREATE USER 'designhub'@'localhost' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON design_hub.* TO 'designhub'@'localhost';
FLUSH PRIVILEGES;
```
#### 2.3 导入数据库表
```bash
# 执行数据库脚本(如果有SQL文件)
mysql -u root -p design_hub < database/schema.sql
```
**注意**:项目使用MyBatis Plus,实体类会自动映射到数据库表。确保在启动前,数据库连接配置正确。
### 3. 配置 Redis
#### 3.1 安装 Redis
**macOS**:
```bash
brew install redis
brew services start redis
```
**Linux**:
```bash
sudo apt install redis-server
sudo systemctl start redis
sudo systemctl enable redis
```
**Windows**:
- 下载Redis for Windows
- 解压并运行 `redis-server.exe`
#### 3.2 验证Redis
```bash
redis-cli ping
# 应返回:PONG
```
### 4. 配置后端
#### 4.1 编辑配置文件
编辑 `/backend/src/main/resources/application.yml`:
```yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/design_hub?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root # 修改为你的MySQL用户名
password: your_password # 修改为你的MySQL密码
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: localhost
port: 6379
password: # 如果Redis没有密码,留空
database: 0
timeout: 3000
jwt:
secret: your-secret-key-change-this-in-production # 生产环境请修改
access-token-expiration: 7200000 # 2小时
refresh-token-expiration: 604800000 # 7天
server:
port: 8080
```
#### 4.2 构建并运行后端
```bash
cd backend
# 使用 Maven 构建
mvn clean install -DskipTests
# 运行后端
mvn spring-boot:run
# 或者使用 JAR 文件
java -jar target/designhub-backend-1.0.0.jar
```
**后端启动成功标志**:
- 控制台输出 `Started DesignHubApplication`
- 访问 http://localhost:8080/swagger-ui.html 查看API文档
### 5. 配置前端
#### 5.1 安装依赖
```bash
cd frontend
# 使用 pnpm(推荐)
pnpm install
# 或使用 npm
npm install
# 或使用 yarn
yarn install
```
#### 5.2 配置环境变量
创建 `/frontend/.env.development`:
```env
# 开发环境配置
VITE_APP_BASE_API=http://localhost:8080/api
VITE_APP_TITLE=DesignHub
VITE_APP_VERSION=6.0
```
#### 5.3 运行前端
```bash
# 开发模式
pnpm dev
# 或
npm run dev
```
**前端启动成功标志**:
- 控制台输出本地访问地址(默认:http://localhost:5173)
- 浏览器自动打开页面
### 6. 访问系统
打开浏览器,访问 `http://localhost:5173`
**默认测试账号**:
- 用户名:`admin`
- 密码:`123456`
---
## 🌐 生产环境部署
### 1. 服务器准备
#### 1.1 更新系统
```bash
# CentOS/RHEL
sudo yum update -y
# Ubuntu/Debian
sudo apt update && sudo apt upgrade -y
```
#### 1.2 安装必要软件
```bash
# 安装 JDK 17
sudo yum install java-17-openjdk -y
# 安装 Nginx
sudo yum install nginx -y
# 安装 Git(如需)
sudo yum install git -y
```
### 2. 部署数据库
#### 2.1 安装 MySQL 8.0
```bash
# 下载MySQL Yum Repository
wget https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm
# 安装Repository
sudo rpm -ivh mysql80-community-release-el7-3.noarch.rpm
# 安装MySQL
sudo yum install mysql-server -y
# 启动MySQL
sudo systemctl start mysqld
sudo systemctl enable mysqld
# 获取临时密码
sudo grep 'temporary password' /var/log/mysqld.log
# 修改root密码
mysql_secure_installation
```
#### 2.2 创建数据库和用户
```sql
CREATE DATABASE design_hub CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'designhub'@'%' IDENTIFIED BY 'StrongPassword123!';
GRANT ALL PRIVILEGES ON design_hub.* TO 'designhub'@'%';
FLUSH PRIVILEGES;
```
#### 2.3 导入数据
```bash
mysql -u designhub -p design_hub < /path/to/schema.sql
```
### 3. 部署 Redis
```bash
# 安装Redis
sudo yum install redis -y
# 配置Redis(可选设置密码)
sudo vi /etc/redis.conf
# 找到 requirepass,设置密码
requirepass YourStrongPassword
# 启动Redis
sudo systemctl start redis
sudo systemctl enable redis
```
### 4. 部署后端
#### 4.1 上传代码
```bash
# 创建应用目录
sudo mkdir -p /opt/designhub/backend
sudo chown -R $USER:$USER /opt/designhub
# 上传代码(使用scp、rsync或git)
# scp方式
scp -r backend/* user@server:/opt/designhub/backend/
# 或使用git
cd /opt/designhub
git clone <repository_url>
```
#### 4.2 配置生产环境
编辑 `/opt/designhub/backend/src/main/resources/application-prod.yml`:
```yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/design_hub?useSSL=true
username: designhub
password: StrongPassword123!
redis:
host: localhost
port: 6379
password: YourStrongPassword
jwt:
secret: change-this-to-a-very-strong-secret-key-in-production
logging:
level:
root: INFO
com.designhub: INFO
file:
name: /opt/designhub/logs/application.log
```
#### 4.3 构建后端
```bash
cd /opt/designhub/backend
# 构建
mvn clean package -DskipTests -Pprod
# 移动JAR文件
mv target/designhub-backend-1.0.0.jar /opt/designhub/
```
#### 4.4 创建系统服务
创建 `/etc/systemd/system/designhub-backend.service`:
```ini
[Unit]
Description=DesignHub Backend Service
After=network.target
[Service]
Type=simple
User=designhub
WorkingDirectory=/opt/designhub
ExecStart=/usr/bin/java -jar /opt/designhub/designhub-backend-1.0.0.jar --spring.profiles.active=prod
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
```
#### 4.5 启动后端服务
```bash
# 重新加载systemd
sudo systemctl daemon-reload
# 启动服务
sudo systemctl start designhub-backend
# 设置开机自启
sudo systemctl enable designhub-backend
# 查看状态
sudo systemctl status designhub-backend
# 查看日志
sudo journalctl -u designhub-backend -f
```
### 5. 部署前端
#### 5.1 构建前端
```bash
cd /opt/designhub/frontend
# 安装依赖
pnpm install --production
# 创建生产环境配置
cat > .env.production << EOF
VITE_APP_BASE_API=https://api.yourdomain.com/api
VITE_APP_TITLE=DesignHub
VITE_APP_VERSION=6.0
EOF
# 构建
pnpm build
# 构建产物在 dist/ 目录
```
#### 5.2 配置 Nginx
创建 `/etc/nginx/conf.d/designhub.conf`:
```nginx
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# 前端静态资源
root /opt/designhub/frontend/dist;
index index.html;
# 前端路由
location / {
try_files $uri $uri/ /index.html;
}
# API代理
location /api/ {
proxy_pass http://localhost:8080/api/;
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;
# WebSocket支持(如需)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Gzip压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}
```
#### 5.3 配置 HTTPS (Let's Encrypt)
```bash
# 安装certbot
sudo yum install certbot python3-certbot-nginx -y
# 获取SSL证书
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# 自动续期测试
sudo certbot renew --dry-run
```
#### 5.4 启动 Nginx
```bash
# 测试配置
sudo nginx -t
# 启动Nginx
sudo systemctl start nginx
sudo systemctl enable nginx
# 重新加载配置
sudo systemctl reload nginx
```
### 6. 防火墙配置
```bash
# CentOS/RHEL (firewalld)
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
# Ubuntu (ufw)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
```
---
## 🐳 Docker 部署
### 1. 后端 Dockerfile
创建 `/backend/Dockerfile`:
```dockerfile
FROM openjdk:17-slim
WORKDIR /app
# 复制JAR文件
COPY target/designhub-backend-1.0.0.jar app.jar
# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 暴露端口
EXPOSE 8080
# 启动命令
ENTRYPOINT ["java", "-jar", "app.jar", "--spring.profiles.active=prod"]
```
### 2. 前端 Dockerfile
创建 `/frontend/Dockerfile`:
```dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
# 复制package文件
COPY package*.json ./
COPY pnpm-lock.yaml ./
# 安装依赖
RUN npm install -g pnpm
RUN pnpm install
# 复制源代码
COPY . .
# 构建
RUN pnpm build
# 生产环境
FROM nginx:alpine
# 复制构建产物
COPY --from=builder /app/dist /usr/share/nginx/html
# 复制Nginx配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```
### 3. Docker Compose
创建 `/docker-compose.yml`:
```yaml
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: designhub-mysql
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: design_hub
MYSQL_USER: designhub
MYSQL_PASSWORD: designhubpass
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
networks:
- designhub-network
redis:
image: redis:7-alpine
container_name: designhub-redis
command: redis-server --requirepass redispassword
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- designhub-network
backend:
build: ./backend
container_name: designhub-backend
environment:
SPRING_PROFILES_ACTIVE: prod
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/design_hub
SPRING_DATASOURCE_USERNAME: designhub
SPRING_DATASOURCE_PASSWORD: designhubpass
SPRING_REDIS_HOST: redis
SPRING_REDIS_PASSWORD: redispassword
ports:
- "8080:8080"
depends_on:
- mysql
- redis
networks:
- designhub-network
frontend:
build: ./frontend
container_name: designhub-frontend
ports:
- "80:80"
depends_on:
- backend
networks:
- designhub-network
volumes:
mysql_data:
redis_data:
networks:
designhub-network:
driver: bridge
```
### 4. 启动 Docker Compose
```bash
# 构建并启动所有服务
docker-compose up -d
# 查看日志
docker-compose logs -f
# 停止所有服务
docker-compose down
# 重启服务
docker-compose restart
```
---
## ❓ 常见问题
### 1. 后端无法连接数据库
**问题**:`java.sql.SQLException: Access denied for user`
**解决**:
```sql
-- 检查用户权限
SELECT User, Host FROM mysql.user;
-- 重新授权
GRANT ALL PRIVILEGES ON design_hub.* TO 'designhub'@'%' IDENTIFIED BY 'password';
FLUSH PRIVILEGES;
```
### 2. Redis连接失败
**问题**:`Could not connect to Redis at localhost:6379`
**解决**:
```bash
# 检查Redis是否运行
sudo systemctl status redis
# 检查端口
netstat -tuln | grep 6379
# 测试连接
redis-cli -h localhost -p 6379 -a password ping
```
### 3. 前端API请求跨域
**问题**:`CORS policy: No 'Access-Control-Allow-Origin' header`
**解决**:后端添加CORS配置(已在项目中配置)
### 4. JWT Token过期
**问题**:频繁退出登录
**解决**:调整Token过期时间或实现自动刷新机制(已实现)
### 5. 文件上传失败
**问题**:上传大文件失败
**解决**:
```yaml
# application.yml
spring:
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
```
---
## 🛠️ 维护与监控
### 1. 日志管理
#### 后端日志
```bash
# 查看实时日志
tail -f /opt/designhub/logs/application.log
# 按日期查看
less /opt/designhub/logs/application-2025-11-07.log
# 日志清理(保留30天)
find /opt/designhub/logs -name "*.log" -mtime +30 -delete
```
#### Nginx日志
```bash
# 访问日志
tail -f /var/log/nginx/access.log
# 错误日志
tail -f /var/log/nginx/error.log
```
### 2. 数据库备份
#### 自动备份脚本
创建 `/opt/designhub/scripts/backup_db.sh`:
```bash
#!/bin/bash
# 配置
DB_NAME="design_hub"
DB_USER="designhub"
DB_PASS="password"
BACKUP_DIR="/opt/designhub/backups"
DATE=$(date +%Y%m%d_%H%M%S)
# 创建备份目录
mkdir -p $BACKUP_DIR
# 备份数据库
mysqldump -u$DB_USER -p$DB_PASS $DB_NAME > $BACKUP_DIR/backup_$DATE.sql
# 压缩备份
gzip $BACKUP_DIR/backup_$DATE.sql
# 删除7天前的备份
find $BACKUP_DIR -name "backup_*.sql.gz" -mtime +7 -delete
echo "Backup completed: backup_$DATE.sql.gz"
```
#### 设置定时任务
```bash
# 编辑crontab
crontab -e
# 每天凌晨2点备份
0 2 * * * /opt/designhub/scripts/backup_db.sh
```
### 3. 性能监控
#### 系统资源监控
```bash
# CPU和内存
top
# 磁盘使用
df -h
# 网络连接
netstat -tuln
# 进程监控
ps aux | grep java
```
#### 应用监控
**Spring Boot Actuator**(已集成):
- 访问 http://yourdomain.com/actuator/health
- 访问 http://yourdomain.com/actuator/metrics
### 4. 安全加固
#### 系统安全
```bash
# 关闭不必要的服务
sudo systemctl disable bluetooth
sudo systemctl disable cups
# 更新安全补丁
sudo yum update --security -y
# 配置fail2ban
sudo yum install fail2ban -y
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
```
#### 应用安全
- 定期更新依赖版本
- 使用强密码
- 启用HTTPS
- 配置防火墙
- 定期安全审计
---
## 📞 技术支持
如遇到部署问题,请:
1. 查看本文档"常见问题"部分
2. 检查日志文件
3. 联系技术支持团队
---
**祝您部署顺利!** 🎉

418
DEVELOPMENT_GUIDE.md Normal file
View File

@ -0,0 +1,418 @@
# DesignHub 开发启动指南
## 📋 目录
- [环境准备](#环境准备)
- [项目初始化](#项目初始化)
- [数据库配置](#数据库配置)
- [后端启动](#后端启动)
- [前端启动](#前端启动)
- [开发规范](#开发规范)
- [常见问题](#常见问题)
## 🛠️ 环境准备
### 必备软件
确保您的开发环境已安装以下软件:
| 软件 | 版本要求 | 说明 |
|------|---------|------|
| Java | >= 17 | 后端运行环境 |
| Node.js | >= 18.0 | 前端运行环境 |
| MySQL | >= 8.0 | 数据库 |
| Redis | >= 7.0 | 缓存服务(可选,开发阶段可不启用) |
| Maven | >= 3.8 | Java构建工具 |
| Git | 最新版本 | 版本控制 |
### 推荐开发工具
- **后端开发**: IntelliJ IDEA / Eclipse
- **前端开发**: VS Code
- **数据库管理**: Navicat / DBeaver / MySQL Workbench
- **API测试**: Postman / Apifox
- **Redis管理**: RedisInsight / Another Redis Desktop Manager
## 🚀 项目初始化
### 1. 克隆项目(如果从远程仓库)
```bash
git clone <repository-url>
cd qiangdanxitong
```
### 2. 项目结构说明
```
qiangdanxitong/
├── frontend/ # 前端项目(Vue 3)
├── backend/ # 后端项目(Spring Boot)
├── database/ # 数据库脚本
├── docs/ # 文档
├── PROJECT_REQUIREMENTS.md # 需求文档
└── README.md # 项目说明
```
## 💾 数据库配置
### 1. 创建数据库
使用 MySQL 客户端或管理工具执行以下命令:
```bash
# 方式1:使用命令行
mysql -u root -p < database/schema.sql
mysql -u root -p < database/init-data.sql
# 方式2:使用 MySQL 客户端
mysql -u root -p
source /Users/lanmu/Desktop/qiangdanxitong/database/schema.sql;
source /Users/lanmu/Desktop/qiangdanxitong/database/init-data.sql;
```
### 2. 验证数据库
```sql
-- 登录MySQL
mysql -u root -p
-- 切换到designhub数据库
USE designhub;
-- 查看所有表
SHOW TABLES;
-- 查看部门数据
SELECT * FROM sys_dept;
-- 查看任务类型
SELECT * FROM task_type;
```
### 3. 配置数据库连接
编辑 `backend/src/main/resources/application-dev.yml`:
```yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/designhub?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 你的MySQL密码
```
## 🔧 后端启动
### 方式1:使用IDE启动(推荐)
1. 用 IntelliJ IDEA 打开 `backend` 目录
2. 等待 Maven 自动下载依赖
3. 找到 `DesignHubApplication.java` 主类
4. 右键 -> Run 'DesignHubApplication'
### 方式2:使用命令行启动
```bash
cd backend
# 首次启动需要先编译
mvn clean install
# 启动应用
mvn spring-boot:run
# 或者直接运行jar包
java -jar target/designhub-backend-1.0.0.jar
```
### 验证后端启动
启动成功后,访问以下地址:
- **API文档**: http://localhost:8080/api/doc.html
- **健康检查**: http://localhost:8080/api/actuator/health(如果配置了actuator)
控制台应该输出:
```
╔═══════════════════════════════════════════════════════╗
║ ║
║ 🎨 DesignHub Backend Service Started 🎨 ║
║ ║
║ Port: 8080 ║
║ Swagger UI: http://localhost:8080/api/doc.html ║
║ Version: 1.0.0 ║
║ ║
╚═══════════════════════════════════════════════════════╝
```
## 🎨 前端启动
### 1. 安装依赖
```bash
cd frontend
# 使用npm
npm install
# 或使用pnpm(推荐,更快)
pnpm install
# 或使用yarn
yarn install
```
### 2. 启动开发服务器
```bash
# 使用npm
npm run dev
# 使用pnpm
pnpm dev
# 使用yarn
yarn dev
```
### 3. 访问前端
浏览器自动打开或手动访问:
- **开发地址**: http://localhost:3000
控制台应该输出:
```
VITE v5.0.0 ready in 500 ms
➜ Local: http://localhost:3000/
➜ Network: use --host to expose
➜ press h + enter to show help
```
## 📝 开发规范
### Git提交规范
使用 Conventional Commits 规范:
```bash
# 功能开发
git commit -m "feat: 添加任务大厅页面"
# Bug修复
git commit -m "fix: 修复任务列表显示问题"
# 文档更新
git commit -m "docs: 更新README文档"
# 样式调整
git commit -m "style: 调整按钮样式"
# 代码重构
git commit -m "refactor: 重构任务服务层代码"
# 性能优化
git commit -m "perf: 优化数据库查询性能"
# 测试相关
git commit -m "test: 添加任务模块单元测试"
```
### 分支策略
```
main/master -> 生产环境分支
develop -> 开发分支
feature/* -> 功能分支
hotfix/* -> 紧急修复分支
release/* -> 发布分支
```
### 代码风格
#### 前端(TypeScript/Vue)
- 使用 ESLint + Prettier 格式化代码
- 组件命名使用 PascalCase
- 文件命名使用 kebab-case
- 使用 Composition API
```bash
# 格式化代码
npm run format
# 检查代码
npm run lint
```
#### 后端(Java/Spring Boot)
- 遵循 阿里巴巴 Java 开发手册
- 类名使用 PascalCase
- 方法名使用 camelCase
- 常量使用 UPPER_SNAKE_CASE
## 🔍 常见问题
### Q1: 后端启动失败,提示数据库连接错误
**A**: 检查以下几点:
1. MySQL 是否已启动
2. 数据库密码是否正确配置在 `application-dev.yml`
3. 数据库 `designhub` 是否已创建
4. 数据库端口是否为 3306
### Q2: 前端启动后页面空白
**A**:
1. 检查浏览器控制台是否有报错
2. 确认后端API是否正常运行(访问 http://localhost:8080/api/doc.html)
3. 检查前端代理配置(vite.config.ts)
### Q3: Maven 依赖下载缓慢
**A**: 配置国内Maven镜像(阿里云)
编辑 `~/.m2/settings.xml`:
```xml
<mirror>
<id>aliyun</id>
<mirrorOf>central</mirrorOf>
<name>Aliyun Maven Mirror</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
```
### Q4: npm 安装依赖失败
**A**: 切换npm镜像源
```bash
# 临时使用淘宝镜像
npm install --registry=https://registry.npmmirror.com
# 永久设置
npm config set registry https://registry.npmmirror.com
# 使用 nrm 管理镜像源(推荐)
npm install -g nrm
nrm use taobao
```
### Q5: 钉钉登录如何配置
**A**:
1. 登录钉钉开放平台 https://open.dingtalk.com
2. 创建应用,获取 AppKey 和 AppSecret
3. 配置到 `application.yml` 的 `dingtalk` 部分
4. 配置回调地址和权限
```yaml
dingtalk:
app-key: your-app-key
app-secret: your-app-secret
agent-id: your-agent-id
corp-id: your-corp-id
```
### Q6: Redis 连接失败
**A**: 开发阶段可以暂时禁用 Redis
在 `application-dev.yml` 中注释掉 Redis 配置,或确保 Redis 已启动:
```bash
# 启动 Redis(Mac)
brew services start redis
# 启动 Redis(Linux)
redis-server
# 启动 Redis(Windows)
redis-server.exe
# 测试连接
redis-cli ping
# 应该返回 PONG
```
## 📚 下一步
### 开发优先级
1. ✅ **基础架构** - 已完成
- 项目结构
- 数据库设计
- 前后端框架搭建
2. 🔄 **核心功能开发** - 进行中
- [ ] 用户认证模块(Casdoor + 钉钉登录)
- [ ] 任务管理模块
- [ ] 积分管理模块
- [ ] 通知系统
3. ⏳ **进阶功能** - 待开发
- [ ] 激励系统
- [ ] 数据分析
- [ ] 管理后台
### 推荐开发顺序
```
1. 用户认证 → 2. 组织架构 → 3. 任务发起 → 4. 任务大厅 →
5. 我的任务 → 6. 设计师大厅 → 7. 积分系统 → 8. 数据分析 →
9. 激励系统 → 10. 系统配置 → 11. 通知系统
```
## 🤝 团队协作
### 每日开发流程
1. **早上**:拉取最新代码
```bash
git checkout develop
git pull origin develop
```
2. **开发**:创建功能分支
```bash
git checkout -b feature/task-hall
# 编码...
git add .
git commit -m "feat: 完成任务大厅页面"
```
3. **提交**:推送到远程并创建PR
```bash
git push origin feature/task-hall
# 在 GitLab/GitHub 创建 Merge Request / Pull Request
```
4. **审查**:代码审查通过后合并到 develop
### 代码审查清单
- [ ] 代码符合规范
- [ ] 没有明显的 bug
- [ ] 功能完整
- [ ] 已添加必要的注释
- [ ] 通过单元测试
- [ ] UI 符合 OpenAI 设计规范
## 📞 技术支持
如有问题,请联系:
- **技术负责人**: [姓名] - [邮箱]
- **项目文档**: PROJECT_REQUIREMENTS.md
- **API文档**: http://localhost:8080/api/doc.html
---
**祝开发愉快!** 🎉

459
Nginx缓存配置说明.md Normal file
View File

@ -0,0 +1,459 @@
# Nginx缓存配置说明
## 📝 配置概述
**更新时间**: 2025-12-23
**服务器**: 公网服务器 156.225.27.52:50232
**配置文件**: /www/server/panel/vhost/nginx/qd.yydd.ai.conf
**状态**: ✅ 已禁用所有缓存(开发模式)
---
## 🚫 当前缓存设置
### 已禁用的缓存功能
```nginx
location / {
# 反向代理配置
proxy_pass http://127.0.0.1:5246;
# 🚫 禁用缓存 - 开发模式
proxy_buffering off; # 禁用代理缓冲
proxy_cache off; # 禁用代理缓存
proxy_no_cache 1; # 不缓存响应
proxy_cache_bypass 1; # 绕过缓存检查
# 添加缓存控制头
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
add_header Pragma "no-cache";
add_header Expires "0";
}
```
---
## 📊 缓存控制说明
### Nginx层面缓存控制
| 指令 | 作用 | 值 |
|------|------|-----|
| `proxy_buffering` | 代理缓冲 | off(禁用) |
| `proxy_cache` | 代理缓存 | off(禁用) |
| `proxy_no_cache` | 不缓存响应 | 1(启用) |
| `proxy_cache_bypass` | 绕过缓存 | 1(启用) |
### HTTP响应头
| 响应头 | 作用 | 值 |
|--------|------|-----|
| `Cache-Control` | 缓存策略 | no-store, no-cache, must-revalidate |
| `Pragma` | HTTP/1.0兼容 | no-cache |
| `Expires` | 过期时间 | 0(立即过期) |
---
## 🎯 效果说明
### 对开发的好处
1. **✅ 实时更新**
- 前端代码更新立即生效
- 无需手动清除浏览器缓存
- API响应不会被缓存
2. **✅ 调试便利**
- 每次请求都是最新内容
- 便于测试新功能
- 避免缓存导致的问题
3. **✅ 快速迭代**
- 修改代码后立即看到效果
- 减少"看不到更新"的困惑
- 提高开发效率
---
## 🔄 切换到生产模式
### 当系统正式上线后
**建议**:启用缓存以提高性能和减少服务器负载
```nginx
location / {
proxy_pass http://127.0.0.1:5246;
# 启用缓存(生产模式)
proxy_buffering on;
# 静态资源缓存(仅示例,根据实际调整)
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
proxy_pass http://127.0.0.1:5246;
proxy_cache my_cache;
proxy_cache_valid 200 30d;
proxy_cache_valid 404 1m;
add_header Cache-Control "public, max-age=2592000";
}
# API响应不缓存
location /api/ {
proxy_pass http://127.0.0.1:5246;
proxy_buffering off;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
}
```
---
## 📝 切换步骤
### 从开发模式切换到生产模式
```bash
# 1. SSH登录公网服务器
ssh -p 50232 root@156.225.27.52
# 2. 编辑Nginx配置
vi /www/server/panel/vhost/nginx/qd.yydd.ai.conf
# 3. 修改缓存配置(根据需要)
# 移除或注释 proxy_buffering off 等指令
# 添加适当的缓存配置
# 4. 测试配置
nginx -t
# 5. 重载Nginx
nginx -s reload
```
---
## 🧪 测试缓存配置
### 验证缓存已禁用
```bash
# 方式1:查看HTTP响应头
curl -I https://qd.yydd.ai
# 应该看到:
# Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0
# Pragma: no-cache
# Expires: 0
# 方式2:在浏览器开发者工具中
# Network → Headers → Response Headers
# 检查 Cache-Control 等头部
```
### 测试更新是否实时
1. **前端更新测试**
```bash
# 修改前端代码
cd /Users/lanmu/Desktop/qiangdanxitong/frontend
# 编辑任意文件
# 构建并上传
pnpm build
scp -P 5204 -r dist/* root@156.225.27.52:/opt/designhub/frontend/
# 立即刷新浏览器(Ctrl+R,无需Ctrl+Shift+R强制刷新)
# 应该看到更新
```
2. **API更新测试**
```bash
# 修改后端代码
# 重启后端服务
ssh -p 5204 root@156.225.27.52
systemctl restart designhub-backend
# 立即在浏览器中调用API
# 应该返回最新结果
```
---
## ⚠️ 注意事项
### 开发模式的影响
**优点**:
- ✅ 开发调试方便
- ✅ 代码更新实时生效
- ✅ 避免缓存问题
**缺点**:
- ⚠️ 服务器负载略高(每次请求都处理)
- ⚠️ 用户体验略差(无缓存加速)
- ⚠️ 带宽消耗略大
**建议**:
- 开发阶段:使用当前配置(无缓存)
- 生产阶段:启用适当缓存(性能优化)
---
## 🎯 完整配置文件
### 当前配置(开发模式)
```nginx
# /www/server/panel/vhost/nginx/qd.yydd.ai.conf
# HTTP自动跳转HTTPS
server {
listen 80;
server_name qd.yydd.ai;
location /.well-known/acme-challenge/ {
root /www/wwwroot/qd.yydd.ai;
}
location / {
return 301 https://$server_name$request_uri;
}
}
# HTTPS配置
server {
listen 443 ssl http2;
server_name qd.yydd.ai;
# SSL证书
ssl_certificate /etc/letsencrypt/live/qd.yydd.ai/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/qd.yydd.ai/privkey.pem;
# SSL优化
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;
client_max_body_size 100M;
# 反向代理 - 无缓存模式
location / {
proxy_pass http://127.0.0.1:5246;
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;
# WebSocket支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 超时设置
proxy_connect_timeout 300;
proxy_send_timeout 300;
proxy_read_timeout 300;
# 🚫 禁用缓存 - 开发模式
proxy_buffering off;
proxy_cache off;
proxy_no_cache 1;
proxy_cache_bypass 1;
# 添加缓存控制头
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
add_header Pragma "no-cache";
add_header Expires "0";
}
access_log /www/wwwlogs/qd.yydd.ai.https.access.log;
error_log /www/wwwlogs/qd.yydd.ai.https.error.log;
}
```
---
## 🔧 常用命令
### 修改配置后重载
```bash
# SSH登录
ssh -p 50232 root@156.225.27.52
# 编辑配置
vi /www/server/panel/vhost/nginx/qd.yydd.ai.conf
# 测试配置
nginx -t
# 重载Nginx(不中断服务)
nginx -s reload
```
### 查看配置
```bash
# 查看当前配置
ssh -p 50232 root@156.225.27.52
cat /www/server/panel/vhost/nginx/qd.yydd.ai.conf
```
### 验证缓存状态
```bash
# 方式1:curl查看响应头
curl -I https://qd.yydd.ai
# 方式2:详细查看
curl -v https://qd.yydd.ai 2>&1 | grep -i cache
```
---
## 📊 浏览器缓存说明
### 浏览器本地缓存
**注意**: 即使Nginx禁用了缓存,浏览器仍可能有自己的缓存
**开发时的刷新方式**:
| 刷新方式 | 快捷键 | 效果 |
|----------|--------|------|
| 普通刷新 | F5 或 Ctrl+R | 可能使用缓存 |
| 硬刷新 | Ctrl+Shift+R | 强制不使用缓存 |
| 清空缓存 | DevTools → Network → Disable cache | 开发时建议勾选 |
**建议**:
- 开发时打开浏览器开发者工具(F12)
- 勾选 Network → Disable cache
- 这样可以完全禁用浏览器缓存
---
## 🚀 性能对比
### 开发模式 vs 生产模式
| 指标 | 开发模式(无缓存) | 生产模式(有缓存) |
|------|-------------------|-------------------|
| 首次加载 | 正常 | 正常 |
| 重复访问 | 每次都请求服务器 | 使用缓存,快速 |
| 服务器负载 | 较高 | 较低 |
| 带宽消耗 | 较高 | 较低 |
| 开发便利性 | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| 用户体验 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
---
## 💡 最佳实践
### 开发阶段(当前)
✅ **已实施**:
- Nginx层面禁用所有缓存
- 添加 no-cache 响应头
- 便于快速迭代开发
### 测试阶段(即将上线)
建议:
- 保持当前配置
- 测试功能完整性
- 验证无缓存相关bug
### 生产阶段(正式上线后)
建议:
- 启用静态资源缓存(JS/CSS/图片)
- API响应保持不缓存
- 设置合理的缓存时间
- 使用CDN加速
---
## 🔄 快速切换配置
### 配置模板
**开发模式配置**(当前):
```nginx
# 完全禁用缓存
proxy_buffering off;
proxy_cache off;
proxy_no_cache 1;
proxy_cache_bypass 1;
add_header Cache-Control "no-store, no-cache, must-revalidate";
```
**生产模式配置**(未来):
```nginx
# 启用适度缓存
proxy_buffering on;
# 静态资源缓存7天
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
add_header Cache-Control "public, max-age=604800";
}
# API不缓存
location /api/ {
add_header Cache-Control "no-cache";
}
```
---
## 📞 技术支持
### 修改缓存配置
如需修改缓存配置,请:
1. SSH登录公网服务器
2. 编辑 `/www/server/panel/vhost/nginx/qd.yydd.ai.conf`
3. 测试配置:`nginx -t`
4. 重载Nginx:`nginx -s reload`
### 验证配置生效
```bash
# 查看HTTP响应头
curl -I https://qd.yydd.ai | grep -i cache
# 应该看到当前设置的缓存策略
```
---
## 📝 更新记录
| 日期 | 操作 | 说明 |
|------|------|------|
| 2025-12-23 | 禁用缓存 | 开发模式,便于测试 |
| 待定 | 启用缓存 | 生产模式,性能优化 |
---
## 🎯 总结
✅ **当前状态**: 所有缓存已禁用
✅ **适用场景**: 开发测试阶段
✅ **效果**: 代码更新立即生效
✅ **访问地址**: https://qd.yydd.ai
**开发体验**:
- 修改前端代码 → 构建上传 → 普通刷新(F5)即可看到更新
- 修改后端代码 → 重启服务 → 立即生效
- 无需清除浏览器缓存或强制刷新
🎉 **现在可以愉快地开发测试了!**
---
**创建时间**: 2025-12-23
**配置状态**: ✅ 已生效
**验证方式**: 访问 https://qd.yydd.ai 并查看响应头

6335
PROJECT_REQUIREMENTS.md Normal file

File diff suppressed because it is too large Load Diff

362
README.md Normal file
View File

@ -0,0 +1,362 @@
# 🎨 DesignHub - 设计师任务管理平台
<div align="center">
![Version](https://img.shields.io/badge/version-6.0-blue.svg)
![Status](https://img.shields.io/badge/status-completed-success.svg)
![License](https://img.shields.io/badge/license-MIT-green.svg)
**现代化的设计师任务抢单与管理系统**
[功能特性](#-核心功能) • [技术栈](#-技术栈) • [快速开始](#-快速开始) • [文档](#-项目文档) • [截图](#-系统截图)
</div>
---
## 📖 项目简介
DesignHub是一个功能完善的设计师任务管理平台,旨在帮助企业高效管理设计任务的发布、接收、执行和评审全流程。系统采用积分制管理,配备完整的激励机制和数据分析功能,大幅提升设计团队的工作效率和积极性。
### 🎯 核心价值
- **任务流转高效化**:从发布到完成,全程可视化管理
- **积分控制精准化**:多维度积分管理,合理分配资源
- **激励机制完善化**:签到、成就、排行榜,激发团队活力
- **数据分析智能化**:多维度数据统计,辅助决策
- **消息通知及时化**:实时推送,确保信息触达
---
## ✨ 核心功能
### 🔐 用户认证与权限
- JWT双令牌认证机制
- RBAC权限模型(7种角色)
- 钉钉登录预留接口
- 细粒度权限控制
### 📋 任务管理
- 任务发起(配置化类型、品牌、平台)
- 任务抢单(自由抢单/指定分配/招募)
- 任务执行(进度跟踪、时效计算)
- 任务评审(五星评分、质量评价)
- 管理员调配(分配、转移、优先级调整)
### 💰 积分管理
- 团队积分池
- 积分充值与审批
- 周期限额管理(日/周/月)
- 积分消耗与奖励
- 延误扣分机制
### 🏆 激励系统
- **签到系统**:每日签到、连续追踪、奖励递增
- **成就系统**:10个默认成就、自动解锁、奖励发放
- **排行榜**:好评排行、积分排行、效率排行
### 🔔 通知系统
- 站内消息(4种类型)
- 通知分类与筛选
- 已读/未读管理
- 钉钉推送预留接口
### 📊 数据分析
- 需求统计(总览/趋势/类型分布)
- 用户排行(需求量/完成率/满意度)
- 部门统计(积分使用/预警机制)
- 设计师统计(任务量/积分/好评率)
- ECharts可视化图表
### 🏢 部门管理
- 部门CRUD操作
- 部门类型(需求/接单)
- 部门层级结构
- 积分限额配置
### ⚙️ 配置管理
- 需求类型配置
- 品牌/平台/类目配置
- 系统参数配置
- 延误扣分规则
---
## 🛠️ 技术栈
### 前端技术
| 技术 | 版本 | 说明 |
|------|------|------|
| Vue | 3.x | 渐进式JavaScript框架 |
| TypeScript | 5.x | 类型安全 |
| Ant Design Vue | 4.x | UI组件库 |
| Pinia | 2.x | 状态管理 |
| Vue Router | 4.x | 路由管理 |
| Axios | 1.x | HTTP客户端 |
| ECharts | 5.x | 数据可视化 |
| SCSS | - | CSS预处理器 |
### 后端技术
| 技术 | 版本 | 说明 |
|------|------|------|
| Spring Boot | 3.x | Java应用框架 |
| MySQL | 8.0 | 关系型数据库 |
| Redis | 7.x | 缓存数据库 |
| MyBatis Plus | 3.x | ORM框架 |
| JWT | - | Token认证 |
| Swagger | 3.x | API文档 |
| Quartz | 2.x | 任务调度 |
| Apache POI | 5.x | Excel操作 |
### 开发工具
- IntelliJ IDEA - 后端开发
- VS Code - 前端开发
- Maven - 后端构建
- pnpm - 前端包管理
---
## 🚀 快速开始
### 环境要求
- JDK 17+
- Node.js 18+
- MySQL 8.0+
- Redis 6.0+
- Maven 3.6+
- pnpm 8.0+
### 1. 克隆项目
```bash
git clone <repository_url>
cd qiangdanxitong
```
### 2. 配置数据库
```sql
-- 创建数据库
CREATE DATABASE design_hub CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 创建用户
CREATE USER 'designhub'@'localhost' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON design_hub.* TO 'designhub'@'localhost';
FLUSH PRIVILEGES;
```
### 3. 启动后端
```bash
cd backend
# 修改配置文件
vi src/main/resources/application.yml
# 构建并运行
mvn clean install -DskipTests
mvn spring-boot:run
```
后端启动成功后,访问:http://localhost:8080/swagger-ui.html
### 4. 启动前端
```bash
cd frontend
# 安装依赖
pnpm install
# 启动开发服务器
pnpm dev
```
前端启动成功后,访问:http://localhost:5173
### 5. 登录系统
**测试账号**:
- 用户名:`admin`
- 密码:`123456`
---
## 📚 项目文档
| 文档名称 | 说明 | 链接 |
|---------|------|------|
| 需求文档 | 详细的功能需求和设计规范 | [PROJECT_REQUIREMENTS.md](./PROJECT_REQUIREMENTS.md) |
| 项目总结 | 项目概述、功能、技术亮点 | [PROJECT_SUMMARY.md](./PROJECT_SUMMARY.md) |
| 部署指南 | 本地和生产环境部署指南 | [DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md) |
| 当前状态 | 开发进度和模块完成情况 | [CURRENT_STATUS.md](./CURRENT_STATUS.md) |
| 阶段报告 | 各开发阶段的完成报告 | PHASE1-6_COMPLETED.md |
---
## 📸 系统截图
### 登录页面
渐变背景、玻璃拟态设计、流畅动画效果
### 首页工作台
数据卡片、快速操作、待办任务、本周统计
### 任务大厅
任务列表、筛选排序、抢单操作
### 我的任务
双视图(发布/接收)、进度追踪、甘特图
### 设计师大厅
实时状态、进度展示、好评排行榜
### 数据分析
ECharts图表、多维度统计、趋势分析
### 通知中心
消息列表、分类筛选、已读管理
### 签到打卡
签到状态、奖励规则、历史记录
### 成就中心
徽章展示、等级标识、奖励领取
---
## 📊 项目数据
### 代码统计
- 后端Java文件:67个
- 前端Vue/TS文件:36个
- API接口:118+个
- 数据库表:29张
- 完整页面:20+个
- 总代码行数:13000+行
### 功能模块
- 核心业务模块:16个
- 前端页面组件:20+个
- 公共组件:10+个
- 工具类:5个
---
## 🎨 UI设计规范
项目严格遵循**OpenAI官方UI设计规范**:
- ✅ 极简主义设计
- ✅ 现代科技感
- ✅ 清晰的层级结构
- ✅ 一致的交互体验
- ✅ 深色模式优先
### 配色方案
- Primary: `#1890ff` (蓝色)
- Success: `#52c41a` (绿色)
- Warning: `#faad14` (橙色)
- Error: `#f5222d` (红色)
---
## 📈 项目进度
**当前版本**:v6.0
**完成度**:100% ✅
### 开发阶段
- ✅ 阶段1:基础配置与核心功能(完成)
- ✅ 阶段2:部门管理与数据分析(完成)
- ✅ 阶段3:数据导出与系统配置(完成)
- ✅ 阶段4:JWT Token与参数配置(完成)
- ✅ 阶段5:通知、激励、管理员调配(完成)
- ✅ 阶段6:最终收尾与文档完善(完成)
---
## 🔮 未来规划
### 短期(1周内)
- 系统测试(功能/性能/兼容性)
- Bug修复与优化
- 用户操作手册
### 中期(1-3月)
- 钉钉深度集成
- 移动端适配
- AI推荐功能
- 性能优化
### 长期(3-6月)
- 微服务化架构
- 容器化部署
- 数据大屏展示
- 第三方API开放
---
## 🤝 贡献指南
欢迎贡献代码、报告问题或提出建议!
1. Fork本项目
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 提交Pull Request
---
## ❓ 常见问题
### 如何重置密码?
管理员可在用户管理模块重置用户密码。
### 如何配置钉钉登录?
参考 `DEPLOYMENT_GUIDE.md` 中的钉钉集成部分。
### 如何备份数据?
参考 `DEPLOYMENT_GUIDE.md` 中的数据库备份部分。
### 更多问题
请查看 [DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md) 的"常见问题"章节。
---
## 📄 开源协议
本项目采用 [MIT](LICENSE) 协议开源。
---
## 🙏 致谢
感谢以下开源项目:
- [Vue.js](https://vuejs.org/)
- [Spring Boot](https://spring.io/projects/spring-boot)
- [Ant Design](https://ant.design/)
- [ECharts](https://echarts.apache.org/)
- [MyBatis Plus](https://baomidou.com/)
---
## 📞 联系方式
- **项目名称**:DesignHub
- **当前版本**:v6.0
- **完成度**:100%
- **开发时间**:2025年11月7日
---
<div align="center">
**⭐ 如果这个项目对您有帮助,请给个Star支持一下!⭐**
Made with ❤️ by DesignHub Team
</div>

View File

@ -0,0 +1,331 @@
# S3文件存储系统实现说明
## 📋 概述
本系统实现了基于S3兼容存储(MinIO)的文件上传、下载和管理功能,包括:
1. **S3配置管理**:支持多配置管理和动态切换
2. **文件上传**:支持多文件上传到S3存储
3. **文件下载**:带登录验证的文件下载
4. **文件记录**:完整的文件上传记录和关联管理
---
## 🗄️ 数据库设计
### 1. S3配置表 (`sys_s3_config`)
```sql
CREATE TABLE `sys_s3_config` (
`id` bigint NOT NULL AUTO_INCREMENT,
`endpoint` varchar(255) NOT NULL COMMENT 'S3服务端点',
`bucket` varchar(100) NOT NULL COMMENT '存储桶名称',
`access_key_id` varchar(255) NOT NULL,
`secret_access_key` varchar(255) NOT NULL,
`region` varchar(50) DEFAULT 'us-east-1',
`public_domain` varchar(255),
`enable_path_style` tinyint(1) DEFAULT 1,
`file_path` varchar(255) DEFAULT '/',
`is_active` tinyint(1) DEFAULT 1,
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` tinyint(1) DEFAULT 0,
PRIMARY KEY (`id`)
);
```
### 2. 文件上传记录表 (`sys_file_upload`)
```sql
CREATE TABLE `sys_file_upload` (
`id` bigint NOT NULL AUTO_INCREMENT,
`original_name` varchar(255) NOT NULL,
`stored_name` varchar(255) NOT NULL,
`file_path` varchar(500) NOT NULL,
`file_url` varchar(500) NOT NULL,
`file_size` bigint NOT NULL,
`file_type` varchar(100),
`uploader_id` bigint NOT NULL,
`uploader_name` varchar(100),
`related_type` varchar(50),
`related_id` bigint,
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` tinyint(1) DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_uploader` (`uploader_id`),
KEY `idx_related` (`related_type`, `related_id`)
);
```
---
## 🔧 后端实现
### 1. Maven依赖
在 `pom.xml` 中添加:
```xml
<!-- AWS SDK for S3 -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>2.20.26</version>
</dependency>
<!-- Apache Commons IO -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
```
### 2. 核心服务
#### S3Service
- `uploadFile()`: 上传文件到S3
- `getFileUploadById()`: 获取文件记录
- `getFilesByRelated()`: 获取关联文件列表
#### S3ConfigService
- `getActiveConfig()`: 获取激活的配置
- `createConfig()`: 创建新配置
- `updateConfig()`: 更新配置
- `activateConfig()`: 激活指定配置
### 3. API接口
#### S3配置管理 (`/api/s3/config`)
- `GET /active` - 获取激活的配置
- `GET /list` - 获取所有配置
- `GET /{id}` - 获取指定配置
- `POST /` - 创建配置
- `PUT /{id}` - 更新配置
- `DELETE /{id}` - 删除配置
- `PUT /{id}/activate` - 激活配置
#### 文件管理 (`/api/file`)
- `POST /upload` - 上传文件(需要登录)
- 参数:`files` (MultipartFile[]), `relatedType`, `relatedId`
- 返回:文件URL列表
- `GET /download/{id}` - 下载文件(需要登录,验证token)
- 自动重定向到S3文件URL
- `GET /{id}` - 获取文件信息
- `GET /list` - 获取关联文件列表
---
## 🎨 前端实现
### 1. S3配置管理页面
在 `Settings.vue` 中添加S3配置管理标签页:
```vue
<template>
<a-tab-pane key="s3" tab="S3存储配置">
<!-- S3配置表单 -->
<a-form :model="s3ConfigForm">
<a-form-item label="服务端点">
<a-input v-model:value="s3ConfigForm.endpoint" />
</a-form-item>
<a-form-item label="存储桶">
<a-input v-model:value="s3ConfigForm.bucket" />
</a-form-item>
<!-- ... 其他配置项 ... -->
<a-button @click="saveS3Config">保存配置</a-button>
</a-form>
</a-tab-pane>
</template>
```
### 2. 文件上传组件
在 `CreateTask.vue` 中修改文件上传逻辑:
```typescript
// 使用新的S3上传接口
const handleUpload = async (file: File) => {
const formData = new FormData();
formData.append('files', file);
formData.append('relatedType', 'task');
const response = await axios.post('/api/file/upload', formData, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'multipart/form-data'
}
});
return response.data.data; // 返回文件URL数组
};
```
### 3. 文件下载
```typescript
const downloadFile = (fileId: number) => {
const token = localStorage.getItem('token');
window.open(`/api/file/download/${fileId}`, '_blank');
// 浏览器会自动携带token(如果在请求头中)
};
```
---
## 🔐 安全特性
### 1. 登录验证
- 所有文件上传需要有效的JWT token
- 文件下载需要验证登录状态
- token验证失败返回401错误
### 2. 敏感信息保护
- API返回时自动隐藏`secretAccessKey`
- 前端更新时支持保留原密钥
### 3. 权限控制
- 只有管理员可以配置S3
- 普通用户只能上传和下载自己有权限的文件
---
## 📝 使用流程
### 1. 配置S3存储
1. 登录系统,进入**系统设置** > **S3存储配置**
2. 填写S3配置信息:
- 服务端点:`https://s3.1080p.hk`
- 存储桶:`jsxiangche`
- AccessKey和SecretKey
- 公共域名:`https://s3.1080p.hk/jsxiangche`
- 文件路径前缀:`由壹点AI/`
3. 点击保存并激活
### 2. 上传文件
1. 在创建任务页面选择文件
2. 文件自动上传到S3
3. 系统生成文件URL并保存到数据库
4. 任务中显示附件链接
### 3. 下载文件
1. 在任务卡片或详情中点击附件链接
2. 系统验证登录状态
3. 自动跳转到S3文件下载
---
## 🚀 部署步骤
### 1. 执行数据库脚本
```bash
mysql -uroot -p designhub < database/create_s3_config.sql
```
### 2. 更新后端依赖
```bash
cd backend
mvn clean install
```
### 3. 重启后端服务
```bash
systemctl restart designhub-backend
```
### 4. 更新前端
```bash
cd frontend
pnpm build
# 部署到服务器
```
---
## 🔍 测试验证
### 1. 测试S3配置
```bash
curl -X GET http://localhost:8080/api/s3/config/active \
-H "Authorization: Bearer YOUR_TOKEN"
```
### 2. 测试文件上传
```bash
curl -X POST http://localhost:8080/api/file/upload \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "files=@test.pdf" \
-F "relatedType=task" \
-F "relatedId=1"
```
### 3. 测试文件下载
```bash
curl -X GET http://localhost:8080/api/file/download/1 \
-H "Authorization: Bearer YOUR_TOKEN"
```
---
## 📌 注意事项
1. **S3配置安全**:
- AccessKey和SecretKey存储在数据库中,需要加密存储(可选)
- 管理界面只对管理员开放
2. **文件大小限制**:
- 需要在Nginx和Spring Boot中配置文件大小限制
- 建议单文件不超过100MB
3. **文件命名**:
- 使用时间戳+UUID避免文件名冲突
- 保留原始文件名用于显示
4. **错误处理**:
- S3服务不可用时给出友好提示
- 上传失败后清理临时文件
5. **性能优化**:
- 使用S3的预签名URL实现直传(可选)
- 大文件使用分片上传(可选)
---
## 🎯 后续优化建议
1. **直传优化**:前端直接上传到S3,后端只生成签名
2. **CDN加速**:配置CloudFlare等CDN加速文件访问
3. **图片处理**:集成图片压缩和格式转换
4. **文件预览**:支持PDF、图片等文件在线预览
5. **批量操作**:支持批量删除和批量下载
6. **存储统计**:显示存储空间使用情况
---
## ✅ 完成清单
- [x] 数据库表设计
- [x] 后端实体类和Mapper
- [x] S3Service核心服务
- [x] S3ConfigService配置管理
- [x] FileUploadController上传下载
- [x] S3ConfigController配置管理
- [ ] 前端S3配置管理页面
- [ ] 前端文件上传组件改造
- [ ] 系统测试和部署

195
S3部署说明.md Normal file
View File

@ -0,0 +1,195 @@
# S3文件存储系统 - 部署说明
## 📋 部署步骤
### 第一步:上传文件到服务器
在**本地Mac**上执行以下命令,将所有新文件上传到服务器:
```bash
# 设置变量
SERVER="root@192.168.150.22"
BACKEND_DIR="/root/designhub-backend" # 请根据实际情况修改
# 1. 上传实体类
scp backend/src/main/java/com/designhub/entity/S3Config.java $SERVER:$BACKEND_DIR/src/main/java/com/designhub/entity/
scp backend/src/main/java/com/designhub/entity/FileUpload.java $SERVER:$BACKEND_DIR/src/main/java/com/designhub/entity/
# 2. 上传Mapper
scp backend/src/main/java/com/designhub/mapper/S3ConfigMapper.java $SERVER:$BACKEND_DIR/src/main/java/com/designhub/mapper/
scp backend/src/main/java/com/designhub/mapper/FileUploadMapper.java $SERVER:$BACKEND_DIR/src/main/java/com/designhub/mapper/
# 3. 上传Service
scp backend/src/main/java/com/designhub/service/S3Service.java $SERVER:$BACKEND_DIR/src/main/java/com/designhub/service/
scp backend/src/main/java/com/designhub/service/S3ConfigService.java $SERVER:$BACKEND_DIR/src/main/java/com/designhub/service/
# 4. 上传Controller
scp backend/src/main/java/com/designhub/controller/S3ConfigController.java $SERVER:$BACKEND_DIR/src/main/java/com/designhub/controller/
scp backend/src/main/java/com/designhub/controller/FileUploadController.java $SERVER:$BACKEND_DIR/src/main/java/com/designhub/controller/
# 5. 上传pom.xml(已包含AWS SDK依赖)
scp backend/pom.xml $SERVER:$BACKEND_DIR/
```
### 第二步:在服务器上执行数据库脚本
```bash
# SSH登录到服务器
ssh root@192.168.150.22
# 执行数据库脚本
mysql -uroot -p'Abc123456' designhub < /path/to/create_s3_config.sql
# 或者直接执行SQL(如果文件已上传)
mysql -uroot -p'Abc123456' designhub << EOF
-- 执行 create_s3_config.sql 中的内容
EOF
```
### 第三步:编译后端
```bash
# 在服务器上
cd /root/designhub-backend # 根据实际路径修改
# 清理并编译
mvn clean package -DskipTests
```
### 第四步:部署和重启
```bash
# 1. 备份当前JAR
cp /path/to/designhub-backend.jar /path/to/designhub-backend.jar.backup
# 2. 复制新JAR(根据实际路径)
cp target/designhub-backend-*.jar /path/to/designhub-backend.jar
# 3. 重启服务
systemctl restart designhub-backend
# 4. 检查状态
systemctl status designhub-backend
# 5. 查看日志
journalctl -u designhub-backend -f
```
---
## 🔍 验证部署
### 1. 检查数据库表
```sql
-- 检查S3配置表
SELECT * FROM sys_s3_config;
-- 检查文件上传表
SHOW CREATE TABLE sys_file_upload;
```
### 2. 测试API
```bash
# 获取激活的S3配置(需要token)
curl -X GET http://localhost:8080/api/s3/config/active \
-H "Authorization: Bearer YOUR_TOKEN"
# 上传文件测试(需要token)
curl -X POST http://localhost:8080/api/file/upload \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "files=@test.pdf" \
-F "relatedType=task"
```
---
## 📝 文件清单
需要上传的文件:
1. **实体类** (Entity)
- `S3Config.java`
- `FileUpload.java`
2. **Mapper接口**
- `S3ConfigMapper.java`
- `FileUploadMapper.java`
3. **服务类** (Service)
- `S3Service.java`
- `S3ConfigService.java`
4. **控制器** (Controller)
- `S3ConfigController.java`
- `FileUploadController.java`
5. **配置文件**
- `pom.xml` (已添加AWS SDK依赖)
6. **数据库脚本**
- `database/create_s3_config.sql`
---
## ⚠️ 注意事项
1. **路径确认**:请确认服务器上的后端代码路径,可能需要修改 `BACKEND_DIR` 变量
2. **数据库密码**:如果数据库密码不是 `Abc123456`,请修改脚本中的密码
3. **Maven依赖**:确保 `pom.xml` 已包含以下依赖:
```xml
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>2.20.26</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
```
4. **JwtUtil注入**:`FileUploadController` 需要注入 `JwtUtil`,确保 `JwtUtil` 是 `@Component`
5. **S3配置**:部署后需要在数据库中激活S3配置,默认配置已插入但需要确认
---
## 🐛 常见问题
### 问题1:编译失败 - 找不到AWS SDK类
**解决**:检查 `pom.xml` 是否包含AWS SDK依赖,然后执行 `mvn clean install`
### 问题2:运行时错误 - S3配置未激活
**解决**:检查数据库中的 `sys_s3_config` 表,确保有一条 `is_active=1` 的记录
### 问题3:文件上传失败 - 连接S3超时
**解决**:
- 检查S3服务端点是否可访问
- 检查AccessKey和SecretKey是否正确
- 检查网络连接和防火墙设置
---
## ✅ 部署完成检查清单
- [ ] 所有Java文件已上传到服务器
- [ ] `pom.xml` 已更新并包含AWS SDK依赖
- [ ] 数据库脚本已执行,表已创建
- [ ] 后端编译成功,无错误
- [ ] JAR文件已部署到正确位置
- [ ] 服务已重启
- [ ] 服务状态正常(`systemctl status`)
- [ ] 日志无错误信息
- [ ] API测试通过
---
部署完成后,请告诉我,我将继续完成前端的S3配置管理界面!

BIN
backend/backend-src.tar.gz Normal file

Binary file not shown.

BIN
backend/backend_src.tar.gz Normal file

Binary file not shown.

View File

@ -0,0 +1,15 @@
<!-- 将以下依赖添加到 pom.xml 的 <dependencies> 部分 -->
<!-- AWS SDK for S3 -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>2.20.26</version>
</dependency>
<!-- Apache Commons IO for file utilities -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>

205
backend/pom.xml Normal file
View File

@ -0,0 +1,205 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.6</version>
<relativePath/>
</parent>
<groupId>com.designhub</groupId>
<artifactId>designhub-backend</artifactId>
<version>1.0.0</version>
<name>DesignHub Backend</name>
<description>设计师任务抢单管理系统 - 后端服务</description>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mybatis-plus.version>3.5.7</mybatis-plus.version>
<knife4j.version>4.3.0</knife4j.version>
<jwt.version>0.12.3</jwt.version>
<hutool.version>5.8.24</hutool.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Spring Boot AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Spring Boot Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jwt.version}</version>
<scope>runtime</scope>
</dependency>
<!-- Spring Security (仅用于BCryptPasswordEncoder) -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<!-- Knife4j (Swagger UI) -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!-- Hutool 工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 钉钉 SDK -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dingtalk</artifactId>
<version>2.1.67</version>
</dependency>
<!-- Quartz 任务调度 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!-- Apache Commons Lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- Apache Commons Collections -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
<!-- Apache POI (Excel操作) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
<!-- AWS SDK for S3 (MinIO compatible) -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>2.20.26</version>
</dependency>
<!-- Apache Commons IO -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,36 @@
package com.designhub;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* DesignHub 主应用类
* 设计师任务抢单管理系统
*
* @author DesignHub Team
* @version 1.0.0
*/
@SpringBootApplication
@MapperScan("com.designhub.mapper")
@EnableScheduling
public class DesignHubApplication {
public static void main(String[] args) {
SpringApplication.run(DesignHubApplication.class, args);
System.out.println("""
╔═══════════════════════════════════════════════════════╗
║ ║
║ 🎨 DesignHub Backend Service Started 🎨 ║
║ ║
║ Port: 8080 ║
║ Swagger UI: http://localhost:8080/api/doc.html ║
║ Version: 1.0.0 ║
║ ║
╚═══════════════════════════════════════════════════════╝
""");
}
}

View File

@ -0,0 +1,30 @@
package com.designhub.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 权限验证注解
* 用于Controller方法上,标记需要的权限
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {
/**
* 权限代码
*/
String[] value() default {};
/**
* 权限匹配模式:AND-需要所有权限,OR-需要任一权限
*/
LogicalMode mode() default LogicalMode.AND;
enum LogicalMode {
AND, OR
}
}

View File

@ -0,0 +1,30 @@
package com.designhub.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 角色验证注解
* 用于Controller方法上,标记需要的角色
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireRole {
/**
* 角色代码
*/
String[] value() default {};
/**
* 角色匹配模式:AND-需要所有角色,OR-需要任一角色
*/
LogicalMode mode() default LogicalMode.OR;
enum LogicalMode {
AND, OR
}
}

View File

@ -0,0 +1,110 @@
package com.designhub.common;
import lombok.Data;
import java.io.Serializable;
/**
* 统一响应结果类
*
* @param <T> 数据类型
*/
@Data
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 响应码
*/
private Integer code;
/**
* 响应消息
*/
private String message;
/**
* 响应数据
*/
private T data;
/**
* 时间戳
*/
private Long timestamp;
public Result() {
this.timestamp = System.currentTimeMillis();
}
public Result(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
this.timestamp = System.currentTimeMillis();
}
/**
* 成功响应
*/
public static <T> Result<T> success() {
return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), null);
}
/**
* 成功响应(带数据)
*/
public static <T> Result<T> success(T data) {
return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
}
/**
* 成功响应(自定义消息)
*/
public static <T> Result<T> success(String message, T data) {
return new Result<>(ResultCode.SUCCESS.getCode(), message, data);
}
/**
* 失败响应
*/
public static <T> Result<T> error() {
return new Result<>(ResultCode.ERROR.getCode(), ResultCode.ERROR.getMessage(), null);
}
/**
* 失败响应(自定义消息)
*/
public static <T> Result<T> error(String message) {
return new Result<>(ResultCode.ERROR.getCode(), message, null);
}
/**
* 失败响应(自定义码和消息)
*/
public static <T> Result<T> error(Integer code, String message) {
return new Result<>(code, message, null);
}
/**
* 失败响应(使用ResultCode)
*/
public static <T> Result<T> error(ResultCode resultCode) {
return new Result<>(resultCode.getCode(), resultCode.getMessage(), null);
}
/**
* 失败响应(自定义码、消息和数据)
*/
public static <T> Result<T> error(Integer code, String message, T data) {
return new Result<>(code, message, data);
}
/**
* 失败响应(别名方法)
*/
public static <T> Result<T> fail(String message) {
return error(message);
}
}

View File

@ -0,0 +1,59 @@
package com.designhub.common;
import lombok.Getter;
/**
* 响应状态码枚举
*/
@Getter
public enum ResultCode {
// 成功
SUCCESS(200, "操作成功"),
// 客户端错误 4xx
BAD_REQUEST(400, "请求参数错误"),
PARAM_ERROR(400, "参数错误"),
UNAUTHORIZED(401, "未授权,请登录"),
FORBIDDEN(403, "没有权限访问该资源"),
NOT_FOUND(404, "请求的资源不存在"),
METHOD_NOT_ALLOWED(405, "请求方法不支持"),
CONFLICT(409, "数据冲突"),
// 服务器错误 5xx
ERROR(500, "服务器内部错误"),
SERVICE_UNAVAILABLE(503, "服务暂时不可用"),
// 业务错误 1xxx
USER_NOT_EXIST(1001, "用户不存在"),
USER_ALREADY_EXIST(1002, "用户已存在"),
PASSWORD_ERROR(1003, "密码错误"),
USER_DISABLED(1004, "用户已被禁用"),
TOKEN_EXPIRED(1005, "Token已过期"),
TOKEN_INVALID(1006, "Token无效"),
TASK_NOT_EXIST(1101, "任务不存在"),
TASK_ALREADY_TAKEN(1102, "任务已被抢单"),
INSUFFICIENT_SCORE(1103, "积分不足"),
TASK_STATUS_ERROR(1104, "任务状态错误"),
DEPT_NOT_EXIST(1201, "部门不存在"),
DEPT_TYPE_ERROR(1202, "部门类型错误"),
SCORE_LIMIT_EXCEEDED(1203, "超出积分消耗限制"),
FILE_UPLOAD_ERROR(1301, "文件上传失败"),
FILE_TYPE_ERROR(1302, "文件类型不支持"),
FILE_SIZE_ERROR(1303, "文件大小超出限制"),
DINGTALK_AUTH_ERROR(1401, "钉钉认证失败"),
DINGTALK_API_ERROR(1402, "钉钉接口调用失败");
private final Integer code;
private final String message;
ResultCode(Integer code, String message) {
this.code = code;
this.message = message;
}
}

View File

@ -0,0 +1,40 @@
package com.designhub.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* 跨域配置
*/
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// 允许所有域名进行跨域调用(生产环境需要具体指定)
config.addAllowedOriginPattern("*");
// 允许所有请求头
config.addAllowedHeader("*");
// 允许所有请求方法
config.addAllowedMethod("*");
// 允许携带凭证
config.setAllowCredentials(true);
// 暴露哪些头信息
config.addExposedHeader("Authorization");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}

View File

@ -0,0 +1,32 @@
package com.designhub.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Knife4j (Swagger) 配置
* 访问地址:http://localhost:8080/api/doc.html
*/
@Configuration
public class Knife4jConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("DesignHub API 文档")
.description("设计师任务抢单管理系统 RESTful API 接口文档")
.version("v1.0.0")
.contact(new Contact()
.name("DesignHub Team")
.email("support@designhub.com"))
.license(new License()
.name("MIT License")
.url("https://opensource.org/licenses/MIT")));
}
}

View File

@ -0,0 +1,29 @@
package com.designhub.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MyBatis Plus 配置类
*/
@Configuration
public class MybatisPlusConfig {
/**
* MyBatis Plus 分页插件配置
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInnerInterceptor.setMaxLimit(1000L);
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
}

View File

@ -0,0 +1,31 @@
package com.designhub.config;
import com.designhub.service.SystemConfigService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
/**
* 系统配置初始化器
* 在应用启动时自动初始化默认配置
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class SystemConfigInitializer implements ApplicationRunner {
private final SystemConfigService systemConfigService;
@Override
public void run(ApplicationArguments args) {
try {
log.info("开始初始化系统默认配置...");
systemConfigService.initDefaultConfigs();
log.info("系统默认配置初始化完成");
} catch (Exception e) {
log.error("系统默认配置初始化失败", e);
}
}
}

View File

@ -0,0 +1,38 @@
package com.designhub.config;
import com.designhub.interceptor.PermissionInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Web配置类
*/
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final PermissionInterceptor permissionInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(permissionInterceptor)
.addPathPatterns("/api/**") // 拦截所有API请求
.excludePathPatterns(
"/api/auth/**", // 排除认证相关接口(包含注册)
"/api/departments", // 排除部门列表接口(注册页面需要)
"/api/hello", // 排除测试接口
"/api/system/health", // 排除健康检查接口
"/api/system-config/group/site", // 排除公开的网站设置API(登录页面需要)
"/api/system-config/key/site.name", // 排除公开的网站设置API(按key获取)
"/api/system-config/key/site.logo", // 排除公开的网站设置API(按key获取)
"/api/system-config/map/group/site", // 排除公开的网站设置API(Map格式)
"/doc.html", // 排除Swagger文档
"/swagger-ui/**",
"/v3/api-docs/**",
"/webjars/**"
);
}
}

View File

@ -0,0 +1,53 @@
package com.designhub.controller;
import com.designhub.common.Result;
import com.designhub.entity.Achievement;
import com.designhub.entity.UserAchievement;
import com.designhub.service.AchievementService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 成就系统Controller
*/
@Tag(name = "成就系统", description = "成就徽章相关API")
@RestController
@RequestMapping("/api/achievements")
@RequiredArgsConstructor
public class AchievementController {
private final AchievementService achievementService;
@Operation(summary = "获取所有成就列表")
@GetMapping
public Result<List<Achievement>> getAllAchievements() {
List<Achievement> achievements = achievementService.getAllAchievements();
return Result.success(achievements);
}
@Operation(summary = "获取用户已达成的成就")
@GetMapping("/user/{userId}")
public Result<List<UserAchievement>> getUserAchievements(@PathVariable Long userId) {
List<UserAchievement> achievements = achievementService.getUserAchievements(userId);
return Result.success(achievements);
}
@Operation(summary = "领取成就奖励")
@PostMapping("/{achievementId}/claim")
public Result<Boolean> claimReward(@PathVariable Long achievementId, @RequestParam Long userId) {
boolean success = achievementService.claimAchievementReward(userId, achievementId);
return Result.success(success);
}
@Operation(summary = "初始化默认成就")
@PostMapping("/init")
public Result<Void> initDefaultAchievements() {
achievementService.initDefaultAchievements();
return Result.success(null);
}
}

View File

@ -0,0 +1,155 @@
package com.designhub.controller;
import com.designhub.common.Result;
import com.designhub.entity.Task;
import com.designhub.service.AdminTaskService;
import com.designhub.util.JwtUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 管理员任务调配Controller
*/
@Tag(name = "管理员任务管理", description = "管理员任务调配相关API")
@RestController
@RequestMapping("/api/admin/tasks")
@RequiredArgsConstructor
public class AdminTaskController {
private final AdminTaskService adminTaskService;
private final JwtUtil jwtUtil;
@Operation(summary = "分配任务")
@PostMapping("/{taskId}/assign")
public Result<Task> assignTask(
@PathVariable Long taskId,
@RequestParam Long designerId,
@RequestHeader("Authorization") String authHeader
) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long adminId = jwtUtil.getUserIdFromToken(token);
Task task = adminTaskService.assignTask(taskId, designerId, adminId);
return Result.success(task);
}
@Operation(summary = "转移任务")
@PostMapping("/{taskId}/transfer")
public Result<Task> transferTask(
@PathVariable Long taskId,
@RequestBody TransferRequest request,
@RequestHeader("Authorization") String authHeader
) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long adminId = jwtUtil.getUserIdFromToken(token);
Task task = adminTaskService.transferTask(
taskId,
request.getNewDesignerId(),
adminId,
request.getReason()
);
return Result.success(task);
}
@Operation(summary = "批量分配任务")
@PostMapping("/batch-assign")
public Result<Integer> batchAssignTasks(
@RequestBody BatchAssignRequest request,
@RequestHeader("Authorization") String authHeader
) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long adminId = jwtUtil.getUserIdFromToken(token);
int count = adminTaskService.batchAssignTasks(
request.getTaskIds(),
request.getDesignerId(),
adminId
);
return Result.success(count);
}
@Operation(summary = "调整任务优先级")
@PutMapping("/{taskId}/priority")
public Result<Task> adjustTaskPriority(
@PathVariable Long taskId,
@RequestParam Integer priority,
@RequestHeader("Authorization") String authHeader
) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long adminId = jwtUtil.getUserIdFromToken(token);
Task task = adminTaskService.adjustTaskPriority(taskId, priority, adminId);
return Result.success(task);
}
@Operation(summary = "获取任务池列表")
@GetMapping("/pool")
public Result<List<Task>> getTaskPool() {
List<Task> tasks = adminTaskService.getTaskPool();
return Result.success(tasks);
}
@Operation(summary = "获取所有进行中的任务")
@GetMapping("/in-progress")
public Result<List<Task>> getInProgressTasks() {
List<Task> tasks = adminTaskService.getInProgressTasks();
return Result.success(tasks);
}
@Operation(summary = "强制完成任务")
@PostMapping("/{taskId}/force-complete")
public Result<Task> forceCompleteTask(
@PathVariable Long taskId,
@RequestBody AdminActionRequest request,
@RequestHeader("Authorization") String authHeader
) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long adminId = jwtUtil.getUserIdFromToken(token);
Task task = adminTaskService.forceCompleteTask(taskId, adminId, request.getReason());
return Result.success(task);
}
@Operation(summary = "取消任务")
@PostMapping("/{taskId}/cancel")
public Result<Task> cancelTask(
@PathVariable Long taskId,
@RequestBody AdminActionRequest request,
@RequestHeader("Authorization") String authHeader
) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long adminId = jwtUtil.getUserIdFromToken(token);
Task task = adminTaskService.cancelTask(taskId, adminId, request.getReason());
return Result.success(task);
}
// DTO类
public static class TransferRequest {
private Long newDesignerId;
private String reason;
public Long getNewDesignerId() { return newDesignerId; }
public void setNewDesignerId(Long newDesignerId) { this.newDesignerId = newDesignerId; }
public String getReason() { return reason; }
public void setReason(String reason) { this.reason = reason; }
}
public static class BatchAssignRequest {
private List<Long> taskIds;
private Long designerId;
public List<Long> getTaskIds() { return taskIds; }
public void setTaskIds(List<Long> taskIds) { this.taskIds = taskIds; }
public Long getDesignerId() { return designerId; }
public void setDesignerId(Long designerId) { this.designerId = designerId; }
}
public static class AdminActionRequest {
private String reason;
public String getReason() { return reason; }
public void setReason(String reason) { this.reason = reason; }
}
}

View File

@ -0,0 +1,44 @@
package com.designhub.controller;
import com.designhub.common.Result;
import com.designhub.service.AnalyticsService;
import com.designhub.util.JwtUtil;
import com.designhub.vo.DemandStatisticsVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* 数据分析Controller
*/
@Slf4j
@Tag(name = "数据分析", description = "数据分析相关API")
@RestController
@RequestMapping("/api/analytics")
@RequiredArgsConstructor
public class AnalyticsController {
private final AnalyticsService analyticsService;
private final JwtUtil jwtUtil;
@Operation(summary = "获取需求统计数据(根据角色返回不同维度的数据)")
@GetMapping("/demand-statistics")
public Result<DemandStatisticsVO> getDemandStatistics(
@RequestHeader("Authorization") String authHeader,
@RequestParam(required = false, defaultValue = "month") String dateRange,
@RequestParam(required = false) Long deptId,
@RequestParam(required = false) Long userId
) {
// 从token获取当前用户ID
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long currentUserId = jwtUtil.getUserIdFromToken(token);
log.info("获取需求统计数据: currentUserId={}, dateRange={}, deptId={}, userId={}",
currentUserId, dateRange, deptId, userId);
return Result.success(analyticsService.getDemandStatistics(currentUserId, dateRange, deptId, userId));
}
}

View File

@ -0,0 +1,130 @@
package com.designhub.controller;
import com.designhub.common.Result;
import com.designhub.dto.LoginDTO;
import com.designhub.dto.RegisterDTO;
import com.designhub.entity.User;
import com.designhub.exception.BusinessException;
import com.designhub.service.UserService;
import com.designhub.util.JwtUtil;
import com.designhub.vo.LoginVO;
import com.designhub.vo.UserInfoVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* 认证控制器
*/
@Slf4j
@Tag(name = "用户认证", description = "用户登录、登出、刷新令牌等接口")
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final UserService userService;
private final JwtUtil jwtUtil;
@Operation(summary = "用户注册", description = "新用户注册")
@PostMapping("/register")
public Result<User> register(@Valid @RequestBody RegisterDTO registerDTO) {
log.info("用户注册请求: {}", registerDTO.getUsername());
User user = userService.register(registerDTO);
return Result.success(user);
}
@Operation(summary = "用户登录", description = "支持密码登录、钉钉登录、Casdoor登录")
@PostMapping("/login")
public Result<LoginVO> login(@Valid @RequestBody LoginDTO loginDTO) {
log.info("用户登录请求: {}", loginDTO.getUsername());
LoginVO loginVO = userService.login(loginDTO);
return Result.success(loginVO);
}
@Operation(summary = "获取当前用户信息", description = "根据Token获取当前登录用户信息")
@GetMapping("/userinfo")
public Result<UserInfoVO> getUserInfo(@RequestHeader(value = "Authorization", required = false) String authHeader) {
log.info("获取用户信息");
String token = jwtUtil.extractTokenFromHeader(authHeader);
UserInfoVO userInfo = userService.getUserInfoByToken(token);
return Result.success(userInfo);
}
@Operation(summary = "用户登出", description = "退出登录,清除Token")
@PostMapping("/logout")
public Result<String> logout(@RequestHeader(value = "Authorization", required = false) String authHeader) {
log.info("用户登出");
// Token是无状态的,前端删除即可
// 如果需要黑名单机制,可以在Redis中记录
return Result.success("登出成功");
}
@Operation(summary = "刷新令牌", description = "使用刷新令牌获取新的访问令牌")
@PostMapping("/refresh")
public Result<LoginVO> refresh(@RequestHeader("Authorization") String authHeader) {
log.info("刷新令牌");
LoginVO loginVO = userService.refreshToken(authHeader);
return Result.success(loginVO);
}
@Operation(summary = "钉钉扫码登录回调", description = "接收钉钉扫码登录的回调 - 使用OAuth 2.0流程")
@GetMapping("/dingtalk/callback")
@SuppressWarnings("unchecked")
public Result<LoginVO> dingtalkCallback(
@RequestParam String code,
@RequestParam(required = false) String state) {
log.info("钉钉登录回调 - code: {}, state: {}",
code.substring(0, Math.min(10, code.length())) + "...", state);
// 使用新的OAuth 2.0流程登录
LoginDTO loginDTO = new LoginDTO();
loginDTO.setLoginType("dingtalk");
loginDTO.setAuthCode(code);
try {
LoginVO loginVO = userService.login(loginDTO);
log.info("钉钉登录成功: {}", loginVO.getUsername());
return Result.success(loginVO);
} catch (BusinessException e) {
// 检查是否需要选择部门
if (e.getMessage() != null && e.getMessage().startsWith("NEED_SELECT_DEPT:")) {
String[] parts = e.getMessage().split(":");
Map<String, Object> data = new HashMap<>();
data.put("needSelectDept", true);
data.put("userId", parts[1]);
data.put("dingDepartments", parts.length > 2 ? parts[2] : "");
Result<Map<String, Object>> result = Result.error(1901, "需要选择部门", data);
return (Result<LoginVO>) (Result<?>) result;
}
log.error("钉钉登录失败", e);
return Result.error(e.getMessage());
} catch (Exception e) {
log.error("钉钉登录失败", e);
return Result.error("钉钉登录失败: " + e.getMessage());
}
}
@Operation(summary = "钉钉用户选择部门", description = "首次登录时选择系统部门")
@PostMapping("/dingtalk/select-department")
public Result<LoginVO> selectDepartment(
@RequestParam Long userId,
@RequestParam Long deptId) {
log.info("钉钉用户选择部门: userId={}, deptId={}", userId, deptId);
try {
LoginVO loginVO = userService.completeDingTalkUserSetup(userId, deptId);
return Result.success(loginVO);
} catch (Exception e) {
log.error("选择部门失败", e);
return Result.error("选择部门失败: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,84 @@
package com.designhub.controller;
import com.designhub.common.Result;
import com.designhub.entity.CheckIn;
import com.designhub.service.CheckInService;
import com.designhub.util.JwtUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 签到Controller
*/
@Slf4j
@Tag(name = "签到系统", description = "每日签到相关API")
@RestController
@RequestMapping("/api/check-in")
@RequiredArgsConstructor
public class CheckInController {
private final CheckInService checkInService;
private final JwtUtil jwtUtil;
@Operation(summary = "用户签到")
@PostMapping
public Result<CheckIn> checkIn(@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
if (userId == null) {
return Result.error(401, "未登录或登录已过期");
}
CheckIn checkIn = checkInService.checkIn(userId);
return Result.success(checkIn);
}
@Operation(summary = "检查今天是否已签到")
@GetMapping("/today")
public Result<Boolean> hasCheckedInToday(@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
if (userId == null) {
return Result.error(401, "未登录或登录已过期");
}
boolean checked = checkInService.hasCheckedInToday(userId);
return Result.success(checked);
}
@Operation(summary = "获取签到历史记录")
@GetMapping("/history")
public Result<List<CheckIn>> getCheckInHistory(
@RequestHeader("Authorization") String authHeader,
@RequestParam(defaultValue = "30") Integer limit
) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
if (userId == null) {
return Result.error(401, "未登录或登录已过期");
}
List<CheckIn> history = checkInService.getUserCheckInHistory(userId, limit);
return Result.success(history);
}
@Operation(summary = "获取签到统计信息")
@GetMapping("/stats")
public Result<Map<String, Object>> getCheckInStats(@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
if (userId == null) {
return Result.error(401, "未登录或登录已过期");
}
Map<String, Object> stats = new HashMap<>();
stats.put("continuousDays", checkInService.getContinuousDays(userId));
stats.put("totalDays", checkInService.getTotalCheckInDays(userId));
stats.put("hasCheckedInToday", checkInService.hasCheckedInToday(userId));
return Result.success(stats);
}
}

View File

@ -0,0 +1,271 @@
package com.designhub.controller;
import com.designhub.common.Result;
import com.designhub.entity.*;
import com.designhub.service.ConfigService;
import com.designhub.service.DemandTypeDepartmentService;
import com.designhub.service.DepartmentService;
import com.designhub.service.TeamScorePoolService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 配置管理Controller
*/
@Tag(name = "配置管理", description = "系统配置相关API")
@RestController
@RequestMapping("/api/config")
@RequiredArgsConstructor
public class ConfigController {
private final ConfigService configService;
private final DemandTypeDepartmentService demandTypeDepartmentService;
private final TeamScorePoolService teamScorePoolService;
private final DepartmentService departmentService;
// ============ 需求类型管理 ============
@Operation(summary = "获取所有需求类型(包括禁用的,用于管理页面)")
@GetMapping("/demand-types")
public Result<List<DemandType>> getAllDemandTypes() {
return Result.success(configService.getAllDemandTypes());
}
@Operation(summary = "获取所有启用的需求类型(用于下拉选择)")
@GetMapping("/demand-types/enabled")
public Result<List<DemandType>> getEnabledDemandTypes() {
return Result.success(configService.getEnabledDemandTypes());
}
@Operation(summary = "根据ID获取需求类型")
@GetMapping("/demand-types/{id:\\d+}")
public Result<DemandType> getDemandTypeById(@PathVariable Long id) {
return Result.success(configService.getDemandTypeById(id));
}
@Operation(summary = "创建需求类型")
@PostMapping("/demand-types")
public Result<DemandType> createDemandType(@RequestBody DemandType demandType) {
return Result.success(configService.createDemandType(demandType));
}
@Operation(summary = "更新需求类型")
@PutMapping("/demand-types/{id:\\d+}")
public Result<Boolean> updateDemandType(@PathVariable Long id, @RequestBody DemandType demandType) {
demandType.setId(id);
return Result.success(configService.updateDemandType(demandType));
}
@Operation(summary = "删除需求类型")
@DeleteMapping("/demand-types/{id:\\d+}")
public Result<Boolean> deleteDemandType(@PathVariable Long id) {
return Result.success(configService.deleteDemandType(id));
}
@Operation(summary = "设置需求类型的可接单部门")
@PutMapping("/demand-types/{id:\\d+}/departments")
public Result<Boolean> setDemandTypeDepartments(
@PathVariable Long id,
@RequestBody List<Long> departmentIds) {
return Result.success(demandTypeDepartmentService.setDemandTypeDepartments(id, departmentIds));
}
@Operation(summary = "获取需求类型的可接单部门列表")
@GetMapping("/demand-types/{id:\\d+}/departments")
public Result<List<Long>> getDemandTypeDepartments(@PathVariable Long id) {
return Result.success(demandTypeDepartmentService.getDepartmentIdsByDemandType(id));
}
// ============ 品牌管理 ============
@Operation(summary = "获取所有品牌(包括禁用的,用于管理页面)")
@GetMapping("/brands")
public Result<List<Brand>> getAllBrands() {
return Result.success(configService.getAllBrands());
}
@Operation(summary = "获取所有启用的品牌(用于下拉选择)")
@GetMapping("/brands/enabled")
public Result<List<Brand>> getEnabledBrands() {
return Result.success(configService.getEnabledBrands());
}
@Operation(summary = "根据ID获取品牌")
@GetMapping("/brands/{id:\\d+}")
public Result<Brand> getBrandById(@PathVariable Long id) {
return Result.success(configService.getBrandById(id));
}
@Operation(summary = "创建品牌")
@PostMapping("/brands")
public Result<Brand> createBrand(@RequestBody Brand brand) {
return Result.success(configService.createBrand(brand));
}
@Operation(summary = "更新品牌")
@PutMapping("/brands/{id:\\d+}")
public Result<Boolean> updateBrand(@PathVariable Long id, @RequestBody Brand brand) {
brand.setId(id);
return Result.success(configService.updateBrand(brand));
}
@Operation(summary = "删除品牌")
@DeleteMapping("/brands/{id:\\d+}")
public Result<Boolean> deleteBrand(@PathVariable Long id) {
return Result.success(configService.deleteBrand(id));
}
// ============ 平台管理 ============
@Operation(summary = "获取所有平台(包括禁用的,用于管理页面)")
@GetMapping("/platforms")
public Result<List<Platform>> getAllPlatforms() {
return Result.success(configService.getAllPlatforms());
}
@Operation(summary = "获取所有启用的平台(用于下拉选择)")
@GetMapping("/platforms/enabled")
public Result<List<Platform>> getEnabledPlatforms() {
return Result.success(configService.getEnabledPlatforms());
}
@Operation(summary = "根据ID获取平台")
@GetMapping("/platforms/{id:\\d+}")
public Result<Platform> getPlatformById(@PathVariable Long id) {
return Result.success(configService.getPlatformById(id));
}
@Operation(summary = "创建平台")
@PostMapping("/platforms")
public Result<Platform> createPlatform(@RequestBody Platform platform) {
return Result.success(configService.createPlatform(platform));
}
@Operation(summary = "更新平台")
@PutMapping("/platforms/{id:\\d+}")
public Result<Boolean> updatePlatform(@PathVariable Long id, @RequestBody Platform platform) {
platform.setId(id);
return Result.success(configService.updatePlatform(platform));
}
@Operation(summary = "删除平台")
@DeleteMapping("/platforms/{id:\\d+}")
public Result<Boolean> deletePlatform(@PathVariable Long id) {
return Result.success(configService.deletePlatform(id));
}
// ============ 类目管理 ============
@Operation(summary = "获取所有叶子类目(包括禁用的,用于管理页面)")
@GetMapping("/categories/leaf")
public Result<List<Category>> getAllLeafCategories() {
return Result.success(configService.getAllLeafCategories());
}
@Operation(summary = "获取所有启用的叶子类目(用于下拉选择)")
@GetMapping("/categories/leaf/enabled")
public Result<List<Category>> getEnabledLeafCategories() {
return Result.success(configService.getEnabledLeafCategories());
}
@Operation(summary = "获取所有类目")
@GetMapping("/categories")
public Result<List<Category>> getAllCategories() {
return Result.success(configService.getAllCategories());
}
@Operation(summary = "根据父ID获取子类目")
@GetMapping("/categories/parent/{parentId}")
public Result<List<Category>> getCategoriesByParentId(@PathVariable Long parentId) {
return Result.success(configService.getCategoriesByParentId(parentId));
}
@Operation(summary = "根据ID获取类目")
@GetMapping("/categories/{id:\\d+}")
public Result<Category> getCategoryById(@PathVariable Long id) {
return Result.success(configService.getCategoryById(id));
}
@Operation(summary = "创建类目")
@PostMapping("/categories")
public Result<Category> createCategory(@RequestBody Category category) {
return Result.success(configService.createCategory(category));
}
@Operation(summary = "更新类目")
@PutMapping("/categories/{id:\\d+}")
public Result<Boolean> updateCategory(@PathVariable Long id, @RequestBody Category category) {
category.setId(id);
return Result.success(configService.updateCategory(category));
}
@Operation(summary = "删除类目")
@DeleteMapping("/categories/{id:\\d+}")
public Result<Boolean> deleteCategory(@PathVariable Long id) {
return Result.success(configService.deleteCategory(id));
}
// ============ 团队积分池管理 ============
@Operation(summary = "根据部门ID获取积分池")
@GetMapping("/score-pool/dept/{deptId}")
public Result<TeamScorePool> getScorePoolByDeptId(@PathVariable Long deptId) {
TeamScorePool pool = teamScorePoolService.getByDeptId(deptId);
// 如果积分池不存在,自动创建
if (pool == null) {
var department = departmentService.getDepartmentById(deptId);
if (department != null) {
pool = teamScorePoolService.upsertPool(deptId, department.getDeptName());
} else {
// 部门不存在,返回一个默认的积分池对象(余额为0)
pool = new TeamScorePool();
pool.setDeptId(deptId);
pool.setBalance(0);
}
}
return Result.success(pool);
}
@Operation(summary = "检查积分余额是否足够")
@GetMapping("/score-pool/check-balance")
public Result<Boolean> checkBalance(@RequestParam Long deptId, @RequestParam Integer score) {
return Result.success(teamScorePoolService.checkBalance(deptId, score));
}
@Operation(summary = "检查是否超过周期限制")
@GetMapping("/score-pool/check-limit")
public Result<Boolean> checkPeriodLimit(@RequestParam Long deptId, @RequestParam Integer score) {
return Result.success(teamScorePoolService.checkPeriodLimit(deptId, score));
}
@Operation(summary = "充值积分")
@PostMapping("/score-pool/recharge")
public Result<Void> rechargeScore(@RequestParam Long deptId, @RequestParam Integer score) {
teamScorePoolService.rechargeScore(deptId, score);
return Result.success();
}
@Operation(summary = "创建积分池")
@PostMapping("/score-pool")
public Result<TeamScorePool> createScorePool(@RequestBody TeamScorePool pool) {
return Result.success(teamScorePoolService.createPool(pool));
}
@Operation(summary = "更新积分池配置")
@PutMapping("/score-pool/{id}")
public Result<Boolean> updateScorePool(@PathVariable Long id, @RequestBody TeamScorePool pool) {
pool.setId(id);
return Result.success(teamScorePoolService.updatePool(pool));
}
@Operation(summary = "检查是否需要预警")
@GetMapping("/score-pool/alert/{deptId}")
public Result<Boolean> isAlert(@PathVariable Long deptId) {
return Result.success(teamScorePoolService.isAlert(deptId));
}
}

View File

@ -0,0 +1,122 @@
package com.designhub.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.designhub.common.Result;
import com.designhub.entity.Contest;
import com.designhub.entity.ContestParticipant;
import com.designhub.service.ContestService;
import com.designhub.util.JwtUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 竞赛活动Controller
*/
@Tag(name = "竞赛活动", description = "竞赛活动管理相关API")
@RestController
@RequestMapping("/api/contests")
@RequiredArgsConstructor
public class ContestController {
private final ContestService contestService;
private final JwtUtil jwtUtil;
@Operation(summary = "创建竞赛")
@PostMapping
public Result<Contest> createContest(
@RequestBody Contest contest,
@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
contest.setCreatorId(userId);
return Result.success(contestService.createContest(contest));
}
@Operation(summary = "更新竞赛")
@PutMapping("/{id}")
public Result<Contest> updateContest(@PathVariable Long id, @RequestBody Contest contest) {
return Result.success(contestService.updateContest(id, contest));
}
@Operation(summary = "发布竞赛")
@PostMapping("/{id}/publish")
public Result<Contest> publishContest(@PathVariable Long id) {
return Result.success(contestService.publishContest(id));
}
@Operation(summary = "结束竞赛")
@PostMapping("/{id}/end")
public Result<Void> endContest(@PathVariable Long id) {
contestService.endContest(id);
return Result.success();
}
@Operation(summary = "取消竞赛")
@PostMapping("/{id}/cancel")
public Result<Void> cancelContest(@PathVariable Long id) {
contestService.cancelContest(id);
return Result.success();
}
@Operation(summary = "删除竞赛")
@DeleteMapping("/{id}")
public Result<Void> deleteContest(@PathVariable Long id) {
contestService.deleteContest(id);
return Result.success();
}
@Operation(summary = "获取竞赛列表")
@GetMapping
public Result<IPage<Contest>> getContestList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String status) {
return Result.success(contestService.getContestList(page, pageSize, status));
}
@Operation(summary = "获取竞赛详情")
@GetMapping("/{id}")
public Result<Contest> getContestById(@PathVariable Long id) {
return Result.success(contestService.getContestById(id));
}
@Operation(summary = "获取竞赛排行榜")
@GetMapping("/{id}/leaderboard")
public Result<List<ContestParticipant>> getLeaderboard(
@PathVariable Long id,
@RequestParam(required = false, defaultValue = "50") Integer limit) {
return Result.success(contestService.getLeaderboard(id, limit));
}
@Operation(summary = "获取用户在竞赛中的数据")
@GetMapping("/{contestId}/user/{userId}")
public Result<ContestParticipant> getUserContestData(
@PathVariable Long contestId,
@PathVariable Long userId) {
return Result.success(contestService.getUserContestData(contestId, userId));
}
@Operation(summary = "获取我的竞赛数据")
@GetMapping("/{contestId}/my")
public Result<ContestParticipant> getMyContestData(
@PathVariable Long contestId,
@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
return Result.success(contestService.getUserContestData(contestId, userId));
}
@Operation(summary = "领取竞赛奖励")
@PostMapping("/{contestId}/claim-reward")
public Result<Boolean> claimReward(
@PathVariable Long contestId,
@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
return Result.success(contestService.claimReward(contestId, userId));
}
}

View File

@ -0,0 +1,72 @@
package com.designhub.controller;
import com.designhub.common.Result;
import com.designhub.entity.DelayPenaltyRecord;
import com.designhub.service.DelayPenaltyService;
import com.designhub.util.JwtUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 延误扣分Controller
*/
@Slf4j
@Tag(name = "延误扣分管理", description = "延误扣分和申诉相关API")
@RestController
@RequestMapping("/delay-penalty")
@RequiredArgsConstructor
public class DelayPenaltyController {
private final DelayPenaltyService delayPenaltyService;
private final JwtUtil jwtUtil;
@Operation(summary = "根据任务ID查询延误记录")
@GetMapping("/task/{taskId}")
public Result<List<DelayPenaltyRecord>> getByTaskId(@PathVariable Long taskId) {
return Result.success(delayPenaltyService.getDelayRecordsByTaskId(taskId));
}
@Operation(summary = "根据设计师ID查询延误记录")
@GetMapping("/designer/{designerId}")
public Result<List<DelayPenaltyRecord>> getByDesignerId(@PathVariable Long designerId) {
return Result.success(delayPenaltyService.getDelayRecordsByDesignerId(designerId));
}
@Operation(summary = "统计设计师的延误扣分总数")
@GetMapping("/designer/{designerId}/total")
public Result<Integer> getTotalPenaltyScore(@PathVariable Long designerId) {
return Result.success(delayPenaltyService.getTotalPenaltyScoreByDesignerId(designerId));
}
@Operation(summary = "申诉延误扣分")
@PostMapping("/{recordId}/appeal")
public Result<String> appealDelayPenalty(
@PathVariable Long recordId,
@RequestParam String appealReason,
@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
delayPenaltyService.appealDelayPenalty(recordId, userId, appealReason);
return Result.success("申诉提交成功");
}
@Operation(summary = "管理员审批申诉")
@PostMapping("/{recordId}/approve")
public Result<String> approveAppeal(
@PathVariable Long recordId,
@RequestParam Boolean approved,
@RequestParam(required = false) String appealResult,
@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long approverId = jwtUtil.getUserIdFromToken(token);
delayPenaltyService.approveAppeal(recordId, approverId, approved,
appealResult != null ? appealResult : (approved ? "申诉通过" : "申诉驳回"));
return Result.success(approved ? "申诉已通过" : "申诉已驳回");
}
}

View File

@ -0,0 +1,114 @@
package com.designhub.controller;
import com.designhub.common.Result;
import com.designhub.dto.DepartmentCreateRequest;
import com.designhub.dto.DepartmentLimitSettingsRequest;
import com.designhub.dto.DepartmentScoreAdjustRequest;
import com.designhub.dto.DepartmentUpdateRequest;
import com.designhub.entity.Department;
import com.designhub.entity.TeamScorePool;
import com.designhub.service.DepartmentService;
import com.designhub.service.TeamScorePoolService;
import com.designhub.vo.DepartmentOverviewVO;
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.*;
import java.util.List;
/**
* 部门管理Controller
*/
@Tag(name = "部门管理", description = "部门管理相关API")
@RestController
@RequestMapping("/api/departments")
@RequiredArgsConstructor
public class DepartmentController {
private final DepartmentService departmentService;
private final TeamScorePoolService teamScorePoolService;
@Operation(summary = "获取所有部门列表")
@GetMapping
public Result<List<Department>> getAllDepartments() {
return Result.success(departmentService.getAllDepartments());
}
@Operation(summary = "部门概览(含积分信息)")
@GetMapping("/overview")
public Result<List<DepartmentOverviewVO>> getDepartmentOverview() {
return Result.success(departmentService.getDepartmentOverviewList());
}
@Operation(summary = "获取部门树形结构")
@GetMapping("/tree")
public Result<List<Department>> getDepartmentTree() {
return Result.success(departmentService.getDepartmentTree());
}
@Operation(summary = "根据类型获取部门列表")
@GetMapping("/type/{deptType}")
public Result<List<Department>> getDepartmentsByType(@PathVariable String deptType) {
return Result.success(departmentService.getDepartmentsByType(deptType));
}
@Operation(summary = "根据ID获取部门")
@GetMapping("/{id}")
public Result<Department> getDepartmentById(@PathVariable Long id) {
return Result.success(departmentService.getDepartmentById(id));
}
@Operation(summary = "创建部门")
@PostMapping
public Result<DepartmentOverviewVO> createDepartment(@Valid @RequestBody DepartmentCreateRequest request) {
return Result.success(departmentService.createDepartment(request));
}
@Operation(summary = "更新部门")
@PutMapping("/{id}")
public Result<Boolean> updateDepartment(@PathVariable Long id,
@Valid @RequestBody DepartmentUpdateRequest request) {
return Result.success(departmentService.updateDepartment(id, request));
}
@Operation(summary = "更新部门积分限制配置")
@PutMapping("/{id}/limit")
public Result<Boolean> updateLimitSettings(@PathVariable Long id,
@Valid @RequestBody DepartmentLimitSettingsRequest request) {
return Result.success(departmentService.updateLimitSettings(id, request));
}
@Operation(summary = "删除部门")
@DeleteMapping("/{id}")
public Result<Boolean> deleteDepartment(@PathVariable Long id) {
return Result.success(departmentService.deleteDepartment(id));
}
@Operation(summary = "设置部门负责人/主管")
@PostMapping("/{deptId}/head")
public Result<Boolean> setDepartmentHead(
@PathVariable Long deptId,
@RequestParam Long userId,
@RequestParam String type
) {
return Result.success(departmentService.setDepartmentHead(deptId, userId, type));
}
@Operation(summary = "调整部门积分")
@PostMapping("/{deptId}/score-adjust")
public Result<Void> adjustScore(@PathVariable Long deptId,
@Valid @RequestBody DepartmentScoreAdjustRequest request) {
departmentService.adjustScore(deptId, request);
return Result.success();
}
@Operation(summary = "获取部门积分池详情")
@GetMapping("/{deptId}/score-pool")
public Result<TeamScorePool> getScorePool(@PathVariable Long deptId) {
return Result.success(teamScorePoolService.getByDeptId(deptId));
}
}

View File

@ -0,0 +1,54 @@
package com.designhub.controller;
import com.designhub.common.Result;
import com.designhub.entity.Task;
import com.designhub.service.DesignerService;
import com.designhub.vo.DesignerStatsVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 设计师统计Controller
*/
@Tag(name = "设计师管理", description = "设计师统计和排行榜相关API")
@RestController
@RequestMapping("/api/designers")
@RequiredArgsConstructor
public class DesignerController {
private final DesignerService designerService;
@Operation(summary = "获取所有设计师统计信息")
@GetMapping("/stats")
public Result<List<DesignerStatsVO>> getAllDesignerStats() {
return Result.success(designerService.getAllDesignerStats());
}
@Operation(summary = "获取单个设计师统计信息")
@GetMapping("/stats/{userId}")
public Result<DesignerStatsVO> getDesignerStats(@PathVariable Long userId) {
return Result.success(designerService.getDesignerStats(userId));
}
@Operation(summary = "获取排行榜")
@GetMapping("/leaderboard")
public Result<List<DesignerStatsVO>> getLeaderboard(
@RequestParam(required = false, defaultValue = "score") String type,
@RequestParam(required = false, defaultValue = "10") Integer limit,
@Parameter(description = "月份,格式:YYYY-MM,不传则查询当月") @RequestParam(required = false) String month
) {
return Result.success(designerService.getLeaderboard(type, limit, month));
}
@Operation(summary = "获取设计师进行中的任务列表")
@GetMapping("/{userId}/in-progress-tasks")
public Result<List<Task>> getDesignerInProgressTasks(@PathVariable Long userId) {
return Result.success(designerService.getDesignerInProgressTasks(userId));
}
}

View File

@ -0,0 +1,174 @@
package com.designhub.controller;
import com.designhub.common.Result;
import com.designhub.entity.DingTalkConfig;
import com.designhub.service.DingTalkService;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 钉钉集成 Controller
*/
@Tag(name = "钉钉集成", description = "钉钉集成管理API")
@RestController
@RequestMapping("/api/dingtalk")
@RequiredArgsConstructor
public class DingTalkController {
private final DingTalkService dingTalkService;
private final ObjectMapper objectMapper;
@Operation(summary = "获取钉钉配置")
@GetMapping("/config")
public Result<DingTalkConfig> getConfig() {
DingTalkConfig config = dingTalkService.getConfig();
// 如果没有配置,返回默认配置(仅用于前端显示)
if (config == null) {
config = new DingTalkConfig();
config.setName("默认配置");
config.setEnabled(0);
}
// 隐藏敏感信息
if (config.getAppSecret() != null && !config.getAppSecret().isEmpty()) {
config.setAppSecret("******");
}
if (config.getRobotSecret() != null && !config.getRobotSecret().isEmpty()) {
config.setRobotSecret("******");
}
config.setAccessToken(null);
return Result.success(config);
}
@Operation(summary = "保存钉钉配置")
@PostMapping("/config")
public Result<DingTalkConfig> saveConfig(@RequestBody Map<String, Object> configMap) {
try {
// 处理enabledFeatures字段
Object enabledFeaturesObj = configMap.get("enabledFeatures");
String enabledFeaturesJson = null;
if (enabledFeaturesObj != null) {
if (enabledFeaturesObj instanceof String) {
enabledFeaturesJson = (String) enabledFeaturesObj;
} else {
// 转换为JSON字符串
enabledFeaturesJson = objectMapper.writeValueAsString(enabledFeaturesObj);
}
}
// 构建配置对象
DingTalkConfig config = new DingTalkConfig();
config.setId(configMap.get("id") != null ? ((Number) configMap.get("id")).longValue() : null);
config.setName((String) configMap.get("name"));
config.setAgentId((String) configMap.get("agentId"));
config.setAppKey((String) configMap.get("appKey"));
config.setAppSecret((String) configMap.get("appSecret"));
config.setCorpId((String) configMap.get("corpId"));
config.setRobotWebhook((String) configMap.get("robotWebhook"));
config.setRobotSecret((String) configMap.get("robotSecret"));
config.setEnabled(configMap.get("enabled") != null ? ((Number) configMap.get("enabled")).intValue() : 1);
config.setEnabledFeatures(enabledFeaturesJson);
// 如果前端传来的appSecret是占位符,则保留原有的secret
if ("******".equals(config.getAppSecret())) {
DingTalkConfig existingConfig = dingTalkService.getConfig();
if (existingConfig != null) {
config.setAppSecret(existingConfig.getAppSecret());
}
}
if ("******".equals(config.getRobotSecret())) {
DingTalkConfig existingConfig = dingTalkService.getConfig();
if (existingConfig != null) {
config.setRobotSecret(existingConfig.getRobotSecret());
}
}
DingTalkConfig saved = dingTalkService.saveConfig(config);
// 返回时隐藏敏感信息
if (saved.getAppSecret() != null && !saved.getAppSecret().isEmpty()) {
saved.setAppSecret("******");
}
if (saved.getRobotSecret() != null && !saved.getRobotSecret().isEmpty()) {
saved.setRobotSecret("******");
}
saved.setAccessToken(null);
return Result.success(saved);
} catch (Exception e) {
return Result.error("保存配置失败: " + e.getMessage());
}
}
@Operation(summary = "测试钉钉连接")
@PostMapping("/test")
public Result<Boolean> testConnection(@RequestBody DingTalkConfig config) {
return Result.success(dingTalkService.testConnection(config));
}
@Operation(summary = "发送测试消息")
@PostMapping("/test-message")
public Result<Boolean> sendTestMessage(@RequestBody Map<String, String> params) {
String title = params.getOrDefault("title", "测试消息");
String content = params.getOrDefault("content", "这是一条来自DesignHub的测试消息");
return Result.success(dingTalkService.sendRobotMessage(title, content));
}
@Operation(summary = "获取钉钉部门列表")
@GetMapping("/departments")
public Result<List<Map<String, Object>>> getDepartmentList() {
return Result.success(dingTalkService.getDepartmentList());
}
@Operation(summary = "同步钉钉用户")
@PostMapping("/sync-users")
public Result<Integer> syncUsers() {
return Result.success(dingTalkService.syncUsers());
}
@Operation(summary = "检查功能状态")
@GetMapping("/feature/{feature}")
public Result<Boolean> isFeatureEnabled(@PathVariable String feature) {
return Result.success(dingTalkService.isFeatureEnabled(feature));
}
@Operation(summary = "钉钉OAuth登录发起", description = "生成钉钉登录URL")
@GetMapping("/oauth/login")
public Result<Map<String, String>> initiateLogin(@RequestParam(required = false) String redirectUrl) {
if (!dingTalkService.isConfigured()) {
return Result.error("钉钉登录未配置或未启用,请联系管理员在系统设置中配置");
}
DingTalkConfig config = dingTalkService.getConfig();
// 构建回调URL
String callbackUrl = redirectUrl != null ? redirectUrl : "http://localhost:3000/auth/dingtalk/callback";
// 构建钉钉OAuth URL
String authUrl = String.format(
"https://login.dingtalk.com/oauth2/auth?redirect_uri=%s&response_type=code&client_id=%s&scope=openid&state=STATE&prompt=consent",
java.net.URLEncoder.encode(callbackUrl, java.nio.charset.StandardCharsets.UTF_8),
config.getAppKey()
);
Map<String, String> result = new HashMap<>();
result.put("authUrl", authUrl);
result.put("appKey", config.getAppKey());
return Result.success(result);
}
@Operation(summary = "检查钉钉登录是否已配置")
@GetMapping("/is-configured")
public Result<Boolean> isConfigured() {
return Result.success(dingTalkService.isConfigured());
}
}

View File

@ -0,0 +1,55 @@
package com.designhub.controller;
import com.designhub.service.ExportService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
/**
* 数据导出Controller
*/
@Tag(name = "数据导出", description = "数据导出相关API")
@RestController
@RequestMapping("/api/export")
@RequiredArgsConstructor
public class ExportController {
private final ExportService exportService;
@Operation(summary = "导出任务列表")
@GetMapping("/tasks")
public void exportTasks(
HttpServletResponse response,
@RequestParam(required = false) String status,
@RequestParam(required = false) Long publisherId
) throws IOException {
exportService.exportTasks(response, status, publisherId);
}
@Operation(summary = "导出用户列表")
@GetMapping("/users")
public void exportUsers(
HttpServletResponse response,
@RequestParam(required = false) Long deptId,
@RequestParam(required = false) Integer status
) throws IOException {
exportService.exportUsers(response, deptId, status);
}
@Operation(summary = "导出部门列表")
@GetMapping("/departments")
public void exportDepartments(HttpServletResponse response) throws IOException {
exportService.exportDepartments(response);
}
@Operation(summary = "导出设计师统计")
@GetMapping("/designer-stats")
public void exportDesignerStats(HttpServletResponse response) throws IOException {
exportService.exportDesignerStats(response);
}
}

View File

@ -0,0 +1,184 @@
package com.designhub.controller;
import com.designhub.entity.FileUpload;
import com.designhub.entity.User;
import com.designhub.service.S3Service;
import com.designhub.service.UserService;
import com.designhub.util.JwtUtil;
import com.designhub.common.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 文件上传下载Controller
*/
@RestController
@RequestMapping("/api/file")
public class FileUploadController {
@Autowired
private S3Service s3Service;
@Autowired
private UserService userService;
@Autowired
private JwtUtil jwtUtil;
/**
* 上传文件
*
* @param files 文件列表
* @param relatedType 关联类型(可选)
* @param relatedId 关联ID(可选)
* @param request HttpServletRequest
* @return 文件URL列表
*/
@PostMapping("/upload")
public Result<List<String>> uploadFiles(
@RequestParam("files") MultipartFile[] files,
@RequestParam(value = "relatedType", required = false) String relatedType,
@RequestParam(value = "relatedId", required = false) Long relatedId,
HttpServletRequest request) {
try {
// 从token获取用户信息
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
return Result.error("未登录或token无效");
}
token = token.substring(7);
String username = jwtUtil.getUsernameFromToken(token);
if (username == null) {
return Result.error("token无效");
}
User user = userService.getUserByUsername(username);
if (user == null) {
return Result.error("用户不存在");
}
// 上传文件
List<String> fileUrls = new ArrayList<>();
for (MultipartFile file : files) {
if (file.isEmpty()) {
continue;
}
String fileUrl = s3Service.uploadFile(
file,
user.getId(),
user.getRealName() != null ? user.getRealName() : user.getUsername(),
relatedType,
relatedId
);
fileUrls.add(fileUrl);
}
return Result.success(fileUrls);
} catch (Exception e) {
return Result.error("文件上传失败: " + e.getMessage());
}
}
/**
* 下载文件(需要登录)
*
* @param id 文件ID
* @param request HttpServletRequest
* @param response HttpServletResponse
*/
@GetMapping("/download/{id}")
public void downloadFile(
@PathVariable Long id,
HttpServletRequest request,
HttpServletResponse response) {
try {
// 验证登录状态
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("未登录,请先登录");
return;
}
token = token.substring(7);
String username = jwtUtil.getUsernameFromToken(token);
if (username == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("token无效");
return;
}
// 获取文件信息
FileUpload fileUpload = s3Service.getFileUploadById(id);
if (fileUpload == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.getWriter().write("文件不存在");
return;
}
// 重定向到文件URL(S3提供的公开URL)
response.sendRedirect(fileUpload.getFileUrl());
} catch (IOException e) {
try {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().write("文件下载失败: " + e.getMessage());
} catch (IOException ex) {
// ignore
}
}
}
/**
* 获取文件信息
*
* @param id 文件ID
* @return 文件信息
*/
@GetMapping("/{id}")
public Result<FileUpload> getFileInfo(@PathVariable Long id) {
try {
FileUpload fileUpload = s3Service.getFileUploadById(id);
if (fileUpload == null) {
return Result.error("文件不存在");
}
return Result.success(fileUpload);
} catch (Exception e) {
return Result.error("获取文件信息失败: " + e.getMessage());
}
}
/**
* 根据关联信息获取文件列表
*
* @param relatedType 关联类型
* @param relatedId 关联ID
* @return 文件列表
*/
@GetMapping("/list")
public Result<List<FileUpload>> getFilesByRelated(
@RequestParam String relatedType,
@RequestParam Long relatedId) {
try {
List<FileUpload> files = s3Service.getFilesByRelated(relatedType, relatedId);
return Result.success(files);
} catch (Exception e) {
return Result.error("获取文件列表失败: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,50 @@
package com.designhub.controller;
import com.designhub.common.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
/**
* 测试控制器
*/
@Tag(name = "测试接口", description = "用于测试系统是否正常运行")
@RestController
@RequestMapping("/hello")
public class HelloController {
@Operation(summary = "Hello World", description = "测试接口是否可访问")
@GetMapping
public Result<String> hello() {
return Result.success("Hello, DesignHub! 系统运行正常 ✅");
}
@Operation(summary = "系统信息", description = "获取系统基本信息")
@GetMapping("/info")
public Result<Map<String, Object>> info() {
Map<String, Object> info = new HashMap<>();
info.put("name", "DesignHub - 设计师任务管理平台");
info.put("version", "1.0.0");
info.put("time", LocalDateTime.now());
info.put("status", "running");
info.put("description", "基于 Spring Boot 3.x + Vue 3 开发");
return Result.success(info);
}
@Operation(summary = "健康检查", description = "检查系统健康状态")
@GetMapping("/health")
public Result<Map<String, String>> health() {
Map<String, String> health = new HashMap<>();
health.put("status", "UP");
health.put("database", "OK");
health.put("api", "OK");
return Result.success(health);
}
}

View File

@ -0,0 +1,70 @@
package com.designhub.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.designhub.common.Result;
import com.designhub.entity.Notification;
import com.designhub.service.NotificationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
* 通知消息Controller
*/
@Tag(name = "通知消息", description = "通知消息相关API")
@RestController
@RequestMapping("/api/notifications")
@RequiredArgsConstructor
public class NotificationController {
private final NotificationService notificationService;
@Operation(summary = "获取用户通知列表(分页)")
@GetMapping
public Result<Page<Notification>> getUserNotifications(
@RequestParam Long receiverId,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "20") Integer pageSize,
@RequestParam(required = false) Boolean unreadOnly
) {
Page<Notification> page = notificationService.getUserNotifications(receiverId, pageNum, pageSize, unreadOnly);
return Result.success(page);
}
@Operation(summary = "获取未读数量")
@GetMapping("/unread-count")
public Result<Long> getUnreadCount(@RequestParam Long receiverId) {
long count = notificationService.getUnreadCount(receiverId);
return Result.success(count);
}
@Operation(summary = "标记通知为已读")
@PutMapping("/{id}/read")
public Result<Boolean> markAsRead(@PathVariable Long id, @RequestParam Long receiverId) {
boolean success = notificationService.markAsRead(id, receiverId);
return Result.success(success);
}
@Operation(summary = "批量标记所有通知为已读")
@PutMapping("/read-all")
public Result<Integer> markAllAsRead(@RequestParam Long receiverId) {
int count = notificationService.markAllAsRead(receiverId);
return Result.success(count);
}
@Operation(summary = "删除通知")
@DeleteMapping("/{id}")
public Result<Boolean> deleteNotification(@PathVariable Long id, @RequestParam Long receiverId) {
boolean success = notificationService.deleteNotification(id, receiverId);
return Result.success(success);
}
@Operation(summary = "发送通知(管理员)")
@PostMapping
public Result<Notification> sendNotification(@RequestBody Notification notification) {
Notification result = notificationService.sendNotification(notification);
return Result.success(result);
}
}

View File

@ -0,0 +1,73 @@
package com.designhub.controller;
import com.designhub.common.Result;
import com.designhub.entity.Permission;
import com.designhub.service.PermissionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 权限管理Controller
*/
@Tag(name = "权限管理", description = "权限管理相关API")
@RestController
@RequestMapping("/api/permissions")
@RequiredArgsConstructor
public class PermissionController {
private final PermissionService permissionService;
@Operation(summary = "获取所有权限列表")
@GetMapping
public Result<List<Permission>> getAllPermissions() {
return Result.success(permissionService.getAllPermissions());
}
@Operation(summary = "获取权限树形结构")
@GetMapping("/tree")
public Result<List<Permission>> getPermissionTree() {
return Result.success(permissionService.getPermissionTree());
}
@Operation(summary = "根据ID获取权限")
@GetMapping("/{id}")
public Result<Permission> getPermissionById(@PathVariable Long id) {
return Result.success(permissionService.getPermissionById(id));
}
@Operation(summary = "根据用户ID获取权限列表")
@GetMapping("/user/{userId}")
public Result<List<Permission>> getPermissionsByUserId(@PathVariable Long userId) {
return Result.success(permissionService.getPermissionsByUserId(userId));
}
@Operation(summary = "根据用户ID获取权限代码列表")
@GetMapping("/user/{userId}/codes")
public Result<List<String>> getPermissionCodesByUserId(@PathVariable Long userId) {
return Result.success(permissionService.getPermissionCodesByUserId(userId));
}
@Operation(summary = "创建权限")
@PostMapping
public Result<Permission> createPermission(@RequestBody Permission permission) {
return Result.success(permissionService.createPermission(permission));
}
@Operation(summary = "更新权限")
@PutMapping("/{id}")
public Result<Boolean> updatePermission(@PathVariable Long id, @RequestBody Permission permission) {
permission.setId(id);
return Result.success(permissionService.updatePermission(permission));
}
@Operation(summary = "删除权限")
@DeleteMapping("/{id}")
public Result<Boolean> deletePermission(@PathVariable Long id) {
return Result.success(permissionService.deletePermission(id));
}
}

View File

@ -0,0 +1,92 @@
package com.designhub.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.designhub.common.Result;
import com.designhub.entity.RewardRule;
import com.designhub.service.RewardRuleService;
import com.designhub.util.JwtUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 自动奖励规则 Controller
*/
@Tag(name = "自动奖励规则", description = "自动奖励规则管理API")
@RestController
@RequestMapping("/api/reward-rules")
@RequiredArgsConstructor
public class RewardRuleController {
private final RewardRuleService rewardRuleService;
private final JwtUtil jwtUtil;
@Operation(summary = "创建奖励规则")
@PostMapping
public Result<RewardRule> createRule(
@RequestBody RewardRule rule,
@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
rule.setCreatorId(userId);
return Result.success(rewardRuleService.createRule(rule));
}
@Operation(summary = "更新奖励规则")
@PutMapping("/{id}")
public Result<RewardRule> updateRule(@PathVariable Long id, @RequestBody RewardRule rule) {
return Result.success(rewardRuleService.updateRule(id, rule));
}
@Operation(summary = "启用规则")
@PostMapping("/{id}/enable")
public Result<Void> enableRule(@PathVariable Long id) {
rewardRuleService.toggleRule(id, true);
return Result.success();
}
@Operation(summary = "禁用规则")
@PostMapping("/{id}/disable")
public Result<Void> disableRule(@PathVariable Long id) {
rewardRuleService.toggleRule(id, false);
return Result.success();
}
@Operation(summary = "删除规则")
@DeleteMapping("/{id}")
public Result<Void> deleteRule(@PathVariable Long id) {
rewardRuleService.deleteRule(id);
return Result.success();
}
@Operation(summary = "获取规则列表")
@GetMapping
public Result<IPage<RewardRule>> getRuleList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String ruleType) {
return Result.success(rewardRuleService.getRuleList(page, pageSize, ruleType));
}
@Operation(summary = "获取规则详情")
@GetMapping("/{id}")
public Result<RewardRule> getRuleById(@PathVariable Long id) {
return Result.success(rewardRuleService.getRuleById(id));
}
@Operation(summary = "获取所有启用的规则")
@GetMapping("/enabled")
public Result<List<RewardRule>> getEnabledRules() {
return Result.success(rewardRuleService.getEnabledRules());
}
@Operation(summary = "初始化默认规则")
@PostMapping("/init")
public Result<Void> initDefaultRules() {
rewardRuleService.initDefaultRules();
return Result.success();
}
}

View File

@ -0,0 +1,79 @@
package com.designhub.controller;
import com.designhub.common.Result;
import com.designhub.entity.Role;
import com.designhub.service.RoleService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 角色管理Controller
*/
@Tag(name = "角色管理", description = "角色管理相关API")
@RestController
@RequestMapping("/api/roles")
@RequiredArgsConstructor
public class RoleController {
private final RoleService roleService;
@Operation(summary = "获取所有角色列表")
@GetMapping
public Result<List<Role>> getAllRoles() {
return Result.success(roleService.getAllRoles());
}
@Operation(summary = "根据ID获取角色")
@GetMapping("/{id}")
public Result<Role> getRoleById(@PathVariable Long id) {
return Result.success(roleService.getRoleById(id));
}
@Operation(summary = "创建角色")
@PostMapping
public Result<Role> createRole(@RequestBody Role role) {
return Result.success(roleService.createRole(role));
}
@Operation(summary = "更新角色")
@PutMapping("/{id}")
public Result<Boolean> updateRole(@PathVariable Long id, @RequestBody Role role) {
role.setId(id);
return Result.success(roleService.updateRole(role));
}
@Operation(summary = "删除角色")
@DeleteMapping("/{id}")
public Result<Boolean> deleteRole(@PathVariable Long id) {
return Result.success(roleService.deleteRole(id));
}
@Operation(summary = "为角色分配权限")
@PostMapping("/{id}/permissions")
public Result<Boolean> assignPermissions(
@PathVariable Long id,
@RequestBody List<Long> permissionIds
) {
return Result.success(roleService.assignPermissions(id, permissionIds));
}
@Operation(summary = "获取角色的权限ID列表")
@GetMapping("/{id}/permissions")
public Result<List<Long>> getRolePermissionIds(@PathVariable Long id) {
return Result.success(roleService.getRolePermissionIds(id));
}
@Operation(summary = "启用/禁用角色")
@PutMapping("/{id}/status")
public Result<Boolean> toggleRoleStatus(
@PathVariable Long id,
@RequestParam Integer status
) {
return Result.success(roleService.toggleRoleStatus(id, status));
}
}

View File

@ -0,0 +1,132 @@
package com.designhub.controller;
import com.designhub.entity.S3Config;
import com.designhub.service.S3ConfigService;
import com.designhub.common.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* S3配置管理Controller
*/
@RestController
@RequestMapping("/api/s3/config")
public class S3ConfigController {
@Autowired
private S3ConfigService s3ConfigService;
/**
* 获取激活的S3配置
*/
@GetMapping("/active")
public Result<S3Config> getActiveConfig() {
try {
S3Config config = s3ConfigService.getActiveConfig();
// 隐藏敏感信息
if (config != null) {
config.setSecretAccessKey("******");
}
return Result.success(config);
} catch (Exception e) {
return Result.error("获取S3配置失败: " + e.getMessage());
}
}
/**
* 获取所有S3配置
*/
@GetMapping("/list")
public Result<List<S3Config>> getAllConfigs() {
try {
List<S3Config> configs = s3ConfigService.getAllConfigs();
// 隐藏敏感信息
configs.forEach(config -> config.setSecretAccessKey("******"));
return Result.success(configs);
} catch (Exception e) {
return Result.error("获取S3配置列表失败: " + e.getMessage());
}
}
/**
* 根据ID获取S3配置
*/
@GetMapping("/{id}")
public Result<S3Config> getConfigById(@PathVariable Long id) {
try {
S3Config config = s3ConfigService.getConfigById(id);
if (config == null) {
return Result.error("S3配置不存在");
}
// 隐藏敏感信息
config.setSecretAccessKey("******");
return Result.success(config);
} catch (Exception e) {
return Result.error("获取S3配置失败: " + e.getMessage());
}
}
/**
* 创建S3配置
*/
@PostMapping
public Result<S3Config> createConfig(@RequestBody S3Config config) {
try {
S3Config created = s3ConfigService.createConfig(config);
created.setSecretAccessKey("******");
return Result.success(created);
} catch (Exception e) {
return Result.error("创建S3配置失败: " + e.getMessage());
}
}
/**
* 更新S3配置
*/
@PutMapping("/{id}")
public Result<S3Config> updateConfig(@PathVariable Long id, @RequestBody S3Config config) {
try {
// 如果secretAccessKey为"******",说明没有修改,从数据库中获取原值
if ("******".equals(config.getSecretAccessKey())) {
S3Config existingConfig = s3ConfigService.getConfigById(id);
if (existingConfig != null) {
config.setSecretAccessKey(existingConfig.getSecretAccessKey());
}
}
S3Config updated = s3ConfigService.updateConfig(id, config);
updated.setSecretAccessKey("******");
return Result.success(updated);
} catch (Exception e) {
return Result.error("更新S3配置失败: " + e.getMessage());
}
}
/**
* 删除S3配置
*/
@DeleteMapping("/{id}")
public Result<Void> deleteConfig(@PathVariable Long id) {
try {
s3ConfigService.deleteConfig(id);
return Result.success();
} catch (Exception e) {
return Result.error("删除S3配置失败: " + e.getMessage());
}
}
/**
* 激活指定的S3配置
*/
@PutMapping("/{id}/activate")
public Result<Void> activateConfig(@PathVariable Long id) {
try {
s3ConfigService.activateConfig(id);
return Result.success();
} catch (Exception e) {
return Result.error("激活S3配置失败: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,102 @@
package com.designhub.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.designhub.common.Result;
import com.designhub.entity.ScorePool;
import com.designhub.entity.ScorePoolRecord;
import com.designhub.service.ScorePoolService;
import com.designhub.util.JwtUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* 激励积分池 Controller
*/
@Tag(name = "激励积分池", description = "激励积分池管理API")
@RestController
@RequestMapping("/api/score-pools")
@RequiredArgsConstructor
public class ScorePoolController {
private final ScorePoolService scorePoolService;
private final JwtUtil jwtUtil;
@Operation(summary = "创建积分池")
@PostMapping
public Result<ScorePool> createPool(
@RequestBody ScorePool pool,
@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
pool.setCreatorId(userId);
return Result.success(scorePoolService.createPool(pool));
}
@Operation(summary = "更新积分池")
@PutMapping("/{id}")
public Result<ScorePool> updatePool(@PathVariable Long id, @RequestBody ScorePool pool) {
return Result.success(scorePoolService.updatePool(id, pool));
}
@Operation(summary = "获取积分池列表")
@GetMapping
public Result<IPage<ScorePool>> getPoolList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String poolType,
@RequestParam(required = false) Integer year) {
return Result.success(scorePoolService.getPoolList(page, pageSize, poolType, year));
}
@Operation(summary = "获取积分池详情")
@GetMapping("/{id}")
public Result<ScorePool> getPoolById(@PathVariable Long id) {
return Result.success(scorePoolService.getPoolById(id));
}
@Operation(summary = "获取当前活跃积分池")
@GetMapping("/current")
public Result<ScorePool> getCurrentActivePool() {
return Result.success(scorePoolService.getCurrentActivePool());
}
@Operation(summary = "获取积分池统计信息")
@GetMapping("/{id}/statistics")
public Result<Map<String, Object>> getPoolStatistics(@PathVariable Long id) {
return Result.success(scorePoolService.getPoolStatistics(id));
}
@Operation(summary = "获取积分池使用记录")
@GetMapping("/{id}/records")
public Result<IPage<ScorePoolRecord>> getPoolRecords(
@PathVariable Long id,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "20") Integer pageSize) {
return Result.success(scorePoolService.getPoolRecords(id, page, pageSize));
}
@Operation(summary = "关闭积分池")
@PostMapping("/{id}/close")
public Result<Void> closePool(@PathVariable Long id) {
scorePoolService.closePool(id);
return Result.success();
}
@Operation(summary = "结算积分池")
@PostMapping("/{id}/settle")
public Result<Void> settlePool(@PathVariable Long id) {
scorePoolService.settlePool(id);
return Result.success();
}
@Operation(summary = "删除积分池")
@DeleteMapping("/{id}")
public Result<Void> deletePool(@PathVariable Long id) {
scorePoolService.deletePool(id);
return Result.success();
}
}

View File

@ -0,0 +1,87 @@
package com.designhub.controller;
import com.designhub.common.Result;
import com.designhub.entity.SystemConfig;
import com.designhub.service.SystemConfigService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 系统配置Controller
*/
@Tag(name = "系统配置", description = "系统配置相关API")
@RestController
@RequestMapping("/api/system-config")
@RequiredArgsConstructor
public class SystemConfigController {
private final SystemConfigService systemConfigService;
@Operation(summary = "获取所有配置列表")
@GetMapping
public Result<List<SystemConfig>> getAllConfigs() {
return Result.success(systemConfigService.getAllConfigs());
}
@Operation(summary = "根据分组获取配置列表")
@GetMapping("/group/{group}")
public Result<List<SystemConfig>> getConfigsByGroup(@PathVariable String group) {
return Result.success(systemConfigService.getConfigsByGroup(group));
}
@Operation(summary = "根据键获取配置值")
@GetMapping("/key/{key}")
public Result<String> getConfigValue(@PathVariable String key) {
return Result.success(systemConfigService.getConfigValue(key));
}
@Operation(summary = "获取所有配置的Map")
@GetMapping("/map")
public Result<Map<String, String>> getAllConfigsMap() {
return Result.success(systemConfigService.getAllConfigsMap());
}
@Operation(summary = "根据分组获取配置Map")
@GetMapping("/map/group/{group}")
public Result<Map<String, String>> getConfigsMapByGroup(@PathVariable String group) {
return Result.success(systemConfigService.getConfigsMapByGroup(group));
}
@Operation(summary = "创建配置")
@PostMapping
public Result<SystemConfig> createConfig(@RequestBody SystemConfig config) {
return Result.success(systemConfigService.createConfig(config));
}
@Operation(summary = "更新配置")
@PutMapping("/{id}")
public Result<Boolean> updateConfig(@PathVariable Long id, @RequestBody SystemConfig config) {
config.setId(id);
return Result.success(systemConfigService.updateConfig(config));
}
@Operation(summary = "批量更新配置值")
@PutMapping("/batch")
public Result<Boolean> batchUpdateValues(@RequestBody Map<String, String> configMap) {
return Result.success(systemConfigService.batchUpdateValues(configMap));
}
@Operation(summary = "删除配置")
@DeleteMapping("/{id}")
public Result<Boolean> deleteConfig(@PathVariable Long id) {
return Result.success(systemConfigService.deleteConfig(id));
}
@Operation(summary = "初始化默认配置")
@PostMapping("/init")
public Result<Void> initDefaultConfigs() {
systemConfigService.initDefaultConfigs();
return Result.success(null);
}
}

View File

@ -0,0 +1,30 @@
package com.designhub.controller;
import com.designhub.common.Result;
import com.designhub.service.SystemHealthService;
import com.designhub.vo.SystemHealthVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 系统健康检查控制器
*/
@Tag(name = "系统健康", description = "系统自检与健康检查接口")
@RestController
@RequestMapping("/api/system")
@RequiredArgsConstructor
public class SystemHealthController {
private final SystemHealthService systemHealthService;
@Operation(summary = "健康检查", description = "用于部署后快速检测数据库与关键表状态")
@GetMapping("/health")
public Result<SystemHealthVO> health() {
return Result.success(systemHealthService.check());
}
}

View File

@ -0,0 +1,93 @@
package com.designhub.controller;
import com.designhub.common.Result;
import com.designhub.dto.TaskApprovalDTO;
import com.designhub.entity.Task;
import com.designhub.service.TaskApprovalService;
import com.designhub.util.JwtUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 任务审核控制器
*/
@Tag(name = "任务审核管理")
@RestController
@RequestMapping("/api/tasks")
@RequiredArgsConstructor
public class TaskApprovalController {
private final TaskApprovalService taskApprovalService;
private final JwtUtil jwtUtil;
@Operation(summary = "需求部门审核通过")
@PostMapping("/{id}/approve-demand")
public Result<Void> approveDemandLevel(
@PathVariable Long id,
@RequestBody TaskApprovalDTO dto,
@RequestHeader("Authorization") String authHeader
) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long approverId = jwtUtil.getUserIdFromToken(token);
taskApprovalService.approveDemandLevel(id, approverId, dto);
return Result.success();
}
@Operation(summary = "需求部门驳回")
@PostMapping("/{id}/reject-demand")
public Result<Void> rejectByDemandLeader(
@PathVariable Long id,
@RequestBody Map<String, String> params,
@RequestHeader("Authorization") String authHeader
) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long approverId = jwtUtil.getUserIdFromToken(token);
String reason = params.getOrDefault("reason", "");
taskApprovalService.rejectByDemandLeader(id, approverId, reason);
return Result.success();
}
@Operation(summary = "接单部门审核通过")
@PostMapping("/{id}/approve-receiving")
public Result<Void> approveReceivingLevel(
@PathVariable Long id,
@RequestBody TaskApprovalDTO dto,
@RequestHeader("Authorization") String authHeader
) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long approverId = jwtUtil.getUserIdFromToken(token);
taskApprovalService.approveReceivingLevel(id, approverId, dto);
return Result.success();
}
@Operation(summary = "接单部门驳回")
@PostMapping("/{id}/reject-receiving")
public Result<Void> rejectByReceivingLeader(
@PathVariable Long id,
@RequestBody Map<String, String> params,
@RequestHeader("Authorization") String authHeader
) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long approverId = jwtUtil.getUserIdFromToken(token);
String reason = params.getOrDefault("reason", "");
taskApprovalService.rejectByReceivingLeader(id, approverId, reason);
return Result.success();
}
@Operation(summary = "获取待审核任务列表")
@GetMapping("/pending-approval")
public Result<List<Task>> getPendingApprovalTasks(
@RequestHeader("Authorization") String authHeader
) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
List<Task> tasks = taskApprovalService.getPendingApprovalTasks(userId);
return Result.success(tasks);
}
}

View File

@ -0,0 +1,400 @@
package com.designhub.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.designhub.common.Result;
import com.designhub.dto.TaskAssignRequest;
import com.designhub.dto.TaskAuditRequest;
import com.designhub.entity.Task;
import com.designhub.service.TaskService;
import com.designhub.util.JwtUtil;
import com.designhub.vo.DashboardStatsVO;
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 lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* 任务管理控制器
*/
@Slf4j
@Tag(name = "任务管理", description = "任务的增删改查、抢单、审核等接口")
@RestController
@RequestMapping("/api/tasks")
@RequiredArgsConstructor
public class TaskController {
private final TaskService taskService;
private final JwtUtil jwtUtil;
@Operation(summary = "创建任务", description = "发起新的设计任务")
@PostMapping
public Result<Long> createTask(@RequestBody Task task, @RequestHeader("Authorization") String authHeader) {
log.info("创建任务: {}", task.getTitle());
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
task.setPublisherId(userId);
Long taskId = taskService.createTask(task);
return Result.success(taskId);
}
@Operation(summary = "创建流水线任务", description = "创建包含多个子任务的流水线任务")
@PostMapping("/pipeline")
public Result<Long> createPipelineTask(
@RequestBody com.designhub.dto.PipelineTaskDTO dto,
@RequestHeader("Authorization") String authHeader) {
log.info("创建流水线任务: {}", dto.getTitle());
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
Long taskId = taskService.createPipelineTask(dto, userId);
return Result.success("流水线任务创建成功", taskId);
}
@Operation(summary = "获取任务列表", description = "分页查询任务列表,支持多种筛选条件")
@GetMapping
public Result<IPage<Task>> getTaskList(
@Parameter(description = "任务状态") @RequestParam(required = false) String status,
@Parameter(description = "任务类型ID") @RequestParam(required = false) Long taskTypeId,
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "20") Integer pageSize) {
log.info("查询任务列表: status={}, taskTypeId={}, page={}, pageSize={}",
status, taskTypeId, page, pageSize);
IPage<Task> taskPage = taskService.getTaskList(page, pageSize, status, taskTypeId);
return Result.success(taskPage);
}
@Operation(summary = "获取所有待审核任务", description = "获取所有处于审批流程中的任务(包括需求部门审核和接单部门审核)")
@GetMapping("/pending-approval-all")
public Result<IPage<Task>> getAllPendingApprovalTasks(
@Parameter(description = "任务类型ID") @RequestParam(required = false) Long taskTypeId,
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "20") Integer pageSize) {
log.info("查询所有待审核任务: taskTypeId={}, page={}, pageSize={}",
taskTypeId, page, pageSize);
IPage<Task> taskPage = taskService.getAllPendingApprovalTasks(page, pageSize, taskTypeId);
return Result.success(taskPage);
}
@Operation(summary = "获取任务详情", description = "根据任务ID获取详细信息")
@GetMapping("/{id}")
public Result<Task> getTaskById(@PathVariable Long id) {
log.info("查询任务详情: {}", id);
Task task = taskService.getTaskById(id);
return Result.success(task);
}
@Operation(summary = "更新任务", description = "更新任务信息")
@PutMapping("/{id}")
public Result<String> updateTask(@PathVariable Long id, @RequestBody Task task) {
log.info("更新任务: {}", id);
taskService.updateTask(id, task);
return Result.success("更新成功");
}
@Operation(summary = "抢单", description = "设计师抢单")
@PostMapping("/{id}/grab")
public Result<String> grabTask(@PathVariable Long id, @RequestHeader("Authorization") String authHeader) {
log.info("抢单: {}", id);
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
taskService.grabTask(id, userId);
return Result.success("抢单成功");
}
@Operation(summary = "提交任务", description = "设计师提交完成的任务")
@PostMapping("/{id}/submit")
public Result<String> submitTask(
@PathVariable Long id,
@RequestParam String deliveryPath,
@RequestHeader("Authorization") String authHeader) {
log.info("提交任务: {}, 交付路径: {}", id, deliveryPath);
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
taskService.submitTask(id, userId, deliveryPath);
return Result.success("提交成功");
}
@Operation(summary = "审核任务", description = "需求方审核任务")
@PostMapping("/{id}/review")
public Result<String> reviewTask(
@PathVariable Long id,
@RequestParam Boolean approved,
@RequestParam(required = false) String comment,
@RequestParam(required = false) Integer rating,
@RequestHeader("Authorization") String authHeader) {
log.info("审核任务: {}, 通过: {}, 评分: {}", id, approved, rating);
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
taskService.reviewTask(id, userId, approved, comment, rating);
return Result.success(approved ? "审核通过" : "已驳回");
}
@Operation(summary = "取消任务", description = "发布人取消任务")
@PostMapping("/{id}/cancel")
public Result<String> cancelTask(
@PathVariable Long id,
@RequestParam String reason,
@RequestHeader("Authorization") String authHeader) {
log.info("取消任务: {}, 原因: {}", id, reason);
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
taskService.cancelTask(id, userId, reason);
return Result.success("取消成功");
}
@Operation(summary = "获取已完成任务列表", description = "根据用户角色返回不同范围的已完成任务")
@GetMapping("/completed")
public Result<IPage<Task>> getCompletedTasks(
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "12") Integer pageSize,
@Parameter(description = "关键词") @RequestParam(required = false) String keyword,
@Parameter(description = "排序方式") @RequestParam(required = false) String sortBy,
@RequestHeader("Authorization") String authHeader) {
log.info("查询已完成任务: page={}, pageSize={}, keyword={}, sortBy={}",
page, pageSize, keyword, sortBy);
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
IPage<Task> taskPage = taskService.getCompletedTasks(page, pageSize, userId, keyword, sortBy);
return Result.success(taskPage);
}
@Operation(summary = "任务大厅", description = "获取可接单的任务列表,支持多种筛选、搜索和排序")
@GetMapping("/hall")
public Result<IPage<Task>> getTaskHall(
@Parameter(description = "任务类型ID") @RequestParam(required = false) Long taskTypeId,
@Parameter(description = "需求类型ID") @RequestParam(required = false) Long demandTypeId,
@Parameter(description = "品牌ID") @RequestParam(required = false) Long brandId,
@Parameter(description = "类目ID") @RequestParam(required = false) Long categoryId,
@Parameter(description = "平台ID") @RequestParam(required = false) Long platformId,
@Parameter(description = "任务子类型") @RequestParam(required = false) String taskSubType,
@Parameter(description = "最小积分") @RequestParam(required = false) Integer minScore,
@Parameter(description = "最大积分") @RequestParam(required = false) Integer maxScore,
@Parameter(description = "关键词搜索") @RequestParam(required = false) String keyword,
@Parameter(description = "发布人姓名") @RequestParam(required = false) String publisherName,
@Parameter(description = "发布时间范围:today/week/month") @RequestParam(required = false) String timeRange,
@Parameter(description = "排序方式:latest/score/deadline/hot") @RequestParam(required = false) String sortBy,
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "20") Integer pageSize) {
log.info("查询任务大厅: taskTypeId={}, demandTypeId={}, brandId={}, categoryId={}, platformId={}, " +
"taskSubType={}, minScore={}, maxScore={}, keyword={}, publisherName={}, timeRange={}, " +
"sortBy={}, page={}, pageSize={}",
taskTypeId, demandTypeId, brandId, categoryId, platformId, taskSubType,
minScore, maxScore, keyword, publisherName, timeRange, sortBy, page, pageSize);
IPage<Task> taskPage = taskService.getTaskHall(page, pageSize, taskTypeId, demandTypeId,
brandId, categoryId, platformId, taskSubType, minScore, maxScore, keyword,
publisherName, timeRange, sortBy);
return Result.success(taskPage);
}
@Operation(summary = "我的任务", description = "获取当前用户的任务列表")
@GetMapping("/my")
public Result<IPage<Task>> getMyTasks(
@RequestParam(required = false) String role, // publisher 或 receiver
@RequestParam(required = false) String status,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "20") Integer pageSize,
@RequestHeader("Authorization") String authHeader) {
log.info("查询我的任务: role={}, status={}, page={}, pageSize={}",
role, status, page, pageSize);
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
IPage<Task> taskPage;
if ("publisher".equals(role)) {
taskPage = taskService.getMyPublishedTasks(page, pageSize, userId, status);
} else {
taskPage = taskService.getMyReceivedTasks(page, pageSize, userId, status);
}
return Result.success(taskPage);
}
@Operation(summary = "提交任务审核", description = "任务发起人提交任务给部门负责人审核")
@PostMapping("/{id}/submit-audit")
public Result<Boolean> submitTaskForAudit(
@PathVariable Long id,
@RequestHeader("Authorization") String authHeader) {
log.info("提交任务审核: taskId={}", id);
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
return Result.success(taskService.submitTaskForAudit(id, userId));
}
@Operation(summary = "审核任务", description = "部门负责人审核任务(通过或驳回)")
@PostMapping("/audit")
public Result<Boolean> auditTask(
@Valid @RequestBody TaskAuditRequest request,
@RequestHeader("Authorization") String authHeader) {
log.info("审核任务: request={}", request);
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long auditorId = jwtUtil.getUserIdFromToken(token);
return Result.success(taskService.auditTask(request, auditorId));
}
@Operation(summary = "强制指派任务", description = "部门负责人强制指派任务给部门成员")
@PostMapping("/assign")
public Result<Boolean> assignTask(
@Valid @RequestBody TaskAssignRequest request,
@RequestHeader("Authorization") String authHeader) {
log.info("强制指派任务: request={}", request);
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long assignerId = jwtUtil.getUserIdFromToken(token);
return Result.success(taskService.assignTask(request, assignerId));
}
@Operation(summary = "负责人任务列表", description = "需求部门负责人查看部门内所有任务")
@GetMapping("/leader/demand")
public Result<IPage<Task>> getLeaderTaskList(
@RequestParam(required = false) String auditStatus,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "20") Integer pageSize,
@RequestHeader("Authorization") String authHeader) {
log.info("查询负责人任务列表: auditStatus={}, page={}, pageSize={}",
auditStatus, page, pageSize);
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
return Result.success(taskService.getLeaderTaskList(userId, page, pageSize, auditStatus));
}
@Operation(summary = "接单部门负责人视图", description = "接单部门负责人查看部门成员接单明细")
@GetMapping("/leader/receiving")
public Result<IPage<Task>> getLeaderReceivingTaskList(
@RequestParam(required = false) String status,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "20") Integer pageSize,
@RequestHeader("Authorization") String authHeader) {
log.info("查询接单部门负责人视图: status={}, page={}, pageSize={}",
status, page, pageSize);
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
return Result.success(taskService.getLeaderReceivingTaskList(userId, page, pageSize, status));
}
@Operation(summary = "获取工作台统计数据")
@GetMapping("/dashboard/stats")
public Result<DashboardStatsVO> getDashboardStats(
@Parameter(description = "月份,格式:YYYY-MM,不传则查询当月") @RequestParam(required = false) String month,
@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
log.info("获取工作台统计数据: userId={}, month={}", userId, month);
return Result.success(taskService.getDashboardStats(userId, month));
}
@Operation(summary = "获取我的进行中任务")
@GetMapping("/my-in-progress")
public Result<java.util.List<Task>> getMyInProgressTasks(@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
return Result.success(taskService.getMyInProgressTasks(userId));
}
@Operation(summary = "批量更新任务进度")
@PostMapping("/batch-update-progress")
public Result<String> batchUpdateProgress(
@RequestBody java.util.Map<String, Object> payload,
@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
@SuppressWarnings("unchecked")
java.util.List<java.util.Map<String, Object>> updates =
(java.util.List<java.util.Map<String, Object>>) payload.get("updates");
taskService.batchUpdateProgress(userId, updates);
return Result.success("进度更新成功");
}
@Operation(summary = "更新任务进度", description = "接单人更新任务进度")
@PostMapping("/{id}/progress")
public Result<String> updateTaskProgress(
@PathVariable Long id,
@RequestBody java.util.Map<String, Object> payload,
@RequestHeader("Authorization") String authHeader) {
log.info("更新任务进度: taskId={}", id);
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
Integer progress = (Integer) payload.get("progress");
if (progress == null) {
return Result.error("进度值不能为空");
}
taskService.updateTaskProgress(id, userId, progress);
return Result.success("进度更新成功");
}
@Operation(summary = "完结任务", description = "接单人完结任务并提交交付路径")
@PostMapping("/{id}/complete")
public Result<String> completeTask(
@PathVariable Long id,
@RequestBody java.util.Map<String, String> payload,
@RequestHeader("Authorization") String authHeader) {
log.info("完结任务: taskId={}", id);
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
String deliveryPath = payload.get("deliveryPath");
if (deliveryPath == null || deliveryPath.trim().isEmpty()) {
return Result.error("交付路径不能为空");
}
taskService.completeTask(id, userId, deliveryPath);
return Result.success("任务已提交审核");
}
@Operation(summary = "审核通过任务", description = "发起人或部门负责人审核通过任务")
@PostMapping("/{id}/approve")
public Result<String> approveTask(
@PathVariable Long id,
@RequestHeader("Authorization") String authHeader) {
log.info("审核通过任务: taskId={}", id);
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
taskService.approveTask(id, userId);
return Result.success("审核通过");
}
@Operation(summary = "驳回任务", description = "发起人或部门负责人驳回任务")
@PostMapping("/{id}/reject")
public Result<String> rejectTask(
@PathVariable Long id,
@RequestBody java.util.Map<String, String> payload,
@RequestHeader("Authorization") String authHeader) {
log.info("驳回任务: taskId={}", id);
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
String reason = payload.get("reason");
if (reason == null || reason.trim().isEmpty()) {
return Result.error("驳回原因不能为空");
}
taskService.rejectTask(id, userId, reason);
return Result.success("任务已驳回");
}
}

View File

@ -0,0 +1,174 @@
package com.designhub.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.designhub.common.Result;
import com.designhub.entity.TaskItem;
import com.designhub.service.TaskItemService;
import com.designhub.util.JwtUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 任务子项控制器(流水线任务)
*/
@Tag(name = "流水线任务管理", description = "流水线任务子项相关接口")
@RestController
@RequestMapping("/api/tasks/items")
@RequiredArgsConstructor
public class TaskItemController {
private final TaskItemService taskItemService;
private final JwtUtil jwtUtil;
/**
* 获取可接单的子任务列表
*/
@Operation(summary = "获取可接单的子任务列表")
@GetMapping("/available")
public Result<IPage<TaskItem>> getAvailableItems(
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "10") Integer pageSize,
@Parameter(description = "任务类型ID") @RequestParam(required = false) Long taskTypeId,
@Parameter(description = "搜索关键词") @RequestParam(required = false) String keyword) {
IPage<TaskItem> items = taskItemService.getAvailableItems(page, pageSize, taskTypeId, keyword);
return Result.success(items);
}
/**
* 获取进行中的子任务列表
*/
@Operation(summary = "获取进行中的子任务列表")
@GetMapping("/in-progress")
public Result<IPage<TaskItem>> getInProgressItems(
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "10") Integer pageSize,
@RequestHeader("Authorization") String token) {
String actualToken = token.replace("Bearer ", "");
Long userId = jwtUtil.getUserIdFromToken(actualToken);
IPage<TaskItem> items = taskItemService.getInProgressItems(page, pageSize, userId);
return Result.success(items);
}
/**
* 获取已完成的子任务列表
*/
@Operation(summary = "获取已完成的子任务列表")
@GetMapping("/completed")
public Result<IPage<TaskItem>> getCompletedItems(
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "10") Integer pageSize,
@RequestHeader("Authorization") String token) {
String actualToken = token.replace("Bearer ", "");
Long userId = jwtUtil.getUserIdFromToken(actualToken);
IPage<TaskItem> items = taskItemService.getCompletedItems(page, pageSize, userId);
return Result.success(items);
}
/**
* 根据主任务ID获取所有子任务
*/
@Operation(summary = "根据主任务ID获取所有子任务")
@GetMapping("/task/{taskId}")
public Result<List<TaskItem>> getItemsByTaskId(
@Parameter(description = "主任务ID") @PathVariable Long taskId) {
List<TaskItem> items = taskItemService.getItemsByTaskId(taskId);
return Result.success(items);
}
/**
* 获取子任务详情
*/
@Operation(summary = "获取子任务详情")
@GetMapping("/{itemId}")
public Result<TaskItem> getItemById(
@Parameter(description = "子任务ID") @PathVariable Long itemId) {
TaskItem item = taskItemService.getItemById(itemId);
return Result.success(item);
}
/**
* 抢单子任务
*/
@Operation(summary = "抢单子任务")
@PostMapping("/{itemId}/grab")
public Result<String> grabItem(
@Parameter(description = "子任务ID") @PathVariable Long itemId,
@RequestHeader("Authorization") String token) {
String actualToken = token.replace("Bearer ", "");
Long userId = jwtUtil.getUserIdFromToken(actualToken);
taskItemService.grabItem(itemId, userId);
return Result.success("抢单成功");
}
/**
* 更新子任务进度
*/
@Operation(summary = "更新子任务进度")
@PostMapping("/{itemId}/progress")
public Result<String> updateProgress(
@Parameter(description = "子任务ID") @PathVariable Long itemId,
@Parameter(description = "进度(0-100)") @RequestParam Integer progress,
@RequestHeader("Authorization") String token) {
String actualToken = token.replace("Bearer ", "");
Long userId = jwtUtil.getUserIdFromToken(actualToken);
taskItemService.updateProgress(itemId, userId, progress);
return Result.success("进度更新成功");
}
/**
* 完结子任务
*/
@Operation(summary = "完结子任务")
@PostMapping("/{itemId}/complete")
public Result<Map<String, Object>> completeItem(
@Parameter(description = "子任务ID") @PathVariable Long itemId,
@Parameter(description = "交付路径") @RequestBody Map<String, String> request,
@RequestHeader("Authorization") String token) {
String actualToken = token.replace("Bearer ", "");
Long userId = jwtUtil.getUserIdFromToken(actualToken);
String deliveryPath = request.get("deliveryPath");
Map<String, Object> result = taskItemService.completeItem(itemId, userId, deliveryPath);
return Result.success("子任务完成成功", result);
}
/**
* 驳回子任务
*/
@Operation(summary = "驳回子任务")
@PostMapping("/{itemId}/reject")
public Result<String> rejectItem(
@Parameter(description = "子任务ID") @PathVariable Long itemId,
@Parameter(description = "驳回原因") @RequestBody Map<String, String> request) {
String reason = request.get("reason");
taskItemService.rejectItem(itemId, reason);
return Result.success("子任务已驳回");
}
/**
* 获取主任务进度统计
*/
@Operation(summary = "获取主任务进度统计")
@GetMapping("/task/{taskId}/progress")
public Result<Map<String, Object>> getTaskProgress(
@Parameter(description = "主任务ID") @PathVariable Long taskId) {
Map<String, Object> progress = taskItemService.getTaskProgress(taskId);
return Result.success(progress);
}
}

View File

@ -0,0 +1,81 @@
package com.designhub.controller;
import com.designhub.common.Result;
import com.designhub.entity.TaskSubType;
import com.designhub.service.TaskSubTypeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 任务子类型控制器
*/
@Tag(name = "任务子类型管理")
@RestController
@RequestMapping("/api/task-sub-types")
@RequiredArgsConstructor
public class TaskSubTypeController {
private final TaskSubTypeService taskSubTypeService;
@Operation(summary = "获取所有子类型")
@GetMapping
public Result<List<TaskSubType>> getAllSubTypes() {
return Result.success(taskSubTypeService.getAllSubTypes());
}
@Operation(summary = "获取启用的子类型")
@GetMapping("/enabled")
public Result<List<TaskSubType>> getEnabledSubTypes() {
return Result.success(taskSubTypeService.getEnabledSubTypes());
}
@Operation(summary = "根据ID获取子类型")
@GetMapping("/{id}")
public Result<TaskSubType> getById(@PathVariable Long id) {
return Result.success(taskSubTypeService.getById(id));
}
@Operation(summary = "创建子类型")
@PostMapping
public Result<TaskSubType> createSubType(@RequestBody TaskSubType subType) {
return Result.success(taskSubTypeService.createSubType(subType));
}
@Operation(summary = "更新子类型")
@PutMapping("/{id}")
public Result<TaskSubType> updateSubType(
@PathVariable Long id,
@RequestBody TaskSubType subType
) {
return Result.success(taskSubTypeService.updateSubType(id, subType));
}
@Operation(summary = "删除子类型")
@DeleteMapping("/{id}")
public Result<Void> deleteSubType(@PathVariable Long id) {
taskSubTypeService.deleteSubType(id);
return Result.success();
}
@Operation(summary = "切换启用状态")
@PostMapping("/{id}/toggle-enabled")
public Result<Void> toggleEnabled(@PathVariable Long id) {
taskSubTypeService.toggleEnabled(id);
return Result.success();
}
@Operation(summary = "更新排序")
@PostMapping("/{id}/sort")
public Result<Void> updateSortOrder(
@PathVariable Long id,
@Parameter(description = "排序值") @RequestParam Integer sortOrder
) {
taskSubTypeService.updateSortOrder(id, sortOrder);
return Result.success();
}
}

View File

@ -0,0 +1,85 @@
package com.designhub.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.designhub.common.Result;
import com.designhub.entity.TeamPK;
import com.designhub.service.TeamPKService;
import com.designhub.util.JwtUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
* 团队PK Controller
*/
@Tag(name = "团队PK", description = "团队PK管理相关API")
@RestController
@RequestMapping("/api/team-pk")
@RequiredArgsConstructor
public class TeamPKController {
private final TeamPKService teamPKService;
private final JwtUtil jwtUtil;
@Operation(summary = "创建团队PK")
@PostMapping
public Result<TeamPK> createTeamPK(
@RequestBody TeamPK teamPK,
@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
teamPK.setCreatorId(userId);
return Result.success(teamPKService.createTeamPK(teamPK));
}
@Operation(summary = "发布团队PK")
@PostMapping("/{id}/publish")
public Result<TeamPK> publishTeamPK(@PathVariable Long id) {
return Result.success(teamPKService.publishTeamPK(id));
}
@Operation(summary = "结束团队PK")
@PostMapping("/{id}/end")
public Result<Void> endTeamPK(@PathVariable Long id) {
teamPKService.endTeamPK(id);
return Result.success();
}
@Operation(summary = "取消团队PK")
@PostMapping("/{id}/cancel")
public Result<Void> cancelTeamPK(@PathVariable Long id) {
teamPKService.cancelTeamPK(id);
return Result.success();
}
@Operation(summary = "删除团队PK")
@DeleteMapping("/{id}")
public Result<Void> deleteTeamPK(@PathVariable Long id) {
teamPKService.deleteTeamPK(id);
return Result.success();
}
@Operation(summary = "获取团队PK列表")
@GetMapping
public Result<IPage<TeamPK>> getPKList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String status) {
return Result.success(teamPKService.getPKList(page, pageSize, status));
}
@Operation(summary = "获取团队PK详情")
@GetMapping("/{id}")
public Result<TeamPK> getPKById(@PathVariable Long id) {
return Result.success(teamPKService.getPKById(id));
}
@Operation(summary = "刷新PK数据")
@PostMapping("/{id}/refresh")
public Result<TeamPK> refreshPKStats(@PathVariable Long id) {
TeamPK pk = teamPKService.getPKById(id);
teamPKService.refreshPKStats(pk);
return Result.success(teamPKService.getPKById(id));
}
}

View File

@ -0,0 +1,72 @@
package com.designhub.controller;
import com.designhub.common.Result;
import com.designhub.service.ThirdPartyCallbackService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* 第三方平台回调控制器
*/
@Slf4j
@Tag(name = "第三方回调", description = "接收第三方平台的任务完成回调")
@RestController
@RequestMapping("/api/third-party")
@RequiredArgsConstructor
public class ThirdPartyCallbackController {
private final ThirdPartyCallbackService callbackService;
@Operation(summary = "任务完成回调", description = "第三方平台通知任务已完成")
@PostMapping("/task-complete")
public Result<String> taskComplete(@RequestBody Map<String, Object> payload) {
log.info("收到第三方任务完成回调: {}", payload);
try {
String thirdPartyTaskNo = (String) payload.get("thirdPartyTaskNo");
if (thirdPartyTaskNo == null || thirdPartyTaskNo.isEmpty()) {
return Result.fail("缺少必需参数: thirdPartyTaskNo");
}
callbackService.handleTaskCompleteCallback(thirdPartyTaskNo, payload);
return Result.success("回调处理成功");
} catch (Exception e) {
log.error("处理任务完成回调失败", e);
return Result.fail("处理失败: " + e.getMessage());
}
}
@Operation(summary = "任务状态更新回调", description = "第三方平台通知任务状态变更")
@PostMapping("/task-status-update")
public Result<String> taskStatusUpdate(@RequestBody Map<String, Object> payload) {
log.info("收到第三方任务状态更新回调: {}", payload);
try {
String thirdPartyTaskNo = (String) payload.get("thirdPartyTaskNo");
String status = (String) payload.get("status");
if (thirdPartyTaskNo == null || thirdPartyTaskNo.isEmpty()) {
return Result.fail("缺少必需参数: thirdPartyTaskNo");
}
callbackService.handleTaskStatusUpdateCallback(thirdPartyTaskNo, status, payload);
return Result.success("回调处理成功");
} catch (Exception e) {
log.error("处理任务状态更新回调失败", e);
return Result.fail("处理失败: " + e.getMessage());
}
}
@Operation(summary = "查询回调记录", description = "根据第三方任务编号查询回调记录")
@GetMapping("/callback-records/{thirdPartyTaskNo}")
public Result<?> getCallbackRecords(
@Parameter(description = "第三方任务编号") @PathVariable String thirdPartyTaskNo) {
return Result.success(callbackService.getCallbackRecords(thirdPartyTaskNo));
}
}

View File

@ -0,0 +1,171 @@
package com.designhub.controller;
import com.designhub.common.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.PostConstruct;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
/**
* 文件上传Controller
*/
@Slf4j
@RestController
@RequestMapping("/api/upload")
@Tag(name = "文件上传", description = "文件上传相关API")
public class UploadController {
@Value("${file.upload.path:./uploads}")
private String uploadPath;
@Value("${file.upload.max-size:52428800}")
private long maxSize;
@Value("${file.upload.allowed-extensions:.xls,.xlsx,.doc,.docx,.pdf,.zip}")
private String allowedExtensions;
private static final Set<String> ALLOWED_EXTENSIONS_SET = new HashSet<>();
@PostConstruct
public void init() {
String[] extensions = allowedExtensions.split(",");
for (String ext : extensions) {
ALLOWED_EXTENSIONS_SET.add(ext.trim().toLowerCase());
}
// 确保上传目录存在
try {
Path path = Paths.get(uploadPath);
if (!Files.exists(path)) {
Files.createDirectories(path);
log.info("创建上传目录: {}", uploadPath);
}
} catch (IOException e) {
log.error("创建上传目录失败: {}", e.getMessage());
}
}
/**
* 上传文件
*/
@PostMapping("/file")
@Operation(summary = "上传文件", description = "支持Excel、Word、PDF、ZIP等格式")
public Result<Map<String, Object>> uploadFile(@RequestParam("file") MultipartFile file) {
try {
// 验证文件
if (file == null || file.isEmpty()) {
return Result.error(400, "文件不能为空");
}
// 验证文件大小
if (file.getSize() > maxSize) {
return Result.error(400, String.format("文件大小不能超过 %d MB", maxSize / 1024 / 1024));
}
// 验证文件扩展名
String originalFilename = file.getOriginalFilename();
if (originalFilename == null || originalFilename.isEmpty()) {
return Result.error(400, "文件名不能为空");
}
String extension = getFileExtension(originalFilename);
if (!ALLOWED_EXTENSIONS_SET.contains(extension.toLowerCase())) {
return Result.error(400, String.format("不支持的文件格式,支持的格式:%s", allowedExtensions));
}
// 生成文件存储路径
String dateDir = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
String fileName = generateFileName(originalFilename);
String relativePath = dateDir + "/" + fileName;
String fullPath = uploadPath + "/" + relativePath;
// 确保目录存在
Path targetPath = Paths.get(fullPath);
Files.createDirectories(targetPath.getParent());
// 保存文件
file.transferTo(targetPath.toFile());
log.info("文件上传成功: originalName={}, savedPath={}, size={}",
originalFilename, fullPath, file.getSize());
// 构建返回数据
Map<String, Object> data = new HashMap<>();
data.put("fileName", originalFilename);
data.put("filePath", relativePath);
data.put("fileUrl", "/api/upload/download?path=" + relativePath);
data.put("fileSize", file.getSize());
data.put("fileType", extension);
return Result.success(data);
} catch (IOException e) {
log.error("文件上传失败: {}", e.getMessage(), e);
return Result.error(500, "文件上传失败: " + e.getMessage());
} catch (Exception e) {
log.error("文件上传异常: {}", e.getMessage(), e);
return Result.error(500, "文件上传异常: " + e.getMessage());
}
}
/**
* 下载文件
*/
@GetMapping("/download")
@Operation(summary = "下载文件", description = "根据文件路径下载文件")
public org.springframework.http.ResponseEntity<org.springframework.core.io.Resource> downloadFile(
@RequestParam("path") String filePath) {
try {
String fullPath = uploadPath + "/" + filePath;
Path path = Paths.get(fullPath);
if (!Files.exists(path)) {
return org.springframework.http.ResponseEntity.notFound().build();
}
org.springframework.core.io.Resource resource =
new org.springframework.core.io.UrlResource(path.toUri());
return org.springframework.http.ResponseEntity.ok()
.header(org.springframework.http.HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
} catch (Exception e) {
log.error("文件下载失败: {}", e.getMessage(), e);
return org.springframework.http.ResponseEntity.internalServerError().build();
}
}
/**
* 获取文件扩展名
*/
private String getFileExtension(String filename) {
int lastDotIndex = filename.lastIndexOf('.');
if (lastDotIndex > 0 && lastDotIndex < filename.length() - 1) {
return filename.substring(lastDotIndex);
}
return "";
}
/**
* 生成唯一文件名
*/
private String generateFileName(String originalFilename) {
String extension = getFileExtension(originalFilename);
String nameWithoutExt = originalFilename.substring(0, originalFilename.lastIndexOf('.'));
String timestamp = String.valueOf(System.currentTimeMillis());
String random = UUID.randomUUID().toString().substring(0, 8);
return nameWithoutExt + "_" + timestamp + "_" + random + extension;
}
}

View File

@ -0,0 +1,129 @@
package com.designhub.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.designhub.common.Result;
import com.designhub.entity.User;
import com.designhub.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 用户管理Controller
*/
@Tag(name = "用户管理", description = "用户管理相关API")
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
@Slf4j
public class UserController {
private final UserService userService;
@Operation(summary = "分页查询用户列表")
@GetMapping
public Result<Page<User>> getUserList(
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Long deptId,
@RequestParam(required = false) Integer status
) {
return Result.success(userService.getUserList(pageNum, pageSize, keyword, deptId, status));
}
@Operation(summary = "获取全部用户列表")
@GetMapping("/all")
public Result<List<User>> getAllUsers() {
return Result.success(userService.getAllUsers());
}
@Operation(summary = "根据ID获取用户")
@GetMapping("/{id}")
public Result<User> getUserById(@PathVariable Long id) {
return Result.success(userService.getUserById(id));
}
@Operation(summary = "创建用户")
@PostMapping
public Result<User> createUser(@RequestBody User user) {
return Result.success(userService.createUser(user));
}
@Operation(summary = "更新用户")
@PutMapping("/{id}")
public Result<Boolean> updateUser(@PathVariable Long id, @RequestBody User user) {
user.setId(id);
return Result.success(userService.updateUser(user));
}
@Operation(summary = "删除用户")
@DeleteMapping("/{id}")
public Result<Boolean> deleteUser(@PathVariable Long id) {
return Result.success(userService.deleteUser(id));
}
@Operation(summary = "重置密码")
@PostMapping("/{id}/reset-password")
public Result<Boolean> resetPassword(
@PathVariable Long id,
@RequestBody Map<String, String> params
) {
String newPassword = params.getOrDefault("newPassword", "123456");
return Result.success(userService.resetPassword(id, newPassword));
}
@Operation(summary = "修改密码")
@PostMapping("/{id}/change-password")
public Result<Boolean> changePassword(
@PathVariable Long id,
@RequestBody Map<String, String> params
) {
String oldPassword = params.get("oldPassword");
String newPassword = params.get("newPassword");
return Result.success(userService.changePassword(id, oldPassword, newPassword));
}
@Operation(summary = "为用户分配角色")
@PostMapping("/{id}/roles")
public Result<Boolean> assignRoles(
@PathVariable Long id,
@RequestBody List<Long> roleIds
) {
return Result.success(userService.assignRoles(id, roleIds));
}
@Operation(summary = "获取用户的角色ID列表")
@GetMapping("/{id}/roles")
public Result<List<Long>> getUserRoleIds(@PathVariable Long id) {
return Result.success(userService.getUserRoleIds(id));
}
@Operation(summary = "启用/禁用用户")
@PutMapping("/{id}/status")
public Result<Boolean> toggleUserStatus(
@PathVariable Long id,
@RequestParam Integer status
) {
return Result.success(userService.toggleUserStatus(id, status));
}
@Operation(summary = "调整用户所属部门")
@PutMapping("/{id}/department")
public Result<Boolean> updateUserDepartment(
@PathVariable Long id,
@RequestBody Map<String, Long> params
) {
Long deptId = params.get("deptId");
log.info("调整用户部门请求: userId={}, deptId={}, params={}", id, deptId, params);
boolean result = userService.updateUserDepartment(id, deptId);
log.info("调整用户部门结果: userId={}, deptId={}, result={}", id, deptId, result);
return Result.success(result);
}
}

View File

@ -0,0 +1,117 @@
package com.designhub.controller;
import com.designhub.common.Result;
import com.designhub.entity.UserGoal;
import com.designhub.service.UserGoalService;
import com.designhub.util.JwtUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 用户目标Controller
*/
@Tag(name = "目标管理", description = "用户目标管理相关API")
@RestController
@RequestMapping("/api/goals")
@RequiredArgsConstructor
public class UserGoalController {
private final UserGoalService userGoalService;
private final JwtUtil jwtUtil;
@Operation(summary = "创建目标")
@PostMapping
public Result<UserGoal> createGoal(
@RequestBody UserGoal goal,
@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
goal.setUserId(userId);
return Result.success(userGoalService.createGoal(goal));
}
@Operation(summary = "创建预设目标")
@PostMapping("/preset/{type}")
public Result<UserGoal> createPresetGoal(
@PathVariable String type,
@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
return Result.success(userGoalService.createPresetGoal(userId, type));
}
@Operation(summary = "更新目标")
@PutMapping("/{id}")
public Result<UserGoal> updateGoal(@PathVariable Long id, @RequestBody UserGoal goal) {
return Result.success(userGoalService.updateGoal(id, goal));
}
@Operation(summary = "更新目标进度")
@PutMapping("/{id}/progress")
public Result<UserGoal> updateProgress(
@PathVariable Long id,
@RequestParam Integer progressValue) {
return Result.success(userGoalService.updateProgress(id, progressValue));
}
@Operation(summary = "获取我的目标列表")
@GetMapping("/my")
public Result<List<UserGoal>> getMyGoals(
@RequestParam(required = false) String status,
@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
return Result.success(userGoalService.getUserGoals(userId, status));
}
@Operation(summary = "获取我的活跃目标")
@GetMapping("/my/active")
public Result<List<UserGoal>> getMyActiveGoals(
@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
return Result.success(userGoalService.getActiveGoals(userId));
}
@Operation(summary = "获取目标详情")
@GetMapping("/{id}")
public Result<UserGoal> getGoalById(@PathVariable Long id) {
return Result.success(userGoalService.getGoalById(id));
}
@Operation(summary = "领取目标奖励")
@PostMapping("/{id}/claim-reward")
public Result<Boolean> claimReward(
@PathVariable Long id,
@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
return Result.success(userGoalService.claimReward(id, userId));
}
@Operation(summary = "取消目标")
@PostMapping("/{id}/cancel")
public Result<Void> cancelGoal(
@PathVariable Long id,
@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
userGoalService.cancelGoal(id, userId);
return Result.success();
}
@Operation(summary = "删除目标")
@DeleteMapping("/{id}")
public Result<Void> deleteGoal(
@PathVariable Long id,
@RequestHeader("Authorization") String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long userId = jwtUtil.getUserIdFromToken(token);
userGoalService.deleteGoal(id, userId);
return Result.success();
}
}

View File

@ -0,0 +1,86 @@
package com.designhub.dto;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
/**
* 部门创建请求
*/
@Data
public class DepartmentCreateRequest {
@NotBlank(message = "部门名称不能为空")
private String deptName;
/**
* 部门编码,可选
*/
private String deptCode;
@NotBlank(message = "部门类型不能为空")
private String deptType; // demand / receiving
private Long parentId;
/**
* 部门负责人
*/
private Long leaderId;
/**
* 部门描述
*/
private String description;
/**
* 排序
*/
@Min(value = 0, message = "排序必须大于等于0")
private Integer sortOrder = 0;
/**
* 状态:0-禁用 1-启用
*/
private Integer status = 1;
/**
* 接单部门允许的需求类型
*/
private List<String> allowedDemandTypes;
/**
* 是否启用积分限制(仅需求部门)
*/
private Integer limitEnabled;
/**
* 限制周期:DAILY/WEEKLY/MONTHLY
*/
private String limitPeriod;
/**
* 周期限额
*/
private Integer periodLimit;
/**
* 预警阈值(百分比)
*/
private Integer alertThreshold;
/**
* 超限动作:alert/block
*/
private String overLimitAction;
/**
* 初始积分余额(仅需求部门)
*/
@Min(value = 0, message = "初始积分不能为负数")
private Integer initialScoreBalance;
}

View File

@ -0,0 +1,37 @@
package com.designhub.dto;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 部门积分限制配置请求
*/
@Data
public class DepartmentLimitSettingsRequest {
@NotNull(message = "是否启用限制不能为空")
private Integer limitEnabled;
/**
* 限制周期:DAILY/WEEKLY/MONTHLY
*/
private String limitPeriod;
/**
* 周期限额
*/
@Min(value = 0, message = "限制额度不能为负数")
private Integer periodLimit;
/**
* 预警阈值
*/
private Integer alertThreshold;
/**
* 超限动作
*/
private String overLimitAction;
}

View File

@ -0,0 +1,21 @@
package com.designhub.dto;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class DepartmentScoreAdjustRequest {
@NotBlank(message = "操作类型不能为空")
private String action;
@Min(value = 1, message = "调整积分必须大于0")
private Integer amount;
private String remark;
public String getAction() { return action; }
public void setAction(String action) { this.action = action; }
public Integer getAmount() { return amount; }
public void setAmount(Integer amount) { this.amount = amount; }
public String getRemark() { return remark; }
public void setRemark(String remark) { this.remark = remark; }
}

View File

@ -0,0 +1,53 @@
package com.designhub.dto;
import jakarta.validation.constraints.Min;
import java.util.List;
public class DepartmentUpdateRequest {
private String deptName;
private String deptCode;
private String deptType;
private Long parentId;
private Long leaderId;
private String description;
@Min(value = 0, message = "排序必须大于等于0")
private Integer sortOrder;
private Integer status;
private List<String> allowedDemandTypes;
private Integer limitEnabled;
private String limitPeriod;
private Integer periodLimit;
private Integer alertThreshold;
private String overLimitAction;
public String getDeptName() { return deptName; }
public void setDeptName(String deptName) { this.deptName = deptName; }
public String getDeptCode() { return deptCode; }
public void setDeptCode(String deptCode) { this.deptCode = deptCode; }
public String getDeptType() { return deptType; }
public void setDeptType(String deptType) { this.deptType = deptType; }
public Long getParentId() { return parentId; }
public void setParentId(Long parentId) { this.parentId = parentId; }
public Long getLeaderId() { return leaderId; }
public void setLeaderId(Long leaderId) { this.leaderId = leaderId; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public Integer getSortOrder() { return sortOrder; }
public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; }
public Integer getStatus() { return status; }
public void setStatus(Integer status) { this.status = status; }
public List<String> getAllowedDemandTypes() { return allowedDemandTypes; }
public void setAllowedDemandTypes(List<String> allowedDemandTypes) { this.allowedDemandTypes = allowedDemandTypes; }
public Integer getLimitEnabled() { return limitEnabled; }
public void setLimitEnabled(Integer limitEnabled) { this.limitEnabled = limitEnabled; }
public String getLimitPeriod() { return limitPeriod; }
public void setLimitPeriod(String limitPeriod) { this.limitPeriod = limitPeriod; }
public Integer getPeriodLimit() { return periodLimit; }
public void setPeriodLimit(Integer periodLimit) { this.periodLimit = periodLimit; }
public Integer getAlertThreshold() { return alertThreshold; }
public void setAlertThreshold(Integer alertThreshold) { this.alertThreshold = alertThreshold; }
public String getOverLimitAction() { return overLimitAction; }
public void setOverLimitAction(String overLimitAction) { this.overLimitAction = overLimitAction; }
}

View File

@ -0,0 +1,34 @@
package com.designhub.dto;
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
/**
* 登录请求DTO
*/
@Data
public class LoginDTO {
/**
* 用户名
*/
@NotBlank(message = "用户名不能为空")
private String username;
/**
* 密码
*/
@NotBlank(message = "密码不能为空")
private String password;
/**
* 登录类型:password-密码登录 dingtalk-钉钉登录 casdoor-Casdoor登录
*/
private String loginType = "password";
/**
* 钉钉授权码(钉钉登录时使用)
*/
private String authCode;
}

View File

@ -0,0 +1,78 @@
package com.designhub.dto;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* 流水线任务创建请求DTO
*/
@Data
public class PipelineTaskDTO {
/**
* 任务标题
*/
private String title;
/**
* 是否流水线任务
*/
private Boolean isPipeline;
/**
* 流水线模式:sequential(顺序)/parallel(并行)
*/
private String pipelineMode;
/**
* 子任务列表
*/
private List<TaskItemDTO> items;
/**
* 任务其他字段(与普通任务相同)
*/
private Long brandId;
private Long categoryId;
private Long platformId;
private String spuInfo;
private String taskSubType;
private String attachmentUrl;
private String remark;
private Integer priority;
private LocalDateTime deadline; // 截止时间
private String releaseType; // 发布类型:default/recruitment/assigned
private Long receiverId; // 指定接收人ID(assigned模式使用)
/**
* 子任务项DTO
*/
@Data
public static class TaskItemDTO {
/**
* 任务类型ID
*/
private Long taskTypeId;
/**
* 数量
*/
private Integer quantity;
/**
* 单价积分
*/
private Integer unitScore;
/**
* 预估工时
*/
private Double estimatedHours;
/**
* 排序
*/
private Integer sortOrder;
}
}

View File

@ -0,0 +1,52 @@
package com.designhub.dto;
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
/**
* 注册请求DTO
*/
@Data
public class RegisterDTO {
/**
* 用户名
*/
@NotBlank(message = "用户名不能为空")
private String username;
/**
* 密码
*/
@NotBlank(message = "密码不能为空")
private String password;
/**
* 真实姓名
*/
@NotBlank(message = "真实姓名不能为空")
private String realName;
/**
* 角色ID(5-设计师DESIGNER, 6-需求方REQUESTER)
*/
@NotNull(message = "请选择角色")
private Long roleId;
/**
* 部门ID
*/
@NotNull(message = "请选择部门")
private Long deptId;
/**
* 邮箱(可选)
*/
private String email;
/**
* 手机号(可选)
*/
private String phone;
}

View File

@ -0,0 +1,36 @@
package com.designhub.dto;
import lombok.Data;
/**
* 任务审核DTO
*/
@Data
public class TaskApprovalDTO {
/**
* 审核意见
*/
private String comment;
/**
* 任务更新数据(审核时可修改任务)
*/
private TaskUpdateDTO taskUpdates;
// Getters and Setters
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public TaskUpdateDTO getTaskUpdates() {
return taskUpdates;
}
public void setTaskUpdates(TaskUpdateDTO taskUpdates) {
this.taskUpdates = taskUpdates;
}
}

View File

@ -0,0 +1,33 @@
package com.designhub.dto;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 任务指派请求DTO
*/
@Data
public class TaskAssignRequest {
/**
* 任务ID
*/
@NotNull(message = "任务ID不能为空")
private Long taskId;
/**
* 被指派人ID
*/
@NotNull(message = "被指派人ID不能为空")
private Long assigneeId;
/**
* 指派类型:force-强制指派 recommend-推荐
*/
private String assignType = "force";
/**
* 指派说明
*/
private String comment;
}

View File

@ -0,0 +1,19 @@
package com.designhub.dto;
import jakarta.validation.constraints.NotNull;
public class TaskAuditRequest {
@NotNull(message = "任务ID不能为空")
private Long taskId;
@NotNull(message = "审核结果不能为空")
private String result;
private String comment;
public Long getTaskId() { return taskId; }
public void setTaskId(Long taskId) { this.taskId = taskId; }
public String getResult() { return result; }
public void setResult(String result) { this.result = result; }
public String getComment() { return comment; }
public void setComment(String comment) { this.comment = comment; }
}

View File

@ -0,0 +1,116 @@
package com.designhub.dto;
import lombok.Data;
import java.util.List;
/**
* 任务更新DTO(用于审核时修改任务)
*/
@Data
public class TaskUpdateDTO {
private String title;
private String taskSubType;
private Long brandId;
private Long platformId;
private Long categoryId;
private String spuInfo;
private String remark;
private String attachmentUrl;
private List<TaskTypeUpdateDTO> taskTypes;
@Data
public static class TaskTypeUpdateDTO {
private Long taskTypeId;
private Integer quantity;
public Long getTaskTypeId() {
return taskTypeId;
}
public void setTaskTypeId(Long taskTypeId) {
this.taskTypeId = taskTypeId;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
}
// Getters and Setters
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getTaskSubType() {
return taskSubType;
}
public void setTaskSubType(String taskSubType) {
this.taskSubType = taskSubType;
}
public Long getBrandId() {
return brandId;
}
public void setBrandId(Long brandId) {
this.brandId = brandId;
}
public Long getPlatformId() {
return platformId;
}
public void setPlatformId(Long platformId) {
this.platformId = platformId;
}
public Long getCategoryId() {
return categoryId;
}
public void setCategoryId(Long categoryId) {
this.categoryId = categoryId;
}
public String getSpuInfo() {
return spuInfo;
}
public void setSpuInfo(String spuInfo) {
this.spuInfo = spuInfo;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public String getAttachmentUrl() {
return attachmentUrl;
}
public void setAttachmentUrl(String attachmentUrl) {
this.attachmentUrl = attachmentUrl;
}
public List<TaskTypeUpdateDTO> getTaskTypes() {
return taskTypes;
}
public void setTaskTypes(List<TaskTypeUpdateDTO> taskTypes) {
this.taskTypes = taskTypes;
}
}

View File

@ -0,0 +1,95 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 成就徽章实体类
*/
@Data
@TableName("achievement")
public class Achievement implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 成就代码(唯一标识)
*/
private String achievementCode;
/**
* 成就名称
*/
private String achievementName;
/**
* 成就描述
*/
private String description;
/**
* 成就图标URL
*/
private String iconUrl;
/**
* 成就类型:task-任务类, score-积分类, quality-质量类, speed-速度类
*/
private String achievementType;
/**
* 达成条件(JSON格式)
* 例如:{"type":"task_count","value":100}
*/
@TableField("`condition`")
private String condition;
/**
* 奖励积分
*/
private Integer rewardScore;
/**
* 成就等级:bronze-铜牌, silver-银牌, gold-金牌, diamond-钻石
*/
private String level;
/**
* 排序号
*/
private Integer sortOrder;
/**
* 状态:0-禁用,1-启用
*/
@TableField("enabled")
private Integer status;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标志:0-未删除,1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@ -0,0 +1,69 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 品牌实体类
*/
@Data
@TableName("brand")
public class Brand implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 品牌名称
*/
@TableField("brand_name")
private String name;
/**
* 品牌编码
*/
@TableField("brand_code")
private String brandCode;
/**
* 品牌Logo URL
*/
@TableField("logo")
private String logoUrl;
/**
* 品牌描述
*/
private String description;
/**
* 排序号
*/
private Integer sortOrder;
/**
* 是否启用:0-禁用,1-启用
*/
@TableField("status")
private Integer isEnabled;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,84 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 类目实体类
*/
@Data
@TableName("category")
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 父类目ID,0表示一级类目
*/
private Long parentId;
/**
* 类目名称
*/
@TableField("category_name")
private String name;
/**
* 类目编码
*/
@TableField("category_code")
private String categoryCode;
/**
* 类目层级:1-一级,2-二级,3-三级
*/
private Integer level;
/**
* 是否叶子类目:0-否,1-是
*/
private Integer isLeaf;
/**
* 类目路径,用/分隔,如:/1/12/123(数据库不存在此字段)
*/
@TableField(exist = false)
private String path;
/**
* 类目描述
*/
private String description;
/**
* 排序号
*/
private Integer sortOrder;
/**
* 是否启用:0-禁用,1-启用
*/
@TableField("status")
private Integer isEnabled;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,68 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 签到记录实体类
*/
@Data
@TableName("user_check_in")
public class CheckIn implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 签到日期
*/
private LocalDate checkInDate;
/**
* 连续签到天数
*/
private Integer continuousDays;
/**
* 奖励积分
*/
private Integer rewardScore;
/**
* 签到备注
*/
private String remark;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标志:0-未删除,1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@ -0,0 +1,110 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 竞赛活动实体
*/
@Data
@TableName("contest")
public class Contest implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
/**
* 竞赛名称
*/
private String name;
/**
* 竞赛描述
*/
private String description;
/**
* 竞赛类型:monthly-月度竞赛, weekly-周度竞赛, special-专项竞赛
*/
private String contestType;
/**
* 开始时间
*/
private LocalDateTime startTime;
/**
* 结束时间
*/
private LocalDateTime endTime;
/**
* 参与范围:all-全体设计师, department-指定部门, user-指定人员
*/
private String participantScope;
/**
* 指定部门ID列表(JSON数组)
*/
private String departmentIds;
/**
* 指定用户ID列表(JSON数组)
*/
private String userIds;
/**
* 竞赛规则配置(JSON对象)
* 例如:{"taskCompleted":{"score":2,"weight":30},"fiveStarReview":{"score":10,"weight":25}}
*/
private String rules;
/**
* 奖励配置(JSON数组)
* 例如:[{"rank":1,"score":1500,"badge":"月度冠军"},{"rank":2,"score":1000}]
*/
private String rewards;
/**
* 参与奖配置(JSON对象)
*/
private String participationReward;
/**
* 竞赛状态:draft-草稿, scheduled-待开始, active-进行中, ended-已结束, cancelled-已取消
*/
private String status;
/**
* 参与人数
*/
private Integer participantCount;
/**
* 创建人ID
*/
private Long creatorId;
/**
* 是否删除:0-否,1-是
*/
@TableLogic
private Integer deleted;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,107 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 竞赛参与者实体
*/
@Data
@TableName("contest_participant")
public class ContestParticipant implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
/**
* 竞赛ID
*/
private Long contestId;
/**
* 用户ID
*/
private Long userId;
/**
* 用户姓名(冗余)
*/
private String userName;
/**
* 部门ID
*/
private Long deptId;
/**
* 部门名称(冗余)
*/
private String deptName;
/**
* 完成任务数
*/
private Integer taskCompleted;
/**
* 五星好评数
*/
private Integer fiveStarCount;
/**
* 提前完成次数
*/
private Integer earlyCompletionCount;
/**
* 被表扬次数
*/
private Integer praiseCount;
/**
* 无延误天数
*/
private Integer noDelayDays;
/**
* 总得分
*/
private Integer totalScore;
/**
* 当前排名
*/
private Integer currentRank;
/**
* 是否已领取奖励:0-否,1-是
*/
private Integer rewardClaimed;
/**
* 奖励积分
*/
private Integer rewardScore;
/**
* 奖励领取时间
*/
private LocalDateTime rewardClaimTime;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,106 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 延误扣分记录实体类
*/
@Data
@TableName("delay_penalty_record")
public class DelayPenaltyRecord implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 任务ID
*/
private Long taskId;
/**
* 设计师ID
*/
private Long designerId;
/**
* 延误天数
*/
private Integer delayDays;
/**
* 延误小时数
*/
private Long delayHours;
/**
* 延误等级:light-轻微 general-一般 severe-严重 major-重大
*/
private String delayLevel;
/**
* 扣除积分
*/
private Integer penaltyScore;
/**
* 原截止时间
*/
private LocalDateTime originalDeadline;
/**
* 实际完成/转移时间
*/
private LocalDateTime actualTime;
/**
* 状态:confirmed-已确认 appealed-已申诉 approved-申诉通过 rejected-申诉驳回
*/
private String status;
/**
* 申诉原因
*/
private String appealReason;
/**
* 申诉时间
*/
private LocalDateTime appealTime;
/**
* 申诉结果
*/
private String appealResult;
/**
* 审批人ID
*/
private Long approverId;
/**
* 审批时间
*/
private LocalDateTime approveTime;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,86 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 延误扣分规则实体类
*/
@Data
@TableName("delay_penalty_rule")
public class DelayPenaltyRule implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 等级名称:light-轻微 general-一般 severe-严重 major-重大
*/
private String levelName;
/**
* 最小延误天数(实际使用小时数)
*/
private Integer minDays;
/**
* 最小延误小时数
*/
private Integer minHours;
/**
* 最大延误天数(实际使用小时数,NULL表示无上限)
*/
private Integer maxDays;
/**
* 最大延误小时数(NULL表示无上限)
*/
private Integer maxHours;
/**
* 基础扣分
*/
private Integer baseScore;
/**
* 每天递增扣分
*/
private Integer perDayScore;
/**
* 每小时扣分(用于按小时计算的等级)
*/
private Integer perHourScore;
/**
* 规则描述
*/
private String description;
/**
* 是否启用:0-否 1-是
*/
private Integer enabled;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,75 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 需求类型实体类
*/
@Data
@TableName("task_type")
public class DemandType implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 类型名称
*/
@TableField("type_name")
private String name;
/**
* 单价(积分)
*/
@TableField("unit_score")
private Integer unitScore;
/**
* 预估工时(小时)
*/
@TableField("estimated_hours")
private Integer estimatedHours;
/**
* 类型描述
*/
private String description;
/**
* 排序号
*/
private Integer sortOrder;
/**
* 是否启用:0-禁用,1-启用
*/
@TableField("status")
private Integer isEnabled;
/**
* 可接单部门ID列表(逗号分隔),为空表示所有部门都可接单
*/
@TableField("allowed_dept_ids")
private String allowedDeptIds;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,38 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 需求类型-接单部门关联表实体类
*/
@Data
@TableName("demand_type_department")
public class DemandTypeDepartment implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 关联ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 需求类型ID
*/
private Long demandTypeId;
/**
* 接单部门ID
*/
private Long departmentId;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}

View File

@ -0,0 +1,235 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 部门实体类
* 注意:已移除@Data注解,改用手动getter/setter以解决Lombok编译问题
*/
@TableName("sys_dept")
public class Department implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 父部门ID,0表示顶级部门
*/
private Long parentId;
/**
* 部门名称
*/
private String deptName;
/**
* 部门编码
*/
private String deptCode;
/**
* 钉钉部门ID
*/
private String dingDeptId;
/**
* 部门类型:demand-需求部门,receiving-接单部门
*/
private String deptType;
/**
* 部门负责人ID
*/
@TableField("leader_id")
private Long leaderId;
/**
* 部门积分余额(冗余字段,用于快速展示)
*/
@TableField("score_balance")
private Integer scoreBalance;
/**
* 是否启用积分消耗限制:0-否,1-是
*/
@TableField("limit_enabled")
private Integer limitEnabled;
/**
* 限制周期:DAILY/WEEKLY/MONTHLY
*/
@TableField("limit_period")
private String limitPeriod;
/**
* 周期限额
*/
@TableField("period_limit")
private Integer periodLimit;
/**
* 预警阈值(百分比)
*/
@TableField("alert_threshold")
private Integer alertThreshold;
/**
* 超限后的动作:alert-仅提醒,block-禁止发起
*/
@TableField("over_limit_action")
private String overLimitAction;
/**
* 允许的需求类型(接单部门使用,逗号分隔)
*/
@TableField("allowed_demand_types")
private String allowedDemandTypes;
/**
* 部门主管ID(接单部门)- 数据库不存在此字段
*/
@TableField(exist = false)
private Long supervisorId;
/**
* 部门描述 - 数据库不存在此字段
*/
@TableField(exist = false)
private String description;
/**
* 部门层级 - 数据库不存在此字段
*/
@TableField(exist = false)
private Integer level;
/**
* 部门路径 - 数据库不存在此字段
*/
@TableField(exist = false)
private String path;
/**
* 排序号
*/
private Integer sortOrder;
/**
* 状态:0-禁用,1-启用
*/
private Integer status;
/**
* 创建人ID - 数据库不存在此字段
*/
@TableField(exist = false)
private Long createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新人ID - 数据库不存在此字段
*/
@TableField(exist = false)
private Long updateBy;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标志:0-未删除,1-已删除
*/
@TableLogic
private Integer deleted;
// Getters and Setters (手动添加以解决Lombok编译问题)
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Long getParentId() { return parentId; }
public void setParentId(Long parentId) { this.parentId = parentId; }
public String getDeptName() { return deptName; }
public void setDeptName(String deptName) { this.deptName = deptName; }
public String getDeptCode() { return deptCode; }
public void setDeptCode(String deptCode) { this.deptCode = deptCode; }
public String getDingDeptId() { return dingDeptId; }
public void setDingDeptId(String dingDeptId) { this.dingDeptId = dingDeptId; }
public String getDeptType() { return deptType; }
public void setDeptType(String deptType) { this.deptType = deptType; }
public Long getLeaderId() { return leaderId; }
public void setLeaderId(Long leaderId) { this.leaderId = leaderId; }
public Integer getScoreBalance() { return scoreBalance; }
public void setScoreBalance(Integer scoreBalance) { this.scoreBalance = scoreBalance; }
public Integer getLimitEnabled() { return limitEnabled; }
public void setLimitEnabled(Integer limitEnabled) { this.limitEnabled = limitEnabled; }
public String getLimitPeriod() { return limitPeriod; }
public void setLimitPeriod(String limitPeriod) { this.limitPeriod = limitPeriod; }
public Integer getPeriodLimit() { return periodLimit; }
public void setPeriodLimit(Integer periodLimit) { this.periodLimit = periodLimit; }
public Integer getAlertThreshold() { return alertThreshold; }
public void setAlertThreshold(Integer alertThreshold) { this.alertThreshold = alertThreshold; }
public String getOverLimitAction() { return overLimitAction; }
public void setOverLimitAction(String overLimitAction) { this.overLimitAction = overLimitAction; }
public String getAllowedDemandTypes() { return allowedDemandTypes; }
public void setAllowedDemandTypes(String allowedDemandTypes) { this.allowedDemandTypes = allowedDemandTypes; }
public Long getSupervisorId() { return supervisorId; }
public void setSupervisorId(Long supervisorId) { this.supervisorId = supervisorId; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public Integer getLevel() { return level; }
public void setLevel(Integer level) { this.level = level; }
public String getPath() { return path; }
public void setPath(String path) { this.path = path; }
public Integer getSortOrder() { return sortOrder; }
public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; }
public Integer getStatus() { return status; }
public void setStatus(Integer status) { this.status = status; }
public Long getCreateBy() { return createBy; }
public void setCreateBy(Long createBy) { this.createBy = createBy; }
public LocalDateTime getCreateTime() { return createTime; }
public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
public Long getUpdateBy() { return updateBy; }
public void setUpdateBy(Long updateBy) { this.updateBy = updateBy; }
public LocalDateTime getUpdateTime() { return updateTime; }
public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; }
public Integer getDeleted() { return deleted; }
public void setDeleted(Integer deleted) { this.deleted = deleted; }
}

View File

@ -0,0 +1,88 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 钉钉配置实体
*/
@Data
@TableName("dingtalk_config")
public class DingTalkConfig implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
/**
* 配置名称
*/
private String name;
/**
* 应用AgentId
*/
private String agentId;
/**
* 应用AppKey
*/
private String appKey;
/**
* 应用AppSecret (加密存储)
*/
private String appSecret;
/**
* 企业ID (CorpId)
*/
private String corpId;
/**
* 机器人Webhook地址
*/
private String robotWebhook;
/**
* 机器人密钥
*/
private String robotSecret;
/**
* 是否启用:0-禁用,1-启用
*/
private Integer enabled;
/**
* 启用功能配置(JSON)
* 例如:{"notification":true,"login":false,"attendance":false}
*/
private String enabledFeatures;
/**
* Access Token(缓存)
*/
private String accessToken;
/**
* Access Token过期时间
*/
private LocalDateTime tokenExpireTime;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,200 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 文件上传记录实体类
*/
@Data
@TableName("sys_file_upload")
public class FileUpload {
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 原始文件名
*/
private String originalName;
/**
* 存储文件名
*/
private String storedName;
/**
* 文件路径
*/
private String filePath;
/**
* 文件访问URL
*/
private String fileUrl;
/**
* 文件大小(字节)
*/
private Long fileSize;
/**
* 文件类型
*/
private String fileType;
/**
* 上传者ID
*/
private Long uploaderId;
/**
* 上传者姓名
*/
private String uploaderName;
/**
* 关联类型:task-任务, user-用户等
*/
private String relatedType;
/**
* 关联ID
*/
private Long relatedId;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标志:0-未删除,1-已删除
*/
@TableLogic
private Integer deleted;
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getOriginalName() {
return originalName;
}
public void setOriginalName(String originalName) {
this.originalName = originalName;
}
public String getStoredName() {
return storedName;
}
public void setStoredName(String storedName) {
this.storedName = storedName;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public String getFileUrl() {
return fileUrl;
}
public void setFileUrl(String fileUrl) {
this.fileUrl = fileUrl;
}
public Long getFileSize() {
return fileSize;
}
public void setFileSize(Long fileSize) {
this.fileSize = fileSize;
}
public String getFileType() {
return fileType;
}
public void setFileType(String fileType) {
this.fileType = fileType;
}
public Long getUploaderId() {
return uploaderId;
}
public void setUploaderId(Long uploaderId) {
this.uploaderId = uploaderId;
}
public String getUploaderName() {
return uploaderName;
}
public void setUploaderName(String uploaderName) {
this.uploaderName = uploaderName;
}
public String getRelatedType() {
return relatedType;
}
public void setRelatedType(String relatedType) {
this.relatedType = relatedType;
}
public Long getRelatedId() {
return relatedId;
}
public void setRelatedId(Long relatedId) {
this.relatedId = relatedId;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
public Integer getDeleted() {
return deleted;
}
public void setDeleted(Integer deleted) {
this.deleted = deleted;
}
}

View File

@ -0,0 +1,268 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 通知消息实体类
*/
@Data
@TableName("sys_notification")
public class Notification implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 通知类型:system-系统通知, task-任务通知, score-积分通知, review-评审通知
*/
@TableField("type")
private String notificationType;
/**
* 接收用户ID
*/
@TableField("receiver_id")
private Long receiverId;
/**
* 发送用户ID(可为空,系统通知为空)
*/
@TableField(exist = false)
private Long senderId;
/**
* 通知标题
*/
private String title;
/**
* 通知内容
*/
private String content;
/**
* 关联业务ID(任务ID、积分记录ID等)
*/
@TableField("related_task_id")
private Long relatedId;
/**
* 关联业务类型:task, score, review, system
*/
@TableField(exist = false)
private String relatedType;
/**
* 跳转链接
*/
@TableField("link")
private String linkUrl;
/**
* 是否已读:0-未读,1-已读
*/
@TableField("is_read")
private Integer isRead;
/**
* 阅读时间
*/
private LocalDateTime readTime;
/**
* 是否推送到钉钉:0-否,1-是
*/
@TableField("ding_enabled")
private Integer pushDingtalk;
/**
* 钉钉推送状态:pending-待推送, success-成功, failed-失败
*/
@TableField(exist = false)
private String dingtalkStatus;
/**
* 钉钉推送时间
*/
@TableField("ding_push_time")
private LocalDateTime dingtalkPushTime;
/**
* 优先级:low-低, normal-普通, high-高, urgent-紧急
*/
@TableField("ding_priority")
private Integer priority;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标志:0-未删除,1-已删除
*/
@TableLogic
private Integer deleted;
// Getters and Setters (手动添加以解决Lombok编译问题)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getNotificationType() {
return notificationType;
}
public void setNotificationType(String notificationType) {
this.notificationType = notificationType;
}
public Long getReceiverId() {
return receiverId;
}
public void setReceiverId(Long receiverId) {
this.receiverId = receiverId;
}
public Long getSenderId() {
return senderId;
}
public void setSenderId(Long senderId) {
this.senderId = senderId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Long getRelatedId() {
return relatedId;
}
public void setRelatedId(Long relatedId) {
this.relatedId = relatedId;
}
public String getRelatedType() {
return relatedType;
}
public void setRelatedType(String relatedType) {
this.relatedType = relatedType;
}
public String getLinkUrl() {
return linkUrl;
}
public void setLinkUrl(String linkUrl) {
this.linkUrl = linkUrl;
}
public Integer getIsRead() {
return isRead;
}
public void setIsRead(Integer isRead) {
this.isRead = isRead;
}
public LocalDateTime getReadTime() {
return readTime;
}
public void setReadTime(LocalDateTime readTime) {
this.readTime = readTime;
}
public Integer getPushDingtalk() {
return pushDingtalk;
}
public void setPushDingtalk(Integer pushDingtalk) {
this.pushDingtalk = pushDingtalk;
}
public String getDingtalkStatus() {
return dingtalkStatus;
}
public void setDingtalkStatus(String dingtalkStatus) {
this.dingtalkStatus = dingtalkStatus;
}
public LocalDateTime getDingtalkPushTime() {
return dingtalkPushTime;
}
public void setDingtalkPushTime(LocalDateTime dingtalkPushTime) {
this.dingtalkPushTime = dingtalkPushTime;
}
public Integer getPriority() {
return priority;
}
public void setPriority(Integer priority) {
this.priority = priority;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
public Integer getDeleted() {
return deleted;
}
public void setDeleted(Integer deleted) {
this.deleted = deleted;
}
}

View File

@ -0,0 +1,96 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 权限实体类
*/
@Data
@TableName("sys_permission")
public class Permission implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 父权限ID,0表示顶级权限
*/
private Long parentId;
/**
* 权限编码
*/
private String permissionCode;
/**
* 权限名称
*/
private String permissionName;
/**
* 权限类型:menu-菜单,button-按钮,api-接口
*/
private String type;
/**
* 权限路径/接口路径
*/
private String path;
/**
* 请求方法:GET/POST/PUT/DELETE等
*/
private String method;
/**
* 图标
*/
private String icon;
/**
* 组件路径
*/
private String component;
/**
* 权限描述
*/
private String description;
/**
* 排序号
*/
private Integer sortOrder;
/**
* 状态:0-禁用,1-启用
*/
private Integer status;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标志:0-未删除,1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@ -0,0 +1,69 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 平台实体类
*/
@Data
@TableName("platform")
public class Platform implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 平台名称
*/
@TableField("platform_name")
private String name;
/**
* 平台编码
*/
@TableField("platform_code")
private String platformCode;
/**
* 平台图标 URL
*/
@TableField("icon")
private String iconUrl;
/**
* 平台描述
*/
private String description;
/**
* 排序号
*/
private Integer sortOrder;
/**
* 是否启用:0-禁用,1-启用
*/
@TableField("status")
private Integer isEnabled;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,109 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 自动奖励规则实体
*/
@Data
@TableName("reward_rule")
public class RewardRule implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
/**
* 规则名称
*/
private String name;
/**
* 规则描述
*/
private String description;
/**
* 规则类型:daily-每日奖励, weekly-每周奖励, monthly-每月奖励, task-任务奖励, rating-好评奖励
*/
private String ruleType;
/**
* 触发条件(JSON对象)
* 例如:{"type":"task_count","operator":">=","value":10}
*/
private String triggerCondition;
/**
* 奖励类型:score-积分, badge-徽章, both-两者
*/
private String rewardType;
/**
* 奖励积分数
*/
private Integer rewardScore;
/**
* 奖励徽章(JSON对象)
*/
private String rewardBadge;
/**
* 适用范围:all-全员, designer-设计师, department-指定部门
*/
private String scope;
/**
* 指定部门ID列表(JSON数组)
*/
private String scopeDeptIds;
/**
* 是否启用:0-禁用,1-启用
*/
private Integer enabled;
/**
* 执行周期(cron 表达式,仅周期性奖励使用)
*/
private String cronExpression;
/**
* 上次执行时间
*/
private LocalDateTime lastExecuteTime;
/**
* 优先级(数值越大优先级越高)
*/
private Integer priority;
/**
* 是否删除:0-否,1-是
*/
@TableLogic
private Integer deleted;
/**
* 创建人ID
*/
private Long creatorId;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,103 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 角色实体类
*/
@Data
@TableName("sys_role")
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 角色编码
*/
private String roleCode;
/**
* 角色名称
*/
private String roleName;
/**
* 角色描述
*/
private String description;
/**
* 角色级别:1-最高权限,数字越大权限越低
* 当前数据库表中无对应字段,标记为非持久化,避免 SQL 报错
*/
@TableField(exist = false)
private Integer level;
/**
* 数据权限范围:all-全部数据,dept-本部门,self-本人
* 数据库当前未存储该字段
*/
@TableField(exist = false)
private String dataScope;
/**
* 排序号
* 数据库当前未存储该字段
*/
@TableField(exist = false)
private Integer sortOrder;
/**
* 状态:0-禁用,1-启用
*/
private Integer status;
/**
* 是否内置角色:0-否,1-是(内置角色不可删除)
* 数据库当前未存储该字段
*/
@TableField(exist = false)
private Integer builtIn;
/**
* 创建人ID
* 数据库当前未存储该字段
*/
@TableField(exist = false)
private Long createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新人ID
* 数据库当前未存储该字段
*/
@TableField(exist = false)
private Long updateBy;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标志:0-未删除,1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@ -0,0 +1,39 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 角色权限关联实体类
*/
@Data
@TableName("sys_role_permission")
public class RolePermission implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 角色ID
*/
private Long roleId;
/**
* 权限ID
*/
private Long permissionId;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}

View File

@ -0,0 +1,187 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* S3存储配置实体类
*/
@Data
@TableName("sys_s3_config")
public class S3Config {
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* S3服务端点
*/
private String endpoint;
/**
* 存储桶名称
*/
private String bucket;
/**
* 访问密钥ID
*/
private String accessKeyId;
/**
* 访问密钥
*/
private String secretAccessKey;
/**
* 区域
*/
private String region;
/**
* 公共访问域名
*/
private String publicDomain;
/**
* 是否启用路径样式访问
*/
private Boolean enablePathStyle;
/**
* 文件存储路径前缀
*/
private String filePath;
/**
* 是否激活:0-未激活,1-激活
*/
private Boolean isActive;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标志:0-未删除,1-已删除
*/
@TableLogic
private Integer deleted;
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public String getBucket() {
return bucket;
}
public void setBucket(String bucket) {
this.bucket = bucket;
}
public String getAccessKeyId() {
return accessKeyId;
}
public void setAccessKeyId(String accessKeyId) {
this.accessKeyId = accessKeyId;
}
public String getSecretAccessKey() {
return secretAccessKey;
}
public void setSecretAccessKey(String secretAccessKey) {
this.secretAccessKey = secretAccessKey;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
public String getPublicDomain() {
return publicDomain;
}
public void setPublicDomain(String publicDomain) {
this.publicDomain = publicDomain;
}
public Boolean getEnablePathStyle() {
return enablePathStyle;
}
public void setEnablePathStyle(Boolean enablePathStyle) {
this.enablePathStyle = enablePathStyle;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean isActive) {
this.isActive = isActive;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
public Integer getDeleted() {
return deleted;
}
public void setDeleted(Integer deleted) {
this.deleted = deleted;
}
}

View File

@ -0,0 +1,148 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 激励积分池实体
*/
@Data
@TableName("score_pool")
public class ScorePool implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
/**
* 积分池名称
*/
private String name;
/**
* 积分池类型:monthly-月度池, quarterly-季度池, yearly-年度池, special-专项池
*/
private String poolType;
/**
* 周期年份
*/
private Integer year;
/**
* 周期月份(月度池使用)
*/
private Integer month;
/**
* 周期季度(季度池使用,1-4)
*/
private Integer quarter;
/**
* 总预算积分
*/
private Integer totalBudget;
/**
* 已使用积分
*/
private Integer usedScore;
/**
* 剩余积分
*/
private Integer remainingScore;
/**
* 任务完成奖励预算
*/
private Integer taskRewardBudget;
/**
* 任务完成奖励已使用
*/
private Integer taskRewardUsed;
/**
* 好评奖励预算
*/
private Integer ratingRewardBudget;
/**
* 好评奖励已使用
*/
private Integer ratingRewardUsed;
/**
* 竞赛奖励预算
*/
private Integer contestRewardBudget;
/**
* 竞赛奖励已使用
*/
private Integer contestRewardUsed;
/**
* 签到奖励预算
*/
private Integer checkinRewardBudget;
/**
* 签到奖励已使用
*/
private Integer checkinRewardUsed;
/**
* 其他奖励预算
*/
private Integer otherRewardBudget;
/**
* 其他奖励已使用
*/
private Integer otherRewardUsed;
/**
* 超支处理策略:stop-停止发放, alert-提醒继续, auto_increase-自动增加预算
*/
private String overflowStrategy;
/**
* 超支提醒阈值(百分比,如80表示使用80%时提醒)
*/
private Integer alertThreshold;
/**
* 状态:active-进行中, closed-已关闭, settled-已结算
*/
private String status;
/**
* 创建人ID
*/
private Long creatorId;
/**
* 是否删除:0-否,1-是
*/
@TableLogic
private Integer deleted;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,76 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 积分池使用记录实体
*/
@Data
@TableName("score_pool_record")
public class ScorePoolRecord implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
/**
* 积分池ID
*/
private Long poolId;
/**
* 用户ID
*/
private Long userId;
/**
* 用户姓名
*/
private String userName;
/**
* 奖励类型:task-任务, rating-好评, contest-竞赛, checkin-签到, goal-目标, other-其他
*/
private String rewardType;
/**
* 积分变动(正数为发放,负数为扣除)
*/
private Integer scoreChange;
/**
* 变动前池剩余
*/
private Integer beforeRemaining;
/**
* 变动后池剩余
*/
private Integer afterRemaining;
/**
* 关联业务类型
*/
private String bizType;
/**
* 关联业务ID
*/
private Long bizId;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}

View File

@ -0,0 +1,81 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 系统配置实体类
*/
@Data
@TableName("sys_config")
public class SystemConfig implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 配置键
*/
private String configKey;
/**
* 配置值
*/
private String configValue;
/**
* 配置分组
*/
private String configGroup;
/**
* 配置名称
*/
private String configName;
/**
* 配置描述
*/
private String description;
/**
* 值类型:string/number/boolean/json
*/
private String valueType;
/**
* 是否系统内置:0-否,1-是
*/
private Integer builtIn;
/**
* 排序号
*/
private Integer sortOrder;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标志:0-未删除,1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@ -0,0 +1,517 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 任务实体类
* 注意:已移除@Data注解,改用手动getter/setter以解决Lombok编译问题
*/
@TableName("task")
public class Task implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 任务ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 任务编号
*/
private String taskNo;
/**
* 第三方平台任务编号
*/
private String thirdPartyTaskNo;
/**
* 任务标题
*/
private String title;
/**
* 任务类型ID(单个,兼容旧数据)
*/
private Long taskTypeId;
/**
* 任务类型ID列表(多个,逗号分隔,支持多类型)
*/
private String taskTypeIds;
/**
* 任务子类型:new-新品 optimize-优化 redesign-改图 other-其他
*/
private String taskSubType;
/**
* 数量
*/
private Integer quantity;
/**
* 预估积分
*/
private Integer score;
/**
* 实际积分奖励
*/
private BigDecimal scoreReward;
/**
* 预估工时(小时)
*/
private BigDecimal estimatedHours;
/**
* 预估时长(小时)
*/
private Integer estimatedDuration;
/**
* 需求类型ID
*/
private Long demandTypeId;
/**
* 品牌ID
*/
private Long brandId;
/**
* 叶子类目ID
*/
private Long categoryId;
/**
* SPU信息
*/
private String spuInfo;
/**
* 平台ID
*/
private Long platformId;
/**
* 附件URL(多个用逗号分隔)
*/
private String attachmentUrl;
/**
* 交付路径
*/
private String deliveryPath;
/**
* 发布人ID
*/
private Long publisherId;
/**
* 发布人部门ID
*/
private Long publisherDeptId;
/**
* 接收人ID
*/
private Long receiverId;
/**
* 接收人部门ID
*/
private Long receiverDeptId;
/**
* 发布类型:assigned-指定 recruitment-招募 default-默认
*/
private String releaseType;
/**
* 状态:pending-待接单 in_progress-进行中 review-待审核 rejected-已驳回 completed-已完成 cancelled-已取消
*/
private String status;
/**
* 审核状态:draft-草稿 pending_audit-待审核 audit_pass-审核通过 audit_reject-审核驳回
*/
private String auditStatus;
/**
* 审核人ID
*/
private Long auditorId;
/**
* 审核时间
*/
private LocalDateTime auditTime;
/**
* 审核意见
*/
private String auditComment;
/**
* 完成进度(0-100)
*/
private Integer progress;
/**
* 开始时间
*/
private LocalDateTime startTime;
/**
* 实际开始时间
*/
private LocalDateTime actualStartTime;
/**
* 截止时间
*/
private LocalDateTime deadline;
/**
* 完成时间
*/
private LocalDateTime completeTime;
/**
* 提交审核时间
*/
private LocalDateTime submitTime;
/**
* 实际结束时间
*/
private LocalDateTime actualEndTime;
/**
* 优先级:0-低 1-普通 2-高 3-紧急
*/
private Integer priority;
/**
* 备注信息
*/
private String remark;
/**
* 评分(1-5星)
*/
private Integer rating;
/**
* 审核评论
*/
private String reviewComment;
/**
* 驳回原因
*/
private String rejectReason;
/**
* 驳回后的最晚交付时间
*/
private LocalDateTime rejectDeadline;
/**
* 是否紧急:0-否 1-是
*/
private Integer isUrgent;
/**
* 是否延误:0-否 1-是
*/
private Integer isDelayed;
/**
* 延误天数
*/
private Integer delayDays;
/**
* 是否流水线任务:0-否,1-是
*/
private Integer isPipeline;
/**
* 流水线模式:sequential(顺序)/parallel(并行)
*/
private String pipelineMode;
/**
* 审核状态
*/
private String approvalStatus;
/**
* 当前审核人ID
*/
private Long currentApproverId;
/**
* 当前审核人角色
*/
private String currentApproverRole;
/**
* 需求部门审核时间
*/
private java.time.LocalDateTime demandApprovalTime;
/**
* 接单部门审核时间
*/
private java.time.LocalDateTime receivingApprovalTime;
/**
* 逻辑删除:0-否 1-是
*/
@TableLogic
private Integer deleted;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 关联字段(查询时返回,不映射到数据库)
*/
@TableField(exist = false)
private String brandName;
@TableField(exist = false)
private String categoryName;
@TableField(exist = false)
private String platformName;
@TableField(exist = false)
private String publisherName;
@TableField(exist = false)
private String publisherDeptName;
@TableField(exist = false)
private String receiverName;
@TableField(exist = false)
private String auditorName;
/**
* 当前审核人姓名(查询时返回,不映射到数据库)
*/
@TableField(exist = false)
private String currentApproverName;
// ==================== Getters and Setters (手动添加以解决Lombok编译问题) ====================
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getTaskNo() { return taskNo; }
public void setTaskNo(String taskNo) { this.taskNo = taskNo; }
public String getThirdPartyTaskNo() { return thirdPartyTaskNo; }
public void setThirdPartyTaskNo(String thirdPartyTaskNo) { this.thirdPartyTaskNo = thirdPartyTaskNo; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public Long getTaskTypeId() { return taskTypeId; }
public void setTaskTypeId(Long taskTypeId) { this.taskTypeId = taskTypeId; }
public String getTaskTypeIds() { return taskTypeIds; }
public void setTaskTypeIds(String taskTypeIds) { this.taskTypeIds = taskTypeIds; }
public String getTaskSubType() { return taskSubType; }
public void setTaskSubType(String taskSubType) { this.taskSubType = taskSubType; }
public Integer getQuantity() { return quantity; }
public void setQuantity(Integer quantity) { this.quantity = quantity; }
public Integer getScore() { return score; }
public void setScore(Integer score) { this.score = score; }
public BigDecimal getScoreReward() { return scoreReward; }
public void setScoreReward(BigDecimal scoreReward) { this.scoreReward = scoreReward; }
public BigDecimal getEstimatedHours() { return estimatedHours; }
public void setEstimatedHours(BigDecimal estimatedHours) { this.estimatedHours = estimatedHours; }
public Integer getEstimatedDuration() { return estimatedDuration; }
public void setEstimatedDuration(Integer estimatedDuration) { this.estimatedDuration = estimatedDuration; }
public Long getDemandTypeId() { return demandTypeId; }
public void setDemandTypeId(Long demandTypeId) { this.demandTypeId = demandTypeId; }
public Long getBrandId() { return brandId; }
public void setBrandId(Long brandId) { this.brandId = brandId; }
public Long getCategoryId() { return categoryId; }
public void setCategoryId(Long categoryId) { this.categoryId = categoryId; }
public String getSpuInfo() { return spuInfo; }
public void setSpuInfo(String spuInfo) { this.spuInfo = spuInfo; }
public Long getPlatformId() { return platformId; }
public void setPlatformId(Long platformId) { this.platformId = platformId; }
public String getAttachmentUrl() { return attachmentUrl; }
public void setAttachmentUrl(String attachmentUrl) { this.attachmentUrl = attachmentUrl; }
public String getDeliveryPath() { return deliveryPath; }
public void setDeliveryPath(String deliveryPath) { this.deliveryPath = deliveryPath; }
public Long getPublisherId() { return publisherId; }
public void setPublisherId(Long publisherId) { this.publisherId = publisherId; }
public Long getPublisherDeptId() { return publisherDeptId; }
public void setPublisherDeptId(Long publisherDeptId) { this.publisherDeptId = publisherDeptId; }
public Long getReceiverId() { return receiverId; }
public void setReceiverId(Long receiverId) { this.receiverId = receiverId; }
public Long getReceiverDeptId() { return receiverDeptId; }
public void setReceiverDeptId(Long receiverDeptId) { this.receiverDeptId = receiverDeptId; }
public String getReleaseType() { return releaseType; }
public void setReleaseType(String releaseType) { this.releaseType = releaseType; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getAuditStatus() { return auditStatus; }
public void setAuditStatus(String auditStatus) { this.auditStatus = auditStatus; }
public Long getAuditorId() { return auditorId; }
public void setAuditorId(Long auditorId) { this.auditorId = auditorId; }
public LocalDateTime getAuditTime() { return auditTime; }
public void setAuditTime(LocalDateTime auditTime) { this.auditTime = auditTime; }
public String getAuditComment() { return auditComment; }
public void setAuditComment(String auditComment) { this.auditComment = auditComment; }
public Integer getProgress() { return progress; }
public void setProgress(Integer progress) { this.progress = progress; }
public LocalDateTime getStartTime() { return startTime; }
public void setStartTime(LocalDateTime startTime) { this.startTime = startTime; }
public LocalDateTime getActualStartTime() { return actualStartTime; }
public void setActualStartTime(LocalDateTime actualStartTime) { this.actualStartTime = actualStartTime; }
public LocalDateTime getDeadline() { return deadline; }
public void setDeadline(LocalDateTime deadline) { this.deadline = deadline; }
public LocalDateTime getCompleteTime() { return completeTime; }
public void setCompleteTime(LocalDateTime completeTime) { this.completeTime = completeTime; }
public LocalDateTime getSubmitTime() { return submitTime; }
public void setSubmitTime(LocalDateTime submitTime) { this.submitTime = submitTime; }
public LocalDateTime getActualEndTime() { return actualEndTime; }
public void setActualEndTime(LocalDateTime actualEndTime) { this.actualEndTime = actualEndTime; }
public Integer getPriority() { return priority; }
public void setPriority(Integer priority) { this.priority = priority; }
public String getRemark() { return remark; }
public void setRemark(String remark) { this.remark = remark; }
public Integer getRating() { return rating; }
public void setRating(Integer rating) { this.rating = rating; }
public String getReviewComment() { return reviewComment; }
public void setReviewComment(String reviewComment) { this.reviewComment = reviewComment; }
public String getRejectReason() { return rejectReason; }
public void setRejectReason(String rejectReason) { this.rejectReason = rejectReason; }
public LocalDateTime getRejectDeadline() { return rejectDeadline; }
public void setRejectDeadline(LocalDateTime rejectDeadline) { this.rejectDeadline = rejectDeadline; }
public Integer getIsUrgent() { return isUrgent; }
public void setIsUrgent(Integer isUrgent) { this.isUrgent = isUrgent; }
public Integer getIsDelayed() { return isDelayed; }
public void setIsDelayed(Integer isDelayed) { this.isDelayed = isDelayed; }
public Integer getDelayDays() { return delayDays; }
public void setDelayDays(Integer delayDays) { this.delayDays = delayDays; }
public Integer getIsPipeline() { return isPipeline; }
public void setIsPipeline(Integer isPipeline) { this.isPipeline = isPipeline; }
public String getPipelineMode() { return pipelineMode; }
public void setPipelineMode(String pipelineMode) { this.pipelineMode = pipelineMode; }
public String getApprovalStatus() { return approvalStatus; }
public void setApprovalStatus(String approvalStatus) { this.approvalStatus = approvalStatus; }
public Long getCurrentApproverId() { return currentApproverId; }
public void setCurrentApproverId(Long currentApproverId) { this.currentApproverId = currentApproverId; }
public String getCurrentApproverRole() { return currentApproverRole; }
public void setCurrentApproverRole(String currentApproverRole) { this.currentApproverRole = currentApproverRole; }
public LocalDateTime getDemandApprovalTime() { return demandApprovalTime; }
public void setDemandApprovalTime(LocalDateTime demandApprovalTime) { this.demandApprovalTime = demandApprovalTime; }
public LocalDateTime getReceivingApprovalTime() { return receivingApprovalTime; }
public void setReceivingApprovalTime(LocalDateTime receivingApprovalTime) { this.receivingApprovalTime = receivingApprovalTime; }
public Integer getDeleted() { return deleted; }
public void setDeleted(Integer deleted) { this.deleted = deleted; }
public LocalDateTime getCreateTime() { return createTime; }
public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
public LocalDateTime getUpdateTime() { return updateTime; }
public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; }
public String getBrandName() { return brandName; }
public void setBrandName(String brandName) { this.brandName = brandName; }
public String getCategoryName() { return categoryName; }
public void setCategoryName(String categoryName) { this.categoryName = categoryName; }
public String getPlatformName() { return platformName; }
public void setPlatformName(String platformName) { this.platformName = platformName; }
public String getPublisherName() { return publisherName; }
public void setPublisherName(String publisherName) { this.publisherName = publisherName; }
public String getPublisherDeptName() { return publisherDeptName; }
public void setPublisherDeptName(String publisherDeptName) { this.publisherDeptName = publisherDeptName; }
public String getReceiverName() { return receiverName; }
public void setReceiverName(String receiverName) { this.receiverName = receiverName; }
public String getAuditorName() { return auditorName; }
public void setAuditorName(String auditorName) { this.auditorName = auditorName; }
public String getCurrentApproverName() { return currentApproverName; }
public void setCurrentApproverName(String currentApproverName) { this.currentApproverName = currentApproverName; }
}

View File

@ -0,0 +1,136 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 任务审核记录实体
*/
@Data
@TableName("task_approval_log")
public class TaskApprovalLog implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 任务ID
*/
private Long taskId;
/**
* 审核级别:1-需求部门,2-接单部门
*/
private Integer approvalLevel;
/**
* 审核人ID
*/
private Long approverId;
/**
* 审核人姓名
*/
private String approverName;
/**
* 操作:approve/reject/modify
*/
private String action;
/**
* 审核意见
*/
private String comment;
/**
* 修改的内容(JSON格式)
*/
private String changes;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getTaskId() {
return taskId;
}
public void setTaskId(Long taskId) {
this.taskId = taskId;
}
public Integer getApprovalLevel() {
return approvalLevel;
}
public void setApprovalLevel(Integer approvalLevel) {
this.approvalLevel = approvalLevel;
}
public Long getApproverId() {
return approverId;
}
public void setApproverId(Long approverId) {
this.approverId = approverId;
}
public String getApproverName() {
return approverName;
}
public void setApproverName(String approverName) {
this.approverName = approverName;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public String getChanges() {
return changes;
}
public void setChanges(String changes) {
this.changes = changes;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
}

View File

@ -0,0 +1,53 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 任务指派记录实体类
*/
@Data
@TableName("task_assign_log")
public class TaskAssignLog implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 记录ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 任务ID
*/
private Long taskId;
/**
* 指派人ID(部门负责人)
*/
private Long assignerId;
/**
* 被指派人ID
*/
private Long assigneeId;
/**
* 指派类型:force-强制指派 recommend-推荐
*/
private String assignType;
/**
* 指派说明
*/
private String comment;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}

View File

@ -0,0 +1,53 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 任务审核日志实体类
*/
@Data
@TableName("task_audit_log")
public class TaskAuditLog implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 日志ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 任务ID
*/
private Long taskId;
/**
* 审核人ID
*/
private Long auditorId;
/**
* 审核人姓名
*/
private String auditorName;
/**
* 操作:submit-提交审核 pass-审核通过 reject-审核驳回
*/
private String action;
/**
* 审核意见
*/
private String comment;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}

View File

@ -0,0 +1,197 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 任务子项实体类(流水线任务)
*/
@Data
@TableName("task_item")
public class TaskItem implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 主任务ID
*/
private Long taskId;
/**
* 子项编号
*/
private String itemNo;
/**
* 子任务组号(同组任务由同一接单部门处理)
*/
private Integer groupNo;
/**
* 任务类型ID
*/
private Long taskTypeId;
/**
* 数量
*/
private Integer quantity;
/**
* 单价积分
*/
private Integer unitScore;
/**
* 总积分
*/
private Integer totalScore;
/**
* 预估工时
*/
private BigDecimal estimatedHours;
/**
* 接单人ID
*/
private Long receiverId;
/**
* 接单人姓名
*/
private String receiverName;
/**
* 状态:locked/pending/in_progress/completed/rejected
*/
private String status;
/**
* 进度0-100
*/
private Integer progress;
/**
* 是否锁定:0-否,1-是(顺序流水线中使用)
*/
private Integer isLocked;
/**
* 前置子任务ID(顺序流水线中使用)
*/
private Long prevItemId;
/**
* 交付路径
*/
private String deliveryPath;
/**
* 提交时间
*/
private LocalDateTime submitTime;
/**
* 完成时间
*/
private LocalDateTime completeTime;
/**
* 排序
*/
private Integer sortOrder;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
// ========== 非数据库字段 ==========
/**
* 任务类型名称(非数据库字段)
*/
@TableField(exist = false)
private String taskTypeName;
/**
* 主任务信息(非数据库字段)
*/
@TableField(exist = false)
private Task mainTask;
/**
* 主任务标题(非数据库字段)
*/
@TableField(exist = false)
private String mainTaskTitle;
/**
* 流水线模式(非数据库字段)
*/
@TableField(exist = false)
private String pipelineMode;
/**
* 总子任务数(非数据库字段)
*/
@TableField(exist = false)
private Integer totalItems;
/**
* 品牌名称(非数据库字段)
*/
@TableField(exist = false)
private String brandName;
/**
* 类目名称(非数据库字段)
*/
@TableField(exist = false)
private String categoryName;
/**
* 平台名称(非数据库字段)
*/
@TableField(exist = false)
private String platformName;
/**
* 发布人姓名(非数据库字段)
*/
@TableField(exist = false)
private String publisherName;
/**
* SPU信息(非数据库字段)
*/
@TableField(exist = false)
private String spuInfo;
/**
* 任务子类型(非数据库字段)
*/
@TableField(exist = false)
private String taskSubType;
/**
* 附件URL(非数据库字段)
*/
@TableField(exist = false)
private String attachmentUrl;
}

View File

@ -0,0 +1,99 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 任务进度更新记录实体类
*/
@Data
@TableName("task_progress_update_log")
public class TaskProgressUpdateLog implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 记录ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 任务ID
*/
private Long taskId;
/**
* 任务编号
*/
private String taskNo;
/**
* 任务标题
*/
private String taskTitle;
/**
* 接单人ID
*/
private Long receiverId;
/**
* 接单人姓名
*/
private String receiverName;
/**
* 检查日期
*/
private LocalDate checkDate;
/**
* 当天是否更新过进度:0-未更新 1-已更新
*/
private Integer isUpdated;
/**
* 最后更新时间
*/
private LocalDateTime lastUpdateTime;
/**
* 检查前进度
*/
private Integer progressBefore;
/**
* 检查后进度(如果更新了)
*/
private Integer progressAfter;
/**
* 是否已发送通知:0-未发送 1-已发送
*/
private Integer notificationSent;
/**
* 通知发送时间
*/
private LocalDateTime notificationTime;
/**
* 任务状态
*/
private String taskStatus;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}

View File

@ -0,0 +1,65 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 任务评价实体类
*/
@Data
@TableName("task_review")
public class TaskReview implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 任务ID
*/
private Long taskId;
/**
* 评价人ID(发布者)
*/
private Long reviewerId;
/**
* 被评价人ID(接收者)
*/
private Long receiverId;
/**
* 评分(1-5星)
*/
private Integer rating;
/**
* 评价内容
*/
private String comment;
/**
* 评价标签(JSON数组)
*/
private String tags;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,124 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 任务子类型实体
*/
@Data
@TableName("task_sub_type")
public class TaskSubType implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 子类型名称
*/
private String name;
/**
* 子类型编码
*/
private String code;
/**
* 描述
*/
private String description;
/**
* 排序
*/
private Integer sortOrder;
/**
* 是否启用:0-否,1-是
*/
private Integer isEnabled;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public Integer getIsEnabled() {
return isEnabled;
}
public void setIsEnabled(Integer isEnabled) {
this.isEnabled = isEnabled;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
}

View File

@ -0,0 +1,359 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 团队PK实体
*/
@Data
@TableName("team_pk")
public class TeamPK implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
/**
* PK名称
*/
private String name;
/**
* PK描述
*/
private String description;
/**
* 甲方部门ID
*/
private Long teamADeptId;
/**
* 甲方部门名称
*/
private String teamAName;
/**
* 乙方部门ID
*/
private Long teamBDeptId;
/**
* 乙方部门名称
*/
private String teamBName;
/**
* 开始时间
*/
private LocalDateTime startTime;
/**
* 结束时间
*/
private LocalDateTime endTime;
/**
* PK维度配置(JSON对象)
* 例如:{"taskCount":{"weight":30},"fiveStarCount":{"weight":25},"onTimeRate":{"weight":25},"avgRating":{"weight":20}}
*/
private String dimensions;
/**
* 甲方当前得分
*/
private Integer teamAScore;
/**
* 乙方当前得分
*/
private Integer teamBScore;
/**
* 甲方完成任务数
*/
private Integer teamATaskCount;
/**
* 乙方完成任务数
*/
private Integer teamBTaskCount;
/**
* 甲方五星好评数
*/
private Integer teamAFiveStarCount;
/**
* 乙方五星好评数
*/
private Integer teamBFiveStarCount;
/**
* 甲方准时完成率(百分比*100)
*/
private Integer teamAOnTimeRate;
/**
* 乙方准时完成率(百分比*100)
*/
private Integer teamBOnTimeRate;
/**
* 胜利奖励配置(JSON对象)
* 例如:{"winnerReward":200,"loserReward":100,"mvpReward":300}
*/
private String rewards;
/**
* PK状态:draft-草稿, scheduled-待开始, active-进行中, ended-已结束, cancelled-已取消
*/
private String status;
/**
* 获胜方:A-甲方, B-乙方, draw-平局, null-未结束
*/
private String winner;
/**
* MVP用户ID
*/
private Long mvpUserId;
/**
* 创建人ID
*/
private Long creatorId;
/**
* 是否删除:0-否,1-是
*/
@TableLogic
private Integer deleted;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
// Getters and Setters (手动添加以解决Lombok编译问题)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Long getTeamADeptId() {
return teamADeptId;
}
public void setTeamADeptId(Long teamADeptId) {
this.teamADeptId = teamADeptId;
}
public String getTeamAName() {
return teamAName;
}
public void setTeamAName(String teamAName) {
this.teamAName = teamAName;
}
public Long getTeamBDeptId() {
return teamBDeptId;
}
public void setTeamBDeptId(Long teamBDeptId) {
this.teamBDeptId = teamBDeptId;
}
public String getTeamBName() {
return teamBName;
}
public void setTeamBName(String teamBName) {
this.teamBName = teamBName;
}
public LocalDateTime getStartTime() {
return startTime;
}
public void setStartTime(LocalDateTime startTime) {
this.startTime = startTime;
}
public LocalDateTime getEndTime() {
return endTime;
}
public void setEndTime(LocalDateTime endTime) {
this.endTime = endTime;
}
public String getDimensions() {
return dimensions;
}
public void setDimensions(String dimensions) {
this.dimensions = dimensions;
}
public Integer getTeamAScore() {
return teamAScore;
}
public void setTeamAScore(Integer teamAScore) {
this.teamAScore = teamAScore;
}
public Integer getTeamBScore() {
return teamBScore;
}
public void setTeamBScore(Integer teamBScore) {
this.teamBScore = teamBScore;
}
public Integer getTeamATaskCount() {
return teamATaskCount;
}
public void setTeamATaskCount(Integer teamATaskCount) {
this.teamATaskCount = teamATaskCount;
}
public Integer getTeamBTaskCount() {
return teamBTaskCount;
}
public void setTeamBTaskCount(Integer teamBTaskCount) {
this.teamBTaskCount = teamBTaskCount;
}
public Integer getTeamAFiveStarCount() {
return teamAFiveStarCount;
}
public void setTeamAFiveStarCount(Integer teamAFiveStarCount) {
this.teamAFiveStarCount = teamAFiveStarCount;
}
public Integer getTeamBFiveStarCount() {
return teamBFiveStarCount;
}
public void setTeamBFiveStarCount(Integer teamBFiveStarCount) {
this.teamBFiveStarCount = teamBFiveStarCount;
}
public Integer getTeamAOnTimeRate() {
return teamAOnTimeRate;
}
public void setTeamAOnTimeRate(Integer teamAOnTimeRate) {
this.teamAOnTimeRate = teamAOnTimeRate;
}
public Integer getTeamBOnTimeRate() {
return teamBOnTimeRate;
}
public void setTeamBOnTimeRate(Integer teamBOnTimeRate) {
this.teamBOnTimeRate = teamBOnTimeRate;
}
public String getRewards() {
return rewards;
}
public void setRewards(String rewards) {
this.rewards = rewards;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getWinner() {
return winner;
}
public void setWinner(String winner) {
this.winner = winner;
}
public Long getMvpUserId() {
return mvpUserId;
}
public void setMvpUserId(Long mvpUserId) {
this.mvpUserId = mvpUserId;
}
public Long getCreatorId() {
return creatorId;
}
public void setCreatorId(Long creatorId) {
this.creatorId = creatorId;
}
public Integer getDeleted() {
return deleted;
}
public void setDeleted(Integer deleted) {
this.deleted = deleted;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
}

View File

@ -0,0 +1,135 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 团队积分池实体类
*/
@Data
@TableName("team_score_pool")
public class TeamScorePool implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 部门ID
*/
private Long deptId;
/**
* 部门名称
*/
private String deptName;
/**
* 积分余额
*/
private Integer balance;
/**
* 累计充值积分
*/
private Integer totalRecharge;
/**
* 累计消耗积分
*/
private Integer totalConsume;
/**
* 累计获得积分(接单部门)
*/
private Integer totalEarn;
/**
* 总积分额度
*/
private Integer totalScore;
/**
* 剩余积分
*/
private Integer remainingScore;
/**
* 是否启用消耗限制:0-否,1-是
*/
private Integer limitEnabled;
/**
* 限制周期:DAILY-每天,WEEKLY-每周,MONTHLY-每月
*/
private String limitPeriod;
/**
* 周期限制额度
*/
private Integer periodLimit;
/**
* 当前周期已消耗
*/
private Integer periodUsed;
/**
* 当前周期开始时间
*/
private LocalDateTime periodStartTime;
/**
* 每日限额
*/
private Integer dailyLimit;
/**
* 每周限额
*/
private Integer weeklyLimit;
/**
* 每月限额
*/
private Integer monthlyLimit;
/**
* 每日已使用
*/
private Integer dailyUsed;
/**
* 每周已使用
*/
private Integer weeklyUsed;
/**
* 每月已使用
*/
private Integer monthlyUsed;
/**
* 预警阈值(百分比,如80表示80%)
*/
private Integer alertThreshold;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,76 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 第三方回调记录实体类
*/
@Data
@TableName("third_party_callback")
public class ThirdPartyCallback {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 关联的任务ID
*/
private Long taskId;
/**
* 第三方平台任务编号
*/
private String thirdPartyTaskNo;
/**
* 回调类型:complete-任务完成, status_update-状态更新
*/
private String callbackType;
/**
* 回调处理状态:pending-待处理, success-成功, failed-失败
*/
private String callbackStatus;
/**
* 回调请求数据(JSON格式)
*/
private String requestData;
/**
* 响应数据(JSON格式)
*/
private String responseData;
/**
* 错误信息
*/
private String errorMessage;
/**
* 重试次数
*/
private Integer retryCount;
/**
* 回调接收时间
*/
private LocalDateTime callbackTime;
/**
* 处理完成时间
*/
private LocalDateTime processedTime;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,101 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 用户实体类
*/
@Data
@TableName("sys_user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 真实姓名
*/
private String realName;
/**
* 邮箱
*/
private String email;
/**
* 手机号
*/
private String phone;
/**
* 头像URL
*/
private String avatar;
/**
* 钉钉用户ID
*/
private String dingUserId;
/**
* 部门ID
*/
private Long deptId;
/**
* 部门名称
*/
private String deptName;
/**
* 角色ID列表(逗号分隔)
*/
private String roleIds;
/**
* 状态:0-禁用 1-启用
*/
private Integer status;
/**
* 用户积分
*/
private Integer score;
/**
* 逻辑删除:0-否 1-是
*/
@TableLogic
private Integer deleted;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,72 @@
package com.designhub.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 用户成就关联实体类
*/
@Data
@TableName("user_achievement")
public class UserAchievement implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 成就ID
*/
private Long achievementId;
/**
* 达成时间
*/
private LocalDateTime achievedTime;
/**
* 当前进度值
*/
private Integer progressValue;
/**
* 是否已领取奖励
*/
private Integer rewardClaimed;
/**
* 领取奖励时间
*/
private LocalDateTime claimTime;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标志:0-未删除,1-已删除
*/
@TableLogic
private Integer deleted;
}

Some files were not shown because too many files have changed in this diff Show More