Publish apes-Authon via gitea-publish skill

This commit is contained in:
figmar
2026-08-09 08:06:26 +08:00
commit d863fa0550
119 changed files with 10298 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
# Build output
target/
*.class
# IDE
.idea/
*.iml
.vscode/
.settings/
.project
.classpath
# OS
.DS_Store
Thumbs.db
# Logs
logs/
*.log
nohup.out
# Maven
*.jar
*.war
!.mvn/wrapper/maven-wrapper.jar
+203
View File
@@ -0,0 +1,203 @@
# apes-Authon — 账权独立服务
> 从 saas-service(账权平台)提取的纯净认证/授权/用户/角色/权限/多租户体系
> 目标:作为独立微服务运行,为所有业务系统提供统一的账权能力
## 项目背景
本项目源自 `saas-service`(账权平台)单体应用,经过耦合度分析确认账权模块与业务模块零直接 Service 调用,具备独立服务化条件。本仓库是提取后的纯净账权代码,不包含任何业务逻辑(溯源、称重、水肥、巡检、维修、农大同步等)。
### 提取依据
- AuthContext 被 52 个文件 / 111 处引用,但业务 Service → 账权 Service 直接调用为 **0 个**
- 账权模块仅通过 ThreadLocalAuthContext)与业务模块间接耦合
- 新增业务模块(如巡检、维修)均不直接调用账权 Service,验证了独立服务化的可行性
## 技术栈
| 组件 | 版本 | 用途 |
|------|------|------|
| Spring Boot | 2.2.1.RELEASE | Web 框架 |
| Java | 11 | 运行时 |
| MyBatis Plus | 3.5.7 | ORM |
| dynamic-datasource | 3.6.1 | 多租户数据源切换 |
| Redisson | 3.35.0 | Redis 客户端 / 分布式锁 |
| Hutool | 5.8.16 | 通用工具 |
| apes-commons | 0.0.7-SNAPSHOT | 认证核心(AuthContext / @Login / Res / DbEntity |
## 代码结构
```
src/main/java/cn/apes/
├── AuthonApplication.java # 主启动类
└── cloud/
├── controller/ # 11 个 Controller
│ ├── UserController.java # 用户认证(登录/注册/改密/多企业切换)
│ ├── PermissionController.java # 权限管理(菜单/权限码/角色/成员角色)
│ ├── AccessTokenController.java # 外部 API Token 管理
│ ├── CustomerController.java # 客户/企业管理
│ ├── CustomerPackageController.java # 客户套餐管理
│ ├── AppController.java # 应用管理
│ ├── SysOrganizationController.java # 组织架构
│ ├── SysPackagePlanController.java # 套餐方案
│ ├── SysQuotaController.java # 配额模板
│ ├── SysOperationLogController.java # 操作日志
│ └── UserPreferenceController.java # 用户偏好
├── domain/
│ ├── entity/ # 23 个实体类
│ ├── dto/ # 12 个 DTO(含公共 PageDTO
│ ├── vo/ # 3 个 VO
│ └── huoban/ # 11 个伙伴云 API 模型(SSO 依赖)
├── service/ # 6 个 Service 接口
│ └── impl/ # 16 个 Service 实现
├── mapper/ # 23 个 Mapper
├── config/ # 5 个配置类
│ ├── AccessTokenFilterConfig.java # Token 过滤器注册
│ ├── DataSourceHeaderAspect.java # 多租户数据源 AOP 切面
│ ├── TenantSource.java # @TenantSource 注解
│ ├── HbTokenContext.java # 伙伴云 Token ThreadLocal
│ └── SiteConfig.java # 站点配置
├── filter/
│ └── AccessTokenFilter.java # 外部 API 鉴权过滤器
└── util/
├── OperationLogUtil.java # 操作日志工具
└── RsaUtil.java # RSA 加解密工具
```
## 模块说明
### 1. 用户认证
- **密码登录**`UserController.loginPwd()``UserInfoServiceImpl` → Redis 会话
- **多企业切换**`UserController.selectCustomer()` → 切换 Redis 中的企业上下文
- **企业认证**`UserController.certifyEnterprise()` → 提交企业资质
- **注册/改密**`UserController.register()` / `UserController.updatePwd()`
### 2. RBAC 权限模型
```
AppInfo(应用)
└── SysMenu(菜单)── SysPermission(权限码)
└── SysRole(角色)── SysRolePermission(角色-权限关联)
└── SysMemberRole(成员-角色关联)
```
- 菜单树:`PermissionController.menuTree()`
- 权限码:`PermissionController.permissionCodes()`
- 角色管理:`PermissionController` 的 role CRUD
- 成员角色:`PermissionController` 的 member-role 分配
### 3. 多租户管理
- **数据源切换**`@TenantSource` 注解 + `DataSourceHeaderAspect` AOP,从 `X-Tenant-ID` 请求头读取数据源标识
- **租户隔离**:业务数据通过 `AuthContext.getLoginInfo().getCustomer().getId()` 获取 customerId 过滤
### 4. 外部 API 鉴权
- `AccessTokenFilter`order=1)拦截所有请求
- 校验 `X-Access-Token` / `Bearer` Token
- 校验通过后将用户/企业信息写入 Redis,使 `@Login` AOP 正常工作
### 5. SSO 单点登录
- 伙伴云 SSO V1`SsoServiceImpl` → HMAC-SHA256 签名 → 伙伴云 `/sso/get_token`
- 伙伴云 SSO V2`Sso2ServiceImpl` → 同上,不同 baseUrl
### 6. 套餐与配额
```
SysPackagePlan(套餐方案)── SysPackagePlanPermission(套餐-权限)
└── SysPackagePlanQuota(套餐-配额)
CustomerPackage(客户套餐)── CustomerPackageQuota(客户配额)
└── CustomerPackageExtendApply(延期申请)
SysQuota(配额模板)
```
## 外部依赖
### apes-commons JAR
本服务依赖 `cn.apes:apes-commons:0.0.7-SNAPSHOT`,提供以下核心类:
| 类 | 说明 |
|----|------|
| `AuthContext` | ThreadLocal 认证上下文,存储 UserSession |
| `UserSession` | 会话对象(LoginUser + LoginCustomer |
| `LoginUser` | 登录用户信息 |
| `LoginCustomer` | 登录客户/企业信息 |
| `@Login` | 方法级注解,AOP 自动注入登录信息 |
| `Res` | 统一 API 响应包装 |
| `DbEntity` | 基础实体(id / createTime / updateTime / isDel |
**安装方式**:将 `apes-commons-0.0.7-SNAPSHOT.jar` 安装到本地 Maven 仓库:
```bash
mvn install:install-file \
-Dfile=apes-commons-0.0.7-SNAPSHOT.jar \
-DgroupId=cn.apes \
-DartifactId=apes-commons \
-Dversion=0.0.7-SNAPSHOT \
-Dpackaging=jar
```
## 启动方式
```bash
# 1. 安装 apes-commons JAR
mvn install:install-file -Dfile=apes-commons-0.0.7-SNAPSHOT.jar ...
# 2. 编译打包
mvn clean package -DskipTests
# 3. 运行
java -jar target/apes-authon.jar
```
服务默认端口 `8090`context-path `/authon`
## 待完成事项
### Phase 1: 验证编译(当前)
- [ ] 安装 apes-commons JAR 后验证编译通过
- [ ] 补充缺失的 MyBatis XML(如有)
- [ ] 编写基础单元测试
### Phase 2: 包名重构
- [ ] `cn.apes.cloud``cn.apes.authon` 全局包名重构
- [ ] 调整 MapperScan 路径
- [ ] 更新所有 import
### Phase 3: SDK 化
- [ ]`AuthContext` 改造为可远程调用的 SDK
- [ ] 提供 `authon-sdk` 模块供业务系统引用
- [ ] Token 校验改为 HTTP API 调用
### Phase 4: 网关集成
- [ ] 接入 API 网关(Spring Cloud Gateway / Nginx
- [ ] 请求头注入认证信息(替代 ThreadLocal
- [ ] 多租户数据源改为网关路由模式
## 从原项目提取的文件清单
共提取 **114 个 Java 文件**
| 类型 | 数量 |
|------|------|
| Controller | 11 |
| Entity | 23 |
| DTO | 12 |
| VO | 3 |
| Service 接口 | 6 |
| Service 实现 | 16 |
| Mapper | 23 |
| Config | 5 |
| Filter | 1 |
| Util | 2 |
| Huoban DTO | 11 |
| 主启动类 | 1 |
## 关联仓库
- 源仓库:[apes-Authdata](https://gitea.apescale.com/figmar/apes-Authdata) — 完整单体应用(含业务代码)
- 方案文档:`docs/账权模块独立服务化方案.html`(在 apes-Authdata 仓库中)
- 源码对比:`docs/源码版本对比分析-20260809.md`(在 apes-Authdata 仓库中)
+144
View File
@@ -0,0 +1,144 @@
# 账权模块提取说明
> 提取日期:2026-08-09
> 源仓库:apes-Authdata (saas-service)
> 目标仓库:apes-Authon
## 一、提取原则
1. **纯账权逻辑**:只提取认证、授权、用户、角色、菜单、权限、多租户、套餐、配额、SSO、操作日志相关的代码
2. **零业务代码**:不包含溯源(Trace*)、称重(Weight*)、水肥(Fei*)、巡检(Inspection*)、维修(Repair*)、农大同步(Nongda*)、LLM Agent 等任何业务逻辑
3. **保留包名**:暂保留 `cn.apes.cloud` 包名以最小化改动,后续 Phase 2 再统一重构为 `cn.apes.authon`
4. **保留依赖**`apes-commons` 外部 JAR 作为核心依赖保留,提供 AuthContext / @Login / Res / DbEntity 等基础设施
## 二、提取文件清单
### Controller11 个)
| 文件 | 核心功能 |
|------|---------|
| UserController | 密码登录、注册、改密、用户CRUD、企业认证、多企业切换 |
| PermissionController | 菜单树、权限码、角色CRUD、角色权限分配、成员角色管理 |
| AccessTokenController | 外部 API Token 的创建/查询/撤销/启用/重新生成 |
| CustomerController | 客户/企业CRUD、员工管理、应用关联、SSO配置 |
| CustomerPackageController | 客户套餐管理、配额管理、延期申请 |
| AppController | 应用CRUD、应用详情(含菜单+权限+关联客户) |
| SysOrganizationController | 组织架构CRUD、组织树 |
| SysPackagePlanController | 套餐方案CRUD、套餐权限分配 |
| SysQuotaController | 配额模板CRUD |
| SysOperationLogController | 操作日志查询 |
| UserPreferenceController | 用户偏好(UI尺寸)查询/更新 |
### Entity23 个)
UserInfo, SysRole, SysMenu, SysPermission, SysRolePermission, SysMemberRole, SysAccessToken, CustomerInfo, CustomerUser, CustomerApp, CustomerPackage, CustomerPackageQuota, CustomerPackageExtendApply, CustomerQuotaChangeLog, SsoConfig, SysOrganization, SysPackagePlan, SysPackagePlanPermission, SysPackagePlanQuota, SysQuota, UserPreference, SysOperationLog, AppInfo
### Service 接口(6 个)
CustomerPackageService, SysAccessTokenService, SysOperationLogService, SysPackagePlanQuotaService, SysPackagePlanService, SysQuotaService
### Service 实现(16 个)
PermissionServiceImpl, UserInfoServiceImpl, SysAccessTokenServiceImpl, SsoServiceImpl, Sso2ServiceImpl, CustomerUserServiceImpl, CustomerInfoServiceImpl, CustomerAppServiceImpl, SysOrganizationServiceImpl, SysPackagePlanServiceImpl, CustomerPackageServiceImpl, SysQuotaServiceImpl, SysPackagePlanQuotaServiceImpl, UserPreferenceServiceImpl, SysOperationLogServiceImpl, AppInfoServiceImpl
### Mapper23 个)
对应每个 Entity 各一个 Mapper。
### Config5 个)
| 文件 | 说明 |
|------|------|
| AccessTokenFilterConfig | 注册 AccessTokenFilterorder=1,拦截 /* |
| DataSourceHeaderAspect | @TenantSource AOP 切面,多租户数据源切换 |
| TenantSource | @TenantSource 注解定义 |
| HbTokenContext | 伙伴云 Token 的 ThreadLocal 上下文 |
| SiteConfig | 站点配置(支付域名、Cookie域名) |
### Filter1 个)
AccessTokenFilter — 外部 API 鉴权(X-Access-Token / Bearer
### Util2 个)
| 文件 | 说明 |
|------|------|
| OperationLogUtil | 操作日志工具(@PostConstruct 抓 bean + AuthContext 获取操作人) |
| RsaUtil | RSA 加解密(密码加密/解密) |
### DTO/VO15 个)
LoginDTO, RegisterDTO, PwdDTO, UserSearchDTO, RoleSearchDTO, SysRoleDTO, CustomerSearchDTO, EnterpriseCertifyDTO, SelectCustomerDTO, PageDTO, RelationDTO, AppSearchDTO, CustomerPackageVO, CustomerPackageExtendApplyVO, CustomerAppVO
### Huoban DTO11 个)
UpsertOne, DataCreate, DataUpdate, DataFilter, DataFilterCondition, DataUpsert, BulkDel, TableSub, TableColumn, TableInfo, CategoryConfig — 伙伴云 API 请求/响应模型,被 CustomerInfoServiceImpl 用于同步客户数据到伙伴云
## 三、未提取的文件(排除原因)
| 文件 | 排除原因 |
|------|---------|
| CustomerUiConfigServiceImpl | 依赖 `cn.apes.cloud.domain.weight.CustomerUiConfig`weight 业务包) |
| CommonController | 混合业务(文件上传等),非纯账权 |
| WeightController | 称重业务 |
| TrackingConfigController | 追踪配置业务 |
| 所有 Trace*/Weight*/Fei*/Llm*/Nongda* 文件 | 业务模块 |
| RedissonConfiguration | 通用配置,需按实际部署环境重新配置 |
| MybatisPlusConfig | 通用配置,需按实际部署环境重新配置 |
| OssConfiguration | OSS 配置,账权服务不需要 |
| WebSocketConfig | WebSocket 配置,账权服务不需要 |
| RestTemplateConfiguration | HTTP 客户端配置,按需添加 |
| SchedulingConfig | 定时任务配置,按需添加 |
| JacksonAutoConfiguration | Jackson 配置,Spring Boot 自动配置 |
## 四、外部依赖分析
### apes-commons 0.0.7-SNAPSHOT
提供以下核心类(全部为接口级别依赖,无源码):
```
cn.apes.commons.Res → 统一 API 响应包装
cn.apes.commons.auth.AuthContext → ThreadLocal 认证上下文
cn.apes.commons.auth.Login → @Login 注解(方法级,AOP 自动注入)
cn.apes.commons.auth.LoginUser → 登录用户信息
cn.apes.commons.auth.LoginCustomer → 登录客户/企业信息
cn.apes.commons.auth.UserSession → 会话对象(user + customer
cn.apes.commons.domain.DbEntity → 基础实体(id/createTime/updateTime/isDel
```
### 第三方依赖
| 依赖 | 用途 |
|------|------|
| spring-boot-starter-web | REST API |
| spring-boot-starter-aop | @TenantSource 切面 / @Login AOP |
| spring-boot-starter-data-redis | Redis 会话管理 |
| mybatis-plus-boot-starter | ORM |
| dynamic-datasource-spring-boot-starter | 多租户数据源 |
| mysql-connector-java | MySQL 驱动 |
| lombok | 简化代码 |
| hutool-all | 通用工具(HMAC 签名、HTTP 调用等) |
| redisson | Redis 客户端 |
| fastjson | JSON 序列化 |
## 五、耦合度验证
提取过程中验证了以下耦合点:
1. **业务 Service → 账权 Service 直接调用:0 个**
2. **业务 Controller → 账权 Controller 直接调用:0 个**
3. **账权 Service → 业务 Mapper 直接调用:0 个**
4. **唯一的业务包交叉引用**`CustomerInfoServiceImpl``cn.apes.cloud.domain.huoban.UpsertOne`
- 解决方案:将 huoban DTO 包一起提取(11 个纯 DTO 文件,无业务逻辑)
5. **外部 JAR 依赖**`apes-commons` 提供 AuthContext 等核心类
- 解决方案:保留为 Maven 依赖,后续 Phase 3 改造为 SDK
## 六、后续路线图
| 阶段 | 目标 | 状态 |
|------|------|------|
| Phase 1 | 验证编译通过 | 待执行 |
| Phase 2 | 包名重构 cn.apes.cloud → cn.apes.authon | 待启动 |
| Phase 3 | SDK 化(AuthContext → 远程调用) | 待设计 |
| Phase 4 | 网关集成(ThreadLocal → HTTP Header | 待设计 |
+162
View File
@@ -0,0 +1,162 @@
<?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>cn.apes</groupId>
<artifactId>apes-authon</artifactId>
<version>0.1.0-SNAPSHOT</version>
<name>apes-Authon</name>
<description>账权独立服务 — 从 saas-service 提取的纯净认证/授权/用户/角色/权限/多租户体系</description>
<properties>
<java.version>11</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<spring-boot.version>2.2.1.RELEASE</spring-boot.version>
<lombok.version>1.18.36</lombok.version>
<mysql-connector-java.version>8.0.15</mysql-connector-java.version>
<mybatis-plus-boot-starter.version>3.5.7</mybatis-plus-boot-starter.version>
<redisson.version>3.35.0</redisson.version>
<hutool.version>5.8.16</hutool.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Spring Boot Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-boot-starter.version}</version>
</dependency>
<!-- Dynamic DataSource -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.6.1</version>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!-- Hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- Redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${redisson.version}</version>
</dependency>
<!-- Fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<!-- apes-commons (外部 JAR,需手动安装到本地 Maven 仓库) -->
<dependency>
<groupId>cn.apes</groupId>
<artifactId>apes-commons</artifactId>
<version>0.0.7-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>apes-authon</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>11</source>
<target>11</target>
<encoding>UTF-8</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>cn.apes.AuthonApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,30 @@
package cn.apes;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 账权独立服务 — 从 saas-service 提取的纯净认证/授权/用户/角色/权限/多租户体系
*
* 本服务包含:
* - 用户认证(密码登录 / Redis 会话 / Token 管理)
* - RBAC 权限模型(菜单 / 权限码 / 角色 / 成员角色)
* - 多租户管理(客户/企业 / 组织架构 / 数据源切换)
* - 套餐与配额管理
* - SSO 单点登录(伙伴云)
* - 操作日志
* - 用户偏好
*/
@SpringBootApplication
@EnableAsync
@EnableScheduling
@MapperScan("cn.apes.cloud.mapper")
public class AuthonApplication {
public static void main(String[] args) {
SpringApplication.run(AuthonApplication.class, args);
}
}
@@ -0,0 +1,31 @@
package cn.apes.cloud.config;
import cn.apes.cloud.filter.AccessTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.DispatcherType;
/**
* AccessToken 过滤器注册
* 注册为最高优先级,确保在 Spring 其他拦截之前执行
*/
@Configuration
public class AccessTokenFilterConfig {
@Autowired
private AccessTokenFilter accessTokenFilter;
@Bean
public FilterRegistrationBean<AccessTokenFilter> accessTokenFilterRegistration() {
FilterRegistrationBean<AccessTokenFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(accessTokenFilter);
registration.addUrlPatterns("/*");
registration.setOrder(1); // 最高优先级
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setName("accessTokenFilter");
return registration;
}
}
@@ -0,0 +1,64 @@
package cn.apes.cloud.config;
import cn.apes.commons.Res;
import cn.apes.commons.auth.AuthContext;
import cn.apes.commons.auth.Login;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RBucket;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.List;
@Slf4j
@Aspect
@Component
@Order(1)
public class DataSourceHeaderAspect {
List<String> heads = CollUtil.newArrayList("user", "zx","plant");
@Around("@within(tenantSource)")
public Object clazz(ProceedingJoinPoint pjp, TenantSource tenantSource) throws Throwable {
return exc(pjp, tenantSource);
}
@Around("@annotation(tenantSource)")
public Object menthod(ProceedingJoinPoint pjp, TenantSource tenantSource) throws Throwable {
return exc(pjp, tenantSource);
}
Object exc(ProceedingJoinPoint pjp, TenantSource tenantSource) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 保留 AccessToken 过滤器已设置的认证上下文
if (request.getAttribute("accessTokenVerified") == null) {
AuthContext.clean();
}
String head = request.getHeader("X-Tenant-ID");
if (StrUtil.isEmpty(head)) {
head="user";
}
if(!heads.contains(head)) {
throw new RuntimeException("数据源ID有误");
}
DynamicDataSourceContextHolder.push(head);
return pjp.proceed();
}
}
@@ -0,0 +1,32 @@
package cn.apes.cloud.config;
public class HbTokenContext {
private static final ThreadLocal<String> LOGIN_THREAD_LOCAL = new ThreadLocal<>();
/**
* 设置伙伴云token
*
* @param loginInfo 登录信息
*/
public static void setHbToken(String loginInfo) {
LOGIN_THREAD_LOCAL.set(loginInfo);
}
/**
* 获取登录token
*
* @return
*/
public static String getHbToken() {
return LOGIN_THREAD_LOCAL.get();
}
/**
* 清空登录用户信息
*/
public static void clean() {
LOGIN_THREAD_LOCAL.remove();
}
}
@@ -0,0 +1,18 @@
package cn.apes.cloud.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "sit.config")
public class SiteConfig {
@Value("${sit.config.payment.domain}")
private String paymentDomain;
@Value("${sit.config.payment.cookieDomain}")
private String cookieDomain;
}
@@ -0,0 +1,10 @@
package cn.apes.cloud.config;
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TenantSource {
String value() default "user";
}
@@ -0,0 +1,170 @@
package cn.apes.cloud.controller;
import cn.apes.cloud.domain.entity.SysAccessToken;
import cn.apes.cloud.service.SysAccessTokenService;
import cn.apes.cloud.util.OperationLogUtil;
import cn.apes.commons.auth.AuthContext;
import cn.apes.commons.auth.Login;
import cn.apes.commons.auth.LoginCustomer;
import cn.apes.commons.auth.UserSession;
import cn.apes.commons.Res;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* AccessToken 管理接口
*/
@Login
@RestController
@RequestMapping("/sysAccessToken")
public class AccessTokenController {
@Autowired
private SysAccessTokenService accessTokenService;
/**
* 创建 AccessToken
*/
@PostMapping("/create")
public Res create(@RequestBody Map<String, Object> params) {
UserSession authUserInfo = AuthContext.getLoginInfo();
LoginCustomer customer = authUserInfo.getCustomer();
if (customer == null || customer.getId() == null) {
return Res.fail("未找到当前登录客户信息");
}
SysAccessToken record = new SysAccessToken();
record.setCustomerId(customer.getId());
record.setName((String) params.get("name"));
record.setScope(params.get("scope") != null ? (String) params.get("scope") : "*");
record.setWhitelistIps(params.get("whitelistIps") != null ? (String) params.get("whitelistIps") : "");
record.setStatus(1);
if (params.get("expireTime") != null) {
try {
record.setExpireTime(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse((String) params.get("expireTime")));
} catch (Exception e) {
return Res.fail("过期时间格式错误,应为 yyyy-MM-dd HH:mm:ss");
}
}
String token = accessTokenService.generateToken();
record.setToken(token);
record.setCreateBy(authUserInfo.getUser() != null ? authUserInfo.getUser().getId() : null);
accessTokenService.save(record);
OperationLogUtil.log("AccessToken管理", "创建", "创建AccessToken[" + record.getName() + "]");
// 返回完整 Token(只在创建时展示一次)
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Map<String, Object> result = new HashMap<>();
result.put("id", record.getId());
result.put("name", record.getName());
result.put("token", token);
result.put("customerId", record.getCustomerId());
result.put("scope", record.getScope());
result.put("expireTime", record.getExpireTime() != null ? sdf.format(record.getExpireTime()) : null);
result.put("message", "Token 仅在创建时显示一次,请妥善保存");
return Res.success(result);
}
private Long parseId(Map<String, Object> params) {
Object val = params.get("id");
if (val == null) return null;
return Long.parseLong(val.toString());
}
/**
* 分页查询列表
*/
@PostMapping("/page")
public Res page(@RequestBody Map<String, Object> params) {
UserSession authUserInfo = AuthContext.getLoginInfo();
LoginCustomer customer = authUserInfo.getCustomer();
if (customer == null || customer.getId() == null) {
return Res.fail("未找到当前登录客户信息");
}
int pageIndex = params.get("pageIndex") != null ? Integer.parseInt(params.get("pageIndex").toString()) : 1;
int pageSize = params.get("pageSize") != null ? Integer.parseInt(params.get("pageSize").toString()) : 10;
LambdaQueryWrapper<SysAccessToken> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysAccessToken::getCustomerId, customer.getId());
wrapper.orderByDesc(SysAccessToken::getCreateTime);
Page<SysAccessToken> page = accessTokenService.page(new Page<>(pageIndex, pageSize), wrapper);
// 脱敏:不返回完整 token
for (SysAccessToken item : page.getRecords()) {
if (item.getToken() != null && item.getToken().length() > 8) {
item.setToken(item.getToken().substring(0, 6) + "****" + item.getToken().substring(item.getToken().length() - 2));
}
}
return Res.success(page);
}
/**
* 撤销 Token
*/
@PostMapping("/revoke")
public Res revoke(@RequestBody Map<String, Object> params) {
Long id = parseId(params);
if (id == null) return Res.fail("缺少 id 参数");
SysAccessToken record = accessTokenService.getById(id);
String name = record != null ? record.getName() : "id=" + id;
accessTokenService.revoke(id);
OperationLogUtil.log("AccessToken管理", "停用", "停用AccessToken[" + name + "]");
return Res.success("已撤销");
}
/**
* 启用 Token
*/
@PostMapping("/enable")
public Res enable(@RequestBody Map<String, Object> params) {
Long id = parseId(params);
if (id == null) return Res.fail("缺少 id 参数");
SysAccessToken record = accessTokenService.getById(id);
String name = record != null ? record.getName() : "id=" + id;
accessTokenService.enable(id);
OperationLogUtil.log("AccessToken管理", "启用", "启用AccessToken[" + name + "]");
return Res.success("已启用");
}
/**
* 重新生成 Token
*/
@PostMapping("/regenerate")
public Res regenerate(@RequestBody Map<String, Object> params) {
Long id = parseId(params);
if (id == null) return Res.fail("缺少 id 参数");
SysAccessToken record = accessTokenService.getById(id);
String name = record != null ? record.getName() : "id=" + id;
String newToken = accessTokenService.regenerate(id);
OperationLogUtil.log("AccessToken管理", "重新生成", "重新生成AccessToken[" + name + "]");
Map<String, Object> result = new HashMap<>();
result.put("token", newToken);
result.put("message", "新 Token 已生成,旧 Token 立即失效");
return Res.success(result);
}
/**
* 删除 Token
*/
@PostMapping("/delete")
public Res delete(@RequestBody Map<String, Object> params) {
Long id = parseId(params);
if (id == null) return Res.fail("缺少 id 参数");
SysAccessToken record = accessTokenService.getById(id);
String name = record != null ? record.getName() : "id=" + id;
accessTokenService.removeById(id);
OperationLogUtil.log("AccessToken管理", "删除", "删除AccessToken[" + name + "]");
return Res.success("已删除");
}
}
@@ -0,0 +1,110 @@
package cn.apes.cloud.controller;
import cn.apes.commons.Res;
import cn.apes.commons.auth.Login;
import cn.apes.cloud.config.TenantSource;
import cn.apes.cloud.domain.dto.AppSearchDTO;
import cn.apes.cloud.domain.entity.AppInfo;
import cn.apes.cloud.domain.entity.SysMenu;
import cn.apes.cloud.domain.entity.SysPermission;
import cn.apes.cloud.domain.entity.CustomerInfo;
import cn.apes.cloud.domain.entity.CustomerApp;
import cn.apes.cloud.service.impl.AppInfoServiceImpl;
import cn.apes.cloud.service.impl.CustomerAppServiceImpl;
import cn.apes.cloud.service.impl.CustomerInfoServiceImpl;
import cn.apes.cloud.mapper.SysMenuMapper;
import cn.apes.cloud.mapper.SysPermissionMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Login
@RestController
@TenantSource
@RequestMapping("/app")
public class AppController {
@Autowired
AppInfoServiceImpl appInfoService;
@Autowired
CustomerAppServiceImpl customerAppService;
@Autowired
CustomerInfoServiceImpl customerInfoService;
@Autowired
SysMenuMapper sysMenuMapper;
@Autowired
SysPermissionMapper sysPermissionMapper;
/**
* 分页查询应用列表
*/
@PostMapping("/pageApp")
public Res pageApp(@RequestBody AppSearchDTO searchDTO) {
return appInfoService.pageApp(searchDTO);
}
/**
* 新增/编辑应用
*/
@PostMapping("/saveApp")
public Res saveApp(@RequestBody AppInfo appInfo) {
return appInfoService.saveApp(appInfo);
}
/**
* 删除应用
*/
@PostMapping("/deleteApp")
public Res deleteApp(@RequestBody AppInfo appInfo) {
return appInfoService.deleteApp(appInfo);
}
/**
* 获取应用详情(基本信息 + 菜单 + 权限 + 关联客户)
*/
@PostMapping("/getAppDetail")
public Res getAppDetail(@RequestBody AppInfo param) {
String appKey = param.getAppKey();
AppInfo appInfo = appInfoService.getById(appKey);
if (appInfo == null) {
return Res.fail("应用不存在");
}
// 隐藏敏感字段
appInfo.setRemark(null);
// 查询关联的菜单列表
List<SysMenu> menuList = sysMenuMapper.selectList(
new LambdaQueryWrapper<SysMenu>().eq(SysMenu::getAppName, appKey)
);
// 查询关联的权限列表
List<SysPermission> permissionList = sysPermissionMapper.selectList(
new LambdaQueryWrapper<SysPermission>().eq(SysPermission::getAppName, appKey)
);
// 查询关联的客户列表
List<Long> customerIds = customerAppService.getCustomerIdsByAppKey(appKey);
List<CustomerInfo> customerList = new ArrayList<>();
if (!customerIds.isEmpty()) {
customerList = customerInfoService.listByIds(customerIds);
}
Map<String, Object> result = new HashMap<>();
result.put("appInfo", appInfo);
result.put("menuList", menuList);
result.put("permissionList", permissionList);
result.put("customerList", customerList);
return Res.success(result);
}
}
@@ -0,0 +1,196 @@
package cn.apes.cloud.controller;
import cn.apes.commons.Res;
import cn.apes.commons.auth.AuthContext;
import cn.apes.commons.auth.Login;
import cn.apes.commons.auth.LoginCustomer;
import cn.apes.cloud.config.TenantSource;
import cn.apes.cloud.domain.dto.CustomerSearchDTO;
import cn.apes.cloud.domain.entity.CustomerInfo;
import cn.apes.cloud.domain.entity.SsoConfig;
import cn.apes.cloud.service.impl.CustomerAppServiceImpl;
import cn.apes.cloud.service.impl.CustomerInfoServiceImpl;
import cn.apes.cloud.service.impl.CustomerUserServiceImpl;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@Login
@RestController
@TenantSource
@RequestMapping("/customer")
public class CustomerController {
@Autowired
CustomerInfoServiceImpl customerInfoService;
@Autowired
CustomerUserServiceImpl customerUserService;
@Autowired
CustomerAppServiceImpl customerAppService;
@PostMapping("/pageCustomer")
public Res pageCustomer(@RequestBody CustomerSearchDTO searchDTO) {
return customerInfoService.pageCustomer(searchDTO);
}
@GetMapping("/getCustomerDetail")
public Res getCustomerDetail(@RequestParam("id") Long id) {
return customerInfoService.getCustomerDetail(id);
}
@GetMapping("/addUser")
public Res addUser(Long customerId, Long userId) {
return customerUserService.addUser(customerId, userId);
}
@GetMapping("/delUser")
public Res delUser(Long customerId, Long userId) {
return customerUserService.delUser(customerId, userId);
}
/**
* 分页查询员工列表(customerId 从登录上下文获取)
*/
@PostMapping("/employee/page")
public Res pageEmployee(@RequestBody JSONObject data) {
int pageIndex = data.getIntValue("pageIndex");
int pageSize = data.getIntValue("pageSize");
Long orgId = data.getLong("orgId");
if (pageIndex <= 0) pageIndex = 1;
if (pageSize <= 0) pageSize = 10;
return customerUserService.pageEmployee(pageIndex, pageSize, orgId);
}
/**
* 获取员工详情(编辑用)
*/
@GetMapping("/employee/detail")
public Res getEmployeeDetail(@RequestParam("userId") Long userId) {
return customerUserService.getEmployeeDetail(userId);
}
/**
* 更新员工信息
*/
@PostMapping("/employee/update")
public Res updateEmployee(@RequestBody JSONObject data) {
Long userId = data.getLong("userId");
Long orgId = data.getLong("orgId");
String name = data.getString("name");
String title = data.getString("title");
String position = data.getString("position");
String appIds = data.getString("appIds");
return customerUserService.updateEmployee(userId, orgId, name, title, position, appIds);
}
/**
* 新增/编辑客户
*/
@PostMapping("/saveCustomer")
public Res saveCustomer(@RequestBody CustomerInfo customerInfo) {
return customerInfoService.saveCustomer(customerInfo);
}
/**
* 删除客户
*/
@PostMapping("/deleteCustomer")
public Res deleteCustomer(@RequestBody CustomerInfo customerInfo) {
return customerInfoService.deleteCustomer(customerInfo);
}
/**
* 查询当前登录客户已关联的应用列表(从登录上下文获取 customerId
*/
@GetMapping("/getMyApps")
public Res getMyApps() {
LoginCustomer customer = AuthContext.getLoginInfo().getCustomer();
if (customer == null || customer.getId() == null) {
return Res.fail("未找到当前登录客户信息");
}
return Res.success(customerAppService.getMyAppsWithSource(customer.getId()));
}
/**
* 查询指定客户已关联的应用列表(管理端用)
*/
@GetMapping("/getCustomerApps")
public Res getCustomerApps(@RequestParam("customerId") Long customerId) {
return Res.success(customerAppService.getApps(customerId));
}
/**
* 关联应用给客户
*/
@PostMapping("/addCustomerApp")
public Res addCustomerApp(@RequestBody JSONObject data) {
Long customerId = data.getLong("customerId");
String appKey = data.getString("appKey");
return customerAppService.addApp(customerId, appKey);
}
/**
* 解绑客户的应用
*/
@PostMapping("/removeCustomerApp")
public Res removeCustomerApp(@RequestBody JSONObject data) {
Long customerId = data.getLong("customerId");
String appKey = data.getString("appKey");
return customerAppService.removeApp(customerId, appKey);
}
/**
* 查询所有应用(用于选择器)
*/
@GetMapping("/getAllApps")
public Res getAllApps() {
return Res.success(customerAppService.getAllApps());
}
/**
* 获取客户SSO配置(公司级别 user_id=0
*/
@GetMapping("/getSSOConfig")
public Res getSSOConfig(@RequestParam("customerId") Long customerId) {
return customerInfoService.getSSOConfig(customerId);
}
/**
* 保存客户SSO配置(公司级别 user_id=0
*/
@PostMapping("/saveSSOConfig")
public Res saveSSOConfig(@RequestBody SsoConfig ssoConfig) {
return customerInfoService.saveSSOConfig(ssoConfig);
}
/**
* 更新客户状态
*/
@PostMapping("/updateCustomerStatus")
public Res updateCustomerStatus(@RequestBody CustomerInfo customerInfo) {
return customerInfoService.updateCustomerStatus(customerInfo.getId(), customerInfo.getCustomerStatus());
}
/**
* 审核企业认证(通过/拒绝)
*/
@PostMapping("/auditCertification")
public Res auditCertification(@RequestBody JSONObject data) {
Long customerId = data.getLong("customerId");
Integer auditStatus = data.getInteger("customerStatus"); // 1=通过, 5=拒绝
String auditRemark = data.getString("auditRemark"); // 拒绝原因
return customerInfoService.auditCertification(customerId, auditStatus, auditRemark);
}
/**
* 同步全部租户信息到伙伴云(使用 upsetOne 接口)
*/
@PostMapping("/syncTenantsToHuoban")
public Res syncTenantsToHuoban() {
return customerInfoService.syncAllTenantsToHuoban();
}
}
@@ -0,0 +1,143 @@
package cn.apes.cloud.controller;
import cn.apes.commons.Res;
import cn.apes.commons.auth.AuthContext;
import cn.apes.commons.auth.Login;
import cn.apes.commons.auth.LoginCustomer;
import cn.apes.cloud.domain.entity.CustomerPackage;
import cn.apes.cloud.service.CustomerPackageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@Login
@RestController
@RequestMapping("/customerPackage")
public class CustomerPackageController {
@Autowired
private CustomerPackageService customerPackageService;
/**
* 获取客户的套餐列表
*/
@GetMapping("/list")
public Object list(@RequestParam Long customerId) {
return customerPackageService.getCustomerPackages(customerId);
}
/**
* 新增客户套餐
*/
@PostMapping("/add")
public Object add(@RequestBody CustomerPackage customerPackage) {
return customerPackageService.addCustomerPackage(customerPackage);
}
/**
* 编辑客户套餐
*/
@PostMapping("/edit")
public Object edit(@RequestBody java.util.Map<String, Object> data) {
return customerPackageService.editCustomerPackage(data);
}
/**
* 删除客户套餐
*/
@PostMapping("/delete")
public Object delete(@RequestBody CustomerPackage customerPackage) {
return customerPackageService.deleteCustomerPackage(customerPackage.getId());
}
/**
* 保存客户套餐配额
*/
@PostMapping("/saveQuotas")
public Object saveQuotas(@RequestBody java.util.Map<String, Object> data) {
Long packageId = data.get("packageId") != null ? Long.parseLong(data.get("packageId").toString()) : null;
@SuppressWarnings("unchecked")
java.util.Map<String, Object> quotaValues = (java.util.Map<String, Object>) data.get("quotaValues");
return customerPackageService.saveCustomerPackageQuotas(packageId, quotaValues);
}
/**
* 获取客户套餐配额
*/
@GetMapping("/getQuotas")
public Object getQuotas(@RequestParam Long packageId) {
return customerPackageService.getCustomerPackageQuotas(packageId);
}
/**
* 获取当前登录客户的套餐列表(从登录上下文获取 customerId
*/
@GetMapping("/myPackages")
public Res myPackages() {
LoginCustomer customer = AuthContext.getLoginInfo().getCustomer();
if (customer == null || customer.getId() == null) {
return Res.fail("未找到当前登录客户信息");
}
return customerPackageService.getMyPackages(customer.getId());
}
/**
* 管理端:查询所有客户的套餐概况
*/
@PostMapping("/overview")
public Object overview(@RequestBody java.util.Map<String, Object> params) {
return customerPackageService.getCustomerPackageOverview(params);
}
/**
* 提交套餐延期申请
*/
@PostMapping("/submitExtendApply")
public Object submitExtendApply(@RequestBody java.util.Map<String, Object> params) {
params.put("userId", AuthContext.getLoginInfo().getCustomer().getId());
return customerPackageService.submitExtendApply(params);
}
/**
* 管理端:查询套餐延期申请记录列表
*/
@PostMapping("/extendApplyList")
public Object extendApplyList(@RequestBody java.util.Map<String, Object> params) {
return customerPackageService.getExtendApplyList(params);
}
/**
* 审核延期申请(同意/拒绝)
*/
@PostMapping("/reviewExtendApply")
public Object reviewExtendApply(@RequestBody java.util.Map<String, Object> params) {
return customerPackageService.reviewExtendApply(params);
}
/**
* 增加客户套餐额度
*/
@PostMapping("/increaseQuota")
public Object increaseQuota(@RequestBody java.util.Map<String, Object> params) {
return customerPackageService.increaseQuota(params);
}
/**
* 减少客户套餐额度
*/
@PostMapping("/decreaseQuota")
public Object decreaseQuota(@RequestBody java.util.Map<String, Object> params) {
return customerPackageService.decreaseQuota(params);
}
/**
* 获取配额变更日志列表
*/
@GetMapping("/quotaChangeLogs")
public Object getQuotaChangeLogs(@RequestParam java.util.Map<String, Object> params) {
return customerPackageService.getQuotaChangeLogs(params);
}
}
@@ -0,0 +1,226 @@
package cn.apes.cloud.controller;
import cn.apes.commons.Res;
import cn.apes.commons.auth.AuthContext;
import cn.apes.commons.auth.UserSession;
import cn.apes.cloud.config.TenantSource;
import cn.apes.cloud.domain.dto.RoleSearchDTO;
import cn.apes.cloud.domain.entity.SysMenu;
import cn.apes.cloud.domain.entity.SysPermission;
import cn.apes.cloud.domain.entity.SysRole;
import cn.apes.cloud.service.impl.PermissionServiceImpl;
import cn.apes.commons.auth.Login;
import cn.apes.commons.auth.LoginCustomer;
import cn.hutool.http.server.HttpServerResponse;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Login
@RestController
@TenantSource
@RequestMapping("/permission")
public class PermissionController {
@Autowired
PermissionServiceImpl permissionService;
@GetMapping("/currentMenus")
public Res getCurrentMenus() {
UserSession authUserInfo = AuthContext.getLoginInfo();
return permissionService.getMemberMenus(authUserInfo.getUser().getId());
}
/**
* 获取指定应用的全部菜单树(按用户权限过滤)
*/
@GetMapping("/getAppMenus")
public Res getAppMenus(@RequestParam("appName") String appName) {
UserSession authUserInfo = AuthContext.getLoginInfo();
return permissionService.getAppMenus(authUserInfo.getUser().getId(), appName);
}
/**
* 获取当前用户在当前企业的所有权限code,按应用名分组
* 返回格式: {"BASE_APP": ["code1", "code2"], "guobang": ["code1"]}
*/
@GetMapping("/getMyPermissionCodes")
public Res getMyPermissionCodes() {
UserSession authUserInfo = AuthContext.getLoginInfo();
return permissionService.getMyPermissionCodesByApp(authUserInfo.getUser().getId());
}
/**
* 获取指定应用的菜单树(按客户套餐权限过滤)
*/
@GetMapping("/getPackageAppMenus")
public Res getPackageAppMenus(@RequestParam("appName") String appName) {
UserSession authUserInfo = AuthContext.getLoginInfo();
LoginCustomer customer = authUserInfo.getCustomer();
if (customer == null || customer.getId() == null) {
return Res.fail("未找到当前登录客户信息");
}
Long userId = authUserInfo.getUser() != null ? authUserInfo.getUser().getId() : null;
if (userId == null) {
return Res.fail("未找到当前登录用户信息");
}
return permissionService.getPackageAppMenus(userId, customer.getId(), appName);
}
// @GetMapping("/getOldMemberMenus")
// public Res getOldMemberMenus(@RequestHeader("version") String version){
// AuthUserInfo authUserInfo = AuthContext.getLoginInfo();
// AppInfo appInfo = AuthContext.getAppInfo();
// return permissionService.getOldMemberMenus(authUserInfo.getMemberNo(),appInfo.getAppName(),version);
// }
// @GetMapping("/getRolePermission")
// public Res getRolePermission(){
// UserSession authUserInfo = AuthContext.getLoginInfo();
// return permissionService.getRolePermission(authUserInfo.getUser().getId());
// }
@PostMapping("/saveMenu")
public Res saveMenu(@RequestBody SysMenu menu) {
return permissionService.saveMenu(menu);
}
@GetMapping("/getAllMenu")
public Res getAllMenu(@RequestParam("appName") String appName) {
return permissionService.getAllMenu(appName);
}
@PostMapping("/savePermission")
public Res savePermission(@RequestBody SysPermission menu) {
return permissionService.savePermission(menu);
}
@GetMapping("/getAllPermission")
public Res getAllPermission(String appName) {
return permissionService.getAllPermission(appName);
}
/**
* 获取指定套餐下指定应用的权限树(仅包含该套餐拥有的权限)
*/
@GetMapping("/getPlanPermissionTree")
public Res getPlanPermissionTree(@RequestParam("planId") Long planId, @RequestParam("appName") String appName) {
return permissionService.getPlanPermissionTree(planId, appName);
}
@PostMapping("/deletePermission")
public Res deletePermission(@RequestBody JSONObject data) {
Long permissionId = data.getLong("permissionId");
return permissionService.deletePermission(permissionId);
}
@PostMapping("/deleteMenu")
public Res deleteMenu(@RequestBody JSONObject data) {
Long menuId = data.getLong("menuId");
return permissionService.deleteMenu(menuId);
}
@PostMapping("/getAllRole")
public Res getAllRole(@RequestBody RoleSearchDTO searchDTO) {
return permissionService.getAllRole(searchDTO);
}
@PostMapping("/getAllRoleByAppName")
public Res getAllRoleByAppName(@RequestBody RoleSearchDTO searchDTO) {
return permissionService.getAllRoleByAppName(searchDTO);
}
@PostMapping("/saveRole")
public Res saveRole(@RequestBody SysRole role) {
return permissionService.saveRole(role);
}
@PostMapping("/deleteRole")
public Res deleteRole(@RequestBody JSONObject data) {
Long roleId = data.getLong("roleId");
return permissionService.deleteRole(roleId);
}
@PostMapping("/setRolePermission")
public Res setRolePermission(@RequestBody JSONObject data) {
return permissionService.setRolePermission(data);
}
@GetMapping("/getRolePermission")
public Res getRolePermission(Long roleId) {
return permissionService.getRolePermission(roleId);
}
@PostMapping("/setMemberRole")
public Res setMemberRole(@RequestBody JSONObject data) {
return permissionService.setMemberRole(data);
}
/**
* Get available roles for current customer (based on plan's apps)
*/
@GetMapping("/getMemberAvailableRoles")
public Res getMemberAvailableRoles() {
LoginCustomer customer = AuthContext.getLoginInfo().getCustomer();
if (customer == null || customer.getId() == null) {
return Res.fail("未找到当前登录客户信息");
}
return permissionService.getAvailableRolesForCustomer(customer.getId());
}
/**
* Get member's assigned roles (scoped by current customer)
*/
@GetMapping("/getMemberAssignedRoles")
public Res getMemberAssignedRoles(@RequestParam("userId") Long userId) {
LoginCustomer customer = AuthContext.getLoginInfo().getCustomer();
if (customer == null || customer.getId() == null) {
return Res.fail("未找到当前登录客户信息");
}
return permissionService.getMemberRolesByCustomer(customer.getId(), userId);
}
/**
* Set member roles (scoped by current customer)
*/
@PostMapping("/setMemberRolesByCustomer")
public Res setMemberRolesByCustomer(@RequestBody JSONObject data) {
LoginCustomer customer = AuthContext.getLoginInfo().getCustomer();
if (customer == null || customer.getId() == null) {
return Res.fail("未找到当前登录客户信息");
}
data.put("customerId", customer.getId());
return permissionService.setMemberRoleByCustomer(data);
}
@GetMapping("/getHuoBanUrl")
public Res getPageUrl(Long menuId) {
return permissionService.getPageUrl(menuId);
}
/**
* 获取伙伴云 SSO Token 信息(不需要菜单,不返回 pageUrl/menu
*/
@GetMapping("/getHuobanToken")
public Res getHuobanToken() {
return permissionService.getHuobanToken();
}
@GetMapping("/getMenu")
public Res getMenu(Long menuId) {
return permissionService.getMenu(menuId);
}
@GetMapping("/getPaymentMenu")
public Res getMenu(Long menuId, HttpServletResponse response, HttpServletRequest request) {
return permissionService.getPaymentUrl(menuId, response, request);
}
@GetMapping("/changePaymentGroup")
public Res changePaymentGroup(Integer group, HttpServletRequest request){
return permissionService.changePaymentGroup(group, request);
}
}
@@ -0,0 +1,51 @@
package cn.apes.cloud.controller;
import cn.apes.commons.Res;
import cn.apes.commons.auth.Login;
import cn.apes.cloud.config.TenantSource;
import cn.apes.cloud.service.impl.SysOperationLogServiceImpl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* 操作日志控制器
*/
@Login
@RestController
@TenantSource
@RequestMapping("/operationLog")
public class SysOperationLogController {
@Autowired
private SysOperationLogServiceImpl operationLogService;
/**
* 分页查询操作日志
*/
@PostMapping("/page")
public Res pageLogs(@RequestBody Map<String, Object> params) {
int pageIndex = params.get("pageIndex") != null ? ((Number) params.get("pageIndex")).intValue() : 1;
int pageSize = params.get("pageSize") != null ? ((Number) params.get("pageSize")).intValue() : 10;
Long customerId = params.get("customerId") != null ? ((Number) params.get("customerId")).longValue() : null;
Long userId = params.get("userId") != null ? ((Number) params.get("userId")).longValue() : null;
String startDate = params.get("startDate") != null ? params.get("startDate").toString() : null;
String endDate = params.get("endDate") != null ? params.get("endDate").toString() : null;
IPage<Map<String, Object>> result = operationLogService.pageLogs(
pageIndex, pageSize, customerId, userId, startDate, endDate);
Map<String, Object> pageData = new java.util.HashMap<>();
pageData.put("records", result.getRecords());
pageData.put("total", result.getTotal());
return Res.success(pageData);
}
/**
* 获取企业列表(用于下拉筛选)
*/
@GetMapping("/listCustomers")
public Res listCustomers() {
return Res.success(operationLogService.listCustomers());
}
}
@@ -0,0 +1,78 @@
package cn.apes.cloud.controller;
import cn.apes.commons.Res;
import cn.apes.commons.auth.Login;
import cn.apes.cloud.config.TenantSource;
import cn.apes.cloud.domain.entity.SysOrganization;
import cn.apes.cloud.service.impl.SysOrganizationServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 组织管理控制器
*/
@Login
@RestController
@TenantSource
@RequestMapping("/organization")
public class SysOrganizationController {
@Autowired
private SysOrganizationServiceImpl organizationService;
/**
* 分页查询组织列表
*/
@PostMapping("/page")
public Res pageOrganizations(@RequestParam(required = false) String orgName) {
return organizationService.pageOrganizations(orgName);
}
/**
* 查询组织树
*/
@GetMapping("/tree")
public Res getOrganizationTree() {
return organizationService.getOrganizationTree();
}
/**
* 查询组织详情
*/
@GetMapping("/detail")
public Res detail(@RequestParam Long id) {
return organizationService.getDetail(id);
}
/**
* 新增组织
*/
@PostMapping("/add")
public Res addOrganization(@RequestBody SysOrganization org) {
return organizationService.addOrganization(org);
}
/**
* 更新组织
*/
@PostMapping("/update")
public Res updateOrganization(@RequestBody SysOrganization org) {
return organizationService.updateOrganization(org);
}
/**
* 删除组织
*/
@PostMapping("/delete")
public Res deleteOrganization(@RequestParam Long id) {
return organizationService.deleteOrganization(id);
}
/**
* 查询所有启用的组织列表
*/
@GetMapping("/listAll")
public Res listAllOrganizations() {
return organizationService.listAllOrganizations();
}
}
@@ -0,0 +1,164 @@
package cn.apes.cloud.controller;
import cn.apes.cloud.domain.entity.SysPackagePlan;
import cn.apes.cloud.service.impl.SysPackagePlanServiceImpl;
import cn.apes.commons.Res;
import cn.apes.commons.auth.Login;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@Login
@RestController
@RequestMapping("/packagePlan")
public class SysPackagePlanController {
@Autowired
private SysPackagePlanServiceImpl sysPackagePlanService;
@Autowired
private cn.apes.cloud.service.SysPackagePlanQuotaService sysPackagePlanQuotaService;
@PostMapping("/page")
public Res pagePackagePlan(@RequestBody JSONObject data) {
int pageIndex = data.getIntValue("pageIndex");
int pageSize = data.getIntValue("pageSize");
if (pageIndex <= 0) pageIndex = 1;
if (pageSize <= 0) pageSize = 10;
String planName = data.getString("planName");
return Res.success(sysPackagePlanService.pagePackagePlan(pageIndex, pageSize, planName));
}
@PostMapping("/add")
public Res addPackagePlan(@RequestBody SysPackagePlan plan) {
return sysPackagePlanService.addPackagePlan(plan);
}
@PostMapping("/update")
public Res updatePackagePlan(@RequestBody SysPackagePlan plan) {
return sysPackagePlanService.updatePackagePlan(plan);
}
@PostMapping("/delete")
public Res deletePackagePlan(@RequestBody JSONObject data) {
Long id = data.getLong("id");
return sysPackagePlanService.deletePackagePlan(id);
}
@PostMapping("/toggleStatus")
public Res toggleStatus(@RequestBody JSONObject data) {
Long id = data.getLong("id");
Integer status = data.getInteger("status");
return sysPackagePlanService.toggleStatus(id, status);
}
@GetMapping("/detail")
public Res getDetail(@RequestParam Long id) {
SysPackagePlan plan = sysPackagePlanService.getById(id);
if (plan == null) return Res.fail("套餐不存在");
return Res.success(plan);
}
/**
* 获取套餐已分配的权限
*/
@GetMapping("/getPermissions")
public Res getPermissions(@RequestParam Long planId) {
return Res.success(sysPackagePlanService.getPlanPermissions(planId));
}
/**
* 保存套餐权限关联
*/
@PostMapping("/savePermissions")
public Res savePermissions(@RequestBody JSONObject data) {
Long planId = data.getLong("planId");
Map<String, List<Long>> appPermissions = new java.util.HashMap<>();
JSONObject appPermsObj = data.getJSONObject("appPermissions");
if (appPermsObj != null) {
for (String appKey : appPermsObj.keySet()) {
List<Long> permIds = new java.util.ArrayList<>();
Object val = appPermsObj.get(appKey);
if (val instanceof List) {
for (Object item : (List<?>) val) {
if (item instanceof Number) {
permIds.add(((Number) item).longValue());
} else if (item != null) {
try {
permIds.add(Long.parseLong(item.toString()));
} catch (NumberFormatException e) {
// skip
}
}
}
}
appPermissions.put(appKey, permIds);
}
}
return sysPackagePlanService.savePlanPermissions(planId, appPermissions);
}
/**
* 获取套餐配额配置(已绑定的)
*/
@GetMapping("/getQuotas")
public Res getPlanQuotas(@RequestParam Long planId) {
return sysPackagePlanQuotaService.getPlanQuotas(planId);
}
/**
* 获取所有配额模板 + 标记当前套餐是否已配置
*/
@GetMapping("/getAllQuotas")
public Res getAllQuotas(@RequestParam Long planId) {
return sysPackagePlanQuotaService.getAllQuotas(planId);
}
/**
* 获取套餐关联的应用列表
*/
@GetMapping("/getPlanApps")
public Res getPlanApps(@RequestParam Long planId) {
return sysPackagePlanService.getPlanApps(planId);
}
/**
* 保存套餐配额配置
*/
@RequestMapping(value = "/saveQuotas", method = {org.springframework.web.bind.annotation.RequestMethod.GET, org.springframework.web.bind.annotation.RequestMethod.POST})
public Res savePlanQuotas(@RequestBody(required = false) JSONObject data) {
// 兼容 GET 请求(可能被重定向转换)
if (data == null || data.isEmpty()) {
// 尝试从 request parameters 获取
javax.servlet.http.HttpServletRequest request = ((org.springframework.web.context.request.ServletRequestAttributes) org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest();
Long planId = request.getParameter("planId") != null ? Long.parseLong(request.getParameter("planId")) : null;
String quotaValuesStr = request.getParameter("quotaValues");
Map<Long, String> quotaValues = new java.util.HashMap<>();
if (quotaValuesStr != null) {
try {
JSONObject qv = JSONObject.parseObject(quotaValuesStr);
for (String key : qv.keySet()) {
Object val = qv.get(key);
quotaValues.put(Long.parseLong(key), val != null ? val.toString() : null);
}
} catch (Exception e) {
return Res.fail("quotaValues格式错误");
}
}
return sysPackagePlanQuotaService.savePlanQuotas(planId, quotaValues);
}
Long planId = data.getLong("planId");
JSONObject quotaValuesObj = data.getJSONObject("quotaValues");
Map<Long, String> quotaValues = new java.util.HashMap<>();
if (quotaValuesObj != null) {
for (String key : quotaValuesObj.keySet()) {
Object val = quotaValuesObj.get(key);
quotaValues.put(Long.parseLong(key), val != null ? val.toString() : null);
}
}
return sysPackagePlanQuotaService.savePlanQuotas(planId, quotaValues);
}
}
@@ -0,0 +1,51 @@
package cn.apes.cloud.controller;
import cn.apes.cloud.domain.entity.SysQuota;
import cn.apes.cloud.service.impl.SysQuotaServiceImpl;
import cn.apes.commons.Res;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/quota")
public class SysQuotaController {
@Autowired
private SysQuotaServiceImpl sysQuotaService;
@PostMapping("/page")
public Res pageQuota(@RequestBody JSONObject data) {
int pageIndex = data.getIntValue("pageIndex");
int pageSize = data.getIntValue("pageSize");
if (pageIndex <= 0) pageIndex = 1;
if (pageSize <= 0) pageSize = 10;
String appKey = data.getString("appKey");
String quotaName = data.getString("quotaName");
String quotaCode = data.getString("quotaCode");
return Res.success(sysQuotaService.pageQuota(pageIndex, pageSize, appKey, quotaName, quotaCode));
}
@PostMapping("/add")
public Res addQuota(@RequestBody SysQuota quota) {
return sysQuotaService.addQuota(quota);
}
@PostMapping("/update")
public Res updateQuota(@RequestBody SysQuota quota) {
return sysQuotaService.updateQuota(quota);
}
@PostMapping("/delete")
public Res deleteQuota(@RequestBody JSONObject data) {
Long id = data.getLong("id");
return sysQuotaService.deleteQuota(id);
}
@GetMapping("/detail")
public Res getDetail(@RequestParam Long id) {
SysQuota quota = sysQuotaService.getById(id);
if (quota == null) return Res.fail("配额不存在");
return Res.success(quota);
}
}
@@ -0,0 +1,207 @@
package cn.apes.cloud.controller;
import cn.apes.commons.Res;
import cn.apes.commons.auth.AuthContext;
import cn.apes.commons.auth.Login;
import cn.apes.cloud.config.TenantSource;
import cn.apes.cloud.domain.dto.LoginDTO;
import cn.apes.cloud.domain.dto.PwdDTO;
import cn.apes.cloud.domain.dto.RegisterDTO;
import cn.apes.cloud.domain.dto.SelectCustomerDTO;
import cn.apes.cloud.domain.dto.UserSearchDTO;
import cn.apes.cloud.domain.dto.EnterpriseCertifyDTO;
import cn.apes.cloud.domain.entity.CustomerInfo;
import cn.apes.cloud.domain.entity.UserInfo;
import cn.apes.cloud.service.impl.UserInfoServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
@TenantSource
@RequestMapping("/user")
public class UserController {
@Autowired
UserInfoServiceImpl userInfoService;
@Login
@PostMapping("/saveUserInfo")
public Res saveUserInfo(@RequestBody UserInfo userInfo) {
return userInfoService.saveUserInfo(userInfo);
}
@Login
// @CrossOrigin("*")
@PostMapping("/pageUser")
public Res pageUser(@RequestBody UserSearchDTO searchDTO) {
return userInfoService.pageUser(searchDTO);
}
@PostMapping("/loginPwd")
public Res loginPwd(@RequestBody LoginDTO loginDTO) {
log.info("开始登录了");
return userInfoService.loginPwd(loginDTO);
}
/**
* 用户注册
*/
@PostMapping("/register")
public Res register(@RequestBody RegisterDTO dto) {
return userInfoService.register(dto);
}
@Login
@PostMapping("/resetPwd")
public Res resetPwd(@RequestBody UserInfo userInfo) {
return userInfoService.resetPwd(userInfo);
}
@Login
@GetMapping("/getCurrentUser")
public Res getCurrentUser() {
try {
Object loginInfo = AuthContext.getLoginInfo();
if (loginInfo == null) {
return Res.fail("未登录");
}
return Res.success(loginInfo);
} catch (Exception e) {
return Res.fail("获取用户信息失败");
}
}
@Login
@PostMapping("/editPwd")
public Res editPwd(@RequestBody PwdDTO pwdDTO) {
return userInfoService.editPwd(pwdDTO);
}
@Login
@GetMapping("/getUserByPhone")
public Res getUserByPhone(String phone) {
return userInfoService.getUser(phone);
}
@Login
@GetMapping("/getCustomerUser")
public Res getCustomerUser(Long customerId) {
return userInfoService.getCustomerUser(customerId);
}
/**
* 多客户时用户选择企业
*/
@PostMapping("/selectCustomer")
public Res selectCustomer(@RequestBody SelectCustomerDTO dto) {
return userInfoService.selectCustomer(dto.getToken(), dto.getCustomerId());
}
/**
* 获取当前用户关联的企业列表(用于切换企业场景)
*/
@Login
@GetMapping("/getMyCustomers")
public Res getMyCustomers() {
return userInfoService.getMyCustomers();
}
/**
* 编辑当前用户的个人资料(昵称、头像)
*/
@Login
@PostMapping("/editProfile")
public Res editProfile(@RequestBody UserInfo userInfo) {
return userInfoService.editProfile(userInfo);
}
/**
* 编辑当前企业信息(仅更新当前登录用户的所属企业)
*/
@Login
@PostMapping("/customer/editCurrent")
public Res editCurrentCustomer(@RequestBody CustomerInfo customerInfo) {
return userInfoService.editCurrentCustomer(customerInfo);
}
/**
* 获取当前企业信息(从数据库直接读取,不走Redis缓存)
*/
@Login
@GetMapping("/customer/getCurrent")
public Res getCurrentCustomer() {
return userInfoService.getCurrentCustomerFromDb();
}
/**
* 企业认证:用户提交认证表单,创建客户记录并关联当前用户
* 使用登录返回的 token(无需额外登录态校验)
*/
@PostMapping("/certifyEnterprise")
public Res certifyEnterprise(@RequestBody EnterpriseCertifyDTO dto) {
if (dto == null || cn.hutool.core.util.StrUtil.isBlank(dto.getToken())) {
return Res.fail("token不能为空");
}
CustomerInfo customerInfo = new CustomerInfo();
customerInfo.setCustomerName(dto.getCustomerName());
customerInfo.setLogo(dto.getLogo());
customerInfo.setShortName(dto.getShortName());
customerInfo.setCustomerType(dto.getCustomerType());
customerInfo.setCustomerIdNumber(dto.getCustomerIdNumber());
customerInfo.setLegalName(dto.getLegalName());
customerInfo.setLegalIdFront(dto.getLegalIdFront());
customerInfo.setLegalIdBack(dto.getLegalIdBack());
customerInfo.setAdminName(dto.getAdminName());
customerInfo.setAdminPhone(dto.getAdminPhone());
customerInfo.setAdminIdNumber(dto.getAdminIdNumber());
customerInfo.setResponsibleIdFront(dto.getResponsibleIdFront());
customerInfo.setResponsibleIdBack(dto.getResponsibleIdBack());
customerInfo.setIndustry(dto.getIndustry());
customerInfo.setIndustrySub(dto.getIndustrySub());
customerInfo.setSalesChannel(dto.getSalesChannel());
customerInfo.setIndustryType(dto.getIndustryType());
customerInfo.setProvince(dto.getProvince());
customerInfo.setCity(dto.getCity());
customerInfo.setBusinessLicense(dto.getBusinessLicense());
return userInfoService.certifyEnterprise(dto.getToken(), customerInfo);
}
/**
* 获取当前用户的企业信息(用于重新编辑)
*/
@GetMapping("/getPendingCustomer")
public Res getPendingCustomer(@RequestParam("token") String token) {
return userInfoService.getPendingCustomer(token);
}
/**
* 重新提交企业认证
*/
@PostMapping("/recertifyEnterprise")
public Res recertifyEnterprise(@RequestBody EnterpriseCertifyDTO dto) {
CustomerInfo customerInfo = new CustomerInfo();
customerInfo.setCustomerName(dto.getCustomerName());
customerInfo.setShortName(dto.getShortName());
customerInfo.setCustomerIdNumber(dto.getCustomerIdNumber());
customerInfo.setAdminName(dto.getAdminName());
customerInfo.setAdminPhone(dto.getAdminPhone());
customerInfo.setAdminIdNumber(dto.getAdminIdNumber());
customerInfo.setLegalName(dto.getLegalName());
customerInfo.setLegalIdFront(dto.getLegalIdFront());
customerInfo.setLegalIdBack(dto.getLegalIdBack());
customerInfo.setResponsibleIdFront(dto.getResponsibleIdFront());
customerInfo.setResponsibleIdBack(dto.getResponsibleIdBack());
customerInfo.setLogo(dto.getLogo());
customerInfo.setIndustry(dto.getIndustry());
customerInfo.setIndustrySub(dto.getIndustrySub());
customerInfo.setSalesChannel(dto.getSalesChannel());
customerInfo.setIndustryType(dto.getIndustryType());
customerInfo.setProvince(dto.getProvince());
customerInfo.setCity(dto.getCity());
customerInfo.setBusinessLicense(dto.getBusinessLicense());
return userInfoService.recertifyEnterprise(dto.getToken(), customerInfo);
}
}
@@ -0,0 +1,117 @@
package cn.apes.cloud.controller;
import cn.apes.cloud.config.TenantSource;
import cn.apes.cloud.domain.entity.UserPreference;
import cn.apes.cloud.service.impl.UserPreferenceServiceImpl;
import cn.apes.commons.Res;
import cn.apes.commons.auth.Login;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 用户个性化配置控制器
*
* @author OpenClaw
* @since 2026-08-06
*/
@RestController
@TenantSource
@RequestMapping("/userPreference")
public class UserPreferenceController {
@Resource
private UserPreferenceServiceImpl userPreferenceService;
/**
* 查询指定用户的个性化配置
*
* @param userId 用户ID
* @return Res 响应对象
* - code: 状态码,1表示成功
* - data: 个性化配置对象(未配置时返回默认值 uiSize=2)
*/
@Login
@PostMapping("/get")
public Res getUserPreference(@RequestParam Long userId) {
return userPreferenceService.getUserPreference(userId);
}
/**
* 查询当前登录用户的个性化配置
*
* @return Res 响应对象
* - code: 状态码,1表示成功
* - data: 当前用户的个性化配置对象(未配置时返回默认值 uiSize=2)
*/
@Login
@PostMapping("/mine")
public Res getCurrentUserPreference() {
return userPreferenceService.getCurrentUserPreference();
}
/**
* 新增用户个性化配置
*
* @param preference 配置对象
* - userId: 用户ID(必填)
* - uiSize: UI尺寸 1-最大 2-中等 3-最小(必填)
* @return Res 响应对象
* - code: 状态码,1表示成功
* - msg: 操作结果消息
*/
@Login
@PostMapping("/add")
public Res addUserPreference(@RequestBody UserPreference preference) {
return userPreferenceService.addUserPreference(preference);
}
/**
* 更新用户个性化配置
*
* @param preference 配置对象
* - userId: 用户ID(必填)
* - uiSize: UI尺寸 1-最大 2-中等 3-最小(必填)
* @return Res 响应对象
* - code: 状态码,1表示成功
* - msg: 操作结果消息
*/
@Login
@PostMapping("/update")
public Res updateUserPreference(@RequestBody UserPreference preference) {
return userPreferenceService.updateUserPreference(preference);
}
/**
* 保存当前登录用户的个性化配置(不存在则新增,已存在则更新)
*
* @param preference 配置对象
* - uiSize: UI尺寸 1-最大 2-中等 3-最小(必填)
* @return Res 响应对象
* - code: 状态码,1表示成功
* - msg: 操作结果消息
*/
@Login
@PostMapping("/save")
public Res saveCurrentUserPreference(@RequestBody UserPreference preference) {
return userPreferenceService.saveCurrentUserPreference(preference);
}
/**
* 删除用户个性化配置(逻辑删除)
*
* @param userId 用户ID
* @return Res 响应对象
* - code: 状态码,1表示成功
* - msg: 操作结果消息
*/
@Login
@PostMapping("/delete")
public Res deleteUserPreference(@RequestParam Long userId) {
return userPreferenceService.deleteUserPreference(userId);
}
}
@@ -0,0 +1,10 @@
package cn.apes.cloud.domain.dto;
import lombok.Data;
@Data
public class AppSearchDTO extends PageDTO {
private String appName;
private String appKey;
private Boolean userVisible;
}
@@ -0,0 +1,9 @@
package cn.apes.cloud.domain.dto;
import lombok.Data;
@Data
public class CustomerSearchDTO extends PageDTO{
}
@@ -0,0 +1,52 @@
package cn.apes.cloud.domain.dto;
import lombok.Data;
@Data
public class EnterpriseCertifyDTO {
/** 企业名称 */
private String customerName;
/** 企业LOGO */
private String logo;
/** 企业简称 */
private String shortName;
/** 主体类型 1.个人 2.企业 3.个体户 4.政府 */
private Integer customerType;
/** 营业执照号/证件号 */
private String customerIdNumber;
/** 法人姓名 */
private String legalName;
/** 法人身份证正面 */
private String legalIdFront;
/** 法人身份证反面 */
private String legalIdBack;
/** 负责人姓名 */
private String adminName;
/** 负责人手机 */
private String adminPhone;
/** 负责人证件号 */
private String adminIdNumber;
/** 负责人身份证正面 */
private String responsibleIdFront;
/** 负责人身份证反面 */
private String responsibleIdBack;
/** 行业(一级) */
private String industry;
/** 行业(二级细分) */
private String industrySub;
/** 销售渠道 */
private String salesChannel;
/** 行业类型 */
private String industryType;
/** 省份 */
private String province;
/** 城市 */
private String city;
/** 营业执照 */
private String businessLicense;
/**
* Token from login response
*/
private String token;
}
@@ -0,0 +1,9 @@
package cn.apes.cloud.domain.dto;
import lombok.Data;
@Data
public class LoginDTO {
String phone;
String password;
}
@@ -0,0 +1,25 @@
package cn.apes.cloud.domain.dto;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Data;
@Data
public class PageDTO {
Integer pageIndex;
Integer pageSize;
Integer page;
Integer perPage;
public IPage getPage() {
if (page != null && perPage != null) {
return new Page(page, perPage);
}
if (pageIndex != null && pageSize != null) {
return new Page(pageIndex, pageSize);
}
return new Page(1, 10);
}
}
@@ -0,0 +1,10 @@
package cn.apes.cloud.domain.dto;
import lombok.Data;
@Data
public class PwdDTO {
String oldPwd;
String pwd1;
String pwd2;
}
@@ -0,0 +1,19 @@
package cn.apes.cloud.domain.dto;
import lombok.Data;
@Data
public class RegisterDTO {
/**
* 用户昵称
*/
private String nickname;
/**
* 手机号
*/
private String phone;
/**
* 密码
*/
private String password;
}
@@ -0,0 +1,15 @@
package cn.apes.cloud.domain.dto;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class RelationDTO {
String fieldName;
Long fieldId;
List<Long> dataIds =new ArrayList<>();
List<JSONObject> dataList;
}
@@ -0,0 +1,13 @@
package cn.apes.cloud.domain.dto;
import lombok.Data;
import java.util.Set;
@Data
public class RoleSearchDTO extends PageDTO{
Long userNo;
String appName;
Set<String> appNames;
Long planId;
}
@@ -0,0 +1,9 @@
package cn.apes.cloud.domain.dto;
import lombok.Data;
@Data
public class SelectCustomerDTO {
String token;
Long customerId;
}
@@ -0,0 +1,21 @@
package cn.apes.cloud.domain.dto;
import cn.apes.cloud.domain.entity.SysRole;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* description
*
* @date 2024/7/9 09:44
*/
@Data
public class SysRoleDTO implements Serializable {
private static final long serialVersionUID = 1L;
private String appName;
private List<SysRole> SysRoleList;
}
@@ -0,0 +1,9 @@
package cn.apes.cloud.domain.dto;
import lombok.Data;
@Data
public class UserSearchDTO extends PageDTO{
String nickName;
String phone;
}
@@ -0,0 +1,25 @@
package cn.apes.cloud.domain.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;
import java.util.Date;
@Data
public class AppInfo {
@TableId
private String appKey;
private String appIcon;
private String appName;
private String appShortName;
private String appType;
private String remark;
private Boolean userVisible;
private String huobanSpaceId; // 伙伴云工作区ID
private String huobanSpaceGroupId; // 伙伴云工作区用户组ID
private Date createTime;
private Date updateTime;
@TableLogic
private Boolean isDel;
}
@@ -0,0 +1,14 @@
package cn.apes.cloud.domain.entity;
import cn.apes.commons.domain.DbEntity;
import lombok.Data;
import java.io.Serializable;
@Data
public class CustomerApp extends DbEntity implements Serializable {
private Long customerId;
private String appKey;
}
@@ -0,0 +1,142 @@
package cn.apes.cloud.domain.entity;
import cn.apes.commons.domain.DbEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class CustomerInfo extends DbEntity implements Serializable {
@TableId(type = IdType.AUTO)
private Long id;
/**
* 客户类型 1.个人 2.企业 3.个体户 4.政府
*/
private Integer customerType;
/**
* 客户名称
*/
private String customerName;
/**
* 客户LOGO图片URL
*/
private String logo;
/**
* 客户名称
*/
private String shortName;
/**
* 证件号,确保唯一性并不允许空值。
*/
private String customerIdNumber;
/**
* 企业状态:0=待审核,1=使用中,3=已注销,4=暂停服务,5=审核拒绝
*/
private Integer customerStatus;
/**
* 审核拒绝原因
*/
private String auditRemark;
/**
* 审核时间
*/
private java.util.Date auditTime;
/**
* 管理员姓名,不允许空值。
*/
private String adminName;
/**
* 管理员手机号,不允许空值。
*/
private String adminPhone;
/**
* 管理员证件号,确保唯一性并不允许空值。
*/
private String adminIdNumber;
// ========== 资质管理 ==========
/**
* 营业执照图片URL
*/
private String businessLicense;
/**
* 法人姓名
*/
private String legalName;
/**
* 法人身份证正面图片URL
*/
private String legalIdFront;
/**
* 法人身份证反面图片URL
*/
private String legalIdBack;
/**
* 负责人身份证正面图片URL
*/
private String responsibleIdFront;
/**
* 负责人身份证反面图片URL
*/
private String responsibleIdBack;
// ========== 行业 / 区域 / 渠道 ==========
/**
* 行业(一级)
*/
private String industry;
/**
* 行业(二级细分)
*/
private String industrySub;
/**
* 销售渠道
*/
private String salesChannel;
/**
* 行业类型:供应商/加工商/经销商/零售商
*/
private String industryType;
/**
* 省份
*/
private String province;
/**
* 城市
*/
private String city;
/**
* 关联套餐列表(非数据库字段)
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private List<SysPackagePlan> plans;
}
@@ -0,0 +1,53 @@
package cn.apes.cloud.domain.entity;
import cn.apes.commons.domain.DbEntity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
@TableName("customer_package")
public class CustomerPackage extends DbEntity implements Serializable {
/**
* 客户ID
*/
private Long customerId;
/**
* 套餐ID
*/
private Long planId;
/**
* 到期时间
*/
private Date expireTime;
/**
* 套餐名称(非数据库字段)
*/
@TableField(exist = false)
private String planName;
/**
* 套餐级别(非数据库字段)
*/
@TableField(exist = false)
private Integer planLevel;
/**
* 收费模式(非数据库字段)
*/
@TableField(exist = false)
private String chargeMode;
/**
* 金额(非数据库字段)
*/
@TableField(exist = false)
private java.math.BigDecimal amount;
}
@@ -0,0 +1,43 @@
package cn.apes.cloud.domain.entity;
import cn.apes.commons.domain.DbEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("customer_package_extend_apply")
public class CustomerPackageExtendApply extends DbEntity {
/** 客户ID */
private Long customerId;
/** 套餐ID */
private Long planId;
/** 申请用户ID */
private Long userId;
/** 联系电话 */
private String contactPhone;
/** 延期原因 */
private String reason;
/** 申请延期天数 */
private Integer applyDays;
/** 状态:0-申请中 1-已调整 2-已拒绝 */
private Integer status;
/** 操作人ID(同意/拒绝的人) */
private Long operatorId;
/** 操作人姓名 */
private String operatorName;
/** 操作时间 */
private java.util.Date operateTime;
/** 申请时间 */
private java.util.Date applyTime;
}
@@ -0,0 +1,68 @@
package cn.apes.cloud.domain.entity;
import cn.apes.commons.domain.DbEntity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
@Data
@TableName("customer_package_quota")
public class CustomerPackageQuota extends DbEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 客户套餐关联ID
*/
private Long customerPackageId;
/**
* 配额模板ID
*/
private Long quotaId;
/**
* 应用标识
*/
private String appKey;
/**
* 配额编码
*/
private String quotaCode;
/**
* 配额名称
*/
private String quotaName;
/**
* 数据类型
*/
private String dataType;
/**
* 配额值
*/
private String quotaValue;
/**
* 应用名称(非数据库字段)
*/
@TableField(exist = false)
private String appName;
/**
* 单位(非数据库字段,来自sys_quota)
*/
@TableField(exist = false)
private String unit;
/**
* 重置周期(非数据库字段,来自sys_quota)
*/
@TableField(exist = false)
private String resetCycle;
}
@@ -0,0 +1,86 @@
package cn.apes.cloud.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 客户配额变更日志
*/
@Data
@TableName("customer_quota_change_log")
public class CustomerQuotaChangeLog implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 客户套餐关联ID
*/
private Long customerPackageId;
/**
* 配额编码
*/
private String quotaCode;
/**
* 配额名称
*/
private String quotaName;
/**
* 变更类型:1=增加,2=减少
*/
private Integer changeType;
/**
* 变更数量
*/
private Integer changeAmount;
/**
* 变更前值
*/
private String oldValue;
/**
* 变更后值
*/
private String newValue;
/**
* 变更原因
*/
private String reason;
/**
* 操作人ID
*/
private Long operatorId;
/**
* 操作人姓名
*/
private String operatorName;
/**
* 操作时间
*/
private Date operateTime;
/**
* 客户名称(非数据库字段,用于展示)
*/
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
private String customerName;
}
@@ -0,0 +1,29 @@
package cn.apes.cloud.domain.entity;
import cn.apes.commons.domain.DbEntity;
import lombok.Data;
import java.io.Serializable;
@Data
public class CustomerUser extends DbEntity implements Serializable {
private long userId;
private long customerId;
/** 所属组织ID */
private Long orgId;
/** 姓名 */
private String name;
/** 职称 */
private String title;
/** 岗位 */
private String position;
/** 应用ID集合(逗号分隔) */
private String appIds;
}
@@ -0,0 +1,31 @@
package cn.apes.cloud.domain.entity;
import cn.apes.commons.domain.DbEntity;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
@Data
public class SsoConfig extends DbEntity implements Serializable {
@JsonSerialize(using = ToStringSerializer.class)
private Long userId; // 用户ID
private Long customerId;
//伙伴云配置
private String huobanMobile; // 伙伴云绑定手机号
@JsonSerialize(using = ToStringSerializer.class)
private Long huobanCompanyId; // 伙伴云公司ID
private String huobanName; // 伙伴云用户名
private Integer huobanStatus;
private Long huobanUserId; // 伙伴云用户ID
private String huobanUserName; // 伙伴云用户名(搜索返回的name)
private String huobanAvatar; // 伙伴云用户头像
//交易平台配置
private String paymentUserName;
private String paymentPassword;
private Integer paymentGroup;
private Integer paymentStatus;
}
@@ -0,0 +1,75 @@
package cn.apes.cloud.domain.entity;
import cn.apes.commons.domain.DbEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.Date;
/**
* 外部系统调用用的 AccessToken 表
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SysAccessToken extends DbEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Token 值(明文存储)
*/
private String token;
/**
* 名称,用于管理识别
*/
private String name;
/**
* 所属客户(企业)ID
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long customerId;
/**
* 接口范围:* 表示全部,否则为逗号分隔的 Ant 路径
*/
private String scope;
/**
* IP 白名单,多个用逗号分隔,为空表示不限制
*/
private String whitelistIps;
/**
* 状态 1=启用 2=停用
*/
private Integer status;
/**
* 过期时间,NULL 表示永不过期
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date expireTime;
/**
* 最后使用时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date lastUsedTime;
/**
* 最后使用 IP
*/
private String lastUsedIp;
/**
* 创建人
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long createBy;
}
@@ -0,0 +1,38 @@
package cn.apes.cloud.domain.entity;
import cn.apes.commons.domain.DbEntity;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class SysMemberRole extends DbEntity implements Serializable {
private String appName;
/**
* 角色id
*/
private Long roleId;
/**
* 账号id
*/
private Long userId;
/**
* 角色附加数据 一般用于子账号保存主账号userno
*/
private String roleData;
/**
* 角色所属客户(企业),系统角色为0
*/
private Long customerId;
}
@@ -0,0 +1,103 @@
package cn.apes.cloud.domain.entity;
import cn.apes.commons.domain.DbEntity;
import com.alibaba.fastjson.JSONObject;
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.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* @author
* @description sys_menu
* @date 2023-06-29
*/
@Data
public class SysMenu extends DbEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 菜单名称
*/
private String menuTitle;
/**
* 菜单路径
*/
private String menuPath;
/**
* 菜单类型 1.vue菜单2.伙伴云菜单 3.交易系统菜单
*/
private Integer menuType;
private String configJson;
@TableField(exist = false)
private JSONObject config;
private String vuePath;
private String vueName;
private Long amisId;
/**
* 菜单图片
*/
private String menuIcon;
/**
* 父级id 根为0
*/
private Long parentId;
/**
* 平台类型
*/
private String appName;
/**
* 对应的权限code
*/
private String permissionCode;
/**
* 备注
*/
@JsonIgnore
private String remark;
/**
* 低版本
*/
@JsonIgnore
private String lowVersion;
/**
* 高版本
*/
@JsonIgnore
private String highVersion;
private Integer sort;
private boolean hidden;
/**
* 是否在看板显示
*/
private Boolean showOnBoard;
@TableField(exist = false)
private List<SysMenu> child;
public SysMenu() {
}
}
@@ -0,0 +1,65 @@
package cn.apes.cloud.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
* 操作日志表
*/
@Data
@TableName("sys_operation_log")
public class SysOperationLog {
@TableId(type = IdType.AUTO)
private Long id;
/** 用户ID */
private Long userId;
/** 用户名 */
private String userName;
/** 租户/企业ID */
private Long customerId;
/** 企业名称 */
private String customerName;
/** 模块名称 */
private String module;
/** 操作名称 */
private String action;
/** 日志说明 */
private String description;
/** IP地址 */
private String ip;
/** 浏览器/设备信息 */
private String userAgent;
/** 登录地址(预留) */
private String loginLocation;
/** 请求方法 */
private String requestMethod;
/** 请求URL */
private String requestUrl;
/** 请求参数 */
private String requestParams;
/** 操作时间 */
private Date operateTime;
private String createBy;
private Date createTime;
}
@@ -0,0 +1,62 @@
package cn.apes.cloud.domain.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.JsonIgnore;
import lombok.Data;
import java.util.Date;
import java.util.List;
/**
* 组织表
*/
@Data
@TableName("sys_organization")
public class SysOrganization {
@TableId(type = IdType.AUTO)
private Long id;
/** 租户ID */
private Long customerId;
/** 组织名称 */
private String orgName;
/** 上级组织ID,根节点为0 */
private Long parentId;
/** 组织类型 */
private String orgType;
/** 组织说明 */
private String description;
/** 排序 */
private Integer sortOrder;
/** 状态 1启用/0停用 */
private Integer status;
/** 逻辑删除 0正常/1删除 */
private Integer isDel;
private String createBy;
private Date createTime;
private String updateBy;
private Date updateTime;
/** 子节点列表(非数据库字段) */
@TableField(exist = false)
private List<SysOrganization> children;
/** 父级名称(非数据库字段) */
@TableField(exist = false)
private String parentName;
}
@@ -0,0 +1,50 @@
package cn.apes.cloud.domain.entity;
import cn.apes.commons.domain.DbEntity;
import lombok.Data;
import java.io.Serializable;
@Data
public class SysPackagePlan extends DbEntity implements Serializable {
/**
* 套餐名称
*/
private String planName;
/**
* 套餐级别: 1/2/3
*/
private Integer planLevel;
/**
* 套餐说明
*/
private String description;
/**
* 收费模式: monthly/yearly
*/
private String chargeMode;
/**
* 金额
*/
private java.math.BigDecimal amount;
/**
* 状态: 1启用 0停用
*/
private Integer status;
/**
* 图标URL
*/
private String iconUrl;
/**
* 应用ID集合,逗号分隔
*/
private String appIds;
}
@@ -0,0 +1,41 @@
package cn.apes.cloud.domain.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 lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
@TableName("sys_package_plan_permission")
public class SysPackagePlanPermission implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
/**
* 套餐ID
*/
private Long planId;
/**
* 应用Key
*/
private String appKey;
/**
* 权限ID
*/
private Long permissionId;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
}
@@ -0,0 +1,102 @@
package cn.apes.cloud.domain.entity;
import cn.apes.commons.domain.DbEntity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
@Data
@TableName("sys_package_plan_quota")
public class SysPackagePlanQuota extends DbEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 套餐ID
*/
private Long planId;
/**
* 配额模板ID
*/
private Long quotaId;
/**
* 配额值
*/
private String quotaValue;
/**
* 配额名称(非数据库字段)
*/
@TableField(exist = false)
private String quotaName;
/**
* 配额编码(非数据库字段)
*/
@TableField(exist = false)
private String quotaCode;
/**
* 数据类型(非数据库字段)
*/
@TableField(exist = false)
private String dataType;
/**
* 应用标识(非数据库字段)
*/
@TableField(exist = false)
private String appKey;
/**
* 应用名称(非数据库字段)
*/
@TableField(exist = false)
private String appName;
/**
* 最大配额(非数据库字段)
*/
@TableField(exist = false)
private Long maxQuota;
/**
* 最大配额值-小数(非数据库字段)
*/
@TableField(exist = false)
private java.math.BigDecimal maxQuotaValue;
/**
* 枚举值(非数据库字段)
*/
@TableField(exist = false)
private String enumValues;
/**
* 单位(非数据库字段)
*/
@TableField(exist = false)
private String unit;
/**
* 重置周期(非数据库字段)
*/
@TableField(exist = false)
private String resetCycle;
/**
* 默认值(非数据库字段)
*/
@TableField(exist = false)
private String defaultValue;
/**
* 是否已在当前套餐中配置(非数据库字段)
*/
@TableField(exist = false)
private Boolean configured;
}
@@ -0,0 +1,59 @@
package cn.apes.cloud.domain.entity;
import cn.apes.commons.domain.DbEntity;
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 com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* @description sys_permission
* @author zhengkai.blog.csdn.net
* @date 2023-06-29
*/
@Data
public class SysPermission extends DbEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 权限名称
*/
private String permissionName;
/**
* 权限code
*/
private String permissionCode;
/**
* 备注
*/
private String remark;
/**
* 父级id 根为0
*/
private Long parentId;
/**
* 平台类型
*/
private String appName;
@TableField(exist = false)
private List<SysPermission> child;
public SysPermission() {}
}
@@ -0,0 +1,79 @@
package cn.apes.cloud.domain.entity;
import cn.apes.commons.domain.DbEntity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
@Data
public class SysQuota extends DbEntity implements Serializable {
/**
* 配额名称
*/
private String quotaName;
/**
* 标识编码
*/
private String quotaCode;
/**
* 数据类型: integer/decimal/boolean/enum
*/
private String dataType;
/**
* 最大配额(整数类型)
*/
private Long maxQuota;
/**
* 最大配额单位(整数类型)
*/
private String maxQuotaUnit;
/**
* 最大配额值(小数类型)
*/
private java.math.BigDecimal maxQuotaValue;
/**
* 单位(小数类型)
*/
private String unit;
/**
* 枚举值,JSON数组格式
*/
private String enumValues;
/**
* 默认值
*/
private String defaultValue;
/**
* 重置周期: none/daily/weekly/monthly/yearly
*/
private String resetCycle;
/**
* 是否显示: 1显示 0隐藏
*/
private Integer isVisible;
/**
* 说明
*/
private String description;
/**
* 关联应用标识
*/
private String appKey;
}
@@ -0,0 +1,50 @@
package cn.apes.cloud.domain.entity;
import cn.apes.commons.domain.DbEntity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class SysRole extends DbEntity implements Serializable {
/**
* 角色名
*/
private String roleName;
/**
* 角色标识
*/
private String roleKey;
/**
* 角色所属用户,系统角色为0
*/
private Long userNo;
/**
* 所属套餐ID
*/
private Long planId;
/**
* @deprecated 已废弃,角色不再直接关联应用,改为通过套餐关联
* 保留该字段是为了兼容存量数据
*/
@Deprecated
private String appName;
@TableField(exist = false)
private cn.apes.cloud.domain.entity.SysPackagePlan planInfo;
}
@@ -0,0 +1,40 @@
package cn.apes.cloud.domain.entity;
import cn.apes.commons.domain.DbEntity;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* @description 角色权限表
* @author zhengkai.blog.csdn.net
* @date 2023-06-29
*/
@Data
public class SysRolePermission extends DbEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 角色id
*/
private Long roleId;
/**
* 权限id
*/
private Long permissionId;
/**
* 应用Key
*/
private String appKey;
public SysRolePermission() {}
}
@@ -0,0 +1,81 @@
package cn.apes.cloud.domain.entity;
import cn.apes.commons.domain.DbEntity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* 用户信息实体类
*
* @author [你的名字]
* @since [创建时间]
*/
@Data
public class UserInfo extends DbEntity implements Serializable {
/**
* 用户昵称
*/
private String nickName;
/**
* 用户头像链接
*/
private String portrait;
/**
* 用户手机号码
*/
private String phone;
/**
* 邮箱
*/
private String email;
/**
* 备用邮箱
*/
private String backupEmail;
/**
* 用户密码(加密存储)
*/
private String password;
/**
* 用户状态(1.正常 2.禁止登录)
*/
private Integer status;
/**
* 用户备注信息
*/
private String remark;
@TableField(exist = false)
List<CustomerInfo> customerList;
@TableField(exist = false)
List<SysRole> roles;
@TableField(exist = false)
private Integer paymentStatus;
@TableField(exist = false)
private Integer huobanStatus;
@TableField(exist = false)
private Date bindTime;
}
@@ -0,0 +1,53 @@
package cn.apes.cloud.domain.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 lombok.Data;
import java.util.Date;
/**
* 用户个性化配置实体类
* <p>
* 注意:该表以 user_id 作为主键(无自增 id 列),因此不继承 DbEntity
* 避免父类 id 字段映射到不存在的列。
*
* @author OpenClaw
* @since 2026-08-06
*/
@Data
@TableName("user_preference")
public class UserPreference {
/**
* 用户ID(主键)
*/
@TableId(type = IdType.INPUT)
private Long userId;
/**
* UI尺寸 1-最大 2-中等 3-最小
*/
private Integer uiSize;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
/**
* 逻辑删除 0-正常 1-删除
*/
@TableLogic
private Integer isDel;
}
@@ -0,0 +1,12 @@
package cn.apes.cloud.domain.huoban;
import lombok.Data;
import java.util.List;
@Data
public class BulkDel {
private String tableId;
private List<Long> itemIds;
}
@@ -0,0 +1,18 @@
package cn.apes.cloud.domain.huoban;
import lombok.Data;
import java.util.List;
@Data
public class CategoryConfig {
private List<Option> options;
private int isMulti;
private int isTile;
@Data
public static class Option {
private String name;
private Integer id;
}
}
@@ -0,0 +1,28 @@
package cn.apes.cloud.domain.huoban;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import java.util.List;
@Data
public class DataCreate {
public DataCreate() {
}
public DataCreate(Long tableId, JSONObject fields) {
setFields(fields);
setTableId(tableId);
}
private Long tableId;
private String tableName;
private JSONObject fields;
}
@@ -0,0 +1,58 @@
package cn.apes.cloud.domain.huoban;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class DataFilter {
public DataFilter() {
}
public DataFilter(Long tableId) {
setTableId(tableId);
setRelations(new ArrayList<>());
}
private Long tableId;
private String tableName;
private String orderField;
private String orderType;
private Integer limit;
private Integer offset;
private Object filter;
private List<String> relations;
List<DataFilterCondition> conditions = new ArrayList<>();
List<TableSub> subTables;
public void addSubTable(String tableName,Long tableId,String tableField) {
if (subTables == null) {
subTables = new ArrayList<>();
}
TableSub tableSub = new TableSub();
tableSub.setTableId(tableId);
tableSub.setTableName(tableName);
tableSub.setTableField(tableField);
subTables.add(tableSub);
}
public void addCondition(String filedName, String oper, Object value) {
DataFilterCondition condition = new DataFilterCondition();
condition.setValue(value);
condition.setFieldName(filedName);
condition.setOperator(oper);
if (conditions == null) {
conditions = new ArrayList<DataFilterCondition>();
}
conditions.add(condition);
}
}
@@ -0,0 +1,24 @@
package cn.apes.cloud.domain.huoban;
import lombok.Data;
@Data
public class DataFilterCondition {
public DataFilterCondition() {
}
public DataFilterCondition(String fieldName, String operator, Object value) {
setFieldName(fieldName);
setOperator(operator);
setValue(value);
}
private String fieldName;
private Object value;
private String operator;
}
@@ -0,0 +1,23 @@
package cn.apes.cloud.domain.huoban;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
@Data
public class DataUpdate {
public DataUpdate(){
}
private Long tableId;
private String tableName;
private Long itemId;
public DataUpdate(Long tableId, Long itemId,JSONObject fields) {
setTableId(tableId);
setItemId(itemId);
setFields(fields);
}
private JSONObject fields;
}
@@ -0,0 +1,35 @@
package cn.apes.cloud.domain.huoban;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Data
public class DataUpsert {
public DataUpsert(){
}
public DataUpsert(Long tableId,String... _updateFields){
setTableId(tableId);
setUpdateFields(Arrays.asList(_updateFields));
items = new ArrayList<>();
}
public DataUpsert(Long tableId,List<JSONObject> dataItems,String... _updateFields){
setTableId(tableId);
setUpdateFields(Arrays.asList(_updateFields));
setItems(dataItems);
}
public void addItem(JSONObject data){
items.add(data);
}
private Long tableId;
private List<String> updateFields;
private List<JSONObject> items;
private String tableName;
}
@@ -0,0 +1,36 @@
package cn.apes.cloud.domain.huoban;
import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@TableName("huoban_table_column")
@Data
public class TableColumn {
@TableId
@JSONField(name = "field_id")
private Long fieldId;
private Long relationFieldId;
private Long tableId;
private Long spaceId;
private String name;
private String alias;
private String formName;
private String groupName;
@JSONField(name = "field_type")
private String fieldType;
@JSONField(name = "data_type")
private String dataType;
private Boolean required;
private String description;
private String columnConfig;
private Integer relationField;
private Integer canAdd;
private Integer canEdit;
private String defaultValue;
private Integer sortValue;
private String remark;
}
@@ -0,0 +1,27 @@
package cn.apes.cloud.domain.huoban;
import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
@TableName("huoban_table_info")
@Data
public class TableInfo {
@TableId
@JSONField(name = "table_id")
private Long tableId;
private String name;
private String formName;
private String showName;
private String groupConfig;
private String addButton;
private String editButton;
private String alias;
@JSONField(name = "space_id")
private Long spaceId;
@JSONField(name = "created_on")
private Date createdOn;
}
@@ -0,0 +1,10 @@
package cn.apes.cloud.domain.huoban;
import lombok.Data;
@Data
public class TableSub {
private String tableName;
private Long tableId;
private String tableField;
}
@@ -0,0 +1,46 @@
package cn.apes.cloud.domain.huoban;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import java.util.List;
@Data
public class UpsertOne {
public UpsertOne() {
}
public UpsertOne(Long tableId, JSONObject fields) {
setFields(fields);
setTableId(tableId);
}
private Long tableId;
private String tableName;
private JSONObject fields;
private List<String> updateFields;
/**
* 操作 one_update 发现一条数据更新,zero_create 未找到数据新增数据,one_none 有数据时不执行任何操作
*/
private String action;
public DataCreate getDataCreate() {
DataCreate dataCreate = new DataCreate();
dataCreate.setTableId(tableId);
dataCreate.setTableName(tableName);
dataCreate.setFields(fields);
return dataCreate;
}
public DataUpdate getDataUpdate(Long itemId) {
DataUpdate dataUpdate = new DataUpdate();
dataUpdate.setTableId(tableId);
dataUpdate.setTableName(tableName);
dataUpdate.setFields(fields);
dataUpdate.setItemId(itemId);
return dataUpdate;
}
}
@@ -0,0 +1,31 @@
package cn.apes.cloud.domain.vo;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class CustomerAppVO implements Serializable {
private String appKey;
private String appName;
private String appShortName;
private String appIcon;
/**
* 应用类型: basic=基础, internal=内部, paid=付费
*/
private String appType;
private String remark;
private Integer userVisible;
private Date createTime;
/**
* 应用来源: package=套餐包含, app=客户直接关联
*/
private String source;
/**
* 是否过期(仅 source=package 时有效)
*/
private Boolean expired;
}
@@ -0,0 +1,28 @@
package cn.apes.cloud.domain.vo;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class CustomerPackageExtendApplyVO implements Serializable {
private Long id;
private Long customerId;
private String customerName;
private String shortName;
private String adminPhone;
private Long planId;
private String planName;
private Long userId;
private String contactPhone;
private String reason;
private Integer applyDays;
private Date applyTime;
private Integer status;
private String statusText;
private Long operatorId;
private String operatorName;
private Date operateTime;
}
@@ -0,0 +1,21 @@
package cn.apes.cloud.domain.vo;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class CustomerPackageVO implements Serializable {
private Long customerId;
private String shortName;
private String customerName;
private String adminPhone;
private Long packageId;
private Long planId;
private String planName;
private Date expireTime;
/** 剩余天数(负数表示已过期) */
private Long remainingDays;
}
@@ -0,0 +1,201 @@
package cn.apes.cloud.filter;
import cn.apes.commons.auth.AuthContext;
import cn.apes.commons.auth.LoginCustomer;
import cn.apes.commons.auth.LoginUser;
import cn.apes.commons.auth.UserSession;
import cn.apes.cloud.domain.entity.CustomerInfo;
import cn.apes.cloud.domain.entity.SysAccessToken;
import cn.apes.cloud.service.impl.CustomerInfoServiceImpl;
import cn.apes.cloud.service.SysAccessTokenService;
import cn.apes.cloud.util.OperationLogUtil;
import cn.apes.commons.Res;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* AccessToken 校验过滤器
*
* 逻辑:
* 1. 检查请求头是否携带 X-Access-Token 或 Authorization: Bearer <token>
* 2. 如果有,走 AccessToken 校验逻辑
* 3. 校验通过则:
* - 将 Redis 数据写入 Redisuser + customer),使后续 @Login AOP 能正常读取
* - 包装 request,将 token 注入 "token" header,使 @Login AOP 能取到
* 4. 校验失败返回 401
* 5. 如果没有携带 Token,放行,交给后续的 @Login AOP 处理
*/
@Component
public class AccessTokenFilter implements Filter {
@Autowired
private SysAccessTokenService accessTokenService;
@Autowired
private CustomerInfoServiceImpl customerInfoService;
@Autowired
private RedissonClient redissonClient;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String token = extractToken(request);
if (token != null && !token.isEmpty()) {
// 有 Token → 校验
String clientIp = getClientIp(request);
SysAccessToken record = accessTokenService.validate(token, clientIp);
if (record == null) {
// 无效 Token → 返回 401
String method = request.getMethod();
String uri = request.getRequestURI();
OperationLogUtil.logDirect(0L, "外部系统", null, null, "AccessToken", "调用失败(无效Token)",
method + " " + uri, clientIp, request.getHeader("User-Agent"));
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSON.toJSONString(Res.fail("无效或已失效的 AccessToken")));
return;
}
// 校验通过 → 将数据写入 Redis,使 @Login AOP 能正常读取
String redisToken = record.getToken();
writeTokenToRedis(redisToken, record);
// 包装 request,注入 "token" header
final String finalToken = redisToken;
request = new HttpServletRequestWrapper(request) {
@Override
public String getHeader(String name) {
if ("token".equalsIgnoreCase(name)) {
return finalToken;
}
return super.getHeader(name);
}
@Override
public Enumeration<String> getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
if (!names.contains("token")) {
names.add("token");
}
return Collections.enumeration(names);
}
};
// 标记 AccessToken 已验证
request.setAttribute("accessTokenVerified", true);
request.setAttribute("accessTokenCustomerId", record.getCustomerId());
// 记录外部系统调用日志
String method = request.getMethod();
String uri = request.getRequestURI();
String userAgent = request.getHeader("User-Agent");
OperationLogUtil.logDirect(0L, "AccessToken:" + record.getName(), record.getCustomerId(), null,
"AccessToken", "调用", method + " " + uri, clientIp, userAgent);
}
chain.doFilter(request, response);
}
private void writeTokenToRedis(String redisToken, SysAccessToken record) {
try {
// 写入用户信息 (key = token)
// 必须使用 JSONObject 对象写入,使 JsonJacksonCodec 能正确序列化
// 这样 LoginAspect 反序列化时才能得到 JSONObject
JSONObject userObj = new JSONObject();
userObj.put("id", 0L);
userObj.put("nickName", "AccessToken:" + record.getName());
userObj.put("phone", "");
RBucket<JSONObject> userBucket = redissonClient.getBucket(redisToken);
userBucket.set(userObj, 1, TimeUnit.DAYS);
// 查询企业信息表,获取完整企业信息
CustomerInfo customerInfo = customerInfoService.getById(record.getCustomerId());
// 写入企业信息 (key = token:customer)
JSONObject customerObj = new JSONObject();
if (customerInfo != null) {
customerObj.put("id", customerInfo.getId());
customerObj.put("customerType", customerInfo.getCustomerType());
customerObj.put("customerName", customerInfo.getCustomerName());
customerObj.put("logo", customerInfo.getLogo());
customerObj.put("shortName", customerInfo.getShortName());
customerObj.put("customerIdNumber", customerInfo.getCustomerIdNumber());
customerObj.put("customerStatus", customerInfo.getCustomerStatus());
customerObj.put("auditRemark", customerInfo.getAuditRemark());
customerObj.put("auditTime", customerInfo.getAuditTime());
customerObj.put("adminName", customerInfo.getAdminName());
customerObj.put("adminPhone", customerInfo.getAdminPhone());
customerObj.put("adminIdNumber", customerInfo.getAdminIdNumber());
customerObj.put("businessLicense", customerInfo.getBusinessLicense());
customerObj.put("legalName", customerInfo.getLegalName());
customerObj.put("legalIdFront", customerInfo.getLegalIdFront());
customerObj.put("legalIdBack", customerInfo.getLegalIdBack());
customerObj.put("responsibleIdFront", customerInfo.getResponsibleIdFront());
customerObj.put("responsibleIdBack", customerInfo.getResponsibleIdBack());
customerObj.put("industry", customerInfo.getIndustry());
customerObj.put("industrySub", customerInfo.getIndustrySub());
customerObj.put("salesChannel", customerInfo.getSalesChannel());
customerObj.put("industryType", customerInfo.getIndustryType());
customerObj.put("province", customerInfo.getProvince());
customerObj.put("city", customerInfo.getCity());
} else {
customerObj.put("id", record.getCustomerId());
customerObj.put("customerName", "");
}
RBucket<JSONObject> customerBucket = redissonClient.getBucket(redisToken + ":customer");
customerBucket.set(customerObj, 1, TimeUnit.DAYS);
} catch (Exception e) {
// Redis 写入失败不影响主流程
}
}
private String extractToken(HttpServletRequest request) {
// 优先取 X-Access-Token
String token = request.getHeader("X-Access-Token");
if (token != null && !token.isEmpty()) {
return token;
}
// 其次取 Authorization: Bearer
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7).trim();
}
return null;
}
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if (ip != null && ip.contains(",")) {
ip = ip.split(",")[0].trim();
}
return ip;
}
}
@@ -0,0 +1,7 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.AppInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface AppInfoMapper extends BaseMapper<AppInfo> {
}
@@ -0,0 +1,7 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.CustomerApp;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface CustomerAppMapper extends BaseMapper<CustomerApp> {
}
@@ -0,0 +1,7 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.CustomerInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface CustomerInfoMapper extends BaseMapper<CustomerInfo> {
}
@@ -0,0 +1,9 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.CustomerPackageExtendApply;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CustomerPackageExtendApplyMapper extends BaseMapper<CustomerPackageExtendApply> {
}
@@ -0,0 +1,25 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.CustomerPackage;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
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.util.Date;
@Mapper
public interface CustomerPackageMapper extends BaseMapper<CustomerPackage> {
/**
* 绕过 @TableLogic 查询所有记录(含软删除)
*/
@Select("SELECT id, customer_id, plan_id, expire_time, is_del, create_time, update_time FROM customer_package WHERE customer_id = #{customerId} AND plan_id = #{planId} ORDER BY id DESC LIMIT 1")
CustomerPackage selectOneIgnoreLogic(@Param("customerId") Long customerId, @Param("planId") Long planId);
/**
* 绕过 @TableLogic 恢复软删除记录
*/
@Update("UPDATE customer_package SET is_del = 0, expire_time = #{expireTime}, update_time = NOW() WHERE id = #{id}")
int restoreRecord(@Param("id") Long id, @Param("expireTime") Date expireTime);
}
@@ -0,0 +1,16 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.CustomerPackageQuota;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CustomerPackageQuotaMapper extends BaseMapper<CustomerPackageQuota> {
/**
* 物理删除(不走逻辑删除)
*/
@Delete("DELETE FROM customer_package_quota WHERE customer_package_id = #{packageId}")
int physicalDeleteByPackageId(Long packageId);
}
@@ -0,0 +1,9 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.CustomerQuotaChangeLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CustomerQuotaChangeLogMapper extends BaseMapper<CustomerQuotaChangeLog> {
}
@@ -0,0 +1,7 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.CustomerUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface CustomerUserMapper extends BaseMapper<CustomerUser> {
}
@@ -0,0 +1,7 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.SsoConfig;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface SsoConfigMapper extends BaseMapper<SsoConfig> {
}
@@ -0,0 +1,10 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.SysAccessToken;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* AccessToken Mapper
*/
public interface SysAccessTokenMapper extends BaseMapper<SysAccessToken> {
}
@@ -0,0 +1,7 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.SysMemberRole;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface SysMemberRoleMapper extends BaseMapper<SysMemberRole> {
}
@@ -0,0 +1,7 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.SysMenu;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface SysMenuMapper extends BaseMapper<SysMenu> {
}
@@ -0,0 +1,9 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.SysOperationLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysOperationLogMapper extends BaseMapper<SysOperationLog> {
}
@@ -0,0 +1,9 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.SysOrganization;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysOrganizationMapper extends BaseMapper<SysOrganization> {
}
@@ -0,0 +1,9 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.SysPackagePlan;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysPackagePlanMapper extends BaseMapper<SysPackagePlan> {
}
@@ -0,0 +1,9 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.SysPackagePlanPermission;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysPackagePlanPermissionMapper extends BaseMapper<SysPackagePlanPermission> {
}
@@ -0,0 +1,11 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.SysPackagePlanQuota;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysPackagePlanQuotaMapper extends BaseMapper<SysPackagePlanQuota> {
@org.apache.ibatis.annotations.Delete("DELETE FROM sys_package_plan_quota WHERE plan_id = #{planId}")
int physicalDeleteByPlanId(Long planId);
}
@@ -0,0 +1,7 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.SysPermission;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface SysPermissionMapper extends BaseMapper<SysPermission> {
}
@@ -0,0 +1,9 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.SysQuota;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysQuotaMapper extends BaseMapper<SysQuota> {
}
@@ -0,0 +1,10 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.SysRole;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysRoleMapper extends BaseMapper<SysRole> {
}
@@ -0,0 +1,7 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.SysRolePermission;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface SysRolePermissionMapper extends BaseMapper<SysRolePermission> {
}
@@ -0,0 +1,9 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.UserInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}
@@ -0,0 +1,35 @@
package cn.apes.cloud.mapper;
import cn.apes.cloud.domain.entity.UserPreference;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
/**
* 用户个性化配置 Mapper接口
*
* @author OpenClaw
* @since 2026-08-06
*/
public interface UserPreferenceMapper extends BaseMapper<UserPreference> {
/**
* 更新指定用户的配置(包含已逻辑删除的行,同时恢复 is_del=0)
*
* @param userId 用户ID
* @param uiSize UI尺寸 1-最大 2-中等 3-最小
* @return int 影响行数,0表示该用户不存在任何记录(含已删除)
*/
@Update("update user_preference set ui_size = #{uiSize}, is_del = 0 where user_id = #{userId}")
int updateIncludeDeleted(@Param("userId") Long userId, @Param("uiSize") Integer uiSize);
/**
* 统计指定用户的记录数(包含已逻辑删除的行)
*
* @param userId 用户ID
* @return int 记录数(含已删除)
*/
@Select("select count(1) from user_preference where user_id = #{userId}")
int countIncludeDeleted(@Param("userId") Long userId);
}
@@ -0,0 +1,81 @@
package cn.apes.cloud.service;
import cn.apes.cloud.domain.entity.CustomerPackage;
import cn.apes.cloud.domain.vo.CustomerPackageVO;
import cn.apes.cloud.domain.vo.CustomerPackageExtendApplyVO;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
public interface CustomerPackageService extends IService<CustomerPackage> {
/**
* 获取客户的套餐列表
*/
cn.apes.commons.Res getCustomerPackages(Long customerId);
/**
* 为客户添加套餐
*/
cn.apes.commons.Res addCustomerPackage(CustomerPackage customerPackage);
/**
* 编辑客户套餐
*/
cn.apes.commons.Res editCustomerPackage(java.util.Map<String, Object> data);
/**
* 删除客户套餐
*/
cn.apes.commons.Res deleteCustomerPackage(Long id);
/**
* 保存客户套餐配额
*/
cn.apes.commons.Res saveCustomerPackageQuotas(Long packageId, java.util.Map<String, Object> quotaValues);
/**
* 获取客户套餐配额
*/
cn.apes.commons.Res getCustomerPackageQuotas(Long packageId);
/**
* 获取当前客户套餐详情(含配额,用于前端展示)
*/
cn.apes.commons.Res getMyPackages(Long customerId);
/**
* 查询所有客户的套餐概况(管理端)
*/
cn.apes.commons.Res getCustomerPackageOverview(java.util.Map<String, Object> params);
/**
* 提交套餐延期申请
*/
cn.apes.commons.Res submitExtendApply(java.util.Map<String, Object> params);
/**
* 查询套餐延期申请记录列表(管理端)
*/
cn.apes.commons.Res getExtendApplyList(java.util.Map<String, Object> params);
/**
* 审核延期申请(同意/拒绝)
*/
cn.apes.commons.Res reviewExtendApply(java.util.Map<String, Object> params);
/**
* 增加客户套餐额度
*/
cn.apes.commons.Res increaseQuota(java.util.Map<String, Object> params);
/**
* 减少客户套餐额度
*/
cn.apes.commons.Res decreaseQuota(java.util.Map<String, Object> params);
/**
* 获取配额变更日志列表
*/
cn.apes.commons.Res getQuotaChangeLogs(java.util.Map<String, Object> params);
}
@@ -0,0 +1,37 @@
package cn.apes.cloud.service;
import cn.apes.cloud.domain.entity.SysAccessToken;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* AccessToken Service
*/
public interface SysAccessTokenService extends IService<SysAccessToken> {
/**
* 校验 Token 是否有效
* @param token token 值
* @return 匹配的 SysAccessToken,无效则返回 null
*/
SysAccessToken validate(String token, String clientIp);
/**
* 生成新的 Token 值
*/
String generateToken();
/**
* 撤销 Token
*/
void revoke(Long id);
/**
* 启用 Token
*/
void enable(Long id);
/**
* 重新生成 Token(旧 Token 立即失效)
*/
String regenerate(Long id);
}
@@ -0,0 +1,37 @@
package cn.apes.cloud.service;
import cn.apes.cloud.domain.entity.SysOperationLog;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.Date;
import java.util.Map;
/**
* 操作日志服务接口
*/
public interface SysOperationLogService {
/**
* 记录操作日志(公共方法)
*/
void log(Long userId, String userName, Long customerId, String customerName,
String module, String action, String description);
/**
* 记录操作日志(带完整参数)
*/
void log(Long userId, String userName, Long customerId, String customerName,
String module, String action, String description, String ip, String userAgent);
/**
* 分页查询日志列表
*/
IPage<Map<String, Object>> pageLogs(int pageIndex, int pageSize,
Long customerId, Long userId,
String startDate, String endDate);
/**
* 获取企业列表(用于下拉筛选)
*/
java.util.List<Map<String, Object>> listCustomers();
}
@@ -0,0 +1,27 @@
package cn.apes.cloud.service;
import cn.apes.cloud.domain.entity.SysPackagePlanQuota;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
import java.util.Map;
public interface SysPackagePlanQuotaService extends IService<SysPackagePlanQuota> {
/**
* 获取套餐的配额配置列表
*/
cn.apes.commons.Res getPlanQuotas(Long planId);
/**
* 保存套餐的配额配置
* @param planId 套餐ID
* @param quotaValues Map<quotaId, quotaValue>
*/
cn.apes.commons.Res savePlanQuotas(Long planId, Map<Long, String> quotaValues);
/**
* 获取所有配额模板 + 标记是否已在当前套餐中配置
*/
cn.apes.commons.Res getAllQuotas(Long planId);
}
@@ -0,0 +1,25 @@
package cn.apes.cloud.service;
import cn.apes.cloud.domain.entity.SysPackagePlan;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
import java.util.Map;
public interface SysPackagePlanService extends IService<SysPackagePlan> {
/**
* 获取套餐已分配的权限
*/
Map<String, List<Long>> getPlanPermissions(Long planId);
/**
* 保存套餐权限关联
*/
cn.apes.commons.Res savePlanPermissions(Long planId, Map<String, List<Long>> appPermissions);
/**
* 获取套餐关联的应用列表
*/
cn.apes.commons.Res getPlanApps(Long planId);
}
@@ -0,0 +1,7 @@
package cn.apes.cloud.service;
import cn.apes.cloud.domain.entity.SysQuota;
import com.baomidou.mybatisplus.extension.service.IService;
public interface SysQuotaService extends IService<SysQuota> {
}

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