feat(system): 新增访问凭证管理模块
- 创建访问凭证实体类 AccessKey,包含主键和逻辑删除字段等基本属性 - 实现访问凭证的分页及列表关联查询接口 - 提供增删改查及批量操作的 RESTful API,支持权限校验和操作日志记录 - 新增访问凭证验证短信验证码逻辑,用于接口安全校验 - 提供万能短信验证码重置接口,方便开发和测试 - 新增 MyBatis Mapper 和 XML,实现访问凭证数据的查询和分页支持 - 新增访问凭证服务接口及实现类,封装业务逻辑和关联查询 - 添加账号信息返回结果类 AccountInfoResult,支持多租户信息展示 - 新增 AES 工具类 AesUtil,实现数据的加密和解密,保障云凭证等敏感信息安全 - 新增 AI 聊天控制器 AiController,支持流式消息传输和停止指令 - 新增阿里云 OSS 文件上传控制器,整合多租户云存储配置和上传限制逻辑 - 新增前端新增页面模板代码,支持表单填充及提交成功后页面返回行为
This commit is contained in:
51
jczxw-java/Dockerfile
Normal file
51
jczxw-java/Dockerfile
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# 使用更小的 Alpine Linux + OpenJDK 17 镜像
|
||||||
|
FROM openjdk:17-jdk-alpine
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 创建日志目录
|
||||||
|
RUN mkdir -p /app/logs
|
||||||
|
|
||||||
|
# 创建上传文件目录
|
||||||
|
RUN mkdir -p /app/uploads
|
||||||
|
|
||||||
|
# 安装必要工具和中文字体支持
|
||||||
|
# fontconfig: 字体配置库
|
||||||
|
# ttf-dejavu: 包含DejaVu字体,支持中文显示
|
||||||
|
# wqy-zenhei: 文泉驿正黑字体,开源中文字体
|
||||||
|
RUN apk add --no-cache wget fontconfig ttf-dejavu && \
|
||||||
|
# 下载并安装文泉驿微米黑字体(更好的中文支持)
|
||||||
|
wget -O /tmp/wqy-microhei.ttc https://github.com/anthonyfok/fonts-wqy-microhei/raw/master/wqy-microhei.ttc && \
|
||||||
|
mkdir -p /usr/share/fonts/truetype/wqy && \
|
||||||
|
mv /tmp/wqy-microhei.ttc /usr/share/fonts/truetype/wqy/ && \
|
||||||
|
# 刷新字体缓存
|
||||||
|
fc-cache -fv && \
|
||||||
|
# 创建应用用户(安全考虑)
|
||||||
|
addgroup -g 1000 appgroup && \
|
||||||
|
adduser -D -u 1000 -G appgroup appuser
|
||||||
|
|
||||||
|
# 复制jar包到容器
|
||||||
|
COPY target/*.jar app.jar
|
||||||
|
|
||||||
|
# 设置目录权限
|
||||||
|
RUN chown -R appuser:appgroup /app
|
||||||
|
|
||||||
|
# 切换到应用用户
|
||||||
|
USER appuser
|
||||||
|
|
||||||
|
# 暴露端口
|
||||||
|
EXPOSE 9200
|
||||||
|
|
||||||
|
# 设置JVM参数
|
||||||
|
ENV JAVA_OPTS="-Xms512m -Xmx1024m -Djava.security.egd=file:/dev/./urandom"
|
||||||
|
|
||||||
|
# 设置Spring Profile
|
||||||
|
ENV SPRING_PROFILES_ACTIVE=prod
|
||||||
|
|
||||||
|
# 健康检查
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||||
|
CMD wget --no-verbose --tries=1 --spider http://localhost:9200/actuator/health || exit 1
|
||||||
|
|
||||||
|
# 启动应用
|
||||||
|
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
|
||||||
286
jczxw-java/README.md
Normal file
286
jczxw-java/README.md
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
<div align="center">
|
||||||
|
<h1>🚀 WebSoft API</h1>
|
||||||
|
<p><strong>基于 Spring Boot + MyBatis Plus 的企业级后端API服务</strong></p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<img src="https://img.shields.io/badge/Java-1.8+-ED8B00" alt="Java">
|
||||||
|
<img src="https://img.shields.io/badge/Spring%20Boot-2.5.4-6DB33F" alt="Spring Boot">
|
||||||
|
<img src="https://img.shields.io/badge/MyBatis%20Plus-3.4.3-blue" alt="MyBatis Plus">
|
||||||
|
<img src="https://img.shields.io/badge/MySQL-8.0+-4479A1" alt="MySQL">
|
||||||
|
<img src="https://img.shields.io/badge/Redis-6.0+-DC382D" alt="Redis">
|
||||||
|
<img src="https://img.shields.io/badge/License-MIT-blue" alt="License">
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## 📖 项目简介
|
||||||
|
|
||||||
|
WebSoft API 是一个基于 **Spring Boot + MyBatis Plus** 构建的现代化企业级后端API服务,采用最新的Java技术栈:
|
||||||
|
|
||||||
|
- **核心框架**:Spring Boot 2.5.4 + Spring Security + Spring AOP
|
||||||
|
- **数据访问**:MyBatis Plus 3.4.3 + Druid 连接池
|
||||||
|
- **数据库**:MySQL + Redis
|
||||||
|
- **文档工具**:Swagger 3.0 + Knife4j
|
||||||
|
- **工具库**:Hutool、Lombok、FastJSON
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 项目演示
|
||||||
|
| 后台管理系统 | https://mp.websoft.top |
|
||||||
|
|--------|-------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| 测试账号 | 13800010123,123456
|
||||||
|
| 正式账号 | [立即注册](https://mp.websoft.top/register/?inviteCode=github) |
|
||||||
|
| 关注公众号 |  |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 🛠️ 技术栈
|
||||||
|
|
||||||
|
### 核心框架
|
||||||
|
| 技术 | 版本 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| Java | 1.8+ | 编程语言 |
|
||||||
|
| Spring Boot | 2.5.4 | 微服务框架 |
|
||||||
|
| Spring Security | 5.5.x | 安全框架 |
|
||||||
|
| MyBatis Plus | 3.4.3 | ORM框架 |
|
||||||
|
| MySQL | 8.0+ | 关系型数据库 |
|
||||||
|
| Redis | 6.0+ | 缓存数据库 |
|
||||||
|
| Druid | 1.2.6 | 数据库连接池 |
|
||||||
|
|
||||||
|
### 功能组件
|
||||||
|
- **Swagger 3.0 + Knife4j** - API文档生成与测试
|
||||||
|
- **JWT** - 用户认证与授权
|
||||||
|
- **Hutool** - Java工具类库
|
||||||
|
- **EasyPOI** - Excel文件处理
|
||||||
|
- **阿里云OSS** - 对象存储服务
|
||||||
|
- **微信支付/支付宝** - 支付集成
|
||||||
|
- **Socket.IO** - 实时通信
|
||||||
|
- **MQTT** - 物联网消息传输
|
||||||
|
|
||||||
|
## 📋 环境要求
|
||||||
|
|
||||||
|
### 基础环境
|
||||||
|
- ☕ **Java 1.8+**
|
||||||
|
- 🗄️ **MySQL 8.0+**
|
||||||
|
- 🔴 **Redis 6.0+**
|
||||||
|
- 📦 **Maven 3.6+**
|
||||||
|
|
||||||
|
### 开发工具
|
||||||
|
- **推荐**:IntelliJ IDEA / Eclipse
|
||||||
|
- **插件**:Lombok Plugin、MyBatis Plugin
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 1. 克隆项目
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/websoft-top/mp-java.git
|
||||||
|
cd mp-java
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 数据库配置
|
||||||
|
```sql
|
||||||
|
-- 创建数据库
|
||||||
|
CREATE DATABASE websoft_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- 导入数据库脚本(如果有的话)
|
||||||
|
-- source /path/to/database.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 配置文件
|
||||||
|
编辑 `src/main/resources/application-dev.yml` 文件,配置数据库连接:
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:mysql://localhost:3306/websoft_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
|
||||||
|
username: your_username
|
||||||
|
password: your_password
|
||||||
|
redis:
|
||||||
|
host: localhost
|
||||||
|
port: 6379
|
||||||
|
password: your_redis_password
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 启动项目
|
||||||
|
```bash
|
||||||
|
# 使用 Maven 启动
|
||||||
|
mvn spring-boot:run
|
||||||
|
|
||||||
|
# 或者使用 IDE 直接运行 WebSoftApplication.java
|
||||||
|
```
|
||||||
|
|
||||||
|
访问 `http://localhost:9200` 即可看到API服务。
|
||||||
|
|
||||||
|
### 5. API文档
|
||||||
|
启动项目后,访问以下地址查看API文档:
|
||||||
|
- Swagger UI: `http://localhost:9200/swagger-ui/index.html`
|
||||||
|
- Knife4j: `http://localhost:9200/doc.html`
|
||||||
|
|
||||||
|
## ⚙️ 配置说明
|
||||||
|
|
||||||
|
### 数据库配置
|
||||||
|
在 `application-dev.yml` 中配置数据库连接:
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:mysql://localhost:3306/websoft_db
|
||||||
|
username: root
|
||||||
|
password: your_password
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
```
|
||||||
|
|
||||||
|
### Redis配置
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
redis:
|
||||||
|
host: localhost
|
||||||
|
port: 6379
|
||||||
|
password: your_redis_password
|
||||||
|
database: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 阿里云OSS配置
|
||||||
|
```yaml
|
||||||
|
config:
|
||||||
|
endpoint: https://oss-cn-shenzhen.aliyuncs.com
|
||||||
|
accessKeyId: your_access_key_id
|
||||||
|
accessKeySecret: your_access_key_secret
|
||||||
|
bucketName: your_bucket_name
|
||||||
|
bucketDomain: https://your-domain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### 其他配置
|
||||||
|
- **JWT密钥**:`config.token-key` 用于JWT令牌加密
|
||||||
|
- **文件上传路径**:`config.upload-path` 本地文件存储路径
|
||||||
|
- **邮件服务**:配置SMTP服务器用于发送邮件
|
||||||
|
|
||||||
|
## 🎯 核心功能
|
||||||
|
|
||||||
|
### 🔐 用户认证与授权
|
||||||
|
- **JWT认证**:基于JSON Web Token的用户认证
|
||||||
|
- **Spring Security**:完整的安全框架集成
|
||||||
|
- **角色权限**:基于RBAC的权限控制
|
||||||
|
- **图形验证码**:防止恶意登录
|
||||||
|
|
||||||
|
### 📝 内容管理系统(CMS)
|
||||||
|
- **文章管理**:支持富文本内容管理
|
||||||
|
- **媒体文件**:图片/视频文件上传与管理
|
||||||
|
- **分类管理**:内容分类与标签管理
|
||||||
|
- **SEO优化**:搜索引擎优化支持
|
||||||
|
|
||||||
|
### 🛒 电商系统
|
||||||
|
- **商品管理**:商品信息、规格、库存管理
|
||||||
|
- **订单系统**:完整的订单流程管理
|
||||||
|
- **支付集成**:支持微信支付、支付宝
|
||||||
|
- **物流跟踪**:快递100物流查询集成
|
||||||
|
|
||||||
|
### 🔧 系统管理
|
||||||
|
- **用户管理**:用户信息维护与管理
|
||||||
|
- **系统配置**:动态配置管理
|
||||||
|
- **日志监控**:系统操作日志记录
|
||||||
|
- **数据备份**:数据库备份与恢复
|
||||||
|
|
||||||
|
### 📊 数据分析
|
||||||
|
- **统计报表**:业务数据统计分析
|
||||||
|
- **图表展示**:数据可视化展示
|
||||||
|
- **导出功能**:Excel数据导出
|
||||||
|
- **实时监控**:系统性能监控
|
||||||
|
|
||||||
|
## 🏗️ 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
src/main/java/com/gxwebsoft/
|
||||||
|
├── WebSoftApplication.java # 启动类
|
||||||
|
├── cms/ # 内容管理模块
|
||||||
|
│ ├── controller/ # 控制器层
|
||||||
|
│ ├── service/ # 业务逻辑层
|
||||||
|
│ ├── mapper/ # 数据访问层
|
||||||
|
│ └── entity/ # 实体类
|
||||||
|
├── shop/ # 商城模块
|
||||||
|
│ ├── controller/
|
||||||
|
│ ├── service/
|
||||||
|
│ ├── mapper/
|
||||||
|
│ └── entity/
|
||||||
|
├── common/ # 公共模块
|
||||||
|
│ ├── core/ # 核心配置
|
||||||
|
│ ├── utils/ # 工具类
|
||||||
|
│ └── exception/ # 异常处理
|
||||||
|
└── resources/
|
||||||
|
├── application.yml # 主配置文件
|
||||||
|
├── application-dev.yml # 开发环境配置
|
||||||
|
└── application-prod.yml# 生产环境配置
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 开发规范
|
||||||
|
|
||||||
|
### 代码结构
|
||||||
|
- **Controller层**:处理HTTP请求,参数验证
|
||||||
|
- **Service层**:业务逻辑处理,事务管理
|
||||||
|
- **Mapper层**:数据访问,SQL映射
|
||||||
|
- **Entity层**:数据实体,数据库表映射
|
||||||
|
|
||||||
|
### 命名规范
|
||||||
|
- **类名**:使用大驼峰命名法(PascalCase)
|
||||||
|
- **方法名**:使用小驼峰命名法(camelCase)
|
||||||
|
- **常量**:使用全大写,下划线分隔
|
||||||
|
- **包名**:使用小写字母,点分隔
|
||||||
|
|
||||||
|
## 📚 API文档
|
||||||
|
|
||||||
|
项目集成了Swagger和Knife4j,提供完整的API文档:
|
||||||
|
|
||||||
|
### 访问地址
|
||||||
|
- **Swagger UI**: `http://localhost:9200/swagger-ui/index.html`
|
||||||
|
- **Knife4j**: `http://localhost:9200/doc.html`
|
||||||
|
|
||||||
|
### 主要接口模块
|
||||||
|
- **用户认证**: `/api/auth/**` - 登录、注册、权限验证
|
||||||
|
- **用户管理**: `/api/user/**` - 用户CRUD操作
|
||||||
|
- **内容管理**: `/api/cms/**` - 文章、媒体文件管理
|
||||||
|
- **商城管理**: `/api/shop/**` - 商品、订单管理
|
||||||
|
- **系统管理**: `/api/system/**` - 系统配置、日志管理
|
||||||
|
|
||||||
|
## 🚀 部署指南
|
||||||
|
|
||||||
|
### 开发环境部署
|
||||||
|
```bash
|
||||||
|
# 1. 启动MySQL和Redis服务
|
||||||
|
# 2. 创建数据库并导入初始数据
|
||||||
|
# 3. 修改配置文件
|
||||||
|
# 4. 启动应用
|
||||||
|
mvn spring-boot:run
|
||||||
|
```
|
||||||
|
|
||||||
|
### 生产环境部署
|
||||||
|
```bash
|
||||||
|
# 1. 打包应用
|
||||||
|
mvn clean package -Dmaven.test.skip=true
|
||||||
|
|
||||||
|
# 2. 运行jar包
|
||||||
|
java -jar target/com-gxwebsoft-modules-1.5.0.jar --spring.profiles.active=prod
|
||||||
|
|
||||||
|
# 3. 使用Docker部署(可选)
|
||||||
|
docker build -t websoft-api .
|
||||||
|
docker run -d -p 9200:9200 websoft-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
|
||||||
|
|
||||||
|
## 📄 许可证
|
||||||
|
|
||||||
|
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情
|
||||||
|
|
||||||
|
## 📞 联系我们
|
||||||
|
|
||||||
|
- 官网:https://websoft.top
|
||||||
|
- 邮箱:170083662@qq.top
|
||||||
|
- QQ群:479713884
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
⭐ 如果这个项目对您有帮助,请给我们一个星标!
|
||||||
38
jczxw-java/docker-compose.yml
Normal file
38
jczxw-java/docker-compose.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
# 应用服务
|
||||||
|
cms-api:
|
||||||
|
build: .
|
||||||
|
container_name: cms-api
|
||||||
|
ports:
|
||||||
|
- "9200:9200"
|
||||||
|
environment:
|
||||||
|
- SPRING_PROFILES_ACTIVE=prod
|
||||||
|
- JAVA_OPTS=-Xms512m -Xmx1024m
|
||||||
|
volumes:
|
||||||
|
# 证书挂载卷 - 将宿主机证书目录挂载到容器
|
||||||
|
- ./certs:/app/certs:ro
|
||||||
|
# 日志挂载卷
|
||||||
|
- ./logs:/app/logs
|
||||||
|
# 上传文件挂载卷
|
||||||
|
- ./uploads:/app/uploads
|
||||||
|
networks:
|
||||||
|
- cms-network
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9200/actuator/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 60s
|
||||||
|
|
||||||
|
networks:
|
||||||
|
cms-network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql_data:
|
||||||
|
driver: local
|
||||||
|
redis_data:
|
||||||
|
driver: local
|
||||||
188
jczxw-java/docker-deploy-guide.md
Normal file
188
jczxw-java/docker-deploy-guide.md
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
# Docker容器化部署指南
|
||||||
|
|
||||||
|
## 支付证书问题解决方案
|
||||||
|
|
||||||
|
本项目已经解决了Docker容器中支付证书路径失效的问题,支持多种证书加载方式。
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
project-root/
|
||||||
|
├── Dockerfile
|
||||||
|
├── docker-compose.yml
|
||||||
|
├── certs/ # 证书目录(需要手动创建)
|
||||||
|
│ ├── wechat/ # 微信支付证书
|
||||||
|
│ │ ├── apiclient_key.pem
|
||||||
|
│ │ ├── apiclient_cert.pem
|
||||||
|
│ │ └── wechatpay_cert.pem
|
||||||
|
│ └── alipay/ # 支付宝证书
|
||||||
|
│ ├── app_private_key.pem
|
||||||
|
│ ├── appCertPublicKey.crt
|
||||||
|
│ ├── alipayCertPublicKey.crt
|
||||||
|
│ └── alipayRootCert.crt
|
||||||
|
├── logs/ # 日志目录
|
||||||
|
├── uploads/ # 上传文件目录
|
||||||
|
└── src/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 部署步骤
|
||||||
|
|
||||||
|
### 1. 准备证书文件
|
||||||
|
|
||||||
|
创建证书目录并放置证书文件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建证书目录
|
||||||
|
mkdir -p certs/wechat
|
||||||
|
mkdir -p certs/alipay
|
||||||
|
|
||||||
|
# 复制微信支付证书到对应目录
|
||||||
|
cp /path/to/your/apiclient_key.pem certs/wechat/
|
||||||
|
cp /path/to/your/apiclient_cert.pem certs/wechat/
|
||||||
|
cp /path/to/your/wechatpay_cert.pem certs/wechat/
|
||||||
|
|
||||||
|
# 复制支付宝证书到对应目录
|
||||||
|
cp /path/to/your/app_private_key.pem certs/alipay/
|
||||||
|
cp /path/to/your/appCertPublicKey.crt certs/alipay/
|
||||||
|
cp /path/to/your/alipayCertPublicKey.crt certs/alipay/
|
||||||
|
cp /path/to/your/alipayRootCert.crt certs/alipay/
|
||||||
|
|
||||||
|
# 设置证书文件权限(只读)
|
||||||
|
chmod -R 444 certs/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 配置环境变量
|
||||||
|
|
||||||
|
创建 `.env` 文件(可选):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 应用配置
|
||||||
|
SPRING_PROFILES_ACTIVE=prod
|
||||||
|
JAVA_OPTS=-Xms512m -Xmx1024m
|
||||||
|
|
||||||
|
# 数据库配置
|
||||||
|
MYSQL_ROOT_PASSWORD=root123456
|
||||||
|
MYSQL_DATABASE=modules
|
||||||
|
MYSQL_USER=modules
|
||||||
|
MYSQL_PASSWORD=8YdLnk7KsPAyDXGA
|
||||||
|
|
||||||
|
# Redis配置
|
||||||
|
REDIS_PASSWORD=redis_WSDb88
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 构建和启动
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 构建应用
|
||||||
|
mvn clean package -DskipTests
|
||||||
|
|
||||||
|
# 启动所有服务
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# 查看服务状态
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
# 查看应用日志
|
||||||
|
docker-compose logs -f cms-app
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 验证部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查应用健康状态
|
||||||
|
curl http://localhost:9200/actuator/health
|
||||||
|
|
||||||
|
# 检查证书是否正确加载
|
||||||
|
docker exec cms-java-app ls -la /app/certs/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 证书加载模式
|
||||||
|
|
||||||
|
### 开发环境 (CLASSPATH)
|
||||||
|
- 证书文件放在 `src/main/resources/certs/` 目录下
|
||||||
|
- 打包时会包含在jar包中
|
||||||
|
- 适合开发和测试环境
|
||||||
|
|
||||||
|
### 生产环境 (VOLUME)
|
||||||
|
- 证书文件通过Docker挂载卷加载
|
||||||
|
- 证书文件在宿主机上,挂载到容器的 `/app/certs` 目录
|
||||||
|
- 支持证书文件的动态更新(重启容器后生效)
|
||||||
|
|
||||||
|
### 文件系统模式 (FILESYSTEM)
|
||||||
|
- 直接从文件系统路径加载证书
|
||||||
|
- 适合传统部署方式
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
### application.yml 配置
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
certificate:
|
||||||
|
load-mode: VOLUME # 证书加载模式
|
||||||
|
cert-root-path: /app/certs # 证书根目录
|
||||||
|
|
||||||
|
wechat-pay:
|
||||||
|
dev:
|
||||||
|
api-v3-key: "your-api-v3-key"
|
||||||
|
private-key-file: "apiclient_key.pem"
|
||||||
|
apiclient-cert-file: "apiclient_cert.pem"
|
||||||
|
wechatpay-cert-file: "wechatpay_cert.pem"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 环境特定配置
|
||||||
|
|
||||||
|
- **开发环境**: `application-dev.yml` - 使用CLASSPATH模式
|
||||||
|
- **生产环境**: `application-prod.yml` - 使用VOLUME模式
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 1. 证书文件找不到
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查证书文件是否存在
|
||||||
|
docker exec cms-java-app ls -la /app/certs/
|
||||||
|
|
||||||
|
# 检查文件权限
|
||||||
|
docker exec cms-java-app ls -la /app/certs/wechat/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 支付接口调用失败
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看应用日志
|
||||||
|
docker-compose logs cms-app | grep -i cert
|
||||||
|
|
||||||
|
# 检查证书配置
|
||||||
|
docker exec cms-java-app cat /app/application.yml | grep -A 10 certificate
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 容器启动失败
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看详细错误信息
|
||||||
|
docker-compose logs cms-app
|
||||||
|
|
||||||
|
# 检查容器状态
|
||||||
|
docker-compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
## 安全建议
|
||||||
|
|
||||||
|
1. **证书文件权限**: 设置为只读权限 (444)
|
||||||
|
2. **证书目录权限**: 限制访问权限
|
||||||
|
3. **敏感信息**: 使用环境变量或Docker secrets管理敏感配置
|
||||||
|
4. **网络安全**: 使用内部网络,限制端口暴露
|
||||||
|
|
||||||
|
## 更新证书
|
||||||
|
|
||||||
|
1. 停止应用容器:`docker-compose stop cms-app`
|
||||||
|
2. 更新证书文件到 `certs/` 目录
|
||||||
|
3. 重启应用容器:`docker-compose start cms-app`
|
||||||
|
|
||||||
|
## 监控和日志
|
||||||
|
|
||||||
|
- 应用日志:`./logs/` 目录
|
||||||
|
- 容器日志:`docker-compose logs`
|
||||||
|
- 健康检查:访问 `/actuator/health` 端点
|
||||||
|
|
||||||
|
通过以上配置,你的应用在Docker容器中就能正确加载支付证书了!
|
||||||
451
jczxw-java/pom.xml
Normal file
451
jczxw-java/pom.xml
Normal file
@@ -0,0 +1,451 @@
|
|||||||
|
<?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>
|
||||||
|
|
||||||
|
<groupId>com.gxwebsoft</groupId>
|
||||||
|
<artifactId>websopy-api</artifactId>
|
||||||
|
<version>1.5.0</version>
|
||||||
|
|
||||||
|
<name>websopy-api</name>
|
||||||
|
<description>WebSoftApi project for Spring Boot</description>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>2.7.18</version>
|
||||||
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- ==================== 核心框架 ==================== -->
|
||||||
|
<!-- spring-boot-web -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Validation (for @Length, @NotBlank, etc.) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- spring security -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- spring-boot-aop -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-aop</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- spring-boot-configuration-processor -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Lombok -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 数据库 ==================== -->
|
||||||
|
<!-- MySQL -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- PostgreSQL -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Druid 连接池 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>druid-spring-boot-starter</artifactId>
|
||||||
|
<version>1.2.20</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MyBatis-Plus -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||||
|
<version>3.4.3.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MyBatis-Plus 连表插件 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.yulichang</groupId>
|
||||||
|
<artifactId>mybatis-plus-join-boot-starter</artifactId>
|
||||||
|
<version>1.4.5</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MyBatis-Plus 代码生成器 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-generator</artifactId>
|
||||||
|
<version>3.4.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 缓存 ==================== -->
|
||||||
|
<!-- Redis -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Caffeine 内存缓存 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||||
|
<artifactId>caffeine</artifactId>
|
||||||
|
<version>3.1.8</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== JSON 处理 ==================== -->
|
||||||
|
<!-- Jackson JSR310 support for Java 8 time -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
|
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JSch - SSH 连接 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.mwiede</groupId>
|
||||||
|
<artifactId>jsch</artifactId>
|
||||||
|
<version>0.2.18</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Fastjson (app 模块有使用) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>fastjson</artifactId>
|
||||||
|
<version>2.0.43</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Gson -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
<version>2.10.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Tika, 用于获取文件 content-type -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.tika</groupId>
|
||||||
|
<artifactId>tika-core</artifactId>
|
||||||
|
<version>2.9.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== Hutool 工具库 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-core</artifactId>
|
||||||
|
<version>5.8.25</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-extra</artifactId>
|
||||||
|
<version>5.8.25</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-http</artifactId>
|
||||||
|
<version>5.8.25</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-crypto</artifactId>
|
||||||
|
<version>5.8.25</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-json</artifactId>
|
||||||
|
<version>5.8.25</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== JWT 认证 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-api</artifactId>
|
||||||
|
<version>0.11.5</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-impl</artifactId>
|
||||||
|
<version>0.11.5</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-jackson</artifactId>
|
||||||
|
<version>0.11.5</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 图形验证码 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.whvcse</groupId>
|
||||||
|
<artifactId>easy-captcha</artifactId>
|
||||||
|
<version>1.6.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== RabbitMQ 消息队列 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== API 文档 ==================== -->
|
||||||
|
<!-- SpringDoc OpenAPI 3 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-ui</artifactId>
|
||||||
|
<version>1.7.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Knife4j -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.xiaoymin</groupId>
|
||||||
|
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
|
||||||
|
<version>4.3.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== WebSocket ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 邮件 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 微信支付 APIv3 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.wechatpay-apiv3</groupId>
|
||||||
|
<artifactId>wechatpay-java</artifactId>
|
||||||
|
<version>0.2.17</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 阿里云机器翻译 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aliyun</groupId>
|
||||||
|
<artifactId>alimt20181012</artifactId>
|
||||||
|
<version>1.0.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== EasyPoi Excel处理 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.afterturn</groupId>
|
||||||
|
<artifactId>easypoi-base</artifactId>
|
||||||
|
<version>4.4.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== Beetl 模板引擎 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.ibeetl</groupId>
|
||||||
|
<artifactId>beetl</artifactId>
|
||||||
|
<version>3.15.10.RELEASE</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 微信小程序 SDK ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.binarywang</groupId>
|
||||||
|
<artifactId>weixin-java-miniapp</artifactId>
|
||||||
|
<version>4.6.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 快递100 SDK ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.kuaidi100-api</groupId>
|
||||||
|
<artifactId>sdk</artifactId>
|
||||||
|
<version>1.0.13</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 支付宝 SDK ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alipay.sdk</groupId>
|
||||||
|
<artifactId>alipay-sdk-java</artifactId>
|
||||||
|
<version>4.35.0.ALL</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 阿里云 SDK 核心 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aliyun</groupId>
|
||||||
|
<artifactId>aliyun-java-sdk-core</artifactId>
|
||||||
|
<version>4.6.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 阿里云实人认证 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aliyun</groupId>
|
||||||
|
<artifactId>cloudauth20190307</artifactId>
|
||||||
|
<version>3.13.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== JodConverter 文档转PDF ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.livesense</groupId>
|
||||||
|
<artifactId>jodconverter-core</artifactId>
|
||||||
|
<version>1.0.5</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== ZXing 二维码 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.zxing</groupId>
|
||||||
|
<artifactId>core</artifactId>
|
||||||
|
<version>3.5.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== JUnit 测试 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<version>4.11.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== HTTP 客户端 ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>okhttp</artifactId>
|
||||||
|
<version>4.12.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Apache HttpClient (WxOfficialController 等使用) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>4.5.14</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 阿里云 OSS ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aliyun.oss</groupId>
|
||||||
|
<artifactId>aliyun-sdk-oss</artifactId>
|
||||||
|
<version>3.17.4</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 腾讯云 COS ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.qcloud</groupId>
|
||||||
|
<artifactId>cos_api</artifactId>
|
||||||
|
<version>5.6.157</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 华为云 OBS ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.huaweicloud</groupId>
|
||||||
|
<artifactId>esdk-obs-java</artifactId>
|
||||||
|
<version>3.25.10</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 七牛云 Kodo ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.qiniu</groupId>
|
||||||
|
<artifactId>qiniu-java-sdk</artifactId>
|
||||||
|
<version>7.19.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ==================== 微信公众号 SDK ==================== -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.binarywang</groupId>
|
||||||
|
<artifactId>weixin-java-mp</artifactId>
|
||||||
|
<version>4.6.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/java</directory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*Mapper.xml</include>
|
||||||
|
</includes>
|
||||||
|
</resource>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<includes>
|
||||||
|
<include>**</include>
|
||||||
|
</includes>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.project-lombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>17</source>
|
||||||
|
<target>17</target>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.30</version>
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
<version>2.7.18</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>aliYunMaven</id>
|
||||||
|
<url>https://maven.aliyun.com/repository/public</url>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>aliyun-sdk</id>
|
||||||
|
<url>https://maven.aliyun.com/nexus/content/groups/public/</url>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>central</id>
|
||||||
|
<url>https://repo.maven.apache.org/maven2</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.gxwebsoft;
|
||||||
|
|
||||||
|
import com.gxwebsoft.common.core.config.ConfigProperties;
|
||||||
|
import com.gxwebsoft.common.core.config.MqttProperties;
|
||||||
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
|
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动类
|
||||||
|
* Created by WebSoft on 2018-02-22 11:29:03
|
||||||
|
*/
|
||||||
|
@EnableAsync
|
||||||
|
@EnableTransactionManagement
|
||||||
|
@MapperScan("com.gxwebsoft.**.mapper")
|
||||||
|
@EnableConfigurationProperties({ConfigProperties.class, MqttProperties.class})
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableScheduling
|
||||||
|
@EnableWebSocket
|
||||||
|
public class WebSoftApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(WebSoftApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.gxwebsoft.app.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用模块微信支付配置属性
|
||||||
|
* 微信支付 Native 支付(扫码支付)配置
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "app.pay.wx")
|
||||||
|
public class AppPayProperties {
|
||||||
|
|
||||||
|
/** 是否启用微信支付 */
|
||||||
|
private boolean enabled = true;
|
||||||
|
|
||||||
|
/** 商户号 */
|
||||||
|
private String mchId;
|
||||||
|
|
||||||
|
/** 商户证书序列号 */
|
||||||
|
private String merchantSerialNumber;
|
||||||
|
|
||||||
|
/** APIv3 密钥 */
|
||||||
|
private String apiV3Key;
|
||||||
|
|
||||||
|
/** 微信支付 AppId(服务商/直连商户对应的小程序/公众号/网站应用) */
|
||||||
|
private String appId;
|
||||||
|
|
||||||
|
/** 证书根目录路径(生产环境 Docker 挂载卷路径) */
|
||||||
|
private String certRootPath;
|
||||||
|
|
||||||
|
/** 商户私钥文件相对路径(相对于 certRootPath) */
|
||||||
|
private String privateKeyRelativePath = "wechat/apiclient_key.pem";
|
||||||
|
|
||||||
|
/** 微信支付平台证书文件相对路径 */
|
||||||
|
private String wechatpayCertRelativePath = "wechat/wechatpay_cert.pem";
|
||||||
|
|
||||||
|
/** 微信支付公钥文件相对路径(使用公钥模式时必填) */
|
||||||
|
private String wechatpayPublicKeyPath;
|
||||||
|
|
||||||
|
/** 微信支付公钥ID(使用公钥模式时必填) */
|
||||||
|
private String wechatpayPublicKeyId;
|
||||||
|
|
||||||
|
/** 支付成功回调地址 */
|
||||||
|
private String notifyUrl;
|
||||||
|
|
||||||
|
/** 是否为测试模式(使用测试商户号) */
|
||||||
|
private boolean testMode = false;
|
||||||
|
|
||||||
|
// ===================== 测试商户配置(testMode=true 时使用)=====================
|
||||||
|
|
||||||
|
/** 测试商户号 */
|
||||||
|
private String testMchId;
|
||||||
|
|
||||||
|
/** 测试商户序列号 */
|
||||||
|
private String testMerchantSerialNumber;
|
||||||
|
|
||||||
|
/** 测试 APIv3 密钥 */
|
||||||
|
private String testApiV3Key;
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.app.entity.AppApiKey;
|
||||||
|
import com.gxwebsoft.app.param.AppApiKeyParam;
|
||||||
|
import com.gxwebsoft.app.service.AppApiKeyService;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import com.gxwebsoft.common.core.web.PageResult;
|
||||||
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用 API Key 管理控制器
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-02
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "应用API Key管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/apikey")
|
||||||
|
public class AppApiKeyController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppApiKeyService appApiKeyService;
|
||||||
|
|
||||||
|
@Operation(summary = "分页查询API Key列表")
|
||||||
|
@GetMapping
|
||||||
|
public ApiResult<PageResult<AppApiKey>> page(AppApiKeyParam param) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录",null);
|
||||||
|
}
|
||||||
|
param.setUserId(loginUser.getUserId());
|
||||||
|
param.setTenantId(loginUser.getTenantId());
|
||||||
|
return success(appApiKeyService.pageRel(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询API Key详情")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResult<AppApiKey> get(@PathVariable Long id) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录",null);
|
||||||
|
}
|
||||||
|
AppApiKey apiKey = appApiKeyService.getByIdRel(id);
|
||||||
|
if (apiKey == null) {
|
||||||
|
return fail("API Key不存在",null);
|
||||||
|
}
|
||||||
|
if (!apiKey.getUserId().equals(loginUser.getUserId())) {
|
||||||
|
return fail("无权访问此API Key",null);
|
||||||
|
}
|
||||||
|
return success(apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "创建API Key")
|
||||||
|
@PostMapping
|
||||||
|
public ApiResult<AppApiKey> create(@RequestBody AppApiKey apiKey) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录",null);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
AppApiKey result = appApiKeyService.createApiKey(
|
||||||
|
apiKey, loginUser.getUserId(), loginUser.getTenantId());
|
||||||
|
return success("API Key创建成功,请妥善保管", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("创建API Key失败", e);
|
||||||
|
return fail(e.getMessage(),null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "更新API Key信息")
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ApiResult<?> update(@PathVariable Long id, @RequestBody AppApiKey apiKey) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录");
|
||||||
|
}
|
||||||
|
AppApiKey existing = appApiKeyService.getByIdRel(id);
|
||||||
|
if (existing == null) {
|
||||||
|
return fail("API Key不存在");
|
||||||
|
}
|
||||||
|
if (!existing.getUserId().equals(loginUser.getUserId())) {
|
||||||
|
return fail("无权修改此API Key");
|
||||||
|
}
|
||||||
|
apiKey.setId(id);
|
||||||
|
apiKey.setUserId(loginUser.getUserId());
|
||||||
|
try {
|
||||||
|
appApiKeyService.updateApiKey(apiKey);
|
||||||
|
return success("更新成功");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("更新API Key失败", e);
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "更新API Key状态")
|
||||||
|
@PutMapping("/{id}/status")
|
||||||
|
public ApiResult<?> updateStatus(@PathVariable Long id, @RequestParam Integer status) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
appApiKeyService.updateStatus(id, status, loginUser.getUserId());
|
||||||
|
return success(status == 0 ? "已启用" : "已禁用");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("更新API Key状态失败", e);
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "删除API Key")
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResult<?> delete(@PathVariable Long id) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
appApiKeyService.removeApiKey(id, loginUser.getUserId());
|
||||||
|
return success("删除成功");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("删除API Key失败", e);
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取API Key统计信息")
|
||||||
|
@GetMapping("/stats")
|
||||||
|
public ApiResult<Map<String, Object>> stats() {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录",null);
|
||||||
|
}
|
||||||
|
return success(appApiKeyService.statsByUser(
|
||||||
|
loginUser.getUserId(), loginUser.getTenantId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取速率限制信息")
|
||||||
|
@GetMapping("/rate-limits")
|
||||||
|
public ApiResult<Map<String, Object>> rateLimits() {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录",null);
|
||||||
|
}
|
||||||
|
// 返回默认速率限制(实际可根据用户等级调整)
|
||||||
|
Map<String, Object> limits = Map.of(
|
||||||
|
"plan", "免费版",
|
||||||
|
"rps", 5,
|
||||||
|
"dailyLimit", 1000,
|
||||||
|
"usedToday", 0,
|
||||||
|
"remainingToday", 1000
|
||||||
|
);
|
||||||
|
return success(limits);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.app.entity.AppArticleCategory;
|
||||||
|
import com.gxwebsoft.app.param.AppArticleCategoryParam;
|
||||||
|
import com.gxwebsoft.app.service.AppArticleCategoryService;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import com.gxwebsoft.common.core.web.PageResult;
|
||||||
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台文章分类控制器
|
||||||
|
*/
|
||||||
|
@Tag(name = "平台文章分类管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/article-category")
|
||||||
|
public class AppArticleCategoryController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppArticleCategoryService appArticleCategoryService;
|
||||||
|
|
||||||
|
@Operation(summary = "分页查询平台文章分类")
|
||||||
|
@GetMapping("/page")
|
||||||
|
public ApiResult<PageResult<AppArticleCategory>> page(AppArticleCategoryParam param) {
|
||||||
|
if (param.getTenantId() == null) {
|
||||||
|
param.setTenantId(getTenantId());
|
||||||
|
}
|
||||||
|
return success(appArticleCategoryService.page(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询平台文章分类列表")
|
||||||
|
@GetMapping()
|
||||||
|
public ApiResult<List<AppArticleCategory>> list(AppArticleCategoryParam param) {
|
||||||
|
if (param.getTenantId() == null) {
|
||||||
|
param.setTenantId(getTenantId());
|
||||||
|
}
|
||||||
|
return success(appArticleCategoryService.list(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "根据ID读取文章分类")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResult<AppArticleCategory> get(@PathVariable("id") Integer id) {
|
||||||
|
return success(appArticleCategoryService.getDetail(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "新增平台文章分类")
|
||||||
|
@PostMapping()
|
||||||
|
public ApiResult<?> save(@RequestBody AppArticleCategory category) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
Integer tenantId = category.getTenantId() != null ? category.getTenantId() : getTenantId();
|
||||||
|
if (appArticleCategoryService.saveCategory(category, loginUser, tenantId)) {
|
||||||
|
return success("添加成功");
|
||||||
|
}
|
||||||
|
return fail("添加失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "修改平台文章分类")
|
||||||
|
@PutMapping()
|
||||||
|
public ApiResult<?> update(@RequestBody AppArticleCategory category) {
|
||||||
|
if (appArticleCategoryService.updateCategory(category)) {
|
||||||
|
return success("修改成功");
|
||||||
|
}
|
||||||
|
return fail("修改失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "删除平台文章分类")
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResult<?> remove(@PathVariable("id") Integer id) {
|
||||||
|
if (appArticleCategoryService.removeCategory(id)) {
|
||||||
|
return success("删除成功");
|
||||||
|
}
|
||||||
|
return fail("该分类下还有文章,不能删除");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.app.entity.AppArticle;
|
||||||
|
import com.gxwebsoft.app.param.AppArticleParam;
|
||||||
|
import com.gxwebsoft.app.service.AppArticleService;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import com.gxwebsoft.common.core.web.PageResult;
|
||||||
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台文章控制器
|
||||||
|
*/
|
||||||
|
@Tag(name = "平台文章管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/article")
|
||||||
|
public class AppArticleController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppArticleService appArticleService;
|
||||||
|
|
||||||
|
@Operation(summary = "分页查询平台文章")
|
||||||
|
@GetMapping("/page")
|
||||||
|
public ApiResult<PageResult<AppArticle>> page(AppArticleParam param) {
|
||||||
|
if (param.getTenantId() == null) {
|
||||||
|
param.setTenantId(getTenantId());
|
||||||
|
}
|
||||||
|
return success(appArticleService.page(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询平台文章列表")
|
||||||
|
@GetMapping()
|
||||||
|
public ApiResult<List<AppArticle>> list(AppArticleParam param) {
|
||||||
|
if (param.getTenantId() == null) {
|
||||||
|
param.setTenantId(getTenantId());
|
||||||
|
}
|
||||||
|
return success(appArticleService.list(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "根据ID读取文章详情")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResult<AppArticle> get(@PathVariable("id") Integer id) {
|
||||||
|
AppArticle article = appArticleService.getDetail(id, true);
|
||||||
|
if (article == null) {
|
||||||
|
return fail("文章不存在", null);
|
||||||
|
}
|
||||||
|
return success(article);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "根据编码读取文章详情")
|
||||||
|
@GetMapping("/getByCode/{code}")
|
||||||
|
public ApiResult<AppArticle> getByCode(@PathVariable("code") String code) {
|
||||||
|
return success(appArticleService.getByCode(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "新增平台文章")
|
||||||
|
@PostMapping()
|
||||||
|
public ApiResult<?> save(@RequestBody AppArticle article) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
Integer tenantId = article.getTenantId() != null ? article.getTenantId() : getTenantId();
|
||||||
|
if (appArticleService.saveArticle(article, loginUser, tenantId)) {
|
||||||
|
return success("添加成功");
|
||||||
|
}
|
||||||
|
return fail("添加失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "修改平台文章")
|
||||||
|
@PutMapping()
|
||||||
|
public ApiResult<?> update(@RequestBody AppArticle article) {
|
||||||
|
if (appArticleService.updateArticle(article)) {
|
||||||
|
return success("修改成功");
|
||||||
|
}
|
||||||
|
return fail("修改失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "删除平台文章")
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResult<?> remove(@PathVariable("id") Integer id) {
|
||||||
|
if (appArticleService.removeArticle(id)) {
|
||||||
|
return success("删除成功");
|
||||||
|
}
|
||||||
|
return fail("删除失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "平台文章统计")
|
||||||
|
@GetMapping("/data")
|
||||||
|
public ApiResult<Map<String, Integer>> data(AppArticleParam param) {
|
||||||
|
if (param.getTenantId() == null) {
|
||||||
|
param.setTenantId(getTenantId());
|
||||||
|
}
|
||||||
|
return success(appArticleService.getStats(param));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.app.entity.AppCloudCredential;
|
||||||
|
import com.gxwebsoft.app.param.AppCloudCredentialParam;
|
||||||
|
import com.gxwebsoft.app.service.AppCloudCredentialService;
|
||||||
|
import com.gxwebsoft.common.core.annotation.OperationLog;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 云账号凭证管理控制器
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-04
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "云账号凭证管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/cloud-credential")
|
||||||
|
public class AppCloudCredentialController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppCloudCredentialService appCloudCredentialService;
|
||||||
|
|
||||||
|
@Operation(summary = "分页查询凭证列表")
|
||||||
|
@GetMapping("/page")
|
||||||
|
public ApiResult page(AppCloudCredentialParam param) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录");
|
||||||
|
param.setUserId(loginUser.getUserId());
|
||||||
|
param.setTenantId(loginUser.getTenantId());
|
||||||
|
return success(appCloudCredentialService.pageRel(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询凭证列表(不分页)")
|
||||||
|
@GetMapping
|
||||||
|
public ApiResult list(AppCloudCredentialParam param) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录");
|
||||||
|
param.setUserId(loginUser.getUserId());
|
||||||
|
param.setTenantId(loginUser.getTenantId());
|
||||||
|
return success(appCloudCredentialService.listRel(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取凭证详情")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResult get(@PathVariable Long id) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录");
|
||||||
|
AppCloudCredential credential = appCloudCredentialService.getById(id);
|
||||||
|
if (credential == null) return fail("凭证不存在");
|
||||||
|
if (!credential.getUserId().equals(loginUser.getUserId())) {
|
||||||
|
return fail("无权访问此凭证");
|
||||||
|
}
|
||||||
|
return success(credential);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "新增凭证")
|
||||||
|
@PostMapping
|
||||||
|
public ApiResult add(@RequestBody AppCloudCredential credential) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录");
|
||||||
|
credential.setUserId(loginUser.getUserId());
|
||||||
|
credential.setTenantId(loginUser.getTenantId());
|
||||||
|
try {
|
||||||
|
AppCloudCredential result = appCloudCredentialService.add(credential);
|
||||||
|
return success("添加成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "修改凭证")
|
||||||
|
@PutMapping
|
||||||
|
public ApiResult update(@RequestBody AppCloudCredential credential) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录");
|
||||||
|
// 检查归属
|
||||||
|
AppCloudCredential exist = appCloudCredentialService.getById(credential.getId());
|
||||||
|
if (exist == null) return fail("凭证不存在");
|
||||||
|
if (!exist.getUserId().equals(loginUser.getUserId())) {
|
||||||
|
return fail("无权修改此凭证");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
AppCloudCredential result = appCloudCredentialService.update(credential);
|
||||||
|
return success("修改成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "删除凭证")
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResult remove(@PathVariable Long id) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录");
|
||||||
|
try {
|
||||||
|
appCloudCredentialService.remove(id, loginUser.getUserId());
|
||||||
|
return success("删除成功");
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "测试凭证连接")
|
||||||
|
@PostMapping("/test-connection")
|
||||||
|
public ApiResult testConnection(@RequestBody Map<String, Object> params) {
|
||||||
|
String provider = (String) params.get("provider");
|
||||||
|
String accessKeyId = (String) params.get("accessKeyId");
|
||||||
|
String accessKeySecret = (String) params.get("accessKeySecret");
|
||||||
|
String configJson = (String) params.get("configJson");
|
||||||
|
|
||||||
|
if (provider == null || provider.isEmpty()) {
|
||||||
|
return fail("请指定云服务商");
|
||||||
|
}
|
||||||
|
if (accessKeyId == null || accessKeySecret == null) {
|
||||||
|
return fail("请提供访问密钥");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建凭证映射
|
||||||
|
java.util.Map<String, String> credentials = new java.util.HashMap<>();
|
||||||
|
credentials.put("accessKeyId", accessKeyId);
|
||||||
|
credentials.put("accessKeySecret", accessKeySecret);
|
||||||
|
if (configJson != null) {
|
||||||
|
try {
|
||||||
|
Map<String, String> config = new com.alibaba.fastjson.JSONObject()
|
||||||
|
.parseObject(configJson, Map.class);
|
||||||
|
credentials.putAll(config);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("解析配置JSON失败: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean success = appCloudCredentialService.testConnection(provider, credentials);
|
||||||
|
return success ? success("连接成功") : fail("连接失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "根据ID测试凭证连接")
|
||||||
|
@PostMapping("/test/{id}")
|
||||||
|
public ApiResult testConnectionById(@PathVariable Long id) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录");
|
||||||
|
AppCloudCredential credential = appCloudCredentialService.getById(id);
|
||||||
|
if (credential == null) return fail("凭证不存在");
|
||||||
|
if (!credential.getUserId().equals(loginUser.getUserId())) {
|
||||||
|
return fail("无权访问此凭证");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// 使用专门的 ID 查询方法,获取已解密的凭证
|
||||||
|
java.util.Map<String, String> credentials = appCloudCredentialService.getCredentialsByCredentialId(id);
|
||||||
|
if (credentials == null) {
|
||||||
|
return fail("获取凭证失败");
|
||||||
|
}
|
||||||
|
// 使用返回详细消息的测试方法
|
||||||
|
Object[] result = appCloudCredentialService.testConnectionWithMessage(credential.getProvider(), credentials);
|
||||||
|
boolean success = (Boolean) result[0];
|
||||||
|
String message = (String) result[1];
|
||||||
|
|
||||||
|
credential.setTestStatus(success ? 1 : 2);
|
||||||
|
credential.setTestMessage(message);
|
||||||
|
appCloudCredentialService.updateById(credential);
|
||||||
|
|
||||||
|
java.util.Map<String, Object> resultMap = new java.util.HashMap<>();
|
||||||
|
resultMap.put("success", success);
|
||||||
|
resultMap.put("message", success ? "连接成功" : message);
|
||||||
|
return success(resultMap);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("测试连接异常: {}", e.getMessage(), e);
|
||||||
|
// 即使异常也更新状态
|
||||||
|
try {
|
||||||
|
credential.setTestStatus(2);
|
||||||
|
credential.setTestMessage("连接失败: " + e.getMessage());
|
||||||
|
appCloudCredentialService.updateById(credential);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("更新测试状态失败: {}", ex.getMessage());
|
||||||
|
}
|
||||||
|
java.util.Map<String, Object> errorResult = new java.util.HashMap<>();
|
||||||
|
errorResult.put("success", false);
|
||||||
|
errorResult.put("message", "测试连接失败: " + e.getMessage());
|
||||||
|
return success(errorResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取支持的云服务商列表")
|
||||||
|
@GetMapping("/providers")
|
||||||
|
public ApiResult providers() {
|
||||||
|
return success(com.gxwebsoft.app.service.cloud.CloudStorageProviderFactory.getSupportedProviders());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import com.gxwebsoft.app.entity.AppConfig;
|
||||||
|
import com.gxwebsoft.app.param.AppConfigParam;
|
||||||
|
import com.gxwebsoft.app.service.AppConfigService;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.PageResult;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用配置表 Controller
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "应用配置管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/app-config")
|
||||||
|
public class AppConfigController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppConfigService appConfigService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询应用配置
|
||||||
|
*/
|
||||||
|
@Operation(summary = "分页查询应用配置")
|
||||||
|
@GetMapping("/page")
|
||||||
|
public ApiResult<PageResult<AppConfig>> page(AppConfigParam param) {
|
||||||
|
return success(new PageResult<>(appConfigService.page(param)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取应用配置列表
|
||||||
|
*/
|
||||||
|
@Operation(summary = "获取应用配置列表")
|
||||||
|
@GetMapping()
|
||||||
|
public ApiResult<List<AppConfig>> list(AppConfigParam param) {
|
||||||
|
return success(appConfigService.list(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据应用ID获取配置映射
|
||||||
|
*/
|
||||||
|
@Operation(summary = "根据应用ID获取配置映射")
|
||||||
|
@GetMapping("/map/{appId}")
|
||||||
|
public ApiResult<Map<String, Object>> getConfigsByAppId(@PathVariable Integer appId) {
|
||||||
|
return success(appConfigService.getConfigsByAppId(appId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个配置值
|
||||||
|
*/
|
||||||
|
@Operation(summary = "获取单个配置值")
|
||||||
|
@GetMapping("/value")
|
||||||
|
public ApiResult<String> getConfigValue(@RequestParam Integer appId, @RequestParam String configKey) {
|
||||||
|
return success(appConfigService.getConfigValue(appId, configKey),null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存配置
|
||||||
|
*/
|
||||||
|
@Operation(summary = "保存配置")
|
||||||
|
@PostMapping()
|
||||||
|
public ApiResult<?> save(@RequestBody AppConfig config) {
|
||||||
|
appConfigService.saveConfig(config);
|
||||||
|
return success("保存成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存配置
|
||||||
|
*/
|
||||||
|
@Operation(summary = "批量保存配置")
|
||||||
|
@PostMapping("/batch")
|
||||||
|
public ApiResult<?> batchSave(@RequestBody BatchSaveRequest request) {
|
||||||
|
appConfigService.batchSaveConfig(request.getAppId(), request.getConfigs());
|
||||||
|
return success("保存成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新配置
|
||||||
|
*/
|
||||||
|
@Operation(summary = "更新配置")
|
||||||
|
@PutMapping()
|
||||||
|
public ApiResult<?> update(@RequestBody AppConfig config) {
|
||||||
|
appConfigService.updateConfig(config);
|
||||||
|
return success("更新成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除配置
|
||||||
|
*/
|
||||||
|
@Operation(summary = "删除配置")
|
||||||
|
@DeleteMapping("/{configId}")
|
||||||
|
public ApiResult<?> delete(@PathVariable Integer configId) {
|
||||||
|
appConfigService.deleteConfig(configId);
|
||||||
|
return success("删除成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存请求对象
|
||||||
|
*/
|
||||||
|
public static class BatchSaveRequest {
|
||||||
|
private Integer appId;
|
||||||
|
private List<AppConfig> configs;
|
||||||
|
|
||||||
|
public Integer getAppId() {
|
||||||
|
return appId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppId(Integer appId) {
|
||||||
|
this.appId = appId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AppConfig> getConfigs() {
|
||||||
|
return configs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConfigs(List<AppConfig> configs) {
|
||||||
|
this.configs = configs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.app.entity.AppContract;
|
||||||
|
import com.gxwebsoft.app.param.AppContractParam;
|
||||||
|
import com.gxwebsoft.app.service.AppContractService;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import com.gxwebsoft.common.core.web.PageResult;
|
||||||
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合同管理控制器
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-13
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "合同管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/contract")
|
||||||
|
public class AppContractController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppContractService appContractService;
|
||||||
|
|
||||||
|
@Operation(summary = "分页查询合同列表")
|
||||||
|
@GetMapping("/page")
|
||||||
|
public ApiResult<PageResult<AppContract>> page(AppContractParam param) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录", null);
|
||||||
|
return success(appContractService.page(param, loginUser.getUserId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取合同详情")
|
||||||
|
@GetMapping("/{contractId}")
|
||||||
|
public ApiResult<AppContract> detail(@PathVariable Long contractId) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录", null);
|
||||||
|
AppContract contract = appContractService.getById(contractId);
|
||||||
|
if (contract == null || contract.getDeleted() == 1) {
|
||||||
|
return fail("合同不存在", null);
|
||||||
|
}
|
||||||
|
if (!contract.getUserId().equals(loginUser.getUserId())) {
|
||||||
|
return fail("无权查看该合同", null);
|
||||||
|
}
|
||||||
|
return success(contract);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "新增合同")
|
||||||
|
@PostMapping
|
||||||
|
public ApiResult<AppContract> create(@RequestBody AppContract contract) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录", null);
|
||||||
|
try {
|
||||||
|
AppContract result = appContractService.create(contract, loginUser.getUserId());
|
||||||
|
return success("合同创建成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "更新合同")
|
||||||
|
@PutMapping("/{contractId}")
|
||||||
|
public ApiResult<AppContract> update(@PathVariable Long contractId, @RequestBody AppContract contract) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录", null);
|
||||||
|
contract.setContractId(contractId);
|
||||||
|
try {
|
||||||
|
AppContract result = appContractService.update(contract, loginUser.getUserId());
|
||||||
|
return success("合同更新成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "删除合同")
|
||||||
|
@DeleteMapping("/{contractId}")
|
||||||
|
public ApiResult<?> remove(@PathVariable Long contractId) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录");
|
||||||
|
try {
|
||||||
|
appContractService.remove(contractId, loginUser.getUserId());
|
||||||
|
return success("合同已删除");
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "合同统计数据")
|
||||||
|
@GetMapping("/stats")
|
||||||
|
public ApiResult<Map<String, Long>> stats() {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录", null);
|
||||||
|
return success(appContractService.stats(loginUser.getUserId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import com.gxwebsoft.app.service.AppCredentialService;
|
||||||
|
import com.gxwebsoft.app.entity.AppCredential;
|
||||||
|
import com.gxwebsoft.app.param.AppCredentialParam;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.PageResult;
|
||||||
|
import com.gxwebsoft.common.core.web.BatchParam;
|
||||||
|
import com.gxwebsoft.common.core.annotation.OperationLog;
|
||||||
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用密钥凭证控制器
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-03-28 21:29:44
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "应用密钥凭证管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/app-credential")
|
||||||
|
public class AppCredentialController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppCredentialService appCredentialService;
|
||||||
|
|
||||||
|
@Operation(summary = "分页查询应用密钥凭证")
|
||||||
|
@GetMapping("/page")
|
||||||
|
public ApiResult<PageResult<AppCredential>> page(AppCredentialParam param) {
|
||||||
|
return success(appCredentialService.pageRel(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询全部应用密钥凭证")
|
||||||
|
@GetMapping()
|
||||||
|
public ApiResult<List<AppCredential>> list(AppCredentialParam param) {
|
||||||
|
return success(appCredentialService.listRel(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "根据id查询应用密钥凭证")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResult<AppCredential> get(@PathVariable("id") Integer id) {
|
||||||
|
return success(appCredentialService.getByIdRel(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appCredential:save')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "创建应用密钥凭证(自动生成 ClientID 和 ClientSecret)")
|
||||||
|
@PostMapping()
|
||||||
|
public ApiResult<?> save(@RequestBody AppCredential appCredential) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录");
|
||||||
|
}
|
||||||
|
if (appCredential.getAppId() == null) {
|
||||||
|
return fail("请选择关联应用");
|
||||||
|
}
|
||||||
|
appCredential.setUserId(loginUser.getUserId());
|
||||||
|
// 创建并生成密钥
|
||||||
|
AppCredential result = appCredentialService.createCredential(appCredential);
|
||||||
|
return success("创建成功,请保存 ClientSecret,该信息仅展示一次", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appCredential:update')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "修改应用密钥凭证(名称/类型/备注等,不含密钥)")
|
||||||
|
@PutMapping()
|
||||||
|
public ApiResult<?> update(@RequestBody AppCredential appCredential) {
|
||||||
|
// 防止通过此接口直接修改 appId/appSecret
|
||||||
|
appCredential.setAppId(null);
|
||||||
|
appCredential.setClientSecret(null);
|
||||||
|
if (appCredentialService.updateById(appCredential)) {
|
||||||
|
return success("修改成功");
|
||||||
|
}
|
||||||
|
return fail("修改失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appCredential:update')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "重置 AppSecret(重新生成密钥)")
|
||||||
|
@PostMapping("/resetSecret/{id}")
|
||||||
|
public ApiResult<?> resetSecret(@PathVariable("id") Long id) {
|
||||||
|
try {
|
||||||
|
AppCredential result = appCredentialService.resetSecret(id);
|
||||||
|
return success("重置成功,请保存新 AppSecret,该信息仅展示一次", result);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appCredential:update')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "禁用/启用凭证")
|
||||||
|
@PutMapping("/status/{id}/{status}")
|
||||||
|
public ApiResult<?> updateStatus(@PathVariable("id") Long id, @PathVariable("status") Integer status) {
|
||||||
|
if (appCredentialService.updateStatus(id, status)) {
|
||||||
|
return success(status == 0 ? "已启用" : "已禁用");
|
||||||
|
}
|
||||||
|
return fail("操作失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appCredential:remove')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "删除应用密钥凭证")
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResult<?> remove(@PathVariable("id") Integer id) {
|
||||||
|
if (appCredentialService.removeById(id)) {
|
||||||
|
return success("删除成功");
|
||||||
|
}
|
||||||
|
return fail("删除失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appCredential:save')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "批量添加应用密钥凭证")
|
||||||
|
@PostMapping("/batch")
|
||||||
|
public ApiResult<?> saveBatch(@RequestBody List<AppCredential> list) {
|
||||||
|
if (appCredentialService.saveBatch(list)) {
|
||||||
|
return success("添加成功");
|
||||||
|
}
|
||||||
|
return fail("添加失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appCredential:update')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "批量修改应用密钥凭证")
|
||||||
|
@PutMapping("/batch")
|
||||||
|
public ApiResult<?> updateBatch(@RequestBody BatchParam<AppCredential> batchParam) {
|
||||||
|
if (batchParam.update(appCredentialService, "id")) {
|
||||||
|
return success("修改成功");
|
||||||
|
}
|
||||||
|
return fail("修改失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appCredential:remove')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "批量删除应用密钥凭证")
|
||||||
|
@DeleteMapping("/batch")
|
||||||
|
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
|
||||||
|
if (appCredentialService.removeByIds(ids)) {
|
||||||
|
return success("删除成功");
|
||||||
|
}
|
||||||
|
return fail("删除失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import com.gxwebsoft.app.service.AppEventService;
|
||||||
|
import com.gxwebsoft.app.entity.AppEvent;
|
||||||
|
import com.gxwebsoft.app.param.AppEventParam;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.PageResult;
|
||||||
|
import com.gxwebsoft.common.core.annotation.OperationLog;
|
||||||
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用操作动态控制器
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-03-28 21:29:44
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "应用操作动态管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/app-event")
|
||||||
|
public class AppEventController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppEventService appEventService;
|
||||||
|
|
||||||
|
@Operation(summary = "分页查询操作动态")
|
||||||
|
@GetMapping("/page")
|
||||||
|
public ApiResult<PageResult<AppEvent>> page(AppEventParam param) {
|
||||||
|
return success(appEventService.pageRel(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询全部操作动态")
|
||||||
|
@GetMapping()
|
||||||
|
public ApiResult<List<AppEvent>> list(AppEventParam param) {
|
||||||
|
return success(appEventService.listRel(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "根据id查询操作动态")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResult<AppEvent> get(@PathVariable("id") Integer id) {
|
||||||
|
return success(appEventService.getByIdRel(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取应用最新一条动态(用于卡片展示)")
|
||||||
|
@GetMapping("/latest/{appId}")
|
||||||
|
public ApiResult<AppEvent> getLatest(@PathVariable("appId") Long appId) {
|
||||||
|
return success(appEventService.getLatestEvent(appId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appEvent:save')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "手动记录操作动态")
|
||||||
|
@PostMapping()
|
||||||
|
public ApiResult<?> save(@RequestBody AppEvent appEvent) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser != null) {
|
||||||
|
appEvent.setUserId(loginUser.getUserId());
|
||||||
|
appEvent.setTenantId(loginUser.getTenantId());
|
||||||
|
if (appEvent.getOperatorId() == null) {
|
||||||
|
appEvent.setOperatorId(loginUser.getUserId().longValue());
|
||||||
|
}
|
||||||
|
if (appEvent.getOperator() == null) {
|
||||||
|
appEvent.setOperator(loginUser.getNickname());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (appEventService.save(appEvent)) {
|
||||||
|
return success("记录成功");
|
||||||
|
}
|
||||||
|
return fail("记录失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appEvent:update')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "修改操作动态")
|
||||||
|
@PutMapping()
|
||||||
|
public ApiResult<?> update(@RequestBody AppEvent appEvent) {
|
||||||
|
if (appEventService.updateById(appEvent)) {
|
||||||
|
return success("修改成功");
|
||||||
|
}
|
||||||
|
return fail("修改失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appEvent:remove')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "删除操作动态")
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResult<?> remove(@PathVariable("id") Integer id) {
|
||||||
|
if (appEventService.removeById(id)) {
|
||||||
|
return success("删除成功");
|
||||||
|
}
|
||||||
|
return fail("删除失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appEvent:remove')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "清空应用所有动态记录")
|
||||||
|
@DeleteMapping("/clear/{appId}")
|
||||||
|
public ApiResult<?> clearByAppId(@PathVariable("appId") Long appId) {
|
||||||
|
// 只清空当前租户下的数据
|
||||||
|
AppEventParam param = new AppEventParam();
|
||||||
|
param.setAppId(appId);
|
||||||
|
List<AppEvent> list = appEventService.listRel(param);
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
return success("暂无动态记录");
|
||||||
|
}
|
||||||
|
List<Long> ids = list.stream().map(AppEvent::getId).collect(java.util.stream.Collectors.toList());
|
||||||
|
if (appEventService.removeByIds(ids)) {
|
||||||
|
return success("已清空 " + ids.size() + " 条动态记录");
|
||||||
|
}
|
||||||
|
return fail("清空失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appEvent:remove')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "批量删除操作动态")
|
||||||
|
@DeleteMapping("/batch")
|
||||||
|
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
|
||||||
|
if (appEventService.removeByIds(ids)) {
|
||||||
|
return success("删除成功");
|
||||||
|
}
|
||||||
|
return fail("删除失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.app.service.AppInviteService;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用成员邀请 Controller
|
||||||
|
*/
|
||||||
|
@Tag(name = "应用成员邀请")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/developer/invite")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AppInviteController extends BaseController {
|
||||||
|
|
||||||
|
private final AppInviteService appInviteService;
|
||||||
|
|
||||||
|
@Operation(summary = "生成二维码邀请")
|
||||||
|
@PreAuthorize("hasAuthority('app:appUser:save')")
|
||||||
|
@PostMapping("/qrcode")
|
||||||
|
public ApiResult<Map<String, String>> generateQrCode(@RequestBody Map<String, Object> params) {
|
||||||
|
Integer appId = (Integer) params.get("appId");
|
||||||
|
String role = (String) params.get("role");
|
||||||
|
Integer currentUserId = getLoginUserId();
|
||||||
|
|
||||||
|
Map<String, String> result = appInviteService.generateQrCodeInvite(appId, role, currentUserId);
|
||||||
|
return success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "生成链接邀请")
|
||||||
|
@PreAuthorize("hasAuthority('app:appUser:save')")
|
||||||
|
@PostMapping("/link")
|
||||||
|
public ApiResult<Map<String, String>> generateLink(@RequestBody Map<String, Object> params) {
|
||||||
|
Integer appId = (Integer) params.get("appId");
|
||||||
|
String role = (String) params.get("role");
|
||||||
|
Integer currentUserId = getLoginUserId();
|
||||||
|
|
||||||
|
Map<String, String> result = appInviteService.generateLinkInvite(appId, role, currentUserId);
|
||||||
|
return success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "验证邀请")
|
||||||
|
@PostMapping("/verify")
|
||||||
|
public ApiResult<Map<String, Object>> verifyInvite(@RequestBody Map<String, Object> params) {
|
||||||
|
String token = (String) params.get("token");
|
||||||
|
Integer appId = (Integer) params.get("appId");
|
||||||
|
|
||||||
|
Map<String, Object> result = appInviteService.verifyInvite(token, appId);
|
||||||
|
return success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "通过Token获取邀请信息(小程序专用)")
|
||||||
|
@GetMapping("/info")
|
||||||
|
public ApiResult<Map<String, Object>> getInviteInfo(@RequestParam String token) {
|
||||||
|
Map<String, Object> result = appInviteService.getInviteInfoByToken(token);
|
||||||
|
return success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "接受邀请(支持小程序手机号自动注册)")
|
||||||
|
@PostMapping("/accept")
|
||||||
|
public ApiResult<?> acceptInvite(@RequestBody Map<String, Object> params) {
|
||||||
|
String token = (String) params.get("token");
|
||||||
|
Integer currentUserId = getLoginUserId();
|
||||||
|
|
||||||
|
// 已登录用户直接加入
|
||||||
|
if (currentUserId != null) {
|
||||||
|
Integer appId = (Integer) params.get("appId");
|
||||||
|
appInviteService.acceptInvite(token, appId, currentUserId);
|
||||||
|
return success("加入成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 小程序手机号登录 + 加入(需要 code)
|
||||||
|
String code = (String) params.get("code");
|
||||||
|
if (code != null) {
|
||||||
|
// TODO: 调用微信手机号登录接口,自动注册用户并加入
|
||||||
|
// 这里暂时返回提示
|
||||||
|
return fail("手机号登录功能开发中,请先在网页端登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
return fail("请先登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询邀请状态(用于轮询检测是否被使用)")
|
||||||
|
@GetMapping("/status")
|
||||||
|
public ApiResult<Map<String, Object>> getInviteStatus(@RequestParam String token) {
|
||||||
|
Map<String, Object> result = appInviteService.getInviteStatus(token);
|
||||||
|
return success(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,431 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.gxwebsoft.app.entity.AppInviteToken;
|
||||||
|
import com.gxwebsoft.app.entity.AppProduct;
|
||||||
|
import com.gxwebsoft.app.entity.AppUser;
|
||||||
|
import com.gxwebsoft.app.entity.AppUserCache;
|
||||||
|
import com.gxwebsoft.app.mapper.AppInviteTokenMapper;
|
||||||
|
import com.gxwebsoft.app.mapper.AppProductMapper;
|
||||||
|
import com.gxwebsoft.app.mapper.AppUserCacheMapper;
|
||||||
|
import com.gxwebsoft.app.mapper.AppUserMapper;
|
||||||
|
import com.gxwebsoft.app.service.AppUserCacheService;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
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.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小程序邀请接口(处理 /api/_app 路径)
|
||||||
|
* 用于小程序扫码加入应用场景
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "小程序邀请")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/_app/developer/invite")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AppMpInviteController extends BaseController {
|
||||||
|
|
||||||
|
private final AppInviteTokenMapper appInviteTokenMapper;
|
||||||
|
private final AppProductMapper appProductMapper;
|
||||||
|
private final AppUserMapper appUserMapper;
|
||||||
|
private final AppUserCacheMapper appUserCacheMapper;
|
||||||
|
private final AppUserCacheService appUserCacheService;
|
||||||
|
|
||||||
|
@Value("${app.invite.base-url:https://websopy.websoft.top}")
|
||||||
|
private String baseUrl;
|
||||||
|
|
||||||
|
@Value("${spring.redis.host:127.0.0.1}")
|
||||||
|
private String redisHost;
|
||||||
|
|
||||||
|
@Value("${spring.redis.port:6379}")
|
||||||
|
private int redisPort;
|
||||||
|
|
||||||
|
@Value("${spring.redis.password:}")
|
||||||
|
private String redisPassword;
|
||||||
|
|
||||||
|
@Operation(summary = "通过Token获取邀请信息(小程序专用)")
|
||||||
|
@GetMapping("/info")
|
||||||
|
public ApiResult<Map<String, Object>> getInviteInfo(@RequestParam String token) {
|
||||||
|
try {
|
||||||
|
Map<String, Object> result = getInviteInfoByToken(token);
|
||||||
|
return success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取邀请信息失败", e);
|
||||||
|
return fail("获取邀请信息失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "验证邀请(PC端和小程序通用)")
|
||||||
|
@PostMapping("/verify")
|
||||||
|
public ApiResult<Map<String, Object>> verifyInvite(@RequestBody Map<String, Object> params) {
|
||||||
|
try {
|
||||||
|
String token = (String) params.get("token");
|
||||||
|
Integer appId = (Integer) params.get("appId");
|
||||||
|
|
||||||
|
AppInviteToken inviteToken = appInviteTokenMapper.selectByToken(token);
|
||||||
|
if (inviteToken == null) {
|
||||||
|
return fail("邀请不存在或已失效");
|
||||||
|
}
|
||||||
|
if (appId != null && !inviteToken.getAppId().equals(appId)) {
|
||||||
|
return fail("邀请与应用不匹配");
|
||||||
|
}
|
||||||
|
if (inviteToken.getExpireTime().isBefore(LocalDateTime.now())) {
|
||||||
|
return fail("邀请已过期");
|
||||||
|
}
|
||||||
|
if (inviteToken.getStatus() != 0) {
|
||||||
|
return fail("邀请已被使用");
|
||||||
|
}
|
||||||
|
|
||||||
|
AppProduct app = appProductMapper.selectById(inviteToken.getAppId());
|
||||||
|
if (app == null) {
|
||||||
|
return fail("应用不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
AppUserCache inviter = appUserCacheService.getByUserId(inviteToken.getInviterId());
|
||||||
|
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("appId", inviteToken.getAppId());
|
||||||
|
result.put("appName", app.getProductName());
|
||||||
|
result.put("appIcon", app.getIcon());
|
||||||
|
result.put("inviterName", inviter != null ? inviter.getNickname() : "未知");
|
||||||
|
result.put("inviterAvatar", inviter != null ? inviter.getAvatar() : null);
|
||||||
|
result.put("role", inviteToken.getRole());
|
||||||
|
result.put("expireTime", inviteToken.getExpireTime().toString());
|
||||||
|
return success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("验证邀请失败", e);
|
||||||
|
return fail("验证邀请失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "接受邀请(小程序专用,支持已登录和未注册两种模式)")
|
||||||
|
@PostMapping("/accept")
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public ApiResult<?> acceptInvite(@RequestBody Map<String, Object> params) {
|
||||||
|
String token = (String) params.get("token");
|
||||||
|
String code = (String) params.get("code");
|
||||||
|
|
||||||
|
if (StrUtil.isBlank(token)) {
|
||||||
|
return fail("邀请 token 不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 验证邀请 token
|
||||||
|
AppInviteToken inviteToken = appInviteTokenMapper.selectByToken(token);
|
||||||
|
if (inviteToken == null) {
|
||||||
|
return fail("邀请不存在或已失效");
|
||||||
|
}
|
||||||
|
if (inviteToken.getExpireTime().isBefore(LocalDateTime.now())) {
|
||||||
|
return fail("邀请已过期");
|
||||||
|
}
|
||||||
|
if (inviteToken.getStatus() != 0) {
|
||||||
|
return fail("邀请已被使用");
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer userId;
|
||||||
|
|
||||||
|
// 2. 判断是已登录用户还是未注册用户
|
||||||
|
if (StrUtil.isBlank(code)) {
|
||||||
|
// ===== 模式一:已登录用户(通过 Authorization 头识别)=====
|
||||||
|
log.info("接受邀请 - 已登录用户模式(通过token识别)");
|
||||||
|
userId = getLoginUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
return fail("用户未登录,请先登录");
|
||||||
|
}
|
||||||
|
log.info("已登录用户加入应用: userId={}", userId);
|
||||||
|
} else {
|
||||||
|
// ===== 模式二:未注册用户(通过微信授权码获取手机号)=====
|
||||||
|
log.info("接受邀请 - 未注册用户模式(通过手机号授权码)");
|
||||||
|
|
||||||
|
// 2.1 通过微信手机号 code 获取手机号
|
||||||
|
String phone = getPhoneByCode(code);
|
||||||
|
if (StrUtil.isBlank(phone)) {
|
||||||
|
return fail("获取手机号失败,请重试");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.2 查询用户,不存在则创建
|
||||||
|
userId = getOrCreateUserByPhone(phone);
|
||||||
|
if (userId == null) {
|
||||||
|
return fail("用户创建失败");
|
||||||
|
}
|
||||||
|
log.info("未注册用户加入应用: phone={}, userId={}", phone, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 检查是否已经是成员
|
||||||
|
AppUser existUser = appUserMapper.selectOne(
|
||||||
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<AppUser>()
|
||||||
|
.eq(AppUser::getAppId, inviteToken.getAppId().longValue())
|
||||||
|
.eq(AppUser::getUserId, userId)
|
||||||
|
);
|
||||||
|
if (existUser != null && existUser.getInviteStatus() == 0) {
|
||||||
|
return fail("你已经是该应用的成员");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 添加为应用成员(直接加入,不走待确认流程)
|
||||||
|
AppUser appUser = new AppUser();
|
||||||
|
appUser.setAppId(inviteToken.getAppId().longValue());
|
||||||
|
appUser.setUserId(userId);
|
||||||
|
appUser.setRole(inviteToken.getRole() != null ? inviteToken.getRole() : "developer");
|
||||||
|
appUser.setInviteBy(inviteToken.getInviterId().longValue());
|
||||||
|
appUser.setInviteTime(LocalDateTime.now());
|
||||||
|
appUser.setStatus(0);
|
||||||
|
appUser.setInviteStatus(0); // 直接确认
|
||||||
|
appUser.setSortNumber(0);
|
||||||
|
|
||||||
|
// 从 AppProduct 获取 tenantId
|
||||||
|
AppProduct app = appProductMapper.selectById(inviteToken.getAppId());
|
||||||
|
appUser.setTenantId(app != null ? app.getTenantId() : null);
|
||||||
|
|
||||||
|
// 补充用户信息
|
||||||
|
AppUserCache userCache = appUserCacheService.getByUserId(userId);
|
||||||
|
if (userCache != null) {
|
||||||
|
appUser.setUsername(userCache.getUsername());
|
||||||
|
appUser.setNickname(userCache.getNickname());
|
||||||
|
appUser.setAvatar(userCache.getAvatar());
|
||||||
|
appUser.setPhone(userCache.getPhone());
|
||||||
|
}
|
||||||
|
|
||||||
|
appUserMapper.insert(appUser);
|
||||||
|
|
||||||
|
// 6. 标记邀请已使用
|
||||||
|
inviteToken.setStatus(1);
|
||||||
|
appInviteTokenMapper.updateById(inviteToken);
|
||||||
|
|
||||||
|
log.info("小程序扫码加入应用成功: token={}, userId={}, appId={}", token, userId, inviteToken.getAppId());
|
||||||
|
|
||||||
|
// 7. 返回应用信息(复用上面查询的 app 对象)
|
||||||
|
String appName = (app != null) ? app.getProductName() : "应用";
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("success", true);
|
||||||
|
result.put("message", "加入成功");
|
||||||
|
result.put("appName", appName);
|
||||||
|
return success(result);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("接受邀请失败", e);
|
||||||
|
return fail("加入失败,请重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 token 获取邀请信息
|
||||||
|
*/
|
||||||
|
private Map<String, Object> getInviteInfoByToken(String token) throws Exception {
|
||||||
|
AppInviteToken inviteToken = appInviteTokenMapper.selectByToken(token);
|
||||||
|
if (inviteToken == null) {
|
||||||
|
throw new Exception("邀请不存在或已失效");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inviteToken.getExpireTime().isBefore(LocalDateTime.now())) {
|
||||||
|
throw new Exception("邀请已过期");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inviteToken.getStatus() != 0) {
|
||||||
|
throw new Exception("邀请已被使用");
|
||||||
|
}
|
||||||
|
|
||||||
|
AppProduct app = appProductMapper.selectById(inviteToken.getAppId());
|
||||||
|
if (app == null) {
|
||||||
|
throw new Exception("应用不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
AppUserCache inviter = appUserCacheService.getByUserId(inviteToken.getInviterId());
|
||||||
|
|
||||||
|
String roleName = getRoleName(inviteToken.getRole());
|
||||||
|
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("token", token);
|
||||||
|
result.put("appId", inviteToken.getAppId());
|
||||||
|
result.put("appName", app.getProductName());
|
||||||
|
result.put("appLogo", app.getIcon());
|
||||||
|
result.put("inviterName", inviter != null ? inviter.getNickname() : "某位用户");
|
||||||
|
result.put("roleName", roleName);
|
||||||
|
result.put("role", inviteToken.getRole());
|
||||||
|
result.put("expireTime", inviteToken.getExpireTime().toString());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取角色名称
|
||||||
|
*/
|
||||||
|
private String getRoleName(String role) {
|
||||||
|
if (StrUtil.isBlank(role)) return "成员";
|
||||||
|
switch (role) {
|
||||||
|
case "owner": return "所有者";
|
||||||
|
case "admin": return "管理员";
|
||||||
|
case "developer": return "开发者";
|
||||||
|
default: return "成员";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过微信手机号 code 获取手机号
|
||||||
|
*/
|
||||||
|
private String getPhoneByCode(String code) {
|
||||||
|
try {
|
||||||
|
// 获取 access_token(从小程序配置读取)
|
||||||
|
String accessToken = getMiniprogramAccessToken();
|
||||||
|
if (StrUtil.isBlank(accessToken)) {
|
||||||
|
log.warn("获取小程序access_token失败");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String apiUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + accessToken;
|
||||||
|
Map<String, Object> paramMap = new HashMap<>();
|
||||||
|
paramMap.put("code", code);
|
||||||
|
|
||||||
|
String response = HttpUtil.post(apiUrl, JSON.toJSONString(paramMap));
|
||||||
|
JSONObject json = JSON.parseObject(response);
|
||||||
|
|
||||||
|
if (json.containsKey("errcode") && json.getInteger("errcode") != 0) {
|
||||||
|
log.error("微信获取手机号失败: {}", json.getString("errmsg"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析手机号
|
||||||
|
if (json.containsKey("phone_info")) {
|
||||||
|
JSONObject phoneInfo = json.getJSONObject("phone_info");
|
||||||
|
return phoneInfo.getString("phoneNumber");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取手机号异常", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取小程序 AccessToken
|
||||||
|
*/
|
||||||
|
private String getMiniprogramAccessToken() {
|
||||||
|
try {
|
||||||
|
// 使用 WxMiniprogramUtil 获取 access_token
|
||||||
|
String accessToken = com.gxwebsoft.common.core.utils.WxMiniprogramUtil.getAccessToken();
|
||||||
|
if (StrUtil.isBlank(accessToken)) {
|
||||||
|
log.warn("通过 WxMiniprogramUtil 获取小程序 access_token 失败");
|
||||||
|
}
|
||||||
|
return accessToken;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取AccessToken失败", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用微信 API 获取 AccessToken
|
||||||
|
*/
|
||||||
|
private String fetchAccessToken(String appId, String appSecret) {
|
||||||
|
String apiUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="
|
||||||
|
+ appId + "&secret=" + appSecret;
|
||||||
|
String response = HttpUtil.get(apiUrl);
|
||||||
|
JSONObject json = JSON.parseObject(response);
|
||||||
|
return json.getString("access_token");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 Spring Bean
|
||||||
|
*/
|
||||||
|
private Object getSpringBean(Class<?> clazz) {
|
||||||
|
try {
|
||||||
|
Class<?> springContextUtilClass = Class.forName("com.gxwebsoft.common.core.config.SpringContextUtil");
|
||||||
|
Object bean = springContextUtilClass.getMethod("getBean", Class.class)
|
||||||
|
.invoke(null, clazz);
|
||||||
|
return bean;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("获取Spring Bean失败: {}", e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过手机号查询或创建用户
|
||||||
|
* 优先从 app_user_cache 查询,不存在则通过 HTTP 调用主服务器 API 查询/创建(跨库到 gxwebsoft_core.sys_user)
|
||||||
|
*/
|
||||||
|
private Integer getOrCreateUserByPhone(String phone) {
|
||||||
|
try {
|
||||||
|
// 1. 优先从 app_user_cache 查询用户
|
||||||
|
AppUserCache userCache = appUserCacheMapper.selectOne(
|
||||||
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<AppUserCache>()
|
||||||
|
.eq(AppUserCache::getPhone, phone)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (userCache != null) {
|
||||||
|
log.info("从 app_user_cache 找到用户: phone={}, userId={}", phone, userCache.getUserId());
|
||||||
|
return userCache.getUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("app_user_cache 中未找到用户,通过 AppUserCacheService 刷新缓存: phone={}", phone);
|
||||||
|
|
||||||
|
// 2. 通过 AppUserCacheService 从主服务器刷新缓存
|
||||||
|
// AppUserCacheService 会调用 server API 获取用户信息并保存到本地缓存
|
||||||
|
AppUserCache refreshedCache = appUserCacheService.refreshUserCacheByPhone(phone);
|
||||||
|
|
||||||
|
if (refreshedCache != null) {
|
||||||
|
log.info("从主服务器刷新缓存成功: phone={}, userId={}", phone, refreshedCache.getUserId());
|
||||||
|
return refreshedCache.getUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("主服务器中未找到用户,需要创建新用户: phone={}", phone);
|
||||||
|
|
||||||
|
// 3. 用户不存在,通过 HTTP 调用主服务器 API 创建新用户
|
||||||
|
log.info("主服务器中未找到用户,创建新用户: phone={}", phone);
|
||||||
|
|
||||||
|
String createPath = "/system/user/";
|
||||||
|
Map<String, Object> userMap = new HashMap<>();
|
||||||
|
userMap.put("phone", phone);
|
||||||
|
userMap.put("username", "wx_" + phone);
|
||||||
|
userMap.put("nickname", "微信用户");
|
||||||
|
userMap.put("status", 0);
|
||||||
|
userMap.put("platform", "MP-WEIXIN");
|
||||||
|
userMap.put("tenantId", 1); // 默认租户
|
||||||
|
|
||||||
|
// 3. 用户不存在,调用 AppUserCacheService 创建新用户
|
||||||
|
log.info("调用 AppUserCacheService 创建新用户: phone={}", phone);
|
||||||
|
|
||||||
|
// 使用 AppUserCacheService 创建用户(内部会调用 server API)
|
||||||
|
AppUserCache newUserCache = appUserCacheService.createUserByPhone(phone);
|
||||||
|
|
||||||
|
if (newUserCache != null && newUserCache.getUserId() != null) {
|
||||||
|
log.info("新用户创建成功: phone={}, userId={}", phone, newUserCache.getUserId());
|
||||||
|
return newUserCache.getUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
log.error("保存用户失败: phone={}", phone);
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("创建用户失败: phone={}", phone, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步用户到缓存表
|
||||||
|
*/
|
||||||
|
private void syncUserToCache(Integer userId) {
|
||||||
|
try {
|
||||||
|
if (userId == null) return;
|
||||||
|
// 简单实现:更新缓存
|
||||||
|
Object userCacheService = getSpringBean(Class.forName("com.gxwebsoft.app.service.AppUserCacheService"));
|
||||||
|
if (userCacheService != null) {
|
||||||
|
userCacheService.getClass().getMethod("refreshUserCache", Integer.class).invoke(userCacheService, userId);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("同步用户缓存失败: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.app.entity.AppNotification;
|
||||||
|
import com.gxwebsoft.app.param.AppNotificationParam;
|
||||||
|
import com.gxwebsoft.app.service.AppNotificationService;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import com.gxwebsoft.common.core.web.PageResult;
|
||||||
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 站内消息通知控制器
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-03
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "站内消息通知")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/notification")
|
||||||
|
public class AppNotificationController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppNotificationService appNotificationService;
|
||||||
|
|
||||||
|
// ─── 查询接口 ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@Operation(summary = "分页查询通知列表")
|
||||||
|
@GetMapping("/page")
|
||||||
|
public ApiResult<PageResult<AppNotification>> page(AppNotificationParam param) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录", null);
|
||||||
|
return success(appNotificationService.pageByUser(param, loginUser.getUserId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询最近通知(铃铛下拉)")
|
||||||
|
@GetMapping("/recent")
|
||||||
|
public ApiResult<List<AppNotification>> recent(
|
||||||
|
@RequestParam(required = false) String type,
|
||||||
|
@RequestParam(required = false, defaultValue = "20") Integer limit) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录", null);
|
||||||
|
return success(appNotificationService.listRecent(loginUser.getUserId(), type, limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取未读数量统计")
|
||||||
|
@GetMapping("/unread-count")
|
||||||
|
public ApiResult<Map<String, Object>> unreadCount() {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录", null);
|
||||||
|
return success(appNotificationService.getUnreadCount(loginUser.getUserId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 操作接口 ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@Operation(summary = "标记单条通知为已读")
|
||||||
|
@PutMapping("/read/{id}")
|
||||||
|
public ApiResult<?> markRead(@PathVariable Long id) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录");
|
||||||
|
appNotificationService.markRead(id, loginUser.getUserId());
|
||||||
|
return success("已标记为已读");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "标记全部已读")
|
||||||
|
@PutMapping("/read-all")
|
||||||
|
public ApiResult<?> markAllRead(@RequestBody(required = false) Map<String, String> body) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录");
|
||||||
|
String type = (body != null) ? body.get("type") : null;
|
||||||
|
appNotificationService.markAllRead(loginUser.getUserId(), type);
|
||||||
|
return success("已全部标记为已读");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "删除单条通知")
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResult<?> remove(@PathVariable Long id) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录");
|
||||||
|
appNotificationService.removeNotification(id, loginUser.getUserId());
|
||||||
|
return success("已删除");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "清空已读通知")
|
||||||
|
@DeleteMapping("/clear-read")
|
||||||
|
public ApiResult<?> clearRead(@RequestBody(required = false) Map<String, String> body) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录");
|
||||||
|
String type = (body != null) ? body.get("type") : null;
|
||||||
|
appNotificationService.clearRead(loginUser.getUserId(), type);
|
||||||
|
return success("已清空已读通知");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.app.entity.AppPermissionRequest;
|
||||||
|
import com.gxwebsoft.app.param.AppPermissionRequestParam;
|
||||||
|
import com.gxwebsoft.app.service.AppPermissionRequestService;
|
||||||
|
import com.gxwebsoft.common.core.annotation.OperationLog;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import com.gxwebsoft.common.core.web.PageResult;
|
||||||
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限申请控制器
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-03
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "权限申请")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/developer/permission-requests")
|
||||||
|
public class AppPermissionRequestController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppPermissionRequestService appPermissionRequestService;
|
||||||
|
|
||||||
|
@Operation(summary = "获取权限申请列表")
|
||||||
|
@GetMapping("/page")
|
||||||
|
public ApiResult<PageResult<AppPermissionRequest>> page(AppPermissionRequestParam param) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser != null) {
|
||||||
|
param.setUserId(loginUser.getUserId());
|
||||||
|
}
|
||||||
|
return success(appPermissionRequestService.pageRel(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询全部权限申请")
|
||||||
|
@GetMapping
|
||||||
|
public ApiResult<List<AppPermissionRequest>> list(AppPermissionRequestParam param) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser != null) {
|
||||||
|
param.setUserId(loginUser.getUserId());
|
||||||
|
}
|
||||||
|
return success(appPermissionRequestService.listRel(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "根据id查询权限申请")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResult<AppPermissionRequest> get(@PathVariable("id") Long id) {
|
||||||
|
return success(appPermissionRequestService.getByIdRel(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取权限申请统计")
|
||||||
|
@GetMapping("/stats")
|
||||||
|
public ApiResult<Map<String, Object>> stats() {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录", null);
|
||||||
|
}
|
||||||
|
return success(appPermissionRequestService.getPermissionRequestStats(loginUser.getUserId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取可申请的仓库列表")
|
||||||
|
@GetMapping("/available-repos")
|
||||||
|
public ApiResult<List<Map<String, Object>>> availableRepos() {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录", null);
|
||||||
|
}
|
||||||
|
return success(appPermissionRequestService.getAvailableRepositories(loginUser.getUserId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "提交权限申请")
|
||||||
|
@PostMapping
|
||||||
|
public ApiResult<AppPermissionRequest> save(@RequestBody Map<String, String> params) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
String repo = params.get("repo");
|
||||||
|
String reason = params.get("reason");
|
||||||
|
String gitUsername = params.get("gitUsername");
|
||||||
|
|
||||||
|
if (repo == null || repo.trim().isEmpty()) {
|
||||||
|
return fail("请选择申请仓库", null);
|
||||||
|
}
|
||||||
|
if (reason == null || reason.trim().isEmpty()) {
|
||||||
|
return fail("请填写申请理由", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 从 repo 中提取仓库名称(假设格式为 owner/repo-name)
|
||||||
|
String repoName = repo;
|
||||||
|
if (repo.contains("/")) {
|
||||||
|
String[] parts = repo.split("/");
|
||||||
|
repoName = parts[parts.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
AppPermissionRequest request = appPermissionRequestService.createPermissionRequest(
|
||||||
|
loginUser.getUserId(), gitUsername, repo, repoName, reason.trim(), loginUser.getTenantId()
|
||||||
|
);
|
||||||
|
return success("申请提交成功,请等待审核", request);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return fail(e.getMessage(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "审核权限申请-通过")
|
||||||
|
@PutMapping("/{id}/approve")
|
||||||
|
public ApiResult<?> approve(@PathVariable("id") Long id, @RequestBody(required = false) Map<String, String> params) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String note = params != null ? params.get("note") : null;
|
||||||
|
boolean result = appPermissionRequestService.approveRequest(id, loginUser.getUserId(), loginUser.getRealName(), note);
|
||||||
|
return result ? success("审核通过") : fail("审核失败");
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "审核权限申请-拒绝")
|
||||||
|
@PutMapping("/{id}/reject")
|
||||||
|
public ApiResult<?> reject(@PathVariable("id") Long id, @RequestBody Map<String, String> params) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
String reason = params != null ? params.get("reason") : null;
|
||||||
|
if (reason == null || reason.trim().isEmpty()) {
|
||||||
|
return fail("请填写拒绝原因");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean result = appPermissionRequestService.rejectRequest(id, loginUser.getUserId(), loginUser.getRealName(), reason.trim());
|
||||||
|
return result ? success("已拒绝该申请") : fail("拒绝失败");
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.gxwebsoft.app.entity.AppProduct;
|
||||||
|
import com.gxwebsoft.app.param.AppProductParam;
|
||||||
|
import com.gxwebsoft.app.service.AppProductService;
|
||||||
|
import com.gxwebsoft.app.service.AppUserService;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import com.gxwebsoft.common.core.web.PageResult;
|
||||||
|
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 javax.validation.Valid;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用产品 前端控制器
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/product")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Tag(name = "应用产品管理", description = "应用产品管理接口")
|
||||||
|
public class AppProductController extends BaseController {
|
||||||
|
|
||||||
|
private final AppProductService appProductService;
|
||||||
|
private final AppUserService appUserService;
|
||||||
|
|
||||||
|
@GetMapping("/page")
|
||||||
|
@Operation(summary = "分页查询应用列表")
|
||||||
|
public ApiResult<PageResult<AppProduct>> page(
|
||||||
|
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Long current,
|
||||||
|
@Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long size,
|
||||||
|
@Parameter(description = "应用名称") @RequestParam(required = false) String productName,
|
||||||
|
@Parameter(description = "应用标识") @RequestParam(required = false) String productCode,
|
||||||
|
@Parameter(description = "应用类型") @RequestParam(required = false) Integer appType,
|
||||||
|
@Parameter(description = "分类ID") @RequestParam(required = false) Integer categoryId,
|
||||||
|
@Parameter(description = "发布状态") @RequestParam(required = false) String publishStatus,
|
||||||
|
@Parameter(description = "状态") @RequestParam(required = false) Integer status,
|
||||||
|
@Parameter(description = "用户ID(可选,不传则自动用当前登录用户)") @RequestParam(required = false) Integer userId) {
|
||||||
|
|
||||||
|
// 如果没传 userId,自动用当前登录用户
|
||||||
|
if (userId == null) {
|
||||||
|
userId = getLoginUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
AppProduct product = new AppProduct();
|
||||||
|
product.setProductName(productName);
|
||||||
|
product.setProductCode(productCode);
|
||||||
|
product.setAppType(appType);
|
||||||
|
product.setCategoryId(categoryId);
|
||||||
|
product.setPublishStatus(publishStatus);
|
||||||
|
product.setStatus(status);
|
||||||
|
|
||||||
|
IPage<AppProduct> page = appProductService.selectPageList(new Page<>(current, size), product, userId);
|
||||||
|
return success(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/list")
|
||||||
|
@Operation(summary = "获取我的应用列表")
|
||||||
|
public ApiResult<List<AppProduct>> list() {
|
||||||
|
Integer userId = getLoginUserId();
|
||||||
|
return success(appProductService.getByUserId(userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/my/page")
|
||||||
|
@Operation(summary = "分页查询我的应用(创建者)")
|
||||||
|
public ApiResult<PageResult<AppProduct>> myApps(
|
||||||
|
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Long current,
|
||||||
|
@Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long size) {
|
||||||
|
Integer userId = getLoginUserId();
|
||||||
|
Page<AppProduct> page = new Page<>(current, size);
|
||||||
|
return success(appProductService.getMyApps(page, userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/joined/page")
|
||||||
|
@Operation(summary = "分页查询我参与的应用")
|
||||||
|
public ApiResult<PageResult<AppProduct>> joinedApps(
|
||||||
|
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Long current,
|
||||||
|
@Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long size) {
|
||||||
|
Integer userId = getLoginUserId();
|
||||||
|
Page<AppProduct> page = new Page<>(current, size);
|
||||||
|
return success(appProductService.getJoinedApps(page, userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/detail/{id}")
|
||||||
|
@Operation(summary = "获取应用详情")
|
||||||
|
public ApiResult<AppProduct> detail(
|
||||||
|
@Parameter(description = "应用ID") @PathVariable Integer id) {
|
||||||
|
// 记录用户访问(更新最近访问时间)
|
||||||
|
Integer userId = getLoginUserId();
|
||||||
|
if (userId != null) {
|
||||||
|
appUserService.recordVisit(id.longValue(), userId);
|
||||||
|
}
|
||||||
|
return success(appProductService.getDetail(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/visit/{id}")
|
||||||
|
@Operation(summary = "记录应用访问")
|
||||||
|
public ApiResult<?> visit(
|
||||||
|
@Parameter(description = "应用ID") @PathVariable Integer id) {
|
||||||
|
Integer userId = getLoginUserId();
|
||||||
|
if (userId != null) {
|
||||||
|
appUserService.recordVisit(id.longValue(), userId);
|
||||||
|
}
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/info/{code}")
|
||||||
|
@Operation(summary = "根据标识获取应用")
|
||||||
|
public ApiResult<AppProduct> info(
|
||||||
|
@Parameter(description = "应用标识") @PathVariable String code) {
|
||||||
|
return success(appProductService.getByCode(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/create")
|
||||||
|
@Operation(summary = "创建应用")
|
||||||
|
public ApiResult<?> create(@Valid @RequestBody AppProduct product) {
|
||||||
|
boolean success = appProductService.create(product);
|
||||||
|
return success ? success("创建成功") : fail("创建失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/update")
|
||||||
|
@Operation(summary = "更新应用")
|
||||||
|
public ApiResult<?> update(@Valid @RequestBody AppProduct product) {
|
||||||
|
boolean success = appProductService.update(product);
|
||||||
|
return success ? success("更新成功") : fail("更新失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/delete/{id}")
|
||||||
|
@Operation(summary = "删除应用")
|
||||||
|
public ApiResult<?> delete(
|
||||||
|
@Parameter(description = "应用ID") @PathVariable Integer id) {
|
||||||
|
boolean success = appProductService.delete(id);
|
||||||
|
return success ? success("删除成功") : fail("删除失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/submit/{id}")
|
||||||
|
@Operation(summary = "提交审核")
|
||||||
|
public ApiResult<?> submit(
|
||||||
|
@Parameter(description = "应用ID") @PathVariable Integer id) {
|
||||||
|
boolean success = appProductService.submitReview(id);
|
||||||
|
return success ? success("提交成功") : fail("提交失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/approve/{id}")
|
||||||
|
@Operation(summary = "审核通过")
|
||||||
|
public ApiResult<?> approve(
|
||||||
|
@Parameter(description = "应用ID") @PathVariable Integer id) {
|
||||||
|
boolean success = appProductService.approve(id);
|
||||||
|
return success ? success("审核通过") : fail("审核失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/reject/{id}")
|
||||||
|
@Operation(summary = "审核拒绝")
|
||||||
|
public ApiResult<?> reject(
|
||||||
|
@Parameter(description = "应用ID") @PathVariable Integer id,
|
||||||
|
@RequestParam String reason) {
|
||||||
|
boolean success = appProductService.reject(id, reason);
|
||||||
|
return success ? success("操作成功") : fail("操作失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/publish/{id}")
|
||||||
|
@Operation(summary = "上架应用")
|
||||||
|
public ApiResult<?> publish(
|
||||||
|
@Parameter(description = "应用ID") @PathVariable Integer id) {
|
||||||
|
boolean success = appProductService.publish(id);
|
||||||
|
return success ? success("上架成功") : fail("上架失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/unpublish/{id}")
|
||||||
|
@Operation(summary = "下架应用")
|
||||||
|
public ApiResult<?> unpublish(
|
||||||
|
@Parameter(description = "应用ID") @PathVariable Integer id) {
|
||||||
|
boolean success = appProductService.unpublish(id);
|
||||||
|
return success ? success("下架成功") : fail("下架失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/regenerateSecret/{id}")
|
||||||
|
@Operation(summary = "重新生成密钥")
|
||||||
|
public ApiResult<String> regenerateSecret(
|
||||||
|
@Parameter(description = "应用ID") @PathVariable Integer id) {
|
||||||
|
String newSecret = appProductService.regenerateSecret(id);
|
||||||
|
return success(newSecret,null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/market/page")
|
||||||
|
@Operation(summary = "应用市场分页")
|
||||||
|
public ApiResult<PageResult<AppProduct>> marketPage(
|
||||||
|
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Long current,
|
||||||
|
@Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Long size,
|
||||||
|
@Parameter(description = "应用类型") @RequestParam(required = false) Integer appType,
|
||||||
|
@Parameter(description = "关键词") @RequestParam(required = false) String keyword) {
|
||||||
|
|
||||||
|
IPage<AppProduct> page = appProductService.getMarketList(new Page<>(current, size), appType, keyword);
|
||||||
|
return success(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/view/{id}")
|
||||||
|
@Operation(summary = "浏览应用")
|
||||||
|
public ApiResult<?> view(
|
||||||
|
@Parameter(description = "应用ID") @PathVariable Integer id) {
|
||||||
|
appProductService.incrementClicks(id);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/install/{id}")
|
||||||
|
@Operation(summary = "安装应用")
|
||||||
|
public ApiResult<?> install(
|
||||||
|
@Parameter(description = "应用ID") @PathVariable Integer id) {
|
||||||
|
appProductService.incrementInstalls(id);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/download/{id}")
|
||||||
|
@Operation(summary = "下载应用")
|
||||||
|
public ApiResult<?> download(
|
||||||
|
@Parameter(description = "应用ID") @PathVariable Integer id) {
|
||||||
|
appProductService.incrementDownloads(id);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/like/{id}")
|
||||||
|
@Operation(summary = "点赞应用")
|
||||||
|
public ApiResult<?> like(
|
||||||
|
@Parameter(description = "应用ID") @PathVariable Integer id) {
|
||||||
|
appProductService.incrementLikes(id);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/user-stats")
|
||||||
|
@Operation(summary = "按用户ID列表批量统计应用数量")
|
||||||
|
public ApiResult<List<Map<String, Object>>> userStats(
|
||||||
|
@RequestBody List<Integer> userIds) {
|
||||||
|
return success(appProductService.getStatsByUserIds(userIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户可访问的应用列表(带角色信息)
|
||||||
|
* 用于权限过滤:返回用户创建的应用(owner)和被邀请加入的应用(成员角色)
|
||||||
|
* 每个应用包含 myRole 字段,标识用户在该应用中的角色
|
||||||
|
*/
|
||||||
|
@GetMapping("/accessible")
|
||||||
|
@Operation(summary = "获取可访问的应用列表(带角色信息)")
|
||||||
|
public ApiResult<List<AppProduct>> accessibleApps() {
|
||||||
|
Integer userId = getLoginUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
return success(List.of());
|
||||||
|
}
|
||||||
|
return success(appProductService.getAccessibleApps(userId));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,368 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.app.entity.AppResource;
|
||||||
|
import com.gxwebsoft.app.entity.ResourceAccessLevel;
|
||||||
|
import com.gxwebsoft.app.param.AppResourceParam;
|
||||||
|
import com.gxwebsoft.app.service.AppResourceService;
|
||||||
|
import com.gxwebsoft.app.service.AppCloudCredentialService;
|
||||||
|
import com.gxwebsoft.app.service.DatabaseOperatorFactory;
|
||||||
|
import com.gxwebsoft.app.service.DatabaseOperatorService;
|
||||||
|
import com.gxwebsoft.app.service.OnePanelService;
|
||||||
|
import com.gxwebsoft.app.service.SshService;
|
||||||
|
import com.gxwebsoft.app.service.cloud.CloudStorageProvider;
|
||||||
|
import com.gxwebsoft.app.service.cloud.CloudStorageProviderFactory;
|
||||||
|
import com.gxwebsoft.app.service.impl.AppResourceServiceImpl;
|
||||||
|
import com.gxwebsoft.common.core.annotation.OperationLog;
|
||||||
|
import com.gxwebsoft.common.core.utils.DbPasswordUtil;
|
||||||
|
import com.gxwebsoft.common.core.utils.RedisUtil;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import com.gxwebsoft.common.core.web.PageResult;
|
||||||
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开发者资源管理控制器(服务器/数据库/云存储/域名/SSL)
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-03-31
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "开发者资源管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/developer-resource")
|
||||||
|
public class AppResourceController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppResourceService appResourceService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppCloudCredentialService cloudCredentialService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private DatabaseOperatorFactory databaseOperatorFactory;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SshService sshService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private OnePanelService onePanelService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisUtil redisUtil;
|
||||||
|
|
||||||
|
// ─── 查询接口 ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@Operation(summary = "分页查询资源列表(支持协作权限)")
|
||||||
|
@GetMapping("/page")
|
||||||
|
public ApiResult<PageResult<AppResource>> page(AppResourceParam param) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录", null);
|
||||||
|
// **协作权限改进**:不再限制只能查自己的资源,改为查询用户有权访问的资源
|
||||||
|
// 通过修改SQL,查询条件改为:owner_user_id = 当前用户 OR app_id IN (用户有权限的应用)
|
||||||
|
param.setUserId(loginUser.getUserId());
|
||||||
|
param.setTenantId(loginUser.getTenantId());
|
||||||
|
// 调用新方法,传入当前用户ID以便计算 accessLevel 和屏蔽敏感字段
|
||||||
|
return success(appResourceService.pageRel(param, loginUser.getUserId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询资源列表(不分页,支持协作权限)")
|
||||||
|
@GetMapping
|
||||||
|
public ApiResult<List<AppResource>> list(AppResourceParam param) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录", null);
|
||||||
|
param.setUserId(loginUser.getUserId());
|
||||||
|
param.setTenantId(loginUser.getTenantId());
|
||||||
|
// 调用新方法,传入当前用户ID
|
||||||
|
return success(appResourceService.listRel(param, loginUser.getUserId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取资源详情(支持协作权限)")
|
||||||
|
@GetMapping("/{resourceId}")
|
||||||
|
public ApiResult<AppResource> get(@PathVariable Long resourceId) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录", null);
|
||||||
|
AppResource resource = appResourceService.getByIdRel(resourceId, loginUser.getUserId());
|
||||||
|
if (resource == null) return fail("资源不存在", null);
|
||||||
|
// **协作权限改进**:不再直接禁止访问,由 Service 计算 accessLevel,0 表示无权限
|
||||||
|
if (resource.getAccessLevel() == null || resource.getAccessLevel() == 0) {
|
||||||
|
return fail("无权访问此资源", null);
|
||||||
|
}
|
||||||
|
return success(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "统计各类型资源数量")
|
||||||
|
@GetMapping("/stats")
|
||||||
|
public ApiResult<Map<String, Long>> stats() {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录", null);
|
||||||
|
return success(appResourceService.countByType(loginUser.getUserId(), loginUser.getTenantId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 新增/修改接口 ────────────────────────────────────────────
|
||||||
|
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "新增资源")
|
||||||
|
@PostMapping
|
||||||
|
public ApiResult<AppResource> save(@RequestBody AppResource resource) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录", null);
|
||||||
|
if (resource.getResourceType() == null || resource.getResourceType().isEmpty()) {
|
||||||
|
return fail("资源类型不能为空", null);
|
||||||
|
}
|
||||||
|
if (resource.getName() == null || resource.getName().isEmpty()) {
|
||||||
|
return fail("资源名称不能为空", null);
|
||||||
|
}
|
||||||
|
resource.setTenantId(loginUser.getTenantId());
|
||||||
|
try {
|
||||||
|
AppResource result = appResourceService.addResource(resource, loginUser.getUserId());
|
||||||
|
return success("添加成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "修改资源(只有 Owner 可操作)")
|
||||||
|
@PutMapping
|
||||||
|
public ApiResult<AppResource> update(@RequestBody AppResource resource) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录", null);
|
||||||
|
try {
|
||||||
|
AppResource result = appResourceService.updateResource(resource, loginUser.getUserId());
|
||||||
|
return success("修改成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 删除接口 ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "删除资源(逻辑删除,需要短信验证码)")
|
||||||
|
@DeleteMapping("/{resourceId}")
|
||||||
|
public ApiResult<?> remove(@PathVariable Long resourceId, @RequestBody Map<String, String> body) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录");
|
||||||
|
|
||||||
|
// 验证短信验证码
|
||||||
|
String code = body.get("code");
|
||||||
|
if (code == null || code.isEmpty()) {
|
||||||
|
return fail("请输入短信验证码");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证验证码(key 为 code:手机号)
|
||||||
|
String phone = loginUser.getPhone();
|
||||||
|
if (phone == null || phone.isEmpty()) {
|
||||||
|
return fail("请先绑定手机号");
|
||||||
|
}
|
||||||
|
|
||||||
|
String cachedCode = redisUtil.get("code:" + phone);
|
||||||
|
if (cachedCode == null || !cachedCode.equals(code)) {
|
||||||
|
return fail("验证码错误或已过期");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证通过,删除验证码
|
||||||
|
redisUtil.delete("code:" + phone);
|
||||||
|
|
||||||
|
try {
|
||||||
|
appResourceService.removeResource(resourceId, loginUser.getUserId());
|
||||||
|
return success("删除成功");
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "批量删除资源")
|
||||||
|
@DeleteMapping("/batch")
|
||||||
|
public ApiResult<?> removeBatch(@RequestBody List<Long> ids) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录");
|
||||||
|
try {
|
||||||
|
for (Long id : ids) {
|
||||||
|
appResourceService.removeResource(id, loginUser.getUserId());
|
||||||
|
}
|
||||||
|
return success("批量删除成功");
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 数据库操作接口 ──────────────────────────────────────────
|
||||||
|
|
||||||
|
@Operation(summary = "测试服务器数据库连接")
|
||||||
|
@PostMapping("/test-connection")
|
||||||
|
public ApiResult<DatabaseOperatorService.DatabaseOperationResult> testConnection(@RequestBody Map<String, Object> params) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录", null);
|
||||||
|
|
||||||
|
String host = (String) params.get("host");
|
||||||
|
String dbType = (String) params.get("dbType");
|
||||||
|
Integer port = params.get("port") != null ? ((Number) params.get("port")).intValue() : null;
|
||||||
|
String username = (String) params.get("username");
|
||||||
|
String password = (String) params.get("password");
|
||||||
|
String dbName = (String) params.get("dbName");
|
||||||
|
|
||||||
|
if (host == null || host.isEmpty()) return fail("服务器地址不能为空", null);
|
||||||
|
if (username == null || username.isEmpty()) return fail("用户名不能为空", null);
|
||||||
|
|
||||||
|
// 根据数据库类型获取默认端口和对应操作服务
|
||||||
|
if (dbType == null || dbType.isEmpty()) {
|
||||||
|
dbType = "MySQL";
|
||||||
|
}
|
||||||
|
if (port == null) {
|
||||||
|
port = "PostgreSQL".equals(dbType) ? 5432 : 3306;
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseOperatorService operator = databaseOperatorFactory.getOperator(dbType);
|
||||||
|
DatabaseOperatorService.DatabaseOperationResult result =
|
||||||
|
operator.testConnection(host, port, username, password, dbName);
|
||||||
|
return success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "测试服务器 SSH 连接")
|
||||||
|
@PostMapping("/test-ssh")
|
||||||
|
public ApiResult<SshService.ConnectionResult> testSshConnection(@RequestBody Map<String, Object> params) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录", null);
|
||||||
|
|
||||||
|
String host = (String) params.get("host");
|
||||||
|
Integer port = params.get("port") != null ? ((Number) params.get("port")).intValue() : 22;
|
||||||
|
String username = (String) params.get("username");
|
||||||
|
String password = (String) params.get("password");
|
||||||
|
|
||||||
|
SshService.ConnectionResult result = sshService.testConnection(host, port, username, password);
|
||||||
|
return success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取1Panel服务器状态")
|
||||||
|
@GetMapping("/server-status/{resourceId}")
|
||||||
|
public ApiResult<OnePanelService.ServerStatus> getServerStatus(@PathVariable Long resourceId) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录", null);
|
||||||
|
|
||||||
|
AppResource resource = appResourceService.getByIdRel(resourceId, loginUser.getUserId());
|
||||||
|
if (resource == null) return fail("资源不存在", null);
|
||||||
|
|
||||||
|
// 必须是服务器类型资源
|
||||||
|
if (!"server".equals(resource.getResourceType())) {
|
||||||
|
return fail("只有服务器资源才能获取状态", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 必须有 panelPort 配置
|
||||||
|
if (resource.getPanelPort() == null) {
|
||||||
|
return fail("该服务器未配置1Panel端口", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 必须有连接权限(owner/admin/developer)才能获取状态
|
||||||
|
int accessLevel = resource.getAccessLevel() != null ? resource.getAccessLevel() : 0;
|
||||||
|
if (accessLevel < ResourceAccessLevel.VIEW_CONNECTION.getValue()) {
|
||||||
|
return fail("没有权限获取服务器状态", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用1Panel API获取状态
|
||||||
|
OnePanelService.ServerStatus status = onePanelService.getServerStatus(
|
||||||
|
resource.getIp(),
|
||||||
|
resource.getPanelPort(),
|
||||||
|
resource.getPanelPath(),
|
||||||
|
resource.getPanelUsername(),
|
||||||
|
resource.getPanelPassword()
|
||||||
|
);
|
||||||
|
|
||||||
|
return success(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "重试创建数据库(只有 Owner 可操作)")
|
||||||
|
@PostMapping("/retry-create-database/{resourceId}")
|
||||||
|
public ApiResult<?> retryCreateDatabase(@PathVariable Long resourceId) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录");
|
||||||
|
|
||||||
|
AppResource resource = appResourceService.getByIdRel(resourceId, loginUser.getUserId());
|
||||||
|
if (resource == null) return fail("资源不存在");
|
||||||
|
if (!loginUser.getUserId().equals(resource.getOwnerUserId())) return fail("只有资源创建者才能操作");
|
||||||
|
if (!"failed".equals(resource.getStatus()) && !"pending".equals(resource.getStatus())) {
|
||||||
|
return fail("只有创建失败或创建中的资源才能重试");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取 ServiceImpl 实例调用异步方法
|
||||||
|
AppResourceServiceImpl serviceImpl = (AppResourceServiceImpl) appResourceService;
|
||||||
|
serviceImpl.asyncCreateDatabase(resourceId);
|
||||||
|
return success("已开始重新创建,请稍后查看状态");
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "重置数据库密码(只有 Owner 可操作)")
|
||||||
|
@PostMapping("/reset-password/{resourceId}")
|
||||||
|
public ApiResult<Map<String, String>> resetPassword(@PathVariable Long resourceId) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录", null);
|
||||||
|
try {
|
||||||
|
String newPassword = appResourceService.resetDatabasePassword(resourceId, loginUser.getUserId());
|
||||||
|
Map<String, String> result = new HashMap<>();
|
||||||
|
result.put("password", newPassword);
|
||||||
|
return success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "刷新存储桶信息(只有 Owner 可操作)")
|
||||||
|
@PostMapping("/refresh-storage/{resourceId}")
|
||||||
|
public ApiResult<Map<String, Object>> refreshStorage(@PathVariable Long resourceId) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录", null);
|
||||||
|
|
||||||
|
AppResource resource = appResourceService.getByIdRel(resourceId, loginUser.getUserId());
|
||||||
|
if (resource == null) return fail("资源不存在",null);
|
||||||
|
if (!"storage".equals(resource.getResourceType())) {
|
||||||
|
return fail("只有存储桶资源才能刷新",null);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取云账号凭证
|
||||||
|
Map<String, String> credentials;
|
||||||
|
if (resource.getCredentialId() != null) {
|
||||||
|
credentials = cloudCredentialService.getCredentialsByCredentialId(resource.getCredentialId());
|
||||||
|
} else {
|
||||||
|
credentials = cloudCredentialService.getCredentials(resource.getProvider(), loginUser.getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (credentials == null || credentials.isEmpty()) {
|
||||||
|
return fail("请先配置云账号凭证",null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用云厂商 API 获取存储桶信息
|
||||||
|
CloudStorageProvider cloudProvider = CloudStorageProviderFactory.getProvider(resource.getProvider());
|
||||||
|
Map<String, Object> bucketInfo = cloudProvider.getBucketInfo(resource, credentials);
|
||||||
|
|
||||||
|
// 更新到数据库
|
||||||
|
AppResource update = new AppResource();
|
||||||
|
update.setResourceId(resourceId);
|
||||||
|
if (bucketInfo.get("usedBytes") != null) {
|
||||||
|
update.setUsedBytes(((Number) bucketInfo.get("usedBytes")).longValue());
|
||||||
|
}
|
||||||
|
if (bucketInfo.get("objectCount") != null) {
|
||||||
|
update.setUsedCount(((Number) bucketInfo.get("objectCount")).intValue());
|
||||||
|
}
|
||||||
|
appResourceService.updateById(update);
|
||||||
|
|
||||||
|
return success(bucketInfo);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("刷新存储桶信息失败: resourceId={}, error={}", resourceId, e.getMessage(), e);
|
||||||
|
return fail("刷新失败: " + e.getMessage(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import com.gxwebsoft.app.entity.AppSetting;
|
||||||
|
import com.gxwebsoft.app.param.AppSettingParam;
|
||||||
|
import com.gxwebsoft.app.service.AppSettingService;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.PageResult;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台设置表 Controller
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "平台设置管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/setting")
|
||||||
|
public class AppSettingController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppSettingService appSettingService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询设置
|
||||||
|
*/
|
||||||
|
@Operation(summary = "分页查询设置")
|
||||||
|
@GetMapping("/page")
|
||||||
|
public ApiResult<PageResult<AppSetting>> page(AppSettingParam param) {
|
||||||
|
return success(new PageResult<>(appSettingService.page(param)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取设置列表
|
||||||
|
*/
|
||||||
|
@Operation(summary = "获取设置列表")
|
||||||
|
@GetMapping()
|
||||||
|
public ApiResult<List<AppSetting>> list(AppSettingParam param) {
|
||||||
|
return success(appSettingService.list(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据分类获取设置
|
||||||
|
*/
|
||||||
|
@Operation(summary = "根据分类获取设置")
|
||||||
|
@GetMapping("/category/{category}")
|
||||||
|
public ApiResult<List<AppSetting>> getByCategory(@PathVariable String category) {
|
||||||
|
return success(appSettingService.getByCategory(category));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取分类设置值(Map格式)
|
||||||
|
*/
|
||||||
|
@Operation(summary = "获取分类设置值")
|
||||||
|
@GetMapping("/category/{category}/values")
|
||||||
|
public ApiResult<Map<String, String>> getCategoryValues(@PathVariable String category) {
|
||||||
|
return success(appSettingService.getCategoryValues(category));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据key获取单个设置值
|
||||||
|
*/
|
||||||
|
@Operation(summary = "根据key获取设置值")
|
||||||
|
@GetMapping("/key/{key}")
|
||||||
|
public ApiResult<String> getValue(@PathVariable String key) {
|
||||||
|
return success("获取成功", appSettingService.getValue(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据key获取完整设置
|
||||||
|
*/
|
||||||
|
@Operation(summary = "根据key获取完整设置")
|
||||||
|
@GetMapping("/info/{key}")
|
||||||
|
public ApiResult<AppSetting> getByKey(@PathVariable String key) {
|
||||||
|
return success(appSettingService.getByKey(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存或更新设置
|
||||||
|
*/
|
||||||
|
@Operation(summary = "保存或更新设置")
|
||||||
|
@PostMapping()
|
||||||
|
public ApiResult<?> save(@RequestBody AppSetting setting) {
|
||||||
|
appSettingService.saveOrUpdateSetting(setting);
|
||||||
|
return success("保存成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新设置
|
||||||
|
*/
|
||||||
|
@Operation(summary = "更新设置")
|
||||||
|
@PutMapping()
|
||||||
|
public ApiResult<?> update(@RequestBody AppSetting setting) {
|
||||||
|
appSettingService.saveOrUpdateSetting(setting);
|
||||||
|
return success("更新成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存分类设置
|
||||||
|
*/
|
||||||
|
@Operation(summary = "批量保存分类设置")
|
||||||
|
@PostMapping("/batch/{category}")
|
||||||
|
public ApiResult<?> batchSave(@PathVariable String category, @RequestBody Map<String, Object> values) {
|
||||||
|
// 处理值类型转换
|
||||||
|
Map<String, Object> valuesMap = new java.util.HashMap<>(values.size());
|
||||||
|
for (Map.Entry<String, Object> entry : values.entrySet()) {
|
||||||
|
Object val = entry.getValue();
|
||||||
|
if (val == null) {
|
||||||
|
// 跳过 null 值
|
||||||
|
continue;
|
||||||
|
} else if (val instanceof Boolean || val instanceof Number) {
|
||||||
|
// 保持原始类型(Boolean, Integer, Long, Double 等)
|
||||||
|
valuesMap.put(entry.getKey(), val);
|
||||||
|
} else if (val instanceof String) {
|
||||||
|
// 尝试将字符串转换为正确的类型
|
||||||
|
String strVal = (String) val;
|
||||||
|
if ("true".equalsIgnoreCase(strVal)) {
|
||||||
|
valuesMap.put(entry.getKey(), Boolean.TRUE);
|
||||||
|
} else if ("false".equalsIgnoreCase(strVal)) {
|
||||||
|
valuesMap.put(entry.getKey(), Boolean.FALSE);
|
||||||
|
} else {
|
||||||
|
// 尝试转换为数字
|
||||||
|
try {
|
||||||
|
if (strVal.contains(".")) {
|
||||||
|
valuesMap.put(entry.getKey(), Double.parseDouble(strVal));
|
||||||
|
} else {
|
||||||
|
valuesMap.put(entry.getKey(), Long.parseLong(strVal));
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// 保持字符串
|
||||||
|
valuesMap.put(entry.getKey(), strVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (val instanceof java.util.List) {
|
||||||
|
// List 类型保持不变(Jackson 已经正确处理了数组)
|
||||||
|
valuesMap.put(entry.getKey(), val);
|
||||||
|
} else {
|
||||||
|
valuesMap.put(entry.getKey(), val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
appSettingService.batchSave(category, valuesMap);
|
||||||
|
return success("保存成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除设置
|
||||||
|
*/
|
||||||
|
@Operation(summary = "删除设置")
|
||||||
|
@DeleteMapping("/{settingId}")
|
||||||
|
public ApiResult<?> delete(@PathVariable Integer settingId) {
|
||||||
|
appSettingService.removeById(settingId);
|
||||||
|
return success("删除成功");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,593 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.gxwebsoft.app.config.AppPayProperties;
|
||||||
|
import com.gxwebsoft.app.entity.AppProduct;
|
||||||
|
import com.gxwebsoft.app.entity.AppSubscription;
|
||||||
|
import com.gxwebsoft.app.mapper.AppSubscriptionMapper;
|
||||||
|
import com.gxwebsoft.app.mapper.AppProductMapper;
|
||||||
|
import com.gxwebsoft.app.mapper.SysUserCrossDbMapper;
|
||||||
|
import com.gxwebsoft.app.utils.WxNativePayUtil;
|
||||||
|
import com.gxwebsoft.common.core.constants.BalanceConstants;
|
||||||
|
import com.gxwebsoft.common.core.utils.RedisUtil;
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
|
|
||||||
|
import com.wechat.pay.java.core.Config;
|
||||||
|
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
|
||||||
|
import com.wechat.pay.java.service.payments.nativepay.model.Amount;
|
||||||
|
import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest;
|
||||||
|
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
|
||||||
|
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.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用订阅控制器
|
||||||
|
* 路径前缀:/api/app/subscription
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/subscription")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Tag(name = "应用订阅管理")
|
||||||
|
public class AppSubscriptionController extends BaseController {
|
||||||
|
|
||||||
|
private final AppSubscriptionMapper subscriptionMapper;
|
||||||
|
private final AppProductMapper productMapper;
|
||||||
|
private final WxNativePayUtil wxNativePayUtil;
|
||||||
|
private final AppPayProperties appPayProperties;
|
||||||
|
private final SysUserCrossDbMapper sysUserCrossDbMapper;
|
||||||
|
|
||||||
|
@javax.annotation.Resource
|
||||||
|
private RedisUtil redisUtil;
|
||||||
|
|
||||||
|
@Value("${spring.profiles.active:dev}")
|
||||||
|
private String active;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 订阅操作
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建订阅
|
||||||
|
* 免费应用:直接激活
|
||||||
|
* 付费应用:创建待支付记录
|
||||||
|
*/
|
||||||
|
@PostMapping("/subscribe")
|
||||||
|
@Operation(summary = "创建订阅")
|
||||||
|
public Object subscribe(@RequestBody Map<String, Object> params) {
|
||||||
|
Object productIdObj = params.get("productId");
|
||||||
|
if (productIdObj == null) {
|
||||||
|
return fail("productId 不能为空");
|
||||||
|
}
|
||||||
|
String productIdStr = productIdObj.toString();
|
||||||
|
if ("NaN".equals(productIdStr) || "null".equals(productIdStr) || "undefined".equals(productIdStr)) {
|
||||||
|
return fail("无效的应用ID,请刷新页面后重试");
|
||||||
|
}
|
||||||
|
Integer productId;
|
||||||
|
try {
|
||||||
|
productId = Integer.valueOf(productIdStr);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return fail("应用ID格式错误:" + productIdStr);
|
||||||
|
}
|
||||||
|
String subscriptionPeriod = (String) params.getOrDefault("subscriptionPeriod", "month");
|
||||||
|
|
||||||
|
Integer userId = getLoginUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
return fail("请先登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 查询产品信息
|
||||||
|
AppProduct product = productMapper.selectById(productId);
|
||||||
|
if (product == null || !"published".equals(product.getPublishStatus())) {
|
||||||
|
return fail("应用不存在或未上架");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查是否已有 active 订阅(幂等)
|
||||||
|
LambdaQueryWrapper<AppSubscription> existQuery = new LambdaQueryWrapper<>();
|
||||||
|
existQuery.eq(AppSubscription::getUserId, userId)
|
||||||
|
.eq(AppSubscription::getProductId, productId)
|
||||||
|
.eq(AppSubscription::getStatus, "active");
|
||||||
|
if (subscriptionMapper.selectCount(existQuery) > 0) {
|
||||||
|
return fail("您已订阅该应用,无需重复购买");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 计算价格
|
||||||
|
// app_product.price 单位为分(BigDecimal),转为元存到 app_subscription.pay_price
|
||||||
|
BigDecimal priceInYuan = BigDecimal.ZERO;
|
||||||
|
String priceType = product.getPriceType() != null ? product.getPriceType() : "free";
|
||||||
|
if (!"free".equals(priceType) && product.getPrice() != null) {
|
||||||
|
// price 字段单位是分,除以 100 转为元
|
||||||
|
priceInYuan = product.getPrice().divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
|
||||||
|
if ("subscription".equals(priceType) && "year".equals(subscriptionPeriod)) {
|
||||||
|
// 年付:按10个月价格
|
||||||
|
priceInYuan = priceInYuan.multiply(BigDecimal.TEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 创建订阅记录
|
||||||
|
AppSubscription sub = new AppSubscription();
|
||||||
|
sub.setSubscriptionNo(generateSubscriptionNo());
|
||||||
|
sub.setUserId(userId);
|
||||||
|
sub.setProductId(productId);
|
||||||
|
sub.setTenantId(product.getTenantId());
|
||||||
|
sub.setStatus("pending");
|
||||||
|
sub.setPriceType(priceType);
|
||||||
|
sub.setOriginalPrice(priceInYuan);
|
||||||
|
sub.setPayPrice(priceInYuan);
|
||||||
|
sub.setPayStatus(0);
|
||||||
|
sub.setSubscriptionPeriod("subscription".equals(priceType) ? subscriptionPeriod : null);
|
||||||
|
|
||||||
|
// 5. 免费应用直接激活
|
||||||
|
boolean isFree = "free".equals(priceType) || priceInYuan.compareTo(BigDecimal.ZERO) == 0;
|
||||||
|
if (isFree) {
|
||||||
|
sub.setStatus("active");
|
||||||
|
sub.setPayStatus(1);
|
||||||
|
sub.setPayType(12); // 免费标记
|
||||||
|
sub.setPayTime(LocalDateTime.now());
|
||||||
|
sub.setStartTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
if ("subscription".equals(priceType)) {
|
||||||
|
int months = "year".equals(subscriptionPeriod) ? 12 : 1;
|
||||||
|
sub.setExpireTime(LocalDateTime.now().plusMonths(months));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新产品安装量
|
||||||
|
product.setInstalls((product.getInstalls() != null ? product.getInstalls() : 0) + 1);
|
||||||
|
productMapper.updateById(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriptionMapper.insert(sub);
|
||||||
|
|
||||||
|
// 6. 返回结果
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("subscriptionId", sub.getId());
|
||||||
|
result.put("subscriptionNo", sub.getSubscriptionNo());
|
||||||
|
result.put("status", sub.getStatus());
|
||||||
|
result.put("message", isFree ? "订阅成功" : "请完成支付");
|
||||||
|
|
||||||
|
if (!isFree) {
|
||||||
|
result.put("payPrice", sub.getPayPrice());
|
||||||
|
// TODO: 对接现有支付系统,创建支付订单并返回 orderNo
|
||||||
|
// result.put("orderNo", payService.createOrder(sub));
|
||||||
|
}
|
||||||
|
|
||||||
|
return success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户余额
|
||||||
|
*/
|
||||||
|
@GetMapping("/balance")
|
||||||
|
@Operation(summary = "获取用户余额")
|
||||||
|
public Object getBalance() {
|
||||||
|
Integer userId = getLoginUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
return fail("请先登录");
|
||||||
|
}
|
||||||
|
// 跨库查询 gxwebsoft_core.sys_user
|
||||||
|
User freshUser = sysUserCrossDbMapper.selectByUserId(userId);
|
||||||
|
BigDecimal balance = freshUser != null && freshUser.getBalance() != null
|
||||||
|
? freshUser.getBalance() : BigDecimal.ZERO;
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("balance", balance);
|
||||||
|
return success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 余额支付
|
||||||
|
* POST /api/app/subscription/pay/{id}?method=balance
|
||||||
|
*/
|
||||||
|
@PostMapping("/pay/{id}")
|
||||||
|
@Operation(summary = "发起支付")
|
||||||
|
public Object pay(@PathVariable Long id, @RequestParam(defaultValue = "wechat") String method) {
|
||||||
|
AppSubscription sub = subscriptionMapper.selectById(id);
|
||||||
|
if (sub == null) {
|
||||||
|
return fail("订阅不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer userId = getLoginUserId();
|
||||||
|
if (userId == null || !userId.equals(sub.getUserId())) {
|
||||||
|
return fail("无权操作");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("active".equals(sub.getStatus())) {
|
||||||
|
return fail("该订阅已激活");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 免费应用走激活流程
|
||||||
|
if (sub.getPayPrice() == null || sub.getPayPrice().compareTo(BigDecimal.ZERO) == 0) {
|
||||||
|
return fail("该应用为免费应用,无需支付");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 余额支付
|
||||||
|
if ("balance".equals(method)) {
|
||||||
|
return handleBalancePay(sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 微信 Native 支付
|
||||||
|
return handleWechatPay(sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 余额支付处理
|
||||||
|
*/
|
||||||
|
private Object handleBalancePay(AppSubscription sub) {
|
||||||
|
Integer userId = getLoginUserId();
|
||||||
|
|
||||||
|
// 跨库查询用户最新余额(gxwebsoft_core.sys_user)
|
||||||
|
User user = sysUserCrossDbMapper.selectByUserId(userId);
|
||||||
|
if (user == null) {
|
||||||
|
return fail("用户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal currentBalance = user.getBalance() != null ? user.getBalance() : BigDecimal.ZERO;
|
||||||
|
BigDecimal payPrice = sub.getPayPrice();
|
||||||
|
|
||||||
|
// 检查余额是否充足
|
||||||
|
if (currentBalance.compareTo(payPrice) < 0) {
|
||||||
|
return fail("余额不足,当前余额:" + currentBalance + " 元,需要:" + payPrice + " 元");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扣除余额(跨库更新 gxwebsoft_core.sys_user)
|
||||||
|
BigDecimal newBalance = currentBalance.subtract(payPrice);
|
||||||
|
int updated = sysUserCrossDbMapper.updateBalance(userId, newBalance);
|
||||||
|
if (updated <= 0) {
|
||||||
|
return fail("余额扣除失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录余额变动日志(跨库插入 gxwebsoft_core.sys_user_balance_log)
|
||||||
|
sysUserCrossDbMapper.insertBalanceLog(
|
||||||
|
userId,
|
||||||
|
BalanceConstants.BALANCE_USE,
|
||||||
|
payPrice,
|
||||||
|
newBalance,
|
||||||
|
sub.getSubscriptionNo(),
|
||||||
|
"应用订阅:" + sub.getProductName(),
|
||||||
|
user.getTenantId()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 激活订阅
|
||||||
|
sub.setStatus("active");
|
||||||
|
sub.setPayStatus(1);
|
||||||
|
sub.setPayType(0); // 0-余额支付
|
||||||
|
sub.setPayTime(LocalDateTime.now());
|
||||||
|
sub.setStartTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
if ("subscription".equals(sub.getPriceType())) {
|
||||||
|
int months = "year".equals(sub.getSubscriptionPeriod()) ? 12 : 1;
|
||||||
|
sub.setExpireTime(LocalDateTime.now().plusMonths(months));
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriptionMapper.updateById(sub);
|
||||||
|
|
||||||
|
// 更新产品安装量
|
||||||
|
AppProduct product = productMapper.selectById(sub.getProductId());
|
||||||
|
if (product != null) {
|
||||||
|
product.setInstalls((product.getInstalls() != null ? product.getInstalls() : 0) + 1);
|
||||||
|
productMapper.updateById(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入 Redis 标记(兼容轮询)
|
||||||
|
redisUtil.set("wxpay:paid:" + sub.getSubscriptionNo(), "1", 30L, java.util.concurrent.TimeUnit.MINUTES);
|
||||||
|
|
||||||
|
log.info("余额支付成功 — subscriptionNo: {}, userId: {}, amount: {}, remainingBalance: {}",
|
||||||
|
sub.getSubscriptionNo(), userId, payPrice, newBalance);
|
||||||
|
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("paid", true);
|
||||||
|
result.put("balance", newBalance);
|
||||||
|
result.put("subscriptionNo", sub.getSubscriptionNo());
|
||||||
|
return success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信 Native 支付处理
|
||||||
|
*/
|
||||||
|
private Object handleWechatPay(AppSubscription sub) {
|
||||||
|
// 查询产品名称作为支付描述
|
||||||
|
AppProduct product = productMapper.selectById(sub.getProductId());
|
||||||
|
String productName = product != null ? product.getProductName() : "应用订阅";
|
||||||
|
|
||||||
|
// 金额:元转分
|
||||||
|
BigDecimal payPriceYuan = sub.getPayPrice();
|
||||||
|
int totalFee = payPriceYuan.multiply(new BigDecimal(100)).intValue();
|
||||||
|
if (totalFee < 1) {
|
||||||
|
totalFee = 1; // 微信最低1分
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("========== 微信 Native 支付请求 ==========");
|
||||||
|
log.info("商户号: {}", appPayProperties.getMchId());
|
||||||
|
log.info("AppID: {}", appPayProperties.getAppId());
|
||||||
|
log.info("订单号: {}", sub.getSubscriptionNo());
|
||||||
|
log.info("商品描述: {}", productName);
|
||||||
|
log.info("支付金额(分): {}", totalFee);
|
||||||
|
log.info("回调地址: {}", appPayProperties.getNotifyUrl());
|
||||||
|
log.info("==========================================");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Config config = wxNativePayUtil.getConfig(appPayProperties);
|
||||||
|
NativePayService payService = new NativePayService.Builder().config(config).build();
|
||||||
|
|
||||||
|
PrepayRequest request = new PrepayRequest();
|
||||||
|
request.setAppid(appPayProperties.getAppId());
|
||||||
|
request.setMchid(appPayProperties.getMchId());
|
||||||
|
request.setOutTradeNo(sub.getSubscriptionNo());
|
||||||
|
request.setDescription(productName);
|
||||||
|
request.setNotifyUrl(appPayProperties.getNotifyUrl());
|
||||||
|
|
||||||
|
Amount amount = new Amount();
|
||||||
|
amount.setTotal(totalFee);
|
||||||
|
request.setAmount(amount);
|
||||||
|
|
||||||
|
PrepayResponse response = payService.prepay(request);
|
||||||
|
String codeUrl = response.getCodeUrl();
|
||||||
|
|
||||||
|
log.info("微信 Native 支付下单成功 — subscriptionNo: {}, codeUrl: {}", sub.getSubscriptionNo(), codeUrl);
|
||||||
|
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("subscriptionId", sub.getId());
|
||||||
|
result.put("subscriptionNo", sub.getSubscriptionNo());
|
||||||
|
result.put("codeUrl", codeUrl);
|
||||||
|
result.put("payPrice", payPriceYuan);
|
||||||
|
return success(result);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("========== 微信支付下单失败 ==========");
|
||||||
|
log.error("商户号: {}", appPayProperties.getMchId());
|
||||||
|
log.error("AppID: {}", appPayProperties.getAppId());
|
||||||
|
log.error("订单号: {}", sub.getSubscriptionNo());
|
||||||
|
log.error("错误类型: {}", e.getClass().getName());
|
||||||
|
log.error("错误信息: {}", e.getMessage());
|
||||||
|
|
||||||
|
if (e instanceof com.wechat.pay.java.core.exception.ServiceException) {
|
||||||
|
com.wechat.pay.java.core.exception.ServiceException wechatEx =
|
||||||
|
(com.wechat.pay.java.core.exception.ServiceException) e;
|
||||||
|
log.error("微信错误码: {}", wechatEx.getErrorCode());
|
||||||
|
log.error("微信错误信息: {}", wechatEx.getErrorMessage());
|
||||||
|
}
|
||||||
|
log.error("==========================================");
|
||||||
|
|
||||||
|
// 测试模式:返回模拟二维码
|
||||||
|
if (appPayProperties.isEnabled() && !appPayProperties.isTestMode()) {
|
||||||
|
return fail("微信支付服务异常,请稍后重试");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试模式返回可展示的 mock URL
|
||||||
|
String mockCodeUrl = "weixin://wxpay/bizpayurl?pr=TEST" + System.currentTimeMillis();
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("subscriptionId", sub.getId());
|
||||||
|
result.put("subscriptionNo", sub.getSubscriptionNo());
|
||||||
|
result.put("codeUrl", mockCodeUrl);
|
||||||
|
result.put("payPrice", payPriceYuan);
|
||||||
|
result.put("_testMode", true);
|
||||||
|
return success(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 我的订阅
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 我的订阅列表(分页)
|
||||||
|
*/
|
||||||
|
@GetMapping("/my/page")
|
||||||
|
@Operation(summary = "我的订阅列表")
|
||||||
|
public Object myPage(
|
||||||
|
@RequestParam(defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(defaultValue = "10") Integer limit,
|
||||||
|
@RequestParam(required = false) String status) {
|
||||||
|
|
||||||
|
Integer userId = getLoginUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
return success(new Page<AppSubscription>());
|
||||||
|
}
|
||||||
|
|
||||||
|
Page<AppSubscription> pageParam = new Page<>(page, limit);
|
||||||
|
LambdaQueryWrapper<AppSubscription> query = new LambdaQueryWrapper<>();
|
||||||
|
query.eq(AppSubscription::getUserId, userId);
|
||||||
|
if (status != null && !status.isEmpty()) {
|
||||||
|
query.eq(AppSubscription::getStatus, status);
|
||||||
|
}
|
||||||
|
query.orderByDesc(AppSubscription::getCreateTime);
|
||||||
|
|
||||||
|
Page<AppSubscription> pageResult = subscriptionMapper.selectPage(pageParam, query);
|
||||||
|
|
||||||
|
// 填充产品信息(TODO: 后续改为 LEFT JOIN 优化)
|
||||||
|
pageResult.getRecords().forEach(sub -> {
|
||||||
|
AppProduct product = productMapper.selectById(sub.getProductId());
|
||||||
|
if (product != null) {
|
||||||
|
sub.setProductName(product.getProductName());
|
||||||
|
sub.setProductLogo(product.getLogo());
|
||||||
|
sub.setProductIcon(product.getIcon());
|
||||||
|
sub.setProductAppType(product.getAppType());
|
||||||
|
sub.setProductDescription(product.getDescription());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return success(pageResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/detail/{id}")
|
||||||
|
@Operation(summary = "订阅详情")
|
||||||
|
public Object detail(@PathVariable Long id) {
|
||||||
|
AppSubscription sub = subscriptionMapper.selectById(id);
|
||||||
|
if (sub == null) {
|
||||||
|
return fail("订阅不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
AppProduct product = productMapper.selectById(sub.getProductId());
|
||||||
|
if (product != null) {
|
||||||
|
sub.setProductName(product.getProductName());
|
||||||
|
sub.setProductLogo(product.getLogo());
|
||||||
|
sub.setProductIcon(product.getIcon());
|
||||||
|
sub.setProductAppType(product.getAppType());
|
||||||
|
}
|
||||||
|
|
||||||
|
return success(sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 订阅管理
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 续费
|
||||||
|
*/
|
||||||
|
@PostMapping("/renew/{id}")
|
||||||
|
@Operation(summary = "续费")
|
||||||
|
public Object renew(@PathVariable Long id,
|
||||||
|
@RequestParam(defaultValue = "month") String period) {
|
||||||
|
AppSubscription sub = subscriptionMapper.selectById(id);
|
||||||
|
if (sub == null) return fail("订阅不存在");
|
||||||
|
|
||||||
|
Integer userId = getLoginUserId();
|
||||||
|
if (userId == null || !userId.equals(sub.getUserId())) return fail("无权操作");
|
||||||
|
|
||||||
|
int months = "year".equals(period) ? 12 : 1;
|
||||||
|
if ("active".equals(sub.getStatus()) && sub.getExpireTime() != null) {
|
||||||
|
sub.setExpireTime(sub.getExpireTime().plusMonths(months));
|
||||||
|
} else {
|
||||||
|
sub.setExpireTime(LocalDateTime.now().plusMonths(months));
|
||||||
|
}
|
||||||
|
sub.setStatus("active");
|
||||||
|
subscriptionMapper.updateById(sub);
|
||||||
|
return success("续费成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退订/取消
|
||||||
|
*/
|
||||||
|
@PostMapping("/cancel/{id}")
|
||||||
|
@Operation(summary = "退订")
|
||||||
|
public Object cancel(@PathVariable Long id) {
|
||||||
|
AppSubscription sub = subscriptionMapper.selectById(id);
|
||||||
|
if (sub == null) return fail("订阅不存在");
|
||||||
|
|
||||||
|
Integer userId = getLoginUserId();
|
||||||
|
if (userId == null || !userId.equals(sub.getUserId())) return fail("无权操作");
|
||||||
|
|
||||||
|
sub.setStatus("cancelled");
|
||||||
|
subscriptionMapper.updateById(sub);
|
||||||
|
return success("退订成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用/禁用
|
||||||
|
*/
|
||||||
|
@PostMapping("/toggle-enable/{id}")
|
||||||
|
@Operation(summary = "启用/禁用")
|
||||||
|
public Object toggleEnable(@PathVariable Long id, @RequestParam Boolean enabled) {
|
||||||
|
AppSubscription sub = subscriptionMapper.selectById(id);
|
||||||
|
if (sub == null) return fail("订阅不存在");
|
||||||
|
|
||||||
|
Integer userId = getLoginUserId();
|
||||||
|
if (userId == null || !userId.equals(sub.getUserId())) return fail("无权操作");
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
if ("expired".equals(sub.getStatus()) ||
|
||||||
|
(sub.getExpireTime() != null && sub.getExpireTime().isBefore(LocalDateTime.now()))) {
|
||||||
|
return fail("订阅已过期,请先续费");
|
||||||
|
}
|
||||||
|
sub.setStatus("active");
|
||||||
|
} else {
|
||||||
|
sub.setStatus("cancelled");
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriptionMapper.updateById(sub);
|
||||||
|
return success(enabled ? "已启用" : "已禁用");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 查询
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询支付状态(前端轮询用)
|
||||||
|
* 优先查 Redis(回调写入,加速感知),再查数据库兜底
|
||||||
|
*/
|
||||||
|
@GetMapping("/check-status/{subscriptionNo}")
|
||||||
|
@Operation(summary = "查询支付状态")
|
||||||
|
public Object checkStatus(@PathVariable String subscriptionNo) {
|
||||||
|
LambdaQueryWrapper<AppSubscription> query = new LambdaQueryWrapper<>();
|
||||||
|
query.eq(AppSubscription::getSubscriptionNo, subscriptionNo);
|
||||||
|
AppSubscription sub = subscriptionMapper.selectOne(query);
|
||||||
|
|
||||||
|
if (sub == null) {
|
||||||
|
return fail("订阅不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先检查 Redis 支付标记(微信回调写入,加速前端轮询感知)
|
||||||
|
boolean paidViaRedis = "1".equals(redisUtil.get("wxpay:paid:" + subscriptionNo));
|
||||||
|
boolean paidViaDb = sub.getPayStatus() != null && sub.getPayStatus() == 1;
|
||||||
|
boolean isPaid = paidViaRedis || paidViaDb;
|
||||||
|
|
||||||
|
// 补全产品信息
|
||||||
|
AppProduct product = productMapper.selectById(sub.getProductId());
|
||||||
|
if (product != null) {
|
||||||
|
sub.setProductName(product.getProductName());
|
||||||
|
sub.setProductLogo(product.getLogo());
|
||||||
|
sub.setProductIcon(product.getIcon());
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("paid", isPaid);
|
||||||
|
result.put("payStatus", sub.getPayStatus());
|
||||||
|
result.put("status", sub.getStatus());
|
||||||
|
result.put("payTime", sub.getPayTime());
|
||||||
|
result.put("transactionId", sub.getTransactionId());
|
||||||
|
// 补全订单详情字段,前端支付页可直接使用
|
||||||
|
result.put("id", sub.getId());
|
||||||
|
result.put("subscriptionNo", sub.getSubscriptionNo());
|
||||||
|
result.put("productId", sub.getProductId());
|
||||||
|
result.put("productName", sub.getProductName());
|
||||||
|
result.put("productLogo", sub.getProductLogo());
|
||||||
|
result.put("priceType", sub.getPriceType());
|
||||||
|
result.put("payPrice", sub.getPayPrice());
|
||||||
|
result.put("subscriptionPeriod", sub.getSubscriptionPeriod());
|
||||||
|
return success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否已购买某应用
|
||||||
|
*/
|
||||||
|
@GetMapping("/check-purchased/{productId}")
|
||||||
|
@Operation(summary = "检查是否已购买")
|
||||||
|
public Object checkPurchased(@PathVariable Integer productId) {
|
||||||
|
Integer userId = getLoginUserId();
|
||||||
|
if (userId == null) return success(false);
|
||||||
|
|
||||||
|
LambdaQueryWrapper<AppSubscription> query = new LambdaQueryWrapper<>();
|
||||||
|
query.eq(AppSubscription::getUserId, userId)
|
||||||
|
.eq(AppSubscription::getProductId, productId)
|
||||||
|
.in(AppSubscription::getStatus, "active", "pending");
|
||||||
|
return success(subscriptionMapper.selectCount(query) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 内部方法
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
private String generateSubscriptionNo() {
|
||||||
|
String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
|
||||||
|
int random = ThreadLocalRandom.current().nextInt(1000, 9999);
|
||||||
|
return "SUB" + date + random;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.app.entity.AppProduct;
|
||||||
|
import com.gxwebsoft.app.entity.AppTicket;
|
||||||
|
import com.gxwebsoft.app.entity.AppTicketReply;
|
||||||
|
import com.gxwebsoft.app.param.AppTicketParam;
|
||||||
|
import com.gxwebsoft.app.service.AppProductService;
|
||||||
|
import com.gxwebsoft.app.service.AppTicketService;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import com.gxwebsoft.common.core.web.PageResult;
|
||||||
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用工单控制器
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-03-30
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "应用工单管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/ticket")
|
||||||
|
public class AppTicketController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppTicketService appTicketService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppProductService appProductService;
|
||||||
|
|
||||||
|
// ─── 客户端接口 ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@Operation(summary = "查询我的工单(分页)")
|
||||||
|
@GetMapping("/my")
|
||||||
|
public ApiResult<PageResult<AppTicket>> myTickets(AppTicketParam param) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录",null);
|
||||||
|
return success(appTicketService.myPage(param, loginUser.getUserId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "提交工单")
|
||||||
|
@PostMapping("/submit")
|
||||||
|
public ApiResult<AppTicket> submit(@RequestBody AppTicket ticket) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录",null);
|
||||||
|
try {
|
||||||
|
AppTicket result = appTicketService.submit(ticket, loginUser.getUserId());
|
||||||
|
return success("工单提交成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage(),null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "关闭工单(提交人)")
|
||||||
|
@PutMapping("/{ticketId}/close")
|
||||||
|
public ApiResult<?> close(@PathVariable Long ticketId) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录");
|
||||||
|
try {
|
||||||
|
appTicketService.closeByUser(ticketId, loginUser.getUserId());
|
||||||
|
return success("工单已关闭");
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 技术端接口 ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@Operation(summary = "查询所有工单(技术人员)")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public ApiResult<PageResult<AppTicket>> allTickets(AppTicketParam param) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录",null);
|
||||||
|
// 按用户有权限的应用过滤工单(看到的工单限于自己参与的应用)
|
||||||
|
List<AppProduct> accessibleApps = appProductService.getAccessibleApps(loginUser.getUserId());
|
||||||
|
if (accessibleApps != null && !accessibleApps.isEmpty()) {
|
||||||
|
param.setAppIds(accessibleApps.stream().map(AppProduct::getProductId).collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
return success(appTicketService.allPage(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取工单详情")
|
||||||
|
@GetMapping("/{ticketId}")
|
||||||
|
public ApiResult<AppTicket> detail(@PathVariable Long ticketId) {
|
||||||
|
return success(appTicketService.getById(ticketId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "更新工单状态(技术人员)")
|
||||||
|
@PutMapping("/status")
|
||||||
|
public ApiResult<?> updateStatus(@RequestBody Map<String, Object> body) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录");
|
||||||
|
Long ticketId = Long.valueOf(body.get("ticketId").toString());
|
||||||
|
String status = body.get("status").toString();
|
||||||
|
appTicketService.updateStatus(ticketId, status, loginUser.getUserId());
|
||||||
|
return success("状态已更新");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "分配处理人(管理员)")
|
||||||
|
@PutMapping("/assign")
|
||||||
|
public ApiResult<?> assign(@RequestBody Map<String, Object> body) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录");
|
||||||
|
Long ticketId = Long.valueOf(body.get("ticketId").toString());
|
||||||
|
Integer assigneeId = Integer.valueOf(body.get("assigneeId").toString());
|
||||||
|
appTicketService.assign(ticketId, assigneeId);
|
||||||
|
return success("分配成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 回复接口 ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@Operation(summary = "获取工单回复列表")
|
||||||
|
@GetMapping("/{ticketId}/replies")
|
||||||
|
public ApiResult<List<AppTicketReply>> replies(@PathVariable Long ticketId) {
|
||||||
|
return success(appTicketService.getReplies(ticketId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "提交工单回复")
|
||||||
|
@PostMapping("/reply")
|
||||||
|
public ApiResult<AppTicketReply> reply(@RequestBody AppTicketReply reply) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录",null);
|
||||||
|
if (reply.getContent() == null || reply.getContent().trim().isEmpty()) {
|
||||||
|
return fail("回复内容不能为空",null);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
AppTicketReply result = appTicketService.addReply(reply, loginUser.getUserId());
|
||||||
|
return success("回复成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage(),null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 统计 & 辅助 ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
@Operation(summary = "工单统计数据")
|
||||||
|
@GetMapping("/stats")
|
||||||
|
public ApiResult<Map<String, Long>> stats(
|
||||||
|
@RequestParam(required = false) Long appId) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) return fail("请先登录",null);
|
||||||
|
// 技术端不限制用户维度;客户端通过路由区分
|
||||||
|
return success(appTicketService.stats(appId, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取技术人员列表(用于分配)")
|
||||||
|
@GetMapping("/staff-list")
|
||||||
|
public ApiResult<List<Map<String, Object>>> staffList() {
|
||||||
|
return success(appTicketService.getTechStaffList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import com.gxwebsoft.app.service.AppUserService;
|
||||||
|
import com.gxwebsoft.app.entity.AppUser;
|
||||||
|
import com.gxwebsoft.app.param.AppUserParam;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.PageResult;
|
||||||
|
import com.gxwebsoft.common.core.web.BatchParam;
|
||||||
|
import com.gxwebsoft.common.core.annotation.OperationLog;
|
||||||
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
|
import com.gxwebsoft.app.mapper.SysUserCrossDbMapper;
|
||||||
|
import com.gxwebsoft.app.mapper.AppUserMapper;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用成员控制器
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-03-28 21:29:44
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "应用成员管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/app-user")
|
||||||
|
public class AppUserController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppUserService appUserService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SysUserCrossDbMapper sysUserCrossDbMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppUserMapper appUserMapper;
|
||||||
|
|
||||||
|
@Operation(summary = "分页查询应用成员")
|
||||||
|
@GetMapping("/page")
|
||||||
|
public ApiResult<PageResult<AppUser>> page(AppUserParam param) {
|
||||||
|
return success(appUserService.pageRel(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询全部应用成员")
|
||||||
|
@GetMapping()
|
||||||
|
public ApiResult<List<AppUser>> list(AppUserParam param) {
|
||||||
|
return success(appUserService.listRel(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "根据id查询应用成员")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResult<AppUser> get(@PathVariable("id") Integer id) {
|
||||||
|
return success(appUserService.getByIdRel(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appUser:save')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "添加应用成员(手动添加)")
|
||||||
|
@PostMapping()
|
||||||
|
public ApiResult<?> save(@RequestBody AppUser appUser) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser != null) {
|
||||||
|
appUser.setUserId(loginUser.getUserId());
|
||||||
|
appUser.setTenantId(loginUser.getTenantId());
|
||||||
|
}
|
||||||
|
if (appUserService.save(appUser)) {
|
||||||
|
return success("添加成功");
|
||||||
|
}
|
||||||
|
return fail("添加失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "邀请用户成为应用成员(支持用户ID或手机号)")
|
||||||
|
@PostMapping("/invite")
|
||||||
|
public ApiResult<?> invite(@RequestBody AppUser appUser) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录");
|
||||||
|
}
|
||||||
|
// 支持手机号邀请:若 userId 为空但传了 phone,则先按手机号查出用户
|
||||||
|
if (appUser.getUserId() == null && appUser.getPhone() != null && !appUser.getPhone().isEmpty()) {
|
||||||
|
User targetUser = appUserService.findUserByPhone(appUser.getPhone());
|
||||||
|
if (targetUser == null) {
|
||||||
|
return fail("手机号未注册,请确认后再试");
|
||||||
|
}
|
||||||
|
appUser.setUserId(targetUser.getUserId());
|
||||||
|
}
|
||||||
|
if (appUser.getUserId() == null) {
|
||||||
|
return fail("请输入用户ID或手机号");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
AppUser result = appUserService.inviteUser(
|
||||||
|
appUser.getAppId(),
|
||||||
|
appUser.getUserId(),
|
||||||
|
appUser.getRole(),
|
||||||
|
loginUser.getUserId(),
|
||||||
|
loginUser.getTenantId()
|
||||||
|
);
|
||||||
|
return success("邀请成功", result);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appUser:update')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "修改应用成员信息")
|
||||||
|
@PutMapping()
|
||||||
|
public ApiResult<?> update(@RequestBody AppUser appUser) {
|
||||||
|
if (appUserService.updateById(appUser)) {
|
||||||
|
return success("修改成功");
|
||||||
|
}
|
||||||
|
return fail("修改失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appUser:update')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "修改成员角色")
|
||||||
|
@PutMapping("/role/{id}/{role}")
|
||||||
|
public ApiResult<?> updateRole(@PathVariable("id") Long id, @PathVariable("role") String role) {
|
||||||
|
if (appUserService.updateRole(id, role)) {
|
||||||
|
return success("角色修改成功");
|
||||||
|
}
|
||||||
|
return fail("修改失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appUser:remove')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "移除应用成员")
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResult<?> remove(@PathVariable("id") Integer id) {
|
||||||
|
if (appUserService.removeById(id)) {
|
||||||
|
return success("已移除");
|
||||||
|
}
|
||||||
|
return fail("移除失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appUser:save')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "批量添加应用成员")
|
||||||
|
@PostMapping("/batch")
|
||||||
|
public ApiResult<?> saveBatch(@RequestBody List<AppUser> list) {
|
||||||
|
if (appUserService.saveBatch(list)) {
|
||||||
|
return success("添加成功");
|
||||||
|
}
|
||||||
|
return fail("添加失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appUser:update')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "批量修改应用成员")
|
||||||
|
@PutMapping("/batch")
|
||||||
|
public ApiResult<?> updateBatch(@RequestBody BatchParam<AppUser> batchParam) {
|
||||||
|
if (batchParam.update(appUserService, "id")) {
|
||||||
|
return success("修改成功");
|
||||||
|
}
|
||||||
|
return fail("修改失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appUser:remove')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "批量移除应用成员")
|
||||||
|
@DeleteMapping("/batch")
|
||||||
|
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
|
||||||
|
if (appUserService.removeByIds(ids)) {
|
||||||
|
return success("移除成功");
|
||||||
|
}
|
||||||
|
return fail("移除失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "搜索用户(用于邀请成员,支持手机号/用户名/昵称模糊搜索)")
|
||||||
|
@GetMapping("/search")
|
||||||
|
public ApiResult<List<User>> searchUsers(@RequestParam("keyword") String keyword) {
|
||||||
|
if (keyword == null || keyword.trim().isEmpty()) {
|
||||||
|
return success(Collections.emptyList());
|
||||||
|
}
|
||||||
|
List<User> users = appUserService.searchUsers(keyword.trim());
|
||||||
|
return success(users);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 邀请确认相关接口 ============
|
||||||
|
|
||||||
|
@Operation(summary = "查询当前用户的待确认邀请列表")
|
||||||
|
@GetMapping("/invites/pending")
|
||||||
|
public ApiResult<List<AppUser>> listPendingInvites() {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录",null);
|
||||||
|
}
|
||||||
|
return success(appUserService.listPendingInvites(loginUser.getUserId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "统计当前用户的待确认邀请数量")
|
||||||
|
@GetMapping("/invites/pending/count")
|
||||||
|
public ApiResult<Map<String, Long>> countPendingInvites() {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return success(Map.of("count", 0L));
|
||||||
|
}
|
||||||
|
long count = appUserService.countPendingInvites(loginUser.getUserId());
|
||||||
|
return success(Map.of("count", count));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "接受邀请加入应用")
|
||||||
|
@PostMapping("/invites/{inviteId}/accept")
|
||||||
|
public ApiResult<?> acceptInvite(@PathVariable("inviteId") Long inviteId) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (appUserService.acceptInvite(inviteId, loginUser.getUserId())) {
|
||||||
|
return success("已接受邀请,加入应用成功");
|
||||||
|
}
|
||||||
|
return fail("接受邀请失败");
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "拒绝邀请")
|
||||||
|
@PostMapping("/invites/{inviteId}/reject")
|
||||||
|
public ApiResult<?> rejectInvite(@PathVariable("inviteId") Long inviteId) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (appUserService.rejectInvite(inviteId, loginUser.getUserId())) {
|
||||||
|
return success("已拒绝邀请");
|
||||||
|
}
|
||||||
|
return fail("拒绝邀请失败");
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 权限检查接口 ============
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查当前用户是否有开发者中心访问权限
|
||||||
|
* 判断逻辑:
|
||||||
|
* 1. sys_user.type === 2 → 平台开发者,直接放行
|
||||||
|
* 2. 在 app_user 表中有成员记录 → 协作成员,放行
|
||||||
|
* 3. 创建过应用(app_product.user_id) → 自动有权限
|
||||||
|
* 返回:accessible, isPlatformDeveloper, apps(可访问应用列表及角色)
|
||||||
|
*/
|
||||||
|
@Operation(summary = "检查开发者中心访问权限")
|
||||||
|
@GetMapping("/check-access")
|
||||||
|
public ApiResult<Map<String, Object>> checkAccess() {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return success(Map.of(
|
||||||
|
"accessible", false,
|
||||||
|
"isPlatformDeveloper", false,
|
||||||
|
"hasJoinedApps", false
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer userId = loginUser.getUserId();
|
||||||
|
|
||||||
|
// 1. 判断是否平台级开发者(type === 2)
|
||||||
|
boolean isPlatformDeveloper = false;
|
||||||
|
try {
|
||||||
|
Integer userType = sysUserCrossDbMapper.selectUserType(userId);
|
||||||
|
isPlatformDeveloper = userType != null && userType == 2;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("查询用户类型失败,userId={}, error={}", userId, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 查询用户参与的应用(创建的 + 被邀请的)
|
||||||
|
List<Map<String, Object>> apps = appUserMapper.selectUserAccessibleApps(userId);
|
||||||
|
boolean hasJoinedApps = apps != null && !apps.isEmpty();
|
||||||
|
|
||||||
|
// 3. 平台开发者 或 有参与的应用 → 可访问
|
||||||
|
boolean accessible = isPlatformDeveloper || hasJoinedApps;
|
||||||
|
|
||||||
|
// 4. 构建返回结果
|
||||||
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
|
result.put("accessible", accessible);
|
||||||
|
result.put("isPlatformDeveloper", isPlatformDeveloper);
|
||||||
|
result.put("hasJoinedApps", hasJoinedApps);
|
||||||
|
result.put("apps", apps != null ? apps : Collections.emptyList());
|
||||||
|
|
||||||
|
return success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.app.entity.AppUserCache;
|
||||||
|
import com.gxwebsoft.app.service.AppUserCacheService;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户同步控制器(供 server 端调用)
|
||||||
|
*
|
||||||
|
* @author WebSoft
|
||||||
|
* @since 2026-04-04
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "用户同步(server 端调用)")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/user-sync")
|
||||||
|
public class AppUserSyncController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppUserCacheService appUserCacheService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步单个用户到缓存表
|
||||||
|
* 供 server 端用户注册成功后调用
|
||||||
|
*/
|
||||||
|
@Operation(summary = "同步单个用户(server 端调用)")
|
||||||
|
@PostMapping("/single")
|
||||||
|
public ApiResult<?> syncSingleUser(@RequestBody AppUserCache userCache) {
|
||||||
|
log.info("收到用户同步请求: userId={}, username={}, nickname={}, phone={}, tenantId={}",
|
||||||
|
userCache.getUserId(), userCache.getUsername(), userCache.getNickname(), userCache.getPhone(), userCache.getTenantId());
|
||||||
|
|
||||||
|
if (userCache.getUserId() == null) {
|
||||||
|
return fail("userId 不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userCache.getTenantId() == null) {
|
||||||
|
return fail("tenantId 不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 设置更新时间(如果为空)
|
||||||
|
if (userCache.getUpdateTime() == null) {
|
||||||
|
userCache.setUpdateTime(LocalDateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接保存或更新缓存
|
||||||
|
boolean result = appUserCacheService.saveOrUpdate(userCache);
|
||||||
|
if (result) {
|
||||||
|
log.info("用户同步成功: userId={}, username={}, tenantId={}", userCache.getUserId(), userCache.getUsername(), userCache.getTenantId());
|
||||||
|
} else {
|
||||||
|
log.warn("用户同步返回失败: userId={}", userCache.getUserId());
|
||||||
|
}
|
||||||
|
return success("同步成功");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("用户同步失败: userId={}, error={}", userCache.getUserId(), e.getMessage(), e);
|
||||||
|
return fail("同步失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量同步用户到缓存表
|
||||||
|
*/
|
||||||
|
@Operation(summary = "批量同步用户(server 端调用)")
|
||||||
|
@PostMapping("/batch")
|
||||||
|
public ApiResult<?> syncBatchUsers(@RequestBody List<AppUserCache> userCaches) {
|
||||||
|
log.info("收到批量用户同步请求: count={}", userCaches.size());
|
||||||
|
|
||||||
|
if (userCaches.isEmpty()) {
|
||||||
|
return fail("用户列表不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验每个用户数据
|
||||||
|
for (AppUserCache userCache : userCaches) {
|
||||||
|
if (userCache.getUserId() == null) {
|
||||||
|
return fail("用户列表中存在 userId 为空的记录");
|
||||||
|
}
|
||||||
|
if (userCache.getTenantId() == null) {
|
||||||
|
return fail("用户列表中存在 tenantId 为空的记录,userId=" + userCache.getUserId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
appUserCacheService.saveOrUpdateBatch(userCaches);
|
||||||
|
log.info("批量用户同步成功: count={}", userCaches.size());
|
||||||
|
return success("批量同步成功");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("批量用户同步失败: error={}", e.getMessage());
|
||||||
|
return fail("批量同步失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 userId 刷新用户缓存
|
||||||
|
* server 端可以只传 userId,websopy 端通过 API 回查 server 获取完整信息
|
||||||
|
*/
|
||||||
|
@Operation(summary = "刷新用户缓存(server 端调用)")
|
||||||
|
@PostMapping("/refresh/{userId}")
|
||||||
|
public ApiResult<?> refreshUserCache(@PathVariable("userId") Integer userId) {
|
||||||
|
log.info("收到刷新用户缓存请求: userId={}", userId);
|
||||||
|
|
||||||
|
if (userId == null) {
|
||||||
|
return fail("userId 不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
appUserCacheService.refreshUserCache(userId);
|
||||||
|
log.info("用户缓存刷新成功: userId={}", userId);
|
||||||
|
return success("刷新成功");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("用户缓存刷新失败: userId={}, error={}", userId, e.getMessage());
|
||||||
|
return fail("刷新失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除用户缓存
|
||||||
|
* 供 server 端删除用户时调用,同步删除本地缓存
|
||||||
|
*/
|
||||||
|
@Operation(summary = "删除用户缓存(server 端调用)")
|
||||||
|
@PostMapping("/delete/{userId}")
|
||||||
|
public ApiResult<?> deleteUserCache(@PathVariable("userId") Integer userId) {
|
||||||
|
log.info("收到删除用户缓存请求: userId={}", userId);
|
||||||
|
|
||||||
|
if (userId == null) {
|
||||||
|
return fail("userId 不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean result = appUserCacheService.removeById(userId);
|
||||||
|
if (result) {
|
||||||
|
log.info("用户缓存删除成功: userId={}", userId);
|
||||||
|
return success("删除成功");
|
||||||
|
} else {
|
||||||
|
log.warn("用户缓存删除失败或用户不存在: userId={}", userId);
|
||||||
|
return success("用户不存在或已删除");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("用户缓存删除失败: userId={}, error={}", userId, e.getMessage());
|
||||||
|
return fail("删除失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除用户缓存
|
||||||
|
* 供 server 端批量删除用户时调用
|
||||||
|
*/
|
||||||
|
@Operation(summary = "批量删除用户缓存(server 端调用)")
|
||||||
|
@PostMapping("/delete/batch")
|
||||||
|
public ApiResult<?> deleteBatchUserCache(@RequestBody List<Integer> userIds) {
|
||||||
|
log.info("收到批量删除用户缓存请求: count={}", userIds.size());
|
||||||
|
|
||||||
|
if (userIds == null || userIds.isEmpty()) {
|
||||||
|
return fail("userId 列表不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean result = appUserCacheService.removeByIds(userIds);
|
||||||
|
if (result) {
|
||||||
|
log.info("批量用户缓存删除成功: count={}", userIds.size());
|
||||||
|
return success("批量删除成功");
|
||||||
|
} else {
|
||||||
|
log.warn("批量用户缓存删除失败: count={}", userIds.size());
|
||||||
|
return success("删除失败或用户不存在");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("批量用户缓存删除失败: error={}", e.getMessage());
|
||||||
|
return fail("批量删除失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import com.gxwebsoft.app.service.AppVersionService;
|
||||||
|
import com.gxwebsoft.app.entity.AppVersion;
|
||||||
|
import com.gxwebsoft.app.param.AppVersionParam;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.PageResult;
|
||||||
|
import com.gxwebsoft.common.core.web.BatchParam;
|
||||||
|
import com.gxwebsoft.common.core.annotation.OperationLog;
|
||||||
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用版本发布记录控制器
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-03-28 21:29:44
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "应用版本发布管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/app-version")
|
||||||
|
public class AppVersionController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppVersionService appVersionService;
|
||||||
|
|
||||||
|
@Operation(summary = "分页查询版本记录")
|
||||||
|
@GetMapping("/page")
|
||||||
|
public ApiResult<PageResult<AppVersion>> page(AppVersionParam param) {
|
||||||
|
return success(appVersionService.pageRel(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询全部版本记录")
|
||||||
|
@GetMapping()
|
||||||
|
public ApiResult<List<AppVersion>> list(AppVersionParam param) {
|
||||||
|
return success(appVersionService.listRel(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "根据id查询版本")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResult<AppVersion> get(@PathVariable("id") Integer id) {
|
||||||
|
return success(appVersionService.getByIdRel(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取应用当前版本")
|
||||||
|
@GetMapping("/current/{appId}")
|
||||||
|
public ApiResult<AppVersion> getCurrentVersion(@PathVariable("appId") Long appId) {
|
||||||
|
return success(appVersionService.getCurrentVersion(appId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appVersion:save')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "新增版本(构建中状态)")
|
||||||
|
@PostMapping()
|
||||||
|
public ApiResult<?> save(@RequestBody AppVersion appVersion) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser != null) {
|
||||||
|
appVersion.setUserId(loginUser.getUserId());
|
||||||
|
appVersion.setTenantId(loginUser.getTenantId());
|
||||||
|
}
|
||||||
|
// 默认为构建中状态
|
||||||
|
if (appVersion.getStatus() == null) {
|
||||||
|
appVersion.setStatus(0);
|
||||||
|
}
|
||||||
|
if (appVersion.getEnv() == null) {
|
||||||
|
appVersion.setEnv("production");
|
||||||
|
}
|
||||||
|
appVersion.setIsCurrent(false);
|
||||||
|
if (appVersionService.save(appVersion)) {
|
||||||
|
return success("创建成功");
|
||||||
|
}
|
||||||
|
return fail("创建失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appVersion:update')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "修改版本信息")
|
||||||
|
@PutMapping()
|
||||||
|
public ApiResult<?> update(@RequestBody AppVersion appVersion) {
|
||||||
|
if (appVersionService.updateById(appVersion)) {
|
||||||
|
return success("修改成功");
|
||||||
|
}
|
||||||
|
return fail("修改失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appVersion:update')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "发布版本(将此版本设为当前运行版本)")
|
||||||
|
@PostMapping("/publish/{id}")
|
||||||
|
public ApiResult<?> publish(@PathVariable("id") Long id) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
appVersionService.publish(id, loginUser.getUserId());
|
||||||
|
return success("发布成功");
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appVersion:update')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "回滚到指定版本")
|
||||||
|
@PostMapping("/rollback/{id}")
|
||||||
|
public ApiResult<?> rollback(@PathVariable("id") Long id) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
appVersionService.rollback(id, loginUser.getUserId());
|
||||||
|
return success("回滚成功");
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appVersion:remove')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "删除版本记录")
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResult<?> remove(@PathVariable("id") Integer id) {
|
||||||
|
if (appVersionService.removeById(id)) {
|
||||||
|
return success("删除成功");
|
||||||
|
}
|
||||||
|
return fail("删除失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appVersion:save')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "批量添加版本")
|
||||||
|
@PostMapping("/batch")
|
||||||
|
public ApiResult<?> saveBatch(@RequestBody List<AppVersion> list) {
|
||||||
|
if (appVersionService.saveBatch(list)) {
|
||||||
|
return success("添加成功");
|
||||||
|
}
|
||||||
|
return fail("添加失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appVersion:update')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "批量修改版本")
|
||||||
|
@PutMapping("/batch")
|
||||||
|
public ApiResult<?> updateBatch(@RequestBody BatchParam<AppVersion> batchParam) {
|
||||||
|
if (batchParam.update(appVersionService, "id")) {
|
||||||
|
return success("修改成功");
|
||||||
|
}
|
||||||
|
return fail("修改失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:appVersion:remove')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "批量删除版本记录")
|
||||||
|
@DeleteMapping("/batch")
|
||||||
|
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
|
||||||
|
if (appVersionService.removeByIds(ids)) {
|
||||||
|
return success("删除成功");
|
||||||
|
}
|
||||||
|
return fail("删除失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.gxwebsoft.app.config.AppPayProperties;
|
||||||
|
import com.gxwebsoft.app.entity.AppSubscription;
|
||||||
|
import com.gxwebsoft.app.mapper.AppSubscriptionMapper;
|
||||||
|
import com.gxwebsoft.common.core.utils.RedisUtil;
|
||||||
|
import com.wechat.pay.java.core.cipher.AeadAesCipher;
|
||||||
|
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.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.util.Base64Utils;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付回调通知处理
|
||||||
|
* 路径:POST /api/app/subscription/wx-notify
|
||||||
|
* 由微信支付服务器主动调用(Native 扫码支付成功后会回调此地址)
|
||||||
|
* <p>
|
||||||
|
* 注意:前端同时在轮询 check-status 接口,回调只是加速状态更新。
|
||||||
|
* 即使回调失败,前端轮询也能在 3s 内查到已支付状态。
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/subscription")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Tag(name = "微信支付回调")
|
||||||
|
public class AppWxPayController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppSubscriptionMapper subscriptionMapper;
|
||||||
|
@Resource
|
||||||
|
private RedisUtil redisUtil;
|
||||||
|
@Resource
|
||||||
|
private AppPayProperties appPayProperties;
|
||||||
|
|
||||||
|
@Value("${spring.profiles.active:dev}")
|
||||||
|
private String active;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信 Native 支付回调通知
|
||||||
|
* 微信支付成功后会 POST JSON 到此地址
|
||||||
|
*/
|
||||||
|
@PostMapping("/wx-notify")
|
||||||
|
@Operation(summary = "微信支付回调通知")
|
||||||
|
public String wxNotify(
|
||||||
|
@RequestHeader(value = "Wechatpay-Serial", required = false) String serialNumber,
|
||||||
|
@RequestHeader(value = "Wechatpay-Nonce", required = false) String nonce,
|
||||||
|
@RequestHeader(value = "Wechatpay-Signature", required = false) String signature,
|
||||||
|
@RequestHeader(value = "Wechatpay-Timestamp", required = false) String timestamp,
|
||||||
|
@RequestBody String body
|
||||||
|
) {
|
||||||
|
log.info("收到微信支付回调 — headers: serial={}, timestamp={}, body={}", serialNumber, timestamp, body);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 解析通知(解密报文体)
|
||||||
|
JSONObject notification = parseNotification(body);
|
||||||
|
if (notification == null) {
|
||||||
|
log.error("通知解析失败");
|
||||||
|
return failResult("notification parse failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 提取关键字段
|
||||||
|
String eventType = notification.getString("event_type");
|
||||||
|
JSONObject resource = notification.getJSONObject("resource");
|
||||||
|
if (resource == null) {
|
||||||
|
log.error("通知报文体中无 resource 字段");
|
||||||
|
return failResult("no resource");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解密资源
|
||||||
|
String ciphertext = resource.getString("ciphertext");
|
||||||
|
String nonceStr = resource.getString("nonce");
|
||||||
|
String associatedData = resource.getString("associated_data");
|
||||||
|
String apiV3Key = getApiV3Key();
|
||||||
|
|
||||||
|
String plainText;
|
||||||
|
try {
|
||||||
|
plainText = decryptResource(ciphertext, apiV3Key, nonceStr, associatedData);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("资源解密失败: {}", e.getMessage());
|
||||||
|
return failResult("decrypt failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject tradeData = JSON.parseObject(plainText);
|
||||||
|
String outTradeNo = tradeData.getString("out_trade_no");
|
||||||
|
String tradeState = tradeData.getString("trade_state");
|
||||||
|
String transactionId = tradeData.getString("transaction_id");
|
||||||
|
BigDecimal totalAmount = tradeData.getBigDecimal("amount") != null
|
||||||
|
? tradeData.getJSONObject("amount").getBigDecimal("payer_total") : BigDecimal.ZERO;
|
||||||
|
|
||||||
|
log.info("解密后 — outTradeNo={}, tradeState={}, transactionId={}, amount={}",
|
||||||
|
outTradeNo, tradeState, transactionId, totalAmount);
|
||||||
|
|
||||||
|
// 3. 仅处理支付成功事件
|
||||||
|
if (!"TRANSACTION.SUCCESS".equals(eventType) && !"SUCCESS".equals(tradeState)) {
|
||||||
|
log.warn("非成功回调,忽略 — eventType: {}, tradeState: {}", eventType, tradeState);
|
||||||
|
return successResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 查询并更新订阅记录
|
||||||
|
LambdaQueryWrapper<AppSubscription> query = new LambdaQueryWrapper<>();
|
||||||
|
query.eq(AppSubscription::getSubscriptionNo, outTradeNo);
|
||||||
|
AppSubscription sub = subscriptionMapper.selectOne(query);
|
||||||
|
|
||||||
|
if (sub == null) {
|
||||||
|
log.warn("未找到订阅记录 outTradeNo: {}", outTradeNo);
|
||||||
|
return successResult(); // 返回成功,避免微信重复回调
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sub.getPayStatus() != null && sub.getPayStatus() == 1) {
|
||||||
|
log.info("订阅 {} 已支付,跳过重复处理", outTradeNo);
|
||||||
|
return successResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 更新状态
|
||||||
|
sub.setPayStatus(1);
|
||||||
|
sub.setStatus("active");
|
||||||
|
sub.setPayTime(LocalDateTime.now());
|
||||||
|
sub.setTransactionId(transactionId);
|
||||||
|
sub.setPayType(1); // 1=微信支付
|
||||||
|
sub.setStartTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
// 设置到期时间(订阅型)
|
||||||
|
if ("subscription".equals(sub.getPriceType())) {
|
||||||
|
int months = "year".equals(sub.getSubscriptionPeriod()) ? 12 : 1;
|
||||||
|
sub.setExpireTime(LocalDateTime.now().plusMonths(months));
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriptionMapper.updateById(sub);
|
||||||
|
log.info("订阅 {} 支付成功,状态已更新", outTradeNo);
|
||||||
|
|
||||||
|
// 6. 写入 Redis,加速前端轮询感知
|
||||||
|
redisUtil.set("wxpay:paid:" + outTradeNo, "1", Duration.ofHours(24));
|
||||||
|
|
||||||
|
return successResult();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("微信支付回调处理异常: {}", e.getMessage(), e);
|
||||||
|
return failResult("internal error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析通知(V3 API 使用 AES-256-GCM 解密 resource 字段)
|
||||||
|
*/
|
||||||
|
private JSONObject parseNotification(String body) {
|
||||||
|
try {
|
||||||
|
return JSON.parseObject(body);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("JSON 解析失败: {}", body);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES-256-GCM 解密微信 V3 通知 resource.ciphertext
|
||||||
|
* - resource.nonce:Base64 编码的 12 字节随机数
|
||||||
|
* - resource.ciphertext:Base64(AEAD_AES_256_GCM(nonce + plaintext))
|
||||||
|
* 其中 ciphertext 末尾 16 字节为 tag
|
||||||
|
*/
|
||||||
|
private String decryptResource(String ciphertext, String apiV3Key, String nonce, String associatedData)
|
||||||
|
throws Exception {
|
||||||
|
byte[] key = apiV3Key.getBytes(StandardCharsets.UTF_8);
|
||||||
|
// nonce 是 Base64 编码的 12 字节随机数,需要解码
|
||||||
|
byte[] nonceBytes = Base64Utils.decodeFromString(nonce);
|
||||||
|
// associated_data 原样 UTF-8 编码
|
||||||
|
byte[] aad = (associatedData == null ? "" : associatedData).getBytes(StandardCharsets.UTF_8);
|
||||||
|
// ciphertext = Base64(ciphertext_bytes),其中末尾 16 字节为 auth tag
|
||||||
|
byte[] cipherBytes = Base64Utils.decodeFromString(ciphertext);
|
||||||
|
|
||||||
|
// 新版 SDK:AeadAesCipher.decrypt(nonce, associatedData, ciphertextWithTag)
|
||||||
|
AeadAesCipher cipher = new AeadAesCipher(key);
|
||||||
|
return cipher.decrypt(nonceBytes, aad, cipherBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 APIv3 密钥(根据环境选择正式/测试配置)
|
||||||
|
*/
|
||||||
|
private String getApiV3Key() {
|
||||||
|
if ("dev".equals(active) && appPayProperties.isTestMode()) {
|
||||||
|
String key = appPayProperties.getTestApiV3Key();
|
||||||
|
return key != null ? key : appPayProperties.getApiV3Key();
|
||||||
|
}
|
||||||
|
return appPayProperties.getApiV3Key();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String successResult() {
|
||||||
|
return "{\"code\":\"SUCCESS\",\"message\":\"OK\"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String failResult(String msg) {
|
||||||
|
return "{\"code\":\"FAIL\",\"message\":\"" + msg + "\"}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.app.entity.AppBuild;
|
||||||
|
import com.gxwebsoft.app.entity.AppPipeline;
|
||||||
|
import com.gxwebsoft.app.param.AppBuildParam;
|
||||||
|
import com.gxwebsoft.app.param.AppPipelineParam;
|
||||||
|
import com.gxwebsoft.app.service.AppBuildService;
|
||||||
|
import com.gxwebsoft.app.service.AppPipelineService;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import com.gxwebsoft.common.core.web.PageResult;
|
||||||
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CI/CD 控制器
|
||||||
|
* 支持 Jenkins / GitHub Actions / Gitea CI
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-03
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "CI/CD管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/cicd")
|
||||||
|
public class CICDController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppBuildService appBuildService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppPipelineService appPipelineService;
|
||||||
|
|
||||||
|
// ========== 流水线接口 ==========
|
||||||
|
|
||||||
|
@Operation(summary = "分页查询流水线")
|
||||||
|
@GetMapping("/pipeline/page")
|
||||||
|
public ApiResult<PageResult<AppPipeline>> pagePipeline(AppPipelineParam param) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser != null) {
|
||||||
|
param.setUserId(loginUser.getUserId());
|
||||||
|
}
|
||||||
|
return success(appPipelineService.pagePipeline(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询应用的所有流水线")
|
||||||
|
@GetMapping("/pipeline/app/{appId}")
|
||||||
|
public ApiResult<List<AppPipeline>> listByApp(@PathVariable Long appId) {
|
||||||
|
return success(appPipelineService.getByAppId(appId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询流水线详情")
|
||||||
|
@GetMapping("/pipeline/{id}")
|
||||||
|
public ApiResult<AppPipeline> getPipeline(@PathVariable Long id) {
|
||||||
|
return success(appPipelineService.getPipelineDetail(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:cicd:pipeline:save')")
|
||||||
|
@Operation(summary = "创建流水线")
|
||||||
|
@PostMapping("/pipeline")
|
||||||
|
public ApiResult<?> createPipeline(@RequestBody AppPipeline pipeline) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser != null) {
|
||||||
|
pipeline.setUserId(loginUser.getUserId());
|
||||||
|
pipeline.setTenantId(loginUser.getTenantId());
|
||||||
|
}
|
||||||
|
if (appPipelineService.createPipeline(pipeline)) {
|
||||||
|
return success("创建成功");
|
||||||
|
}
|
||||||
|
return fail("创建失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:cicd:pipeline:update')")
|
||||||
|
@Operation(summary = "更新流水线")
|
||||||
|
@PutMapping("/pipeline")
|
||||||
|
public ApiResult<?> updatePipeline(@RequestBody AppPipeline pipeline) {
|
||||||
|
if (appPipelineService.updatePipeline(pipeline)) {
|
||||||
|
return success("更新成功");
|
||||||
|
}
|
||||||
|
return fail("更新失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:cicd:pipeline:remove')")
|
||||||
|
@Operation(summary = "删除流水线")
|
||||||
|
@DeleteMapping("/pipeline/{id}")
|
||||||
|
public ApiResult<?> deletePipeline(@PathVariable Long id) {
|
||||||
|
if (appPipelineService.deletePipeline(id)) {
|
||||||
|
return success("删除成功");
|
||||||
|
}
|
||||||
|
return fail("删除失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:cicd:pipeline:update')")
|
||||||
|
@Operation(summary = "启用/禁用流水线")
|
||||||
|
@PostMapping("/pipeline/{id}/toggle")
|
||||||
|
public ApiResult<?> togglePipeline(@PathVariable Long id, @RequestParam boolean enabled) {
|
||||||
|
if (appPipelineService.togglePipeline(id, enabled)) {
|
||||||
|
return success(enabled ? "已启用" : "已禁用");
|
||||||
|
}
|
||||||
|
return fail("操作失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取流水线状态")
|
||||||
|
@GetMapping("/pipeline/{id}/status")
|
||||||
|
public ApiResult<Map<String, Object>> getPipelineStatus(@PathVariable Long id) {
|
||||||
|
AppPipeline pipeline = appPipelineService.getById(id);
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("id", id);
|
||||||
|
result.put("status", appPipelineService.getPipelineStatus(id));
|
||||||
|
result.put("lastBuildId", pipeline != null ? pipeline.getLastBuildId() : null);
|
||||||
|
result.put("lastBuildTime", pipeline != null ? pipeline.getLastBuildTime() : null);
|
||||||
|
result.put("successCount", pipeline != null ? pipeline.getSuccessCount() : 0);
|
||||||
|
result.put("failureCount", pipeline != null ? pipeline.getFailureCount() : 0);
|
||||||
|
return success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 构建接口 ==========
|
||||||
|
|
||||||
|
@Operation(summary = "分页查询构建记录")
|
||||||
|
@GetMapping("/build/page")
|
||||||
|
public ApiResult<PageResult<AppBuild>> pageBuild(AppBuildParam param) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser != null) {
|
||||||
|
param.setUserId(loginUser.getUserId());
|
||||||
|
}
|
||||||
|
return success(appBuildService.pageBuild(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询应用的所有构建记录")
|
||||||
|
@GetMapping("/build/app/{appId}")
|
||||||
|
public ApiResult<List<AppBuild>> listBuildByApp(@PathVariable Long appId) {
|
||||||
|
AppBuildParam param = new AppBuildParam();
|
||||||
|
param.setAppId(appId);
|
||||||
|
return success(appBuildService.pageBuild(param).getList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询构建详情")
|
||||||
|
@GetMapping("/build/{id}")
|
||||||
|
public ApiResult<AppBuild> getBuild(@PathVariable Long id) {
|
||||||
|
return success(appBuildService.getBuildDetail(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取应用最新构建")
|
||||||
|
@GetMapping("/build/latest/{appId}")
|
||||||
|
public ApiResult<AppBuild> getLatestBuild(@PathVariable Long appId) {
|
||||||
|
return success(appBuildService.getLatestBuild(appId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:cicd:build:save')")
|
||||||
|
@Operation(summary = "触发构建")
|
||||||
|
@PostMapping("/build/trigger")
|
||||||
|
public ApiResult<Map<String, Object>> triggerBuild(
|
||||||
|
@RequestParam Long appId,
|
||||||
|
@RequestParam(required = false) String branch) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
Integer triggeredBy = loginUser != null ? loginUser.getUserId() : null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
AppBuild build = appBuildService.triggerBuild(appId, branch, triggeredBy);
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("id", build.getId());
|
||||||
|
result.put("buildNumber", build.getBuildNumber());
|
||||||
|
result.put("status", build.getStatus());
|
||||||
|
result.put("branch", build.getBranch());
|
||||||
|
return success("构建已触发", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取构建日志")
|
||||||
|
@GetMapping("/build/{id}/log")
|
||||||
|
public ApiResult<Map<String, Object>> getBuildLog(@PathVariable Long id) {
|
||||||
|
try {
|
||||||
|
String log = appBuildService.getBuildLog(id);
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("log", log);
|
||||||
|
result.put("buildId", id);
|
||||||
|
return success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:cicd:build:update')")
|
||||||
|
@Operation(summary = "取消构建")
|
||||||
|
@PostMapping("/build/{id}/cancel")
|
||||||
|
public ApiResult<?> cancelBuild(@PathVariable Long id) {
|
||||||
|
try {
|
||||||
|
if (appBuildService.cancelBuild(id)) {
|
||||||
|
return success("构建已取消");
|
||||||
|
}
|
||||||
|
return fail("取消失败");
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAuthority('app:cicd:build:save')")
|
||||||
|
@Operation(summary = "重试构建")
|
||||||
|
@PostMapping("/build/{id}/retry")
|
||||||
|
public ApiResult<Map<String, Object>> retryBuild(@PathVariable Long id) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
Integer triggeredBy = loginUser != null ? loginUser.getUserId() : null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
AppBuild build = appBuildService.retryBuild(id, triggeredBy);
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("id", build.getId());
|
||||||
|
result.put("buildNumber", build.getBuildNumber());
|
||||||
|
result.put("status", build.getStatus());
|
||||||
|
return success("构建已重试", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取构建统计")
|
||||||
|
@GetMapping("/build/stats/{appId}")
|
||||||
|
public ApiResult<Map<String, Integer>> getBuildStats(@PathVariable Long appId) {
|
||||||
|
return success(appBuildService.getBuildStats(appId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Webhook 回调接口 ==========
|
||||||
|
|
||||||
|
@Operation(summary = "Gitea Webhook 回调")
|
||||||
|
@PostMapping("/webhook/gitea")
|
||||||
|
public ApiResult<?> giteaWebhook(@RequestBody Map<String, Object> payload) {
|
||||||
|
try {
|
||||||
|
appBuildService.handleWebhook("gitea", payload);
|
||||||
|
return success("回调处理成功");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Gitea webhook处理失败: {}", e.getMessage());
|
||||||
|
return fail("处理失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取CI系统配置")
|
||||||
|
@GetMapping("/config")
|
||||||
|
public ApiResult<Map<String, Object>> getCIConfig() {
|
||||||
|
Map<String, Object> config = new HashMap<>();
|
||||||
|
config.put("supportedCI", new String[]{"gitea", "jenkins", "github"});
|
||||||
|
config.put("defaultCI", "gitea");
|
||||||
|
config.put("giteaUrl", "https://git.websoft.top");
|
||||||
|
return success(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
package com.gxwebsoft.app.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.app.entity.AppGitAccount;
|
||||||
|
import com.gxwebsoft.app.service.AppGitAccountService;
|
||||||
|
import com.gxwebsoft.common.core.annotation.OperationLog;
|
||||||
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Git账号绑定控制器
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-02
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "Git账号绑定")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/app/developer")
|
||||||
|
public class GitAccountController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AppGitAccountService appGitAccountService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存Git账号绑定信息
|
||||||
|
*/
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "保存Git账号绑定")
|
||||||
|
@PostMapping("/git-account")
|
||||||
|
public ApiResult<Map<String, Object>> saveGitAccount(@RequestBody Map<String, String> params) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
String username = params.get("username");
|
||||||
|
String email = params.get("email");
|
||||||
|
String remark = params.get("remark");
|
||||||
|
|
||||||
|
try {
|
||||||
|
AppGitAccount account = appGitAccountService.saveGitAccount(username, email, remark, loginUser.getUserId(), loginUser.getTenantId());
|
||||||
|
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("userId", account.getUserId());
|
||||||
|
result.put("gitUsername", account.getUsername());
|
||||||
|
result.put("email", account.getEmail());
|
||||||
|
result.put("remark", account.getRemark());
|
||||||
|
result.put("savedAt", account.getUpdateTime() != null ? account.getUpdateTime().toString() : account.getCreateTime().toString());
|
||||||
|
result.put("status", account.getStatus());
|
||||||
|
|
||||||
|
return success("Git账号绑定成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("保存Git账号绑定失败: {}", e.getMessage());
|
||||||
|
return fail(e.getMessage(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Git账号绑定状态
|
||||||
|
*/
|
||||||
|
@Operation(summary = "获取Git账号绑定状态")
|
||||||
|
@GetMapping({"/git-account", "/git-account/status"})
|
||||||
|
public ApiResult<Map<String, Object>> getGitAccountStatus() {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
AppGitAccount account = appGitAccountService.getGitAccountStatus(loginUser.getUserId(), loginUser.getTenantId());
|
||||||
|
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
if (account != null && account.getStatus() != null && !"not_bound".equals(account.getStatus())) {
|
||||||
|
result.put("username", account.getUsername());
|
||||||
|
result.put("email", account.getEmail());
|
||||||
|
result.put("remark", account.getRemark());
|
||||||
|
result.put("status", account.getStatus());
|
||||||
|
result.put("verificationNote", account.getVerificationNote());
|
||||||
|
if (account.getUpdateTime() != null) {
|
||||||
|
result.put("lastUpdatedAt", account.getUpdateTime().toString());
|
||||||
|
} else if (account.getCreateTime() != null) {
|
||||||
|
result.put("lastUpdatedAt", account.getCreateTime().toString());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.put("status", "not_bound");
|
||||||
|
}
|
||||||
|
|
||||||
|
return success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Gitea服务器信息(静态配置)
|
||||||
|
*/
|
||||||
|
@Operation(summary = "获取Gitea服务器信息")
|
||||||
|
@GetMapping("/gitea-info")
|
||||||
|
public ApiResult<Map<String, Object>> getGiteaInfo() {
|
||||||
|
// 这里返回静态配置,后续可以从配置文件中读取
|
||||||
|
Map<String, Object> info = new HashMap<>();
|
||||||
|
info.put("url", "https://git.websoft.top");
|
||||||
|
info.put("version", "1.21");
|
||||||
|
info.put("registrationEnabled", true);
|
||||||
|
info.put("requireEmailConfirmation", false);
|
||||||
|
info.put("maxRepoCreation", 100);
|
||||||
|
return success(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 管理端接口 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询Git账号绑定列表(管理端)
|
||||||
|
*/
|
||||||
|
@Operation(summary = "分页查询Git账号绑定列表(管理端)")
|
||||||
|
@GetMapping("/git-account/list")
|
||||||
|
public ApiResult<Map<String, Object>> pageGitAccounts(
|
||||||
|
@RequestParam(defaultValue = "1") int page,
|
||||||
|
@RequestParam(defaultValue = "20") int size,
|
||||||
|
@RequestParam(required = false) String status,
|
||||||
|
@RequestParam(required = false) String keyword) {
|
||||||
|
Map<String, Object> result = appGitAccountService.pageGitAccounts(status, keyword, page, size);
|
||||||
|
return success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核Git账号绑定 - 通过
|
||||||
|
*/
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "审核Git账号绑定-通过")
|
||||||
|
@PutMapping("/git-account/{id}/approve")
|
||||||
|
public ApiResult<?> approveAccount(@PathVariable("id") Long id, @RequestBody(required = false) Map<String, String> params) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String note = params != null ? params.get("note") : null;
|
||||||
|
boolean result = appGitAccountService.approveAccount(id, loginUser.getUserId(), loginUser.getRealName(), note);
|
||||||
|
return result ? success("审核通过") : fail("审核失败");
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核Git账号绑定 - 拒绝
|
||||||
|
*/
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "审核Git账号绑定-拒绝")
|
||||||
|
@PutMapping("/git-account/{id}/reject")
|
||||||
|
public ApiResult<?> rejectAccount(@PathVariable("id") Long id, @RequestBody Map<String, String> params) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return fail("请先登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
String reason = params != null ? params.get("reason") : null;
|
||||||
|
if (reason == null || reason.trim().isEmpty()) {
|
||||||
|
return fail("请填写拒绝原因");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean result = appGitAccountService.rejectAccount(id, loginUser.getUserId(), loginUser.getRealName(), reason.trim());
|
||||||
|
return result ? success("已拒绝该绑定申请") : fail("拒绝失败");
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用 API Key 实体
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-02
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@Schema(name = "AppApiKey对象", description = "应用API密钥")
|
||||||
|
public class AppApiKey implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "自增主键")
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "API Key名称")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "API Key密钥值(加密存储)")
|
||||||
|
private String apiKey;
|
||||||
|
|
||||||
|
@Schema(description = "密钥前缀(用于显示,如 sk-xxxxx)")
|
||||||
|
private String keyPrefix;
|
||||||
|
|
||||||
|
@Schema(description = "状态: 0正常, 1禁用")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description = "权限范围,JSON数组字符串")
|
||||||
|
private String scopes;
|
||||||
|
|
||||||
|
@Schema(description = "到期时间,NULL=永不过期")
|
||||||
|
private LocalDateTime expireTime;
|
||||||
|
|
||||||
|
@Schema(description = "最后使用时间")
|
||||||
|
private LocalDateTime lastUsedAt;
|
||||||
|
|
||||||
|
@Schema(description = "使用次数")
|
||||||
|
private Long usageCount;
|
||||||
|
|
||||||
|
@Schema(description = "备注")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Schema(description = "是否删除, 0否, 1是")
|
||||||
|
@TableLogic
|
||||||
|
private Integer deleted;
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "租户ID")
|
||||||
|
private Integer tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "修改时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台文章
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@TableName("app_article")
|
||||||
|
@Schema(name = "AppArticle对象", description = "平台文章")
|
||||||
|
public class AppArticle implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "文章ID")
|
||||||
|
@TableId(value = "article_id", type = IdType.AUTO)
|
||||||
|
private Integer articleId;
|
||||||
|
|
||||||
|
@Schema(description = "文章标题")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Schema(description = "文章类型 0常规 1视频")
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
@Schema(description = "文章模型,article 普通文章,announcement 公告")
|
||||||
|
private String model;
|
||||||
|
|
||||||
|
@Schema(description = "文章编号")
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
@Schema(description = "分类ID")
|
||||||
|
private Integer categoryId;
|
||||||
|
|
||||||
|
@Schema(description = "分类名称")
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String categoryName;
|
||||||
|
|
||||||
|
@Schema(description = "父级分类ID")
|
||||||
|
@TableField(exist = false)
|
||||||
|
private Integer parentId;
|
||||||
|
|
||||||
|
@Schema(description = "封面图")
|
||||||
|
private String image;
|
||||||
|
|
||||||
|
@Schema(description = "来源")
|
||||||
|
private String source;
|
||||||
|
|
||||||
|
@Schema(description = "摘要")
|
||||||
|
private String overview;
|
||||||
|
|
||||||
|
@Schema(description = "正文内容")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@Schema(description = "实际阅读量")
|
||||||
|
private Integer actualViews;
|
||||||
|
|
||||||
|
@Schema(description = "点赞数")
|
||||||
|
private Integer likes;
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "作者")
|
||||||
|
private String author;
|
||||||
|
|
||||||
|
@Schema(description = "是否推荐")
|
||||||
|
private Integer recommend;
|
||||||
|
|
||||||
|
@Schema(description = "排序值")
|
||||||
|
private Integer sortNumber;
|
||||||
|
|
||||||
|
@Schema(description = "状态,0已发布 1草稿/待审核 2已驳回 3违规")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description = "是否删除,0否 1是")
|
||||||
|
@TableLogic
|
||||||
|
private Integer deleted;
|
||||||
|
|
||||||
|
@Schema(description = "租户ID")
|
||||||
|
private Integer tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台文章分类
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@TableName("app_article_category")
|
||||||
|
@Schema(name = "AppArticleCategory对象", description = "平台文章分类")
|
||||||
|
public class AppArticleCategory implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "分类ID")
|
||||||
|
@TableId(value = "category_id", type = IdType.AUTO)
|
||||||
|
private Integer categoryId;
|
||||||
|
|
||||||
|
@Schema(description = "分类标识")
|
||||||
|
private String categoryCode;
|
||||||
|
|
||||||
|
@Schema(description = "分类名称")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Schema(description = "类型 0列表 1单页 2外链")
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
@Schema(description = "分类图片")
|
||||||
|
private String image;
|
||||||
|
|
||||||
|
@Schema(description = "上级分类ID")
|
||||||
|
private Integer parentId;
|
||||||
|
|
||||||
|
@Schema(description = "访问路径")
|
||||||
|
private String path;
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "文章数")
|
||||||
|
@TableField(exist = false)
|
||||||
|
private Integer count;
|
||||||
|
|
||||||
|
@Schema(description = "排序值")
|
||||||
|
private Integer sortNumber;
|
||||||
|
|
||||||
|
@Schema(description = "备注")
|
||||||
|
private String comments;
|
||||||
|
|
||||||
|
@Schema(description = "是否隐藏")
|
||||||
|
private Integer hide;
|
||||||
|
|
||||||
|
@Schema(description = "是否推荐")
|
||||||
|
private Integer recommend;
|
||||||
|
|
||||||
|
@Schema(description = "是否显示在首页")
|
||||||
|
private Integer showIndex;
|
||||||
|
|
||||||
|
@Schema(description = "状态 0正常 1禁用")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description = "是否删除,0否 1是")
|
||||||
|
@TableLogic
|
||||||
|
private Integer deleted;
|
||||||
|
|
||||||
|
@Schema(description = "租户ID")
|
||||||
|
private Integer tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
118
jczxw-java/src/main/java/com/gxwebsoft/app/entity/AppBuild.java
Normal file
118
jczxw-java/src/main/java/com/gxwebsoft/app/entity/AppBuild.java
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CI/CD 构建记录实体
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@Schema(name = "AppBuild对象", description = "CI/CD构建记录")
|
||||||
|
@TableName("app_build")
|
||||||
|
public class AppBuild implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "自增ID")
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "关联应用ID")
|
||||||
|
private Long appId;
|
||||||
|
|
||||||
|
@Schema(description = "关联版本ID(可选)")
|
||||||
|
private Long versionId;
|
||||||
|
|
||||||
|
@Schema(description = "构建编号(如 run-123)")
|
||||||
|
private String buildNumber;
|
||||||
|
|
||||||
|
@Schema(description = "构建名称")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "触发分支(如 main、develop)")
|
||||||
|
private String branch;
|
||||||
|
|
||||||
|
@Schema(description = "提交哈希(可选)")
|
||||||
|
private String commitSha;
|
||||||
|
|
||||||
|
@Schema(description = "提交消息(可选)")
|
||||||
|
private String commitMessage;
|
||||||
|
|
||||||
|
@Schema(description = "提交作者(可选)")
|
||||||
|
private String commitAuthor;
|
||||||
|
|
||||||
|
@Schema(description = "CI系统类型: gitea/jenkins/github")
|
||||||
|
private String ciType;
|
||||||
|
|
||||||
|
@Schema(description = "CI系统中的任务ID")
|
||||||
|
private String ciJobId;
|
||||||
|
|
||||||
|
@Schema(description = "CI系统中的运行ID")
|
||||||
|
private String ciRunId;
|
||||||
|
|
||||||
|
@Schema(description = "CI系统API地址")
|
||||||
|
private String ciApiUrl;
|
||||||
|
|
||||||
|
@Schema(description = "状态: pending/running/success/failed/cancelled")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "构建开始时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime startedAt;
|
||||||
|
|
||||||
|
@Schema(description = "构建结束时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime finishedAt;
|
||||||
|
|
||||||
|
@Schema(description = "构建耗时(秒)")
|
||||||
|
private Integer duration;
|
||||||
|
|
||||||
|
@Schema(description = "构建日志URL")
|
||||||
|
private String logUrl;
|
||||||
|
|
||||||
|
@Schema(description = "构建产物URL(如 JAR、Docker镜像)")
|
||||||
|
private String artifactUrl;
|
||||||
|
|
||||||
|
@Schema(description = "构建产物名称")
|
||||||
|
private String artifactName;
|
||||||
|
|
||||||
|
@Schema(description = "构建产物大小(字节)")
|
||||||
|
private Long artifactSize;
|
||||||
|
|
||||||
|
@Schema(description = "失败原因")
|
||||||
|
private String errorMessage;
|
||||||
|
|
||||||
|
@Schema(description = "触发方式: manual/webhook/schedule")
|
||||||
|
private String triggerType;
|
||||||
|
|
||||||
|
@Schema(description = "触发人用户ID")
|
||||||
|
private Integer triggeredBy;
|
||||||
|
|
||||||
|
@Schema(description = "扩展配置(JSON)")
|
||||||
|
private String config;
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "租户id")
|
||||||
|
private Integer tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "修改时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 云账号凭证(阿里云/腾讯云/华为云等)
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-04
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@TableName("app_cloud_credential")
|
||||||
|
@Schema(name = "AppCloudCredential对象", description = "云账号凭证")
|
||||||
|
public class AppCloudCredential implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "自增ID")
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/** 云服务商: aliyun/tencent/huawei/qiniu */
|
||||||
|
private String provider;
|
||||||
|
|
||||||
|
/** 凭证名称 */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 访问密钥 ID (AK) */
|
||||||
|
private String accessKeyId;
|
||||||
|
|
||||||
|
/** 访问密钥密钥 (SK),AES加密存储 */
|
||||||
|
private String accessKeySecret;
|
||||||
|
|
||||||
|
/** 额外配置,JSON格式 */
|
||||||
|
private String configJson;
|
||||||
|
|
||||||
|
/** 状态: 0正常 1冻结 */
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
/** 备注 */
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/** 所属用户ID */
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
/** 租户ID */
|
||||||
|
private Integer tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "是否删除: 0否 1是")
|
||||||
|
private Integer deleted;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
/** 测试状态: 0未测试 1成功 2失败 */
|
||||||
|
private Integer testStatus;
|
||||||
|
|
||||||
|
/** 测试消息 */
|
||||||
|
private String testMessage;
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用配置表
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@TableName("app_config")
|
||||||
|
public class AppConfig implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "config_id", type = IdType.AUTO)
|
||||||
|
private Integer configId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用ID
|
||||||
|
*/
|
||||||
|
private Integer appId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置键
|
||||||
|
*/
|
||||||
|
private String configKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置值(JSON或字符串)
|
||||||
|
*/
|
||||||
|
private String configValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置类型:general/api/callback/wechat/payment/git等
|
||||||
|
*/
|
||||||
|
private String configType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否加密 0否 1是
|
||||||
|
*/
|
||||||
|
private Integer isEncrypted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否敏感信息 0否 1是
|
||||||
|
*/
|
||||||
|
private Integer isSecret;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置说明
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序号
|
||||||
|
*/
|
||||||
|
private Integer sortNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户id
|
||||||
|
*/
|
||||||
|
private Long tenantId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
private Long createdTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
private Long updatedTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否删除 0否 1是
|
||||||
|
*/
|
||||||
|
@TableLogic
|
||||||
|
private Integer deleted;
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合同管理
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-13
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@TableName("app_contract")
|
||||||
|
@Schema(name = "AppContract对象", description = "合同管理")
|
||||||
|
public class AppContract implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "合同ID")
|
||||||
|
@TableId(value = "contract_id", type = IdType.AUTO)
|
||||||
|
private Long contractId;
|
||||||
|
|
||||||
|
@Schema(description = "合同编号")
|
||||||
|
private String contractNo;
|
||||||
|
|
||||||
|
@Schema(description = "合同名称")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Schema(description = "合同类型: service/cooperation/purchase/other")
|
||||||
|
private String contractType;
|
||||||
|
|
||||||
|
@Schema(description = "甲方名称")
|
||||||
|
private String partyA;
|
||||||
|
|
||||||
|
@Schema(description = "乙方名称")
|
||||||
|
private String partyB;
|
||||||
|
|
||||||
|
@Schema(description = "合同金额")
|
||||||
|
private BigDecimal amount;
|
||||||
|
|
||||||
|
@Schema(description = "合同开始日期")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
|
private LocalDate startDate;
|
||||||
|
|
||||||
|
@Schema(description = "合同结束日期")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
|
private LocalDate endDate;
|
||||||
|
|
||||||
|
@Schema(description = "状态: draft/pending/active/expired/terminated")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "合同附件URL")
|
||||||
|
private String fileUrl;
|
||||||
|
|
||||||
|
@Schema(description = "合同附件原始文件名")
|
||||||
|
private String fileName;
|
||||||
|
|
||||||
|
@Schema(description = "备注")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Schema(description = "创建用户ID")
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "创建用户名(冗余)")
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
@Schema(description = "是否删除: 0否 1是")
|
||||||
|
private Integer deleted;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
@Schema(description = "租户ID")
|
||||||
|
private Integer tenantId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用密钥凭证
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-03-28 21:29:43
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@Schema(name = "AppCredential对象", description = "应用密钥凭证")
|
||||||
|
public class AppCredential implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "自增ID")
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "关联应用ID(AppProduct.productId)")
|
||||||
|
private String appId;
|
||||||
|
|
||||||
|
@Schema(description = "OAuth Client ID(公开,格式:app_xxxxxxxxxxxx)")
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
@Schema(description = "OAuth Client Secret(加密存储)")
|
||||||
|
private String clientSecret;
|
||||||
|
|
||||||
|
@Schema(description = "凭证类型: server/client/webhook")
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
@Schema(description = "权限范围,空格分隔")
|
||||||
|
private String scopes;
|
||||||
|
|
||||||
|
@Schema(description = "到期时间,NULL=永不过期")
|
||||||
|
private LocalDateTime expireTime;
|
||||||
|
|
||||||
|
@Schema(description = "最后使用时间")
|
||||||
|
private LocalDateTime lastUsedAt;
|
||||||
|
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Schema(description = "排序(数字越小越靠前)")
|
||||||
|
private Integer sortNumber;
|
||||||
|
|
||||||
|
@Schema(description = "状态, 0正常, 1冻结")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description = "是否删除, 0否, 1是")
|
||||||
|
@TableLogic
|
||||||
|
private Integer deleted;
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "租户id")
|
||||||
|
private Integer tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "修改时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用操作动态
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-03-28 21:29:44
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@Schema(name = "AppEvent对象", description = "应用操作动态")
|
||||||
|
public class AppEvent implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "自增ID")
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "关联应用ID")
|
||||||
|
private Long appId;
|
||||||
|
|
||||||
|
@Schema(description = "事件类型: created/published/updated/domain_bound/member_added/status_changed")
|
||||||
|
private String eventType;
|
||||||
|
|
||||||
|
@Schema(description = "事件标题,如已发布")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Schema(description = "详细描述")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@Schema(description = "操作人用户ID")
|
||||||
|
private Long operatorId;
|
||||||
|
|
||||||
|
@Schema(description = "操作人名称(冗余)")
|
||||||
|
private String operator;
|
||||||
|
|
||||||
|
@Schema(description = "关联ID,如版本ID")
|
||||||
|
private Long refId;
|
||||||
|
|
||||||
|
@Schema(description = "关联类型")
|
||||||
|
private String refType;
|
||||||
|
|
||||||
|
@Schema(description = "扩展数据")
|
||||||
|
private String extra;
|
||||||
|
|
||||||
|
@Schema(description = "排序(数字越小越靠前)")
|
||||||
|
private Integer sortNumber;
|
||||||
|
|
||||||
|
@Schema(description = "状态, 0正常, 1冻结")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "租户id")
|
||||||
|
private Integer tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "修改时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Git账号绑定(开发者Gitea账号)
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-02
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@Schema(name = "AppGitAccount对象", description = "Git账号绑定(开发者Gitea账号)")
|
||||||
|
@TableName("app_git_account")
|
||||||
|
public class AppGitAccount implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "自增ID")
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "Gitea用户名(唯一)")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Schema(description = "联系邮箱")
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@Schema(description = "备注")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Schema(description = "状态: pending待审核/verified已通过/rejected已拒绝")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "审核备注")
|
||||||
|
private String verificationNote;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
@Schema(description = "租户ID")
|
||||||
|
private Integer tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "是否删除, 0否, 1是")
|
||||||
|
@TableLogic
|
||||||
|
private Integer deleted;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用邀请Token实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("app_invite_token")
|
||||||
|
public class AppInviteToken {
|
||||||
|
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邀请token
|
||||||
|
*/
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用ID
|
||||||
|
*/
|
||||||
|
private Integer appId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邀请角色
|
||||||
|
*/
|
||||||
|
private String role;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邀请人ID
|
||||||
|
*/
|
||||||
|
private Integer inviterId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过期时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime expireTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态:0-未使用,1-已使用
|
||||||
|
*/
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接受人ID
|
||||||
|
*/
|
||||||
|
private Integer acceptUserId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接受时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime acceptTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 站内消息通知
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@TableName("app_notification")
|
||||||
|
@Schema(name = "AppNotification对象", description = "站内消息通知")
|
||||||
|
public class AppNotification implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "通知ID")
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "接收用户ID")
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "通知类型: ticket/review/system/resource/permission/member/payment")
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
@Schema(description = "通知标题")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Schema(description = "通知内容摘要")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@Schema(description = "是否已读: 0未读 1已读")
|
||||||
|
private Integer isRead;
|
||||||
|
|
||||||
|
@Schema(description = "关联业务ID(如工单ID、权限申请ID等)")
|
||||||
|
private Long refId;
|
||||||
|
|
||||||
|
@Schema(description = "关联业务类型(如 ticket、permission_request 等)")
|
||||||
|
private String refType;
|
||||||
|
|
||||||
|
@Schema(description = "跳转链接")
|
||||||
|
private String linkUrl;
|
||||||
|
|
||||||
|
@Schema(description = "发送者ID(系统通知为0)")
|
||||||
|
private Integer senderId;
|
||||||
|
|
||||||
|
@Schema(description = "发送者名称")
|
||||||
|
private String senderName;
|
||||||
|
|
||||||
|
@Schema(description = "发送者头像")
|
||||||
|
private String senderAvatar;
|
||||||
|
|
||||||
|
@Schema(description = "租户ID")
|
||||||
|
private Integer tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限申请记录(开发者Git仓库访问申请)
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@Schema(name = "AppPermissionRequest对象", description = "权限申请记录(开发者Git仓库访问申请)")
|
||||||
|
@TableName("app_permission_request")
|
||||||
|
public class AppPermissionRequest implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "自增ID")
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "Git用户名")
|
||||||
|
private String gitUsername;
|
||||||
|
|
||||||
|
@Schema(description = "申请仓库路径")
|
||||||
|
private String repo;
|
||||||
|
|
||||||
|
@Schema(description = "仓库名称")
|
||||||
|
private String repoName;
|
||||||
|
|
||||||
|
@Schema(description = "申请理由")
|
||||||
|
private String reason;
|
||||||
|
|
||||||
|
@Schema(description = "状态: pending待审核/approved已通过/rejected已拒绝")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "拒绝原因")
|
||||||
|
private String rejectReason;
|
||||||
|
|
||||||
|
@Schema(description = "审核人用户ID")
|
||||||
|
private Integer reviewerId;
|
||||||
|
|
||||||
|
@Schema(description = "审核人姓名")
|
||||||
|
private String reviewerName;
|
||||||
|
|
||||||
|
@Schema(description = "审核时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime reviewedAt;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
@Schema(description = "是否删除, 0否, 1是")
|
||||||
|
@TableLogic
|
||||||
|
private Integer deleted;
|
||||||
|
|
||||||
|
@Schema(description = "租户ID")
|
||||||
|
private Integer tenantId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CI/CD 流水线配置实体
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@Schema(name = "AppPipeline对象", description = "CI/CD流水线配置")
|
||||||
|
@TableName("app_pipeline")
|
||||||
|
public class AppPipeline implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "自增ID")
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "关联应用ID")
|
||||||
|
private Long appId;
|
||||||
|
|
||||||
|
@Schema(description = "流水线名称")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "流水线描述")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "CI系统类型: gitea/jenkins/github")
|
||||||
|
private String ciType;
|
||||||
|
|
||||||
|
@Schema(description = "Gitea仓库全称(如 gxwebsoft/my-app)")
|
||||||
|
private String repoFullName;
|
||||||
|
|
||||||
|
@Schema(description = "Gitea工作流文件名(如 build.yml)")
|
||||||
|
private String workflowFile;
|
||||||
|
|
||||||
|
@Schema(description = "流水线阶段: build/test/deploy")
|
||||||
|
private String stages;
|
||||||
|
|
||||||
|
@Schema(description = "环境: development/staging/production")
|
||||||
|
private String env;
|
||||||
|
|
||||||
|
@Schema(description = "默认触发分支")
|
||||||
|
private String defaultBranch;
|
||||||
|
|
||||||
|
@Schema(description = "是否启用")
|
||||||
|
private Boolean enabled;
|
||||||
|
|
||||||
|
@Schema(description = "自动部署")
|
||||||
|
private Boolean autoDeploy;
|
||||||
|
|
||||||
|
@Schema(description = "构建超时时间(秒)")
|
||||||
|
private Integer timeout;
|
||||||
|
|
||||||
|
@Schema(description = "配置JSON(变量、环境等)")
|
||||||
|
private String config;
|
||||||
|
|
||||||
|
@Schema(description = "最近一次构建ID")
|
||||||
|
private Long lastBuildId;
|
||||||
|
|
||||||
|
@Schema(description = "最近构建状态")
|
||||||
|
private String lastBuildStatus;
|
||||||
|
|
||||||
|
@Schema(description = "最近构建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime lastBuildTime;
|
||||||
|
|
||||||
|
@Schema(description = "构建成功次数")
|
||||||
|
private Integer successCount;
|
||||||
|
|
||||||
|
@Schema(description = "构建失败次数")
|
||||||
|
private Integer failureCount;
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "租户id")
|
||||||
|
private Integer tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "修改时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,321 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import javax.validation.constraints.Size;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用产品主表
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2024-09-10
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(name = "AppProduct对象", description = "应用产品主表")
|
||||||
|
public class AppProduct implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
// ==================== 核心字段 ====================
|
||||||
|
@Schema(description = "应用ID")
|
||||||
|
@TableId(value = "product_id", type = IdType.AUTO)
|
||||||
|
private Integer productId;
|
||||||
|
|
||||||
|
@Schema(description = "应用名称")
|
||||||
|
@Size(max = 100, message = "应用名称长度不能超过100")
|
||||||
|
private String productName;
|
||||||
|
|
||||||
|
@Schema(description = "应用标识(唯一)")
|
||||||
|
@Size(max = 50, message = "应用标识长度不能超过50")
|
||||||
|
private String productCode;
|
||||||
|
|
||||||
|
@Schema(description = "应用密钥")
|
||||||
|
private String productSecret;
|
||||||
|
|
||||||
|
// ==================== 应用类型 ====================
|
||||||
|
@Schema(description = "应用类型: 10网站 20微信小程序 30抖音小程序 40百度小程序 50支付宝小程序 60Android 70iOS 80macOS 90Windows 100插件")
|
||||||
|
private Integer appType;
|
||||||
|
|
||||||
|
@Schema(description = "应用类型名称")
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String appTypeName;
|
||||||
|
|
||||||
|
// ==================== 分类信息 ====================
|
||||||
|
@Schema(description = "分类ID")
|
||||||
|
private Integer categoryId;
|
||||||
|
|
||||||
|
@Schema(description = "行业类型(父级)")
|
||||||
|
private String industryParent;
|
||||||
|
|
||||||
|
@Schema(description = "行业类型(子级)")
|
||||||
|
private String industryChild;
|
||||||
|
|
||||||
|
// ==================== 基础信息 ====================
|
||||||
|
@Schema(description = "应用Logo")
|
||||||
|
private String logo;
|
||||||
|
|
||||||
|
@Schema(description = "应用图标")
|
||||||
|
private String icon;
|
||||||
|
|
||||||
|
@Schema(description = "二维码")
|
||||||
|
private String qrcode;
|
||||||
|
|
||||||
|
@Schema(description = "应用截图(JSON数组)")
|
||||||
|
private String screenshots;
|
||||||
|
|
||||||
|
@Schema(description = "应用简介")
|
||||||
|
@Size(max = 500, message = "应用简介长度不能超过500")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "详细说明")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@Schema(description = "关键词")
|
||||||
|
@Size(max = 200, message = "关键词长度不能超过200")
|
||||||
|
private String keywords;
|
||||||
|
|
||||||
|
// ==================== 配置信息 ====================
|
||||||
|
@Schema(description = "域名")
|
||||||
|
private String domain;
|
||||||
|
|
||||||
|
@Schema(description = "域名前缀")
|
||||||
|
private String prefix;
|
||||||
|
|
||||||
|
@Schema(description = "包名/AppID")
|
||||||
|
private String packageName;
|
||||||
|
|
||||||
|
@Schema(description = "后台地址")
|
||||||
|
private String adminUrl;
|
||||||
|
|
||||||
|
@Schema(description = "API地址")
|
||||||
|
private String apiUrl;
|
||||||
|
|
||||||
|
@Schema(description = "下载地址")
|
||||||
|
private String downloadUrl;
|
||||||
|
|
||||||
|
// ==================== 版本信息 ====================
|
||||||
|
@Schema(description = "版本号")
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
@Schema(description = "版本: standard标准版 professional专业版 perpetual永久授权")
|
||||||
|
private String edition;
|
||||||
|
|
||||||
|
@Schema(description = "最低版本要求")
|
||||||
|
private String minVersion;
|
||||||
|
|
||||||
|
// ==================== 价格与交付 ====================
|
||||||
|
@Schema(description = "定价: free免费 one_time一次性 subscription订阅")
|
||||||
|
private String priceType;
|
||||||
|
|
||||||
|
@Schema(description = "价格(元)")
|
||||||
|
private BigDecimal price;
|
||||||
|
|
||||||
|
@Schema(description = "划线价格")
|
||||||
|
private BigDecimal linePrice;
|
||||||
|
|
||||||
|
@Schema(description = "续费价格")
|
||||||
|
private BigDecimal renewPrice;
|
||||||
|
|
||||||
|
@Schema(description = "交付方式: 1源码 2托管 3授权")
|
||||||
|
private Integer deliveryMethod;
|
||||||
|
|
||||||
|
@Schema(description = "计费方式: 1按年 2按月 3一次性")
|
||||||
|
private Integer chargingMethod;
|
||||||
|
|
||||||
|
@Schema(description = "订阅周期: month/year")
|
||||||
|
private String subscriptionPeriod;
|
||||||
|
|
||||||
|
// ==================== 发布管理 ====================
|
||||||
|
@Schema(description = "发布状态: developing开发中 pending_review待审核 published已上架 rejected审核未通过 deprecated已下架")
|
||||||
|
private String publishStatus;
|
||||||
|
|
||||||
|
@Schema(description = "发布时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date publishTime;
|
||||||
|
|
||||||
|
@Schema(description = "审核时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date reviewTime;
|
||||||
|
|
||||||
|
@Schema(description = "审核人ID")
|
||||||
|
private Integer reviewerId;
|
||||||
|
|
||||||
|
@Schema(description = "拒绝原因")
|
||||||
|
private String rejectReason;
|
||||||
|
|
||||||
|
// ==================== 统计数据 ====================
|
||||||
|
@Schema(description = "浏览次数")
|
||||||
|
private Integer clicks;
|
||||||
|
|
||||||
|
@Schema(description = "安装次数")
|
||||||
|
private Integer installs;
|
||||||
|
|
||||||
|
@Schema(description = "下载次数")
|
||||||
|
private Integer downloads;
|
||||||
|
|
||||||
|
@Schema(description = "评分(1-5)")
|
||||||
|
private BigDecimal rating;
|
||||||
|
|
||||||
|
@Schema(description = "点赞数")
|
||||||
|
private Integer likes;
|
||||||
|
|
||||||
|
// ==================== 开发者信息 ====================
|
||||||
|
@Schema(description = "开发者")
|
||||||
|
private String developer;
|
||||||
|
|
||||||
|
@Schema(description = "开发者电话")
|
||||||
|
private String developerPhone;
|
||||||
|
|
||||||
|
@Schema(description = "开发者邮箱")
|
||||||
|
private String developerEmail;
|
||||||
|
|
||||||
|
// ==================== 运营配置 ====================
|
||||||
|
@Schema(description = "是否推荐: 0否 1是")
|
||||||
|
private Integer recommend;
|
||||||
|
|
||||||
|
@Schema(description = "是否官方: 0否 1是")
|
||||||
|
private Integer official;
|
||||||
|
|
||||||
|
@Schema(description = "是否上架市场: 0否 1是")
|
||||||
|
private Integer market;
|
||||||
|
|
||||||
|
@Schema(description = "是否显示首页: 0否 1是")
|
||||||
|
private Integer showIndex;
|
||||||
|
|
||||||
|
@Schema(description = "是否可搜索: 0否 1是")
|
||||||
|
private Integer searchEnabled;
|
||||||
|
|
||||||
|
// ==================== 站点配置 ====================
|
||||||
|
@Schema(description = "模板ID")
|
||||||
|
private Integer templateId;
|
||||||
|
|
||||||
|
@Schema(description = "样式配置JSON")
|
||||||
|
private String style;
|
||||||
|
|
||||||
|
@Schema(description = "扩展配置JSON")
|
||||||
|
private String config;
|
||||||
|
|
||||||
|
@Schema(description = "主题色")
|
||||||
|
private String themeColor;
|
||||||
|
|
||||||
|
@Schema(description = "语言")
|
||||||
|
private String lang;
|
||||||
|
|
||||||
|
// ==================== 备案信息 ====================
|
||||||
|
@Schema(description = "ICP备案号")
|
||||||
|
private String icpNo;
|
||||||
|
|
||||||
|
@Schema(description = "公安备案号")
|
||||||
|
private String policeNo;
|
||||||
|
|
||||||
|
// ==================== 状态管理 ====================
|
||||||
|
@Schema(description = "状态: 0未开通 1运行中 2维护中 3已关闭 4已欠费 5违规停机")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description = "状态说明")
|
||||||
|
private String statusText;
|
||||||
|
|
||||||
|
@Schema(description = "运行状态: 0停止 1运行中 2维护中")
|
||||||
|
private Integer running;
|
||||||
|
|
||||||
|
@Schema(description = "是否到期")
|
||||||
|
@TableField(exist = false)
|
||||||
|
private Integer expired;
|
||||||
|
|
||||||
|
@Schema(description = "剩余天数")
|
||||||
|
@TableField(exist = false)
|
||||||
|
private Long expiredDays;
|
||||||
|
|
||||||
|
@Schema(description = "即将过期")
|
||||||
|
@TableField(exist = false)
|
||||||
|
private Integer soon;
|
||||||
|
|
||||||
|
@Schema(description = "到期时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date expirationTime;
|
||||||
|
|
||||||
|
// ==================== 系统字段 ====================
|
||||||
|
@Schema(description = "排序号")
|
||||||
|
private Integer sortNumber;
|
||||||
|
|
||||||
|
@Schema(description = "是否删除: 0否 1是")
|
||||||
|
@TableLogic
|
||||||
|
private Integer deleted;
|
||||||
|
|
||||||
|
@Schema(description = "创建用户ID")
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "租户ID")
|
||||||
|
private Integer tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
@Schema(description = "修改时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
// ==================== 非数据库字段 ====================
|
||||||
|
|
||||||
|
@Schema(description = "当前用户在该应用中的角色(仅 accessible 查询返回)")
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String myRole;
|
||||||
|
|
||||||
|
// ==================== 应用类型常量 ====================
|
||||||
|
public static final int APP_TYPE_WEBSITE = 10; // 网站
|
||||||
|
public static final int APP_TYPE_WX_MINI = 20; // 微信小程序
|
||||||
|
public static final int APP_TYPE_DOUYIN_MINI = 30; // 抖音小程序
|
||||||
|
public static final int APP_TYPE_BAIDU_MINI = 40; // 百度小程序
|
||||||
|
public static final int APP_TYPE_ALIPAY_MINI = 50; // 支付宝小程序
|
||||||
|
public static final int APP_TYPE_ANDROID = 60; // Android APP
|
||||||
|
public static final int APP_TYPE_IOS = 70; // iOS APP
|
||||||
|
public static final int APP_TYPE_MACOS = 80; // macOS 应用
|
||||||
|
public static final int APP_TYPE_WINDOWS = 90; // Windows 应用
|
||||||
|
public static final int APP_TYPE_PLUGIN = 100; // 插件
|
||||||
|
|
||||||
|
// ==================== 发布状态常量 ====================
|
||||||
|
public static final String PUBLISH_DEVELOPING = "developing"; // 开发中
|
||||||
|
public static final String PUBLISH_PENDING_REVIEW = "pending_review"; // 待审核
|
||||||
|
public static final String PUBLISH_PUBLISHED = "published"; // 已上架
|
||||||
|
public static final String PUBLISH_REJECTED = "rejected"; // 审核未通过
|
||||||
|
public static final String PUBLISH_DEPRECATED = "deprecated"; // 已下架
|
||||||
|
|
||||||
|
// ==================== 价格类型常量 ====================
|
||||||
|
public static final String PRICE_FREE = "free"; // 免费
|
||||||
|
public static final String PRICE_ONE_TIME = "one_time"; // 一次性
|
||||||
|
public static final String PRICE_SUBSCRIPTION = "subscription"; // 订阅
|
||||||
|
|
||||||
|
// ==================== 版本常量 ====================
|
||||||
|
public static final String EDITION_STANDARD = "standard"; // 标准版
|
||||||
|
public static final String EDITION_PROFESSIONAL = "professional"; // 专业版
|
||||||
|
public static final String EDITION_PERPETUAL = "perpetual"; // 永久授权
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取应用类型名称
|
||||||
|
*/
|
||||||
|
public String getAppTypeName() {
|
||||||
|
if (appType == null) return "未知";
|
||||||
|
switch (appType) {
|
||||||
|
case APP_TYPE_WEBSITE: return "网站";
|
||||||
|
case APP_TYPE_WX_MINI: return "微信小程序";
|
||||||
|
case APP_TYPE_DOUYIN_MINI: return "抖音小程序";
|
||||||
|
case APP_TYPE_BAIDU_MINI: return "百度小程序";
|
||||||
|
case APP_TYPE_ALIPAY_MINI: return "支付宝小程序";
|
||||||
|
case APP_TYPE_ANDROID: return "Android APP";
|
||||||
|
case APP_TYPE_IOS: return "iOS APP";
|
||||||
|
case APP_TYPE_MACOS: return "macOS 应用";
|
||||||
|
case APP_TYPE_WINDOWS: return "Windows 应用";
|
||||||
|
case APP_TYPE_PLUGIN: return "插件";
|
||||||
|
default: return "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开发者资源(服务器/数据库/云存储/域名/SSL证书)
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-03-31
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@TableName("app_resource")
|
||||||
|
@Schema(name = "AppResource对象", description = "开发者资源")
|
||||||
|
public class AppResource implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "资源ID")
|
||||||
|
@TableId(value = "resource_id", type = IdType.AUTO)
|
||||||
|
private Long resourceId;
|
||||||
|
|
||||||
|
@Schema(description = "资源类型: server/database/storage/domain/ssl")
|
||||||
|
private String resourceType;
|
||||||
|
|
||||||
|
@Schema(description = "资源名称")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "服务商: tencent/aliyun/huawei/other")
|
||||||
|
private String provider;
|
||||||
|
|
||||||
|
@Schema(description = "关联应用ID(可选)")
|
||||||
|
private Long appId;
|
||||||
|
|
||||||
|
@Schema(description = "关联应用名称(冗余)")
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String appName;
|
||||||
|
|
||||||
|
// ─── 服务器字段 ───────────────────────────────────────
|
||||||
|
@Schema(description = "IP地址(服务器用)")
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
@Schema(description = "SSH端口(服务器用,默认22)")
|
||||||
|
private Integer sshPort;
|
||||||
|
|
||||||
|
@Schema(description = "SSH用户名(服务器用,用于远程执行命令)")
|
||||||
|
private String sshUsername;
|
||||||
|
|
||||||
|
@Schema(description = "SSH密码(AES加密,服务器用,用于远程执行命令)")
|
||||||
|
private String sshPassword;
|
||||||
|
|
||||||
|
@Schema(description = "MySQL端口(服务器用,默认3306)")
|
||||||
|
private Integer mysqlPort;
|
||||||
|
|
||||||
|
@Schema(description = "PostgreSQL端口(服务器用,默认5432)")
|
||||||
|
private Integer pgPort;
|
||||||
|
|
||||||
|
@Schema(description = "1Panel面板端口(服务器用,默认8888)")
|
||||||
|
private Integer panelPort;
|
||||||
|
|
||||||
|
@Schema(description = "1Panel面板用户名(服务器用)")
|
||||||
|
private String panelUsername;
|
||||||
|
|
||||||
|
@Schema(description = "1Panel面板密码(AES加密,服务器用)")
|
||||||
|
private String panelPassword;
|
||||||
|
|
||||||
|
@Schema(description = "1Panel面板路径前缀(服务器用,如 /abc123)")
|
||||||
|
private String panelPath;
|
||||||
|
|
||||||
|
@Schema(description = "MySQL管理员用户名(服务器用,用于远程建库)")
|
||||||
|
private String adminUsername;
|
||||||
|
|
||||||
|
@Schema(description = "MySQL管理员密码(AES加密,服务器用,用于远程建库)")
|
||||||
|
private String adminPassword;
|
||||||
|
|
||||||
|
// ─── 数据库字段 ───────────────────────────────────────
|
||||||
|
@Schema(description = "数据库类型: MySQL/PostgreSQL/Redis/MongoDB(数据库用)")
|
||||||
|
private String dbType;
|
||||||
|
|
||||||
|
@Schema(description = "关联服务器资源ID(数据库用)")
|
||||||
|
private Long serverResourceId;
|
||||||
|
|
||||||
|
@Schema(description = "连接主机地址(数据库用)")
|
||||||
|
private String host;
|
||||||
|
|
||||||
|
@Schema(description = "数据库连接端口(根据dbType从服务器资源获取,前端显示用)")
|
||||||
|
private Integer port;
|
||||||
|
|
||||||
|
@Schema(description = "数据库用户名(数据库用)")
|
||||||
|
private String dbUsername;
|
||||||
|
|
||||||
|
@Schema(description = "数据库密码(数据库用)")
|
||||||
|
private String dbPassword;
|
||||||
|
|
||||||
|
// ─── 云存储字段 ───────────────────────────────────────
|
||||||
|
@Schema(description = "地区/Region(云存储用)")
|
||||||
|
private String region;
|
||||||
|
|
||||||
|
@Schema(description = "云账号凭证ID(云存储用,关联app_cloud_credential)")
|
||||||
|
private Long credentialId;
|
||||||
|
|
||||||
|
@Schema(description = "访问权限: public-read/private(云存储用)")
|
||||||
|
private String acl;
|
||||||
|
|
||||||
|
@Schema(description = "已用空间(字节,云存储用)")
|
||||||
|
private Long usedBytes;
|
||||||
|
|
||||||
|
@Schema(description = "对象数量(云存储用)")
|
||||||
|
private Integer usedCount;
|
||||||
|
|
||||||
|
// ─── 域名字段 ─────────────────────────────────────────
|
||||||
|
@Schema(description = "域名(域名用)")
|
||||||
|
private String domain;
|
||||||
|
|
||||||
|
@Schema(description = "注册商(域名用)")
|
||||||
|
private String registrar;
|
||||||
|
|
||||||
|
@Schema(description = "是否已备案(域名用)")
|
||||||
|
private Boolean icp;
|
||||||
|
|
||||||
|
@Schema(description = "ICP备案号(域名用)")
|
||||||
|
private String icpNo;
|
||||||
|
|
||||||
|
@Schema(description = "是否已绑定SSL(域名用,冗余)")
|
||||||
|
private Boolean sslBound;
|
||||||
|
|
||||||
|
// ─── SSL证书字段 ──────────────────────────────────────
|
||||||
|
@Schema(description = "证书类型: DV/OV/EV(SSL用)")
|
||||||
|
private String certType;
|
||||||
|
|
||||||
|
@Schema(description = "颁发机构(SSL用)")
|
||||||
|
private String issuer;
|
||||||
|
|
||||||
|
@Schema(description = "私钥(SSL用,AES加密存储)")
|
||||||
|
private String privateKey;
|
||||||
|
|
||||||
|
@Schema(description = "公钥(SSL用)")
|
||||||
|
private String publicKey;
|
||||||
|
|
||||||
|
@Schema(description = "证书内容/证书文件(SSL用)")
|
||||||
|
private String certificate;
|
||||||
|
|
||||||
|
@Schema(description = "证书链(SSL用,中间证书)")
|
||||||
|
private String certChain;
|
||||||
|
|
||||||
|
// ─── Git仓库字段 ───────────────────────────────────────
|
||||||
|
@Schema(description = "Git仓库路径(git用,如: websopy/core)")
|
||||||
|
private String gitPath;
|
||||||
|
|
||||||
|
@Schema(description = "Git Clone URL(git用)")
|
||||||
|
private String gitCloneUrl;
|
||||||
|
|
||||||
|
@Schema(description = "Git Web访问URL(git用,Gitea页面地址)")
|
||||||
|
private String gitWebUrl;
|
||||||
|
|
||||||
|
@Schema(description = "Git权限级别: read/write/admin(git用)")
|
||||||
|
private String gitAccessLevel;
|
||||||
|
|
||||||
|
// ─── 通用字段 ─────────────────────────────────────────
|
||||||
|
@Schema(description = "状态: running/stopped/expired/pending")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "到期时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
|
private LocalDate expireAt;
|
||||||
|
|
||||||
|
@Schema(description = "备注")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Schema(description = "所属用户ID")
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "资源创建者userId(权限控制基准,创建时自动设置)")
|
||||||
|
private Long ownerUserId;
|
||||||
|
|
||||||
|
@Schema(description = "租户ID")
|
||||||
|
private Integer tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "是否删除: 0否 1是")
|
||||||
|
private Integer deleted;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
// ─── 协作权限(非持久化,由后端查询时计算) ────────────────────
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "当前用户对此资源的访问级别: 0=无权限 1=基础查看 2=连接查看 3=完全权限(Owner)")
|
||||||
|
private Integer accessLevel;
|
||||||
|
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "当前用户是否为资源Owner")
|
||||||
|
private Boolean isOwner;
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台设置表
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@TableName("app_setting")
|
||||||
|
public class AppSetting implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "setting_id", type = IdType.AUTO)
|
||||||
|
private Integer settingId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置分类:basic/review/market/register/notify/maintenance
|
||||||
|
*/
|
||||||
|
private String category;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置项标识
|
||||||
|
*/
|
||||||
|
private String settingKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置项名称
|
||||||
|
*/
|
||||||
|
private String settingName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置值(JSON格式)
|
||||||
|
*/
|
||||||
|
private String settingValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置类型:string/number/boolean/json
|
||||||
|
*/
|
||||||
|
private String valueType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置说明
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序号
|
||||||
|
*/
|
||||||
|
private Integer sortNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用 0否 1是
|
||||||
|
*/
|
||||||
|
private Integer isEnabled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否公开(前端可读)0否 1是
|
||||||
|
*/
|
||||||
|
private Integer isPublic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户id
|
||||||
|
*/
|
||||||
|
private Long tenantId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
private Long createdTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
private Long updatedTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否删除 0否 1是
|
||||||
|
*/
|
||||||
|
@TableLogic
|
||||||
|
private Integer deleted;
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用订阅实体
|
||||||
|
* 对应表:app_subscription
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("app_subscription")
|
||||||
|
public class AppSubscription {
|
||||||
|
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/** 订阅编号(业务唯一) */
|
||||||
|
private String subscriptionNo;
|
||||||
|
|
||||||
|
/** 购买用户ID */
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
/** 应用产品ID */
|
||||||
|
private Integer productId;
|
||||||
|
|
||||||
|
/** 租户ID */
|
||||||
|
private Integer tenantId;
|
||||||
|
|
||||||
|
/** 订阅状态: pending/active/expired/cancelled */
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/** 价格类型: free/one_time/subscription */
|
||||||
|
private String priceType;
|
||||||
|
|
||||||
|
/** 原价(单位:元) */
|
||||||
|
private BigDecimal originalPrice;
|
||||||
|
|
||||||
|
/** 实付金额(单位:元) */
|
||||||
|
private BigDecimal payPrice;
|
||||||
|
|
||||||
|
/** 支付方式: 0-余额, 1-微信, 2-支付宝, 12-免费 */
|
||||||
|
private Integer payType;
|
||||||
|
|
||||||
|
/** 支付状态: 0-未支付, 1-已支付 */
|
||||||
|
private Integer payStatus;
|
||||||
|
|
||||||
|
/** 支付时间 */
|
||||||
|
private LocalDateTime payTime;
|
||||||
|
|
||||||
|
/** 第三方交易号 */
|
||||||
|
private String transactionId;
|
||||||
|
|
||||||
|
/** 订阅周期: month/year */
|
||||||
|
private String subscriptionPeriod;
|
||||||
|
|
||||||
|
/** 生效时间 */
|
||||||
|
private LocalDateTime startTime;
|
||||||
|
|
||||||
|
/** 到期时间(订阅型) */
|
||||||
|
private LocalDateTime expireTime;
|
||||||
|
|
||||||
|
/** 是否自动续费 0-否 1-是 */
|
||||||
|
private Integer autoRenew;
|
||||||
|
|
||||||
|
/** 分配的域名 */
|
||||||
|
private String instanceDomain;
|
||||||
|
|
||||||
|
/** 实例管理后台URL */
|
||||||
|
private String instanceAdminUrl;
|
||||||
|
|
||||||
|
/** 实例配置(JSON) */
|
||||||
|
private String instanceConfig;
|
||||||
|
|
||||||
|
/** 关联的支付订单号 */
|
||||||
|
private String orderNo;
|
||||||
|
|
||||||
|
/** 关联的支付订单ID */
|
||||||
|
private Long orderId;
|
||||||
|
|
||||||
|
/** 备注 */
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@TableLogic
|
||||||
|
private Integer deleted;
|
||||||
|
|
||||||
|
private Integer sortNumber;
|
||||||
|
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
// ===== 关联查询字段(非数据库字段) =====
|
||||||
|
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String productName;
|
||||||
|
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String productIcon;
|
||||||
|
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String productLogo;
|
||||||
|
|
||||||
|
@TableField(exist = false)
|
||||||
|
private Integer productAppType;
|
||||||
|
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String productDescription;
|
||||||
|
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String developerName;
|
||||||
|
}
|
||||||
104
jczxw-java/src/main/java/com/gxwebsoft/app/entity/AppTicket.java
Normal file
104
jczxw-java/src/main/java/com/gxwebsoft/app/entity/AppTicket.java
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import com.gxwebsoft.common.core.config.JsonArrayToStringDeserializer;
|
||||||
|
import com.gxwebsoft.common.core.config.JsonStringToArraySerializer;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用工单
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-03-30
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@TableName("app_ticket")
|
||||||
|
@Schema(name = "AppTicket对象", description = "应用工单")
|
||||||
|
public class AppTicket implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "工单ID")
|
||||||
|
@TableId(value = "ticket_id", type = IdType.AUTO)
|
||||||
|
private Long ticketId;
|
||||||
|
|
||||||
|
@Schema(description = "工单编号(TK-yyyyMMddHHmmss+4位随机)")
|
||||||
|
private String ticketNo;
|
||||||
|
|
||||||
|
@Schema(description = "工单标题")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Schema(description = "工单内容描述")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@Schema(description = "关联应用ID")
|
||||||
|
@com.fasterxml.jackson.annotation.JsonProperty("productId")
|
||||||
|
private Long appId;
|
||||||
|
|
||||||
|
@Schema(description = "应用名称(冗余)")
|
||||||
|
private String appName;
|
||||||
|
|
||||||
|
@Schema(description = "工单分类: bug/feature/consultation/complaint/other")
|
||||||
|
private String category;
|
||||||
|
|
||||||
|
@Schema(description = "优先级: low/normal/high/urgent")
|
||||||
|
private String priority;
|
||||||
|
|
||||||
|
@Schema(description = "状态: pending/assigned/processing/resolved/closed/rejected")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "附件JSON数组")
|
||||||
|
@JsonDeserialize(using = JsonArrayToStringDeserializer.class)
|
||||||
|
@JsonSerialize(using = JsonStringToArraySerializer.class)
|
||||||
|
private String attachments;
|
||||||
|
|
||||||
|
@Schema(description = "提交人用户ID")
|
||||||
|
private Integer submitUserId;
|
||||||
|
|
||||||
|
@Schema(description = "提交人昵称(冗余)")
|
||||||
|
private String submitUserName;
|
||||||
|
|
||||||
|
@Schema(description = "提交人头像(冗余)")
|
||||||
|
private String submitUserAvatar;
|
||||||
|
|
||||||
|
@Schema(description = "分配的处理人用户ID")
|
||||||
|
private Integer assigneeId;
|
||||||
|
|
||||||
|
@Schema(description = "处理人昵称(冗余)")
|
||||||
|
private String assigneeName;
|
||||||
|
|
||||||
|
@Schema(description = "处理人头像(冗余)")
|
||||||
|
private String assigneeAvatar;
|
||||||
|
|
||||||
|
@Schema(description = "回复数量")
|
||||||
|
private Integer replyCount;
|
||||||
|
|
||||||
|
@Schema(description = "解决时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime resolvedTime;
|
||||||
|
|
||||||
|
@Schema(description = "关闭时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime closedTime;
|
||||||
|
|
||||||
|
@Schema(description = "是否删除: 0否 1是")
|
||||||
|
private Integer deleted;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import com.gxwebsoft.common.core.config.JsonArrayToStringDeserializer;
|
||||||
|
import com.gxwebsoft.common.core.config.JsonStringToArraySerializer;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单回复
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-03-30
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@TableName("app_ticket_reply")
|
||||||
|
@Schema(name = "AppTicketReply对象", description = "工单回复")
|
||||||
|
public class AppTicketReply implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "回复ID")
|
||||||
|
@TableId(value = "reply_id", type = IdType.AUTO)
|
||||||
|
private Long replyId;
|
||||||
|
|
||||||
|
@Schema(description = "关联工单ID")
|
||||||
|
private Long ticketId;
|
||||||
|
|
||||||
|
@Schema(description = "回复内容")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@Schema(description = "附件JSON数组")
|
||||||
|
@JsonDeserialize(using = JsonArrayToStringDeserializer.class)
|
||||||
|
@JsonSerialize(using = JsonStringToArraySerializer.class)
|
||||||
|
private String attachments;
|
||||||
|
|
||||||
|
@Schema(description = "回复人用户ID")
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "回复人昵称(冗余)")
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
@Schema(description = "回复人头像(冗余)")
|
||||||
|
private String userAvatar;
|
||||||
|
|
||||||
|
@Schema(description = "是否是技术人员/客服: 0否 1是")
|
||||||
|
private Integer isStaff;
|
||||||
|
|
||||||
|
@Schema(description = "是否删除: 0否 1是")
|
||||||
|
private Integer deleted;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用成员
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-03-28 21:29:44
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@Schema(name = "AppUser对象", description = "应用成员")
|
||||||
|
public class AppUser implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "自增ID")
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "关联应用ID")
|
||||||
|
private Long appId;
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "用户名(冗余)")
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Schema(description = "昵称(冗余)")
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String nickname;
|
||||||
|
|
||||||
|
@Schema(description = "头像(冗余)")
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
@Schema(description = "手机号(冗余,脱敏存储)")
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
@Schema(description = "角色: owner/admin/developer/viewer")
|
||||||
|
private String role;
|
||||||
|
|
||||||
|
@Schema(description = "邀请人用户ID")
|
||||||
|
private Long inviteBy;
|
||||||
|
|
||||||
|
@Schema(description = "加入时间")
|
||||||
|
private LocalDateTime inviteTime;
|
||||||
|
|
||||||
|
@Schema(description = "排序(数字越小越靠前)")
|
||||||
|
private Integer sortNumber;
|
||||||
|
|
||||||
|
@Schema(description = "状态, 0正常, 1冻结")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description = "邀请状态: 0-正常(直接加入), 1-待确认, 2-已拒绝")
|
||||||
|
private Integer inviteStatus;
|
||||||
|
|
||||||
|
@Schema(description = "邀请过期时间")
|
||||||
|
private LocalDateTime inviteExpireTime;
|
||||||
|
|
||||||
|
@Schema(description = "租户id")
|
||||||
|
private Integer tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "修改时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
// ========== 关联字段(非数据库字段)==========
|
||||||
|
|
||||||
|
@Schema(description = "应用名称(关联app_product)")
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String productName;
|
||||||
|
|
||||||
|
@Schema(description = "应用图标(关联app_product)")
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String icon;
|
||||||
|
|
||||||
|
@Schema(description = "应用编码(关联app_product)")
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String productCode;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.DesensitizedUtil;
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息缓存表
|
||||||
|
* 用于缓存 sys_user 的常用字段,方便 app 模块其他表关联查询用户信息
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("app_user_cache")
|
||||||
|
@Schema(name = "AppUserCache对象", description = "用户信息缓存")
|
||||||
|
public class AppUserCache implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "用户ID(主键)")
|
||||||
|
@TableId(value = "user_id", type = IdType.INPUT)
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "用户名")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Schema(description = "昵称")
|
||||||
|
private String nickname;
|
||||||
|
|
||||||
|
@Schema(description = "头像")
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
@Schema(description = "手机号(脱敏存储)")
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
@Schema(description = "用户状态,0正常,1冻结")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description = "缓存更新时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
@Schema(description = "租户ID")
|
||||||
|
private Integer tenantId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取脱敏手机号
|
||||||
|
*/
|
||||||
|
public String getMobile() {
|
||||||
|
return DesensitizedUtil.mobilePhone(this.phone);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用版本发布记录
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-03-28 21:29:44
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@Schema(name = "AppVersion对象", description = "应用版本发布记录")
|
||||||
|
public class AppVersion implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "自增ID")
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "关联应用ID")
|
||||||
|
private Long appId;
|
||||||
|
|
||||||
|
@Schema(description = "版本号,如 1.0.0")
|
||||||
|
private String versionNo;
|
||||||
|
|
||||||
|
@Schema(description = "版本名称")
|
||||||
|
private String versionName;
|
||||||
|
|
||||||
|
@Schema(description = "版本更新说明")
|
||||||
|
private String changelog;
|
||||||
|
|
||||||
|
@Schema(description = "安装包地址")
|
||||||
|
private String packageUrl;
|
||||||
|
|
||||||
|
@Schema(description = "包大小(字节)")
|
||||||
|
private Long packageSize;
|
||||||
|
|
||||||
|
@Schema(description = "包MD5/SHA256")
|
||||||
|
private String packageHash;
|
||||||
|
|
||||||
|
@Schema(description = "环境: development/staging/production")
|
||||||
|
private String env;
|
||||||
|
|
||||||
|
@Schema(description = "状态 0=构建中 1=已发布 2=已回滚 3=构建失败")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description = "是否为当前版本")
|
||||||
|
private Boolean isCurrent;
|
||||||
|
|
||||||
|
@Schema(description = "发布人用户ID")
|
||||||
|
private Long publishBy;
|
||||||
|
|
||||||
|
@Schema(description = "发布时间")
|
||||||
|
private LocalDateTime publishTime;
|
||||||
|
|
||||||
|
@Schema(description = "备注")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Schema(description = "排序(数字越小越靠前)")
|
||||||
|
private Integer sortNumber;
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "租户id")
|
||||||
|
private Integer tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "修改时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.gxwebsoft.app.entity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源访问级别枚举
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 0 - 无权限:不是应用团队成员
|
||||||
|
* 1 - 基础查看:所有团队成员,可看名称/IP/端口/状态
|
||||||
|
* 2 - 连接查看:admin/owner 角色,额外可看用户名/Host/连接方式
|
||||||
|
* 3 - 完全权限:资源创建者(Owner),可看密码/私钥,可编辑/删除
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-05
|
||||||
|
*/
|
||||||
|
public enum ResourceAccessLevel {
|
||||||
|
|
||||||
|
NONE(0),
|
||||||
|
VIEW_BASIC(1),
|
||||||
|
VIEW_CONNECTION(2),
|
||||||
|
FULL(3);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
ResourceAccessLevel(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前级别是否大于等于指定级别
|
||||||
|
*/
|
||||||
|
public boolean atLeast(ResourceAccessLevel level) {
|
||||||
|
return this.value >= level.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.gxwebsoft.app.entity.AppApiKey;
|
||||||
|
import com.gxwebsoft.app.param.AppApiKeyParam;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用 API Key Mapper
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-02
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AppApiKeyMapper extends BaseMapper<AppApiKey> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询(关联查询)
|
||||||
|
*/
|
||||||
|
IPage<AppApiKey> selectPageRel(Page<AppApiKey> page, @Param("param") AppApiKeyParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询列表(关联查询)
|
||||||
|
*/
|
||||||
|
java.util.List<AppApiKey> selectListRel(@Param("param") AppApiKeyParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID查询详情(关联查询)
|
||||||
|
*/
|
||||||
|
AppApiKey selectByIdRel(@Param("id") Long id);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.gxwebsoft.app.entity.AppArticleCategory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台文章分类 Mapper
|
||||||
|
*/
|
||||||
|
public interface AppArticleCategoryMapper extends BaseMapper<AppArticleCategory> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.gxwebsoft.app.entity.AppArticle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台文章 Mapper
|
||||||
|
*/
|
||||||
|
public interface AppArticleMapper extends BaseMapper<AppArticle> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.gxwebsoft.app.entity.AppBuild;
|
||||||
|
import com.gxwebsoft.app.param.AppBuildParam;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CI/CD 构建记录 Mapper
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-03
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AppBuildMapper extends BaseMapper<AppBuild> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询构建列表(关联应用信息)
|
||||||
|
*/
|
||||||
|
List<AppBuild> selectBuildList(@Param("param") AppBuildParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询构建详情(关联应用信息)
|
||||||
|
*/
|
||||||
|
AppBuild selectBuildDetail(@Param("id") Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询应用最新构建
|
||||||
|
*/
|
||||||
|
AppBuild selectLatestBuild(@Param("appId") Long appId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询应用构建统计
|
||||||
|
*/
|
||||||
|
Integer countBuildsByStatus(@Param("appId") Long appId, @Param("status") String status);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.gxwebsoft.app.entity.AppCloudCredential;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 云账号凭证 Mapper
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-04
|
||||||
|
*/
|
||||||
|
public interface AppCloudCredentialMapper extends BaseMapper<AppCloudCredential> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
*/
|
||||||
|
List<AppCloudCredential> selectPageRel(@Param("page") IPage<AppCloudCredential> page,
|
||||||
|
@Param("param") AppCloudCredential param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询列表
|
||||||
|
*/
|
||||||
|
List<AppCloudCredential> selectListRel(@Param("param") AppCloudCredential param);
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.gxwebsoft.app.entity.AppConfig;
|
||||||
|
import com.gxwebsoft.app.param.AppConfigParam;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用配置表 Mapper
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AppConfigMapper extends BaseMapper<AppConfig> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量获取应用配置
|
||||||
|
*/
|
||||||
|
List<Map<String, Object>> selectConfigsByAppId(Integer appId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
*
|
||||||
|
* @param page 分页对象
|
||||||
|
* @param param 查询参数
|
||||||
|
* @return List<AppConfig>
|
||||||
|
*/
|
||||||
|
List<AppConfig> selectPageRel(@Param("page") IPage<AppConfig> page,
|
||||||
|
@Param("param") AppConfigParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询全部
|
||||||
|
*
|
||||||
|
* @param param 查询参数
|
||||||
|
* @return List<AppConfig>
|
||||||
|
*/
|
||||||
|
List<AppConfig> selectListRel(@Param("param") AppConfigParam param);
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.gxwebsoft.app.entity.AppContract;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合同管理 Mapper
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-13
|
||||||
|
*/
|
||||||
|
public interface AppContractMapper extends BaseMapper<AppContract> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询当天最大合同序号
|
||||||
|
*/
|
||||||
|
@Select("SELECT COALESCE(MAX(SUBSTRING_INDEX(contract_no, '-', -1) + 0), 0) " +
|
||||||
|
"FROM app_contract WHERE contract_no LIKE CONCAT('HT-', #{datePart}, '-%')")
|
||||||
|
Integer selectMaxSeqForDate(@Param("datePart") String datePart);
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.gxwebsoft.app.entity.AppCredential;
|
||||||
|
import com.gxwebsoft.app.param.AppCredentialParam;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用密钥凭证Mapper
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-03-28 21:29:43
|
||||||
|
*/
|
||||||
|
public interface AppCredentialMapper extends BaseMapper<AppCredential> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
*
|
||||||
|
* @param page 分页对象
|
||||||
|
* @param param 查询参数
|
||||||
|
* @return List<AppCredential>
|
||||||
|
*/
|
||||||
|
List<AppCredential> selectPageRel(@Param("page") IPage<AppCredential> page,
|
||||||
|
@Param("param") AppCredentialParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询全部
|
||||||
|
*
|
||||||
|
* @param param 查询参数
|
||||||
|
* @return List<User>
|
||||||
|
*/
|
||||||
|
List<AppCredential> selectListRel(@Param("param") AppCredentialParam param);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.gxwebsoft.app.entity.AppEvent;
|
||||||
|
import com.gxwebsoft.app.param.AppEventParam;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用操作动态Mapper
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-03-28 21:29:44
|
||||||
|
*/
|
||||||
|
public interface AppEventMapper extends BaseMapper<AppEvent> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
*
|
||||||
|
* @param page 分页对象
|
||||||
|
* @param param 查询参数
|
||||||
|
* @return List<AppEvent>
|
||||||
|
*/
|
||||||
|
List<AppEvent> selectPageRel(@Param("page") IPage<AppEvent> page,
|
||||||
|
@Param("param") AppEventParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询全部
|
||||||
|
*
|
||||||
|
* @param param 查询参数
|
||||||
|
* @return List<User>
|
||||||
|
*/
|
||||||
|
List<AppEvent> selectListRel(@Param("param") AppEventParam param);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.gxwebsoft.app.entity.AppGitAccount;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Git账号绑定 Mapper
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-02
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AppGitAccountMapper extends BaseMapper<AppGitAccount> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户ID查询
|
||||||
|
*/
|
||||||
|
AppGitAccount selectByUserId(@Param("userId") Integer userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户名查询(检查唯一性)
|
||||||
|
*/
|
||||||
|
AppGitAccount selectByUsername(@Param("username") String username);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查用户名是否被其他用户绑定(同一租户下)
|
||||||
|
*/
|
||||||
|
AppGitAccount checkUsernameOccupied(@Param("username") String username, @Param("excludeUserId") Integer excludeUserId, @Param("tenantId") Integer tenantId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户ID和租户ID查询
|
||||||
|
*/
|
||||||
|
AppGitAccount selectByUserIdAndTenantId(@Param("userId") Integer userId, @Param("tenantId") Integer tenantId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.gxwebsoft.app.entity.AppInviteToken;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用邀请Token Mapper
|
||||||
|
*/
|
||||||
|
public interface AppInviteTokenMapper extends BaseMapper<AppInviteToken> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据token查询
|
||||||
|
*/
|
||||||
|
@Select("SELECT * FROM app_invite_token WHERE token = #{token} LIMIT 1")
|
||||||
|
AppInviteToken selectByToken(@Param("token") String token);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.gxwebsoft.app.entity.AppNotification;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 站内消息通知 Mapper
|
||||||
|
*/
|
||||||
|
public interface AppNotificationMapper extends BaseMapper<AppNotification> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.gxwebsoft.app.entity.AppPermissionRequest;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限申请Mapper
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-03
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AppPermissionRequestMapper extends BaseMapper<AppPermissionRequest> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户已可访问的仓库列表
|
||||||
|
*/
|
||||||
|
List<String> getAccessibleRepos(@Param("userId") Integer userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有可用仓库列表(含访问状态)
|
||||||
|
*/
|
||||||
|
List<Map<String, Object>> getAllReposWithAccessStatus(@Param("userId") Integer userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.gxwebsoft.app.entity.AppPipeline;
|
||||||
|
import com.gxwebsoft.app.param.AppPipelineParam;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CI/CD 流水线配置 Mapper
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-03
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AppPipelineMapper extends BaseMapper<AppPipeline> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询流水线列表
|
||||||
|
*/
|
||||||
|
List<AppPipeline> selectPipelineList(@Param("param") AppPipelineParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询流水线详情
|
||||||
|
*/
|
||||||
|
AppPipeline selectPipelineDetail(@Param("id") Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询应用的所有流水线
|
||||||
|
*/
|
||||||
|
List<AppPipeline> selectByAppId(@Param("appId") Long appId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.gxwebsoft.app.entity.AppProduct;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用产品 Mapper 接口
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AppProductMapper extends BaseMapper<AppProduct> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询应用列表
|
||||||
|
* @param userId 用户ID(可选,为空则查所有)
|
||||||
|
*/
|
||||||
|
IPage<AppProduct> selectPageList(Page<AppProduct> page, @Param("ew") AppProduct product, @Param("userId") Integer userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户的应用列表(创建者)
|
||||||
|
*/
|
||||||
|
List<AppProduct> selectByUserId(@Param("userId") Integer userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询用户创建的应用
|
||||||
|
*/
|
||||||
|
IPage<AppProduct> selectPageByUserId(Page<AppProduct> page, @Param("userId") Integer userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询用户参与的应用
|
||||||
|
*/
|
||||||
|
IPage<AppProduct> selectPageJoinedApps(Page<AppProduct> page, @Param("userId") Integer userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据应用标识查询
|
||||||
|
*/
|
||||||
|
AppProduct selectByCode(@Param("code") String code);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新发布状态
|
||||||
|
*/
|
||||||
|
int updatePublishStatus(@Param("productId") Integer productId, @Param("status") String status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加浏览次数
|
||||||
|
*/
|
||||||
|
int incrementClicks(@Param("productId") Integer productId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加安装次数
|
||||||
|
*/
|
||||||
|
int incrementInstalls(@Param("productId") Integer productId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加下载次数
|
||||||
|
*/
|
||||||
|
int incrementDownloads(@Param("productId") Integer productId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加点赞数
|
||||||
|
*/
|
||||||
|
int incrementLikes(@Param("productId") Integer productId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按用户ID列表批量统计应用数量
|
||||||
|
*
|
||||||
|
* @param userIds 用户ID列表
|
||||||
|
* @return 统计结果列表,每项包含 userId, totalCount, publishedCount
|
||||||
|
*/
|
||||||
|
List<Map> selectStatsByUserIds(@Param("userIds") List<Integer> userIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户可访问的应用列表(带 myRole 字段)
|
||||||
|
* 包含:用户创建的应用(owner)+ 被邀请加入的应用(成员角色)
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 应用列表(带 myRole 字段)
|
||||||
|
*/
|
||||||
|
List<AppProduct> selectAccessibleApps(@Param("userId") Integer userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.gxwebsoft.app.entity.AppResource;
|
||||||
|
import com.gxwebsoft.app.param.AppResourceParam;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开发者资源 Mapper
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-03-31
|
||||||
|
*/
|
||||||
|
public interface AppResourceMapper extends BaseMapper<AppResource> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询(关联应用名称)
|
||||||
|
*/
|
||||||
|
List<AppResource> selectPageRel(@Param("page") IPage<AppResource> page,
|
||||||
|
@Param("param") AppResourceParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询全部列表(关联应用名称)
|
||||||
|
*/
|
||||||
|
List<AppResource> selectListRel(@Param("param") AppResourceParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户查询有权限的资源(包括协作资源)
|
||||||
|
* 查询条件:owner_user_id = #{userId} OR app_id IN (用户有权限的应用ID列表)
|
||||||
|
*/
|
||||||
|
List<AppResource> selectListByUserAccess(@Param("param") AppResourceParam param,
|
||||||
|
@Param("userAppIds") List<Long> userAppIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计各类型资源数量(仅个人资源),返回 [{resourceType, cnt}]
|
||||||
|
*/
|
||||||
|
List<java.util.Map<String, Object>> countByType(@Param("userId") Integer userId,
|
||||||
|
@Param("tenantId") Integer tenantId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计各类型资源数量(包含协作者有权限的资源)
|
||||||
|
*/
|
||||||
|
List<java.util.Map<String, Object>> countByTypeForUser(@Param("userId") Integer userId,
|
||||||
|
@Param("tenantId") Integer tenantId,
|
||||||
|
@Param("userAppIds") List<Long> userAppIds);
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.gxwebsoft.app.entity.AppSetting;
|
||||||
|
import com.gxwebsoft.app.param.AppSettingParam;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台设置表 Mapper
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AppSettingMapper extends BaseMapper<AppSetting> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
*/
|
||||||
|
IPage<AppSetting> selectPageRel(IPage<AppSetting> page, @Param("param") AppSettingParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询列表
|
||||||
|
*/
|
||||||
|
List<AppSetting> selectListRel(@Param("param") AppSettingParam param);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.gxwebsoft.app.entity.AppSubscription;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface AppSubscriptionMapper extends BaseMapper<AppSubscription> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.gxwebsoft.app.entity.AppTicket;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用工单 Mapper
|
||||||
|
*/
|
||||||
|
public interface AppTicketMapper extends BaseMapper<AppTicket> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询当天最大序号(ticket_no 格式:TK-YYMMDD-XXXX)
|
||||||
|
*/
|
||||||
|
@Select("SELECT COALESCE(MAX(SUBSTRING(ticket_no, 13, 4) + 0), 0) " +
|
||||||
|
"FROM app_ticket WHERE ticket_no LIKE CONCAT('TK-', #{datePart}, '-%')")
|
||||||
|
Integer selectMaxSeqForDate(@Param("datePart") String datePart);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.gxwebsoft.app.entity.AppTicketReply;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单回复 Mapper
|
||||||
|
*/
|
||||||
|
public interface AppTicketReplyMapper extends BaseMapper<AppTicketReply> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.gxwebsoft.app.entity.AppUserCache;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户缓存 Mapper
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-03
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AppUserCacheMapper extends BaseMapper<AppUserCache> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.gxwebsoft.app.entity.AppUser;
|
||||||
|
import com.gxwebsoft.app.param.AppUserParam;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用成员Mapper
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-03-28 21:29:44
|
||||||
|
*/
|
||||||
|
public interface AppUserMapper extends BaseMapper<AppUser> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
*
|
||||||
|
* @param page 分页对象
|
||||||
|
* @param param 查询参数
|
||||||
|
* @return List<AppUser>
|
||||||
|
*/
|
||||||
|
List<AppUser> selectPageRel(@Param("page") IPage<AppUser> page,
|
||||||
|
@Param("param") AppUserParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询全部
|
||||||
|
*
|
||||||
|
* @param param 查询参数
|
||||||
|
* @return List<User>
|
||||||
|
*/
|
||||||
|
List<AppUser> selectListRel(@Param("param") AppUserParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户参与的应用列表(含角色信息),用于 check-access 和 accessible 接口
|
||||||
|
* 返回:appId, productName, productCode, icon, role, isOwner
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 用户参与的应用及角色信息列表
|
||||||
|
*/
|
||||||
|
List<java.util.Map<String, Object>> selectUserAccessibleApps(@Param("userId") Integer userId);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.gxwebsoft.app.entity.AppVersion;
|
||||||
|
import com.gxwebsoft.app.param.AppVersionParam;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用版本发布记录Mapper
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-03-28 21:29:44
|
||||||
|
*/
|
||||||
|
public interface AppVersionMapper extends BaseMapper<AppVersion> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
*
|
||||||
|
* @param page 分页对象
|
||||||
|
* @param param 查询参数
|
||||||
|
* @return List<AppVersion>
|
||||||
|
*/
|
||||||
|
List<AppVersion> selectPageRel(@Param("page") IPage<AppVersion> page,
|
||||||
|
@Param("param") AppVersionParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询全部
|
||||||
|
*
|
||||||
|
* @param param 查询参数
|
||||||
|
* @return List<User>
|
||||||
|
*/
|
||||||
|
List<AppVersion> selectListRel(@Param("param") AppVersionParam param);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package com.gxwebsoft.app.mapper;
|
||||||
|
|
||||||
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
|
import com.gxwebsoft.common.system.entity.UserBalanceLog;
|
||||||
|
import org.apache.ibatis.annotations.Insert;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
import org.apache.ibatis.annotations.Update;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跨库查询 Mapper
|
||||||
|
* 用于从 gxwebsoft_core 库查询 sys_user 表
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface SysUserCrossDbMapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据手机号查询用户
|
||||||
|
*/
|
||||||
|
@Select("SELECT user_id AS userId, username, nickname, avatar, phone, email, status, tenant_id AS tenantId, balance " +
|
||||||
|
"FROM gxwebsoft_core.sys_user WHERE phone = #{phone} AND deleted = 0 LIMIT 1")
|
||||||
|
User selectByPhone(@Param("phone") String phone);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户ID查询用户
|
||||||
|
*/
|
||||||
|
@Select("SELECT user_id AS userId, username, nickname, avatar, phone, email, status, tenant_id AS tenantId, balance " +
|
||||||
|
"FROM gxwebsoft_core.sys_user WHERE user_id = #{userId} AND deleted = 0 LIMIT 1")
|
||||||
|
User selectByUserId(@Param("userId") Integer userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索用户(按手机号、用户名、昵称模糊搜索)
|
||||||
|
*/
|
||||||
|
@Select("<script>" +
|
||||||
|
"SELECT user_id AS userId, username, nickname, avatar, phone, email, status, tenant_id AS tenantId, balance " +
|
||||||
|
"FROM gxwebsoft_core.sys_user " +
|
||||||
|
"WHERE deleted = 0 " +
|
||||||
|
"AND (phone LIKE CONCAT('%', #{keyword}, '%') " +
|
||||||
|
" OR username LIKE CONCAT('%', #{keyword}, '%') " +
|
||||||
|
" OR nickname LIKE CONCAT('%', #{keyword}, '%')) " +
|
||||||
|
"LIMIT 20" +
|
||||||
|
"</script>")
|
||||||
|
List<User> searchUsers(@Param("keyword") String keyword);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户类型(type字段:0普通用户 1企业用户 2开发者用户)
|
||||||
|
*/
|
||||||
|
@Select("SELECT type FROM gxwebsoft_core.sys_user WHERE user_id = #{userId} AND deleted = 0 LIMIT 1")
|
||||||
|
Integer selectUserType(@Param("userId") Integer userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户余额(跨库更新 gxwebsoft_core.sys_user)
|
||||||
|
*/
|
||||||
|
@Update("UPDATE gxwebsoft_core.sys_user SET balance = #{balance} WHERE user_id = #{userId} AND deleted = 0")
|
||||||
|
int updateBalance(@Param("userId") Integer userId, @Param("balance") BigDecimal balance);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户ID更新(跨库更新 gxwebsoft_core.sys_user)
|
||||||
|
*/
|
||||||
|
@Update("<script>" +
|
||||||
|
"UPDATE gxwebsoft_core.sys_user SET " +
|
||||||
|
"<if test='balance != null'>balance = #{balance},</if>" +
|
||||||
|
"update_time = NOW() " +
|
||||||
|
"WHERE user_id = #{userId} AND deleted = 0" +
|
||||||
|
"</script>")
|
||||||
|
int updateByUserId(@Param("userId") Integer userId, @Param("balance") BigDecimal balance);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入余额日志(跨库插入 gxwebsoft_core.sys_user_balance_log)
|
||||||
|
*/
|
||||||
|
@Insert("INSERT INTO gxwebsoft_core.sys_user_balance_log " +
|
||||||
|
"(user_id, scene, money, balance, order_no, comments, tenant_id, create_time) " +
|
||||||
|
"VALUES (#{userId}, #{scene}, #{money}, #{balance}, #{orderNo}, #{comments}, #{tenantId}, NOW())")
|
||||||
|
int insertBalanceLog(@Param("userId") Integer userId,
|
||||||
|
@Param("scene") Integer scene,
|
||||||
|
@Param("money") BigDecimal money,
|
||||||
|
@Param("balance") BigDecimal balance,
|
||||||
|
@Param("orderNo") String orderNo,
|
||||||
|
@Param("comments") String comments,
|
||||||
|
@Param("tenantId") Integer tenantId);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.gxwebsoft.app.mapper.AppApiKeyMapper">
|
||||||
|
|
||||||
|
<resultMap id="BaseResultMap" type="com.gxwebsoft.app.entity.AppApiKey">
|
||||||
|
<id column="id" property="id"/>
|
||||||
|
<result column="name" property="name"/>
|
||||||
|
<result column="api_key" property="apiKey"/>
|
||||||
|
<result column="key_prefix" property="keyPrefix"/>
|
||||||
|
<result column="status" property="status"/>
|
||||||
|
<result column="scopes" property="scopes"/>
|
||||||
|
<result column="expire_time" property="expireTime"/>
|
||||||
|
<result column="last_used_at" property="lastUsedAt"/>
|
||||||
|
<result column="usage_count" property="usageCount"/>
|
||||||
|
<result column="remark" property="remark"/>
|
||||||
|
<result column="deleted" property="deleted"/>
|
||||||
|
<result column="user_id" property="userId"/>
|
||||||
|
<result column="tenant_id" property="tenantId"/>
|
||||||
|
<result column="create_time" property="createTime"/>
|
||||||
|
<result column="update_time" property="updateTime"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
id, name, api_key, key_prefix, status, scopes, expire_time,
|
||||||
|
last_used_at, usage_count, remark, deleted, user_id, tenant_id,
|
||||||
|
create_time, update_time
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<!-- 分页查询 -->
|
||||||
|
<select id="selectPageRel" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM app_api_key
|
||||||
|
WHERE deleted = 0
|
||||||
|
<if test="param.name != null and param.name != ''">
|
||||||
|
AND name LIKE CONCAT('%', #{param.name}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="param.status != null">
|
||||||
|
AND status = #{param.status}
|
||||||
|
</if>
|
||||||
|
<if test="param.userId != null">
|
||||||
|
AND user_id = #{param.userId}
|
||||||
|
</if>
|
||||||
|
<if test="param.tenantId != null">
|
||||||
|
AND tenant_id = #{param.tenantId}
|
||||||
|
</if>
|
||||||
|
ORDER BY create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 列表查询 -->
|
||||||
|
<select id="selectListRel" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM app_api_key
|
||||||
|
WHERE deleted = 0
|
||||||
|
<if test="param.name != null and param.name != ''">
|
||||||
|
AND name LIKE CONCAT('%', #{param.name}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="param.status != null">
|
||||||
|
AND status = #{param.status}
|
||||||
|
</if>
|
||||||
|
<if test="param.userId != null">
|
||||||
|
AND user_id = #{param.userId}
|
||||||
|
</if>
|
||||||
|
<if test="param.tenantId != null">
|
||||||
|
AND tenant_id = #{param.tenantId}
|
||||||
|
</if>
|
||||||
|
ORDER BY create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 根据ID查询 -->
|
||||||
|
<select id="selectByIdRel" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM app_api_key
|
||||||
|
WHERE id = #{id} AND deleted = 0
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.gxwebsoft.app.mapper.AppBuildMapper">
|
||||||
|
|
||||||
|
<resultMap id="BaseResultMap" type="com.gxwebsoft.app.entity.AppBuild">
|
||||||
|
<id column="id" property="id"/>
|
||||||
|
<result column="app_id" property="appId"/>
|
||||||
|
<result column="version_id" property="versionId"/>
|
||||||
|
<result column="build_number" property="buildNumber"/>
|
||||||
|
<result column="name" property="name"/>
|
||||||
|
<result column="branch" property="branch"/>
|
||||||
|
<result column="commit_sha" property="commitSha"/>
|
||||||
|
<result column="commit_message" property="commitMessage"/>
|
||||||
|
<result column="commit_author" property="commitAuthor"/>
|
||||||
|
<result column="ci_type" property="ciType"/>
|
||||||
|
<result column="ci_job_id" property="ciJobId"/>
|
||||||
|
<result column="ci_run_id" property="ciRunId"/>
|
||||||
|
<result column="ci_api_url" property="ciApiUrl"/>
|
||||||
|
<result column="status" property="status"/>
|
||||||
|
<result column="started_at" property="startedAt"/>
|
||||||
|
<result column="finished_at" property="finishedAt"/>
|
||||||
|
<result column="duration" property="duration"/>
|
||||||
|
<result column="log_url" property="logUrl"/>
|
||||||
|
<result column="artifact_url" property="artifactUrl"/>
|
||||||
|
<result column="artifact_name" property="artifactName"/>
|
||||||
|
<result column="artifact_size" property="artifactSize"/>
|
||||||
|
<result column="error_message" property="errorMessage"/>
|
||||||
|
<result column="trigger_type" property="triggerType"/>
|
||||||
|
<result column="triggered_by" property="triggeredBy"/>
|
||||||
|
<result column="config" property="config"/>
|
||||||
|
<result column="user_id" property="userId"/>
|
||||||
|
<result column="tenant_id" property="tenantId"/>
|
||||||
|
<result column="create_time" property="createTime"/>
|
||||||
|
<result column="update_time" property="updateTime"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
id, app_id, version_id, build_number, name, branch, commit_sha, commit_message,
|
||||||
|
commit_author, ci_type, ci_job_id, ci_run_id, ci_api_url, status, started_at,
|
||||||
|
finished_at, duration, log_url, artifact_url, artifact_name, artifact_size,
|
||||||
|
error_message, trigger_type, triggered_by, config, user_id, tenant_id,
|
||||||
|
create_time, update_time
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="selectBuildList" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM app_build
|
||||||
|
WHERE deleted = 0
|
||||||
|
<if test="param.appId != null">
|
||||||
|
AND app_id = #{param.appId}
|
||||||
|
</if>
|
||||||
|
<if test="param.userId != null">
|
||||||
|
AND user_id = #{param.userId}
|
||||||
|
</if>
|
||||||
|
<if test="param.status != null and param.status != ''">
|
||||||
|
AND status = #{param.status}
|
||||||
|
</if>
|
||||||
|
<if test="param.ciType != null and param.ciType != ''">
|
||||||
|
AND ci_type = #{param.ciType}
|
||||||
|
</if>
|
||||||
|
<if test="param.branch != null and param.branch != ''">
|
||||||
|
AND branch = #{param.branch}
|
||||||
|
</if>
|
||||||
|
ORDER BY create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectBuildDetail" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM app_build
|
||||||
|
WHERE id = #{id} AND deleted = 0
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectLatestBuild" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM app_build
|
||||||
|
WHERE app_id = #{appId} AND deleted = 0
|
||||||
|
ORDER BY create_time DESC
|
||||||
|
LIMIT 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="countBuildsByStatus" resultType="java.lang.Integer">
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM app_build
|
||||||
|
WHERE app_id = #{appId} AND status = #{status} AND deleted = 0
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.gxwebsoft.app.mapper.AppCloudCredentialMapper">
|
||||||
|
|
||||||
|
<!-- 关联查询sql -->
|
||||||
|
<sql id="selectSql">
|
||||||
|
SELECT a.*
|
||||||
|
FROM app_cloud_credential a
|
||||||
|
<where>
|
||||||
|
<if test="param.id != null">
|
||||||
|
AND a.id = #{param.id}
|
||||||
|
</if>
|
||||||
|
<if test="param.provider != null and param.provider != ''">
|
||||||
|
AND a.provider = #{param.provider}
|
||||||
|
</if>
|
||||||
|
<if test="param.name != null and param.name != ''">
|
||||||
|
AND a.name LIKE CONCAT('%', #{param.name}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="param.status != null">
|
||||||
|
AND a.status = #{param.status}
|
||||||
|
</if>
|
||||||
|
<if test="param.deleted != null">
|
||||||
|
AND a.deleted = #{param.deleted}
|
||||||
|
</if>
|
||||||
|
<if test="param.deleted == null">
|
||||||
|
AND a.deleted = 0
|
||||||
|
</if>
|
||||||
|
<if test="param.userId != null">
|
||||||
|
AND a.user_id = #{param.userId}
|
||||||
|
</if>
|
||||||
|
<if test="param.tenantId != null">
|
||||||
|
AND a.tenant_id = #{param.tenantId}
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<!-- 分页查询 -->
|
||||||
|
<select id="selectPageRel" resultType="com.gxwebsoft.app.entity.AppCloudCredential">
|
||||||
|
<include refid="selectSql"></include>
|
||||||
|
ORDER BY a.id DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询全部 -->
|
||||||
|
<select id="selectListRel" resultType="com.gxwebsoft.app.entity.AppCloudCredential">
|
||||||
|
<include refid="selectSql"></include>
|
||||||
|
ORDER BY a.id DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.gxwebsoft.app.mapper.AppConfigMapper">
|
||||||
|
|
||||||
|
<!-- 关联查询sql -->
|
||||||
|
<sql id="selectSql">
|
||||||
|
SELECT a.*
|
||||||
|
FROM app_config a
|
||||||
|
<where>
|
||||||
|
a.deleted = 0
|
||||||
|
<if test="param.configId != null">
|
||||||
|
AND a.config_id = #{param.configId}
|
||||||
|
</if>
|
||||||
|
<if test="param.appId != null">
|
||||||
|
AND a.app_id = #{param.appId}
|
||||||
|
</if>
|
||||||
|
<if test="param.configKey != null and param.configKey != ''">
|
||||||
|
AND a.config_key LIKE CONCAT('%', #{param.configKey}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="param.configType != null and param.configType != ''">
|
||||||
|
AND a.config_type = #{param.configType}
|
||||||
|
</if>
|
||||||
|
<if test="param.isSecret != null">
|
||||||
|
AND a.is_secret = #{param.isSecret}
|
||||||
|
</if>
|
||||||
|
<if test="param.tenantId != null">
|
||||||
|
AND a.tenant_id = #{param.tenantId}
|
||||||
|
</if>
|
||||||
|
<if test="param.createTimeStart != null">
|
||||||
|
AND a.create_time >= #{param.createTimeStart}
|
||||||
|
</if>
|
||||||
|
<if test="param.createTimeEnd != null">
|
||||||
|
AND a.create_time <= #{param.createTimeEnd}
|
||||||
|
</if>
|
||||||
|
<if test="param.keywords != null and param.keywords != ''">
|
||||||
|
AND a.config_key LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<!-- 分页查询 -->
|
||||||
|
<select id="selectPageRel" resultType="com.gxwebsoft.app.entity.AppConfig">
|
||||||
|
<include refid="selectSql"></include>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询全部 -->
|
||||||
|
<select id="selectListRel" resultType="com.gxwebsoft.app.entity.AppConfig">
|
||||||
|
<include refid="selectSql"></include>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 批量获取应用配置(自动解密) -->
|
||||||
|
<select id="selectConfigsByAppId" resultType="java.util.HashMap">
|
||||||
|
SELECT
|
||||||
|
config_key as configKey,
|
||||||
|
config_value as configValue,
|
||||||
|
config_type as configType,
|
||||||
|
is_encrypted as isEncrypted,
|
||||||
|
is_secret as isSecret,
|
||||||
|
description
|
||||||
|
FROM app_config
|
||||||
|
WHERE app_id = #{appId}
|
||||||
|
AND deleted = 0
|
||||||
|
ORDER BY config_type, sort_number, config_id
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.gxwebsoft.app.mapper.AppCredentialMapper">
|
||||||
|
|
||||||
|
<!-- 关联查询sql -->
|
||||||
|
<sql id="selectSql">
|
||||||
|
SELECT a.*, w.product_name, w.product_code, w.icon
|
||||||
|
FROM app_credential a
|
||||||
|
LEFT JOIN app_product w ON a.app_id = w.product_id AND w.deleted = 0
|
||||||
|
<where>
|
||||||
|
<if test="param.id != null">
|
||||||
|
AND a.id = #{param.id}
|
||||||
|
</if>
|
||||||
|
<if test="param.appId != null">
|
||||||
|
AND a.app_id = #{param.appId}
|
||||||
|
</if>
|
||||||
|
<if test="param.name != null and param.name != ''">
|
||||||
|
AND a.name LIKE CONCAT('%', #{param.name}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="param.clientId != null and param.clientId != ''">
|
||||||
|
AND a.client_id = #{param.clientId}
|
||||||
|
</if>
|
||||||
|
<if test="param.type != null and param.type != ''">
|
||||||
|
AND a.type = #{param.type}
|
||||||
|
</if>
|
||||||
|
<if test="param.scopes != null and param.scopes != ''">
|
||||||
|
AND a.scopes LIKE CONCAT('%', #{param.scopes}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="param.remark != null and param.remark != ''">
|
||||||
|
AND a.remark LIKE CONCAT('%', #{param.remark}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="param.sortNumber != null">
|
||||||
|
AND a.sort_number = #{param.sortNumber}
|
||||||
|
</if>
|
||||||
|
<if test="param.status != null">
|
||||||
|
AND a.status = #{param.status}
|
||||||
|
</if>
|
||||||
|
<if test="param.deleted != null">
|
||||||
|
AND a.deleted = #{param.deleted}
|
||||||
|
</if>
|
||||||
|
<if test="param.deleted == null">
|
||||||
|
AND a.deleted = 0
|
||||||
|
</if>
|
||||||
|
<if test="param.userId != null">
|
||||||
|
AND a.user_id = #{param.userId}
|
||||||
|
</if>
|
||||||
|
<if test="param.createTimeStart != null">
|
||||||
|
AND a.create_time >= #{param.createTimeStart}
|
||||||
|
</if>
|
||||||
|
<if test="param.createTimeEnd != null">
|
||||||
|
AND a.create_time <= #{param.createTimeEnd}
|
||||||
|
</if>
|
||||||
|
<if test="param.keywords != null and param.keywords != ''">
|
||||||
|
AND (a.name LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
|
OR a.client_id LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
|
)
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<!-- 分页查询 -->
|
||||||
|
<select id="selectPageRel" resultType="com.gxwebsoft.app.entity.AppCredential">
|
||||||
|
<include refid="selectSql"></include>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询全部 -->
|
||||||
|
<select id="selectListRel" resultType="com.gxwebsoft.app.entity.AppCredential">
|
||||||
|
<include refid="selectSql"></include>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.gxwebsoft.app.mapper.AppEventMapper">
|
||||||
|
|
||||||
|
<!-- 关联查询sql -->
|
||||||
|
<sql id="selectSql">
|
||||||
|
SELECT a.*, w.product_name, w.product_code, w.icon
|
||||||
|
FROM app_event a
|
||||||
|
LEFT JOIN app_product w ON a.app_id = w.product_id AND w.deleted = 0
|
||||||
|
<where>
|
||||||
|
<if test="param.id != null">
|
||||||
|
AND a.id = #{param.id}
|
||||||
|
</if>
|
||||||
|
<if test="param.appId != null">
|
||||||
|
AND a.app_id = #{param.appId}
|
||||||
|
</if>
|
||||||
|
<if test="param.eventType != null and param.eventType != ''">
|
||||||
|
AND a.event_type = #{param.eventType}
|
||||||
|
</if>
|
||||||
|
<if test="param.title != null and param.title != ''">
|
||||||
|
AND a.title LIKE CONCAT('%', #{param.title}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="param.operatorId != null">
|
||||||
|
AND a.operator_id = #{param.operatorId}
|
||||||
|
</if>
|
||||||
|
<if test="param.refId != null">
|
||||||
|
AND a.ref_id = #{param.refId}
|
||||||
|
</if>
|
||||||
|
<if test="param.refType != null and param.refType != ''">
|
||||||
|
AND a.ref_type = #{param.refType}
|
||||||
|
</if>
|
||||||
|
<if test="param.status != null">
|
||||||
|
AND a.status = #{param.status}
|
||||||
|
</if>
|
||||||
|
<if test="param.userId != null">
|
||||||
|
AND a.user_id = #{param.userId}
|
||||||
|
</if>
|
||||||
|
<if test="param.tenantId != null">
|
||||||
|
AND a.tenant_id = #{param.tenantId}
|
||||||
|
</if>
|
||||||
|
<if test="param.createTimeStart != null">
|
||||||
|
AND a.create_time >= #{param.createTimeStart}
|
||||||
|
</if>
|
||||||
|
<if test="param.createTimeEnd != null">
|
||||||
|
AND a.create_time <= #{param.createTimeEnd}
|
||||||
|
</if>
|
||||||
|
<if test="param.keywords != null and param.keywords != ''">
|
||||||
|
AND (a.title LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
|
OR a.content LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
|
)
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY a.create_time DESC
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<!-- 分页查询 -->
|
||||||
|
<select id="selectPageRel" resultType="com.gxwebsoft.app.entity.AppEvent">
|
||||||
|
<include refid="selectSql"></include>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询全部 -->
|
||||||
|
<select id="selectListRel" resultType="com.gxwebsoft.app.entity.AppEvent">
|
||||||
|
<include refid="selectSql"></include>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.gxwebsoft.app.mapper.AppGitAccountMapper">
|
||||||
|
|
||||||
|
<resultMap id="BaseResultMap" type="com.gxwebsoft.app.entity.AppGitAccount">
|
||||||
|
<id column="id" property="id"/>
|
||||||
|
<result column="user_id" property="userId"/>
|
||||||
|
<result column="username" property="username"/>
|
||||||
|
<result column="email" property="email"/>
|
||||||
|
<result column="remark" property="remark"/>
|
||||||
|
<result column="status" property="status"/>
|
||||||
|
<result column="verification_note" property="verificationNote"/>
|
||||||
|
<result column="tenant_id" property="tenantId"/>
|
||||||
|
<result column="create_time" property="createTime"/>
|
||||||
|
<result column="update_time" property="updateTime"/>
|
||||||
|
<result column="deleted" property="deleted"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<!-- 根据用户ID查询 -->
|
||||||
|
<select id="selectByUserId" resultMap="BaseResultMap">
|
||||||
|
SELECT * FROM app_git_account
|
||||||
|
WHERE user_id = #{userId} AND deleted = 0
|
||||||
|
LIMIT 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 根据用户名查询(检查唯一性) -->
|
||||||
|
<select id="selectByUsername" resultMap="BaseResultMap">
|
||||||
|
SELECT * FROM app_git_account
|
||||||
|
WHERE username = #{username} AND deleted = 0
|
||||||
|
LIMIT 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 检查用户名是否被其他用户绑定(同一租户下) -->
|
||||||
|
<select id="checkUsernameOccupied" resultMap="BaseResultMap">
|
||||||
|
SELECT * FROM app_git_account
|
||||||
|
WHERE username = #{username} AND user_id != #{excludeUserId} AND tenant_id = #{tenantId} AND deleted = 0
|
||||||
|
LIMIT 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 根据用户ID和租户ID查询 -->
|
||||||
|
<select id="selectByUserIdAndTenantId" resultMap="BaseResultMap">
|
||||||
|
SELECT * FROM app_git_account
|
||||||
|
WHERE user_id = #{userId} AND tenant_id = #{tenantId} AND deleted = 0
|
||||||
|
LIMIT 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.gxwebsoft.app.mapper.AppPermissionRequestMapper">
|
||||||
|
|
||||||
|
<!-- 获取用户已可访问的仓库列表 -->
|
||||||
|
<select id="getAccessibleRepos" resultType="java.lang.String">
|
||||||
|
SELECT DISTINCT repo
|
||||||
|
FROM app_permission_request
|
||||||
|
WHERE user_id = #{userId}
|
||||||
|
AND status = 'approved'
|
||||||
|
AND deleted = 0
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 获取所有可用仓库列表(含访问状态) -->
|
||||||
|
<select id="getAllReposWithAccessStatus" resultType="java.util.Map">
|
||||||
|
SELECT
|
||||||
|
r.path AS value,
|
||||||
|
r.name AS label,
|
||||||
|
r.description,
|
||||||
|
r.access_level AS accessLevel,
|
||||||
|
CASE WHEN pr.repo IS NOT NULL THEN 1 ELSE 0 END AS isAccessible
|
||||||
|
FROM app_repository r
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT repo
|
||||||
|
FROM app_permission_request
|
||||||
|
WHERE user_id = #{userId}
|
||||||
|
AND status = 'approved'
|
||||||
|
AND deleted = 0
|
||||||
|
) pr ON r.path = pr.repo
|
||||||
|
WHERE r.deleted = 0
|
||||||
|
ORDER BY r.name
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.gxwebsoft.app.mapper.AppPipelineMapper">
|
||||||
|
|
||||||
|
<resultMap id="BaseResultMap" type="com.gxwebsoft.app.entity.AppPipeline">
|
||||||
|
<id column="id" property="id"/>
|
||||||
|
<result column="app_id" property="appId"/>
|
||||||
|
<result column="name" property="name"/>
|
||||||
|
<result column="description" property="description"/>
|
||||||
|
<result column="ci_type" property="ciType"/>
|
||||||
|
<result column="repo_full_name" property="repoFullName"/>
|
||||||
|
<result column="workflow_file" property="workflowFile"/>
|
||||||
|
<result column="stages" property="stages"/>
|
||||||
|
<result column="env" property="env"/>
|
||||||
|
<result column="default_branch" property="defaultBranch"/>
|
||||||
|
<result column="enabled" property="enabled"/>
|
||||||
|
<result column="auto_deploy" property="autoDeploy"/>
|
||||||
|
<result column="timeout" property="timeout"/>
|
||||||
|
<result column="config" property="config"/>
|
||||||
|
<result column="last_build_id" property="lastBuildId"/>
|
||||||
|
<result column="last_build_status" property="lastBuildStatus"/>
|
||||||
|
<result column="last_build_time" property="lastBuildTime"/>
|
||||||
|
<result column="success_count" property="successCount"/>
|
||||||
|
<result column="failure_count" property="failureCount"/>
|
||||||
|
<result column="user_id" property="userId"/>
|
||||||
|
<result column="tenant_id" property="tenantId"/>
|
||||||
|
<result column="create_time" property="createTime"/>
|
||||||
|
<result column="update_time" property="updateTime"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
id, app_id, name, description, ci_type, repo_full_name, workflow_file, stages,
|
||||||
|
env, default_branch, enabled, auto_deploy, timeout, config, last_build_id,
|
||||||
|
last_build_status, last_build_time, success_count, failure_count, user_id,
|
||||||
|
tenant_id, create_time, update_time
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="selectPipelineList" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM app_pipeline
|
||||||
|
WHERE deleted = 0
|
||||||
|
<if test="param.appId != null">
|
||||||
|
AND app_id = #{param.appId}
|
||||||
|
</if>
|
||||||
|
<if test="param.userId != null">
|
||||||
|
AND user_id = #{param.userId}
|
||||||
|
</if>
|
||||||
|
<if test="param.ciType != null and param.ciType != ''">
|
||||||
|
AND ci_type = #{param.ciType}
|
||||||
|
</if>
|
||||||
|
<if test="param.enabled != null">
|
||||||
|
AND enabled = #{param.enabled}
|
||||||
|
</if>
|
||||||
|
ORDER BY create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectPipelineDetail" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM app_pipeline
|
||||||
|
WHERE id = #{id} AND deleted = 0
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectByAppId" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM app_pipeline
|
||||||
|
WHERE app_id = #{appId} AND deleted = 0
|
||||||
|
ORDER BY create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.gxwebsoft.app.mapper.AppProductMapper">
|
||||||
|
|
||||||
|
<resultMap id="BaseResultMap" type="com.gxwebsoft.app.entity.AppProduct">
|
||||||
|
<id column="product_id" property="productId"/>
|
||||||
|
<result column="product_name" property="productName"/>
|
||||||
|
<result column="product_code" property="productCode"/>
|
||||||
|
<result column="product_secret" property="productSecret"/>
|
||||||
|
<result column="app_type" property="appType"/>
|
||||||
|
<result column="category_id" property="categoryId"/>
|
||||||
|
<result column="industry_parent" property="industryParent"/>
|
||||||
|
<result column="industry_child" property="industryChild"/>
|
||||||
|
<result column="logo" property="logo"/>
|
||||||
|
<result column="icon" property="icon"/>
|
||||||
|
<result column="qrcode" property="qrcode"/>
|
||||||
|
<result column="screenshots" property="screenshots"/>
|
||||||
|
<result column="description" property="description"/>
|
||||||
|
<result column="content" property="content"/>
|
||||||
|
<result column="keywords" property="keywords"/>
|
||||||
|
<result column="domain" property="domain"/>
|
||||||
|
<result column="prefix" property="prefix"/>
|
||||||
|
<result column="package_name" property="packageName"/>
|
||||||
|
<result column="admin_url" property="adminUrl"/>
|
||||||
|
<result column="api_url" property="apiUrl"/>
|
||||||
|
<result column="download_url" property="downloadUrl"/>
|
||||||
|
<result column="version" property="version"/>
|
||||||
|
<result column="edition" property="edition"/>
|
||||||
|
<result column="min_version" property="minVersion"/>
|
||||||
|
<result column="price_type" property="priceType"/>
|
||||||
|
<result column="price" property="price"/>
|
||||||
|
<result column="line_price" property="linePrice"/>
|
||||||
|
<result column="renew_price" property="renewPrice"/>
|
||||||
|
<result column="delivery_method" property="deliveryMethod"/>
|
||||||
|
<result column="charging_method" property="chargingMethod"/>
|
||||||
|
<result column="subscription_period" property="subscriptionPeriod"/>
|
||||||
|
<result column="publish_status" property="publishStatus"/>
|
||||||
|
<result column="publish_time" property="publishTime"/>
|
||||||
|
<result column="review_time" property="reviewTime"/>
|
||||||
|
<result column="reviewer_id" property="reviewerId"/>
|
||||||
|
<result column="reject_reason" property="rejectReason"/>
|
||||||
|
<result column="clicks" property="clicks"/>
|
||||||
|
<result column="installs" property="installs"/>
|
||||||
|
<result column="downloads" property="downloads"/>
|
||||||
|
<result column="rating" property="rating"/>
|
||||||
|
<result column="likes" property="likes"/>
|
||||||
|
<result column="developer" property="developer"/>
|
||||||
|
<result column="developer_phone" property="developerPhone"/>
|
||||||
|
<result column="developer_email" property="developerEmail"/>
|
||||||
|
<result column="recommend" property="recommend"/>
|
||||||
|
<result column="official" property="official"/>
|
||||||
|
<result column="market" property="market"/>
|
||||||
|
<result column="show_index" property="showIndex"/>
|
||||||
|
<result column="search_enabled" property="searchEnabled"/>
|
||||||
|
<result column="template_id" property="templateId"/>
|
||||||
|
<result column="style" property="style"/>
|
||||||
|
<result column="config" property="config"/>
|
||||||
|
<result column="theme_color" property="themeColor"/>
|
||||||
|
<result column="lang" property="lang"/>
|
||||||
|
<result column="icp_no" property="icpNo"/>
|
||||||
|
<result column="police_no" property="policeNo"/>
|
||||||
|
<result column="status" property="status"/>
|
||||||
|
<result column="status_text" property="statusText"/>
|
||||||
|
<result column="running" property="running"/>
|
||||||
|
<result column="expiration_time" property="expirationTime"/>
|
||||||
|
<result column="sort_number" property="sortNumber"/>
|
||||||
|
<result column="deleted" property="deleted"/>
|
||||||
|
<result column="user_id" property="userId"/>
|
||||||
|
<result column="tenant_id" property="tenantId"/>
|
||||||
|
<result column="create_time" property="createTime"/>
|
||||||
|
<result column="update_time" property="updateTime"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<resultMap id="DetailResultMap" type="com.gxwebsoft.app.entity.AppProduct" extends="BaseResultMap">
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<!-- 带 myRole 的结果映射(用于 accessible 接口) -->
|
||||||
|
<resultMap id="AccessibleResultMap" type="com.gxwebsoft.app.entity.AppProduct" extends="BaseResultMap">
|
||||||
|
<result column="my_role" property="myRole"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<!-- 分页查询 -->
|
||||||
|
<!-- 当 userId 不为空时,只返回该用户创建的应用或参与的应用 -->
|
||||||
|
<select id="selectPageList" resultMap="DetailResultMap">
|
||||||
|
SELECT DISTINCT p.*
|
||||||
|
FROM app_product p
|
||||||
|
<where>
|
||||||
|
<if test="ew.productName != null and ew.productName != ''">
|
||||||
|
AND p.product_name LIKE CONCAT('%', #{ew.productName}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="ew.productCode != null and ew.productCode != ''">
|
||||||
|
AND p.product_code = #{ew.productCode}
|
||||||
|
</if>
|
||||||
|
<if test="ew.appType != null">
|
||||||
|
AND p.app_type = #{ew.appType}
|
||||||
|
</if>
|
||||||
|
<if test="ew.categoryId != null">
|
||||||
|
AND p.category_id = #{ew.categoryId}
|
||||||
|
</if>
|
||||||
|
<if test="ew.publishStatus != null and ew.publishStatus != ''">
|
||||||
|
AND p.publish_status = #{ew.publishStatus}
|
||||||
|
</if>
|
||||||
|
<if test="ew.status != null">
|
||||||
|
AND p.status = #{ew.status}
|
||||||
|
</if>
|
||||||
|
<if test="ew.userId != null">
|
||||||
|
AND p.user_id = #{ew.userId}
|
||||||
|
</if>
|
||||||
|
<if test="ew.tenantId != null">
|
||||||
|
AND p.tenant_id = #{ew.tenantId}
|
||||||
|
</if>
|
||||||
|
<if test="ew.market != null">
|
||||||
|
AND p.market = #{ew.market}
|
||||||
|
</if>
|
||||||
|
<if test="ew.recommend != null">
|
||||||
|
AND p.recommend = #{ew.recommend}
|
||||||
|
</if>
|
||||||
|
<if test="ew.deleted != null">
|
||||||
|
AND p.deleted = #{ew.deleted}
|
||||||
|
</if>
|
||||||
|
<!-- 如果传入了 userId,过滤为该用户创建或参与的应用(已确认的成员) -->
|
||||||
|
<if test="userId != null">
|
||||||
|
AND (
|
||||||
|
p.user_id = #{userId}
|
||||||
|
OR EXISTS (
|
||||||
|
SELECT 1 FROM app_user au
|
||||||
|
WHERE au.app_id = p.product_id
|
||||||
|
AND au.user_id = #{userId}
|
||||||
|
AND au.status = 0
|
||||||
|
AND au.invite_status = 0 <!-- 只查询已确认的邀请 -->
|
||||||
|
)
|
||||||
|
)
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY p.sort_number ASC, p.create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 根据用户ID查询 -->
|
||||||
|
<select id="selectByUserId" resultMap="DetailResultMap">
|
||||||
|
SELECT p.*
|
||||||
|
FROM app_product p
|
||||||
|
WHERE p.user_id = #{userId}
|
||||||
|
AND p.deleted = 0
|
||||||
|
ORDER BY p.create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 分页查询用户创建的应用 -->
|
||||||
|
<select id="selectPageByUserId" resultMap="DetailResultMap">
|
||||||
|
SELECT p.*
|
||||||
|
FROM app_product p
|
||||||
|
WHERE p.user_id = #{userId}
|
||||||
|
AND p.deleted = 0
|
||||||
|
ORDER BY p.sort_number ASC, p.create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 分页查询用户参与的应用(通过app_user关联,不是创建者,且已确认) -->
|
||||||
|
<!-- 按 app_user.update_time 排序,实现"最近使用"效果 -->
|
||||||
|
<select id="selectPageJoinedApps" resultMap="DetailResultMap">
|
||||||
|
SELECT p.*
|
||||||
|
FROM app_product p
|
||||||
|
INNER JOIN app_user au ON p.product_id = au.app_id
|
||||||
|
WHERE au.user_id = #{userId}
|
||||||
|
AND au.status = 0
|
||||||
|
AND au.invite_status = 0 <!-- 只查询已确认的邀请 -->
|
||||||
|
AND p.deleted = 0
|
||||||
|
ORDER BY au.update_time DESC, p.sort_number ASC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 根据标识查询 -->
|
||||||
|
<select id="selectByCode" resultMap="DetailResultMap">
|
||||||
|
SELECT p.*
|
||||||
|
FROM app_product p
|
||||||
|
WHERE p.product_code = #{code}
|
||||||
|
AND p.deleted = 0
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 更新发布状态 -->
|
||||||
|
<update id="updatePublishStatus">
|
||||||
|
UPDATE app_product
|
||||||
|
SET publish_status = #{status},
|
||||||
|
update_time = NOW()
|
||||||
|
<if test="status == 'published'">
|
||||||
|
, publish_time = NOW()
|
||||||
|
</if>
|
||||||
|
<if test="status == 'pending_review'">
|
||||||
|
, review_time = NOW()
|
||||||
|
</if>
|
||||||
|
WHERE product_id = #{productId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<!-- 增加浏览次数 -->
|
||||||
|
<update id="incrementClicks">
|
||||||
|
UPDATE app_product SET clicks = clicks + 1 WHERE product_id = #{productId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<!-- 增加安装次数 -->
|
||||||
|
<update id="incrementInstalls">
|
||||||
|
UPDATE app_product SET installs = installs + 1 WHERE product_id = #{productId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<!-- 增加下载次数 -->
|
||||||
|
<update id="incrementDownloads">
|
||||||
|
UPDATE app_product SET downloads = downloads + 1 WHERE product_id = #{productId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<!-- 增加点赞数 -->
|
||||||
|
<update id="incrementLikes">
|
||||||
|
UPDATE app_product SET likes = likes + 1 WHERE product_id = #{productId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<!-- 按用户ID列表批量统计应用数量(一条SQL搞定) -->
|
||||||
|
<select id="selectStatsByUserIds" resultType="java.util.Map">
|
||||||
|
SELECT
|
||||||
|
user_id AS userId,
|
||||||
|
COUNT(*) AS totalCount,
|
||||||
|
SUM(CASE WHEN publish_status = 'published' THEN 1 ELSE 0 END) AS publishedCount
|
||||||
|
FROM app_product
|
||||||
|
WHERE deleted = 0
|
||||||
|
AND user_id IN
|
||||||
|
<foreach collection="userIds" item="uid" open="(" separator="," close=")">
|
||||||
|
#{uid}
|
||||||
|
</foreach>
|
||||||
|
GROUP BY user_id
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询用户可访问的应用列表(带角色信息),用于 accessible 接口 -->
|
||||||
|
<select id="selectAccessibleApps" resultMap="AccessibleResultMap">
|
||||||
|
SELECT
|
||||||
|
p.*,
|
||||||
|
CASE
|
||||||
|
WHEN p.user_id = #{userId} THEN 'owner'
|
||||||
|
ELSE au.role
|
||||||
|
END AS my_role
|
||||||
|
FROM app_product p
|
||||||
|
LEFT JOIN app_user au ON p.product_id = au.app_id
|
||||||
|
AND au.user_id = #{userId}
|
||||||
|
AND au.status = 0
|
||||||
|
AND au.invite_status = 0 <!-- 只查询已确认的邀请 -->
|
||||||
|
WHERE p.deleted = 0
|
||||||
|
AND (
|
||||||
|
p.user_id = #{userId}
|
||||||
|
OR au.id IS NOT NULL
|
||||||
|
)
|
||||||
|
ORDER BY my_role DESC, p.create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.gxwebsoft.app.mapper.AppResourceMapper">
|
||||||
|
|
||||||
|
<!-- 关联查询 SQL(基础版本,仅 owner 资源) -->
|
||||||
|
<sql id="selectSql">
|
||||||
|
SELECT a.*, w.product_name as appName
|
||||||
|
FROM app_resource a
|
||||||
|
LEFT JOIN app_product w ON a.app_id = w.product_id AND w.deleted = 0
|
||||||
|
<where>
|
||||||
|
a.deleted = 0
|
||||||
|
<if test="param.resourceId != null">
|
||||||
|
AND a.resource_id = #{param.resourceId}
|
||||||
|
</if>
|
||||||
|
<if test="param.resourceType != null and param.resourceType != ''">
|
||||||
|
AND a.resource_type = #{param.resourceType}
|
||||||
|
</if>
|
||||||
|
<if test="param.appId != null">
|
||||||
|
AND a.app_id = #{param.appId}
|
||||||
|
</if>
|
||||||
|
<if test="param.provider != null and param.provider != ''">
|
||||||
|
AND a.provider = #{param.provider}
|
||||||
|
</if>
|
||||||
|
<if test="param.status != null and param.status != ''">
|
||||||
|
AND a.status = #{param.status}
|
||||||
|
</if>
|
||||||
|
<if test="param.userId != null">
|
||||||
|
<!-- 协作权限升级:改为 owner_user_id 或 app_id 有权限 -->
|
||||||
|
AND (a.owner_user_id = #{param.userId}
|
||||||
|
<if test="param.userAppIds != null and param.userAppIds.size > 0">
|
||||||
|
<!-- 用户有权限的应用ID列表 -->
|
||||||
|
OR a.app_id IN
|
||||||
|
<foreach collection="param.userAppIds" item="appId" open="(" separator="," close=")">
|
||||||
|
#{appId}
|
||||||
|
</foreach>
|
||||||
|
</if>
|
||||||
|
)
|
||||||
|
</if>
|
||||||
|
<if test="param.tenantId != null">
|
||||||
|
AND a.tenant_id = #{param.tenantId}
|
||||||
|
</if>
|
||||||
|
<if test="param.keywords != null and param.keywords != ''">
|
||||||
|
AND (
|
||||||
|
a.name LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
|
OR a.ip LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
|
OR a.domain LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
|
OR a.host LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
|
OR w.product_name LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
|
)
|
||||||
|
</if>
|
||||||
|
<if test="param.createTimeStart != null">
|
||||||
|
AND a.create_time >= #{param.createTimeStart}
|
||||||
|
</if>
|
||||||
|
<if test="param.createTimeEnd != null">
|
||||||
|
AND a.create_time <= #{param.createTimeEnd}
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY a.create_time DESC
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<!-- 分页查询 -->
|
||||||
|
<select id="selectPageRel" resultType="com.gxwebsoft.app.entity.AppResource">
|
||||||
|
<include refid="selectSql"/>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询全部 -->
|
||||||
|
<select id="selectListRel" resultType="com.gxwebsoft.app.entity.AppResource">
|
||||||
|
<include refid="selectSql"/>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 根据用户权限查询资源(可扩展使用) -->
|
||||||
|
<select id="selectListByUserAccess" resultType="com.gxwebsoft.app.entity.AppResource">
|
||||||
|
<include refid="selectSql"/>
|
||||||
|
<!-- 注意:此处已通过 <include> 引入了 where 条件,其中包含 userAppIds 判断 -->
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 按类型统计数量(仅自己的资源) -->
|
||||||
|
<select id="countByType" resultType="java.util.Map">
|
||||||
|
SELECT resource_type AS resourceType, COUNT(*) AS cnt
|
||||||
|
FROM app_resource
|
||||||
|
WHERE deleted = 0
|
||||||
|
<if test="userId != null">
|
||||||
|
AND owner_user_id = #{userId}
|
||||||
|
</if>
|
||||||
|
<if test="tenantId != null">
|
||||||
|
AND tenant_id = #{tenantId}
|
||||||
|
</if>
|
||||||
|
GROUP BY resource_type
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 按类型统计数量(包含协作资源) -->
|
||||||
|
<select id="countByTypeForUser" resultType="java.util.Map">
|
||||||
|
SELECT resource_type AS resourceType, COUNT(*) AS cnt
|
||||||
|
FROM app_resource
|
||||||
|
WHERE deleted = 0
|
||||||
|
AND (
|
||||||
|
owner_user_id = #{userId}
|
||||||
|
OR app_id IN
|
||||||
|
<if test="userAppIds != null and userAppIds.size() > 0">
|
||||||
|
<!-- 用户有权限的应用ID列表 -->
|
||||||
|
<foreach collection="userAppIds" item="appId" open="(" separator="," close=")">
|
||||||
|
#{appId}
|
||||||
|
</foreach>
|
||||||
|
</if>
|
||||||
|
<if test="userAppIds == null or userAppIds.size() == 0">
|
||||||
|
(-1) <!-- 没有权限的应用,仅计数自己的资源 -->
|
||||||
|
</if>
|
||||||
|
)
|
||||||
|
<if test="tenantId != null">
|
||||||
|
AND tenant_id = #{tenantId}
|
||||||
|
</if>
|
||||||
|
GROUP BY resource_type
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.gxwebsoft.app.mapper.AppSettingMapper">
|
||||||
|
|
||||||
|
<!-- 关联查询sql -->
|
||||||
|
<sql id="selectSql">
|
||||||
|
SELECT a.*
|
||||||
|
FROM app_setting a
|
||||||
|
<where>
|
||||||
|
a.deleted = 0
|
||||||
|
<if test="param.settingId != null">
|
||||||
|
AND a.setting_id = #{param.settingId}
|
||||||
|
</if>
|
||||||
|
<if test="param.category != null and param.category != ''">
|
||||||
|
AND a.category = #{param.category}
|
||||||
|
</if>
|
||||||
|
<if test="param.settingKey != null and param.settingKey != ''">
|
||||||
|
AND a.setting_key = #{param.settingKey}
|
||||||
|
</if>
|
||||||
|
<if test="param.settingName != null and param.settingName != ''">
|
||||||
|
AND a.setting_name LIKE CONCAT('%', #{param.settingName}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="param.valueType != null and param.valueType != ''">
|
||||||
|
AND a.value_type = #{param.valueType}
|
||||||
|
</if>
|
||||||
|
<if test="param.isEnabled != null">
|
||||||
|
AND a.is_enabled = #{param.isEnabled}
|
||||||
|
</if>
|
||||||
|
<if test="param.isPublic != null">
|
||||||
|
AND a.is_public = #{param.isPublic}
|
||||||
|
</if>
|
||||||
|
<if test="param.tenantId != null">
|
||||||
|
AND a.tenant_id = #{param.tenantId}
|
||||||
|
</if>
|
||||||
|
<if test="param.keywords != null and param.keywords != ''">
|
||||||
|
AND (a.setting_key LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
|
OR a.setting_name LIKE CONCAT('%', #{param.keywords}, '%'))
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<!-- 分页查询 -->
|
||||||
|
<select id="selectPageRel" resultType="com.gxwebsoft.app.entity.AppSetting">
|
||||||
|
<include refid="selectSql"></include>
|
||||||
|
ORDER BY a.category, a.sort_number, a.setting_id
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询全部 -->
|
||||||
|
<select id="selectListRel" resultType="com.gxwebsoft.app.entity.AppSetting">
|
||||||
|
<include refid="selectSql"></include>
|
||||||
|
ORDER BY a.category, a.sort_number, a.setting_id
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.gxwebsoft.app.mapper.AppUserCacheMapper">
|
||||||
|
|
||||||
|
<resultMap id="BaseResultMap" type="com.gxwebsoft.app.entity.AppUserCache">
|
||||||
|
<id column="user_id" property="userId"/>
|
||||||
|
<result column="username" property="username"/>
|
||||||
|
<result column="nickname" property="nickname"/>
|
||||||
|
<result column="avatar" property="avatar"/>
|
||||||
|
<result column="phone" property="phone"/>
|
||||||
|
<result column="status" property="status"/>
|
||||||
|
<result column="update_time" property="updateTime"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.gxwebsoft.app.mapper.AppUserMapper">
|
||||||
|
|
||||||
|
<!-- 关联查询sql:使用 app_user_cache 获取用户信息,避免跨库 JOIN -->
|
||||||
|
<sql id="selectSql">
|
||||||
|
SELECT a.*, w.product_name, w.product_code, w.icon,
|
||||||
|
c.username, c.nickname, c.avatar, c.phone
|
||||||
|
FROM app_user a
|
||||||
|
LEFT JOIN app_product w ON a.app_id = w.product_id AND w.deleted = 0 AND w.tenant_id = a.tenant_id
|
||||||
|
LEFT JOIN app_user_cache c ON a.user_id = c.user_id
|
||||||
|
<where>
|
||||||
|
<if test="param.id != null">
|
||||||
|
AND a.id = #{param.id}
|
||||||
|
</if>
|
||||||
|
<if test="param.appId != null">
|
||||||
|
AND a.app_id = #{param.appId}
|
||||||
|
</if>
|
||||||
|
<if test="param.userId != null">
|
||||||
|
AND a.user_id = #{param.userId}
|
||||||
|
</if>
|
||||||
|
<if test="param.username != null and param.username != ''">
|
||||||
|
AND (a.username LIKE CONCAT('%', #{param.username}, '%')
|
||||||
|
OR a.nickname LIKE CONCAT('%', #{param.username}, '%'))
|
||||||
|
</if>
|
||||||
|
<if test="param.role != null and param.role != ''">
|
||||||
|
AND a.role = #{param.role}
|
||||||
|
</if>
|
||||||
|
<if test="param.inviteBy != null">
|
||||||
|
AND a.invite_by = #{param.inviteBy}
|
||||||
|
</if>
|
||||||
|
<if test="param.sortNumber != null">
|
||||||
|
AND a.sort_number = #{param.sortNumber}
|
||||||
|
</if>
|
||||||
|
<if test="param.status != null">
|
||||||
|
AND a.status = #{param.status}
|
||||||
|
</if>
|
||||||
|
<if test="param.tenantId != null">
|
||||||
|
AND a.tenant_id = #{param.tenantId}
|
||||||
|
</if>
|
||||||
|
<if test="param.createTimeStart != null">
|
||||||
|
AND a.create_time >= #{param.createTimeStart}
|
||||||
|
</if>
|
||||||
|
<if test="param.createTimeEnd != null">
|
||||||
|
AND a.create_time <= #{param.createTimeEnd}
|
||||||
|
</if>
|
||||||
|
<if test="param.keywords != null and param.keywords != ''">
|
||||||
|
AND (a.username LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
|
OR a.nickname LIKE CONCAT('%', #{param.keywords}, '%'))
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<!-- 分页查询 -->
|
||||||
|
<select id="selectPageRel" resultType="com.gxwebsoft.app.entity.AppUser">
|
||||||
|
<include refid="selectSql"></include>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询全部 -->
|
||||||
|
<select id="selectListRel" resultType="com.gxwebsoft.app.entity.AppUser">
|
||||||
|
<include refid="selectSql"></include>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询用户参与的应用(含角色信息),用于 check-access 和 accessible 接口 -->
|
||||||
|
<!-- 包含:创建的应用(owner)+ 被邀请加入的应用(成员角色,且已确认) -->
|
||||||
|
<select id="selectUserAccessibleApps" resultType="java.util.Map">
|
||||||
|
SELECT
|
||||||
|
p.product_id AS appId,
|
||||||
|
p.product_name AS productName,
|
||||||
|
p.product_code AS productCode,
|
||||||
|
p.icon AS icon,
|
||||||
|
CASE
|
||||||
|
WHEN p.user_id = #{userId} THEN 'owner'
|
||||||
|
ELSE au.role
|
||||||
|
END AS role,
|
||||||
|
CASE
|
||||||
|
WHEN p.user_id = #{userId} THEN 1
|
||||||
|
ELSE 0
|
||||||
|
END AS isOwner
|
||||||
|
FROM app_product p
|
||||||
|
LEFT JOIN app_user au ON p.product_id = au.app_id
|
||||||
|
AND au.user_id = #{userId}
|
||||||
|
AND au.status = 0
|
||||||
|
AND au.invite_status = 0 <!-- 只查询已确认的邀请 -->
|
||||||
|
WHERE p.deleted = 0
|
||||||
|
AND (
|
||||||
|
p.user_id = #{userId}
|
||||||
|
OR au.id IS NOT NULL
|
||||||
|
)
|
||||||
|
ORDER BY isOwner DESC, p.create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.gxwebsoft.app.mapper.AppVersionMapper">
|
||||||
|
|
||||||
|
<!-- 关联查询sql -->
|
||||||
|
<sql id="selectSql">
|
||||||
|
SELECT a.*, w.product_name, w.product_code, w.icon
|
||||||
|
FROM app_version a
|
||||||
|
LEFT JOIN app_product w ON a.app_id = w.product_id AND w.deleted = 0
|
||||||
|
<where>
|
||||||
|
<if test="param.id != null">
|
||||||
|
AND a.id = #{param.id}
|
||||||
|
</if>
|
||||||
|
<if test="param.appId != null">
|
||||||
|
AND a.app_id = #{param.appId}
|
||||||
|
</if>
|
||||||
|
<if test="param.versionNo != null and param.versionNo != ''">
|
||||||
|
AND a.version_no = #{param.versionNo}
|
||||||
|
</if>
|
||||||
|
<if test="param.versionName != null and param.versionName != ''">
|
||||||
|
AND a.version_name LIKE CONCAT('%', #{param.versionName}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="param.env != null and param.env != ''">
|
||||||
|
AND a.env = #{param.env}
|
||||||
|
</if>
|
||||||
|
<if test="param.status != null">
|
||||||
|
AND a.status = #{param.status}
|
||||||
|
</if>
|
||||||
|
<if test="param.isCurrent != null">
|
||||||
|
AND a.is_current = #{param.isCurrent}
|
||||||
|
</if>
|
||||||
|
<if test="param.publishBy != null">
|
||||||
|
AND a.publish_by = #{param.publishBy}
|
||||||
|
</if>
|
||||||
|
<if test="param.userId != null">
|
||||||
|
AND a.user_id = #{param.userId}
|
||||||
|
</if>
|
||||||
|
<if test="param.tenantId != null">
|
||||||
|
AND a.tenant_id = #{param.tenantId}
|
||||||
|
</if>
|
||||||
|
<if test="param.createTimeStart != null">
|
||||||
|
AND a.create_time >= #{param.createTimeStart}
|
||||||
|
</if>
|
||||||
|
<if test="param.createTimeEnd != null">
|
||||||
|
AND a.create_time <= #{param.createTimeEnd}
|
||||||
|
</if>
|
||||||
|
<if test="param.keywords != null and param.keywords != ''">
|
||||||
|
AND (a.version_no LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
|
OR a.version_name LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
|
OR a.changelog LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
|
)
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<!-- 分页查询 -->
|
||||||
|
<select id="selectPageRel" resultType="com.gxwebsoft.app.entity.AppVersion">
|
||||||
|
<include refid="selectSql"></include>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询全部 -->
|
||||||
|
<select id="selectListRel" resultType="com.gxwebsoft.app.entity.AppVersion">
|
||||||
|
<include refid="selectSql"></include>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.gxwebsoft.app.param;
|
||||||
|
|
||||||
|
import com.gxwebsoft.common.core.annotation.QueryField;
|
||||||
|
import com.gxwebsoft.common.core.annotation.QueryType;
|
||||||
|
import com.gxwebsoft.common.core.web.BaseParam;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用 API Key 查询参数
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-02
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
@Schema(name = "AppApiKeyParam对象", description = "AppApiKey查询参数")
|
||||||
|
public class AppApiKeyParam extends BaseParam {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "Key名称(模糊搜索)")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "状态: 0正常, 1禁用")
|
||||||
|
@QueryField(type = QueryType.EQ)
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
|
@QueryField(type = QueryType.EQ)
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "应用ID")
|
||||||
|
@QueryField(type = QueryType.EQ)
|
||||||
|
private Long appId;
|
||||||
|
|
||||||
|
@Schema(description = "租户ID")
|
||||||
|
@QueryField(type = QueryType.EQ)
|
||||||
|
private Integer tenantId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.gxwebsoft.app.param;
|
||||||
|
|
||||||
|
import com.gxwebsoft.common.core.web.BaseParam;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台文章分类查询参数
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Schema(description = "平台文章分类查询参数")
|
||||||
|
public class AppArticleCategoryParam extends BaseParam {
|
||||||
|
|
||||||
|
@Schema(description = "分类ID")
|
||||||
|
private Integer categoryId;
|
||||||
|
|
||||||
|
@Schema(description = "状态")
|
||||||
|
private Integer status;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.gxwebsoft.app.param;
|
||||||
|
|
||||||
|
import com.gxwebsoft.common.core.web.BaseParam;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台文章查询参数
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Schema(description = "平台文章查询参数")
|
||||||
|
public class AppArticleParam extends BaseParam {
|
||||||
|
|
||||||
|
@Schema(description = "文章ID")
|
||||||
|
private Integer articleId;
|
||||||
|
|
||||||
|
@Schema(description = "文章模型,article/announcement")
|
||||||
|
private String model;
|
||||||
|
|
||||||
|
@Schema(description = "状态")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description = "分类ID")
|
||||||
|
private Integer categoryId;
|
||||||
|
|
||||||
|
@Schema(description = "是否推荐")
|
||||||
|
private Integer recommend;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.gxwebsoft.app.param;
|
||||||
|
|
||||||
|
import com.gxwebsoft.common.core.annotation.QueryField;
|
||||||
|
import com.gxwebsoft.common.core.annotation.QueryType;
|
||||||
|
import com.gxwebsoft.common.core.web.BaseParam;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CI/CD 构建查询参数
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@Schema(name = "AppBuildParam对象", description = "构建查询参数")
|
||||||
|
public class AppBuildParam extends BaseParam {
|
||||||
|
|
||||||
|
@Schema(description = "应用ID")
|
||||||
|
private Long appId;
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "构建状态")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "CI系统类型")
|
||||||
|
private String ciType;
|
||||||
|
|
||||||
|
@Schema(description = "分支")
|
||||||
|
private String branch;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.gxwebsoft.app.param;
|
||||||
|
|
||||||
|
import com.gxwebsoft.common.core.annotation.QueryField;
|
||||||
|
import com.gxwebsoft.common.core.annotation.QueryType;
|
||||||
|
import com.gxwebsoft.common.core.web.BaseParam;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 云账号凭证查询参数
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-04
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
@Schema(name = "AppCloudCredentialParam对象", description = "云账号凭证查询参数")
|
||||||
|
public class AppCloudCredentialParam extends BaseParam {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "自增ID")
|
||||||
|
@QueryField(type = QueryType.EQ)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "云服务商: aliyun/tencent/huawei/qiniu")
|
||||||
|
@QueryField(type = QueryType.EQ)
|
||||||
|
private String provider;
|
||||||
|
|
||||||
|
@Schema(description = "凭证名称")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "状态: 0正常 1冻结")
|
||||||
|
@QueryField(type = QueryType.EQ)
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description = "是否删除: 0否 1是")
|
||||||
|
@QueryField(type = QueryType.EQ)
|
||||||
|
private Integer deleted;
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
|
@QueryField(type = QueryType.EQ)
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@Schema(description = "租户ID")
|
||||||
|
@QueryField(type = QueryType.EQ)
|
||||||
|
private Integer tenantId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.gxwebsoft.app.param;
|
||||||
|
|
||||||
|
import com.gxwebsoft.common.core.web.BaseParam;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用配置表查询参数
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class AppConfigParam extends BaseParam {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置ID
|
||||||
|
*/
|
||||||
|
private Integer configId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用ID
|
||||||
|
*/
|
||||||
|
private Integer appId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置键
|
||||||
|
*/
|
||||||
|
private String configKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置类型
|
||||||
|
*/
|
||||||
|
private String configType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否敏感信息
|
||||||
|
*/
|
||||||
|
private Integer isSecret;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.gxwebsoft.app.param;
|
||||||
|
|
||||||
|
import com.gxwebsoft.common.core.web.PageParam;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合同查询参数
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-13
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Schema(description = "合同查询参数")
|
||||||
|
public class AppContractParam extends PageParam {
|
||||||
|
|
||||||
|
@Schema(description = "合同类型: service/cooperation/purchase/other")
|
||||||
|
private String contractType;
|
||||||
|
|
||||||
|
@Schema(description = "状态: draft/pending/active/expired/terminated")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "关键词(合同名称/编号)")
|
||||||
|
private String keywords;
|
||||||
|
|
||||||
|
@Schema(description = "开始日期(用于筛选创建时间范围)")
|
||||||
|
private String startDate;
|
||||||
|
|
||||||
|
@Schema(description = "结束日期(用于筛选创建时间范围)")
|
||||||
|
private String endDate;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user