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
@@ -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> {
}
@@ -0,0 +1,79 @@
package cn.apes.cloud.service.impl;
import cn.apes.commons.Res;
import cn.apes.cloud.domain.dto.AppSearchDTO;
import cn.apes.cloud.domain.entity.AppInfo;
import cn.apes.cloud.mapper.AppInfoMapper;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class AppInfoServiceImpl extends ServiceImpl<AppInfoMapper, AppInfo> {
public Res pageApp(AppSearchDTO searchDTO) {
LambdaQueryWrapper<AppInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StrUtil.isNotEmpty(searchDTO.getAppName()),AppInfo::getAppName, searchDTO.getAppName());
queryWrapper.eq(StrUtil.isNotEmpty(searchDTO.getAppKey()),AppInfo::getAppKey, searchDTO.getAppKey());
queryWrapper.eq(searchDTO.getUserVisible() != null, AppInfo::getUserVisible, searchDTO.getUserVisible());
IPage<AppInfo> pageData=page(searchDTO.getPage(),queryWrapper);
return Res.success(pageData);
}
/**
* 新增/编辑应用
*/
public Res saveApp(AppInfo appInfo) {
if (StrUtil.isEmpty(appInfo.getAppName())) {
return Res.fail("应用名称不能为空");
}
if (StrUtil.isEmpty(appInfo.getAppKey())) {
return Res.fail("应用标识不能为空");
}
// 检查 appKey 是否重复(编辑时排除自身)
AppInfo existing = getOne(new LambdaQueryWrapper<AppInfo>().eq(AppInfo::getAppKey, appInfo.getAppKey()));
if (existing != null) {
// appKey 已存在,判断是否是编辑
// 如果传入的 appIcon/remark 等不同,视为编辑
existing.setAppName(appInfo.getAppName());
existing.setAppShortName(appInfo.getAppShortName());
existing.setAppType(appInfo.getAppType());
existing.setAppIcon(appInfo.getAppIcon());
existing.setRemark(appInfo.getRemark());
existing.setUserVisible(appInfo.getUserVisible());
existing.setHuobanSpaceId(appInfo.getHuobanSpaceId());
existing.setHuobanSpaceGroupId(appInfo.getHuobanSpaceGroupId());
existing.setUpdateTime(new Date());
updateById(existing);
return Res.success();
}
// 新增
appInfo.setCreateTime(new Date());
appInfo.setUpdateTime(new Date());
if (appInfo.getUserVisible() == null) {
appInfo.setUserVisible(true);
}
save(appInfo);
return Res.success();
}
/**
* 删除应用
*/
public Res deleteApp(AppInfo appInfo) {
if (StrUtil.isEmpty(appInfo.getAppKey())) {
return Res.fail("应用标识不能为空");
}
LambdaQueryWrapper<AppInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AppInfo::getAppKey, appInfo.getAppKey());
remove(queryWrapper);
return Res.success();
}
}
@@ -0,0 +1,220 @@
package cn.apes.cloud.service.impl;
import cn.apes.commons.Res;
import cn.apes.cloud.domain.entity.CustomerInfo;
import cn.apes.cloud.domain.entity.AppInfo;
import cn.apes.cloud.domain.entity.CustomerApp;
import cn.apes.cloud.domain.entity.CustomerPackage;
import cn.apes.cloud.domain.entity.CustomerPackageQuota;
import cn.apes.cloud.domain.entity.SysPackagePlan;
import cn.apes.cloud.domain.vo.CustomerAppVO;
import cn.apes.cloud.mapper.AppInfoMapper;
import cn.apes.cloud.mapper.CustomerAppMapper;
import cn.apes.cloud.mapper.CustomerPackageMapper;
import cn.apes.cloud.mapper.CustomerPackageQuotaMapper;
import cn.apes.cloud.mapper.SysPackagePlanMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import cn.apes.cloud.service.impl.CustomerInfoServiceImpl;
import cn.apes.cloud.util.OperationLogUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@Service
public class CustomerAppServiceImpl extends ServiceImpl<CustomerAppMapper, CustomerApp> {
@Autowired
private AppInfoMapper appInfoMapper;
@Autowired
private CustomerPackageMapper customerPackageMapper;
@Autowired
private CustomerPackageQuotaMapper customerPackageQuotaMapper;
@Autowired
private SysPackagePlanMapper sysPackagePlanMapper;
@Autowired
private CustomerInfoServiceImpl customerInfoService;
/**
* 查询客户可见的应用列表(含关联时间)
* 条件(OR):1. 已关联的应用 2. appType 为 basic(基础应用对所有用户可见)
*/
public List<CustomerAppVO> getApps(Long customerId) {
return buildAppList(customerId, false);
}
/**
* 查询当前登录客户的应用列表(含套餐来源标识)
* 合并客户关联的应用 + 套餐包含的应用,去重后返回。
* source 字段: "package" = 应用在套餐中存在, "app" = 仅客户关联表中存在
*/
public List<CustomerAppVO> getMyAppsWithSource(Long customerId) {
return buildAppList(customerId, true);
}
/**
* 核心构建方法
* @param customerId 客户ID
* @param includeSource 是否填充 source 字段
*/
private List<CustomerAppVO> buildAppList(Long customerId, boolean includeSource) {
// 1. 查出客户已关联的应用 appKeys
LambdaQueryWrapper<CustomerApp> appRelWrapper = new LambdaQueryWrapper<>();
appRelWrapper.eq(CustomerApp::getCustomerId, customerId);
List<CustomerApp> appRelList = list(appRelWrapper);
Set<String> associatedKeys = appRelList.stream()
.map(CustomerApp::getAppKey)
.collect(Collectors.toSet());
Map<String, CustomerApp> appRelMap = appRelList.stream()
.collect(Collectors.toMap(CustomerApp::getAppKey, c -> c));
// 2. 查出客户所有生效的套餐(未删除)
LambdaQueryWrapper<CustomerPackage> pkgWrapper = new LambdaQueryWrapper<>();
pkgWrapper.eq(CustomerPackage::getCustomerId, customerId);
pkgWrapper.eq(CustomerPackage::getIsDel, 0);
List<CustomerPackage> packages = customerPackageMapper.selectList(pkgWrapper);
// 2.1 从套餐对应的 sys_package_plan.appIds 获取应用列表
Map<String, Boolean> appExpiredMap = new java.util.HashMap<>();
LocalDateTime now = LocalDateTime.now();
Set<String> packageKeys = new HashSet<>();
if (!packages.isEmpty()) {
for (CustomerPackage pkg : packages) {
SysPackagePlan plan = sysPackagePlanMapper.selectById(pkg.getPlanId());
if (plan != null && plan.getAppIds() != null && !plan.getAppIds().isEmpty()) {
boolean expired = pkg.getExpireTime() != null
&& now.isAfter(pkg.getExpireTime().toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime());
String[] keys = plan.getAppIds().split(",");
for (String k : keys) {
String trimmed = k.trim();
if (!trimmed.isEmpty()) {
packageKeys.add(trimmed);
// 只要有一个未过期的套餐包含该app,就未过期
if (!appExpiredMap.containsKey(trimmed) || !appExpiredMap.get(trimmed)) {
appExpiredMap.put(trimmed, expired);
}
}
}
}
}
}
// 4. 合并所有 appKey
Set<String> allKeys = new HashSet<>();
allKeys.addAll(associatedKeys);
allKeys.addAll(packageKeys);
// 5. 查询 basic 类型应用(对所有用户可见)
LambdaQueryWrapper<AppInfo> basicWrapper = new LambdaQueryWrapper<>();
basicWrapper.eq(AppInfo::getAppType, "basic");
List<AppInfo> basicApps = appInfoMapper.selectList(basicWrapper);
for (AppInfo app : basicApps) {
allKeys.add(app.getAppKey());
// basic 应用如果没有被套餐包含且没有被关联,source = "app"
}
if (allKeys.isEmpty()) {
return new ArrayList<>();
}
// 6. 查出完整应用信息
LambdaQueryWrapper<AppInfo> appWrapper = new LambdaQueryWrapper<>();
appWrapper.in(AppInfo::getAppKey, allKeys);
List<AppInfo> appInfoList = appInfoMapper.selectList(appWrapper);
// 7. 组装 VO
List<CustomerAppVO> result = new ArrayList<>();
for (AppInfo appInfo : appInfoList) {
CustomerAppVO vo = new CustomerAppVO();
BeanUtils.copyProperties(appInfo, vo);
vo.setUserVisible(Boolean.TRUE.equals(appInfo.getUserVisible()) ? 1 : 0);
CustomerApp rel = appRelMap.get(appInfo.getAppKey());
if (rel != null) {
vo.setCreateTime(rel.getCreateTime());
}
// source 标识:优先 package
if (includeSource) {
if (packageKeys.contains(appInfo.getAppKey())) {
vo.setSource("package");
vo.setExpired(appExpiredMap.getOrDefault(appInfo.getAppKey(), true));
} else {
vo.setSource("app");
vo.setExpired(false);
}
}
result.add(vo);
}
return result;
}
/**
* 关联应用给客户
*/
public Res addApp(Long customerId, String appKey) {
LambdaQueryWrapper<CustomerApp> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CustomerApp::getCustomerId, customerId);
wrapper.eq(CustomerApp::getAppKey, appKey);
if (count(wrapper) > 0) {
return Res.fail("该应用已关联");
}
CustomerApp customerApp = new CustomerApp();
customerApp.setCustomerId(customerId);
customerApp.setAppKey(appKey);
save(customerApp);
// 记录操作日志
String customerName = "";
CustomerInfo ci = customerInfoService.getById(customerId);
if (ci != null) customerName = ci.getCustomerName();
OperationLogUtil.log("企业管理", "关联应用", "关联应用给企业: " + customerName + " (" + appKey + ")");
return Res.success();
}
/**
* 解绑客户的应用
*/
public Res removeApp(Long customerId, String appKey) {
LambdaQueryWrapper<CustomerApp> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CustomerApp::getCustomerId, customerId);
wrapper.eq(CustomerApp::getAppKey, appKey);
remove(wrapper);
// 记录操作日志
String customerName = "";
CustomerInfo ci = customerInfoService.getById(customerId);
if (ci != null) customerName = ci.getCustomerName();
OperationLogUtil.log("企业管理", "解绑应用", "解绑企业应用: " + customerName + " (" + appKey + ")");
return Res.success();
}
/**
* 查询所有应用(用于选择器)
*/
public List<AppInfo> getAllApps() {
return appInfoMapper.selectList(null);
}
/**
* 根据应用标识查询关联的客户ID列表
*/
public List<Long> getCustomerIdsByAppKey(String appKey) {
LambdaQueryWrapper<CustomerApp> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CustomerApp::getAppKey, appKey);
List<CustomerApp> list = list(wrapper);
return list.stream().map(CustomerApp::getCustomerId).collect(Collectors.toList());
}
}
@@ -0,0 +1,306 @@
package cn.apes.cloud.service.impl;
import cn.apes.commons.Res;
import cn.apes.cloud.service.impl.huoban.HuobanUtil;
import com.alibaba.fastjson.JSONObject;
import cn.apes.cloud.domain.huoban.UpsertOne;
import cn.apes.cloud.service.SysOperationLogService;
import cn.apes.cloud.util.OperationLogUtil;
import cn.apes.cloud.domain.dto.CustomerSearchDTO;
import cn.apes.cloud.domain.entity.CustomerInfo;
import cn.apes.cloud.domain.entity.CustomerPackage;
import cn.apes.cloud.domain.entity.SsoConfig;
import cn.apes.cloud.domain.entity.SysPackagePlan;
import cn.apes.cloud.domain.entity.UserInfo;
import cn.apes.cloud.mapper.CustomerInfoMapper;
import cn.apes.cloud.mapper.CustomerPackageMapper;
import cn.apes.cloud.mapper.SsoConfigMapper;
import cn.apes.cloud.service.SysPackagePlanService;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, CustomerInfo> {
@Autowired
SsoConfigMapper ssoConfigMapper;
@Autowired
CustomerPackageMapper customerPackageMapper;
@Autowired
SysPackagePlanService sysPackagePlanService;
@Autowired
SysOperationLogService sysOperationLogService;
@Autowired
cn.apes.cloud.service.impl.huoban.HuobanData2ServiceImpl huobanData2Service;
public Res pageCustomer(CustomerSearchDTO searchDTO) {
LambdaQueryWrapper<CustomerInfo> lambdaQueryWrapper = new LambdaQueryWrapper<>();
IPage<CustomerInfo> pageData = page(searchDTO.getPage(), lambdaQueryWrapper);
// 关联填充每个客户的套餐信息
for (CustomerInfo customer : pageData.getRecords()) {
LambdaQueryWrapper<CustomerPackage> pkgWrapper = new LambdaQueryWrapper<>();
pkgWrapper.eq(CustomerPackage::getCustomerId, customer.getId());
List<CustomerPackage> packages = customerPackageMapper.selectList(pkgWrapper);
if (!packages.isEmpty()) {
List<SysPackagePlan> plans = packages.stream()
.map(p -> sysPackagePlanService.getById(p.getPlanId()))
.filter(p -> p != null)
.collect(Collectors.toList());
customer.setPlans(plans);
}
}
return Res.success(pageData);
}
public Res getCustomerDetail(Long id) {
if (id == null) {
return Res.fail("客户ID不能为空");
}
CustomerInfo customerInfo = getById(id);
return Res.success(customerInfo);
}
/**
* 新增/编辑客户
*/
public Res saveCustomer(CustomerInfo customerInfo) {
if (StrUtil.isEmpty(customerInfo.getCustomerName())) {
return Res.fail("客户名称不能为空");
}
if (StrUtil.isEmpty(customerInfo.getAdminName())) {
return Res.fail("负责人姓名不能为空");
}
if (StrUtil.isEmpty(customerInfo.getAdminPhone())) {
return Res.fail("负责人手机号不能为空");
}
if (customerInfo.getCustomerStatus() == null) {
customerInfo.setCustomerStatus(1);
}
boolean isNew = customerInfo.getId() == null;
saveOrUpdate(customerInfo);
String action = isNew ? "新增企业" : "编辑企业";
String desc = (isNew ? "新增企业: " : "编辑企业: ") + customerInfo.getCustomerName();
OperationLogUtil.log("企业管理", action, desc);
return Res.success();
}
/**
* 删除客户
*/
public Res deleteCustomer(CustomerInfo customerInfo) {
if (customerInfo.getId() == null) {
return Res.fail("客户ID不能为空");
}
String customerName = "";
CustomerInfo existing = getById(customerInfo.getId());
if (existing != null) customerName = existing.getCustomerName();
removeById(customerInfo.getId());
OperationLogUtil.log("企业管理", "删除企业", "删除企业: " + customerName);
return Res.success();
}
/**
* 获取客户SSO配置(公司级别 user_id=0
*/
public Res getSSOConfig(Long customerId) {
LambdaQueryWrapper<SsoConfig> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SsoConfig::getUserId, 0L);
wrapper.eq(SsoConfig::getCustomerId, customerId);
SsoConfig config = ssoConfigMapper.selectOne(wrapper);
if (config == null) {
config = new SsoConfig();
config.setCustomerId(customerId);
config.setUserId(0L);
config.setHuobanCompanyId(5100000024031260L);
config.setHuobanStatus(1);
}
return Res.success(config);
}
/**
* 保存客户SSO配置(公司级别 user_id=0
*/
public Res saveSSOConfig(SsoConfig ssoConfig) {
if (ssoConfig.getCustomerId() == null) {
return Res.fail("客户ID不能为空");
}
// 查询是否已存在
LambdaQueryWrapper<SsoConfig> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SsoConfig::getUserId, 0L);
wrapper.eq(SsoConfig::getCustomerId, ssoConfig.getCustomerId());
SsoConfig existing = ssoConfigMapper.selectOne(wrapper);
if (existing != null) {
ssoConfig.setId(existing.getId());
}
// 设置公司级别
ssoConfig.setUserId(0L);
// 默认值
if (ssoConfig.getHuobanStatus() == null) {
ssoConfig.setHuobanStatus(1);
}
if (ssoConfig.getHuobanCompanyId() == null) {
ssoConfig.setHuobanCompanyId(5100000024031260L);
}
ssoConfigMapper.insertOrUpdate(ssoConfig);
return Res.success();
}
/**
* 更新客户状态
*/
public Res updateCustomerStatus(Long customerId, Integer customerStatus) {
if (customerId == null) {
return Res.fail("客户ID不能为空");
}
if (customerStatus == null || (customerStatus != 1 && customerStatus != 2 && customerStatus != 3 && customerStatus != 4)) {
return Res.fail("无效的客户状态");
}
CustomerInfo customerInfo = getById(customerId);
if (customerInfo == null) {
return Res.fail("客户不存在");
}
customerInfo.setCustomerStatus(customerStatus);
updateById(customerInfo);
String statusName = "";
switch (customerStatus) {
case 1: statusName = "使用中"; break;
case 2: statusName = "待审核"; break;
case 3: statusName = "已注销"; break;
case 4: statusName = "暂停服务"; break;
default: statusName = String.valueOf(customerStatus);
}
String action = customerStatus == 3 ? "注销企业" : (customerStatus == 4 ? "暂停服务" : (customerStatus == 1 ? "恢复服务" : "更新状态"));
OperationLogUtil.log("企业管理", action, statusName + " - " + customerInfo.getCustomerName());
return Res.success();
}
/**
* 审核企业认证
* @param customerId 企业ID
* @param auditStatus 审核结果:1=通过,5=拒绝
* @param auditRemark 拒绝原因(拒绝时必填)
*/
public Res auditCertification(Long customerId, Integer auditStatus, String auditRemark) {
if (customerId == null) {
return Res.fail("企业ID不能为空");
}
if (auditStatus == null || (auditStatus != 1 && auditStatus != 5)) {
return Res.fail("无效的审核状态,1=通过,5=拒绝");
}
if (auditStatus == 5 && cn.hutool.core.util.StrUtil.isBlank(auditRemark)) {
return Res.fail("审核拒绝时必须填写原因");
}
CustomerInfo customerInfo = getById(customerId);
if (customerInfo == null) {
return Res.fail("企业不存在");
}
customerInfo.setCustomerStatus(auditStatus);
customerInfo.setAuditRemark(auditRemark);
customerInfo.setAuditTime(new java.util.Date());
updateById(customerInfo);
log.info("企业认证审核完成: customerId=" + customerId + ", customerStatus=" + auditStatus + ", remark=" + auditRemark);
String auditAction = auditStatus == 1 ? "审核通过" : "审核拒绝";
String auditDesc = auditAction + " - " + customerInfo.getCustomerName() + (auditStatus == 5 ? " (" + auditRemark + ")" : "");
OperationLogUtil.log("企业认证", auditAction, auditDesc);
return Res.success();
}
/**
* 同步全部租户到伙伴云
* 遍历所有非删除状态的租户,通过 upsetOne 接口同步数据
*/
public Res syncAllTenantsToHuoban() {
try {
// 查询全部租户
LambdaQueryWrapper<CustomerInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CustomerInfo::getIsDel, 0);
List<CustomerInfo> allCustomers = list(wrapper);
int successCount = 0;
int failCount = 0;
StringBuilder failMsg = new StringBuilder();
for (CustomerInfo customer : allCustomers) {
try {
// 构建 upsetOne 请求参数
JSONObject fields = new JSONObject();
fields.put("customerId", customer.getId());
fields.put("customerName", customer.getCustomerName());
fields.put("shortName", customer.getShortName());
fields.put("customerType", customer.getCustomerType());
fields.put("adminName", customer.getAdminName());
fields.put("adminPhone", customer.getAdminPhone());
fields.put("customerStatus", customer.getCustomerStatus());
fields.put("industry", customer.getIndustry());
fields.put("industrySub", customer.getIndustrySub());
fields.put("salesChannel", customer.getSalesChannel());
fields.put("industryType", customer.getIndustryType());
fields.put("province", customer.getProvince());
fields.put("city", customer.getCity());
if (customer.getLogo() != null) fields.put("logo", customer.getLogo());
if (customer.getCustomerIdNumber() != null) fields.put("customerIdNumber", customer.getCustomerIdNumber());
// 通过 HuobanData2ServiceImpl.upsetOne 调用
UpsertOne upsertOne = new UpsertOne(2100000000008750L, fields);
upsertOne.setUpdateFields(java.util.Collections.singletonList("customerId"));
upsertOne.setAction("one_update");
Res res = huobanData2Service.upsetOne(upsertOne);
if (res != null && res.getCode() == 1) {
successCount++;
} else {
failCount++;
failMsg.append(customer.getCustomerName()).append("(").append(res != null ? res.getMsg() : "null").append("); ");
}
} catch (Exception e) {
failCount++;
failMsg.append(customer.getCustomerName()).append("(").append(e.getMessage()).append("); ");
}
}
String result = "同步完成:成功 " + successCount + "";
if (failCount > 0) {
result += ",失败 " + failCount + "";
if (failMsg.length() > 200) {
result += ",详情:" + failMsg.substring(0, 200) + "...";
} else {
result += ",详情:" + failMsg.toString();
}
}
if (failCount == 0) {
return Res.success(result);
} else {
return Res.fail(result);
}
} catch (Exception e) {
return Res.fail("同步异常:" + e.getMessage());
}
}
}
@@ -0,0 +1,887 @@
package cn.apes.cloud.service.impl;
import cn.apes.cloud.domain.entity.AppInfo;
import cn.apes.cloud.domain.entity.CustomerInfo;
import cn.apes.commons.auth.AuthContext;
import cn.apes.commons.auth.LoginUser;
import cn.apes.cloud.domain.entity.CustomerPackageExtendApply;
import cn.apes.cloud.mapper.CustomerPackageExtendApplyMapper;
import cn.apes.cloud.mapper.CustomerQuotaChangeLogMapper;
import cn.apes.cloud.domain.entity.CustomerQuotaChangeLog;
import cn.apes.cloud.domain.entity.CustomerPackage;
import cn.apes.cloud.domain.entity.CustomerInfo;
import cn.apes.commons.auth.AuthContext;
import cn.apes.commons.auth.LoginUser;
import cn.apes.cloud.domain.entity.CustomerPackageExtendApply;
import cn.apes.cloud.mapper.CustomerPackageExtendApplyMapper;
import cn.apes.cloud.domain.entity.SysPackagePlan;
import cn.apes.cloud.domain.vo.CustomerPackageVO;
import cn.apes.cloud.domain.vo.CustomerPackageExtendApplyVO;
import cn.apes.cloud.mapper.CustomerInfoMapper;
import cn.apes.cloud.domain.entity.CustomerPackageQuota;
import cn.apes.cloud.domain.entity.SysPackagePlan;
import cn.apes.cloud.domain.entity.SysPackagePlanQuota;
import cn.apes.cloud.domain.entity.SysQuota;
import cn.apes.cloud.mapper.AppInfoMapper;
import cn.apes.cloud.mapper.CustomerPackageMapper;
import cn.apes.cloud.mapper.CustomerPackageQuotaMapper;
import cn.apes.cloud.mapper.SysPackagePlanQuotaMapper;
import cn.apes.cloud.mapper.SysQuotaMapper;
import cn.apes.cloud.service.CustomerPackageService;
import cn.apes.cloud.service.impl.CustomerInfoServiceImpl;
import cn.apes.cloud.service.SysPackagePlanService;
import cn.apes.cloud.util.OperationLogUtil;
import cn.apes.cloud.service.SysQuotaService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class CustomerPackageServiceImpl extends ServiceImpl<CustomerPackageMapper, CustomerPackage> implements CustomerPackageService {
@Autowired
private SysPackagePlanService sysPackagePlanService;
@Autowired
private CustomerPackageQuotaMapper customerPackageQuotaMapper;
@Autowired
private SysQuotaService sysQuotaService;
@Autowired
private SysQuotaMapper sysQuotaMapper;
@Autowired
private AppInfoMapper appInfoMapper;
@Autowired
private CustomerInfoServiceImpl customerInfoService;
@Autowired
private SysPackagePlanQuotaMapper sysPackagePlanQuotaMapper;
@Autowired
private CustomerInfoMapper customerInfoMapper;
@Autowired
private CustomerPackageExtendApplyMapper customerPackageExtendApplyMapper;
@Autowired
private CustomerQuotaChangeLogMapper customerQuotaChangeLogMapper;
@Override
public cn.apes.commons.Res getCustomerPackages(Long customerId) {
if (customerId == null) {
return cn.apes.commons.Res.fail("客户ID不能为空");
}
LambdaQueryWrapper<CustomerPackage> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CustomerPackage::getCustomerId, customerId);
wrapper.orderByDesc(CustomerPackage::getCreateTime);
List<CustomerPackage> list = list(wrapper);
// 填充套餐信息
for (CustomerPackage cp : list) {
SysPackagePlan plan = sysPackagePlanService.getById(cp.getPlanId());
if (plan != null) {
cp.setPlanName(plan.getPlanName());
cp.setPlanLevel(plan.getPlanLevel());
cp.setChargeMode(plan.getChargeMode());
cp.setAmount(plan.getAmount());
}
}
return cn.apes.commons.Res.success(list);
}
@Override
@org.springframework.transaction.annotation.Transactional(rollbackFor = Exception.class)
public cn.apes.commons.Res addCustomerPackage(CustomerPackage customerPackage) {
if (customerPackage.getCustomerId() == null) {
return cn.apes.commons.Res.fail("客户ID不能为空");
}
if (customerPackage.getPlanId() == null) {
return cn.apes.commons.Res.fail("套餐ID不能为空");
}
if (customerPackage.getExpireTime() == null) {
return cn.apes.commons.Res.fail("到期时间不能为空");
}
// 检查是否已存在(包括软删除的记录,避免唯一索引冲突)
CustomerPackage existing = baseMapper.selectOneIgnoreLogic(customerPackage.getCustomerId(), customerPackage.getPlanId());
if (existing != null) {
if (existing.getIsDel() == 1) {
// 存在已删除记录,恢复并更新
baseMapper.restoreRecord(existing.getId(), customerPackage.getExpireTime());
// 删除旧的配额记录后重新初始化
customerPackageQuotaMapper.physicalDeleteByPackageId(existing.getId());
// 重新初始化配额
initCustomerPackageQuotas(existing.getId(), existing.getPlanId());
return cn.apes.commons.Res.success(existing);
}
return cn.apes.commons.Res.fail("该客户已拥有此套餐");
}
save(customerPackage);
// 获取套餐名称和企业名称用于日志
String customerName = "";
CustomerInfo ci = customerInfoService.getById(customerPackage.getCustomerId());
if (ci != null) customerName = ci.getCustomerName();
SysPackagePlan plan = sysPackagePlanService.getById(customerPackage.getPlanId());
String planName = plan != null ? plan.getPlanName() : "套餐ID:" + customerPackage.getPlanId();
OperationLogUtil.log("企业管理", "分配套餐", "为企业 " + customerName + " 分配套餐: " + planName);
// MyBatis-Plus save() may not populate the auto-generated ID back into the entity
// Query it back to ensure we have the correct packageId for quota init
Long packageId = customerPackage.getId();
if (packageId == null) {
LambdaQueryWrapper<CustomerPackage> fetchWrapper = new LambdaQueryWrapper<>();
fetchWrapper.eq(CustomerPackage::getCustomerId, customerPackage.getCustomerId());
fetchWrapper.eq(CustomerPackage::getPlanId, customerPackage.getPlanId());
fetchWrapper.orderByDesc(CustomerPackage::getId);
fetchWrapper.last("LIMIT 1");
CustomerPackage saved = getOne(fetchWrapper);
if (saved != null) {
packageId = saved.getId();
customerPackage.setId(packageId);
}
}
if (packageId != null) {
// 自动从套餐计划的配额定义初始化 customer_package_quota
initCustomerPackageQuotas(packageId, customerPackage.getPlanId());
}
return cn.apes.commons.Res.success(customerPackage);
}
@Override
@org.springframework.transaction.annotation.Transactional(rollbackFor = Exception.class)
public cn.apes.commons.Res editCustomerPackage(java.util.Map<String, Object> data) {
Long id = data.get("id") != null ? Long.parseLong(data.get("id").toString()) : null;
if (id == null) {
return cn.apes.commons.Res.fail("ID不能为空");
}
CustomerPackage existing = getById(id);
if (existing == null) {
return cn.apes.commons.Res.fail("记录不存在");
}
// 获取企业名称用于日志
String customerName = "";
CustomerInfo ci = customerInfoService.getById(existing.getCustomerId());
if (ci != null) customerName = ci.getCustomerName();
SysPackagePlan plan = sysPackagePlanService.getById(existing.getPlanId());
String planName = plan != null ? plan.getPlanName() : "";
CustomerPackage update = new CustomerPackage();
update.setId(id);
boolean hasChange = false;
if (data.get("planId") != null) {
update.setPlanId(Long.parseLong(data.get("planId").toString()));
hasChange = true;
}
if (data.get("expireTime") != null) {
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
update.setExpireTime(sdf.parse(data.get("expireTime").toString()));
} catch (Exception e) {
return cn.apes.commons.Res.fail("日期格式错误");
}
hasChange = true;
}
if (!hasChange) {
return cn.apes.commons.Res.fail("没有需要更新的字段");
}
// 如果更换了套餐计划,重新初始化配额
Long newPlanId = data.get("planId") != null ? Long.parseLong(data.get("planId").toString()) : null;
updateById(update);
// 获取新套餐名称
String newPlanName = planName;
if (newPlanId != null) {
SysPackagePlan newPlan = sysPackagePlanService.getById(newPlanId);
newPlanName = newPlan != null ? newPlan.getPlanName() : "套餐ID:" + newPlanId;
customerPackageQuotaMapper.physicalDeleteByPackageId(id);
initCustomerPackageQuotas(id, newPlanId);
}
OperationLogUtil.log("企业管理", "编辑套餐", "编辑企业 " + customerName + " 套餐: " + newPlanName);
return cn.apes.commons.Res.success();
}
@Override
@org.springframework.transaction.annotation.Transactional(rollbackFor = Exception.class)
public cn.apes.commons.Res deleteCustomerPackage(Long id) {
if (id == null) {
return cn.apes.commons.Res.fail("ID不能为空");
}
CustomerPackage existing = getById(id);
String customerName = "";
if (existing != null) {
CustomerInfo ci = customerInfoService.getById(existing.getCustomerId());
if (ci != null) customerName = ci.getCustomerName();
SysPackagePlan plan = sysPackagePlanService.getById(existing.getPlanId());
String planName = plan != null ? plan.getPlanName() : "";
OperationLogUtil.log("企业管理", "删除套餐", "删除企业 " + customerName + " 套餐: " + planName);
}
removeById(id);
return cn.apes.commons.Res.success();
}
/**
* 从套餐计划的配额定义初始化 customer_package_quota 记录
*/
private void initCustomerPackageQuotas(Long packageId, Long planId) {
// 查询该套餐计划的所有配额定义
LambdaQueryWrapper<SysPackagePlanQuota> planQuotaWrapper = new LambdaQueryWrapper<>();
planQuotaWrapper.eq(SysPackagePlanQuota::getPlanId, planId);
List<SysPackagePlanQuota> planQuotas = sysPackagePlanQuotaMapper.selectList(planQuotaWrapper);
if (planQuotas.isEmpty()) {
return;
}
// 预加载 quota_id -> SysQuota
List<Long> quotaIds = planQuotas.stream()
.map(SysPackagePlanQuota::getQuotaId)
.filter(id -> id != null)
.collect(Collectors.toList());
if (quotaIds.isEmpty()) {
return;
}
java.util.Map<Long, SysQuota> quotaMap = sysQuotaService.listByIds(quotaIds).stream()
.collect(java.util.stream.Collectors.toMap(SysQuota::getId, q -> q));
// 插入 customer_package_quota 记录
for (SysPackagePlanQuota planQuota : planQuotas) {
SysQuota sq = quotaMap.get(planQuota.getQuotaId());
if (sq == null) continue;
CustomerPackageQuota entity = new CustomerPackageQuota();
entity.setCustomerPackageId(packageId);
entity.setQuotaId(sq.getId());
entity.setQuotaCode(sq.getQuotaCode());
entity.setQuotaName(sq.getQuotaName());
entity.setDataType(sq.getDataType());
entity.setAppKey(sq.getAppKey());
entity.setQuotaValue(planQuota.getQuotaValue());
customerPackageQuotaMapper.insert(entity);
}
}
@Override
@org.springframework.transaction.annotation.Transactional(rollbackFor = Exception.class)
public cn.apes.commons.Res saveCustomerPackageQuotas(Long packageId, java.util.Map<String, Object> quotaValues) {
if (packageId == null) {
return cn.apes.commons.Res.fail("套餐关联ID不能为空");
}
if (quotaValues == null) {
quotaValues = new java.util.HashMap<>();
}
// 物理删除旧的(避免逻辑删除后 uk_pkg_quota 冲突)
customerPackageQuotaMapper.physicalDeleteByPackageId(packageId);
// 预加载 quota_code -> SysQuota 映射
List<SysQuota> allQuotas = sysQuotaService.list();
java.util.Map<String, SysQuota> quotaMap = new java.util.HashMap<>();
for (SysQuota q : allQuotas) {
quotaMap.put(q.getQuotaCode(), q);
}
// 插入新的
for (java.util.Map.Entry<String, Object> entry : quotaValues.entrySet()) {
String quotaCode = entry.getKey();
Object value = entry.getValue();
if (value == null || value.toString().isEmpty()) continue;
SysQuota sysQuota = quotaMap.get(quotaCode);
if (sysQuota == null) continue;
CustomerPackageQuota entity = new CustomerPackageQuota();
entity.setCustomerPackageId(packageId);
entity.setQuotaId(sysQuota.getId());
entity.setQuotaCode(quotaCode);
entity.setQuotaName(sysQuota.getQuotaName());
entity.setDataType(sysQuota.getDataType());
entity.setAppKey(sysQuota.getAppKey());
entity.setQuotaValue(value.toString());
customerPackageQuotaMapper.insert(entity);
}
return cn.apes.commons.Res.success();
}
@Override
public cn.apes.commons.Res getCustomerPackageQuotas(Long packageId) {
if (packageId == null) {
return cn.apes.commons.Res.fail("套餐关联ID不能为空");
}
LambdaQueryWrapper<CustomerPackageQuota> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CustomerPackageQuota::getCustomerPackageId, packageId);
java.util.List<CustomerPackageQuota> list = customerPackageQuotaMapper.selectList(wrapper);
if (!list.isEmpty()) {
java.util.Set<Long> quotaIds = list.stream()
.map(CustomerPackageQuota::getQuotaId)
.filter(id -> id != null)
.collect(java.util.stream.Collectors.toSet());
if (!quotaIds.isEmpty()) {
java.util.List<SysQuota> templates = sysQuotaMapper.selectBatchIds(quotaIds);
for (CustomerPackageQuota item : list) {
if (item.getQuotaId() != null) {
for (SysQuota t : templates) {
if (t.getId().equals(item.getQuotaId())) {
String u = t.getUnit() != null ? t.getUnit() : t.getMaxQuotaUnit();
item.setUnit(u);
break;
}
}
}
}
}
}
return cn.apes.commons.Res.success(list);
}
@Override
public cn.apes.commons.Res getMyPackages(Long customerId) {
if (customerId == null) {
return cn.apes.commons.Res.fail("客户ID不能为空");
}
// 1. 查询客户所有套餐
LambdaQueryWrapper<CustomerPackage> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CustomerPackage::getCustomerId, customerId);
wrapper.orderByDesc(CustomerPackage::getCreateTime);
List<CustomerPackage> packages = list(wrapper);
if (packages.isEmpty()) {
return cn.apes.commons.Res.success(new ArrayList<>());
}
// 2. 填充套餐详情 + 配额
List<Map<String, Object>> result = new ArrayList<>();
for (CustomerPackage cp : packages) {
Map<String, Object> item = new HashMap<>();
// 套餐基本信息
item.put("id", cp.getId());
item.put("customerId", cp.getCustomerId());
item.put("planId", cp.getPlanId());
item.put("expireTime", cp.getExpireTime());
item.put("createTime", cp.getCreateTime());
item.put("updateTime", cp.getUpdateTime());
// 套餐详情
SysPackagePlan plan = sysPackagePlanService.getById(cp.getPlanId());
if (plan != null) {
item.put("planName", plan.getPlanName());
item.put("planLevel", plan.getPlanLevel());
item.put("chargeMode", plan.getChargeMode());
item.put("amount", plan.getAmount());
item.put("description", plan.getDescription());
item.put("status", plan.getStatus());
item.put("iconUrl", plan.getIconUrl());
}
// 配额配置
LambdaQueryWrapper<CustomerPackageQuota> quotaWrapper = new LambdaQueryWrapper<>();
quotaWrapper.eq(CustomerPackageQuota::getCustomerPackageId, cp.getId());
List<CustomerPackageQuota> quotas = customerPackageQuotaMapper.selectList(quotaWrapper);
// 从 sys_quota 获取 unit 和 resetCycle 补充到配额列表中
if (!quotas.isEmpty()) {
List<Long> quotaIds = quotas.stream()
.map(CustomerPackageQuota::getQuotaId)
.filter(id -> id != null)
.collect(Collectors.toList());
if (!quotaIds.isEmpty()) {
List<SysQuota> sysQuotas = sysQuotaService.listByIds(quotaIds);
for (CustomerPackageQuota q : quotas) {
if (q.getQuotaId() != null) {
for (SysQuota sq : sysQuotas) {
if (sq.getId().equals(q.getQuotaId())) {
String u = sq.getUnit() != null ? sq.getUnit() : sq.getMaxQuotaUnit();
q.setUnit(u);
q.setResetCycle(sq.getResetCycle());
break;
}
}
}
}
}
}
item.put("quotas", quotas);
// 应用列表:从套餐配置的 appIds 获取,与配额无关
List<Map<String, Object>> appInfoList = new ArrayList<>();
if (plan != null && plan.getAppIds() != null && !plan.getAppIds().isEmpty()) {
List<String> appKeys = java.util.Arrays.stream(plan.getAppIds().split(","))
.map(String::trim)
.filter(k -> !k.isEmpty())
.collect(java.util.stream.Collectors.toList());
if (!appKeys.isEmpty()) {
List<AppInfo> apps = appInfoMapper.selectBatchIds(appKeys);
Map<String, String> appKeyToName = apps.stream()
.collect(java.util.stream.Collectors.toMap(AppInfo::getAppKey, a -> a.getAppName() != null ? a.getAppName() : a.getAppKey()));
for (String key : appKeys) {
Map<String, Object> ai = new HashMap<>();
ai.put("appKey", key);
ai.put("appName", appKeyToName.getOrDefault(key, key));
appInfoList.add(ai);
}
}
}
item.put("appKeys", appInfoList);
result.add(item);
}
return cn.apes.commons.Res.success(result);
}
@Override
public cn.apes.commons.Res getCustomerPackageOverview(java.util.Map<String, Object> params) {
// 分页参数
int pageNum = params.get("pageNum") != null ? Integer.parseInt(params.get("pageNum").toString()) : 1;
int pageSize = params.get("pageSize") != null ? Integer.parseInt(params.get("pageSize").toString()) : 10;
// 查询所有未删除的客户套餐
LambdaQueryWrapper<CustomerPackage> cpWrapper = new LambdaQueryWrapper<>();
cpWrapper.eq(CustomerPackage::getIsDel, 0);
cpWrapper.last("ORDER BY CASE WHEN expire_time IS NULL THEN 1 ELSE 0 END, expire_time ASC");
List<CustomerPackage> allPackages = list(cpWrapper);
// 获取套餐详情(planName
List<CustomerPackageVO> fullList = new ArrayList<>();
for (CustomerPackage cp : allPackages) {
CustomerPackageVO vo = new CustomerPackageVO();
vo.setPackageId(cp.getId());
vo.setCustomerId(cp.getCustomerId());
vo.setPlanId(cp.getPlanId());
vo.setExpireTime(cp.getExpireTime());
// 查询客户信息
CustomerInfo ci = customerInfoMapper.selectById(cp.getCustomerId());
if (ci != null) {
vo.setCustomerName(ci.getCustomerName());
vo.setShortName(ci.getShortName());
vo.setAdminPhone(ci.getAdminPhone());
}
// 查询套餐名称
SysPackagePlan plan = sysPackagePlanService.getById(cp.getPlanId());
if (plan != null) {
vo.setPlanName(plan.getPlanName());
}
// 计算剩余天数
if (cp.getExpireTime() != null) {
long diff = cp.getExpireTime().getTime() - System.currentTimeMillis();
long days = diff / (1000 * 60 * 60 * 24);
vo.setRemainingDays(days);
} else {
vo.setRemainingDays(-1L); // 无过期时间
}
fullList.add(vo);
}
// 分页
int total = fullList.size();
int fromIndex = (pageNum - 1) * pageSize;
int toIndex = Math.min(fromIndex + pageSize, total);
List<CustomerPackageVO> pageData = fromIndex >= total ? new ArrayList<>() : fullList.subList(fromIndex, toIndex);
// 组装分页结果
java.util.Map<String, Object> result = new java.util.HashMap<>();
result.put("list", pageData);
result.put("total", total);
result.put("pageNum", pageNum);
result.put("pageSize", pageSize);
result.put("totalPages", (total + pageSize - 1) / pageSize);
return cn.apes.commons.Res.success(result);
}
public cn.apes.commons.Res submitExtendApply(java.util.Map<String, Object> params) {
Long customerId = params.get("customerId") != null ? Long.valueOf(params.get("customerId").toString()) : null;
Long planId = params.get("planId") != null ? Long.valueOf(params.get("planId").toString()) : null;
Long userId = params.get("userId") != null ? Long.valueOf(params.get("userId").toString()) : null;
String contactPhone = params.get("contactPhone") != null ? params.get("contactPhone").toString() : null;
String reason = params.get("reason") != null ? params.get("reason").toString() : null;
Integer applyDays = params.get("applyDays") != null ? Integer.valueOf(params.get("applyDays").toString()) : null;
if (customerId == null || planId == null || userId == null) {
return cn.apes.commons.Res.error("缺少必要参数");
}
if (applyDays == null || applyDays < 0 || applyDays > 180) {
return cn.apes.commons.Res.error("延期天数需在0-180天之间");
}
CustomerPackageExtendApply apply = new CustomerPackageExtendApply();
apply.setCustomerId(customerId);
apply.setPlanId(planId);
apply.setUserId(userId);
apply.setContactPhone(contactPhone);
apply.setReason(reason);
apply.setApplyDays(applyDays);
apply.setStatus(0);
apply.setApplyTime(new java.util.Date());
customerPackageExtendApplyMapper.insert(apply);
return cn.apes.commons.Res.success();
}
@Override
public cn.apes.commons.Res getExtendApplyList(java.util.Map<String, Object> params) {
// 分页参数
int pageNum = params.get("pageNum") != null ? Integer.parseInt(params.get("pageNum").toString()) : 1;
int pageSize = params.get("pageSize") != null ? Integer.parseInt(params.get("pageSize").toString()) : 10;
// 查询所有延期申请记录(不走 @TableLogic,查全部)
LambdaQueryWrapper<CustomerPackageExtendApply> wrapper = new LambdaQueryWrapper<>();
wrapper.orderByDesc(CustomerPackageExtendApply::getApplyTime);
List<CustomerPackageExtendApply> allList = customerPackageExtendApplyMapper.selectList(wrapper);
// 组装 VO
List<CustomerPackageExtendApplyVO> fullList = new ArrayList<>();
for (CustomerPackageExtendApply item : allList) {
CustomerPackageExtendApplyVO vo = new CustomerPackageExtendApplyVO();
vo.setId(item.getId());
vo.setCustomerId(item.getCustomerId());
vo.setPlanId(item.getPlanId());
vo.setUserId(item.getUserId());
vo.setContactPhone(item.getContactPhone());
vo.setReason(item.getReason());
vo.setApplyDays(item.getApplyDays());
vo.setApplyTime(item.getApplyTime());
vo.setStatus(item.getStatus());
// 状态文本
if (item.getStatus() != null) {
String[] statusTexts = {"申请中", "已调整", "已拒绝"};
vo.setStatusText(item.getStatus() >= 0 && item.getStatus() < statusTexts.length ? statusTexts[item.getStatus()] : "未知");
}
// 操作人信息
vo.setOperatorId(item.getOperatorId());
vo.setOperatorName(item.getOperatorName());
vo.setOperateTime(item.getOperateTime());
// 查询企业信息
CustomerInfo ci = customerInfoMapper.selectById(item.getCustomerId());
if (ci != null) {
vo.setCustomerName(ci.getCustomerName());
vo.setShortName(ci.getShortName());
vo.setAdminPhone(ci.getAdminPhone());
}
// 查询套餐名称
SysPackagePlan plan = sysPackagePlanService.getById(item.getPlanId());
if (plan != null) {
vo.setPlanName(plan.getPlanName());
}
fullList.add(vo);
}
// 前端筛选条件(可选)
String keyword = params.get("keyword") != null ? params.get("keyword").toString() : null;
Integer status = params.get("status") != null && !params.get("status").toString().isEmpty() ? Integer.parseInt(params.get("status").toString()) : null;
if (keyword != null && !keyword.isEmpty()) {
fullList = fullList.stream().filter(vo ->
(vo.getCustomerName() != null && vo.getCustomerName().contains(keyword))
|| (vo.getShortName() != null && vo.getShortName().contains(keyword))
|| (vo.getPlanName() != null && vo.getPlanName().contains(keyword))
).collect(java.util.stream.Collectors.toList());
}
if (status != null) {
fullList = fullList.stream().filter(vo -> vo.getStatus() != null && vo.getStatus().equals(status)).collect(java.util.stream.Collectors.toList());
}
// 分页
int total = fullList.size();
int fromIndex = (pageNum - 1) * pageSize;
int toIndex = Math.min(fromIndex + pageSize, total);
List<CustomerPackageExtendApplyVO> pageData = fromIndex >= total ? new ArrayList<>() : fullList.subList(fromIndex, toIndex);
// 组装结果
java.util.Map<String, Object> result = new java.util.HashMap<>();
result.put("list", pageData);
result.put("total", total);
result.put("pageNum", pageNum);
result.put("pageSize", pageSize);
result.put("totalPages", (total + pageSize - 1) / pageSize);
return cn.apes.commons.Res.success(result);
}
/**
* 审核延期申请(同意/拒绝)
*/
public cn.apes.commons.Res reviewExtendApply(java.util.Map<String, Object> params) {
Long applyId = params.get("applyId") != null ? Long.valueOf(params.get("applyId").toString()) : null;
Integer action = params.get("action") != null ? Integer.valueOf(params.get("action").toString()) : null;
Integer newApplyDays = params.get("applyDays") != null ? Integer.valueOf(params.get("applyDays").toString()) : null;
if (applyId == null || action == null) {
return cn.apes.commons.Res.error("缺少必要参数");
}
if (action != 1 && action != 2) {
return cn.apes.commons.Res.error("操作类型无效");
}
CustomerPackageExtendApply applyRecord = customerPackageExtendApplyMapper.selectById(applyId);
if (applyRecord == null) {
return cn.apes.commons.Res.error("申请记录不存在");
}
if (applyRecord.getStatus() != 0) {
return cn.apes.commons.Res.error("该申请已处理");
}
// 获取操作人
LoginUser operator = AuthContext.getLoginInfo().getUser();
String operatorName = operator.getNickName() != null ? operator.getNickName() : operator.getPhone();
// 更新状态
applyRecord.setStatus(action);
applyRecord.setOperatorId(operator.getId());
applyRecord.setOperatorName(operatorName);
applyRecord.setOperateTime(new java.util.Date());
// 使用操作人信息
String logUserName = operator.getNickName() != null ? operator.getNickName() : operator.getPhone();
if (action == 1) {
// 同意调整 - 延长套餐
int days = (newApplyDays != null && newApplyDays > 0 && newApplyDays <= 180) ? newApplyDays : applyRecord.getApplyDays();
applyRecord.setApplyDays(days);
// 查询当前套餐的 expire_time
LambdaQueryWrapper<CustomerPackage> pkgWrapper = new LambdaQueryWrapper<>();
pkgWrapper.eq(CustomerPackage::getCustomerId, applyRecord.getCustomerId())
.eq(CustomerPackage::getPlanId, applyRecord.getPlanId())
.eq(CustomerPackage::getIsDel, 0);
CustomerPackage pkg = getOne(pkgWrapper);
if (pkg != null && pkg.getExpireTime() != null) {
// 延长
java.util.Calendar cal = java.util.Calendar.getInstance();
cal.setTime(pkg.getExpireTime());
cal.add(java.util.Calendar.DAY_OF_YEAR, days);
pkg.setExpireTime(cal.getTime());
updateById(pkg);
OperationLogUtil.log("延期申请管理", "同意延期",
"同意企业延期申请,延长套餐 " + days + "");
} else {
return cn.apes.commons.Res.error("未找到对应的套餐");
}
} else {
// 拒绝
OperationLogUtil.log("延期申请管理", "拒绝延期",
"拒绝企业延期申请");
}
customerPackageExtendApplyMapper.updateById(applyRecord);
return cn.apes.commons.Res.success();
}
@Override
@org.springframework.transaction.annotation.Transactional(rollbackFor = Exception.class)
public cn.apes.commons.Res increaseQuota(java.util.Map<String, Object> params) {
Long packageId = params.get("packageId") != null ? Long.parseLong(params.get("packageId").toString()) : null;
String quotaCode = params.get("quotaCode") != null ? params.get("quotaCode").toString() : null;
Integer amount = params.get("amount") != null ? Integer.parseInt(params.get("amount").toString()) : null;
String reason = params.get("reason") != null ? params.get("reason").toString() : "";
if (packageId == null || quotaCode == null || amount == null || amount <= 0) {
return cn.apes.commons.Res.fail("参数错误");
}
// 查询当前配额
LambdaQueryWrapper<CustomerPackageQuota> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CustomerPackageQuota::getCustomerPackageId, packageId)
.eq(CustomerPackageQuota::getQuotaCode, quotaCode);
CustomerPackageQuota quota = customerPackageQuotaMapper.selectOne(wrapper);
if (quota == null) {
return cn.apes.commons.Res.fail("配额项不存在");
}
// 更新配额值
int oldValue = Integer.parseInt(quota.getQuotaValue());
int newValue = oldValue + amount;
quota.setQuotaValue(String.valueOf(newValue));
customerPackageQuotaMapper.updateById(quota);
// 记录变更日志
CustomerQuotaChangeLog log = new CustomerQuotaChangeLog();
log.setCustomerPackageId(packageId);
log.setQuotaCode(quotaCode);
log.setQuotaName(quota.getQuotaName());
log.setChangeType(1); // 1=增加
log.setChangeAmount(amount);
log.setOldValue(String.valueOf(oldValue));
log.setNewValue(String.valueOf(newValue));
log.setReason(reason);
log.setOperateTime(new java.util.Date());
try {
log.setOperatorId(AuthContext.getLoginInfo().getUser().getId());
log.setOperatorName(AuthContext.getLoginInfo().getUser().getNickName());
} catch (Exception e) {
// AccessToken 模式下可能没有登录信息
log.setOperatorName("系统/外部调用");
}
customerQuotaChangeLogMapper.insert(log);
return cn.apes.commons.Res.success();
}
@Override
@org.springframework.transaction.annotation.Transactional(rollbackFor = Exception.class)
public cn.apes.commons.Res decreaseQuota(java.util.Map<String, Object> params) {
Long packageId = params.get("packageId") != null ? Long.parseLong(params.get("packageId").toString()) : null;
String quotaCode = params.get("quotaCode") != null ? params.get("quotaCode").toString() : null;
Integer amount = params.get("amount") != null ? Integer.parseInt(params.get("amount").toString()) : null;
String reason = params.get("reason") != null ? params.get("reason").toString() : "";
if (packageId == null || quotaCode == null || amount == null || amount <= 0) {
return cn.apes.commons.Res.fail("参数错误");
}
// 查询当前配额
LambdaQueryWrapper<CustomerPackageQuota> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CustomerPackageQuota::getCustomerPackageId, packageId)
.eq(CustomerPackageQuota::getQuotaCode, quotaCode);
CustomerPackageQuota quota = customerPackageQuotaMapper.selectOne(wrapper);
if (quota == null) {
return cn.apes.commons.Res.fail("配额项不存在");
}
// 检查是否会导致负数
int oldValue = Integer.parseInt(quota.getQuotaValue());
int newValue = oldValue - amount;
if (newValue < 0) {
return cn.apes.commons.Res.fail("减少后配额不能为负数");
}
// 更新配额值
quota.setQuotaValue(String.valueOf(newValue));
customerPackageQuotaMapper.updateById(quota);
// 记录变更日志
CustomerQuotaChangeLog log = new CustomerQuotaChangeLog();
log.setCustomerPackageId(packageId);
log.setQuotaCode(quotaCode);
log.setQuotaName(quota.getQuotaName());
log.setChangeType(2); // 2=减少
log.setChangeAmount(amount);
log.setOldValue(String.valueOf(oldValue));
log.setNewValue(String.valueOf(newValue));
log.setReason(reason);
log.setOperateTime(new java.util.Date());
try {
log.setOperatorId(AuthContext.getLoginInfo().getUser().getId());
log.setOperatorName(AuthContext.getLoginInfo().getUser().getNickName());
} catch (Exception e) {
// AccessToken 模式下可能没有登录信息
log.setOperatorName("系统/外部调用");
}
customerQuotaChangeLogMapper.insert(log);
return cn.apes.commons.Res.success();
}
@Override
public cn.apes.commons.Res getQuotaChangeLogs(java.util.Map<String, Object> params) {
Long packageId = params.get("packageId") != null ? Long.parseLong(params.get("packageId").toString()) : null;
String keyword = params.get("keyword") != null ? params.get("keyword").toString() : null;
Integer pageNum = params.get("pageNum") != null ? Integer.parseInt(params.get("pageNum").toString()) : 1;
Integer pageSize = params.get("pageSize") != null ? Integer.parseInt(params.get("pageSize").toString()) : 10;
com.baomidou.mybatisplus.extension.plugins.pagination.Page<CustomerQuotaChangeLog> page =
new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageNum, pageSize);
LambdaQueryWrapper<CustomerQuotaChangeLog> wrapper = new LambdaQueryWrapper<>();
if (packageId != null) {
wrapper.eq(CustomerQuotaChangeLog::getCustomerPackageId, packageId);
}
if (keyword != null && !keyword.trim().isEmpty()) {
wrapper.and(w -> w.like(CustomerQuotaChangeLog::getQuotaName, keyword)
.or().like(CustomerQuotaChangeLog::getQuotaCode, keyword)
.or().like(CustomerQuotaChangeLog::getOperatorName, keyword)
.or().like(CustomerQuotaChangeLog::getReason, keyword));
}
wrapper.orderByDesc(CustomerQuotaChangeLog::getOperateTime);
com.baomidou.mybatisplus.extension.plugins.pagination.Page<CustomerQuotaChangeLog> result =
customerQuotaChangeLogMapper.selectPage(page, wrapper);
// 关联客户信息
List<CustomerQuotaChangeLog> logs = result.getRecords();
if (logs != null && !logs.isEmpty()) {
// 收集所有 customerPackageId
List<Long> packageIds = logs.stream()
.map(CustomerQuotaChangeLog::getCustomerPackageId)
.distinct()
.collect(Collectors.toList());
// 查询套餐对应的客户ID
LambdaQueryWrapper<CustomerPackage> pkgWrapper = new LambdaQueryWrapper<>();
pkgWrapper.in(CustomerPackage::getId, packageIds);
List<CustomerPackage> packages = baseMapper.selectList(pkgWrapper);
Map<Long, Long> packageToCustomerMap = packages.stream()
.collect(Collectors.toMap(CustomerPackage::getId, CustomerPackage::getCustomerId));
// 查询客户信息
List<Long> customerIds = packages.stream()
.map(CustomerPackage::getCustomerId)
.distinct()
.collect(Collectors.toList());
Map<Long, String> customerNameMap = new HashMap<>();
if (!customerIds.isEmpty()) {
LambdaQueryWrapper<CustomerInfo> custWrapper = new LambdaQueryWrapper<>();
custWrapper.in(CustomerInfo::getId, customerIds);
List<CustomerInfo> customers = customerInfoMapper.selectList(custWrapper);
customerNameMap = customers.stream()
.collect(Collectors.toMap(CustomerInfo::getId, CustomerInfo::getCustomerName));
}
// 设置客户名称到日志记录
for (CustomerQuotaChangeLog log : logs) {
Long customerId = packageToCustomerMap.get(log.getCustomerPackageId());
if (customerId != null) {
log.setCustomerName(customerNameMap.getOrDefault(customerId, ""));
}
}
}
return cn.apes.commons.Res.success(result);
}
}

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