Compare commits
82 Commits
master
...
aiResource
| Author | SHA1 | Date |
|---|---|---|
|
|
9d3b8d677a | |
|
|
8117c3ecc1 | |
|
|
c1cb8a6fa7 | |
|
|
318d565cd6 | |
|
|
e9021d5b50 | |
|
|
64d5b72a41 | |
|
|
052ede9a3c | |
|
|
c7be0e383c | |
|
|
42d1fcf1ac | |
|
|
d3c378bad7 | |
|
|
36a1f70963 | |
|
|
c9c237c901 | |
|
|
e388ee1560 | |
|
|
0a39106b05 | |
|
|
b72dd69408 | |
|
|
d0e44d8d2a | |
|
|
e98663e87b | |
|
|
a5e2efe2df | |
|
|
6f881dad5c | |
|
|
1883e78fcb | |
|
|
2b2cb706aa | |
|
|
1c414c2327 | |
|
|
bb23a02b45 | |
|
|
3b2b3c7d99 | |
|
|
b7b2600df6 | |
|
|
7357093ab1 | |
|
|
675a080b29 | |
|
|
a349b2e88f | |
|
|
0d042f50d6 | |
|
|
5459afcf7a | |
|
|
7b112fab92 | |
|
|
4344217563 | |
|
|
b73eece682 | |
|
|
c7983bd62c | |
|
|
7b81b7c73e | |
|
|
659293e2f6 | |
|
|
d2826fccbd | |
|
|
266e0a86a7 | |
|
|
eef339d228 | |
|
|
1fa2facb27 | |
|
|
7c0572fae1 | |
|
|
cd48af644e | |
|
|
c9f1566094 | |
|
|
3ad087a325 | |
|
|
a9dd3c779e | |
|
|
96a11998f3 | |
|
|
ec3249fab4 | |
|
|
fac5f3158f | |
|
|
084042891d | |
|
|
ea17f00fd8 | |
|
|
0a31382003 | |
|
|
fbbe78553d | |
|
|
fcefe43872 | |
|
|
3126082f61 | |
|
|
d20a84f214 | |
|
|
2acfd3c80d | |
|
|
a07c47b6b7 | |
|
|
41ec65b242 | |
|
|
0643fda6b6 | |
|
|
bc7c212bb6 | |
|
|
904bbf1cab | |
|
|
7628e38d3a | |
|
|
fb9da470e1 | |
|
|
22f095b01d | |
|
|
c39c826bdb | |
|
|
b7e2541c1d | |
|
|
91a96be75f | |
|
|
9f42c51a5f | |
|
|
5a55ea027b | |
|
|
cecc14868a | |
|
|
b3f5a4898f | |
|
|
965cedc275 | |
|
|
53ff26058f | |
|
|
46b1b9ed81 | |
|
|
20bd41ebf7 | |
|
|
a416b7c856 | |
|
|
d662395f1e | |
|
|
58bdc10a4b | |
|
|
343d31024e | |
|
|
020472c4c1 | |
|
|
356ffd8915 | |
|
|
3a2560f700 |
|
|
@ -4,6 +4,7 @@ import com.microservices.common.core.constant.SecurityConstants;
|
||||||
import com.microservices.common.core.constant.ServiceNameConstants;
|
import com.microservices.common.core.constant.ServiceNameConstants;
|
||||||
import com.microservices.common.core.domain.R;
|
import com.microservices.common.core.domain.R;
|
||||||
import com.microservices.system.api.domain.SysFile;
|
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.domain.SysFileInfo;
|
||||||
import com.microservices.system.api.factory.RemoteFileFallbackFactory;
|
import com.microservices.system.api.factory.RemoteFileFallbackFactory;
|
||||||
import feign.Response;
|
import feign.Response;
|
||||||
|
|
@ -52,6 +53,10 @@ public interface RemoteFileService {
|
||||||
@GetMapping("/open/getFile/{fileId}")
|
@GetMapping("/open/getFile/{fileId}")
|
||||||
public R<SysFileInfo> getFile(@PathVariable("fileId") Long 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获取文件信息
|
* 根据文件Id获取文件信息
|
||||||
*
|
*
|
||||||
|
|
@ -127,4 +132,15 @@ public interface RemoteFileService {
|
||||||
@PostMapping(value = "/common/uploadFileToForge/{fileIdentifiers}")
|
@PostMapping(value = "/common/uploadFileToForge/{fileIdentifiers}")
|
||||||
R<List<String>> uploadFileToForge(@PathVariable("fileIdentifiers") String fileIdentifiers,
|
R<List<String>> uploadFileToForge(@PathVariable("fileIdentifiers") String fileIdentifiers,
|
||||||
@RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
@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
|
@Data
|
||||||
public class SysFile
|
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.common.core.exception.ServiceException;
|
||||||
import com.microservices.system.api.RemoteFileService;
|
import com.microservices.system.api.RemoteFileService;
|
||||||
import com.microservices.system.api.domain.SysFile;
|
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.domain.SysFileInfo;
|
||||||
import feign.Response;
|
import feign.Response;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
@ -46,6 +47,11 @@ public class RemoteFileFallbackFactory implements FallbackFactory<RemoteFileServ
|
||||||
return R.fail("根据文件Id获取文件信息失败:" + throwable.getMessage());
|
return R.fail("根据文件Id获取文件信息失败:" + throwable.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R<SysFileInfo> getFileDetail(Long fileId, String source) {
|
||||||
|
return R.fail("根据文件id获取文件详细信息失败:" + throwable.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public R<SysFileInfo> getFileByFileIdentifier(String fileIdentifier, String source) {
|
public R<SysFileInfo> getFileByFileIdentifier(String fileIdentifier, String source) {
|
||||||
return R.fail("根据文件标识获取文件信息失败:" + throwable.getMessage());
|
return R.fail("根据文件标识获取文件信息失败:" + throwable.getMessage());
|
||||||
|
|
@ -80,6 +86,11 @@ public class RemoteFileFallbackFactory implements FallbackFactory<RemoteFileServ
|
||||||
public R<List<String>> uploadFileToForge(String fileIdentifiers, String source) {
|
public R<List<String>> uploadFileToForge(String fileIdentifiers, String source) {
|
||||||
return R.fail("上传本地文件到Forge失败:" + throwable.getMessage());
|
return R.fail("上传本地文件到Forge失败:" + throwable.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R<List<SysFileDownloadLogVo>> getDownloadUserList(String fileIds, String source) {
|
||||||
|
return R.fail("获取下载记录列表失败:" + throwable.getMessage());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,14 @@
|
||||||
<groupId>com.microservices</groupId>
|
<groupId>com.microservices</groupId>
|
||||||
<artifactId>microservices-common-security</artifactId>
|
<artifactId>microservices-common-security</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Microservices Common HttpClient -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.microservices</groupId>
|
||||||
|
<artifactId>microservices-common-httpClient</artifactId>
|
||||||
|
<version>3.6.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.microservices.auth.controller;
|
||||||
|
|
||||||
|
import com.microservices.auth.service.AiAgentTokenService;
|
||||||
|
import com.microservices.auth.vo.AIAgentTokenVo;
|
||||||
|
import com.microservices.common.core.domain.R;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Casdoor 预置账号 token 中转接口
|
||||||
|
*
|
||||||
|
* @author microservices
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
public class AiAgentController
|
||||||
|
{
|
||||||
|
@Autowired
|
||||||
|
private AiAgentTokenService aiAgentTokenService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 Astron Agent(Casdoor)access_token,前端凭此 token 自带 Bearer 头直连 Astron 业务接口。
|
||||||
|
* 网关路由后完整路径为 /auth/casdoor/token。
|
||||||
|
*/
|
||||||
|
@PostMapping("/aiAgent/login")
|
||||||
|
public R<AIAgentTokenVo> token()
|
||||||
|
{
|
||||||
|
return R.ok(aiAgentTokenService.getAccessToken());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,302 @@
|
||||||
|
package com.microservices.auth.service;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.microservices.auth.vo.AIAgentTokenVo;
|
||||||
|
import com.microservices.common.core.exception.ServiceException;
|
||||||
|
import com.microservices.common.core.utils.StringUtils;
|
||||||
|
import com.microservices.common.httpClient.service.HttpAPIService;
|
||||||
|
import com.microservices.common.redis.service.RedisService;
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.NameValuePair;
|
||||||
|
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||||
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AIAgent 预置账号 token 中转服务(Token 中转形态)。
|
||||||
|
* <p>
|
||||||
|
* 托管预置账号向 AIAgent 换取 access_token,前端凭此 token 直连 Astron Agent 业务接口。
|
||||||
|
* 7 天有效期的 token 走 Redis 缓存;并发首登通过 SETNX 分布式锁 + 双重检查防止击穿。
|
||||||
|
*
|
||||||
|
* @author microservices
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class AiAgentTokenService {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(AiAgentTokenService.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AIAgent 服务地址
|
||||||
|
*/
|
||||||
|
@Value("${aiAgent.url:}")
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 换 token 端点,留空则取 ${url}/api/login/oauth/access_token
|
||||||
|
*/
|
||||||
|
@Value("${aiAgent.token-endpoint:}")
|
||||||
|
private String tokenEndpoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuth client_id
|
||||||
|
*/
|
||||||
|
@Value("${aiAgent.client-id:4b25ca036879216a020a}")
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预置账号
|
||||||
|
*/
|
||||||
|
@Value("${aiAgent.username:}")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预置密码(敏感,走配置中心,勿入库)
|
||||||
|
*/
|
||||||
|
@Value("${aiAgent.password:}")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权范围
|
||||||
|
*/
|
||||||
|
@Value("${aiAgent.scope:profile}")
|
||||||
|
private String scope = "profile";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提前 N 秒视为过期,避免边界
|
||||||
|
*/
|
||||||
|
@Value("${aiAgent.token-advance-expire-seconds:60}")
|
||||||
|
private long tokenAdvanceExpireSeconds = 60L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token 缓存 key 前缀,完整 key:AIAgent:token:{clientId}:{username}
|
||||||
|
*/
|
||||||
|
private static final String CACHE_KEY_PREFIX = "AIAgent:token:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分布式锁 key 前缀,完整 key:AIAgent:lock:{username}
|
||||||
|
*/
|
||||||
|
private static final String LOCK_KEY_PREFIX = "AIAgent:lock:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 锁自动释放时间(秒),防止持锁线程异常导致死锁
|
||||||
|
*/
|
||||||
|
private static final long LOCK_TTL_SECONDS = 30L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未抢到锁时的单次等待时间(毫秒)
|
||||||
|
*/
|
||||||
|
private static final long LOCK_WAIT_MILLIS = 300L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未抢到锁时的最大等待次数
|
||||||
|
*/
|
||||||
|
private static final int LOCK_WAIT_MAX_RETRIES = 10;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private HttpAPIService httpAPIService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisService redisService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对外主入口:缓存命中且未过期直接返回;已过期则 refresh;无缓存则 password 登录。
|
||||||
|
*/
|
||||||
|
public AIAgentTokenVo getAccessToken() {
|
||||||
|
AIAgentTokenVo cached = getCache();
|
||||||
|
if (cached != null && !isExpired(cached)) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
String lockKey = lockKey();
|
||||||
|
String lockValue = UUID.randomUUID().toString();
|
||||||
|
if (tryLock(lockKey, lockValue)) {
|
||||||
|
try {
|
||||||
|
// 双重检查:抢锁期间可能已有其他线程写入缓存
|
||||||
|
cached = getCache();
|
||||||
|
if (cached != null && !isExpired(cached)) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
// 已有缓存但临近过期 → 优先 refresh,失败回退 password 登录
|
||||||
|
if (cached != null && StringUtils.isNotEmpty(cached.getRefreshToken())) {
|
||||||
|
try {
|
||||||
|
return refreshByRefreshToken(cached.getRefreshToken());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("AIAgent refresh 续期失败,回退到密码登录:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return loginByPassword();
|
||||||
|
} finally {
|
||||||
|
unlock(lockKey, lockValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未抢到锁:等待持锁线程写入缓存后复用,避免并发重复登录
|
||||||
|
for (int i = 0; i < LOCK_WAIT_MAX_RETRIES; i++) {
|
||||||
|
sleep(LOCK_WAIT_MILLIS);
|
||||||
|
cached = getCache();
|
||||||
|
if (cached != null && !isExpired(cached)) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 极端情况下仍未拿到缓存:降级直接登录,保证请求可用
|
||||||
|
log.warn("AIAgent token 锁等待超时,降级直接登录");
|
||||||
|
return loginByPassword();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* password grant 换取 token 并写缓存。
|
||||||
|
*/
|
||||||
|
public AIAgentTokenVo loginByPassword() {
|
||||||
|
List<NameValuePair> form = new ArrayList<>();
|
||||||
|
form.add(new BasicNameValuePair("grant_type", "password"));
|
||||||
|
form.add(new BasicNameValuePair("client_id", clientId));
|
||||||
|
form.add(new BasicNameValuePair("username", username));
|
||||||
|
form.add(new BasicNameValuePair("password", password));
|
||||||
|
form.add(new BasicNameValuePair("scope", scope));
|
||||||
|
return requestAndCache(form, "password");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* refresh grant 续期并更新缓存;失败抛异常由调用方回退处理。
|
||||||
|
*/
|
||||||
|
public AIAgentTokenVo refreshByRefreshToken(String refreshToken) {
|
||||||
|
List<NameValuePair> form = new ArrayList<>();
|
||||||
|
form.add(new BasicNameValuePair("grant_type", "refresh_token"));
|
||||||
|
form.add(new BasicNameValuePair("client_id", clientId));
|
||||||
|
form.add(new BasicNameValuePair("refresh_token", refreshToken));
|
||||||
|
form.add(new BasicNameValuePair("scope", scope));
|
||||||
|
return requestAndCache(form, "refresh_token");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用 AIAgent 换 token 端点并写缓存。
|
||||||
|
*/
|
||||||
|
private AIAgentTokenVo requestAndCache(List<NameValuePair> form, String grantType) {
|
||||||
|
AIAgentTokenVo token = requestToken(form, grantType);
|
||||||
|
setCache(token);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
private String resolveTokenEndpoint()
|
||||||
|
{
|
||||||
|
return StringUtils.isNotEmpty(tokenEndpoint) ? tokenEndpoint : url + "/api/login/oauth/access_token";
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 调用 AIAgent 换 token 端点,解析响应。
|
||||||
|
* <p>
|
||||||
|
* 注意:请求体为 form-urlencoded;AIAgent 即便业务失败也常返回 HTTP 200,
|
||||||
|
* 必须解析 body 中的 error 字段判断成败。
|
||||||
|
*/
|
||||||
|
private AIAgentTokenVo requestToken(List<NameValuePair> form, String grantType) {
|
||||||
|
ensureConfigured();
|
||||||
|
HttpEntity entity = new UrlEncodedFormEntity(form, StandardCharsets.UTF_8);
|
||||||
|
JSONObject resp;
|
||||||
|
try {
|
||||||
|
resp = httpAPIService.doPostFormData(resolveTokenEndpoint(), entity, null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("调用 AIAgent {} 端点失败:{}", grantType, e.getMessage());
|
||||||
|
throw new ServiceException("第三方登录服务暂不可用");
|
||||||
|
}
|
||||||
|
if (resp == null) {
|
||||||
|
throw new ServiceException("第三方登录服务响应为空");
|
||||||
|
}
|
||||||
|
if (resp.containsKey("error")) {
|
||||||
|
String desc = resp.getString("error_description");
|
||||||
|
if (StringUtils.isEmpty(desc)) {
|
||||||
|
desc = resp.getString("error");
|
||||||
|
}
|
||||||
|
log.error("AIAgent {} 失败:{}", grantType, resp);
|
||||||
|
throw new ServiceException("第三方登录失败:" + desc);
|
||||||
|
}
|
||||||
|
String accessToken = resp.getString("access_token");
|
||||||
|
if (StringUtils.isEmpty(accessToken)) {
|
||||||
|
log.error("AIAgent {} 响应缺少 access_token:{}", grantType, resp);
|
||||||
|
throw new ServiceException("第三方登录失败:未获取到 access_token");
|
||||||
|
}
|
||||||
|
Long expiresInObj = resp.getLong("expires_in");
|
||||||
|
long expiresIn = expiresInObj == null ? 0L : expiresInObj;
|
||||||
|
String tokenType = resp.getString("token_type");
|
||||||
|
if (StringUtils.isEmpty(tokenType)) {
|
||||||
|
tokenType = "Bearer";
|
||||||
|
}
|
||||||
|
long expiresAt = System.currentTimeMillis() + (expiresIn > 0 ? expiresIn * 1000L : 0L);
|
||||||
|
return new AIAgentTokenVo(accessToken, resp.getString("refresh_token"), tokenType, expiresAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureConfigured() {
|
||||||
|
if (StringUtils.isAnyBlank(url, clientId,
|
||||||
|
username, password)) {
|
||||||
|
throw new ServiceException("AIAgent 登录配置不完整,请检查配置中心");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 缓存 ----
|
||||||
|
|
||||||
|
private String cacheKey() {
|
||||||
|
return CACHE_KEY_PREFIX + clientId + ":" + username;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AIAgentTokenVo getCache() {
|
||||||
|
try {
|
||||||
|
return redisService.getCacheObject(cacheKey());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("读取 AIAgent token 缓存失败:{}", e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCache(AIAgentTokenVo token) {
|
||||||
|
// TTL = expires_in - 提前量,与"视为过期"的时间点对齐
|
||||||
|
long ttl = Math.max(1L, token.getExpiresIn() - tokenAdvanceExpireSeconds);
|
||||||
|
redisService.setCacheObject(cacheKey(), token, ttl, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isExpired(AIAgentTokenVo token) {
|
||||||
|
if (token == null || token.getExpiresAt() == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
long boundary = token.getExpiresAt() - tokenAdvanceExpireSeconds * 1000L;
|
||||||
|
return System.currentTimeMillis() >= boundary;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 分布式锁(基于 Redis SETNX + TTL)----
|
||||||
|
|
||||||
|
private String lockKey() {
|
||||||
|
return LOCK_KEY_PREFIX + username;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private boolean tryLock(String key, String value) {
|
||||||
|
Boolean ok = redisService.redisTemplate.opsForValue()
|
||||||
|
.setIfAbsent(key, value, LOCK_TTL_SECONDS, TimeUnit.SECONDS);
|
||||||
|
return Boolean.TRUE.equals(ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void unlock(String key, String value) {
|
||||||
|
try {
|
||||||
|
Object current = redisService.redisTemplate.opsForValue().get(key);
|
||||||
|
if (value.equals(current)) {
|
||||||
|
redisService.redisTemplate.delete(key);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("释放 AIAgent token 锁失败:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sleep(long millis) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(millis);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package com.microservices.auth.vo;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Casdoor(Astron Agent)token 中转返回对象
|
||||||
|
* <p>
|
||||||
|
* 同时作为 Redis 缓存值:accessToken/refreshToken/tokenType/expiresAt 持久化,
|
||||||
|
* expiresIn 由 expiresAt 实时计算。
|
||||||
|
*
|
||||||
|
* @author microservices
|
||||||
|
*/
|
||||||
|
public class AIAgentTokenVo implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** 访问令牌 */
|
||||||
|
private String accessToken;
|
||||||
|
|
||||||
|
/** 刷新令牌 */
|
||||||
|
private String refreshToken;
|
||||||
|
|
||||||
|
/** 令牌类型,恒为 Bearer */
|
||||||
|
private String tokenType;
|
||||||
|
|
||||||
|
/** 过期绝对时间戳(毫秒) */
|
||||||
|
private Long expiresAt;
|
||||||
|
|
||||||
|
public AIAgentTokenVo()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public AIAgentTokenVo(String accessToken, String refreshToken, String tokenType, Long expiresAt)
|
||||||
|
{
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
this.refreshToken = refreshToken;
|
||||||
|
this.tokenType = tokenType;
|
||||||
|
this.expiresAt = expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAccessToken()
|
||||||
|
{
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccessToken(String accessToken)
|
||||||
|
{
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRefreshToken()
|
||||||
|
{
|
||||||
|
return refreshToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRefreshToken(String refreshToken)
|
||||||
|
{
|
||||||
|
this.refreshToken = refreshToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTokenType()
|
||||||
|
{
|
||||||
|
return tokenType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTokenType(String tokenType)
|
||||||
|
{
|
||||||
|
this.tokenType = tokenType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getExpiresAt()
|
||||||
|
{
|
||||||
|
return expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpiresAt(Long expiresAt)
|
||||||
|
{
|
||||||
|
this.expiresAt = expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 距过期剩余秒数(实时计算,避免缓存命中后下发陈旧的剩余时间)
|
||||||
|
*/
|
||||||
|
public long getExpiresIn()
|
||||||
|
{
|
||||||
|
return expiresAt == null ? 0L : Math.max(0L, (expiresAt - System.currentTimeMillis()) / 1000L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -254,6 +254,15 @@ public class CacheConstants {
|
||||||
return PACKAGE_FILE_IS_FINISH + fileIdentifier;
|
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:";
|
public final static String GLOBAL_RESOURCES_DATA_KEY = "GLOBAL_RESOURCES_DATA:";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,17 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
@Aspect
|
@Aspect
|
||||||
@Component
|
@Component
|
||||||
public class LogAspect
|
public class LogAspect {
|
||||||
{
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
|
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");
|
private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|
@ -54,8 +57,7 @@ public class LogAspect
|
||||||
* 处理请求前执行
|
* 处理请求前执行
|
||||||
*/
|
*/
|
||||||
@Before(value = "@annotation(controllerLog)")
|
@Before(value = "@annotation(controllerLog)")
|
||||||
public void boBefore(JoinPoint joinPoint, Log controllerLog)
|
public void boBefore(JoinPoint joinPoint, Log controllerLog) {
|
||||||
{
|
|
||||||
TIME_THREADLOCAL.set(System.currentTimeMillis());
|
TIME_THREADLOCAL.set(System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,42 +67,41 @@ public class LogAspect
|
||||||
* @param joinPoint 切点
|
* @param joinPoint 切点
|
||||||
*/
|
*/
|
||||||
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
|
@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);
|
handleLog(joinPoint, controllerLog, null, jsonResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拦截异常操作
|
* 拦截异常操作
|
||||||
*
|
*
|
||||||
* @param joinPoint 切点
|
* @param joinPoint 切点
|
||||||
* @param e 异常
|
* @param e 异常
|
||||||
*/
|
*/
|
||||||
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "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);
|
handleLog(joinPoint, controllerLog, e, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult)
|
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {
|
||||||
{
|
try {
|
||||||
try
|
|
||||||
{
|
|
||||||
// *========数据库日志=========*//
|
// *========数据库日志=========*//
|
||||||
SysOperLog operLog = new SysOperLog();
|
SysOperLog operLog = new SysOperLog();
|
||||||
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
|
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
|
||||||
// 请求的地址
|
// 请求的地址
|
||||||
String ip = IpUtils.getIpAddr();
|
String ip = IpUtils.getIpAddr();
|
||||||
operLog.setOperIp(ip);
|
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();
|
String username = SecurityUtils.getUsername();
|
||||||
if (StringUtils.isNotBlank(username))
|
if (StringUtils.isNotBlank(username)) {
|
||||||
{
|
|
||||||
operLog.setOperName(username);
|
operLog.setOperName(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e != null)
|
if (e != null) {
|
||||||
{
|
|
||||||
operLog.setStatus(BusinessStatus.FAIL.ordinal());
|
operLog.setStatus(BusinessStatus.FAIL.ordinal());
|
||||||
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
|
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
|
||||||
}
|
}
|
||||||
|
|
@ -108,36 +109,29 @@ public class LogAspect
|
||||||
String className = joinPoint.getTarget().getClass().getName();
|
String className = joinPoint.getTarget().getClass().getName();
|
||||||
String methodName = joinPoint.getSignature().getName();
|
String methodName = joinPoint.getSignature().getName();
|
||||||
operLog.setMethod(className + "." + methodName + "()");
|
operLog.setMethod(className + "." + methodName + "()");
|
||||||
// 设置请求方式
|
|
||||||
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
|
|
||||||
// 处理设置注解上的参数
|
// 处理设置注解上的参数
|
||||||
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
|
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
|
||||||
// 设置消耗时间
|
// 设置消耗时间
|
||||||
operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
|
operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
|
||||||
// 保存数据库
|
// 保存数据库
|
||||||
asyncLogService.saveSysLog(operLog);
|
asyncLogService.saveSysLog(operLog);
|
||||||
}
|
} catch (Exception exp) {
|
||||||
catch (Exception exp)
|
|
||||||
{
|
|
||||||
// 记录本地异常日志
|
// 记录本地异常日志
|
||||||
log.error("异常信息:{}", exp.getMessage());
|
log.error("异常信息:{}", exp.getMessage());
|
||||||
exp.printStackTrace();
|
exp.printStackTrace();
|
||||||
}
|
} finally {
|
||||||
finally
|
|
||||||
{
|
|
||||||
TIME_THREADLOCAL.remove();
|
TIME_THREADLOCAL.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取注解中对方法的描述信息 用于Controller层注解
|
* 获取注解中对方法的描述信息 用于Controller层注解
|
||||||
*
|
*
|
||||||
* @param log 日志
|
* @param log 日志
|
||||||
* @param operLog 操作日志
|
* @param operLog 操作日志
|
||||||
* @throws Exception
|
* @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动作
|
// 设置action动作
|
||||||
operLog.setBusinessType(log.businessType().ordinal());
|
operLog.setBusinessType(log.businessType().ordinal());
|
||||||
// 设置标题
|
// 设置标题
|
||||||
|
|
@ -145,36 +139,30 @@ public class LogAspect
|
||||||
// 设置操作人类别
|
// 设置操作人类别
|
||||||
operLog.setOperatorType(log.operatorType().ordinal());
|
operLog.setOperatorType(log.operatorType().ordinal());
|
||||||
// 是否需要保存request,参数和值
|
// 是否需要保存request,参数和值
|
||||||
if (log.isSaveRequestData())
|
if (log.isSaveRequestData()) {
|
||||||
{
|
|
||||||
// 获取参数的信息,传入到数据库中。
|
// 获取参数的信息,传入到数据库中。
|
||||||
setRequestValue(joinPoint, operLog, log.excludeParamNames());
|
setRequestValue(joinPoint, operLog, log.excludeParamNames());
|
||||||
}
|
}
|
||||||
// 是否需要保存response,参数和值
|
// 是否需要保存response,参数和值
|
||||||
if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult))
|
if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) {
|
||||||
{
|
|
||||||
operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
|
operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取请求的参数,放到log中
|
* 获取请求的参数,放到log中
|
||||||
*
|
*
|
||||||
* @param operLog 操作日志
|
* @param operLog 操作日志
|
||||||
* @throws Exception 异常
|
* @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();
|
String requestMethod = operLog.getRequestMethod();
|
||||||
Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
|
Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
|
||||||
if (StringUtils.isEmpty(paramsMap)
|
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);
|
String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
|
||||||
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
|
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000));
|
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)
|
private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) {
|
||||||
{
|
StringBuilder params = new StringBuilder();
|
||||||
String params = "";
|
if (paramsArray != null) {
|
||||||
if (paramsArray != null && paramsArray.length > 0)
|
for (Object o : paramsArray) {
|
||||||
{
|
if (StringUtils.isNotNull(o) && !isFilterObject(o)) {
|
||||||
for (Object o : paramsArray)
|
try {
|
||||||
{
|
|
||||||
if (StringUtils.isNotNull(o) && !isFilterObject(o))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames));
|
String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames));
|
||||||
params += jsonObj.toString() + " ";
|
// 清洗控制字符(保留常见空白字符:\t, \n, \r)
|
||||||
}
|
// ASCII 0-8, 11-12, 14-31, 127 是控制字符,需要去除
|
||||||
catch (Exception e)
|
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));
|
return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断是否需要过滤的对象。
|
* 判断是否需要过滤的对象。
|
||||||
*
|
*
|
||||||
* @param o 对象信息。
|
* @param o 对象信息。
|
||||||
* @return 如果是需要过滤的对象,则返回true;否则返回false。
|
* @return 如果是需要过滤的对象,则返回true;否则返回false。
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public boolean isFilterObject(final Object o)
|
public boolean isFilterObject(final Object o) {
|
||||||
{
|
|
||||||
Class<?> clazz = o.getClass();
|
Class<?> clazz = o.getClass();
|
||||||
if (clazz.isArray())
|
if (clazz.isArray()) {
|
||||||
{
|
|
||||||
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
|
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
|
||||||
}
|
} else if (Collection.class.isAssignableFrom(clazz)) {
|
||||||
else if (Collection.class.isAssignableFrom(clazz))
|
|
||||||
{
|
|
||||||
Collection collection = (Collection) o;
|
Collection collection = (Collection) o;
|
||||||
for (Object value : collection)
|
for (Object value : collection) {
|
||||||
{
|
|
||||||
return value instanceof MultipartFile;
|
return value instanceof MultipartFile;
|
||||||
}
|
}
|
||||||
}
|
} else if (Map.class.isAssignableFrom(clazz)) {
|
||||||
else if (Map.class.isAssignableFrom(clazz))
|
|
||||||
{
|
|
||||||
Map map = (Map) o;
|
Map map = (Map) o;
|
||||||
for (Object value : map.entrySet())
|
for (Object value : map.entrySet()) {
|
||||||
{
|
|
||||||
Map.Entry entry = (Map.Entry) value;
|
Map.Entry entry = (Map.Entry) value;
|
||||||
return entry.getValue() instanceof MultipartFile;
|
return entry.getValue() instanceof MultipartFile;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.microservices.common.security.feign;
|
||||||
import feign.RequestInterceptor;
|
import feign.RequestInterceptor;
|
||||||
import feign.codec.Encoder;
|
import feign.codec.Encoder;
|
||||||
import feign.form.FormEncoder;
|
import feign.form.FormEncoder;
|
||||||
|
import feign.form.spring.SpringFormEncoder;
|
||||||
import org.springframework.beans.factory.ObjectFactory;
|
import org.springframework.beans.factory.ObjectFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
||||||
|
|
@ -33,6 +34,6 @@ public class FeignAutoConfiguration
|
||||||
// 重要的是返回类型要是 Encoder 并且实现类必须是 FormEncoder 或者其子类
|
// 重要的是返回类型要是 Encoder 并且实现类必须是 FormEncoder 或者其子类
|
||||||
@Bean
|
@Bean
|
||||||
public Encoder feignFormEncoder() {
|
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.nio.charset.StandardCharsets;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author otto
|
* @author otto
|
||||||
|
|
@ -51,6 +52,9 @@ public class CmsDocInputDto {
|
||||||
@ApiModelProperty("关键词")
|
@ApiModelProperty("关键词")
|
||||||
private String keywords;
|
private String keywords;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "发布时间,隐藏参数,内部调用使用", hidden = true)
|
||||||
|
private Date publishDate;
|
||||||
|
|
||||||
public String getContent() {
|
public String getContent() {
|
||||||
String base64decodedString;
|
String base64decodedString;
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
@ -366,6 +367,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
||||||
boolean success = insertCommonCmsDoc(cmsDoc,
|
boolean success = insertCommonCmsDoc(cmsDoc,
|
||||||
"创建文章:" + StringUtils.truncateStringAndConcatenatingSpecifiedString(cmsDoc.getName(), 100, "..."),
|
"创建文章:" + StringUtils.truncateStringAndConcatenatingSpecifiedString(cmsDoc.getName(), 100, "..."),
|
||||||
cmsDocInputDto.getContent());
|
cmsDocInputDto.getContent());
|
||||||
|
innerInitPublishDate(cmsDoc,cmsDocInputDto.getPublishDate());
|
||||||
return success ? cmsDoc.getId().intValue() : null;
|
return success ? cmsDoc.getId().intValue() : null;
|
||||||
} else {
|
} else {
|
||||||
//创建临时分支,创建文件,创建PR
|
//创建临时分支,创建文件,创建PR
|
||||||
|
|
@ -375,11 +377,21 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
||||||
boolean success = insertCmsDocWithAudit(cmsDoc,
|
boolean success = insertCmsDocWithAudit(cmsDoc,
|
||||||
"创建文章:" + StringUtils.truncateStringAndConcatenatingSpecifiedString(cmsDoc.getName(), 100, "..."),
|
"创建文章:" + StringUtils.truncateStringAndConcatenatingSpecifiedString(cmsDoc.getName(), 100, "..."),
|
||||||
cmsDocInputDto.getContent());
|
cmsDocInputDto.getContent());
|
||||||
|
innerInitPublishDate(cmsDoc,cmsDocInputDto.getPublishDate());
|
||||||
return success ? cmsDoc.getId().intValue() : null;
|
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) {
|
private boolean isCurrentUserZoneAdmin(Long deptId) {
|
||||||
boolean isZoneAdmin = false;
|
boolean isZoneAdmin = false;
|
||||||
List<SysUserDeptRole> sysUserDeptRoleList = FeignUtils.getReturnData(
|
List<SysUserDeptRole> sysUserDeptRoleList = FeignUtils.getReturnData(
|
||||||
|
|
@ -1215,7 +1227,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
||||||
private void checkDocName(String docName, Long deptId, String dirName) {
|
private void checkDocName(String docName, Long deptId, String dirName) {
|
||||||
//检查该文件是否已存在
|
//检查该文件是否已存在
|
||||||
CmsDoc oldCmsDoc = cmsDocMapper.selectCmsDocByNameAndDeptId(docName, deptId, dirName);
|
CmsDoc oldCmsDoc = cmsDocMapper.selectCmsDocByNameAndDeptId(docName, deptId, dirName);
|
||||||
if (oldCmsDoc != null) {
|
if (oldCmsDoc != null && DocAuditStatus.AUDIT_PASS.getAuditCode().equals(oldCmsDoc.getAuditStatus())) {
|
||||||
if (oldCmsDoc.getIsDir()) {
|
if (oldCmsDoc.getIsDir()) {
|
||||||
throw new ServiceException("该栏目已存在(栏目名称[%s])", docName);
|
throw new ServiceException("该栏目已存在(栏目名称[%s])", docName);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1228,7 +1240,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
||||||
private boolean insertCmsDocWithAudit(CmsDoc cmsDoc, String message, String content) {
|
private boolean insertCmsDocWithAudit(CmsDoc cmsDoc, String message, String content) {
|
||||||
//检查该文件是否已存在
|
//检查该文件是否已存在
|
||||||
CmsDoc oldCmsDoc = cmsDocMapper.selectCmsDocByNameAndDeptId(cmsDoc.getName(), cmsDoc.getDeptId(), cmsDoc.getDirName());
|
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() + "])");
|
throw new ServiceException("该文章已存在(文章名称[" + cmsDoc.getName() + "])");
|
||||||
}
|
}
|
||||||
boolean insertResult = insertCmsDocDatabase(cmsDoc, content);
|
boolean insertResult = insertCmsDocDatabase(cmsDoc, content);
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ public class CmsGitLinkRequestUrl extends GitLinkRequestUrl {
|
||||||
public static GitLinkRequestUrl ADD_USER_TO_ORGANIZATION_TEAM(String orgIdentifier, Long teamId) {
|
public static GitLinkRequestUrl ADD_USER_TO_ORGANIZATION_TEAM(String orgIdentifier, Long teamId) {
|
||||||
return getAdminGitLinkRequestUrl(String.format("/api/organizations/%s/teams/%d/team_users.json", orgIdentifier, teamId));
|
return getAdminGitLinkRequestUrl(String.format("/api/organizations/%s/teams/%d/team_users.json", orgIdentifier, teamId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String TEAMS_BY_ORGANIZATION_URL(Long orgId) {
|
public static String TEAMS_BY_ORGANIZATION_URL(Long orgId) {
|
||||||
return String.format("/api/organizations/%s/teams.json", orgId);
|
return String.format("/api/organizations/%s/teams.json", orgId);
|
||||||
}
|
}
|
||||||
|
|
@ -112,7 +113,7 @@ public class CmsGitLinkRequestUrl extends GitLinkRequestUrl {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GitLinkRequestUrl ADD_USER_FROM_PROJECT(CmsProject cmsProject) {
|
public static GitLinkRequestUrl ADD_USER_FROM_PROJECT(CmsProject cmsProject) {
|
||||||
return getGitLinkRequestUrl(
|
return getAdminGitLinkRequestUrl(
|
||||||
String.format("/api/%s/%s/collaborators.json", cmsProject.getUsername(), cmsProject.getIdentifier())
|
String.format("/api/%s/%s/collaborators.json", cmsProject.getUsername(), cmsProject.getIdentifier())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,7 @@
|
||||||
and is_dir = 1
|
and is_dir = 1
|
||||||
</otherwise>
|
</otherwise>
|
||||||
</choose>
|
</choose>
|
||||||
|
and audit_status = '1'
|
||||||
</where>
|
</where>
|
||||||
</select>
|
</select>
|
||||||
<select id="selectCmsDocDirByNameAndDeptId" resultMap="CmsDocResult">
|
<select id="selectCmsDocDirByNameAndDeptId" resultMap="CmsDocResult">
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,7 @@ public class ChunkedUploadController extends BaseController {
|
||||||
return genericsSuccess(result);
|
return genericsSuccess(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 屏蔽取消上传接口
|
||||||
/**
|
/**
|
||||||
* 取消上传
|
* 取消上传
|
||||||
* <p>
|
* <p>
|
||||||
|
|
@ -147,7 +148,6 @@ public class ChunkedUploadController extends BaseController {
|
||||||
*
|
*
|
||||||
* @param uploadId 上传ID
|
* @param uploadId 上传ID
|
||||||
* @return 取消结果
|
* @return 取消结果
|
||||||
*/
|
|
||||||
@DeleteMapping("/cancel/{uploadId}")
|
@DeleteMapping("/cancel/{uploadId}")
|
||||||
@ApiOperation("取消上传")
|
@ApiOperation("取消上传")
|
||||||
@ApiImplicitParams({
|
@ApiImplicitParams({
|
||||||
|
|
@ -157,6 +157,7 @@ public class ChunkedUploadController extends BaseController {
|
||||||
boolean result = chunkedUploadService.cancelUpload(uploadId);
|
boolean result = chunkedUploadService.cancelUpload(uploadId);
|
||||||
return genericsSuccess(result ? CancelUploadResponseVo.success() : CancelUploadResponseVo.failed("取消上传失败"));
|
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.core.web.domain.AjaxResult;
|
||||||
import com.microservices.common.security.annotation.InnerAuth;
|
import com.microservices.common.security.annotation.InnerAuth;
|
||||||
import com.microservices.file.service.ISysFileInfoService;
|
import com.microservices.file.service.ISysFileInfoService;
|
||||||
|
import com.microservices.system.api.domain.SysFileDownloadLogVo;
|
||||||
import com.microservices.system.api.domain.SysFileInfo;
|
import com.microservices.system.api.domain.SysFileInfo;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiImplicitParam;
|
import io.swagger.annotations.ApiImplicitParam;
|
||||||
|
|
@ -235,4 +236,29 @@ public class CommonController extends BaseController {
|
||||||
R<List<String>> uploadFileToForge(@PathVariable("fileIdentifiers") String fileIdentifiers) {
|
R<List<String>> uploadFileToForge(@PathVariable("fileIdentifiers") String fileIdentifiers) {
|
||||||
return R.ok(sysFileInfoService.uploadFileToForge(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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
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.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import springfox.documentation.annotations.ApiIgnore;
|
import springfox.documentation.annotations.ApiIgnore;
|
||||||
|
|
@ -33,7 +34,7 @@ public class SysFileController extends BaseController {
|
||||||
*/
|
*/
|
||||||
@InnerAuth
|
@InnerAuth
|
||||||
@PostMapping("upload")
|
@PostMapping("upload")
|
||||||
public R<SysFile> upload(MultipartFile file,
|
public R<SysFile> upload(@RequestPart(value = "file") MultipartFile file,
|
||||||
@RequestParam("type") String type,
|
@RequestParam("type") String type,
|
||||||
@RequestParam("hierarchy") String hierarchy) {
|
@RequestParam("hierarchy") String hierarchy) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -43,6 +44,7 @@ public class SysFileController extends BaseController {
|
||||||
sysFile.setName(fileInfo.getFilePath());
|
sysFile.setName(fileInfo.getFilePath());
|
||||||
sysFile.setUrl(fileInfo.getDownloadUrl());
|
sysFile.setUrl(fileInfo.getDownloadUrl());
|
||||||
sysFile.setFileIdentifier(fileInfo.getFileIdentifier());
|
sysFile.setFileIdentifier(fileInfo.getFileIdentifier());
|
||||||
|
sysFile.setFileId(fileInfo.getFileId());
|
||||||
return R.ok(sysFile);
|
return R.ok(sysFile);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("上传文件失败", 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 com.microservices.system.api.domain.SysFileInfo;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -85,4 +86,9 @@ public interface SysFileInfoMapper {
|
||||||
* @return 文件
|
* @return 文件
|
||||||
*/
|
*/
|
||||||
SysFileInfo selectSysFileInfoByIdentifier(String fileIdentifier);
|
SysFileInfo selectSysFileInfoByIdentifier(String fileIdentifier);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计引用同一物理文件路径的记录数
|
||||||
|
*/
|
||||||
|
int countByFilePath(@Param("filePath") String filePath);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.microservices.file.service;
|
package com.microservices.file.service;
|
||||||
|
|
||||||
import com.microservices.file.domain.FileUploadSession;
|
import com.microservices.file.domain.FileUploadSession;
|
||||||
|
import com.microservices.system.api.domain.SysFileDownloadLogVo;
|
||||||
import com.microservices.system.api.domain.SysFileInfo;
|
import com.microservices.system.api.domain.SysFileInfo;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
|
@ -8,6 +9,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件管理Service接口
|
* 文件管理Service接口
|
||||||
|
|
@ -177,4 +179,18 @@ public interface ISysFileInfoService {
|
||||||
List<String> uploadFileToForge(String fileIdentifiers);
|
List<String> uploadFileToForge(String fileIdentifiers);
|
||||||
|
|
||||||
String downloadFileByUrl(String fileUrl, String type, String hierarchy);
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -215,13 +215,15 @@ public class ChunkedUploadServiceImpl implements IChunkedUploadService {
|
||||||
// 清理临时文件
|
// 清理临时文件
|
||||||
cleanupTempFiles(session);
|
cleanupTempFiles(session);
|
||||||
|
|
||||||
// 返回已存在文件信息
|
// 基于原文件创建新的数据库记录(独立downloadCount,共享物理文件)
|
||||||
|
SysFileInfo newFileInfo = fileInfoService.cloneFileInfo(fileInfo);
|
||||||
|
|
||||||
return CheckInstantUploadVo.instantSuccess(
|
return CheckInstantUploadVo.instantSuccess(
|
||||||
fileInfo.getFileId(),
|
newFileInfo.getFileId(),
|
||||||
fileInfo.getFileOriginName(),
|
newFileInfo.getFileOriginName(),
|
||||||
fileInfo.getFilePath(),
|
newFileInfo.getFilePath(),
|
||||||
fileInfo.getDownloadUrl(),
|
newFileInfo.getDownloadUrl(),
|
||||||
fileInfo.getFileSizeInfo()
|
newFileInfo.getFileSizeInfo()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.file.FileUtils;
|
||||||
import com.microservices.common.core.utils.html.EscapeUtil;
|
import com.microservices.common.core.utils.html.EscapeUtil;
|
||||||
import com.microservices.common.core.utils.uuid.IdUtils;
|
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.domain.GitLinkRequestUrl;
|
||||||
import com.microservices.common.httpClient.util.GitLinkRequestHelper;
|
import com.microservices.common.httpClient.util.GitLinkRequestHelper;
|
||||||
import com.microservices.common.redis.service.RedisService;
|
import com.microservices.common.redis.service.RedisService;
|
||||||
|
import com.microservices.common.security.auth.AuthUtil;
|
||||||
import com.microservices.common.security.utils.SecurityUtils;
|
import com.microservices.common.security.utils.SecurityUtils;
|
||||||
import com.microservices.file.domain.FileUploadSession;
|
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.mapper.SysFileInfoMapper;
|
||||||
import com.microservices.file.service.IChunkedUploadService;
|
import com.microservices.file.service.IChunkedUploadService;
|
||||||
import com.microservices.file.service.IFileCommonAsyncService;
|
import com.microservices.file.service.IFileCommonAsyncService;
|
||||||
import com.microservices.file.service.ISysFileInfoAsyncService;
|
import com.microservices.file.service.ISysFileInfoAsyncService;
|
||||||
import com.microservices.file.service.ISysFileInfoService;
|
import com.microservices.file.service.ISysFileInfoService;
|
||||||
import com.microservices.file.utils.FileUploadUtils;
|
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.SysFileInfo;
|
||||||
|
import com.microservices.system.api.domain.SysUser;
|
||||||
|
import com.microservices.system.api.utils.FeignUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
@ -44,6 +53,7 @@ import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件管理Service业务层处理
|
* 文件管理Service业务层处理
|
||||||
|
|
@ -57,6 +67,10 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
||||||
@Autowired
|
@Autowired
|
||||||
private SysFileInfoMapper sysFileInfoMapper;
|
private SysFileInfoMapper sysFileInfoMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
private SysFileDownloadLogMapper sysFileDownloadLogMapper;
|
||||||
|
@Autowired
|
||||||
|
private RemoteUserService remoteUserService;
|
||||||
|
@Autowired
|
||||||
private GitLinkRequestHelper gitLinkRequestHelper;
|
private GitLinkRequestHelper gitLinkRequestHelper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISysFileInfoAsyncService fileInfoAsyncService;
|
private ISysFileInfoAsyncService fileInfoAsyncService;
|
||||||
|
|
@ -173,8 +187,9 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
||||||
for (int i = 0; i < fileIds.length; i++) {
|
for (int i = 0; i < fileIds.length; i++) {
|
||||||
Long fileId = fileIds[i];
|
Long fileId = fileIds[i];
|
||||||
SysFileInfo sysFileInfo = sysFileInfoMapper.selectSysFileInfoByFileId(fileId);
|
SysFileInfo sysFileInfo = sysFileInfoMapper.selectSysFileInfoByFileId(fileId);
|
||||||
String filePath = localFilePath + StringUtils.substringAfter(sysFileInfo.getFilePath(), Constants.RESOURCE_PREFIX);
|
if (sysFileInfo != null) {
|
||||||
FileUtils.deleteFile(filePath);
|
safeDeletePhysicalFile(sysFileInfo.getFilePath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return sysFileInfoMapper.deleteSysFileInfoByFileIds(fileIds);
|
return sysFileInfoMapper.deleteSysFileInfoByFileIds(fileIds);
|
||||||
}
|
}
|
||||||
|
|
@ -188,16 +203,18 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
||||||
@Override
|
@Override
|
||||||
public int deleteSysFileInfoByFileId(Long fileId) {
|
public int deleteSysFileInfoByFileId(Long fileId) {
|
||||||
SysFileInfo sysFileInfo = sysFileInfoMapper.selectSysFileInfoByFileId(fileId);
|
SysFileInfo sysFileInfo = sysFileInfoMapper.selectSysFileInfoByFileId(fileId);
|
||||||
String filePath = localFilePath + StringUtils.substringAfter(sysFileInfo.getFilePath(), Constants.RESOURCE_PREFIX);
|
if (sysFileInfo != null) {
|
||||||
FileUtils.deleteFile(filePath);
|
safeDeletePhysicalFile(sysFileInfo.getFilePath());
|
||||||
|
}
|
||||||
return sysFileInfoMapper.deleteSysFileInfoByFileId(fileId);
|
return sysFileInfoMapper.deleteSysFileInfoByFileId(fileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int deleteSysFileInfoByFileObjectName(String fileObjectName) {
|
public int deleteSysFileInfoByFileObjectName(String fileObjectName) {
|
||||||
SysFileInfo sysFileInfo = sysFileInfoMapper.selectSysFileInfoByFileObjectName(fileObjectName);
|
SysFileInfo sysFileInfo = sysFileInfoMapper.selectSysFileInfoByFileObjectName(fileObjectName);
|
||||||
String filePath = localFilePath + StringUtils.substringAfter(sysFileInfo.getFilePath(), Constants.RESOURCE_PREFIX);
|
if (sysFileInfo != null) {
|
||||||
FileUtils.deleteFile(filePath);
|
safeDeletePhysicalFile(sysFileInfo.getFilePath());
|
||||||
|
}
|
||||||
return sysFileInfoMapper.deleteSysFileInfoByFileObjectName(fileObjectName);
|
return sysFileInfoMapper.deleteSysFileInfoByFileObjectName(fileObjectName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,10 +288,10 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
||||||
@Override
|
@Override
|
||||||
public void fileDownloadById(Long fileId, HttpServletResponse response, HttpServletRequest request) {
|
public void fileDownloadById(Long fileId, HttpServletResponse response, HttpServletRequest request) {
|
||||||
SysFileInfo sysFileInfo = selectSysFileInfoByFileId(fileId);
|
SysFileInfo sysFileInfo = selectSysFileInfoByFileId(fileId);
|
||||||
if (StringUtils.isNotEmpty(sysFileInfo.getFileIdentifier())) {
|
// if (StringUtils.isNotEmpty(sysFileInfo.getFileIdentifier())) {
|
||||||
throw new ServiceException("当前文件无法下载");
|
// throw new ServiceException("当前文件无法下载");
|
||||||
}
|
// }
|
||||||
fileDownload(sysFileInfo, response);
|
fileDownload(sysFileInfo, response, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -283,7 +300,7 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
||||||
if (StringUtils.isEmpty(sysFileInfo.getFilePath())) {
|
if (StringUtils.isEmpty(sysFileInfo.getFilePath())) {
|
||||||
throw new ServiceException("当前文件无法下载");
|
throw new ServiceException("当前文件无法下载");
|
||||||
}
|
}
|
||||||
fileDownload(sysFileInfo, response);
|
fileDownload(sysFileInfo, response, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -397,6 +414,7 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
||||||
hierarchy = validPathSafety(hierarchy);
|
hierarchy = validPathSafety(hierarchy);
|
||||||
}
|
}
|
||||||
boolean isLocalUpload = true;
|
boolean isLocalUpload = true;
|
||||||
|
boolean isHierarchyPath = false;
|
||||||
Long fileId = null;
|
Long fileId = null;
|
||||||
switch (fileType) {
|
switch (fileType) {
|
||||||
case "pms-gitlink":
|
case "pms-gitlink":
|
||||||
|
|
@ -431,6 +449,7 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
||||||
sysFileInfo.setFileIdentifier(genFileIdentifier());
|
sysFileInfo.setFileIdentifier(genFileIdentifier());
|
||||||
baseFilePath += "/" + fileType + "/" + hierarchy + "/";
|
baseFilePath += "/" + fileType + "/" + hierarchy + "/";
|
||||||
isCommonFileType = false;
|
isCommonFileType = false;
|
||||||
|
isHierarchyPath = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "dms": {
|
case "dms": {
|
||||||
|
|
@ -445,6 +464,13 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
||||||
isCommonFileType = false;
|
isCommonFileType = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "ai-resource": {
|
||||||
|
sysFileInfo.setFileIdentifier(genFileIdentifier());
|
||||||
|
baseFilePath += "/" + fileType + "/" + hierarchy + "/";
|
||||||
|
isCommonFileType = false;
|
||||||
|
isHierarchyPath = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "zone-identifier": {
|
case "zone-identifier": {
|
||||||
// 生成唯一标识,防止用户通过id递增访问
|
// 生成唯一标识,防止用户通过id递增访问
|
||||||
sysFileInfo.setFileIdentifier(genFileIdentifier());
|
sysFileInfo.setFileIdentifier(genFileIdentifier());
|
||||||
|
|
@ -507,7 +533,7 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
||||||
sysFileInfo.setFileOriginName(session.getFileName());
|
sysFileInfo.setFileOriginName(session.getFileName());
|
||||||
}
|
}
|
||||||
if (!isCommonFileType) {
|
if (!isCommonFileType) {
|
||||||
if ("pms".equals(fileType)) {
|
if (isHierarchyPath) {
|
||||||
filePath = EscapeUtil.removeExtraSlashOfUrl("/" + fileType + "/" + hierarchy + "/" + filePath);
|
filePath = EscapeUtil.removeExtraSlashOfUrl("/" + fileType + "/" + hierarchy + "/" + filePath);
|
||||||
} else {
|
} else {
|
||||||
filePath = EscapeUtil.removeExtraSlashOfUrl("/" + fileType + "/" + filePath);
|
filePath = EscapeUtil.removeExtraSlashOfUrl("/" + fileType + "/" + filePath);
|
||||||
|
|
@ -598,7 +624,7 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
||||||
return fileIdentifier;
|
return fileIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fileDownload(SysFileInfo sysFileInfo, HttpServletResponse response) {
|
private void fileDownload(SysFileInfo sysFileInfo, HttpServletResponse response, HttpServletRequest request) {
|
||||||
try {
|
try {
|
||||||
String fileName = sysFileInfo.getFileOriginName();
|
String fileName = sysFileInfo.getFileOriginName();
|
||||||
String filePath = localFilePath + sysFileInfo.getFilePath();
|
String filePath = localFilePath + sysFileInfo.getFilePath();
|
||||||
|
|
@ -608,6 +634,8 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
||||||
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
||||||
}
|
}
|
||||||
FileUtils.setAttachmentResponseHeader(response, fileName);
|
FileUtils.setAttachmentResponseHeader(response, fileName);
|
||||||
|
// 在写文件之前记录下载日志,避免写文件异常导致日志丢失
|
||||||
|
recordDownloadLog(sysFileInfo.getFileId(), request);
|
||||||
FileUtils.writeBytes(filePath, response.getOutputStream());
|
FileUtils.writeBytes(filePath, response.getOutputStream());
|
||||||
int downloadCount = 1;
|
int downloadCount = 1;
|
||||||
if (sysFileInfo.getDownloadCount() != null) {
|
if (sysFileInfo.getDownloadCount() != null) {
|
||||||
|
|
@ -615,12 +643,43 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
||||||
}
|
}
|
||||||
sysFileInfo.setDownloadCount(downloadCount);
|
sysFileInfo.setDownloadCount(downloadCount);
|
||||||
updateSysFileInfo(sysFileInfo);
|
updateSysFileInfo(sysFileInfo);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ServiceException("文件下载异常:%s", e.getMessage());
|
throw new ServiceException("文件下载异常:%s", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录文件下载日志(登录用户记录用户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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件大小校验
|
* 文件大小校验
|
||||||
*
|
*
|
||||||
|
|
@ -670,4 +729,70 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
||||||
}
|
}
|
||||||
return fileIdentifier;
|
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="contentTypeMessage != null">content_type_message,</if>
|
||||||
<if test="mimeType != null">mime_type,</if>
|
<if test="mimeType != null">mime_type,</if>
|
||||||
<if test="isPartialMatch != null">is_partial_match,</if>
|
<if test="isPartialMatch != null">is_partial_match,</if>
|
||||||
|
<if test="downloadCount != null">download_count,</if>
|
||||||
</trim>
|
</trim>
|
||||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||||
<if test="fileOriginName != null and fileOriginName != ''">#{fileOriginName},</if>
|
<if test="fileOriginName != null and fileOriginName != ''">#{fileOriginName},</if>
|
||||||
|
|
@ -115,6 +116,7 @@
|
||||||
<if test="contentTypeMessage != null">#{contentTypeMessage},</if>
|
<if test="contentTypeMessage != null">#{contentTypeMessage},</if>
|
||||||
<if test="mimeType != null">#{mimeType},</if>
|
<if test="mimeType != null">#{mimeType},</if>
|
||||||
<if test="isPartialMatch != null">#{isPartialMatch},</if>
|
<if test="isPartialMatch != null">#{isPartialMatch},</if>
|
||||||
|
<if test="downloadCount != null">#{downloadCount},</if>
|
||||||
</trim>
|
</trim>
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
|
|
@ -159,4 +161,8 @@
|
||||||
from sys_file_info
|
from sys_file_info
|
||||||
where file_object_name = #{fileObjectName}
|
where file_object_name = #{fileObjectName}
|
||||||
</delete>
|
</delete>
|
||||||
|
|
||||||
|
<select id="countByFilePath" resultType="int">
|
||||||
|
select count(*) from sys_file_info where file_path = #{filePath} and del_flag = 'N'
|
||||||
|
</select>
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|
@ -133,6 +133,12 @@
|
||||||
<version>3.6.2</version>
|
<version>3.6.2</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- Apache Commons Compress - 解决 ZIP 文件中文文件名 GBK 编码导致的 MALFORMED 问题 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-compress</artifactId>
|
||||||
|
<version>1.26.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
||||||
|
|
@ -11,22 +11,21 @@ import com.microservices.common.security.annotation.RequiresPermissions;
|
||||||
import com.microservices.common.security.utils.SecurityUtils;
|
import com.microservices.common.security.utils.SecurityUtils;
|
||||||
import com.microservices.zone.detail.domain.vo.ZoneFrontRoleVo;
|
import com.microservices.zone.detail.domain.vo.ZoneFrontRoleVo;
|
||||||
import com.microservices.zone.detail.service.IZoneDetailService;
|
import com.microservices.zone.detail.service.IZoneDetailService;
|
||||||
import com.microservices.zone.project.domain.ZoneProjectType;
|
|
||||||
import com.microservices.zone.project.domain.vo.*;
|
import com.microservices.zone.project.domain.vo.*;
|
||||||
import com.microservices.zone.project.service.IZoneProjectService;
|
import com.microservices.zone.project.service.IZoneProjectService;
|
||||||
import com.microservices.zone.project.service.IZoneProjectTypeService;
|
import com.microservices.zone.project.service.IZoneProjectTypeService;
|
||||||
import com.microservices.zone.resource.domain.ZoneResource;
|
import com.microservices.zone.resource.domain.ZoneResource;
|
||||||
import com.microservices.zone.resource.domain.vo.ZoneResourceDataVo;
|
import com.microservices.zone.resource.domain.vo.*;
|
||||||
import com.microservices.zone.resource.domain.vo.ZoneResourceInputVo;
|
import com.microservices.zone.resource.service.IAiResourceParseService;
|
||||||
import com.microservices.zone.resource.domain.vo.ZoneResourceSearchVo;
|
|
||||||
import com.microservices.zone.resource.domain.vo.ZoneResourceUpdateVo;
|
|
||||||
import com.microservices.zone.resource.service.IZoneResourceService;
|
import com.microservices.zone.resource.service.IZoneResourceService;
|
||||||
|
import io.swagger.annotations.*;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import io.swagger.annotations.ApiParam;
|
import io.swagger.annotations.ApiParam;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -41,15 +40,15 @@ public class FrontController extends BaseController {
|
||||||
@Autowired
|
@Autowired
|
||||||
private IZoneResourceService zoneResourceService;
|
private IZoneResourceService zoneResourceService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IAiResourceParseService aiResourceParseService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IZoneProjectService zoneProjectService;
|
private IZoneProjectService zoneProjectService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IZoneDetailService zoneDetailService;
|
private IZoneDetailService zoneDetailService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZoneProjectTypeService zoneProjectTypeService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询我的资源列表
|
* 查询我的资源列表
|
||||||
*/
|
*/
|
||||||
|
|
@ -157,7 +156,7 @@ public class FrontController extends BaseController {
|
||||||
@Log(title = "专区前台项目操作", businessType = BusinessType.UPDATE)
|
@Log(title = "专区前台项目操作", businessType = BusinessType.UPDATE)
|
||||||
@PutMapping("/editProject/{projectId}")
|
@PutMapping("/editProject/{projectId}")
|
||||||
@ApiOperation(value = "编辑项目聚合")
|
@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()));
|
return success(zoneProjectService.updateMyZoneProject(projectId, zoneProjectFrontUpdateVo.toZoneProjectUpdateVo()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,4 +183,187 @@ public class FrontController extends BaseController {
|
||||||
public GenericsAjaxResult<ZoneFrontRoleVo> checkCurrentRole(@PathVariable Long zoneId) {
|
public GenericsAjaxResult<ZoneFrontRoleVo> checkCurrentRole(@PathVariable Long zoneId) {
|
||||||
return genericsSuccess(zoneDetailService.checkCurrentRoleInZone(zoneId));
|
return genericsSuccess(zoneDetailService.checkCurrentRoleInZone(zoneId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 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.ZoneBaseDataVo;
|
||||||
import com.microservices.zone.detail.domain.vo.ZoneDetailDataVo;
|
import com.microservices.zone.detail.domain.vo.ZoneDetailDataVo;
|
||||||
import com.microservices.zone.detail.service.IZoneDetailService;
|
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.ZoneMemberDataVo;
|
||||||
import com.microservices.zone.member.domain.vo.ZoneMemberOpenSearchVo;
|
import com.microservices.zone.member.domain.vo.ZoneMemberOpenSearchVo;
|
||||||
import com.microservices.zone.member.domain.vo.ZoneMemberOverviewVo;
|
import com.microservices.zone.member.domain.vo.ZoneMemberOverviewVo;
|
||||||
|
|
@ -29,10 +32,14 @@ import com.microservices.zone.project.domain.vo.ZoneProjectSearchVo;
|
||||||
import com.microservices.zone.project.domain.vo.ZoneStatisticsVo;
|
import com.microservices.zone.project.domain.vo.ZoneStatisticsVo;
|
||||||
import com.microservices.zone.project.service.IZoneProjectService;
|
import com.microservices.zone.project.service.IZoneProjectService;
|
||||||
import com.microservices.zone.project.service.IZoneProjectTypeService;
|
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.ZoneResourceDomain;
|
||||||
import com.microservices.zone.resource.domain.ZoneResourceType;
|
import com.microservices.zone.resource.domain.ZoneResourceType;
|
||||||
import com.microservices.zone.resource.domain.vo.ZoneResourceDataVo;
|
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.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.IZoneResourceDomainService;
|
||||||
import com.microservices.zone.resource.service.IZoneResourceService;
|
import com.microservices.zone.resource.service.IZoneResourceService;
|
||||||
import com.microservices.zone.resource.service.IZoneResourceTypeService;
|
import com.microservices.zone.resource.service.IZoneResourceTypeService;
|
||||||
|
|
@ -98,6 +105,9 @@ public class OpenController extends BaseController {
|
||||||
@Autowired
|
@Autowired
|
||||||
private IZoneVisitsService zoneVisitsService;
|
private IZoneVisitsService zoneVisitsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IGlobalSearchService globalSearchService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增特色专区用户浏览
|
* 新增特色专区用户浏览
|
||||||
*/
|
*/
|
||||||
|
|
@ -297,14 +307,9 @@ public class OpenController extends BaseController {
|
||||||
*/
|
*/
|
||||||
@GetMapping("/{zoneId}/resourceDomain/list")
|
@GetMapping("/{zoneId}/resourceDomain/list")
|
||||||
@ApiOperation("查询特色专区资源领域列表")
|
@ApiOperation("查询特色专区资源领域列表")
|
||||||
public GenericsTableDataInfo<ZoneResourceDomain> resourceList(
|
public GenericsTableDataInfo<ZoneResourceDomain> resourceList(ZoneResourceDomainSearchVo zoneResourceDomainSearchVo) {
|
||||||
@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);
|
|
||||||
startPage();
|
startPage();
|
||||||
List<ZoneResourceDomain> list = zoneResourceDomainService.selectZoneResourceDomainList(zoneResourceDomain);
|
List<ZoneResourceDomain> list = zoneResourceDomainService.selectZoneResourceDomainList(zoneResourceDomainSearchVo.toZoneResourceDomain());
|
||||||
return getGenericsDataTable(list);
|
return getGenericsDataTable(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -415,9 +420,6 @@ public class OpenController extends BaseController {
|
||||||
return getGenericsDataTable(list);
|
return getGenericsDataTable(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IGlobalSearchService globalSearchService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局搜索接口(POST方法)
|
* 全局搜索接口(POST方法)
|
||||||
*
|
*
|
||||||
|
|
@ -430,4 +432,24 @@ public class OpenController extends BaseController {
|
||||||
GlobalSearchResponse response = globalSearchService.globalSearch(globalSearchRequest);
|
GlobalSearchResponse response = globalSearchService.globalSearch(globalSearchRequest);
|
||||||
return new GenericsTableDataInfo<>(response.getResults(), response.getTotal());
|
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)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import com.microservices.system.api.RemoteDeptService;
|
||||||
import com.microservices.system.api.RemoteUserService;
|
import com.microservices.system.api.RemoteUserService;
|
||||||
import com.microservices.system.api.domain.SysDept;
|
import com.microservices.system.api.domain.SysDept;
|
||||||
import com.microservices.system.api.domain.SysDictData;
|
import com.microservices.system.api.domain.SysDictData;
|
||||||
|
import com.microservices.system.api.domain.SysUser;
|
||||||
import com.microservices.system.api.domain.SysUserDeptRole;
|
import com.microservices.system.api.domain.SysUserDeptRole;
|
||||||
import com.microservices.system.api.model.LoginUser;
|
import com.microservices.system.api.model.LoginUser;
|
||||||
import com.microservices.system.api.utils.FeignUtils;
|
import com.microservices.system.api.utils.FeignUtils;
|
||||||
|
|
@ -32,10 +33,14 @@ import com.microservices.zone.detail.domain.vo.*;
|
||||||
import com.microservices.zone.detail.mapper.ZoneDetailMapper;
|
import com.microservices.zone.detail.mapper.ZoneDetailMapper;
|
||||||
import com.microservices.zone.detail.service.IZoneDetailService;
|
import com.microservices.zone.detail.service.IZoneDetailService;
|
||||||
import com.microservices.zone.member.domain.ZoneMember;
|
import com.microservices.zone.member.domain.ZoneMember;
|
||||||
|
import com.microservices.zone.member.domain.ZoneMemberLevel;
|
||||||
|
import com.microservices.zone.member.domain.ZoneMemberType;
|
||||||
|
import com.microservices.zone.member.domain.vo.ZoneMemberInputVo;
|
||||||
import com.microservices.zone.member.domain.vo.ZoneMemberSearchVo;
|
import com.microservices.zone.member.domain.vo.ZoneMemberSearchVo;
|
||||||
import com.microservices.zone.member.mapper.ZoneMemberLevelMapper;
|
import com.microservices.zone.member.mapper.ZoneMemberLevelMapper;
|
||||||
import com.microservices.zone.member.mapper.ZoneMemberMapper;
|
import com.microservices.zone.member.mapper.ZoneMemberMapper;
|
||||||
import com.microservices.zone.member.mapper.ZoneMemberTypeMapper;
|
import com.microservices.zone.member.mapper.ZoneMemberTypeMapper;
|
||||||
|
import com.microservices.zone.member.service.IZoneMemberService;
|
||||||
import com.microservices.zone.partners.mapper.ZonePartnersMapper;
|
import com.microservices.zone.partners.mapper.ZonePartnersMapper;
|
||||||
import com.microservices.zone.partners.mapper.ZonePartnersTypeMapper;
|
import com.microservices.zone.partners.mapper.ZonePartnersTypeMapper;
|
||||||
import com.microservices.zone.project.domain.ZoneProject;
|
import com.microservices.zone.project.domain.ZoneProject;
|
||||||
|
|
@ -106,6 +111,9 @@ public class ZoneDetailServiceImpl implements IZoneDetailService {
|
||||||
private ZonePartnersTypeMapper zonePartnersTypeMapper;
|
private ZonePartnersTypeMapper zonePartnersTypeMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
@Lazy
|
@Lazy
|
||||||
|
private IZoneMemberService zoneMemberService;
|
||||||
|
@Autowired
|
||||||
|
@Lazy
|
||||||
private IZoneSpecialProjectService zoneSpecialProjectService;
|
private IZoneSpecialProjectService zoneSpecialProjectService;
|
||||||
@Value("${zone.zoneAdminUrl}")
|
@Value("${zone.zoneAdminUrl}")
|
||||||
private String zoneAdminUrl;
|
private String zoneAdminUrl;
|
||||||
|
|
@ -119,6 +127,8 @@ public class ZoneDetailServiceImpl implements IZoneDetailService {
|
||||||
private String docManagePath;
|
private String docManagePath;
|
||||||
@Value("${zone.projectManagePath:/zone/project/list}")
|
@Value("${zone.projectManagePath:/zone/project/list}")
|
||||||
private String projectManagePath;
|
private String projectManagePath;
|
||||||
|
@Value("${zone.autoMemberZoneIds:}")
|
||||||
|
private String autoMemberZoneIds;
|
||||||
@Autowired
|
@Autowired
|
||||||
private RedisService redisService;
|
private RedisService redisService;
|
||||||
|
|
||||||
|
|
@ -643,11 +653,58 @@ public class ZoneDetailServiceImpl implements IZoneDetailService {
|
||||||
return JSON.toJSONString(zoneDetail);
|
return JSON.toJSONString(zoneDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private ZoneMemberInputVo newZoneMemberInputVo(Long zoneId, SysUser sysUser) {
|
||||||
|
if (sysUser.getGitlinkUserId() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ZoneMemberInputVo zoneMemberInputVo = new ZoneMemberInputVo();
|
||||||
|
zoneMemberInputVo.setGitLinkUserId(sysUser.getGitlinkUserId());
|
||||||
|
zoneMemberInputVo.setName(sysUser.getNickName());
|
||||||
|
zoneMemberInputVo.setImageUrl(sysUser.getAvatar());
|
||||||
|
ZoneMemberType zoneMemberTypeSearch = new ZoneMemberType();
|
||||||
|
zoneMemberTypeSearch.setZoneId(zoneId);
|
||||||
|
List<ZoneMemberType> zoneMemberTypes = zoneMemberTypeMapper.selectZoneMemberTypeList(zoneMemberTypeSearch);
|
||||||
|
if (zoneMemberTypes == null || zoneMemberTypes.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Long zoneMemberTypeId = zoneMemberTypes.get(0).getId();
|
||||||
|
zoneMemberInputVo.setMemberTypeId(zoneMemberTypeId);
|
||||||
|
ZoneMemberLevel zoneMemberLevelSearch = new ZoneMemberLevel();
|
||||||
|
zoneMemberLevelSearch.setZoneId(zoneId);
|
||||||
|
List<ZoneMemberLevel> zoneMemberLevels = zoneMemberLevelMapper.selectZoneMemberLevelList(zoneMemberLevelSearch);
|
||||||
|
if (zoneMemberLevels == null || zoneMemberLevels.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Long zoneMemberLevelId = zoneMemberLevels.get(0).getId();
|
||||||
|
zoneMemberInputVo.setMemberLevelId(zoneMemberLevelId);
|
||||||
|
zoneMemberInputVo.setIsSendInvitation(false);
|
||||||
|
zoneMemberInputVo.setZoneId(zoneId);
|
||||||
|
return zoneMemberInputVo;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ZoneFrontRoleVo checkCurrentRoleInZone(Long zoneId) {
|
public ZoneFrontRoleVo checkCurrentRoleInZone(Long zoneId) {
|
||||||
ZoneFrontRoleVo zoneFrontRoleVo = new ZoneFrontRoleVo();
|
ZoneFrontRoleVo zoneFrontRoleVo = new ZoneFrontRoleVo();
|
||||||
ZoneDetail zoneDetail = selectZoneDetailById(zoneId);
|
ZoneDetail zoneDetail = selectZoneDetailById(zoneId);
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||||
|
// 当前专区为指定专区时,用户将自动成为专区会员
|
||||||
|
if (StringUtils.isNotEmpty(autoMemberZoneIds)) {
|
||||||
|
String[] idArr = autoMemberZoneIds.split(",");
|
||||||
|
for (String s : idArr) {
|
||||||
|
if (s.equals(String.valueOf(zoneId))) {
|
||||||
|
ZoneMember zoneMember = zoneMemberMapper.selectZoneMemberByZoneIdAndUserId(zoneId, loginUser.getUserId());
|
||||||
|
if (zoneMember == null) {
|
||||||
|
ZoneMemberInputVo zoneMemberInputVo = newZoneMemberInputVo(zoneId, loginUser.getSysUser());
|
||||||
|
if (zoneMemberInputVo == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
zoneMemberService.insertZoneMember(zoneMemberInputVo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SysDept sysDept = FeignUtils.getReturnData(remoteDeptService.getDeptByDeptId(zoneDetail.getDeptId(), SecurityConstants.INNER));
|
SysDept sysDept = FeignUtils.getReturnData(remoteDeptService.getDeptByDeptId(zoneDetail.getDeptId(), SecurityConstants.INNER));
|
||||||
List<SysUserDeptRole> userIdentifyList = FeignUtils.getReturnData(
|
List<SysUserDeptRole> userIdentifyList = FeignUtils.getReturnData(
|
||||||
remoteUserService.getSysUserDeptRoleListByUserName(loginUser.getUsername(), SecurityConstants.INNER)
|
remoteUserService.getSysUserDeptRoleListByUserName(loginUser.getUsername(), SecurityConstants.INNER)
|
||||||
|
|
|
||||||
|
|
@ -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;
|
package com.microservices.zone.member.domain.vo;
|
||||||
|
|
||||||
|
import com.microservices.system.api.domain.SysUser;
|
||||||
import com.microservices.zone.member.domain.ZoneMember;
|
import com.microservices.zone.member.domain.ZoneMember;
|
||||||
import io.swagger.annotations.ApiModel;
|
import io.swagger.annotations.ApiModel;
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
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.annotation.Log;
|
||||||
import com.microservices.common.log.enums.BusinessType;
|
import com.microservices.common.log.enums.BusinessType;
|
||||||
import com.microservices.common.security.annotation.RequiresPermissions;
|
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.domain.vo.*;
|
||||||
import com.microservices.zone.project.service.IZoneProjectService;
|
import com.microservices.zone.project.service.IZoneProjectService;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
|
|
@ -39,6 +40,9 @@ public class ZoneProjectController extends BaseController {
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@ApiOperation("查询项目聚合列表")
|
@ApiOperation("查询项目聚合列表")
|
||||||
public GenericsTableDataInfo<ZoneProjectDataVo> list(ZoneProjectSearchVo zoneProjectSearchVo) {
|
public GenericsTableDataInfo<ZoneProjectDataVo> list(ZoneProjectSearchVo zoneProjectSearchVo) {
|
||||||
|
if (Boolean.TRUE.equals(zoneProjectSearchVo.getIsMine())) {
|
||||||
|
zoneProjectSearchVo.setCreateBy(SecurityUtils.getUsername());
|
||||||
|
}
|
||||||
return zoneProjectService.selectZoneProjectList(zoneProjectSearchVo, false);
|
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,7 @@ public class ZoneProjectSearchVo extends ZoneProjectOpenSearchVo {
|
||||||
private String auditStatus;
|
private String auditStatus;
|
||||||
@ApiModelProperty(value = "是否为我的项目", hidden = true)
|
@ApiModelProperty(value = "是否为我的项目", hidden = true)
|
||||||
private Boolean isMyProject;
|
private Boolean isMyProject;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "是否只查询我的")
|
||||||
|
private Boolean isMine;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,15 @@ public interface IZoneProjectService {
|
||||||
*/
|
*/
|
||||||
GenericsTableDataInfo<ZoneProjectDataVo> selectMyZoneProjectList(ZoneProjectSearchVo zoneProjectSearchVo);
|
GenericsTableDataInfo<ZoneProjectDataVo> selectMyZoneProjectList(ZoneProjectSearchVo zoneProjectSearchVo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户项目统计信息
|
||||||
|
*
|
||||||
|
* @param zoneId
|
||||||
|
* @param username 创建者用户名
|
||||||
|
* @return 统计信息
|
||||||
|
*/
|
||||||
|
UserProjectStatisticsVo getUserProjectStatistics(Long zoneId, String username);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量删除我的项目(校验创建者)
|
* 批量删除我的项目(校验创建者)
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package com.microservices.zone.project.service.impl;
|
package com.microservices.zone.project.service.impl;
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSON;
|
|
||||||
import com.alibaba.fastjson2.JSONArray;
|
import com.alibaba.fastjson2.JSONArray;
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.microservices.common.core.constant.CacheConstants;
|
import com.microservices.common.core.constant.CacheConstants;
|
||||||
|
|
@ -676,6 +675,70 @@ public class ZoneProjectServiceImpl implements IZoneProjectService {
|
||||||
return PageUtils.toListPage(zoneProjectList, this::toZoneProjectDataVoList);
|
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
|
@Override
|
||||||
public int deleteMyZoneProjectByIds(Long[] ids) {
|
public int deleteMyZoneProjectByIds(Long[] ids) {
|
||||||
for (Long id : 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 com.microservices.zone.resource.service.IZoneResourceService;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import io.swagger.annotations.ApiParam;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
|
@ -106,4 +107,17 @@ public class ZoneResourceController extends BaseController {
|
||||||
public AjaxResult remove(@PathVariable Long[] ids) {
|
public AjaxResult remove(@PathVariable Long[] ids) {
|
||||||
return toAjax(zoneResourceService.deleteZoneResourceByIds(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;
|
package com.microservices.zone.resource.domain;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.microservices.common.core.annotation.Excel;
|
import com.microservices.common.core.annotation.Excel;
|
||||||
import com.microservices.common.core.utils.bean.BeanUtils;
|
import com.microservices.common.core.utils.bean.BeanUtils;
|
||||||
import com.microservices.zone.detail.domain.ZoneSort;
|
import com.microservices.zone.detail.domain.ZoneSort;
|
||||||
import com.microservices.zone.resource.domain.vo.ZoneResourceDataVo;
|
import com.microservices.zone.resource.domain.vo.ZoneResourceDataVo;
|
||||||
|
import com.microservices.zone.resource.utils.ResourceExtendDataUtils;
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
|
@ -64,7 +66,7 @@ public class ZoneResource extends ZoneSort {
|
||||||
@ApiModelProperty(value = "资源简介")
|
@ApiModelProperty(value = "资源简介")
|
||||||
private String summary;
|
private String summary;
|
||||||
|
|
||||||
@ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过)")
|
@ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过 3:已下架 4:草稿)")
|
||||||
private String auditStatus;
|
private String auditStatus;
|
||||||
/**
|
/**
|
||||||
* 是否首页展示(0代表不展示 1代表展示)
|
* 是否首页展示(0代表不展示 1代表展示)
|
||||||
|
|
@ -93,8 +95,23 @@ public class ZoneResource extends ZoneSort {
|
||||||
@ApiModelProperty(value = "资源标签")
|
@ApiModelProperty(value = "资源标签")
|
||||||
private String keywords;
|
private String keywords;
|
||||||
|
|
||||||
@ApiModelProperty(value = "拓展数据")
|
@ApiModelProperty(value = "拓展数据(JSON格式)")
|
||||||
private String extendData;
|
private JSONObject extendData;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "资源分类:SKILL/MODEL/DATASET,NULL代表原资源")
|
||||||
|
private String resourceCategory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 浏览次数
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(value = "浏览次数")
|
||||||
|
private Integer viewCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点赞次数
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(value = "点赞次数")
|
||||||
|
private Integer likeCount;
|
||||||
|
|
||||||
public ZoneResourceDataVo toZoneResourceDataVo() {
|
public ZoneResourceDataVo toZoneResourceDataVo() {
|
||||||
ZoneResourceDataVo target = new ZoneResourceDataVo();
|
ZoneResourceDataVo target = new ZoneResourceDataVo();
|
||||||
|
|
@ -105,4 +122,34 @@ public class ZoneResource extends ZoneSort {
|
||||||
|
|
||||||
return target;
|
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.common.core.annotation.Excel;
|
||||||
import com.microservices.zone.detail.domain.ZoneSort;
|
import com.microservices.zone.detail.domain.ZoneSort;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -23,4 +24,7 @@ public class ZoneResourceDomain extends ZoneSort {
|
||||||
*/
|
*/
|
||||||
@Excel(name = "资源领域简介")
|
@Excel(name = "资源领域简介")
|
||||||
private String introduction;
|
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;
|
package com.microservices.zone.resource.domain.vo;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import com.microservices.system.api.domain.SimpleSysUser;
|
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.domain.ZoneResourceType;
|
||||||
|
import com.microservices.zone.resource.utils.ResourceExtendDataUtils;
|
||||||
import io.swagger.annotations.ApiModel;
|
import io.swagger.annotations.ApiModel;
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
@ -64,7 +67,7 @@ public class ZoneResourceDataVo {
|
||||||
@ApiModelProperty(value = "资源简介")
|
@ApiModelProperty(value = "资源简介")
|
||||||
private String summary;
|
private String summary;
|
||||||
|
|
||||||
@ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过)")
|
@ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过 3:已下架 4:草稿)")
|
||||||
private String auditStatus;
|
private String auditStatus;
|
||||||
/**
|
/**
|
||||||
* 审核时间
|
* 审核时间
|
||||||
|
|
@ -73,8 +76,23 @@ public class ZoneResourceDataVo {
|
||||||
private Date auditTime;
|
private Date auditTime;
|
||||||
@ApiModelProperty(value = "审核人")
|
@ApiModelProperty(value = "审核人")
|
||||||
private String auditBy;
|
private String auditBy;
|
||||||
@ApiModelProperty(value = "拓展数据")
|
|
||||||
private String extendData;
|
|
||||||
@ApiModelProperty(value = "备注")
|
@ApiModelProperty(value = "备注")
|
||||||
private String remark;
|
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)
|
@ApiModelProperty(value = "所属专区", required = true)
|
||||||
@NotNull
|
@NotNull
|
||||||
private Long zoneId;
|
private Long zoneId;
|
||||||
|
@ApiModelProperty(value = "领域分类:SKILL/MODEL/DATASET,NULL代表原资源")
|
||||||
|
private String domainCategory;
|
||||||
|
|
||||||
public ZoneResourceDomain toZoneResourceDomain() {
|
public ZoneResourceDomain toZoneResourceDomain() {
|
||||||
ZoneResourceDomain target = new ZoneResourceDomain();
|
ZoneResourceDomain target = new ZoneResourceDomain();
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import io.swagger.annotations.ApiModel;
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author otto
|
* @author otto
|
||||||
*/
|
*/
|
||||||
|
|
@ -13,13 +15,17 @@ import lombok.Data;
|
||||||
public class ZoneResourceDomainSearchVo {
|
public class ZoneResourceDomainSearchVo {
|
||||||
@ApiModelProperty(value = "资源领域名称")
|
@ApiModelProperty(value = "资源领域名称")
|
||||||
private String name;
|
private String name;
|
||||||
@ApiModelProperty(value = "所属专区")
|
@ApiModelProperty(value = "所属专区", required = true)
|
||||||
|
@NotNull
|
||||||
private Long zoneId;
|
private Long zoneId;
|
||||||
|
@ApiModelProperty(value = "领域分类:SKILL/MODEL/DATASET,NULL或者不传代表所有资源领域")
|
||||||
|
private String domainCategory;
|
||||||
|
|
||||||
public ZoneResourceDomain toZoneResourceDomain() {
|
public ZoneResourceDomain toZoneResourceDomain() {
|
||||||
ZoneResourceDomain zoneResourceDomain = new ZoneResourceDomain();
|
ZoneResourceDomain zoneResourceDomain = new ZoneResourceDomain();
|
||||||
zoneResourceDomain.setZoneId(zoneId);
|
zoneResourceDomain.setZoneId(zoneId);
|
||||||
zoneResourceDomain.setName(name);
|
zoneResourceDomain.setName(name);
|
||||||
|
zoneResourceDomain.setDomainCategory(domainCategory);
|
||||||
return zoneResourceDomain;
|
return zoneResourceDomain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import io.swagger.annotations.ApiModel;
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author otto
|
* @author otto
|
||||||
*/
|
*/
|
||||||
|
|
@ -17,6 +19,9 @@ public class ZoneResourceFileVo extends SysFileInfo {
|
||||||
@ApiModelProperty(value = "资源类型列表")
|
@ApiModelProperty(value = "资源类型列表")
|
||||||
private ZoneResourceType zoneResourceType;
|
private ZoneResourceType zoneResourceType;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "下载用户列表")
|
||||||
|
private List<String> downloadUserList;
|
||||||
|
|
||||||
public ZoneResourceFileVo(ZoneResourceType zoneResourceType, SysFileInfo sysFileInfo) {
|
public ZoneResourceFileVo(ZoneResourceType zoneResourceType, SysFileInfo sysFileInfo) {
|
||||||
this.zoneResourceType = zoneResourceType;
|
this.zoneResourceType = zoneResourceType;
|
||||||
BeanUtils.copyProperties(sysFileInfo, this);
|
BeanUtils.copyProperties(sysFileInfo, this);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.microservices.zone.resource.domain.vo;
|
package com.microservices.zone.resource.domain.vo;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.microservices.common.core.utils.bean.BeanUtils;
|
import com.microservices.common.core.utils.bean.BeanUtils;
|
||||||
import com.microservices.zone.resource.domain.ZoneResource;
|
import com.microservices.zone.resource.domain.ZoneResource;
|
||||||
import io.swagger.annotations.ApiModel;
|
import io.swagger.annotations.ApiModel;
|
||||||
|
|
@ -19,7 +20,7 @@ public class ZoneResourceInputVo {
|
||||||
@NotNull
|
@NotNull
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过)", hidden = true)
|
@ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过 3:已下架 4:草稿)", hidden = true)
|
||||||
private String auditStatus = "1";
|
private String auditStatus = "1";
|
||||||
@ApiModelProperty(value = "资源简介")
|
@ApiModelProperty(value = "资源简介")
|
||||||
private String summary;
|
private String summary;
|
||||||
|
|
@ -34,12 +35,16 @@ public class ZoneResourceInputVo {
|
||||||
@NotNull
|
@NotNull
|
||||||
private Long zoneId;
|
private Long zoneId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "资源分类:SKILL/MODEL/DATASET,NULL代表原资源", hidden = true)
|
||||||
|
private String resourceCategory = null;
|
||||||
|
|
||||||
@ApiModelProperty(value = "拓展数据")
|
@ApiModelProperty(value = "拓展数据")
|
||||||
private String extendData;
|
private String extendData;
|
||||||
|
|
||||||
public ZoneResource toZoneResource() {
|
public ZoneResource toZoneResource() {
|
||||||
ZoneResource target = new ZoneResource();
|
ZoneResource target = new ZoneResource();
|
||||||
BeanUtils.copyProperties(this, target);
|
BeanUtils.copyProperties(this, target);
|
||||||
|
target.setExtendData(JSONObject.parseObject(extendData));
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,11 @@ public class ZoneResourceOpenSearchVo extends BaseEntity {
|
||||||
@ApiModelProperty(value = "是否根据下载次数降序排序(与orderByColumn互斥,该属性优先级更高)")
|
@ApiModelProperty(value = "是否根据下载次数降序排序(与orderByColumn互斥,该属性优先级更高)")
|
||||||
private Boolean orderByDownloadCount = false;
|
private Boolean orderByDownloadCount = false;
|
||||||
|
|
||||||
@ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过)")
|
@ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过 3:已下架 4:草稿)",hidden = true)
|
||||||
private String auditStatus;
|
private String auditStatus="1";
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "资源分类:SKILL/MODEL/DATASET,NULL或者不传代表所有资源")
|
||||||
|
private String resourceCategory;
|
||||||
|
|
||||||
public ZoneResource toZoneResource() {
|
public ZoneResource toZoneResource() {
|
||||||
ZoneResource target = new ZoneResource();
|
ZoneResource target = new ZoneResource();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.microservices.zone.resource.domain.vo;
|
package com.microservices.zone.resource.domain.vo;
|
||||||
|
|
||||||
|
import com.microservices.common.security.utils.SecurityUtils;
|
||||||
import com.microservices.zone.resource.domain.ZoneResource;
|
import com.microservices.zone.resource.domain.ZoneResource;
|
||||||
import io.swagger.annotations.ApiModel;
|
import io.swagger.annotations.ApiModel;
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
|
@ -18,12 +19,21 @@ public class ZoneResourceSearchVo extends ZoneResourceOpenSearchVo {
|
||||||
@NotNull
|
@NotNull
|
||||||
private Long zoneId;
|
private Long zoneId;
|
||||||
|
|
||||||
@ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过)")
|
@ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过 3:已下架 4:草稿 all:所有)")
|
||||||
private String auditStatus;
|
private String auditStatus;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "资源分类:SKILL/MODEL/DATASET,NULL或者不传代表所有资源")
|
||||||
|
private String resourceCategory;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "是否只查询我的")
|
||||||
|
private Boolean isMine;
|
||||||
|
|
||||||
public ZoneResource toZoneResource() {
|
public ZoneResource toZoneResource() {
|
||||||
ZoneResource zoneResource = super.toZoneResource();
|
ZoneResource zoneResource = super.toZoneResource();
|
||||||
zoneResource.setZoneId(zoneId);
|
zoneResource.setZoneId(zoneId);
|
||||||
|
if (Boolean.TRUE.equals(isMine)) {
|
||||||
|
zoneResource.setCreateBy(SecurityUtils.getUsername());
|
||||||
|
}
|
||||||
return zoneResource;
|
return zoneResource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.microservices.zone.resource.domain.vo;
|
package com.microservices.zone.resource.domain.vo;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.microservices.common.core.utils.bean.BeanUtils;
|
import com.microservices.common.core.utils.bean.BeanUtils;
|
||||||
import com.microservices.zone.resource.domain.ZoneResource;
|
import com.microservices.zone.resource.domain.ZoneResource;
|
||||||
import io.swagger.annotations.ApiModel;
|
import io.swagger.annotations.ApiModel;
|
||||||
|
|
@ -34,7 +35,7 @@ public class ZoneResourceUpdateVo {
|
||||||
@ApiModelProperty(value = "是否首页展示(0代表不展示 1代表展示)")
|
@ApiModelProperty(value = "是否首页展示(0代表不展示 1代表展示)")
|
||||||
private String isHomepage;
|
private String isHomepage;
|
||||||
|
|
||||||
@ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过)", hidden = true)
|
@ApiModelProperty(value = "审核状态(0:待审核,1:审核通过 2:审核不通过 3:已下架 4:草稿)", hidden = true)
|
||||||
private String auditStatus = "1";
|
private String auditStatus = "1";
|
||||||
@ApiModelProperty(value = "备注", hidden = true)
|
@ApiModelProperty(value = "备注", hidden = true)
|
||||||
private String remark;
|
private String remark;
|
||||||
|
|
@ -44,6 +45,7 @@ public class ZoneResourceUpdateVo {
|
||||||
public ZoneResource toZoneResource() {
|
public ZoneResource toZoneResource() {
|
||||||
ZoneResource target = new ZoneResource();
|
ZoneResource target = new ZoneResource();
|
||||||
BeanUtils.copyProperties(this, target);
|
BeanUtils.copyProperties(this, target);
|
||||||
|
target.setExtendData(JSONObject.parseObject(extendData));
|
||||||
return target;
|
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 name
|
||||||
* @param zoneId
|
* @param zoneId
|
||||||
|
* @param domainCategory
|
||||||
* @return
|
* @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删除资源领域
|
* 根据专区Id删除资源领域
|
||||||
|
|
|
||||||
|
|
@ -70,9 +70,10 @@ public interface ZoneResourceMapper {
|
||||||
*
|
*
|
||||||
* @param name
|
* @param name
|
||||||
* @param zoneId
|
* @param zoneId
|
||||||
|
* @param resourceCategory
|
||||||
* @return
|
* @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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取资源数量
|
* 获取资源数量
|
||||||
|
|
@ -119,4 +120,39 @@ public interface ZoneResourceMapper {
|
||||||
@Param("zoneId") Long zoneId,
|
@Param("zoneId") Long zoneId,
|
||||||
@Param("offset") int offset,
|
@Param("offset") int offset,
|
||||||
@Param("pageSize") int pageSize);
|
@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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
package com.microservices.zone.resource.service;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.microservices.common.core.web.page.GenericsTableDataInfo;
|
import com.microservices.common.core.web.page.GenericsTableDataInfo;
|
||||||
import com.microservices.system.api.domain.SysFileInfo;
|
import com.microservices.system.api.domain.SysFileInfo;
|
||||||
import com.microservices.zone.resource.domain.ZoneResource;
|
import com.microservices.zone.resource.domain.ZoneResource;
|
||||||
|
|
@ -138,4 +139,167 @@ public interface IZoneResourceService {
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
Integer selectMyZoneResourceCount(ZoneResource zoneResourceSearch);
|
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
|
@Override
|
||||||
public int insertZoneResourceDomain(ZoneResourceDomainInputVo zoneResourceDomainInputVo) {
|
public int insertZoneResourceDomain(ZoneResourceDomainInputVo zoneResourceDomainInputVo) {
|
||||||
ZoneDetail zoneDetail = zoneDetailService.selectZoneDetailById(zoneResourceDomainInputVo.getZoneId());
|
ZoneDetail zoneDetail = zoneDetailService.selectZoneDetailById(zoneResourceDomainInputVo.getZoneId());
|
||||||
checkNameInZone(zoneResourceDomainInputVo.getZoneId(), zoneResourceDomainInputVo.getName());
|
checkNameInZone(zoneResourceDomainInputVo.getZoneId(), zoneResourceDomainInputVo.getName(),zoneResourceDomainInputVo.getDomainCategory());
|
||||||
ZoneResourceDomain zoneResourceDomain = zoneResourceDomainInputVo.toZoneResourceDomain();
|
ZoneResourceDomain zoneResourceDomain = zoneResourceDomainInputVo.toZoneResourceDomain();
|
||||||
zoneResourceDomain.setCreateTime(DateUtils.getNowDate());
|
zoneResourceDomain.setCreateTime(DateUtils.getNowDate());
|
||||||
zoneResourceDomain.setCreateBy(SecurityUtils.getUsername());
|
zoneResourceDomain.setCreateBy(SecurityUtils.getUsername());
|
||||||
|
|
@ -86,11 +86,13 @@ public class ZoneResourceDomainServiceImpl implements IZoneResourceDomainService
|
||||||
/**
|
/**
|
||||||
* 检查该特色专区下是否已存在该名称的资源领域
|
* 检查该特色专区下是否已存在该名称的资源领域
|
||||||
*
|
*
|
||||||
|
* @param domainCategory
|
||||||
* @param zoneId
|
* @param zoneId
|
||||||
* @param name
|
* @param name
|
||||||
|
* @param domainCategory
|
||||||
*/
|
*/
|
||||||
private void checkNameInZone(Long zoneId, String name) {
|
private void checkNameInZone(Long zoneId, String name, String domainCategory) {
|
||||||
ZoneResourceDomain oldZoneResourceDomain = zoneResourceDomainMapper.selectZoneResourceDomainByNameAndZoneId(name, zoneId);
|
ZoneResourceDomain oldZoneResourceDomain = zoneResourceDomainMapper.selectZoneResourceDomainByNameAndZoneId(name, zoneId,domainCategory);
|
||||||
if (oldZoneResourceDomain != null) {
|
if (oldZoneResourceDomain != null) {
|
||||||
throw new ServiceException("该特色专区下资源领域名称已存在(资源领域名称[" + name + "])");
|
throw new ServiceException("该特色专区下资源领域名称已存在(资源领域名称[" + name + "])");
|
||||||
}
|
}
|
||||||
|
|
@ -106,7 +108,7 @@ public class ZoneResourceDomainServiceImpl implements IZoneResourceDomainService
|
||||||
public int updateZoneResourceDomain(ZoneResourceDomainUpdateVo zoneResourceDomainUpdateVo) {
|
public int updateZoneResourceDomain(ZoneResourceDomainUpdateVo zoneResourceDomainUpdateVo) {
|
||||||
ZoneResourceDomain oldZoneResourceDomain = selectZoneResourceDomainById(zoneResourceDomainUpdateVo.getId());
|
ZoneResourceDomain oldZoneResourceDomain = selectZoneResourceDomainById(zoneResourceDomainUpdateVo.getId());
|
||||||
if (!oldZoneResourceDomain.getName().equals(zoneResourceDomainUpdateVo.getName())) {
|
if (!oldZoneResourceDomain.getName().equals(zoneResourceDomainUpdateVo.getName())) {
|
||||||
checkNameInZone(oldZoneResourceDomain.getZoneId(), zoneResourceDomainUpdateVo.getName());
|
checkNameInZone(oldZoneResourceDomain.getZoneId(), zoneResourceDomainUpdateVo.getName(), oldZoneResourceDomain.getDomainCategory());
|
||||||
}
|
}
|
||||||
ZoneResourceDomain zoneResourceDomain = zoneResourceDomainUpdateVo.toZoneResourceDomain();
|
ZoneResourceDomain zoneResourceDomain = zoneResourceDomainUpdateVo.toZoneResourceDomain();
|
||||||
zoneResourceDomain.setUpdateTime(DateUtils.getNowDate());
|
zoneResourceDomain.setUpdateTime(DateUtils.getNowDate());
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.microservices.zone.resource.service.impl;
|
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.HttpStatus;
|
||||||
import com.microservices.common.core.constant.SecurityConstants;
|
import com.microservices.common.core.constant.SecurityConstants;
|
||||||
import com.microservices.common.core.enums.SystemRole;
|
import com.microservices.common.core.enums.SystemRole;
|
||||||
|
|
@ -14,6 +15,7 @@ import com.microservices.common.security.auth.AuthUtil;
|
||||||
import com.microservices.common.security.utils.SecurityUtils;
|
import com.microservices.common.security.utils.SecurityUtils;
|
||||||
import com.microservices.system.api.RemoteFileService;
|
import com.microservices.system.api.RemoteFileService;
|
||||||
import com.microservices.system.api.RemoteUserService;
|
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.SysFileInfo;
|
||||||
import com.microservices.system.api.domain.SysUser;
|
import com.microservices.system.api.domain.SysUser;
|
||||||
import com.microservices.system.api.utils.FeignUtils;
|
import com.microservices.system.api.utils.FeignUtils;
|
||||||
|
|
@ -21,15 +23,17 @@ import com.microservices.zone.common.service.IZoneSortService;
|
||||||
import com.microservices.zone.detail.domain.ZoneDetail;
|
import com.microservices.zone.detail.domain.ZoneDetail;
|
||||||
import com.microservices.zone.detail.domain.ZoneSort;
|
import com.microservices.zone.detail.domain.ZoneSort;
|
||||||
import com.microservices.zone.detail.service.IZoneDetailService;
|
import com.microservices.zone.detail.service.IZoneDetailService;
|
||||||
import com.microservices.zone.resource.domain.ZoneResource;
|
import com.microservices.zone.resource.domain.*;
|
||||||
import com.microservices.zone.resource.domain.ZoneResourceToType;
|
|
||||||
import com.microservices.zone.resource.domain.ZoneResourceType;
|
|
||||||
import com.microservices.zone.resource.domain.vo.*;
|
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.ZoneResourceMapper;
|
||||||
import com.microservices.zone.resource.mapper.ZoneResourceToTypeMapper;
|
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.IZoneResourceDomainService;
|
||||||
import com.microservices.zone.resource.service.IZoneResourceService;
|
import com.microservices.zone.resource.service.IZoneResourceService;
|
||||||
import com.microservices.zone.resource.service.IZoneResourceTypeService;
|
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.specialProject.service.IZoneSpecialProjectRelevancyService;
|
||||||
import com.microservices.zone.utils.ZoneConstants;
|
import com.microservices.zone.utils.ZoneConstants;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
@ -37,9 +41,15 @@ import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -72,6 +82,10 @@ public class ZoneResourceServiceImpl implements IZoneResourceService {
|
||||||
@Autowired
|
@Autowired
|
||||||
@Lazy
|
@Lazy
|
||||||
private IZoneSpecialProjectRelevancyService zoneSpecialProjectRelevancyService;
|
private IZoneSpecialProjectRelevancyService zoneSpecialProjectRelevancyService;
|
||||||
|
@Autowired
|
||||||
|
private ZoneResourcesLikeMapper zoneResourcesLikeMapper;
|
||||||
|
@Autowired
|
||||||
|
private IAiResourceParseService aiResourceParseService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询特色专区资源聚合
|
* 查询特色专区资源聚合
|
||||||
|
|
@ -99,17 +113,17 @@ public class ZoneResourceServiceImpl implements IZoneResourceService {
|
||||||
@DataScope(deptAlias = "zr")
|
@DataScope(deptAlias = "zr")
|
||||||
public GenericsTableDataInfo<ZoneResourceDataVo> selectZoneResourceList(ZoneResource zoneResourceSearch) {
|
public GenericsTableDataInfo<ZoneResourceDataVo> selectZoneResourceList(ZoneResource zoneResourceSearch) {
|
||||||
List<ZoneResource> zoneResourceList = zoneResourceMapper.selectZoneResourceList(zoneResourceSearch);
|
List<ZoneResource> zoneResourceList = zoneResourceMapper.selectZoneResourceList(zoneResourceSearch);
|
||||||
return PageUtils.toPage(zoneResourceList, this::zoneResourceToData);
|
return PageUtils.toPage(zoneResourceList, z -> zoneResourceToData(z, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@DataScope(deptAlias = "zr")
|
@DataScope(deptAlias = "zr")
|
||||||
public GenericsTableDataInfo<ZoneResourceDataVo> selectZoneResourceList(ZoneResourceOpenSearchVo zoneResourceSearchVo) {
|
public GenericsTableDataInfo<ZoneResourceDataVo> selectZoneResourceList(ZoneResourceOpenSearchVo zoneResourceSearchVo) {
|
||||||
List<ZoneResource> zoneResourceList = zoneResourceMapper.selectZoneResourceListBySearchVo(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();
|
ZoneResourceDataVo zoneResourceDataVo = zoneResource.toZoneResourceDataVo();
|
||||||
int downloadCount = 0;
|
int downloadCount = 0;
|
||||||
if (StringUtils.isNotEmpty(zoneResource.getFileIds())) {
|
if (StringUtils.isNotEmpty(zoneResource.getFileIds())) {
|
||||||
|
|
@ -148,6 +162,9 @@ public class ZoneResourceServiceImpl implements IZoneResourceService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
zoneResourceDataVo.setDownloadCount(downloadCount);
|
zoneResourceDataVo.setDownloadCount(downloadCount);
|
||||||
|
if (removeSkill && zoneResourceDataVo.getExtendData() != null) {
|
||||||
|
zoneResourceDataVo.getExtendData().remove(ResourceExtendDataUtils.SKILL_ROOT);
|
||||||
|
}
|
||||||
return zoneResourceDataVo;
|
return zoneResourceDataVo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,9 +172,7 @@ public class ZoneResourceServiceImpl implements IZoneResourceService {
|
||||||
if (1 == zoneDetail.getResourceNeedAudit()) {
|
if (1 == zoneDetail.getResourceNeedAudit()) {
|
||||||
boolean isOperator = AuthUtil.hasUserIdentity(zoneDetail.getDeptId(), SystemRole.ZONE_OPERATOR.getRoleKey());
|
boolean isOperator = AuthUtil.hasUserIdentity(zoneDetail.getDeptId(), SystemRole.ZONE_OPERATOR.getRoleKey());
|
||||||
boolean isMember = AuthUtil.hasUserIdentity(zoneDetail.getDeptId(), SystemRole.ZONE_MEMBER.getRoleKey());
|
boolean isMember = AuthUtil.hasUserIdentity(zoneDetail.getDeptId(), SystemRole.ZONE_MEMBER.getRoleKey());
|
||||||
if (isOperator || isMember) {
|
return isOperator || isMember;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -171,7 +186,7 @@ public class ZoneResourceServiceImpl implements IZoneResourceService {
|
||||||
@Override
|
@Override
|
||||||
public ZoneResourceDataVo insertZoneResource(ZoneResourceInputVo zoneResourceInputVo) {
|
public ZoneResourceDataVo insertZoneResource(ZoneResourceInputVo zoneResourceInputVo) {
|
||||||
ZoneDetail zoneDetail = zoneDetailService.selectZoneDetailById(zoneResourceInputVo.getZoneId());
|
ZoneDetail zoneDetail = zoneDetailService.selectZoneDetailById(zoneResourceInputVo.getZoneId());
|
||||||
checkNameInZone(zoneResourceInputVo.getZoneId(), zoneResourceInputVo.getName());
|
checkNameInZone(zoneResourceInputVo.getZoneId(), zoneResourceInputVo.getName(), zoneResourceInputVo.getResourceCategory());
|
||||||
zoneResourceDomainService.selectZoneResourceDomainById(zoneResourceInputVo.getDomainId());
|
zoneResourceDomainService.selectZoneResourceDomainById(zoneResourceInputVo.getDomainId());
|
||||||
ZoneResource zoneResource = zoneResourceInputVo.toZoneResource();
|
ZoneResource zoneResource = zoneResourceInputVo.toZoneResource();
|
||||||
// 专区开启资源审核后,专区会员和专区运营人员新增资源都需要审核
|
// 专区开启资源审核后,专区会员和专区运营人员新增资源都需要审核
|
||||||
|
|
@ -208,12 +223,11 @@ public class ZoneResourceServiceImpl implements IZoneResourceService {
|
||||||
public List<String> getAllFileSuffix(String fileIds) {
|
public List<String> getAllFileSuffix(String fileIds) {
|
||||||
List<SysFileInfo> sysFileInfoList = getAllFileByFileIds(fileIds);
|
List<SysFileInfo> sysFileInfoList = getAllFileByFileIds(fileIds);
|
||||||
// 获取文件后缀列表
|
// 获取文件后缀列表
|
||||||
List<String> suffixList = sysFileInfoList.stream()
|
|
||||||
|
return sysFileInfoList.stream()
|
||||||
.map(x -> x.getFileSuffix().toLowerCase())
|
.map(x -> x.getFileSuffix().toLowerCase())
|
||||||
.distinct()
|
.distinct()
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
return suffixList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<SysFileInfo> getAllFileByFileIds(String fileIds) {
|
public List<SysFileInfo> getAllFileByFileIds(String fileIds) {
|
||||||
|
|
@ -224,7 +238,7 @@ public class ZoneResourceServiceImpl implements IZoneResourceService {
|
||||||
);
|
);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("从文件微服务获取资源附件时失败:{}", e.getMessage());
|
logger.error("从文件微服务获取资源附件时失败:{}", e.getMessage());
|
||||||
throw new ServiceException("资源附件列表异常:"+e.getMessage());
|
throw new ServiceException("资源附件列表异常:" + e.getMessage());
|
||||||
}
|
}
|
||||||
return sysFileInfoList;
|
return sysFileInfoList;
|
||||||
}
|
}
|
||||||
|
|
@ -260,8 +274,16 @@ public class ZoneResourceServiceImpl implements IZoneResourceService {
|
||||||
@Override
|
@Override
|
||||||
public ZoneResourceDataVo selectOpenZoneResourceDataById(Long resourceId, HttpServletRequest request) {
|
public ZoneResourceDataVo selectOpenZoneResourceDataById(Long resourceId, HttpServletRequest request) {
|
||||||
ZoneResource zoneResource = selectZoneResourceById(resourceId);
|
ZoneResource zoneResource = selectZoneResourceById(resourceId);
|
||||||
|
|
||||||
|
if (AiResourceCategory.isAiResourceCategory(zoneResource.getResourceCategory())) {
|
||||||
|
// 增加浏览次数
|
||||||
|
incrementViewCount(zoneResource.getId());
|
||||||
|
}
|
||||||
if (ZoneConstants.RESOURCE_AUDIT_PASS.equals(zoneResource.getAuditStatus())) {
|
if (ZoneConstants.RESOURCE_AUDIT_PASS.equals(zoneResource.getAuditStatus())) {
|
||||||
return zoneResourceToData(zoneResource);
|
ZoneResourceDataVo dataVo = zoneResourceToData(zoneResource, false);
|
||||||
|
// 填充下载用户信息
|
||||||
|
populateDownloadUsers(dataVo);
|
||||||
|
return dataVo;
|
||||||
} else {
|
} else {
|
||||||
String gitLinkToken = AuthUtil.getGitLinkRequestToken(request);
|
String gitLinkToken = AuthUtil.getGitLinkRequestToken(request);
|
||||||
if (StringUtils.isNotEmpty(gitLinkToken)) {
|
if (StringUtils.isNotEmpty(gitLinkToken)) {
|
||||||
|
|
@ -269,7 +291,10 @@ public class ZoneResourceServiceImpl implements IZoneResourceService {
|
||||||
remoteUserService.getUserNameByGitLinkToken(gitLinkToken, SecurityConstants.INNER)
|
remoteUserService.getUserNameByGitLinkToken(gitLinkToken, SecurityConstants.INNER)
|
||||||
);
|
);
|
||||||
if (zoneResource.getCreateBy().equals(currentUsername)) {
|
if (zoneResource.getCreateBy().equals(currentUsername)) {
|
||||||
return zoneResourceToData(zoneResource);
|
ZoneResourceDataVo dataVo = zoneResourceToData(zoneResource, false);
|
||||||
|
// 填充下载用户信息
|
||||||
|
populateDownloadUsers(dataVo);
|
||||||
|
return dataVo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -277,13 +302,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
|
@Override
|
||||||
public GenericsTableDataInfo<ZoneResourceDataVo> selectMyZoneResourceList(ZoneResource zoneResourceSearch) {
|
public GenericsTableDataInfo<ZoneResourceDataVo> selectMyZoneResourceList(ZoneResource zoneResourceSearch) {
|
||||||
if (zoneResourceSearch == null || zoneResourceSearch.getZoneId() == null) {
|
if (zoneResourceSearch == null || zoneResourceSearch.getZoneId() == null) {
|
||||||
throw new ServiceException("获取我的资源列表时必须传入专区Id");
|
throw new ServiceException("获取我的资源列表时必须传入专区Id");
|
||||||
}
|
}
|
||||||
List<ZoneResource> zoneResourceList = zoneResourceMapper.selectZoneResourceList(zoneResourceSearch);
|
List<ZoneResource> zoneResourceList = zoneResourceMapper.selectZoneResourceList(zoneResourceSearch);
|
||||||
return PageUtils.toPage(zoneResourceList, this::zoneResourceToData);
|
return PageUtils.toPage(zoneResourceList, x -> zoneResourceToData(x, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -297,8 +412,8 @@ public class ZoneResourceServiceImpl implements IZoneResourceService {
|
||||||
* @param zoneId
|
* @param zoneId
|
||||||
* @param name
|
* @param name
|
||||||
*/
|
*/
|
||||||
private void checkNameInZone(Long zoneId, String name) {
|
private void checkNameInZone(Long zoneId, String name, String resourceCategory) {
|
||||||
ZoneResource oldZoneResource = zoneResourceMapper.selectZoneResourceByNameAndZoneId(name, zoneId);
|
ZoneResource oldZoneResource = zoneResourceMapper.selectZoneResourceByNameAndZoneId(name, zoneId, resourceCategory);
|
||||||
if (oldZoneResource != null) {
|
if (oldZoneResource != null) {
|
||||||
throw new ServiceException("该特色专区下资源名称已存在(资源名称[" + name + "])");
|
throw new ServiceException("该特色专区下资源名称已存在(资源名称[" + name + "])");
|
||||||
}
|
}
|
||||||
|
|
@ -314,7 +429,7 @@ public class ZoneResourceServiceImpl implements IZoneResourceService {
|
||||||
public ZoneResourceDataVo updateZoneResource(ZoneResourceUpdateVo zoneResourceUpdateVo) {
|
public ZoneResourceDataVo updateZoneResource(ZoneResourceUpdateVo zoneResourceUpdateVo) {
|
||||||
ZoneResource oldZoneResource = selectZoneResourceById(zoneResourceUpdateVo.getId());
|
ZoneResource oldZoneResource = selectZoneResourceById(zoneResourceUpdateVo.getId());
|
||||||
if (!oldZoneResource.getName().equals(zoneResourceUpdateVo.getName())) {
|
if (!oldZoneResource.getName().equals(zoneResourceUpdateVo.getName())) {
|
||||||
checkNameInZone(oldZoneResource.getZoneId(), zoneResourceUpdateVo.getName());
|
checkNameInZone(oldZoneResource.getZoneId(), zoneResourceUpdateVo.getName(), oldZoneResource.getResourceCategory());
|
||||||
}
|
}
|
||||||
if (zoneResourceUpdateVo.getFileIds() != null) {
|
if (zoneResourceUpdateVo.getFileIds() != null) {
|
||||||
List<ZoneResourceToType> oldResourceToTypeList = zoneResourceToTypeMapper
|
List<ZoneResourceToType> oldResourceToTypeList = zoneResourceToTypeMapper
|
||||||
|
|
@ -340,9 +455,7 @@ public class ZoneResourceServiceImpl implements IZoneResourceService {
|
||||||
// 获取删除的类型Id列表
|
// 获取删除的类型Id列表
|
||||||
Long[] delZoneResourceTypeIds = newTypeIdList
|
Long[] delZoneResourceTypeIds = newTypeIdList
|
||||||
.stream()
|
.stream()
|
||||||
.filter(x -> !newTypeIdList.contains(x))
|
.filter(x -> !newTypeIdList.contains(x)).toArray(Long[]::new);
|
||||||
.collect(Collectors.toList())
|
|
||||||
.toArray(new Long[0]);
|
|
||||||
if (delZoneResourceTypeIds.length > 0) {
|
if (delZoneResourceTypeIds.length > 0) {
|
||||||
// 删除资源与资源类型关联关系
|
// 删除资源与资源类型关联关系
|
||||||
zoneResourceToTypeMapper.deleteResourceToTypesByResourceIdAndTypeIds(oldZoneResource.getId(), delZoneResourceTypeIds);
|
zoneResourceToTypeMapper.deleteResourceToTypesByResourceIdAndTypeIds(oldZoneResource.getId(), delZoneResourceTypeIds);
|
||||||
|
|
@ -363,8 +476,10 @@ public class ZoneResourceServiceImpl implements IZoneResourceService {
|
||||||
zoneResource.setSort(setSort);
|
zoneResource.setSort(setSort);
|
||||||
}
|
}
|
||||||
ZoneDetail zoneDetail = zoneDetailService.selectZoneDetailById(oldZoneResource.getZoneId());
|
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);
|
zoneResource.setAuditStatus(ZoneConstants.RESOURCE_NOT_AUDIT);
|
||||||
}
|
}
|
||||||
zoneResourceMapper.updateZoneResource(zoneResource);
|
zoneResourceMapper.updateZoneResource(zoneResource);
|
||||||
|
|
@ -408,6 +523,462 @@ public class ZoneResourceServiceImpl implements IZoneResourceService {
|
||||||
@Override
|
@Override
|
||||||
public ZoneResourceDataVo selectZoneResourceDataById(Long id) {
|
public ZoneResourceDataVo selectZoneResourceDataById(Long id) {
|
||||||
ZoneResource zoneResource = selectZoneResourceById(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -62,10 +62,18 @@ public class ZoneConstants {
|
||||||
* 资源审核状态-审核通过
|
* 资源审核状态-审核通过
|
||||||
*/
|
*/
|
||||||
public final static String RESOURCE_AUDIT_PASS = "1";
|
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_AUDIT_FAILED = "2";
|
||||||
|
/**
|
||||||
|
* 资源审核状态-草稿
|
||||||
|
*/
|
||||||
|
public final static String RESOURCE_DRAFT = "4";
|
||||||
/**
|
/**
|
||||||
* 资源审核状态-待审核
|
* 资源审核状态-待审核
|
||||||
*/
|
*/
|
||||||
|
|
@ -83,6 +91,10 @@ public class ZoneConstants {
|
||||||
* 项目审核状态-待审核
|
* 项目审核状态-待审核
|
||||||
*/
|
*/
|
||||||
public final static String PROJECT_NOT_AUDIT = "0";
|
public final static String PROJECT_NOT_AUDIT = "0";
|
||||||
|
/**
|
||||||
|
* 资源审核状态-已下架
|
||||||
|
*/
|
||||||
|
public final static String RESOURCE_TAKEN_DOWN = "3";
|
||||||
/**
|
/**
|
||||||
* 前台权限-管理者
|
* 前台权限-管理者
|
||||||
*/
|
*/
|
||||||
|
|
@ -99,4 +111,24 @@ public class ZoneConstants {
|
||||||
public static String getFileSuffixOfResourceTypeRedisKey(Long zoneId, String fileSuffix) {
|
public static String getFileSuffixOfResourceTypeRedisKey(Long zoneId, String fileSuffix) {
|
||||||
return REDIS_KEY_FILE_SUFFIX_OF_RESOURCE_TYPE_PREFIX + zoneId + "_" + 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>
|
||||||
|
|
@ -148,6 +148,7 @@
|
||||||
</otherwise>
|
</otherwise>
|
||||||
</choose>
|
</choose>
|
||||||
<if test="isHomepage != null ">and zp.is_homepage = #{isHomepage}</if>
|
<if test="isHomepage != null ">and zp.is_homepage = #{isHomepage}</if>
|
||||||
|
<if test="createBy != null and createBy != ''">and zp.create_by = #{createBy}</if>
|
||||||
|
|
||||||
<!-- 数据范围过滤 -->
|
<!-- 数据范围过滤 -->
|
||||||
${params.dataScope}
|
${params.dataScope}
|
||||||
|
|
@ -165,6 +166,7 @@
|
||||||
<if test="name != null and name != ''">and zp.name like concat('%', #{name}, '%')</if>
|
<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="projectTypeId != null ">and zp.project_type_id = #{projectTypeId}</if>
|
||||||
<if test="zoneId != null ">and zp.zone_id = #{zoneId}</if>
|
<if test="zoneId != null ">and zp.zone_id = #{zoneId}</if>
|
||||||
|
<if test="createBy != null and createBy != ''">and zp.create_by = #{createBy}</if>
|
||||||
<choose>
|
<choose>
|
||||||
<when test="isMyProject != null and isMyProject">
|
<when test="isMyProject != null and isMyProject">
|
||||||
and zp.audit_status != '2'
|
and zp.audit_status != '2'
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
<result property="updateTime" column="update_time"/>
|
<result property="updateTime" column="update_time"/>
|
||||||
<result property="zoneId" column="zone_id"/>
|
<result property="zoneId" column="zone_id"/>
|
||||||
<result property="introduction" column="introduction"/>
|
<result property="introduction" column="introduction"/>
|
||||||
|
<result property="domainCategory" column="domain_category"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectZoneResourceDomainVo">
|
<sql id="selectZoneResourceDomainVo">
|
||||||
|
|
@ -29,7 +30,8 @@
|
||||||
update_by,
|
update_by,
|
||||||
update_time,
|
update_time,
|
||||||
introduction,
|
introduction,
|
||||||
zone_id
|
zone_id,
|
||||||
|
domain_category
|
||||||
from zone_resource_domain
|
from zone_resource_domain
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
|
|
@ -44,7 +46,8 @@
|
||||||
zrd.update_by,
|
zrd.update_by,
|
||||||
zrd.update_time,
|
zrd.update_time,
|
||||||
zrd.introduction,
|
zrd.introduction,
|
||||||
zrd.zone_id
|
zrd.zone_id,
|
||||||
|
zrd.domain_category
|
||||||
from zone_resource_domain zrd
|
from zone_resource_domain zrd
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
|
|
@ -52,6 +55,7 @@
|
||||||
<include refid="selectZoneResourceDomainAliasVo"/>
|
<include refid="selectZoneResourceDomainAliasVo"/>
|
||||||
<where>
|
<where>
|
||||||
<if test="name != null and name != ''">and zrd.name like concat('%', #{name}, '%')</if>
|
<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="sort != null ">and zrd.sort = #{sort}</if>
|
||||||
<if test="deptId != null ">and zrd.dept_id = #{deptId}</if>
|
<if test="deptId != null ">and zrd.dept_id = #{deptId}</if>
|
||||||
<if test="zoneId != null ">and zrd.zone_id = #{zoneId}</if>
|
<if test="zoneId != null ">and zrd.zone_id = #{zoneId}</if>
|
||||||
|
|
@ -70,6 +74,7 @@
|
||||||
<include refid="selectZoneResourceDomainVo"/>
|
<include refid="selectZoneResourceDomainVo"/>
|
||||||
where name = #{name}
|
where name = #{name}
|
||||||
and zone_id = #{zoneId}
|
and zone_id = #{zoneId}
|
||||||
|
and domain_category = #{domainCategory}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<insert id="insertZoneResourceDomain" parameterType="ZoneResourceDomain" useGeneratedKeys="true" keyProperty="id">
|
<insert id="insertZoneResourceDomain" parameterType="ZoneResourceDomain" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
|
@ -85,6 +90,7 @@
|
||||||
<if test="updateTime != null">update_time,</if>
|
<if test="updateTime != null">update_time,</if>
|
||||||
<if test="zoneId != null">zone_id,</if>
|
<if test="zoneId != null">zone_id,</if>
|
||||||
<if test="introduction != null">introduction,</if>
|
<if test="introduction != null">introduction,</if>
|
||||||
|
<if test="domainCategory != null">domain_category,</if>
|
||||||
</trim>
|
</trim>
|
||||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||||
<if test="name != null and name != ''">#{name},</if>
|
<if test="name != null and name != ''">#{name},</if>
|
||||||
|
|
@ -97,6 +103,7 @@
|
||||||
<if test="updateTime != null">#{updateTime},</if>
|
<if test="updateTime != null">#{updateTime},</if>
|
||||||
<if test="zoneId != null">#{zoneId},</if>
|
<if test="zoneId != null">#{zoneId},</if>
|
||||||
<if test="introduction != null">#{introduction},</if>
|
<if test="introduction != null">#{introduction},</if>
|
||||||
|
<if test="domainCategory != null">#{domainCategory},</if>
|
||||||
</trim>
|
</trim>
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
|
|
@ -113,6 +120,7 @@
|
||||||
<if test="updateTime != null">update_time = #{updateTime},</if>
|
<if test="updateTime != null">update_time = #{updateTime},</if>
|
||||||
<if test="zoneId != null">zone_id = #{zoneId},</if>
|
<if test="zoneId != null">zone_id = #{zoneId},</if>
|
||||||
<if test="introduction != null">introduction = #{introduction},</if>
|
<if test="introduction != null">introduction = #{introduction},</if>
|
||||||
|
<if test="domainCategory != null">domain_category = #{domainCategory},</if>
|
||||||
</trim>
|
</trim>
|
||||||
where id = #{id}
|
where id = #{id}
|
||||||
</update>
|
</update>
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,11 @@
|
||||||
<result property="summary" column="summary"/>
|
<result property="summary" column="summary"/>
|
||||||
<result property="remark" column="remark"/>
|
<result property="remark" column="remark"/>
|
||||||
<result property="keywords" column="keywords"/>
|
<result property="keywords" column="keywords"/>
|
||||||
<result property="extendData" column="extend_data"/>
|
<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"
|
<association property="zoneResourceDomain" column="domain_id"
|
||||||
javaType="com.microservices.zone.resource.domain.ZoneResourceDomain"
|
javaType="com.microservices.zone.resource.domain.ZoneResourceDomain"
|
||||||
resultMap="domainResult"/>
|
resultMap="domainResult"/>
|
||||||
|
|
@ -65,7 +69,10 @@
|
||||||
remark,
|
remark,
|
||||||
update_time,
|
update_time,
|
||||||
keywords,
|
keywords,
|
||||||
extend_data
|
extend_data,
|
||||||
|
resource_category,
|
||||||
|
view_count,
|
||||||
|
like_count
|
||||||
from zone_resource
|
from zone_resource
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
|
|
@ -91,6 +98,9 @@
|
||||||
zr.remark,
|
zr.remark,
|
||||||
zr.keywords,
|
zr.keywords,
|
||||||
zr.extend_data,
|
zr.extend_data,
|
||||||
|
zr.resource_category,
|
||||||
|
zr.view_count,
|
||||||
|
zr.like_count,
|
||||||
zrd.name as domain_name
|
zrd.name as domain_name
|
||||||
from zone_resource zr
|
from zone_resource zr
|
||||||
left join zone_resource_domain zrd on zr.domain_id = zrd.id
|
left join zone_resource_domain zrd on zr.domain_id = zrd.id
|
||||||
|
|
@ -119,6 +129,9 @@
|
||||||
zr.remark,
|
zr.remark,
|
||||||
zr.keywords,
|
zr.keywords,
|
||||||
zr.extend_data,
|
zr.extend_data,
|
||||||
|
zr.resource_category,
|
||||||
|
zr.view_count,
|
||||||
|
zr.like_count,
|
||||||
zrd.name as domain_name
|
zrd.name as domain_name
|
||||||
from zone_resource zr
|
from zone_resource zr
|
||||||
left join zone_resource_domain zrd on zr.domain_id = zrd.id
|
left join zone_resource_domain zrd on zr.domain_id = zrd.id
|
||||||
|
|
@ -133,9 +146,12 @@
|
||||||
<if test="deptId != null ">and zr.dept_id = #{deptId}</if>
|
<if test="deptId != null ">and zr.dept_id = #{deptId}</if>
|
||||||
<if test="createBy != null and createBy != ''">and zr.create_by = #{createBy}</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="keywords != null and keywords != ''">and zr.keywords like concat('%', #{keywords}, '%')</if>
|
||||||
|
<if test="resourceCategory != null and resourceCategory !=''">and zr.resource_category =
|
||||||
|
#{resourceCategory}
|
||||||
|
</if>
|
||||||
<choose>
|
<choose>
|
||||||
<when test="auditStatus != null and auditStatus !=''">
|
<when test="auditStatus != null and auditStatus !=''">
|
||||||
and zr.audit_status = #{auditStatus}
|
<if test="auditStatus != 'all'">and zr.audit_status = #{auditStatus}</if>
|
||||||
</when>
|
</when>
|
||||||
<otherwise>
|
<otherwise>
|
||||||
and zr.audit_status = '1'
|
and zr.audit_status = '1'
|
||||||
|
|
@ -146,7 +162,20 @@
|
||||||
</if>
|
</if>
|
||||||
<!-- 数据范围过滤 -->
|
<!-- 数据范围过滤 -->
|
||||||
${params.dataScope}
|
${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>
|
</where>
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
|
|
@ -162,6 +191,7 @@
|
||||||
<if test="searchTypeId != null ">
|
<if test="searchTypeId != null ">
|
||||||
and zr.id in (select resource_id from zone_resource_to_type where type_id=#{searchTypeId})
|
and zr.id in (select resource_id from zone_resource_to_type where type_id=#{searchTypeId})
|
||||||
</if>
|
</if>
|
||||||
|
<if test="resourceCategory != null and resourceCategory !=''">and zr.resource_category = #{resourceCategory}</if>
|
||||||
<choose>
|
<choose>
|
||||||
<when test="auditStatus != null and auditStatus !=''">
|
<when test="auditStatus != null and auditStatus !=''">
|
||||||
and zr.audit_status = #{auditStatus}
|
and zr.audit_status = #{auditStatus}
|
||||||
|
|
@ -173,12 +203,17 @@
|
||||||
<!-- 数据范围过滤 -->
|
<!-- 数据范围过滤 -->
|
||||||
${params.dataScope}
|
${params.dataScope}
|
||||||
<choose>
|
<choose>
|
||||||
<when test="orderByDownloadCount != null and orderByDownloadCount">
|
<when test="resourceCategory != null and resourceCategory !=''">
|
||||||
order by (select sum(download_count) from sys_file_info sf where FIND_IN_SET(sf.file_id,zr.file_ids)) desc
|
ORDER BY
|
||||||
</when>
|
zr.like_count DESC,
|
||||||
<when test="orderByDownloadCount = null and orderByColumn = null and orderByColumn =''">
|
(select sum(download_count) from sys_file_info sf where FIND_IN_SET(sf.file_id,zr.file_ids)) desc,
|
||||||
order by zr.sort DESC
|
zr.view_count DESC,
|
||||||
|
zr.update_time DESC,
|
||||||
|
zr.create_time DESC
|
||||||
</when>
|
</when>
|
||||||
|
<otherwise>
|
||||||
|
ORDER BY zr.sort DESC
|
||||||
|
</otherwise>
|
||||||
</choose>
|
</choose>
|
||||||
</where>
|
</where>
|
||||||
</sql>
|
</sql>
|
||||||
|
|
@ -202,6 +237,7 @@
|
||||||
<include refid="selectZoneResourceVo"/>
|
<include refid="selectZoneResourceVo"/>
|
||||||
where name = #{name}
|
where name = #{name}
|
||||||
and zone_id = #{zoneId}
|
and zone_id = #{zoneId}
|
||||||
|
and resource_category = #{resourceCategory}
|
||||||
</select>
|
</select>
|
||||||
<select id="selectZoneResourceCount" resultType="java.lang.Integer">
|
<select id="selectZoneResourceCount" resultType="java.lang.Integer">
|
||||||
select count(0)
|
select count(0)
|
||||||
|
|
@ -237,6 +273,9 @@
|
||||||
<if test="remark != null">remark,</if>
|
<if test="remark != null">remark,</if>
|
||||||
<if test="keywords != null">keywords,</if>
|
<if test="keywords != null">keywords,</if>
|
||||||
<if test="extendData != null">extend_data,</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>
|
||||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||||
<if test="name != null">#{name},</if>
|
<if test="name != null">#{name},</if>
|
||||||
|
|
@ -259,7 +298,10 @@
|
||||||
<if test="auditBy != null">#{auditBy},</if>
|
<if test="auditBy != null">#{auditBy},</if>
|
||||||
<if test="remark != null">#{remark},</if>
|
<if test="remark != null">#{remark},</if>
|
||||||
<if test="keywords != null">#{keywords},</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>
|
</trim>
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
|
|
@ -285,7 +327,10 @@
|
||||||
<if test="auditTime != null">audit_time = #{auditTime},</if>
|
<if test="auditTime != null">audit_time = #{auditTime},</if>
|
||||||
<if test="auditBy != null">audit_by = #{auditBy},</if>
|
<if test="auditBy != null">audit_by = #{auditBy},</if>
|
||||||
<if test="keywords != null">keywords = #{keywords},</if>
|
<if test="keywords != null">keywords = #{keywords},</if>
|
||||||
<if test="extendData != null">extend_data = #{extendData},</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>
|
</trim>
|
||||||
where id = #{id}
|
where id = #{id}
|
||||||
</update>
|
</update>
|
||||||
|
|
@ -354,4 +399,27 @@
|
||||||
AND zr.zone_id = #{zoneId}
|
AND zr.zone_id = #{zoneId}
|
||||||
</if>
|
</if>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<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>
|
</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>
|
<pagehelper.boot.version>1.4.6</pagehelper.boot.version>
|
||||||
<druid.version>1.2.16</druid.version>
|
<druid.version>1.2.16</druid.version>
|
||||||
<dynamic-ds.version>3.5.2</dynamic-ds.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>
|
<velocity.version>2.3</velocity.version>
|
||||||
<fastjson.version>2.0.42</fastjson.version>
|
<fastjson.version>2.0.42</fastjson.version>
|
||||||
<jjwt.version>0.9.1</jjwt.version>
|
<jjwt.version>0.9.1</jjwt.version>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue