chore: initialize qiming workspace repository
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
package com.xspaceagi.system.web.aspect;
|
||||
|
||||
import com.xspaceagi.system.application.dto.UserDto;
|
||||
import com.xspaceagi.system.application.service.SysUserPermissionCacheService;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.enums.ResourceEnum;
|
||||
import com.xspaceagi.system.spec.exception.ResourcePermissionException;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 资源权限切面
|
||||
* 支持方法级别和类级别的 @RequireResource 注解,方法上的注解优先于类上的
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
public class ResourcePermissionAspect {
|
||||
|
||||
@Resource
|
||||
private SysUserPermissionCacheService sysUserPermissionCacheService;
|
||||
|
||||
/**
|
||||
* 环绕通知,拦截带有 @RequireResource 注解的方法或类
|
||||
* 方法级别:仅该方法受注解限制;类级别:该类的所有方法都受注解限制
|
||||
*/
|
||||
@Around("@annotation(com.xspaceagi.system.spec.annotation.RequireResource) || @within(com.xspaceagi.system.spec.annotation.RequireResource)")
|
||||
public Object checkResourcePermission(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
RequireResource requireResource = resolveRequireResource(joinPoint);
|
||||
if (requireResource == null) {
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
|
||||
// 如果允许跳过权限检查,直接执行方法
|
||||
if (requireResource.skipCheck()) {
|
||||
log.debug("跳过资源权限检查: {}", joinPoint.getSignature().getName());
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
|
||||
try {
|
||||
UserDto currentUser = (UserDto) RequestContext.get().getUser();
|
||||
if (currentUser == null) {
|
||||
log.warn("User not logged in, access denied: {}", joinPoint.getSignature().getName());
|
||||
throw new ResourcePermissionException("用户未登录");
|
||||
}
|
||||
|
||||
List<String> resourceCodes = collectResourceCodes(requireResource);
|
||||
if (CollectionUtils.isEmpty(resourceCodes)) {
|
||||
log.warn("资源编码为空,拒绝访问: {}", joinPoint.getSignature().getName());
|
||||
throw new ResourcePermissionException("资源编码不能为空");
|
||||
}
|
||||
|
||||
sysUserPermissionCacheService.checkResourcePermissionAny(currentUser.getId(), resourceCodes);
|
||||
|
||||
log.debug("资源权限验证通过,用户[{}]访问资源[{}]: {}",
|
||||
currentUser.getId(), resourceCodes, joinPoint.getSignature().getName());
|
||||
|
||||
return joinPoint.proceed();
|
||||
|
||||
} catch (ResourcePermissionException e) {
|
||||
log.warn("资源Permission check failed: {}", e.getMessage());
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 @RequireResource 注解:优先取方法上的,其次取类上的
|
||||
*/
|
||||
private RequireResource resolveRequireResource(ProceedingJoinPoint joinPoint) {
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
RequireResource requireResource = AnnotationUtils.findAnnotation(method, RequireResource.class);
|
||||
if (requireResource != null) {
|
||||
return requireResource;
|
||||
}
|
||||
return AnnotationUtils.findAnnotation(method.getDeclaringClass(), RequireResource.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从注解中收集所有资源码(value 枚举 + resourceCodes)
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private List<String> collectResourceCodes(RequireResource requireResource) {
|
||||
List<String> allCodes = new ArrayList<>();
|
||||
if (requireResource.value() != null && requireResource.value().length > 0) {
|
||||
for (ResourceEnum e : requireResource.value()) {
|
||||
if (e != null && e.getCode() != null) {
|
||||
allCodes.add(e.getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (requireResource.codes() != null && requireResource.codes().length > 0) {
|
||||
for (String s : requireResource.codes()) {
|
||||
if (StringUtils.isNotBlank(s)) {
|
||||
allCodes.add(s.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
return allCodes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.xspaceagi.system.web.aspect;
|
||||
|
||||
import com.xspaceagi.system.application.dto.UserDto;
|
||||
import com.xspaceagi.system.infra.dao.entity.User;
|
||||
import com.xspaceagi.system.spec.annotation.SaasAdmin;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
|
||||
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.reflect.MethodSignature;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
public class SaasAdminAspect {
|
||||
|
||||
private static final Long SAAS_ADMIN_TENANT_ID = 1L;
|
||||
|
||||
@Around("@annotation(com.xspaceagi.system.spec.annotation.SaasAdmin) || @within(com.xspaceagi.system.spec.annotation.SaasAdmin)")
|
||||
public Object checkSaasAdminPermission(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
SaasAdmin saasAdmin = resolveSaasAdmin(joinPoint);
|
||||
if (saasAdmin == null) {
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
|
||||
if (saasAdmin.skipCheck()) {
|
||||
log.debug("跳过 SaaS 管理员权限检查: {}", joinPoint.getSignature().getName());
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
|
||||
if (RequestContext.get() == null || RequestContext.get().getTenantId() == null) {
|
||||
log.warn("上下文或租户ID为空,拒绝访问: {}", joinPoint.getSignature().getName());
|
||||
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied);
|
||||
}
|
||||
|
||||
if (!SAAS_ADMIN_TENANT_ID.equals(RequestContext.get().getTenantId())) {
|
||||
log.warn("拒绝访问: tenantId={}, method={}", RequestContext.get().getTenantId(), joinPoint.getSignature().getName());
|
||||
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied);
|
||||
}
|
||||
|
||||
UserDto userDto = (UserDto) RequestContext.get().getUser();
|
||||
if (userDto == null || userDto.getRole() != User.Role.Admin) {
|
||||
log.warn("拒绝访问: userId={}, method={}", userDto != null ? userDto.getId() : null, joinPoint.getSignature().getName());
|
||||
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied);
|
||||
}
|
||||
|
||||
log.debug("SaaS Admin check passed, user [{}] accessing: {}", userDto.getId(), joinPoint.getSignature().getName());
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
|
||||
private SaasAdmin resolveSaasAdmin(ProceedingJoinPoint joinPoint) {
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
SaasAdmin saasAdmin = AnnotationUtils.findAnnotation(method, SaasAdmin.class);
|
||||
if (saasAdmin != null) {
|
||||
return saasAdmin;
|
||||
}
|
||||
return AnnotationUtils.findAnnotation(method.getDeclaringClass(), SaasAdmin.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
import com.xspaceagi.system.application.dto.CategoryCreateDto;
|
||||
import com.xspaceagi.system.application.dto.CategoryUpdateDto;
|
||||
import com.xspaceagi.system.application.service.CategoryApplicationService;
|
||||
import com.xspaceagi.system.infra.dao.entity.Category;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.utils.I18nUtil;
|
||||
import com.xspaceagi.system.web.controller.base.BaseController;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.*;
|
||||
|
||||
/**
|
||||
* 分类管理控制器
|
||||
*/
|
||||
@Slf4j
|
||||
@Tag(name = "分类管理", description = "分类管理相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/system/category")
|
||||
public class CategoryController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private CategoryApplicationService categoryApplicationService;
|
||||
|
||||
/**
|
||||
* 创建分类
|
||||
*/
|
||||
@RequireResource(CATEGORY_CONFIG_ADD)
|
||||
@Operation(summary = "创建分类")
|
||||
@PostMapping(value = "/create", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Category> create(@RequestBody CategoryCreateDto dto) {
|
||||
log.info("[create] Create category, name={}, code={}, type={}", dto.getName(), dto.getCode(), dto.getType());
|
||||
Assert.notNull(dto.getName(), "Name is required");
|
||||
Assert.notNull(dto.getCode(), "Code is required");
|
||||
Assert.notNull(dto.getType(), "Type is required");
|
||||
Category category = new Category();
|
||||
category.setName(dto.getName());
|
||||
category.setCode(dto.getCode());
|
||||
category.setType(dto.getType());
|
||||
category.setDescription(dto.getDescription());
|
||||
|
||||
return categoryApplicationService.create(category, getUser());
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新分类
|
||||
*/
|
||||
@RequireResource(CATEGORY_CONFIG_MODIFY)
|
||||
@Operation(summary = "更新分类")
|
||||
@PostMapping(value = "/update", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Category> update(@RequestBody CategoryUpdateDto dto) {
|
||||
log.info("[update] Update category, id={}, name={}, code={}, type={}",
|
||||
dto.getId(), dto.getName(), dto.getCode(), dto.getType());
|
||||
Assert.notNull(dto.getId(), "ID is required");
|
||||
Category category = new Category();
|
||||
category.setId(dto.getId());
|
||||
category.setName(dto.getName());
|
||||
category.setCode(dto.getCode());
|
||||
category.setType(dto.getType());
|
||||
category.setDescription(dto.getDescription());
|
||||
|
||||
return categoryApplicationService.update(category, getUser());
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分类
|
||||
*/
|
||||
@RequireResource(CATEGORY_CONFIG_DELETE)
|
||||
@Operation(summary = "删除分类")
|
||||
@PostMapping(value = "/delete/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Void> delete(@PathVariable("id") Long id) {
|
||||
log.info("[delete] Delete category, id={}", id);
|
||||
Assert.notNull(id, "ID不能为空");
|
||||
return categoryApplicationService.delete(id, getUser());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据类型查询分类列表
|
||||
*/
|
||||
@RequireResource(CATEGORY_CONFIG_QUERY)
|
||||
@Operation(summary = "根据类型查询分类列表")
|
||||
@GetMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<List<Category>> listByType(@RequestParam("type") String type) {
|
||||
log.info("[listByType] 查询分类列表, type={}", type);
|
||||
List<Category> list = categoryApplicationService.listByType(type);
|
||||
|
||||
I18nUtil.replaceSystemMessage(list);
|
||||
return ReqResult.success(list);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
import com.xspaceagi.system.sdk.service.UserAccessKeyApiService;
|
||||
import com.xspaceagi.system.sdk.service.dto.UserAccessKeyDto;
|
||||
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
|
||||
import com.xspaceagi.system.spec.enums.HttpStatusEnum;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Slf4j
|
||||
public class ChatKeyCheck {
|
||||
|
||||
public static void check(HttpServletRequest request, UserAccessKeyApiService userAccessKeyApiService) {
|
||||
String fragment = request.getHeader("Fragment");
|
||||
String referer = request.getHeader("Referer");
|
||||
String chatKey = null;
|
||||
if (referer != null && referer.contains("chat-temp")) {
|
||||
try {
|
||||
URL url = new URL(referer);
|
||||
chatKey = url.getPath().replace("chat-temp", "").replace("/", "");
|
||||
} catch (Exception e) {
|
||||
//
|
||||
log.warn("referer error", e);
|
||||
}
|
||||
} else if (StringUtils.isNotBlank(fragment)) {
|
||||
//fragment:chatKey=ck-1ce5a050c8f64f5a8b555d06b4818038
|
||||
//用正则获取chatKey的值,其中 chatKey后面为:-或其他字符串
|
||||
chatKey = extractChatKey(fragment);
|
||||
}
|
||||
if (StringUtils.isNotBlank(chatKey)) {
|
||||
UserAccessKeyDto userAccessKeyDto = userAccessKeyApiService.queryAccessKey(chatKey);
|
||||
if (userAccessKeyDto == null) {
|
||||
throw BizException.of(HttpStatusEnum.UNAUTHORIZED, ErrorCodeEnum.UNAUTHORIZED,
|
||||
BizExceptionCodeEnum.systemUnauthorizedOrSessionExpired);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String extractChatKey(String fragment) {
|
||||
// 正则表达式提取chatKey
|
||||
String regex = "chatKey=([^&]+)";
|
||||
Pattern pattern = Pattern.compile(regex);
|
||||
Matcher matcher = pattern.matcher(fragment);
|
||||
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
import com.xspaceagi.system.application.constant.I18nLangTagConstraints;
|
||||
import com.xspaceagi.system.application.constant.SupportedLocaleConstants;
|
||||
import com.xspaceagi.system.application.dto.I18nLangDto;
|
||||
import com.xspaceagi.system.application.dto.SupportedLocaleOptionDto;
|
||||
import com.xspaceagi.system.application.dto.UserDto;
|
||||
import com.xspaceagi.system.application.service.I18nApplicationService;
|
||||
import com.xspaceagi.system.application.service.I18nLangApplicationService;
|
||||
import com.xspaceagi.system.application.service.UserApplicationService;
|
||||
import com.xspaceagi.system.infra.dao.entity.User;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 国际化管理器
|
||||
*/
|
||||
@Slf4j
|
||||
@Tag(name = "多语言查询", description = "国际化相关接口(语言管理、多语言配置)")
|
||||
@RestController
|
||||
@RequestMapping("/api/i18n")
|
||||
public class I18nController {
|
||||
|
||||
@Resource
|
||||
private I18nApplicationService i18nApplicationService;
|
||||
|
||||
@Resource
|
||||
private I18nLangApplicationService i18nLangApplicationService;
|
||||
|
||||
@Resource
|
||||
private UserApplicationService userApplicationService;
|
||||
|
||||
/**
|
||||
* 查询系统语言 Map
|
||||
*/
|
||||
@Operation(summary = "查询指定语言信息")
|
||||
@GetMapping(value = "/query", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Map<String, String>> querySystemLangMap(@RequestParam(value = "side", required = false) String side,
|
||||
@RequestParam(value = "lang", required = false) String lang,
|
||||
HttpServletResponse response) {
|
||||
log.info("[querySystemLangMap] 查询系统语言 Map, lang={}", lang);
|
||||
String resolved = StringUtils.isBlank(lang) ? RequestContext.get().getLang() : lang.trim();
|
||||
lang = StringUtils.isBlank(resolved) ? resolved
|
||||
: I18nLangTagConstraints.tryNormalizeToStoredForm(resolved).orElse(resolved);
|
||||
Map<String, String> map = i18nApplicationService.querySystemLangMap(RequestContext.get().getTenantId(), side, lang);
|
||||
|
||||
if (StringUtils.isBlank(lang)) {
|
||||
return ReqResult.success(map);
|
||||
}
|
||||
|
||||
if (RequestContext.get().isLogin()) {
|
||||
UserDto update = new UserDto();
|
||||
update.setLang(lang);
|
||||
update.setId(RequestContext.get().getUserId());
|
||||
userApplicationService.update(update);
|
||||
UserDto userDto = (UserDto) RequestContext.get().getUser();
|
||||
if (userDto.getRole() != User.Role.Admin) {
|
||||
removeSystemManageMessage(map);
|
||||
}
|
||||
} else {
|
||||
removeSystemManageMessage(map);
|
||||
}
|
||||
Cookie cookie = new Cookie("lang", lang);
|
||||
cookie.setMaxAge(86400 * 365);
|
||||
cookie.setPath("/");
|
||||
cookie.setHttpOnly(true);
|
||||
response.addCookie(cookie);
|
||||
return ReqResult.success(map);
|
||||
}
|
||||
|
||||
private void removeSystemManageMessage(Map<String, String> map) {
|
||||
if (map == null || map.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 先收集需要删除的 key,避免在遍历过程中直接修改 map
|
||||
List<String> keysToRemove = map.keySet().stream()
|
||||
.filter(key -> key.startsWith("PC.System")) // TODO 确认管理端的 key前缀
|
||||
.toList();
|
||||
// 批量删除
|
||||
keysToRemove.forEach(map::remove);
|
||||
}
|
||||
|
||||
@Operation(summary = "查询语言列表(用户侧:不展示租户默认,仅标当前会话语言)")
|
||||
@GetMapping(value = "/lang/list", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<List<I18nLangDto>> queryLangList() {
|
||||
List<I18nLangDto> i18nLangs = i18nLangApplicationService.queryAll();
|
||||
|
||||
String currentLang = RequestContext.get().getLang();
|
||||
if (StringUtils.isNotBlank(currentLang)) {
|
||||
//不把租户默认当默认展示,统一先置为非默认
|
||||
i18nLangs.forEach(l -> l.setIsDefault(0));
|
||||
|
||||
i18nLangs.stream()
|
||||
.filter(l -> l.getLang() != null && l.getLang().equalsIgnoreCase(currentLang))
|
||||
.findFirst()
|
||||
.ifPresent(l -> l.setIsDefault(1));
|
||||
}
|
||||
return ReqResult.success(i18nLangs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,523 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.xspaceagi.system.application.constant.SupportedLocaleConstants;
|
||||
import com.xspaceagi.system.application.dto.*;
|
||||
import com.xspaceagi.system.application.constant.I18nLangTagConstraints;
|
||||
import com.xspaceagi.system.application.dto.permission.export.I18nConfigExportDto;
|
||||
import com.xspaceagi.system.application.service.I18nApplicationService;
|
||||
import com.xspaceagi.system.application.service.I18nConfigDiffService;
|
||||
import com.xspaceagi.system.application.service.I18nExportService;
|
||||
import com.xspaceagi.system.application.service.I18nImportService;
|
||||
import com.xspaceagi.system.application.service.I18nLangApplicationService;
|
||||
import com.xspaceagi.system.infra.dao.entity.Tenant;
|
||||
import com.xspaceagi.system.infra.dao.service.TenantService;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.annotation.SaasAdmin;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.constants.I18nSyncConstants;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.enums.I18nSideEnum;
|
||||
import com.xspaceagi.system.spec.jackson.JsonSerializeUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.*;
|
||||
|
||||
/**
|
||||
* 国际化管理器
|
||||
*/
|
||||
@Slf4j
|
||||
@Tag(name = "多语言管理", description = "国际化相关接口(语言管理、多语言配置)")
|
||||
@RestController
|
||||
@RequestMapping("/api/system/i18n")
|
||||
public class I18nManageController {
|
||||
|
||||
@Resource
|
||||
private I18nLangApplicationService i18nLangApplicationService;
|
||||
|
||||
@Resource
|
||||
private I18nApplicationService i18nApplicationService;
|
||||
|
||||
@Resource
|
||||
private I18nExportService i18nExportService;
|
||||
|
||||
@Resource
|
||||
private I18nImportService i18nImportService;
|
||||
|
||||
@Resource
|
||||
private I18nConfigDiffService i18nConfigDiffService;
|
||||
|
||||
@Resource
|
||||
private TenantService tenantService;
|
||||
|
||||
private final long TRANSLATE_BATCH_SIZE = 50L;
|
||||
|
||||
// ==================== I18nLang 相关接口 ====================
|
||||
|
||||
@Operation(summary = "可选语言列表(用于前端初始化;与 Accept-Language 匹配项排在第 1 位)")
|
||||
@GetMapping(value = "/locale/bootstrap", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<List<SupportedLocaleOptionDto>> localeBootstrap(HttpServletRequest request) {
|
||||
List<SupportedLocaleOptionDto> options = SupportedLocaleConstants.DEFAULT_LOCALE_TAG_WHITELIST.stream()
|
||||
.map(SupportedLocaleOptionDto::fromTag)
|
||||
.collect(Collectors.toList());
|
||||
List<Locale> supportedLocales = SupportedLocaleConstants.DEFAULT_LOCALE_TAG_WHITELIST.stream()
|
||||
.map(Locale::forLanguageTag)
|
||||
.toList();
|
||||
|
||||
String acceptLanguage = request.getHeader("Accept-Language");
|
||||
if (StringUtils.isBlank(acceptLanguage)) {
|
||||
return ReqResult.success(options);
|
||||
}
|
||||
try {
|
||||
List<Locale.LanguageRange> ranges = Locale.LanguageRange.parse(acceptLanguage);
|
||||
Locale matched = Locale.lookup(ranges, supportedLocales);
|
||||
if (matched == null) {
|
||||
return ReqResult.success(options);
|
||||
}
|
||||
String matchedTag = matched.toLanguageTag();
|
||||
Optional<SupportedLocaleOptionDto> hit = options.stream()
|
||||
.filter(o -> matchedTag.equalsIgnoreCase(o.getTag()))
|
||||
.findFirst();
|
||||
if (hit.isEmpty()) {
|
||||
return ReqResult.success(options);
|
||||
}
|
||||
List<SupportedLocaleOptionDto> reordered = new ArrayList<>(options.size());
|
||||
reordered.add(hit.get());
|
||||
options.stream()
|
||||
.filter(o -> !matchedTag.equalsIgnoreCase(o.getTag()))
|
||||
.forEach(reordered::add);
|
||||
return ReqResult.success(reordered);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.debug("Accept-Language 解析失败: {}", acceptLanguage, e);
|
||||
return ReqResult.success(options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增语言
|
||||
*/
|
||||
@RequireResource(I18N_LANG_ADD)
|
||||
@Operation(summary = "新增语言")
|
||||
@PostMapping(value = "/lang/add", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Long> addLang(@RequestBody @Valid I18nLangAddDto addDto) {
|
||||
log.info("[addLang] 新增语言,name={}, lang={}", addDto.getName(), addDto.getLang());
|
||||
Assert.notNull(addDto.getName(), "Parameter 'name' cannot be left blank.");
|
||||
Assert.notNull(addDto.getLang(), "Parameter 'lang' cannot be left blank.");
|
||||
Long id = i18nLangApplicationService.add(addDto);
|
||||
return ReqResult.success(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除语言
|
||||
*/
|
||||
@RequireResource(I18N_LANG_DELETE)
|
||||
@Operation(summary = "删除语言")
|
||||
@PostMapping(value = "/lang/delete/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Void> deleteLang(@PathVariable("id") Long id) {
|
||||
log.info("[deleteLang] 删除语言,id={}", id);
|
||||
Assert.notNull(id, "Parameter 'id' cannot be left blank.");
|
||||
i18nLangApplicationService.delete(id);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新语言
|
||||
*/
|
||||
@RequireResource(I18N_LANG_MODIFY)
|
||||
@Operation(summary = "更新语言")
|
||||
@PostMapping(value = "/lang/update", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Void> updateLang(@RequestBody @Valid I18nLangUpdateDto updateDto) {
|
||||
log.info("[updateLang] 更新语言,id={}, name={}, status={}",
|
||||
updateDto.getId(), updateDto.getName(), updateDto.getStatus());
|
||||
Assert.notNull(updateDto.getId(), "Parameter 'id' cannot be left blank.");
|
||||
i18nLangApplicationService.update(updateDto);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置为默认语言
|
||||
*/
|
||||
@RequireResource(I18N_LANG_MODIFY)
|
||||
@Operation(summary = "设置为默认语言")
|
||||
@PostMapping(value = "/lang/setDefault/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Void> setDefaultLang(@PathVariable("id") Long id) {
|
||||
log.info("[setDefaultLang] 设置为默认语言,id={}", id);
|
||||
i18nLangApplicationService.setDefault(id);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询全部语言
|
||||
*/
|
||||
@RequireResource(I18N_LANG_QUERY)
|
||||
@Operation(summary = "查询全部语言")
|
||||
@GetMapping(value = "/lang/list", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<List<I18nLangDto>> queryAllLang() {
|
||||
log.info("[queryAllLang] 查询全部语言");
|
||||
List<I18nLangDto> list = i18nLangApplicationService.queryAll();
|
||||
return ReqResult.success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新语言排序
|
||||
*/
|
||||
@RequireResource(I18N_LANG_MODIFY)
|
||||
@Operation(summary = "批量更新语言排序")
|
||||
@PostMapping(value = "/lang/sort", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Void> updateLangSort(@RequestBody @Valid List<I18nLangDto> sortList) {
|
||||
log.info("[updateLangSort] 批量更新语言排序,size={}", sortList.size());
|
||||
i18nLangApplicationService.updateSort(sortList);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
// ==================== I18nConfig 相关接口 ====================
|
||||
|
||||
@RequireResource(I18N_LANG_QUERY)
|
||||
@Operation(summary = "查询多语言端列表")
|
||||
@GetMapping(value = "/side/list", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<List<String>> moduleList() {
|
||||
return ReqResult.success(Arrays.stream(I18nSideEnum.values()).map(I18nSideEnum::getSide).toList());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 分页查询多语言配置(条件:side、lang、module 精确匹配,key 对 fieldKey 模糊匹配)
|
||||
*/
|
||||
@RequireResource(I18N_LANG_QUERY)
|
||||
@Operation(summary = "分页查询多语言配置列表")
|
||||
@PostMapping(value = "/config/list", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<IPage<I18nConfigDto>> queryI18nConfigList(@RequestBody(required = false) I18nConfigQueryDto query) {
|
||||
if (query == null) {
|
||||
query = new I18nConfigQueryDto();
|
||||
}
|
||||
log.info("[queryI18nConfigList] 查询多语言配置, query={}", query);
|
||||
IPage<I18nConfigDto> page = i18nApplicationService.queryI18nConfigPage(RequestContext.get().getTenantId(), query);
|
||||
return ReqResult.success(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件与 {@link #queryI18nConfigList} 一致,忽略分页字段,全量导出为 JSON 文件(结构与内置 i18n-config 单行一致)。
|
||||
*/
|
||||
@RequireResource(I18N_LANG_QUERY)
|
||||
@Operation(summary = "按条件导出多语言配置为 JSON 文件")
|
||||
@PostMapping(value = "/config/export", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
|
||||
public byte[] exportI18nConfig(@RequestBody(required = false) I18nConfigQueryDto query,
|
||||
HttpServletResponse response) {
|
||||
if (query == null) {
|
||||
query = new I18nConfigQueryDto();
|
||||
}
|
||||
log.info("[exportI18nConfig] 按条件导出多语言配置, query={}", query);
|
||||
I18nConfigQueryExportDto data = i18nApplicationService.exportI18nConfig(RequestContext.get().getTenantId(), query);
|
||||
String json = JsonSerializeUtil.toJSONString(data);
|
||||
byte[] body = json.getBytes(StandardCharsets.UTF_8);
|
||||
String lang = "";
|
||||
if (StringUtils.isNotBlank(query.getLang())) {
|
||||
lang = I18nLangTagConstraints.tryNormalizeToStoredForm(query.getLang().trim()).orElse(query.getLang().trim());
|
||||
}
|
||||
String fileName = "i18n-config-" + lang + ".json";
|
||||
response.setHeader("Content-Disposition",
|
||||
"attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));
|
||||
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
|
||||
return body;
|
||||
}
|
||||
|
||||
// @RequireResource(I18N_LANG_TRANSLATE)
|
||||
// @Operation(summary = "翻译全部配置(不为空的不会翻译)")
|
||||
// @GetMapping(value = "/config/translateAll", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
// public Flux<ReqResult<I18nConfigDto>> translateI18nConfigList(@RequestParam(value = "lang", required = false) String lang) {
|
||||
// List<I18nConfigDto> list = i18nApplicationService.queryI18nConfigList(RequestContext.get().getTenantId(), "System", null, null, null, lang);
|
||||
// Flux<ReqResult<I18nConfigDto>> flux = Flux.create(listFluxSink -> {
|
||||
// list.forEach(item -> {
|
||||
// if (StringUtils.isNotBlank(item.getValue())) {
|
||||
// return;
|
||||
// }
|
||||
// try {
|
||||
// listFluxSink.next(ReqResult.success(i18nApplicationService.translateMessage(item)));
|
||||
// } catch (Exception e) {
|
||||
// log.warn("翻译异常", e);
|
||||
// listFluxSink.next(ReqResult.error(I18nMessage.ALERT_MSG_098.getKey()));
|
||||
// }
|
||||
// });
|
||||
// listFluxSink.complete();
|
||||
// });
|
||||
// return flux.publishOn(Schedulers.boundedElastic());
|
||||
// }
|
||||
|
||||
// @RequireResource(I18N_LANG_TRANSLATE)
|
||||
// @Operation(summary = "翻译指定配置(会覆盖原有的)")
|
||||
// @PostMapping(value = "/config/translate", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
// public ReqResult<I18nConfigDto> translateI18nConfig(@RequestBody I18nConfigDto i18nConfigDto) {
|
||||
// return ReqResult.success(i18nApplicationService.translateMessage(i18nConfigDto));
|
||||
// }
|
||||
|
||||
@RequireResource(I18N_LANG_TRANSLATE)
|
||||
@Operation(summary = "翻译单个key")
|
||||
@PostMapping(value = "/config/translateKey", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Void> translateForKey(@RequestBody I18nConfigDto i18nConfigDto,
|
||||
@RequestParam("sourceLang") String sourceLang,
|
||||
@RequestParam("targetLang") String targetLang) {
|
||||
Assert.hasText(sourceLang, "The source language cannot be empty.");
|
||||
Assert.hasText(targetLang, "The target language cannot be empty.");
|
||||
String sourceCanon = I18nLangTagConstraints.tryNormalizeToStoredForm(sourceLang)
|
||||
.orElseThrow(() -> new IllegalArgumentException(I18nLangTagConstraints.LANG_TAG_MESSAGE_EN));
|
||||
String targetCanon = I18nLangTagConstraints.tryNormalizeToStoredForm(targetLang)
|
||||
.orElseThrow(() -> new IllegalArgumentException(I18nLangTagConstraints.LANG_TAG_MESSAGE_EN));
|
||||
Assert.isTrue(!I18nLangTagConstraints.sameLanguageTag(sourceCanon, targetCanon),
|
||||
"The source language and target language cannot be the same.");
|
||||
|
||||
Long tenantId = RequestContext.get().getTenantId();
|
||||
i18nApplicationService.translateForKey(tenantId, i18nConfigDto, sourceCanon, targetCanon);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(I18N_LANG_TRANSLATE)
|
||||
@Operation(summary = "批量翻译指定 key 列表")
|
||||
@PostMapping(value = "/config/translateKeys", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Void> translateForKeys(@RequestBody @Valid I18nBatchTranslateDto request) {
|
||||
String sourceCanon = I18nLangTagConstraints.tryNormalizeToStoredForm(request.getSourceLang())
|
||||
.orElseThrow(() -> new IllegalArgumentException(I18nLangTagConstraints.LANG_TAG_MESSAGE_EN));
|
||||
String targetCanon = I18nLangTagConstraints.tryNormalizeToStoredForm(request.getTargetLang())
|
||||
.orElseThrow(() -> new IllegalArgumentException(I18nLangTagConstraints.LANG_TAG_MESSAGE_EN));
|
||||
Assert.isTrue(!I18nLangTagConstraints.sameLanguageTag(sourceCanon, targetCanon),
|
||||
"The source language and target language cannot be the same.");
|
||||
|
||||
Long tenantId = RequestContext.get().getTenantId();
|
||||
i18nApplicationService.translateForKeys(tenantId, request.getKeys(), sourceCanon, targetCanon);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(I18N_LANG_TRANSLATE)
|
||||
@Operation(summary = "翻译所有key")
|
||||
@PostMapping(value = "/config/translateAll", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<ServerSentEvent<ReqResult<Map<String, Object>>>> translateForAll(@RequestParam("sourceLang") String sourceLang, @RequestParam("targetLang") String targetLang) {
|
||||
Assert.hasText(sourceLang, "The source language cannot be empty.");
|
||||
Assert.hasText(targetLang, "The target language cannot be empty.");
|
||||
String sourceCanon = I18nLangTagConstraints.tryNormalizeToStoredForm(sourceLang)
|
||||
.orElseThrow(() -> new IllegalArgumentException(I18nLangTagConstraints.LANG_TAG_MESSAGE_EN));
|
||||
String targetCanon = I18nLangTagConstraints.tryNormalizeToStoredForm(targetLang)
|
||||
.orElseThrow(() -> new IllegalArgumentException(I18nLangTagConstraints.LANG_TAG_MESSAGE_EN));
|
||||
Assert.isTrue(!I18nLangTagConstraints.sameLanguageTag(sourceCanon, targetCanon),
|
||||
"The source language and target language cannot be the same.");
|
||||
Long tenantId = RequestContext.get().getTenantId();
|
||||
List<I18nConfigDto> sourceAll = i18nApplicationService.queryI18nConfigList(tenantId, "System", null, null, null, sourceCanon);
|
||||
Map<String, I18nConfigDto> sourceMap = sourceAll.stream()
|
||||
.filter(item -> StringUtils.isNotBlank(item.getKey()) && StringUtils.isNotBlank(item.getValue()))
|
||||
.collect(Collectors.toMap(I18nConfigDto::getKey, item -> item, (a, b) -> a, LinkedHashMap::new));
|
||||
List<I18nConfigDto> targetAll = i18nApplicationService.queryI18nConfigList(tenantId, "System", null, null, null, targetCanon);
|
||||
List<I18nConfigDto> blankTargetList = targetAll.stream()
|
||||
.filter(item -> StringUtils.isBlank(item.getValue()))
|
||||
.toList();
|
||||
long total = blankTargetList.size();
|
||||
int totalPages = total == 0 ? 0 : (int) ((total + TRANSLATE_BATCH_SIZE - 1) / TRANSLATE_BATCH_SIZE);
|
||||
List<Map<String, Object>> failedItems = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
Flux<ServerSentEvent<ReqResult<Map<String, Object>>>> progressStream = Flux
|
||||
.range(1, totalPages)
|
||||
.concatMap(pageNo -> Mono.fromCallable(() -> {
|
||||
RequestContext.setThreadTenantId(tenantId);
|
||||
try {
|
||||
int fromIndex = Math.toIntExact((long) (pageNo - 1) * TRANSLATE_BATCH_SIZE);
|
||||
int toIndex = Math.min(fromIndex + Math.toIntExact(TRANSLATE_BATCH_SIZE), blankTargetList.size());
|
||||
List<I18nConfigDto> targetChunk = blankTargetList.subList(fromIndex, toIndex);
|
||||
List<I18nConfigDto> sourceChunk = targetChunk.stream()
|
||||
.map(item -> sourceMap.get(item.getKey()))
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
try {
|
||||
if (!sourceChunk.isEmpty()) {
|
||||
i18nApplicationService.translateForKeysBatch(tenantId, sourceChunk, sourceCanon, targetCanon);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("translateAll 单页翻译失败, pageNo={}, sourceLang={}, targetLang={}",
|
||||
pageNo, sourceCanon, targetCanon, e);
|
||||
Map<String, Object> failed = new HashMap<>();
|
||||
failed.put("pageNo", pageNo);
|
||||
failed.put("error", e.getMessage());
|
||||
failed.put("count", targetChunk.size());
|
||||
failed.put("keys", targetChunk.stream().map(I18nConfigDto::getKey).toList());
|
||||
failedItems.add(failed);
|
||||
}
|
||||
long completed = Math.min((long) pageNo * TRANSLATE_BATCH_SIZE, total);
|
||||
Map<String, Object> payload = new java.util.HashMap<>();
|
||||
payload.put("phase", "progress");
|
||||
payload.put("total", total);
|
||||
payload.put("completed", completed);
|
||||
payload.put("percent", total == 0 ? 100 : (int) (completed * 100 / total));
|
||||
return ServerSentEvent.<ReqResult<Map<String, Object>>>builder()
|
||||
.event("progress")
|
||||
.data(ReqResult.success(payload))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.warn("translateAll 单页处理失败, pageNo={}, sourceLang={}, targetLang={}",
|
||||
pageNo, sourceCanon, targetCanon, e);
|
||||
Map<String, Object> failed = new HashMap<>();
|
||||
failed.put("pageNo", pageNo);
|
||||
failed.put("error", e.getMessage());
|
||||
failed.put("count", 0);
|
||||
failed.put("keys", List.of());
|
||||
failedItems.add(failed);
|
||||
long completed = Math.min((long) pageNo * TRANSLATE_BATCH_SIZE, total);
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("phase", "progress");
|
||||
payload.put("total", total);
|
||||
payload.put("completed", completed);
|
||||
payload.put("percent", total == 0 ? 100 : (int) (completed * 100 / total));
|
||||
return ServerSentEvent.<ReqResult<Map<String, Object>>>builder()
|
||||
.event("progress")
|
||||
.data(ReqResult.success(payload))
|
||||
.build();
|
||||
} finally {
|
||||
RequestContext.remove();
|
||||
}
|
||||
}).subscribeOn(Schedulers.boundedElastic()));
|
||||
Flux<ServerSentEvent<ReqResult<Map<String, Object>>>> doneStream = Mono.fromCallable(() -> {
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("phase", "done");
|
||||
payload.put("success", failedItems.isEmpty());
|
||||
payload.put("total", total);
|
||||
payload.put("completed", total);
|
||||
payload.put("percent", 100);
|
||||
payload.put("failedCount", failedItems.size());
|
||||
payload.put("failedItems", failedItems);
|
||||
return ServerSentEvent.<ReqResult<Map<String, Object>>>builder()
|
||||
.event("done")
|
||||
.data(ReqResult.success(payload))
|
||||
.build();
|
||||
}).flux();
|
||||
Flux<ServerSentEvent<ReqResult<Map<String, Object>>>> businessStream = progressStream.concatWith(doneStream);
|
||||
Flux<ServerSentEvent<ReqResult<Map<String, Object>>>> heartbeatStream = Flux.interval(Duration.ofSeconds(10))
|
||||
.map(seq -> ServerSentEvent.<ReqResult<Map<String, Object>>>builder()
|
||||
.event("ping")
|
||||
.data(ReqResult.success(Map.of("phase", "ping", "ts", System.currentTimeMillis())))
|
||||
.build())
|
||||
.takeUntilOther(businessStream.ignoreElements());
|
||||
return Flux.merge(businessStream, heartbeatStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增或更新多语言配置åå
|
||||
*/
|
||||
@RequireResource(I18N_LANG_MODIFY)
|
||||
@Operation(summary = "新增或更新多语言配置")
|
||||
@PostMapping(value = "/config/addOrUpdate", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Void> addOrUpdateI18nConfig(@RequestBody @Valid I18nConfigDto dto) {
|
||||
log.info("[addOrUpdateI18nConfig] 新增或更新多语言配置,type={}, module={}, fieldKey={}",
|
||||
dto.getType(), dto.getModule(), dto.getKey());
|
||||
i18nApplicationService.addOrUpdateI18nConfig(dto);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(I18N_LANG_MODIFY)
|
||||
@Operation(summary = "批量新增或更新多语言配置")
|
||||
@PostMapping(value = "/config/batchAddOrUpdate", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Void> batchAddOrUpdateI18nConfig(@RequestBody @Valid List<I18nConfigDto> configs) {
|
||||
Assert.notEmpty(configs, "The configuration list cannot be empty.");
|
||||
Assert.isTrue(configs.size() <= 10000, "Supports up to 10,000 configurations per batch.");
|
||||
i18nApplicationService.batchAddOrUpdateI18nConfig(configs);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(I18N_LANG_DELETE)
|
||||
@Operation(summary = "批量删除多语言配置")
|
||||
@PostMapping(value = "/config/batchDelete", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Void> batchDeleteI18nConfig(@RequestBody List<I18nConfigDto> configs) {
|
||||
Assert.notEmpty(configs, "The configuration list cannot be empty.");
|
||||
i18nApplicationService.batchDeleteI18nConfig(configs);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@SaasAdmin
|
||||
@Operation(hidden = true, summary = "比对两个版本的 i18n 配置并生成新增/变更差异文件")
|
||||
@GetMapping(value = "/config/diff", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<String> diffConfig(@RequestParam String fromVersion, @RequestParam String toVersion) {
|
||||
try {
|
||||
var split = i18nConfigDiffService.generateDiff(fromVersion, toVersion);
|
||||
String baseDir = "./" + I18nSyncConstants.I18N_JSON_EXPORT_BASE_PATH + "/";
|
||||
|
||||
String addFileName = I18nSyncConstants.buildI18nConfigAddFileName(toVersion);
|
||||
writeDiffJsonFile(baseDir + addFileName, split.getAddRows());
|
||||
|
||||
String updateFileName = I18nSyncConstants.buildI18nConfigUpdateFileName(toVersion);
|
||||
writeDiffJsonFile(baseDir + updateFileName, split.getUpdateRows());
|
||||
|
||||
return ReqResult.success("已生成差异文件: " + addFileName + "(新增 " + split.getAddRows().size()
|
||||
+ " 条), " + updateFileName + "(变更 " + split.getUpdateRows().size() + " 条)");
|
||||
} catch (IOException e) {
|
||||
return ReqResult.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void writeDiffJsonFile(String saveToPath, Object rows) throws IOException {
|
||||
Path path = Paths.get(saveToPath).toAbsolutePath();
|
||||
Files.createDirectories(path.getParent());
|
||||
Files.writeString(path, JsonSerializeUtil.toJSONString(rows), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@SaasAdmin
|
||||
@Operation(hidden = true, summary = "将指定版本的新增/变更差异配置导入到租户")
|
||||
@GetMapping(value = "/config/import-diff", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<String> importDiffConfig(@RequestParam Long tenantId, @RequestParam String version) {
|
||||
Tenant tenant = tenantService.getById(tenantId);
|
||||
if (tenant == null) {
|
||||
return ReqResult.error("租户不存在");
|
||||
}
|
||||
i18nImportService.addConfigToTenant(tenant, version);
|
||||
i18nImportService.updateConfigToTenant(tenant, version);
|
||||
return ReqResult.success("差异配置导入成功(新增 + 变更)");
|
||||
}
|
||||
|
||||
@SaasAdmin
|
||||
@Operation(hidden = true)
|
||||
@GetMapping(value = "/exportToFile", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Object> exportToFile(@RequestParam String version, @RequestParam List<String> langs) {
|
||||
try {
|
||||
I18nConfigExportDto dto = i18nExportService.exportConfig(version, langs);
|
||||
String json = JsonSerializeUtil.toJSONString(dto.getConfigs());
|
||||
String saveToPath = "./" + I18nSyncConstants.I18N_JSON_EXPORT_BASE_PATH + "/"
|
||||
+ I18nSyncConstants.buildI18nConfigExportFileName(version);
|
||||
Path path = Paths.get(saveToPath).toAbsolutePath();
|
||||
Files.createDirectories(path.getParent());
|
||||
Files.writeString(path, json, StandardCharsets.UTF_8);
|
||||
return ReqResult.success("已导出");
|
||||
} catch (IOException e) {
|
||||
return ReqResult.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@SaasAdmin
|
||||
@Operation(hidden = true)
|
||||
@GetMapping(value = "/importFromFile", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<String> importFromFile(@RequestParam Long tenantId, @RequestParam String version) {
|
||||
Tenant tenant = tenantService.getById(tenantId);
|
||||
if (tenant == null) {
|
||||
return ReqResult.error("租户不存在");
|
||||
}
|
||||
i18nImportService.importLangToTenant(tenant, version);
|
||||
i18nImportService.importConfigToTenant(tenant, version);
|
||||
return ReqResult.success("导入成功");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.MultiFormatWriter;
|
||||
import com.google.zxing.client.j2se.MatrixToImageWriter;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.xspaceagi.file.sdk.IFileAccessService;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
|
||||
import com.xspaceagi.system.spec.enums.HttpStatusEnum;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
|
||||
import com.xspaceagi.system.web.emoj.IconGenerator;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
|
||||
@Tag(name = "文件上传")
|
||||
@Slf4j
|
||||
@RestController
|
||||
public class IconGenerateController {
|
||||
|
||||
private static final Color[] rgbs = new Color[]{
|
||||
new Color(117, 189, 108),
|
||||
new Color(77, 87, 224),
|
||||
new Color(63, 118, 247),
|
||||
new Color(238, 193, 79),
|
||||
new Color(160, 109, 237)
|
||||
};
|
||||
|
||||
private static final Map<String, Color> componentColorMap = Map.of(
|
||||
"workflow", new Color(96, 183, 93),
|
||||
"plugin", new Color(75, 64, 222),
|
||||
"knowledge", new Color(228, 148, 51),
|
||||
"table", new Color(255, 187, 0)
|
||||
);
|
||||
|
||||
@Value("${file.uploadFolder:}")
|
||||
private String uploadFolder;
|
||||
|
||||
@Resource
|
||||
private IFileAccessService iFileAccessService;
|
||||
|
||||
private final IconGenerator iconGenerator = new IconGenerator();
|
||||
|
||||
|
||||
@GetMapping("/api/file/**")
|
||||
public void downloadFile(HttpServletRequest request, HttpServletResponse response) {
|
||||
if (!RequestContext.get().isLogin()) {
|
||||
try {
|
||||
iFileAccessService.checkFileUrlAk(request.getRequestURI(), request.getParameter("ak"));
|
||||
} catch (Exception e) {
|
||||
throw BizException.of(HttpStatusEnum.UNAUTHORIZED, ErrorCodeEnum.UNAUTHORIZED,
|
||||
BizExceptionCodeEnum.systemUnauthorizedOrSessionExpired);
|
||||
}
|
||||
}
|
||||
try {
|
||||
String key = request.getRequestURI().substring("/api/file/".length());
|
||||
String path0 = uploadFolder.endsWith("/") ? uploadFolder + key : uploadFolder + "/" + key;
|
||||
Path path = Paths.get(path0);
|
||||
if (Files.exists(path)) {
|
||||
//根据key的后缀设置contentType
|
||||
String contentType = Files.probeContentType(path);
|
||||
response.setContentType(contentType);
|
||||
Files.copy(path, response.getOutputStream());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("文件下载失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping(path = "/api/logo/**", produces = "image/png")
|
||||
public byte[] defaultLogo(HttpServletRequest request) throws IOException {
|
||||
String key = request.getRequestURI().substring("/api/logo/".length());
|
||||
String text = URLDecoder.decode(key, StandardCharsets.UTF_8);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
BufferedImage image = iconGenerator.generateIcon(text, 200);
|
||||
ImageIO.write(image, "png", baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@GetMapping(path = "/api/logo/{type}/**", produces = "image/png")
|
||||
public byte[] defaultLogo0(@PathVariable String type, HttpServletRequest request) throws IOException {
|
||||
String key = request.getRequestURI().substring(("/api/logo/" + type).length());
|
||||
String text = URLDecoder.decode(key, StandardCharsets.UTF_8);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
BufferedImage image = iconGenerator.generateIcon(text, 200);
|
||||
ImageIO.write(image, "png", baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@GetMapping("/api/qr/{base64text}")
|
||||
public void generateQrCode(@PathVariable("base64text") String base64text, HttpServletResponse response) {
|
||||
byte[] decode = Base64.decode(base64text);
|
||||
try {
|
||||
BitMatrix matrix = new MultiFormatWriter().encode(new String(decode), BarcodeFormat.QR_CODE, 300, 300);
|
||||
MatrixToImageWriter.writeToStream(matrix, "PNG", response.getOutputStream());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
import com.xspaceagi.eco.market.sdk.reponse.ClientSecretResponse;
|
||||
import com.xspaceagi.system.application.dto.*;
|
||||
import com.xspaceagi.system.application.service.AuthService;
|
||||
import com.xspaceagi.system.application.service.NotifyMessageApplicationService;
|
||||
import com.xspaceagi.system.infra.dao.entity.NotifyMessage;
|
||||
import com.xspaceagi.system.infra.dao.entity.User;
|
||||
import com.xspaceagi.system.infra.rpc.ClientSecretRpcService;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.utils.HttpClient;
|
||||
import com.xspaceagi.system.web.dto.EventRespDto;
|
||||
import com.xspaceagi.system.web.dto.PullMessageAckDto;
|
||||
import com.xspaceagi.system.web.dto.PullMessageRequestDto;
|
||||
import com.xspaceagi.system.web.dto.PullMessageResponseDto;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "消息通知相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/notify")
|
||||
public class NotifyMessageController {
|
||||
|
||||
@Resource
|
||||
private NotifyMessageApplicationService notifyMessageApplicationService;
|
||||
|
||||
@Resource
|
||||
private AuthService authService;
|
||||
|
||||
@Resource
|
||||
private HttpClient httpClient;
|
||||
|
||||
@Value("${installation-source}")
|
||||
private String installationSource;
|
||||
|
||||
@Value("${app.version}")
|
||||
private String version;
|
||||
|
||||
@Value("${eco-market.server.base-url}")
|
||||
private String cloudApi;
|
||||
|
||||
@Resource
|
||||
private ClientSecretRpcService clientSecretRpcService;
|
||||
|
||||
|
||||
@Operation(summary = "查询用户消息列表")
|
||||
@RequestMapping(path = "/message/list", method = RequestMethod.POST)
|
||||
public ReqResult<List<NotifyMessageDto>> listQuery(@RequestBody NotifyMessageQueryDto messageQueryDto) {
|
||||
messageQueryDto.setUserId(RequestContext.get().getUserId());
|
||||
return ReqResult.success(notifyMessageApplicationService.queryNotifyMessageList(messageQueryDto));
|
||||
}
|
||||
|
||||
private void pullMessage() {
|
||||
UserDto userDto = (UserDto) RequestContext.get().getUser();
|
||||
TenantConfigDto tenantConfigDto = (TenantConfigDto) RequestContext.get().getTenantConfig();
|
||||
if (userDto.getRole() == User.Role.Admin) {
|
||||
// 管理员拉取云端消息
|
||||
ClientSecretResponse clientSecretResponse = clientSecretRpcService.queryClientSecret(tenantConfigDto.getTenantId());
|
||||
if (clientSecretResponse != null) {
|
||||
PullMessageRequestDto pullMessageRequestDto = new PullMessageRequestDto();
|
||||
pullMessageRequestDto.setTenantName(tenantConfigDto.getSiteName());
|
||||
pullMessageRequestDto.setSiteUrl(tenantConfigDto.getSiteUrl());
|
||||
pullMessageRequestDto.setClientId(clientSecretResponse.getClientId());
|
||||
pullMessageRequestDto.setClientSecret(clientSecretResponse.getClientSecret());
|
||||
pullMessageRequestDto.setInstallationSource(installationSource);
|
||||
pullMessageRequestDto.setVersion(version);
|
||||
pullMessageRequestDto.setUser(userDto.getNickName() != null ? userDto.getNickName() : userDto.getUserName());
|
||||
pullMessageRequestDto.setUid(userDto.getUid());
|
||||
String content = httpClient.post(cloudApi + "/api/message/pull", JSON.toJSONString(pullMessageRequestDto), Map.of("Authorization", "Bearer " + clientSecretResponse.getClientSecret()));
|
||||
ReqResult<List<PullMessageResponseDto>> pullMessageResponseDtos = JSON.parseObject(content, new TypeReference<ReqResult<List<PullMessageResponseDto>>>() {
|
||||
});
|
||||
if (pullMessageResponseDtos != null && CollectionUtils.isNotEmpty(pullMessageResponseDtos.getData())) {
|
||||
pullMessageResponseDtos.getData().forEach(pullMessageResponseDto -> notifyMessageApplicationService.sendNotifyMessage(SendNotifyMessageDto.builder()
|
||||
.scope(NotifyMessage.MessageScope.System)
|
||||
.content(pullMessageResponseDto.getContent())
|
||||
.senderId(RequestContext.get().getUserId())
|
||||
.userIds(Arrays.asList(RequestContext.get().getUserId()))
|
||||
.build()));
|
||||
PullMessageAckDto pullMessageAckDto = new PullMessageAckDto();
|
||||
pullMessageAckDto.setClientId(clientSecretResponse.getClientId());
|
||||
pullMessageAckDto.setClientSecret(clientSecretResponse.getClientSecret());
|
||||
pullMessageAckDto.setUid(userDto.getUid());
|
||||
pullMessageAckDto.setMessageIds(pullMessageResponseDtos.getData().stream().map(PullMessageResponseDto::getMessageId).collect(Collectors.toList()));
|
||||
httpClient.post(cloudApi + "/api/message/ack", JSON.toJSONString(pullMessageAckDto), Map.of("Authorization", "Bearer " + clientSecretResponse.getClientSecret()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "查询用户未读消息数量")
|
||||
@RequestMapping(path = "/message/unread/count", method = RequestMethod.GET)
|
||||
public ReqResult<Long> unReadCount() {
|
||||
return ReqResult.success(notifyMessageApplicationService.countUnreadNotifyMessage(RequestContext.get().getUserId()));
|
||||
}
|
||||
|
||||
@Operation(summary = "清除所有未读消息")
|
||||
@RequestMapping(path = "/message/unread/clear", method = RequestMethod.GET)
|
||||
public ReqResult<Void> unReadClear() {
|
||||
notifyMessageApplicationService.updateAllUnreadNotifyMessage(RequestContext.get().getUserId());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "更新指定未读消息为已读", description = "消息ID列表作为POST参数,例如 [1,2,4]")
|
||||
@RequestMapping(path = "/message/read", method = RequestMethod.POST)
|
||||
public ReqResult<Void> read(@RequestBody @Schema(description = "消息ID列表") List<Long> ids) {
|
||||
notifyMessageApplicationService.updateReadStatus(RequestContext.get().getUserId(), ids);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "事件通知查询接口")
|
||||
@RequestMapping(path = "/event/collect/batch", method = RequestMethod.GET)
|
||||
public ReqResult<EventRespDto> collectEvent() {
|
||||
String clientId = authService.getClientId(RequestContext.get().getUserId(), RequestContext.get().getToken());
|
||||
List<EventDto<?>> eventDtos = notifyMessageApplicationService.collectEventList(RequestContext.get().getUserId(), clientId);
|
||||
EventRespDto eventRespDto = new EventRespDto();
|
||||
eventRespDto.setHasEvent(!eventDtos.isEmpty());
|
||||
eventRespDto.setEventList(eventDtos);
|
||||
eventRespDto.setVersion(version);
|
||||
try {
|
||||
// 之前的逻辑
|
||||
pullMessage();
|
||||
} catch (Exception e) {
|
||||
log.error("触发消息拉取事件失败", e);
|
||||
}
|
||||
return ReqResult.success(eventRespDto);
|
||||
}
|
||||
|
||||
@Operation(summary = "事件通知清除接口")
|
||||
@RequestMapping(path = "/event/clear", method = RequestMethod.GET)
|
||||
public ReqResult<Void> clearEvent() {
|
||||
String clientId = authService.getClientId(RequestContext.get().getUserId(), RequestContext.get().getToken());
|
||||
notifyMessageApplicationService.clearEventList(RequestContext.get().getUserId(), clientId);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
import com.xspaceagi.system.infra.dao.entity.OpenApiDefinition;
|
||||
import com.xspaceagi.system.infra.dao.entity.User;
|
||||
import com.xspaceagi.system.infra.dao.service.OpenApiDefinitionService;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.enums.PermissionTargetTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "开放API", description = "开放API相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/system/open-api")
|
||||
public class OpenApiController {
|
||||
|
||||
@Resource
|
||||
private OpenApiDefinitionService openApiDefinitionService;
|
||||
|
||||
@Operation(summary = "查询开放API列表")
|
||||
@GetMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<List<OpenApiDefinition>> queryAll(@RequestParam("targetType") Integer targetType) {
|
||||
if (PermissionTargetTypeEnum.isInValid(targetType)) {
|
||||
return ReqResult.error("参数targetType错误");
|
||||
}
|
||||
List<OpenApiDefinition> list = openApiDefinitionService.queryAll();
|
||||
PermissionTargetTypeEnum targetTypeEnum = PermissionTargetTypeEnum.getByCode(targetType);
|
||||
|
||||
if (targetTypeEnum == PermissionTargetTypeEnum.GROUP) {
|
||||
List<OpenApiDefinition> filteredList = filterUserRoleApis(list);
|
||||
log.info("[queryAll] List open APIs, targetType={}, size={}", targetType, filteredList.size());
|
||||
return ReqResult.success(filteredList);
|
||||
}
|
||||
log.info("[queryAll] List open APIs, targetType={}, size={}", targetType, list == null ? 0 : list.size());
|
||||
return ReqResult.success(list);
|
||||
}
|
||||
|
||||
private List<OpenApiDefinition> filterUserRoleApis(List<OpenApiDefinition> list) {
|
||||
List<OpenApiDefinition> result = new ArrayList<>();
|
||||
if (list == null) {
|
||||
return result;
|
||||
}
|
||||
for (OpenApiDefinition item : list) {
|
||||
OpenApiDefinition copied = copyAndFilter(item);
|
||||
if (copied != null) {
|
||||
result.add(copied);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private OpenApiDefinition copyAndFilter(OpenApiDefinition item) {
|
||||
if (item == null) {
|
||||
return null;
|
||||
}
|
||||
User.Role role = item.getRole();
|
||||
if (role != null && role != User.Role.User) {
|
||||
return null;
|
||||
}
|
||||
OpenApiDefinition copied = new OpenApiDefinition();
|
||||
copied.setName(item.getName());
|
||||
copied.setKey(item.getKey());
|
||||
copied.setRole(item.getRole());
|
||||
copied.setPath(item.getPath());
|
||||
|
||||
if (item.getApiList() != null) {
|
||||
List<OpenApiDefinition> children = new ArrayList<>();
|
||||
for (OpenApiDefinition child : item.getApiList()) {
|
||||
OpenApiDefinition copiedChild = copyAndFilter(child);
|
||||
if (copiedChild != null) {
|
||||
children.add(copiedChild);
|
||||
}
|
||||
}
|
||||
copied.setApiList(children);
|
||||
}
|
||||
return copied;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
import com.xspaceagi.system.application.dto.SpaceDto;
|
||||
import com.xspaceagi.system.application.dto.SpaceUserDto;
|
||||
import com.xspaceagi.system.application.dto.TenantConfigDto;
|
||||
import com.xspaceagi.system.application.dto.UserDto;
|
||||
import com.xspaceagi.system.application.service.SpaceApplicationService;
|
||||
import com.xspaceagi.system.application.service.UserApplicationService;
|
||||
import com.xspaceagi.system.infra.dao.entity.Space;
|
||||
import com.xspaceagi.system.infra.dao.entity.SpaceUser;
|
||||
import com.xspaceagi.system.sdk.permission.SpacePermissionService;
|
||||
import com.xspaceagi.system.sdk.permission.IUserDataPermissionRpcService;
|
||||
import com.xspaceagi.system.sdk.service.dto.UserDataPermissionDto;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
|
||||
import com.xspaceagi.system.spec.utils.I18nUtil;
|
||||
import com.xspaceagi.system.web.dto.*;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.*;
|
||||
|
||||
@Tag(name = "工作空间相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/space")
|
||||
@Slf4j
|
||||
public class SpaceController {
|
||||
|
||||
@Resource
|
||||
private SpaceApplicationService spaceApplicationService;
|
||||
|
||||
@Resource
|
||||
private SpacePermissionService spacePermissionService;
|
||||
|
||||
@Resource
|
||||
private IUserDataPermissionRpcService userDataPermissionRpcService;
|
||||
|
||||
@Resource
|
||||
private UserApplicationService userApplicationService;
|
||||
|
||||
@RequireResource(SPACE_CREATE)
|
||||
@Operation(summary = "创建团队空间接口")
|
||||
@RequestMapping(path = "/add", method = RequestMethod.POST)
|
||||
public ReqResult<Long> add(@RequestBody @Valid SpaceAddDto spaceAddDto) {
|
||||
// 创建团队空间数量限制
|
||||
UserDataPermissionDto userDataPermission = userDataPermissionRpcService.getUserDataPermission(RequestContext.get().getUserId());
|
||||
userDataPermission.checkMaxSpaceCount(spaceApplicationService.countUserCreatedTeamSpaces(RequestContext.get().getUserId()).intValue());
|
||||
SpaceDto spaceDto = SpaceDto.builder()
|
||||
.name(spaceAddDto.getName())
|
||||
.description(spaceAddDto.getDescription())
|
||||
.icon(spaceAddDto.getIcon())
|
||||
.creatorId(RequestContext.get().getUserId())
|
||||
.type(Space.Type.Team)
|
||||
.build();
|
||||
return ReqResult.success(spaceApplicationService.add(spaceDto));
|
||||
}
|
||||
|
||||
@RequireResource(SPACE_MODIFY)
|
||||
@Operation(summary = "更新团队空间接口")
|
||||
@RequestMapping(path = "/update", method = RequestMethod.POST)
|
||||
public ReqResult<Void> update(@RequestBody @Valid SpaceUpdateDto spaceUpdateDto) {
|
||||
spacePermissionService.checkSpaceAdminPermission(spaceUpdateDto.getId());
|
||||
spaceApplicationService.update(SpaceDto.builder().id(spaceUpdateDto.getId()).icon(spaceUpdateDto.getIcon())
|
||||
.name(spaceUpdateDto.getName()).description(spaceUpdateDto.getDescription())
|
||||
.receivePublish(spaceUpdateDto.getReceivePublish()).allowDevelop(spaceUpdateDto.getAllowDevelop()).build());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(SPACE_DELETE)
|
||||
@Operation(summary = "删除空间接口")
|
||||
@RequestMapping(path = "/delete/{spaceId}", method = RequestMethod.POST)
|
||||
public ReqResult<Void> delete(@PathVariable Long spaceId) {
|
||||
SpaceDto spaceDto = spaceApplicationService.queryById(spaceId);
|
||||
if (spaceDto != null && spaceDto.getType() == Space.Type.Personal) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemPersonalSpaceDeleteForbidden);
|
||||
}
|
||||
spacePermissionService.checkSpaceOwnerPermission(spaceId);
|
||||
spaceApplicationService.delete(spaceId);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
//@RequireResource(SPACE_QUERY_DETAIL)
|
||||
@Operation(summary = "查询指定空间信息")
|
||||
@RequestMapping(path = "/get/{spaceId}", method = RequestMethod.GET)
|
||||
public ReqResult<SpaceDto> getOne(@PathVariable Long spaceId) {
|
||||
spacePermissionService.checkSpaceUserPermission(spaceId);
|
||||
SpaceDto spaceDto = spaceApplicationService.queryById(spaceId);
|
||||
SpaceUserDto spaceUserDto = spaceApplicationService.querySpaceUser(spaceId, RequestContext.get().getUserId());
|
||||
if (spaceUserDto != null) {
|
||||
spaceDto.setCurrentUserRole(spaceUserDto.getRole());
|
||||
}
|
||||
UserDto userDto = userApplicationService.queryById(spaceDto.getCreatorId());
|
||||
if (userDto != null) {
|
||||
spaceDto.setCreatorName(userDto.getUserName());
|
||||
}
|
||||
I18nUtil.replaceSystemMessage(spaceDto);
|
||||
return ReqResult.success(spaceDto);
|
||||
}
|
||||
|
||||
@Operation(summary = "跳转到用户空间页面")
|
||||
@RequestMapping(path = "/redirect", method = RequestMethod.GET)
|
||||
public void redirect(HttpServletResponse response) throws IOException {
|
||||
SpaceDto spaceDto = spaceApplicationService.queryListByUserId(RequestContext.get().getUserId()).stream().filter(spaceDto1 -> spaceDto1.getType() == Space.Type.Personal).findFirst().orElse(null);
|
||||
TenantConfigDto tenantConfigDto = (TenantConfigDto) RequestContext.get().getTenantConfig();
|
||||
if (spaceDto == null) {
|
||||
response.sendRedirect(tenantConfigDto.getCasClientHostUrl());
|
||||
}
|
||||
String redirectUrl = tenantConfigDto.getCasClientHostUrl().endsWith("/") ? tenantConfigDto.getCasClientHostUrl() + "space/" + spaceDto.getId() + "/develop" :
|
||||
tenantConfigDto.getCasClientHostUrl() + "/space/" + spaceDto.getId() + "/develop";
|
||||
response.sendRedirect(redirectUrl);
|
||||
}
|
||||
|
||||
//@RequireResource(SPACE_QUERY_LIST)
|
||||
@Operation(summary = "查询用户空间列表")
|
||||
@RequestMapping(path = "/list", method = RequestMethod.GET)
|
||||
public ReqResult<List<SpaceDto>> list() {
|
||||
List<SpaceDto> spaceDtoList = spaceApplicationService.queryListByUserId(RequestContext.get().getUserId());
|
||||
spaceDtoList.forEach(spaceDto -> {
|
||||
SpaceUserDto spaceUserDto = spaceApplicationService.querySpaceUser(spaceDto.getId(), RequestContext.get().getUserId());
|
||||
if (spaceUserDto != null) {
|
||||
spaceDto.setCurrentUserRole(spaceUserDto.getRole());
|
||||
}
|
||||
});
|
||||
I18nUtil.replaceSystemMessage(spaceDtoList);
|
||||
return ReqResult.success(spaceDtoList);
|
||||
}
|
||||
|
||||
@RequireResource(SPACE_ADD_USER)
|
||||
@Operation(summary = "增加团队成员接口")
|
||||
@RequestMapping(path = "/user/add", method = RequestMethod.POST)
|
||||
public ReqResult<Void> addSpaceUser(@RequestBody @Valid List<SpaceUserAddDto> spaceUserAddDtos) {
|
||||
if (CollectionUtils.isEmpty(spaceUserAddDtos)) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemParamRequired);
|
||||
}
|
||||
spaceUserAddDtos.forEach(spaceUserAddDto -> {
|
||||
if (spaceUserAddDto.getRole() == SpaceUser.Role.Owner) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemCannotAddOwner);
|
||||
}
|
||||
spacePermissionService.checkSpaceAdminPermission(spaceUserAddDto.getSpaceId());
|
||||
});
|
||||
spaceUserAddDtos.forEach(spaceUserAddDto -> {
|
||||
SpaceDto spaceDto = spaceApplicationService.queryById(spaceUserAddDto.getSpaceId());
|
||||
// 团队空间才允许添加成员
|
||||
if (spaceDto != null && spaceDto.getType() != Space.Type.Personal) {
|
||||
SpaceUserDto spaceUserDto = new SpaceUserDto();
|
||||
spaceUserDto.setUserId(spaceUserAddDto.getUserId());
|
||||
spaceUserDto.setRole(spaceUserAddDto.getRole());
|
||||
spaceUserDto.setSpaceId(spaceUserAddDto.getSpaceId());
|
||||
spaceApplicationService.addSpaceUser(spaceUserDto);
|
||||
}
|
||||
});
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "更新团队成员角色接口")
|
||||
@RequestMapping(path = "/user/role/update", method = RequestMethod.POST)
|
||||
public ReqResult<Void> updateSpaceUserRole(@RequestBody @Valid SpaceUserRoleUpdateDto spaceUserRoleUpdateDto) {
|
||||
if (spaceUserRoleUpdateDto.getRole() == SpaceUser.Role.Owner) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemCannotUpdateToOwner);
|
||||
}
|
||||
spacePermissionService.checkSpaceAdminPermission(spaceUserRoleUpdateDto.getSpaceId());
|
||||
spaceApplicationService.updateSpaceUserRole(spaceUserRoleUpdateDto.getSpaceId(), spaceUserRoleUpdateDto.getUserId(), spaceUserRoleUpdateDto.getRole());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(SPACE_DELETE_USER)
|
||||
@Operation(summary = "删除团队成员接口")
|
||||
@RequestMapping(path = "/user/delete", method = RequestMethod.POST)
|
||||
public ReqResult<Void> deleteUser(@RequestBody @Valid SpaceUserDeleteDto spaceUserDeleteDto) {
|
||||
spacePermissionService.checkSpaceAdminPermission(spaceUserDeleteDto.getSpaceId());
|
||||
if (spaceUserDeleteDto.getUserId().equals(RequestContext.get().getUserId())) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemCannotDeleteSelf);
|
||||
}
|
||||
SpaceDto spaceDto = spaceApplicationService.queryById(spaceUserDeleteDto.getSpaceId());
|
||||
if (spaceDto == null) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemSpaceNotFound);
|
||||
}
|
||||
if (spaceDto.getCreatorId().equals(spaceUserDeleteDto.getUserId())) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemCannotDeleteSpaceCreator);
|
||||
}
|
||||
spaceApplicationService.deleteSpaceUser(spaceUserDeleteDto.getSpaceId(), spaceUserDeleteDto.getUserId());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(SPACE_QUERY_USER_LIST)
|
||||
@Operation(summary = "查询团队成员列表接口")
|
||||
@RequestMapping(path = "/user/list", method = RequestMethod.POST)
|
||||
public ReqResult<List<SpaceUserDto>> getTeamUserList(@RequestBody @Valid SpaceUserQueryDto spaceUserQueryDto) {
|
||||
spacePermissionService.checkSpaceUserPermission(spaceUserQueryDto.getSpaceId());
|
||||
List<SpaceUserDto> teamUserDtoList = spaceApplicationService.querySpaceUserList(spaceUserQueryDto.getSpaceId());
|
||||
teamUserDtoList = teamUserDtoList.stream().filter(spaceUserDto -> {
|
||||
boolean flag = true;
|
||||
if (StringUtils.isNotBlank(spaceUserQueryDto.getKw())) {
|
||||
if (!spaceUserDto.getUserName().contains(spaceUserQueryDto.getKw()) && !spaceUserDto.getNickName().contains(spaceUserQueryDto.getKw())) {
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
if (spaceUserQueryDto.getRole() != null && spaceUserDto.getRole() != spaceUserQueryDto.getRole()) {
|
||||
flag = false;
|
||||
}
|
||||
return flag;
|
||||
}).toList();
|
||||
return ReqResult.success(teamUserDtoList);
|
||||
}
|
||||
|
||||
@RequireResource(SPACE_TRANSFER)
|
||||
@Operation(summary = "空间转让接口")
|
||||
@RequestMapping(path = "/transfer", method = RequestMethod.POST)
|
||||
public ReqResult<Void> transfer(@RequestBody @Valid SpaceTransferDto spaceTransferDto) {
|
||||
spacePermissionService.checkSpaceOwnerPermission(spaceTransferDto.getSpaceId());
|
||||
spaceApplicationService.transfer(spaceTransferDto.getSpaceId(), spaceTransferDto.getTargetUserId());
|
||||
return ReqResult.success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
import com.xspaceagi.eco.market.sdk.reponse.ClientSecretResponse;
|
||||
import com.xspaceagi.system.infra.rpc.ClientSecretRpcService;
|
||||
import com.xspaceagi.system.sdk.service.UserAccessKeyApiService;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.web.dto.SttResult;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.UUID;
|
||||
|
||||
@Tag(name = "语音转换相关接口")
|
||||
@Slf4j
|
||||
@RestController
|
||||
public class SpeechApiController {
|
||||
|
||||
static {
|
||||
System.setProperty("jdk.httpclient.keepalive.timeout", "0");
|
||||
}
|
||||
|
||||
@Value("${stt.api.base-url:}")
|
||||
private String baseUrl;
|
||||
|
||||
@Resource
|
||||
private UserAccessKeyApiService userAccessKeyApiService;
|
||||
|
||||
@Resource
|
||||
private ClientSecretRpcService clientSecretRpcService;
|
||||
|
||||
private final HttpClient client = HttpClient.newBuilder().connectTimeout(java.time.Duration.ofSeconds(10)).version(HttpClient.Version.HTTP_1_1).build();
|
||||
|
||||
@Operation(summary = "语音转文本", description = "上传语音文件,转换为文本")
|
||||
@PostMapping("/api/audio/stt")
|
||||
public ReqResult<SttResult> stt(@RequestParam("file") MultipartFile file, HttpServletRequest request) {
|
||||
if (!RequestContext.get().isLogin()) {
|
||||
ChatKeyCheck.check(request, userAccessKeyApiService);
|
||||
}
|
||||
if (file.isEmpty()) {
|
||||
return ReqResult.error("Please select a file to upload");
|
||||
}
|
||||
|
||||
try {
|
||||
ClientSecretResponse clientSecretResponse = clientSecretRpcService.queryClientSecret(RequestContext.get().getTenantId());
|
||||
if (clientSecretResponse == null) {
|
||||
return ReqResult.error("Client secret not found");
|
||||
}
|
||||
String content = uploadFile(baseUrl, file.getBytes(), clientSecretResponse.getClientSecret());
|
||||
ReqResult result = JSON.parseObject(content, new TypeReference<ReqResult<SttResult>>() {
|
||||
});
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("File upload failed", e);
|
||||
return ReqResult.error("File upload failed");
|
||||
}
|
||||
}
|
||||
|
||||
public String uploadFile(String baseUrl, byte[] fileBytes, String clientSecret) throws Exception {
|
||||
String boundary = UUID.randomUUID().toString();
|
||||
String header = "--" + boundary + "\r\n" +
|
||||
"Content-Disposition: form-data; name=\"audio\"; filename=\"" + "audio.wav" + "\"\r\n" +
|
||||
"Content-Type: application/octet-stream\r\n\r\n";
|
||||
String footer = "\r\n--" + boundary + "--\r\n";
|
||||
|
||||
byte[] headerBytes = header.getBytes();
|
||||
byte[] footerBytes = footer.getBytes();
|
||||
|
||||
byte[] requestBody = new byte[headerBytes.length + fileBytes.length + footerBytes.length];
|
||||
System.arraycopy(headerBytes, 0, requestBody, 0, headerBytes.length);
|
||||
System.arraycopy(fileBytes, 0, requestBody, headerBytes.length, fileBytes.length);
|
||||
System.arraycopy(footerBytes, 0, requestBody, headerBytes.length + fileBytes.length, footerBytes.length);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(baseUrl + "/transcribe"))
|
||||
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
|
||||
.header("Authorization", "Bearer " + clientSecret)
|
||||
.header("Request-Id", UUID.randomUUID().toString())
|
||||
.timeout(java.time.Duration.ofSeconds(60))
|
||||
.POST(HttpRequest.BodyPublishers.ofByteArray(requestBody))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
return response.body();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
import com.xspaceagi.system.application.service.SysApplicationService;
|
||||
import com.xspaceagi.system.application.service.SysUserPermissionCacheService;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/sys")
|
||||
public class SysController {
|
||||
|
||||
private static final DateTimeFormatter FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
@Resource
|
||||
private SysApplicationService sysApplicationService;
|
||||
@Resource
|
||||
private SysUserPermissionCacheService sysUserPermissionCacheService;
|
||||
|
||||
/**
|
||||
* 获取系统启动时间,排查问题使用
|
||||
*/
|
||||
@GetMapping(value = "/v", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<String> startTime() {
|
||||
LocalDateTime serverStartupTime = sysApplicationService.getServerStartupTime();
|
||||
return ReqResult.success(serverStartupTime != null ? serverStartupTime.format(FORMAT) : null);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/permission-cache-time", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<String> permissionCacheTime() {
|
||||
Long timestamp = sysUserPermissionCacheService.getPermissionLatestCacheTime();
|
||||
if (timestamp == null) {
|
||||
return ReqResult.success(null);
|
||||
}
|
||||
LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault());
|
||||
return ReqResult.success(dateTime.format(FORMAT));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.xspaceagi.system.infra.dao.model.OperatorLogModel;
|
||||
import com.xspaceagi.system.infra.repository.ISysOperatorLogRepository;
|
||||
import com.xspaceagi.system.infra.service.QueryVoListDelegateService;
|
||||
import com.xspaceagi.system.sdk.operate.ActionType;
|
||||
import com.xspaceagi.system.sdk.operate.OperationLogReporter;
|
||||
import com.xspaceagi.system.sdk.operate.SystemEnum;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.page.PageQueryParamVo;
|
||||
import com.xspaceagi.system.spec.page.PageQueryVo;
|
||||
import com.xspaceagi.system.spec.page.SuperPage;
|
||||
import com.xspaceagi.system.web.dto.operatorlog.EnumOptionDto;
|
||||
import com.xspaceagi.system.web.dto.operatorlog.OperatorLogDto;
|
||||
import com.xspaceagi.system.web.dto.operatorlog.OperatorLogQueryDto;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "系统配置-操作日志")
|
||||
@RestController
|
||||
@RequestMapping("/api/sys/operator/log")
|
||||
@Slf4j
|
||||
public class SysOperatorLogController {
|
||||
|
||||
@Resource
|
||||
private QueryVoListDelegateService queryVoListDelegateService;
|
||||
|
||||
|
||||
@Resource
|
||||
private ISysOperatorLogRepository sysOperatorLogRepository;
|
||||
|
||||
@OperationLogReporter(actionType = ActionType.QUERY,
|
||||
action = "操作日志页面列表查询", objectName = "操作日志", systemCode = SystemEnum.SYSTEM)
|
||||
@Operation(summary = "操作日志页面列表查询")
|
||||
@ApiResponses(
|
||||
@ApiResponse(responseCode = "0000", description = "成功")
|
||||
)
|
||||
@RequestMapping(path = "/list", method = RequestMethod.POST)
|
||||
public ReqResult<IPage<OperatorLogDto>> list(@RequestBody PageQueryVo<OperatorLogQueryDto> pageQueryVo) {
|
||||
OperatorLogQueryDto queryFilter = pageQueryVo.getQueryFilter();
|
||||
|
||||
// 时间戳转换为 LocalDateTime(复用现有时间范围查询机制)
|
||||
if (queryFilter != null) {
|
||||
if (queryFilter.getCreateTimeGt() != null || queryFilter.getCreateTimeLt() != null) {
|
||||
List<LocalDateTime> created = new ArrayList<>(2);
|
||||
if (queryFilter.getCreateTimeGt() != null) {
|
||||
created.add(LocalDateTime.ofInstant(
|
||||
Instant.ofEpochMilli(queryFilter.getCreateTimeGt()),
|
||||
ZoneId.systemDefault()));
|
||||
}
|
||||
if (queryFilter.getCreateTimeLt() != null) {
|
||||
created.add(LocalDateTime.ofInstant(
|
||||
Instant.ofEpochMilli(queryFilter.getCreateTimeLt()),
|
||||
ZoneId.systemDefault()));
|
||||
}
|
||||
queryFilter.setCreated(created);
|
||||
}
|
||||
}
|
||||
|
||||
PageQueryParamVo pageQueryParamVo = new PageQueryParamVo(pageQueryVo);
|
||||
|
||||
SuperPage<OperatorLogModel> superPage = this.queryVoListDelegateService.queryVoList(this.sysOperatorLogRepository,
|
||||
pageQueryParamVo, null);
|
||||
|
||||
var userModelList = superPage.getRecords();
|
||||
|
||||
//类型转换
|
||||
List<OperatorLogDto> dtoList = userModelList.stream().map(OperatorLogDto::convertToDto).toList();
|
||||
SuperPage<OperatorLogDto> iPage = SuperPage.build(superPage, dtoList);
|
||||
iPage.setCurrent(pageQueryVo.getCurrent());
|
||||
iPage.setSize(pageQueryVo.getPageSize());
|
||||
return ReqResult.success(iPage);
|
||||
}
|
||||
|
||||
@OperationLogReporter(actionType = ActionType.QUERY,
|
||||
action = "操作日志详情查看", objectName = "操作日志", systemCode = SystemEnum.SYSTEM)
|
||||
@Operation(summary = "操作日志详情查看")
|
||||
@ApiResponses(
|
||||
@ApiResponse(responseCode = "0000", description = "成功")
|
||||
)
|
||||
@RequestMapping(path = "/queryById", method = RequestMethod.GET)
|
||||
public ReqResult<OperatorLogDto> queryById(Long id) {
|
||||
|
||||
var model = this.sysOperatorLogRepository.queryOneInfoById(id);
|
||||
OperatorLogDto dto = OperatorLogDto.convertToDto(model);
|
||||
return ReqResult.success(dto);
|
||||
}
|
||||
|
||||
@OperationLogReporter(actionType = ActionType.QUERY,
|
||||
action = "获取系统编码选项", objectName = "系统编码", systemCode = SystemEnum.SYSTEM)
|
||||
@Operation(summary = "获取系统编码选项列表")
|
||||
@ApiResponses(@ApiResponse(responseCode = "0000", description = "成功"))
|
||||
@RequestMapping(path = "/systemCode/options", method = RequestMethod.GET)
|
||||
public ReqResult<List<EnumOptionDto>> getSystemCodeOptions() {
|
||||
List<EnumOptionDto> options = Arrays.stream(SystemEnum.values())
|
||||
.map(e -> EnumOptionDto.builder()
|
||||
.label(e.getDesc())
|
||||
.value(e.name())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
return ReqResult.success(options);
|
||||
}
|
||||
|
||||
@OperationLogReporter(actionType = ActionType.QUERY,
|
||||
action = "获取操作类型选项", objectName = "操作类型", systemCode = SystemEnum.SYSTEM)
|
||||
@Operation(summary = "获取操作类型选项列表")
|
||||
@ApiResponses(@ApiResponse(responseCode = "0000", description = "成功"))
|
||||
@RequestMapping(path = "/actionType/options", method = RequestMethod.GET)
|
||||
public ReqResult<List<EnumOptionDto>> getActionTypeOptions() {
|
||||
List<EnumOptionDto> options = Arrays.stream(ActionType.values())
|
||||
.map(e -> EnumOptionDto.builder()
|
||||
.label(e.getName())
|
||||
.value(e.name())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
return ReqResult.success(options);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
import com.xspaceagi.system.application.dto.TenantConfigDto;
|
||||
import com.xspaceagi.system.application.dto.TenantConfigItemDto;
|
||||
import com.xspaceagi.system.application.service.TenantConfigApplicationService;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.utils.I18nUtil;
|
||||
import com.xspaceagi.system.spec.utils.RedisUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.*;
|
||||
|
||||
@Tag(name = "系统管理-系统配置相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/system/config")
|
||||
public class TenantConfigManageController {
|
||||
|
||||
@Resource
|
||||
private TenantConfigApplicationService tenantConfigApplicationService;
|
||||
|
||||
@Resource
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
@RequireResource({SYSTEM_SETTING_BASIC})
|
||||
@Operation(summary = "查询配置列表")
|
||||
@RequestMapping(path = "/list", method = RequestMethod.POST)
|
||||
public ReqResult<List<TenantConfigItemDto>> listQuery() {
|
||||
List<TenantConfigItemDto> tenantConfigList = tenantConfigApplicationService.getTenantConfigList();
|
||||
I18nUtil.replaceSystemMessage(tenantConfigList);
|
||||
redisUtil.expire("tenant_config:" + RequestContext.get().getTenantId(), 0);
|
||||
return ReqResult.success(tenantConfigList);
|
||||
}
|
||||
|
||||
@RequireResource({SYSTEM_SETTING_SAVE})
|
||||
@Operation(summary = "更新配置信息")
|
||||
@RequestMapping(path = "/add", method = RequestMethod.POST)
|
||||
public ReqResult<Void> update(@RequestBody TenantConfigDto tenantConfigDto) {
|
||||
tenantConfigDto.setTenantId(RequestContext.get().getTenantId());
|
||||
tenantConfigApplicationService.updateConfig(tenantConfigDto);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource({SYSTEM_SETTING_SAVE})
|
||||
@Operation(summary = "更新配置信息")
|
||||
@RequestMapping(path = "/save", method = RequestMethod.POST)
|
||||
public ReqResult<Void> save(@RequestBody TenantConfigDto tenantConfigDto) {
|
||||
tenantConfigDto.setTenantId(RequestContext.get().getTenantId());
|
||||
tenantConfigApplicationService.updateConfig(tenantConfigDto);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource({SYSTEM_THEME_CONFIG_SAVE})
|
||||
@Operation(summary = "更新主题配置")
|
||||
@RequestMapping(path = "/update-theme", method = RequestMethod.POST)
|
||||
public ReqResult<Void> updateTheme(@RequestBody TenantConfigDto tenantConfigDto) {
|
||||
tenantConfigDto.setTenantId(RequestContext.get().getTenantId());
|
||||
tenantConfigApplicationService.updateConfig(tenantConfigDto);
|
||||
return ReqResult.success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
import com.xspaceagi.file.sdk.IFileAccessService;
|
||||
import com.xspaceagi.system.application.dto.TenantConfigDto;
|
||||
import com.xspaceagi.system.application.dto.UserDto;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.utils.I18nUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "租户信息查询接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/tenant")
|
||||
public class TenantController {
|
||||
|
||||
@Value("${app.version:1.0.0}")
|
||||
private String version;
|
||||
|
||||
@Resource
|
||||
private IFileAccessService iFileAccessService;
|
||||
|
||||
@Value("${supportCustomDomain:false}")
|
||||
private String supportCustomDomain;
|
||||
|
||||
@Value("${spring.servlet.multipart.max-file-size:100MB}")
|
||||
private String maxFileSize;
|
||||
|
||||
@Value("${license:}")
|
||||
private String license;
|
||||
|
||||
@Value("${model-api-proxy.base-api-url:}")
|
||||
private String baseApiUrl;
|
||||
|
||||
@Operation(summary = "租户配置信息查询接口")
|
||||
@RequestMapping(path = "/config", method = RequestMethod.GET)
|
||||
public ReqResult<TenantConfigDto> getConfig() {
|
||||
TenantConfigDto tenantConfigDto = (TenantConfigDto) RequestContext.get().getTenantConfig();
|
||||
tenantConfigDto.setDefaultAgentIds(null);
|
||||
tenantConfigDto.setDomainNames(null);
|
||||
tenantConfigDto.setDefaultSuggestModelId(null);
|
||||
tenantConfigDto.setDefaultSummaryModelId(null);
|
||||
tenantConfigDto.setRecommendAgentIds(null);
|
||||
tenantConfigDto.setCodeSafeCheckPrompt(null);
|
||||
tenantConfigDto.setGlobalSystemPrompt(null);
|
||||
tenantConfigDto.setUserWhiteList(null);
|
||||
tenantConfigDto.setSmtpHost(null);
|
||||
tenantConfigDto.setSmtpPassword(null);
|
||||
tenantConfigDto.setSmtpUsername(null);
|
||||
tenantConfigDto.setSmtpPort(null);
|
||||
tenantConfigDto.setSmsAccessKeyId(null);
|
||||
tenantConfigDto.setSmsAccessKeySecret(null);
|
||||
tenantConfigDto.setSmsSignName(null);
|
||||
tenantConfigDto.setSmsTemplateCode(null);
|
||||
tenantConfigDto.setCaptchaAccessKeyId(null);
|
||||
tenantConfigDto.setCaptchaAccessKeySecret(null);
|
||||
tenantConfigDto.setMpAppId(null);
|
||||
tenantConfigDto.setMpAppSecret(null);
|
||||
tenantConfigDto.setSandboxConfig(null);
|
||||
tenantConfigDto.setVersion(version);
|
||||
tenantConfigDto.setSupportCustomDomain(StringUtils.equals(supportCustomDomain, "true"));
|
||||
tenantConfigDto.setEnabledSandbox(true);
|
||||
tenantConfigDto.setSiteLogo(iFileAccessService.getFileUrlWithAk(tenantConfigDto.getSiteLogo(), true));
|
||||
tenantConfigDto.setFaviconUrl(iFileAccessService.getFileUrlWithAk(tenantConfigDto.getFaviconUrl(), true));
|
||||
tenantConfigDto.setSquareBanner(iFileAccessService.getFileUrlWithAk(tenantConfigDto.getSquareBanner(), true));
|
||||
tenantConfigDto.setMaxFileSize(maxFileSize);
|
||||
if (StringUtils.isBlank(tenantConfigDto.getTemplateConfig()) || tenantConfigDto.getTemplateConfig().equals("{}")) {
|
||||
tenantConfigDto.setTemplateConfig("{\"primaryColor\":\"#5147ff\",\"backgroundId\":\"bg-variant-1\",\"antdTheme\":\"light\",\"layoutStyle\":\"light\",\"navigationStyle\":\"style1\",\"timestamp\":1757425328082}");
|
||||
}
|
||||
String userName = "";
|
||||
if (RequestContext.get().isLogin()) {
|
||||
UserDto user = (UserDto) RequestContext.get().getUser();
|
||||
if (StringUtils.isNotBlank(user.getNickName())) {
|
||||
userName = user.getNickName();
|
||||
} else {
|
||||
userName = user.getUserName();
|
||||
}
|
||||
}
|
||||
if (StringUtils.isBlank(tenantConfigDto.getHomeSlogan())) {
|
||||
tenantConfigDto.setHomeSlogan("Hi {{USER_NAME}}, how can I help you today?");
|
||||
}
|
||||
if (StringUtils.isNotBlank(tenantConfigDto.getHomeSlogan())) {
|
||||
tenantConfigDto.setHomeSlogan(tenantConfigDto.getHomeSlogan().replace("{{USER_NAME}}", userName));
|
||||
}
|
||||
if (StringUtils.isBlank(tenantConfigDto.getLoginPageText())) {
|
||||
tenantConfigDto.setLoginPageText("Easily build and deploy your<br>private Agentic AI solution");
|
||||
}
|
||||
if (StringUtils.isBlank(tenantConfigDto.getLoginPageSubText())) {
|
||||
tenantConfigDto.setLoginPageSubText("Comprehensive workflow and plugin development, RAG knowledge base and data table storage, MCP integration and open capabilities");
|
||||
}
|
||||
if (StringUtils.isNotBlank(license)) {
|
||||
tenantConfigDto.setCommercialEdition(true);
|
||||
}
|
||||
|
||||
String siteUrl = tenantConfigDto.getSiteUrl();
|
||||
if (StringUtils.isNotBlank(baseApiUrl)) {
|
||||
siteUrl = baseApiUrl;
|
||||
}
|
||||
if (siteUrl.endsWith("/")) {
|
||||
siteUrl = siteUrl.substring(0, siteUrl.length() - 1);
|
||||
}
|
||||
tenantConfigDto.setBaseModelApiUrl(siteUrl + "/api/proxy/model");
|
||||
I18nUtil.replaceSystemMessage(tenantConfigDto);
|
||||
return ReqResult.success(tenantConfigDto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
import com.xspaceagi.system.application.dto.TenantAddDto;
|
||||
import com.xspaceagi.system.application.dto.TenantDto;
|
||||
import com.xspaceagi.system.application.service.TenantConfigApplicationService;
|
||||
import com.xspaceagi.system.infra.dao.entity.Tenant;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Tag(name = "系统管理-租户管理相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/tenant")
|
||||
public class TenantManageController {
|
||||
|
||||
@Resource
|
||||
private TenantConfigApplicationService tenantConfigApplicationService;
|
||||
|
||||
|
||||
@Operation(summary = "查询租户列表")
|
||||
@RequestMapping(path = "/list", method = RequestMethod.POST)
|
||||
public ReqResult<List<TenantDto>> listQuery() {
|
||||
checkPermission();
|
||||
return ReqResult.success(tenantConfigApplicationService.getTenantList());
|
||||
}
|
||||
|
||||
@Operation(summary = "新增租户")
|
||||
@RequestMapping(path = "/add", method = RequestMethod.POST)
|
||||
public ReqResult<Void> add(@RequestBody @Valid TenantAddDto tenant) {
|
||||
checkPermission();
|
||||
if (tenantConfigApplicationService.queryTenantIdByDomainName(tenant.getDomain()) != null) {
|
||||
return ReqResult.error("域名`" + tenant.getDomain() + "`已被占用");
|
||||
}
|
||||
tenant.setDomain(tenant.getDomain().toLowerCase());
|
||||
tenantConfigApplicationService.addTenant(tenant);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "更新租户信息")
|
||||
@RequestMapping(path = "/update", method = RequestMethod.POST)
|
||||
public ReqResult<Void> update(@RequestBody Tenant tenant) {
|
||||
checkPermission();
|
||||
tenantConfigApplicationService.updateTenant(tenant);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
private void checkPermission() {
|
||||
//租户ID为1的管理员拥有所有租户的管理权限
|
||||
if (RequestContext.get().getTenantId() != 1) {
|
||||
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
import com.xspaceagi.agent.core.sdk.IModelRpcService;
|
||||
import com.xspaceagi.system.application.dto.UpdateApiKeyDto;
|
||||
import com.xspaceagi.system.application.service.UserApiKeyApplicationService;
|
||||
import com.xspaceagi.system.infra.dao.entity.OpenApiDefinition;
|
||||
import com.xspaceagi.system.sdk.service.dto.UserAccessKeyDto;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.web.dto.ApiInvokeStatDto;
|
||||
import com.xspaceagi.system.web.dto.ApiKeyCreateDto;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "用户APIKEY管理", description = "用户APIKEY管理相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/user/api-key")
|
||||
public class UserApiKeyController {
|
||||
|
||||
@Resource
|
||||
private UserApiKeyApplicationService userApiKeyApplicationService;
|
||||
|
||||
@Resource
|
||||
private IModelRpcService iModelRpcService;
|
||||
|
||||
@Operation(summary = "新增创建APIKEY")
|
||||
@PostMapping("/create")
|
||||
public ReqResult<UserAccessKeyDto> create(@RequestBody ApiKeyCreateDto apiKeyCreateDto) {
|
||||
UserAccessKeyDto userApiKey = userApiKeyApplicationService.createUserApiKey(RequestContext.get().getUserId(), apiKeyCreateDto.getName(), apiKeyCreateDto.getExpire());
|
||||
return ReqResult.success(userApiKey);
|
||||
}
|
||||
|
||||
@Operation(summary = "更新APIKEY")
|
||||
@PostMapping("/update")
|
||||
public ReqResult<Void> update(@RequestBody UpdateApiKeyDto updateApiKeyDto) {
|
||||
updateApiKeyDto.setUserId(RequestContext.get().getUserId());
|
||||
userApiKeyApplicationService.updateUserApiKey(updateApiKeyDto);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "删除APIKEY")
|
||||
@PostMapping("/delete/{apiKey}")
|
||||
public ReqResult<Void> delete(@PathVariable String apiKey) {
|
||||
userApiKeyApplicationService.deleteUserApiKey(RequestContext.get().getUserId(), apiKey);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "查询用户APIKEY列表")
|
||||
@GetMapping("/list")
|
||||
public ReqResult<List<UserAccessKeyDto>> list() {
|
||||
List<UserAccessKeyDto> userApiKeys = userApiKeyApplicationService.getUserApiKeys(RequestContext.get().getUserId());
|
||||
return ReqResult.success(userApiKeys);
|
||||
}
|
||||
|
||||
@Operation(summary = "获取用户有权限的API列表")
|
||||
@GetMapping("/open-api-definitions")
|
||||
public ReqResult<List<OpenApiDefinition>> openApiDefinitions() {
|
||||
List<OpenApiDefinition> openApiDefinitions = userApiKeyApplicationService.queryOpenApiDefinitions(RequestContext.get().getUserId());
|
||||
return ReqResult.success(openApiDefinitions);
|
||||
}
|
||||
|
||||
|
||||
//TODO 完善
|
||||
@Operation(summary = "APIKEY对应的接口调用统计")
|
||||
@GetMapping("/stats")
|
||||
public ReqResult<List<ApiInvokeStatDto>> stats() {
|
||||
List<OpenApiDefinition> openApiDefinitions = userApiKeyApplicationService.queryOpenApiDefinitions(RequestContext.get().getUserId());
|
||||
List<ApiInvokeStatDto> apiInvokeStats = new ArrayList<>();
|
||||
openApiDefinitions.forEach(openApiDefinition -> {
|
||||
if (CollectionUtils.isNotEmpty(openApiDefinition.getApiList())) {
|
||||
openApiDefinition.getApiList().forEach(apiDefinition -> {
|
||||
ApiInvokeStatDto apiInvokeStat = new ApiInvokeStatDto();
|
||||
apiInvokeStat.setName(apiDefinition.getName());
|
||||
apiInvokeStat.setPath(apiDefinition.getPath());
|
||||
apiInvokeStat.setKey(apiDefinition.getKey());
|
||||
apiInvokeStat.setTotal(ApiInvokeStatDto.InvokeCount.builder().failCount(0L).successCount(100L).totalCount(20L).build());
|
||||
apiInvokeStat.setToday(ApiInvokeStatDto.InvokeCount.builder().failCount(0L).successCount(100L).totalCount(20L).build());
|
||||
apiInvokeStat.setYesterday(ApiInvokeStatDto.InvokeCount.builder().failCount(0L).successCount(100L).totalCount(20L).build());
|
||||
apiInvokeStat.setWeek(ApiInvokeStatDto.InvokeCount.builder().failCount(0L).successCount(100L).totalCount(20L).build());
|
||||
apiInvokeStat.setMonth(ApiInvokeStatDto.InvokeCount.builder().failCount(0L).successCount(100L).totalCount(20L).build());
|
||||
apiInvokeStats.add(apiInvokeStat);
|
||||
});
|
||||
}
|
||||
});
|
||||
return ReqResult.success(apiInvokeStats);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
import com.xspaceagi.system.application.dto.TenantConfigDto;
|
||||
import com.xspaceagi.system.application.dto.UserDto;
|
||||
import com.xspaceagi.system.application.dto.permission.MenuNodeDto;
|
||||
import com.xspaceagi.system.application.service.AuthService;
|
||||
import com.xspaceagi.system.application.service.UserApplicationService;
|
||||
import com.xspaceagi.system.application.service.impl.SysUserPermissionCacheServiceImpl;
|
||||
import com.xspaceagi.system.infra.dao.entity.User;
|
||||
import com.xspaceagi.system.infra.verify.VerifyCodeSendAndCheckService;
|
||||
import com.xspaceagi.system.infra.verify.captcha.CaptchaConfig;
|
||||
import com.xspaceagi.system.infra.verify.email.SmtpConfig;
|
||||
import com.xspaceagi.system.infra.verify.sms.SmsConfig;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.PageQueryVo;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.enums.CodeTypeEnum;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
import com.xspaceagi.system.spec.utils.I18nUtil;
|
||||
import com.xspaceagi.system.web.dto.*;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "用户相关接口", description = "错误码:4010为未认证")
|
||||
@RestController
|
||||
@RequestMapping("/api/user")
|
||||
public class UserController {
|
||||
|
||||
@Resource
|
||||
private UserApplicationService userApplicationService;
|
||||
|
||||
@Resource
|
||||
private VerifyCodeSendAndCheckService verifyCodeSendAndCheckService;
|
||||
|
||||
@Resource
|
||||
private SysUserPermissionCacheServiceImpl sysUserAuthCacheService;
|
||||
|
||||
@Resource
|
||||
private SysUserPermissionCacheServiceImpl sysUserPermissionCacheService;
|
||||
|
||||
@Resource
|
||||
private AuthService authService;
|
||||
|
||||
@Operation(summary = "密码登录接口")
|
||||
@RequestMapping(path = "/passwordLogin", method = RequestMethod.POST)
|
||||
public ReqResult<LoginResDto> passwordLogin(@RequestBody PasswordLoginDto loginDto, HttpServletRequest request, HttpServletResponse response) {
|
||||
verifyCodeSendAndCheckService.checkCaptchaVerifyParam(buildCaptchaConfig(), loginDto.getCaptchaVerifyParam());
|
||||
String token = authService.loginWithPassword(StringUtils.isBlank(loginDto.getPhone()) ? loginDto.getPhoneOrEmail() : loginDto.getPhone(), loginDto.getPassword());
|
||||
return ReqResult.success(buildLoginResDto(token, request, response));
|
||||
}
|
||||
|
||||
@Operation(summary = "验证码登录/注册接口")
|
||||
@RequestMapping(path = "/codeLogin", method = RequestMethod.POST)
|
||||
public ReqResult<LoginResDto> codeLogin(@RequestBody CodeLoginDto loginDto, HttpServletRequest request, HttpServletResponse response) {
|
||||
String token = authService.loginWithCode(StringUtils.isBlank(loginDto.getPhone()) ? loginDto.getPhoneOrEmail() : loginDto.getPhone(), loginDto.getCode());
|
||||
return ReqResult.success(buildLoginResDto(token, request, response));
|
||||
}
|
||||
|
||||
@Operation(summary = "小程序code登录")
|
||||
@RequestMapping(path = "/mp/codeLogin", method = RequestMethod.POST)
|
||||
public ReqResult<LoginResDto> mpCodeLogin(@RequestBody MPCodeLoginDto loginDto, HttpServletRequest request, HttpServletResponse response) {
|
||||
String token = authService.loginWithMpCode(loginDto.getCode());
|
||||
return ReqResult.success(buildLoginResDto(token, request, response));
|
||||
}
|
||||
|
||||
@Operation(summary = "退出登录接口")
|
||||
@RequestMapping(path = "/logout", method = RequestMethod.GET)
|
||||
public ReqResult<Void> logout(HttpServletResponse response) {
|
||||
authService.expireToken(RequestContext.get().getToken());
|
||||
Cookie cookie = new Cookie("ticket", "");
|
||||
cookie.setMaxAge(-1);
|
||||
cookie.setPath("/");
|
||||
response.addCookie(cookie);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "发送验证码")
|
||||
@RequestMapping(path = "/code/send", method = RequestMethod.POST)
|
||||
public ReqResult<Void> sendCode(@RequestBody CodeSendDto codeSendDto) {
|
||||
if (!RequestContext.get().isLogin()) {
|
||||
verifyCodeSendAndCheckService.checkCaptchaVerifyParam(buildCaptchaConfig(), codeSendDto.getCaptchaVerifyParam());
|
||||
}
|
||||
TenantConfigDto tenantConfigDto = (TenantConfigDto) RequestContext.get().getTenantConfig();
|
||||
if (CodeSendDto.Type.RESET_PASSWORD == codeSendDto.getType()) {
|
||||
UserDto curUserDto = (UserDto) RequestContext.get().getUser();
|
||||
//3 为邮箱登录
|
||||
if (tenantConfigDto.getAuthType() == TenantConfigDto.AuthTypeEnum.EMAIL.getCode()) {
|
||||
codeSendDto.setEmail(curUserDto.getEmail());
|
||||
}
|
||||
if (tenantConfigDto.getAuthType() == TenantConfigDto.AuthTypeEnum.PHONE.getCode()) {
|
||||
codeSendDto.setPhone(curUserDto.getPhone());
|
||||
} else {
|
||||
codeSendDto.setEmail(curUserDto.getEmail());
|
||||
}
|
||||
}
|
||||
String code = null;
|
||||
if (StringUtils.isNotBlank(codeSendDto.getPhone())) {
|
||||
SmsConfig smsConfig = new SmsConfig();
|
||||
smsConfig.setSmsAccessKeyId(tenantConfigDto.getSmsAccessKeyId());
|
||||
smsConfig.setSmsAccessKeySecret(tenantConfigDto.getSmsAccessKeySecret());
|
||||
smsConfig.setSmsSignName(tenantConfigDto.getSmsSignName());
|
||||
smsConfig.setSmsTemplateCode(tenantConfigDto.getSmsTemplateCode());
|
||||
code = verifyCodeSendAndCheckService.sendPhoneCode(smsConfig, CodeTypeEnum.valueOf(codeSendDto.getType().name()), codeSendDto.getPhone());
|
||||
}
|
||||
if (StringUtils.isNotBlank(codeSendDto.getEmail())) {
|
||||
SmtpConfig stmpConfig = new SmtpConfig();
|
||||
stmpConfig.setHost(tenantConfigDto.getSmtpHost());
|
||||
stmpConfig.setPort(tenantConfigDto.getSmtpPort());
|
||||
stmpConfig.setUsername(tenantConfigDto.getSmtpUsername());
|
||||
stmpConfig.setPassword(tenantConfigDto.getSmtpPassword());
|
||||
stmpConfig.setSiteName(tenantConfigDto.getSiteName());
|
||||
code = verifyCodeSendAndCheckService.sendEmailCode(stmpConfig, CodeTypeEnum.valueOf(codeSendDto.getType().name()), codeSendDto.getEmail());
|
||||
}
|
||||
log.info("发送验证码:{},手机号:{},邮箱:{}", code, codeSendDto.getPhone(), codeSendDto.getEmail());
|
||||
if (tenantConfigDto.getAuthType() == TenantConfigDto.AuthTypeEnum.PHONE.getCode() && StringUtils.isBlank(tenantConfigDto.getSmsAccessKeyId())) {
|
||||
throw new BizException(I18nUtil.systemMessage("Backend.User.SendCode.SmsServiceNotConfigured", code));
|
||||
}
|
||||
if (tenantConfigDto.getAuthType() == TenantConfigDto.AuthTypeEnum.EMAIL.getCode() && StringUtils.isBlank(tenantConfigDto.getSmtpUsername())) {
|
||||
throw new BizException(I18nUtil.systemMessage("Backend.User.SendCode.EmailServiceNotConfigured", code));
|
||||
}
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "检测邮箱是否被占用")
|
||||
@RequestMapping(path = "/email/check/{email}", method = RequestMethod.POST)
|
||||
public ReqResult<Boolean> checkEmail(@PathVariable String email) {
|
||||
//验证邮箱格式,邮箱用户名支持 .
|
||||
if (!email.matches("^[a-zA-Z0-9._-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$")) {
|
||||
throw new BizException(I18nUtil.systemMessage("Backend.User.Email.InvalidFormat"));
|
||||
}
|
||||
boolean isUsed = null != userApplicationService.queryUserByEmail(email);
|
||||
return ReqResult.success(isUsed);
|
||||
}
|
||||
|
||||
@Operation(summary = "绑定邮箱")
|
||||
@RequestMapping(path = "/email/bind", method = RequestMethod.POST)
|
||||
public ReqResult<Void> bindEmail(@RequestBody BindEmailDto bindEmailDto) {
|
||||
if (StringUtils.isNotBlank(bindEmailDto.getEmail())) {
|
||||
//验证邮箱格式
|
||||
if (!bindEmailDto.getEmail().matches("^[a-zA-Z0-9._-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$")) {
|
||||
throw new BizException(I18nUtil.systemMessage("Backend.User.Email.InvalidFormat"));
|
||||
}
|
||||
verifyCodeSendAndCheckService.checkEmailCode(CodeTypeEnum.BIND_EMAIL, bindEmailDto.getEmail(), bindEmailDto.getCode());
|
||||
boolean isUsed = null != userApplicationService.queryUserByEmail(bindEmailDto.getEmail());
|
||||
if (isUsed) {
|
||||
throw new BizException(I18nUtil.systemMessage("Backend.User.Email.Occupied"));
|
||||
}
|
||||
} else if (StringUtils.isNotBlank(bindEmailDto.getPhone())) {
|
||||
verifyCodeSendAndCheckService.checkPhoneCode(CodeTypeEnum.BIND_EMAIL, bindEmailDto.getPhone(), bindEmailDto.getCode());
|
||||
boolean isUsed = null != userApplicationService.queryUserByPhone(bindEmailDto.getPhone());
|
||||
if (isUsed) {
|
||||
throw new BizException(I18nUtil.systemMessage("Backend.User.Phone.Occupied"));
|
||||
}
|
||||
}
|
||||
UserDto userDto = (UserDto) RequestContext.get().getUser();
|
||||
UserDto userUpdate = new UserDto();
|
||||
userUpdate.setId(userDto.getId());
|
||||
userUpdate.setEmail(bindEmailDto.getEmail());
|
||||
userUpdate.setPhone(bindEmailDto.getPhone());
|
||||
userApplicationService.update(userUpdate);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "重置密码")
|
||||
@RequestMapping(path = "/password/reset", method = RequestMethod.POST)
|
||||
public ReqResult<Void> resetPassword(@RequestBody ResetPasswordDto resetPasswordDto) {
|
||||
TenantConfigDto tenantConfigDto = (TenantConfigDto) RequestContext.get().getTenantConfig();
|
||||
UserDto curUserDto = (UserDto) RequestContext.get().getUser();
|
||||
if ((tenantConfigDto.getAuthType() == 1 && StringUtils.isNotBlank(tenantConfigDto.getSmsAccessKeyId())) || tenantConfigDto.getAuthType() == 3 && StringUtils.isNotBlank(tenantConfigDto.getSmtpUsername())) {
|
||||
try {
|
||||
verifyCodeSendAndCheckService.checkPhoneCode(CodeTypeEnum.RESET_PASSWORD, curUserDto.getPhone(), resetPasswordDto.getCode());
|
||||
} catch (Exception e) {
|
||||
verifyCodeSendAndCheckService.checkEmailCode(CodeTypeEnum.RESET_PASSWORD, curUserDto.getEmail(), resetPasswordDto.getCode());
|
||||
}
|
||||
}
|
||||
UserDto userDto = (UserDto) RequestContext.get().getUser();
|
||||
UserDto userUpdate = new UserDto();
|
||||
userUpdate.setId(userDto.getId());
|
||||
userUpdate.setPassword(resetPasswordDto.getNewPassword());
|
||||
userApplicationService.update(userUpdate);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "首次登录设置密码")
|
||||
@RequestMapping(path = "/password/set", method = RequestMethod.POST)
|
||||
public ReqResult<Void> setPassword(@RequestBody SetPasswordDto setPasswordDto) {
|
||||
UserDto userDto = (UserDto) RequestContext.get().getUser();
|
||||
if (userDto.getResetPass() == 0) {
|
||||
UserDto userUpdate = new UserDto();
|
||||
userUpdate.setId(userDto.getId());
|
||||
userUpdate.setPassword(setPasswordDto.getPassword());
|
||||
userUpdate.setResetPass(1);
|
||||
userApplicationService.update(userUpdate);
|
||||
}
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "更新用户信息")
|
||||
@RequestMapping(path = "/update", method = RequestMethod.POST)
|
||||
public ReqResult<Void> update(@RequestBody UserUpdateDto userUpdateDto) {
|
||||
UserDto userDto = (UserDto) RequestContext.get().getUser();
|
||||
UserDto userUpdate = new UserDto();
|
||||
userUpdate.setId(userDto.getId());
|
||||
userUpdate.setAvatar(userUpdateDto.getAvatar());
|
||||
userUpdate.setNickName(userUpdateDto.getNickName());
|
||||
userUpdate.setUserName(userUpdateDto.getUserName());
|
||||
userApplicationService.update(userUpdate);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "查询当前登录用户信息")
|
||||
@RequestMapping(path = "/getLoginInfo", method = RequestMethod.GET)
|
||||
public ReqResult<UserDto> getLoginUserInfo() {
|
||||
UserDto user = (UserDto) RequestContext.get().getUser();
|
||||
user.setLangMap(null);
|
||||
return ReqResult.success(user);
|
||||
}
|
||||
|
||||
|
||||
@Operation(summary = "获取当前登录用户的动态认证码")
|
||||
@RequestMapping(path = "/dynamicCode", method = RequestMethod.GET)
|
||||
public ReqResult<String> dynamicCode() {
|
||||
return ReqResult.success(userApplicationService.getUserDynamicCode(((UserDto) RequestContext.get().getUser()).getId()));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询用户信息")
|
||||
@RequestMapping(path = "/query", method = RequestMethod.POST)
|
||||
public ReqResult<UserDto> queryUser(@RequestBody UserInfoQueryDto userInfoQueryDto) {
|
||||
UserDto userDto = null;
|
||||
if (StringUtils.isNotBlank(userInfoQueryDto.getUserName())) {
|
||||
userDto = userApplicationService.queryUserByUserName(userInfoQueryDto.getUserName());
|
||||
} else if (StringUtils.isNotBlank(userInfoQueryDto.getEmail())) {
|
||||
userDto = userApplicationService.queryUserByEmail(userInfoQueryDto.getEmail());
|
||||
} else if (StringUtils.isNotBlank(userInfoQueryDto.getPhone())) {
|
||||
userDto = userApplicationService.queryUserByPhone(userInfoQueryDto.getPhone());
|
||||
}
|
||||
// 返回用户信息时,不返回密码、手机号、邮箱、状态、角色
|
||||
if (userDto != null) {
|
||||
userDto.setPassword(null);
|
||||
userDto.setPhone(null);
|
||||
userDto.setEmail(null);
|
||||
userDto.setStatus(null);
|
||||
userDto.setRole(null);
|
||||
if (userDto.getStatus() == User.Status.Deleted) {
|
||||
return ReqResult.success(null);
|
||||
}
|
||||
}
|
||||
return ReqResult.success(userDto);
|
||||
}
|
||||
|
||||
@Operation(summary = "创建临时ticket")
|
||||
@RequestMapping(path = "/ticket/create", method = RequestMethod.POST)
|
||||
public ReqResult<String> createTicket(@RequestBody TicketCreateDto ticketCreateDto) {
|
||||
UserDto loginUserInfo = authService.getLoginUserInfo(ticketCreateDto.getToken());
|
||||
if (loginUserInfo != null) {
|
||||
return ReqResult.success(authService.newTicket(loginUserInfo, ticketCreateDto.getToken()));
|
||||
}
|
||||
return ReqResult.error("error auth token");
|
||||
}
|
||||
|
||||
@Operation(summary = "根据关键字搜索用户信息")
|
||||
@RequestMapping(path = "/search", method = RequestMethod.POST)
|
||||
public ReqResult<List<UserDto>> search(@RequestBody UserQueryDto userQueryDto) {
|
||||
PageQueryVo<com.xspaceagi.system.application.dto.UserQueryDto> pageQueryVo = new PageQueryVo<>();
|
||||
pageQueryVo.setPageNo(1L);
|
||||
pageQueryVo.setPageSize(1L);
|
||||
com.xspaceagi.system.application.dto.UserQueryDto userQueryDto1 = new com.xspaceagi.system.application.dto.UserQueryDto();
|
||||
userQueryDto1.setUserName(userQueryDto.getKw());
|
||||
pageQueryVo.setQueryFilter(userQueryDto1);
|
||||
List<UserDto> records = userApplicationService.listQuery(pageQueryVo).getRecords();
|
||||
records.forEach(userDto -> {
|
||||
if (StringUtils.isNotBlank(userDto.getNickName())) {
|
||||
userDto.setUserName(userDto.getNickName() + "(" + userDto.getUserName() + ")");
|
||||
}
|
||||
userDto.setPassword(null);
|
||||
userDto.setPhone(null);
|
||||
userDto.setEmail(null);
|
||||
userDto.setStatus(null);
|
||||
//此处需要返回role,用于限制用户是否能绑定角色
|
||||
//userDto.setRole(null);
|
||||
});
|
||||
//records大于五条时,只返回前五条
|
||||
if (records.size() > 5) {
|
||||
records = records.subList(0, 5);
|
||||
}
|
||||
return ReqResult.success(records);
|
||||
}
|
||||
|
||||
@Operation(summary = "查询用户的菜单权限(树形结构)")
|
||||
@GetMapping("/list-menu")
|
||||
public ReqResult<List<MenuNodeDto>> getMenuList() {
|
||||
Long userId = ((UserDto) RequestContext.get().getUser()).getId();
|
||||
if (userId == null) {
|
||||
return ReqResult.error("用户未登录");
|
||||
}
|
||||
List<MenuNodeDto> menuTree = sysUserAuthCacheService.getUserMenuTree(userId);
|
||||
I18nUtil.replaceSystemMessage(menuTree);
|
||||
return ReqResult.success(menuTree);
|
||||
}
|
||||
|
||||
@GetMapping("/refresh-permission")
|
||||
public ReqResult<Void> refreshPermission() {
|
||||
Long userId = ((UserDto) RequestContext.get().getUser()).getId();
|
||||
if (userId == null) {
|
||||
return ReqResult.error("用户未登录");
|
||||
}
|
||||
sysUserPermissionCacheService.clearCacheByUserIds(List.of(userId));
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@GetMapping("/lang")
|
||||
public ReqResult<Void> lang(HttpServletRequest request) {
|
||||
Long userId = ((UserDto) RequestContext.get().getUser()).getId();
|
||||
if (userId == null) {
|
||||
return ReqResult.error("用户未登录");
|
||||
}
|
||||
sysUserPermissionCacheService.clearCacheByUserIds(List.of(userId));
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
private LoginResDto buildLoginResDto(String token, HttpServletRequest request, HttpServletResponse response) {
|
||||
TenantConfigDto tenantConfig = (TenantConfigDto) RequestContext.get().getTenantConfig();
|
||||
int expire = tenantConfig.getAuthExpire() == null ? 86400 * 30 : tenantConfig.getAuthExpire().intValue() * 60;
|
||||
Cookie cookie = new Cookie("ticket", token);
|
||||
cookie.setMaxAge(expire);
|
||||
cookie.setPath("/");
|
||||
cookie.setHttpOnly(true);
|
||||
response.addCookie(cookie);
|
||||
|
||||
UserDto userDto = authService.getLoginUserInfo(token);
|
||||
String referer = request.getHeader("Referer");
|
||||
String redirect = extractRedirect(referer);
|
||||
String header = request.getHeader("X-Client-Type");
|
||||
if (!log.isDebugEnabled() && header == null) {
|
||||
token = "";
|
||||
}
|
||||
return LoginResDto.builder().resetPass(userDto.getResetPass()).redirect(redirect).expireDate(new Date(System.currentTimeMillis() + expire * 1000L)).token(token).build();
|
||||
}
|
||||
|
||||
private CaptchaConfig buildCaptchaConfig() {
|
||||
TenantConfigDto tenantConfig = (TenantConfigDto) RequestContext.get().getTenantConfig();
|
||||
CaptchaConfig captchaConfig = new CaptchaConfig();
|
||||
captchaConfig.setCaptchaAccessKeyId(tenantConfig.getCaptchaAccessKeyId());
|
||||
captchaConfig.setCaptchaAccessKeySecret(tenantConfig.getCaptchaAccessKeySecret());
|
||||
captchaConfig.setOpenCaptcha(tenantConfig.getOpenCaptcha());
|
||||
return captchaConfig;
|
||||
}
|
||||
|
||||
private static String extractRedirect(String referer) {
|
||||
String redirect = null;
|
||||
if (StringUtils.isNotBlank(referer)) {
|
||||
try {
|
||||
String[] parts = referer.split("\\?");
|
||||
if (parts.length > 1) {
|
||||
String queryString = parts[1];
|
||||
String[] params = queryString.split("&");
|
||||
for (String param : params) {
|
||||
String[] keyValue = param.split("=");
|
||||
if (keyValue.length == 2 && "redirect".equals(keyValue[0])) {
|
||||
redirect = URLDecoder.decode(keyValue[1], "UTF-8");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
}
|
||||
}
|
||||
return redirect;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.xspaceagi.system.application.dto.SendNotifyMessageDto;
|
||||
import com.xspaceagi.system.application.dto.UserDto;
|
||||
import com.xspaceagi.system.application.dto.UserQueryDto;
|
||||
import com.xspaceagi.system.application.service.NotifyMessageApplicationService;
|
||||
import com.xspaceagi.system.application.service.UserApplicationService;
|
||||
import com.xspaceagi.system.infra.dao.entity.NotifyMessage;
|
||||
import com.xspaceagi.system.infra.dao.entity.User;
|
||||
import com.xspaceagi.system.infra.dao.mapper.UserMapper;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.PageQueryVo;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
|
||||
import com.xspaceagi.system.web.dto.UserAddDto;
|
||||
import com.xspaceagi.system.web.dto.UserStatsDto;
|
||||
import com.xspaceagi.system.web.dto.UserUpdateDto;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.*;
|
||||
|
||||
@Tag(name = "系统管理-用户管理相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/system/user")
|
||||
public class UserManageController {
|
||||
|
||||
@Resource
|
||||
private UserApplicationService userApplicationService;
|
||||
|
||||
@Resource
|
||||
private NotifyMessageApplicationService notifyMessageApplicationService;
|
||||
|
||||
@Resource
|
||||
private UserMapper userMapper;
|
||||
|
||||
@RequireResource(USER_MANAGE_QUERY)
|
||||
@Operation(summary = "查询用户列表")
|
||||
@RequestMapping(path = "/list", method = RequestMethod.POST)
|
||||
public ReqResult<IPage<UserDto>> listQuery(@RequestBody PageQueryVo<UserQueryDto> pageQueryVo) {
|
||||
checkAdmin();
|
||||
return ReqResult.success(userApplicationService.listQuery(pageQueryVo));
|
||||
}
|
||||
|
||||
@RequireResource(SYSTEM_DASHBOARD)
|
||||
@Operation(summary = "获取用户统计")
|
||||
@RequestMapping(path = "/stats", method = RequestMethod.GET)
|
||||
public ReqResult<UserStatsDto> getUserStats() {
|
||||
checkAdmin();
|
||||
|
||||
Long totalUserCount = userMapper.countTotalUsers();
|
||||
Long todayNewUserCount = userMapper.countTodayNewUsers();
|
||||
|
||||
List<UserStatsDto.TrendItem> last7DaysTrend = userMapper.getLast7DaysNewUserTrend().stream()
|
||||
.map(UserStatsDto::fromMap)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<UserStatsDto.TrendItem> last30DaysTrend = userMapper.getLast30DaysNewUserTrend().stream()
|
||||
.map(UserStatsDto::fromMap)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<UserStatsDto.TrendItem> monthlyTrend = userMapper.getMonthlyNewUserTrend().stream()
|
||||
.map(UserStatsDto::fromMap)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
UserStatsDto stats = UserStatsDto.builder()
|
||||
.totalUserCount(totalUserCount != null ? totalUserCount : 0L)
|
||||
.todayNewUserCount(todayNewUserCount != null ? todayNewUserCount : 0L)
|
||||
.last7DaysTrend(last7DaysTrend)
|
||||
.last30DaysTrend(last30DaysTrend)
|
||||
.monthlyTrend(monthlyTrend)
|
||||
.build();
|
||||
|
||||
return ReqResult.success(stats);
|
||||
}
|
||||
|
||||
@RequireResource(USER_MANAGE_ADD)
|
||||
@Operation(summary = "添加用户")
|
||||
@RequestMapping(path = "/add", method = RequestMethod.POST)
|
||||
public ReqResult<Void> addUser(@RequestBody UserAddDto userAddDto) {
|
||||
checkAdmin();
|
||||
UserDto userDto = new UserDto();
|
||||
BeanUtils.copyProperties(userAddDto, userDto);
|
||||
if (StringUtils.isNotBlank(userAddDto.getPassword())) {
|
||||
userDto.setResetPass(1);
|
||||
}
|
||||
userApplicationService.add(userDto);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(USER_MANAGE_MODIFY)
|
||||
@Operation(summary = "更新用户信息")
|
||||
@RequestMapping(path = "/updateById/{id}", method = RequestMethod.POST)
|
||||
public ReqResult<Void> updateUserById(@PathVariable Long id, @RequestBody UserUpdateDto userUpdateDto) {
|
||||
checkAdmin();
|
||||
UserDto userUpdate = new UserDto();
|
||||
userUpdate.setId(id);
|
||||
userUpdate.setAvatar(userUpdateDto.getAvatar());
|
||||
userUpdate.setNickName(userUpdateDto.getNickName());
|
||||
userUpdate.setUserName(userUpdateDto.getUserName());
|
||||
userUpdate.setPhone(userUpdateDto.getPhone());
|
||||
userUpdate.setEmail(userUpdateDto.getEmail());
|
||||
userUpdate.setRole(userUpdateDto.getRole());
|
||||
userUpdate.setPassword(userUpdateDto.getPassword());
|
||||
userApplicationService.update(userUpdate);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(USER_MANAGE_DISABLE)
|
||||
@Operation(summary = "禁用用户")
|
||||
@RequestMapping(path = "/disable/{id}", method = RequestMethod.POST)
|
||||
public ReqResult<Void> disableUserById(@PathVariable Long id) {
|
||||
checkAdmin();
|
||||
UserDto userUpdate = new UserDto();
|
||||
userUpdate.setId(id);
|
||||
userUpdate.setStatus(User.Status.Disabled);
|
||||
userApplicationService.update(userUpdate);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(USER_MANAGE_ENABLE)
|
||||
@Operation(summary = "启用用户")
|
||||
@RequestMapping(path = "/enable/{id}", method = RequestMethod.POST)
|
||||
public ReqResult<Void> enableUserById(@PathVariable Long id) {
|
||||
checkAdmin();
|
||||
UserDto userUpdate = new UserDto();
|
||||
userUpdate.setId(id);
|
||||
userUpdate.setStatus(User.Status.Enabled);
|
||||
userApplicationService.update(userUpdate);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(USER_MANAGE_DISABLE)
|
||||
@Operation(summary = "删除用户")
|
||||
@RequestMapping(path = "/delete/{id}", method = RequestMethod.POST)
|
||||
public ReqResult<Void> deleteUserById(@PathVariable Long id) {
|
||||
checkAdmin();
|
||||
userApplicationService.logicDelete(id);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(USER_MANAGE_SEND_MESSAGE)
|
||||
@Operation(summary = "发送通知消息")
|
||||
@RequestMapping(path = "/notify/message/send", method = RequestMethod.POST)
|
||||
public ReqResult<Void> notify(@RequestBody SendNotifyMessageDto notifyMessageDto) {
|
||||
notifyMessageDto.setSenderId(RequestContext.get().getUserId());
|
||||
if (CollectionUtils.isNotEmpty(notifyMessageDto.getUserIds())) {
|
||||
notifyMessageDto.setScope(NotifyMessage.MessageScope.System);
|
||||
} else {
|
||||
notifyMessageDto.setScope(NotifyMessage.MessageScope.Broadcast);
|
||||
}
|
||||
notifyMessageApplicationService.sendNotifyMessage(notifyMessageDto);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前简单的在商家范围支持普通用户和管理员两种角色;后续再迭代完整的权限角色
|
||||
*/
|
||||
private void checkAdmin() {
|
||||
if (((UserDto) RequestContext.get().getUser()).getRole() != User.Role.Admin) {
|
||||
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
import com.xspaceagi.system.application.dto.UsageDto;
|
||||
import com.xspaceagi.system.application.dto.UserMetricDto;
|
||||
import com.xspaceagi.system.application.service.UserMetricApplicationService;
|
||||
import com.xspaceagi.system.sdk.permission.IUserDataPermissionRpcService;
|
||||
import com.xspaceagi.system.sdk.service.dto.BizType;
|
||||
import com.xspaceagi.system.sdk.service.dto.PeriodType;
|
||||
import com.xspaceagi.system.sdk.service.dto.PeriodUtils;
|
||||
import com.xspaceagi.system.sdk.service.dto.UserDataPermissionDto;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.enums.PeriodTypeEnum;
|
||||
import com.xspaceagi.system.spec.utils.I18nUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "用户计量统计", description = "用户计量相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/user/metric")
|
||||
public class UserMetricController {
|
||||
|
||||
@Resource
|
||||
private UserMetricApplicationService userMetricApplicationService;
|
||||
|
||||
@Resource
|
||||
private IUserDataPermissionRpcService userDataPermissionRpcService;
|
||||
|
||||
@Operation(summary = "查询用户各项已使用情况")
|
||||
@GetMapping("/usage")
|
||||
public ReqResult<UsageDto> usage() {
|
||||
UserDataPermissionDto userDataPermission = userDataPermissionRpcService.getUserDataPermission(RequestContext.get().getUserId());
|
||||
UsageDto usageDto = new UsageDto();
|
||||
List<UserMetricDto> userMetricDtos = userMetricApplicationService.queryByUserId(RequestContext.get().getUserId(), PeriodUtils.getCurrentPeriod(PeriodTypeEnum.DAY.getCode()));
|
||||
Map<String, BigInteger> bizMap = userMetricDtos.stream().collect(Collectors.toMap(userMetricDto -> userMetricDto.getBizType() + "-" + userMetricDto.getPeriodType(), val -> val.getValue().toBigInteger(), (v1, v2) -> v1));
|
||||
Object todayTokenUsage = bizMap.get(BizType.TOKEN_USAGE.name() + "-" + PeriodType.DAY);
|
||||
if (todayTokenUsage == null) {
|
||||
todayTokenUsage = "0";
|
||||
}
|
||||
UsageDto.Usage tokenUsage = UsageDto.Usage.builder().usage(todayTokenUsage.toString()).limit(userDataPermission.getTokenLimit().getLimitPerDay() == -1 ? I18nUtil.systemMessage("Backend.UserMetric.Unlimited") : userDataPermission.getTokenLimit().getLimitPerDay().toString()).build();
|
||||
tokenUsage.setDescription(tokenUsage.getUsage() + "/" + tokenUsage.getLimit());
|
||||
usageDto.setTodayTokenUsage(tokenUsage);
|
||||
|
||||
Object generalAgentChatUsage = bizMap.get(BizType.GENERAL_AGENT_CHAT.name() + "-" + PeriodType.DAY);
|
||||
if (generalAgentChatUsage == null) {
|
||||
generalAgentChatUsage = "0";
|
||||
}
|
||||
usageDto.setTodayAgentPromptUsage(UsageDto.Usage.builder().usage(generalAgentChatUsage.toString()).limit(userDataPermission.getAgentDailyPromptLimit() == -1 ? I18nUtil.systemMessage("Backend.UserMetric.Unlimited") : userDataPermission.getAgentDailyPromptLimit().toString()).build());
|
||||
usageDto.getTodayAgentPromptUsage().setDescription(usageDto.getTodayAgentPromptUsage().getUsage() + "/" + usageDto.getTodayAgentPromptUsage().getLimit());
|
||||
|
||||
Object appDevChatUsage = bizMap.get(BizType.APP_DEV_CHAT.name() + "-" + PeriodType.DAY);
|
||||
if (appDevChatUsage == null) {
|
||||
appDevChatUsage = 0;
|
||||
}
|
||||
usageDto.setTodayPageAppPromptUsage(UsageDto.Usage.builder().usage(appDevChatUsage.toString()).limit(userDataPermission.getPageDailyPromptLimit() == -1 ? I18nUtil.systemMessage("Backend.UserMetric.Unlimited") : userDataPermission.getPageDailyPromptLimit().toString()).build());
|
||||
usageDto.getTodayPageAppPromptUsage().setDescription(usageDto.getTodayPageAppPromptUsage().getUsage() + "/" + usageDto.getTodayPageAppPromptUsage().getLimit());
|
||||
usageDto.setNewAgentUsage(UsageDto.Usage.builder().description(userDataPermission.getMaxAgentCount() == -1 ? I18nUtil.systemMessage("Backend.UserMetric.Unlimited") : userDataPermission.getMaxAgentCount().toString()).build());
|
||||
usageDto.setNewTableUsage(UsageDto.Usage.builder().description(userDataPermission.getMaxDataTableCount() == -1 ? I18nUtil.systemMessage("Backend.UserMetric.Unlimited") : userDataPermission.getMaxDataTableCount().toString()).build());
|
||||
usageDto.setNewTaskUsage(UsageDto.Usage.builder().description(userDataPermission.getMaxScheduledTaskCount() == -1 ? I18nUtil.systemMessage("Backend.UserMetric.Unlimited") : userDataPermission.getMaxScheduledTaskCount().toString()).build());
|
||||
usageDto.setNewKnowledgeBaseUsage(UsageDto.Usage.builder().description(userDataPermission.getMaxKnowledgeCount() == -1 ? I18nUtil.systemMessage("Backend.UserMetric.Unlimited") : userDataPermission.getMaxKnowledgeCount().toString()).build());
|
||||
usageDto.setKnowledgeBaseStorageUsage(UsageDto.Usage.builder().description(userDataPermission.getKnowledgeStorageLimitGb().compareTo(BigDecimal.valueOf(-1L)) == 0 ? I18nUtil.systemMessage("Backend.UserMetric.Unlimited") : userDataPermission.getKnowledgeStorageLimitGb().toBigInteger() + "GB").build());
|
||||
usageDto.setNewWorkspaceUsage(UsageDto.Usage.builder().description(userDataPermission.getMaxSpaceCount() == -1 ? I18nUtil.systemMessage("Backend.UserMetric.Unlimited") : userDataPermission.getMaxSpaceCount().toString()).build());
|
||||
usageDto.setSandboxMemoryLimit(UsageDto.Usage.builder().description(userDataPermission.getAgentComputerMemoryGb() == null ? I18nUtil.systemMessage("Backend.UserMetric.Unlimited") : userDataPermission.getAgentComputerMemoryGb() + "GB").build());
|
||||
usageDto.setNewPageAppUsage(UsageDto.Usage.builder().description(userDataPermission.getMaxPageAppCount() == -1 ? I18nUtil.systemMessage("Backend.UserMetric.Unlimited") : userDataPermission.getMaxPageAppCount().toString()).build());
|
||||
|
||||
return ReqResult.success(usageDto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.xspaceagi.system.web.controller;
|
||||
|
||||
import com.xspaceagi.system.infra.dao.mapper.UserReqMapper;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.web.dto.UserAccessStatsDto;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import net.sf.jsqlparser.expression.LongValue;
|
||||
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
import net.sf.jsqlparser.statement.Statement;
|
||||
import net.sf.jsqlparser.statement.select.*;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.SYSTEM_DASHBOARD;
|
||||
|
||||
@Tag(name = "用户请求统计查询")
|
||||
@RestController
|
||||
@RequestMapping("/api/system/request")
|
||||
public class UserRequestController {
|
||||
|
||||
@Resource
|
||||
private UserReqMapper userReqMapper;
|
||||
|
||||
@RequireResource(SYSTEM_DASHBOARD)
|
||||
@Operation(summary = "执行统计SQL")
|
||||
@RequestMapping(path = "/sql", method = RequestMethod.POST)
|
||||
public ReqResult<List<Map<String, Object>>> sqlExecute(@RequestBody Map<String, Object> params) throws JSQLParserException {
|
||||
Assert.notNull(params.get("sql"), "SQL cannot be left blank.");
|
||||
Assert.isTrue(params.get("sql").toString().toLowerCase().trim().startsWith("select"), "SQL must be start with select");
|
||||
//确保sql只能是查询
|
||||
Statement statement = CCJSqlParserUtil.parse(params.get("sql").toString());
|
||||
Select select = (Select) statement;
|
||||
PlainSelect plainSelect = select.getPlainSelect();
|
||||
if (plainSelect != null) {
|
||||
// 替换所有表名
|
||||
replaceTableName(plainSelect.getFromItem(), "user_req");
|
||||
if (plainSelect.getJoins() != null) {
|
||||
for (Join join : plainSelect.getJoins()) {
|
||||
replaceTableName(join.getRightItem(), "user_req");
|
||||
}
|
||||
}
|
||||
// 检查是否有 LIMIT,如果没有则加上 LIMIT 1000
|
||||
if (plainSelect.getLimit() == null) {
|
||||
var limit = new Limit().withRowCount(new LongValue(1000));
|
||||
plainSelect.setLimit(limit);
|
||||
}
|
||||
}
|
||||
|
||||
return ReqResult.success(userReqMapper.select(statement.toString()));
|
||||
}
|
||||
|
||||
@RequireResource(SYSTEM_DASHBOARD)
|
||||
@Operation(summary = "获取用户访问统计")
|
||||
@RequestMapping(path = "/stats", method = RequestMethod.GET)
|
||||
public ReqResult<UserAccessStatsDto> getAccessStats() {
|
||||
Long todayUserCount = userReqMapper.countTodayUsers();
|
||||
Long last7DaysUserCount = userReqMapper.countLast7DaysUsers();
|
||||
Long last30DaysUserCount = userReqMapper.countLast30DaysUsers();
|
||||
|
||||
List<UserAccessStatsDto.TrendItem> last7DaysTrend = userReqMapper.getLast7DaysTrend().stream()
|
||||
.map(UserAccessStatsDto::fromMap)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
UserAccessStatsDto stats = UserAccessStatsDto.builder()
|
||||
.todayUserCount(todayUserCount != null ? todayUserCount : 0L)
|
||||
.last7DaysUserCount(last7DaysUserCount != null ? last7DaysUserCount : 0L)
|
||||
.last30DaysUserCount(last30DaysUserCount != null ? last30DaysUserCount : 0L)
|
||||
.last7DaysTrend(last7DaysTrend)
|
||||
.build();
|
||||
|
||||
return ReqResult.success(stats);
|
||||
}
|
||||
|
||||
private static void replaceTableName(FromItem fromItem, String newTableName) {
|
||||
if (fromItem instanceof Table table) {
|
||||
table.setName(newTableName);
|
||||
} else if (fromItem instanceof LateralSubSelect lateral) {
|
||||
var select = lateral.getSelect();
|
||||
if (select instanceof PlainSelect subPlainSelect) {
|
||||
replaceTableName(subPlainSelect.getFromItem(), newTableName);
|
||||
if (subPlainSelect.getJoins() != null) {
|
||||
for (Join join : subPlainSelect.getJoins()) {
|
||||
replaceTableName(join.getRightItem(), newTableName);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (fromItem instanceof ParenthesedFromItem parenthesed) {
|
||||
replaceTableName(parenthesed.getFromItem(), newTableName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.xspaceagi.system.web.controller.api;
|
||||
|
||||
import com.xspaceagi.system.application.dto.TenantConfigDto;
|
||||
import com.xspaceagi.system.application.service.AuthService;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Tag(name = "开放API-用户认证跳转")
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class RedirectApiController {
|
||||
@Resource
|
||||
private AuthService authService;
|
||||
|
||||
@Operation(summary = "根据ticket认证校验并跳转")
|
||||
@RequestMapping(path = "/ticket/redirect", method = RequestMethod.GET)
|
||||
public void redirect(@RequestParam(name = "ticket") String ticket, @RequestParam(name = "redirectUrl") String redirectUrl,
|
||||
HttpServletResponse response) {
|
||||
String token = authService.getTokenByTicket(ticket);
|
||||
if (token == null) {
|
||||
throw new IllegalArgumentException("Error ticket");
|
||||
}
|
||||
TenantConfigDto tenantConfig = (TenantConfigDto) RequestContext.get().getTenantConfig();
|
||||
int expire = tenantConfig.getAuthExpire() == null ? 86400 * 30 : tenantConfig.getAuthExpire().intValue() * 60;
|
||||
Cookie cookie = new Cookie("ticket", token);
|
||||
cookie.setHttpOnly(true);
|
||||
cookie.setMaxAge(expire);
|
||||
cookie.setPath("/");
|
||||
// iframe SameSite=None; Secure
|
||||
cookie.setSecure(true);
|
||||
cookie.setAttribute("SameSite","None");
|
||||
response.addCookie(cookie);
|
||||
try {
|
||||
response.sendRedirect(URLDecoder.decode(redirectUrl, StandardCharsets.UTF_8));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.xspaceagi.system.web.controller.api;
|
||||
|
||||
import com.xspaceagi.system.application.dto.SpaceDto;
|
||||
import com.xspaceagi.system.application.dto.SpaceUserDto;
|
||||
import com.xspaceagi.system.application.service.SpaceApplicationService;
|
||||
import com.xspaceagi.system.infra.dao.entity.Space;
|
||||
import com.xspaceagi.system.sdk.permission.SpacePermissionService;
|
||||
import com.xspaceagi.system.sdk.permission.IUserDataPermissionRpcService;
|
||||
import com.xspaceagi.system.sdk.service.dto.UserDataPermissionDto;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
|
||||
import com.xspaceagi.system.web.dto.SpaceAddDto;
|
||||
import com.xspaceagi.system.web.dto.SpaceUpdateDto;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Tag(name = "开放API-工作空间相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/space")
|
||||
@Slf4j
|
||||
public class SpaceApiController {
|
||||
|
||||
@Resource
|
||||
private SpaceApplicationService spaceApplicationService;
|
||||
|
||||
@Resource
|
||||
private SpacePermissionService spacePermissionService;
|
||||
|
||||
@Resource
|
||||
private IUserDataPermissionRpcService userDataPermissionRpcService;
|
||||
|
||||
@Operation(summary = "创建团队空间接口")
|
||||
@RequestMapping(path = "/add", method = RequestMethod.POST)
|
||||
public ReqResult<Long> add(@RequestBody @Valid SpaceAddDto spaceAddDto) {
|
||||
// 创建团队空间数量限制
|
||||
UserDataPermissionDto userDataPermission = userDataPermissionRpcService.getUserDataPermission(RequestContext.get().getUserId());
|
||||
userDataPermission.checkMaxSpaceCount(spaceApplicationService.countUserCreatedTeamSpaces(RequestContext.get().getUserId()).intValue());
|
||||
SpaceDto spaceDto = SpaceDto.builder()
|
||||
.name(spaceAddDto.getName())
|
||||
.description(spaceAddDto.getDescription())
|
||||
.icon(spaceAddDto.getIcon())
|
||||
.creatorId(RequestContext.get().getUserId())
|
||||
.type(Space.Type.Team)
|
||||
.build();
|
||||
return ReqResult.success(spaceApplicationService.add(spaceDto));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新团队空间接口")
|
||||
@RequestMapping(path = "/{id}/update", method = RequestMethod.POST)
|
||||
public ReqResult<Void> update(@PathVariable Long id, @RequestBody @Valid SpaceUpdateDto spaceUpdateDto) {
|
||||
spaceUpdateDto.setId(id);
|
||||
spacePermissionService.checkSpaceAdminPermission(spaceUpdateDto.getId());
|
||||
spaceApplicationService.update(SpaceDto.builder().id(spaceUpdateDto.getId()).icon(spaceUpdateDto.getIcon())
|
||||
.name(spaceUpdateDto.getName()).description(spaceUpdateDto.getDescription())
|
||||
.receivePublish(spaceUpdateDto.getReceivePublish()).allowDevelop(spaceUpdateDto.getAllowDevelop()).build());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "删除空间接口")
|
||||
@RequestMapping(path = "/{id}/delete", method = RequestMethod.POST)
|
||||
public ReqResult<Void> delete(@PathVariable Long id) {
|
||||
SpaceDto spaceDto = spaceApplicationService.queryById(id);
|
||||
if (spaceDto != null && spaceDto.getType() == Space.Type.Personal) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemPersonalSpaceDeleteForbidden);
|
||||
}
|
||||
spacePermissionService.checkSpaceOwnerPermission(id);
|
||||
spaceApplicationService.delete(id);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "查询用户空间列表")
|
||||
@RequestMapping(path = "/list", method = RequestMethod.GET)
|
||||
public ReqResult<List<SpaceDto>> list() {
|
||||
List<SpaceDto> spaceDtoList = spaceApplicationService.queryListByUserId(RequestContext.get().getUserId());
|
||||
spaceDtoList.forEach(spaceDto -> {
|
||||
SpaceUserDto spaceUserDto = spaceApplicationService.querySpaceUser(spaceDto.getId(), RequestContext.get().getUserId());
|
||||
if (spaceUserDto != null) {
|
||||
spaceDto.setCurrentUserRole(spaceUserDto.getRole());
|
||||
}
|
||||
});
|
||||
return ReqResult.success(spaceDtoList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package com.xspaceagi.system.web.controller.api;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.xspaceagi.system.application.dto.SpaceDto;
|
||||
import com.xspaceagi.system.application.dto.UserDto;
|
||||
import com.xspaceagi.system.application.dto.UserQueryDto;
|
||||
import com.xspaceagi.system.application.service.AuthService;
|
||||
import com.xspaceagi.system.application.service.SpaceApplicationService;
|
||||
import com.xspaceagi.system.application.service.UserApplicationService;
|
||||
import com.xspaceagi.system.infra.dao.entity.User;
|
||||
import com.xspaceagi.system.spec.dto.PageQueryVo;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.web.dto.UserAddDto;
|
||||
import com.xspaceagi.system.web.dto.UserUpdateDto;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Tag(name = "开放API-用户管理相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/system/user")
|
||||
public class UserManApiController {
|
||||
|
||||
@Resource
|
||||
private UserApplicationService userApplicationService;
|
||||
|
||||
@Resource
|
||||
private SpaceApplicationService spaceApplicationService;
|
||||
|
||||
@Resource
|
||||
private AuthService authService;
|
||||
|
||||
@Operation(summary = "分页查询用户列表")
|
||||
@RequestMapping(path = "/list", method = RequestMethod.POST)
|
||||
public ReqResult<IPage<UserDto>> listQuery(@RequestBody PageQueryVo<UserQueryDto> pageQueryVo) {
|
||||
return ReqResult.success(userApplicationService.listQuery(pageQueryVo));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询用户详细信息")
|
||||
@RequestMapping(path = "/detail", method = RequestMethod.GET)
|
||||
public ReqResult<UserDto> detailQuery(@RequestParam(name = "uid", required = false) String uid,
|
||||
@RequestParam(name = "id", required = false) Long id,
|
||||
@RequestParam(name = "username", required = false) String username,
|
||||
@RequestParam(name = "email", required = false) String email,
|
||||
@RequestParam(name = "phone", required = false) String phone) {
|
||||
UserDto userDto = null;
|
||||
if (id != null) {
|
||||
userDto = userApplicationService.queryById(id);
|
||||
}
|
||||
if (userDto == null && StringUtils.isNotBlank(uid)) {
|
||||
userDto = userApplicationService.queryUserByUid(uid);
|
||||
}
|
||||
if (userDto == null && StringUtils.isNotBlank(username)) {
|
||||
userDto = userApplicationService.queryUserByUserName(username);
|
||||
}
|
||||
if (userDto == null && StringUtils.isNotBlank(email)) {
|
||||
userDto = userApplicationService.queryUserByEmail(email);
|
||||
}
|
||||
if (userDto == null && StringUtils.isNotBlank(phone)) {
|
||||
userDto = userApplicationService.queryUserByPhone(phone);
|
||||
}
|
||||
if (userDto == null) {
|
||||
throw new IllegalArgumentException("Invalid param");
|
||||
}
|
||||
return ReqResult.success(userDto);
|
||||
}
|
||||
|
||||
@Operation(summary = "添加用户")
|
||||
@RequestMapping(path = "/add", method = RequestMethod.POST)
|
||||
public ReqResult<Long> addUser(@RequestBody UserAddDto userAddDto) {
|
||||
Assert.notNull(userAddDto, "Invalid param");
|
||||
Assert.isTrue(StringUtils.isNotBlank(userAddDto.getPhone()) || StringUtils.isNotBlank(userAddDto.getEmail()), "email and phone cannot be null at the same time");
|
||||
UserDto userDto = new UserDto();
|
||||
BeanUtils.copyProperties(userAddDto, userDto);
|
||||
if (StringUtils.isNotBlank(userAddDto.getPassword())) {
|
||||
userDto.setResetPass(1);
|
||||
}
|
||||
userApplicationService.add(userDto);
|
||||
return ReqResult.success(userDto.getId());
|
||||
}
|
||||
|
||||
@Operation(summary = "更新用户信息")
|
||||
@RequestMapping(path = "/{id}/update", method = RequestMethod.POST)
|
||||
public ReqResult<Void> updateUserById(@PathVariable Long id, @RequestBody UserUpdateDto userUpdateDto) {
|
||||
UserDto userUpdate = new UserDto();
|
||||
userUpdate.setId(id);
|
||||
userUpdate.setAvatar(userUpdateDto.getAvatar());
|
||||
userUpdate.setUserName(userUpdateDto.getUserName());
|
||||
userUpdate.setNickName(userUpdateDto.getNickName());
|
||||
userUpdate.setPhone(userUpdateDto.getPhone());
|
||||
userUpdate.setEmail(userUpdateDto.getEmail());
|
||||
userUpdate.setRole(userUpdateDto.getRole());
|
||||
userUpdate.setPassword(userUpdateDto.getPassword());
|
||||
//写一个验证,以上字段不能同时为空
|
||||
Assert.isTrue(StringUtils.isNotBlank(userUpdateDto.getNickName()) || StringUtils.isNotBlank(userUpdateDto.getUserName())
|
||||
|| StringUtils.isNotBlank(userUpdateDto.getEmail()) || StringUtils.isNotBlank(userUpdateDto.getPhone())
|
||||
|| StringUtils.isNotBlank(userUpdateDto.getAvatar()) || userUpdateDto.getRole() != null
|
||||
|| StringUtils.isNotBlank(userUpdateDto.getPassword()), "nickName, userName, email, phone, avatar, role, password cannot be null at the same time");
|
||||
userApplicationService.update(userUpdate);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "禁用用户")
|
||||
@RequestMapping(path = "/{id}/disable", method = RequestMethod.POST)
|
||||
public ReqResult<Void> disableUserById(@PathVariable Long id) {
|
||||
UserDto userUpdate = new UserDto();
|
||||
userUpdate.setId(id);
|
||||
userUpdate.setStatus(User.Status.Disabled);
|
||||
userApplicationService.update(userUpdate);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "启用用户")
|
||||
@RequestMapping(path = "/{id}/enable", method = RequestMethod.POST)
|
||||
public ReqResult<Void> enableUserById(@PathVariable Long id) {
|
||||
UserDto userUpdate = new UserDto();
|
||||
userUpdate.setId(id);
|
||||
userUpdate.setStatus(User.Status.Enabled);
|
||||
userApplicationService.update(userUpdate);
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
|
||||
@Operation(summary = "查询用户空间列表")
|
||||
@RequestMapping(path = "/{id}/space/list", method = RequestMethod.GET)
|
||||
public ReqResult<List<SpaceDto>> spaceList(@PathVariable Long id) {
|
||||
List<SpaceDto> spaceDtoList = spaceApplicationService.queryListByUserId(id);
|
||||
return ReqResult.success(spaceDtoList);
|
||||
}
|
||||
|
||||
@Operation(summary = "创建指定用户的认证ticket")
|
||||
@RequestMapping(path = "/{id}/ticket/create", method = RequestMethod.POST)
|
||||
public ReqResult<String> createTicket(@PathVariable Long id) {
|
||||
UserDto userDto = userApplicationService.queryById(id);
|
||||
if (userDto == null) {
|
||||
throw new IllegalArgumentException("Error user id");
|
||||
}
|
||||
String token = authService.createToken(userDto, "open-api-" + UUID.randomUUID().toString().replace("-", ""));
|
||||
String ticket = authService.newTicket(userDto, token);
|
||||
return ReqResult.success(ticket);
|
||||
}
|
||||
|
||||
@Operation(summary = "清除用户通过ticket获得的认证")
|
||||
@RequestMapping(path = "/{id}/ticket/clear", method = RequestMethod.POST)
|
||||
public ReqResult<Void> clearAll(@PathVariable Long id) {
|
||||
authService.expireUserAllToken(id, "open-api-");
|
||||
return ReqResult.success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.xspaceagi.system.web.controller.base;
|
||||
|
||||
|
||||
import com.xspaceagi.system.application.dto.UserDto;
|
||||
import com.xspaceagi.system.infra.dao.entity.User;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.common.UserContext;
|
||||
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 公共逻辑处理
|
||||
*
|
||||
* @author soddy
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class BaseController {
|
||||
|
||||
// @Resource
|
||||
// private LoginWrapper loginWrapper;
|
||||
|
||||
/**
|
||||
* 获取当前登录用户信息
|
||||
*/
|
||||
public UserContext getUser() {
|
||||
var userDto = (UserDto) RequestContext.get().getUser();
|
||||
if (userDto == null) {
|
||||
throw BizException.of(ErrorCodeEnum.UNAUTHORIZED, BizExceptionCodeEnum.systemUserNotLoggedInWeb);
|
||||
}
|
||||
return UserContext.builder()
|
||||
.userId(userDto.getId())
|
||||
.userName(userDto.getUserName())
|
||||
.nickName(userDto.getNickName())
|
||||
.avatar(userDto.getAvatar())
|
||||
.email(userDto.getEmail())
|
||||
.phone(userDto.getPhone())
|
||||
.status(userDto.getStatus() == User.Status.Enabled ? 1 : -1)
|
||||
.tenantId(userDto.getTenantId())
|
||||
.tenantName(null)
|
||||
.orgId(null)
|
||||
.orgName(null)
|
||||
.roleType(userDto.getRole() == User.Role.Admin ? 1 : 2)
|
||||
.build();
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 获取当前用户信息
|
||||
// */
|
||||
// public UserContext getUserInfo() {
|
||||
// var user = loginWrapper.getUser();
|
||||
// if (Objects.isNull(user)) {
|
||||
// user = UserContext.builder().build();
|
||||
// }
|
||||
// return user;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 获取当前用户id
|
||||
// */
|
||||
// public Long getUserId() {
|
||||
// return loginWrapper.getUserId();
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.xspaceagi.system.web.controller.permission;
|
||||
|
||||
import com.xspaceagi.system.application.dto.permission.export.PermissionExportDto;
|
||||
import com.xspaceagi.system.application.service.PermissionDiffService;
|
||||
import com.xspaceagi.system.application.service.PermissionExportService;
|
||||
import com.xspaceagi.system.application.service.PermissionImportService;
|
||||
import com.xspaceagi.system.infra.dao.entity.Tenant;
|
||||
import com.xspaceagi.system.infra.dao.service.TenantService;
|
||||
import com.xspaceagi.system.spec.annotation.SaasAdmin;
|
||||
import com.xspaceagi.system.spec.constants.PermissionSyncConstants;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.jackson.JsonSerializeUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/system/permission")
|
||||
public class PermissionSyncController {
|
||||
|
||||
@Resource
|
||||
private PermissionExportService permissionExportService;
|
||||
|
||||
@Resource
|
||||
private PermissionImportService permissionImportService;
|
||||
|
||||
@Resource
|
||||
private PermissionDiffService permissionDiffService;
|
||||
|
||||
@Resource
|
||||
private TenantService tenantService;
|
||||
|
||||
@SaasAdmin
|
||||
@GetMapping(value = "/export", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Object> export(@RequestParam String version) {
|
||||
try {
|
||||
PermissionExportDto dto = permissionExportService.exportConfig(version);
|
||||
String json = JsonSerializeUtil.toJSONString(dto);
|
||||
|
||||
String saveToPath = "./" + PermissionSyncConstants.PERMISSION_JSON_EXPORT_BASE_PATH + "/" + PermissionSyncConstants.buildExportFileName(version);
|
||||
Path path = Paths.get(saveToPath).toAbsolutePath();
|
||||
Files.createDirectories(path.getParent());
|
||||
Files.writeString(path, json, StandardCharsets.UTF_8);
|
||||
return ReqResult.success("已导出");
|
||||
} catch (IOException e) {
|
||||
return ReqResult.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@SaasAdmin
|
||||
@GetMapping(value = "/import", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<String> importPermission(@RequestParam Long tenantId, @RequestParam String version) {
|
||||
Tenant tenant = tenantService.getById(tenantId);
|
||||
if (tenant == null) {
|
||||
return ReqResult.error("租户不存在");
|
||||
}
|
||||
permissionImportService.importToTenant(tenant, version);
|
||||
return ReqResult.success("导入成功");
|
||||
}
|
||||
|
||||
@SaasAdmin
|
||||
@GetMapping(value = "/diff", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<String> diff(@RequestParam String fromVersion, @RequestParam String toVersion) {
|
||||
try {
|
||||
var diff = permissionDiffService.generateDiff(fromVersion, toVersion);
|
||||
String json = JsonSerializeUtil.toJSONString(diff);
|
||||
|
||||
String fileName = PermissionSyncConstants.buildDiffFileName(toVersion);
|
||||
String saveToPath = "./" + PermissionSyncConstants.PERMISSION_JSON_EXPORT_BASE_PATH + "/" + fileName;
|
||||
Path path = Paths.get(saveToPath).toAbsolutePath();
|
||||
Files.createDirectories(path.getParent());
|
||||
Files.writeString(path, json, StandardCharsets.UTF_8);
|
||||
return ReqResult.success("已生成差异文件: " + fileName);
|
||||
} catch (IOException e) {
|
||||
return ReqResult.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@SaasAdmin
|
||||
@GetMapping(value = "/import-diff", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<String> importDiff(@RequestParam Long tenantId, @RequestParam String version) {
|
||||
Tenant tenant = tenantService.getById(tenantId);
|
||||
if (tenant == null) {
|
||||
return ReqResult.error("租户不存在");
|
||||
}
|
||||
permissionImportService.importDiffToTenant(tenant, version);
|
||||
return ReqResult.success("差异导入成功");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
package com.xspaceagi.system.web.controller.permission;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.xspaceagi.system.application.converter.GroupBindMenuModelConverter;
|
||||
import com.xspaceagi.system.application.converter.MenuNodeConverter;
|
||||
import com.xspaceagi.system.application.converter.SysDataPermissionConverter;
|
||||
import com.xspaceagi.system.application.dto.permission.*;
|
||||
import com.xspaceagi.system.application.service.SysDataPermissionApplicationService;
|
||||
import com.xspaceagi.system.application.service.SysGroupApplicationService;
|
||||
import com.xspaceagi.system.application.service.SysSubjectPermissionApplicationService;
|
||||
import com.xspaceagi.system.domain.model.GroupBindMenuModel;
|
||||
import com.xspaceagi.system.domain.model.MenuNode;
|
||||
import com.xspaceagi.system.domain.model.SortIndex;
|
||||
import com.xspaceagi.system.infra.dao.entity.SysDataPermission;
|
||||
import com.xspaceagi.system.infra.dao.entity.SysGroup;
|
||||
import com.xspaceagi.system.infra.dao.entity.User;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.dto.PageQueryVo;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.enums.*;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
|
||||
import com.xspaceagi.system.spec.jackson.JsonSerializeUtil;
|
||||
import com.xspaceagi.system.spec.utils.I18nUtil;
|
||||
import com.xspaceagi.system.web.controller.base.BaseController;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.*;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "权限管理-用户组", description = "用户组相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/system/group")
|
||||
public class SysGroupController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private SysGroupApplicationService sysGroupApplicationService;
|
||||
@Resource
|
||||
private SysDataPermissionApplicationService sysDataPermissionApplicationService;
|
||||
@Resource
|
||||
private SysSubjectPermissionApplicationService sysSubjectPermissionApplicationService;
|
||||
|
||||
@RequireResource(USER_GROUP_MANAGE_ADD)
|
||||
@Operation(summary = "添加组")
|
||||
@PostMapping(value = "/add", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Void> addGroup(@RequestBody SysGroupAddDto dto) {
|
||||
if (dto == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
if (dto.getSource() != null && SourceEnum.isInValid(dto.getSource())) {
|
||||
return ReqResult.error("参数source错误");
|
||||
}
|
||||
if (dto.getStatus() != null && StatusEnum.isInValid(dto.getStatus())) {
|
||||
return ReqResult.error("参数status错误");
|
||||
}
|
||||
|
||||
SysGroup group = new SysGroup();
|
||||
BeanUtils.copyProperties(dto, group);
|
||||
sysGroupApplicationService.addGroup(group, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(USER_GROUP_MANAGE_MODIFY)
|
||||
@Operation(summary = "更新组")
|
||||
@PostMapping("/update")
|
||||
public ReqResult<Void> updateGroup(@RequestBody SysGroupUpdateDto dto) {
|
||||
if (dto == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
if (dto.getSource() != null && SourceEnum.isInValid(dto.getSource())) {
|
||||
return ReqResult.error("参数source错误");
|
||||
}
|
||||
if (dto.getStatus() != null && StatusEnum.isInValid(dto.getStatus())) {
|
||||
return ReqResult.error("参数status错误");
|
||||
}
|
||||
|
||||
SysGroup group = new SysGroup();
|
||||
BeanUtils.copyProperties(dto, group);
|
||||
group.setCode(null); // code不允许更新
|
||||
sysGroupApplicationService.updateGroup(group, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(USER_GROUP_MANAGE_DELETE)
|
||||
@Operation(summary = "删除组")
|
||||
@PostMapping("/delete/{groupId}")
|
||||
public ReqResult<Void> deleteGroup(@PathVariable Long groupId) {
|
||||
if (groupId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
sysGroupApplicationService.deleteGroup(groupId, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(USER_GROUP_MANAGE_QUERY)
|
||||
@Operation(summary = "根据ID查询组")
|
||||
@GetMapping("/{groupId}")
|
||||
public ReqResult<SysGroupDto> getGroupById(@PathVariable Long groupId) {
|
||||
if (groupId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
|
||||
SysGroup group = sysGroupApplicationService.getGroupById(groupId);
|
||||
if (group == null) {
|
||||
return ReqResult.error("用户组不存在");
|
||||
}
|
||||
SysGroupDto groupDto = new SysGroupDto();
|
||||
BeanUtils.copyProperties(group, groupDto);
|
||||
|
||||
// SysDataPermission dataPermission = sysDataPermissionApplicationService.getByTarget(PermissionTargetTypeEnum.GROUP, group.getId());
|
||||
// if (dataPermission != null) {
|
||||
// groupDto.setModelIds(dataPermission.getModelIds());
|
||||
// groupDto.setTokenLimit(dataPermission.getTokenLimit());
|
||||
// }
|
||||
I18nUtil.replaceSystemMessage(groupDto);
|
||||
return ReqResult.success(groupDto);
|
||||
}
|
||||
|
||||
@RequireResource(USER_GROUP_MANAGE_QUERY)
|
||||
@Operation(summary = "根据编码查询组")
|
||||
@GetMapping("/code/{groupCode}")
|
||||
public ReqResult<SysGroupDto> getGroupByCode(@PathVariable String groupCode) {
|
||||
if (StringUtils.isBlank(groupCode)) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
|
||||
SysGroup group = sysGroupApplicationService.getGroupByCode(groupCode);
|
||||
if (group == null) {
|
||||
return ReqResult.error("用户组不存在");
|
||||
}
|
||||
SysGroupDto groupDto = new SysGroupDto();
|
||||
BeanUtils.copyProperties(group, groupDto);
|
||||
|
||||
// SysDataPermission dataPermission = sysDataPermissionApplicationService.getByTarget(PermissionTargetTypeEnum.GROUP, group.getId());
|
||||
// if (dataPermission != null) {
|
||||
// groupDto.setModelIds(dataPermission.getModelIds());
|
||||
// groupDto.setTokenLimit(dataPermission.getTokenLimit());
|
||||
// }
|
||||
I18nUtil.replaceSystemMessage(groupDto);
|
||||
return ReqResult.success(groupDto);
|
||||
}
|
||||
|
||||
@RequireResource(USER_GROUP_MANAGE_MODIFY)
|
||||
@Operation(summary = "调整用户组顺序")
|
||||
@PostMapping("/update-sort")
|
||||
public ReqResult<Void> updateSortIndex(@RequestBody SortIndexUpdateDto dto) {
|
||||
if (dto == null || CollectionUtils.isEmpty(dto.getItems())) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
List<SortIndex> sortIndexList = dto.getItems().stream()
|
||||
.map(item -> {
|
||||
SortIndex model = new SortIndex();
|
||||
model.setId(item.getId());
|
||||
model.setParentId(item.getParentId());
|
||||
model.setSortIndex(item.getSortIndex());
|
||||
return model;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
sysGroupApplicationService.batchUpdateGroupSort(sortIndexList, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(USER_GROUP_MANAGE_QUERY)
|
||||
@Operation(summary = "根据条件查询组列表")
|
||||
@GetMapping("/list")
|
||||
public ReqResult<List<SysGroupDto>> getGroupList(SysGroupQueryDto dto) {
|
||||
SysGroup sysGroup = new SysGroup();
|
||||
if (dto != null) {
|
||||
BeanUtils.copyProperties(dto, sysGroup);
|
||||
}
|
||||
|
||||
List<SysGroup> groupList = sysGroupApplicationService.getGroupList(sysGroup);
|
||||
if (CollectionUtils.isEmpty(groupList)) {
|
||||
return ReqResult.success();
|
||||
}
|
||||
// List<Long> groupIds = groupList.stream().map(SysGroup::getId).toList();
|
||||
// List<SysDataPermission> dataPermissionList = sysDataPermissionApplicationService.getByTargetList(PermissionTargetTypeEnum.GROUP, groupIds);
|
||||
// Map<Long, SysDataPermission> permissionMap = dataPermissionList.stream()
|
||||
// .collect(Collectors.toMap(SysDataPermission::getTargetId, p -> p, (a, b) -> a));
|
||||
|
||||
List<SysGroupDto> dtoList = groupList.stream().map(r -> {
|
||||
SysGroupDto groupDto = new SysGroupDto();
|
||||
BeanUtils.copyProperties(r, groupDto);
|
||||
// SysDataPermission dataPermission = permissionMap.get(r.getId());
|
||||
// if (dataPermission != null) {
|
||||
// groupDto.setModelIds(dataPermission.getModelIds());
|
||||
// groupDto.setTokenLimit(dataPermission.getTokenLimit());
|
||||
// }
|
||||
return groupDto;
|
||||
}).collect(Collectors.toList());
|
||||
I18nUtil.replaceSystemMessage(dtoList);
|
||||
return ReqResult.success(dtoList);
|
||||
}
|
||||
|
||||
@RequireResource(USER_GROUP_MANAGE_BIND_USER)
|
||||
@Operation(summary = "分页查询组已绑定的用户列表,支持按userName模糊筛选")
|
||||
@PostMapping("/list-user")
|
||||
public ReqResult<IPage<SysUserDto>> getUserListByGroupId(@RequestBody PageQueryVo<SysGroupUserQueryDto> pageQueryVo) {
|
||||
if (pageQueryVo == null || pageQueryVo.getQueryFilter() == null || pageQueryVo.getQueryFilter().getGroupId() == null) {
|
||||
return ReqResult.error("用户组ID不能为空");
|
||||
}
|
||||
SysGroupUserQueryDto queryDto = pageQueryVo.getQueryFilter();
|
||||
Long groupId = queryDto.getGroupId();
|
||||
String userName = queryDto.getUserName();
|
||||
long pageNo = pageQueryVo.getPageNo() != null ? pageQueryVo.getPageNo() : 1L;
|
||||
long pageSize = pageQueryVo.getPageSize() != null ? pageQueryVo.getPageSize() : 10L;
|
||||
|
||||
IPage<User> userPage = sysGroupApplicationService.getUserPageByGroupId(groupId, userName, pageNo, pageSize);
|
||||
List<SysUserDto> dtoList = userPage.getRecords().stream().map(user -> {
|
||||
SysUserDto userDto = new SysUserDto();
|
||||
BeanUtils.copyProperties(user, userDto);
|
||||
userDto.setUserId(user.getId());
|
||||
return userDto;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
IPage<SysUserDto> resultPage = new Page<>(userPage.getCurrent(), userPage.getSize(), userPage.getTotal());
|
||||
resultPage.setRecords(dtoList);
|
||||
return ReqResult.success(resultPage);
|
||||
}
|
||||
|
||||
@RequireResource(USER_GROUP_MANAGE_BIND_DATA)
|
||||
@Operation(summary = "查询用户组数据权限")
|
||||
@GetMapping("/data-permission/{groupId}")
|
||||
public ReqResult<SysDataPermissionBindDto> getGroupDataPermission(@PathVariable Long groupId) {
|
||||
if (groupId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
SysDataPermission dataPermission = sysDataPermissionApplicationService.getByTarget(PermissionTargetTypeEnum.GROUP, groupId);
|
||||
SysDataPermissionBindDto result = SysDataPermissionConverter.toDto(dataPermission);
|
||||
result = result == null ? new SysDataPermissionBindDto() : result;
|
||||
|
||||
result.setModelIds(sysSubjectPermissionApplicationService.listSubjectIdsByTarget(
|
||||
PermissionTargetTypeEnum.GROUP, groupId, PermissionSubjectTypeEnum.MODEL));
|
||||
result.setAgentIds(sysSubjectPermissionApplicationService.listSubjectIdsByTarget(
|
||||
PermissionTargetTypeEnum.GROUP, groupId, PermissionSubjectTypeEnum.AGENT));
|
||||
result.setPageAgentIds(sysSubjectPermissionApplicationService.listSubjectIdsByTarget(
|
||||
PermissionTargetTypeEnum.GROUP, groupId, PermissionSubjectTypeEnum.PAGE));
|
||||
Map<String, String> openApiConfigMap = sysSubjectPermissionApplicationService.listSubjectKeyConfigByTarget(
|
||||
PermissionTargetTypeEnum.GROUP, groupId, PermissionSubjectTypeEnum.OPEN_API);
|
||||
List<SysDataPermissionBindDto.OpenApiConfig> openApiConfigs = openApiConfigMap.entrySet().stream()
|
||||
.map(entry -> {
|
||||
SysDataPermissionBindDto.OpenApiConfig config = new SysDataPermissionBindDto.OpenApiConfig();
|
||||
config.setKey(entry.getKey());
|
||||
if (StringUtils.isNotBlank(entry.getValue())) {
|
||||
try {
|
||||
Map<String, Integer> valueMap = JsonSerializeUtil.parseObject(entry.getValue(), new TypeReference<Map<String, Integer>>() {});
|
||||
config.setRpm(valueMap == null ? null : valueMap.get("rpm"));
|
||||
config.setRpd(valueMap == null ? null : valueMap.get("rpd"));
|
||||
} catch (Exception ignore) {
|
||||
config.setRpm(null);
|
||||
config.setRpd(null);
|
||||
}
|
||||
}
|
||||
return config;
|
||||
})
|
||||
.toList();
|
||||
result.setOpenApiConfigs(openApiConfigs);
|
||||
|
||||
result.setKnowledgeIds(sysSubjectPermissionApplicationService.listSubjectIdsByTarget(
|
||||
PermissionTargetTypeEnum.GROUP, groupId, PermissionSubjectTypeEnum.KNOWLEDGE));
|
||||
|
||||
return ReqResult.success(result);
|
||||
}
|
||||
|
||||
@RequireResource(USER_GROUP_MANAGE_BIND_DATA)
|
||||
@Operation(summary = "组绑定数据权限(全量覆盖)")
|
||||
@PostMapping("/bind-data-permission")
|
||||
public ReqResult<Void> bindDataPermission(@RequestBody SysGroupBindDataPermissionDto dto) {
|
||||
if (dto == null || dto.getGroupId() == null) {
|
||||
return ReqResult.error("用户组ID不能为空");
|
||||
}
|
||||
SysDataPermissionBindDto bindDto = dto.getDataPermission();
|
||||
SysDataPermission dataPermission = SysDataPermissionConverter.toEntity(bindDto);
|
||||
sysGroupApplicationService.bindDataPermission(dto.getGroupId(), dataPermission, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(USER_GROUP_MANAGE_BIND_USER)
|
||||
@Operation(summary = "组绑定用户(全量覆盖)")
|
||||
@PostMapping("/bind-user")
|
||||
public ReqResult<Void> bindUser(@RequestBody SysGroupBindUserDto dto) {
|
||||
if (dto == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
sysGroupApplicationService.groupBindUser(dto.getGroupId(), dto.getUserIds(), getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(USER_GROUP_MANAGE_BIND_USER)
|
||||
@Operation(summary = "用户组添加用户")
|
||||
@PostMapping("/add-user")
|
||||
public ReqResult<Void> addUser(@RequestBody SysGroupBindUserSingleDto dto) {
|
||||
if (dto == null || dto.getGroupId() == null || dto.getUserId() == null) {
|
||||
return ReqResult.error("用户组ID和用户ID不能为空");
|
||||
}
|
||||
sysGroupApplicationService.groupAddUser(dto.getGroupId(), dto.getUserId(), getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(USER_GROUP_MANAGE_BIND_USER)
|
||||
@Operation(summary = "用户组移除用户")
|
||||
@PostMapping("/remove-user")
|
||||
public ReqResult<Void> removeUser(@RequestBody SysGroupBindUserSingleDto dto) {
|
||||
if (dto == null || dto.getGroupId() == null || dto.getUserId() == null) {
|
||||
return ReqResult.error("用户组ID和用户ID不能为空");
|
||||
}
|
||||
sysGroupApplicationService.groupRemoveUser(dto.getGroupId(), dto.getUserId(), getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(USER_GROUP_MANAGE_BIND_MENU)
|
||||
@Operation(summary = "组绑定菜单(全量覆盖)")
|
||||
@PostMapping("/bind-menu")
|
||||
public ReqResult<Void> bindMenu(@RequestBody SysGroupBindMenuDto dto) {
|
||||
if (dto == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
// 用户组不允许绑定 生态市场/系统管理
|
||||
checkForbiddenMenuCodes(dto.getMenuTree());
|
||||
|
||||
GroupBindMenuModel model = GroupBindMenuModelConverter.convertToModel(dto);
|
||||
sysGroupApplicationService.bindMenu(model, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(USER_GROUP_MANAGE_BIND_MENU)
|
||||
@Operation(summary = "查询组已绑定的菜单(树形结构)")
|
||||
@GetMapping("/list-menu/{groupId}")
|
||||
public ReqResult<List<MenuNodeDto>> getMenuListByGroupId(@PathVariable Long groupId) {
|
||||
if (groupId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
// 获取处理好的菜单树(已包含资源详细信息)
|
||||
List<MenuNode> menuNodeList = sysGroupApplicationService.getMenuTreeByGroupId(groupId);
|
||||
|
||||
// 转换为DTO
|
||||
List<MenuNodeDto> menuDtoList = MenuNodeConverter.convertMenuTreeToDtoTree(menuNodeList);
|
||||
|
||||
I18nUtil.replaceSystemMessage(menuDtoList);
|
||||
return ReqResult.success(menuDtoList);
|
||||
}
|
||||
|
||||
private void checkForbiddenMenuCodes(List<MenuNodeDto> nodes) {
|
||||
if (CollectionUtils.isEmpty(nodes)) {
|
||||
return;
|
||||
}
|
||||
for (MenuNodeDto node : nodes) {
|
||||
if (StringUtils.isNotBlank(node.getCode())) {
|
||||
if (node.getCode().equals(MenuEnum.ECO_MARKET.getCode()) && node.getMenuBindType() > 0) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemGroupCannotBindForbiddenMenu,
|
||||
MenuEnum.ECO_MARKET.getName());
|
||||
}
|
||||
if (node.getCode().equals(MenuEnum.SYSTEM_MANAGE.getCode()) && node.getMenuBindType() > 0) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemGroupCannotBindForbiddenMenu,
|
||||
MenuEnum.SYSTEM_MANAGE.getName());
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(node.getChildren())) {
|
||||
checkForbiddenMenuCodes(node.getChildren());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
package com.xspaceagi.system.web.controller.permission;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.xspaceagi.system.application.dto.permission.*;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.utils.I18nUtil;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
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.RestController;
|
||||
|
||||
import com.xspaceagi.system.application.service.SysMenuApplicationService;
|
||||
import com.xspaceagi.system.application.service.SysResourceApplicationService;
|
||||
import com.xspaceagi.system.domain.model.MenuNode;
|
||||
import com.xspaceagi.system.domain.model.SortIndex;
|
||||
import com.xspaceagi.system.infra.dao.entity.SysMenu;
|
||||
import com.xspaceagi.system.infra.dao.entity.SysMenuResource;
|
||||
import com.xspaceagi.system.infra.dao.entity.SysResource;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.enums.BindTypeEnum;
|
||||
import com.xspaceagi.system.spec.enums.OpenTypeEnum;
|
||||
import com.xspaceagi.system.spec.enums.SourceEnum;
|
||||
import com.xspaceagi.system.spec.enums.YesOrNoEnum;
|
||||
import com.xspaceagi.system.web.controller.base.BaseController;
|
||||
import com.xspaceagi.system.application.converter.MenuBindResourceModelConverter;
|
||||
import com.xspaceagi.system.application.converter.MenuTreeUtil;
|
||||
import com.xspaceagi.system.application.converter.ResourceTreeUtil;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.*;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "权限管理-菜单", description = "菜单相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/system/menu")
|
||||
public class SysMenuController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private SysMenuApplicationService sysMenuApplicationService;
|
||||
|
||||
@Resource
|
||||
private SysResourceApplicationService sysResourceApplicationService;
|
||||
|
||||
@RequireResource(MENU_MANAGE_ADD)
|
||||
@Operation(summary = "添加菜单")
|
||||
@PostMapping(value = "/add",produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Void> addMenu(@RequestBody SysMenuAddDto dto) {
|
||||
if (dto == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
if (dto.getSource() != null && SourceEnum.isInValid(dto.getSource())) {
|
||||
return ReqResult.error("参数source错误");
|
||||
}
|
||||
if (dto.getOpenType() != null && OpenTypeEnum.isInValid(dto.getOpenType())) {
|
||||
return ReqResult.error("参数openType错误");
|
||||
}
|
||||
if (dto.getStatus() != null && YesOrNoEnum.isInValid(dto.getStatus())) {
|
||||
return ReqResult.error("参数status错误");
|
||||
}
|
||||
|
||||
SysMenu menu = new SysMenu();
|
||||
BeanUtils.copyProperties(dto, menu);
|
||||
|
||||
MenuNode menuNode = null;
|
||||
if (CollectionUtils.isNotEmpty(dto.getResourceTree())) {
|
||||
SysMenuBindResourceDto bindResourceDto = new SysMenuBindResourceDto();
|
||||
bindResourceDto.setResourceTree(dto.getResourceTree());
|
||||
menuNode = MenuBindResourceModelConverter.convertToMenuNode(bindResourceDto);
|
||||
}
|
||||
sysMenuApplicationService.addMenu(menu, menuNode, SourceEnum.CUSTOM.getCode(), getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(MENU_MANAGE_MODIFY)
|
||||
@Operation(summary = "更新菜单")
|
||||
@PostMapping("/update")
|
||||
public ReqResult<Void> updateMenu(@RequestBody SysMenuUpdateDto dto) {
|
||||
if (dto == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
if (dto.getSource() != null && SourceEnum.isInValid(dto.getSource())) {
|
||||
return ReqResult.error("参数source错误");
|
||||
}
|
||||
if (dto.getOpenType() != null && OpenTypeEnum.isInValid(dto.getOpenType())) {
|
||||
return ReqResult.error("参数openType错误");
|
||||
}
|
||||
if (dto.getStatus() != null && YesOrNoEnum.isInValid(dto.getStatus())) {
|
||||
return ReqResult.error("参数status错误");
|
||||
}
|
||||
|
||||
SysMenu menu = new SysMenu();
|
||||
BeanUtils.copyProperties(dto, menu);
|
||||
|
||||
MenuNode menuNode = null;
|
||||
if (CollectionUtils.isNotEmpty(dto.getResourceTree())) {
|
||||
SysMenuBindResourceDto bindResourceDto = new SysMenuBindResourceDto();
|
||||
bindResourceDto.setResourceTree(dto.getResourceTree());
|
||||
menuNode = MenuBindResourceModelConverter.convertToMenuNode(bindResourceDto);
|
||||
}
|
||||
|
||||
menu.setCode(null); // code不允许更新
|
||||
sysMenuApplicationService.updateMenu(menu, menuNode, SourceEnum.CUSTOM.getCode(), getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(MENU_MANAGE_DELETE)
|
||||
@Operation(summary = "删除菜单")
|
||||
@PostMapping("/delete/{menuId}")
|
||||
public ReqResult<Void> deleteMenu(@PathVariable Long menuId) {
|
||||
if (menuId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
sysMenuApplicationService.deleteMenu(menuId, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(MENU_MANAGE_QUERY)
|
||||
@Operation(summary = "根据ID查询菜单")
|
||||
@GetMapping("/{menuId}")
|
||||
public ReqResult<MenuNodeDto> getMenuById(@PathVariable Long menuId) {
|
||||
if (menuId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
SysMenu menu = sysMenuApplicationService.getMenuById(menuId);
|
||||
if (menu == null) {
|
||||
return ReqResult.error("菜单不存在");
|
||||
}
|
||||
MenuNodeDto dto = new MenuNodeDto();
|
||||
BeanUtils.copyProperties(menu, dto);
|
||||
|
||||
ReqResult<List<ResourceNodeDto>> resourceListByMenuId = getResourceListByMenuId(menuId);
|
||||
dto.setResourceTree(resourceListByMenuId.getData());
|
||||
|
||||
I18nUtil.replaceSystemMessage(dto);
|
||||
return ReqResult.success(dto);
|
||||
}
|
||||
|
||||
@RequireResource(MENU_MANAGE_QUERY)
|
||||
@Operation(summary = "根据编码查询菜单")
|
||||
@GetMapping("/code/{menuCode}")
|
||||
public ReqResult<MenuNodeDto> getMenuByCode(@PathVariable String menuCode) {
|
||||
if (StringUtils.isBlank(menuCode)) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
SysMenu menu = sysMenuApplicationService.getMenuByCode(menuCode);
|
||||
if (menu == null) {
|
||||
return ReqResult.error("菜单不存在");
|
||||
}
|
||||
MenuNodeDto dto = new MenuNodeDto();
|
||||
BeanUtils.copyProperties(menu, dto);
|
||||
|
||||
ReqResult<List<ResourceNodeDto>> resourceListByMenuId = getResourceListByMenuId(menu.getId());
|
||||
dto.setResourceTree(resourceListByMenuId.getData());
|
||||
|
||||
I18nUtil.replaceSystemMessage(dto);
|
||||
return ReqResult.success(dto);
|
||||
}
|
||||
|
||||
@RequireResource(MENU_MANAGE_QUERY)
|
||||
@Operation(summary = "根据条件查询菜单列表(树形结构)")
|
||||
@GetMapping("/list")
|
||||
public ReqResult<List<MenuNodeDto>> getMenuList(SysMenuQueryDto queryDto) {
|
||||
SysMenu sysMenu = new SysMenu();
|
||||
if (queryDto != null) {
|
||||
BeanUtils.copyProperties(queryDto, sysMenu);
|
||||
}
|
||||
List<SysMenu> menuList = sysMenuApplicationService.getMenuList(sysMenu);
|
||||
|
||||
List<MenuNodeDto> menuDtoList = menuList.stream().map(menu -> {
|
||||
MenuNodeDto dto = new MenuNodeDto();
|
||||
BeanUtils.copyProperties(menu, dto);
|
||||
return dto;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
List<MenuNodeDto> treeList = MenuTreeUtil.buildMenuTree(menuDtoList);
|
||||
// 统一处理:如果 parentId 为空,则设置为 0
|
||||
fillMenuParentIdIfNull(treeList);
|
||||
|
||||
// 如果 queryDto 为 null 或所有字段都为空(查询完整菜单树),需要添加根节点
|
||||
if (isMenuQueryEmpty(queryDto)) {
|
||||
MenuNodeDto rootNode = getMenuRoot();
|
||||
rootNode.setChildren(treeList);
|
||||
List<MenuNodeDto> result = new ArrayList<>();
|
||||
result.add(rootNode);
|
||||
|
||||
I18nUtil.replaceSystemMessage(treeList);
|
||||
return ReqResult.success(result);
|
||||
}
|
||||
|
||||
I18nUtil.replaceSystemMessage(treeList);
|
||||
return ReqResult.success(treeList);
|
||||
}
|
||||
|
||||
@RequireResource(MENU_MANAGE_MODIFY)
|
||||
@Operation(summary = "批量调整菜单顺序")
|
||||
@PostMapping("/update-sort")
|
||||
public ReqResult<Void> updateMenuSort(@RequestBody SortIndexUpdateDto dto) {
|
||||
if (dto == null || CollectionUtils.isEmpty(dto.getItems())) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
List<SortIndex> sortIndexList = dto.getItems().stream()
|
||||
.map(item -> {
|
||||
SortIndex model = new SortIndex();
|
||||
model.setId(item.getId());
|
||||
model.setParentId(item.getParentId());
|
||||
model.setSortIndex(item.getSortIndex());
|
||||
return model;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
sysMenuApplicationService.batchUpdateMenuSort(sortIndexList, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(MENU_MANAGE_MODIFY)
|
||||
@Operation(summary = "菜单绑定资源")
|
||||
@PostMapping("/bind-resource")
|
||||
public ReqResult<Void> bindResource(@RequestBody SysMenuBindResourceDto dto) {
|
||||
if (dto == null || dto.getMenuId() == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
MenuNode menuNode = MenuBindResourceModelConverter.convertToMenuNode(dto);
|
||||
sysMenuApplicationService.bindResource(menuNode, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(MENU_MANAGE_QUERY)
|
||||
@Operation(summary = "查询菜单绑定的资源(树形结构)")
|
||||
@GetMapping("/list-resource/{menuId}")
|
||||
public ReqResult<List<ResourceNodeDto>> getResourceListByMenuId(@PathVariable Long menuId) {
|
||||
if (menuId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
|
||||
// 查询菜单绑定的资源列表(可能为空)
|
||||
List<SysMenuResource> menuResourceList = sysMenuApplicationService.getResourceListByMenuId(menuId);
|
||||
|
||||
// 查询所有资源列表(构建完整资源树)
|
||||
List<SysResource> resourceList = sysResourceApplicationService.getResourceList(null);
|
||||
|
||||
// 构建资源父子关系映射
|
||||
Map<Long, List<SysResource>> resourceChildrenMap = new HashMap<>();
|
||||
for (SysResource resource : resourceList) {
|
||||
Long parentId = resource.getParentId();
|
||||
if (parentId != null && parentId != 0L) {
|
||||
resourceChildrenMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(resource);
|
||||
}
|
||||
}
|
||||
|
||||
// 提取 资源ID->绑定类型 映射(初始映射,只包含数据库中存储的资源)
|
||||
Map<Long, Integer> resourceBindTypeMap = CollectionUtils.isEmpty(menuResourceList)
|
||||
? new HashMap<>()
|
||||
: menuResourceList.stream()
|
||||
.filter(mr -> mr.getResourceId() != null)
|
||||
.collect(Collectors.toMap(
|
||||
SysMenuResource::getResourceId,
|
||||
SysMenuResource::getResourceBindType,
|
||||
(existing, replacement) -> existing // 如果有重复,保留第一个
|
||||
));
|
||||
|
||||
// 对于绑定类型为 ALL 的资源,需要将其所有子资源也标记为 ALL
|
||||
// 注意:ALL 类型的资源会强制覆盖子资源的绑定类型
|
||||
Set<Long> allBindResourceIds = new HashSet<>();
|
||||
for (SysMenuResource menuResource : menuResourceList) {
|
||||
if (menuResource.getResourceId() == null) {
|
||||
continue;
|
||||
}
|
||||
Integer bindType = menuResource.getResourceBindType();
|
||||
if (BindTypeEnum.ALL.getCode().equals(bindType)) {
|
||||
allBindResourceIds.add(menuResource.getResourceId());
|
||||
}
|
||||
}
|
||||
|
||||
// 对于 ALL 类型的资源,递归收集所有子资源,并强制设置为 ALL
|
||||
Set<Long> processedResourceIds = new HashSet<>();
|
||||
for (Long allBindResourceId : allBindResourceIds) {
|
||||
collectChildrenAndSetBindType(allBindResourceId, resourceChildrenMap,
|
||||
resourceBindTypeMap, processedResourceIds, BindTypeEnum.ALL.getCode());
|
||||
}
|
||||
|
||||
// 构建资源树(完整资源树)
|
||||
// 注意:对于 PART 类型的资源,子节点会根据 resourceBindTypeMap 中的具体值来标记
|
||||
// 如果子节点不在 resourceBindTypeMap 中,则标记为 NONE(未绑定)
|
||||
List<ResourceNodeDto> resourceTree = ResourceTreeUtil.buildResourceTree(resourceList, resourceBindTypeMap);
|
||||
|
||||
// 检查资源树中是否已经有 root 根节点
|
||||
boolean hasRootNode = resourceTree.stream()
|
||||
.anyMatch(node -> "root".equals(node.getCode()) && node.getId() != null && node.getId() == 0L);
|
||||
|
||||
// 如果没有 root 根节点,则添加
|
||||
if (!hasRootNode) {
|
||||
ResourceNodeDto rootNode = getResourceRoot();
|
||||
|
||||
// 根据业务逻辑设置根节点的绑定类型
|
||||
Integer rootBindType;
|
||||
if (CollectionUtils.isEmpty(menuResourceList)) {
|
||||
// 如果菜单绑定的资源列表为空,设置为 NONE
|
||||
rootBindType = BindTypeEnum.NONE.getCode();
|
||||
} else if (CollectionUtils.isEmpty(resourceTree)) {
|
||||
// 如果资源树为空,设置为 NONE
|
||||
rootBindType = BindTypeEnum.NONE.getCode();
|
||||
} else {
|
||||
// 检查所有根节点的直接子节点(parentId=0或null)的绑定类型是否都是 ALL
|
||||
// 从原始资源列表中找出所有根节点(parentId=0或null),检查它们的绑定类型
|
||||
List<SysResource> rootResources = resourceList.stream()
|
||||
.filter(resource -> resource.getParentId() == null || resource.getParentId() == 0L)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (rootResources.isEmpty()) {
|
||||
rootBindType = BindTypeEnum.NONE.getCode();
|
||||
} else {
|
||||
boolean allChildrenAreAll = rootResources.stream()
|
||||
.allMatch(resource -> {
|
||||
Integer bindType = resourceBindTypeMap.get(resource.getId());
|
||||
return bindType != null && BindTypeEnum.ALL.getCode().equals(bindType);
|
||||
});
|
||||
if (allChildrenAreAll) {
|
||||
rootBindType = BindTypeEnum.ALL.getCode();
|
||||
} else {
|
||||
rootBindType = BindTypeEnum.PART.getCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
rootNode.setResourceBindType(rootBindType);
|
||||
rootNode.setChildren(resourceTree);
|
||||
List<ResourceNodeDto> result = new ArrayList<>();
|
||||
result.add(rootNode);
|
||||
|
||||
I18nUtil.replaceSystemMessage(result);
|
||||
return ReqResult.success(result);
|
||||
}
|
||||
|
||||
// 如果已经有 root 根节点,直接返回资源树
|
||||
I18nUtil.replaceSystemMessage(resourceTree);
|
||||
return ReqResult.success(resourceTree);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归收集某个资源的所有子资源,并强制设置绑定类型
|
||||
* 注意:此方法用于处理 ALL 类型的资源,会强制覆盖子资源的绑定类型
|
||||
*/
|
||||
private void collectChildrenAndSetBindType(Long resourceId,
|
||||
Map<Long, List<SysResource>> resourceChildrenMap,
|
||||
Map<Long, Integer> resourceBindTypeMap,
|
||||
Set<Long> processedResourceIds,
|
||||
Integer bindType) {
|
||||
if (processedResourceIds.contains(resourceId)) {
|
||||
return; // 避免重复处理
|
||||
}
|
||||
processedResourceIds.add(resourceId);
|
||||
|
||||
List<SysResource> children = resourceChildrenMap.get(resourceId);
|
||||
if (CollectionUtils.isEmpty(children)) {
|
||||
return;
|
||||
}
|
||||
for (SysResource child : children) {
|
||||
if (child.getId() == null) {
|
||||
continue;
|
||||
}
|
||||
// 强制设置子资源的绑定类型(ALL 类型会覆盖子资源的原有绑定类型)
|
||||
resourceBindTypeMap.put(child.getId(), bindType);
|
||||
// 递归处理子资源的子资源
|
||||
collectChildrenAndSetBindType(child.getId(), resourceChildrenMap, resourceBindTypeMap,
|
||||
processedResourceIds, bindType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断菜单查询条件是否为空
|
||||
* @param queryDto 查询条件
|
||||
* @return true表示查询条件为空(null或所有字段都为null/空值)
|
||||
*/
|
||||
private boolean isMenuQueryEmpty(SysMenuQueryDto queryDto) {
|
||||
if (queryDto == null) {
|
||||
return true;
|
||||
}
|
||||
return StringUtils.isBlank(queryDto.getCode())
|
||||
&& StringUtils.isBlank(queryDto.getName())
|
||||
&& queryDto.getSource() == null
|
||||
&& queryDto.getParentId() == null
|
||||
&& queryDto.getStatus() == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归处理菜单树,如果 parentId 为空则设置为 0
|
||||
*/
|
||||
private void fillMenuParentIdIfNull(List<MenuNodeDto> list) {
|
||||
if (CollectionUtils.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
for (MenuNodeDto node : list) {
|
||||
if (node.getParentId() == null) {
|
||||
node.setParentId(0L);
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(node.getChildren())) {
|
||||
fillMenuParentIdIfNull(node.getChildren());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MenuNodeDto getMenuRoot() {
|
||||
MenuNodeDto rootNode = new MenuNodeDto();
|
||||
rootNode.setId(0L);
|
||||
rootNode.setCode("root");
|
||||
rootNode.setName("根节点");
|
||||
rootNode.setParentId(null);
|
||||
rootNode.setDescription("根节点");
|
||||
rootNode.setSource(SourceEnum.SYSTEM.getCode());
|
||||
rootNode.setOpenType(OpenTypeEnum.CURRENT_TAB.getCode());
|
||||
rootNode.setStatus(YesOrNoEnum.Y.getKey());
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
private ResourceNodeDto getResourceRoot() {
|
||||
ResourceNodeDto rootNode = new ResourceNodeDto();
|
||||
rootNode.setId(0L);
|
||||
rootNode.setCode("root");
|
||||
rootNode.setName("根节点");
|
||||
rootNode.setDescription("根节点");
|
||||
rootNode.setParentId(null);
|
||||
rootNode.setSource(SourceEnum.SYSTEM.getCode());
|
||||
rootNode.setStatus(YesOrNoEnum.Y.getKey());
|
||||
return rootNode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package com.xspaceagi.system.web.controller.permission;
|
||||
|
||||
import com.xspaceagi.system.application.dto.permission.*;
|
||||
import com.xspaceagi.system.application.service.SysResourceApplicationService;
|
||||
import com.xspaceagi.system.domain.model.SortIndex;
|
||||
import com.xspaceagi.system.infra.dao.entity.SysResource;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.enums.ResourceTypeEnum;
|
||||
import com.xspaceagi.system.spec.enums.SourceEnum;
|
||||
import com.xspaceagi.system.spec.enums.YesOrNoEnum;
|
||||
import com.xspaceagi.system.spec.utils.I18nUtil;
|
||||
import com.xspaceagi.system.web.controller.base.BaseController;
|
||||
import com.xspaceagi.system.application.converter.ResourceTreeUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.*;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "权限管理-资源", description = "资源相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/system/resource")
|
||||
public class SysResourceController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private SysResourceApplicationService sysResourceApplicationService;
|
||||
|
||||
@RequireResource(RESOURCE_MANAGE_ADD)
|
||||
@Operation(summary = "添加资源")
|
||||
@PostMapping(value = "/add",produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Void> addResource(@RequestBody SysResourceAddDto dto) {
|
||||
if (dto == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
if (dto.getSource() != null && SourceEnum.isInValid(dto.getSource())) {
|
||||
return ReqResult.error("参数source错误");
|
||||
}
|
||||
if (dto.getStatus() != null && YesOrNoEnum.isInValid(dto.getStatus())) {
|
||||
return ReqResult.error("参数status错误");
|
||||
}
|
||||
if (dto.getType() != null && ResourceTypeEnum.isInValid(dto.getType())) {
|
||||
return ReqResult.error("参数type错误");
|
||||
}
|
||||
|
||||
SysResource resource = new SysResource();
|
||||
BeanUtils.copyProperties(dto, resource);
|
||||
sysResourceApplicationService.addResource(resource, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(RESOURCE_MANAGE_MODIFY)
|
||||
@Operation(summary = "更新资源")
|
||||
@PostMapping("/update")
|
||||
public ReqResult<Void> updateResource(@RequestBody SysResourceUpdateDto dto) {
|
||||
if (dto == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
if (dto.getSource() != null && SourceEnum.isInValid(dto.getSource())) {
|
||||
return ReqResult.error("参数source错误");
|
||||
}
|
||||
if (dto.getStatus() != null && YesOrNoEnum.isInValid(dto.getStatus())) {
|
||||
return ReqResult.error("参数status错误");
|
||||
}
|
||||
if (dto.getType() != null && ResourceTypeEnum.isInValid(dto.getType())) {
|
||||
return ReqResult.error("参数type错误");
|
||||
}
|
||||
|
||||
SysResource resource = new SysResource();
|
||||
BeanUtils.copyProperties(dto, resource);
|
||||
resource.setCode(null); // code不允许更新
|
||||
sysResourceApplicationService.updateResource(resource, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(RESOURCE_MANAGE_DELETE)
|
||||
@Operation(summary = "删除资源")
|
||||
@PostMapping("/delete/{resourceId}")
|
||||
public ReqResult<Void> deleteResource(@PathVariable Long resourceId) {
|
||||
if (resourceId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
sysResourceApplicationService.deleteResource(resourceId, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(RESOURCE_MANAGE_QUERY)
|
||||
@Operation(summary = "根据ID查询资源")
|
||||
@GetMapping("/{resourceId}")
|
||||
public ReqResult<ResourceNodeDto> getResourceById(@PathVariable Long resourceId) {
|
||||
if (resourceId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
SysResource resource = sysResourceApplicationService.getResourceById(resourceId);
|
||||
if (resource == null) {
|
||||
return ReqResult.error("资源不存在");
|
||||
}
|
||||
ResourceNodeDto dto = new ResourceNodeDto();
|
||||
BeanUtils.copyProperties(resource, dto);
|
||||
|
||||
I18nUtil.replaceSystemMessage(dto);
|
||||
return ReqResult.success(dto);
|
||||
}
|
||||
|
||||
@RequireResource(RESOURCE_MANAGE_QUERY)
|
||||
@Operation(summary = "根据编码查询资源")
|
||||
@GetMapping("/code/{resourceCode}")
|
||||
public ReqResult<ResourceNodeDto> getResourceByCode(@PathVariable String resourceCode) {
|
||||
if (StringUtils.isBlank(resourceCode)) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
SysResource resource = sysResourceApplicationService.getResourceByCode(resourceCode);
|
||||
if (resource == null) {
|
||||
return ReqResult.error("资源不存在");
|
||||
}
|
||||
ResourceNodeDto dto = new ResourceNodeDto();
|
||||
BeanUtils.copyProperties(resource, dto);
|
||||
|
||||
I18nUtil.replaceSystemMessage(dto);
|
||||
return ReqResult.success(dto);
|
||||
}
|
||||
|
||||
@RequireResource(RESOURCE_MANAGE_MODIFY)
|
||||
@Operation(summary = "调整资源顺序")
|
||||
@PostMapping("/update-sort")
|
||||
public ReqResult<Void> updateSortIndex(@RequestBody SortIndexUpdateDto dto) {
|
||||
if (dto == null || CollectionUtils.isEmpty(dto.getItems())) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
List<SortIndex> sortIndexList = dto.getItems().stream()
|
||||
.map(item -> {
|
||||
SortIndex model = new SortIndex();
|
||||
model.setId(item.getId());
|
||||
model.setParentId(item.getParentId());
|
||||
model.setSortIndex(item.getSortIndex());
|
||||
return model;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
sysResourceApplicationService.batchUpdateResourceSort(sortIndexList, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(RESOURCE_MANAGE_QUERY)
|
||||
@Operation(summary = "根据条件查询资源列表(树形结构)")
|
||||
@GetMapping("/list")
|
||||
public ReqResult<List<ResourceNodeDto>> getResourceList(SysResourceQueryDto queryDto) {
|
||||
SysResource sysResource = new SysResource();
|
||||
if (queryDto != null) {
|
||||
BeanUtils.copyProperties(queryDto, sysResource);
|
||||
}
|
||||
List<SysResource> resourceList = sysResourceApplicationService.getResourceList(sysResource);
|
||||
|
||||
// 构建资源树
|
||||
List<ResourceNodeDto> tree = ResourceTreeUtil.buildResourceTree(resourceList);
|
||||
|
||||
// 如果 queryDto 为 null 或所有字段都为空(查询完整资源树),需要添加根节点
|
||||
if (isQueryEmpty(queryDto)) {
|
||||
ResourceNodeDto rootNode = new ResourceNodeDto();
|
||||
rootNode.setId(0L);
|
||||
rootNode.setCode("root");
|
||||
rootNode.setName("根节点");
|
||||
rootNode.setParentId(null);
|
||||
rootNode.setChildren(tree);
|
||||
List<ResourceNodeDto> result = new ArrayList<>();
|
||||
result.add(rootNode);
|
||||
|
||||
I18nUtil.replaceSystemMessage(result);
|
||||
return ReqResult.success(result);
|
||||
}
|
||||
|
||||
I18nUtil.replaceSystemMessage(tree);
|
||||
return ReqResult.success(tree);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断查询条件是否为空
|
||||
*/
|
||||
private boolean isQueryEmpty(SysResourceQueryDto queryDto) {
|
||||
if (queryDto == null) {
|
||||
return true;
|
||||
}
|
||||
return StringUtils.isBlank(queryDto.getCode())
|
||||
&& StringUtils.isBlank(queryDto.getName())
|
||||
&& queryDto.getSource() == null
|
||||
&& queryDto.getType() == null
|
||||
&& queryDto.getParentId() == null
|
||||
&& queryDto.getStatus() == null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
package com.xspaceagi.system.web.controller.permission;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.xspaceagi.system.application.converter.MenuNodeConverter;
|
||||
import com.xspaceagi.system.application.converter.RoleBindMenuModelConverter;
|
||||
import com.xspaceagi.system.application.converter.SysDataPermissionConverter;
|
||||
import com.xspaceagi.system.application.dto.permission.*;
|
||||
import com.xspaceagi.system.application.service.SysDataPermissionApplicationService;
|
||||
import com.xspaceagi.system.application.service.SysRoleApplicationService;
|
||||
import com.xspaceagi.system.application.service.SysSubjectPermissionApplicationService;
|
||||
import com.xspaceagi.system.domain.model.MenuNode;
|
||||
import com.xspaceagi.system.domain.model.RoleBindMenuModel;
|
||||
import com.xspaceagi.system.domain.model.SortIndex;
|
||||
import com.xspaceagi.system.infra.dao.entity.SysDataPermission;
|
||||
import com.xspaceagi.system.infra.dao.entity.SysRole;
|
||||
import com.xspaceagi.system.infra.dao.entity.User;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.enums.PermissionSubjectTypeEnum;
|
||||
import com.xspaceagi.system.spec.enums.PermissionTargetTypeEnum;
|
||||
import com.xspaceagi.system.spec.enums.SourceEnum;
|
||||
import com.xspaceagi.system.spec.enums.StatusEnum;
|
||||
import com.xspaceagi.system.spec.jackson.JsonSerializeUtil;
|
||||
import com.xspaceagi.system.spec.utils.I18nUtil;
|
||||
import com.xspaceagi.system.web.controller.base.BaseController;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import com.xspaceagi.system.spec.dto.PageQueryVo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.*;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "权限管理-角色", description = "角色相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/system/role")
|
||||
public class SysRoleController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private SysRoleApplicationService sysRoleApplicationService;
|
||||
@Autowired
|
||||
private SysDataPermissionApplicationService sysDataPermissionApplicationService;
|
||||
@Autowired
|
||||
private SysSubjectPermissionApplicationService sysSubjectPermissionApplicationService;
|
||||
|
||||
@RequireResource(ROLE_MANAGE_ADD)
|
||||
@Operation(summary = "添加角色")
|
||||
@PostMapping(value = "/add", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReqResult<Void> addRole(@RequestBody SysRoleAddDto dto) {
|
||||
if (dto == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
if (dto.getSource() != null && SourceEnum.isInValid(dto.getSource())) {
|
||||
return ReqResult.error("参数source错误");
|
||||
}
|
||||
if (dto.getStatus() != null && StatusEnum.isInValid(dto.getStatus())) {
|
||||
return ReqResult.error("参数status错误");
|
||||
}
|
||||
|
||||
SysRole role = new SysRole();
|
||||
BeanUtils.copyProperties(dto, role);
|
||||
sysRoleApplicationService.addRole(role, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(ROLE_MANAGE_MODIFY)
|
||||
@Operation(summary = "更新角色")
|
||||
@PostMapping("/update")
|
||||
public ReqResult<Void> updateRole(@RequestBody SysRoleUpdateDto dto) {
|
||||
if (dto == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
if (dto.getSource() != null && SourceEnum.isInValid(dto.getSource())) {
|
||||
return ReqResult.error("参数source错误");
|
||||
}
|
||||
if (dto.getStatus() != null && StatusEnum.isInValid(dto.getStatus())) {
|
||||
return ReqResult.error("参数status错误");
|
||||
}
|
||||
|
||||
SysRole role = new SysRole();
|
||||
BeanUtils.copyProperties(dto, role);
|
||||
role.setCode(null); // code不允许更新
|
||||
sysRoleApplicationService.updateRole(role, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(ROLE_MANAGE_DELETE)
|
||||
@Operation(summary = "删除角色")
|
||||
@PostMapping("/delete/{roleId}")
|
||||
public ReqResult<Void> deleteRole(@PathVariable Long roleId) {
|
||||
if (roleId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
|
||||
sysRoleApplicationService.deleteRole(roleId, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(ROLE_MANAGE_QUERY)
|
||||
@Operation(summary = "根据ID查询角色")
|
||||
@GetMapping("/{roleId}")
|
||||
public ReqResult<SysRoleDto> getRoleById(@PathVariable Long roleId) {
|
||||
if (roleId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
|
||||
SysRole role = sysRoleApplicationService.getRoleById(roleId);
|
||||
if (role == null) {
|
||||
return ReqResult.error("角色不存在");
|
||||
}
|
||||
SysRoleDto roleDto = new SysRoleDto();
|
||||
BeanUtils.copyProperties(role, roleDto);
|
||||
|
||||
// SysDataPermission dataPermission = sysDataPermissionApplicationService.getByTarget(PermissionTargetTypeEnum.ROLE, role.getId());
|
||||
// if (dataPermission != null) {
|
||||
// roleDto.setModelIds(dataPermission.getModelIds());
|
||||
// roleDto.setTokenLimit(dataPermission.getTokenLimit());
|
||||
// }
|
||||
I18nUtil.replaceSystemMessage(roleDto);
|
||||
return ReqResult.success(roleDto);
|
||||
}
|
||||
|
||||
@RequireResource(ROLE_MANAGE_QUERY)
|
||||
@Operation(summary = "根据编码查询角色")
|
||||
@GetMapping("/code/{roleCode}")
|
||||
public ReqResult<SysRoleDto> getRoleByCode(@PathVariable String roleCode) {
|
||||
if (StringUtils.isBlank(roleCode)) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
|
||||
SysRole role = sysRoleApplicationService.getRoleByCode(roleCode);
|
||||
if (role == null) {
|
||||
return ReqResult.error("角色不存在");
|
||||
}
|
||||
SysRoleDto roleDto = new SysRoleDto();
|
||||
BeanUtils.copyProperties(role, roleDto);
|
||||
|
||||
// SysDataPermission dataPermission = sysDataPermissionApplicationService.getByTarget(PermissionTargetTypeEnum.ROLE, role.getId());
|
||||
// if (dataPermission != null) {
|
||||
// roleDto.setModelIds(dataPermission.getModelIds());
|
||||
// roleDto.setTokenLimit(dataPermission.getTokenLimit());
|
||||
// }
|
||||
I18nUtil.replaceSystemMessage(roleDto);
|
||||
return ReqResult.success(roleDto);
|
||||
}
|
||||
|
||||
@RequireResource(ROLE_MANAGE_MODIFY)
|
||||
@Operation(summary = "调整角色顺序")
|
||||
@PostMapping("/update-sort")
|
||||
public ReqResult<Void> updateSortIndex(@RequestBody SortIndexUpdateDto dto) {
|
||||
if (dto == null || CollectionUtils.isEmpty(dto.getItems())) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
List<SortIndex> sortIndexList = dto.getItems().stream()
|
||||
.map(item -> {
|
||||
SortIndex model = new SortIndex();
|
||||
model.setId(item.getId());
|
||||
model.setParentId(item.getParentId());
|
||||
model.setSortIndex(item.getSortIndex());
|
||||
return model;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
sysRoleApplicationService.batchUpdateRoleSort(sortIndexList, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(ROLE_MANAGE_QUERY)
|
||||
@Operation(summary = "根据条件查询角色")
|
||||
@GetMapping("/list")
|
||||
public ReqResult<List<SysRoleDto>> getRoleList(SysRoleQueryDto dto) {
|
||||
SysRole sysRole = new SysRole();
|
||||
if (dto != null) {
|
||||
BeanUtils.copyProperties(dto, sysRole);
|
||||
}
|
||||
|
||||
List<SysRole> roleList = sysRoleApplicationService.getRoleList(sysRole);
|
||||
if (CollectionUtils.isEmpty(roleList)) {
|
||||
return ReqResult.success();
|
||||
}
|
||||
// List<Long> roleIds = roleList.stream().map(SysRole::getId).toList();
|
||||
// List<SysDataPermission> dataPermissionList = sysDataPermissionApplicationService.getByTargetList(PermissionTargetTypeEnum.ROLE, roleIds);
|
||||
// Map<Long, SysDataPermission> permissionMap = dataPermissionList.stream()
|
||||
// .collect(Collectors.toMap(SysDataPermission::getTargetId, p -> p, (a, b) -> a));
|
||||
|
||||
List<SysRoleDto> dtoList = roleList.stream().map(r -> {
|
||||
SysRoleDto roleDto = new SysRoleDto();
|
||||
BeanUtils.copyProperties(r, roleDto);
|
||||
// SysDataPermission dataPermission = permissionMap.get(r.getId());
|
||||
// if (dataPermission != null) {
|
||||
// roleDto.setModelIds(dataPermission.getModelIds());
|
||||
// roleDto.setTokenLimit(dataPermission.getTokenLimit());
|
||||
// }
|
||||
return roleDto;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
I18nUtil.replaceSystemMessage(dtoList);
|
||||
return ReqResult.success(dtoList);
|
||||
}
|
||||
|
||||
@RequireResource(ROLE_MANAGE_BIND_USER)
|
||||
@Operation(summary = "分页查询角色已绑定的用户,支持按userName模糊筛选")
|
||||
@PostMapping("/list-user")
|
||||
public ReqResult<IPage<SysUserDto>> getUserListByRoleId(@RequestBody PageQueryVo<SysRoleUserQueryDto> pageQueryVo) {
|
||||
if (pageQueryVo == null || pageQueryVo.getQueryFilter() == null || pageQueryVo.getQueryFilter().getRoleId() == null) {
|
||||
return ReqResult.error("角色ID不能为空");
|
||||
}
|
||||
SysRoleUserQueryDto queryDto = pageQueryVo.getQueryFilter();
|
||||
Long roleId = queryDto.getRoleId();
|
||||
String userName = queryDto.getUserName();
|
||||
long pageNo = pageQueryVo.getPageNo() != null ? pageQueryVo.getPageNo() : 1L;
|
||||
long pageSize = pageQueryVo.getPageSize() != null ? pageQueryVo.getPageSize() : 10L;
|
||||
|
||||
IPage<User> userPage = sysRoleApplicationService.getUserPageByRoleId(roleId, userName, pageNo, pageSize);
|
||||
List<SysUserDto> dtoList = userPage.getRecords().stream().map(user -> {
|
||||
SysUserDto userDto = new SysUserDto();
|
||||
BeanUtils.copyProperties(user, userDto);
|
||||
userDto.setUserId(user.getId());
|
||||
return userDto;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
IPage<SysUserDto> resultPage = new Page<>(userPage.getCurrent(), userPage.getSize(), userPage.getTotal());
|
||||
resultPage.setRecords(dtoList);
|
||||
return ReqResult.success(resultPage);
|
||||
}
|
||||
|
||||
@RequireResource(ROLE_MANAGE_BIND_USER)
|
||||
@Operation(summary = "角色绑定用户(全量覆盖)")
|
||||
@PostMapping("/bind-user")
|
||||
public ReqResult<Void> bindUser(@RequestBody SysRoleBindUserDto dto) {
|
||||
if (dto == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
sysRoleApplicationService.roleBindUser(dto.getRoleId(), dto.getUserIds(), getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(ROLE_MANAGE_BIND_USER)
|
||||
@Operation(summary = "角色添加用户")
|
||||
@PostMapping("/add-user")
|
||||
public ReqResult<Void> addUser(@RequestBody SysRoleBindUserSingleDto dto) {
|
||||
if (dto == null || dto.getRoleId() == null || dto.getUserId() == null) {
|
||||
return ReqResult.error("角色ID和用户ID不能为空");
|
||||
}
|
||||
sysRoleApplicationService.roleAddUser(dto.getRoleId(), dto.getUserId(), getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(ROLE_MANAGE_BIND_USER)
|
||||
@Operation(summary = "角色移除用户")
|
||||
@PostMapping("/remove-user")
|
||||
public ReqResult<Void> removeUser(@RequestBody SysRoleBindUserSingleDto dto) {
|
||||
if (dto == null || dto.getRoleId() == null || dto.getUserId() == null) {
|
||||
return ReqResult.error("角色ID和用户ID不能为空");
|
||||
}
|
||||
sysRoleApplicationService.roleRemoveUser(dto.getRoleId(), dto.getUserId(), getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(ROLE_MANAGE_BIND_DATA)
|
||||
@Operation(summary = "查询角色数据权限")
|
||||
@GetMapping("/data-permission/{roleId}")
|
||||
public ReqResult<SysDataPermissionBindDto> getRoleDataPermission(@PathVariable Long roleId) {
|
||||
if (roleId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
SysDataPermission dataPermission = sysDataPermissionApplicationService.getByTarget(PermissionTargetTypeEnum.ROLE, roleId);
|
||||
SysDataPermissionBindDto result = SysDataPermissionConverter.toDto(dataPermission);
|
||||
result = result == null ? new SysDataPermissionBindDto() : result;
|
||||
|
||||
result.setModelIds(sysSubjectPermissionApplicationService.listSubjectIdsByTarget(
|
||||
PermissionTargetTypeEnum.ROLE, roleId, PermissionSubjectTypeEnum.MODEL));
|
||||
result.setAgentIds(sysSubjectPermissionApplicationService.listSubjectIdsByTarget(
|
||||
PermissionTargetTypeEnum.ROLE, roleId, PermissionSubjectTypeEnum.AGENT));
|
||||
result.setPageAgentIds(sysSubjectPermissionApplicationService.listSubjectIdsByTarget(
|
||||
PermissionTargetTypeEnum.ROLE, roleId, PermissionSubjectTypeEnum.PAGE));
|
||||
Map<String, String> openApiConfigMap = sysSubjectPermissionApplicationService.listSubjectKeyConfigByTarget(
|
||||
PermissionTargetTypeEnum.ROLE, roleId, PermissionSubjectTypeEnum.OPEN_API);
|
||||
List<SysDataPermissionBindDto.OpenApiConfig> openApiConfigs = openApiConfigMap.entrySet().stream()
|
||||
.map(entry -> {
|
||||
SysDataPermissionBindDto.OpenApiConfig config = new SysDataPermissionBindDto.OpenApiConfig();
|
||||
config.setKey(entry.getKey());
|
||||
if (StringUtils.isNotBlank(entry.getValue())) {
|
||||
try {
|
||||
Map<String, Integer> valueMap = JsonSerializeUtil.parseObject(entry.getValue(), new TypeReference<Map<String, Integer>>() {});
|
||||
config.setRpm(valueMap == null ? null : valueMap.get("rpm"));
|
||||
config.setRpd(valueMap == null ? null : valueMap.get("rpd"));
|
||||
} catch (Exception ignore) {
|
||||
config.setRpm(null);
|
||||
config.setRpd(null);
|
||||
}
|
||||
}
|
||||
return config;
|
||||
})
|
||||
.toList();
|
||||
result.setOpenApiConfigs(openApiConfigs);
|
||||
|
||||
result.setKnowledgeIds(sysSubjectPermissionApplicationService.listSubjectIdsByTarget(
|
||||
PermissionTargetTypeEnum.ROLE, roleId, PermissionSubjectTypeEnum.KNOWLEDGE));
|
||||
|
||||
return ReqResult.success(result);
|
||||
}
|
||||
|
||||
@RequireResource(ROLE_MANAGE_BIND_DATA)
|
||||
@Operation(summary = "角色绑定数据权限(全量覆盖)")
|
||||
@PostMapping("/bind-data-permission")
|
||||
public ReqResult<Void> bindDataPermission(@RequestBody SysRoleBindDataPermissionDto dto) {
|
||||
if (dto == null || dto.getRoleId() == null) {
|
||||
return ReqResult.error("角色ID不能为空");
|
||||
}
|
||||
SysDataPermissionBindDto bindDto = dto.getDataPermission();
|
||||
SysDataPermission dataPermission = SysDataPermissionConverter.toEntity(bindDto);
|
||||
sysRoleApplicationService.bindDataPermission(dto.getRoleId(), dataPermission, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(ROLE_MANAGE_BIND_MENU)
|
||||
@Operation(summary = "角色绑定菜单(全量覆盖)")
|
||||
@PostMapping("/bind-menu")
|
||||
public ReqResult<Void> bindMenu(@RequestBody SysRoleBindMenuDto dto) {
|
||||
if (dto == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
RoleBindMenuModel model = RoleBindMenuModelConverter.convertToModel(dto);
|
||||
sysRoleApplicationService.bindMenu(model, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(ROLE_MANAGE_BIND_MENU)
|
||||
@Operation(summary = "查询角色已绑定的菜单(树形结构)")
|
||||
@GetMapping("/list-menu/{roleId}")
|
||||
public ReqResult<List<MenuNodeDto>> getMenuListByRoleId(@PathVariable Long roleId) {
|
||||
if (roleId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
|
||||
// 获取处理好的菜单树(已包含资源详细信息)
|
||||
List<MenuNode> menuNodeList = sysRoleApplicationService.getMenuTreeByRoleId(roleId);
|
||||
|
||||
// 转换为DTO
|
||||
List<MenuNodeDto> menuDtoList = MenuNodeConverter.convertMenuTreeToDtoTree(menuNodeList);
|
||||
|
||||
I18nUtil.replaceSystemMessage(menuDtoList);
|
||||
return ReqResult.success(menuDtoList);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package com.xspaceagi.system.web.controller.permission;
|
||||
|
||||
import com.xspaceagi.system.application.dto.permission.*;
|
||||
import com.xspaceagi.system.application.service.SysDataPermissionApplicationService;
|
||||
import com.xspaceagi.system.application.service.SysGroupApplicationService;
|
||||
import com.xspaceagi.system.application.service.SysRoleApplicationService;
|
||||
import com.xspaceagi.system.application.service.impl.SysUserPermissionCacheServiceImpl;
|
||||
import com.xspaceagi.system.infra.dao.entity.SysGroup;
|
||||
import com.xspaceagi.system.infra.dao.entity.SysRole;
|
||||
import com.xspaceagi.system.sdk.service.dto.UserDataPermissionDto;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.annotation.SaasAdmin;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.utils.I18nUtil;
|
||||
import com.xspaceagi.system.web.controller.base.BaseController;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.*;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "权限管理-用户", description = "用户权限相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/system/user")
|
||||
public class SysUserController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private SysRoleApplicationService sysRoleApplicationService;
|
||||
@Resource
|
||||
private SysGroupApplicationService sysGroupApplicationService;
|
||||
@Resource
|
||||
private SysDataPermissionApplicationService sysDataPermissionApplicationService;
|
||||
@Resource
|
||||
private SysUserPermissionCacheServiceImpl sysUserPermissionCacheService;
|
||||
|
||||
@RequireResource(USER_MANAGE_BIND_ROLE)
|
||||
@Operation(summary = "查询用户绑定的角色列表")
|
||||
@GetMapping("/list-role/{userId}")
|
||||
public ReqResult<List<SysRoleDto>> getRoleListByUserId(@PathVariable Long userId) {
|
||||
if (userId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
|
||||
List<SysRole> roleList = sysRoleApplicationService.getRoleListByUserId(userId);
|
||||
if (CollectionUtils.isEmpty(roleList)) {
|
||||
return ReqResult.success();
|
||||
}
|
||||
List<SysRoleDto> dtoList = roleList.stream().map(role -> {
|
||||
SysRoleDto roleDto = new SysRoleDto();
|
||||
BeanUtils.copyProperties(role, roleDto);
|
||||
return roleDto;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
I18nUtil.replaceSystemMessage(dtoList);
|
||||
return ReqResult.success(dtoList);
|
||||
}
|
||||
|
||||
@RequireResource(USER_MANAGE_BIND_ROLE)
|
||||
@Operation(summary = "用户绑定角色(全量覆盖)")
|
||||
@PostMapping("/bind-role")
|
||||
public ReqResult<Void> bindUser(@RequestBody SysUserBindRoleDto dto) {
|
||||
if (dto == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
sysRoleApplicationService.userBindRole(dto.getUserId(), dto.getRoleIds(), getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(USER_MANAGE_BIND_GROUP)
|
||||
@Operation(summary = "查询用户绑定的组列表")
|
||||
@GetMapping("/list-group/{userId}")
|
||||
public ReqResult<List<SysGroupDto>> getGroupListByUserId(@PathVariable Long userId) {
|
||||
if (userId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
List<SysGroup> groupList = sysGroupApplicationService.getGroupListByUserId(userId);
|
||||
if (CollectionUtils.isEmpty(groupList)) {
|
||||
return ReqResult.success();
|
||||
}
|
||||
List<SysGroupDto> dtoList = groupList.stream().map(group -> {
|
||||
SysGroupDto groupDto = new SysGroupDto();
|
||||
BeanUtils.copyProperties(group, groupDto);
|
||||
return groupDto;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
I18nUtil.replaceSystemMessage(dtoList);
|
||||
return ReqResult.success(dtoList);
|
||||
}
|
||||
|
||||
@RequireResource(USER_MANAGE_BIND_GROUP)
|
||||
@Operation(summary = "用户绑定组(全量覆盖)")
|
||||
@PostMapping("/bind-group")
|
||||
public ReqResult<Void> bindGroup(@RequestBody SysUserBindGroupDto dto) {
|
||||
if (dto == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
sysGroupApplicationService.userBindGroup(dto.getUserId(), dto.getGroupIds(), getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@RequireResource(USER_MANAGE_QUERY_MENU_PERMISSION)
|
||||
@Operation(summary = "查询用户的菜单权限(树形结构)")
|
||||
@GetMapping("/list-menu/{userId}")
|
||||
public ReqResult<List<MenuNodeDto>> getMenuListByUserId(@PathVariable Long userId) {
|
||||
if (userId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
List<MenuNodeDto> menuTree = sysUserPermissionCacheService.getUserMenuTree(userId);
|
||||
|
||||
I18nUtil.replaceSystemMessage(menuTree);
|
||||
return ReqResult.success(menuTree);
|
||||
}
|
||||
|
||||
@RequireResource(USER_MANAGE_QUERY_DATA_PERMISSION)
|
||||
@Operation(summary = "查询用户数据权限")
|
||||
@GetMapping("/data-permission/{userId}")
|
||||
public ReqResult<UserDataPermissionDto> getUserDataPermission(@PathVariable Long userId) {
|
||||
if (userId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
UserDataPermissionDto dataPermission = sysDataPermissionApplicationService.getUserDataPermission(userId);
|
||||
return ReqResult.success(dataPermission);
|
||||
}
|
||||
|
||||
@SaasAdmin
|
||||
@Operation(summary = "清除指定用户权限缓存")
|
||||
@GetMapping("/cache/clear-user")
|
||||
public ReqResult<Void> clearUserCache(Long tenantId, Long userId) {
|
||||
if (userId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
sysUserPermissionCacheService.clearCacheByTenantAndUserIds(tenantId, List.of(userId));
|
||||
return ReqResult.success();
|
||||
}
|
||||
|
||||
@SaasAdmin
|
||||
@Operation(summary = "清除所有用户权限缓存")
|
||||
@GetMapping("/cache/clear-all")
|
||||
public ReqResult<Void> clearAllUserCache(Long tenantId) {
|
||||
sysUserPermissionCacheService.clearCacheAllByTenant(tenantId);
|
||||
return ReqResult.success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
public class ApiInvokeStatDto {
|
||||
|
||||
@Schema(description = "接口名称")
|
||||
private String name;
|
||||
@Schema(description = "接口路径")
|
||||
private String path;
|
||||
@Schema(description = "接口标识,查询调用记录时from参数传改值")
|
||||
private String key;
|
||||
@Schema(description = "接口调用总次数")
|
||||
private InvokeCount total;
|
||||
@Schema(description = "接口调用今日次数")
|
||||
private InvokeCount today;
|
||||
@Schema(description = "接口调用昨日次数")
|
||||
private InvokeCount yesterday;
|
||||
@Schema(description = "接口调用本周次数")
|
||||
private InvokeCount week;
|
||||
@Schema(description = "接口调用本月次数")
|
||||
private InvokeCount month;
|
||||
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public static class InvokeCount {
|
||||
private Long totalCount;
|
||||
private Long successCount;
|
||||
private Long failCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class ApiKeyCreateDto {
|
||||
|
||||
@Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String name;
|
||||
@Schema(description = "过期时间")
|
||||
private Date expire;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class BindEmailDto implements Serializable {
|
||||
|
||||
@Schema(description = "邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String email;
|
||||
|
||||
@Schema(description = "手机号码", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "验证码", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String code;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Schema(description = "验证码登录")
|
||||
@Data
|
||||
public class CodeLoginDto implements Serializable {
|
||||
|
||||
@Schema(description = "完整的手机号码,例如 8613888888888")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "完整的邮箱地址,与手机号码二选一,例如 xxx@xx.com")
|
||||
private String phoneOrEmail;
|
||||
|
||||
@NotNull
|
||||
@Schema(description = "验证码", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String code;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class CodeSendDto implements Serializable {
|
||||
|
||||
@Schema(description = "验证码类型", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Type type;
|
||||
|
||||
@Schema(description = "完整的手机号码,例如 8613888888888")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "邮箱地址,与手机号码二选一")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "验证码参数")
|
||||
private String captchaVerifyParam;
|
||||
|
||||
public enum Type {
|
||||
LOGIN_OR_REGISTER,
|
||||
RESET_PASSWORD,
|
||||
BIND_EMAIL
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import com.xspaceagi.system.application.dto.EventDto;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class EventRespDto {
|
||||
|
||||
private String version;
|
||||
|
||||
private boolean hasEvent;
|
||||
|
||||
private List<EventDto<?>> eventList;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class LoginResDto implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "认证token,认证方式,header中传Authorization:Bearer token")
|
||||
private String token;
|
||||
|
||||
@Schema(description = "token过期时间")
|
||||
private Date expireDate;
|
||||
|
||||
@Schema(description = "判断用户是否设置过密码,如果未设置过,需要弹出密码设置框让用户设置密码")
|
||||
private Integer resetPass;
|
||||
|
||||
@Schema(description = "登录成功后跳转的页面")
|
||||
private String redirect;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Schema(description = "小程序code登录")
|
||||
@Data
|
||||
public class MPCodeLoginDto implements Serializable {
|
||||
|
||||
@NotNull
|
||||
@Schema(description = "小程序端拿到的code", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String code;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Schema(description = "密码登录")
|
||||
@Data
|
||||
public class PasswordLoginDto implements Serializable {
|
||||
|
||||
@Schema(description = "完整的手机号码,例如 8613888888888", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "完整的邮箱地址,与手机号码二选一,例如 xxx@xx.com")
|
||||
private String phoneOrEmail;
|
||||
|
||||
@NotNull
|
||||
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String password;
|
||||
|
||||
@Schema(description = "验证码参数")
|
||||
private String captchaVerifyParam;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class PullMessageAckDto {
|
||||
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private List<String> messageIds;
|
||||
private String uid;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PullMessageRequestDto {
|
||||
|
||||
private String tenantName;
|
||||
private String siteUrl;
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private String installationSource;
|
||||
private String version;
|
||||
private String user;
|
||||
private String uid;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PullMessageResponseDto {
|
||||
private String messageId;
|
||||
private String content;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class ResetPasswordDto implements Serializable {
|
||||
|
||||
@Schema(description = "新密码", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String newPassword;
|
||||
|
||||
@Schema(description = "验证码", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String code;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class SetPasswordDto implements Serializable {
|
||||
|
||||
@Schema(description = "新密码", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class SpaceAddDto implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "空间名称", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "Space name is required")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "空间类型")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "空间图标")
|
||||
private String icon;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class SpaceTransferDto implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "空间ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "Space ID is required")
|
||||
private Long spaceId;
|
||||
|
||||
@Schema(description = "目标用户ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "Target user ID is required")
|
||||
private Long targetUserId;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class SpaceUpdateDto implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "空间ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "Space ID is required")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "空间名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "空间类型")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "空间图标")
|
||||
private String icon;
|
||||
|
||||
@Schema(description = "空间是否接收来自外部的发布")
|
||||
private Integer receivePublish;
|
||||
|
||||
@Schema(description = "空间是否开启开发功能")
|
||||
private Integer allowDevelop;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import com.xspaceagi.system.infra.dao.entity.SpaceUser;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class SpaceUserAddDto implements Serializable {
|
||||
|
||||
@Schema(description = "空间ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "Space ID is required")
|
||||
private Long spaceId;
|
||||
|
||||
@Schema(description = "用户ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "User ID is required")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "用户角色", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "User role is required")
|
||||
private SpaceUser.Role role;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class SpaceUserDeleteDto implements Serializable {
|
||||
|
||||
@Schema(description = "空间ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "Space ID is required")
|
||||
private Long spaceId;
|
||||
|
||||
@Schema(description = "用户ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "User ID is required")
|
||||
private Long userId;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import com.xspaceagi.system.infra.dao.entity.SpaceUser;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class SpaceUserQueryDto implements Serializable {
|
||||
|
||||
@Schema(description = "空间ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "Space ID is required")
|
||||
private Long spaceId;
|
||||
|
||||
@Schema(description = "关键字", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private String kw;
|
||||
|
||||
@Schema(description = "角色", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private SpaceUser.Role role;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import com.xspaceagi.system.infra.dao.entity.SpaceUser;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class SpaceUserRoleUpdateDto implements Serializable {
|
||||
|
||||
@Schema(description = "空间ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Long spaceId;
|
||||
|
||||
@Schema(description = "用户ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "用户角色", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private SpaceUser.Role role;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SttResult {
|
||||
|
||||
private String text;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class TicketCreateDto {
|
||||
|
||||
@Schema(description = "认证Token")
|
||||
private String token;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Schema(description = "文件上传结果")
|
||||
@Data
|
||||
public class UploadResultDto implements Serializable {
|
||||
|
||||
@Schema(description = "文件完整的网络地址")
|
||||
private String url;
|
||||
|
||||
@Schema(description = "文件唯一标识")
|
||||
private String key;
|
||||
|
||||
@Schema(description = "文件名称")
|
||||
private String fileName;
|
||||
|
||||
@Schema(description = "文件类型")
|
||||
private String mimeType;
|
||||
|
||||
@Schema(description = "文件大小")
|
||||
private int size;
|
||||
|
||||
@Schema(description = "图片宽度")
|
||||
private int width;
|
||||
|
||||
@Schema(description = "图片高度")
|
||||
private int height;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "用户访问统计响应")
|
||||
public class UserAccessStatsDto {
|
||||
|
||||
@Schema(description = "今日访问用户数")
|
||||
private Long todayUserCount;
|
||||
|
||||
@Schema(description = "七日访问用户数")
|
||||
private Long last7DaysUserCount;
|
||||
|
||||
@Schema(description = "30日访问用户数")
|
||||
private Long last30DaysUserCount;
|
||||
|
||||
@Schema(description = "七日访问趋势")
|
||||
private List<TrendItem> last7DaysTrend;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "趋势数据项")
|
||||
public static class TrendItem {
|
||||
@Schema(description = "日期/月份")
|
||||
private String date;
|
||||
|
||||
@Schema(description = "用户数")
|
||||
private Long userCount;
|
||||
}
|
||||
|
||||
public static TrendItem fromMap(Map<String, Object> map) {
|
||||
return TrendItem.builder()
|
||||
.date(map.get("date") != null ? map.get("date").toString() : map.get("month").toString())
|
||||
.userCount(Long.valueOf(map.get("user_count").toString()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import com.xspaceagi.system.infra.dao.entity.User;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class UserAddDto implements Serializable {
|
||||
|
||||
@Schema(description = "用户唯一标识,可以记录三方账号系统唯一标识")
|
||||
private String uid;
|
||||
|
||||
@Schema(description = "手机号码")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "密码")
|
||||
private String password;
|
||||
|
||||
@Schema(description = "姓名")
|
||||
private String userName;
|
||||
|
||||
@Schema(description = "手机号码")
|
||||
private String nickName;
|
||||
|
||||
@Schema(description = "头像")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "角色")
|
||||
private User.Role role;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class UserInfoQueryDto implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "用户名(用户名、邮箱、手机号三选一)")
|
||||
private String userName;
|
||||
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "手机号码")
|
||||
private String phone;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import com.xspaceagi.system.spec.enums.PeriodTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
@Schema(description = "用户计量更新请求")
|
||||
public class UserMetricUpdateDto {
|
||||
|
||||
@Schema(description = "用户ID", required = true)
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "业务类型", required = true)
|
||||
private String bizType;
|
||||
|
||||
@Schema(description = "时段类型", required = true)
|
||||
private String periodType;
|
||||
|
||||
@Schema(description = "时段值", required = true)
|
||||
private String period;
|
||||
|
||||
@Schema(description = "计量值", required = true)
|
||||
private BigDecimal value;
|
||||
|
||||
@Schema(description = "操作类型:add-增加值,set-设置值", required = true)
|
||||
private String operationType;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class UserQueryDto implements Serializable {
|
||||
|
||||
private String kw;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "用户统计响应")
|
||||
public class UserStatsDto {
|
||||
|
||||
@Schema(description = "用户总数")
|
||||
private Long totalUserCount;
|
||||
|
||||
@Schema(description = "今日新增用户数")
|
||||
private Long todayNewUserCount;
|
||||
|
||||
@Schema(description = "7日新增趋势")
|
||||
private List<TrendItem> last7DaysTrend;
|
||||
|
||||
@Schema(description = "30日新增趋势")
|
||||
private List<TrendItem> last30DaysTrend;
|
||||
|
||||
@Schema(description = "按月新增趋势")
|
||||
private List<TrendItem> monthlyTrend;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "趋势数据项")
|
||||
public static class TrendItem {
|
||||
@Schema(description = "日期/月份")
|
||||
private String date;
|
||||
|
||||
@Schema(description = "新增用户数")
|
||||
private Long userCount;
|
||||
}
|
||||
|
||||
public static TrendItem fromMap(Map<String, Object> map) {
|
||||
return TrendItem.builder()
|
||||
.date(map.get("date") != null ? map.get("date").toString() : map.get("month").toString())
|
||||
.userCount(Long.valueOf(map.get("user_count").toString()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.xspaceagi.system.web.dto;
|
||||
|
||||
import com.xspaceagi.system.infra.dao.entity.User;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class UserUpdateDto implements Serializable {
|
||||
|
||||
@Schema(description = "用户名")
|
||||
private String userName;
|
||||
|
||||
@Schema(description = "用户昵称")
|
||||
private String nickName;
|
||||
|
||||
@Schema(description = "用户头像地址")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "角色")
|
||||
private User.Role role;
|
||||
|
||||
@Schema(description = "管理员邮箱")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "手机号码")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "密码")
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.xspaceagi.system.web.dto.operatorlog;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Schema(description = "枚举选项")
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class EnumOptionDto {
|
||||
|
||||
@Schema(description = "显示文本")
|
||||
private String label;
|
||||
|
||||
@Schema(description = "实际值")
|
||||
private String value;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.xspaceagi.system.web.dto.operatorlog;
|
||||
|
||||
import com.xspaceagi.system.infra.dao.model.OperatorLogModel;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 操作日志(SysOperatorLog)实体类
|
||||
*
|
||||
* @author p1
|
||||
* @since 2024-11-01 11:16:02
|
||||
*/
|
||||
@Schema(description = "操作日志")
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
public class OperatorLogDto {
|
||||
/**
|
||||
* 自增主键
|
||||
*/
|
||||
@Schema(description = "自增主键", example = "1")
|
||||
private Long id;
|
||||
/**
|
||||
* 1:操作类型;2:访问日志
|
||||
*/
|
||||
@Schema(description = "操作类型;1:操作类型;2:访问日志", example = "1")
|
||||
private Long operateType;
|
||||
/**
|
||||
* 系统编码
|
||||
*/
|
||||
@Schema(description = "系统编码", example = "SYS001")
|
||||
private String systemCode;
|
||||
/**
|
||||
* 系统名称
|
||||
*/
|
||||
@Schema(description = "系统名称", example = "系统管理")
|
||||
private String systemName;
|
||||
/**
|
||||
* 操作对象,比如:用户表,角色表,菜单表
|
||||
*/
|
||||
@Schema(description = "操作对象,比如:用户表,角色表,菜单表", example = "用户表")
|
||||
private String object;
|
||||
/**
|
||||
* 操作动作,比如:新增,删除,修改,查看
|
||||
*/
|
||||
@Schema(description = "操作动作,比如:新增,删除,修改,查看", example = "新增")
|
||||
private String action;
|
||||
/**
|
||||
* 操作内容,比如评估页面
|
||||
*/
|
||||
@Schema(description = "操作内容,比如评估页面", example = "评估页面")
|
||||
private String operateContent;
|
||||
/**
|
||||
* 额外的操作内容信息记录,比如:更新提交的数据内容
|
||||
*/
|
||||
@Schema(description = "额外的操作内容信息记录,比如:更新提交的数据内容", example = "更新了用户信息")
|
||||
private String extraContent;
|
||||
/**
|
||||
* 操作人所属机构id
|
||||
*/
|
||||
@Schema(description = "操作人所属机构id", example = "1")
|
||||
private Long orgId;
|
||||
/**
|
||||
* 操作人所属机构名称
|
||||
*/
|
||||
@Schema(description = "操作人所属机构名称", example = "技术部")
|
||||
private String orgName;
|
||||
/**
|
||||
* 创建人id
|
||||
*/
|
||||
@Schema(description = "创建人id", example = "1")
|
||||
private Long creatorId;
|
||||
/**
|
||||
* 创建人名称
|
||||
*/
|
||||
@Schema(description = "创建人名称", example = "张三")
|
||||
private String creator;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Schema(description = "创建时间", example = "2024-11-01T11:16:03")
|
||||
private LocalDateTime created;
|
||||
|
||||
|
||||
public static OperatorLogDto convertToDto(OperatorLogModel model) {
|
||||
OperatorLogDto operatorLogDto = new OperatorLogDto();
|
||||
operatorLogDto.setId(model.getId());
|
||||
operatorLogDto.setOperateType(model.getOperateType());
|
||||
operatorLogDto.setSystemCode(model.getSystemCode());
|
||||
operatorLogDto.setSystemName(model.getSystemName());
|
||||
operatorLogDto.setObject(model.getObjectOp());
|
||||
operatorLogDto.setAction(model.getAction());
|
||||
operatorLogDto.setOperateContent(model.getOperateContent());
|
||||
operatorLogDto.setExtraContent(model.getExtraContent());
|
||||
operatorLogDto.setOrgId(model.getOrgId());
|
||||
operatorLogDto.setOrgName(model.getOrgName());
|
||||
operatorLogDto.setCreatorId(model.getCreatorId());
|
||||
operatorLogDto.setCreator(model.getCreator());
|
||||
operatorLogDto.setCreated(model.getCreated());
|
||||
return operatorLogDto;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.xspaceagi.system.web.dto.operatorlog;
|
||||
|
||||
import com.xspaceagi.system.sdk.operate.OperateTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "操作日志查询条件")
|
||||
@Getter
|
||||
@Setter
|
||||
public class OperatorLogQueryDto implements Serializable {
|
||||
|
||||
|
||||
@Schema(description = "系统名称")
|
||||
private List<String> systemName;
|
||||
|
||||
@Schema(description = "系统编码")
|
||||
private List<String> systemCode;
|
||||
|
||||
@Schema(description = "操作动作,比如:新增,删除,修改,查看")
|
||||
private List<String> action;
|
||||
|
||||
@Schema(description = "创建人ID")
|
||||
private List<Long> creatorId;
|
||||
|
||||
/**
|
||||
* @see OperateTypeEnum
|
||||
*/
|
||||
@Schema(description = "操作日志类型")
|
||||
private Integer operateType;
|
||||
|
||||
@Schema(description = "操作方式")
|
||||
private String actionType;
|
||||
|
||||
@Schema(description = "对象名称")
|
||||
private String object;
|
||||
|
||||
@Schema(description = "操作内容")
|
||||
private String operateContent;
|
||||
|
||||
@Schema(description = "额外内容(请求参数)")
|
||||
private String extraContent;
|
||||
|
||||
@Schema(description = "创建人名称")
|
||||
private String creator;
|
||||
|
||||
@Schema(description = "创建时间-开始(毫秒时间戳)")
|
||||
private Long createTimeGt;
|
||||
|
||||
@Schema(description = "创建时间-结束(毫秒时间戳)")
|
||||
private Long createTimeLt;
|
||||
|
||||
@Schema(description = "创建时间范围(内部使用)")
|
||||
private List<LocalDateTime> created;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.xspaceagi.system.web.emoj;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class IconGenerator {
|
||||
|
||||
private static final Color[] rgbs = new Color[]{
|
||||
new Color(117, 189, 108),
|
||||
new Color(77, 87, 224),
|
||||
new Color(63, 118, 247),
|
||||
new Color(238, 193, 79),
|
||||
new Color(160, 109, 237)
|
||||
};
|
||||
|
||||
public enum IconStyle {
|
||||
CLASSIC, MODERN, MINIMAL, COLORFUL
|
||||
}
|
||||
|
||||
public enum IconFormat {
|
||||
PNG, JPG, SVG, ICO
|
||||
}
|
||||
|
||||
private Map<String, Color> colorSchemes;
|
||||
|
||||
public IconGenerator() {
|
||||
initColorSchemes();
|
||||
}
|
||||
|
||||
private void initColorSchemes() {
|
||||
colorSchemes = new HashMap<>();
|
||||
colorSchemes.put("java_blue", new Color(0, 115, 150));
|
||||
colorSchemes.put("java_orange", new Color(255, 140, 0));
|
||||
colorSchemes.put("dark", new Color(45, 45, 45));
|
||||
colorSchemes.put("light", new Color(245, 245, 245));
|
||||
}
|
||||
|
||||
public BufferedImage generateIcon(String text, int size) {
|
||||
BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = image.createGraphics();
|
||||
// 设置渲染质量
|
||||
setupGraphics(g2d);
|
||||
drawClassicIcon(g2d, text, size);
|
||||
g2d.dispose();
|
||||
return image;
|
||||
}
|
||||
|
||||
public static String getFirstNonSpecialChar(String str) {
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
char c = str.charAt(i);
|
||||
if (Character.isLetterOrDigit(c)) {
|
||||
return String.valueOf(c).toUpperCase(); // 将字符转为字符串返回
|
||||
}
|
||||
}
|
||||
return "A"; // 如果没有找到符合条件的字符,返回空字符串
|
||||
}
|
||||
|
||||
private void setupGraphics(Graphics2D g2d) {
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
}
|
||||
|
||||
private void drawClassicIcon(Graphics2D g2d, String text, int size) {
|
||||
try {
|
||||
String decode = URLDecoder.decode(text, "UTF-8");
|
||||
text = getFirstNonSpecialChar(decode);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
int index = Math.abs(text.hashCode()) % rgbs.length;
|
||||
Color bgColor = rgbs[index];
|
||||
RadialGradientPaint radialGradient = new RadialGradientPaint(
|
||||
new Point2D.Double(size * 0.3, size * 0.3), // 中心点偏左上
|
||||
size * 0.6f, // 半径
|
||||
new float[]{0.0f, 0.6f, 1.0f},
|
||||
new Color[]{
|
||||
new Color(bgColor.getRed(), bgColor.getGreen(), bgColor.getBlue(), 250), // 中心较亮
|
||||
new Color(bgColor.getRed(), bgColor.getGreen(), bgColor.getBlue(), 200), // 中等亮度
|
||||
new Color(bgColor.getRed(), bgColor.getGreen(), bgColor.getBlue(), 150) // 边缘透明
|
||||
},
|
||||
MultipleGradientPaint.CycleMethod.NO_CYCLE
|
||||
);
|
||||
g2d.setPaint(radialGradient);
|
||||
g2d.fillRoundRect(0, 0, size, size, size / 8, size / 8);
|
||||
|
||||
// 绘制文字
|
||||
g2d.setColor(Color.WHITE);
|
||||
Font font = new Font("Arial", Font.BOLD, size * 2 / 5);
|
||||
g2d.setFont(font);
|
||||
|
||||
FontMetrics fm = g2d.getFontMetrics();
|
||||
int x = (size - fm.stringWidth(text)) / 2;
|
||||
int y = (size - fm.getHeight()) / 2 + fm.getAscent();
|
||||
|
||||
g2d.drawString(text, x, y);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package com.xspaceagi.system.web.i18n;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.xspaceagi.agent.core.adapter.application.ModelApplicationService;
|
||||
import com.xspaceagi.system.application.service.I18nLlmTranslator;
|
||||
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.ai.retry.NonTransientAiException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 通过租户默认对话模型翻译({@link ModelApplicationService#queryDefaultModelConfig()})
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class I18nLlmTranslatorImpl implements I18nLlmTranslator {
|
||||
|
||||
private static final int MAX_CHUNK = 6000;
|
||||
|
||||
@Resource
|
||||
private ModelApplicationService modelApplicationService;
|
||||
|
||||
@Override
|
||||
public String translate(String text, String sourceLangTag, String targetLangTag) {
|
||||
if (StringUtils.isBlank(text)) {
|
||||
return text;
|
||||
}
|
||||
if (sourceLangTag != null && targetLangTag != null && sourceLangTag.equalsIgnoreCase(targetLangTag)) {
|
||||
return text;
|
||||
}
|
||||
StringBuilder merged = new StringBuilder();
|
||||
for (int start = 0; start < text.length(); start += MAX_CHUNK) {
|
||||
int end = Math.min(start + MAX_CHUNK, text.length());
|
||||
if (end < text.length()) {
|
||||
int lineBreak = text.lastIndexOf('\n', end - 1);
|
||||
if (lineBreak > start) {
|
||||
end = lineBreak + 1;
|
||||
}
|
||||
}
|
||||
String chunk = text.substring(start, end);
|
||||
merged.append(translateChunk(chunk, sourceLangTag, targetLangTag));
|
||||
}
|
||||
return merged.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> translateBatch(Map<String, String> textByKey, String sourceLangTag, String targetLangTag) {
|
||||
if (textByKey == null || textByKey.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
try {
|
||||
String prompt = buildBatchPrompt(textByKey, sourceLangTag, targetLangTag);
|
||||
String raw = modelApplicationService.call(prompt);
|
||||
String cleaned = sanitizeModelOutput(raw);
|
||||
String jsonText = extractJsonObject(cleaned);
|
||||
if (!JSON.isValidObject(jsonText)) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemI18nBatchTranslationNotJson,
|
||||
StringUtils.abbreviate(cleaned, 200));
|
||||
}
|
||||
JSONObject jsonObject = JSON.parseObject(jsonText);
|
||||
Map<String, String> result = new LinkedHashMap<>();
|
||||
textByKey.keySet().forEach(key -> {
|
||||
String v = jsonObject.getString(key);
|
||||
if (StringUtils.isBlank(v)) {
|
||||
String sourceText = textByKey.get(key);
|
||||
if (StringUtils.isBlank(sourceText)) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemI18nBatchTranslationKeyOrValueMissing, key);
|
||||
}
|
||||
log.warn("批量翻译缺少 key 或值为空,回退单条翻译: {}", key);
|
||||
String fallback = translate(sourceText, sourceLangTag, targetLangTag);
|
||||
if (StringUtils.isBlank(fallback)) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemI18nBatchTranslationKeyOrValueMissing, key);
|
||||
}
|
||||
v = fallback;
|
||||
}
|
||||
result.put(key, v);
|
||||
});
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.warn("大模型批量翻译失败 {} -> {}", sourceLangTag, targetLangTag, e);
|
||||
throw toBizException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String translateChunk(String chunk, String sourceLangTag, String targetLangTag) {
|
||||
try {
|
||||
String prompt = buildPrompt(chunk, sourceLangTag, targetLangTag);
|
||||
String raw = modelApplicationService.call(prompt);
|
||||
String cleaned = sanitizeModelOutput(raw);
|
||||
if (StringUtils.isBlank(cleaned)) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemI18nTranslationEmpty);
|
||||
}
|
||||
return cleaned;
|
||||
} catch (Exception e) {
|
||||
log.warn("大模型翻译失败 {} -> {}", sourceLangTag, targetLangTag, e);
|
||||
throw toBizException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private BizException toBizException(Exception e) {
|
||||
if (e instanceof BizException bizException) {
|
||||
return bizException;
|
||||
}
|
||||
if (e instanceof NonTransientAiException && containsAuthFailure(e)) {
|
||||
return BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemI18nTranslationModelAuthFailed);
|
||||
}
|
||||
return BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemI18nTranslationFailedWithReason,
|
||||
e.getMessage() != null ? e.getMessage() : "");
|
||||
}
|
||||
|
||||
private boolean containsAuthFailure(Throwable throwable) {
|
||||
Throwable current = throwable;
|
||||
while (current != null) {
|
||||
String msg = current.getMessage();
|
||||
if (StringUtils.containsIgnoreCase(msg, "401")
|
||||
|| StringUtils.contains(msg, "身份验证失败")
|
||||
|| StringUtils.containsIgnoreCase(msg, "authentication")) {
|
||||
return true;
|
||||
}
|
||||
current = current.getCause();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String buildPrompt(String sourceText, String sourceLangTag, String targetLangTag) {
|
||||
return """
|
||||
You are a professional UI localization translator.
|
||||
|
||||
Translate the text below from language "%s" to language "%s".
|
||||
|
||||
Rules:
|
||||
- Output ONLY the translated text. No quotes, no markdown fences, no explanations.
|
||||
- Preserve placeholders and formatting exactly: %%s, %%d, {0}, {{name}}, HTML tags, etc.
|
||||
- Preserve line breaks and meaningful whitespace.
|
||||
|
||||
Text to translate:
|
||||
""".formatted(sourceLangTag, targetLangTag)
|
||||
+ sourceText;
|
||||
}
|
||||
|
||||
private static String buildBatchPrompt(Map<String, String> textByKey, String sourceLangTag, String targetLangTag) {
|
||||
String json = JSON.toJSONString(textByKey);
|
||||
return """
|
||||
You are a professional UI localization translator.
|
||||
|
||||
Translate all values in the given JSON object from language "%s" to language "%s".
|
||||
Keep the keys exactly unchanged.
|
||||
|
||||
Rules:
|
||||
- Output ONLY one JSON object.
|
||||
- Keep the same keys, no added/removed keys.
|
||||
- Translate only values.
|
||||
- Preserve placeholders and formatting exactly: %%s, %%d, {0}, {{name}}, HTML tags, etc.
|
||||
|
||||
Input JSON:
|
||||
%s
|
||||
""".formatted(sourceLangTag, targetLangTag, json);
|
||||
}
|
||||
|
||||
private static String extractJsonObject(String text) {
|
||||
if (StringUtils.isBlank(text)) {
|
||||
return "";
|
||||
}
|
||||
int start = text.indexOf('{');
|
||||
int end = text.lastIndexOf('}');
|
||||
if (start >= 0 && end > start) {
|
||||
return text.substring(start, end + 1);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private static String sanitizeModelOutput(String raw) {
|
||||
if (raw == null) {
|
||||
return "";
|
||||
}
|
||||
String t = raw.trim();
|
||||
if (t.startsWith("```")) {
|
||||
int firstNl = t.indexOf('\n');
|
||||
if (firstNl > 0) {
|
||||
t = t.substring(firstNl + 1);
|
||||
}
|
||||
int fence = t.lastIndexOf("```");
|
||||
if (fence > 0) {
|
||||
t = t.substring(0, fence);
|
||||
}
|
||||
}
|
||||
return t.trim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.xspaceagi.system.web.service;
|
||||
|
||||
import com.xspaceagi.system.application.service.CategoryApplicationService;
|
||||
import com.xspaceagi.system.infra.dao.entity.Category;
|
||||
import com.xspaceagi.system.infra.dao.service.CategoryService;
|
||||
import com.xspaceagi.system.sdk.service.CategoryApiService;
|
||||
import com.xspaceagi.system.sdk.service.dto.CategoryDto;
|
||||
import com.xspaceagi.system.spec.tenant.thread.TenantFunctions;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 分类API服务实现
|
||||
*/
|
||||
@Service
|
||||
public class CategoryApiServiceImpl implements CategoryApiService {
|
||||
|
||||
@Resource
|
||||
private CategoryApplicationService categoryApplicationService;
|
||||
|
||||
@Resource
|
||||
private CategoryService categoryService;
|
||||
|
||||
@Override
|
||||
public CategoryDto insert(CategoryDto categoryDto) {
|
||||
Assert.notNull(categoryDto, "categoryDto不能为空");
|
||||
Assert.hasText(categoryDto.getName(), "名称不能为空");
|
||||
Assert.hasText(categoryDto.getCode(), "编码不能为空");
|
||||
Assert.hasText(categoryDto.getType(), "类型不能为空");
|
||||
Assert.notNull(categoryDto.getTenantId(), "租户ID不能为空");
|
||||
Category category = new Category();
|
||||
BeanUtils.copyProperties(categoryDto, category);
|
||||
TenantFunctions.runWithIgnoreCheck(() -> categoryService.save(category));
|
||||
return convertToDto(category);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CategoryDto getById(Long id) {
|
||||
Category category = categoryApplicationService.getById(id);
|
||||
return convertToDto(category);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CategoryDto> listByType(String type) {
|
||||
List<Category> categories = categoryApplicationService.listByType(type);
|
||||
return categories.stream()
|
||||
.map(this::convertToDto)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CategoryDto> listByTenantId(Long tenantId) {
|
||||
List<Category> categories = categoryApplicationService.listByTenantId(tenantId);
|
||||
return categories.stream()
|
||||
.map(this::convertToDto)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CategoryDto> listByTypeAndTenantId(String type, Long tenantId) {
|
||||
Assert.hasText(type, "type cannot be null");
|
||||
Assert.notNull(tenantId, "tenantId cannot be null");
|
||||
List<Category> categories = TenantFunctions.callWithIgnoreCheck(() -> categoryApplicationService.listByTypeAndTenantId(type, tenantId));
|
||||
return categories.stream()
|
||||
.map(this::convertToDto)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CategoryDto getByCode(String code) {
|
||||
Category category = categoryApplicationService.getByCode(code);
|
||||
return convertToDto(category);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Category实体转换为CategoryDto
|
||||
*/
|
||||
private CategoryDto convertToDto(Category category) {
|
||||
if (category == null) {
|
||||
return null;
|
||||
}
|
||||
CategoryDto dto = new CategoryDto();
|
||||
BeanUtils.copyProperties(category, dto);
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.xspaceagi.system.web.service;
|
||||
|
||||
import com.xspaceagi.system.sdk.retry.dto.RetrySubmission;
|
||||
import com.xspaceagi.system.sdk.retry.server.IRetrySubmitService;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.application.service.RetryApplicationService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
@Service
|
||||
public class RetrySubmitService implements IRetrySubmitService {
|
||||
|
||||
@Resource
|
||||
private RetryApplicationService retryApplicationService;
|
||||
|
||||
@Override
|
||||
public ReqResult<Void> submitRetryData(RetrySubmission retrySubmission) {
|
||||
retryApplicationService.submitRetryData(retrySubmission);
|
||||
return ReqResult.success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package com.xspaceagi.system.web.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.xspaceagi.system.application.dto.UserDto;
|
||||
import com.xspaceagi.system.application.service.UserApplicationService;
|
||||
import com.xspaceagi.system.infra.dao.entity.UserAccessKey;
|
||||
import com.xspaceagi.system.infra.dao.service.UserAccessKeyService;
|
||||
import com.xspaceagi.system.sdk.service.UserAccessKeyApiService;
|
||||
import com.xspaceagi.system.sdk.service.dto.UserAccessKeyDto;
|
||||
import com.xspaceagi.system.spec.jackson.JsonSerializeUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class UserAccessKeyApiServiceImpl implements UserAccessKeyApiService {
|
||||
|
||||
@Resource
|
||||
private UserAccessKeyService userAccessKeyService;
|
||||
|
||||
@Resource
|
||||
private UserApplicationService userApplicationService;
|
||||
|
||||
@Override
|
||||
public UserAccessKeyDto newAccessKey(Long userId, UserAccessKeyDto.AKTargetType targetType, String targetId) {
|
||||
return newAccessKey(userId, targetType, targetId, "ak-" + UUID.randomUUID().toString().replace("-", ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserAccessKeyDto newAccessKey(Long userId, UserAccessKeyDto.AKTargetType targetType, String targetId, String accessKey) {
|
||||
UserAccessKey userAccessKey = new UserAccessKey();
|
||||
userAccessKey.setUserId(userId);
|
||||
userAccessKey.setTargetType(targetType);
|
||||
userAccessKey.setTargetId(targetId);
|
||||
userAccessKey.setAccessKey(accessKey);
|
||||
userAccessKey.setConfig(UserAccessKeyDto.UserAccessKeyConfig.builder().isDevMode(0).enabled(true).build());
|
||||
userAccessKeyService.save(userAccessKey);
|
||||
UserAccessKeyDto userAccessKeyDto = new UserAccessKeyDto();
|
||||
BeanUtils.copyProperties(userAccessKey, userAccessKeyDto);
|
||||
return userAccessKeyDto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserAccessKeyDto newAccessKey(Long tenantId, Long userId, UserAccessKeyDto.AKTargetType targetType, String targetId, UserAccessKeyDto.UserAccessKeyConfig userAccessKeyConfig) {
|
||||
UserAccessKey userAccessKey = new UserAccessKey();
|
||||
userAccessKey.setTenantId(tenantId);
|
||||
userAccessKey.setUserId(userId);
|
||||
userAccessKey.setTargetType(targetType);
|
||||
userAccessKey.setTargetId(targetId);
|
||||
userAccessKey.setAccessKey("ak-" + UUID.randomUUID().toString().replace("-", ""));
|
||||
userAccessKey.setConfig(userAccessKeyConfig);
|
||||
userAccessKeyService.save(userAccessKey);
|
||||
UserAccessKeyDto userAccessKeyDto = new UserAccessKeyDto();
|
||||
BeanUtils.copyProperties(userAccessKey, userAccessKeyDto);
|
||||
return userAccessKeyDto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAccessKey(Long userId, String accessKey) {
|
||||
userAccessKeyService.remove(new QueryWrapper<UserAccessKey>().eq("access_key", accessKey).eq("user_id", userId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAccessKeyWithAgentId(Long agentId, String accessKey) {
|
||||
userAccessKeyService.remove(new LambdaQueryWrapper<UserAccessKey>().eq(UserAccessKey::getAccessKey, accessKey)
|
||||
.eq(UserAccessKey::getTargetType, UserAccessKeyDto.AKTargetType.Agent)
|
||||
.eq(UserAccessKey::getTargetId, agentId)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserAccessKeyDto> queryAccessKeyList(Long userId, UserAccessKeyDto.AKTargetType targetType, String targetId) {
|
||||
LambdaQueryWrapper<UserAccessKey> queryWrapper = new LambdaQueryWrapper<>();
|
||||
if (userId != null) {
|
||||
queryWrapper.eq(UserAccessKey::getUserId, userId);
|
||||
}
|
||||
if (targetType != null) {
|
||||
queryWrapper.eq(UserAccessKey::getTargetType, targetType);
|
||||
}
|
||||
if (targetId != null) {
|
||||
queryWrapper.eq(UserAccessKey::getTargetId, targetId);
|
||||
}
|
||||
List<UserAccessKey> userAccessKeys = userAccessKeyService.list(queryWrapper);
|
||||
//userIds
|
||||
List<Long> userIds = userAccessKeys.stream().map(UserAccessKey::getUserId).distinct().collect(Collectors.toList());
|
||||
Map<Long, UserDto> userMap = new HashMap<>();
|
||||
if (CollectionUtils.isNotEmpty(userIds)) {
|
||||
List<UserDto> users = userApplicationService.queryUserListByIds(userIds);
|
||||
users.forEach(userDto -> userMap.put(userDto.getId(), userDto));
|
||||
}
|
||||
|
||||
return userAccessKeys.stream().map(userAccessKey -> {
|
||||
UserDto userDto = userMap.get(userAccessKey.getUserId());
|
||||
UserAccessKeyDto.Creator creator = null;
|
||||
if (userDto != null) {
|
||||
creator = new UserAccessKeyDto.Creator();
|
||||
creator.setUserName(userDto.getNickName() == null ? userDto.getUserName() : userDto.getNickName());
|
||||
creator.setUserId(userDto.getId());
|
||||
}
|
||||
return UserAccessKeyDto.builder()
|
||||
.id(userAccessKey.getId())
|
||||
.tenantId(userAccessKey.getTenantId())
|
||||
.userId(userAccessKey.getUserId())
|
||||
.targetType(userAccessKey.getTargetType())
|
||||
.targetId(userAccessKey.getTargetId())
|
||||
.accessKey(userAccessKey.getAccessKey())
|
||||
.config(userAccessKey.getConfig())
|
||||
.created(userAccessKey.getCreated())
|
||||
.status(userAccessKey.getStatus())
|
||||
.expire(userAccessKey.getExpire())
|
||||
.name(userAccessKey.getName())
|
||||
.creator(creator)
|
||||
.build();
|
||||
}
|
||||
).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserAccessKeyDto queryAccessKey(Long userId, UserAccessKeyDto.AKTargetType targetType, String targetId) {
|
||||
UserAccessKey userAccessKey = userAccessKeyService.getOne(new QueryWrapper<UserAccessKey>()
|
||||
.eq("user_id", userId)
|
||||
.eq("target_type", targetType)
|
||||
.eq("target_id", targetId), false
|
||||
);
|
||||
if (userAccessKey == null) {
|
||||
return null;
|
||||
}
|
||||
return UserAccessKeyDto.builder()
|
||||
.id(userAccessKey.getId())
|
||||
.tenantId(userAccessKey.getTenantId())
|
||||
.userId(userAccessKey.getUserId())
|
||||
.targetType(userAccessKey.getTargetType())
|
||||
.targetId(userAccessKey.getTargetId())
|
||||
.accessKey(userAccessKey.getAccessKey())
|
||||
.config(userAccessKey.getConfig())
|
||||
.created(userAccessKey.getCreated())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserAccessKeyDto queryAccessKey(String accessKey) {
|
||||
if (accessKey == null) {
|
||||
return null;
|
||||
}
|
||||
UserAccessKey userAccessKey = userAccessKeyService.getOne(new QueryWrapper<UserAccessKey>().eq("access_key", accessKey));
|
||||
if (userAccessKey == null) {
|
||||
return null;
|
||||
}
|
||||
return UserAccessKeyDto.builder()
|
||||
.id(userAccessKey.getId())
|
||||
.tenantId(userAccessKey.getTenantId())
|
||||
.name(userAccessKey.getName())
|
||||
.userId(userAccessKey.getUserId())
|
||||
.targetType(userAccessKey.getTargetType())
|
||||
.targetId(userAccessKey.getTargetId())
|
||||
.accessKey(userAccessKey.getAccessKey())
|
||||
.config(userAccessKey.getConfig())
|
||||
.created(userAccessKey.getCreated())
|
||||
.expire(userAccessKey.getExpire())
|
||||
.status(userAccessKey.getStatus())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserAccessKeyDto refreshAccessKey(Long id) {
|
||||
UserAccessKey userAccessKeyUpdate = new UserAccessKey();
|
||||
userAccessKeyUpdate.setId(id);
|
||||
userAccessKeyUpdate.setAccessKey(UUID.randomUUID().toString().replace("-", ""));
|
||||
userAccessKeyService.updateById(userAccessKeyUpdate);
|
||||
UserAccessKey userAccessKey = userAccessKeyService.getById(id);
|
||||
if (userAccessKey == null) {
|
||||
return null;
|
||||
}
|
||||
return UserAccessKeyDto.builder()
|
||||
.id(userAccessKey.getId())
|
||||
.tenantId(userAccessKey.getTenantId())
|
||||
.userId(userAccessKey.getUserId())
|
||||
.targetType(userAccessKey.getTargetType())
|
||||
.targetId(userAccessKey.getTargetId())
|
||||
.accessKey(userAccessKey.getAccessKey())
|
||||
.config(userAccessKey.getConfig())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAgentDevMode(Long userId, Long agentId, String accessKey, Integer devMode) {
|
||||
if (devMode == null || !devMode.equals(1)) {
|
||||
devMode = 0;
|
||||
}
|
||||
LambdaUpdateWrapper<UserAccessKey> updateWrapper = new LambdaUpdateWrapper<>();
|
||||
if (userId != null) {
|
||||
updateWrapper.eq(UserAccessKey::getUserId, userId);
|
||||
}
|
||||
updateWrapper.eq(UserAccessKey::getTargetId, agentId);
|
||||
updateWrapper.eq(UserAccessKey::getTargetType, UserAccessKeyDto.AKTargetType.Agent);
|
||||
updateWrapper.eq(UserAccessKey::getAccessKey, accessKey);
|
||||
updateWrapper.set(UserAccessKey::getConfig, JsonSerializeUtil.toJSONStringGeneric(UserAccessKeyDto.UserAccessKeyConfig.builder().isDevMode(devMode).build()));
|
||||
userAccessKeyService.update(updateWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUserAccessKeyConfig(Long id, UserAccessKeyDto.UserAccessKeyConfig userAccessKeyConfig) {
|
||||
LambdaUpdateWrapper<UserAccessKey> updateWrapper = new LambdaUpdateWrapper<>();
|
||||
updateWrapper.eq(UserAccessKey::getId, id);
|
||||
updateWrapper.set(UserAccessKey::getConfig, JsonSerializeUtil.toJSONStringGeneric(userAccessKeyConfig));
|
||||
userAccessKeyService.update(updateWrapper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
||||
<id>repack</id>
|
||||
<formats>
|
||||
<format>jar</format>
|
||||
</formats>
|
||||
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<dependencySets>
|
||||
<dependencySet>
|
||||
<!--
|
||||
重新打包需要包含的jar包,这个过程会解压所有的jar包并按照jar的规则重新组合成一个jar包
|
||||
这里需要包含当前这个工程 ,格式是{groupId}:{artifactId}
|
||||
-->
|
||||
<includes>
|
||||
<!-- 包含项目自己 -->
|
||||
<include>com.xspaceagi:system-web</include>
|
||||
<include>com.xspaceagi:system-application</include>
|
||||
<include>com.xspaceagi:system-domain</include>
|
||||
<include>com.xspaceagi:system-infra</include>
|
||||
<include>com.xspaceagi:system-spec</include>
|
||||
</includes>
|
||||
<!-- 打包结果目录:/ 表示target/ -->
|
||||
<outputDirectory>/</outputDirectory>
|
||||
<!-- 是否解压依赖 -->
|
||||
<unpack>true</unpack>
|
||||
<!-- 打包的scope -->
|
||||
<!-- <scope>system</scope>-->
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
</assembly>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>system-web</artifactId>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>system-web</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
Reference in New Issue
Block a user