Merge pull request '小强社区初始化版本' (#6) from otto/microservices:xiaoqiang into xiaoqiang
This commit is contained in:
commit
cbb052b3a7
94
README.md
94
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<T>、常量
|
||||
│ ├── 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 |
|
||||
|
||||
|
|
@ -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<SysFileInfo> getFile(@PathVariable("fileId") Long fileId);
|
||||
|
||||
@GetMapping("/common/getFileDetail/{fileId}")
|
||||
public R<SysFileInfo> 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<List<String>> uploadFileToForge(@PathVariable("fileIdentifiers") String fileIdentifiers,
|
||||
@RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
||||
|
||||
/**
|
||||
* 根据文件ID列表获取下载记录(内部接口)
|
||||
*
|
||||
* @param fileIds 文件ID列表,逗号分隔
|
||||
* @param source 内部调用鉴权
|
||||
* @return 下载记录列表
|
||||
*/
|
||||
@GetMapping("/common/getDownloadUserList/{fileIds}")
|
||||
R<List<SysFileDownloadLogVo>> getDownloadUserList(@PathVariable("fileIds") String fileIds,
|
||||
@RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ import lombok.Data;
|
|||
@Data
|
||||
public class SysFile
|
||||
{
|
||||
/**
|
||||
* 文件标识
|
||||
*/
|
||||
private Long fileId;
|
||||
/**
|
||||
* 文件标识
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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<RemoteFileServ
|
|||
return R.fail("根据文件Id获取文件信息失败:" + throwable.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<SysFileInfo> getFileDetail(Long fileId, String source) {
|
||||
return R.fail("根据文件id获取文件详细信息失败:" + throwable.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<SysFileInfo> getFileByFileIdentifier(String fileIdentifier, String source) {
|
||||
return R.fail("根据文件标识获取文件信息失败:" + throwable.getMessage());
|
||||
|
|
@ -80,6 +86,11 @@ public class RemoteFileFallbackFactory implements FallbackFactory<RemoteFileServ
|
|||
public R<List<String>> uploadFileToForge(String fileIdentifiers, String source) {
|
||||
return R.fail("上传本地文件到Forge失败:" + throwable.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<List<SysFileDownloadLogVo>> getDownloadUserList(String fileIds, String source) {
|
||||
return R.fail("获取下载记录列表失败:" + throwable.getMessage());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:";
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<SysUserDeptRole> 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);
|
||||
|
|
|
|||
|
|
@ -239,6 +239,7 @@
|
|||
and is_dir = 1
|
||||
</otherwise>
|
||||
</choose>
|
||||
and audit_status = '1'
|
||||
</where>
|
||||
</select>
|
||||
<select id="selectCmsDocByNameAndDeptIdIgnoreDirName" resultMap="CmsDocResult">
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ public class ChunkedUploadController extends BaseController {
|
|||
return genericsSuccess(result);
|
||||
}
|
||||
|
||||
/* 屏蔽取消上传接口
|
||||
/**
|
||||
* 取消上传
|
||||
* <p>
|
||||
|
|
@ -147,7 +148,6 @@ public class ChunkedUploadController extends BaseController {
|
|||
*
|
||||
* @param uploadId 上传ID
|
||||
* @return 取消结果
|
||||
*/
|
||||
@DeleteMapping("/cancel/{uploadId}")
|
||||
@ApiOperation("取消上传")
|
||||
@ApiImplicitParams({
|
||||
|
|
@ -157,6 +157,7 @@ public class ChunkedUploadController extends BaseController {
|
|||
boolean result = chunkedUploadService.cancelUpload(uploadId);
|
||||
return genericsSuccess(result ? CancelUploadResponseVo.success() : CancelUploadResponseVo.failed("取消上传失败"));
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* 获取上传进度
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.microservices.common.core.web.controller.BaseController;
|
|||
import com.microservices.common.core.web.domain.AjaxResult;
|
||||
import com.microservices.common.security.annotation.InnerAuth;
|
||||
import com.microservices.file.service.ISysFileInfoService;
|
||||
import com.microservices.system.api.domain.SysFileDownloadLogVo;
|
||||
import com.microservices.system.api.domain.SysFileInfo;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
|
|
@ -235,4 +236,29 @@ public class CommonController extends BaseController {
|
|||
R<List<String>> uploadFileToForge(@PathVariable("fileIdentifiers") String fileIdentifiers) {
|
||||
return R.ok(sysFileInfoService.uploadFileToForge(fileIdentifiers));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件Id获取文件信息
|
||||
*/
|
||||
@InnerAuth
|
||||
@GetMapping("/getFileDetail/{fileId}")
|
||||
@ApiOperation(value = "根据文件Id获取文件信息")
|
||||
public R<SysFileInfo> getFileDetail(@PathVariable("fileId") Long fileId) {
|
||||
SysFileInfo sysFileInfo = sysFileInfoService.selectSysFileInfoByFileId(fileId);
|
||||
sysFileInfo.setFilePath(localFilePath + sysFileInfo.getFilePath());
|
||||
return R.ok(sysFileInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件ID列表获取下载记录(内部接口)
|
||||
*
|
||||
* @param fileIds 文件ID列表,逗号分隔
|
||||
* @return 下载记录列表
|
||||
*/
|
||||
@InnerAuth
|
||||
@GetMapping("/getDownloadUserList/{fileIds}")
|
||||
@ApiOperation(value = "根据文件ID列表获取下载记录", hidden = true)
|
||||
public R<List<SysFileDownloadLogVo>> getDownloadUserList(@PathVariable("fileIds") String fileIds) {
|
||||
return R.ok(sysFileInfoService.getDownloadUserListByFileIds(fileIds));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import org.slf4j.LoggerFactory;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import springfox.documentation.annotations.ApiIgnore;
|
||||
|
|
@ -33,7 +34,7 @@ public class SysFileController extends BaseController {
|
|||
*/
|
||||
@InnerAuth
|
||||
@PostMapping("upload")
|
||||
public R<SysFile> upload(MultipartFile file,
|
||||
public R<SysFile> upload(@RequestPart(value = "file") MultipartFile file,
|
||||
@RequestParam("type") String type,
|
||||
@RequestParam("hierarchy") String hierarchy) {
|
||||
try {
|
||||
|
|
@ -43,6 +44,7 @@ public class SysFileController extends BaseController {
|
|||
sysFile.setName(fileInfo.getFilePath());
|
||||
sysFile.setUrl(fileInfo.getDownloadUrl());
|
||||
sysFile.setFileIdentifier(fileInfo.getFileIdentifier());
|
||||
sysFile.setFileId(fileInfo.getFileId());
|
||||
return R.ok(sysFile);
|
||||
} catch (Exception e) {
|
||||
log.error("上传文件失败", e);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
package com.microservices.file.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 文件下载记录对象 sys_file_download_log
|
||||
*
|
||||
* @author microservices
|
||||
* @date 2026-06-29
|
||||
*/
|
||||
@Data
|
||||
public class SysFileDownloadLog {
|
||||
/** 主键 */
|
||||
private Long id;
|
||||
|
||||
/** 文件ID */
|
||||
private Long fileId;
|
||||
|
||||
/** 下载用户ID */
|
||||
private Long userId;
|
||||
|
||||
/** 下载用户 */
|
||||
private String userName;
|
||||
|
||||
/** 下载时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date downloadTime;
|
||||
|
||||
/** IP地址 */
|
||||
private String ipAddr;
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.microservices.file.mapper;
|
||||
|
||||
import com.microservices.file.domain.SysFileDownloadLog;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件下载记录Mapper接口
|
||||
*
|
||||
* @author microservices
|
||||
* @date 2026-06-29
|
||||
*/
|
||||
@Mapper
|
||||
public interface SysFileDownloadLogMapper {
|
||||
|
||||
/**
|
||||
* 新增文件下载记录
|
||||
*
|
||||
* @param sysFileDownloadLog 文件下载记录
|
||||
* @return 结果
|
||||
*/
|
||||
int insertSysFileDownloadLog(SysFileDownloadLog sysFileDownloadLog);
|
||||
|
||||
/**
|
||||
* 根据文件ID列表查询下载用户列表
|
||||
*
|
||||
* @param fileIds 文件ID列表
|
||||
* @return 文件下载记录列表
|
||||
*/
|
||||
List<SysFileDownloadLog> selectDownloadLogByFileIds(@Param("fileIds") Long[] fileIds);
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.microservices.file.mapper;
|
|||
|
||||
import com.microservices.system.api.domain.SysFileInfo;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -85,4 +86,9 @@ public interface SysFileInfoMapper {
|
|||
* @return 文件
|
||||
*/
|
||||
SysFileInfo selectSysFileInfoByIdentifier(String fileIdentifier);
|
||||
|
||||
/**
|
||||
* 统计引用同一物理文件路径的记录数
|
||||
*/
|
||||
int countByFilePath(@Param("filePath") String filePath);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.microservices.file.service;
|
||||
|
||||
import com.microservices.file.domain.FileUploadSession;
|
||||
import com.microservices.system.api.domain.SysFileDownloadLogVo;
|
||||
import com.microservices.system.api.domain.SysFileInfo;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
|
|
@ -8,6 +9,7 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 文件管理Service接口
|
||||
|
|
@ -177,4 +179,18 @@ public interface ISysFileInfoService {
|
|||
List<String> uploadFileToForge(String fileIdentifiers);
|
||||
|
||||
String downloadFileByUrl(String fileUrl, String type, String hierarchy);
|
||||
|
||||
/**
|
||||
* 基于已有文件信息克隆一条新的数据库记录(秒传场景使用)
|
||||
* 新记录拥有独立的fileId和downloadCount(=0),共享物理文件
|
||||
*/
|
||||
SysFileInfo cloneFileInfo(SysFileInfo source);
|
||||
|
||||
/**
|
||||
* 根据文件ID列表获取下载记录列表
|
||||
*
|
||||
* @param fileIds 文件ID列表,逗号分隔
|
||||
* @return 下载记录列表
|
||||
*/
|
||||
List<SysFileDownloadLogVo> getDownloadUserListByFileIds(String fileIds);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,6 +122,21 @@ public class ChunkedUploadServiceImpl implements IChunkedUploadService {
|
|||
throw new ServiceException("文件大小无效");
|
||||
}
|
||||
|
||||
// 1.1 文件名安全校验
|
||||
String fileName = input.getFileName();
|
||||
// 禁止路径穿越
|
||||
if (fileName.contains("..")) {
|
||||
throw new ServiceException("文件名包含非法字符");
|
||||
}
|
||||
// 禁止路径分隔符
|
||||
if (fileName.contains("/") || fileName.contains("\\")) {
|
||||
throw new ServiceException("文件名包含非法字符");
|
||||
}
|
||||
// 文件名长度限制
|
||||
if (fileName.length() > 255) {
|
||||
throw new ServiceException("文件名过长(最大255字符)");
|
||||
}
|
||||
|
||||
// 校验文件大小
|
||||
if (input.getFileSize() > maxFileSize) {
|
||||
throw new ServiceException("文件大小超过限制: %s(%d字节) ", FileUtils.calcFileSize(maxFileSize), maxFileSize);
|
||||
|
|
@ -215,13 +230,15 @@ public class ChunkedUploadServiceImpl implements IChunkedUploadService {
|
|||
// 清理临时文件
|
||||
cleanupTempFiles(session);
|
||||
|
||||
// 返回已存在文件信息
|
||||
// 基于原文件创建新的数据库记录(独立downloadCount,共享物理文件)
|
||||
SysFileInfo newFileInfo = fileInfoService.cloneFileInfo(fileInfo);
|
||||
|
||||
return CheckInstantUploadVo.instantSuccess(
|
||||
fileInfo.getFileId(),
|
||||
fileInfo.getFileOriginName(),
|
||||
fileInfo.getFilePath(),
|
||||
fileInfo.getDownloadUrl(),
|
||||
fileInfo.getFileSizeInfo()
|
||||
newFileInfo.getFileId(),
|
||||
newFileInfo.getFileOriginName(),
|
||||
newFileInfo.getFilePath(),
|
||||
newFileInfo.getDownloadUrl(),
|
||||
newFileInfo.getFileSizeInfo()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -316,16 +333,18 @@ public class ChunkedUploadServiceImpl implements IChunkedUploadService {
|
|||
throw new ServiceException("分片记录不存在");
|
||||
}
|
||||
|
||||
// 6. 检查是否已上传
|
||||
if (FileUploadChunk.Status.UPLOADED.getCode().equals(chunkRecord.getStatus())) {
|
||||
File existingFile = new File(chunkRecord.getChunkPath());
|
||||
if (existingFile.exists()) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("success", true);
|
||||
result.put("message", "分片已存在");
|
||||
result.put("chunkNumber", chunkNumber);
|
||||
return result;
|
||||
}
|
||||
// 6. 检查是否已上传,避免重复计数
|
||||
boolean isAlreadyUploaded = FileUploadChunk.Status.UPLOADED.getCode().equals(chunkRecord.getStatus())
|
||||
&& new File(chunkRecord.getChunkPath()).exists();
|
||||
|
||||
if (isAlreadyUploaded) {
|
||||
// 已上传的分片直接返回,不重复写入和计数
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("success", true);
|
||||
result.put("message", "分片已存在");
|
||||
result.put("chunkNumber", chunkNumber);
|
||||
result.put("skipped", true);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 7. 保存分片文件
|
||||
|
|
@ -345,13 +364,14 @@ public class ChunkedUploadServiceImpl implements IChunkedUploadService {
|
|||
chunkRecord.setUploadTime(DateUtils.getNowDate());
|
||||
chunkMapper.updateChunk(chunkRecord);
|
||||
|
||||
// 9. 更新会话已上传分片数
|
||||
// 9. 更新会话已上传分片数(只有真正写入新分片才+1)
|
||||
sessionMapper.incrementUploadedChunks(uploadId);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("success", true);
|
||||
result.put("message", "分片上传成功");
|
||||
result.put("chunkNumber", chunkNumber);
|
||||
result.put("skipped", false);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -408,32 +428,57 @@ public class ChunkedUploadServiceImpl implements IChunkedUploadService {
|
|||
String fileName = FileUploadUtils.extractFilename(session.getFileName());
|
||||
String mergedFilePath = FileUploadUtils.getAbsoluteFile(baseFilePath, fileName).getAbsolutePath();
|
||||
|
||||
try (FileOutputStream fos = new FileOutputStream(mergedFilePath)) {
|
||||
List<FileUploadChunk> chunks = chunkMapper.selectByUploadId(session.getUploadId());
|
||||
for (FileUploadChunk chunk : chunks) {
|
||||
File chunkFile = new File(chunk.getChunkPath());
|
||||
if (!chunkFile.exists()) {
|
||||
throw new ServiceException("分片文件不存在: " + chunk.getChunkPath());
|
||||
}
|
||||
List<FileUploadChunk> chunks = null;
|
||||
try {
|
||||
// 先获取分片列表(已按chunk_number排序)
|
||||
chunks = chunkMapper.selectByUploadId(session.getUploadId());
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(chunkFile)) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
while ((bytesRead = fis.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, bytesRead);
|
||||
// 使用缓冲流提升合并效率
|
||||
try (FileOutputStream fos = new FileOutputStream(mergedFilePath);
|
||||
BufferedOutputStream bos = new BufferedOutputStream(fos, 1024 * 1024)) { // 1MB buffer
|
||||
|
||||
for (FileUploadChunk chunk : chunks) {
|
||||
File chunkFile = new File(chunk.getChunkPath());
|
||||
if (!chunkFile.exists()) {
|
||||
throw new ServiceException("分片文件不存在: " + chunk.getChunkPath());
|
||||
}
|
||||
|
||||
// 使用 Files.copy 提升性能
|
||||
Files.copy(chunkFile.toPath(), bos);
|
||||
}
|
||||
bos.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("合并分片文件失败: uploadId={}", session.getUploadId(), e);
|
||||
// 合并失败时清理已写入的无效文件
|
||||
cleanupFailedMerge(mergedFilePath);
|
||||
throw new ServiceException("合并分片文件失败");
|
||||
}
|
||||
|
||||
// 哈希校验(FULL类型进行后置校验)
|
||||
validateFullFileHash(mergedFilePath, session);
|
||||
|
||||
return FileUploadUtils.getPathFileName(fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理合并失败的文件
|
||||
*
|
||||
* @param mergedFilePath 合并后的文件路径
|
||||
*/
|
||||
private void cleanupFailedMerge(String mergedFilePath) {
|
||||
if (mergedFilePath != null && !mergedFilePath.isEmpty()) {
|
||||
File file = new File(mergedFilePath);
|
||||
if (file.exists() && file.length() > 0) {
|
||||
// 仅在文件存在且有内容时删除(避免误删空文件或不存在文件)
|
||||
boolean deleted = file.delete();
|
||||
if (!deleted) {
|
||||
logger.warn("清理合并失败文件失败: {}", mergedFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean cancelUpload(String uploadId) {
|
||||
|
|
|
|||
|
|
@ -110,10 +110,16 @@ public class FileCommonAsyncServiceImpl implements IFileCommonAsyncService {
|
|||
}
|
||||
|
||||
/**
|
||||
* 异步清理临时文件
|
||||
* 异步清理临时文件(复用线程池,避免频繁创建线程)
|
||||
*/
|
||||
private void cleanupTempFilesAsync(FileUploadSession session) {
|
||||
new Thread(() -> chunkedUploadService.cleanupTempFiles(session)).start();
|
||||
CustomExecutorFactory.threadPoolExecutor.execute(() -> {
|
||||
try {
|
||||
chunkedUploadService.cleanupTempFiles(session);
|
||||
} catch (Exception e) {
|
||||
logger.error("异步清理临时文件失败: uploadId={}", session.getUploadId(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -13,18 +13,27 @@ import com.microservices.common.core.utils.file.FileTypeUtils;
|
|||
import com.microservices.common.core.utils.file.FileUtils;
|
||||
import com.microservices.common.core.utils.html.EscapeUtil;
|
||||
import com.microservices.common.core.utils.uuid.IdUtils;
|
||||
import com.microservices.common.core.constant.SecurityConstants;
|
||||
import com.microservices.common.core.utils.ip.IpUtils;
|
||||
import com.microservices.common.httpClient.domain.GitLinkRequestUrl;
|
||||
import com.microservices.common.httpClient.util.GitLinkRequestHelper;
|
||||
import com.microservices.common.redis.service.RedisService;
|
||||
import com.microservices.common.security.auth.AuthUtil;
|
||||
import com.microservices.common.security.utils.SecurityUtils;
|
||||
import com.microservices.file.domain.FileUploadSession;
|
||||
import com.microservices.file.domain.SysFileDownloadLog;
|
||||
import com.microservices.file.mapper.SysFileDownloadLogMapper;
|
||||
import com.microservices.system.api.domain.SysFileDownloadLogVo;
|
||||
import com.microservices.file.mapper.SysFileInfoMapper;
|
||||
import com.microservices.file.service.IChunkedUploadService;
|
||||
import com.microservices.file.service.IFileCommonAsyncService;
|
||||
import com.microservices.file.service.ISysFileInfoAsyncService;
|
||||
import com.microservices.file.service.ISysFileInfoService;
|
||||
import com.microservices.file.utils.FileUploadUtils;
|
||||
import com.microservices.system.api.RemoteUserService;
|
||||
import com.microservices.system.api.domain.SysFileInfo;
|
||||
import com.microservices.system.api.domain.SysUser;
|
||||
import com.microservices.system.api.utils.FeignUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
|
@ -44,6 +53,7 @@ import java.nio.file.Path;
|
|||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 文件管理Service业务层处理
|
||||
|
|
@ -57,6 +67,10 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
@Autowired
|
||||
private SysFileInfoMapper sysFileInfoMapper;
|
||||
@Autowired
|
||||
private SysFileDownloadLogMapper sysFileDownloadLogMapper;
|
||||
@Autowired
|
||||
private RemoteUserService remoteUserService;
|
||||
@Autowired
|
||||
private GitLinkRequestHelper gitLinkRequestHelper;
|
||||
@Autowired
|
||||
private ISysFileInfoAsyncService fileInfoAsyncService;
|
||||
|
|
@ -178,8 +192,9 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
for (int i = 0; i < fileIds.length; i++) {
|
||||
Long fileId = fileIds[i];
|
||||
SysFileInfo sysFileInfo = sysFileInfoMapper.selectSysFileInfoByFileId(fileId);
|
||||
String filePath = localFilePath + StringUtils.substringAfter(sysFileInfo.getFilePath(), Constants.RESOURCE_PREFIX);
|
||||
FileUtils.deleteFile(filePath);
|
||||
if (sysFileInfo != null) {
|
||||
safeDeletePhysicalFile(sysFileInfo.getFilePath());
|
||||
}
|
||||
}
|
||||
return sysFileInfoMapper.deleteSysFileInfoByFileIds(fileIds);
|
||||
}
|
||||
|
|
@ -193,16 +208,18 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
@Override
|
||||
public int deleteSysFileInfoByFileId(Long fileId) {
|
||||
SysFileInfo sysFileInfo = sysFileInfoMapper.selectSysFileInfoByFileId(fileId);
|
||||
String filePath = localFilePath + StringUtils.substringAfter(sysFileInfo.getFilePath(), Constants.RESOURCE_PREFIX);
|
||||
FileUtils.deleteFile(filePath);
|
||||
if (sysFileInfo != null) {
|
||||
safeDeletePhysicalFile(sysFileInfo.getFilePath());
|
||||
}
|
||||
return sysFileInfoMapper.deleteSysFileInfoByFileId(fileId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteSysFileInfoByFileObjectName(String fileObjectName) {
|
||||
SysFileInfo sysFileInfo = sysFileInfoMapper.selectSysFileInfoByFileObjectName(fileObjectName);
|
||||
String filePath = localFilePath + StringUtils.substringAfter(sysFileInfo.getFilePath(), Constants.RESOURCE_PREFIX);
|
||||
FileUtils.deleteFile(filePath);
|
||||
if (sysFileInfo != null) {
|
||||
safeDeletePhysicalFile(sysFileInfo.getFilePath());
|
||||
}
|
||||
return sysFileInfoMapper.deleteSysFileInfoByFileObjectName(fileObjectName);
|
||||
}
|
||||
|
||||
|
|
@ -276,10 +293,10 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
@Override
|
||||
public void fileDownloadById(Long fileId, HttpServletResponse response, HttpServletRequest request) {
|
||||
SysFileInfo sysFileInfo = selectSysFileInfoByFileId(fileId);
|
||||
if (StringUtils.isNotEmpty(sysFileInfo.getFileIdentifier())) {
|
||||
throw new ServiceException("当前文件无法下载");
|
||||
}
|
||||
fileDownload(sysFileInfo, response);
|
||||
// if (StringUtils.isNotEmpty(sysFileInfo.getFileIdentifier())) {
|
||||
// throw new ServiceException("当前文件无法下载");
|
||||
// }
|
||||
fileDownload(sysFileInfo, response, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -288,7 +305,7 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
if (StringUtils.isEmpty(sysFileInfo.getFilePath())) {
|
||||
throw new ServiceException("当前文件无法下载");
|
||||
}
|
||||
fileDownload(sysFileInfo, response);
|
||||
fileDownload(sysFileInfo, response, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -402,6 +419,7 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
hierarchy = validPathSafety(hierarchy);
|
||||
}
|
||||
boolean isLocalUpload = true;
|
||||
boolean isHierarchyPath = false;
|
||||
Long fileId = null;
|
||||
switch (fileType) {
|
||||
case "pms-gitlink":
|
||||
|
|
@ -436,6 +454,7 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
sysFileInfo.setFileIdentifier(genFileIdentifier());
|
||||
baseFilePath += "/" + fileType + "/" + hierarchy + "/";
|
||||
isCommonFileType = false;
|
||||
isHierarchyPath = true;
|
||||
break;
|
||||
}
|
||||
case "dms": {
|
||||
|
|
@ -450,6 +469,13 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
isCommonFileType = false;
|
||||
break;
|
||||
}
|
||||
case "ai-resource": {
|
||||
sysFileInfo.setFileIdentifier(genFileIdentifier());
|
||||
baseFilePath += "/" + fileType + "/" + hierarchy + "/";
|
||||
isCommonFileType = false;
|
||||
isHierarchyPath = true;
|
||||
break;
|
||||
}
|
||||
case "zone-identifier": {
|
||||
// 生成唯一标识,防止用户通过id递增访问
|
||||
sysFileInfo.setFileIdentifier(genFileIdentifier());
|
||||
|
|
@ -512,7 +538,7 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
sysFileInfo.setFileOriginName(session.getFileName());
|
||||
}
|
||||
if (!isCommonFileType) {
|
||||
if ("pms".equals(fileType)) {
|
||||
if (isHierarchyPath) {
|
||||
filePath = EscapeUtil.removeExtraSlashOfUrl("/" + fileType + "/" + hierarchy + "/" + filePath);
|
||||
} else {
|
||||
filePath = EscapeUtil.removeExtraSlashOfUrl("/" + fileType + "/" + filePath);
|
||||
|
|
@ -603,7 +629,7 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
return fileIdentifier;
|
||||
}
|
||||
|
||||
private void fileDownload(SysFileInfo sysFileInfo, HttpServletResponse response) {
|
||||
private void fileDownload(SysFileInfo sysFileInfo, HttpServletResponse response, HttpServletRequest request) {
|
||||
try {
|
||||
String fileName = sysFileInfo.getFileOriginName();
|
||||
String filePath = localFilePath + sysFileInfo.getFilePath();
|
||||
|
|
@ -613,6 +639,8 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
||||
}
|
||||
FileUtils.setAttachmentResponseHeader(response, fileName);
|
||||
// 在写文件之前记录下载日志,避免写文件异常导致日志丢失
|
||||
recordDownloadLog(sysFileInfo.getFileId(), request);
|
||||
FileUtils.writeBytes(filePath, response.getOutputStream());
|
||||
int downloadCount = 1;
|
||||
if (sysFileInfo.getDownloadCount() != null) {
|
||||
|
|
@ -635,6 +663,38 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录文件下载日志(登录用户记录用户ID和用户名,未登录仅记录IP)
|
||||
* 参考 zone 模块 open 接口获取用户信息的方式:通过请求中的 GitLink Token 解析用户
|
||||
*/
|
||||
private void recordDownloadLog(Long fileId, HttpServletRequest request) {
|
||||
try {
|
||||
SysFileDownloadLog downloadLog = new SysFileDownloadLog();
|
||||
downloadLog.setFileId(fileId);
|
||||
// 通过 GitLink Token 获取用户信息(参考 zone OpenController.checkZoneRole 实现)
|
||||
String gitLinkToken = AuthUtil.getGitLinkRequestToken(request);
|
||||
if (StringUtils.isNotEmpty(gitLinkToken)) {
|
||||
String username = FeignUtils.getReturnData(
|
||||
remoteUserService.getUserNameByGitLinkToken(gitLinkToken, SecurityConstants.INNER)
|
||||
);
|
||||
if (StringUtils.isNotEmpty(username)) {
|
||||
downloadLog.setUserName(username);
|
||||
SysUser sysUser = FeignUtils.getReturnData(
|
||||
remoteUserService.getSysUserByUserName(username, SecurityConstants.INNER)
|
||||
);
|
||||
if (sysUser != null) {
|
||||
downloadLog.setUserId(sysUser.getUserId());
|
||||
}
|
||||
}
|
||||
}
|
||||
downloadLog.setDownloadTime(DateUtils.getNowDate());
|
||||
downloadLog.setIpAddr(IpUtils.getIpAddr(request));
|
||||
sysFileDownloadLogMapper.insertSysFileDownloadLog(downloadLog);
|
||||
} catch (Exception e) {
|
||||
logger.error("记录文件下载日志失败:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件大小校验
|
||||
*
|
||||
|
|
@ -684,4 +744,70 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
}
|
||||
return fileIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全删除物理文件:检查是否有其他数据库记录引用同一物理文件路径
|
||||
*/
|
||||
private void safeDeletePhysicalFile(String dbFilePath) {
|
||||
int refCount = sysFileInfoMapper.countByFilePath(dbFilePath);
|
||||
if (refCount <= 1) {
|
||||
String physicalPath = localFilePath + StringUtils.substringAfter(dbFilePath, Constants.RESOURCE_PREFIX);
|
||||
FileUtils.deleteFile(physicalPath);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SysFileDownloadLogVo> getDownloadUserListByFileIds(String fileIds) {
|
||||
if (StringUtils.isBlank(fileIds)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
String[] fileIdStrList = fileIds.split(",");
|
||||
Long[] fileIdArr = new Long[fileIdStrList.length];
|
||||
for (int i = 0; i < fileIdStrList.length; i++) {
|
||||
fileIdArr[i] = Long.valueOf(fileIdStrList[i].trim());
|
||||
}
|
||||
// 查询文件信息,构建 fileId -> fileOriginName 映射
|
||||
Map<Long, String> fileNameMap = new HashMap<>();
|
||||
for (Long fileId : fileIdArr) {
|
||||
SysFileInfo fileInfo = sysFileInfoMapper.selectSysFileInfoByFileId(fileId);
|
||||
if (fileInfo != null) {
|
||||
fileNameMap.put(fileId, fileInfo.getFileOriginName());
|
||||
}
|
||||
}
|
||||
List<SysFileDownloadLog> downloadLogList = sysFileDownloadLogMapper.selectDownloadLogByFileIds(fileIdArr);
|
||||
if (downloadLogList == null || downloadLogList.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return downloadLogList.stream().map(log -> {
|
||||
SysFileDownloadLogVo vo = new SysFileDownloadLogVo();
|
||||
vo.setFileId(log.getFileId());
|
||||
vo.setFileOriginName(fileNameMap.get(log.getFileId()));
|
||||
vo.setUserId(log.getUserId());
|
||||
vo.setUserName(log.getUserName());
|
||||
vo.setIpAddr(log.getIpAddr());
|
||||
vo.setDownloadTime(log.getDownloadTime());
|
||||
return vo;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SysFileInfo cloneFileInfo(SysFileInfo source) {
|
||||
SysFileInfo clone = new SysFileInfo();
|
||||
clone.setFileIdentifier(genFileIdentifier());
|
||||
clone.setFileOriginName(source.getFileOriginName());
|
||||
clone.setFileSuffix(source.getFileSuffix());
|
||||
clone.setFileSizeInfo(source.getFileSizeInfo());
|
||||
clone.setFileObjectName(source.getFileObjectName());
|
||||
clone.setFilePath(source.getFilePath());
|
||||
clone.setDownloadUrl(source.getDownloadUrl());
|
||||
clone.setContentType(source.getContentType());
|
||||
clone.setContentTypeName(source.getContentTypeName());
|
||||
clone.setContentTypeMessage(source.getContentTypeMessage());
|
||||
clone.setMimeType(source.getMimeType());
|
||||
clone.setIsPartialMatch(source.getIsPartialMatch());
|
||||
clone.setDelFlag("N");
|
||||
clone.setDownloadCount(0);
|
||||
sysFileInfoMapper.insertSysFileInfo(clone);
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.microservices.file.mapper.SysFileDownloadLogMapper">
|
||||
|
||||
<resultMap type="com.microservices.file.domain.SysFileDownloadLog" id="SysFileDownloadLogResult">
|
||||
<result property="id" column="id"/>
|
||||
<result property="fileId" column="file_id"/>
|
||||
<result property="userId" column="user_id"/>
|
||||
<result property="userName" column="user_name"/>
|
||||
<result property="downloadTime" column="download_time"/>
|
||||
<result property="ipAddr" column="ip_addr"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectSysFileDownloadLogVo">
|
||||
select id, file_id, user_id, user_name, download_time, ip_addr
|
||||
from sys_file_download_log
|
||||
</sql>
|
||||
|
||||
<insert id="insertSysFileDownloadLog" parameterType="com.microservices.file.domain.SysFileDownloadLog" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into sys_file_download_log
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="fileId != null">file_id,</if>
|
||||
<if test="userId != null">user_id,</if>
|
||||
<if test="userName != null and userName != ''">user_name,</if>
|
||||
<if test="downloadTime != null">download_time,</if>
|
||||
<if test="ipAddr != null and ipAddr != ''">ip_addr,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="fileId != null">#{fileId},</if>
|
||||
<if test="userId != null">#{userId},</if>
|
||||
<if test="userName != null and userName != ''">#{userName},</if>
|
||||
<if test="downloadTime != null">#{downloadTime},</if>
|
||||
<if test="ipAddr != null and ipAddr != ''">#{ipAddr},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<select id="selectDownloadLogByFileIds" resultMap="SysFileDownloadLogResult">
|
||||
<include refid="selectSysFileDownloadLogVo"/>
|
||||
where file_id in
|
||||
<foreach item="fileId" collection="fileIds" open="(" separator="," close=")">
|
||||
#{fileId}
|
||||
</foreach>
|
||||
order by download_time desc
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
|
@ -97,6 +97,7 @@
|
|||
<if test="contentTypeMessage != null">content_type_message,</if>
|
||||
<if test="mimeType != null">mime_type,</if>
|
||||
<if test="isPartialMatch != null">is_partial_match,</if>
|
||||
<if test="downloadCount != null">download_count,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="fileOriginName != null and fileOriginName != ''">#{fileOriginName},</if>
|
||||
|
|
@ -115,6 +116,7 @@
|
|||
<if test="contentTypeMessage != null">#{contentTypeMessage},</if>
|
||||
<if test="mimeType != null">#{mimeType},</if>
|
||||
<if test="isPartialMatch != null">#{isPartialMatch},</if>
|
||||
<if test="downloadCount != null">#{downloadCount},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
|
|
@ -159,4 +161,8 @@
|
|||
from sys_file_info
|
||||
where file_object_name = #{fileObjectName}
|
||||
</delete>
|
||||
|
||||
<select id="countByFilePath" resultType="int">
|
||||
select count(*) from sys_file_info where file_path = #{filePath} and del_flag = 'N'
|
||||
</select>
|
||||
</mapper>
|
||||
|
|
@ -139,6 +139,12 @@
|
|||
<version>3.6.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- Apache Commons Compress - 解决 ZIP 文件中文文件名 GBK 编码导致的 MALFORMED 问题 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>1.26.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
|||
|
|
@ -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<AiSkillParseResultVo> 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<UserResourceStatisticsVo> 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<UserProjectStatisticsVo> 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<JSONObject> myResource(@PathVariable("ids") String ids) {
|
||||
return new GenericsTableDataInfo<>(zoneResourceService.selectZoneResourcesIsLike(ids));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ZoneResourceDomain> 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<ZoneResourceDomain> resourceList(ZoneResourceDomainSearchVo zoneResourceDomainSearchVo) {
|
||||
startPage();
|
||||
List<ZoneResourceDomain> list = zoneResourceDomainService.selectZoneResourceDomainList(zoneResourceDomain);
|
||||
List<ZoneResourceDomain> 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<ZoneJournalsDataTableVo> journalsList(@Validated ZoneJournalsSearchVo searchVo) {
|
||||
startPage();
|
||||
List<ZoneJournalsParentDataVo> list = zoneJournalsService.selectJournalsList(searchVo);
|
||||
return genericsSuccess(ZoneJournalsDataTableVo.toZoneJournalsDataTableVo(getDataTable(list)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:}")
|
||||
|
|
|
|||
|
|
@ -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<ZoneJournalsParentDataVo> list(ZoneJournalsSearchVo searchVo) {
|
||||
startPage();
|
||||
List<ZoneJournalsParentDataVo> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<SimpleGitlinkFileInfo> attachments;
|
||||
|
||||
/** 评论数量 */
|
||||
@ApiModelProperty(value = "评论数量")
|
||||
private Integer comments_count = 0;
|
||||
|
||||
/** 是否详情 */
|
||||
@ApiModelProperty(value = "是否详情")
|
||||
private Boolean is_journal_detail = false;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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<ZoneJournalsChildrenDataVo> children_journals;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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<ZoneJournals> selectZoneJournalsList(ZoneJournals zoneJournals);
|
||||
|
||||
/**
|
||||
* 查询子评论列表
|
||||
*
|
||||
* @param parentId 父评论ID
|
||||
* @return 子评论集合
|
||||
*/
|
||||
List<ZoneJournals> 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);
|
||||
}
|
||||
|
|
@ -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<ZoneJournalsParentDataVo> 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);
|
||||
}
|
||||
|
|
@ -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<ZoneJournalsParentDataVo> 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<ZoneJournals> 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<ZoneJournals> 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<SysFileInfo> 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<ZoneJournals> childrenJournalsList = zoneJournalsMapper.selectChildrenZoneJournalsList(zoneJournals.getId());
|
||||
List<ZoneJournalsChildrenDataVo> 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<SimpleGitlinkFileInfo> getAttachments(String attachmentIdentifiers) {
|
||||
if (StringUtils.isEmpty(attachmentIdentifiers)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
try {
|
||||
List<SysFileInfo> 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<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<ZoneProjectDataVo> list(ZoneProjectSearchVo zoneProjectSearchVo) {
|
||||
if (Boolean.TRUE.equals(zoneProjectSearchVo.getIsMine())) {
|
||||
zoneProjectSearchVo.setCreateBy(SecurityUtils.getUsername());
|
||||
}
|
||||
return zoneProjectService.selectZoneProjectList(zoneProjectSearchVo, false);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,6 +122,15 @@ public interface IZoneProjectService {
|
|||
*/
|
||||
GenericsTableDataInfo<ZoneProjectDataVo> selectMyZoneProjectList(ZoneProjectSearchVo zoneProjectSearchVo);
|
||||
|
||||
/**
|
||||
* 查询用户项目统计信息
|
||||
*
|
||||
* @param zoneId
|
||||
* @param username 创建者用户名
|
||||
* @return 统计信息
|
||||
*/
|
||||
UserProjectStatisticsVo getUserProjectStatistics(Long zoneId, String username);
|
||||
|
||||
/**
|
||||
* 批量删除我的项目(校验创建者)
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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<ZoneProject> allProjects = zoneProjectMapper.selectZoneProjectList(query);
|
||||
|
||||
List<String> gitLinkProjectIdList = allProjects.stream()
|
||||
.map(x -> String.valueOf(x.getGitlinkProjectId()))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
int totalLikeCount = 0;
|
||||
if (!gitLinkProjectIdList.isEmpty()) {
|
||||
try {
|
||||
List<JSONObject> gitLinkProjectList = zoneAsyncService.getGitlinkProjectsDetail(gitLinkProjectIdList);
|
||||
Map<Long, JSONObject> 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) {
|
||||
|
|
|
|||
|
|
@ -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<AiSkillParseResultVo> 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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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<SysFileDownloadLogVo> downloadUserSummary;
|
||||
|
||||
@ApiModelProperty(value = "浏览次数")
|
||||
private Integer viewCount;
|
||||
|
||||
@ApiModelProperty(value = "点赞次数")
|
||||
private Integer likeCount;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String> downloadUserList;
|
||||
|
||||
public ZoneResourceFileVo(ZoneResourceType zoneResourceType, SysFileInfo sysFileInfo) {
|
||||
this.zoneResourceType = zoneResourceType;
|
||||
BeanUtils.copyProperties(sysFileInfo, this);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<JSONObject> {
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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删除资源领域
|
||||
|
|
|
|||
|
|
@ -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<ZoneResource> 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的资源)
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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<ZoneResourcesLike> selectByResourceIds(String[] idArr);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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<ZoneResourceDataVo> selectAiSkillResourceList(ZoneResource zoneResource);
|
||||
|
||||
/**
|
||||
* 查询AI模型资源列表
|
||||
*
|
||||
* @param zoneResource 查询条件
|
||||
* @return 分页数据
|
||||
*/
|
||||
GenericsTableDataInfo<ZoneResourceDataVo> selectAiModelResourceList(ZoneResource zoneResource);
|
||||
|
||||
/**
|
||||
* 查询AI数据集资源列表
|
||||
*
|
||||
* @param zoneResource 查询条件
|
||||
* @return 分页数据
|
||||
*/
|
||||
GenericsTableDataInfo<ZoneResourceDataVo> 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<ZoneResourceDataVo> 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<JSONObject> 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);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String> skillMdPaths = new ArrayList<>();
|
||||
List<FileTreeNode> 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<FileTreeNode> fileTreeNodes = new ArrayList<>();
|
||||
|
||||
Enumeration<ZipArchiveEntry> 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<FileTreeNode> nodes) throws IOException {
|
||||
// 优先查找根目录 README.md(忽略大小写)
|
||||
Enumeration<ZipArchiveEntry> 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<FileTreeNode> nodes) {
|
||||
Set<String> 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<FileTreeNode> nodes) {
|
||||
JSONObject root = new JSONObject();
|
||||
root.put("name", "root");
|
||||
root.put("type", "folder");
|
||||
JSONArray rootChildren = new JSONArray();
|
||||
root.put("children", rootChildren);
|
||||
|
||||
Map<String, JSONObject> 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<FileTreeNode> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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<ZoneResourceDataVo> selectZoneResourceList(ZoneResource zoneResourceSearch) {
|
||||
List<ZoneResource> zoneResourceList = zoneResourceMapper.selectZoneResourceList(zoneResourceSearch);
|
||||
return PageUtils.toPage(zoneResourceList, this::zoneResourceToData);
|
||||
return PageUtils.toPage(zoneResourceList, z -> zoneResourceToData(z, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@DataScope(deptAlias = "zr")
|
||||
public GenericsTableDataInfo<ZoneResourceDataVo> selectZoneResourceList(ZoneResourceOpenSearchVo zoneResourceSearchVo) {
|
||||
List<ZoneResource> 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<String> getAllFileSuffix(String fileIds) {
|
||||
List<SysFileInfo> sysFileInfoList = getAllFileByFileIds(fileIds);
|
||||
// 获取文件后缀列表
|
||||
List<String> suffixList = sysFileInfoList.stream()
|
||||
|
||||
return sysFileInfoList.stream()
|
||||
.map(x -> x.getFileSuffix().toLowerCase())
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return suffixList;
|
||||
}
|
||||
|
||||
public List<SysFileInfo> 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<SysFileDownloadLogVo> downloadLogList = FeignUtils.getReturnData(
|
||||
remoteFileService.getDownloadUserList(fileIds, SecurityConstants.INNER)
|
||||
);
|
||||
if (downloadLogList == null) {
|
||||
downloadLogList = new ArrayList<>();
|
||||
}
|
||||
// 补全用户详情(昵称、手机号、邮箱)
|
||||
enrichDownloadLogUserInfo(downloadLogList);
|
||||
// 按文件ID分组,填充到各附件VO中
|
||||
Map<Long, List<String>> 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<String> 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<SysFileDownloadLogVo> downloadLogList) {
|
||||
if (downloadLogList == null || downloadLogList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 收集所有非空的用户名(去重)
|
||||
Set<String> userNames = downloadLogList.stream()
|
||||
.map(SysFileDownloadLogVo::getUserName)
|
||||
.filter(StringUtils::isNotEmpty)
|
||||
.collect(Collectors.toSet());
|
||||
if (userNames.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 逐个查询用户信息并构建映射
|
||||
Map<String, SysUser> 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<ZoneResourceDataVo> selectMyZoneResourceList(ZoneResource zoneResourceSearch) {
|
||||
if (zoneResourceSearch == null || zoneResourceSearch.getZoneId() == null) {
|
||||
throw new ServiceException("获取我的资源列表时必须传入专区Id");
|
||||
}
|
||||
List<ZoneResource> 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<ZoneResourceToType> 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<ZoneResourceDataVo> selectAiSkillResourceList(ZoneResource zoneResource) {
|
||||
zoneResource.setResourceCategory(AiResourceCategory.SKILL.getCode());
|
||||
List<ZoneResource> list = zoneResourceMapper.selectZoneResourceList(zoneResource);
|
||||
return PageUtils.toPage(list, x -> zoneResourceToData(x, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public GenericsTableDataInfo<ZoneResourceDataVo> selectAiModelResourceList(ZoneResource zoneResource) {
|
||||
zoneResource.setResourceCategory(AiResourceCategory.MODEL.getCode());
|
||||
List<ZoneResource> list = zoneResourceMapper.selectZoneResourceList(zoneResource);
|
||||
return PageUtils.toPage(list, x -> zoneResourceToData(x, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public GenericsTableDataInfo<ZoneResourceDataVo> selectAiDatasetResourceList(ZoneResource zoneResource) {
|
||||
zoneResource.setResourceCategory(AiResourceCategory.DATASET.getCode());
|
||||
List<ZoneResource> 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<ZoneResource> 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<JSONObject> selectZoneResourcesIsLike(String ids) {
|
||||
List<JSONObject> list = new ArrayList<>();
|
||||
if (StringUtils.isNotEmpty(ids)) {
|
||||
String[] idArr = ids.split(",");
|
||||
List<ZoneResourcesLike> 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<ZoneResourceDataVo> selectSkillRecommendations(int limit) {
|
||||
List<ZoneResource> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,165 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.microservices.zone.journals.mapper.ZoneJournalsMapper">
|
||||
|
||||
<resultMap type="com.microservices.zone.journals.domain.ZoneJournals" id="ZoneJournalsResult">
|
||||
<result property="id" column="id" />
|
||||
<result property="journalizedId" column="journalized_id" />
|
||||
<result property="journalizedType" column="journalized_type" />
|
||||
<result property="userId" column="user_id" />
|
||||
<result property="notes" column="notes" />
|
||||
<result property="parentId" column="parent_id" />
|
||||
<result property="replyId" column="reply_id" />
|
||||
<result property="commentsCount" column="comments_count" />
|
||||
<result property="attachmentIdentifiers" column="attachment_identifiers"/>
|
||||
<result property="receiversLogin" column="receivers_login" />
|
||||
<result property="delFlag" column="del_flag" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createdOn" column="created_on" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updatedOn" column="updated_on" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectZoneJournalsVo">
|
||||
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
|
||||
</sql>
|
||||
|
||||
<select id="selectZoneJournalsList" parameterType="com.microservices.zone.journals.domain.ZoneJournals" resultMap="ZoneJournalsResult">
|
||||
<include refid="selectZoneJournalsVo"/>
|
||||
<where>
|
||||
<if test="journalizedId != null "> and journalized_id = #{journalizedId}</if>
|
||||
<if test="journalizedType != null and journalizedType != ''"> and journalized_type = #{journalizedType}</if>
|
||||
<if test="userId != null "> and user_id = #{userId}</if>
|
||||
and del_flag = '0'
|
||||
and parent_id is NULL
|
||||
</where>
|
||||
order by created_on desc
|
||||
</select>
|
||||
|
||||
<select id="selectZoneJournalsById" parameterType="Long" resultMap="ZoneJournalsResult">
|
||||
<include refid="selectZoneJournalsVo"/>
|
||||
where id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="selectChildrenZoneJournalsList" resultMap="ZoneJournalsResult">
|
||||
<include refid="selectZoneJournalsVo"/>
|
||||
where parent_id = #{parentId}
|
||||
and del_flag = '0'
|
||||
order by created_on asc
|
||||
</select>
|
||||
|
||||
<select id="countByJournalized" resultType="int">
|
||||
select count(*)
|
||||
from zone_journals
|
||||
where journalized_id = #{journalizedId}
|
||||
and journalized_type = #{journalizedType}
|
||||
and del_flag = '0'
|
||||
</select>
|
||||
|
||||
<insert id="insertZoneJournals" parameterType="com.microservices.zone.journals.domain.ZoneJournals" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into zone_journals
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="journalizedId != null">journalized_id,</if>
|
||||
<if test="journalizedType != null and journalizedType != ''">journalized_type,</if>
|
||||
<if test="userId != null">user_id,</if>
|
||||
<if test="notes != null and notes != ''">notes,</if>
|
||||
<if test="parentId != null">parent_id,</if>
|
||||
<if test="replyId != null">reply_id,</if>
|
||||
<if test="commentsCount != null">comments_count,</if>
|
||||
<if test="attachmentIdentifiers != null">attachment_identifiers,</if>
|
||||
<if test="receiversLogin != null">receivers_login,</if>
|
||||
<if test="delFlag != null">del_flag,</if>
|
||||
<if test="createBy != null">create_by,</if>
|
||||
<if test="createdOn != null">created_on,</if>
|
||||
<if test="updateBy != null">update_by,</if>
|
||||
<if test="updatedOn != null">updated_on,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="journalizedId != null">#{journalizedId},</if>
|
||||
<if test="journalizedType != null and journalizedType != ''">#{journalizedType},</if>
|
||||
<if test="userId != null">#{userId},</if>
|
||||
<if test="notes != null and notes != ''">#{notes},</if>
|
||||
<if test="parentId != null">#{parentId},</if>
|
||||
<if test="replyId != null">#{replyId},</if>
|
||||
<if test="commentsCount != null">#{commentsCount},</if>
|
||||
<if test="attachmentIdentifiers != null">#{attachmentIdentifiers},</if>
|
||||
<if test="receiversLogin != null">#{receiversLogin},</if>
|
||||
<if test="delFlag != null">#{delFlag},</if>
|
||||
<if test="createBy != null">#{createBy},</if>
|
||||
<if test="createdOn != null">#{createdOn},</if>
|
||||
<if test="updateBy != null">#{updateBy},</if>
|
||||
<if test="updatedOn != null">#{updatedOn},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<update id="updateZoneJournals" parameterType="com.microservices.zone.journals.domain.ZoneJournals">
|
||||
update zone_journals
|
||||
<trim prefix="SET" suffixOverrides=",">
|
||||
<if test="notes != null and notes != ''">notes = #{notes},</if>
|
||||
<if test="attachmentIdentifiers != null">attachment_identifiers = #{attachmentIdentifiers},</if>
|
||||
<if test="receiversLogin != null">receivers_login = #{receiversLogin},</if>
|
||||
<if test="updateBy != null">update_by = #{updateBy},</if>
|
||||
<if test="updatedOn != null">updated_on = #{updatedOn},</if>
|
||||
</trim>
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<update id="updateCommentsCountById">
|
||||
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>
|
||||
|
||||
<update id="updateJournalsIdByTypeAndOldId">
|
||||
update zone_journals
|
||||
set journalized_id = #{journalId}
|
||||
where journalized_type = #{type}
|
||||
and journalized_id = #{oldJournalId}
|
||||
</update>
|
||||
|
||||
<update id="deleteZoneJournalsById" parameterType="Long">
|
||||
update zone_journals set del_flag = '2' where id = #{id}
|
||||
</update>
|
||||
|
||||
<update id="deleteZoneJournalsByIds" parameterType="String">
|
||||
update zone_journals set del_flag = '2' where id in
|
||||
<foreach item="id" collection="array" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
<update id="deleteZoneJournalsByParentId">
|
||||
update zone_journals set del_flag = '2' where parent_id = #{parentId}
|
||||
</update>
|
||||
|
||||
<update id="deleteZoneJournalsByTypeAndJournalId">
|
||||
update zone_journals set del_flag = '2'
|
||||
where journalized_id = #{journalId}
|
||||
and journalized_type = #{type}
|
||||
</update>
|
||||
</mapper>
|
||||
|
|
@ -155,6 +155,7 @@
|
|||
</choose>
|
||||
<if test="isHomepage != null ">and zp.is_homepage = #{isHomepage}</if>
|
||||
<if test="excludeId != null ">and zp.id != #{excludeId}</if>
|
||||
<if test="createBy != null and createBy != ''">and zp.create_by = #{createBy}</if>
|
||||
|
||||
<!-- 数据范围过滤 -->
|
||||
${params.dataScope}
|
||||
|
|
@ -172,6 +173,7 @@
|
|||
<if test="name != null and name != ''">and zp.name like concat('%', #{name}, '%')</if>
|
||||
<if test="projectTypeId != null ">and zp.project_type_id = #{projectTypeId}</if>
|
||||
<if test="zoneId != null ">and zp.zone_id = #{zoneId}</if>
|
||||
<if test="createBy != null and createBy != ''">and zp.create_by = #{createBy}</if>
|
||||
<choose>
|
||||
<when test="excludesFailure != null and excludesFailure">
|
||||
and zp.audit_status != '2'
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
<result property="updateTime" column="update_time"/>
|
||||
<result property="zoneId" column="zone_id"/>
|
||||
<result property="introduction" column="introduction"/>
|
||||
<result property="domainCategory" column="domain_category"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectZoneResourceDomainVo">
|
||||
|
|
@ -29,7 +30,8 @@
|
|||
update_by,
|
||||
update_time,
|
||||
introduction,
|
||||
zone_id
|
||||
zone_id,
|
||||
domain_category
|
||||
from zone_resource_domain
|
||||
</sql>
|
||||
|
||||
|
|
@ -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
|
||||
</sql>
|
||||
|
||||
|
|
@ -52,6 +55,7 @@
|
|||
<include refid="selectZoneResourceDomainAliasVo"/>
|
||||
<where>
|
||||
<if test="name != null and name != ''">and zrd.name like concat('%', #{name}, '%')</if>
|
||||
<if test="domainCategory != null and domainCategory !=''">and zrd.domain_category = #{domainCategory}</if>
|
||||
<if test="sort != null ">and zrd.sort = #{sort}</if>
|
||||
<if test="deptId != null ">and zrd.dept_id = #{deptId}</if>
|
||||
<if test="zoneId != null ">and zrd.zone_id = #{zoneId}</if>
|
||||
|
|
@ -70,6 +74,7 @@
|
|||
<include refid="selectZoneResourceDomainVo"/>
|
||||
where name = #{name}
|
||||
and zone_id = #{zoneId}
|
||||
and domain_category = #{domainCategory}
|
||||
</select>
|
||||
|
||||
<insert id="insertZoneResourceDomain" parameterType="ZoneResourceDomain" useGeneratedKeys="true" keyProperty="id">
|
||||
|
|
@ -85,6 +90,7 @@
|
|||
<if test="updateTime != null">update_time,</if>
|
||||
<if test="zoneId != null">zone_id,</if>
|
||||
<if test="introduction != null">introduction,</if>
|
||||
<if test="domainCategory != null">domain_category,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="name != null and name != ''">#{name},</if>
|
||||
|
|
@ -97,6 +103,7 @@
|
|||
<if test="updateTime != null">#{updateTime},</if>
|
||||
<if test="zoneId != null">#{zoneId},</if>
|
||||
<if test="introduction != null">#{introduction},</if>
|
||||
<if test="domainCategory != null">#{domainCategory},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
|
|
@ -113,6 +120,7 @@
|
|||
<if test="updateTime != null">update_time = #{updateTime},</if>
|
||||
<if test="zoneId != null">zone_id = #{zoneId},</if>
|
||||
<if test="introduction != null">introduction = #{introduction},</if>
|
||||
<if test="domainCategory != null">domain_category = #{domainCategory},</if>
|
||||
</trim>
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@
|
|||
<result property="keywords" column="keywords"/>
|
||||
<result property="extendData" column="extend_data"/>
|
||||
<result property="earnedPoints" column="earned_points"/>
|
||||
<result property="extendData" column="extend_data"
|
||||
typeHandler="com.microservices.zone.resource.handler.JsonObjectTypeHandler"/>
|
||||
<result property="resourceCategory" column="resource_category"/>
|
||||
<result property="viewCount" column="view_count"/>
|
||||
<result property="likeCount" column="like_count"/>
|
||||
<association property="zoneResourceDomain" column="domain_id"
|
||||
javaType="com.microservices.zone.resource.domain.ZoneResourceDomain"
|
||||
resultMap="domainResult"/>
|
||||
|
|
@ -67,7 +72,10 @@
|
|||
update_time,
|
||||
keywords,
|
||||
extend_data,
|
||||
earned_points
|
||||
earned_points,
|
||||
resource_category,
|
||||
view_count,
|
||||
like_count
|
||||
from zone_resource
|
||||
</sql>
|
||||
|
||||
|
|
@ -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 @@
|
|||
<if test="deptId != null ">and zr.dept_id = #{deptId}</if>
|
||||
<if test="createBy != null and createBy != ''">and zr.create_by = #{createBy}</if>
|
||||
<if test="keywords != null and keywords != ''">and zr.keywords like concat('%', #{keywords}, '%')</if>
|
||||
<if test="resourceCategory != null and resourceCategory !=''">and zr.resource_category =
|
||||
#{resourceCategory}
|
||||
</if>
|
||||
<choose>
|
||||
<when test="auditStatus != null and auditStatus !=''">
|
||||
and zr.audit_status = #{auditStatus}
|
||||
<if test="auditStatus != 'all'">and zr.audit_status = #{auditStatus}</if>
|
||||
</when>
|
||||
<otherwise>
|
||||
and zr.audit_status = '1'
|
||||
|
|
@ -150,7 +167,20 @@
|
|||
</if>
|
||||
<!-- 数据范围过滤 -->
|
||||
${params.dataScope}
|
||||
ORDER BY zr.sort DESC
|
||||
<choose>
|
||||
<when test="resourceCategory != null and resourceCategory !=''">
|
||||
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
|
||||
</when>
|
||||
<otherwise>
|
||||
ORDER BY zr.sort DESC
|
||||
</otherwise>
|
||||
</choose>
|
||||
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
|
|
@ -166,6 +196,7 @@
|
|||
<if test="searchTypeId != null ">
|
||||
and zr.id in (select resource_id from zone_resource_to_type where type_id=#{searchTypeId})
|
||||
</if>
|
||||
<if test="resourceCategory != null and resourceCategory !=''">and zr.resource_category = #{resourceCategory}</if>
|
||||
<choose>
|
||||
<when test="auditStatus != null and auditStatus !=''">
|
||||
and zr.audit_status = #{auditStatus}
|
||||
|
|
@ -177,12 +208,17 @@
|
|||
<!-- 数据范围过滤 -->
|
||||
${params.dataScope}
|
||||
<choose>
|
||||
<when test="orderByDownloadCount != null and orderByDownloadCount">
|
||||
order by (select sum(download_count) from sys_file_info sf where FIND_IN_SET(sf.file_id,zr.file_ids)) desc
|
||||
</when>
|
||||
<when test="orderByDownloadCount = null and orderByColumn = null and orderByColumn =''">
|
||||
order by zr.sort DESC
|
||||
<when test="resourceCategory != null and resourceCategory !=''">
|
||||
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
|
||||
</when>
|
||||
<otherwise>
|
||||
ORDER BY zr.sort DESC
|
||||
</otherwise>
|
||||
</choose>
|
||||
</where>
|
||||
</sql>
|
||||
|
|
@ -206,6 +242,7 @@
|
|||
<include refid="selectZoneResourceVo"/>
|
||||
where name = #{name}
|
||||
and zone_id = #{zoneId}
|
||||
and resource_category = #{resourceCategory}
|
||||
</select>
|
||||
<select id="selectZoneResourceCount" resultType="java.lang.Integer">
|
||||
select count(0)
|
||||
|
|
@ -241,6 +278,9 @@
|
|||
<if test="remark != null">remark,</if>
|
||||
<if test="keywords != null">keywords,</if>
|
||||
<if test="extendData != null">extend_data,</if>
|
||||
<if test="resourceCategory != null">resource_category,</if>
|
||||
<if test="viewCount != null">view_count,</if>
|
||||
<if test="likeCount != null">like_count,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="name != null">#{name},</if>
|
||||
|
|
@ -263,7 +303,10 @@
|
|||
<if test="auditBy != null">#{auditBy},</if>
|
||||
<if test="remark != null">#{remark},</if>
|
||||
<if test="keywords != null">#{keywords},</if>
|
||||
<if test="extendData != null">#{extendData},</if>
|
||||
<if test="extendData != null">#{extendData, typeHandler=com.microservices.zone.resource.handler.JsonObjectTypeHandler},</if>
|
||||
<if test="resourceCategory != null">#{resourceCategory},</if>
|
||||
<if test="viewCount != null">#{viewCount},</if>
|
||||
<if test="likeCount != null">#{likeCount},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
|
|
@ -291,6 +334,10 @@
|
|||
<if test="keywords != null">keywords = #{keywords},</if>
|
||||
<if test="extendData != null">extend_data = #{extendData},</if>
|
||||
<if test="earnedPoints != null">earned_points = #{earnedPoints},</if>
|
||||
<if test="extendData != null">extend_data = #{extendData, typeHandler=com.microservices.zone.resource.handler.JsonObjectTypeHandler},</if>
|
||||
<if test="resourceCategory != null">resource_category = #{resourceCategory},</if>
|
||||
<if test="viewCount != null">view_count = #{viewCount},</if>
|
||||
<if test="likeCount != null">like_count = #{likeCount},</if>
|
||||
</trim>
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
|
@ -382,4 +429,27 @@
|
|||
set earned_points = IFNULL(earned_points, 0) + #{addPoints}
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<select id="selectSkillResourceByName" resultType="com.microservices.zone.resource.domain.ZoneResource">
|
||||
<include refid="selectZoneResourceVo"/>
|
||||
where name = #{name} and resource_category = 'SKILL' and del_flag = '0'
|
||||
</select>
|
||||
|
||||
<select id="selectSkillRecommendations" resultMap="ZoneResourceResult">
|
||||
<include refid="selectZoneResourceAliasVo"/>
|
||||
where zr.resource_category = #{resourceCategory}
|
||||
and zr.audit_status = #{auditStatus}
|
||||
and zr.del_flag = '0'
|
||||
order by (select sum(download_count) from sys_file_info sf where FIND_IN_SET(sf.file_id,zr.file_ids)) desc,
|
||||
zr.like_count desc, zr.view_count desc
|
||||
limit #{limit}
|
||||
</select>
|
||||
|
||||
<update id="incrementViewCount">
|
||||
update zone_resource set view_count = IFNULL(view_count, 0) + 1 where id = #{id}
|
||||
</update>
|
||||
|
||||
<update id="updateLikeCount">
|
||||
update zone_resource set like_count = IFNULL(like_count, 0) + #{delta} where id = #{id}
|
||||
</update>
|
||||
</mapper>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.microservices.zone.resource.mapper.ZoneResourcesLikeMapper">
|
||||
<resultMap type="com.microservices.zone.resource.domain.ZoneResourcesLike" id="ZoneResourcesLikeResult">
|
||||
<result property="id" column="id"/>
|
||||
<result property="resourceId" column="resource_id"/>
|
||||
<result property="userId" column="user_id"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectByResourceAndUser"
|
||||
resultMap="ZoneResourcesLikeResult">
|
||||
select id, resource_id, user_id, create_time
|
||||
from zone_resources_like
|
||||
where resource_id = #{resourceId} and user_id = #{userId}
|
||||
</select>
|
||||
<select id="selectByResourceIds"
|
||||
resultMap="ZoneResourcesLikeResult">
|
||||
select id, resource_id, user_id, create_time
|
||||
from zone_resources_like
|
||||
where resource_id in
|
||||
<foreach item="id" collection="array" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</select>
|
||||
|
||||
<insert id="insertLike" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into zone_resources_like (resource_id, user_id, create_time)
|
||||
values (#{resourceId}, #{userId}, #{createTime})
|
||||
</insert>
|
||||
|
||||
<delete id="deleteLike">
|
||||
delete from zone_resources_like
|
||||
where resource_id = #{resourceId} and user_id = #{userId}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
2
pom.xml
2
pom.xml
|
|
@ -27,7 +27,7 @@
|
|||
<pagehelper.boot.version>1.4.6</pagehelper.boot.version>
|
||||
<druid.version>1.2.16</druid.version>
|
||||
<dynamic-ds.version>3.5.2</dynamic-ds.version>
|
||||
<commons.io.version>2.11.0</commons.io.version>
|
||||
<commons.io.version>2.15.1</commons.io.version>
|
||||
<velocity.version>2.3</velocity.version>
|
||||
<fastjson.version>2.0.42</fastjson.version>
|
||||
<jjwt.version>0.9.1</jjwt.version>
|
||||
|
|
|
|||
Loading…
Reference in New Issue