diff --git a/README.md b/README.md index 9697411cd..5fe4d5a94 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,58 @@ -## 系统模块 - -~~~ -com.microservices -├── microservices-ui // 前端框架 -├── microservices-gateway // 网关模块 -├── microservices-auth // 认证中心 -├── microservices-api // 接口模块 -│ └── microservices-api-system // 系统接口 -├── microservices-common // 通用模块 -│ └── microservices-common-async // 异步线程池配置 -│ └── microservices-common-core // 核心模块 -│ └── microservices-common-datascope // 权限范围 -│ └── microservices-common-datasource // 多数据源 -│ └── microservices-common-httpClient // http调用 -│ └── microservices-common-log // 日志记录 -│ └── microservices-common-redis // 缓存服务 -│ └── microservices-common-seata // seata分布式事务 -│ └── microservices-common-security // 安全模块 -│ └── microservices-common-swagger // 系统接口 -├── microservices-modules // 业务模块 -│ └── microservices-cms // 内容管理模块 -│ └── microservices-dms // 数据管理体系模块 -│ └── microservices-dss // 数据统计系统模块 -│ └── microservices-file // 文件服务 -│ └── microservices-gen // 代码生成 -│ └── microservices-job // 定时任务 -│ └── microservices-pms // 项目管理系统模块 -│ └── microservices-system // 系统模块 -│ └── microservices-wiki // Wiki模块 -│ └── microservices-zone // 特色专区系统模块 -├── microservices-visual // 图形化管理模块 -│ └── microservices-visual-monitor // 监控中心 -├──pom.xml // 公共依赖 -~~~ - +基于 Spring Cloud Alibaba 的微服务平台,为 GitLink / 小强社区提供后端服务支持。 +## 技术栈 + +| 技术 | 版本 | +|------|------| +| Java | 1.8 | +| Spring Boot | 2.7.7 | +| Spring Cloud | 2021.0.5 | +| Spring Cloud Alibaba | 2021.0.4.0 | +| 平台版本 | 3.6.2 | + +## 系统架构 + +``` + microservices/ + ├── microservices-gateway # Spring Cloud Gateway 网关 [8080] + ├── microservices-auth # 认证中心(JWT + Redis)[9110] + ├── microservices-api/ # Feign 远程调用接口定义 + │ └── microservices-api-system # 各服务的 RemoteXxxService 接口 + ├── microservices-common/ # 公共库 + │ ├── microservices-common-core # 通用工具、基类、R、常量 + │ ├── microservices-common-security # 权限注解(@RequiresPermissions等) + │ ├── microservices-common-datascope # 数据权限过滤 + │ ├── microservices-common-datasource # 多数据源切换(@Master/@Slave) + │ ├── microservices-common-redis # Redis 缓存服务 + │ ├── microservices-common-log # 操作日志记录 + │ ├── microservices-common-async # 异步线程池 + │ ├── microservices-common-httpClient # HTTP 客户端 + │ └── microservices-common-swagger # Knife4j/Swagger API 文档 + └── microservices-modules/ # 业务服务模块 + ├── microservices-modules-system # 系统管理 [9114] + ├── microservices-modules-zone # 特色专区 [9117](主要开发模块) + ├── microservices-modules-cms # 内容管理 [9116] + ├── microservices-modules-pms # 项目管理 [9118] + ├── microservices-modules-file # 文件服务 [9111](MinIO/FastDFS) + ├── microservices-modules-gen # 代码生成 [9112] + ├── microservices-modules-job # 定时任务 [9113] + ├── microservices-modules-dss # 数据统计 [9198] + ├── microservices-modules-dms # 数据管理 [9119] + └── microservices-modules-wiki # Wiki [9199] + ``` + +## 服务端口一览 + +| 服务 | 端口 | 说明 | +|------|------|------| +| microservices-gateway | 8080 | API 网关 | +| microservices-auth | 9110 | 认证中心 | +| microservices-modules-file | 9111 | 文件服务 | +| microservices-modules-gen | 9112 | 代码生成 | +| microservices-modules-job | 9113 | 定时任务 | +| microservices-modules-system | 9114 | 系统管理 | +| microservices-modules-cms | 9116 | 内容管理 | +| microservices-modules-zone | 9117 | 特色专区 | +| microservices-modules-dms | 9119 | 数据管理 | +| microservices-modules-dms | 9198 | 数据统计 | +| microservices-modules-wiki | 9199 | Wiki | + \ No newline at end of file diff --git a/microservices-api/microservices-api-system/src/main/java/com/microservices/system/api/RemoteFileService.java b/microservices-api/microservices-api-system/src/main/java/com/microservices/system/api/RemoteFileService.java index 67052ee82..8c7df060d 100644 --- a/microservices-api/microservices-api-system/src/main/java/com/microservices/system/api/RemoteFileService.java +++ b/microservices-api/microservices-api-system/src/main/java/com/microservices/system/api/RemoteFileService.java @@ -4,6 +4,7 @@ import com.microservices.common.core.constant.SecurityConstants; import com.microservices.common.core.constant.ServiceNameConstants; import com.microservices.common.core.domain.R; import com.microservices.system.api.domain.SysFile; +import com.microservices.system.api.domain.SysFileDownloadLogVo; import com.microservices.system.api.domain.SysFileInfo; import com.microservices.system.api.factory.RemoteFileFallbackFactory; import feign.Response; @@ -52,6 +53,10 @@ public interface RemoteFileService { @GetMapping("/open/getFile/{fileId}") public R getFile(@PathVariable("fileId") Long fileId); + @GetMapping("/common/getFileDetail/{fileId}") + public R getFileDetail(@PathVariable("fileId") Long fileId, + @RequestHeader(SecurityConstants.FROM_SOURCE) String source); + /** * 根据文件Id获取文件信息 * @@ -127,4 +132,15 @@ public interface RemoteFileService { @PostMapping(value = "/common/uploadFileToForge/{fileIdentifiers}") R> uploadFileToForge(@PathVariable("fileIdentifiers") String fileIdentifiers, @RequestHeader(SecurityConstants.FROM_SOURCE) String source); + + /** + * 根据文件ID列表获取下载记录(内部接口) + * + * @param fileIds 文件ID列表,逗号分隔 + * @param source 内部调用鉴权 + * @return 下载记录列表 + */ + @GetMapping("/common/getDownloadUserList/{fileIds}") + R> getDownloadUserList(@PathVariable("fileIds") String fileIds, + @RequestHeader(SecurityConstants.FROM_SOURCE) String source); } diff --git a/microservices-api/microservices-api-system/src/main/java/com/microservices/system/api/domain/SysFile.java b/microservices-api/microservices-api-system/src/main/java/com/microservices/system/api/domain/SysFile.java index ccf2eb1e8..3a6c52596 100644 --- a/microservices-api/microservices-api-system/src/main/java/com/microservices/system/api/domain/SysFile.java +++ b/microservices-api/microservices-api-system/src/main/java/com/microservices/system/api/domain/SysFile.java @@ -10,6 +10,10 @@ import lombok.Data; @Data public class SysFile { + /** + * 文件标识 + */ + private Long fileId; /** * 文件标识 */ diff --git a/microservices-api/microservices-api-system/src/main/java/com/microservices/system/api/domain/SysFileDownloadLogVo.java b/microservices-api/microservices-api-system/src/main/java/com/microservices/system/api/domain/SysFileDownloadLogVo.java new file mode 100644 index 000000000..e8b6f4514 --- /dev/null +++ b/microservices-api/microservices-api-system/src/main/java/com/microservices/system/api/domain/SysFileDownloadLogVo.java @@ -0,0 +1,46 @@ +package com.microservices.system.api.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +/** + * 文件下载记录VO(供跨服务调用) + * + * @author microservices + * @date 2026-06-29 + */ +@Data +@ApiModel("文件下载记录") +public class SysFileDownloadLogVo { + @ApiModelProperty(value = "文件ID") + private Long fileId; + + @ApiModelProperty(value = "文件原始名称") + private String fileOriginName; + + @ApiModelProperty(value = "下载用户ID") + private Long userId; + + @ApiModelProperty(value = "下载用户") + private String userName; + + @ApiModelProperty(value = "用户昵称") + private String nickName; + + @ApiModelProperty(value = "手机号码") + private String phonenumber; + + @ApiModelProperty(value = "邮箱") + private String email; + + @ApiModelProperty(value = "IP地址") + private String ipAddr; + + @ApiModelProperty(value = "下载时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date downloadTime; +} diff --git a/microservices-api/microservices-api-system/src/main/java/com/microservices/system/api/factory/RemoteFileFallbackFactory.java b/microservices-api/microservices-api-system/src/main/java/com/microservices/system/api/factory/RemoteFileFallbackFactory.java index 1bf85bbfe..5347fcbdf 100644 --- a/microservices-api/microservices-api-system/src/main/java/com/microservices/system/api/factory/RemoteFileFallbackFactory.java +++ b/microservices-api/microservices-api-system/src/main/java/com/microservices/system/api/factory/RemoteFileFallbackFactory.java @@ -4,6 +4,7 @@ import com.microservices.common.core.domain.R; import com.microservices.common.core.exception.ServiceException; import com.microservices.system.api.RemoteFileService; import com.microservices.system.api.domain.SysFile; +import com.microservices.system.api.domain.SysFileDownloadLogVo; import com.microservices.system.api.domain.SysFileInfo; import feign.Response; import org.slf4j.Logger; @@ -46,6 +47,11 @@ public class RemoteFileFallbackFactory implements FallbackFactory getFileDetail(Long fileId, String source) { + return R.fail("根据文件id获取文件详细信息失败:" + throwable.getMessage()); + } + @Override public R getFileByFileIdentifier(String fileIdentifier, String source) { return R.fail("根据文件标识获取文件信息失败:" + throwable.getMessage()); @@ -80,6 +86,11 @@ public class RemoteFileFallbackFactory implements FallbackFactory> uploadFileToForge(String fileIdentifiers, String source) { return R.fail("上传本地文件到Forge失败:" + throwable.getMessage()); } + + @Override + public R> getDownloadUserList(String fileIds, String source) { + return R.fail("获取下载记录列表失败:" + throwable.getMessage()); + } }; } } diff --git a/microservices-common/microservices-common-core/src/main/java/com/microservices/common/core/constant/CacheConstants.java b/microservices-common/microservices-common-core/src/main/java/com/microservices/common/core/constant/CacheConstants.java index 342f91498..876cbbc13 100644 --- a/microservices-common/microservices-common-core/src/main/java/com/microservices/common/core/constant/CacheConstants.java +++ b/microservices-common/microservices-common-core/src/main/java/com/microservices/common/core/constant/CacheConstants.java @@ -258,6 +258,15 @@ public class CacheConstants { return PACKAGE_FILE_IS_FINISH + fileIdentifier; } + /** + * 项目点赞统计缓存 Key前缀 + */ + private final static String PROJECT_STATISTICS_LIKE_COUNT = "project_statistics_like_count:"; + + public static String getProjectStatisticsLikeCount(Long zoneId, String username) { + return PROJECT_STATISTICS_LIKE_COUNT + zoneId + "-" + username; + } + // 全局资源数据缓存,最近一年统计数据 public final static String GLOBAL_RESOURCES_DATA_KEY = "GLOBAL_RESOURCES_DATA:"; diff --git a/microservices-common/microservices-common-log/src/main/java/com/microservices/common/log/aspect/LogAspect.java b/microservices-common/microservices-common-log/src/main/java/com/microservices/common/log/aspect/LogAspect.java index 84e7f8996..0cabd2ab2 100644 --- a/microservices-common/microservices-common-log/src/main/java/com/microservices/common/log/aspect/LogAspect.java +++ b/microservices-common/microservices-common-log/src/main/java/com/microservices/common/log/aspect/LogAspect.java @@ -37,14 +37,17 @@ import java.util.Map; */ @Aspect @Component -public class LogAspect -{ +public class LogAspect { private static final Logger log = LoggerFactory.getLogger(LogAspect.class); - /** 排除敏感属性字段 */ - public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }; + /** + * 排除敏感属性字段 + */ + public static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"}; - /** 计算操作消耗时间 */ + /** + * 计算操作消耗时间 + */ private static final ThreadLocal TIME_THREADLOCAL = new NamedThreadLocal("Cost Time"); @Autowired @@ -54,8 +57,7 @@ public class LogAspect * 处理请求前执行 */ @Before(value = "@annotation(controllerLog)") - public void boBefore(JoinPoint joinPoint, Log controllerLog) - { + public void boBefore(JoinPoint joinPoint, Log controllerLog) { TIME_THREADLOCAL.set(System.currentTimeMillis()); } @@ -65,42 +67,41 @@ public class LogAspect * @param joinPoint 切点 */ @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") - public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) - { + public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) { handleLog(joinPoint, controllerLog, null, jsonResult); } /** * 拦截异常操作 - * + * * @param joinPoint 切点 - * @param e 异常 + * @param e 异常 */ @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e") - public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) - { + public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) { handleLog(joinPoint, controllerLog, e, null); } - protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) - { - try - { + protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) { + try { // *========数据库日志=========*// SysOperLog operLog = new SysOperLog(); operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); // 请求的地址 String ip = IpUtils.getIpAddr(); operLog.setOperIp(ip); - operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); + if (ServletUtils.getRequest() != null) { + operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); + // 设置请求方式 + operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); + } + String username = SecurityUtils.getUsername(); - if (StringUtils.isNotBlank(username)) - { + if (StringUtils.isNotBlank(username)) { operLog.setOperName(username); } - if (e != null) - { + if (e != null) { operLog.setStatus(BusinessStatus.FAIL.ordinal()); operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000)); } @@ -108,36 +109,29 @@ public class LogAspect String className = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); operLog.setMethod(className + "." + methodName + "()"); - // 设置请求方式 - operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); // 处理设置注解上的参数 getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); // 设置消耗时间 operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get()); // 保存数据库 asyncLogService.saveSysLog(operLog); - } - catch (Exception exp) - { + } catch (Exception exp) { // 记录本地异常日志 log.error("异常信息:{}", exp.getMessage()); exp.printStackTrace(); - } - finally - { + } finally { TIME_THREADLOCAL.remove(); } } /** * 获取注解中对方法的描述信息 用于Controller层注解 - * - * @param log 日志 + * + * @param log 日志 * @param operLog 操作日志 * @throws Exception */ - public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception - { + public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception { // 设置action动作 operLog.setBusinessType(log.businessType().ordinal()); // 设置标题 @@ -145,36 +139,30 @@ public class LogAspect // 设置操作人类别 operLog.setOperatorType(log.operatorType().ordinal()); // 是否需要保存request,参数和值 - if (log.isSaveRequestData()) - { + if (log.isSaveRequestData()) { // 获取参数的信息,传入到数据库中。 setRequestValue(joinPoint, operLog, log.excludeParamNames()); } // 是否需要保存response,参数和值 - if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) - { + if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) { operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000)); } } /** * 获取请求的参数,放到log中 - * + * * @param operLog 操作日志 * @throws Exception 异常 */ - private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception - { + private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception { String requestMethod = operLog.getRequestMethod(); Map paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest()); if (StringUtils.isEmpty(paramsMap) - && (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))) - { + && (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))) { String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames); operLog.setOperParam(StringUtils.substring(params, 0, 2000)); - } - else - { + } else { operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000)); } } @@ -182,64 +170,53 @@ public class LogAspect /** * 参数拼装 */ - private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) - { - String params = ""; - if (paramsArray != null && paramsArray.length > 0) - { - for (Object o : paramsArray) - { - if (StringUtils.isNotNull(o) && !isFilterObject(o)) - { - try - { + private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) { + StringBuilder params = new StringBuilder(); + if (paramsArray != null) { + for (Object o : paramsArray) { + if (StringUtils.isNotNull(o) && !isFilterObject(o)) { + try { String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames)); - params += jsonObj.toString() + " "; - } - catch (Exception e) - { + // 清洗控制字符(保留常见空白字符:\t, \n, \r) + // ASCII 0-8, 11-12, 14-31, 127 是控制字符,需要去除 + String cleaned = jsonObj.replaceAll("[\\x00-\\x08\\x0B-\\x0C\\x0E-\\x1F\\x7F]", ""); + params.append(cleaned).append(" "); + } catch (Exception e) { + // 记录日志而非静默吞掉,便于排查问题 + log.error("序列化操作日志参数失败: {}", e.getMessage()); } } } } - return params.trim(); + return params.toString().trim(); } /** * 忽略敏感属性 */ - public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames) - { + public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames) { return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames)); } /** * 判断是否需要过滤的对象。 - * + * * @param o 对象信息。 * @return 如果是需要过滤的对象,则返回true;否则返回false。 */ @SuppressWarnings("rawtypes") - public boolean isFilterObject(final Object o) - { + public boolean isFilterObject(final Object o) { Class clazz = o.getClass(); - if (clazz.isArray()) - { + if (clazz.isArray()) { return clazz.getComponentType().isAssignableFrom(MultipartFile.class); - } - else if (Collection.class.isAssignableFrom(clazz)) - { + } else if (Collection.class.isAssignableFrom(clazz)) { Collection collection = (Collection) o; - for (Object value : collection) - { + for (Object value : collection) { return value instanceof MultipartFile; } - } - else if (Map.class.isAssignableFrom(clazz)) - { + } else if (Map.class.isAssignableFrom(clazz)) { Map map = (Map) o; - for (Object value : map.entrySet()) - { + for (Object value : map.entrySet()) { Map.Entry entry = (Map.Entry) value; return entry.getValue() instanceof MultipartFile; } diff --git a/microservices-common/microservices-common-security/src/main/java/com/microservices/common/security/feign/FeignAutoConfiguration.java b/microservices-common/microservices-common-security/src/main/java/com/microservices/common/security/feign/FeignAutoConfiguration.java index ba6f95f20..4e2b1361a 100644 --- a/microservices-common/microservices-common-security/src/main/java/com/microservices/common/security/feign/FeignAutoConfiguration.java +++ b/microservices-common/microservices-common-security/src/main/java/com/microservices/common/security/feign/FeignAutoConfiguration.java @@ -3,6 +3,7 @@ package com.microservices.common.security.feign; import feign.RequestInterceptor; import feign.codec.Encoder; import feign.form.FormEncoder; +import feign.form.spring.SpringFormEncoder; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; @@ -33,6 +34,6 @@ public class FeignAutoConfiguration // 重要的是返回类型要是 Encoder 并且实现类必须是 FormEncoder 或者其子类 @Bean public Encoder feignFormEncoder() { - return new FormEncoder(new SpringEncoder(messageConverters)); + return new SpringFormEncoder(new SpringEncoder(messageConverters)); } } diff --git a/microservices-modules/microservices-modules-cms/src/main/java/com/microservices/cms/doc/domain/CmsDocInputDto.java b/microservices-modules/microservices-modules-cms/src/main/java/com/microservices/cms/doc/domain/CmsDocInputDto.java index b153a4684..3ac294310 100644 --- a/microservices-modules/microservices-modules-cms/src/main/java/com/microservices/cms/doc/domain/CmsDocInputDto.java +++ b/microservices-modules/microservices-modules-cms/src/main/java/com/microservices/cms/doc/domain/CmsDocInputDto.java @@ -8,6 +8,7 @@ import lombok.Data; import java.nio.charset.StandardCharsets; import java.util.Base64; +import java.util.Date; /** * @author otto @@ -51,6 +52,9 @@ public class CmsDocInputDto { @ApiModelProperty("关键词") private String keywords; + @ApiModelProperty(value = "发布时间,隐藏参数,内部调用使用", hidden = true) + private Date publishDate; + public String getContent() { String base64decodedString; try { diff --git a/microservices-modules/microservices-modules-cms/src/main/java/com/microservices/cms/doc/service/impl/CmsDocServiceImpl.java b/microservices-modules/microservices-modules-cms/src/main/java/com/microservices/cms/doc/service/impl/CmsDocServiceImpl.java index b0efdff90..fdda900e3 100644 --- a/microservices-modules/microservices-modules-cms/src/main/java/com/microservices/cms/doc/service/impl/CmsDocServiceImpl.java +++ b/microservices-modules/microservices-modules-cms/src/main/java/com/microservices/cms/doc/service/impl/CmsDocServiceImpl.java @@ -50,11 +50,7 @@ import javax.servlet.http.HttpServletRequest; import java.security.MessageDigest; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -400,6 +396,7 @@ public class CmsDocServiceImpl implements ICmsDocService { // 自动审核通过时也需要增加积分 cmsAsyncService.addPublishDocPointsAsync(cmsDoc.getDeptId(), cmsDoc.getCreateBy(), cmsDoc.getId(), cmsDoc.getName()); } + innerInitPublishDate(cmsDoc,cmsDocInputDto.getPublishDate()); return success ? cmsDoc.getId().intValue() : null; } else { //创建临时分支,创建文件,创建PR @@ -409,11 +406,21 @@ public class CmsDocServiceImpl implements ICmsDocService { boolean success = insertCmsDocWithAudit(cmsDoc, "创建文章:" + StringUtils.truncateStringAndConcatenatingSpecifiedString(cmsDoc.getName(), 100, "..."), cmsDocInputDto.getContent()); + innerInitPublishDate(cmsDoc,cmsDocInputDto.getPublishDate()); return success ? cmsDoc.getId().intValue() : null; } } + private void innerInitPublishDate(CmsDoc cmsDoc, Date publishDate) { + if(publishDate!=null){ + cmsDoc.setCreateTime(publishDate); + cmsDoc.setUpdateTime(publishDate); + cmsDoc.setPublishTime(publishDate); + cmsDocMapper.updateCmsDoc(cmsDoc); + } + } + private boolean isCurrentUserZoneAdmin(Long deptId) { boolean isZoneAdmin = false; List sysUserDeptRoleList = FeignUtils.getReturnData( @@ -1402,7 +1409,7 @@ public class CmsDocServiceImpl implements ICmsDocService { private void checkDocName(String docName, Long deptId, String dirName) { //检查该文件是否已存在 CmsDoc oldCmsDoc = cmsDocMapper.selectCmsDocByNameAndDeptId(docName, deptId, dirName); - if (oldCmsDoc != null) { + if (oldCmsDoc != null && DocAuditStatus.AUDIT_PASS.getAuditCode().equals(oldCmsDoc.getAuditStatus())) { if (oldCmsDoc.getIsDir()) { throw new ServiceException("该栏目已存在(栏目名称[%s])", docName); } else { @@ -1415,7 +1422,7 @@ public class CmsDocServiceImpl implements ICmsDocService { private boolean insertCmsDocWithAudit(CmsDoc cmsDoc, String message, String content) { //检查该文件是否已存在 CmsDoc oldCmsDoc = cmsDocMapper.selectCmsDocByNameAndDeptId(cmsDoc.getName(), cmsDoc.getDeptId(), cmsDoc.getDirName()); - if (oldCmsDoc != null) { + if (oldCmsDoc != null && DocAuditStatus.AUDIT_PASS.getAuditCode().equals(oldCmsDoc.getAuditStatus())) { throw new ServiceException("该文章已存在(文章名称[" + cmsDoc.getName() + "])"); } boolean insertResult = insertCmsDocDatabase(cmsDoc, content); diff --git a/microservices-modules/microservices-modules-cms/src/main/resources/mapper/cms/CmsDocMapper.xml b/microservices-modules/microservices-modules-cms/src/main/resources/mapper/cms/CmsDocMapper.xml index ffe525ab0..70bd1fb68 100644 --- a/microservices-modules/microservices-modules-cms/src/main/resources/mapper/cms/CmsDocMapper.xml +++ b/microservices-modules/microservices-modules-cms/src/main/resources/mapper/cms/CmsDocMapper.xml @@ -239,6 +239,7 @@ and is_dir = 1 + and audit_status = '1' + + where file_id in + + #{fileId} + + order by download_time desc + + + diff --git a/microservices-modules/microservices-modules-file/src/main/resources/mapper/file/SysFileInfoMapper.xml b/microservices-modules/microservices-modules-file/src/main/resources/mapper/file/SysFileInfoMapper.xml index 16ff37e0a..6f44159a3 100644 --- a/microservices-modules/microservices-modules-file/src/main/resources/mapper/file/SysFileInfoMapper.xml +++ b/microservices-modules/microservices-modules-file/src/main/resources/mapper/file/SysFileInfoMapper.xml @@ -97,6 +97,7 @@ content_type_message, mime_type, is_partial_match, + download_count, #{fileOriginName}, @@ -115,6 +116,7 @@ #{contentTypeMessage}, #{mimeType}, #{isPartialMatch}, + #{downloadCount}, @@ -159,4 +161,8 @@ from sys_file_info where file_object_name = #{fileObjectName} + + \ No newline at end of file diff --git a/microservices-modules/microservices-modules-zone/pom.xml b/microservices-modules/microservices-modules-zone/pom.xml index 45e575e6a..0ed52920b 100644 --- a/microservices-modules/microservices-modules-zone/pom.xml +++ b/microservices-modules/microservices-modules-zone/pom.xml @@ -139,6 +139,12 @@ 3.6.2 compile + + + org.apache.commons + commons-compress + 1.26.0 + diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/common/controller/FrontController.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/common/controller/FrontController.java index 889d00339..3d771d37d 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/common/controller/FrontController.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/common/controller/FrontController.java @@ -21,13 +21,17 @@ import com.microservices.zone.resource.domain.vo.ZoneResourceDataVo; import com.microservices.zone.resource.domain.vo.ZoneResourceInputVo; import com.microservices.zone.resource.domain.vo.ZoneResourceSearchVo; import com.microservices.zone.resource.domain.vo.ZoneResourceUpdateVo; +import com.microservices.zone.resource.domain.vo.*; +import com.microservices.zone.resource.service.IAiResourceParseService; import com.microservices.zone.resource.service.IZoneResourceService; +import io.swagger.annotations.*; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import java.util.Collections; import java.util.List; @@ -43,6 +47,9 @@ public class FrontController extends BaseController { @Autowired private IZoneResourceService zoneResourceService; + @Autowired + private IAiResourceParseService aiResourceParseService; + @Autowired private IZoneProjectService zoneProjectService; @@ -162,7 +169,7 @@ public class FrontController extends BaseController { @Log(title = "专区前台项目操作", businessType = BusinessType.UPDATE) @PutMapping("/editProject/{projectId}") @ApiOperation(value = "编辑项目聚合") - public AjaxResult editProject(@PathVariable("projectId") Long projectId,@RequestBody ZoneProjectFrontUpdateVo zoneProjectFrontUpdateVo) { + public AjaxResult editProject(@PathVariable("projectId") Long projectId, @RequestBody ZoneProjectFrontUpdateVo zoneProjectFrontUpdateVo) { return success(zoneProjectService.updateMyZoneProject(projectId, zoneProjectFrontUpdateVo.toZoneProjectUpdateVo())); } @@ -194,4 +201,187 @@ public class FrontController extends BaseController { } return genericsSuccess(zoneFrontRoleVo); } + + // ==================== Skill资源广场 - 我的资源管理 ==================== + + /** + * 上传ZIP并解析 + */ + @RequiresPermissions("zone:front:skill:add") + @PostMapping("/skill/parse") + @ApiOperation("上传ZIP并解析Skill包") + @ApiImplicitParams({ + @ApiImplicitParam(name = "file", value = "文件实体", paramType = "form", dataType = "_file"), + }) + public GenericsAjaxResult parseSkillZip(@RequestPart("file") MultipartFile file) { + return genericsSuccess(aiResourceParseService.parseSkillZip(file)); + } + + /** + * 创建Skill资源 + */ + @RequiresPermissions("zone:front:skill:add") + @Log(title = "创建Skill资源", businessType = BusinessType.INSERT) + @PostMapping("/skill") + @ApiOperation("创建Skill资源") + public AjaxResult addSkill(@RequestBody @Validated AiSkillInputVo inputVo) { + return success(zoneResourceService.insertAiSkillResource(inputVo)); + } + + @RequiresPermissions("zone:front:resource:add") + @Log(title = "重新提交被驳回的资源", businessType = BusinessType.UPDATE) + @PutMapping("/resource/{id}/resubmit") + @ApiOperation(value = "重新提交被驳回的资源") + public AjaxResult resubmitAiResource( + @ApiParam(value = "资源ID", required = true) @PathVariable("id") Long id) { + return toAjax(zoneResourceService.resubmitAiResource(id)); + } + + @RequiresPermissions("zone:front:resource:edit") + @Log(title = "撤回审核中的资源", businessType = BusinessType.UPDATE) + @PutMapping("/resource/{id}/withdraw") + @ApiOperation(value = "撤回审核中的资源") + public AjaxResult withdrawAiResource( + @ApiParam(value = "资源ID", required = true) @PathVariable("id") Long id) { + return toAjax(zoneResourceService.withdrawAiResource(id)); + } + + /** + * 下架资源 + */ + @RequiresPermissions("zone:front:resource:edit") + @Log(title = "下架资源", businessType = BusinessType.UPDATE) + @PutMapping("/{id}/takeDown") + @ApiOperation("下架资源") + public AjaxResult takeDownResource( + @ApiParam(value = "资源ID", required = true) @PathVariable("id") Long id, + @RequestParam(value = "reason", required = false) String reason) { + return toAjax(zoneResourceService.takeDownAiResource(id, reason)); + } + + /** + * 上架资源 + */ + @RequiresPermissions("zone:front:resource:edit") + @Log(title = "上架资源", businessType = BusinessType.UPDATE) + @PutMapping("/{id}/takeUp") + @ApiOperation("上架资源") + public AjaxResult takeUpResource( + @ApiParam(value = "资源ID", required = true) @PathVariable("id") Long id, + @RequestParam(value = "reason", required = false) String reason) { + return toAjax(zoneResourceService.takeUpAiResource(id, reason)); + } + + /** + * 获取资源统计 + */ + @RequiresPermissions("zone:front:resource:query") + @GetMapping("/resourceStatistics") + @ApiOperation("获取资源统计") + @ApiImplicitParams({ + @ApiImplicitParam(name = "isMine", value = "是否查询我的资源(true-我的资源,false-专区资源)", paramType = "query", dataType = "boolean"), + @ApiImplicitParam(name = "zoneId", value = "专区ID", paramType = "query", dataType = "Long", required = true), + @ApiImplicitParam(name = "resourceCategory", value = "资源分类(SKILL/MODEL/DATASET),为空则查询所有", paramType = "query", dataType = "String") + }) + public GenericsAjaxResult resourceStatistics( + @ApiParam(name = "isMine", value = "是否查询我的资源") @RequestParam(value = "isMine", required = false) Boolean isMine, + @ApiParam(name = "zoneId") @RequestParam(value = "zoneId") Long zoneId, + @ApiParam(name = "resourceCategory") @RequestParam(value = "resourceCategory", required = false) String resourceCategory) { + return genericsSuccess(zoneResourceService.getResourceStatistics(zoneId, isMine, resourceCategory)); + } + + /** + * 获取我的项目聚合统计 + */ + @RequiresPermissions("zone:front:project:query") + @GetMapping("/myProjectStatistics") + @ApiOperation("获取我的项目聚合统计") + @ApiImplicitParams({ + @ApiImplicitParam(name = "zoneId", value = "专区ID", paramType = "query", dataType = "Long", required = true) + }) + public GenericsAjaxResult myProjectStatistics(@ApiParam(name = "zoneId") @RequestParam(value = "zoneId") Long zoneId) { + return genericsSuccess(zoneProjectService.getUserProjectStatistics(zoneId,SecurityUtils.getUsername())); + } + + /** + * 点赞/取消点赞 + */ + @RequiresPermissions("zone:front:resource:query") + @PostMapping("/{id}/like") + @ApiOperation("点赞/取消点赞资源") + public AjaxResult toggleLike( + @ApiParam(value = "资源ID", required = true) @PathVariable("id") Long id) { + boolean liked = zoneResourceService.toggleZoneResourcesLike(id); + return success(liked ? "已点赞" : "已取消点赞"); + } + + /** + * 修改Skill资源 + */ + @RequiresPermissions("zone:front:skill:edit") + @Log(title = "修改Skill资源", businessType = BusinessType.UPDATE) + @PutMapping("/skill") + @ApiOperation("修改Skill资源") + public AjaxResult editSkill(@RequestBody @Validated AiSkillUpdateVo updateVo) { + return success(zoneResourceService.updateAiSkillResource(updateVo)); + } + + // ==================== 模型资源 ==================== + + /** + * 创建Model资源 + */ + @RequiresPermissions("zone:front:model:add") + @Log(title = "创建Model资源", businessType = BusinessType.INSERT) + @PostMapping("/resource/model") + @ApiOperation("创建Model资源") + public AjaxResult addModel(@RequestBody @Validated AiModelInputVo inputVo) { + return success(zoneResourceService.insertAiModelResource(inputVo)); + } + + /** + * 修改Model资源 + */ + @RequiresPermissions("zone:front:model:edit") + @Log(title = "修改Model资源", businessType = BusinessType.UPDATE) + @PutMapping("/resource/model") + @ApiOperation("修改Model资源") + public AjaxResult editModel(@RequestBody @Validated AiModelUpdateVo updateVo) { + return success(zoneResourceService.updateAiModelResource(updateVo)); + } + + // ==================== 数据集资源 ==================== + + /** + * 创建Dataset资源 + */ + @RequiresPermissions("zone:front:dataset:add") + @Log(title = "创建Dataset资源", businessType = BusinessType.INSERT) + @PostMapping("/resource/dataset") + @ApiOperation("创建Dataset资源") + public AjaxResult addDataset(@RequestBody @Validated AiDatasetInputVo inputVo) { + return success(zoneResourceService.insertAiDatasetResource(inputVo)); + } + + /** + * 修改Dataset资源 + */ + @RequiresPermissions("zone:front:dataset:edit") + @Log(title = "修改Dataset资源", businessType = BusinessType.UPDATE) + @PutMapping("/resource/dataset") + @ApiOperation("修改Dataset资源") + public AjaxResult editDataset(@RequestBody @Validated AiDatasetUpdateVo updateVo) { + return success(zoneResourceService.updateAiDatasetResource(updateVo)); + } + + + /** + * 查询资源是否点赞 + */ + @RequiresPermissions("zone:front:resource:query") + @GetMapping("/{ids}/like") + @ApiOperation(value = "查询资源是否点赞") + public GenericsTableDataInfo myResource(@PathVariable("ids") String ids) { + return new GenericsTableDataInfo<>(zoneResourceService.selectZoneResourcesIsLike(ids)); + } } diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/common/controller/OpenController.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/common/controller/OpenController.java index d9c976889..459aa587f 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/common/controller/OpenController.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/common/controller/OpenController.java @@ -16,6 +16,9 @@ import com.microservices.zone.dataset.service.IZoneDataSetService; import com.microservices.zone.detail.domain.vo.ZoneBaseDataVo; import com.microservices.zone.detail.domain.vo.ZoneDetailDataVo; import com.microservices.zone.detail.service.IZoneDetailService; +import com.microservices.zone.journals.domain.vo.ZoneJournalsDataTableVo; +import com.microservices.zone.journals.domain.vo.ZoneJournalsParentDataVo; +import com.microservices.zone.journals.domain.vo.ZoneJournalsSearchVo; import com.microservices.zone.member.domain.vo.ZoneMemberDataVo; import com.microservices.zone.member.domain.vo.ZoneMemberOpenSearchVo; import com.microservices.zone.member.domain.vo.ZoneMemberOverviewVo; @@ -31,10 +34,14 @@ import com.microservices.zone.project.domain.vo.ZoneProjectSearchVo; import com.microservices.zone.project.domain.vo.ZoneStatisticsVo; import com.microservices.zone.project.service.IZoneProjectService; import com.microservices.zone.project.service.IZoneProjectTypeService; +import com.microservices.zone.resource.domain.ZoneResource; import com.microservices.zone.resource.domain.ZoneResourceDomain; import com.microservices.zone.resource.domain.ZoneResourceType; import com.microservices.zone.resource.domain.vo.ZoneResourceDataVo; +import com.microservices.zone.resource.domain.vo.ZoneResourceDomainSearchVo; import com.microservices.zone.resource.domain.vo.ZoneResourceOpenSearchVo; +import com.microservices.zone.resource.domain.vo.ZoneResourceSearchVo; +import com.microservices.zone.resource.enums.AiResourceCategory; import com.microservices.zone.resource.service.IZoneResourceDomainService; import com.microservices.zone.resource.service.IZoneResourceService; import com.microservices.zone.resource.service.IZoneResourceTypeService; @@ -103,6 +110,9 @@ public class OpenController extends BaseController { @Autowired private IZoneVisitsService zoneVisitsService; + @Autowired + private IGlobalSearchService globalSearchService; + /** * 新增特色专区用户浏览 */ @@ -323,14 +333,9 @@ public class OpenController extends BaseController { */ @GetMapping("/{zoneId}/resourceDomain/list") @ApiOperation("查询特色专区资源领域列表") - public GenericsTableDataInfo resourceList( - @ApiParam(name = "zoneId", value = "专区Id", required = true) @PathVariable("zoneId") Long zoneId, - @ApiParam(name = "name", value = "资源领域名称") String name) { - ZoneResourceDomain zoneResourceDomain = new ZoneResourceDomain(); - zoneResourceDomain.setName(name); - zoneResourceDomain.setZoneId(zoneId); + public GenericsTableDataInfo resourceList(ZoneResourceDomainSearchVo zoneResourceDomainSearchVo) { startPage(); - List list = zoneResourceDomainService.selectZoneResourceDomainList(zoneResourceDomain); + List list = zoneResourceDomainService.selectZoneResourceDomainList(zoneResourceDomainSearchVo.toZoneResourceDomain()); return getGenericsDataTable(list); } @@ -441,9 +446,6 @@ public class OpenController extends BaseController { return getGenericsDataTable(list); } - @Autowired - private IGlobalSearchService globalSearchService; - /** * 全局搜索接口(POST方法) * @@ -456,4 +458,24 @@ public class OpenController extends BaseController { GlobalSearchResponse response = globalSearchService.globalSearch(globalSearchRequest); return new GenericsTableDataInfo<>(response.getResults(), response.getTotal()); } + + // ==================== 通用评论 - 开放接口 ==================== + + @Autowired + private com.microservices.zone.journals.service.IZoneJournalsService zoneJournalsService; + + /** + * 公开-资源评论列表 + */ + @GetMapping("/journals") + @ApiOperation("公开-评论列表") + @ApiImplicitParams({ + @ApiImplicitParam(name = "pageNum", value = "当前记录起始索引", paramType = "query", dataType = "Integer"), + @ApiImplicitParam(name = "pageSize", value = "每页显示记录数", paramType = "query", dataType = "Integer") + }) + public GenericsAjaxResult journalsList(@Validated ZoneJournalsSearchVo searchVo) { + startPage(); + List list = zoneJournalsService.selectJournalsList(searchVo); + return genericsSuccess(ZoneJournalsDataTableVo.toZoneJournalsDataTableVo(getDataTable(list))); + } } diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/detail/service/impl/ZoneDetailServiceImpl.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/detail/service/impl/ZoneDetailServiceImpl.java index 8e7d8c5fb..c8e970786 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/detail/service/impl/ZoneDetailServiceImpl.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/detail/service/impl/ZoneDetailServiceImpl.java @@ -111,10 +111,10 @@ public class ZoneDetailServiceImpl implements IZoneDetailService { private ZonePartnersTypeMapper zonePartnersTypeMapper; @Autowired @Lazy - private IZoneSpecialProjectService zoneSpecialProjectService; + private IZoneMemberService zoneMemberService; @Autowired @Lazy - private IZoneMemberService zoneMemberService; + private IZoneSpecialProjectService zoneSpecialProjectService; @Value("${zone.zoneAdminUrl}") private String zoneAdminUrl; @@ -128,12 +128,12 @@ public class ZoneDetailServiceImpl implements IZoneDetailService { private String docManagePath; @Value("${zone.projectManagePath:/zone/project/list}") private String projectManagePath; + @Value("${zone.autoMemberZoneIds:}") + private String autoMemberZoneIds; @Value("${zone.resourceManageUrl}") private String resourceManageUrl; @Value("${zone.docManageUrl}") private String docManageUrl; - @Value("${zone.autoMemberZoneIds:}") - private String autoMemberZoneIds; @Value("${zone.autoMemberTypeName:}") private String autoMemberTypeName; @Value("${zone.autoMemberLevelName:}") diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/controller/ZoneJournalsController.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/controller/ZoneJournalsController.java new file mode 100644 index 000000000..d480100d1 --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/controller/ZoneJournalsController.java @@ -0,0 +1,109 @@ +package com.microservices.zone.journals.controller; + +import com.microservices.common.core.web.controller.BaseController; +import com.microservices.common.core.web.domain.AjaxResult; +import com.microservices.common.core.web.page.GenericsTableDataInfo; +import com.microservices.common.core.web.page.TableDataInfo; +import com.microservices.common.log.annotation.Log; +import com.microservices.common.log.enums.BusinessType; +import com.microservices.common.security.annotation.RequiresPermissions; +import com.microservices.zone.journals.domain.ZoneJournals; +import com.microservices.zone.journals.domain.vo.ZoneJournalsInputVo; +import com.microservices.zone.journals.domain.vo.ZoneJournalsParentDataVo; +import com.microservices.zone.journals.domain.vo.ZoneJournalsSearchVo; +import com.microservices.zone.journals.domain.vo.ZoneJournalsUpdateVo; +import com.microservices.zone.journals.service.IZoneJournalsService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 通用评论Controller + * + * @author otto + * @date 2025-04-30 + */ +@RestController +@RequestMapping("/journals") +@Api(tags = "通用评论接口") +public class ZoneJournalsController extends BaseController { + + @Autowired + private IZoneJournalsService zoneJournalsService; + + /** + * 查询评论列表(二级结构) + */ +// @RequiresPermissions("zone:journals:list") + @GetMapping("/list") + @ApiOperation("查询评论列表") + public GenericsTableDataInfo list(ZoneJournalsSearchVo searchVo) { + startPage(); + List list = zoneJournalsService.selectJournalsList(searchVo); + return new GenericsTableDataInfo<>(list); + } + + /** + * 获取评论详细信息 + */ +// @RequiresPermissions("zone:journals:query") + @GetMapping("/{id}") + @ApiOperation("获取评论详细信息") + public AjaxResult getInfo(@ApiParam(value = "评论ID", required = true) @PathVariable("id") Long id) { + ZoneJournals zoneJournals = zoneJournalsService.selectZoneJournalsById(id); + return success(zoneJournals); + } + + /** + * 新增评论 + */ +// @RequiresPermissions("zone:journals:add") + @Log(title = "新增评论", businessType = BusinessType.INSERT) + @PostMapping + @ApiOperation("新增评论") + public AjaxResult add(@RequestBody @Validated ZoneJournalsInputVo inputVo) { + Long journalId = zoneJournalsService.insertJournals(inputVo); + return success(journalId); + } + + /** + * 修改评论 + */ +// @RequiresPermissions("zone:journals:edit") + @Log(title = "修改评论", businessType = BusinessType.UPDATE) + @PutMapping + @ApiOperation("修改评论") + public AjaxResult edit(@RequestBody @Validated ZoneJournalsUpdateVo updateVo) { + int result = zoneJournalsService.updateJournals(updateVo); + return toAjax(result); + } + + /** + * 删除评论 + */ +// @RequiresPermissions("zone:journals:remove") + @Log(title = "删除评论", businessType = BusinessType.DELETE) + @DeleteMapping("/{id}") + @ApiOperation("删除评论") + public AjaxResult remove(@ApiParam(value = "评论ID", required = true) @PathVariable("id") Long id) { + int result = zoneJournalsService.deleteJournalsById(id); + return toAjax(result); + } + + /** + * 批量删除评论 + */ +// @RequiresPermissions("zone:journals:remove") + @Log(title = "批量删除评论", businessType = BusinessType.DELETE) + @DeleteMapping("/batch/{ids}") + @ApiOperation("批量删除评论") + public AjaxResult removeBatch(@ApiParam(value = "评论ID数组", required = true) @PathVariable Long[] ids) { + int result = zoneJournalsService.deleteJournalsByIds(ids); + return toAjax(result); + } +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/ZoneJournals.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/ZoneJournals.java new file mode 100644 index 000000000..c7e7af126 --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/ZoneJournals.java @@ -0,0 +1,84 @@ +package com.microservices.zone.journals.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.microservices.common.core.web.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +/** + * 通用评论对象 zone_journals + * + * @author otto + * @date 2025-04-30 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("通用评论对象") +public class ZoneJournals extends BaseEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 评论对象类型常量 + */ + public static final String JOURNAL_TYPE_ZONE_RESOURCE = "resource"; + + /** 评论ID */ + @ApiModelProperty(value = "评论ID") + private Long id; + + /** 评论对象ID */ + @ApiModelProperty(value = "评论对象ID") + private Long journalizedId; + + /** 评论对象类型 */ + @ApiModelProperty(value = "评论对象类型") + private String journalizedType; + + /** GitLink用户ID */ + @ApiModelProperty(value = "GitLink用户ID") + private Long userId; + + /** 评论内容 */ + @ApiModelProperty(value = "评论内容") + private String notes; + + /** 父级评论ID */ + @ApiModelProperty(value = "父级评论ID") + private Long parentId; + + /** 回复的评论ID */ + @ApiModelProperty(value = "回复的评论ID") + private Long replyId; + + /** 评论数量 */ + @ApiModelProperty(value = "评论数量") + private Integer commentsCount; + + /** 附件标识列表 */ + @ApiModelProperty(value = "附件标识列表") + private String attachmentIdentifiers; + + /** @用户列表 */ + @ApiModelProperty(value = "@用户列表") + private String receiversLogin; + + /** 删除标志 */ + @ApiModelProperty(value = "删除标志") + private String delFlag; + + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty(value = "创建时间") + private Date createdOn; + + /** 更新时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty(value = "更新时间") + private Date updatedOn; +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsChildrenDataVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsChildrenDataVo.java new file mode 100644 index 000000000..3653f25ee --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsChildrenDataVo.java @@ -0,0 +1,23 @@ +package com.microservices.zone.journals.domain.vo; + +import com.microservices.system.api.domain.SimpleSysUser; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通用评论子级评论数据VO + * + * @author otto + * @date 2025-04-30 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("子级评论数据对象") +public class ZoneJournalsChildrenDataVo extends ZoneJournalsDataVo { + + /** 回复的用户 */ + @ApiModelProperty(value = "回复的用户") + private SimpleSysUser reply_user; +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsDataTableVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsDataTableVo.java new file mode 100644 index 000000000..4e6954cd5 --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsDataTableVo.java @@ -0,0 +1,40 @@ +package com.microservices.zone.journals.domain.vo; + +import com.microservices.common.core.web.page.TableDataInfo; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * 评论列表对象 + */ +@Data +@ApiModel("评论列表对象") +public class ZoneJournalsDataTableVo { + @ApiModelProperty(value = "评论列表") + private List journals; + + @ApiModelProperty(value = "评论总数量") + private Long total_comment_journals_count; + + @ApiModelProperty(value = "当前总数量") + private Long total_count; + + @ApiModelProperty(value = "评论+操作总数量") + private Long total_journals_count; + + @ApiModelProperty(value = "操作总数量") + private Long total_operate_journals_count; + + public static ZoneJournalsDataTableVo toZoneJournalsDataTableVo(TableDataInfo tableDataInfo) { + ZoneJournalsDataTableVo target = new ZoneJournalsDataTableVo(); + target.setJournals(tableDataInfo.getRows()); + target.setTotal_comment_journals_count(tableDataInfo.getTotal()); + target.setTotal_count(tableDataInfo.getTotal()); + target.setTotal_journals_count(tableDataInfo.getTotal()); + target.setTotal_operate_journals_count(0L); + return target; + } +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsDataVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsDataVo.java new file mode 100644 index 000000000..0a295f3b2 --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsDataVo.java @@ -0,0 +1,57 @@ +package com.microservices.zone.journals.domain.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.microservices.system.api.domain.SimpleGitlinkFileInfo; +import com.microservices.system.api.domain.SysFileInfo; +import com.microservices.system.api.domain.SimpleSysUser; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +/** + * 通用评论基础数据VO + * + * @author otto + * @date 2025-04-30 + */ +@Data +@ApiModel("评论数据对象") +public class ZoneJournalsDataVo { + + /** 评论ID */ + @ApiModelProperty(value = "评论ID") + private Long id; + + /** 评论用户 */ + @ApiModelProperty(value = "评论用户") + private SimpleSysUser user; + + /** 评论内容 */ + @ApiModelProperty(value = "评论内容") + private String notes; + + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty(value = "创建时间") + private Date created_on; + + /** 更新时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty(value = "更新时间") + private Date updated_on; + + /** 附件列表 */ + @ApiModelProperty(value = "附件列表") + private List attachments; + + /** 评论数量 */ + @ApiModelProperty(value = "评论数量") + private Integer comments_count = 0; + + /** 是否详情 */ + @ApiModelProperty(value = "是否详情") + private Boolean is_journal_detail = false; +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsInputVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsInputVo.java new file mode 100644 index 000000000..fc16959b3 --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsInputVo.java @@ -0,0 +1,52 @@ +package com.microservices.zone.journals.domain.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +/** + * 通用评论输入VO + * + * @author otto + * @date 2025-04-30 + */ +@Data +@ApiModel("评论输入对象") +public class ZoneJournalsInputVo { + + /** 评论对象ID */ + @NotNull(message = "评论对象ID不能为空") + @ApiModelProperty(value = "评论对象ID", required = true) + private Long journalized_id; + + /** 评论对象类型 */ + @NotBlank(message = "评论对象类型不能为空(资源:resource)") + @ApiModelProperty(value = "评论对象类型", required = true) + private String journalized_type; + + /** 父级评论ID */ + @ApiModelProperty(value = "父级评论ID") + private Long parent_id; + + /** 回复的评论ID */ + @ApiModelProperty(value = "回复的评论ID") + private Long reply_id; + + /** 评论内容 */ + @NotBlank(message = "评论内容不能为空") + @Size(max = 2000, message = "评论内容不能超过2000个字符") + @ApiModelProperty(value = "评论内容", required = true) + private String notes; + + /** @用户列表 */ + @ApiModelProperty(value = "@用户列表") + private String[] receivers_login; + + /** 附件标识列表 */ + @ApiModelProperty(value = "附件标识列表") + private String attachment_identifiers; +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsParentDataVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsParentDataVo.java new file mode 100644 index 000000000..ba3be5048 --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsParentDataVo.java @@ -0,0 +1,24 @@ +package com.microservices.zone.journals.domain.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 通用评论父级评论数据VO + * + * @author otto + * @date 2025-04-30 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("父级评论数据对象") +public class ZoneJournalsParentDataVo extends ZoneJournalsDataVo { + + /** 子级评论列表 */ + @ApiModelProperty(value = "子级评论列表") + private List children_journals; +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsSearchVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsSearchVo.java new file mode 100644 index 000000000..f1d7e82e5 --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsSearchVo.java @@ -0,0 +1,28 @@ +package com.microservices.zone.journals.domain.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 通用评论查询VO + * + * @author otto + * @date 2025-04-30 + */ +@Data +@ApiModel("评论查询对象") +public class ZoneJournalsSearchVo { + /** 评论对象ID */ + @NotNull + @ApiModelProperty(value = "评论对象ID") + private Long journalized_id; + + /** 评论对象类型 */ + @NotBlank + @ApiModelProperty(value = "评论对象类型(资源:resource)") + private String journalized_type; +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsUpdateVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsUpdateVo.java new file mode 100644 index 000000000..c4d75227e --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/domain/vo/ZoneJournalsUpdateVo.java @@ -0,0 +1,39 @@ +package com.microservices.zone.journals.domain.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +/** + * 通用评论更新VO + * + * @author otto + * @date 2025-04-30 + */ +@Data +@ApiModel("评论更新对象") +public class ZoneJournalsUpdateVo { + + /** 评论ID */ + @NotNull(message = "评论ID不能为空") + @ApiModelProperty(value = "评论ID", required = true) + private Long id; + + /** 评论内容 */ + @NotBlank(message = "评论内容不能为空") + @Size(max = 2000, message = "评论内容不能超过2000个字符") + @ApiModelProperty(value = "评论内容", required = true) + private String notes; + + /** @用户列表 */ + @ApiModelProperty(value = "@用户列表") + private String[] receivers_login; + + /** 附件标识列表 */ + @ApiModelProperty(value = "附件标识列表") + private String attachment_identifiers; +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/mapper/ZoneJournalsMapper.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/mapper/ZoneJournalsMapper.java new file mode 100644 index 000000000..145e71dba --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/mapper/ZoneJournalsMapper.java @@ -0,0 +1,115 @@ +package com.microservices.zone.journals.mapper; + +import com.microservices.zone.journals.domain.ZoneJournals; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 通用评论Mapper接口 + * + * @author otto + * @date 2025-04-30 + */ +@Mapper +public interface ZoneJournalsMapper { + + /** + * 查询评论 + * + * @param id 评论主键 + * @return 评论 + */ + ZoneJournals selectZoneJournalsById(Long id); + + /** + * 查询评论列表(父评论) + * + * @param zoneJournals 评论查询条件 + * @return 评论集合 + */ + List selectZoneJournalsList(ZoneJournals zoneJournals); + + /** + * 查询子评论列表 + * + * @param parentId 父评论ID + * @return 子评论集合 + */ + List selectChildrenZoneJournalsList(@Param("parentId") Long parentId); + + /** + * 新增评论 + * + * @param zoneJournals 评论 + * @return 结果 + */ + int insertZoneJournals(ZoneJournals zoneJournals); + + /** + * 修改评论 + * + * @param zoneJournals 评论 + * @return 结果 + */ + int updateZoneJournals(ZoneJournals zoneJournals); + + /** + * 删除评论(逻辑删除) + * + * @param id 评论主键 + * @return 结果 + */ + int deleteZoneJournalsById(Long id); + + /** + * 批量删除评论(逻辑删除) + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + int deleteZoneJournalsByIds(Long[] ids); + + /** + * 删除子评论(逻辑删除) + * + * @param parentId 父评论ID + * @return 结果 + */ + int deleteZoneJournalsByParentId(@Param("parentId") Long parentId); + + /** + * 根据类型和评论对象Id删除所有评论 + * + * @param type 类型 + * @param journalId 评论对象Id + */ + void deleteZoneJournalsByTypeAndJournalId(@Param("type") String type, @Param("journalId") Long journalId); + + /** + * 批量更新关联ID + * + * @param type 类型 + * @param oldJournalId 旧评论对象Id + * @param journalId 新评论对象Id + */ + void updateJournalsIdByTypeAndOldId(@Param("type") String type, @Param("oldJournalId") Long oldJournalId, @Param("journalId") Long journalId); + + /** + * 更新父评论的子评论数量 + * + * @param id 父评论ID + * @return 结果 + */ + int updateCommentsCountById(@Param("id") Long id); + + /** + * 统计评论数量 + * + * @param journalizedId 评论对象ID + * @param journalizedType 评论对象类型 + * @return 评论数量 + */ + int countByJournalized(@Param("journalizedId") Long journalizedId, @Param("journalizedType") String journalizedType); +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/service/IZoneJournalsService.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/service/IZoneJournalsService.java new file mode 100644 index 000000000..24991ad6e --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/service/IZoneJournalsService.java @@ -0,0 +1,83 @@ +package com.microservices.zone.journals.service; + +import com.microservices.zone.journals.domain.ZoneJournals; +import com.microservices.zone.journals.domain.vo.ZoneJournalsInputVo; +import com.microservices.zone.journals.domain.vo.ZoneJournalsParentDataVo; +import com.microservices.zone.journals.domain.vo.ZoneJournalsSearchVo; +import com.microservices.zone.journals.domain.vo.ZoneJournalsUpdateVo; + +import java.util.List; + +/** + * 通用评论Service接口 + * + * @author otto + * @date 2025-04-30 + */ +public interface IZoneJournalsService { + + /** + * 查询评论 + * + * @param id 评论主键 + * @return 评论 + */ + ZoneJournals selectZoneJournalsById(Long id); + + /** + * 查询评论列表(二级结构) + * + * @param searchVo 查询条件 + * @return 评论列表(父评论包含子评论) + */ + List selectJournalsList(ZoneJournalsSearchVo searchVo); + + /** + * 新增评论 + * + * @param inputVo 评论输入信息 + * @return 评论ID + */ + Long insertJournals(ZoneJournalsInputVo inputVo); + + /** + * 修改评论 + * + * @param updateVo 评论更新信息 + * @return 结果 + */ + int updateJournals(ZoneJournalsUpdateVo updateVo); + + /** + * 删除评论(级联删除子评论) + * + * @param id 评论主键 + * @return 结果 + */ + int deleteJournalsById(Long id); + + /** + * 批量删除评论 + * + * @param ids 需要删除的评论主键集合 + * @return 结果 + */ + int deleteJournalsByIds(Long[] ids); + + /** + * 根据类型和评论对象Id删除所有评论 + * + * @param type 类型 + * @param journalId 评论对象Id + */ + void deleteJournalsByTypeAndJournalId(String type, Long journalId); + + /** + * 批量更新关联ID + * + * @param type 类型 + * @param oldJournalId 旧评论对象Id + * @param journalId 新评论对象Id + */ + void updateJournalsIdByTypeAndOldId(String type, Long oldJournalId, Long journalId); +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/service/impl/ZoneJournalsServiceImpl.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/service/impl/ZoneJournalsServiceImpl.java new file mode 100644 index 000000000..f96e87926 --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/journals/service/impl/ZoneJournalsServiceImpl.java @@ -0,0 +1,406 @@ +package com.microservices.zone.journals.service.impl; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.microservices.common.core.exception.ServiceException; +import com.microservices.common.core.utils.DateUtils; +import com.microservices.common.core.utils.StringUtils; +import com.microservices.common.security.utils.SecurityUtils; +import com.microservices.system.api.RemoteFileService; +import com.microservices.system.api.domain.SimpleGitlinkFileInfo; +import com.microservices.system.api.domain.SysFileInfo; +import com.microservices.system.api.domain.SimpleSysUser; +import com.microservices.system.api.utils.FeignUtils; +import com.microservices.zone.common.service.IZoneCommonService; +import com.microservices.zone.journals.domain.ZoneJournals; +import com.microservices.zone.journals.domain.vo.ZoneJournalsChildrenDataVo; +import com.microservices.zone.journals.domain.vo.ZoneJournalsInputVo; +import com.microservices.zone.journals.domain.vo.ZoneJournalsParentDataVo; +import com.microservices.zone.journals.domain.vo.ZoneJournalsSearchVo; +import com.microservices.zone.journals.domain.vo.ZoneJournalsUpdateVo; +import com.microservices.zone.journals.mapper.ZoneJournalsMapper; +import com.microservices.zone.journals.service.IZoneJournalsService; +import com.microservices.zone.resource.service.IZoneResourceService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 通用评论Service业务层处理 + * + * @author otto + * @date 2025-04-30 + */ +@Service +public class ZoneJournalsServiceImpl implements IZoneJournalsService { + + @Autowired + private ZoneJournalsMapper zoneJournalsMapper; + + @Autowired + private IZoneCommonService zoneCommonService; + + @Autowired + private IZoneResourceService zoneResourceService; + + @Autowired + private RemoteFileService remoteFileService; + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * 查询评论 + * + * @param id 评论主键 + * @return 评论 + */ + @Override + public ZoneJournals selectZoneJournalsById(Long id) { + ZoneJournals zoneJournals = zoneJournalsMapper.selectZoneJournalsById(id); + if (zoneJournals == null) { + throw new ServiceException("该评论不存在(评论Id[%d])", id); + } + // 校验评论对象是否存在 + checkByType(zoneJournals); + return zoneJournals; + } + + /** + * 查询评论列表(二级结构) + * + * @param searchVo 查询条件 + * @return 评论列表(父评论包含子评论) + */ + @Override + public List selectJournalsList(ZoneJournalsSearchVo searchVo) { + // 校验评论对象是否存在 + ZoneJournals checkJournal = new ZoneJournals(); + checkJournal.setJournalizedId(searchVo.getJournalized_id()); + checkJournal.setJournalizedType(searchVo.getJournalized_type()); + checkByType(checkJournal); + + // 查询父评论列表 + ZoneJournals query = new ZoneJournals(); + query.setJournalizedId(searchVo.getJournalized_id()); + query.setJournalizedType(searchVo.getJournalized_type()); + List parentJournalsList = zoneJournalsMapper.selectZoneJournalsList(query); + + // 转换为VO并组装子评论 + return parentJournalsList.stream() + .map(this::toParentDataVo) + .collect(Collectors.toList()); + } + + /** + * 新增评论 + * + * @param inputVo 评论输入信息 + * @return 评论ID + */ + @Override + public Long insertJournals(ZoneJournalsInputVo inputVo) { + // 校验评论对象类型 + ZoneJournals zoneJournals = new ZoneJournals(); + zoneJournals.setJournalizedId(inputVo.getJournalized_id()); + zoneJournals.setJournalizedType(inputVo.getJournalized_type()); + checkByType(zoneJournals); + + // 如果是子评论,校验父评论 + if (inputVo.getParent_id() != null) { + ZoneJournals parentJournal = selectZoneJournalsById(inputVo.getParent_id()); + if (parentJournal.getParentId() != null) { + throw new ServiceException("该评论非父级评论(评论ID[%d])", parentJournal.getId()); + } + if (!parentJournal.getJournalizedType().equals(inputVo.getJournalized_type())) { + throw new ServiceException("当前父级评论与当前评论类型不相同"); + } + if (!parentJournal.getJournalizedId().equals(inputVo.getJournalized_id())) { + throw new ServiceException("当前父级评论与当前评论对象不相同"); + } + } + + // 如果有回复评论,校验回复评论 + if (inputVo.getReply_id() != null) { + if (inputVo.getParent_id() == null) { + throw new ServiceException("一级评论不能进行回复操作"); + } + ZoneJournals replyJournal = selectZoneJournalsById(inputVo.getReply_id()); + if (!replyJournal.getJournalizedType().equals(inputVo.getJournalized_type())) { + throw new ServiceException("当前回复评论与当前评论类型不相同"); + } + if (!replyJournal.getJournalizedId().equals(inputVo.getJournalized_id())) { + throw new ServiceException("当前回复评论与当前评论对象不相同"); + } + } + + // 校验附件列表 + checkAttachments(inputVo.getAttachment_identifiers()); + + // 构建评论对象 + ZoneJournals newJournal = new ZoneJournals(); + newJournal.setJournalizedId(inputVo.getJournalized_id()); + newJournal.setJournalizedType(inputVo.getJournalized_type()); + newJournal.setParentId(inputVo.getParent_id()); + newJournal.setReplyId(inputVo.getReply_id()); + newJournal.setNotes(inputVo.getNotes()); + newJournal.setAttachmentIdentifiers(inputVo.getAttachment_identifiers()); + + // 处理@用户列表 + if (inputVo.getReceivers_login() != null && inputVo.getReceivers_login().length > 0) { + try { + newJournal.setReceiversLogin(objectMapper.writeValueAsString(inputVo.getReceivers_login())); + } catch (Exception e) { + throw new ServiceException("@用户列表格式错误"); + } + } + + // 获取当前用户ID + Long userId = SecurityUtils.getUserId(); + if (userId == null || userId == 0) { + throw new ServiceException("无法获取当前用户信息"); + } + newJournal.setUserId(userId); + newJournal.setCreateBy(SecurityUtils.getUsername()); + newJournal.setCreatedOn(DateUtils.getNowDate()); + + // 插入评论 + zoneJournalsMapper.insertZoneJournals(newJournal); + + // 如果是子评论,更新父评论的子评论数量 + if (inputVo.getParent_id() != null) { + zoneJournalsMapper.updateCommentsCountById(inputVo.getParent_id()); + } + + return newJournal.getId(); + } + + /** + * 修改评论 + * + * @param updateVo 评论更新信息 + * @return 结果 + */ + @Override + public int updateJournals(ZoneJournalsUpdateVo updateVo) { + // 查询原评论并校验 + ZoneJournals oldJournal = selectZoneJournalsById(updateVo.getId()); + + // 校验是否为评论创建者 + Long userId = SecurityUtils.getUserId(); + if (!oldJournal.getUserId().equals(userId)) { + throw new ServiceException("只能修改自己的评论"); + } + + // 校验附件列表 + checkAttachments(updateVo.getAttachment_identifiers()); + + // 构建更新对象 + ZoneJournals updateJournal = new ZoneJournals(); + updateJournal.setId(updateVo.getId()); + updateJournal.setNotes(updateVo.getNotes()); + updateJournal.setAttachmentIdentifiers(updateVo.getAttachment_identifiers()); + + // 处理@用户列表 + if (updateVo.getReceivers_login() != null && updateVo.getReceivers_login().length > 0) { + try { + updateJournal.setReceiversLogin(objectMapper.writeValueAsString(updateVo.getReceivers_login())); + } catch (Exception e) { + throw new ServiceException("@用户列表格式错误"); + } + } + + updateJournal.setUpdateBy(SecurityUtils.getUsername()); + updateJournal.setUpdatedOn(DateUtils.getNowDate()); + + return zoneJournalsMapper.updateZoneJournals(updateJournal); + } + + /** + * 删除评论(级联删除子评论) + * + * @param id 评论主键 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteJournalsById(Long id) { + ZoneJournals zoneJournals = selectZoneJournalsById(id); + + // 校验是否为评论创建者 + Long userId = SecurityUtils.getUserId(); + if (!zoneJournals.getUserId().equals(userId)) { + throw new ServiceException("只能删除自己的评论"); + } + + // 如果是父级评论,先删除所有子评论 + if (zoneJournals.getParentId() == null) { + List childrenJournalsList = zoneJournalsMapper.selectChildrenZoneJournalsList(zoneJournals.getId()); + for (ZoneJournals childrenJournal : childrenJournalsList) { + zoneJournalsMapper.deleteZoneJournalsById(childrenJournal.getId()); + } + } + + return zoneJournalsMapper.deleteZoneJournalsById(id); + } + + /** + * 批量删除评论 + * + * @param ids 需要删除的评论主键集合 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteJournalsByIds(Long[] ids) { + int success = 0; + for (Long id : ids) { + success += deleteJournalsById(id); + } + return success; + } + + /** + * 根据类型和评论对象Id删除所有评论 + * + * @param type 类型 + * @param journalId 评论对象Id + */ + @Override + public void deleteJournalsByTypeAndJournalId(String type, Long journalId) { + zoneJournalsMapper.deleteZoneJournalsByTypeAndJournalId(type, journalId); + } + + /** + * 批量更新关联ID + * + * @param type 类型 + * @param oldJournalId 旧评论对象Id + * @param journalId 新评论对象Id + */ + @Override + public void updateJournalsIdByTypeAndOldId(String type, Long oldJournalId, Long journalId) { + zoneJournalsMapper.updateJournalsIdByTypeAndOldId(type, oldJournalId, journalId); + } + + /** + * 根据类型校验评论对象是否存在 + * + * @param zoneJournals 评论对象 + */ + private void checkByType(ZoneJournals zoneJournals) { + if (zoneJournals.getJournalizedType().equals(ZoneJournals.JOURNAL_TYPE_ZONE_RESOURCE)) {// 校验资源是否存在 + zoneResourceService.selectZoneResourceById(zoneJournals.getJournalizedId()); + } else { + throw new ServiceException("当前不支持该类型评论(评论类型[%s])", zoneJournals.getJournalizedType()); + } + } + + /** + * 校验附件列表 + * + * @param attachmentIdentifiers 附件标识列表 + */ + private void checkAttachments(String attachmentIdentifiers) { + if (StringUtils.isEmpty(attachmentIdentifiers)) { + return; + } + try { + List fileInfos = FeignUtils.getReturnData( + remoteFileService.getFileListByIdentifier(attachmentIdentifiers) + ); + if (CollectionUtils.isEmpty(fileInfos)) { + throw new ServiceException("附件不存在或已被删除"); + } + } catch (Exception e) { + throw new ServiceException("附件校验失败:%s", e.getMessage()); + } + } + + /** + * 转换为父评论VO + * + * @param zoneJournals 评论实体 + * @return 父评论VO + */ + private ZoneJournalsParentDataVo toParentDataVo(ZoneJournals zoneJournals) { + ZoneJournalsParentDataVo parentVo = new ZoneJournalsParentDataVo(); + parentVo.setId(zoneJournals.getId()); + parentVo.setNotes(zoneJournals.getNotes()); + parentVo.setCreated_on(zoneJournals.getCreatedOn()); + parentVo.setUpdated_on(zoneJournals.getUpdatedOn()); + + // 获取用户信息 + SimpleSysUser user = zoneCommonService.getSimpleSysUserByUserId(zoneJournals.getUserId()); + parentVo.setUser(user); + + // 获取附件列表 + parentVo.setAttachments(getAttachments(zoneJournals.getAttachmentIdentifiers())); + + // 获取子评论列表 + List childrenJournalsList = zoneJournalsMapper.selectChildrenZoneJournalsList(zoneJournals.getId()); + List childrenVoList = childrenJournalsList.stream() + .map(this::toChildrenDataVo) + .collect(Collectors.toList()); + parentVo.setChildren_journals(childrenVoList); + parentVo.setComments_count(childrenVoList.size()); + + return parentVo; + } + + /** + * 转换为子评论VO + * + * @param zoneJournals 评论实体 + * @return 子评论VO + */ + private ZoneJournalsChildrenDataVo toChildrenDataVo(ZoneJournals zoneJournals) { + ZoneJournalsChildrenDataVo childrenVo = new ZoneJournalsChildrenDataVo(); + childrenVo.setId(zoneJournals.getId()); + childrenVo.setNotes(zoneJournals.getNotes()); + childrenVo.setCreated_on(zoneJournals.getCreatedOn()); + childrenVo.setUpdated_on(zoneJournals.getUpdatedOn()); + + // 获取用户信息 + SimpleSysUser user = zoneCommonService.getSimpleSysUserByUserId(zoneJournals.getUserId()); + childrenVo.setUser(user); + + // 获取附件列表 + childrenVo.setAttachments(getAttachments(zoneJournals.getAttachmentIdentifiers())); + + // 获取回复用户信息 + if (zoneJournals.getReplyId() != null) { + ZoneJournals replyJournal = zoneJournalsMapper.selectZoneJournalsById(zoneJournals.getReplyId()); + if (replyJournal != null) { + SimpleSysUser replyUser = zoneCommonService.getSimpleSysUserByUserId(replyJournal.getUserId()); + childrenVo.setReply_user(replyUser); + } + } + + return childrenVo; + } + + /** + * 获取附件列表 + * + * @param attachmentIdentifiers 附件标识列表 + * @return 附件列表 + */ + private List getAttachments(String attachmentIdentifiers) { + if (StringUtils.isEmpty(attachmentIdentifiers)) { + return new ArrayList<>(); + } + try { + List fileInfos = FeignUtils.getReturnData( + remoteFileService.getFileListByIdentifier(attachmentIdentifiers) + ); + return CollectionUtils.isEmpty(fileInfos) ? new ArrayList<>() : fileInfos.stream().map(SysFileInfo::toSimpleGitlinkFileInfo).collect(Collectors.toList()); + } catch (Exception e) { + return new ArrayList<>(); + } + } +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/member/domain/vo/ZoneMemberInputVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/member/domain/vo/ZoneMemberInputVo.java index 2185c07de..d3946f97a 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/member/domain/vo/ZoneMemberInputVo.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/member/domain/vo/ZoneMemberInputVo.java @@ -1,5 +1,6 @@ package com.microservices.zone.member.domain.vo; +import com.microservices.system.api.domain.SysUser; import com.microservices.zone.member.domain.ZoneMember; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/project/controller/ZoneProjectController.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/project/controller/ZoneProjectController.java index f3af0daa7..0d57374b8 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/project/controller/ZoneProjectController.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/project/controller/ZoneProjectController.java @@ -8,6 +8,7 @@ import com.microservices.common.core.web.page.GenericsTableDataInfo; import com.microservices.common.log.annotation.Log; import com.microservices.common.log.enums.BusinessType; import com.microservices.common.security.annotation.RequiresPermissions; +import com.microservices.common.security.utils.SecurityUtils; import com.microservices.zone.project.domain.vo.*; import com.microservices.zone.project.service.IZoneProjectService; import io.swagger.annotations.Api; @@ -39,6 +40,9 @@ public class ZoneProjectController extends BaseController { @GetMapping("/list") @ApiOperation("查询项目聚合列表") public GenericsTableDataInfo list(ZoneProjectSearchVo zoneProjectSearchVo) { + if (Boolean.TRUE.equals(zoneProjectSearchVo.getIsMine())) { + zoneProjectSearchVo.setCreateBy(SecurityUtils.getUsername()); + } return zoneProjectService.selectZoneProjectList(zoneProjectSearchVo, false); } diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/project/domain/vo/UserProjectStatisticsVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/project/domain/vo/UserProjectStatisticsVo.java new file mode 100644 index 000000000..5fb75815f --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/project/domain/vo/UserProjectStatisticsVo.java @@ -0,0 +1,22 @@ +package com.microservices.zone.project.domain.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 用户项目统计信息VO + */ +@Data +@ApiModel("项目统计信息") +public class UserProjectStatisticsVo { + + @ApiModelProperty(value = "点赞总数") + private Integer likeCount; + + @ApiModelProperty(value = "已发布总数") + private Integer publishedCount; + + @ApiModelProperty(value = "待审核总数") + private Integer pendingCount; +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/project/domain/vo/ZoneProjectSearchVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/project/domain/vo/ZoneProjectSearchVo.java index 3365824ee..41e4ffb7f 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/project/domain/vo/ZoneProjectSearchVo.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/project/domain/vo/ZoneProjectSearchVo.java @@ -20,4 +20,9 @@ public class ZoneProjectSearchVo extends ZoneProjectOpenSearchVo { private String auditStatus; @ApiModelProperty(value = "排除未通过审核项目", hidden = true) private Boolean excludesFailure; + @ApiModelProperty(value = "是否为我的项目", hidden = true) + private Boolean isMyProject; + + @ApiModelProperty(value = "是否只查询我的") + private Boolean isMine; } diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/project/service/IZoneProjectService.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/project/service/IZoneProjectService.java index 40e1fe6ba..fe7034d67 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/project/service/IZoneProjectService.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/project/service/IZoneProjectService.java @@ -122,6 +122,15 @@ public interface IZoneProjectService { */ GenericsTableDataInfo selectMyZoneProjectList(ZoneProjectSearchVo zoneProjectSearchVo); + /** + * 查询用户项目统计信息 + * + * @param zoneId + * @param username 创建者用户名 + * @return 统计信息 + */ + UserProjectStatisticsVo getUserProjectStatistics(Long zoneId, String username); + /** * 批量删除我的项目(校验创建者) * diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/project/service/impl/ZoneProjectServiceImpl.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/project/service/impl/ZoneProjectServiceImpl.java index a76148b17..60572a574 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/project/service/impl/ZoneProjectServiceImpl.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/project/service/impl/ZoneProjectServiceImpl.java @@ -698,6 +698,70 @@ public class ZoneProjectServiceImpl implements IZoneProjectService { return PageUtils.toListPage(zoneProjectList, this::toZoneProjectDataVoList); } + @Override + public UserProjectStatisticsVo getUserProjectStatistics(Long zoneId, String username) { + UserProjectStatisticsVo statistics = new UserProjectStatisticsVo(); + + // 1. 尝试从缓存获取点赞总数 + Integer cachedLikeCount = redisService.getCacheObject(CacheConstants.getProjectStatisticsLikeCount(zoneId, username)); + if (cachedLikeCount != null) { + statistics.setLikeCount(cachedLikeCount); + } else { + // 缓存未命中,查询并计算点赞总数 + ZoneProjectSearchVo query = new ZoneProjectSearchVo(); + query.setCreateBy(username); + query.setZoneId(zoneId); + List allProjects = zoneProjectMapper.selectZoneProjectList(query); + + List gitLinkProjectIdList = allProjects.stream() + .map(x -> String.valueOf(x.getGitlinkProjectId())) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + int totalLikeCount = 0; + if (!gitLinkProjectIdList.isEmpty()) { + try { + List gitLinkProjectList = zoneAsyncService.getGitlinkProjectsDetail(gitLinkProjectIdList); + Map gitLinkProjectMap = gitLinkProjectList.stream() + .collect(Collectors.toMap(x -> x.getLong("id"), x -> x, (a, b) -> a)); + for (ZoneProject project : allProjects) { + if (project.getGitlinkProjectId() != null) { + JSONObject gitLinkProject = gitLinkProjectMap.get(project.getGitlinkProjectId()); + if (gitLinkProject != null) { + Integer praisesCount = gitLinkProject.getInteger("praises_count"); + if (praisesCount != null) { + totalLikeCount += praisesCount; + } + } + } + } + } catch (Exception e) { + logger.error("批量获取用户{}的GitLink项目点赞数失败: {}", username, e.getMessage()); + } + } + statistics.setLikeCount(totalLikeCount); + redisService.setCacheObject(CacheConstants.getProjectStatisticsLikeCount(zoneId, username), totalLikeCount, 1L, java.util.concurrent.TimeUnit.HOURS); + } + + // 2. 统计已发布数量 + ZoneProjectSearchVo publishedQuery = new ZoneProjectSearchVo(); + publishedQuery.setCreateBy(username); + publishedQuery.setZoneId(zoneId); + publishedQuery.setAuditStatus(ZoneConstants.PROJECT_AUDIT_PASS); + Integer publishedCount = zoneProjectMapper.selectZoneProjectListCount(publishedQuery); + statistics.setPublishedCount(publishedCount); + + // 3. 统计待审核数量 + ZoneProjectSearchVo pendingQuery = new ZoneProjectSearchVo(); + pendingQuery.setCreateBy(username); + pendingQuery.setZoneId(zoneId); + pendingQuery.setAuditStatus(ZoneConstants.PROJECT_NOT_AUDIT); + Integer pendingCount = zoneProjectMapper.selectZoneProjectListCount(pendingQuery); + statistics.setPendingCount(pendingCount); + + return statistics; + } + @Override public int deleteMyZoneProjectByIds(Long[] ids) { for (Long id : ids) { diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/controller/AiResourceController.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/controller/AiResourceController.java new file mode 100644 index 000000000..60ee5052a --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/controller/AiResourceController.java @@ -0,0 +1,115 @@ +package com.microservices.zone.resource.controller; + +import com.microservices.common.core.web.controller.BaseController; +import com.microservices.common.core.web.domain.AjaxResult; +import com.microservices.common.core.web.domain.GenericsAjaxResult; +import com.microservices.common.core.web.page.GenericsTableDataInfo; +import com.microservices.common.log.annotation.Log; +import com.microservices.common.log.enums.BusinessType; +import com.microservices.common.security.annotation.RequiresPermissions; +import com.microservices.zone.resource.domain.ZoneResource; +import com.microservices.zone.resource.domain.vo.*; +import com.microservices.zone.resource.service.IAiResourceParseService; +import com.microservices.zone.resource.service.IZoneResourceService; +import io.swagger.annotations.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +/** + * AI资源管理Controller + */ +@RestController +@RequestMapping("/ai-resource") +@Api(tags = "AI资源管理接口") +public class AiResourceController extends BaseController { + + @Autowired + private IZoneResourceService zoneResourceService; + @Autowired + private IAiResourceParseService aiResourceParseService; + + /** + * 上传ZIP并解析 + */ + @RequiresPermissions("resource:ai:skill:add") + @PostMapping("/skill/parse") + @ApiOperation("上传ZIP并解析Skill包") + @ApiImplicitParams({ + @ApiImplicitParam(name = "file", value = "文件实体", paramType = "form", dataType = "_file"), + }) + public GenericsAjaxResult parseSkillZip(@RequestPart("file") MultipartFile file) { + return genericsSuccess(aiResourceParseService.parseSkillZip(file)); + } + + /** + * 创建Skill资源 + */ + @RequiresPermissions("resource:ai:skill:add") + @Log(title = "创建Skill资源", businessType = BusinessType.INSERT) + @PostMapping("/skill") + @ApiOperation("创建Skill资源") + public AjaxResult addSkill(@RequestBody @Validated AiSkillInputVo inputVo) { + return success(zoneResourceService.insertAiSkillResource(inputVo)); + } + + /** + * 修改Skill资源 + */ + @RequiresPermissions("resource:ai:skill:edit") + @Log(title = "修改Skill资源", businessType = BusinessType.UPDATE) + @PutMapping("/skill") + @ApiOperation("修改Skill资源") + public AjaxResult editSkill(@RequestBody @Validated AiSkillUpdateVo updateVo) { + return success(zoneResourceService.updateAiSkillResource(updateVo)); + } + + // ==================== Model 资源接口 ==================== + + /** + * 创建Model资源 + */ + @RequiresPermissions("resource:ai:model:add") + @Log(title = "创建Model资源", businessType = BusinessType.INSERT) + @PostMapping("/model") + @ApiOperation("创建Model资源") + public AjaxResult addModel(@RequestBody @Validated AiModelInputVo inputVo) { + return success(zoneResourceService.insertAiModelResource(inputVo)); + } + + /** + * 修改Model资源 + */ + @RequiresPermissions("resource:ai:model:edit") + @Log(title = "修改Model资源", businessType = BusinessType.UPDATE) + @PutMapping("/model") + @ApiOperation("修改Model资源") + public AjaxResult editModel(@RequestBody @Validated AiModelUpdateVo updateVo) { + return success(zoneResourceService.updateAiModelResource(updateVo)); + } + + // ==================== Dataset 资源接口 ==================== + + /** + * 创建Dataset资源 + */ + @RequiresPermissions("resource:ai:dataset:add") + @Log(title = "创建Dataset资源", businessType = BusinessType.INSERT) + @PostMapping("/dataset") + @ApiOperation("创建Dataset资源") + public AjaxResult addDataset(@RequestBody @Validated AiDatasetInputVo inputVo) { + return success(zoneResourceService.insertAiDatasetResource(inputVo)); + } + + /** + * 修改Dataset资源 + */ + @RequiresPermissions("resource:ai:dataset:edit") + @Log(title = "修改Dataset资源", businessType = BusinessType.UPDATE) + @PutMapping("/dataset") + @ApiOperation("修改Dataset资源") + public AjaxResult editDataset(@RequestBody @Validated AiDatasetUpdateVo updateVo) { + return success(zoneResourceService.updateAiDatasetResource(updateVo)); + } +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/controller/ZoneResourceController.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/controller/ZoneResourceController.java index 6c8cf8a03..9fdc64bc6 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/controller/ZoneResourceController.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/controller/ZoneResourceController.java @@ -12,6 +12,7 @@ import com.microservices.zone.resource.domain.vo.*; import com.microservices.zone.resource.service.IZoneResourceService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -106,4 +107,17 @@ public class ZoneResourceController extends BaseController { public AjaxResult remove(@PathVariable Long[] ids) { return toAjax(zoneResourceService.deleteZoneResourceByIds(ids)); } + + /** + * 下架资源 + */ +// @RequiresPermissions("ai:resource:audit") + @Log(title = "下架资源", businessType = BusinessType.UPDATE) + @PutMapping("/{id}/takeDown") + @ApiOperation("下架资源") + public AjaxResult takeDownResource( + @ApiParam(value = "资源ID", required = true) @PathVariable("id") Long id, + @RequestParam(value = "reason", required = false) String reason) { + return toAjax(zoneResourceService.takeDownAiResource(id, reason)); + } } diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/AiResourceComment.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/AiResourceComment.java new file mode 100644 index 000000000..a659c5114 --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/AiResourceComment.java @@ -0,0 +1,34 @@ +package com.microservices.zone.resource.domain; + +import lombok.Data; + +import java.util.Date; + +/** + * AI资源评论实体 + */ +@Data +public class AiResourceComment { + + private Long id; + + private Long resourceId; + + private Long userId; + + private String content; + + private Long parentId; + + private Long replyId; + + private String attachmentIdentifiers; + + private Integer likeCount; + + private String delFlag; + + private Date createTime; + + private Date updateTime; +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/ZoneResource.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/ZoneResource.java index 0afdbe0e5..99867d885 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/ZoneResource.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/ZoneResource.java @@ -1,9 +1,11 @@ package com.microservices.zone.resource.domain; +import com.alibaba.fastjson2.JSONObject; import com.microservices.common.core.annotation.Excel; import com.microservices.common.core.utils.bean.BeanUtils; import com.microservices.zone.detail.domain.ZoneSort; import com.microservices.zone.resource.domain.vo.ZoneResourceDataVo; +import com.microservices.zone.resource.utils.ResourceExtendDataUtils; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @@ -64,7 +66,7 @@ public class ZoneResource extends ZoneSort { @ApiModelProperty(value = "资源简介") private String summary; - @ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过)") + @ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过 3:已下架 4:草稿)") private String auditStatus; /** * 是否首页展示(0代表不展示 1代表展示) @@ -93,8 +95,23 @@ public class ZoneResource extends ZoneSort { @ApiModelProperty(value = "资源标签") private String keywords; - @ApiModelProperty(value = "拓展数据") - private String extendData; + @ApiModelProperty(value = "拓展数据(JSON格式)") + private JSONObject extendData; + + @ApiModelProperty(value = "资源分类:SKILL/MODEL/DATASET,NULL代表原资源") + private String resourceCategory; + + /** + * 浏览次数 + */ + @ApiModelProperty(value = "浏览次数") + private Integer viewCount; + + /** + * 点赞次数 + */ + @ApiModelProperty(value = "点赞次数") + private Integer likeCount; /** * 已获得积分(通过资源下载/浏览等途径获得) @@ -111,4 +128,34 @@ public class ZoneResource extends ZoneSort { return target; } + + // ==================== extendData 便捷方法 ==================== + + /** + * 获取 SKILL.md 内容(从 extendData 中提取) + */ + public String getSkillContent() { + return ResourceExtendDataUtils.extractSkillContent(this.extendData); + } + + /** + * 获取目录树 JSON(从 extendData 中提取) + */ + public JSONObject getSkillFileTree() { + return ResourceExtendDataUtils.extractSkillFileTree(this.extendData); + } + + /** + * 设置 SKILL.md 内容(更新 extendData) + */ + public void setSkillContent(String skillContent) { + this.extendData = ResourceExtendDataUtils.updateSkillContent(this.extendData, skillContent); + } + + /** + * 设置目录树 JSON(更新 extendData) + */ + public void setSkillFileTree(JSONObject fileTreeJson) { + this.extendData = ResourceExtendDataUtils.updateSkillFileTree(this.extendData, fileTreeJson); + } } diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/ZoneResourceDomain.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/ZoneResourceDomain.java index 6ce708df8..ed8b6daae 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/ZoneResourceDomain.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/ZoneResourceDomain.java @@ -2,6 +2,7 @@ package com.microservices.zone.resource.domain; import com.microservices.common.core.annotation.Excel; import com.microservices.zone.detail.domain.ZoneSort; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** @@ -23,4 +24,7 @@ public class ZoneResourceDomain extends ZoneSort { */ @Excel(name = "资源领域简介") private String introduction; + + @ApiModelProperty(value = "领域分类:SKILL/MODEL/DATASET,NULL代表原资源领域") + private String domainCategory; } diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/ZoneResourcesLike.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/ZoneResourcesLike.java new file mode 100644 index 000000000..70c82cead --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/ZoneResourcesLike.java @@ -0,0 +1,20 @@ +package com.microservices.zone.resource.domain; + +import lombok.Data; + +import java.util.Date; + +/** + * 资源点赞实体 + */ +@Data +public class ZoneResourcesLike { + + private Long id; + + private Long resourceId; + + private Long userId; + + private Date createTime; +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiDatasetInputVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiDatasetInputVo.java new file mode 100644 index 000000000..a00fd8cb2 --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiDatasetInputVo.java @@ -0,0 +1,56 @@ +package com.microservices.zone.resource.domain.vo; + +import com.microservices.zone.resource.enums.AiResourceCategory; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +/** + * 数据集提报输入VO + */ +@Data +@ApiModel("数据集提报输入") +public class AiDatasetInputVo { + + @ApiModelProperty(value = "数据集名称", required = true) + @NotBlank(message = "数据集名称不能为空") + @Size(max = 100, message = "数据集名称不能超过100个字符") + private String name; + + @ApiModelProperty(value = "资源领域Id", required = true) + @NotNull(message = "资源领域Id不能为空") + private Long domainId; + + @ApiModelProperty(value = "数据集简介") + @Size(max = 500, message = "数据集简介不能超过500个字符") + private String summary; + + @ApiModelProperty(value = "资源标签,逗号分隔") + private String keywords; + + @ApiModelProperty(value = "数据集文件Id", required = true) + @NotNull(message = "数据集文件Id不能为空") + private Long fileId; + + @ApiModelProperty(value = "所属专区", required = true) + @NotNull(message = "所属专区不能为空") + private Long zoneId; + + + public ZoneResourceInputVo toZoneResourceInputVo() { + // 构建 ZoneResourceInputVo + ZoneResourceInputVo zoneResourceInputVo = new ZoneResourceInputVo(); + zoneResourceInputVo.setName(this.getName()); + zoneResourceInputVo.setSummary(this.getSummary()); + zoneResourceInputVo.setKeywords(this.getKeywords()); + zoneResourceInputVo.setDomainId(this.getDomainId()); + zoneResourceInputVo.setZoneId(this.getZoneId()); + zoneResourceInputVo.setFileIds(String.valueOf(this.getFileId())); + zoneResourceInputVo.setResourceCategory(AiResourceCategory.DATASET.getCode()); + return zoneResourceInputVo; + } +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiDatasetUpdateVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiDatasetUpdateVo.java new file mode 100644 index 000000000..736d27e30 --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiDatasetUpdateVo.java @@ -0,0 +1,63 @@ +package com.microservices.zone.resource.domain.vo; + +import com.alibaba.fastjson2.JSONObject; +import com.microservices.zone.resource.domain.ZoneResource; +import com.microservices.zone.resource.utils.ResourceExtendDataUtils; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +/** + * 数据集修改输入VO + */ +@Data +@ApiModel("数据集修改输入") +public class AiDatasetUpdateVo { + + @ApiModelProperty(value = "资源Id", required = true) + @NotNull(message = "资源Id不能为空") + private Long id; + + @ApiModelProperty(value = "数据集名称") + @Size(max = 100, message = "数据集名称不能超过100个字符") + private String name; + + @ApiModelProperty(value = "数据集简介") + @Size(max = 500, message = "数据集简介不能超过500个字符") + private String summary; + + @ApiModelProperty(value = "资源标签,逗号分隔") + private String keywords; + + @ApiModelProperty(value = "数据集文件Id") + private Long fileId; + + @ApiModelProperty(value = "资源领域Id") + private Long domainId; + + @ApiModelProperty(value = "README.md内容") + private String readmeContent; + + public ZoneResourceUpdateVo toZoneResourceUpdateVo(ZoneResource existing){ + ZoneResourceUpdateVo zoneResourceUpdateVo = new ZoneResourceUpdateVo(); + zoneResourceUpdateVo.setId(this.getId()); + zoneResourceUpdateVo.setName(this.getName()); + zoneResourceUpdateVo.setSummary(this.getSummary()); + zoneResourceUpdateVo.setKeywords(this.getKeywords()); + zoneResourceUpdateVo.setDomainId(this.getDomainId()); + + if (readmeContent != null) { + JSONObject extendData = existing.getExtendData(); + if (extendData == null) { + extendData = new JSONObject(); + } + extendData = ResourceExtendDataUtils.updateModelOrDatasetReadmeContent(existing.getResourceCategory(), extendData, readmeContent); + zoneResourceUpdateVo.setExtendData(extendData.toJSONString()); + } + + return zoneResourceUpdateVo; + } +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiModelInputVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiModelInputVo.java new file mode 100644 index 000000000..41cdff86b --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiModelInputVo.java @@ -0,0 +1,55 @@ +package com.microservices.zone.resource.domain.vo; + +import com.microservices.zone.resource.enums.AiResourceCategory; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +/** + * 模型提报输入VO + */ +@Data +@ApiModel("模型提报输入") +public class AiModelInputVo { + + @ApiModelProperty(value = "模型名称", required = true) + @NotBlank(message = "模型名称不能为空") + @Size(max = 100, message = "模型名称不能超过100个字符") + private String name; + + @ApiModelProperty(value = "资源领域Id", required = true) + @NotNull(message = "资源领域Id不能为空") + private Long domainId; + + @ApiModelProperty(value = "模型简介") + @Size(max = 500, message = "模型简介不能超过500个字符") + private String summary; + + @ApiModelProperty(value = "资源标签,逗号分隔") + private String keywords; + + @ApiModelProperty(value = "模型文件Id", required = true) + @NotNull(message = "模型文件Id不能为空") + private Long fileId; + + @ApiModelProperty(value = "所属专区", required = true) + @NotNull(message = "所属专区不能为空") + private Long zoneId; + + public ZoneResourceInputVo toZoneResourceInputVo() { + // 构建 ZoneResourceInputVo + ZoneResourceInputVo zoneResourceInputVo = new ZoneResourceInputVo(); + zoneResourceInputVo.setName(this.getName()); + zoneResourceInputVo.setSummary(this.getSummary()); + zoneResourceInputVo.setKeywords(this.getKeywords()); + zoneResourceInputVo.setDomainId(this.getDomainId()); + zoneResourceInputVo.setZoneId(this.getZoneId()); + zoneResourceInputVo.setFileIds(String.valueOf(this.getFileId())); + zoneResourceInputVo.setResourceCategory(AiResourceCategory.MODEL.getCode()); + return zoneResourceInputVo; + } +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiModelUpdateVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiModelUpdateVo.java new file mode 100644 index 000000000..342fe97bd --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiModelUpdateVo.java @@ -0,0 +1,63 @@ +package com.microservices.zone.resource.domain.vo; + +import com.alibaba.fastjson2.JSONObject; +import com.microservices.zone.resource.domain.ZoneResource; +import com.microservices.zone.resource.utils.ResourceExtendDataUtils; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +/** + * 模型修改输入VO + */ +@Data +@ApiModel("模型修改输入") +public class AiModelUpdateVo { + + @ApiModelProperty(value = "资源Id", required = true) + @NotNull(message = "资源Id不能为空") + private Long id; + + @ApiModelProperty(value = "模型名称") + @Size(max = 100, message = "模型名称不能超过100个字符") + private String name; + + @ApiModelProperty(value = "模型简介") + @Size(max = 500, message = "模型简介不能超过500个字符") + private String summary; + + @ApiModelProperty(value = "资源标签,逗号分隔") + private String keywords; + + @ApiModelProperty(value = "模型文件Id") + private Long fileId; + + @ApiModelProperty(value = "资源领域Id") + private Long domainId; + + @ApiModelProperty(value = "README.md内容") + private String readmeContent; + + public ZoneResourceUpdateVo toZoneResourceUpdateVo(ZoneResource existing){ + ZoneResourceUpdateVo zoneResourceUpdateVo = new ZoneResourceUpdateVo(); + zoneResourceUpdateVo.setId(this.getId()); + zoneResourceUpdateVo.setName(this.getName()); + zoneResourceUpdateVo.setSummary(this.getSummary()); + zoneResourceUpdateVo.setKeywords(this.getKeywords()); + zoneResourceUpdateVo.setDomainId(this.getDomainId()); + + if (readmeContent != null) { + JSONObject extendData = existing.getExtendData(); + if (extendData == null) { + extendData = new JSONObject(); + } + extendData = ResourceExtendDataUtils.updateModelOrDatasetReadmeContent(existing.getResourceCategory(), extendData, readmeContent); + zoneResourceUpdateVo.setExtendData(extendData.toJSONString()); + } + + return zoneResourceUpdateVo; + } +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiSkillInputVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiSkillInputVo.java new file mode 100644 index 000000000..7d5ad9d95 --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiSkillInputVo.java @@ -0,0 +1,66 @@ +package com.microservices.zone.resource.domain.vo; + +import com.alibaba.fastjson2.JSONObject; +import com.microservices.common.core.utils.StringUtils; +import com.microservices.common.core.utils.bean.BeanUtils; +import com.microservices.zone.resource.enums.AiResourceCategory; +import com.microservices.zone.resource.utils.ResourceExtendDataUtils; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +/** + * Skill提报输入VO + */ +@Data +@ApiModel("Skill提报输入") +public class AiSkillInputVo { + + @ApiModelProperty(value = "Skill名称", required = true) + @NotBlank(message = "Skill名称不能为空") + @Size(max = 100, message = "Skill名称不能超过100个字符") + private String name; + @ApiModelProperty(value = "资源领域Id", required = true) + @NotNull + private Long domainId; + + @ApiModelProperty(value = "Skill简介") +// @Size(max = 500, message = "Skill简介不能超过500个字符") + private String summary; + + @ApiModelProperty(value = "资源标签,逗号分隔") + private String keywords; + + @ApiModelProperty(value = "Skill文件Id") + private Long fileId; + + @ApiModelProperty(value = "SKILL.md内容", required = true) + @NotBlank(message = "SKILL.md内容不能为空") + private String skillContent; + + @ApiModelProperty(value = "目录树JSON") + private String fileTreeJson; + @ApiModelProperty(value = "所属专区", required = true) + @NotNull + private Long zoneId; + + public ZoneResourceInputVo toZoneResourceInputVo() { + ZoneResourceInputVo target = new ZoneResourceInputVo(); + BeanUtils.copyProperties(this, target, "skillContent", "fileTreeJson"); + target.setFileIds(String.valueOf(fileId)); + + // 将 Skill 特有字段存入 extendData(JSONObject) + JSONObject fileTreeJsonObj = StringUtils.isEmpty(fileTreeJson) + ? null + : JSONObject.parseObject(fileTreeJson); + JSONObject extendData = ResourceExtendDataUtils.buildSkillExtendData( + skillContent, fileTreeJsonObj); + target.setExtendData(extendData.toJSONString()); + target.setResourceCategory(AiResourceCategory.SKILL.getCode()); + return target; + } +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiSkillParseResultVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiSkillParseResultVo.java new file mode 100644 index 000000000..602638f12 --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiSkillParseResultVo.java @@ -0,0 +1,29 @@ +package com.microservices.zone.resource.domain.vo; + +import com.alibaba.fastjson2.JSONObject; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * Skill ZIP解析结果VO + */ +@Data +@ApiModel("Skill ZIP解析结果") +public class AiSkillParseResultVo { + + @ApiModelProperty(value = "SKILL.md文件内容") + private String skillMdContent; + + @ApiModelProperty(value = "ZIP包目录树结构JSON") + private String fileTreeJson; + + @ApiModelProperty(value = "上传后的文件id") + private Long fileId; + + @ApiModelProperty(value = "Skill描述(从YAML front matter中提取)") + private String summary; + + @ApiModelProperty(value = "Skill名称(从YAML front matter中提取)") + private String name; +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiSkillUpdateVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiSkillUpdateVo.java new file mode 100644 index 000000000..b288a25a8 --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiSkillUpdateVo.java @@ -0,0 +1,80 @@ +package com.microservices.zone.resource.domain.vo; + +import com.alibaba.fastjson2.JSONObject; +import com.microservices.common.core.utils.StringUtils; +import com.microservices.common.core.utils.bean.BeanUtils; +import com.microservices.zone.resource.domain.ZoneResource; +import com.microservices.zone.resource.utils.ResourceExtendDataUtils; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +/** + * Skill修改输入VO + */ +@Data +@ApiModel("Skill修改输入") +public class AiSkillUpdateVo { + + @ApiModelProperty(value = "资源Id", required = true) + @NotNull(message = "资源Id不能为空") + private Long id; + + @ApiModelProperty(value = "Skill名称") + @Size(max = 100, message = "Skill名称不能超过100个字符") + private String name; + + @ApiModelProperty(value = "Skill简介") +// @Size(max = 500, message = "Skill简介不能超过500个字符") + private String summary; + + @ApiModelProperty(value = "Skill文件Id") + private Long fileId; + + @ApiModelProperty(value = "SKILL.md内容") + private String skillContent; + + @ApiModelProperty(value = "目录树JSON") + private String fileTreeJson; + @ApiModelProperty(value = "资源领域Id") + private Long domainId; + + /** + * 转换为 ZoneResource(合并 extendData) + * + * @param existing 现有资源对象,用于获取原有 extendData + * @return 合并后的 ZoneResource + */ + public ZoneResourceUpdateVo toZoneResourceUpdateVo(ZoneResource existing) { + ZoneResourceUpdateVo target = new ZoneResourceUpdateVo(); + BeanUtils.copyProperties(this, target); + if (fileId == null) { + target.setFileIds(existing.getFileIds()); + } else { + target.setFileIds(String.valueOf(fileId)); + } + + // 获取现有 extendData 或创建新的 + JSONObject extendData = existing.getExtendData(); + if (extendData == null) { + extendData = new JSONObject(); + } + + // 更新 skillContent + if (skillContent != null) { + extendData = ResourceExtendDataUtils.updateSkillContent(extendData, skillContent); + } + + // 更新 fileTree + if (fileTreeJson != null) { + JSONObject fileTreeObj = JSONObject.parseObject(fileTreeJson); + extendData = ResourceExtendDataUtils.updateSkillFileTree(extendData, fileTreeObj); + } + + target.setExtendData(extendData.toJSONString()); + return target; + } +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiUploadedResourceParseResultVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiUploadedResourceParseResultVo.java new file mode 100644 index 000000000..c093f8250 --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/AiUploadedResourceParseResultVo.java @@ -0,0 +1,22 @@ +package com.microservices.zone.resource.domain.vo; + +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; + +/** + * 已上传资源ZIP解析结果 + */ +@Data +public class AiUploadedResourceParseResultVo { + + /** + * README.md 文件内容,不存在时为 null + */ + private String readmeContent; + + /** + * 目录树 JSON 字符串 + */ + private JSONObject fileTreeJson; + private String fileSizeInfo; +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/UserResourceStatisticsVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/UserResourceStatisticsVo.java new file mode 100644 index 000000000..6501fae67 --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/UserResourceStatisticsVo.java @@ -0,0 +1,22 @@ +package com.microservices.zone.resource.domain.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 用户资源统计信息VO + */ +@Data +@ApiModel("资源统计信息") +public class UserResourceStatisticsVo { + + @ApiModelProperty(value = "点赞总数") + private Integer likeCount; + + @ApiModelProperty(value = "已发布总数") + private Integer publishedCount; + + @ApiModelProperty(value = "待审核总数") + private Integer pendingCount; +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceDataVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceDataVo.java index 06e93c045..0e7e8f433 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceDataVo.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceDataVo.java @@ -1,8 +1,11 @@ package com.microservices.zone.resource.domain.vo; +import com.alibaba.fastjson2.JSONObject; import com.fasterxml.jackson.annotation.JsonFormat; import com.microservices.system.api.domain.SimpleSysUser; +import com.microservices.system.api.domain.SysFileDownloadLogVo; import com.microservices.zone.resource.domain.ZoneResourceType; +import com.microservices.zone.resource.utils.ResourceExtendDataUtils; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @@ -64,7 +67,7 @@ public class ZoneResourceDataVo { @ApiModelProperty(value = "资源简介") private String summary; - @ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过)") + @ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过 3:已下架 4:草稿)") private String auditStatus; /** * 审核时间 @@ -73,8 +76,23 @@ public class ZoneResourceDataVo { private Date auditTime; @ApiModelProperty(value = "审核人") private String auditBy; - @ApiModelProperty(value = "拓展数据") - private String extendData; @ApiModelProperty(value = "备注") private String remark; + @ApiModelProperty(value = "拓展数据(JSON格式)") + private JSONObject extendData; + + @ApiModelProperty(value = "资源分类:SKILL/MODEL/DATASET,NULL代表原资源") + private String resourceCategory; + + @ApiModelProperty(value = "审核意见/驳回原因") + private String auditReason; + + @ApiModelProperty(value = "下载记录列表(所有附件的下载明细,含用户名、IP、时间)") + private List downloadUserSummary; + + @ApiModelProperty(value = "浏览次数") + private Integer viewCount; + + @ApiModelProperty(value = "点赞次数") + private Integer likeCount; } diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceDomainInputVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceDomainInputVo.java index fd19c9fc1..cb57a9ce4 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceDomainInputVo.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceDomainInputVo.java @@ -23,6 +23,8 @@ public class ZoneResourceDomainInputVo { @ApiModelProperty(value = "所属专区", required = true) @NotNull private Long zoneId; + @ApiModelProperty(value = "领域分类:SKILL/MODEL/DATASET,NULL代表原资源") + private String domainCategory; public ZoneResourceDomain toZoneResourceDomain() { ZoneResourceDomain target = new ZoneResourceDomain(); diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceDomainSearchVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceDomainSearchVo.java index 32879d6ae..c4ba59880 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceDomainSearchVo.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceDomainSearchVo.java @@ -5,6 +5,8 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import javax.validation.constraints.NotNull; + /** * @author otto */ @@ -13,13 +15,17 @@ import lombok.Data; public class ZoneResourceDomainSearchVo { @ApiModelProperty(value = "资源领域名称") private String name; - @ApiModelProperty(value = "所属专区") + @ApiModelProperty(value = "所属专区", required = true) + @NotNull private Long zoneId; + @ApiModelProperty(value = "领域分类:SKILL/MODEL/DATASET,NULL或者不传代表所有资源领域") + private String domainCategory; public ZoneResourceDomain toZoneResourceDomain() { ZoneResourceDomain zoneResourceDomain = new ZoneResourceDomain(); zoneResourceDomain.setZoneId(zoneId); zoneResourceDomain.setName(name); + zoneResourceDomain.setDomainCategory(domainCategory); return zoneResourceDomain; } } diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceFileVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceFileVo.java index bd201457f..decfb096f 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceFileVo.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceFileVo.java @@ -7,6 +7,8 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import java.util.List; + /** * @author otto */ @@ -17,6 +19,9 @@ public class ZoneResourceFileVo extends SysFileInfo { @ApiModelProperty(value = "资源类型列表") private ZoneResourceType zoneResourceType; + @ApiModelProperty(value = "下载用户列表") + private List downloadUserList; + public ZoneResourceFileVo(ZoneResourceType zoneResourceType, SysFileInfo sysFileInfo) { this.zoneResourceType = zoneResourceType; BeanUtils.copyProperties(sysFileInfo, this); diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceInputVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceInputVo.java index 391e2f4a3..44654a1d3 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceInputVo.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceInputVo.java @@ -1,5 +1,6 @@ package com.microservices.zone.resource.domain.vo; +import com.alibaba.fastjson2.JSONObject; import com.microservices.common.core.utils.bean.BeanUtils; import com.microservices.zone.resource.domain.ZoneResource; import io.swagger.annotations.ApiModel; @@ -19,7 +20,7 @@ public class ZoneResourceInputVo { @NotNull private String name; - @ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过)", hidden = true) + @ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过 3:已下架 4:草稿)", hidden = true) private String auditStatus = "1"; @ApiModelProperty(value = "资源简介") private String summary; @@ -34,12 +35,16 @@ public class ZoneResourceInputVo { @NotNull private Long zoneId; + @ApiModelProperty(value = "资源分类:SKILL/MODEL/DATASET,NULL代表原资源", hidden = true) + private String resourceCategory = null; + @ApiModelProperty(value = "拓展数据") private String extendData; public ZoneResource toZoneResource() { ZoneResource target = new ZoneResource(); BeanUtils.copyProperties(this, target); + target.setExtendData(JSONObject.parseObject(extendData)); return target; } } diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceOpenSearchVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceOpenSearchVo.java index a95c4f6b9..757330366 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceOpenSearchVo.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceOpenSearchVo.java @@ -53,8 +53,11 @@ public class ZoneResourceOpenSearchVo extends BaseEntity { @ApiModelProperty(value = "是否根据下载次数降序排序(与orderByColumn互斥,该属性优先级更高)") private Boolean orderByDownloadCount = false; - @ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过)") - private String auditStatus; + @ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过 3:已下架 4:草稿)",hidden = true) + private String auditStatus="1"; + + @ApiModelProperty(value = "资源分类:SKILL/MODEL/DATASET,NULL或者不传代表所有资源") + private String resourceCategory; public ZoneResource toZoneResource() { ZoneResource target = new ZoneResource(); diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceSearchVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceSearchVo.java index 69766752f..3dc8793cf 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceSearchVo.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceSearchVo.java @@ -1,5 +1,6 @@ package com.microservices.zone.resource.domain.vo; +import com.microservices.common.security.utils.SecurityUtils; import com.microservices.zone.resource.domain.ZoneResource; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -18,12 +19,21 @@ public class ZoneResourceSearchVo extends ZoneResourceOpenSearchVo { @NotNull private Long zoneId; - @ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过)") + @ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过 3:已下架 4:草稿 all:所有)") private String auditStatus; + @ApiModelProperty(value = "资源分类:SKILL/MODEL/DATASET,NULL或者不传代表所有资源") + private String resourceCategory; + + @ApiModelProperty(value = "是否只查询我的") + private Boolean isMine; + public ZoneResource toZoneResource() { ZoneResource zoneResource = super.toZoneResource(); zoneResource.setZoneId(zoneId); + if (Boolean.TRUE.equals(isMine)) { + zoneResource.setCreateBy(SecurityUtils.getUsername()); + } return zoneResource; } } diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceUpdateVo.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceUpdateVo.java index ca8785eb7..dd9343d02 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceUpdateVo.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceUpdateVo.java @@ -1,5 +1,6 @@ package com.microservices.zone.resource.domain.vo; +import com.alibaba.fastjson2.JSONObject; import com.microservices.common.core.utils.bean.BeanUtils; import com.microservices.zone.resource.domain.ZoneResource; import io.swagger.annotations.ApiModel; @@ -34,7 +35,7 @@ public class ZoneResourceUpdateVo { @ApiModelProperty(value = "是否首页展示(0代表不展示 1代表展示)") private String isHomepage; - @ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过)", hidden = true) + @ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过 3:已下架 4:草稿)", hidden = true) private String auditStatus = "1"; @ApiModelProperty(value = "备注", hidden = true) private String remark; @@ -44,6 +45,7 @@ public class ZoneResourceUpdateVo { public ZoneResource toZoneResource() { ZoneResource target = new ZoneResource(); BeanUtils.copyProperties(this, target); + target.setExtendData(JSONObject.parseObject(extendData)); return target; } } diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/enums/AiResourceCategory.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/enums/AiResourceCategory.java new file mode 100644 index 000000000..38696b4fa --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/enums/AiResourceCategory.java @@ -0,0 +1,31 @@ +package com.microservices.zone.resource.enums; + +/** + * AI资源类型枚举 + */ +public enum AiResourceCategory { + + SKILL("SKILL", "Skill技能"), + MODEL("MODEL", "模型"), + DATASET("DATASET", "数据集"); + + private final String code; + private final String desc; + + AiResourceCategory(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + public static boolean isAiResourceCategory(String code) { + return SKILL.getCode().equals(code) || MODEL.getCode().equals(code) || DATASET.getCode().equals(code); + } +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/handler/JsonObjectTypeHandler.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/handler/JsonObjectTypeHandler.java new file mode 100644 index 000000000..6b3cbf61c --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/handler/JsonObjectTypeHandler.java @@ -0,0 +1,55 @@ +package com.microservices.zone.resource.handler; + +import com.alibaba.fastjson2.JSONObject; +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * JSONObject 与 String 之间的类型转换处理器 + * 用于 extendData 字段:代码中操作 JSONObject,数据库存储 TEXT + */ +@MappedTypes(JSONObject.class) +@MappedJdbcTypes(JdbcType.VARCHAR) +public class JsonObjectTypeHandler extends BaseTypeHandler { + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, JSONObject parameter, JdbcType jdbcType) throws SQLException { + ps.setString(i, parameter.toJSONString()); + } + + @Override + public JSONObject getNullableResult(ResultSet rs, String columnName) throws SQLException { + String value = rs.getString(columnName); + return parseJSONObject(value); + } + + @Override + public JSONObject getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + String value = rs.getString(columnIndex); + return parseJSONObject(value); + } + + @Override + public JSONObject getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { + String value = cs.getString(columnIndex); + return parseJSONObject(value); + } + + private JSONObject parseJSONObject(String value) { + if (value == null || value.trim().isEmpty()) { + return null; + } + try { + return JSONObject.parseObject(value); + } catch (Exception e) { + return null; + } + } +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/mapper/ZoneResourceDomainMapper.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/mapper/ZoneResourceDomainMapper.java index c58419d11..2e22252a5 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/mapper/ZoneResourceDomainMapper.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/mapper/ZoneResourceDomainMapper.java @@ -67,9 +67,10 @@ public interface ZoneResourceDomainMapper { * * @param name * @param zoneId + * @param domainCategory * @return */ - ZoneResourceDomain selectZoneResourceDomainByNameAndZoneId(@Param("name") String name, @Param("zoneId") Long zoneId); + ZoneResourceDomain selectZoneResourceDomainByNameAndZoneId(@Param("name") String name, @Param("zoneId") Long zoneId, @Param("domainCategory") String domainCategory); /** * 根据专区Id删除资源领域 diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/mapper/ZoneResourceMapper.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/mapper/ZoneResourceMapper.java index 79de7f7b8..e638dd53f 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/mapper/ZoneResourceMapper.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/mapper/ZoneResourceMapper.java @@ -70,9 +70,10 @@ public interface ZoneResourceMapper { * * @param name * @param zoneId + * @param resourceCategory * @return */ - ZoneResource selectZoneResourceByNameAndZoneId(@Param("name") String name, @Param("zoneId") Long zoneId); + ZoneResource selectZoneResourceByNameAndZoneId(@Param("name") String name, @Param("zoneId") Long zoneId, @Param("resourceCategory") String resourceCategory); /** * 获取资源数量 @@ -120,6 +121,41 @@ public interface ZoneResourceMapper { @Param("offset") int offset, @Param("pageSize") int pageSize); + /** + * 通过名称查询Skill资源(resource_category='SKILL') + * + * @param name Skill名称 + * @return 资源 + */ + ZoneResource selectSkillResourceByName(@Param("name") String name); + + /** + * 查询已发布的Skill推荐列表 + * + * @param resourceCategory 资源分类 + * @param auditStatus 审核状态 + * @param limit 数量限制 + * @return 资源列表 + */ + List selectSkillRecommendations(@Param("resourceCategory") String resourceCategory, + @Param("auditStatus") String auditStatus, + @Param("limit") int limit); + + /** + * 增加浏览次数 + * + * @param id 资源ID + */ + int incrementViewCount(@Param("id") Long id); + + /** + * 增加点赞次数 + * + * @param id 资源ID + * @param delta 变化量(正数加、负数减) + */ + int updateLikeCount(@Param("id") Long id, @Param("delta") int delta); + /** * 通过文件ID查询资源(fileIds字段中包含该fileId的资源) * diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/mapper/ZoneResourcesLikeMapper.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/mapper/ZoneResourcesLikeMapper.java new file mode 100644 index 000000000..b7964de8a --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/mapper/ZoneResourcesLikeMapper.java @@ -0,0 +1,42 @@ +package com.microservices.zone.resource.mapper; + +import com.microservices.zone.resource.domain.ZoneResourcesLike; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 资源点赞Mapper接口 + */ +@Mapper +public interface ZoneResourcesLikeMapper { + + /** + * 查询用户对指定资源的点赞记录 + * + * @param resourceId 资源ID + * @param userId 用户ID + * @return 点赞记录 + */ + ZoneResourcesLike selectByResourceAndUser(@Param("resourceId") Long resourceId, @Param("userId") Long userId); + + /** + * 新增点赞记录 + * + * @param zoneResourcesLike 点赞记录 + * @return 影响行数 + */ + int insertLike(ZoneResourcesLike zoneResourcesLike); + + /** + * 删除点赞记录 + * + * @param resourceId 资源ID + * @param userId 用户ID + * @return 影响行数 + */ + int deleteLike(@Param("resourceId") Long resourceId, @Param("userId") Long userId); + + List selectByResourceIds(String[] idArr); +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/service/IAiResourceParseService.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/service/IAiResourceParseService.java new file mode 100644 index 000000000..f00bb9e77 --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/service/IAiResourceParseService.java @@ -0,0 +1,31 @@ +package com.microservices.zone.resource.service; + +import com.microservices.zone.resource.domain.vo.AiSkillParseResultVo; +import com.microservices.zone.resource.domain.vo.AiUploadedResourceParseResultVo; +import org.springframework.web.multipart.MultipartFile; + +/** + * AI资源ZIP解析服务接口 + */ +public interface IAiResourceParseService { + + /** + * 解析Skill ZIP包 + * 1. 校验ZIP内必须包含 SKILL.md + * 2. 提取 SKILL.md 内容 + * 3. 生成目录树结构 JSON + * + * @param zipFile 上传的ZIP文件 + * @return 解析结果 + */ + AiSkillParseResultVo parseSkillZip(MultipartFile zipFile); + + /** + * 解析已上传的资源ZIP包(模型/数据集) + * 基于已通过分片上传到服务器的文件,解析目录树和 README.md + * + * @param fileId 已上传文件的ID + * @return 解析结果 + */ + AiUploadedResourceParseResultVo parseUploadedZip(Long fileId); +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/service/IZoneResourceService.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/service/IZoneResourceService.java index 6ca3f0de2..dcc8395ad 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/service/IZoneResourceService.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/service/IZoneResourceService.java @@ -1,5 +1,6 @@ package com.microservices.zone.resource.service; +import com.alibaba.fastjson2.JSONObject; import com.microservices.common.core.web.page.GenericsTableDataInfo; import com.microservices.system.api.domain.SysFileInfo; import com.microservices.zone.resource.domain.ZoneResource; @@ -138,4 +139,167 @@ public interface IZoneResourceService { * @return */ Integer selectMyZoneResourceCount(ZoneResource zoneResourceSearch); + + /** + * 创建Skill资源(AI资源广场) + * + * @param inputVo Skill提报输入 + * @return 资源数据 + */ + ZoneResourceDataVo insertAiSkillResource(AiSkillInputVo inputVo); + + /** + * 修改Skill资源(AI资源广场) + * + * @param updateVo Skill修改输入 + * @return 资源数据 + */ + ZoneResourceDataVo updateAiSkillResource(AiSkillUpdateVo updateVo); + + /** + * 查询Skill资源详情(AI资源广场) + * + * @param id 资源ID + * @return 资源数据 + */ + ZoneResourceDataVo selectAiSkillResourceById(Long id); + + /** + * 查询Skill资源列表(AI资源广场) + * + * @param zoneResource 查询条件 + * @return 分页数据 + */ + GenericsTableDataInfo selectAiSkillResourceList(ZoneResource zoneResource); + + /** + * 查询AI模型资源列表 + * + * @param zoneResource 查询条件 + * @return 分页数据 + */ + GenericsTableDataInfo selectAiModelResourceList(ZoneResource zoneResource); + + /** + * 查询AI数据集资源列表 + * + * @param zoneResource 查询条件 + * @return 分页数据 + */ + GenericsTableDataInfo selectAiDatasetResourceList(ZoneResource zoneResource); + + /** + * 查询资源统计信息 + * + * @param zoneId 专区ID(isMine=false时使用) + * @param isMine 创建者(isMine=true时使用) + * @param resourceCategory 资源分类(SKILL/MODEL/DATASET/null表示所有) + * @return 统计信息 + */ + UserResourceStatisticsVo getResourceStatistics(Long zoneId, Boolean isMine, String resourceCategory); + + /** + * 下架AI资源 + * + * @param id 资源ID + * @param reason 下架原因 + * @return 影响行数 + */ + int takeDownAiResource(Long id, String reason); + + /** + * 重新提交被驳回的AI资源 + * + * @param id 资源ID + * @return 影响行数 + */ + int resubmitAiResource(Long id); + + /** + * 撤回审核中的AI资源 + * + * @param id 资源ID + * @return 影响行数 + */ + int withdrawAiResource(Long id); + + /** + * 查询Skill推荐列表 + * + * @param limit 数量限制 + * @return 资源列表 + */ + List selectSkillRecommendations(int limit); + + /** + * 资源点赞/取消点赞 + * + * @param resourceId 资源ID + * @return true-已点赞, false-已取消 + */ + boolean toggleZoneResourcesLike(Long resourceId); + + /** + * 增加AI资源浏览次数 + * + * @param id 资源ID + */ + void incrementViewCount(Long id); + + int takeUpAiResource(Long id, String reason); + + List selectZoneResourcesIsLike(String ids); + + // ==================== AI资源广场 Model 功能 ==================== + + /** + * 创建Model资源(AI资源广场) + * + * @param inputVo Model提报输入 + * @return 资源数据 + */ + ZoneResourceDataVo insertAiModelResource(AiModelInputVo inputVo); + + /** + * 修改Model资源(AI资源广场) + * + * @param updateVo Model修改输入 + * @return 资源数据 + */ + ZoneResourceDataVo updateAiModelResource(AiModelUpdateVo updateVo); + + /** + * 查询Model资源详情(AI资源广场) + * + * @param id 资源ID + * @return 资源数据 + */ + ZoneResourceDataVo selectAiModelResourceById(Long id); + + // ==================== AI资源广场 Dataset 功能 ==================== + + /** + * 创建Dataset资源(AI资源广场) + * + * @param inputVo Dataset提报输入 + * @return 资源数据 + */ + ZoneResourceDataVo insertAiDatasetResource(AiDatasetInputVo inputVo); + + /** + * 修改Dataset资源(AI资源广场) + * + * @param updateVo Dataset修改输入 + * @return 资源数据 + */ + ZoneResourceDataVo updateAiDatasetResource(AiDatasetUpdateVo updateVo); + + /** + * 查询Dataset资源详情(AI资源广场) + * + * @param id 资源ID + * @return 资源数据 + */ + ZoneResourceDataVo selectAiDatasetResourceById(Long id); + } diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/service/impl/AiResourceParseServiceImpl.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/service/impl/AiResourceParseServiceImpl.java new file mode 100644 index 000000000..738433caa --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/service/impl/AiResourceParseServiceImpl.java @@ -0,0 +1,567 @@ +package com.microservices.zone.resource.service.impl; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.microservices.common.core.constant.SecurityConstants; +import com.microservices.common.core.exception.ServiceException; +import com.microservices.common.core.utils.StringUtils; +import com.microservices.system.api.RemoteFileService; +import com.microservices.system.api.domain.SysFile; +import com.microservices.system.api.domain.SysFileInfo; +import com.microservices.system.api.utils.FeignUtils; +import com.microservices.zone.resource.domain.vo.AiSkillParseResultVo; +import com.microservices.zone.resource.domain.vo.AiUploadedResourceParseResultVo; +import com.microservices.zone.resource.service.IAiResourceParseService; +import com.microservices.zone.utils.ZoneConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; +import org.apache.commons.compress.archivers.zip.ZipFile; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.*; + +/** + * Skill ZIP解析服务实现 + */ +@Service +public class AiResourceParseServiceImpl implements IAiResourceParseService { + + private static final Logger logger = LoggerFactory.getLogger(AiResourceParseServiceImpl.class); + private static final String SKILL_MD_FILE = "SKILL.md"; + private static final String README_MD_FILE = "README.md"; + + private final RemoteFileService remoteFileService; + + public AiResourceParseServiceImpl(RemoteFileService remoteFileService) { + this.remoteFileService = remoteFileService; + } + + @Override + public AiSkillParseResultVo parseSkillZip(MultipartFile zipFile) { + // 1. 校验文件大小 + if (zipFile.getSize() > ZoneConstants.SKILL_ZIP_MAX_SIZE) { + throw new ServiceException("ZIP包大小不能超过50MB"); + } + + // 2. 校验文件格式 + String originalFilename = zipFile.getOriginalFilename(); + if (originalFilename == null || !originalFilename.toLowerCase().endsWith(".zip")) { + throw new ServiceException("仅支持ZIP格式文件"); + } + + try (ZipArchiveInputStream zais = new ZipArchiveInputStream(zipFile.getInputStream(), "GBK", true)) { + ZipArchiveEntry entry; + List skillMdPaths = new ArrayList<>(); + List fileTreeNodes = new ArrayList<>(); + + // 第一遍遍历:收集所有一级和二级目录下的 SKILL.md 路径 + while ((entry = zais.getNextEntry()) != null) { + String entryName = entry.getName(); + if (entry.isDirectory()) { + continue; + } + // 防止路径遍历攻击 + if (entryName.contains("..")) { + throw new ServiceException("ZIP包中包含非法路径"); + } + + // 收集一级或二级目录下的 SKILL.md + if (SKILL_MD_FILE.equalsIgnoreCase(new File(entryName).getName())) { + int slashCount = entryName.split("/").length - 1; + if (slashCount <= 1) { // 一级或二级 + skillMdPaths.add(entryName); + } + } + + // 收集文件信息构建目录树 + fileTreeNodes.add(new FileTreeNode(entryName, entry.getSize())); + } + + // 确定要读取的 SKILL.md 路径 + String targetSkillMdPath; + if (skillMdPaths.isEmpty()) { + throw new ServiceException("ZIP包中必须包含 SKILL.md 文件"); + } else { + // 优先选择根目录的 SKILL.md + targetSkillMdPath = null; + for (String path : skillMdPaths) { + if (path.equalsIgnoreCase("SKILL.md")) { + targetSkillMdPath = path; + break; + } + } + // 如果根目录不存在,选择第一个二级目录的 SKILL.md(按字母顺序) + if (targetSkillMdPath == null) { + Collections.sort(skillMdPaths, String.CASE_INSENSITIVE_ORDER); + targetSkillMdPath = skillMdPaths.get(0); + } + } + + // 第二遍遍历:读取目标 SKILL.md 的内容 + String skillMdContent; + try (ZipArchiveInputStream zais2 = new ZipArchiveInputStream(zipFile.getInputStream(), "GBK", true)) { + ZipArchiveEntry entry2; + skillMdContent = null; + while ((entry2 = zais2.getNextEntry()) != null) { + if (targetSkillMdPath.equals(entry2.getName())) { + skillMdContent = readEntryContent(zais2); + break; + } + } + } + + // 3. 校验 SKILL.md 内容 + if (skillMdContent == null) { + throw new ServiceException("ZIP包中必须包含 SKILL.md 文件"); + } + + // 4. 校验 SKILL.md 内容长度 + if (skillMdContent.trim().length() < ZoneConstants.SKILL_MD_MIN_LENGTH) { + throw new ServiceException("SKILL.md 内容不得少于" + ZoneConstants.SKILL_MD_MIN_LENGTH + "字"); + } + + // 5. 构建目录树 JSON + JSONObject fileTreeJson = buildFileTreeJson(fileTreeNodes); + + AiSkillParseResultVo result = new AiSkillParseResultVo(); + + // 从 YAML front matter 中提取 name 和 description 字段 + String name = extractName(skillMdContent); + result.setName(name); + + String description = extractDescription(skillMdContent); + result.setSummary(description); + + result.setSkillMdContent(skillMdContent); + + result.setFileTreeJson(fileTreeJson.toJSONString()); + + // 解析成功后,自动上传ZIP文件到File微服务(上传失败则阻断整个接口) + SysFile sysFile = FeignUtils.getReturnData( + remoteFileService.upload(zipFile, "ai-resource", "skills", SecurityConstants.INNER) + ); + if (sysFile == null || StringUtils.isEmpty(sysFile.getFileIdentifier())) { + throw new ServiceException("文件上传返回为空,请重试"); + } + result.setFileId(sysFile.getFileId()); + + return result; + + } catch (IOException e) { + logger.error("ZIP包解析失败", e); + throw new ServiceException("ZIP包解析失败:" + e.getMessage()); + } + } + + private String readEntryContent(ZipArchiveInputStream zais) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + int len; + while ((len = zais.read(buffer)) > 0) { + baos.write(buffer, 0, len); + } + return new String(baos.toByteArray(), StandardCharsets.UTF_8); + } + + @Override + public AiUploadedResourceParseResultVo parseUploadedZip(Long fileId) { + if (fileId == null) { + throw new ServiceException("文件ID不能为空"); + } + // 1. 获取文件信息 + SysFileInfo sysFileInfo = FeignUtils.getReturnData(remoteFileService.getFileDetail(fileId, SecurityConstants.INNER)); + if (sysFileInfo == null) { + throw new ServiceException("文件不存在"); + } + + // 2. 校验文件格式 + String fileSuffix = sysFileInfo.getFileSuffix(); + if (!"zip".equalsIgnoreCase(fileSuffix)) { + throw new ServiceException("仅支持ZIP格式文件"); + } + + // 3. 构建绝对路径 + String absolutePath = sysFileInfo.getFilePath(); + File zipFile = new File(absolutePath); + if (!zipFile.exists()) { + throw new ServiceException("ZIP文件不存在:" + sysFileInfo.getFilePath()); + } + + // 4. 使用 ZipFile 解析(读取 Central Directory,支持随机访问,大文件秒级响应) + try (ZipFile zf = new ZipFile(zipFile, "GBK", true)) { + String readmeContent = null; + List fileTreeNodes = new ArrayList<>(); + + Enumeration entries = zf.getEntries(); + while (entries.hasMoreElements()) { + ZipArchiveEntry entry = entries.nextElement(); + String entryName = entry.getName(); + if (entry.isDirectory()) { + continue; + } + if (entryName.contains("..")) { + throw new ServiceException("ZIP包中包含非法路径"); + } + + fileTreeNodes.add(new FileTreeNode(entryName, entry.getSize())); + } + + // 5. 查找并读取 README.md + readmeContent = findAndReadReadmeMd(zf, fileTreeNodes); + + // 6. 构建目录树 JSON + JSONObject fileTreeJson = buildFileTreeJsonFast(fileTreeNodes); + + AiUploadedResourceParseResultVo result = new AiUploadedResourceParseResultVo(); + result.setReadmeContent(readmeContent); + result.setFileTreeJson(fileTreeJson); + result.setFileSizeInfo(sysFileInfo.getFileSizeInfo()); + return result; + + } catch (Exception e) { + logger.error("ZIP包解析失败: fileId={}, path={}", fileId, absolutePath, e); + throw new ServiceException("ZIP包解析失败:" + e.getMessage()); + } + } + + private String findAndReadReadmeMd(ZipFile zf, List nodes) throws IOException { + // 优先查找根目录 README.md(忽略大小写) + Enumeration entries = zf.getEntries(); + ZipArchiveEntry readmeEntry = null; + + // 查找根目录下的 README.md + while (entries.hasMoreElements()) { + ZipArchiveEntry entry = entries.nextElement(); + String fileName = new File(entry.getName()).getName(); + if (README_MD_FILE.equalsIgnoreCase(fileName) && entry.getName().indexOf('/') == -1) { + readmeEntry = entry; + break; + } + } + + if (readmeEntry != null && !readmeEntry.isDirectory()) { + return readZipEntryContent(zf, readmeEntry); + } + + // 其次查找顶层唯一文件夹下的 README.md(忽略大小写) + String topFolder = findSingleTopFolder(nodes); + if (topFolder != null) { + entries = zf.getEntries(); // 重置枚举 + while (entries.hasMoreElements()) { + ZipArchiveEntry entry = entries.nextElement(); + String expectedPath = topFolder + "/" + README_MD_FILE; + if (expectedPath.equalsIgnoreCase(entry.getName())) { + readmeEntry = entry; + break; + } + } + + if (readmeEntry != null && !readmeEntry.isDirectory()) { + return readZipEntryContent(zf, readmeEntry); + } + } + + return null; + } + + private String findSingleTopFolder(List nodes) { + Set topFolders = new HashSet<>(); + for (FileTreeNode node : nodes) { + int slashIndex = node.path.indexOf('/'); + if (slashIndex > 0) { + topFolders.add(node.path.substring(0, slashIndex)); + } + } + return topFolders.size() == 1 ? topFolders.iterator().next() : null; + } + + private String readZipEntryContent(ZipFile zf, ZipArchiveEntry entry) throws IOException { + try (InputStream is = zf.getInputStream(entry)) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + int len; + while ((len = is.read(buffer)) > 0) { + baos.write(buffer, 0, len); + } + return new String(baos.toByteArray(), StandardCharsets.UTF_8); + } + } + + private JSONObject buildFileTreeJsonFast(List nodes) { + JSONObject root = new JSONObject(); + root.put("name", "root"); + root.put("type", "folder"); + JSONArray rootChildren = new JSONArray(); + root.put("children", rootChildren); + + Map folderCache = new LinkedHashMap<>(); + + for (FileTreeNode node : nodes) { + String[] parts = node.path.split("/"); + JSONArray currentChildren = rootChildren; + StringBuilder pathBuilder = new StringBuilder(); + + for (int i = 0; i < parts.length; i++) { + String part = parts[i]; + if (StringUtils.isEmpty(part)) { + continue; + } + boolean isFile = (i == parts.length - 1); + + if (isFile) { + JSONObject fileNode = new JSONObject(); + fileNode.put("name", part); + fileNode.put("type", "file"); + fileNode.put("size", node.size); + currentChildren.add(fileNode); + } else { + pathBuilder.append(part); + String cacheKey = pathBuilder.toString(); + pathBuilder.append('/'); + + JSONObject existingFolder = folderCache.get(cacheKey); + if (existingFolder == null) { + existingFolder = new JSONObject(); + existingFolder.put("name", part); + existingFolder.put("type", "folder"); + existingFolder.put("children", new JSONArray()); + currentChildren.add(existingFolder); + folderCache.put(cacheKey, existingFolder); + } + currentChildren = existingFolder.getJSONArray("children"); + } + } + } + + // 排序:文件夹在前,文件在后,同类型按字母顺序 + sortChildren(rootChildren); + + // 如果根目录只有一个子文件夹,直接返回该子文件夹 + JSONArray children = root.getJSONArray("children"); + if (children.size() == 1 && "folder".equals(children.getJSONObject(0).get("type"))) { + return children.getJSONObject(0); + } + return root; + } + + private JSONObject buildFileTreeJson(List nodes) { + // 构建树形结构的目录 + JSONObject root = new JSONObject(); + root.put("name", "root"); + root.put("type", "folder"); + JSONArray rootChildren = new JSONArray(); + root.put("children", rootChildren); + + for (FileTreeNode node : nodes) { + String[] parts = node.path.split("/"); + JSONArray currentChildren = rootChildren; + + for (int i = 0; i < parts.length; i++) { + String part = parts[i]; + if (StringUtils.isEmpty(part)) { + continue; + } + boolean isFile = (i == parts.length - 1); + + if (isFile) { + JSONObject fileNode = new JSONObject(); + fileNode.put("name", part); + fileNode.put("type", "file"); + fileNode.put("size", node.size); + currentChildren.add(fileNode); + } else { + // 查找是否已存在该文件夹 + JSONObject existingFolder = null; + for (int j = 0; j < currentChildren.size(); j++) { + JSONObject child = currentChildren.getJSONObject(j); + if (part.equals(child.get("name")) && "folder".equals(child.get("type"))) { + existingFolder = child; + break; + } + } + + if (existingFolder == null) { + existingFolder = new JSONObject(); + existingFolder.put("name", part); + existingFolder.put("type", "folder"); + existingFolder.put("children", new JSONArray()); + currentChildren.add(existingFolder); + } + currentChildren = existingFolder.getJSONArray("children"); + } + } + } + + // 排序:文件夹在前,文件在后,同类型按字母顺序 + sortChildren(rootChildren); + + // 如果根目录只有一个子文件夹,直接返回该子文件夹 + JSONArray children = root.getJSONArray("children"); + if (children.size() == 1 && "folder".equals(children.getJSONObject(0).get("type"))) { + return children.getJSONObject(0); + } + return root; + } + + private void sortChildren(JSONArray children) { + if (children == null || children.isEmpty()) { + return; + } + if (children.size() > 1) { + children.sort((a, b) -> { + JSONObject ja = (JSONObject) a; + JSONObject jb = (JSONObject) b; + boolean aIsFolder = "folder".equals(ja.getString("type")); + boolean bIsFolder = "folder".equals(jb.getString("type")); + if (aIsFolder != bIsFolder) { + return aIsFolder ? -1 : 1; + } + return ja.getString("name").compareToIgnoreCase(jb.getString("name")); + }); + } + for (int i = 0; i < children.size(); i++) { + JSONObject child = children.getJSONObject(i); + if ("folder".equals(child.getString("type"))) { + sortChildren(child.getJSONArray("children")); + } + } + } + + private static class FileTreeNode { + final String path; + final long size; + + FileTreeNode(String path, long size) { + this.path = path; + this.size = size; + } + } + + /** + * 从 SKILL.md 内容中提取 description 字段 + * + * @param skillMdContent SKILL.md 文件内容 + * @return description 内容,如果不存在则返回 null + */ + private String extractDescription(String skillMdContent) { + if (StringUtils.isEmpty(skillMdContent)) { + return null; + } + + String[] lines = skillMdContent.split("\\n"); + boolean inFrontMatter = false; + boolean foundStart = false; + boolean inMultiLineDesc = false; + StringBuilder multiLineDesc = new StringBuilder(); + + for (String line : lines) { + String trimmed = line.trim(); + + // 检测 front matter 开始 + if (!foundStart && trimmed.equals("---")) { + foundStart = true; + inFrontMatter = true; + continue; + } + + // 检测 front matter 结束 + if (foundStart && trimmed.equals("---")) { + break; + } + + // 在 front matter 中查找 description + if (inFrontMatter && trimmed.startsWith("description:")) { + String descValue = trimmed.substring("description:".length()).trim(); + + // 检查是否为多行格式 + if (descValue.equals("|") || descValue.equals(">")) { + inMultiLineDesc = true; + continue; + } else { + // 单行格式,处理引号包裹的情况 + if (descValue.startsWith("\"") && descValue.endsWith("\"")) { + return descValue.substring(1, descValue.length() - 1); + } + if (descValue.startsWith("'") && descValue.endsWith("'")) { + return descValue.substring(1, descValue.length() - 1); + } + return descValue; + } + } + + // 处理多行 description 内容 + if (inMultiLineDesc) { + // 遇到新的 YAML key(xxx: 格式),结束多行模式 + if (trimmed.matches("^[a-zA-Z_]+:.*")) { + break; + } + // 追加非空行内容,用空格连接 + if (!trimmed.isEmpty()) { + if (multiLineDesc.length() > 0) { + multiLineDesc.append(" "); + } + multiLineDesc.append(trimmed); + } + } + } + + // 返回多行拼接结果 + if (multiLineDesc.length() > 0) { + return multiLineDesc.toString(); + } + + return null; + } + + /** + * 从 SKILL.md 内容中提取 name 字段 + * + * @param skillMdContent SKILL.md 文件内容 + * @return name 内容,如果不存在则返回 null + */ + private String extractName(String skillMdContent) { + if (StringUtils.isEmpty(skillMdContent)) { + return null; + } + + String[] lines = skillMdContent.split("\\n"); + boolean inFrontMatter = false; + boolean foundStart = false; + + for (String line : lines) { + String trimmed = line.trim(); + + if (!foundStart && trimmed.equals("---")) { + foundStart = true; + inFrontMatter = true; + continue; + } + + if (foundStart && trimmed.equals("---")) { + break; + } + + if (inFrontMatter && trimmed.startsWith("name:")) { + String name = trimmed.substring("name:".length()).trim(); + if (name.startsWith("\"") && name.endsWith("\"")) { + return name.substring(1, name.length() - 1); + } + if (name.startsWith("'") && name.endsWith("'")) { + return name.substring(1, name.length() - 1); + } + return name; + } + } + + return null; + } +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/service/impl/ZoneResourceDomainServiceImpl.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/service/impl/ZoneResourceDomainServiceImpl.java index 810918212..49ff845a9 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/service/impl/ZoneResourceDomainServiceImpl.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/service/impl/ZoneResourceDomainServiceImpl.java @@ -73,7 +73,7 @@ public class ZoneResourceDomainServiceImpl implements IZoneResourceDomainService @Override public int insertZoneResourceDomain(ZoneResourceDomainInputVo zoneResourceDomainInputVo) { ZoneDetail zoneDetail = zoneDetailService.selectZoneDetailById(zoneResourceDomainInputVo.getZoneId()); - checkNameInZone(zoneResourceDomainInputVo.getZoneId(), zoneResourceDomainInputVo.getName()); + checkNameInZone(zoneResourceDomainInputVo.getZoneId(), zoneResourceDomainInputVo.getName(),zoneResourceDomainInputVo.getDomainCategory()); ZoneResourceDomain zoneResourceDomain = zoneResourceDomainInputVo.toZoneResourceDomain(); zoneResourceDomain.setCreateTime(DateUtils.getNowDate()); zoneResourceDomain.setCreateBy(SecurityUtils.getUsername()); @@ -86,11 +86,13 @@ public class ZoneResourceDomainServiceImpl implements IZoneResourceDomainService /** * 检查该特色专区下是否已存在该名称的资源领域 * + * @param domainCategory * @param zoneId * @param name + * @param domainCategory */ - private void checkNameInZone(Long zoneId, String name) { - ZoneResourceDomain oldZoneResourceDomain = zoneResourceDomainMapper.selectZoneResourceDomainByNameAndZoneId(name, zoneId); + private void checkNameInZone(Long zoneId, String name, String domainCategory) { + ZoneResourceDomain oldZoneResourceDomain = zoneResourceDomainMapper.selectZoneResourceDomainByNameAndZoneId(name, zoneId,domainCategory); if (oldZoneResourceDomain != null) { throw new ServiceException("该特色专区下资源领域名称已存在(资源领域名称[" + name + "])"); } @@ -106,7 +108,7 @@ public class ZoneResourceDomainServiceImpl implements IZoneResourceDomainService public int updateZoneResourceDomain(ZoneResourceDomainUpdateVo zoneResourceDomainUpdateVo) { ZoneResourceDomain oldZoneResourceDomain = selectZoneResourceDomainById(zoneResourceDomainUpdateVo.getId()); if (!oldZoneResourceDomain.getName().equals(zoneResourceDomainUpdateVo.getName())) { - checkNameInZone(oldZoneResourceDomain.getZoneId(), zoneResourceDomainUpdateVo.getName()); + checkNameInZone(oldZoneResourceDomain.getZoneId(), zoneResourceDomainUpdateVo.getName(), oldZoneResourceDomain.getDomainCategory()); } ZoneResourceDomain zoneResourceDomain = zoneResourceDomainUpdateVo.toZoneResourceDomain(); zoneResourceDomain.setUpdateTime(DateUtils.getNowDate()); diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/service/impl/ZoneResourceServiceImpl.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/service/impl/ZoneResourceServiceImpl.java index 2af438942..5e4349481 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/service/impl/ZoneResourceServiceImpl.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/service/impl/ZoneResourceServiceImpl.java @@ -1,5 +1,6 @@ package com.microservices.zone.resource.service.impl; +import com.alibaba.fastjson2.JSONObject; import com.microservices.common.core.constant.HttpStatus; import com.microservices.common.core.constant.SecurityConstants; import com.microservices.common.core.constant.UserConstants; @@ -16,6 +17,7 @@ import com.microservices.common.security.service.TokenService; import com.microservices.common.security.utils.SecurityUtils; import com.microservices.system.api.RemoteFileService; import com.microservices.system.api.RemoteUserService; +import com.microservices.system.api.domain.SysFileDownloadLogVo; import com.microservices.system.api.domain.SysFileInfo; import com.microservices.system.api.domain.SysUser; import com.microservices.system.api.utils.FeignUtils; @@ -27,12 +29,17 @@ import com.microservices.zone.member.service.IZonePointsAccountService; import com.microservices.zone.resource.domain.ZoneResource; import com.microservices.zone.resource.domain.ZoneResourceToType; import com.microservices.zone.resource.domain.ZoneResourceType; +import com.microservices.zone.resource.domain.*; import com.microservices.zone.resource.domain.vo.*; +import com.microservices.zone.resource.enums.AiResourceCategory; import com.microservices.zone.resource.mapper.ZoneResourceMapper; import com.microservices.zone.resource.mapper.ZoneResourceToTypeMapper; +import com.microservices.zone.resource.mapper.ZoneResourcesLikeMapper; import com.microservices.zone.resource.service.IZoneResourceDomainService; import com.microservices.zone.resource.service.IZoneResourceService; import com.microservices.zone.resource.service.IZoneResourceTypeService; +import com.microservices.zone.resource.service.IAiResourceParseService; +import com.microservices.zone.resource.utils.ResourceExtendDataUtils; import com.microservices.zone.specialProject.service.IZoneSpecialProjectRelevancyService; import com.microservices.zone.utils.ZoneConstants; import org.slf4j.Logger; @@ -41,9 +48,15 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; /** @@ -80,6 +93,10 @@ public class ZoneResourceServiceImpl implements IZoneResourceService { private IZoneSpecialProjectRelevancyService zoneSpecialProjectRelevancyService; @Autowired private IZonePointsAccountService zonePointsAccountService; + @Autowired + private ZoneResourcesLikeMapper zoneResourcesLikeMapper; + @Autowired + private IAiResourceParseService aiResourceParseService; /** * 查询特色专区资源聚合 @@ -107,17 +124,17 @@ public class ZoneResourceServiceImpl implements IZoneResourceService { @DataScope(deptAlias = "zr") public GenericsTableDataInfo selectZoneResourceList(ZoneResource zoneResourceSearch) { List zoneResourceList = zoneResourceMapper.selectZoneResourceList(zoneResourceSearch); - return PageUtils.toPage(zoneResourceList, this::zoneResourceToData); + return PageUtils.toPage(zoneResourceList, z -> zoneResourceToData(z, true)); } @Override @DataScope(deptAlias = "zr") public GenericsTableDataInfo selectZoneResourceList(ZoneResourceOpenSearchVo zoneResourceSearchVo) { List zoneResourceList = zoneResourceMapper.selectZoneResourceListBySearchVo(zoneResourceSearchVo); - return PageUtils.toPage(zoneResourceList, this::zoneResourceToData); + return PageUtils.toPage(zoneResourceList, z -> zoneResourceToData(z, true)); } - private ZoneResourceDataVo zoneResourceToData(ZoneResource zoneResource) { + private ZoneResourceDataVo zoneResourceToData(ZoneResource zoneResource, boolean removeSkill) { ZoneResourceDataVo zoneResourceDataVo = zoneResource.toZoneResourceDataVo(); int downloadCount = 0; if (StringUtils.isNotEmpty(zoneResource.getFileIds())) { @@ -156,6 +173,9 @@ public class ZoneResourceServiceImpl implements IZoneResourceService { } } zoneResourceDataVo.setDownloadCount(downloadCount); + if (removeSkill && zoneResourceDataVo.getExtendData() != null) { + zoneResourceDataVo.getExtendData().remove(ResourceExtendDataUtils.SKILL_ROOT); + } return zoneResourceDataVo; } @@ -175,7 +195,7 @@ public class ZoneResourceServiceImpl implements IZoneResourceService { @Override public ZoneResourceDataVo insertZoneResource(ZoneResourceInputVo zoneResourceInputVo) { ZoneDetail zoneDetail = zoneDetailService.selectZoneDetailById(zoneResourceInputVo.getZoneId()); - checkNameInZone(zoneResourceInputVo.getZoneId(), zoneResourceInputVo.getName()); + checkNameInZone(zoneResourceInputVo.getZoneId(), zoneResourceInputVo.getName(), zoneResourceInputVo.getResourceCategory()); zoneResourceDomainService.selectZoneResourceDomainById(zoneResourceInputVo.getDomainId()); ZoneResource zoneResource = zoneResourceInputVo.toZoneResource(); // 专区开启资源审核后,专区会员和专区运营人员新增资源都需要审核 @@ -217,12 +237,11 @@ public class ZoneResourceServiceImpl implements IZoneResourceService { public List getAllFileSuffix(String fileIds) { List sysFileInfoList = getAllFileByFileIds(fileIds); // 获取文件后缀列表 - List suffixList = sysFileInfoList.stream() + + return sysFileInfoList.stream() .map(x -> x.getFileSuffix().toLowerCase()) .distinct() .collect(Collectors.toList()); - - return suffixList; } public List getAllFileByFileIds(String fileIds) { @@ -233,7 +252,7 @@ public class ZoneResourceServiceImpl implements IZoneResourceService { ); } catch (Exception e) { logger.error("从文件微服务获取资源附件时失败:{}", e.getMessage()); - throw new ServiceException("资源附件列表异常:"+e.getMessage()); + throw new ServiceException("资源附件列表异常:" + e.getMessage()); } return sysFileInfoList; } @@ -274,8 +293,16 @@ public class ZoneResourceServiceImpl implements IZoneResourceService { @Override public ZoneResourceDataVo selectOpenZoneResourceDataById(Long resourceId, HttpServletRequest request) { ZoneResource zoneResource = selectZoneResourceById(resourceId); + + if (AiResourceCategory.isAiResourceCategory(zoneResource.getResourceCategory())) { + // 增加浏览次数 + incrementViewCount(zoneResource.getId()); + } if (ZoneConstants.RESOURCE_AUDIT_PASS.equals(zoneResource.getAuditStatus())) { - return zoneResourceToData(zoneResource); + ZoneResourceDataVo dataVo = zoneResourceToData(zoneResource, false); + // 填充下载用户信息 + populateDownloadUsers(dataVo); + return dataVo; } else { String gitLinkToken = AuthUtil.getGitLinkRequestToken(request); if (StringUtils.isNotEmpty(gitLinkToken)) { @@ -283,7 +310,10 @@ public class ZoneResourceServiceImpl implements IZoneResourceService { remoteUserService.getUserNameByGitLinkToken(gitLinkToken, SecurityConstants.INNER) ); if (zoneResource.getCreateBy().equals(currentUsername)) { - return zoneResourceToData(zoneResource); + ZoneResourceDataVo dataVo = zoneResourceToData(zoneResource, false); + // 填充下载用户信息 + populateDownloadUsers(dataVo); + return dataVo; } } } @@ -291,13 +321,103 @@ public class ZoneResourceServiceImpl implements IZoneResourceService { } + /** + * 填充文件下载用户列表 + */ + private void populateDownloadUsers(ZoneResourceDataVo dataVo) { + if (dataVo == null || dataVo.getFileList() == null || dataVo.getFileList().isEmpty()) { + return; + } + try { + // 收集所有文件ID + String fileIds = dataVo.getFileList().stream() + .map(x -> String.valueOf(x.getFileId())) + .collect(Collectors.joining(",")); + if (StringUtils.isEmpty(fileIds)) { + return; + } + // 通过Feign调用文件微服务获取下载记录 + List downloadLogList = FeignUtils.getReturnData( + remoteFileService.getDownloadUserList(fileIds, SecurityConstants.INNER) + ); + if (downloadLogList == null) { + downloadLogList = new ArrayList<>(); + } + // 补全用户详情(昵称、手机号、邮箱) + enrichDownloadLogUserInfo(downloadLogList); + // 按文件ID分组,填充到各附件VO中 + Map> fileUserMap = downloadLogList.stream() + .collect(Collectors.groupingBy( + SysFileDownloadLogVo::getFileId, + Collectors.mapping(SysFileDownloadLogVo::getUserName, + Collectors.toCollection(LinkedHashSet::new)) + )) + .entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> new ArrayList<>(entry.getValue()) + )); + for (ZoneResourceFileVo fileVo : dataVo.getFileList()) { + List userList = fileUserMap.get(fileVo.getFileId()); + fileVo.setDownloadUserList(userList != null ? userList : new ArrayList<>()); + } + // 外层直接用原始下载记录列表 + dataVo.setDownloadUserSummary(downloadLogList); + } catch (Exception e) { + logger.error("填充下载用户信息失败:{}", e.getMessage()); + } + } + + /** + * 补全下载记录中的用户详情(昵称、手机号、邮箱) + */ + private void enrichDownloadLogUserInfo(List downloadLogList) { + if (downloadLogList == null || downloadLogList.isEmpty()) { + return; + } + // 收集所有非空的用户名(去重) + Set userNames = downloadLogList.stream() + .map(SysFileDownloadLogVo::getUserName) + .filter(StringUtils::isNotEmpty) + .collect(Collectors.toSet()); + if (userNames.isEmpty()) { + return; + } + // 逐个查询用户信息并构建映射 + Map userMap = new HashMap<>(); + for (String userName : userNames) { + try { + SysUser sysUser = FeignUtils.getReturnData( + remoteUserService.getSysUserByUserName(userName, SecurityConstants.INNER) + ); + if (sysUser != null) { + userMap.put(userName, sysUser); + } + } catch (Exception e) { + logger.warn("查询用户[{}]信息失败:{}", userName, e.getMessage()); + } + } + // 填充用户详情到下载记录 + for (SysFileDownloadLogVo logVo : downloadLogList) { + if (StringUtils.isEmpty(logVo.getUserName())) { + continue; + } + SysUser sysUser = userMap.get(logVo.getUserName()); + if (sysUser != null) { + logVo.setNickName(sysUser.getNickName()); + logVo.setPhonenumber(sysUser.getPhonenumber()); + logVo.setEmail(sysUser.getEmail()); + } + } + } + @Override public GenericsTableDataInfo selectMyZoneResourceList(ZoneResource zoneResourceSearch) { if (zoneResourceSearch == null || zoneResourceSearch.getZoneId() == null) { throw new ServiceException("获取我的资源列表时必须传入专区Id"); } List zoneResourceList = zoneResourceMapper.selectZoneResourceList(zoneResourceSearch); - return PageUtils.toPage(zoneResourceList, this::zoneResourceToData); + return PageUtils.toPage(zoneResourceList, x -> zoneResourceToData(x, true)); } @Override @@ -311,8 +431,8 @@ public class ZoneResourceServiceImpl implements IZoneResourceService { * @param zoneId * @param name */ - private void checkNameInZone(Long zoneId, String name) { - ZoneResource oldZoneResource = zoneResourceMapper.selectZoneResourceByNameAndZoneId(name, zoneId); + private void checkNameInZone(Long zoneId, String name, String resourceCategory) { + ZoneResource oldZoneResource = zoneResourceMapper.selectZoneResourceByNameAndZoneId(name, zoneId, resourceCategory); if (oldZoneResource != null) { throw new ServiceException("该特色专区下资源名称已存在(资源名称[" + name + "])"); } @@ -328,7 +448,7 @@ public class ZoneResourceServiceImpl implements IZoneResourceService { public ZoneResourceDataVo updateZoneResource(ZoneResourceUpdateVo zoneResourceUpdateVo) { ZoneResource oldZoneResource = selectZoneResourceById(zoneResourceUpdateVo.getId()); if (!oldZoneResource.getName().equals(zoneResourceUpdateVo.getName())) { - checkNameInZone(oldZoneResource.getZoneId(), zoneResourceUpdateVo.getName()); + checkNameInZone(oldZoneResource.getZoneId(), zoneResourceUpdateVo.getName(), oldZoneResource.getResourceCategory()); } if (zoneResourceUpdateVo.getFileIds() != null) { List oldResourceToTypeList = zoneResourceToTypeMapper @@ -354,9 +474,7 @@ public class ZoneResourceServiceImpl implements IZoneResourceService { // 获取删除的类型Id列表 Long[] delZoneResourceTypeIds = newTypeIdList .stream() - .filter(x -> !newTypeIdList.contains(x)) - .collect(Collectors.toList()) - .toArray(new Long[0]); + .filter(x -> !newTypeIdList.contains(x)).toArray(Long[]::new); if (delZoneResourceTypeIds.length > 0) { // 删除资源与资源类型关联关系 zoneResourceToTypeMapper.deleteResourceToTypesByResourceIdAndTypeIds(oldZoneResource.getId(), delZoneResourceTypeIds); @@ -377,8 +495,10 @@ public class ZoneResourceServiceImpl implements IZoneResourceService { zoneResource.setSort(setSort); } ZoneDetail zoneDetail = zoneDetailService.selectZoneDetailById(oldZoneResource.getZoneId()); - // 专区开启资源审核后,专区会员和专区运营人员编辑资源都需要审核 - if (resourceNeedAudit(zoneDetail)) { + // 草稿状态的资源重新编辑后变为待审核 + if (ZoneConstants.RESOURCE_DRAFT.equals(oldZoneResource.getAuditStatus())) { + zoneResource.setAuditStatus(ZoneConstants.RESOURCE_NOT_AUDIT); + } else if (resourceNeedAudit(zoneDetail)) { zoneResource.setAuditStatus(ZoneConstants.RESOURCE_NOT_AUDIT); } zoneResourceMapper.updateZoneResource(zoneResource); @@ -422,6 +542,462 @@ public class ZoneResourceServiceImpl implements IZoneResourceService { @Override public ZoneResourceDataVo selectZoneResourceDataById(Long id) { ZoneResource zoneResource = selectZoneResourceById(id); - return zoneResourceToData(zoneResource); + return zoneResourceToData(zoneResource, false); } + + // ==================== AI资源广场 Skill 功能 ==================== + + @Override + public ZoneResourceDataVo insertAiSkillResource(AiSkillInputVo inputVo) { + ZoneResourceDomain zoneResourceDomain = zoneResourceDomainService.selectZoneResourceDomainById(inputVo.getDomainId()); + if (!AiResourceCategory.SKILL.getCode().equals(zoneResourceDomain.getDomainCategory())) { + throw new ServiceException("当前资源领域非SKILL类型"); + } + ZoneResourceInputVo zoneResourceInputVo = inputVo.toZoneResourceInputVo(); + return insertZoneResource(zoneResourceInputVo); + } + + @Override + public ZoneResourceDataVo updateAiSkillResource(AiSkillUpdateVo updateVo) { + ZoneResource oldResource = getAiResourceById(updateVo.getId()); + if (!AiResourceCategory.SKILL.getCode().equals(oldResource.getResourceCategory())) { + throw new ServiceException("当前资源非Skill资源"); + } + // 已发布状态不允许直接修改 + if (ZoneConstants.RESOURCE_AUDIT_PASS.equals(oldResource.getAuditStatus())) { + throw new ServiceException("已发布的资源不允许直接修改,请先下架后再修改"); + } + if (ZoneConstants.RESOURCE_TAKEN_DOWN.equals(oldResource.getAuditStatus())) { + oldResource.setAuditStatus(ZoneConstants.RESOURCE_NOT_AUDIT); + } + if (updateVo.getDomainId() != null) { + ZoneResourceDomain zoneResourceDomain = zoneResourceDomainService.selectZoneResourceDomainById(updateVo.getDomainId()); + if (!AiResourceCategory.SKILL.getCode().equals(zoneResourceDomain.getDomainCategory())) { + throw new ServiceException("当前资源领域非SKILL类型"); + } + } + ZoneResourceUpdateVo zoneResourceUpdateVo = updateVo.toZoneResourceUpdateVo(oldResource); + return updateZoneResource(zoneResourceUpdateVo); + } + + @Override + public ZoneResourceDataVo selectAiSkillResourceById(Long id) { + ZoneResource resource = getAiResourceById(id); + return zoneResourceToData(resource, false); + } + + @Override + public GenericsTableDataInfo selectAiSkillResourceList(ZoneResource zoneResource) { + zoneResource.setResourceCategory(AiResourceCategory.SKILL.getCode()); + List list = zoneResourceMapper.selectZoneResourceList(zoneResource); + return PageUtils.toPage(list, x -> zoneResourceToData(x, true)); + } + + @Override + public GenericsTableDataInfo selectAiModelResourceList(ZoneResource zoneResource) { + zoneResource.setResourceCategory(AiResourceCategory.MODEL.getCode()); + List list = zoneResourceMapper.selectZoneResourceList(zoneResource); + return PageUtils.toPage(list, x -> zoneResourceToData(x, true)); + } + + @Override + public GenericsTableDataInfo selectAiDatasetResourceList(ZoneResource zoneResource) { + zoneResource.setResourceCategory(AiResourceCategory.DATASET.getCode()); + List list = zoneResourceMapper.selectZoneResourceList(zoneResource); + return PageUtils.toPage(list, x -> zoneResourceToData(x, true)); + } + + @Override + public UserResourceStatisticsVo getResourceStatistics(Long zoneId, Boolean isMine, String resourceCategory) { + String createBy = null; + if (isMine) { + createBy = SecurityUtils.getUsername(); + } + UserResourceStatisticsVo statistics = new UserResourceStatisticsVo(); + + // 构造查询条件 + ZoneResource likeQuery = new ZoneResource(); + likeQuery.setZoneId(zoneId); + if (StringUtils.isNotEmpty(createBy)) { + likeQuery.setCreateBy(createBy); + } + if (StringUtils.isNotEmpty(resourceCategory)) { + likeQuery.setResourceCategory(resourceCategory); + } + + // 查询点赞总数 + List resourceList = zoneResourceMapper.selectZoneResourceList(likeQuery); + int totalLikeCount = 0; + for (ZoneResource resource : resourceList) { + if (resource.getLikeCount() != null) { + totalLikeCount += resource.getLikeCount(); + } + } + statistics.setLikeCount(totalLikeCount); + + // 统计已发布数量 + ZoneResource publishedQuery = new ZoneResource(); + publishedQuery.setZoneId(zoneId); + if (StringUtils.isNotEmpty(createBy)) { + publishedQuery.setCreateBy(createBy); + } + if (StringUtils.isNotEmpty(resourceCategory)) { + publishedQuery.setResourceCategory(resourceCategory); + } + publishedQuery.setAuditStatus(ZoneConstants.RESOURCE_AUDIT_PASS); + Integer publishedCount = zoneResourceMapper.selectZoneResourceCount(publishedQuery); + statistics.setPublishedCount(publishedCount); + + // 统计待审核数量 + ZoneResource pendingQuery = new ZoneResource(); + pendingQuery.setZoneId(zoneId); + if (StringUtils.isNotEmpty(createBy)) { + pendingQuery.setCreateBy(createBy); + } + if (StringUtils.isNotEmpty(resourceCategory)) { + pendingQuery.setResourceCategory(resourceCategory); + } + pendingQuery.setAuditStatus(ZoneConstants.RESOURCE_NOT_AUDIT); + Integer pendingCount = zoneResourceMapper.selectZoneResourceCount(pendingQuery); + statistics.setPendingCount(pendingCount); + + return statistics; + } + + @Override + public int takeDownAiResource(Long id, String reason) { + ZoneResource resource = getAiResourceById(id); + + if (!ZoneConstants.RESOURCE_AUDIT_PASS.equals(resource.getAuditStatus())) { + throw new ServiceException("仅已发布的资源可以下架"); + } + + resource.setAuditStatus(ZoneConstants.RESOURCE_TAKEN_DOWN); + if (StringUtils.isNotEmpty(reason)) { + resource.setRemark(reason); + } + resource.setUpdateTime(DateUtils.getNowDate()); + resource.setUpdateBy(SecurityUtils.getUsername()); + return zoneResourceMapper.updateZoneResource(resource); + } + + @Override + public int takeUpAiResource(Long id, String reason) { + ZoneResource resource = getAiResourceById(id); + if (!ZoneConstants.RESOURCE_TAKEN_DOWN.equals(resource.getAuditStatus())) { + throw new ServiceException("仅已下架的资源可以重新上架"); + } + + resource.setAuditStatus(ZoneConstants.RESOURCE_AUDIT_PASS); + if (StringUtils.isNotEmpty(reason)) { + resource.setRemark(reason); + } + resource.setUpdateTime(DateUtils.getNowDate()); + resource.setUpdateBy(SecurityUtils.getUsername()); + return zoneResourceMapper.updateZoneResource(resource); + } + + @Override + public List selectZoneResourcesIsLike(String ids) { + List list = new ArrayList<>(); + if (StringUtils.isNotEmpty(ids)) { + String[] idArr = ids.split(","); + List zoneResourcesLikeList = zoneResourcesLikeMapper.selectByResourceIds(idArr); + for (int i = 0; i < idArr.length; i++) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("id", idArr[i]); + if (zoneResourcesLikeList != null && !zoneResourcesLikeList.isEmpty()) { + int finalI = i; + jsonObject.put("isLike", zoneResourcesLikeList.stream().anyMatch(x -> idArr[finalI].equals(String.valueOf(x.getResourceId())))); + } else { + jsonObject.put("isLike", false); + } + list.add(jsonObject); + } + } + return list; + } + + @Override + public int resubmitAiResource(Long id) { + ZoneResource resource = getAiResourceById(id); + checkResourceOwner(resource); + + if (!ZoneConstants.RESOURCE_AUDIT_FAILED.equals(resource.getAuditStatus()) + && !ZoneConstants.RESOURCE_TAKEN_DOWN.equals(resource.getAuditStatus()) + && !ZoneConstants.RESOURCE_DRAFT.equals(resource.getAuditStatus())) { + throw new ServiceException("仅被驳回、已下架或草稿的资源可以重新提交"); + } + + resource.setAuditStatus(ZoneConstants.RESOURCE_NOT_AUDIT); + resource.setRemark(null); + resource.setUpdateTime(DateUtils.getNowDate()); + return zoneResourceMapper.updateZoneResource(resource); + } + + @Override + public int withdrawAiResource(Long id) { + ZoneResource resource = getAiResourceById(id); + checkResourceOwner(resource); + + if (!ZoneConstants.RESOURCE_NOT_AUDIT.equals(resource.getAuditStatus())) { + throw new ServiceException("仅待审核的资源可以撤回"); + } + + resource.setAuditStatus(ZoneConstants.RESOURCE_DRAFT); + resource.setUpdateTime(DateUtils.getNowDate()); + return zoneResourceMapper.updateZoneResource(resource); + } + + @Override + public List selectSkillRecommendations(int limit) { + List list = zoneResourceMapper.selectSkillRecommendations( + AiResourceCategory.SKILL.getCode(), ZoneConstants.RESOURCE_AUDIT_PASS, limit); + return list.stream().map(z -> zoneResourceToData(z, false)).collect(Collectors.toList()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean toggleZoneResourcesLike(Long resourceId) { + // 校验资源存在且已发布 + ZoneResource resource = getAiResourceById(resourceId); + if (!ZoneConstants.RESOURCE_AUDIT_PASS.equals(resource.getAuditStatus())) { + throw new ServiceException("仅已发布的资源可以点赞"); + } + + Long userId = SecurityUtils.getGitlinkUserId(); + if (userId == null) { + throw new ServiceException("当前用户未绑定平台账号,无法点赞"); + } + + ZoneResourcesLike existingLike = zoneResourcesLikeMapper.selectByResourceAndUser(resourceId, userId); + if (existingLike != null) { + // 取消点赞 + zoneResourcesLikeMapper.deleteLike(resourceId, userId); + zoneResourceMapper.updateLikeCount(resourceId, -1); + return false; + } else { + // 新增点赞 + ZoneResourcesLike like = new ZoneResourcesLike(); + like.setResourceId(resourceId); + like.setUserId(userId); + like.setCreateTime(DateUtils.getNowDate()); + zoneResourcesLikeMapper.insertLike(like); + zoneResourceMapper.updateLikeCount(resourceId, 1); + return true; + } + } + + @Override + public void incrementViewCount(Long id) { + zoneResourceMapper.incrementViewCount(id); + } + + // ==================== AI资源辅助方法 ==================== + + private ZoneResource getAiResourceById(Long id) { + ZoneResource resource = zoneResourceMapper.selectZoneResourceById(id); + if (resource == null) { + throw new ServiceException("资源不存在(资源id[" + id + "])"); + } + if (!AiResourceCategory.isAiResourceCategory(resource.getResourceCategory())) { + throw new ServiceException("该资源不是AI资源"); + } + return resource; + } + + private void checkResourceOwner(ZoneResource resource) { + if (!resource.getCreateBy().equals(SecurityUtils.getUsername())) { + throw new ServiceException("仅允许操作自己发布的资源"); + } + } + + private void validateSkillNameUnique(String name, Long excludeId) { + if (StringUtils.isEmpty(name)) { + return; + } + ZoneResource existing = zoneResourceMapper.selectSkillResourceByName(name); + if (existing != null && !existing.getId().equals(excludeId)) { + throw new ServiceException("Skill名称已存在(名称[" + name + "])"); + } + } + + private void validateKeywords(String keywords) { + if (StringUtils.isEmpty(keywords)) { + return; + } + String[] tags = keywords.split(","); + if (tags.length > ZoneConstants.KEYWORD_MAX_COUNT) { + throw new ServiceException("标签数量不能超过" + ZoneConstants.KEYWORD_MAX_COUNT + "个"); + } + for (String tag : tags) { + if (tag.trim().length() > ZoneConstants.KEYWORD_MAX_LENGTH) { + throw new ServiceException("单个标签不能超过" + ZoneConstants.KEYWORD_MAX_LENGTH + "个字符"); + } + } + } + + // ==================== AI资源广场 Model 功能 ==================== + + @Override + public ZoneResourceDataVo insertAiModelResource(AiModelInputVo inputVo) { + // 1. 校验领域类型是否为 MODEL + ZoneResourceDomain zoneResourceDomain = zoneResourceDomainService.selectZoneResourceDomainById(inputVo.getDomainId()); + if (zoneResourceDomain == null) { + throw new ServiceException("资源领域不存在"); + } + if (!AiResourceCategory.MODEL.getCode().equals(zoneResourceDomain.getDomainCategory())) { + throw new ServiceException("当前资源领域非MODEL类型"); + } + return insertModelOrDatasetResource(inputVo.toZoneResourceInputVo(), inputVo.getFileId()); + } + + @Override + public ZoneResourceDataVo updateAiModelResource(AiModelUpdateVo updateVo) { + // 1. 获取现有资源 + ZoneResource oldResource = getAiResourceById(updateVo.getId()); + if (!AiResourceCategory.MODEL.getCode().equals(oldResource.getResourceCategory())) { + throw new ServiceException("当前资源非Model资源"); + } + + // 2. 状态校验 + if (ZoneConstants.RESOURCE_AUDIT_PASS.equals(oldResource.getAuditStatus())) { + throw new ServiceException("已发布的资源不允许直接修改,请先下架后再修改"); + } + if (ZoneConstants.RESOURCE_TAKEN_DOWN.equals(oldResource.getAuditStatus())) { + oldResource.setAuditStatus(ZoneConstants.RESOURCE_NOT_AUDIT); + } + + // 3. 领域校验 + if (updateVo.getDomainId() != null) { + ZoneResourceDomain zoneResourceDomain = zoneResourceDomainService.selectZoneResourceDomainById(updateVo.getDomainId()); + if (zoneResourceDomain == null) { + throw new ServiceException("资源领域不存在"); + } + if (!AiResourceCategory.MODEL.getCode().equals(zoneResourceDomain.getDomainCategory())) { + throw new ServiceException("当前资源领域非MODEL类型"); + } + } + + return updateModelOrDatasetResource(oldResource, updateVo.toZoneResourceUpdateVo(oldResource), updateVo.getFileId()); + + + } + + private ZoneResourceDataVo updateModelOrDatasetResource(ZoneResource oldResource, ZoneResourceUpdateVo updateVo, Long fileId) { + // 4. 获取 VO 层已合并的 extendData(可能包含 readmeContent),或使用旧的 + JSONObject extendData; + if (updateVo.getExtendData() != null) { + extendData = JSONObject.parseObject(updateVo.getExtendData()); + } else { + extendData = oldResource.getExtendData(); + } + if (extendData == null) { + extendData = new JSONObject(); + } + + // 5. 如果更新了文件,重新解析 + if (fileId != null) { + AiUploadedResourceParseResultVo parseResult = aiResourceParseService.parseUploadedZip(fileId); + + // 更新 extendData + JSONObject fileTreeJsonObj = parseResult.getFileTreeJson(); + extendData = ResourceExtendDataUtils.updateModelOrDatasetReadmeContent(oldResource.getResourceCategory(), extendData, parseResult.getReadmeContent()); + extendData = ResourceExtendDataUtils.updateModelOrDatasetFileTree(oldResource.getResourceCategory(), extendData, fileTreeJsonObj); + + // 更新 fileSize + if (!extendData.containsKey(oldResource.getResourceCategory())) { + extendData.put(oldResource.getResourceCategory(), new JSONObject()); + } + extendData.getJSONObject(oldResource.getResourceCategory()).put(ResourceExtendDataUtils.FILE_SIZE, parseResult.getFileSizeInfo()); + } + + + if (fileId == null) { + updateVo.setFileIds(oldResource.getFileIds()); + } else { + updateVo.setFileIds(String.valueOf(fileId)); + } + updateVo.setExtendData(extendData.toJSONString()); + + // 7. 更新资源 + return updateZoneResource(updateVo); + } + + @Override + public ZoneResourceDataVo selectAiModelResourceById(Long id) { + ZoneResource resource = getAiResourceById(id); + if (!AiResourceCategory.MODEL.getCode().equals(resource.getResourceCategory())) { + throw new ServiceException("当前资源非Model资源"); + } + return zoneResourceToData(resource, false); + } + + // ==================== AI资源广场 Dataset 功能 ==================== + + @Override + public ZoneResourceDataVo insertAiDatasetResource(AiDatasetInputVo inputVo) { + // 1. 校验领域类型是否为 DATASET + ZoneResourceDomain zoneResourceDomain = zoneResourceDomainService.selectZoneResourceDomainById(inputVo.getDomainId()); + if (zoneResourceDomain == null) { + throw new ServiceException("资源领域不存在"); + } + if (!AiResourceCategory.DATASET.getCode().equals(zoneResourceDomain.getDomainCategory())) { + throw new ServiceException("当前资源领域非DATASET类型"); + } + return insertModelOrDatasetResource(inputVo.toZoneResourceInputVo(), inputVo.getFileId()); + + } + + private ZoneResourceDataVo insertModelOrDatasetResource(ZoneResourceInputVo inputVo, Long fileId) { + // 2. 调用 parseUploadedZip 解析文件,获取 readmeContent 和 fileTreeJson + AiUploadedResourceParseResultVo parseResult = aiResourceParseService.parseUploadedZip(fileId); + + JSONObject extendData = ResourceExtendDataUtils.buildModelOrDataSetExtendData( + inputVo.getResourceCategory(), parseResult); + inputVo.setExtendData(extendData.toJSONString()); + + // 6. 创建资源 + return insertZoneResource(inputVo); + } + + @Override + public ZoneResourceDataVo updateAiDatasetResource(AiDatasetUpdateVo updateVo) { + // 1. 获取现有资源 + ZoneResource oldResource = getAiResourceById(updateVo.getId()); + if (!AiResourceCategory.DATASET.getCode().equals(oldResource.getResourceCategory())) { + throw new ServiceException("当前资源非Dataset资源"); + } + + // 2. 状态校验 + if (ZoneConstants.RESOURCE_AUDIT_PASS.equals(oldResource.getAuditStatus())) { + throw new ServiceException("已发布的资源不允许直接修改,请先下架后再修改"); + } + if (ZoneConstants.RESOURCE_TAKEN_DOWN.equals(oldResource.getAuditStatus())) { + oldResource.setAuditStatus(ZoneConstants.RESOURCE_NOT_AUDIT); + } + + // 3. 领域校验 + if (updateVo.getDomainId() != null) { + ZoneResourceDomain zoneResourceDomain = zoneResourceDomainService.selectZoneResourceDomainById(updateVo.getDomainId()); + if (zoneResourceDomain == null) { + throw new ServiceException("资源领域不存在"); + } + if (!AiResourceCategory.DATASET.getCode().equals(zoneResourceDomain.getDomainCategory())) { + throw new ServiceException("当前资源领域非DATASET类型"); + } + } + + return updateModelOrDatasetResource(oldResource, updateVo.toZoneResourceUpdateVo(oldResource), updateVo.getFileId()); + } + + @Override + public ZoneResourceDataVo selectAiDatasetResourceById(Long id) { + ZoneResource resource = getAiResourceById(id); + if (!AiResourceCategory.DATASET.getCode().equals(resource.getResourceCategory())) { + throw new ServiceException("当前资源非Dataset资源"); + } + return zoneResourceToData(resource, false); + } + } diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/utils/ResourceExtendDataUtils.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/utils/ResourceExtendDataUtils.java new file mode 100644 index 000000000..11ecead37 --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/utils/ResourceExtendDataUtils.java @@ -0,0 +1,234 @@ +package com.microservices.zone.resource.utils; + +import com.alibaba.fastjson2.JSONObject; +import com.microservices.common.core.utils.StringUtils; +import com.microservices.zone.resource.domain.vo.AiUploadedResourceParseResultVo; + +/** + * 资源 extendData 工具类 + * 用于处理不同资源类型的扩展数据 + */ +public class ResourceExtendDataUtils { + + public static final String SKILL_ROOT = "SKILL"; + private static final String SKILL_CONTENT = "content"; + + private static final String MODEL_ROOT = "MODEL"; + private static final String DATASET_ROOT = "DATASET"; + private static final String CONTENT = "content"; + public static final String FILE_TREE = "fileTree"; + public static final String FILE_SIZE = "fileSize"; + + /** + * 构建 Skill 资源的 extendData JSONObject + * + * @param skillContent SKILL.md 内容 + * @param fileTreeJson 目录树 JSONObject + * @return extendData JSONObject + */ + public static JSONObject buildSkillExtendData(String skillContent, JSONObject fileTreeJson) { + JSONObject extendData = new JSONObject(); + JSONObject skill = new JSONObject(); + skill.put(SKILL_CONTENT, skillContent); + if (fileTreeJson != null) { + skill.put(FILE_TREE, fileTreeJson); + } + extendData.put(SKILL_ROOT, skill); + return extendData; + } + + /** + * 从 extendData 中提取 skillContent + * + * @param extendData extendData JSONObject + * @return skillContent,不存在返回 null + */ + public static String extractSkillContent(JSONObject extendData) { + if (extendData == null || !extendData.containsKey(SKILL_ROOT)) { + return null; + } + JSONObject skill = extendData.getJSONObject(SKILL_ROOT); + return skill.getString(SKILL_CONTENT); + } + + /** + * 从 extendData 中提取 fileTree + * + * @param extendData extendData JSONObject + * @return fileTree JSONObject,不存在返回 null + */ + public static JSONObject extractSkillFileTree(JSONObject extendData) { + if (extendData == null || !extendData.containsKey(SKILL_ROOT)) { + return null; + } + JSONObject skill = extendData.getJSONObject(SKILL_ROOT); + return skill.getJSONObject(FILE_TREE); + } + + /** + * 更新 extendData 中的 skillContent + * + * @param extendData extendData JSONObject + * @param skillContent 新的 skillContent + * @return 更新后的 extendData + */ + public static JSONObject updateSkillContent(JSONObject extendData, String skillContent) { + if (extendData == null) { + extendData = new JSONObject(); + } + if (!extendData.containsKey(SKILL_ROOT)) { + extendData.put(SKILL_ROOT, new JSONObject()); + } + JSONObject skill = extendData.getJSONObject(SKILL_ROOT); + skill.put(SKILL_CONTENT, skillContent); + return extendData; + } + + /** + * 更新 extendData 中的 fileTree + * + * @param extendData extendData JSONObject + * @param fileTreeJson 新的 fileTree + * @return 更新后的 extendData + */ + public static JSONObject updateSkillFileTree(JSONObject extendData, JSONObject fileTreeJson) { + if (extendData == null) { + extendData = new JSONObject(); + } + if (!extendData.containsKey(SKILL_ROOT)) { + extendData.put(SKILL_ROOT, new JSONObject()); + } + JSONObject skill = extendData.getJSONObject(SKILL_ROOT); + skill.put(FILE_TREE, fileTreeJson); + return extendData; + } + + // ==================== Model 资源 extendData 方法 ==================== + + /** + * 构建 Model/数据集 资源的 extendData JSONObject + * + * @return extendData JSONObject + */ + public static JSONObject buildModelOrDataSetExtendData(String resourceCategory, AiUploadedResourceParseResultVo parseResult) { + JSONObject extendData = new JSONObject(); + JSONObject data = new JSONObject(); + if (parseResult.getReadmeContent() != null) { + data.put(CONTENT, parseResult.getReadmeContent()); + } + + if (parseResult.getFileTreeJson() != null) { + data.put(FILE_TREE, parseResult.getFileTreeJson()); + } + if (parseResult.getFileSizeInfo() != null) { + data.put(FILE_SIZE, parseResult.getFileSizeInfo()); + } + extendData.put(resourceCategory, data); + return extendData; + } + + /** + * 从 extendData 中提取 model 的 readmeContent + */ + public static String extractModelReadmeContent(JSONObject extendData) { + if (extendData == null || !extendData.containsKey(MODEL_ROOT)) { + return null; + } + JSONObject model = extendData.getJSONObject(MODEL_ROOT); + return model.getString(CONTENT); + } + + /** + * 从 extendData 中提取 model 的 fileTree + */ + public static JSONObject extractModelFileTree(JSONObject extendData) { + if (extendData == null || !extendData.containsKey(MODEL_ROOT)) { + return null; + } + JSONObject model = extendData.getJSONObject(MODEL_ROOT); + return model.getJSONObject(FILE_TREE); + } + + /** + * 更新 extendData 中的 readmeContent + */ + public static JSONObject updateModelOrDatasetReadmeContent(String resourceCategory, JSONObject extendData, String readmeContent) { + if (extendData == null) { + extendData = new JSONObject(); + } + if (!extendData.containsKey(resourceCategory)) { + extendData.put(resourceCategory, new JSONObject()); + } + JSONObject data = extendData.getJSONObject(resourceCategory); + data.put(CONTENT, readmeContent); + return extendData; + } + + /** + * 更新 extendData 中的 model fileTree + */ + public static JSONObject updateModelOrDatasetFileTree(String resourceCategory, JSONObject extendData, JSONObject fileTreeJson) { + if (extendData == null) { + extendData = new JSONObject(); + } + if (!extendData.containsKey(resourceCategory)) { + extendData.put(resourceCategory, new JSONObject()); + } + JSONObject model = extendData.getJSONObject(resourceCategory); + model.put(FILE_TREE, fileTreeJson); + return extendData; + } + + + /** + * 从 extendData 中提取 dataset 的 readmeContent + */ + public static String extractDatasetReadmeContent(JSONObject extendData) { + if (extendData == null || !extendData.containsKey(DATASET_ROOT)) { + return null; + } + JSONObject dataset = extendData.getJSONObject(DATASET_ROOT); + return dataset.getString(CONTENT); + } + + /** + * 从 extendData 中提取 dataset 的 fileTree + */ + public static JSONObject extractDatasetFileTree(JSONObject extendData) { + if (extendData == null || !extendData.containsKey(DATASET_ROOT)) { + return null; + } + JSONObject dataset = extendData.getJSONObject(DATASET_ROOT); + return dataset.getJSONObject(FILE_TREE); + } + + /** + * 更新 extendData 中的 dataset readmeContent + */ + public static JSONObject updateDatasetReadmeContent(JSONObject extendData, String readmeContent) { + if (extendData == null) { + extendData = new JSONObject(); + } + if (!extendData.containsKey(DATASET_ROOT)) { + extendData.put(DATASET_ROOT, new JSONObject()); + } + JSONObject dataset = extendData.getJSONObject(DATASET_ROOT); + dataset.put(CONTENT, readmeContent); + return extendData; + } + + /** + * 更新 extendData 中的 dataset fileTree + */ + public static JSONObject updateDatasetFileTree(JSONObject extendData, JSONObject fileTreeJson) { + if (extendData == null) { + extendData = new JSONObject(); + } + if (!extendData.containsKey(DATASET_ROOT)) { + extendData.put(DATASET_ROOT, new JSONObject()); + } + JSONObject dataset = extendData.getJSONObject(DATASET_ROOT); + dataset.put(FILE_TREE, fileTreeJson); + return extendData; + } +} diff --git a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/utils/ZoneConstants.java b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/utils/ZoneConstants.java index e7692e6e0..a0882b303 100644 --- a/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/utils/ZoneConstants.java +++ b/microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/utils/ZoneConstants.java @@ -235,10 +235,18 @@ public class ZoneConstants { * 资源审核状态-审核通过 */ public final static String RESOURCE_AUDIT_PASS = "1"; + /** + * 资源审核状态-所有状态 + */ + public final static String RESOURCE_AUDIT_ALL = "all"; /** * 资源审核状态-审核未通过 */ public final static String RESOURCE_AUDIT_FAILED = "2"; + /** + * 资源审核状态-草稿 + */ + public final static String RESOURCE_DRAFT = "4"; /** * 资源审核状态-待审核 */ @@ -256,6 +264,10 @@ public class ZoneConstants { * 项目审核状态-待审核 */ public final static String PROJECT_NOT_AUDIT = "0"; + /** + * 资源审核状态-已下架 + */ + public final static String RESOURCE_TAKEN_DOWN = "3"; /** * 前台权限-管理者 */ @@ -272,4 +284,24 @@ public class ZoneConstants { public static String getFileSuffixOfResourceTypeRedisKey(Long zoneId, String fileSuffix) { return REDIS_KEY_FILE_SUFFIX_OF_RESOURCE_TYPE_PREFIX + zoneId + "_" + fileSuffix; } + + /** + * Skill ZIP文件最大大小(50MB) + */ + public final static long SKILL_ZIP_MAX_SIZE = 50 * 1024 * 1024; + + /** + * SKILL.md 最少内容长度 + */ + public final static int SKILL_MD_MIN_LENGTH = 100; + + /** + * 标签最大数量 + */ + public final static int KEYWORD_MAX_COUNT = 5; + + /** + * 单个标签最大字符数 + */ + public final static int KEYWORD_MAX_LENGTH = 10; } diff --git a/microservices-modules/microservices-modules-zone/src/main/resources/mapper/zone/ZoneJournalsMapper.xml b/microservices-modules/microservices-modules-zone/src/main/resources/mapper/zone/ZoneJournalsMapper.xml new file mode 100644 index 000000000..a6d2b472f --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/resources/mapper/zone/ZoneJournalsMapper.xml @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + select id, + journalized_id, + journalized_type, + user_id, + notes, + parent_id, + reply_id, + comments_count, + attachment_identifiers, + receivers_login, + del_flag, + create_by, + created_on, + update_by, + updated_on + from zone_journals + + + + + + + + + + + + insert into zone_journals + + journalized_id, + journalized_type, + user_id, + notes, + parent_id, + reply_id, + comments_count, + attachment_identifiers, + receivers_login, + del_flag, + create_by, + created_on, + update_by, + updated_on, + + + #{journalizedId}, + #{journalizedType}, + #{userId}, + #{notes}, + #{parentId}, + #{replyId}, + #{commentsCount}, + #{attachmentIdentifiers}, + #{receiversLogin}, + #{delFlag}, + #{createBy}, + #{createdOn}, + #{updateBy}, + #{updatedOn}, + + + + + update zone_journals + + notes = #{notes}, + attachment_identifiers = #{attachmentIdentifiers}, + receivers_login = #{receiversLogin}, + update_by = #{updateBy}, + updated_on = #{updatedOn}, + + where id = #{id} + + + + UPDATE zone_journals + SET comments_count = ( + SELECT COUNT(*) + FROM ( + SELECT 1 + FROM zone_journals + WHERE parent_id = #{id} + AND del_flag = '0' + ) AS tmp + ) + WHERE id = #{id} + + + + update zone_journals + set journalized_id = #{journalId} + where journalized_type = #{type} + and journalized_id = #{oldJournalId} + + + + update zone_journals set del_flag = '2' where id = #{id} + + + + update zone_journals set del_flag = '2' where id in + + #{id} + + + + + update zone_journals set del_flag = '2' where parent_id = #{parentId} + + + + update zone_journals set del_flag = '2' + where journalized_id = #{journalId} + and journalized_type = #{type} + + diff --git a/microservices-modules/microservices-modules-zone/src/main/resources/mapper/zone/ZoneProjectMapper.xml b/microservices-modules/microservices-modules-zone/src/main/resources/mapper/zone/ZoneProjectMapper.xml index aba5ae95f..261db78d8 100644 --- a/microservices-modules/microservices-modules-zone/src/main/resources/mapper/zone/ZoneProjectMapper.xml +++ b/microservices-modules/microservices-modules-zone/src/main/resources/mapper/zone/ZoneProjectMapper.xml @@ -155,6 +155,7 @@ and zp.is_homepage = #{isHomepage} and zp.id != #{excludeId} + and zp.create_by = #{createBy} ${params.dataScope} @@ -172,6 +173,7 @@ and zp.name like concat('%', #{name}, '%') and zp.project_type_id = #{projectTypeId} and zp.zone_id = #{zoneId} + and zp.create_by = #{createBy} and zp.audit_status != '2' diff --git a/microservices-modules/microservices-modules-zone/src/main/resources/mapper/zone/ZoneResourceDomainMapper.xml b/microservices-modules/microservices-modules-zone/src/main/resources/mapper/zone/ZoneResourceDomainMapper.xml index 2584a7978..b38937b0f 100644 --- a/microservices-modules/microservices-modules-zone/src/main/resources/mapper/zone/ZoneResourceDomainMapper.xml +++ b/microservices-modules/microservices-modules-zone/src/main/resources/mapper/zone/ZoneResourceDomainMapper.xml @@ -16,6 +16,7 @@ + @@ -29,7 +30,8 @@ update_by, update_time, introduction, - zone_id + zone_id, + domain_category from zone_resource_domain @@ -44,7 +46,8 @@ zrd.update_by, zrd.update_time, zrd.introduction, - zrd.zone_id + zrd.zone_id, + zrd.domain_category from zone_resource_domain zrd @@ -52,6 +55,7 @@ and zrd.name like concat('%', #{name}, '%') + and zrd.domain_category = #{domainCategory} and zrd.sort = #{sort} and zrd.dept_id = #{deptId} and zrd.zone_id = #{zoneId} @@ -70,6 +74,7 @@ where name = #{name} and zone_id = #{zoneId} + and domain_category = #{domainCategory} @@ -85,6 +90,7 @@ update_time, zone_id, introduction, + domain_category, #{name}, @@ -97,6 +103,7 @@ #{updateTime}, #{zoneId}, #{introduction}, + #{domainCategory}, @@ -113,6 +120,7 @@ update_time = #{updateTime}, zone_id = #{zoneId}, introduction = #{introduction}, + domain_category = #{domainCategory}, where id = #{id} diff --git a/microservices-modules/microservices-modules-zone/src/main/resources/mapper/zone/ZoneResourceMapper.xml b/microservices-modules/microservices-modules-zone/src/main/resources/mapper/zone/ZoneResourceMapper.xml index a396c37db..3a91a1f17 100644 --- a/microservices-modules/microservices-modules-zone/src/main/resources/mapper/zone/ZoneResourceMapper.xml +++ b/microservices-modules/microservices-modules-zone/src/main/resources/mapper/zone/ZoneResourceMapper.xml @@ -28,6 +28,11 @@ + + + + @@ -67,7 +72,10 @@ update_time, keywords, extend_data, - earned_points + earned_points, + resource_category, + view_count, + like_count from zone_resource @@ -93,6 +101,9 @@ zr.remark, zr.keywords, zr.extend_data, + zr.resource_category, + zr.view_count, + zr.like_count, zr.earned_points, zrd.name as domain_name from zone_resource zr @@ -123,6 +134,9 @@ zr.keywords, zr.extend_data, zr.earned_points, + zr.resource_category, + zr.view_count, + zr.like_count, zrd.name as domain_name from zone_resource zr left join zone_resource_domain zrd on zr.domain_id = zrd.id @@ -137,9 +151,12 @@ and zr.dept_id = #{deptId} and zr.create_by = #{createBy} and zr.keywords like concat('%', #{keywords}, '%') + and zr.resource_category = + #{resourceCategory} + - and zr.audit_status = #{auditStatus} + and zr.audit_status = #{auditStatus} and zr.audit_status = '1' @@ -150,7 +167,20 @@ ${params.dataScope} - ORDER BY zr.sort DESC + + + ORDER BY + zr.like_count DESC, + (select sum(download_count) from sys_file_info sf where FIND_IN_SET(sf.file_id,zr.file_ids)) desc, + zr.view_count DESC, + zr.update_time DESC, + zr.create_time DESC + + + ORDER BY zr.sort DESC + + + @@ -166,6 +196,7 @@ and zr.id in (select resource_id from zone_resource_to_type where type_id=#{searchTypeId}) + and zr.resource_category = #{resourceCategory} and zr.audit_status = #{auditStatus} @@ -177,12 +208,17 @@ ${params.dataScope} - - order by (select sum(download_count) from sys_file_info sf where FIND_IN_SET(sf.file_id,zr.file_ids)) desc - - - order by zr.sort DESC + + ORDER BY + zr.like_count DESC, + (select sum(download_count) from sys_file_info sf where FIND_IN_SET(sf.file_id,zr.file_ids)) desc, + zr.view_count DESC, + zr.update_time DESC, + zr.create_time DESC + + ORDER BY zr.sort DESC + @@ -206,6 +242,7 @@ where name = #{name} and zone_id = #{zoneId} + and resource_category = #{resourceCategory} + + where name = #{name} and resource_category = 'SKILL' and del_flag = '0' + + + + + + update zone_resource set view_count = IFNULL(view_count, 0) + 1 where id = #{id} + + + + update zone_resource set like_count = IFNULL(like_count, 0) + #{delta} where id = #{id} + \ No newline at end of file diff --git a/microservices-modules/microservices-modules-zone/src/main/resources/mapper/zone/ZoneResourcesLikeMapper.xml b/microservices-modules/microservices-modules-zone/src/main/resources/mapper/zone/ZoneResourcesLikeMapper.xml new file mode 100644 index 000000000..a8d92ab8d --- /dev/null +++ b/microservices-modules/microservices-modules-zone/src/main/resources/mapper/zone/ZoneResourcesLikeMapper.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + insert into zone_resources_like (resource_id, user_id, create_time) + values (#{resourceId}, #{userId}, #{createTime}) + + + + delete from zone_resources_like + where resource_id = #{resourceId} and user_id = #{userId} + + + diff --git a/pom.xml b/pom.xml index f8ee3bea7..6459d48dc 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ 1.4.6 1.2.16 3.5.2 - 2.11.0 + 2.15.1 2.3 2.0.42 0.9.1