forked from Gitlink/microservices
Compare commits
319 Commits
dev_monito
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
dcae975112 | |
|
|
c641ae6ab2 | |
|
|
7e5127b078 | |
|
|
0e13149981 | |
|
|
fd0d726a33 | |
|
|
58387872f0 | |
|
|
b62c7a6164 | |
|
|
62d4418679 | |
|
|
ca4c8bb614 | |
|
|
2a0d9c89e7 | |
|
|
bbe9fa517e | |
|
|
3cf58c94d9 | |
|
|
d1040faf52 | |
|
|
49a58bed6c | |
|
|
76f865624e | |
|
|
385dd55c25 | |
|
|
9d1d5527c8 | |
|
|
47e24de4fb | |
|
|
7a44a2113d | |
|
|
1bf9522d8f | |
|
|
5aa499f4de | |
|
|
35d5b1318e | |
|
|
4ccc743bd2 | |
|
|
84f624fdb7 | |
|
|
e43fb391fa | |
|
|
fc9e511aa0 | |
|
|
0abc43f033 | |
|
|
9fe4c69487 | |
|
|
d2d2f7368c | |
|
|
77125630e5 | |
|
|
ab53fa6ee0 | |
|
|
45f77beb73 | |
|
|
62c56097e2 | |
|
|
66c8132fe3 | |
|
|
f0725c78a1 | |
|
|
7093cda7bb | |
|
|
2a075ab571 | |
|
|
eba2082e2b | |
|
|
2cf4762b2c | |
|
|
b43c6384d3 | |
|
|
4d378d560d | |
|
|
6006440878 | |
|
|
4bbd80f739 | |
|
|
06633ff0a8 | |
|
|
6688c1f0e1 | |
|
|
93fb7eacf0 | |
|
|
5d6a64a4cc | |
|
|
887f4d1616 | |
|
|
49576e5ad0 | |
|
|
666c9c7b5e | |
|
|
95e5178de8 | |
|
|
8ce9a96fee | |
|
|
43572e7698 | |
|
|
3a5c9e03cf | |
|
|
bb2f414aa4 | |
|
|
76fa1f5447 | |
|
|
5e20807aed | |
|
|
83566e947a | |
|
|
f3a718d75a | |
|
|
7c6614bddb | |
|
|
c2b992c990 | |
|
|
0e63d5efcd | |
|
|
29c09c7091 | |
|
|
7506ec7cb2 | |
|
|
0132d56eb2 | |
|
|
93edb6b138 | |
|
|
3599ac3d4a | |
|
|
44e7ed5605 | |
|
|
b1d46f9471 | |
|
|
9b0b8c4362 | |
|
|
f9d779704a | |
|
|
2fa5be38b2 | |
|
|
22f3a8dc6b | |
|
|
df594efd9a | |
|
|
d2e0c54cdd | |
|
|
dc65b2d299 | |
|
|
d62d474848 | |
|
|
388423d53c | |
|
|
adbb5d04db | |
|
|
ee2ae04586 | |
|
|
2decec46ee | |
|
|
4402c972c9 | |
|
|
14748b4253 | |
|
|
ac22a5ba15 | |
|
|
f80bc0219c | |
|
|
941f30d6b3 | |
|
|
19dd235a26 | |
|
|
dea2c5e4d6 | |
|
|
5f1e8ddaf9 | |
|
|
1f3f3546a6 | |
|
|
f2d18d8334 | |
|
|
f491cd4753 | |
|
|
7bf2a532b3 | |
|
|
87a79ee7f4 | |
|
|
c358ffcfb7 | |
|
|
3e4f489914 | |
|
|
e559bcc48a | |
|
|
3b599bed3f | |
|
|
8c68611369 | |
|
|
1ef0cdcef2 | |
|
|
75ca1eff4d | |
|
|
8a69939e84 | |
|
|
77e3a9e0d3 | |
|
|
effaebe0d4 | |
|
|
1a8b537dae | |
|
|
86c3f502f1 | |
|
|
0ddac0b423 | |
|
|
9543d7fa38 | |
|
|
e7fcf69c9a | |
|
|
b26081dc17 | |
|
|
21658c6b66 | |
|
|
80e7b1f1b3 | |
|
|
85b19136a8 | |
|
|
e28691709f | |
|
|
2bb93099b5 | |
|
|
d98998422d | |
|
|
e7478556d4 | |
|
|
22ac8bec90 | |
|
|
bd34c5beae | |
|
|
33dd51c5f1 | |
|
|
ebe565af2e | |
|
|
30a3119580 | |
|
|
68673c355a | |
|
|
6c1a4589e7 | |
|
|
bc4d402c88 | |
|
|
21868ae523 | |
|
|
1ac64877f8 | |
|
|
9fa1545e11 | |
|
|
845993f7c8 | |
|
|
2edc3bacc7 | |
|
|
fc08a962ce | |
|
|
541b43d768 | |
|
|
6bb1b3c6eb | |
|
|
b784d44e26 | |
|
|
012ed2b25b | |
|
|
7219a66b5a | |
|
|
24b117a5a2 | |
|
|
590bc076c1 | |
|
|
41baf01039 | |
|
|
e3d33c0507 | |
|
|
3e10ed1b54 | |
|
|
883fdfdb75 | |
|
|
da4c832e82 | |
|
|
92bea2ec47 | |
|
|
ff38c7a6a0 | |
|
|
16fc7fd3ee | |
|
|
f61117526e | |
|
|
a00cdb4238 | |
|
|
c61346b4d9 | |
|
|
c734737e32 | |
|
|
fc6d67b1ed | |
|
|
1ea5e87767 | |
|
|
f1bb7f7b3f | |
|
|
0f97696614 | |
|
|
fcfd415fdc | |
|
|
e12afda308 | |
|
|
d882970a32 | |
|
|
eb22a4ebeb | |
|
|
fcd190bfac | |
|
|
5b05d90522 | |
|
|
40620e18b5 | |
|
|
7ffbdad4bc | |
|
|
31ad557697 | |
|
|
8f5b4f1882 | |
|
|
820ecc2d61 | |
|
|
a75d2b55fd | |
|
|
c218b86a39 | |
|
|
94fb839f2c | |
|
|
d631abe3a1 | |
|
|
e2ab5396ea | |
|
|
6982b6451d | |
|
|
3b178aa0a6 | |
|
|
b62c85f373 | |
|
|
215d7b6914 | |
|
|
6e29169fa4 | |
|
|
6c0c459d60 | |
|
|
18d0a9ce3e | |
|
|
7930e4a155 | |
|
|
77d8a8ae23 | |
|
|
8dfac68f66 | |
|
|
eef0e81d11 | |
|
|
054905987f | |
|
|
91ce2fff08 | |
|
|
08f6aaa414 | |
|
|
7582c5cb11 | |
|
|
f7229b6d42 | |
|
|
2fe06ea788 | |
|
|
ffc5f2e26e | |
|
|
01eb5464e4 | |
|
|
13d34fb2e9 | |
|
|
6830387bb8 | |
|
|
b429c568f5 | |
|
|
e2347e6609 | |
|
|
9305ae91c6 | |
|
|
7dbbbbb090 | |
|
|
7f0d3d4dcb | |
|
|
6c62ba619b | |
|
|
6fb9c13bba | |
|
|
900dcdf4d7 | |
|
|
a6a656820a | |
|
|
0bd824966e | |
|
|
543689fc61 | |
|
|
d8c951f9e0 | |
|
|
f6f2ee0f5d | |
|
|
7376dcbef4 | |
|
|
fbb40e3359 | |
|
|
5318bc1a8e | |
|
|
cbb08235f3 | |
|
|
dc6428df41 | |
|
|
b39ea22a43 | |
|
|
9404cc38e3 | |
|
|
beb6c6e727 | |
|
|
8270bcacd6 | |
|
|
2897c163f3 | |
|
|
16cb2b3477 | |
|
|
354c10f841 | |
|
|
0cf376a45d | |
|
|
431cd7404d | |
|
|
181546fab2 | |
|
|
3389e839a1 | |
|
|
fded90d57c | |
|
|
a0384614d3 | |
|
|
cd483c065d | |
|
|
822505b989 | |
|
|
9799da2fef | |
|
|
43149d52bf | |
|
|
aff24c83d2 | |
|
|
1c6563f915 | |
|
|
4f5f9a283c | |
|
|
a688c9b836 | |
|
|
77cd5bbc3a | |
|
|
e493757e2b | |
|
|
e80a0ded7a | |
|
|
c3962a9e3d | |
|
|
481c3ddfbd | |
|
|
d30cfd56f2 | |
|
|
8c1832780f | |
|
|
cc52682aa8 | |
|
|
fec5dd7f3b | |
|
|
8f2ff93dca | |
|
|
8850ed8fbc | |
|
|
45bcfeafca | |
|
|
4126803cbe | |
|
|
cf03c82425 | |
|
|
9f1d89da49 | |
|
|
9301975900 | |
|
|
33ff6795d1 | |
|
|
27bd86998c | |
|
|
1f4b5c6140 | |
|
|
56424116ae | |
|
|
995e5016fe | |
|
|
5e8f9fec06 | |
|
|
ed0912a1a6 | |
|
|
5e9bc67cb7 | |
|
|
22a212a72a | |
|
|
37dcb5b98c | |
|
|
1141da00bf | |
|
|
4847718321 | |
|
|
cdead12a04 | |
|
|
e906a46eec | |
|
|
53e5c8c13b | |
|
|
ee43cbeaa8 | |
|
|
8aa1e777bb | |
|
|
8d8294ba6f | |
|
|
5ef10bfc1f | |
|
|
b71ec53ab1 | |
|
|
b6034b469c | |
|
|
66ffcc7595 | |
|
|
f015936a45 | |
|
|
f86d3213cd | |
|
|
7b4ee072dd | |
|
|
f336e702ac | |
|
|
613bc5fc07 | |
|
|
a4bdcb1547 | |
|
|
365ab34101 | |
|
|
8faedfebe0 | |
|
|
9edc2b8be9 | |
|
|
c22addc4e5 | |
|
|
43d2ce4231 | |
|
|
fca7e4e502 | |
|
|
e92c412c4e | |
|
|
f94a885908 | |
|
|
5776ddd748 | |
|
|
661709efaf | |
|
|
7760f5afb3 | |
|
|
575170a801 | |
|
|
be80d64dd5 | |
|
|
6f1068f082 | |
|
|
3589a9b51a | |
|
|
e9e3852364 | |
|
|
1aabdcf752 | |
|
|
44f0bfb4c8 | |
|
|
ab58222217 | |
|
|
a3ccf62c10 | |
|
|
a4e92ca636 | |
|
|
14fc5ffe82 | |
|
|
33546ec525 | |
|
|
57826cc52a | |
|
|
2c5a1c3e43 | |
|
|
4b81b296da | |
|
|
8bfd8c6e1a | |
|
|
3e4c801cbd | |
|
|
c76b6efa20 | |
|
|
0d833872b6 | |
|
|
4782b8c0c0 | |
|
|
e792599fc3 | |
|
|
5899277c28 | |
|
|
2e9c3d0f9d | |
|
|
457c93d821 | |
|
|
b7e68454ac | |
|
|
a54f6fc8a4 | |
|
|
5527e4ba6a | |
|
|
e3921d833b | |
|
|
b69d1f16e9 | |
|
|
43a3245ec0 | |
|
|
723825064c | |
|
|
16e941df45 | |
|
|
bfdbe31287 | |
|
|
e80d7f8a6f |
|
|
@ -9,6 +9,8 @@ import com.microservices.system.api.factory.RemoteCmsFallbackFactory;
|
|||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户服务
|
||||
*
|
||||
|
|
@ -158,4 +160,22 @@ public interface RemoteCmsService {
|
|||
@GetMapping("/doc/getJSONObject/{cmsDocId}")
|
||||
R<JSONObject> selectCmsDocJSONObjectById(@PathVariable(value = "cmsDocId") Long cmsDocId
|
||||
, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
||||
|
||||
@PutMapping("/project/changeOrgRole/user/{userId}/operate/{operate}")
|
||||
R<Boolean> changeUserOrgRole(@PathVariable("userId") Long userId, @PathVariable("operate") int operate
|
||||
, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
||||
|
||||
/**
|
||||
* 首页文章合并列表(接收置顶文章 docId 有序列表,返回合并去重后的结果)
|
||||
* 供 Zone 服务 /open/getTopDoc 改造后调用
|
||||
*
|
||||
* @param zoneId 专区ID
|
||||
* @param topDocIds 置顶文章 docId 有序列表
|
||||
* @param source 来源
|
||||
* @return 合并后的首页文章 JSONObject 列表
|
||||
*/
|
||||
@PostMapping("/doc/homePageDocMergeList/{zoneId}")
|
||||
R<List<JSONObject>> selectHomePageDocMergeList(@PathVariable("zoneId") Long zoneId,
|
||||
@RequestBody List<Long> topDocIds,
|
||||
@RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ public interface RemoteDeptService {
|
|||
* 根据组织id删除部门
|
||||
*
|
||||
* @param deptId 部门id
|
||||
* @param source 请求来源
|
||||
* @param source 请求来源
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
@DeleteMapping("/dept/deleteDeptByDeptId")
|
||||
|
|
@ -105,7 +105,7 @@ public interface RemoteDeptService {
|
|||
* 通过用户名查询用户下组织及组织对应角色列表
|
||||
*
|
||||
* @param deptId 组织ID
|
||||
* @param source 请求来源
|
||||
* @param source 请求来源
|
||||
* @return 用户下组织及组织对应角色列表
|
||||
*/
|
||||
@GetMapping("/SysUserDeptRole/userByDeptId/{deptId}/list")
|
||||
|
|
@ -115,7 +115,7 @@ public interface RemoteDeptService {
|
|||
/**
|
||||
* 查询组织编号下拥有传入角色权限字符串的用户名
|
||||
*
|
||||
* @param deptId 组织编号
|
||||
* @param deptId 组织编号
|
||||
* @param roleKeys 角色权限字符串
|
||||
* @return 组织下拥有传入角色权限字符串的用户名列表
|
||||
*/
|
||||
|
|
@ -126,7 +126,7 @@ public interface RemoteDeptService {
|
|||
* 检查用户是否在指定部门下具有指定角色
|
||||
*
|
||||
* @param currentDeptId 组织编号
|
||||
* @param roleKey 角色权限字符串
|
||||
* @param roleKey 角色权限字符串
|
||||
* @return 组织下拥有传入角色权限字符串的用户名列表
|
||||
*/
|
||||
@GetMapping("/SysUserDeptRole/checkUserInDeptHasRole")
|
||||
|
|
@ -143,6 +143,17 @@ public interface RemoteDeptService {
|
|||
@GetMapping("/SysUserDeptRole/checkUserInDeptHasPerms")
|
||||
R<Boolean> checkUserInDeptHasPerms(@RequestParam("currentDeptId") Long currentDeptId, @RequestParam("userId") Long userId, @RequestParam("perms") String perms, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
||||
|
||||
/**
|
||||
* 检查用户是否在指定部门或指定部门的父级部门下具有指定菜单权限
|
||||
*
|
||||
* @param deptId 指定组织id
|
||||
* @param userId 用户id
|
||||
* @param perms 菜单权限
|
||||
* @return 用户是否在指定部门下具有菜单权限 是true 否false
|
||||
*/
|
||||
@GetMapping("/SysUserDeptRole/checkUserInDeptOrParentDeptHasPerms")
|
||||
R<Boolean> checkUserInDeptOrParentDeptHasPerms(@RequestParam("deptId") Long deptId, @RequestParam("userId") Long userId, @RequestParam("perms") String perms, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
||||
|
||||
/**
|
||||
* 检查用户是否在指定部门下
|
||||
*
|
||||
|
|
@ -152,4 +163,7 @@ public interface RemoteDeptService {
|
|||
*/
|
||||
@GetMapping("/SysUserDeptRole/checkUserInDept")
|
||||
R<Boolean> checkUserInDept(@RequestParam("currentDeptId") Long currentDeptId, @RequestParam("userId") Long userId, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
||||
|
||||
@GetMapping("/SysUserDeptRole/getSysUserDeptRoleByDeptIdAndUserId")
|
||||
R<SysUserDeptRole> getSysUserDeptRoleByDeptIdAndUserId(@RequestParam("deptId") Long deptId, @RequestParam("userId") Long userId, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,15 @@ public interface RemoteUserService {
|
|||
@GetMapping("/user/getSysUserByUserId/{userId}")
|
||||
public R<SysUser> getSysUserByUserId(@PathVariable("userId") Long userId, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
||||
|
||||
/**
|
||||
* 通过用户ID数组批量获取用户信息(POST方法,解决URL长度限制)
|
||||
*
|
||||
* @param userIds 用户ID数组
|
||||
* @param source 请求来源
|
||||
* @return 结果
|
||||
*/
|
||||
@PostMapping("/user/getSysUserListByUserIds")
|
||||
R<List<SysUser>> postSysUserListByUserIds(@RequestBody Long[] userIds, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
||||
|
||||
/**
|
||||
* 组织下新增用户身份
|
||||
|
|
@ -64,7 +73,7 @@ public interface RemoteUserService {
|
|||
* @param deptId
|
||||
* @param userId
|
||||
* @param roleKey
|
||||
* @param source 请求来源
|
||||
* @param source 请求来源
|
||||
* @return 结果
|
||||
*/
|
||||
@PostMapping("/SysUserDeptRole/dept/{deptId}/user/{userId}/role/{roleKey}")
|
||||
|
|
@ -74,14 +83,13 @@ public interface RemoteUserService {
|
|||
, @PathVariable("roleKey") String roleKey
|
||||
, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
||||
|
||||
|
||||
/**
|
||||
* 组织下删除用户身份
|
||||
*
|
||||
* @param deptId
|
||||
* @param userId
|
||||
* @param roleKey
|
||||
* @param source 请求来源
|
||||
* @param source 请求来源
|
||||
* @return 结果
|
||||
*/
|
||||
@DeleteMapping("/SysUserDeptRole/dept/{deptId}/user/{userId}/role/{roleKey}")
|
||||
|
|
@ -91,18 +99,16 @@ public interface RemoteUserService {
|
|||
@PathVariable("roleKey") String roleKey,
|
||||
@RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
||||
|
||||
|
||||
/**
|
||||
* 注册用户信息
|
||||
*
|
||||
* @param sysUser 用户信息
|
||||
* @param source 请求来源
|
||||
* @param source 请求来源
|
||||
* @return 结果
|
||||
*/
|
||||
@PostMapping("/user/register")
|
||||
public R<Boolean> registerUserInfo(@RequestBody SysUser sysUser, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
||||
|
||||
|
||||
/**
|
||||
* 通过GitLinkToken获取本系统Token
|
||||
*
|
||||
|
|
@ -123,7 +129,6 @@ public interface RemoteUserService {
|
|||
@GetMapping("/SysUserDeptRole/userByUserName/{userName}/list")
|
||||
public R<List<SysUserDeptRole>> getSysUserDeptRoleListByUserName(@PathVariable("userName") String userName, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
||||
|
||||
|
||||
/**
|
||||
* 通过用户名查询用户下第一个身份
|
||||
*
|
||||
|
|
@ -195,7 +200,7 @@ public interface RemoteUserService {
|
|||
/**
|
||||
* 通过GitLink用户Id创建用户
|
||||
*
|
||||
* @param gitLinkUserId GitLink用户Id
|
||||
* @param gitLinkUserId GitLinkUserId
|
||||
* @param source 请求来源
|
||||
* @return 系统用户
|
||||
*/
|
||||
|
|
@ -206,7 +211,7 @@ public interface RemoteUserService {
|
|||
* 根据角色Key获取菜单权限集合
|
||||
*
|
||||
* @param roleKey 角色Key
|
||||
* @param source 请求来源
|
||||
* @param source 请求来源
|
||||
* @return 菜单权限集合
|
||||
*/
|
||||
@GetMapping("/menu/roleMenuPermissionByRoleKey/{roleKey}")
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import org.slf4j.LoggerFactory;
|
|||
import org.springframework.cloud.openfeign.FallbackFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 内容管理服务降级处理
|
||||
*
|
||||
|
|
@ -83,6 +85,16 @@ public class RemoteCmsFallbackFactory implements FallbackFactory<RemoteCmsServic
|
|||
public R<JSONObject> selectCmsDocJSONObjectById(Long cmsDocId, String source) {
|
||||
return R.fail("获取文章JSON对象失败:" + throwable.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<Boolean> changeUserOrgRole(Long userId, int operate, String source) {
|
||||
return R.fail("更新组织权限失败:" + throwable.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<List<JSONObject>> selectHomePageDocMergeList(Long zoneId, List<Long> topDocIds, String source) {
|
||||
return R.fail("获取首页文章合并列表失败:" + throwable.getMessage());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,10 +83,20 @@ public class RemoteDeptFallbackFactory implements FallbackFactory<RemoteDeptServ
|
|||
return R.fail("检查用户是否在指定部门下具有指定菜单权限失败:" + throwable.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<Boolean> checkUserInDeptOrParentDeptHasPerms(Long deptId, Long userId, String perms, String source) {
|
||||
return R.fail("检查用户是否在指定部门或指定部门的父级部门下具有指定菜单权限失败:" + throwable.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<Boolean> checkUserInDept(Long currentDeptId, Long userId, String source) {
|
||||
return R.fail("检查用户是否在指定部门下失败:" + throwable.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<SysUserDeptRole> getSysUserDeptRoleByDeptIdAndUserId(Long deptId, Long userId, String source) {
|
||||
return R.fail("获取用户在指定部门下的角色失败:" + throwable.getMessage());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,12 @@ public class RemoteUserFallbackFactory implements FallbackFactory<RemoteUserServ
|
|||
return R.fail("获取用户失败:" + throwable.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<List<SysUser>> postSysUserListByUserIds(Long[] userIds, String source) {
|
||||
|
||||
return R.fail("批量获取用户失败:" + throwable.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<Boolean> addUserIdentify(Long deptId, Long userId, String roleKey, String source) {
|
||||
return R.fail("组织下新增用户身份失败:" + throwable.getMessage());
|
||||
|
|
|
|||
|
|
@ -80,6 +80,10 @@ public class CacheConstants {
|
|||
* 通过组织Id获取专区详情JSON对象
|
||||
*/
|
||||
public final static String ZONE_JSON_OF_DEPT_ID_KEY = "zoneJSONOfDeptId:";
|
||||
/**
|
||||
* 积分发放部门
|
||||
*/
|
||||
public final static String ZONE_POINTS_DEPT_ID_KEY = "zonePointsDeptId";
|
||||
/**
|
||||
* 内容管理同步缓存前缀
|
||||
*/
|
||||
|
|
@ -245,6 +249,18 @@ public class CacheConstants {
|
|||
return ZONE_PROJECT_SORT_BY_SCORE + "*:" + zoneId + ":*";
|
||||
}
|
||||
|
||||
/**
|
||||
* 专区统计数据缓存前缀
|
||||
*/
|
||||
private final static String ZONE_STATISTICS_KEY = "zone:statistics:";
|
||||
|
||||
/**
|
||||
* 获取专区统计数据缓存key
|
||||
*/
|
||||
public static String getZoneStatisticsKey(Long zoneId) {
|
||||
return ZONE_STATISTICS_KEY + zoneId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件打包是否完成(0:代表未完成;1:代表已完成;-1:代表打包失败,需要重新打包)
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ package com.microservices.common.core.constant;
|
|||
*
|
||||
* @author microservices
|
||||
*/
|
||||
public class UserConstants
|
||||
{
|
||||
public class UserConstants {
|
||||
/**
|
||||
* 平台内系统用户的唯一标志
|
||||
*/
|
||||
|
|
@ -28,55 +27,89 @@ public class UserConstants
|
|||
*/
|
||||
public static final String DEL_FLAG_FALSE = "0";
|
||||
|
||||
/** 正常状态 */
|
||||
/**
|
||||
* 正常状态
|
||||
*/
|
||||
public static final String NORMAL = "0";
|
||||
|
||||
/** 异常状态 */
|
||||
/**
|
||||
* 异常状态
|
||||
*/
|
||||
public static final String EXCEPTION = "1";
|
||||
|
||||
/** 用户封禁状态 */
|
||||
/**
|
||||
* 用户封禁状态
|
||||
*/
|
||||
public static final String USER_DISABLE = "1";
|
||||
|
||||
/** 角色封禁状态 */
|
||||
/**
|
||||
* 角色封禁状态
|
||||
*/
|
||||
public static final String ROLE_DISABLE = "1";
|
||||
|
||||
/** 部门正常状态 */
|
||||
/**
|
||||
* 部门正常状态
|
||||
*/
|
||||
public static final String DEPT_NORMAL = "0";
|
||||
|
||||
/** 部门停用状态 */
|
||||
/**
|
||||
* 部门停用状态
|
||||
*/
|
||||
public static final String DEPT_DISABLE = "1";
|
||||
|
||||
/** 字典正常状态 */
|
||||
/**
|
||||
* 字典正常状态
|
||||
*/
|
||||
public static final String DICT_NORMAL = "0";
|
||||
|
||||
/** 是否为系统默认(是) */
|
||||
/**
|
||||
* 是否为系统默认(是)
|
||||
*/
|
||||
public static final String YES = "Y";
|
||||
|
||||
/** 是否菜单外链(是) */
|
||||
/**
|
||||
* 是否菜单外链(是)
|
||||
*/
|
||||
public static final String YES_FRAME = "0";
|
||||
|
||||
/** 是否菜单外链(否) */
|
||||
/**
|
||||
* 是否菜单外链(否)
|
||||
*/
|
||||
public static final String NO_FRAME = "1";
|
||||
|
||||
/** 菜单类型(目录) */
|
||||
/**
|
||||
* 菜单类型(目录)
|
||||
*/
|
||||
public static final String TYPE_DIR = "M";
|
||||
|
||||
/** 菜单类型(菜单) */
|
||||
/**
|
||||
* 菜单类型(菜单)
|
||||
*/
|
||||
public static final String TYPE_MENU = "C";
|
||||
|
||||
/** 菜单类型(按钮) */
|
||||
/**
|
||||
* 菜单类型(按钮)
|
||||
*/
|
||||
public static final String TYPE_BUTTON = "F";
|
||||
|
||||
/** Layout组件标识 */
|
||||
/**
|
||||
* Layout组件标识
|
||||
*/
|
||||
public final static String LAYOUT = "Layout";
|
||||
|
||||
/** ParentView组件标识 */
|
||||
/**
|
||||
* ParentView组件标识
|
||||
*/
|
||||
public final static String PARENT_VIEW = "ParentView";
|
||||
|
||||
/** InnerLink组件标识 */
|
||||
/**
|
||||
* InnerLink组件标识
|
||||
*/
|
||||
public final static String INNER_LINK = "InnerLink";
|
||||
|
||||
/** 校验是否唯一的返回标识 */
|
||||
/**
|
||||
* 校验是否唯一的返回标识
|
||||
*/
|
||||
public final static boolean UNIQUE = true;
|
||||
public final static boolean NOT_UNIQUE = false;
|
||||
|
||||
|
|
@ -122,4 +155,28 @@ public class UserConstants
|
|||
* 项目管理-知识库管理权限
|
||||
*/
|
||||
public static final String PMS_PERMS_DOCS_MANAGE = "pms:docs:manage";
|
||||
/**
|
||||
* 文章管理-文章删除权限
|
||||
*/
|
||||
public static final String CMS_PERMS_DOCS_DELETE = "cms:doc:remove";
|
||||
/**
|
||||
* 文章管理-新增文章权限
|
||||
*/
|
||||
public static final String CMS_PERMS_DOCS_ADD = "cms:doc:add";
|
||||
/**
|
||||
* 文章管理-编辑文章权限
|
||||
*/
|
||||
public static final String CMS_PERMS_DOCS_EDIT = "cms:doc:edit";
|
||||
/**
|
||||
* 文章管理-审核文章权限
|
||||
*/
|
||||
public static final String CMS_PERMS_DOCS_AUDIT = "cms:doc:audit";
|
||||
/**
|
||||
* 专区管理-审核项目权限
|
||||
*/
|
||||
public static final String ZONE_PERMS_PROJECT_AUDIT = "zone:project:audit";
|
||||
/**
|
||||
* 专区管理-审核资源权限
|
||||
*/
|
||||
public static final String ZONE_PERMS_RESOURCE_AUDIT = "zone:resource:audit";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ package com.microservices.common.core.enums;
|
|||
public enum DocAndZoneOperation
|
||||
{
|
||||
OPERATION_CREATE("创建", "create"), OPERATION_UPDATE("更新", "update"),
|
||||
OPERATION_DELETE("删除", "delete");
|
||||
OPERATION_DELETE("删除", "delete"), OPERATION_NON_REPO_UPDATE("非仓库更新", "nonRepoUpdate");
|
||||
|
||||
private final String operationName;
|
||||
private final String operationCode;
|
||||
|
|
|
|||
|
|
@ -211,6 +211,17 @@ public class HttpAPIService {
|
|||
return doRequest(httpDelete, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 不带Header的get请求
|
||||
*
|
||||
* @param url
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public JSONObject doGet(String url) throws Exception {
|
||||
return doGet(url, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 带Header的get请求
|
||||
*
|
||||
|
|
|
|||
|
|
@ -115,8 +115,7 @@ public class AuthLogic {
|
|||
* @param permission 权限字符串
|
||||
* @return 用户是否具备某权限
|
||||
*/
|
||||
public boolean hasPermi(String permission)
|
||||
{
|
||||
public boolean hasPermi(String permission) {
|
||||
return hasPermi(tokenService.getMenuPermissions(), permission);
|
||||
}
|
||||
|
||||
|
|
@ -126,10 +125,8 @@ public class AuthLogic {
|
|||
* @param permission 权限字符串
|
||||
* @return 用户是否具备某权限
|
||||
*/
|
||||
public void checkPermi(String permission)
|
||||
{
|
||||
if (!hasPermi(tokenService.getMenuPermissions(), permission))
|
||||
{
|
||||
public void checkPermi(String permission) {
|
||||
if (!hasPermi(tokenService.getMenuPermissions(), permission)) {
|
||||
throw new NotPermissionException(permission);
|
||||
}
|
||||
}
|
||||
|
|
@ -153,13 +150,10 @@ public class AuthLogic {
|
|||
*
|
||||
* @param permissions 权限列表
|
||||
*/
|
||||
public void checkPermiAnd(String... permissions)
|
||||
{
|
||||
public void checkPermiAnd(String... permissions) {
|
||||
Set<String> permissionList = tokenService.getMenuPermissions();
|
||||
for (String permission : permissions)
|
||||
{
|
||||
if (!hasPermi(permissionList, permission))
|
||||
{
|
||||
for (String permission : permissions) {
|
||||
if (!hasPermi(permissionList, permission)) {
|
||||
throw new NotPermissionException(permission);
|
||||
}
|
||||
}
|
||||
|
|
@ -170,13 +164,10 @@ public class AuthLogic {
|
|||
*
|
||||
* @param permissions 权限码数组
|
||||
*/
|
||||
public void checkPermiOr(String... permissions)
|
||||
{
|
||||
public void checkPermiOr(String... permissions) {
|
||||
Set<String> permissionList = tokenService.getMenuPermissions();
|
||||
for (String permission : permissions)
|
||||
{
|
||||
if (hasPermi(permissionList, permission))
|
||||
{
|
||||
for (String permission : permissions) {
|
||||
if (hasPermi(permissionList, permission)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -191,8 +182,7 @@ public class AuthLogic {
|
|||
* @param role 角色标识
|
||||
* @return 用户是否具备某角色
|
||||
*/
|
||||
public boolean hasRole(String role)
|
||||
{
|
||||
public boolean hasRole(String role) {
|
||||
return hasRole(tokenService.getRolePermissions(), role);
|
||||
}
|
||||
|
||||
|
|
@ -225,13 +215,10 @@ public class AuthLogic {
|
|||
*
|
||||
* @param roles 角色标识数组
|
||||
*/
|
||||
public void checkRoleAnd(String... roles)
|
||||
{
|
||||
public void checkRoleAnd(String... roles) {
|
||||
Set<String> roleList = tokenService.getRolePermissions();
|
||||
for (String role : roles)
|
||||
{
|
||||
if (!hasRole(roleList, role))
|
||||
{
|
||||
for (String role : roles) {
|
||||
if (!hasRole(roleList, role)) {
|
||||
throw new NotRoleException(role);
|
||||
}
|
||||
}
|
||||
|
|
@ -242,18 +229,14 @@ public class AuthLogic {
|
|||
*
|
||||
* @param roles 角色标识数组
|
||||
*/
|
||||
public void checkRoleOr(String... roles)
|
||||
{
|
||||
public void checkRoleOr(String... roles) {
|
||||
Set<String> roleList = tokenService.getRolePermissions();
|
||||
for (String role : roles)
|
||||
{
|
||||
if (hasRole(roleList, role))
|
||||
{
|
||||
for (String role : roles) {
|
||||
if (hasRole(roleList, role)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (roles.length > 0)
|
||||
{
|
||||
if (roles.length > 0) {
|
||||
throw new NotRoleException(roles);
|
||||
}
|
||||
}
|
||||
|
|
@ -323,6 +306,9 @@ public class AuthLogic {
|
|||
public boolean hasUserIdentity(Long deptId, String roleKey) {
|
||||
LoginUser loginUser = getLoginUser();
|
||||
List<SysUserDeptRole> sysUserDeptRoleList = tokenService.getAllUserIdentity(loginUser.getUsername());
|
||||
if (sysUserDeptRoleList == null) {
|
||||
return false;
|
||||
}
|
||||
return sysUserDeptRoleList.stream()
|
||||
.anyMatch(x -> {
|
||||
if (x.getDeptId() != null && x.getSysRole() != null && x.getDeptId().equals(deptId)) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.microservices.common.core.utils.uuid.IdUtils;
|
|||
import com.microservices.common.redis.service.RedisService;
|
||||
import com.microservices.common.security.interceptor.HeaderInterceptor;
|
||||
import com.microservices.common.security.utils.SecurityUtils;
|
||||
import com.microservices.system.api.RemoteDeptService;
|
||||
import com.microservices.system.api.RemoteUserService;
|
||||
import com.microservices.system.api.domain.SysRole;
|
||||
import com.microservices.system.api.domain.SysUser;
|
||||
|
|
@ -38,6 +39,8 @@ public class TokenService {
|
|||
private RedisService redisService;
|
||||
@Autowired
|
||||
private RemoteUserService remoteUserService;
|
||||
@Autowired
|
||||
private RemoteDeptService remoteDeptService;
|
||||
|
||||
protected static final long MILLIS_SECOND = 1000;
|
||||
|
||||
|
|
@ -329,4 +332,16 @@ public class TokenService {
|
|||
}
|
||||
return loginUser.getSysUser();
|
||||
}
|
||||
|
||||
public Boolean hasDeptPerms(Long deptId, String perms) {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
Boolean hasPerms = FeignUtils.getReturnData(
|
||||
remoteDeptService.checkUserInDeptOrParentDeptHasPerms(deptId, loginUser.getUserId(), perms, SecurityConstants.INNER)
|
||||
);
|
||||
|
||||
if (hasPerms == null) {
|
||||
hasPerms = false;
|
||||
}
|
||||
return hasPerms;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.microservices.cms.doc.controller;
|
|||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.microservices.cms.doc.domain.*;
|
||||
import com.microservices.cms.doc.service.ICmsDocService;
|
||||
import com.microservices.common.core.constant.UserConstants;
|
||||
import com.microservices.common.core.domain.R;
|
||||
import com.microservices.common.core.web.controller.BaseController;
|
||||
import com.microservices.common.core.web.domain.AjaxResult;
|
||||
|
|
@ -17,6 +18,7 @@ import io.swagger.annotations.*;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
|
@ -106,23 +108,13 @@ public class CmsDocController extends BaseController {
|
|||
@ApiImplicitParam(name = "pageNum", value = "当前记录起始索引", paramType = "query", dataType = "Integer"),
|
||||
@ApiImplicitParam(name = "pageSize", value = "每页显示记录数", paramType = "query", dataType = "Integer"),
|
||||
})
|
||||
public TableDataInfo docListByZone(
|
||||
public GenericsTableDataInfo<CmsDocAllDto> docListByZone(
|
||||
@ApiParam(name = "zoneId", value = "专区Id") @PathVariable("zoneId") Long zoneId
|
||||
, CmsDocSearchVo cmsDocSearchVo) {
|
||||
Long deptId = cmsDocService.getDeptIdByZoneId(zoneId);
|
||||
cmsDocSearchVo.setIsOpen(false);
|
||||
cmsDocSearchVo.setIsOpen(true);
|
||||
startPage();
|
||||
List<CmsDocAllDto> list = cmsDocService.selectCmsDocListByDept(deptId, cmsDocSearchVo);
|
||||
list.forEach(x -> {
|
||||
String docDetailUrl = cmsDocService.getDocDetailUrl(x.getDeptId(), x.getId());
|
||||
x.setDocDetailUrl(docDetailUrl);
|
||||
|
||||
Boolean editable = cmsDocService.isCmsDocEditable(x);
|
||||
x.setEditable(editable);
|
||||
|
||||
});
|
||||
int count = cmsDocService.selectCmsDocCountByDept(deptId, cmsDocSearchVo);
|
||||
return getDataTable(list, count);
|
||||
return cmsDocService.selectAllDocList(deptId, cmsDocSearchVo);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -164,7 +156,7 @@ public class CmsDocController extends BaseController {
|
|||
/**
|
||||
* 新增文章信息
|
||||
*/
|
||||
@RequiresPermissions("cms:doc:add")
|
||||
@RequiresPermissions(UserConstants.CMS_PERMS_DOCS_ADD)
|
||||
@Log(title = "文章信息", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/dir/{dirId}")
|
||||
@ApiOperation("在栏目下创建文章")
|
||||
|
|
@ -201,7 +193,7 @@ public class CmsDocController extends BaseController {
|
|||
/**
|
||||
* 删除文章
|
||||
*/
|
||||
@RequiresPermissions("cms:doc:remove")
|
||||
@RequiresPermissions(UserConstants.CMS_PERMS_DOCS_DELETE)
|
||||
@Log(title = "文章信息", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
@ApiOperation("删除文章/栏目")
|
||||
|
|
@ -212,13 +204,13 @@ public class CmsDocController extends BaseController {
|
|||
/**
|
||||
* 修改文章信息
|
||||
*/
|
||||
@RequiresPermissions("cms:doc:edit")
|
||||
@RequiresPermissions(UserConstants.CMS_PERMS_DOCS_EDIT)
|
||||
@Log(title = "文章信息", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/{id}")
|
||||
@ApiOperation("更新文章")
|
||||
public AjaxResult edit(@ApiParam(name = "id", value = "文章Id") @PathVariable Long id,
|
||||
@ApiParam(name = "cmsDoc", value = "文章实体对象") @RequestBody CmsDocDto cmsDocDto) {
|
||||
return toAjax(cmsDocService.updateCmsDoc(id, cmsDocDto));
|
||||
return success(cmsDocService.updateCmsDoc(id, cmsDocDto));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -231,18 +223,18 @@ public class CmsDocController extends BaseController {
|
|||
public AjaxResult editCmsDIr(@ApiParam(name = "id", value = "栏目Id") @PathVariable Long id,
|
||||
@ApiParam(name = "cmsDirUpdateVo", value = "栏目更新对象") @RequestBody CmsDirUpdateVo cmsDirUpdateVo) {
|
||||
cmsDocService.updateCmsDir(id, cmsDirUpdateVo);
|
||||
return success("异步更新栏目信息");
|
||||
return success("栏目信息更新成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核文章信息
|
||||
*/
|
||||
@RequiresPermissions("cms:doc:audit")
|
||||
@RequiresPermissions(UserConstants.CMS_PERMS_DOCS_AUDIT)
|
||||
@Log(title = "文章信息", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/{id}/audit")
|
||||
@ApiOperation("审核文章")
|
||||
public AjaxResult audit(@ApiParam(name = "id", value = "文章Id") @PathVariable Long id,
|
||||
@ApiParam(name = "cmsDocAuditVo", value = "文章审核对象") @RequestBody CmsDocAuditVo cmsDocAuditVo) {
|
||||
@ApiParam(name = "cmsDocAuditVo", value = "文章审核对象") @RequestBody CmsDocAuditVo cmsDocAuditVo) {
|
||||
return toAjax(cmsDocService.auditCmsDoc(id, cmsDocAuditVo));
|
||||
}
|
||||
|
||||
|
|
@ -315,6 +307,24 @@ public class CmsDocController extends BaseController {
|
|||
return R.ok(cmsDocJSONObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部接口:首页文章合并列表(接收置顶文章 docId 有序列表,返回合并去重后的结果)
|
||||
* 供 Zone 服务 Feign 调用,用于 /open/getTopDoc 改造
|
||||
*/
|
||||
@InnerAuth
|
||||
@PostMapping("/homePageDocMergeList/{zoneId}")
|
||||
@ApiOperation(value = "首页文章合并列表", hidden = true)
|
||||
public R<List<JSONObject>> selectHomePageDocMergeList(
|
||||
@PathVariable("zoneId") Long zoneId,
|
||||
@RequestBody List<Long> topDocIds) {
|
||||
List<CmsDocBaseDto> list = cmsDocService.selectHomePageDocMergeList(zoneId, topDocIds);
|
||||
List<JSONObject> result = new ArrayList<>();
|
||||
for (CmsDocBaseDto dto : list) {
|
||||
result.add(JSONObject.from(dto));
|
||||
}
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过组织Id获取专区下文章阅读数量
|
||||
*
|
||||
|
|
|
|||
|
|
@ -5,12 +5,17 @@ import com.microservices.cms.doc.domain.*;
|
|||
import com.microservices.cms.doc.service.ICmsDocCommentService;
|
||||
import com.microservices.cms.doc.service.ICmsDocService;
|
||||
import com.microservices.cms.doc.service.ICmsWechatService;
|
||||
import com.microservices.cms.search.dto.DocSearchResult;
|
||||
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.core.web.page.TableDataInfo;
|
||||
import com.microservices.common.log.annotation.Log;
|
||||
import com.microservices.common.log.enums.BusinessType;
|
||||
import com.microservices.cms.search.dto.DocSearchRequest;
|
||||
import com.microservices.cms.search.dto.DocSearchResponse;
|
||||
import com.microservices.cms.search.service.IGlobalSearchService;
|
||||
import io.swagger.annotations.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
|
@ -35,6 +40,22 @@ public class OpenController extends BaseController {
|
|||
@Autowired
|
||||
private ICmsWechatService cmsWechatService;
|
||||
|
||||
@Autowired
|
||||
private IGlobalSearchService globalSearchService;
|
||||
|
||||
/**
|
||||
* 文章搜索接口
|
||||
*
|
||||
* @return 搜索结果
|
||||
*/
|
||||
@ApiOperation("文章搜索")
|
||||
@PostMapping("/search")
|
||||
public GenericsTableDataInfo<DocSearchResult> docSearch(@RequestBody DocSearchRequest docSearchRequest) {
|
||||
logger.info("文章搜索请求: keyword={}", docSearchRequest.getKeyword());
|
||||
DocSearchResponse response = globalSearchService.docSearch(docSearchRequest);
|
||||
return new GenericsTableDataInfo<>(response.getResults(), response.getTotal());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询栏目列表
|
||||
*/
|
||||
|
|
@ -68,7 +89,7 @@ public class OpenController extends BaseController {
|
|||
public GenericsTableDataInfo<CmsDocBaseDto> docList(@ApiParam(name = "dirId", value = "栏目Id") @PathVariable("dirId") Long dirId
|
||||
, CmsDocSearchVo cmsDocSearchVo) {
|
||||
cmsDocSearchVo.setIsOpen(true);
|
||||
List<CmsDocBaseDto> list = cmsDocService.selectCmsDocList(dirId, cmsDocSearchVo);
|
||||
List<CmsDocBaseDto> list = cmsDocService.selectSetDefaultHeadImgCmsDocList(dirId, cmsDocSearchVo);
|
||||
return getAllGenericsDataTableToPage(list);
|
||||
}
|
||||
|
||||
|
|
@ -100,6 +121,19 @@ public class OpenController extends BaseController {
|
|||
return getAllGenericsDataTableToPage(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询文章列表(不区分栏目)
|
||||
*/
|
||||
@GetMapping("/zone/{zoneId}/allDocList")
|
||||
@ApiOperation("查询文章列表(不区分栏目)")
|
||||
public GenericsTableDataInfo<CmsDocAllDto> allDocList(@ApiParam(name = "zoneId", value = "专区Id") @PathVariable("zoneId") Long zoneId,
|
||||
CmsDocSearchVo cmsDocSearchVo) {
|
||||
Long deptId = cmsDocService.getDeptIdByZoneId(zoneId);
|
||||
cmsDocSearchVo.setIsOpen(true);
|
||||
startPage();
|
||||
return cmsDocService.selectAllDocList(deptId, cmsDocSearchVo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询首页文章列表
|
||||
*/
|
||||
|
|
@ -175,9 +209,9 @@ public class OpenController extends BaseController {
|
|||
@Log(title = "同步文章数据", businessType = BusinessType.UPDATE)
|
||||
public AjaxResult syncDocData(
|
||||
@ApiParam(name = "deptId", value = "组织Id", required = true) @PathVariable("deptId") Long deptId
|
||||
,@RequestBody() String bodyStr
|
||||
, @RequestBody() String bodyStr
|
||||
, HttpServletRequest request) {
|
||||
cmsDocService.syncDocData(deptId,bodyStr);
|
||||
cmsDocService.syncDocData(deptId, bodyStr);
|
||||
return AjaxResult.success("同步消息发送成功");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ public class CmsDir implements Comparable<CmsDir> {
|
|||
@ApiModelProperty(value = "排序", hidden = true)
|
||||
private Integer sort;
|
||||
|
||||
@ApiModelProperty(value = "文章头图")
|
||||
private String headImg;
|
||||
|
||||
public String getName() {
|
||||
if (name != null) {
|
||||
name = name.replaceAll(CmsConstants.DIR_SEPARATOR,
|
||||
|
|
|
|||
|
|
@ -21,9 +21,6 @@ public class CmsDirUpdateVo {
|
|||
@ApiModelProperty(value = "顺序")
|
||||
private Integer order;
|
||||
|
||||
public CmsDir toCmsDir() {
|
||||
CmsDir cmsDir = new CmsDir();
|
||||
cmsDir.setName(name);
|
||||
return cmsDir;
|
||||
}
|
||||
@ApiModelProperty(value = "文章头图")
|
||||
private String headImg;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.microservices.cms.doc.domain;
|
|||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.microservices.cms.utils.CmsConstants;
|
||||
import com.microservices.common.core.annotation.Excel;
|
||||
import com.microservices.common.core.utils.bean.BeanUtils;
|
||||
import com.microservices.common.core.web.domain.BaseSortEntity;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
|
@ -36,19 +37,27 @@ public class CmsDoc extends BaseSortEntity {
|
|||
@Excel(name = "文件名称")
|
||||
private String fileName;
|
||||
|
||||
/** 文件路径 */
|
||||
/**
|
||||
* 文件路径
|
||||
*/
|
||||
@Excel(name = "文件路径")
|
||||
private String filePath;
|
||||
|
||||
/** 文件SHA */
|
||||
/**
|
||||
* 文件SHA
|
||||
*/
|
||||
@Excel(name = "文件SHA")
|
||||
private String fileSha;
|
||||
|
||||
/** 文件大小 */
|
||||
/**
|
||||
* 文件大小
|
||||
*/
|
||||
@Excel(name = "文件大小")
|
||||
private Long fileSize;
|
||||
|
||||
/** 文件URL */
|
||||
/**
|
||||
* 文件URL
|
||||
*/
|
||||
@Excel(name = "文件URL")
|
||||
private String fileUrl;
|
||||
|
||||
|
|
@ -162,10 +171,16 @@ public class CmsDoc extends BaseSortEntity {
|
|||
*/
|
||||
private String keywords;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
public CmsDir copyToCmsDir() {
|
||||
CmsDir cmsDir = new CmsDir();
|
||||
cmsDir.setId(id);
|
||||
cmsDir.setName(name);
|
||||
cmsDir.setHeadImg(headImg);
|
||||
cmsDir.setSort(this.getSort());
|
||||
cmsDir.setCreatedAt(getCreateTime());
|
||||
return cmsDir;
|
||||
|
|
@ -186,6 +201,8 @@ public class CmsDoc extends BaseSortEntity {
|
|||
HotCmsDocDto hotCmsDocDto = new HotCmsDocDto();
|
||||
hotCmsDocDto.setId(id);
|
||||
hotCmsDocDto.setName(name);
|
||||
hotCmsDocDto.setSummary(summary);
|
||||
hotCmsDocDto.setPublishTime(publishTime);
|
||||
return hotCmsDocDto;
|
||||
}
|
||||
|
||||
|
|
@ -207,30 +224,12 @@ public class CmsDoc extends BaseSortEntity {
|
|||
}
|
||||
|
||||
private CmsDocBaseDto getCmsDocBaseDto(CmsDocBaseDto cmsDocDto) {
|
||||
cmsDocDto.setId(id);
|
||||
cmsDocDto.setName(name);
|
||||
cmsDocDto.setHeadImg(headImg);
|
||||
cmsDocDto.setIsHomepage(isHomepage);
|
||||
cmsDocDto.setSummary(summary);
|
||||
cmsDocDto.setEditorType(editorType);
|
||||
cmsDocDto.setDeptId(deptId);
|
||||
|
||||
BeanUtils.copyProperties(this, cmsDocDto);
|
||||
if (visits == null) {
|
||||
cmsDocDto.setVisits(0L);
|
||||
} else {
|
||||
cmsDocDto.setVisits(visits);
|
||||
}
|
||||
|
||||
cmsDocDto.setPublishTime(publishTime);
|
||||
cmsDocDto.setPublishUser(publishUser);
|
||||
cmsDocDto.setPublishUserUrl(publishUserUrl);
|
||||
cmsDocDto.setPublishUserImage(publishUserImage);
|
||||
cmsDocDto.setPublishUserNickname(publishUserNickname);
|
||||
cmsDocDto.setAuditStatus(auditStatus);
|
||||
cmsDocDto.setAuditType(auditType);
|
||||
cmsDocDto.setCreateBy(createBy);
|
||||
cmsDocDto.setKeywords(keywords);
|
||||
|
||||
return cmsDocDto;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,4 +20,10 @@ public class CmsDocAuditVo {
|
|||
*/
|
||||
@ApiModelProperty(value = "是否重新编辑: 0不允许重新编辑 1允许重新编辑")
|
||||
private Integer reedit;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@ApiModelProperty(value = "审核备注")
|
||||
private String remark;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,10 @@ public class CmsDocBaseDto {
|
|||
*/
|
||||
@ApiModelProperty("关键词")
|
||||
private String keywords;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
public void setPublishTime(Date publishTime) {
|
||||
if (publishTime != null) {
|
||||
|
|
|
|||
|
|
@ -83,4 +83,10 @@ public class CmsDocSearchVo {
|
|||
*/
|
||||
@ApiModelProperty("关键词")
|
||||
private String keywords;
|
||||
|
||||
/**
|
||||
* 排除文章Id
|
||||
*/
|
||||
@ApiModelProperty(value = "排除文章Id", hidden = true)
|
||||
private Long excludeId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package com.microservices.cms.doc.domain;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* CmsDoc with full-text search score
|
||||
* 用于全局搜索结果,包含全文搜索相关度评分
|
||||
*
|
||||
* @author otto
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class CmsDocWithScore extends CmsDoc implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 全文搜索相关度评分(来自 MySQL MATCH...AGAINST)
|
||||
*/
|
||||
private Double searchScore;
|
||||
}
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
package com.microservices.cms.doc.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.microservices.common.core.annotation.Excel;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author otto
|
||||
*/
|
||||
|
|
@ -23,4 +26,11 @@ public class HotCmsDocDto {
|
|||
@Excel(name = "文章名称")
|
||||
@ApiModelProperty("文章名称")
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty("文章概要")
|
||||
private String summary;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@ApiModelProperty("发布时间")
|
||||
private Date publishTime;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.microservices.cms.doc.domain.gitlink;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.microservices.common.core.exception.ServiceException;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
|
|
@ -22,4 +24,12 @@ public class EntryDto implements Comparable<EntryDto> {
|
|||
// 重写Comparable接口的compareTo方法,根据年龄升序排列,降序修改相减顺序即可
|
||||
return Math.toIntExact(entryDto.getCommit().getCreated_at_unix() - this.commit.getCreated_at_unix());
|
||||
}
|
||||
|
||||
public static EntryDto getEntryDtoFromJson(JSONObject subEntriesResult) {
|
||||
FileEntryDto fileEntryDto = subEntriesResult.toJavaObject(FileEntryDto.class);
|
||||
if (fileEntryDto == null || fileEntryDto.getEntries() == null) {
|
||||
throw new ServiceException("该文章不存在");
|
||||
}
|
||||
return fileEntryDto.getEntries();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.microservices.cms.doc.mapper;
|
|||
|
||||
import com.microservices.cms.doc.domain.CmsDoc;
|
||||
import com.microservices.cms.doc.domain.CmsDocSearchVo;
|
||||
import com.microservices.cms.doc.domain.CmsDocWithScore;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
|
|
@ -23,6 +24,14 @@ public interface CmsDocMapper {
|
|||
*/
|
||||
public CmsDoc selectCmsDocById(Long id);
|
||||
|
||||
/**
|
||||
* 批量查询文章信息
|
||||
*
|
||||
* @param ids 文章ID列表
|
||||
* @return 文章信息列表
|
||||
*/
|
||||
List<CmsDoc> selectCmsDocByIds(@Param("ids") List<Long> ids);
|
||||
|
||||
/**
|
||||
* 查询文章信息列表
|
||||
*
|
||||
|
|
@ -31,6 +40,17 @@ public interface CmsDocMapper {
|
|||
*/
|
||||
public List<CmsDoc> selectCmsDocList(CmsDocSearchVo cmsDocSearchVo);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 查询文章信息列表(设置默认头图为栏目头图)
|
||||
*
|
||||
* @param cmsDocSearchVo 文章信息
|
||||
* @return 文章信息集合
|
||||
*/
|
||||
List<CmsDoc> selectCmsDocListSetDefaultHeadImg(CmsDocSearchVo cmsDocSearchVo);
|
||||
|
||||
|
||||
/**
|
||||
* 新增文章信息
|
||||
*
|
||||
|
|
@ -71,6 +91,11 @@ public interface CmsDocMapper {
|
|||
|
||||
CmsDoc selectCmsDocByFilePathAndDeptId(@Param("filePath") String filePath, @Param("deptId") Long deptId);
|
||||
|
||||
/**
|
||||
* 按文章名+deptId查询(忽略dirName),用于Webhook防重判断
|
||||
*/
|
||||
CmsDoc selectCmsDocByNameAndDeptIdIgnoreDirName(@Param("name") String name, @Param("deptId") Long deptId);
|
||||
|
||||
List<CmsDoc> selectHotCmsDocList(CmsDoc cmsDocInput);
|
||||
|
||||
List<CmsDoc> selectCmsDocDirByDeptIdAndInName(@Param("deptId") Long deptId, @Param("cmsDirNameList") List<String> cmsDirNameList);
|
||||
|
|
@ -172,4 +197,50 @@ public interface CmsDocMapper {
|
|||
Long getNotAuditCmsCountByDeptId(Long deptId);
|
||||
|
||||
Long getCmsDocVisitsByDeptId(Long deptId);
|
||||
|
||||
/**
|
||||
* 根据组织id获取专区下所有文章和栏目
|
||||
* @param deptId 组织id
|
||||
* @return 文章和栏目列表
|
||||
*/
|
||||
List<CmsDoc> selectCmsDocAndDirListByDept(Long deptId);
|
||||
|
||||
void updateCmsDocShaByPathAndDeptId(@Param("sha") String sha, @Param("filePath") String filePath, @Param("deptId") Long deptId);
|
||||
|
||||
/**
|
||||
* 全局搜索 - 全文索引查询
|
||||
*
|
||||
* @param keyword 搜索关键词
|
||||
* @param deptId 组织ID(可选)
|
||||
* @param offset 偏移量
|
||||
* @param pageSize 每页数量
|
||||
* @return 搜索结果列表
|
||||
*/
|
||||
List<CmsDoc> globalSearch(@Param("keyword") String keyword, @Param("deptId") Long deptId,
|
||||
@Param("offset") int offset, @Param("pageSize") int pageSize);
|
||||
|
||||
/**
|
||||
* 全局搜索结果计数
|
||||
*
|
||||
* @param keyword 搜索关键词
|
||||
* @param deptId 组织ID(可选)
|
||||
* @return 结果数量
|
||||
*/
|
||||
Long globalSearchCount(@Param("keyword") String keyword, @Param("deptId") Long deptId);
|
||||
|
||||
/**
|
||||
* 全局搜索 - 全文索引查询(含评分)
|
||||
*
|
||||
* @param keyword 搜索关键词
|
||||
* @param deptId 组织ID(可选)
|
||||
* @param offset 偏移量
|
||||
* @param pageSize 每页数量
|
||||
* @return 搜索结果列表(含评分)
|
||||
*/
|
||||
List<CmsDocWithScore> globalSearchWithScore(
|
||||
@Param("keyword") String keyword,
|
||||
@Param("deptId") Long deptId,
|
||||
@Param("offset") int offset,
|
||||
@Param("pageSize") int pageSize
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ public interface ICmsAsyncService {
|
|||
|
||||
void asyncCreateBranchAndDeleteFileAndCreatePR(CmsDoc newDocWithAudit, CmsProject cmsProject);
|
||||
|
||||
void asyncMoveFileToNewDir(CmsProject cmsProject, CmsDoc oldCmsDir, CmsDoc newCmsDir);
|
||||
void asyncMoveFileToNewDir(CmsProject cmsProject, CmsDoc oldCmsDir, String newCmsDirName);
|
||||
|
||||
/**
|
||||
* 推送文章数据到搜索引擎
|
||||
|
|
@ -99,4 +99,38 @@ public interface ICmsAsyncService {
|
|||
* @param cmsDoc 文章
|
||||
*/
|
||||
void pushDocDataSearchEngine(CmsDoc cmsDoc);
|
||||
|
||||
void clearTmpDoc(CmsProject cmsProject,List<CmsDoc> tmpDocList);
|
||||
|
||||
/**
|
||||
* 异步增加文章发布积分
|
||||
*
|
||||
* @param deptId 组织ID
|
||||
* @param createBy 创建人用户名
|
||||
* @param docId 文章ID
|
||||
* @param subjectDesc 文章标题
|
||||
*/
|
||||
void addPublishDocPointsAsync(Long deptId, String createBy, Long docId, String subjectDesc);
|
||||
|
||||
/**
|
||||
* 异步增加文章浏览积分(每满100次+2,上限50)
|
||||
*
|
||||
* @param deptId 组织ID
|
||||
* @param createBy 创建人用户名
|
||||
* @param docId 文章ID
|
||||
* @param docName 文章名称
|
||||
* @param totalVisits 累计阅读次数
|
||||
* @param pointsTriggerCount 当前第几次增加积分
|
||||
*/
|
||||
void addVisitsDocPointsAsync(Long deptId, String createBy, Long docId, String docName, Long totalVisits);
|
||||
|
||||
/**
|
||||
* 异步增加文章被设为精选的积分
|
||||
*
|
||||
* @param deptId 组织ID
|
||||
* @param createBy 创建人用户名
|
||||
* @param docId 文章ID
|
||||
* @param subjectDesc 文章标题
|
||||
*/
|
||||
void addFeaturedDocPointsAsync(Long deptId, String createBy, Long docId, String subjectDesc);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.microservices.cms.doc.domain.*;
|
|||
import com.microservices.cms.doc.domain.gitlink.EntryDto;
|
||||
import com.microservices.cms.doc.domain.gitlink.FileEntryDto;
|
||||
import com.microservices.cms.project.domain.CmsProject;
|
||||
import com.microservices.common.core.web.page.GenericsTableDataInfo;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
|
@ -56,9 +57,9 @@ public interface ICmsDocService {
|
|||
*
|
||||
* @param id
|
||||
* @param cmsDocDto 文章信息
|
||||
* @return 结果
|
||||
* @return 结果 文章id
|
||||
*/
|
||||
public boolean updateCmsDoc(Long id, CmsDocDto cmsDocDto);
|
||||
public Long updateCmsDoc(Long id, CmsDocDto cmsDocDto);
|
||||
|
||||
/**
|
||||
* 批量删除文章信息
|
||||
|
|
@ -144,6 +145,15 @@ public interface ICmsDocService {
|
|||
*/
|
||||
List<CmsDocBaseDto> selectHomePageDocList(Long deptId);
|
||||
|
||||
/**
|
||||
* 查询首页文章合并列表(积分置顶文章 + 首页推荐文章,自动去重,置顶在前)
|
||||
*
|
||||
* @param zoneId 专区ID
|
||||
* @param topDocIds 置顶文章 docId 有序列表(前端按此顺序排列)
|
||||
* @return 合并后的首页文章列表(isTop=true 标识置顶文章)
|
||||
*/
|
||||
List<CmsDocBaseDto> selectHomePageDocMergeList(Long zoneId, List<Long> topDocIds);
|
||||
|
||||
/**
|
||||
* 初始化栏目
|
||||
*
|
||||
|
|
@ -198,6 +208,8 @@ public interface ICmsDocService {
|
|||
*/
|
||||
List<CmsDocAllDto> selectCmsDocListByDept(Long deptId, CmsDocSearchVo cmsDocSearchVo);
|
||||
|
||||
GenericsTableDataInfo<CmsDocAllDto> selectAllDocList(Long deptId, CmsDocSearchVo cmsDocSearchVo);
|
||||
|
||||
/**
|
||||
* 查询专区下文章数量
|
||||
*
|
||||
|
|
@ -288,4 +300,12 @@ public interface ICmsDocService {
|
|||
CmsDocBaseDto selectCmsDocBaseInfoById(Long id);
|
||||
|
||||
Long getCmsDocVisitsByDeptId(Long deptId);
|
||||
|
||||
List<CmsDocBaseDto> selectSetDefaultHeadImgCmsDocList(Long dirId, CmsDocSearchVo cmsDocSearchVo);
|
||||
|
||||
List<CmsDoc> selectCmsDocAndDirListByDept(Long deptId);
|
||||
|
||||
void updateCmsDocShaByPathAndDeptId(String sha, String filePath, Long deptId);
|
||||
|
||||
List<CmsDoc> getTmpDocList(CmsDoc cmsDoc);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,14 +16,18 @@ import com.microservices.cms.project.service.ICmsProjectService;
|
|||
import com.microservices.cms.utils.CmsConstants;
|
||||
import com.microservices.cms.utils.CmsGitLinkRequestUrl;
|
||||
import com.microservices.cms.utils.CmsUtils;
|
||||
import com.microservices.cms.utils.CustomExecutorFactory;
|
||||
import com.microservices.common.core.constant.CacheConstants;
|
||||
import com.microservices.common.core.constant.Constants;
|
||||
import com.microservices.common.core.constant.SecurityConstants;
|
||||
import com.microservices.common.core.enums.DocAndZoneOperation;
|
||||
import com.microservices.common.core.enums.DocAuditStatus;
|
||||
import com.microservices.common.core.enums.SystemRole;
|
||||
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.core.utils.html.EscapeUtil;
|
||||
import com.microservices.common.core.utils.sign.Base64;
|
||||
import com.microservices.common.core.utils.uuid.IdUtils;
|
||||
import com.microservices.common.httpClient.constant.GitLinkConstants;
|
||||
import com.microservices.common.httpClient.service.HttpAPIService;
|
||||
|
|
@ -31,6 +35,7 @@ import com.microservices.common.httpClient.util.GitLinkRequestHelper;
|
|||
import com.microservices.common.redis.service.RedisService;
|
||||
import com.microservices.common.security.utils.SecurityUtils;
|
||||
import com.microservices.system.api.RemoteDeptService;
|
||||
import com.microservices.system.api.RemoteUserService;
|
||||
import com.microservices.system.api.RemoteZoneService;
|
||||
import com.microservices.system.api.utils.FeignUtils;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
|
|
@ -47,6 +52,7 @@ import org.springframework.util.DigestUtils;
|
|||
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
|
@ -92,7 +98,10 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
|
|||
@Autowired
|
||||
private RemoteZoneService remoteZoneService;
|
||||
@Autowired
|
||||
private RemoteUserService remoteUserService;
|
||||
@Autowired
|
||||
private RequestConfig config;
|
||||
|
||||
/**
|
||||
* 异步设置文章更新时间
|
||||
*
|
||||
|
|
@ -109,7 +118,7 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
|
|||
} else {
|
||||
redisService.setCacheObject(updateDocIntervalKey, cmsProject.getName(), 1L, TimeUnit.MINUTES);
|
||||
}
|
||||
CmsDocSearchVo cmsDocSearch=new CmsDocSearchVo();
|
||||
CmsDocSearchVo cmsDocSearch = new CmsDocSearchVo();
|
||||
cmsDocSearch.setDeptId(cmsProject.getDeptId());
|
||||
List<CmsDoc> cmsDocList = cmsDocMapper.selectCmsDocList(cmsDocSearch);
|
||||
for (EntryDto entryDto : entryDtoList) {
|
||||
|
|
@ -150,7 +159,7 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
|
|||
webhook.put("eventCondition", "push");
|
||||
webhook.put("http_method", "POST");
|
||||
webhook.put("secret", "");
|
||||
webhook.put("url", gatewayUrl+"/cms/doc/open/dept/"+cmsProject.getDeptId());
|
||||
webhook.put("url", gatewayUrl + "/cms/doc/open/dept/" + cmsProject.getDeptId());
|
||||
webhook.put("events", new String[]{"push"});
|
||||
jsonObject.put("webhook", webhook);
|
||||
//增加Webhook
|
||||
|
|
@ -231,7 +240,7 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
|
|||
}
|
||||
|
||||
private void syncDocAddedData(CmsProject cmsProject, List<String> addedList) {
|
||||
for (String addedFilePath:addedList) {
|
||||
for (String addedFilePath : addedList) {
|
||||
try {
|
||||
String[] filePathList = addedFilePath.split("/");
|
||||
if (filePathList.length != 2) {
|
||||
|
|
@ -247,18 +256,27 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
|
|||
}
|
||||
CmsDoc cmsDoc = cmsDocMapper.selectCmsDocByNameAndDeptId(CmsUtils.getFileNameNoSuffix(entry.getName()), cmsProject.getDeptId(), dirName);
|
||||
if (cmsDoc == null) {
|
||||
syncCreateDoc(entry, dirName, cmsProject.getDeptId(), fileEntry.getLast_commit());
|
||||
// 防重:按文章名+deptId查一次,排除正在移动栏目的已有记录
|
||||
String docName = CmsUtils.getFileNameNoSuffix(entry.getName());
|
||||
CmsDoc existingDoc = cmsDocMapper.selectCmsDocByNameAndDeptIdIgnoreDirName(docName, cmsProject.getDeptId());
|
||||
if (existingDoc != null) {
|
||||
existingDoc.setDirName(dirName);
|
||||
existingDoc.setFilePath(dirName + "/" + existingDoc.getFileName());
|
||||
cmsDocMapper.updateCmsDoc(existingDoc);
|
||||
} else {
|
||||
syncCreateDoc(entry, dirName, cmsProject.getDeptId(), fileEntry.getLast_commit());
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch (Exception e){
|
||||
} catch (Exception e) {
|
||||
logger.error("[{}]同步新增文章发生异常:{}"
|
||||
, cmsProject.getName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void syncDocRemovedData(CmsProject cmsProject,List<String> removedList) {
|
||||
for (String removedFilePath:removedList) {
|
||||
private void syncDocRemovedData(CmsProject cmsProject, List<String> removedList) {
|
||||
for (String removedFilePath : removedList) {
|
||||
try {
|
||||
String[] filePathList = removedFilePath.split("/");
|
||||
if (filePathList.length != 2) {
|
||||
|
|
@ -357,7 +375,7 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
|
|||
cmsDocSearch.setDeptId(cmsDir.getDeptId());
|
||||
cmsDocSearch.setDirName(cmsDir.getName());
|
||||
cmsDocSearch.setIsDir(false);
|
||||
List<CmsDoc> cmsDocList=cmsDocMapper.selectCmsDocList(cmsDocSearch);
|
||||
List<CmsDoc> cmsDocList = cmsDocMapper.selectCmsDocList(cmsDocSearch);
|
||||
//获取数据库所有文件的filePath
|
||||
List<String> databaseFilePathList = cmsDocList.stream().map(CmsDoc::getFilePath).collect(Collectors.toList());
|
||||
//获取需要新增的文章
|
||||
|
|
@ -383,16 +401,16 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
|
|||
@Override
|
||||
public void asyncDocData(Long deptId, String bodyStr) {
|
||||
//防止多次操作同时执行,将同步内容放入队列,根据先后时间执行同步动作
|
||||
String cmsSyncDeptIdKey=CacheConstants.CMS_SYNC_DEPT_ID_KEY+deptId;
|
||||
String cmsSyncDeptIdKey = CacheConstants.CMS_SYNC_DEPT_ID_KEY + deptId;
|
||||
//为当前同步操作生成UUID,若拿到的UUID为自身,则执行本次同步动作
|
||||
String UUID= IdUtils.fastSimpleUUID();
|
||||
String UUID = IdUtils.fastSimpleUUID();
|
||||
redisService.leftPush(cmsSyncDeptIdKey, UUID);
|
||||
//设置缓存过期时间,防止异常情况导致同步无法继续执行
|
||||
redisService.expire(cmsSyncDeptIdKey, 3600, TimeUnit.SECONDS);
|
||||
String currentUUID = String.valueOf(redisService.listIndexOf(cmsSyncDeptIdKey, -1));
|
||||
//设置最大循环阈值,防止意外情况导致死循环(7200次,约1小时左右)
|
||||
int threshold=7200;
|
||||
while (!UUID.equals(currentUUID)&&threshold!=0){
|
||||
int threshold = 7200;
|
||||
while (!UUID.equals(currentUUID) && threshold != 0) {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
|
|
@ -402,41 +420,41 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
|
|||
threshold--;
|
||||
}
|
||||
|
||||
try{
|
||||
CmsProject cmsProject=cmsProjectService.selectCmsProjectByDeptId(deptId);
|
||||
JSONObject body=JSONObject.parseObject(bodyStr);
|
||||
try {
|
||||
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(deptId);
|
||||
JSONObject body = JSONObject.parseObject(bodyStr);
|
||||
//获取本次推送中Commit总数
|
||||
Integer totalCommits=body.getInteger("total_commits");
|
||||
Integer totalCommits = body.getInteger("total_commits");
|
||||
//从webhook请求中获取本次提交内容
|
||||
JSONArray commits=body.getJSONArray("commits");
|
||||
JSONArray commits = body.getJSONArray("commits");
|
||||
//当commit总数大于提交的列表数量时,代表此次PUSH未携带所有commit,此时进行一次全量同步
|
||||
if(totalCommits!=null
|
||||
&&commits!=null
|
||||
&&totalCommits>commits.size()){
|
||||
if (totalCommits != null
|
||||
&& commits != null
|
||||
&& totalCommits > commits.size()) {
|
||||
fullSyncDocData(cmsProject);
|
||||
}else{
|
||||
for (int i = commits.size()-1; i >=0 ; i--) {
|
||||
JSONObject commit=commits.getJSONObject(i);
|
||||
} else {
|
||||
for (int i = commits.size() - 1; i >= 0; i--) {
|
||||
JSONObject commit = commits.getJSONObject(i);
|
||||
//获取新增的文件列表
|
||||
JSONArray addedList=commit.getJSONArray("added");
|
||||
if(addedList!=null){
|
||||
syncDocAddedData(cmsProject,addedList.toJavaList(String.class));
|
||||
JSONArray addedList = commit.getJSONArray("added");
|
||||
if (addedList != null) {
|
||||
syncDocAddedData(cmsProject, addedList.toJavaList(String.class));
|
||||
}
|
||||
//获取删除的文件列表
|
||||
JSONArray removedList=commit.getJSONArray("removed");
|
||||
if(removedList!=null){
|
||||
syncDocRemovedData(cmsProject,removedList.toJavaList(String.class));
|
||||
JSONArray removedList = commit.getJSONArray("removed");
|
||||
if (removedList != null) {
|
||||
syncDocRemovedData(cmsProject, removedList.toJavaList(String.class));
|
||||
}
|
||||
//获取更新的文件列表
|
||||
JSONArray modifiedList=commit.getJSONArray("modified");
|
||||
if(modifiedList!=null){
|
||||
JSONArray modifiedList = commit.getJSONArray("modified");
|
||||
if (modifiedList != null) {
|
||||
syncDocModifiedData(cmsProject, modifiedList.toJavaList(String.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch (Exception e) {
|
||||
} catch (Exception e) {
|
||||
logger.error("同步文章时发送异常:{}", e.getMessage());
|
||||
}finally {
|
||||
} finally {
|
||||
//本次同步执行完成后,再弹出该UUID
|
||||
redisService.rightPop(cmsSyncDeptIdKey, 500, TimeUnit.MICROSECONDS);
|
||||
}
|
||||
|
|
@ -469,12 +487,12 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void syncCreateDir(String dirName,EntryDto entry,CmsProject cmsProject,boolean isCreateKeep){
|
||||
public void syncCreateDir(String dirName, EntryDto entry, CmsProject cmsProject, boolean isCreateKeep) {
|
||||
CmsDoc cmsDir = new CmsDoc();
|
||||
cmsDir.setName(dirName);
|
||||
cmsDir.setCreateTime(entry.getCommit().getCreated_at());
|
||||
boolean insertResult;
|
||||
if(isCreateKeep){
|
||||
if (isCreateKeep) {
|
||||
//若该栏目不存在,检查文件夹中是否存在.keep文件
|
||||
JSONObject subEntriesResult = gitLinkRequestHelper.doGet(
|
||||
CmsGitLinkRequestUrl.GET_FILE_SUB_ENTRIES(cmsProject, cmsDir.getName(), CmsConstants.BRANCH_MASTER));
|
||||
|
|
@ -484,14 +502,14 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
|
|||
|
||||
if (keepEntry != null) {
|
||||
//若存在,则在数据库中创建该栏目
|
||||
insertResult=createCmsDir(cmsDir,cmsProject.getDeptId(),keepEntry);
|
||||
insertResult = createCmsDir(cmsDir, cmsProject.getDeptId(), keepEntry);
|
||||
} else {
|
||||
//若不存在,则在数据库中创建该栏目,同时在仓库的该文件夹下创建.keep文件
|
||||
insertResult = cmsDocService.insertCmsDocDir(cmsProject.getDeptId(), cmsDir.copyToCmsDir());
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
//若不自动创建,则在数据库中创建根据文件名该栏目
|
||||
insertResult=createCmsDir(cmsDir,cmsProject.getDeptId(),entry);
|
||||
insertResult = createCmsDir(cmsDir, cmsProject.getDeptId(), entry);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -500,7 +518,7 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean createCmsDir(CmsDoc cmsDir,Long deptId,EntryDto entry){
|
||||
private boolean createCmsDir(CmsDoc cmsDir, Long deptId, EntryDto entry) {
|
||||
cmsDir.setIsDir(true);
|
||||
cmsDir.setFilePath(cmsDir.getDirPath());
|
||||
cmsDir.setDeptId(deptId);
|
||||
|
|
@ -614,11 +632,12 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
|
|||
100, "..."), tmpBranchName);
|
||||
} catch (ServiceException e) {
|
||||
//忽略重复创建PR异常
|
||||
if (!"请求GitLink失败(合并请求已存在)".equals(e.getMessage())) {
|
||||
if (!e.getMessage().contains("合并请求已存在")) {
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cmsDocService.updateCmsCommonDatabase(cmsDoc, tmpBranchName);
|
||||
}
|
||||
|
||||
|
|
@ -678,6 +697,7 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
|
|||
requestBody);
|
||||
cmsDoc.setAuditPrNumber(result.getString("pull_request_number") + "-" + result.getString("pull_request_id"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CmsDoc updateCmsDocDatabaseAndUpdateForGitLink(CmsDoc oldCmsDoc, CmsDocDto cmsDocDto, String branchName) {
|
||||
if (oldCmsDoc == null) {
|
||||
|
|
@ -718,8 +738,8 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
|
|||
|
||||
if (isChangeName || isChangeDir) {
|
||||
oldCmsDoc = renameAndChangeDirCmsDoc(oldCmsDoc, cmsProject, newName, newDirName, branchName);
|
||||
//todo 此处是否需要更新数据库待讨论
|
||||
//updateCmsCommonDatabase(oldCmsDoc);
|
||||
// 立即更新数据库中的dirName/filePath,避免GitLink Webhook回调时查不到记录导致重复INSERT
|
||||
cmsDocMapper.updateCmsDoc(oldCmsDoc);
|
||||
}
|
||||
|
||||
boolean isChangeContent = false;
|
||||
|
|
@ -871,27 +891,87 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Async
|
||||
public void asyncMoveFileToNewDir(CmsProject cmsProject, CmsDoc oldCmsDir, CmsDoc newCmsDir) {
|
||||
public void asyncMoveFileToNewDir(CmsProject cmsProject, CmsDoc oldCmsDir, String newCmsDirName) {
|
||||
try {
|
||||
remoteZoneService.lockCmsByDeptId(cmsProject.getDeptId(), 1, SecurityConstants.INNER);
|
||||
//创建新栏目
|
||||
String message = String.format("重命名栏目:由[%s]重命名为[%s]", oldCmsDir.getName(), newCmsDir.getName());
|
||||
cmsDocService.createFileByGitLink(newCmsDir, message, "");
|
||||
//在数据库中重命名该栏目,防止后续操作推送的WebHook重复创建栏目
|
||||
cmsDocService.updateCmsCommonDatabase(newCmsDir, CmsConstants.BRANCH_MASTER);
|
||||
//将原栏目中所有文章移动到新栏目中
|
||||
moveCmsDocToNewCmsDir(cmsProject, oldCmsDir.getName(), newCmsDir.getName(), message);
|
||||
//删除旧栏目
|
||||
deleteCmsDocByGitLink(cmsProject, oldCmsDir);
|
||||
JSONObject dirFilesJson = gitLinkRequestHelper.doGet(CmsGitLinkRequestUrl.GET_DIR_SUB_ENTRIES(
|
||||
cmsProject,
|
||||
oldCmsDir.getName(),
|
||||
CmsConstants.BRANCH_MASTER));
|
||||
List<EntryDto> entryDtoList = cmsDocService.getEntriesFromRequestResult(dirFilesJson, cmsProject, false);
|
||||
CountDownLatch countDownLatch = new CountDownLatch(entryDtoList.size());
|
||||
// 异步获取每篇文章内容
|
||||
entryDtoList.forEach(entryDto -> {
|
||||
CustomExecutorFactory.threadPoolExecutor.execute(() -> {
|
||||
try {
|
||||
JSONObject fileSubEntriesResult = gitLinkRequestHelper.doGet(
|
||||
CmsGitLinkRequestUrl.GET_FILE_SUB_ENTRIES(
|
||||
cmsProject,
|
||||
entryDto.getPath(),
|
||||
CmsConstants.BRANCH_MASTER));
|
||||
EntryDto cmsDocEntryDto = EntryDto.getEntryDtoFromJson(fileSubEntriesResult);
|
||||
entryDto.setContent(cmsDocEntryDto.getContent());
|
||||
} finally {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
});
|
||||
});
|
||||
try {
|
||||
countDownLatch.await();
|
||||
} catch (InterruptedException e) {
|
||||
logger.error("栏目重命名失败(将对应代码库中文章移动至新文件夹失败):{}", e.getMessage());
|
||||
throw new ServiceException("栏目重命名失败");
|
||||
}
|
||||
|
||||
for (EntryDto entryDto : entryDtoList) {
|
||||
JSONObject updateFileBody = new JSONObject();
|
||||
updateFileBody.put("branch", CmsConstants.BRANCH_MASTER);
|
||||
updateFileBody.put("message", "移动文章至新栏目");
|
||||
updateFileBody.put("from_path", entryDto.getPath());
|
||||
String newFilePath = newCmsDirName + "/" + entryDto.getName();
|
||||
updateFileBody.put("filepath", newFilePath);
|
||||
updateFileBody.put("base64_filepath", Base64.contentToBase64(newFilePath));
|
||||
updateFileBody.put("sha", entryDto.getSha());
|
||||
String content = entryDto.getContent();
|
||||
if (content == null) {
|
||||
content = "";
|
||||
}
|
||||
updateFileBody.put("content", content);
|
||||
gitLinkRequestHelper.doPut(CmsGitLinkRequestUrl.UPDATE_FILE(cmsProject), updateFileBody);
|
||||
}
|
||||
CmsDoc newCmsDoc = oldCmsDir.copyToRenameCmsDir(newCmsDirName);
|
||||
//在数据库中重命名该栏目
|
||||
cmsDocService.updateCmsCommonDatabase(newCmsDoc, CmsConstants.BRANCH_MASTER);
|
||||
} catch (Exception e) {
|
||||
logger.error("重命名栏目失败(原栏目名称[{}],新栏目名称[{}]):{}", oldCmsDir.getName(), newCmsDir.getName(), e.getMessage());
|
||||
throw new ServiceException("重命名栏目失败(原栏目名称[" + oldCmsDir.getName() + "],新栏目名称[" + newCmsDir.getName() + "])");
|
||||
} finally {
|
||||
remoteZoneService.lockCmsByDeptId(cmsProject.getDeptId(), 0, SecurityConstants.INNER);
|
||||
logger.error("重命名栏目失败(原栏目名称[{}],新栏目名称[{}]):{}", oldCmsDir.getName(), newCmsDirName, e.getMessage());
|
||||
throw new ServiceException("重命名栏目失败(原栏目名称[" + oldCmsDir.getName() + "],新栏目名称[" + newCmsDirName + "])");
|
||||
}
|
||||
// finally {
|
||||
// remoteZoneService.lockCmsByDeptId(cmsProject.getDeptId(), 0, SecurityConstants.INNER);
|
||||
// }
|
||||
}
|
||||
|
||||
// @Override
|
||||
// @Async
|
||||
// public void asyncMoveFileToNewDir(CmsProject cmsProject, CmsDoc oldCmsDir, CmsDoc newCmsDir) {
|
||||
// try {
|
||||
// remoteZoneService.lockCmsByDeptId(cmsProject.getDeptId(), 1, SecurityConstants.INNER);
|
||||
// //创建新栏目
|
||||
// String message = String.format("重命名栏目:由[%s]重命名为[%s]", oldCmsDir.getName(), newCmsDir.getName());
|
||||
// cmsDocService.createFileByGitLink(newCmsDir, message, "");
|
||||
// //在数据库中重命名该栏目,防止后续操作推送的WebHook重复创建栏目
|
||||
// cmsDocService.updateCmsCommonDatabase(newCmsDir, CmsConstants.BRANCH_MASTER);
|
||||
// //将原栏目中所有文章移动到新栏目中
|
||||
// moveCmsDocToNewCmsDir(cmsProject, oldCmsDir.getName(), newCmsDir.getName(), message);
|
||||
// //删除旧栏目
|
||||
// deleteCmsDocByGitLink(cmsProject, oldCmsDir);
|
||||
// } catch (Exception e) {
|
||||
// logger.error("重命名栏目失败(原栏目名称[{}],新栏目名称[{}]):{}", oldCmsDir.getName(), newCmsDir.getName(), e.getMessage());
|
||||
// throw new ServiceException("重命名栏目失败(原栏目名称[" + oldCmsDir.getName() + "],新栏目名称[" + newCmsDir.getName() + "])");
|
||||
// } finally {
|
||||
// remoteZoneService.lockCmsByDeptId(cmsProject.getDeptId(), 0, SecurityConstants.INNER);
|
||||
// }
|
||||
// }
|
||||
|
||||
@Async
|
||||
@Override
|
||||
public void pushDocDataSearchEngine(CmsDoc cmsDoc) {
|
||||
|
|
@ -938,6 +1018,23 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearTmpDoc(CmsProject cmsProject, List<CmsDoc> tmpDocList) {
|
||||
CustomExecutorFactory.threadPoolExecutor.execute(() -> {
|
||||
for (CmsDoc tmpDoc : tmpDocList) {
|
||||
//拒绝GitLink合并请求
|
||||
String gitLinkPullRequestNumber = tmpDoc.getAuditPrNumber().split("-")[0];
|
||||
gitLinkRequestHelper.doPost(
|
||||
CmsGitLinkRequestUrl.REFUSE_MERGE_PULL_REQUEST(cmsProject, gitLinkPullRequestNumber), null);
|
||||
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, tmpDoc.getAuditTmpBranchName()), null);
|
||||
// 所有删除操作调整为将审核状态设置为已拒绝
|
||||
tmpDoc.setAuditStatus(DocAuditStatus.AUDIT_FAIL.getAuditCode());
|
||||
tmpDoc.setRemark("管理员删除");
|
||||
cmsDocMapper.updateCmsDoc(tmpDoc);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void moveCmsDocToNewCmsDir(CmsProject cmsProject, String oldDirName, String newDirName, String message) {
|
||||
CmsDocSearchVo cmsDocSearch = new CmsDocSearchVo();
|
||||
cmsDocSearch.setIsDir(false);
|
||||
|
|
@ -990,4 +1087,127 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为积分部门
|
||||
*
|
||||
* @param deptId 部门ID
|
||||
* @return 是否为积分部门
|
||||
*/
|
||||
private boolean isPointsDept(Long deptId) {
|
||||
Long cachedPointsDeptId = redisService.getCacheObject(CacheConstants.ZONE_POINTS_DEPT_ID_KEY);
|
||||
return cachedPointsDeptId != null && cachedPointsDeptId > 0 && cachedPointsDeptId.equals(deptId);
|
||||
}
|
||||
|
||||
@Async
|
||||
@Override
|
||||
public void addPublishDocPointsAsync(Long deptId, String createBy, Long docId, String subjectDesc) {
|
||||
try {
|
||||
if (deptId == null || createBy == null) {
|
||||
logger.warn("文章审核通过增加积分失败,deptId或createBy为空,docId={}", docId);
|
||||
return;
|
||||
}
|
||||
// 检查是否为积分部门
|
||||
if (!isPointsDept(deptId)) {
|
||||
logger.info("文章审核通过增加积分跳过,非积分部门,deptId={},docId={}", deptId, docId);
|
||||
return;
|
||||
}
|
||||
// 获取用户ID
|
||||
com.microservices.system.api.domain.SysUser sysUser = FeignUtils.getReturnData(
|
||||
remoteUserService.getSysUserByUserName(createBy, SecurityConstants.INNER));
|
||||
if (sysUser == null || sysUser.getUserId() == null) {
|
||||
logger.warn("文章审核通过增加积分失败,未找到用户信息,createBy={},docId={}", createBy, docId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 直接写入 Redis 积分队列,由 Zone 模块定时任务消费
|
||||
JSONObject task = new JSONObject();
|
||||
task.put("sourceType", CmsConstants.PUBLISH_DOC);
|
||||
task.put("userId", sysUser.getUserId());
|
||||
task.put("deptId", deptId);
|
||||
task.put("sourceId", docId);
|
||||
task.put("subjectDesc", subjectDesc);
|
||||
task.put("operatorId", sysUser.getUserId());
|
||||
redisService.leftPush(CmsConstants.ZONE_POINTS_ASYNC_QUEUE_KEY, task.toJSONString());
|
||||
logger.info("文章审核通过增加积分任务已写入队列,docId={},userId={},deptId={}", docId, sysUser.getUserId(), deptId);
|
||||
} catch (Exception e) {
|
||||
// 捕获异常,确保不影响主事务
|
||||
logger.error("文章审核通过增加积分异常,docId={}", docId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Async
|
||||
@Override
|
||||
public void addVisitsDocPointsAsync(Long deptId, String createBy, Long docId, String docName, Long totalVisits) {
|
||||
try {
|
||||
if (deptId == null || createBy == null) {
|
||||
logger.warn("文章浏览增加积分失败,参数不合法,deptId={},createBy={}", deptId, createBy);
|
||||
return;
|
||||
}
|
||||
// 检查是否为积分部门
|
||||
if (!isPointsDept(deptId)) {
|
||||
logger.info("文章浏览增加积分跳过,非积分部门,deptId={},docId={}", deptId, docId);
|
||||
return;
|
||||
}
|
||||
// 获取用户ID
|
||||
com.microservices.system.api.domain.SysUser sysUser = FeignUtils.getReturnData(
|
||||
remoteUserService.getSysUserByUserName(createBy, SecurityConstants.INNER));
|
||||
if (sysUser == null || sysUser.getUserId() == null) {
|
||||
logger.warn("文章浏览增加积分失败,未找到用户信息,createBy={},docId={}", createBy, docId);
|
||||
return;
|
||||
}
|
||||
// 直接写入 Redis 积分队列,由 Zone 模块定时任务消费
|
||||
JSONObject task = new JSONObject();
|
||||
task.put("sourceType", CmsConstants.VISITS_DOC);
|
||||
task.put("userId", sysUser.getUserId());
|
||||
task.put("deptId", deptId);
|
||||
task.put("sourceId", docId);
|
||||
// JSONObject sourceData = new JSONObject();
|
||||
// sourceData.put("totalVisits", totalVisits);
|
||||
// sourceData.put("currentIncrement", pointsTriggerCount);
|
||||
task.put("sourceData", totalVisits);
|
||||
task.put("subjectDesc", docName);
|
||||
task.put("operatorId", SecurityUtils.getUserId());
|
||||
redisService.leftPush(CmsConstants.ZONE_POINTS_ASYNC_QUEUE_KEY, task.toJSONString());
|
||||
logger.info("文章浏览增加积分任务已写入队列,docId={},userId={},deptId={},totalVisits={}", docId, sysUser.getUserId(), deptId, totalVisits);
|
||||
} catch (Exception e) {
|
||||
// 捕获异常,确保不影响主事务
|
||||
logger.error("文章浏览增加积分异常,docId={}", docId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Async
|
||||
@Override
|
||||
public void addFeaturedDocPointsAsync(Long deptId, String createBy, Long docId, String subjectDesc) {
|
||||
try {
|
||||
if (deptId == null || createBy == null) {
|
||||
logger.warn("积分-文章精选:参数不合法,deptId={},createBy={}", deptId, createBy);
|
||||
return;
|
||||
}
|
||||
// 检查是否为积分部门
|
||||
if (!isPointsDept(deptId)) {
|
||||
logger.info("积分-文章精选:非积分部门,deptId={},docId={}", deptId, docId);
|
||||
return;
|
||||
}
|
||||
// 获取用户ID
|
||||
com.microservices.system.api.domain.SysUser sysUser = FeignUtils.getReturnData(
|
||||
remoteUserService.getSysUserByUserName(createBy, SecurityConstants.INNER));
|
||||
if (sysUser == null || sysUser.getUserId() == null) {
|
||||
logger.warn("积分-文章精选:未找到用户信息,createBy={},docId={}", createBy, docId);
|
||||
return;
|
||||
}
|
||||
// 直接写入 Redis 积分队列,由 Zone 模块定时任务消费
|
||||
JSONObject task = new JSONObject();
|
||||
task.put("sourceType", CmsConstants.FEATURED_DOC);
|
||||
task.put("userId", sysUser.getUserId());
|
||||
task.put("deptId", deptId);
|
||||
task.put("sourceId", docId);
|
||||
task.put("subjectDesc", "《" + subjectDesc + "》管理员标记");
|
||||
task.put("operatorId", SecurityUtils.getUserId());
|
||||
redisService.leftPush(CmsConstants.ZONE_POINTS_ASYNC_QUEUE_KEY, task.toJSONString());
|
||||
logger.info("文章被设为精选增加积分任务已写入队列,docId={},userId={},deptId={}", docId, sysUser.getUserId(), deptId);
|
||||
} catch (Exception e) {
|
||||
// 捕获异常,确保不影响主事务
|
||||
logger.error("文章被设为精选增加积分异常,docId={}", docId, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.microservices.cms.utils.CmsGitLinkRequestUrl;
|
|||
import com.microservices.common.core.constant.CacheConstants;
|
||||
import com.microservices.common.core.constant.HttpStatus;
|
||||
import com.microservices.common.core.constant.SecurityConstants;
|
||||
import com.microservices.common.core.constant.UserConstants;
|
||||
import com.microservices.common.core.enums.DocAndZoneOperation;
|
||||
import com.microservices.common.core.enums.DocAuditStatus;
|
||||
import com.microservices.common.core.enums.SystemRole;
|
||||
|
|
@ -23,10 +24,12 @@ import com.microservices.common.core.utils.DateUtils;
|
|||
import com.microservices.common.core.utils.StringUtils;
|
||||
import com.microservices.common.core.utils.bean.BeanUtils;
|
||||
import com.microservices.common.core.utils.html.EscapeUtil;
|
||||
import com.microservices.common.core.web.page.GenericsTableDataInfo;
|
||||
import com.microservices.common.httpClient.constant.GitLinkConstants;
|
||||
import com.microservices.common.httpClient.util.GitLinkRequestHelper;
|
||||
import com.microservices.common.redis.service.RedisService;
|
||||
import com.microservices.common.security.auth.AuthUtil;
|
||||
import com.microservices.common.security.service.TokenService;
|
||||
import com.microservices.common.security.utils.SecurityUtils;
|
||||
import com.microservices.system.api.RemoteDeptService;
|
||||
import com.microservices.system.api.RemoteUserService;
|
||||
|
|
@ -47,7 +50,11 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
|
@ -79,6 +86,8 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
@Autowired
|
||||
private RemoteDeptService remoteDeptService;
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
@Autowired
|
||||
@Lazy
|
||||
private ICmsDocCommentService cmsDocCommentService;
|
||||
@Value("${cms.detailPath:/newdetail}")
|
||||
|
|
@ -88,7 +97,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
* 查询文章信息
|
||||
*
|
||||
* @param id 文章信息主键
|
||||
* @param isOpen
|
||||
* @param isOpen 是否公开访问
|
||||
* @return 文章信息
|
||||
*/
|
||||
@Override
|
||||
|
|
@ -103,7 +112,14 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
|
||||
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(cmsDoc.getDeptId());
|
||||
|
||||
String branchName = DocAuditStatus.AUDIT_PASS.getAuditCode().equals(cmsDoc.getAuditStatus()) ? CmsConstants.BRANCH_MASTER : cmsDoc.getAuditTmpBranchName();
|
||||
// 确定查询文件的分支:已发布文章或非仓库改动使用 master,其他使用临时分支
|
||||
String branchName;
|
||||
if (DocAuditStatus.AUDIT_PASS.getAuditCode().equals(cmsDoc.getAuditStatus())
|
||||
|| (cmsDoc.getAuditType() != null && cmsDoc.getAuditType().contains(DocAndZoneOperation.OPERATION_NON_REPO_UPDATE.getOperationCode()))) {
|
||||
branchName = CmsConstants.BRANCH_MASTER;
|
||||
} else {
|
||||
branchName = cmsDoc.getAuditTmpBranchName();
|
||||
}
|
||||
FileEntryDto fileEntryDto = getFileEntryDto(cmsProject, cmsDoc.getFilePath(), branchName);
|
||||
EntryDto entryDto = fileEntryDto.getEntries();
|
||||
|
||||
|
|
@ -114,7 +130,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
if (cmsDoc.getPublishTime() != null && cmsDoc.getPublishTime().after(cmsDoc.getCreateTime())) {
|
||||
cmsDocDto.setIsFirstPublish(false);
|
||||
}
|
||||
if (isOpen) {
|
||||
if (isOpen && DocAuditStatus.AUDIT_PASS.getAuditCode().equals(cmsDoc.getAuditStatus())) {
|
||||
updateVisits(cmsDocDto.getId());
|
||||
}
|
||||
return cmsDocDto;
|
||||
|
|
@ -170,6 +186,21 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
return cmsDocList.stream().map(CmsDoc::copyToCmsDocAllDto).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public GenericsTableDataInfo<CmsDocAllDto> selectAllDocList(Long deptId, CmsDocSearchVo cmsDocSearchVo) {
|
||||
List<CmsDocAllDto> list = selectCmsDocListByDept(deptId, cmsDocSearchVo);
|
||||
list.forEach(x -> {
|
||||
String docDetailUrl = getDocDetailUrl(x.getDeptId(), x.getId());
|
||||
x.setDocDetailUrl(docDetailUrl);
|
||||
|
||||
Boolean editable = isCmsDocEditable(x);
|
||||
x.setEditable(editable);
|
||||
|
||||
});
|
||||
int count = selectCmsDocCountByDept(deptId, cmsDocSearchVo);
|
||||
return new GenericsTableDataInfo<>(list, Long.valueOf(count));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int selectCmsDocCountByDept(Long deptId, CmsDocSearchVo cmsDocSearchVo) {
|
||||
cmsDocSearchVo.setDeptId(deptId);
|
||||
|
|
@ -197,20 +228,21 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
@Override
|
||||
public void updateCmsDir(Long id, CmsDirUpdateVo cmsDirUpdateVo) {
|
||||
CmsDoc oldCmsDir = selectCmsDir(id);
|
||||
checkCmsLockStatus(oldCmsDir.getDeptId());
|
||||
//设置栏目排序
|
||||
setCmsSort(cmsDirUpdateVo.getOrder(), oldCmsDir, false, null);
|
||||
oldCmsDir.setHeadImg(cmsDirUpdateVo.getHeadImg());
|
||||
updateCmsCommonDatabase(oldCmsDir, CmsConstants.BRANCH_MASTER);
|
||||
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(oldCmsDir.getDeptId());
|
||||
CmsDoc newCmsDir = oldCmsDir.copyToRenameCmsDir(cmsDirUpdateVo.getName());
|
||||
String newCmsDirName = cmsDirUpdateVo.getName();
|
||||
|
||||
if (StringUtils.isNotEmpty(newCmsDir.getName()) && !newCmsDir.getName().equals(oldCmsDir.getName())) {
|
||||
CmsDoc cmsDir = cmsDocMapper.selectCmsDocDirByNameAndDeptId(newCmsDir.getName(), oldCmsDir.getDeptId());
|
||||
if (StringUtils.isNotEmpty(newCmsDirName) && !newCmsDirName.equals(oldCmsDir.getName())) {
|
||||
CmsDoc cmsDir = cmsDocMapper.selectCmsDocDirByNameAndDeptId(newCmsDirName, oldCmsDir.getDeptId());
|
||||
if (cmsDir != null) {
|
||||
throw new ServiceException("栏目名称已存在,不允许进行重命名(新栏目名称[" + newCmsDir.getName() + "])");
|
||||
}
|
||||
cmsAsyncService.asyncMoveFileToNewDir(cmsProject, oldCmsDir, newCmsDir);
|
||||
throw new ServiceException("栏目名称已存在,不允许进行重命名(新栏目名称[" + newCmsDirName + "])");
|
||||
}
|
||||
// 异步修改栏目名称(将仓库内原文件夹下所有文章移动到新文件夹下)
|
||||
cmsAsyncService.asyncMoveFileToNewDir(cmsProject, oldCmsDir, newCmsDirName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -231,14 +263,14 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
@Override
|
||||
public List<CmsDocDetailDto> selectCmsDocDetailList(Long dirId) {
|
||||
List<CmsDocDetailDto> cmsDocDetailDtoList = new ArrayList<>();
|
||||
CmsDoc cmsDir = cmsDocMapper.selectCmsDocDirById(dirId);
|
||||
CmsDoc cmsDir = selectCmsDir(dirId);
|
||||
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(cmsDir.getDeptId());
|
||||
CmsDocSearchVo cmsDocSearchVo = new CmsDocSearchVo();
|
||||
cmsDocSearchVo.setDirName(cmsDir.getName());
|
||||
cmsDocSearchVo.setDeptId(cmsDir.getDeptId());
|
||||
cmsDocSearchVo.setIsDir(false);
|
||||
List<CmsDoc> cmsDocList = cmsDocMapper.selectCmsDocList(cmsDocSearchVo);
|
||||
List<CompletableFuture<CmsDocDetailDto>> futures = new ArrayList();
|
||||
List<CmsDoc> cmsDocList = cmsDocMapper.selectCmsDocListSetDefaultHeadImg(cmsDocSearchVo);
|
||||
List<CompletableFuture<CmsDocDetailDto>> futures = new ArrayList<>();
|
||||
for (CmsDoc cmsDoc : cmsDocList) {
|
||||
CompletableFuture<CmsDocDetailDto> future = CompletableFuture.supplyAsync(() -> {
|
||||
CmsDocDetailDto cmsDocDetailDto = cmsDoc.copyToCmsDocDetailDto();
|
||||
|
|
@ -283,6 +315,21 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
return selectCmsDocListByDatabase(dirId, cmsDocSearchVo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CmsDocBaseDto> selectSetDefaultHeadImgCmsDocList(Long dirId, CmsDocSearchVo cmsDocSearchVo) {
|
||||
return selectCmsDocListSetDefaultHeadImgByDatabase(dirId, cmsDocSearchVo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CmsDoc> selectCmsDocAndDirListByDept(Long deptId) {
|
||||
return cmsDocMapper.selectCmsDocAndDirListByDept(deptId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCmsDocShaByPathAndDeptId(String sha, String filePath, Long deptId) {
|
||||
cmsDocMapper.updateCmsDocShaByPathAndDeptId(sha, filePath, deptId);
|
||||
}
|
||||
|
||||
private List<CmsDocBaseDto> selectCmsDocListByDatabase(Long dirId, CmsDocSearchVo cmsDocSearchVo) {
|
||||
CmsDoc cmsDocDir = selectCmsDir(dirId);
|
||||
|
||||
|
|
@ -295,6 +342,25 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
return cmsDocList.stream().map(CmsDoc::copyToCmsDocBaseDto).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文章默认头图为栏目头图
|
||||
*
|
||||
* @param dirId 栏目id
|
||||
* @param cmsDocSearchVo 文章查询条件
|
||||
* @return 文章列表
|
||||
*/
|
||||
private List<CmsDocBaseDto> selectCmsDocListSetDefaultHeadImgByDatabase(Long dirId, CmsDocSearchVo cmsDocSearchVo) {
|
||||
CmsDoc cmsDocDir = selectCmsDir(dirId);
|
||||
|
||||
cmsDocSearchVo.setDeptId(cmsDocDir.getDeptId());
|
||||
cmsDocSearchVo.setDirName(cmsDocDir.getName());
|
||||
List<CmsDoc> cmsDocList = cmsDocMapper.selectCmsDocListSetDefaultHeadImg(cmsDocSearchVo);
|
||||
if (cmsDocList == null || cmsDocList.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return cmsDocList.stream().map(CmsDoc::copyToCmsDocBaseDto).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查专区资讯是否处于锁定状态,若处于则抛出异常
|
||||
*
|
||||
|
|
@ -324,12 +390,16 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
checkCmsLockStatus(dirDoc.getDeptId());
|
||||
CmsDoc cmsDoc = cmsDocInputDto.toCmsDoc(dirDoc);
|
||||
|
||||
if (isNoAuditRequired(dirDoc.getDeptId()) || isCurrentUserZoneAdmin(dirDoc.getDeptId())) {
|
||||
if (isNoAuditRequired(dirDoc.getDeptId()) || tokenService.hasDeptPerms(dirDoc.getDeptId(), UserConstants.CMS_PERMS_DOCS_AUDIT)) {
|
||||
//文章自动置为审核通过状态值
|
||||
cmsDoc.setAuditStatus(DocAuditStatus.AUDIT_PASS.getAuditCode());
|
||||
boolean success = insertCommonCmsDoc(cmsDoc,
|
||||
"创建文章:" + StringUtils.truncateStringAndConcatenatingSpecifiedString(cmsDoc.getName(), 100, "..."),
|
||||
cmsDocInputDto.getContent());
|
||||
if (success) {
|
||||
// 自动审核通过时也需要增加积分
|
||||
cmsAsyncService.addPublishDocPointsAsync(cmsDoc.getDeptId(), cmsDoc.getCreateBy(), cmsDoc.getId(), cmsDoc.getName());
|
||||
}
|
||||
return success ? cmsDoc.getId().intValue() : null;
|
||||
} else {
|
||||
//创建临时分支,创建文件,创建PR
|
||||
|
|
@ -367,7 +437,9 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
public String getCmsDocFileContent(CmsProject cmsProject, CmsDoc cmsDoc) {
|
||||
String branchName = CmsConstants.BRANCH_MASTER;
|
||||
if (!DocAuditStatus.AUDIT_PASS.getAuditCode().equals(cmsDoc.getAuditStatus())) {
|
||||
branchName = cmsDoc.getAuditTmpBranchName();
|
||||
if (StringUtils.isNotEmpty(cmsDoc.getAuditTmpBranchName())) {
|
||||
branchName = cmsDoc.getAuditTmpBranchName();
|
||||
}
|
||||
}
|
||||
//当删除文章为文件夹时,判断该文件夹下是否存在文章,若存在需先删除所有文章
|
||||
JSONObject getSubEntriesResult = gitLinkRequestHelper.doGet(
|
||||
|
|
@ -387,9 +459,34 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean auditCmsDoc(Long docId, CmsDocAuditVo cmsDocAuditVo) {
|
||||
CmsDoc cmsDoc = cmsDocMapper.selectCmsDocById(docId);
|
||||
if (cmsDoc == null) {
|
||||
throw new ServiceException("该文章不存在");
|
||||
}
|
||||
checkCmsLockStatus(cmsDoc.getDeptId());
|
||||
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(cmsDoc.getDeptId());
|
||||
|
||||
// 设置审核备注
|
||||
cmsDoc.setRemark(cmsDocAuditVo.getRemark());
|
||||
|
||||
if (cmsDocAuditVo.getPass() == 1) {
|
||||
// 处理非仓库改动的审核通过
|
||||
if (cmsDoc.getAuditType() != null
|
||||
&& cmsDoc.getAuditType().contains(DocAndZoneOperation.OPERATION_NON_REPO_UPDATE.getOperationCode())) {
|
||||
Long oldCmsDocId = Long.parseLong(cmsDoc.getAuditType().split("-")[1]);
|
||||
CmsDoc oldCmsDoc = cmsDocMapper.selectCmsDocById(oldCmsDocId);
|
||||
|
||||
// 用新文章的非仓库字段更新旧文章
|
||||
oldCmsDoc.setHeadImg(cmsDoc.getHeadImg());
|
||||
oldCmsDoc.setSummary(cmsDoc.getSummary());
|
||||
oldCmsDoc.setKeywords(cmsDoc.getKeywords());
|
||||
oldCmsDoc.setRemark(cmsDoc.getRemark());
|
||||
cmsDocMapper.updateCmsDoc(oldCmsDoc);
|
||||
|
||||
// 删除待审核记录
|
||||
cmsDocMapper.deleteCmsDocById(cmsDoc.getId());
|
||||
return true;
|
||||
}
|
||||
|
||||
//同意合并请求
|
||||
JSONObject result = mergePullRequestByGitLink(cmsProject, cmsDoc);
|
||||
if (result.containsKey(GitLinkConstants.STATUS_KEY)) {
|
||||
|
|
@ -411,8 +508,16 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
}
|
||||
}
|
||||
if (cmsDocAuditVo.getPass() == 0) {
|
||||
// 处理非仓库改动的审核拒绝
|
||||
if (cmsDoc.getAuditType().contains(DocAndZoneOperation.OPERATION_NON_REPO_UPDATE.getOperationCode())) {
|
||||
handleCmsDocWithNoPassByReedit(cmsDocAuditVo, cmsDoc, cmsProject);
|
||||
return true;
|
||||
}
|
||||
|
||||
//拒绝GitLink合并请求
|
||||
JSONObject result = refuseMergePullRequestByGitLink(cmsDoc, cmsProject);
|
||||
String gitLinkPullRequestNumber = cmsDoc.getAuditPrNumber().split("-")[0];
|
||||
JSONObject result = gitLinkRequestHelper.doPost(
|
||||
CmsGitLinkRequestUrl.REFUSE_MERGE_PULL_REQUEST(cmsProject, gitLinkPullRequestNumber), null);
|
||||
if (result.containsKey(GitLinkConstants.STATUS_KEY)) {
|
||||
//2.若拒绝GitLink合并请求操作返回正确的状态值,更新数据库
|
||||
if (result.getInteger(GitLinkConstants.STATUS_KEY).equals(1)) {
|
||||
|
|
@ -459,8 +564,10 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
if (cmsDocAuditVo.getReedit().equals(0)) {
|
||||
//删除待审核文章
|
||||
cmsDocMapper.deleteCmsDocById(cmsDoc.getId());
|
||||
//删除分支
|
||||
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
|
||||
// 删除分支(如果有)
|
||||
if (StringUtils.isNotEmpty(cmsDoc.getAuditTmpBranchName())) {
|
||||
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -472,7 +579,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
int success = deleteCmsDocDatabaseById(ids);
|
||||
if (success == ids.length) {
|
||||
//删除临时分支
|
||||
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
|
||||
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -492,7 +599,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
int success = cmsDocMapper.deleteCmsDocById(newDocId);
|
||||
if (success > 0) {
|
||||
//删除临时分支
|
||||
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
|
||||
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -506,11 +613,13 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
int success = cmsDocMapper.updateCmsDoc(cmsDoc);
|
||||
if (success > 0) {
|
||||
//删除临时分支
|
||||
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
|
||||
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
|
||||
}
|
||||
|
||||
// 推送文章地址到搜索引擎
|
||||
cmsAsyncService.pushDocDataSearchEngine(cmsDoc);
|
||||
// 文章审核通过后,给作者增加积分(异步执行,不影响主事务)
|
||||
cmsAsyncService.addPublishDocPointsAsync(cmsDoc.getDeptId(), cmsDoc.getCreateBy(), cmsDoc.getId(), cmsDoc.getName());
|
||||
}
|
||||
|
||||
private JSONObject mergePullRequestByGitLink(CmsProject cmsProject, CmsDoc cmsDoc) {
|
||||
|
|
@ -525,12 +634,6 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
CmsGitLinkRequestUrl.MERGE_PULL_REQUEST(cmsProject, gitLinkPullRequestId), requestBody);
|
||||
}
|
||||
|
||||
private JSONObject refuseMergePullRequestByGitLink(CmsDoc cmsDoc, CmsProject cmsProject) {
|
||||
//1.调用gitlink拒绝合并请求接口
|
||||
return gitLinkRequestHelper.doPost(
|
||||
CmsGitLinkRequestUrl.REFUSE_MERGE_PULL_REQUEST(cmsProject, cmsDoc.getAuditPrNumber()), null);
|
||||
}
|
||||
|
||||
public static String sha1(String data) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||
|
|
@ -552,6 +655,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
throw new ServiceException("计算字符串Sha出错");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCmsSort(Integer order, CmsDoc oldCmsDoc, boolean isChangeOnDir, String isHomePage) {
|
||||
//调整文章/栏目顺序
|
||||
|
|
@ -671,11 +775,47 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public boolean updateCmsDoc(Long id, CmsDocDto cmsDocDto) {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long updateCmsDoc(Long id, CmsDocDto cmsDocDto) {
|
||||
CmsDoc oldCmsDoc = cmsDocMapper.selectCmsDocById(id);
|
||||
checkCmsLockStatus(oldCmsDoc.getDeptId());
|
||||
if (!hasRepoRelatedChanged(oldCmsDoc, cmsDocDto)) {
|
||||
return cmsDocMapper.updateCmsDoc(oldCmsDoc) > 0;
|
||||
// 更新非仓库字段到原文章对象
|
||||
if (StringUtils.isNotEmpty(cmsDocDto.getHeadImg())) {
|
||||
oldCmsDoc.setHeadImg(cmsDocDto.getHeadImg());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(cmsDocDto.getSummary())) {
|
||||
oldCmsDoc.setSummary(cmsDocDto.getSummary());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(cmsDocDto.getKeywords())) {
|
||||
oldCmsDoc.setKeywords(cmsDocDto.getKeywords());
|
||||
}
|
||||
|
||||
// 管理员或免审核:直接更新原记录
|
||||
if (tokenService.hasDeptPerms(oldCmsDoc.getDeptId(), UserConstants.CMS_PERMS_DOCS_AUDIT)
|
||||
|| isNoAuditRequired(oldCmsDoc.getDeptId())) {
|
||||
cmsDocMapper.updateCmsDoc(oldCmsDoc);
|
||||
return oldCmsDoc.getId();
|
||||
}
|
||||
|
||||
// 需要审核:创建新的待审核记录(与仓库改动逻辑一致)
|
||||
if (oldCmsDoc.getAuditStatus().equals(DocAuditStatus.AUDIT_PASS.getAuditCode())) {
|
||||
CmsDoc newDocWithAudit = insertDocWithAudit(oldCmsDoc,
|
||||
DocAndZoneOperation.OPERATION_NON_REPO_UPDATE.getOperationCode() + "-" + oldCmsDoc.getId(),
|
||||
DocAuditStatus.TO_BE_AUDIT.getAuditCode());
|
||||
return newDocWithAudit.getId();
|
||||
}
|
||||
oldCmsDoc.setAuditStatus(DocAuditStatus.TO_BE_AUDIT.getAuditCode());
|
||||
if (oldCmsDoc.getAuditType().contains(DocAndZoneOperation.OPERATION_CREATE.getOperationCode())
|
||||
|| oldCmsDoc.getAuditType().contains(DocAndZoneOperation.OPERATION_UPDATE.getOperationCode())) {
|
||||
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(oldCmsDoc.getDeptId());
|
||||
// 重新开启PR
|
||||
String gitLinkPullRequestNumber = oldCmsDoc.getAuditPrNumber().split("-")[0];
|
||||
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.REOPEN_PULL_REQUEST(cmsProject, gitLinkPullRequestNumber), null);
|
||||
}
|
||||
// 待审核/未通过文章重新编辑
|
||||
cmsDocMapper.updateCmsDoc(oldCmsDoc);
|
||||
return oldCmsDoc.getId();
|
||||
}
|
||||
// 当文章名称发生更改时,判断文章名是否在当前栏目下已存在
|
||||
if (StringUtils.isNotEmpty(cmsDocDto.getName()) && !cmsDocDto.getName().equals(oldCmsDoc.getName())) {
|
||||
|
|
@ -688,8 +828,16 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
}
|
||||
checkDocName(cmsDocDto.getName(), oldCmsDoc.getDeptId(), dirName);
|
||||
}
|
||||
|
||||
// 当用户更新栏目时,需检查新栏目下是否已存在相同文章名
|
||||
if (cmsDocDto.getDirId() != null) {
|
||||
CmsDir cmsDir = selectCmsDirById(cmsDocDto.getDirId(), false);
|
||||
if (cmsDir != null && !cmsDir.getName().equals(oldCmsDoc.getDirName())) {
|
||||
checkDocName(cmsDocDto.getName(), oldCmsDoc.getDeptId(), cmsDir.getName());
|
||||
}
|
||||
}
|
||||
//用户为专区管理员或专区无审核机制时,直接更新文章及仓库主分支
|
||||
if (isCurrentUserZoneAdmin(oldCmsDoc.getDeptId()) || isNoAuditRequired(oldCmsDoc.getDeptId())) {
|
||||
if (tokenService.hasDeptPerms(oldCmsDoc.getDeptId(), UserConstants.CMS_PERMS_DOCS_AUDIT) || isNoAuditRequired(oldCmsDoc.getDeptId())) {
|
||||
//专区管理员重新编辑并提交审核未通过文章 或 重新编辑并提交待审核文章时专区不需审核机制
|
||||
if (!oldCmsDoc.getAuditStatus().equals(DocAuditStatus.AUDIT_PASS.getAuditCode())) {
|
||||
//文章审核类型:update-原文章id 取出原文章id直接更新,删除待审核更新类型文章
|
||||
|
|
@ -697,14 +845,15 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
Long originCmsDocId = Long.parseLong(oldCmsDoc.getAuditType().split("-")[1]);
|
||||
CmsDoc originCmsDoc = cmsDocMapper.selectCmsDocById(originCmsDocId);
|
||||
deleteTmpBranchIfBranchExist(oldCmsDoc);
|
||||
BeanUtils.copyProperties(originCmsDoc,oldCmsDoc);
|
||||
BeanUtils.copyProperties(originCmsDoc, oldCmsDoc);
|
||||
cmsDocMapper.deleteCmsDocById(id);
|
||||
} else
|
||||
//文章审核类型:create 直接新增文章
|
||||
if (oldCmsDoc.getAuditType() != null && oldCmsDoc.getAuditType().contains(DocAndZoneOperation.OPERATION_CREATE.getOperationCode())) {
|
||||
deleteTmpBranchIfBranchExist(oldCmsDoc);
|
||||
cmsDocMapper.deleteCmsDocById(id);
|
||||
return directlyInsertAuditedCmsDocWhenZoneHasNoAuditRequired(cmsDocDto, oldCmsDoc);
|
||||
directlyInsertAuditedCmsDocWhenZoneHasNoAuditRequired(cmsDocDto, oldCmsDoc);
|
||||
return oldCmsDoc.getId();
|
||||
}
|
||||
}
|
||||
String branchName = CmsConstants.BRANCH_MASTER;
|
||||
|
|
@ -712,25 +861,62 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
oldCmsDoc.setAuditStatus(DocAuditStatus.AUDIT_PASS.getAuditCode());
|
||||
oldCmsDoc.setAuditTmpBranchName(null);
|
||||
oldCmsDoc.setKeywords(cmsDocDto.getKeywords());
|
||||
return updateCmsCommonDatabase(oldCmsDoc, CmsConstants.BRANCH_MASTER) > 0;
|
||||
updateCmsCommonDatabase(oldCmsDoc, CmsConstants.BRANCH_MASTER);
|
||||
return oldCmsDoc.getId();
|
||||
} else {
|
||||
|
||||
CmsDoc newDocWithAudit = new CmsDoc();
|
||||
//待审核、未通过文章重新编辑并提交
|
||||
if (DocAuditStatus.AUDIT_FAIL.getAuditCode().equals(oldCmsDoc.getAuditStatus())
|
||||
|| DocAuditStatus.TO_BE_AUDIT.getAuditCode().equals(oldCmsDoc.getAuditStatus())) {
|
||||
|
||||
// 非仓库改动待审核记录:检查是否有仓库改动
|
||||
if (oldCmsDoc.getAuditType() != null
|
||||
&& oldCmsDoc.getAuditType().contains(DocAndZoneOperation.OPERATION_NON_REPO_UPDATE.getOperationCode())) {
|
||||
|
||||
if (!hasRepoRelatedChanged(oldCmsDoc, cmsDocDto)) {
|
||||
// 纯非仓库改动:继续在当前记录上更新
|
||||
oldCmsDoc.setAuditStatus(DocAuditStatus.TO_BE_AUDIT.getAuditCode());
|
||||
if (StringUtils.isNotEmpty(cmsDocDto.getHeadImg())) {
|
||||
oldCmsDoc.setHeadImg(cmsDocDto.getHeadImg());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(cmsDocDto.getSummary())) {
|
||||
oldCmsDoc.setSummary(cmsDocDto.getSummary());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(cmsDocDto.getKeywords())) {
|
||||
oldCmsDoc.setKeywords(cmsDocDto.getKeywords());
|
||||
}
|
||||
cmsDocMapper.updateCmsDoc(oldCmsDoc);
|
||||
return oldCmsDoc.getId();
|
||||
} else {
|
||||
// 有仓库改动:转为仓库改动类型,删除当前记录,重新创建
|
||||
Long oldCmsDocId = Long.parseLong(oldCmsDoc.getAuditType().split("-")[1]);
|
||||
CmsDoc originCmsDoc = cmsDocMapper.selectCmsDocById(oldCmsDocId);
|
||||
cmsDocMapper.deleteCmsDocById(oldCmsDoc.getId());
|
||||
|
||||
// 按照仓库改动逻辑重新创建
|
||||
newDocWithAudit = insertDocWithAudit(originCmsDoc,
|
||||
DocAndZoneOperation.OPERATION_UPDATE.getOperationCode() + "-" + oldCmsDocId,
|
||||
DocAuditStatus.TO_BE_AUDIT.getAuditCode());
|
||||
cmsAsyncService.asyncUpdateBranchAndFileAndCreatePR(newDocWithAudit, cmsDocDto);
|
||||
return newDocWithAudit.getId();
|
||||
}
|
||||
}
|
||||
|
||||
// 仓库改动待审核记录:继续在当前记录上更新
|
||||
oldCmsDoc.setAuditStatus(DocAuditStatus.TO_BE_AUDIT.getAuditCode());
|
||||
newDocWithAudit = oldCmsDoc;
|
||||
} else
|
||||
} else {
|
||||
//更新已发布文章
|
||||
if (oldCmsDoc.getAuditStatus().equals(DocAuditStatus.AUDIT_PASS.getAuditCode())) {
|
||||
//1.创建一条auditType="update-原文章id",audit_status为2,其余字段与待更新文章一致的新文章数据
|
||||
newDocWithAudit = insertDocWithAudit(oldCmsDoc,
|
||||
DocAndZoneOperation.OPERATION_UPDATE.getOperationCode() + "-" + id,
|
||||
DocAuditStatus.TO_BE_AUDIT.getAuditCode());
|
||||
//1.创建一条auditType="update-原文章id",audit_status为2,其余字段与待更新文章一致的新文章数据
|
||||
newDocWithAudit = insertDocWithAudit(oldCmsDoc,
|
||||
DocAndZoneOperation.OPERATION_UPDATE.getOperationCode() + "-" + id,
|
||||
DocAuditStatus.TO_BE_AUDIT.getAuditCode());
|
||||
}
|
||||
}
|
||||
cmsAsyncService.asyncUpdateBranchAndFileAndCreatePR(newDocWithAudit, cmsDocDto);
|
||||
return true;
|
||||
return newDocWithAudit.getId();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -782,10 +968,10 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
private void deleteTmpBranchIfBranchExist(CmsDoc cmsDoc) {
|
||||
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(cmsDoc.getDeptId());
|
||||
try {
|
||||
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
|
||||
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
|
||||
} catch (ServiceException e) {
|
||||
//忽略分支不存在异常
|
||||
if(!"分支不存在!".equals(e.getMessage())) {
|
||||
if (!"分支不存在!".equals(e.getMessage())) {
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
|
@ -813,15 +999,29 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
}
|
||||
|
||||
private CmsDoc insertDocWithAudit(CmsDoc oldCmsDoc, String auditType, String auditStatus) {
|
||||
List<CmsDoc> tmpDocList = getTmpDocList(oldCmsDoc);
|
||||
if (!tmpDocList.isEmpty()) {
|
||||
throw new ServiceException("已存在待审核操作,请等待审核完成后再进行操作");
|
||||
}
|
||||
oldCmsDoc.setId(null);
|
||||
oldCmsDoc.setAuditType(auditType);
|
||||
oldCmsDoc.setAuditStatus(auditStatus);
|
||||
oldCmsDoc.setCreateBy(SecurityUtils.getUsername());
|
||||
|
||||
// 非 repo 改动:显式设置 auditTmpBranchName 为 null(没有对应的分支)
|
||||
if (auditType != null && auditType.contains(DocAndZoneOperation.OPERATION_NON_REPO_UPDATE.getOperationCode())) {
|
||||
oldCmsDoc.setAuditTmpBranchName(null);
|
||||
}
|
||||
|
||||
cmsDocMapper.insertCmsDoc(oldCmsDoc);
|
||||
return oldCmsDoc;
|
||||
}
|
||||
|
||||
private CmsDoc insertDeletedDocWithAudit(CmsDoc oldCmsDoc, Long oldCmsDocId) {
|
||||
List<CmsDoc> tmpDocList = getTmpDocList(oldCmsDoc);
|
||||
if (!tmpDocList.isEmpty()) {
|
||||
throw new ServiceException("已存在待审核操作,请等待审核完成后再进行操作");
|
||||
}
|
||||
oldCmsDoc.setId(null);
|
||||
oldCmsDoc.setAuditType(DocAndZoneOperation.OPERATION_DELETE.getOperationCode() + "-" + oldCmsDocId);
|
||||
oldCmsDoc.setAuditStatus(DocAuditStatus.TO_BE_AUDIT.getAuditCode());
|
||||
|
|
@ -863,6 +1063,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
cmsDoc.setIsDir(true);
|
||||
cmsDoc.setFilePath(cmsDoc.getDirPath(cmsDir.getName()));
|
||||
cmsDoc.setDeptId(deptId);
|
||||
cmsDoc.setHeadImg(cmsDir.getHeadImg());
|
||||
cmsDoc.setName(cmsDir.getName());
|
||||
return insertCommonCmsDoc(cmsDoc, "创建栏目:" + cmsDir.getName(), "");
|
||||
}
|
||||
|
|
@ -949,7 +1150,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
List<CmsDoc> cmsDocList = cmsDocMapper.selectHotCmsDocList(cmsDocInput);
|
||||
if (cmsDocList != null) {
|
||||
//todo:需检查该文章是否在GitLink中存在
|
||||
return cmsDocList.stream().map(x -> x.copyToHotCmsDocDto()).collect(Collectors.toList());
|
||||
return cmsDocList.stream().map(CmsDoc::copyToHotCmsDocDto).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
return new ArrayList<>();
|
||||
|
|
@ -966,7 +1167,13 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
visits = visits + cmsDoc.getVisits();
|
||||
}
|
||||
cmsDoc.setVisits(visits);
|
||||
return cmsDocMapper.updateCmsDoc(cmsDoc) > 0;
|
||||
int rows = cmsDocMapper.updateCmsDoc(cmsDoc);
|
||||
if (rows > 0) {
|
||||
cmsAsyncService.addVisitsDocPointsAsync(cmsDoc.getDeptId(), cmsDoc.getCreateBy(),
|
||||
cmsDoc.getId(), cmsDoc.getName(), cmsDoc.getVisits());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -990,7 +1197,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
for (CmsDoc cmsDir : cmsDirList) {
|
||||
CmsDocSearchVo cmsDocSearchVo = new CmsDocSearchVo();
|
||||
cmsDocSearchVo.setIsOpen(isOpen);
|
||||
List<CmsDocBaseDto> cmsDocBaseDtoList = selectCmsDocList(cmsDir.getId(), cmsDocSearchVo);
|
||||
List<CmsDocBaseDto> cmsDocBaseDtoList = selectSetDefaultHeadImgCmsDocList(cmsDir.getId(), cmsDocSearchVo);
|
||||
CmsDocOverviewDto cmsDocOverviewDto = new CmsDocOverviewDto();
|
||||
cmsDocOverviewDto.setId(cmsDir.getId());
|
||||
cmsDocOverviewDto.setName(cmsDir.getName());
|
||||
|
|
@ -1090,6 +1297,9 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
//首页推荐时,需将顺序设置为1
|
||||
setCmsSort(1, cmsDoc, false, null);
|
||||
cmsDoc.setIsHomepage(CmsConstants.TRUE);
|
||||
// 文章被设为精选,异步增加作者积分
|
||||
cmsAsyncService.addFeaturedDocPointsAsync(cmsDoc.getDeptId(), cmsDoc.getCreateBy(),
|
||||
cmsDoc.getId(), cmsDoc.getName());
|
||||
}
|
||||
return cmsDocMapper.updateCmsDoc(cmsDoc);
|
||||
}
|
||||
|
|
@ -1104,7 +1314,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
CmsDocSearchVo cmsDocSearch = new CmsDocSearchVo();
|
||||
cmsDocSearch.setDeptId(deptId);
|
||||
cmsDocSearch.setIsHomepage(CmsConstants.TRUE);
|
||||
List<CmsDoc> cmsDocList = cmsDocMapper.selectCmsDocList(cmsDocSearch);
|
||||
List<CmsDoc> cmsDocList = cmsDocMapper.selectCmsDocListSetDefaultHeadImg(cmsDocSearch);
|
||||
List<CmsDocBaseDto> cmsDocBaseDtoList = new ArrayList<>();
|
||||
for (CmsDoc cmsDoc : cmsDocList) {
|
||||
CmsDocBaseDto cmsDocBaseDto = cmsDoc.copyToCmsDocBaseDto();
|
||||
|
|
@ -1113,6 +1323,41 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
return cmsDocBaseDtoList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CmsDocBaseDto> selectHomePageDocMergeList(Long zoneId, List<Long> topDocIds) {
|
||||
Long deptId = getDeptIdByZoneId(zoneId);
|
||||
Set<Long> addedDocIds = new HashSet<>();
|
||||
|
||||
// 1. 获取首页推荐文章列表
|
||||
List<CmsDocBaseDto> homePageDocs = selectHomePageDocList(deptId);
|
||||
List<CmsDocBaseDto> result = new ArrayList<>();
|
||||
|
||||
// 2. 批量查询置顶文章完整数据,按传入顺序放入结果头部
|
||||
if (topDocIds != null && !topDocIds.isEmpty()) {
|
||||
List<CmsDoc> topCmsDocs = cmsDocMapper.selectCmsDocByIds(topDocIds);
|
||||
Map<Long, CmsDoc> topDocMap = new HashMap<>();
|
||||
for (CmsDoc doc : topCmsDocs) {
|
||||
topDocMap.put(doc.getId(), doc);
|
||||
}
|
||||
for (Long docId : topDocIds) {
|
||||
CmsDoc doc = topDocMap.get(docId);
|
||||
if (doc != null) {
|
||||
result.add(doc.copyToCmsDocBaseDto());
|
||||
addedDocIds.add(docId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 追加首页推荐文章(跳过已在置顶列表中的)
|
||||
for (CmsDocBaseDto doc : homePageDocs) {
|
||||
if (!addedDocIds.contains(doc.getId())) {
|
||||
result.add(doc);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public CmsDir initCmsDir(Long deptId, CmsDir cmsDirInput) {
|
||||
|
|
@ -1259,6 +1504,17 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
cmsAsyncService.updateCmsPublishDataByGitLink(cmsProject, cmsDoc, branchName);
|
||||
}
|
||||
|
||||
private int deleteCmsDir(CmsProject cmsProject, CmsDoc cmsDir) {
|
||||
if (!isNoAuditRequired(cmsDir.getDeptId()) || tokenService.hasDeptPerms(cmsDir.getDeptId(), UserConstants.CMS_PERMS_DOCS_AUDIT)) {
|
||||
boolean deleteResult = cmsAsyncService.deleteCmsDocForGitLink(cmsDir, cmsProject, CmsConstants.BRANCH_MASTER);
|
||||
if (deleteResult) {
|
||||
return cmsDocMapper.deleteCmsDocById(cmsDir.getId());
|
||||
}
|
||||
throw new ServiceException("栏目删除失败");
|
||||
}
|
||||
throw new ServiceException("无权限删除栏目");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteCmsDocById(Long id) {
|
||||
CmsDoc cmsDoc = cmsDocMapper.selectCmsDocById(id);
|
||||
|
|
@ -1268,22 +1524,42 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
checkCmsLockStatus(cmsDoc.getDeptId());
|
||||
//2、获取专区项目
|
||||
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(cmsDoc.getDeptId());
|
||||
if (cmsDoc.getIsDir()) {
|
||||
return deleteCmsDir(cmsProject, cmsDoc);
|
||||
}
|
||||
|
||||
//删除待审核、审核未通过文章——删除临时分支,拒绝合并请求
|
||||
if (DocAuditStatus.TO_BE_AUDIT.getAuditCode().equals(cmsDoc.getAuditStatus())
|
||||
|| DocAuditStatus.AUDIT_FAIL.getAuditCode().equals(cmsDoc.getAuditStatus())) {
|
||||
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
|
||||
cmsDocMapper.deleteCmsDocById(id);
|
||||
return 1;
|
||||
if (DocAuditStatus.TO_BE_AUDIT.getAuditCode().equals(cmsDoc.getAuditStatus())) {
|
||||
// 非仓库改动类型直接删除
|
||||
if (cmsDoc.getAuditType().contains(DocAndZoneOperation.OPERATION_NON_REPO_UPDATE.getOperationCode())) {
|
||||
return cmsDocMapper.deleteCmsDocById(cmsDoc.getId());
|
||||
}
|
||||
|
||||
//拒绝GitLink合并请求
|
||||
String gitLinkPullRequestNumber = cmsDoc.getAuditPrNumber().split("-")[0];
|
||||
gitLinkRequestHelper.doPost(
|
||||
CmsGitLinkRequestUrl.REFUSE_MERGE_PULL_REQUEST(cmsProject, gitLinkPullRequestNumber), null);
|
||||
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
|
||||
return cmsDocMapper.deleteCmsDocById(cmsDoc.getId());
|
||||
}
|
||||
if (isCurrentUserZoneAdmin(cmsDoc.getDeptId()) //当前用户为专区管理员
|
||||
if (DocAuditStatus.AUDIT_FAIL.getAuditCode().equals(cmsDoc.getAuditStatus())) {
|
||||
return cmsDocMapper.deleteCmsDocById(cmsDoc.getId());
|
||||
}
|
||||
// 判读当前用户是否拥有文章直接删除权限
|
||||
if (tokenService.hasDeptPerms(cmsDoc.getDeptId(), UserConstants.CMS_PERMS_DOCS_AUDIT) //当前用户为专区管理员
|
||||
|| isNoAuditRequired(cmsDoc.getDeptId()) //当前专区文章发布无需审核机制
|
||||
) {
|
||||
String branchName = CmsConstants.BRANCH_MASTER;
|
||||
boolean deleteResult = cmsAsyncService.deleteCmsDocForGitLink(cmsDoc, cmsProject, branchName);
|
||||
if (deleteResult) {
|
||||
return deleteCmsDocDatabaseById(cmsDoc.getId());
|
||||
// 拒绝相关合并请求并清理所有临时分支,同时删除所有临时申请
|
||||
List<CmsDoc> tmpDocList = getTmpDocList(cmsDoc);
|
||||
cmsAsyncService.clearTmpDoc(cmsProject, tmpDocList);
|
||||
cmsDoc.setAuditStatus(DocAuditStatus.AUDIT_FAIL.getAuditCode());
|
||||
cmsDoc.setRemark("管理员删除");
|
||||
return cmsDocMapper.updateCmsDoc(cmsDoc);
|
||||
}
|
||||
|
||||
} else {
|
||||
//1.创建一条auditType="delete-原文章id",audit_status=2,其余字段与待删除文章一致的新文章数据
|
||||
CmsDoc newDocWithAudit = insertDeletedDocWithAudit(cmsDoc, id);
|
||||
|
|
@ -1293,6 +1569,18 @@ public class CmsDocServiceImpl implements ICmsDocService {
|
|||
throw new ServiceException("文件删除失败");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CmsDoc> getTmpDocList(CmsDoc cmsDoc) {
|
||||
CmsDocSearchVo cmsDocSearchVo = new CmsDocSearchVo();
|
||||
cmsDocSearchVo.setName(cmsDoc.getName());
|
||||
cmsDocSearchVo.setDeptId(cmsDoc.getDeptId());
|
||||
cmsDocSearchVo.setIsDir(false);
|
||||
cmsDocSearchVo.setDirName(cmsDoc.getDirName());
|
||||
cmsDocSearchVo.setAuditStatus(DocAuditStatus.TO_BE_AUDIT.getAuditCode());
|
||||
// 拒绝相关合并请求并清理所有临时分支,同时删除所有临时申请
|
||||
return cmsDocMapper.selectCmsDocList(cmsDocSearchVo);
|
||||
}
|
||||
|
||||
public int deleteCmsDocDatabaseById(Long id) {
|
||||
CmsDoc cmsDoc = cmsDocMapper.selectCmsDocById(id);
|
||||
if (cmsDoc != null && !cmsDoc.getIsDir()) {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,16 @@ public class CmsProjectController extends BaseController {
|
|||
return R.ok(changeRoleResult);
|
||||
}
|
||||
|
||||
@InnerAuth
|
||||
@PutMapping("/changeOrgRole/user/{userId}/operate/{operate}")
|
||||
@ApiOperation(value = "修改用户所在GItLink组织中的权限", hidden = true)
|
||||
public R<Boolean> changeUserOrgRole(@PathVariable("userId") Long userId, @PathVariable("operate") Long operate
|
||||
) {
|
||||
Boolean changeRoleResult = cmsProjectService.changeUserOrgRole(userId, operate);
|
||||
return R.ok(changeRoleResult);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询专区对应项目列表
|
||||
*/
|
||||
|
|
@ -95,8 +105,12 @@ public class CmsProjectController extends BaseController {
|
|||
@GetMapping("/getProjectByDeptId/{deptId}")
|
||||
public R<String> getProjectByDeptId(
|
||||
@ApiParam(name = "deptId", value = "组织Id") @PathVariable(value = "deptId") Long deptId) {
|
||||
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(deptId);
|
||||
return R.ok(JSONObject.toJSONString(cmsProject));
|
||||
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptIdAndNoException(deptId);
|
||||
if (cmsProject == null) {
|
||||
return R.ok(null);
|
||||
} else {
|
||||
return R.ok(JSONObject.toJSONString(cmsProject));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -170,4 +184,16 @@ public class CmsProjectController extends BaseController {
|
|||
public R<Boolean> removeByDeptId(@PathVariable(value = "deptId") Long deptId) {
|
||||
return R.ok(cmsProjectService.deleteCmsProjectByDeptId(deptId) > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化专区对应项目
|
||||
*/
|
||||
@RequiresPermissions("zone:project:add")
|
||||
@ApiOperation("初始化专区对应项目")
|
||||
@Log(title = "初始化专区对应项目", businessType = BusinessType.INSERT)
|
||||
@PostMapping("initCmsProject/dept/{deptId}")
|
||||
public AjaxResult initCmsProject(
|
||||
@ApiParam(name = "deptId", value = "组织Id") @PathVariable(value = "deptId") Long deptId) {
|
||||
return toAjax(cmsProjectService.initZoneCmsProjectByDept(deptId));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.microservices.cms.project.domain;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author otto
|
||||
*/
|
||||
@Data
|
||||
public class GitlinkOrganizationTeamVo implements Serializable {
|
||||
@ApiModelProperty(value = "团队权限")
|
||||
private String authorize;
|
||||
@ApiModelProperty(value = "团队Id")
|
||||
private Long id;
|
||||
@ApiModelProperty(value = "团队名称")
|
||||
private String name;
|
||||
@ApiModelProperty(value = "团队昵称")
|
||||
private String nickname;
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.microservices.cms.project.mapper;
|
|||
|
||||
import com.microservices.cms.project.domain.CmsProject;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -64,4 +65,25 @@ public interface CmsProjectMapper {
|
|||
CmsProject selectCmsProjectByDeptId(Long deptId);
|
||||
|
||||
CmsProject selectCmsProjectByIdentifier(String identifier);
|
||||
|
||||
/**
|
||||
* 全局搜索 - 全文索引查询
|
||||
*
|
||||
* @param keyword 搜索关键词
|
||||
* @param deptId 组织ID(可选)
|
||||
* @param offset 偏移量
|
||||
* @param pageSize 每页数量
|
||||
* @return 搜索结果列表
|
||||
*/
|
||||
List<CmsProject> globalSearch(@Param("keyword") String keyword, @Param("deptId") Long deptId,
|
||||
@Param("offset") int offset, @Param("pageSize") int pageSize);
|
||||
|
||||
/**
|
||||
* 全局搜索结果计数
|
||||
*
|
||||
* @param keyword 搜索关键词
|
||||
* @param deptId 组织ID(可选)
|
||||
* @return 结果数量
|
||||
*/
|
||||
Long globalSearchCount(@Param("keyword") String keyword, @Param("deptId") Long deptId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,4 +78,10 @@ public interface ICmsProjectService {
|
|||
Boolean updateCmsProjectByDept(SysDept dept);
|
||||
|
||||
boolean operateProtectedBranchByZoneAuditSetting(String userName, String identifier, Long deptId, String operationType);
|
||||
|
||||
boolean initZoneCmsProjectByDept(Long deptId);
|
||||
|
||||
CmsProject selectCmsProjectByDeptIdAndNoException(Long deptId);
|
||||
|
||||
Boolean changeUserOrgRole(Long userId, Long operate);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,24 @@
|
|||
package com.microservices.cms.project.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.microservices.cms.doc.domain.CmsDir;
|
||||
import com.microservices.cms.doc.domain.CmsDocBaseDto;
|
||||
import com.microservices.cms.doc.domain.CmsDocSearchVo;
|
||||
import com.microservices.cms.doc.domain.*;
|
||||
import com.microservices.cms.doc.service.ICmsAsyncService;
|
||||
import com.microservices.cms.doc.service.ICmsDocService;
|
||||
import com.microservices.cms.project.domain.CmsProject;
|
||||
import com.microservices.cms.project.domain.GitlinkOrganizationTeamVo;
|
||||
import com.microservices.cms.project.mapper.CmsProjectMapper;
|
||||
import com.microservices.cms.project.service.ICmsProjectService;
|
||||
import com.microservices.cms.utils.CmsConstants;
|
||||
import com.microservices.cms.utils.CmsGitLinkRequestUrl;
|
||||
import com.microservices.common.core.constant.HttpStatus;
|
||||
import com.microservices.common.core.constant.SecurityConstants;
|
||||
import com.microservices.common.core.enums.SystemRole;
|
||||
import com.microservices.common.core.exception.ServiceException;
|
||||
import com.microservices.common.core.utils.PinYinStringUtils;
|
||||
import com.microservices.common.core.utils.StringUtils;
|
||||
import com.microservices.common.datascope.annotation.DataScope;
|
||||
import com.microservices.common.httpClient.domain.GitLinkRequestUrl;
|
||||
import com.microservices.common.httpClient.util.GitLinkRequestHelper;
|
||||
import com.microservices.system.api.RemoteDeptService;
|
||||
import com.microservices.system.api.RemoteUserService;
|
||||
|
|
@ -23,6 +26,7 @@ import com.microservices.system.api.domain.SysDept;
|
|||
import com.microservices.system.api.domain.SysRole;
|
||||
import com.microservices.system.api.domain.SysUser;
|
||||
import com.microservices.system.api.utils.FeignUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
|
@ -37,6 +41,7 @@ import java.util.List;
|
|||
* @author otto
|
||||
* @date 2023-03-27
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class CmsProjectServiceImpl implements ICmsProjectService {
|
||||
@Autowired
|
||||
|
|
@ -228,6 +233,75 @@ public class CmsProjectServiceImpl implements ICmsProjectService {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initZoneCmsProjectByDept(Long deptId) {
|
||||
boolean initResult = false;
|
||||
CmsProject cmsProject = cmsProjectMapper.selectCmsProjectByDeptId(deptId);
|
||||
if (cmsProject != null) {
|
||||
JSONObject project = null;
|
||||
try {
|
||||
project = gitLinkRequestHelper.doGet(CmsGitLinkRequestUrl.GET_PROJECT_DETAIL(cmsProject.getUsername(), cmsProject.getIdentifier()));
|
||||
} catch (ServiceException e) {
|
||||
if (!e.getCode().equals(HttpStatus.NOT_FOUND)) {
|
||||
throw new ServiceException(e.getCode(), e.getMessage());
|
||||
}
|
||||
}
|
||||
if (project != null) {
|
||||
throw new ServiceException("专区对应项目已存在,不能重复初始化");
|
||||
} else {
|
||||
Long organizationId = getOrganizationId();
|
||||
// 创建专区对应项目
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("user_id", organizationId);
|
||||
jsonObject.put("name", cmsProject.getName());
|
||||
jsonObject.put("repository_name", cmsProject.getIdentifier());
|
||||
jsonObject.put("description", cmsProject.getDescription());
|
||||
jsonObject.put("private", false);
|
||||
//调用GitLink接口创建项目
|
||||
JSONObject result = gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.CREATE_PROJECT(), jsonObject);
|
||||
initResult = result != null && cmsProject.getIdentifier().equals(result.getString("identifier"));
|
||||
}
|
||||
} else {
|
||||
Long projectId = insertZoneCmsProjectByDept(deptId);
|
||||
if (projectId != null && projectId > 0) {
|
||||
cmsProject = cmsProjectMapper.selectCmsProjectById(projectId);
|
||||
initResult = true;
|
||||
}
|
||||
}
|
||||
if (initResult) {
|
||||
// 批量创建文章对应代码库文件
|
||||
List<CmsDoc> cmsDocAndDirList = cmsDocService.selectCmsDocAndDirListByDept(deptId);
|
||||
if (cmsDocAndDirList == null || cmsDocAndDirList.isEmpty()) {
|
||||
return initResult;
|
||||
}
|
||||
JSONObject docCommitJson = new JSONObject();
|
||||
docCommitJson.put("branch", CmsConstants.BRANCH_MASTER);
|
||||
docCommitJson.put("message", "初始化专区文章对应代码库文件");
|
||||
JSONArray docFiles = new JSONArray();
|
||||
for (CmsDoc cmsDoc : cmsDocAndDirList) {
|
||||
JSONObject docFile = new JSONObject();
|
||||
docFile.put("action_type", "create");
|
||||
docFile.put("content", "");
|
||||
docFile.put("encoding", "text");
|
||||
docFile.put("file_path", cmsDoc.getFilePath());
|
||||
docFiles.add(docFile);
|
||||
}
|
||||
docCommitJson.put("files", docFiles);
|
||||
JSONObject result = gitLinkRequestHelper.doPost(
|
||||
CmsGitLinkRequestUrl.BATCH_COMMIT(cmsProject.getUsername(), cmsProject.getIdentifier())
|
||||
, docCommitJson);
|
||||
if (result == null) {
|
||||
throw new ServiceException("专区对应项目初始化成功,但专区文章在项目中创建失败");
|
||||
}
|
||||
JSONArray resultContents = result.getJSONArray("contents");
|
||||
for (int i = 0; i < resultContents.size(); i++) {
|
||||
JSONObject contentJson = resultContents.getJSONObject(i);
|
||||
cmsDocService.updateCmsDocShaByPathAndDeptId(contentJson.getString("sha"), contentJson.getString("path"), deptId);
|
||||
}
|
||||
}
|
||||
return initResult;
|
||||
}
|
||||
|
||||
|
||||
private String handleSpecialCharacters(String name) {
|
||||
return StringUtils.replace(name, " ", "-");
|
||||
|
|
@ -242,6 +316,45 @@ public class CmsProjectServiceImpl implements ICmsProjectService {
|
|||
return cmsProject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CmsProject selectCmsProjectByDeptIdAndNoException(Long deptId) {
|
||||
return cmsProjectMapper.selectCmsProjectByDeptId(deptId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean changeUserOrgRole(Long userId, Long operate) {
|
||||
Long gitlinkOrgId = getOrganizationId();
|
||||
SysUser sysUser = FeignUtils.getReturnData(remoteUserService.getSysUserByUserId(userId, SecurityConstants.INNER));
|
||||
if (sysUser == null) {
|
||||
return false;
|
||||
}
|
||||
List<GitlinkOrganizationTeamVo> gitlinkOrganizationTeamVoList = gitLinkRequestHelper.getAllDataByPage(
|
||||
CmsGitLinkRequestUrl.TEAMS_BY_ORGANIZATION_URL(gitlinkOrgId), "teams", GitlinkOrganizationTeamVo.class
|
||||
);
|
||||
GitlinkOrganizationTeamVo ownerTeam = gitlinkOrganizationTeamVoList.stream().filter(x -> "owner".equals(x.getAuthorize())).findFirst().orElse(null);
|
||||
if (ownerTeam == null) {
|
||||
return false;
|
||||
}
|
||||
if (operate == 1) {
|
||||
JSONObject addUserToTeam = new JSONObject();
|
||||
addUserToTeam.put("username", sysUser.getUserName());
|
||||
gitLinkRequestHelper.doPost(
|
||||
CmsGitLinkRequestUrl.ADD_USER_TO_ORGANIZATION_TEAM(gitLinkOrganization, ownerTeam.getId()), addUserToTeam);
|
||||
}
|
||||
if (operate == 3) {
|
||||
try {
|
||||
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_USER_FROM_ORGANIZATION_TEAM(
|
||||
gitLinkOrganization,
|
||||
ownerTeam.getId(),
|
||||
sysUser.getUserName()
|
||||
));
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean changeUserRole(Long userId, Long oldDeptId, Long newDeptId, Long oldRoleId, Long newRoleId) {
|
||||
SysUser sysUser = FeignUtils.getReturnData(remoteUserService.getSysUserByUserId(userId, SecurityConstants.INNER));
|
||||
|
|
@ -260,9 +373,10 @@ public class CmsProjectServiceImpl implements ICmsProjectService {
|
|||
try {
|
||||
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.REMOVE_USER_FROM_PROJECT(oldCmsProject), requestBody);
|
||||
} catch (ServiceException e) {
|
||||
if (!e.getMessage().contains("用户为组织成员,请到组织下操作")) {
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
log.error("将用户从原项目中移除发生异常:{}", e.getMessage());
|
||||
// if (!e.getMessage().contains("用户为组织成员,请到组织下操作")||!e.getMessage().contains("还不是项目成员")) {
|
||||
// throw new ServiceException(e.getMessage());
|
||||
// }
|
||||
}
|
||||
}
|
||||
//将用户加入新的项目
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
package com.microservices.cms.search.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 文章搜索请求对象
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("文章搜索请求对象")
|
||||
public class DocSearchRequest implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 搜索关键词
|
||||
*/
|
||||
@ApiModelProperty(value = "搜索关键词", required = true)
|
||||
private String keyword;
|
||||
|
||||
/**
|
||||
* 专区ID
|
||||
*/
|
||||
@ApiModelProperty(value = "专区ID")
|
||||
private Long zoneId;
|
||||
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
@ApiModelProperty(value = "页码")
|
||||
private Integer pageNum = 1;
|
||||
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
@ApiModelProperty(value = "每页数量")
|
||||
private Integer pageSize = 20;
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.microservices.cms.search.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 全局搜索响应对象
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("文章搜索响应对象")
|
||||
public class DocSearchResponse implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 总结果数
|
||||
*/
|
||||
@ApiModelProperty(value = "总结果数")
|
||||
private Long total;
|
||||
|
||||
/**
|
||||
* 搜索结果列表
|
||||
*/
|
||||
@ApiModelProperty(value = "搜索结果列表")
|
||||
private List<DocSearchResult> results;
|
||||
|
||||
/**
|
||||
* 各类型结果数量统计
|
||||
*/
|
||||
@ApiModelProperty(value = "各类型结果数量统计")
|
||||
private Map<String, Long> typeCounts;
|
||||
|
||||
/**
|
||||
* 搜索耗时(毫秒)
|
||||
*/
|
||||
@ApiModelProperty(value = "搜索耗时(毫秒)")
|
||||
private Long searchTime;
|
||||
|
||||
/**
|
||||
* 搜索关键词
|
||||
*/
|
||||
@ApiModelProperty(value = "搜索关键词")
|
||||
private String keyword;
|
||||
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
@ApiModelProperty(value = "当前页码")
|
||||
private Integer pageNum;
|
||||
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
@ApiModelProperty(value = "每页数量")
|
||||
private Integer pageSize;
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.microservices.cms.search.dto;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 文章搜索结果对象
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("文章搜索结果对象")
|
||||
public class DocSearchResult implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@ApiModelProperty(value = "ID")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
@ApiModelProperty(value = "标题")
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 内容摘要
|
||||
*/
|
||||
@ApiModelProperty(value = "内容摘要")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 组织ID
|
||||
*/
|
||||
@ApiModelProperty(value = "组织ID")
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@ApiModelProperty(value = "创建时间")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 相关度评分
|
||||
*/
|
||||
@ApiModelProperty(value = "相关度评分")
|
||||
private Double score;
|
||||
|
||||
/**
|
||||
* 扩展数据(JSON格式)
|
||||
*/
|
||||
@ApiModelProperty(value = "扩展数据")
|
||||
private JSONObject extendData;
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.microservices.cms.search.service;
|
||||
|
||||
import com.microservices.cms.search.dto.DocSearchRequest;
|
||||
import com.microservices.cms.search.dto.DocSearchResponse;
|
||||
|
||||
/**
|
||||
* 全局搜索服务接口
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
public interface IGlobalSearchService {
|
||||
|
||||
/**
|
||||
* 全局搜索
|
||||
*
|
||||
* @param request 搜索请求
|
||||
* @return 搜索响应
|
||||
*/
|
||||
DocSearchResponse docSearch(DocSearchRequest request);
|
||||
}
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
package com.microservices.cms.search.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.microservices.cms.doc.domain.CmsDoc;
|
||||
import com.microservices.cms.doc.domain.CmsDocWithScore;
|
||||
import com.microservices.cms.doc.mapper.CmsDocMapper;
|
||||
import com.microservices.cms.doc.service.ICmsDocService;
|
||||
import com.microservices.cms.search.service.IGlobalSearchService;
|
||||
import com.microservices.common.core.utils.StringUtils;
|
||||
import com.microservices.common.redis.service.RedisService;
|
||||
import com.microservices.cms.search.dto.DocSearchRequest;
|
||||
import com.microservices.cms.search.dto.DocSearchResponse;
|
||||
import com.microservices.cms.search.dto.DocSearchResult;
|
||||
import com.microservices.common.security.utils.SecurityUtils;
|
||||
import com.microservices.system.api.model.LoginUser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 全局搜索服务实现
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Service
|
||||
public class GlobalSearchServiceImpl implements IGlobalSearchService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GlobalSearchServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private CmsDocMapper cmsDocMapper;
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
@Autowired
|
||||
private ICmsDocService cmsDocService;
|
||||
|
||||
private static final String SEARCH_CACHE_PREFIX = "search:cache:";
|
||||
private static final Long CACHE_EXPIRE_SECONDS = 300L; // 5分钟
|
||||
|
||||
@Override
|
||||
public DocSearchResponse docSearch(DocSearchRequest request) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// 参数校验
|
||||
if (StringUtils.isEmpty(request.getKeyword())) {
|
||||
return buildEmptyResponse(request);
|
||||
}
|
||||
|
||||
// 1. 尝试从缓存获取(仅第一页)
|
||||
if (request.getPageNum() == 1) {
|
||||
String cacheKey = buildCacheKey(request);
|
||||
|
||||
DocSearchResponse cached = redisService.getCacheObject(SEARCH_CACHE_PREFIX + cacheKey);
|
||||
if (cached != null) {
|
||||
cached.setSearchTime(System.currentTimeMillis() - startTime);
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 并行搜索各类型
|
||||
List<DocSearchResult> allResults = new ArrayList<>();
|
||||
Map<String, Long> typeCounts = new HashMap<>();
|
||||
|
||||
// 获取当前用户信息
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
|
||||
// 搜索CMS文章
|
||||
List<DocSearchResult> docResults = searchDocs(request);
|
||||
allResults.addAll(docResults);
|
||||
typeCounts.put("doc", (long) docResults.size());
|
||||
|
||||
// 3. 按相关度评分排序
|
||||
allResults.sort((a, b) -> {
|
||||
if (b.getScore() != null && a.getScore() != null) {
|
||||
return b.getScore().compareTo(a.getScore());
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
// 4. 分页
|
||||
int total = allResults.size();
|
||||
int start = (request.getPageNum() - 1) * request.getPageSize();
|
||||
int end = Math.min(start + request.getPageSize(), total);
|
||||
List<DocSearchResult> pagedResults = start < total ?
|
||||
allResults.subList(start, end) : new ArrayList<>();
|
||||
|
||||
// 5. 构建响应
|
||||
DocSearchResponse response = new DocSearchResponse();
|
||||
response.setTotal((long) total);
|
||||
response.setResults(pagedResults);
|
||||
response.setTypeCounts(typeCounts);
|
||||
response.setKeyword(request.getKeyword());
|
||||
response.setPageNum(request.getPageNum());
|
||||
response.setPageSize(request.getPageSize());
|
||||
response.setSearchTime(System.currentTimeMillis() - startTime);
|
||||
|
||||
// 6. 缓存结果(仅第一页)
|
||||
if (request.getPageNum() == 1) {
|
||||
String cacheKey = buildCacheKey(request);
|
||||
redisService.setCacheObject(SEARCH_CACHE_PREFIX + cacheKey, response, CACHE_EXPIRE_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
// 7. 记录搜索日志
|
||||
recordSearchLog(request, response, loginUser);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录搜索日志
|
||||
*/
|
||||
private void recordSearchLog(DocSearchRequest request, DocSearchResponse response, LoginUser loginUser) {
|
||||
Long userId = loginUser != null ? loginUser.getUserId() : null;
|
||||
String searchTypes = "文章";
|
||||
Long deptId = cmsDocService.getDeptIdByZoneId(request.getZoneId());
|
||||
logger.info("全局搜索 - 关键词: {}, 用户ID: {}, 耗时: {}ms, 结果数: {}, 类型: {}, 专区Id: {}, 组织ID: {}",
|
||||
request.getKeyword(),
|
||||
userId,
|
||||
response.getSearchTime(),
|
||||
response.getTotal(),
|
||||
searchTypes,
|
||||
request.getZoneId(),
|
||||
deptId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索CMS文章
|
||||
*/
|
||||
private List<DocSearchResult> searchDocs(DocSearchRequest request) {
|
||||
List<DocSearchResult> results = new ArrayList<>();
|
||||
try {
|
||||
// 使用部门ID参数
|
||||
Long deptId = cmsDocService.getDeptIdByZoneId(request.getZoneId());
|
||||
|
||||
// 使用新的带评分的查询方法
|
||||
List<CmsDocWithScore> docs = cmsDocMapper.globalSearchWithScore(
|
||||
request.getKeyword(),
|
||||
deptId,
|
||||
(request.getPageNum() - 1) * request.getPageSize(),
|
||||
request.getPageSize()
|
||||
);
|
||||
|
||||
for (CmsDocWithScore doc : docs) {
|
||||
DocSearchResult result = new DocSearchResult();
|
||||
result.setId(doc.getId());
|
||||
result.setTitle(highlightKeyword(doc.getName(), request.getKeyword()));
|
||||
result.setContent(highlightKeyword(doc.getSummary(), request.getKeyword()));
|
||||
result.setDeptId(doc.getDeptId());
|
||||
result.setCreateTime(doc.getCreateTime());
|
||||
|
||||
// 使用数据库返回的实际搜索评分
|
||||
Double score = doc.getSearchScore();
|
||||
if (score == null || score == 0.0) {
|
||||
// 对于 LIKE 匹配(无全文评分),赋予最小评分
|
||||
score = 0.1;
|
||||
}
|
||||
result.setScore(score);
|
||||
|
||||
JSONObject extendData = new JSONObject();
|
||||
extendData.put("visits", doc.getVisits());
|
||||
extendData.put("publishTime", doc.getPublishTime());
|
||||
result.setExtendData(extendData);
|
||||
results.add(result);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 记录错误但继续处理其他类型
|
||||
System.err.println("搜索CMS文章失败: " + e.getMessage());
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建缓存键
|
||||
*/
|
||||
private String buildCacheKey(DocSearchRequest request) {
|
||||
Long deptId = cmsDocService.getDeptIdByZoneId(request.getZoneId());
|
||||
StringBuilder key = new StringBuilder(request.getKeyword());
|
||||
key.append(":").append(String.join(",", "Doc"));
|
||||
if (deptId != null) {
|
||||
key.append(":dept=").append(deptId);
|
||||
}
|
||||
return key.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建空响应
|
||||
*/
|
||||
private DocSearchResponse buildEmptyResponse(DocSearchRequest request) {
|
||||
DocSearchResponse response = new DocSearchResponse();
|
||||
response.setTotal(0L);
|
||||
response.setResults(new ArrayList<>());
|
||||
response.setTypeCounts(new HashMap<>());
|
||||
response.setKeyword(request.getKeyword());
|
||||
response.setPageNum(request.getPageNum());
|
||||
response.setPageSize(request.getPageSize());
|
||||
response.setSearchTime(0L);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将文本中的关键词用 <em> 标签包裹(不区分大小写)
|
||||
*/
|
||||
private String highlightKeyword(String text, String keyword) {
|
||||
if (StringUtils.isEmpty(text) || StringUtils.isEmpty(keyword)) {
|
||||
return text;
|
||||
}
|
||||
return text.replaceAll("(?i)(" + Pattern.quote(keyword) + ")", "<em>$1</em>");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.microservices.cms.common.service.impl;
|
||||
package com.microservices.cms.service.impl;
|
||||
|
||||
import com.microservices.cms.common.service.ISysUserService;
|
||||
import com.microservices.common.core.constant.SecurityConstants;
|
||||
|
|
|
|||
|
|
@ -25,4 +25,24 @@ public class CmsConstants {
|
|||
* 是
|
||||
*/
|
||||
public final static String TRUE = "1";
|
||||
|
||||
/**
|
||||
* 专区积分异步队列 Redis Key(与 Zone 模块保持一致)
|
||||
*/
|
||||
public final static String ZONE_POINTS_ASYNC_QUEUE_KEY = "zone:points:async:queue";
|
||||
|
||||
/**
|
||||
* 发布文章积分类型
|
||||
*/
|
||||
public final static String PUBLISH_DOC = "publishDoc";
|
||||
|
||||
/**
|
||||
* 浏览文章积分类型
|
||||
*/
|
||||
public final static String VISITS_DOC = "visitsDoc";
|
||||
|
||||
/**
|
||||
* 精选文章积分类型
|
||||
*/
|
||||
public final static String FEATURED_DOC = "featuredDoc";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,18 @@ import java.util.Map;
|
|||
|
||||
public class CmsGitLinkRequestUrl extends GitLinkRequestUrl {
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
public static String TEAMS_BY_ORGANIZATION_URL(Long orgId) {
|
||||
return String.format("/api/organizations/%s/teams.json", orgId);
|
||||
}
|
||||
|
||||
public static GitLinkRequestUrl DELETE_USER_FROM_ORGANIZATION_TEAM(String orgIdentifier, Long teamId, String username) {
|
||||
return getAdminGitLinkRequestUrl(String.format("/api/organizations/%s/teams/%d/team_users/%s.json", orgIdentifier, teamId, username));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建项目
|
||||
*/
|
||||
|
|
@ -66,7 +78,7 @@ public class CmsGitLinkRequestUrl extends GitLinkRequestUrl {
|
|||
|
||||
public static GitLinkRequestUrl DELETE_BRANCH(CmsProject cmsProject, String branchName) {
|
||||
return getGitLinkRequestUrl(
|
||||
String.format("/api/v1/%s/%s/branches/%s.json", cmsProject.getUsername(), cmsProject.getIdentifier(), branchName)
|
||||
String.format("/api/v1/%s/%s/branches/delete.json?branch_name=%s", cmsProject.getUsername(), cmsProject.getIdentifier(), branchName)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -76,6 +88,12 @@ public class CmsGitLinkRequestUrl extends GitLinkRequestUrl {
|
|||
);
|
||||
}
|
||||
|
||||
public static GitLinkRequestUrl REOPEN_PULL_REQUEST(CmsProject cmsProject, String giteaPrNumber) {
|
||||
return getGitLinkRequestUrl(
|
||||
String.format("/api/v1/%s/%s/pulls/%s/reopen", cmsProject.getUsername(), cmsProject.getIdentifier(), giteaPrNumber)
|
||||
);
|
||||
}
|
||||
|
||||
public static GitLinkRequestUrl GET_PULL_REQUEST_DETAILS(CmsProject cmsProject, String giteaPrNumber) {
|
||||
return getGitLinkRequestUrl(
|
||||
String.format("/api/%s/%s/pulls/%s.json", cmsProject.getUsername(), cmsProject.getIdentifier(), giteaPrNumber)
|
||||
|
|
@ -101,7 +119,7 @@ public class CmsGitLinkRequestUrl extends GitLinkRequestUrl {
|
|||
}
|
||||
|
||||
public static GitLinkRequestUrl ADD_USER_FROM_PROJECT(CmsProject cmsProject) {
|
||||
return getGitLinkRequestUrl(
|
||||
return getAdminGitLinkRequestUrl(
|
||||
String.format("/api/%s/%s/collaborators.json", cmsProject.getUsername(), cmsProject.getIdentifier())
|
||||
);
|
||||
}
|
||||
|
|
@ -148,19 +166,19 @@ public class CmsGitLinkRequestUrl extends GitLinkRequestUrl {
|
|||
);
|
||||
}
|
||||
|
||||
public static GitLinkRequestUrl CREATE_PROTECTED_BRANCHES(String username, String identifier, Map<String,String> params, List<String> pushWhitelistUsernames) throws URISyntaxException {
|
||||
public static GitLinkRequestUrl CREATE_PROTECTED_BRANCHES(String username, String identifier, Map<String, String> params, List<String> pushWhitelistUsernames) throws URISyntaxException {
|
||||
String path = buildUri(String.format("/api/%s/%s/protected_branches.json", username, identifier), params);
|
||||
path = concatenateParametersAfterUrl(path, "push_whitelist_usernames[]", pushWhitelistUsernames);
|
||||
return getGitLinkRequestUrl(path);
|
||||
}
|
||||
|
||||
public static GitLinkRequestUrl UPDATE_PROTECTED_BRANCHES(String username, String identifier, Map<String,String> params, List<String> pushWhitelistUsernames) throws URISyntaxException {
|
||||
public static GitLinkRequestUrl UPDATE_PROTECTED_BRANCHES(String username, String identifier, Map<String, String> params, List<String> pushWhitelistUsernames) throws URISyntaxException {
|
||||
String path = buildUri(String.format("/api/%s/%s/protected_branches/master.json", username, identifier), params);
|
||||
path = concatenateParametersAfterUrl(path, "push_whitelist_usernames[]", pushWhitelistUsernames);
|
||||
return getGitLinkRequestUrl(path);
|
||||
}
|
||||
|
||||
public static GitLinkRequestUrl DELETE_PROTECTED_BRANCHES(String username, String identifier, Map<String,String> params, List<String> pushWhitelistUsernames) throws URISyntaxException {
|
||||
public static GitLinkRequestUrl DELETE_PROTECTED_BRANCHES(String username, String identifier, Map<String, String> params, List<String> pushWhitelistUsernames) throws URISyntaxException {
|
||||
String path = buildUri(String.format("/api/%s/%s/protected_branches/master.json", username, identifier), params);
|
||||
path = concatenateParametersAfterUrl(path, "push_whitelist_usernames[]", pushWhitelistUsernames);
|
||||
return getGitLinkRequestUrl(path);
|
||||
|
|
@ -197,4 +215,12 @@ public class CmsGitLinkRequestUrl extends GitLinkRequestUrl {
|
|||
public static String ZONE_DOC_URL(String gitlinkUrl, String zoneKey, Long docId) {
|
||||
return EscapeUtil.removeExtraSlashOfUrl(String.format("%s/zone/%s/newdetail/%d", gitlinkUrl, zoneKey, docId));
|
||||
}
|
||||
|
||||
public static GitLinkRequestUrl GET_PROJECT_DETAIL(String username, String identifier) {
|
||||
return getAdminGitLinkRequestUrl(String.format("/api/%s/%s/detail.json", username, identifier));
|
||||
}
|
||||
|
||||
public static GitLinkRequestUrl BATCH_COMMIT(String username, String identifier) {
|
||||
return getAdminGitLinkRequestUrl(String.format("/api/v1/%s/%s/contents/batch.json", username, identifier));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
package com.microservices.cms.utils;
|
||||
|
||||
import com.microservices.common.core.exception.ServiceException;
|
||||
import com.microservices.common.core.threadPool.ThreadPoolExecutorWrap;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 线程池工厂
|
||||
*
|
||||
* @author OTTO
|
||||
*/
|
||||
public class CustomExecutorFactory {
|
||||
/**
|
||||
* 创建线程池
|
||||
* 存在并发处理的情况,设置核心线程为2,设置有界队列长度为100,最大线程数为10
|
||||
* 当超出队列已满且达到最大线程数时抛出异常
|
||||
* Ncpu=CPU数量
|
||||
* Ucpu=目标CPU的使用率,0<=Ucpu<=1
|
||||
* W/C=任务等待时间与任务计算时间的比率
|
||||
* Nthreads =Ncpu*Ucpu*(1+W/C)
|
||||
*/
|
||||
public static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutorWrap(
|
||||
5
|
||||
, 10
|
||||
, 10
|
||||
, TimeUnit.SECONDS
|
||||
, new ArrayBlockingQueue<>(100)
|
||||
, Executors.defaultThreadFactory()
|
||||
, (r, executor) -> {
|
||||
throw new ServiceException("目前处理的人太多了,请稍后再试");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@
|
|||
<result property="auditTmpBranchName" column="audit_tmp_branch_name"/>
|
||||
<result property="auditPrNumber" column="audit_pr_number"/>
|
||||
<result property="keywords" column="keywords"/>
|
||||
<result property="remark" column="remark"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectCmsDocVo">
|
||||
|
|
@ -71,7 +72,8 @@
|
|||
audit_type,
|
||||
audit_tmp_branch_name,
|
||||
keywords,
|
||||
audit_pr_number
|
||||
audit_pr_number,
|
||||
remark
|
||||
from cms_doc
|
||||
</sql>
|
||||
|
||||
|
|
@ -103,10 +105,103 @@
|
|||
cd.is_homepage,
|
||||
cd.editor_type,
|
||||
cd.keywords,
|
||||
cd.is_dir
|
||||
cd.is_dir,
|
||||
cd.audit_status,
|
||||
cd.audit_type,
|
||||
cd.audit_tmp_branch_name,
|
||||
cd.audit_pr_number,
|
||||
cd.remark
|
||||
from cms_doc cd
|
||||
</sql>
|
||||
|
||||
<sql id="selectCmsDocWithScoreVo">
|
||||
select cd.id,
|
||||
cd.create_by,
|
||||
cd.create_time,
|
||||
cd.update_by,
|
||||
cd.update_time,
|
||||
cd.name,
|
||||
cd.file_name,
|
||||
cd.file_path,
|
||||
cd.file_sha,
|
||||
cd.file_size,
|
||||
cd.file_url,
|
||||
cd.html_url,
|
||||
cd.visits,
|
||||
cd.sort,
|
||||
cd.publish_time,
|
||||
cd.publish_user,
|
||||
cd.publish_user_image,
|
||||
cd.publish_user_url,
|
||||
cd.publish_user_nickname,
|
||||
cd.summary,
|
||||
cd.head_img,
|
||||
cd.dir_name,
|
||||
cd.dept_id,
|
||||
cd.issue_id,
|
||||
cd.is_homepage,
|
||||
cd.editor_type,
|
||||
cd.keywords,
|
||||
cd.is_dir,
|
||||
cd.audit_status,
|
||||
cd.audit_type,
|
||||
cd.audit_tmp_branch_name,
|
||||
cd.audit_pr_number,
|
||||
-- 计算全文搜索相关度评分
|
||||
MATCH(cd.name, cd.summary) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE) AS search_score,
|
||||
cd.remark
|
||||
from cms_doc cd
|
||||
</sql>
|
||||
|
||||
<sql id="selectCmsDocAliasSetDefaultHeadImgVo">
|
||||
select cd.id,
|
||||
cd.create_by,
|
||||
cd.create_time,
|
||||
cd.update_by,
|
||||
cd.update_time,
|
||||
cd.name,
|
||||
cd.file_name,
|
||||
cd.file_path,
|
||||
cd.file_sha,
|
||||
cd.file_size,
|
||||
cd.file_url,
|
||||
cd.html_url,
|
||||
cd.visits,
|
||||
cd.sort,
|
||||
cd.publish_time,
|
||||
cd.publish_user,
|
||||
cd.publish_user_image,
|
||||
cd.publish_user_url,
|
||||
cd.publish_user_nickname,
|
||||
cd.summary,
|
||||
-- 处理文章和栏目头图为NULL或空字符串的情况
|
||||
CASE
|
||||
-- 文章头图不为空(非NULL且非空字符串)
|
||||
WHEN cd.head_img IS NOT NULL AND TRIM(cd.head_img) != '' THEN cd.head_img
|
||||
-- 栏目存在且头图不为空
|
||||
WHEN dir.head_img IS NOT NULL AND TRIM(dir.head_img) != '' THEN dir.head_img
|
||||
END AS head_img,
|
||||
cd.dir_name,
|
||||
cd.dept_id,
|
||||
cd.issue_id,
|
||||
cd.is_homepage,
|
||||
cd.editor_type,
|
||||
cd.keywords,
|
||||
cd.is_dir,
|
||||
cd.audit_status,
|
||||
cd.audit_type,
|
||||
cd.audit_tmp_branch_name,
|
||||
cd.audit_pr_number,
|
||||
cd.remark
|
||||
from cms_doc cd
|
||||
left join cms_doc dir on cd.dir_name = dir.name and cd.dept_id = dir.dept_id and dir.is_dir=1
|
||||
</sql>
|
||||
|
||||
<select id="selectCmsDocAndDirListByDept" resultMap="CmsDocResult">
|
||||
<include refid="selectCmsDocVo"/>
|
||||
where dept_id=#{deptId}
|
||||
</select>
|
||||
|
||||
<select id="selectCmsDocList" parameterType="com.microservices.cms.doc.domain.CmsDocSearchVo"
|
||||
resultMap="CmsDocResult">
|
||||
<include refid="selectCmsDocVo"/>
|
||||
|
|
@ -122,10 +217,20 @@
|
|||
where id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="selectCmsDocByIds" resultMap="CmsDocResult">
|
||||
<include refid="selectCmsDocVo"/>
|
||||
where id in
|
||||
<foreach collection="ids" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
and audit_status = '1'
|
||||
</select>
|
||||
|
||||
<select id="selectCmsDocByNameAndDeptId" resultMap="CmsDocResult">
|
||||
<include refid="selectCmsDocVo"/>
|
||||
<where>
|
||||
and name = #{name}
|
||||
and audit_status != 0
|
||||
and dept_id = #{deptId}
|
||||
<choose>
|
||||
<when test="dirName != null">
|
||||
|
|
@ -137,6 +242,10 @@
|
|||
</choose>
|
||||
</where>
|
||||
</select>
|
||||
<select id="selectCmsDocByNameAndDeptIdIgnoreDirName" resultMap="CmsDocResult">
|
||||
<include refid="selectCmsDocVo"/>
|
||||
where name = #{name} and dept_id = #{deptId} and is_dir = 0
|
||||
</select>
|
||||
<select id="selectCmsDocDirByNameAndDeptId" resultMap="CmsDocResult">
|
||||
<include refid="selectCmsDocVo"/>
|
||||
where name = #{name} and dept_id = #{deptId} and is_dir = 1
|
||||
|
|
@ -150,13 +259,13 @@
|
|||
where file_path = #{filePath} and dept_id = #{deptId} and is_dir = 0
|
||||
</select>
|
||||
<select id="selectHotCmsDocList" parameterType="com.microservices.cms.doc.domain.CmsDoc" resultMap="CmsDocResult">
|
||||
<include refid="selectCmsDocVo"/>
|
||||
<include refid="selectListWhere"/>
|
||||
<include refid="selectCmsDocAliasSetDefaultHeadImgVo"/>
|
||||
<include refid="selectListWhereAlias"/>
|
||||
ORDER BY
|
||||
visits DESC,
|
||||
sort DESC,
|
||||
update_time DESC,
|
||||
create_time DESC
|
||||
cd.visits DESC,
|
||||
cd.sort DESC,
|
||||
cd.update_time DESC,
|
||||
cd.create_time DESC
|
||||
|
||||
</select>
|
||||
<select id="selectCmsDocCount" resultType="java.lang.Integer">
|
||||
|
|
@ -177,6 +286,24 @@
|
|||
<if test="auditStatus != null and auditStatus != '-1' ">and audit_status = #{auditStatus}</if>
|
||||
<if test="auditStatus == null ">and audit_status = '1'</if>
|
||||
<if test="createBy != null and createBy != '' ">and create_by = #{createBy}</if>
|
||||
<if test="excludeId != null ">and id != #{excludeId}</if>
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<sql id="selectListWhereAlias">
|
||||
<where>
|
||||
<if test="name != null and name != ''">and cd.name like concat('%', #{name}, '%')</if>
|
||||
<if test="keywords != null and keywords != ''">and cd.keywords like concat('%', #{keywords}, '%')</if>
|
||||
<if test="fileName != null and fileName != ''">and cd.file_name like concat('%', #{fileName}, '%')</if>
|
||||
<if test="filePath != null and filePath != ''">and cd.file_path = #{filePath}</if>
|
||||
<if test="publishTime != null ">and cd.publish_time = #{publishTime}</if>
|
||||
<if test="isDir != null ">and cd.is_dir = #{isDir}</if>
|
||||
<if test="dirName != null ">and cd.dir_name = #{dirName}</if>
|
||||
<if test="deptId != null ">and cd.dept_id = #{deptId}</if>
|
||||
<if test="isHomepage != null ">and cd.is_homepage = #{isHomepage}</if>
|
||||
<if test="auditStatus != null and auditStatus != '-1' ">and cd.audit_status = #{auditStatus}</if>
|
||||
<if test="auditStatus == null ">and cd.audit_status = '1'</if>
|
||||
<if test="createBy != null and createBy != '' ">and cd.create_by = #{createBy}</if>
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
|
|
@ -283,6 +410,15 @@
|
|||
from cms_doc
|
||||
where dept_id = #{deptId};
|
||||
</select>
|
||||
<select id="selectCmsDocListSetDefaultHeadImg" parameterType="com.microservices.cms.doc.domain.CmsDocSearchVo"
|
||||
resultMap="CmsDocResult">
|
||||
<include refid="selectCmsDocAliasSetDefaultHeadImgVo"/>
|
||||
<include refid="selectListWhereAlias"/>
|
||||
ORDER BY
|
||||
cd.sort DESC,
|
||||
cd.update_time DESC,
|
||||
cd.create_time DESC
|
||||
</select>
|
||||
|
||||
<insert id="insertCmsDoc" parameterType="com.microservices.cms.doc.domain.CmsDoc" useGeneratedKeys="true"
|
||||
keyProperty="id">
|
||||
|
|
@ -388,6 +524,7 @@
|
|||
<if test="auditTmpBranchName != null">audit_tmp_branch_name = #{auditTmpBranchName},</if>
|
||||
<if test="auditPrNumber != null">audit_pr_number = #{auditPrNumber},</if>
|
||||
<if test="keywords != null">keywords = #{keywords},</if>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
</trim>
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
|
@ -405,6 +542,12 @@
|
|||
and dept_id = #{deptId}
|
||||
and is_dir = #{isDir};
|
||||
</update>
|
||||
<update id="updateCmsDocShaByPathAndDeptId">
|
||||
update cms_doc
|
||||
set file_sha=#{sha}
|
||||
where file_path=#{filePath}
|
||||
and dept_id=#{deptId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteCmsDocById" parameterType="Long">
|
||||
delete
|
||||
|
|
@ -418,4 +561,70 @@
|
|||
#{id}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<!-- 全局搜索 - 全文索引查询(含 LIKE 后备搜索) -->
|
||||
<select id="globalSearch" resultMap="CmsDocResult">
|
||||
<include refid="selectCmsDocAliasVo"/>
|
||||
WHERE
|
||||
cd.is_dir = 0
|
||||
AND cd.audit_status = '1'
|
||||
AND (
|
||||
MATCH(cd.name, cd.summary) AGAINST(#{keyword} IN BOOLEAN MODE)
|
||||
OR cd.name LIKE CONCAT('%', #{keyword}, '%')
|
||||
OR cd.summary LIKE CONCAT('%', #{keyword}, '%')
|
||||
)
|
||||
<if test="deptId != null">
|
||||
AND cd.dept_id = #{deptId}
|
||||
</if>
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN MATCH(cd.name, cd.summary) AGAINST(#{keyword} IN BOOLEAN MODE) > 0 THEN 1
|
||||
ELSE 2
|
||||
END,
|
||||
cd.update_time DESC
|
||||
LIMIT #{offset}, #{pageSize}
|
||||
</select>
|
||||
|
||||
<select id="globalSearchCount" resultType="java.lang.Long">
|
||||
SELECT COUNT(*)
|
||||
FROM cms_doc cd
|
||||
WHERE
|
||||
cd.is_dir = 0
|
||||
AND cd.audit_status = '1'
|
||||
AND (
|
||||
MATCH(cd.name, cd.summary) AGAINST(#{keyword} IN BOOLEAN MODE)
|
||||
OR cd.name LIKE CONCAT('%', #{keyword}, '%')
|
||||
OR cd.summary LIKE CONCAT('%', #{keyword}, '%')
|
||||
)
|
||||
<if test="deptId != null">
|
||||
AND cd.dept_id = #{deptId}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!-- ResultMap for CmsDocWithScore -->
|
||||
<resultMap type="com.microservices.cms.doc.domain.CmsDocWithScore" id="CmsDocWithScoreResult" extends="CmsDocResult">
|
||||
<result property="searchScore" column="search_score"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 全局搜索 - 带评分的全文索引查询 -->
|
||||
<select id="globalSearchWithScore" resultMap="CmsDocWithScoreResult">
|
||||
<include refid="selectCmsDocWithScoreVo"/>
|
||||
WHERE
|
||||
cd.is_dir = 0
|
||||
AND cd.audit_status = '1'
|
||||
AND (
|
||||
MATCH(cd.name, cd.summary) AGAINST(#{keyword} IN BOOLEAN MODE)
|
||||
OR cd.name LIKE CONCAT('%', #{keyword}, '%')
|
||||
OR cd.summary LIKE CONCAT('%', #{keyword}, '%')
|
||||
)
|
||||
<if test="deptId != null">
|
||||
AND cd.dept_id = #{deptId}
|
||||
</if>
|
||||
ORDER BY
|
||||
-- 主要排序:按相关度评分降序
|
||||
search_score DESC,
|
||||
-- 次要排序:更新时间降序
|
||||
cd.update_time DESC
|
||||
LIMIT #{offset}, #{pageSize}
|
||||
</select>
|
||||
</mapper>
|
||||
|
|
@ -159,4 +159,38 @@
|
|||
item="id" collection="array" open="(" separator="," close=")">#{id}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<!-- 全局搜索 - 全文索引查询 -->
|
||||
<select id="globalSearch" resultMap="CmsProjectResult">
|
||||
SELECT id,
|
||||
name,
|
||||
description,
|
||||
user_id,
|
||||
dept_id,
|
||||
identifier,
|
||||
username,
|
||||
create_by,
|
||||
create_time,
|
||||
update_time
|
||||
FROM cms_project cp
|
||||
WHERE
|
||||
MATCH(cp.name) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE)
|
||||
<if test="deptId != null">
|
||||
AND cp.dept_id = #{deptId}
|
||||
</if>
|
||||
ORDER BY
|
||||
MATCH(cp.name) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE) DESC,
|
||||
cp.update_time DESC
|
||||
LIMIT #{offset}, #{pageSize}
|
||||
</select>
|
||||
|
||||
<select id="globalSearchCount" resultType="java.lang.Long">
|
||||
SELECT COUNT(*)
|
||||
FROM cms_project cp
|
||||
WHERE
|
||||
MATCH(cp.name) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE)
|
||||
<if test="deptId != null">
|
||||
AND cp.dept_id = #{deptId}
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
||||
|
|
@ -87,18 +87,10 @@
|
|||
<groupId>com.microservices</groupId>
|
||||
<artifactId>microservices-common-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microservices</groupId>
|
||||
<artifactId>microservices-common-swagger</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microservices</groupId>
|
||||
<artifactId>microservices-common-log</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microservices</groupId>
|
||||
<artifactId>microservices-common-core</artifactId>
|
||||
|
|
@ -130,6 +122,11 @@
|
|||
<artifactId>similarity</artifactId>
|
||||
<version>1.1.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.hankcs</groupId>
|
||||
<artifactId>hanlp</artifactId>
|
||||
<version>portable-1.8.4</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
|
|
|
|||
|
|
@ -59,8 +59,8 @@ public class AchievementsServiceImpl implements IAchievementsService {
|
|||
@Autowired
|
||||
private IBehaviorImageService behaviorImageService;
|
||||
|
||||
@Value("${markerSpaceUrl}")
|
||||
public String markerSpaceUrl;
|
||||
@Value("${makerSpaceUrl}")
|
||||
public String makerSpaceUrl;
|
||||
|
||||
@Value("${http.gitLinkUrl}")
|
||||
public String gitLinkUrl;
|
||||
|
|
@ -217,7 +217,7 @@ public class AchievementsServiceImpl implements IAchievementsService {
|
|||
}
|
||||
|
||||
if (Objects.equals(source, "2")) {
|
||||
concatUrl(list, markerSpaceUrl);
|
||||
concatUrl(list, makerSpaceUrl);
|
||||
}
|
||||
|
||||
if (Objects.equals(source, "4")) {
|
||||
|
|
|
|||
|
|
@ -377,6 +377,9 @@ public class TalentReferralService {
|
|||
*/
|
||||
public Object competitionExpertTalentReferral(Long competitionId) {
|
||||
TaskVo task = talentReferralMapper.selectCompetitionById(competitionId);
|
||||
if (task == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<KeyValueVo> categories = new ArrayList<>();
|
||||
List<TaskVo> completedTasks = talentReferralMapper.selectCompetitionIdByStatus();
|
||||
List<ExpertVo> allExperts = talentReferralMapper.selectExpertsBy(new ExpertVo());
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ import static com.microservices.dms.constant.TaskConstant.MARKER_SPACE_DOWNLOAD_
|
|||
public class TaskResourceLibraryService {
|
||||
private final TaskResourceLibraryMapper taskResourceLibraryMapper;
|
||||
|
||||
@Value("${markerSpaceUrl}")
|
||||
public String markerSpaceUrl;
|
||||
@Value("${makerSpaceUrl}")
|
||||
public String makerSpaceUrl;
|
||||
|
||||
public int addClick(Clicker click) {
|
||||
click.setCreatedAt(DateUtils.getNowDate());
|
||||
|
|
@ -104,7 +104,7 @@ public class TaskResourceLibraryService {
|
|||
List<KeyValVo<String, String>> attachments = taskResourceLibraryMapper.getAttachments(l.getPaperId());
|
||||
for (KeyValVo<String, String> a : attachments) {
|
||||
Object v = a.getV();
|
||||
a.setV(String.format(MARKER_SPACE_DOWNLOAD_API_BY_ID, markerSpaceUrl, v));
|
||||
a.setV(String.format(MARKER_SPACE_DOWNLOAD_API_BY_ID, makerSpaceUrl, v));
|
||||
}
|
||||
l.setAttachmentList(attachments);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ public class ExpertResourceLibraryServiceImpl implements IExpertResourceLibraryS
|
|||
@Autowired
|
||||
private ExpertResourceLibraryMapper expertResourceLibraryMapper;
|
||||
|
||||
@Value("${markerSpaceUrl}")
|
||||
public String markerSpaceUrl;
|
||||
@Value("${makerSpaceUrl}")
|
||||
public String makerSpaceUrl;
|
||||
/**
|
||||
* 查询专家资源库
|
||||
*
|
||||
|
|
@ -77,7 +77,7 @@ public class ExpertResourceLibraryServiceImpl implements IExpertResourceLibraryS
|
|||
private void buildFullUrl(Map<String, Object> map) {
|
||||
if (map != null && map.containsKey("id") && map.get("id") != null) {
|
||||
String id = map.get("id").toString();
|
||||
map.put("url", String.format(MARKER_SPACE_DOWNLOAD_API_BY_ID, markerSpaceUrl, id));
|
||||
map.put("url", String.format(MARKER_SPACE_DOWNLOAD_API_BY_ID, makerSpaceUrl, id));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -140,24 +140,55 @@
|
|||
</select>
|
||||
|
||||
<select id="selectActivityUserLibraryListAll" resultType="com.microservices.dms.behaviorImage.domain.ActivityUserLibrary" parameterType="com.microservices.dms.behaviorImage.domain.ActivityUserLibrary">
|
||||
select res.userId,res.loginName,res.userName,res.isExpert,res.isAuth,res.relatedCompetition,res.relatedTask,res.p1+res.p2+res.p3 as "relatedProject" from (
|
||||
select u.id as "userId",u.login as "loginName",u.nickname as "userName",u.is_expert as "isExpert",u.authentication as "isAuth",
|
||||
(select count(1) as "value" from tasks where is_delete='0' and status between 3 and 8 and user_id =u.id) as "relatedTask",
|
||||
(select count(1) from competition_infos ci inner join competition_users cu on ci.id = cu.competition_info_id where cu.user_id =u.id) as "relatedCompetition",
|
||||
(SELECT count(1) as "pCount" FROM `projects` WHERE id != 0 AND user_id =u.id) as p1,
|
||||
( SELECT count(1) as "pCount"
|
||||
FROM `projects`
|
||||
INNER JOIN `members` ON `members`.`project_id` = `projects`.`id`
|
||||
WHERE `projects`.`id` != 0 AND `projects`.`user_id` !=u.id AND `members`.`user_id` =u.id) as p2,
|
||||
(SELECT count(1) as "pCount"
|
||||
FROM `projects`
|
||||
INNER JOIN `team_projects` ON `team_projects`.`project_id` = `projects`.`id`
|
||||
INNER JOIN `teams` ON `teams`.`id` = `team_projects`.`team_id`
|
||||
INNER JOIN `team_users` ON `team_users`.`team_id` = `teams`.`id`
|
||||
WHERE `projects`.`id` != 0 AND `team_users`.`user_id` =u.id) as p3
|
||||
from users u) res
|
||||
where (res.relatedCompetition >0 or res.relatedTask >0 or res.p1+res.p2+res.p3>0)
|
||||
<if test="userName != null and userName != ''"> and res.userName like concat('%', #{userName}, '%')</if>
|
||||
SELECT
|
||||
u.id AS userId,
|
||||
u.login AS loginName,
|
||||
u.nickname AS userName,
|
||||
u.is_expert AS isExpert,
|
||||
u.authentication AS isAuth,
|
||||
IFNULL(t.relatedTask, 0) AS relatedTask,
|
||||
IFNULL(c.relatedCompetition, 0) AS relatedCompetition,
|
||||
IFNULL(p1.p1,0) + IFNULL(p2.p2,0) + IFNULL(p3.p3,0) AS relatedProject
|
||||
FROM users u
|
||||
LEFT JOIN (
|
||||
SELECT user_id, COUNT(*) AS relatedTask
|
||||
FROM tasks
|
||||
WHERE is_delete = '0' AND status BETWEEN 3 AND 8
|
||||
GROUP BY user_id
|
||||
) t ON u.id = t.user_id
|
||||
LEFT JOIN (
|
||||
SELECT cu.user_id, COUNT(*) AS relatedCompetition
|
||||
FROM competition_users cu
|
||||
INNER JOIN competition_infos ci ON ci.id = cu.competition_info_id
|
||||
GROUP BY cu.user_id
|
||||
) c ON u.id = c.user_id
|
||||
LEFT JOIN (
|
||||
SELECT user_id, COUNT(*) AS p1
|
||||
FROM projects
|
||||
GROUP BY user_id
|
||||
) p1 ON u.id = p1.user_id
|
||||
LEFT JOIN (
|
||||
SELECT m.user_id, COUNT(*) AS p2
|
||||
FROM members m
|
||||
INNER JOIN projects p ON m.project_id = p.id AND p.user_id != m.user_id
|
||||
GROUP BY m.user_id
|
||||
) p2 ON u.id = p2.user_id
|
||||
LEFT JOIN (
|
||||
SELECT tu.user_id, COUNT(DISTINCT tp.project_id) AS p3
|
||||
FROM team_users tu
|
||||
INNER JOIN teams tm ON tu.team_id = tm.id
|
||||
INNER JOIN team_projects tp ON tm.id = tp.team_id
|
||||
INNER JOIN projects p ON tp.project_id = p.id
|
||||
GROUP BY tu.user_id
|
||||
) p3 ON u.id = p3.user_id
|
||||
WHERE (
|
||||
IFNULL(c.relatedCompetition, 0) > 0
|
||||
OR IFNULL(t.relatedTask, 0) > 0
|
||||
OR (IFNULL(p1.p1,0) + IFNULL(p2.p2,0) + IFNULL(p3.p3,0)) > 0
|
||||
)
|
||||
<if test="userName != null and userName != ''">
|
||||
AND u.nickname like concat('%', #{userName}, '%')
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<select id="getrelatedProjectCount" resultType="long">
|
||||
|
|
@ -308,23 +339,47 @@
|
|||
</select>
|
||||
|
||||
<select id="getActivityStatistic" resultType="com.microservices.dms.behaviorImage.domain.ActivityUserTotal">
|
||||
select count(1) as "totalUser",sum(r.isExpert) as "totalExpert",sum(r.enterprise_certification+r.authentication) as "authTotal" from (
|
||||
select res.userId,res.loginName,res.userName,res.isExpert,res.authentication,res.enterprise_certification,res.relatedCompetition,res.relatedTask,res.p1+res.p2+res.p3 as "relatedProject" from (
|
||||
select u.id as "userId",u.login as "loginName",u.nickname as "userName",u.is_expert as "isExpert",u.authentication,u.enterprise_certification,
|
||||
(select count(1) as "value" from tasks where is_delete='0' and status between 3 and 8 and user_id =u.id) as "relatedTask",
|
||||
(select count(1) from competition_infos ci inner join competition_users cu on ci.id = cu.competition_info_id where cu.user_id =u.id) as "relatedCompetition",
|
||||
(SELECT count(1) as "pCount" FROM `projects` WHERE id != 0 AND user_id =u.id) as p1,
|
||||
( SELECT count(1) as "pCount"
|
||||
FROM `projects` INNER JOIN `members` ON `members`.`project_id` = `projects`.`id`
|
||||
WHERE `projects`.`id` != 0 AND `projects`.`user_id` !=u.id AND `members`.`user_id` =u.id) as p2,
|
||||
(SELECT count(1) as "pCount"
|
||||
FROM `projects`
|
||||
INNER JOIN `team_projects` ON `team_projects`.`project_id` = `projects`.`id`
|
||||
INNER JOIN `teams` ON `teams`.`id` = `team_projects`.`team_id`
|
||||
INNER JOIN `team_users` ON `team_users`.`team_id` = `teams`.`id`
|
||||
WHERE `projects`.`id` != 0 AND `team_users`.`user_id` =u.id) as p3
|
||||
from users u) res
|
||||
where res.relatedCompetition >0 or res.relatedTask >0 or res.p1+res.p2+res.p3>0 )r
|
||||
SELECT
|
||||
COUNT(*) AS totalUser,
|
||||
SUM(u.is_expert) AS totalExpert,
|
||||
SUM(u.authentication + u.enterprise_certification) AS authTotal
|
||||
FROM users u
|
||||
LEFT JOIN (
|
||||
SELECT user_id, COUNT(*) AS relatedTask
|
||||
FROM tasks
|
||||
WHERE is_delete = '0' AND status BETWEEN 3 AND 8
|
||||
GROUP BY user_id
|
||||
) t ON u.id = t.user_id
|
||||
LEFT JOIN (
|
||||
SELECT cu.user_id, COUNT(*) AS relatedCompetition
|
||||
FROM competition_users cu
|
||||
INNER JOIN competition_infos ci ON ci.id = cu.competition_info_id
|
||||
GROUP BY cu.user_id
|
||||
) c ON u.id = c.user_id
|
||||
LEFT JOIN (
|
||||
SELECT user_id, COUNT(*) AS p1
|
||||
FROM projects
|
||||
GROUP BY user_id
|
||||
) p1 ON u.id = p1.user_id
|
||||
LEFT JOIN (
|
||||
SELECT m.user_id, COUNT(*) AS p2
|
||||
FROM members m
|
||||
INNER JOIN projects p ON m.project_id = p.id AND p.user_id != m.user_id
|
||||
GROUP BY m.user_id
|
||||
) p2 ON u.id = p2.user_id
|
||||
LEFT JOIN (
|
||||
SELECT tu.user_id, COUNT(DISTINCT tp.project_id) AS p3
|
||||
FROM team_users tu
|
||||
INNER JOIN teams tm ON tu.team_id = tm.id
|
||||
INNER JOIN team_projects tp ON tm.id = tp.team_id
|
||||
INNER JOIN projects p ON tp.project_id = p.id
|
||||
GROUP BY tu.user_id
|
||||
) p3 ON u.id = p3.user_id
|
||||
WHERE (
|
||||
IFNULL(c.relatedCompetition, 0) > 0
|
||||
OR IFNULL(t.relatedTask, 0) > 0
|
||||
OR (IFNULL(p1.p1, 0) + IFNULL(p2.p2, 0) + IFNULL(p3.p3, 0)) > 0
|
||||
)
|
||||
</select>
|
||||
|
||||
<select id="getUserBehaviorSum" resultType="com.microservices.dms.behaviorImage.domain.UserTypeTotalVo">
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.microservices.common.security.annotation.EnableRyFeignClients;
|
|||
import com.microservices.common.swagger.annotation.EnableCustomSwagger2;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
/**
|
||||
* 文件服务
|
||||
|
|
@ -15,6 +16,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||
@EnableCustomConfig
|
||||
@SpringBootApplication
|
||||
@EnableRyFeignClients
|
||||
@EnableScheduling
|
||||
public class MicroservicesFileApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MicroservicesFileApplication.class, args);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,176 @@
|
|||
package com.microservices.file.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.file.domain.vo.CheckChunkVo;
|
||||
import com.microservices.file.domain.vo.CheckInstantUploadVo;
|
||||
import com.microservices.file.domain.vo.InitChunkedUploadVo;
|
||||
import com.microservices.file.domain.vo.UploadProgressVo;
|
||||
import com.microservices.file.domain.vo.input.CheckInstantUploadInputVo;
|
||||
import com.microservices.file.domain.vo.input.InitChunkedUploadInputVo;
|
||||
import com.microservices.file.domain.vo.response.*;
|
||||
import com.microservices.file.service.IChunkedUploadService;
|
||||
import com.microservices.system.api.domain.SysFileInfo;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiImplicitParams;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 分片上传控制器
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/chunked")
|
||||
@Api(tags = "分片上传接口")
|
||||
public class ChunkedUploadController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private IChunkedUploadService chunkedUploadService;
|
||||
|
||||
/**
|
||||
* 初始化分片上传
|
||||
* <p>
|
||||
* 功能:
|
||||
* 1. 创建上传会话
|
||||
* 2. 根据文件大小判断哈希类型(FULL/SAMPLE)
|
||||
* 3. 返回采样点信息(SAMPLE类型)
|
||||
*
|
||||
* @param input 初始化上传请求参数
|
||||
* @return 初始化结果,包含 uploadId、totalChunks、chunkSize、hashType、sampleChunks
|
||||
*/
|
||||
@PostMapping("/init")
|
||||
@ApiOperation("初始化分片上传")
|
||||
public GenericsAjaxResult<InitChunkedUploadResponseVo> initChunkedUpload(@Validated @RequestBody InitChunkedUploadInputVo input) {
|
||||
InitChunkedUploadVo result = chunkedUploadService.initChunkedUpload(input);
|
||||
return genericsSuccess(InitChunkedUploadResponseVo.from(result));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查秒传(简化版)
|
||||
* <p>
|
||||
* 功能:
|
||||
* 1. 校验上传会话和时间窗口
|
||||
* 2. 查询是否存在相同的 file_hash 且状态为已完成
|
||||
* 3. 秒传成功:删除当前会话,返回已存在文件信息
|
||||
* 4. 不能秒传:返回继续上传
|
||||
*
|
||||
* @param input 检查秒传请求参数
|
||||
* @return 检查结果
|
||||
*/
|
||||
@PostMapping("/checkInstantUpload")
|
||||
@ApiOperation("检查秒传")
|
||||
public GenericsAjaxResult<CheckInstantUploadResponseVo> checkInstantUpload(@Validated @RequestBody CheckInstantUploadInputVo input) {
|
||||
CheckInstantUploadVo result = chunkedUploadService.checkInstantUpload(input.getUploadId(), input.getFileSignature());
|
||||
return genericsSuccess(CheckInstantUploadResponseVo.from(result));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查分片是否已上传(断点续传)
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @param chunkNumber 分片序号
|
||||
* @return 分片检查结果
|
||||
*/
|
||||
@GetMapping("/check/{uploadId}/{chunkNumber}")
|
||||
@ApiOperation("检查分片是否已上传")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "uploadId", value = "上传ID", paramType = "path", dataType = "String", required = true),
|
||||
@ApiImplicitParam(name = "chunkNumber", value = "分片序号", paramType = "path", dataType = "Integer", required = true)
|
||||
})
|
||||
public GenericsAjaxResult<CheckChunkResponseVo> checkChunk(@PathVariable("uploadId") String uploadId, @PathVariable("chunkNumber") Integer chunkNumber) {
|
||||
CheckChunkVo result = chunkedUploadService.checkChunk(uploadId, chunkNumber);
|
||||
return genericsSuccess(CheckChunkResponseVo.from(result));
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传分片
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @param chunkNumber 分片序号
|
||||
* @param chunk 分片文件
|
||||
* @return 上传结果
|
||||
*/
|
||||
@PostMapping("/upload/{uploadId}/{chunkNumber}")
|
||||
@ApiOperation("上传分片")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "uploadId", value = "上传ID", paramType = "path", dataType = "String", required = true),
|
||||
@ApiImplicitParam(name = "chunkNumber", value = "分片序号", paramType = "path", dataType = "Integer", required = true),
|
||||
@ApiImplicitParam(name = "chunk", value = "分片文件", paramType = "form", dataType = "_file", required = true)
|
||||
})
|
||||
public GenericsAjaxResult<UploadChunkResponseVo> uploadChunk(
|
||||
@PathVariable("uploadId") String uploadId,
|
||||
@PathVariable("chunkNumber") Integer chunkNumber,
|
||||
@RequestPart("chunk") MultipartFile chunk) {
|
||||
chunkedUploadService.uploadChunk(uploadId, chunkNumber, chunk);
|
||||
return genericsSuccess(UploadChunkResponseVo.success(chunkNumber));
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并分片(异步)
|
||||
* <p>
|
||||
* 功能:
|
||||
* 1. 提交合并任务,立即返回
|
||||
* 2. 客户端通过轮询进度接口获取合并结果
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @return 合并任务提交结果
|
||||
*/
|
||||
@PostMapping("/merge/{uploadId}")
|
||||
@ApiOperation("合并分片")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "uploadId", value = "上传ID", paramType = "path", dataType = "String", required = true)
|
||||
})
|
||||
public GenericsAjaxResult<Map<String, String>> mergeChunks(@PathVariable("uploadId") String uploadId) {
|
||||
chunkedUploadService.mergeChunks(uploadId);
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("uploadId", uploadId);
|
||||
result.put("message", "合并任务已提交,请通过进度接口查询结果");
|
||||
return genericsSuccess(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消上传
|
||||
* <p>
|
||||
* 功能:
|
||||
* 1. 删除上传会话记录
|
||||
* 2. 清理已上传的分片文件
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @return 取消结果
|
||||
*/
|
||||
@DeleteMapping("/cancel/{uploadId}")
|
||||
@ApiOperation("取消上传")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "uploadId", value = "上传ID", paramType = "path", dataType = "String", required = true)
|
||||
})
|
||||
public GenericsAjaxResult<CancelUploadResponseVo> cancelUpload(@PathVariable("uploadId") String uploadId) {
|
||||
boolean result = chunkedUploadService.cancelUpload(uploadId);
|
||||
return genericsSuccess(result ? CancelUploadResponseVo.success() : CancelUploadResponseVo.failed("取消上传失败"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上传进度
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @return 上传进度
|
||||
*/
|
||||
@GetMapping("/progress/{uploadId}")
|
||||
@ApiOperation("获取上传进度")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "uploadId", value = "上传ID", paramType = "path", dataType = "String", required = true)
|
||||
})
|
||||
public GenericsAjaxResult<UploadProgressResponseVo> getProgress(@PathVariable("uploadId") String uploadId) {
|
||||
UploadProgressVo progress = chunkedUploadService.getProgress(uploadId);
|
||||
return genericsSuccess(UploadProgressResponseVo.from(progress));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.microservices.file.controller;
|
||||
|
||||
import com.microservices.common.core.domain.R;
|
||||
import com.microservices.common.core.exception.ServiceException;
|
||||
import com.microservices.common.core.web.domain.AjaxResult;
|
||||
import com.microservices.file.service.ISysFileInfoService;
|
||||
import com.microservices.system.api.domain.SysFileInfo;
|
||||
|
|
@ -120,19 +121,20 @@ public class OpenController {
|
|||
@ApiImplicitParam(name = "hierarchy", value = "层级结构(eg: pms项目管理中{enterpriseIdentifier}/products,{enterpriseIdentifier}/projects,{enterpriseIdentifier}/docs/{projectId})", paramType = "query", dataType = "String")
|
||||
})
|
||||
public AjaxResult uploadFile(@RequestPart("file") MultipartFile file, String type, String hierarchy) {
|
||||
SysFileInfo sysFileInfo = sysFileInfoService.upload(file, type, hierarchy);
|
||||
AjaxResult ajax = AjaxResult.success();
|
||||
ajax.put("url", sysFileInfo.getDownloadUrl());
|
||||
if (sysFileInfo.getFileIdentifier() != null) {
|
||||
ajax.put("fileIdentifier", sysFileInfo.getFileIdentifier());
|
||||
}
|
||||
ajax.put("fileName", sysFileInfo.getFilePath());
|
||||
if (sysFileInfo.getFileId() != null) {
|
||||
ajax.put("fileId", sysFileInfo.getFileId());
|
||||
}
|
||||
ajax.put("fileOriginName", sysFileInfo.getFileOriginName());
|
||||
ajax.put("fileSuffix", sysFileInfo.getFileSuffix());
|
||||
ajax.put("fileSize", sysFileInfo.getFileSizeInfo());
|
||||
return ajax;
|
||||
throw new ServiceException("该接口已弃用!");
|
||||
// SysFileInfo sysFileInfo = sysFileInfoService.upload(file, type, hierarchy);
|
||||
// AjaxResult ajax = AjaxResult.success();
|
||||
// ajax.put("url", sysFileInfo.getDownloadUrl());
|
||||
// if (sysFileInfo.getFileIdentifier() != null) {
|
||||
// ajax.put("fileIdentifier", sysFileInfo.getFileIdentifier());
|
||||
// }
|
||||
// ajax.put("fileName", sysFileInfo.getFilePath());
|
||||
// if (sysFileInfo.getFileId() != null) {
|
||||
// ajax.put("fileId", sysFileInfo.getFileId());
|
||||
// }
|
||||
// ajax.put("fileOriginName", sysFileInfo.getFileOriginName());
|
||||
// ajax.put("fileSuffix", sysFileInfo.getFileSuffix());
|
||||
// ajax.put("fileSize", sysFileInfo.getFileSizeInfo());
|
||||
// return ajax;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
package com.microservices.file.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.util.Date;
|
||||
|
||||
/**
|
||||
* 文件分片记录对象 sys_file_upload_chunk
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ApiModel("文件分片记录")
|
||||
public class FileUploadChunk extends BaseEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 分片ID
|
||||
*/
|
||||
@ApiModelProperty(value = "分片ID")
|
||||
private Long chunkId;
|
||||
|
||||
/**
|
||||
* 上传ID
|
||||
*/
|
||||
@ApiModelProperty(value = "上传ID")
|
||||
private String uploadId;
|
||||
|
||||
/**
|
||||
* 分片序号(从0开始)
|
||||
*/
|
||||
@ApiModelProperty(value = "分片序号(从0开始)")
|
||||
private Integer chunkNumber;
|
||||
|
||||
/**
|
||||
* 分片大小(字节)
|
||||
*/
|
||||
@ApiModelProperty(value = "分片大小(字节)")
|
||||
private Long chunkSize;
|
||||
|
||||
/**
|
||||
* 分片文件存储路径
|
||||
*/
|
||||
@ApiModelProperty(value = "分片文件存储路径")
|
||||
private String chunkPath;
|
||||
|
||||
/**
|
||||
* 状态:PENDING-待上传,UPLOADED-已上传,MERGED-已合并
|
||||
*/
|
||||
@ApiModelProperty(value = "状态:PENDING-待上传,UPLOADED-已上传,MERGED-已合并")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 上传时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@ApiModelProperty(value = "上传时间")
|
||||
private Date uploadTime;
|
||||
|
||||
/**
|
||||
* 分片状态枚举
|
||||
*/
|
||||
public enum Status {
|
||||
PENDING("PENDING", "待上传"),
|
||||
UPLOADED("UPLOADED", "已上传"),
|
||||
MERGED("MERGED", "已合并");
|
||||
|
||||
private final String code;
|
||||
private final String desc;
|
||||
|
||||
Status(String code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
package com.microservices.file.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.util.Date;
|
||||
|
||||
/**
|
||||
* 文件分片上传会话对象 sys_file_upload_session
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ApiModel("文件分片上传会话")
|
||||
public class FileUploadSession extends BaseEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 会话ID
|
||||
*/
|
||||
@ApiModelProperty(value = "会话ID")
|
||||
private Long sessionId;
|
||||
|
||||
/**
|
||||
* 上传ID(唯一标识)
|
||||
*/
|
||||
@ApiModelProperty(value = "上传ID(唯一标识)")
|
||||
private String uploadId;
|
||||
|
||||
/**
|
||||
* 文件ID(关联sys_file_info表)
|
||||
*/
|
||||
@ApiModelProperty(value = "文件ID(关联sys_file_info表)")
|
||||
private Long fileId;
|
||||
|
||||
/**
|
||||
* 原始文件名
|
||||
*/
|
||||
@ApiModelProperty(value = "原始文件名")
|
||||
private String fileName;
|
||||
|
||||
/**
|
||||
* 文件总大小(字节)
|
||||
*/
|
||||
@ApiModelProperty(value = "文件总大小(字节)")
|
||||
private Long fileSize;
|
||||
|
||||
/**
|
||||
* 文件签名(用于秒传校验)
|
||||
* - FULL 类型:完整文件的 MD5 值
|
||||
* - SAMPLE 类型:采样点哈希值
|
||||
*/
|
||||
@ApiModelProperty(value = "文件签名(用于秒传校验,可能是完整MD5或采样点哈希)")
|
||||
private String fileSignature;
|
||||
|
||||
/**
|
||||
* 分片大小(字节)
|
||||
*/
|
||||
@ApiModelProperty(value = "分片大小(字节)")
|
||||
private Long chunkSize;
|
||||
|
||||
/**
|
||||
* 总分片数
|
||||
*/
|
||||
@ApiModelProperty(value = "总分片数")
|
||||
private Integer totalChunks;
|
||||
|
||||
/**
|
||||
* 已上传分片数
|
||||
*/
|
||||
@ApiModelProperty(value = "已上传分片数")
|
||||
private Integer uploadedChunks;
|
||||
|
||||
/**
|
||||
* 上传类型(cms/dms/resource/pms等)
|
||||
*/
|
||||
@ApiModelProperty(value = "上传类型(cms/dms/resource/pms等)")
|
||||
private String uploadType;
|
||||
|
||||
/**
|
||||
* 层级路径
|
||||
*/
|
||||
@ApiModelProperty(value = "层级路径")
|
||||
private String hierarchy;
|
||||
|
||||
/**
|
||||
* 状态:INIT-初始化,UPLOADING-上传中,MERGING-合并中,COMPLETED-已完成,FAILED-失败,MERGE_FAILED-合并失败
|
||||
*/
|
||||
@ApiModelProperty(value = "状态:INIT-初始化,UPLOADING-上传中,MERGING-合并中,COMPLETED-已完成,FAILED-失败,MERGE_FAILED-合并失败")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 临时文件存储路径
|
||||
*/
|
||||
@ApiModelProperty(value = "临时文件存储路径")
|
||||
private String tempFilePath;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
@ApiModelProperty(value = "错误信息")
|
||||
private String errorMessage;
|
||||
|
||||
/**
|
||||
* 过期时间(24小时)
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@ApiModelProperty(value = "过期时间(24小时)")
|
||||
private Date expireTime;
|
||||
|
||||
/**
|
||||
* 哈希类型:FULL=完整文件MD5, SAMPLE=采样点哈希
|
||||
*/
|
||||
@ApiModelProperty(value = "哈希类型:FULL=完整文件MD5, SAMPLE=采样点哈希")
|
||||
private String hashType;
|
||||
|
||||
/**
|
||||
* 采样点分片序号列表(逗号分隔,仅SAMPLE类型)
|
||||
*/
|
||||
@ApiModelProperty(value = "采样点分片序号列表")
|
||||
private String sampleChunks;
|
||||
|
||||
/**
|
||||
* 上传状态枚举
|
||||
*/
|
||||
public enum Status {
|
||||
INIT("INIT", "初始化"),
|
||||
UPLOADING("UPLOADING", "上传中"),
|
||||
MERGING("MERGING", "合并中"),
|
||||
COMPLETED("COMPLETED", "已完成"),
|
||||
FAILED("FAILED", "失败"),
|
||||
MERGE_FAILED("MERGE_FAILED", "合并失败");
|
||||
|
||||
private final String code;
|
||||
private final String desc;
|
||||
|
||||
Status(String code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.microservices.file.domain.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 分片检查返回VO
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("分片检查返回")
|
||||
public class CheckChunkVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 分片是否存在
|
||||
*/
|
||||
@ApiModelProperty(value = "分片是否存在")
|
||||
private Boolean exists;
|
||||
|
||||
/**
|
||||
* 分片序号
|
||||
*/
|
||||
@ApiModelProperty(value = "分片序号")
|
||||
private Integer chunkNumber;
|
||||
|
||||
/**
|
||||
* 分片大小(字节)
|
||||
*/
|
||||
@ApiModelProperty(value = "分片大小(字节)")
|
||||
private Long chunkSize;
|
||||
|
||||
public static CheckChunkVo exists(Integer chunkNumber, Long chunkSize) {
|
||||
CheckChunkVo vo = new CheckChunkVo();
|
||||
vo.setExists(true);
|
||||
vo.setChunkNumber(chunkNumber);
|
||||
vo.setChunkSize(chunkSize);
|
||||
return vo;
|
||||
}
|
||||
|
||||
public static CheckChunkVo notExists(Integer chunkNumber) {
|
||||
CheckChunkVo vo = new CheckChunkVo();
|
||||
vo.setExists(false);
|
||||
vo.setChunkNumber(chunkNumber);
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package com.microservices.file.domain.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@ApiModel("检查秒传返回")
|
||||
public class CheckInstantUploadVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("是否可以秒传")
|
||||
private Boolean canInstantUpload = false;
|
||||
|
||||
@ApiModelProperty("是否可以断点续传")
|
||||
private Boolean canResumingUpload = false;
|
||||
|
||||
@ApiModelProperty("已存在文件ID(秒传时)")
|
||||
private Long existingFileId;
|
||||
|
||||
@ApiModelProperty("已存在文件名(秒传时)")
|
||||
private String existingFileName;
|
||||
|
||||
@ApiModelProperty("已存在文件路径(秒传时)")
|
||||
private String existingFilePath;
|
||||
|
||||
@ApiModelProperty("已存在文件下载地址(秒传时)")
|
||||
private String existingFileUrl;
|
||||
|
||||
@ApiModelProperty("已存在文件大小(秒传时)")
|
||||
private String existingFileSize;
|
||||
|
||||
@ApiModelProperty("已存在未上传完成的上传Id(断点续传时)")
|
||||
private String resumingUploadId;
|
||||
|
||||
@ApiModelProperty("已上传的分片Id列表(断点续传时)-当状态为合并中时该值为null,请直接调用进度接口查看进度")
|
||||
private List<Integer> completedUploadChunkedNumberList;
|
||||
|
||||
@ApiModelProperty("已存在未上传完成的上传状态(断点续传时)")
|
||||
private String resumingUploadStatus;
|
||||
|
||||
@ApiModelProperty("消息")
|
||||
private String message;
|
||||
|
||||
public static CheckInstantUploadVo instantSuccess(Long fileId, String fileName, String filePath, String fileUrl, String fileSize) {
|
||||
CheckInstantUploadVo vo = new CheckInstantUploadVo();
|
||||
vo.setCanInstantUpload(true);
|
||||
vo.setExistingFileId(fileId);
|
||||
vo.setExistingFileName(fileName);
|
||||
vo.setExistingFilePath(filePath);
|
||||
vo.setExistingFileUrl(fileUrl);
|
||||
vo.setExistingFileSize(fileSize);
|
||||
vo.setMessage("秒传成功");
|
||||
return vo;
|
||||
}
|
||||
|
||||
public static CheckInstantUploadVo resumingSuccess(String uploadId, List<Integer> completedUploadChunkedIdList,String resumingUploadStatus) {
|
||||
CheckInstantUploadVo vo = new CheckInstantUploadVo();
|
||||
vo.setCanResumingUpload(true);
|
||||
vo.setResumingUploadId(uploadId);
|
||||
vo.setCompletedUploadChunkedNumberList(completedUploadChunkedIdList);
|
||||
vo.setResumingUploadStatus(resumingUploadStatus);
|
||||
vo.setMessage("断点续传成功");
|
||||
return vo;
|
||||
}
|
||||
|
||||
public static CheckInstantUploadVo needUpload(String message) {
|
||||
CheckInstantUploadVo vo = new CheckInstantUploadVo();
|
||||
vo.setCanInstantUpload(false);
|
||||
vo.setMessage(message);
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package com.microservices.file.domain.vo;
|
||||
|
||||
import com.microservices.system.api.domain.SysFileInfo;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分片上传初始化返回VO
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("分片上传初始化返回")
|
||||
public class InitChunkedUploadVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 上传ID
|
||||
*/
|
||||
@ApiModelProperty(value = "上传ID")
|
||||
private String uploadId;
|
||||
|
||||
/**
|
||||
* 总分片数
|
||||
*/
|
||||
@ApiModelProperty(value = "总分片数")
|
||||
private Integer totalChunks;
|
||||
|
||||
/**
|
||||
* 分片大小(字节)
|
||||
*/
|
||||
@ApiModelProperty(value = "分片大小(字节)")
|
||||
private Long chunkSize;
|
||||
|
||||
|
||||
/**
|
||||
* 上传分片的并发数
|
||||
*/
|
||||
@ApiModelProperty(value = "上传分片的并发数")
|
||||
private Integer concurrent;
|
||||
|
||||
/**
|
||||
* 哈希类型:FULL=完整文件MD5, SAMPLE=采样点哈希
|
||||
*/
|
||||
@ApiModelProperty(value = "哈希类型(FULL=完整文件MD5, SAMPLE=采样点哈希)")
|
||||
private String hashType;
|
||||
|
||||
/**
|
||||
* 采样点分片序号列表(仅SAMPLE类型有值)
|
||||
*/
|
||||
@ApiModelProperty(value = "采样点分片序号列表")
|
||||
private List<Integer> sampleChunks;
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
package com.microservices.file.domain.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 上传进度返回VO
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("上传进度返回")
|
||||
public class UploadProgressVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 上传ID
|
||||
*/
|
||||
@ApiModelProperty(value = "上传ID")
|
||||
private String uploadId;
|
||||
|
||||
/**
|
||||
* 文件名
|
||||
*/
|
||||
@ApiModelProperty(value = "文件名")
|
||||
private String fileName;
|
||||
|
||||
/**
|
||||
* 文件总大小(字节)
|
||||
*/
|
||||
@ApiModelProperty(value = "文件总大小(字节)")
|
||||
private Long fileSize;
|
||||
|
||||
/**
|
||||
* 总分片数
|
||||
*/
|
||||
@ApiModelProperty(value = "总分片数")
|
||||
private Integer totalChunks;
|
||||
|
||||
/**
|
||||
* 已上传分片数
|
||||
*/
|
||||
@ApiModelProperty(value = "已上传分片数")
|
||||
private Integer uploadedChunks;
|
||||
|
||||
/**
|
||||
* 上传进度(百分比)
|
||||
*/
|
||||
@ApiModelProperty(value = "上传进度(百分比)")
|
||||
private Integer progress;
|
||||
|
||||
/**
|
||||
* 上传状态
|
||||
*/
|
||||
@ApiModelProperty(value = "上传状态")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 上传速度(字节/秒)
|
||||
*/
|
||||
@ApiModelProperty(value = "上传速度(字节/秒)")
|
||||
private Long uploadSpeed;
|
||||
|
||||
/**
|
||||
* 预计剩余时间(秒)
|
||||
*/
|
||||
@ApiModelProperty(value = "预计剩余时间(秒)")
|
||||
private Long remainingSeconds;
|
||||
|
||||
/**
|
||||
* 合并进度(0-100,仅在MERGING状态时有效)
|
||||
*/
|
||||
@ApiModelProperty(value = "合并进度")
|
||||
private Integer mergeProgress;
|
||||
|
||||
/**
|
||||
* 文件ID(合并完成后返回)
|
||||
*/
|
||||
@ApiModelProperty(value = "文件ID")
|
||||
private Long fileId;
|
||||
|
||||
/**
|
||||
* 文件下载地址(合并完成后返回)
|
||||
*/
|
||||
@ApiModelProperty(value = "文件下载地址")
|
||||
private String downloadUrl;
|
||||
|
||||
/**
|
||||
* 文件路径(合并完成后返回)
|
||||
*/
|
||||
@ApiModelProperty(value = "文件路径")
|
||||
private String filePath;
|
||||
|
||||
/**
|
||||
* 错误信息(合并失败时返回)
|
||||
*/
|
||||
@ApiModelProperty(value = "错误信息")
|
||||
private String errorMessage;
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.microservices.file.domain.vo.input;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 检查分片请求
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("检查分片请求")
|
||||
public class CheckChunkInputVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty(value = "上传ID", required = true)
|
||||
@NotBlank(message = "上传ID不能为空")
|
||||
private String uploadId;
|
||||
|
||||
@ApiModelProperty(value = "分片序号", required = true)
|
||||
@NotNull(message = "分片序号不能为空")
|
||||
private Integer chunkNumber;
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.microservices.file.domain.vo.input;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@ApiModel("检查秒传请求")
|
||||
public class CheckInstantUploadInputVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty(value = "上传ID", required = true)
|
||||
@NotBlank(message = "上传ID不能为空")
|
||||
private String uploadId;
|
||||
|
||||
@NotBlank(message = "文件签名不能为空")
|
||||
@ApiModelProperty(value = "文件签名(用于秒传校验,可能是完整MD5或采样点哈希)", required = true)
|
||||
private String fileSignature;
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.microservices.file.domain.vo.input;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 初始化分片上传请求
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("初始化分片上传请求")
|
||||
public class InitChunkedUploadInputVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty(value = "文件名", required = true)
|
||||
@NotBlank(message = "文件名不能为空")
|
||||
private String fileName;
|
||||
|
||||
@ApiModelProperty(value = "文件大小(字节)", required = true)
|
||||
@NotNull(message = "文件大小不能为空")
|
||||
private Long fileSize;
|
||||
|
||||
@ApiModelProperty(value = "文件类型(cms/dms/resource/pms等)")
|
||||
private String type;
|
||||
|
||||
@ApiModelProperty(value = "层级结构")
|
||||
private String hierarchy;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.microservices.file.domain.vo.input;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合并分片请求
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("合并分片请求")
|
||||
public class MergeChunksInputVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty(value = "上传ID", required = true)
|
||||
@NotBlank(message = "上传ID不能为空")
|
||||
private String uploadId;
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.microservices.file.domain.vo.response;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 取消上传响应
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("取消上传响应")
|
||||
public class CancelUploadResponseVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("是否取消成功")
|
||||
private Boolean success;
|
||||
|
||||
@ApiModelProperty("消息")
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 创建成功响应
|
||||
*/
|
||||
public static CancelUploadResponseVo success() {
|
||||
CancelUploadResponseVo response = new CancelUploadResponseVo();
|
||||
response.setSuccess(true);
|
||||
response.setMessage("上传已取消");
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建失败响应
|
||||
*/
|
||||
public static CancelUploadResponseVo failed(String message) {
|
||||
CancelUploadResponseVo response = new CancelUploadResponseVo();
|
||||
response.setSuccess(false);
|
||||
response.setMessage(message);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.microservices.file.domain.vo.response;
|
||||
|
||||
import com.microservices.file.domain.vo.CheckChunkVo;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 检查分片响应
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("检查分片响应")
|
||||
public class CheckChunkResponseVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("分片是否存在")
|
||||
private Boolean exists;
|
||||
|
||||
@ApiModelProperty("分片序号")
|
||||
private Integer chunkNumber;
|
||||
|
||||
@ApiModelProperty("分片大小(字节)")
|
||||
private Long chunkSize;
|
||||
|
||||
/**
|
||||
* 从CheckChunkVo转换
|
||||
*/
|
||||
public static CheckChunkResponseVo from(CheckChunkVo vo) {
|
||||
CheckChunkResponseVo response = new CheckChunkResponseVo();
|
||||
response.setExists(vo.getExists());
|
||||
response.setChunkNumber(vo.getChunkNumber());
|
||||
response.setChunkSize(vo.getChunkSize());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.microservices.file.domain.vo.response;
|
||||
|
||||
import com.microservices.file.domain.vo.CheckInstantUploadVo;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@ApiModel("检查秒传响应")
|
||||
public class CheckInstantUploadResponseVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("是否可以秒传")
|
||||
private Boolean canInstantUpload;
|
||||
|
||||
@ApiModelProperty("是否可以断点续传")
|
||||
private Boolean canResumingUpload;
|
||||
|
||||
@ApiModelProperty("已存在文件ID")
|
||||
private Long existingFileId;
|
||||
|
||||
@ApiModelProperty("已存在文件名")
|
||||
private String existingFileName;
|
||||
|
||||
@ApiModelProperty("已存在文件路径")
|
||||
private String existingFilePath;
|
||||
|
||||
@ApiModelProperty("已存在文件下载地址")
|
||||
private String existingFileUrl;
|
||||
|
||||
@ApiModelProperty("已存在文件大小")
|
||||
private String existingFileSize;
|
||||
|
||||
@ApiModelProperty("已存在未上传完成的上传Id(断点续传时)")
|
||||
private String resumingUploadId;
|
||||
|
||||
@ApiModelProperty("已上传的分片Id列表(断点续传时)-当状态为合并中时该值为null,请直接调用进度接口查看进度")
|
||||
private List<Integer> completedUploadChunkedNumberList;
|
||||
|
||||
@ApiModelProperty("已存在未上传完成的上传状态,UPLOADING-上传中,MERGING-合并中(断点续传时)")
|
||||
private String resumingUploadStatus;
|
||||
|
||||
@ApiModelProperty("消息")
|
||||
private String message;
|
||||
|
||||
public static CheckInstantUploadResponseVo from(CheckInstantUploadVo vo) {
|
||||
CheckInstantUploadResponseVo response = new CheckInstantUploadResponseVo();
|
||||
response.setCanInstantUpload(vo.getCanInstantUpload());
|
||||
response.setCanResumingUpload(vo.getCanResumingUpload());
|
||||
response.setExistingFileId(vo.getExistingFileId());
|
||||
response.setExistingFileName(vo.getExistingFileName());
|
||||
response.setExistingFilePath(vo.getExistingFilePath());
|
||||
response.setExistingFileUrl(vo.getExistingFileUrl());
|
||||
response.setExistingFileSize(vo.getExistingFileSize());
|
||||
response.setResumingUploadId(vo.getResumingUploadId());
|
||||
response.setCompletedUploadChunkedNumberList(vo.getCompletedUploadChunkedNumberList());
|
||||
response.setResumingUploadStatus(vo.getResumingUploadStatus());
|
||||
response.setMessage(vo.getMessage());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.microservices.file.domain.vo.response;
|
||||
|
||||
import com.microservices.file.domain.vo.InitChunkedUploadVo;
|
||||
import com.microservices.system.api.domain.SysFileInfo;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 初始化分片上传响应
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("初始化分片上传响应")
|
||||
public class InitChunkedUploadResponseVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("上传ID")
|
||||
private String uploadId;
|
||||
|
||||
@ApiModelProperty("总分片数")
|
||||
private Integer totalChunks;
|
||||
|
||||
@ApiModelProperty("分片大小(字节)")
|
||||
private Long chunkSize;
|
||||
|
||||
@ApiModelProperty("上传分片的并发数")
|
||||
private Integer concurrent;
|
||||
|
||||
|
||||
@ApiModelProperty("哈希类型(FULL=完整文件MD5, SAMPLE=采样点哈希)")
|
||||
private String hashType;
|
||||
|
||||
@ApiModelProperty("采样点分片序号列表")
|
||||
private List<Integer> sampleChunks;
|
||||
|
||||
/**
|
||||
* 从InitChunkedUploadVo转换
|
||||
*/
|
||||
public static InitChunkedUploadResponseVo from(InitChunkedUploadVo vo) {
|
||||
InitChunkedUploadResponseVo response = new InitChunkedUploadResponseVo();
|
||||
response.setUploadId(vo.getUploadId());
|
||||
response.setTotalChunks(vo.getTotalChunks());
|
||||
response.setChunkSize(vo.getChunkSize());
|
||||
response.setConcurrent(vo.getConcurrent());
|
||||
response.setHashType(vo.getHashType());
|
||||
response.setSampleChunks(vo.getSampleChunks());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package com.microservices.file.domain.vo.response;
|
||||
|
||||
import com.microservices.system.api.domain.SysFileInfo;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合并分片响应
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("合并分片响应")
|
||||
public class MergeChunksResponseVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("文件下载地址")
|
||||
private String url;
|
||||
|
||||
@ApiModelProperty("文件标识")
|
||||
private String fileIdentifier;
|
||||
|
||||
@ApiModelProperty("文件路径")
|
||||
private String fileName;
|
||||
|
||||
@ApiModelProperty("文件ID")
|
||||
private Long fileId;
|
||||
|
||||
@ApiModelProperty("文件原始名称")
|
||||
private String fileOriginName;
|
||||
|
||||
@ApiModelProperty("文件后缀")
|
||||
private String fileSuffix;
|
||||
|
||||
@ApiModelProperty("文件大小")
|
||||
private String fileSize;
|
||||
|
||||
/**
|
||||
* 从SysFileInfo转换
|
||||
*/
|
||||
public static MergeChunksResponseVo from(SysFileInfo fileInfo) {
|
||||
MergeChunksResponseVo response = new MergeChunksResponseVo();
|
||||
response.setUrl(fileInfo.getDownloadUrl());
|
||||
response.setFileIdentifier(fileInfo.getFileIdentifier());
|
||||
response.setFileName(fileInfo.getFilePath());
|
||||
response.setFileId(fileInfo.getFileId());
|
||||
response.setFileOriginName(fileInfo.getFileOriginName());
|
||||
response.setFileSuffix(fileInfo.getFileSuffix());
|
||||
response.setFileSize(fileInfo.getFileSizeInfo());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.microservices.file.domain.vo.response;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 上传分片响应
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("上传分片响应")
|
||||
public class UploadChunkResponseVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("分片序号")
|
||||
private Integer chunkNumber;
|
||||
|
||||
@ApiModelProperty("是否上传成功")
|
||||
private Boolean success;
|
||||
|
||||
@ApiModelProperty("消息")
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 创建成功响应
|
||||
*/
|
||||
public static UploadChunkResponseVo success(Integer chunkNumber) {
|
||||
UploadChunkResponseVo response = new UploadChunkResponseVo();
|
||||
response.setChunkNumber(chunkNumber);
|
||||
response.setSuccess(true);
|
||||
response.setMessage("分片上传成功");
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建失败响应
|
||||
*/
|
||||
public static UploadChunkResponseVo failed(Integer chunkNumber, String message) {
|
||||
UploadChunkResponseVo response = new UploadChunkResponseVo();
|
||||
response.setChunkNumber(chunkNumber);
|
||||
response.setSuccess(false);
|
||||
response.setMessage(message);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package com.microservices.file.domain.vo.response;
|
||||
|
||||
import com.microservices.file.domain.vo.UploadProgressVo;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 上传进度响应
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("上传进度响应")
|
||||
public class UploadProgressResponseVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("上传ID")
|
||||
private String uploadId;
|
||||
|
||||
@ApiModelProperty("文件名")
|
||||
private String fileName;
|
||||
|
||||
@ApiModelProperty("文件总大小(字节)")
|
||||
private Long fileSize;
|
||||
|
||||
@ApiModelProperty("总分片数")
|
||||
private Integer totalChunks;
|
||||
|
||||
@ApiModelProperty("已上传分片数")
|
||||
private Integer uploadedChunks;
|
||||
|
||||
@ApiModelProperty("上传进度(百分比)")
|
||||
private Integer progress;
|
||||
|
||||
@ApiModelProperty("上传状态")
|
||||
private String status;
|
||||
|
||||
@ApiModelProperty("上传速度(字节/秒)")
|
||||
private Long uploadSpeed;
|
||||
|
||||
@ApiModelProperty("预计剩余时间(秒)")
|
||||
private Long remainingSeconds;
|
||||
|
||||
@ApiModelProperty("合并进度(0-100,仅在MERGING状态时有效)")
|
||||
private Integer mergeProgress;
|
||||
|
||||
@ApiModelProperty("文件ID(合并完成后返回)")
|
||||
private Long fileId;
|
||||
|
||||
@ApiModelProperty("文件下载地址(合并完成后返回)")
|
||||
private String downloadUrl;
|
||||
|
||||
@ApiModelProperty("文件路径(合并完成后返回)")
|
||||
private String filePath;
|
||||
|
||||
@ApiModelProperty("错误信息(合并失败时返回)")
|
||||
private String errorMessage;
|
||||
|
||||
/**
|
||||
* 从UploadProgressVo转换
|
||||
*/
|
||||
public static UploadProgressResponseVo from(UploadProgressVo vo) {
|
||||
UploadProgressResponseVo response = new UploadProgressResponseVo();
|
||||
response.setUploadId(vo.getUploadId());
|
||||
response.setFileName(vo.getFileName());
|
||||
response.setFileSize(vo.getFileSize());
|
||||
response.setTotalChunks(vo.getTotalChunks());
|
||||
response.setUploadedChunks(vo.getUploadedChunks());
|
||||
response.setProgress(vo.getProgress());
|
||||
response.setStatus(vo.getStatus());
|
||||
response.setUploadSpeed(vo.getUploadSpeed());
|
||||
response.setRemainingSeconds(vo.getRemainingSeconds());
|
||||
response.setMergeProgress(vo.getMergeProgress());
|
||||
response.setFileId(vo.getFileId());
|
||||
response.setDownloadUrl(vo.getDownloadUrl());
|
||||
response.setFilePath(vo.getFilePath());
|
||||
response.setErrorMessage(vo.getErrorMessage());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package com.microservices.file.mapper;
|
||||
|
||||
import com.microservices.file.domain.FileUploadChunk;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件分片记录Mapper接口
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Mapper
|
||||
public interface FileUploadChunkMapper {
|
||||
|
||||
/**
|
||||
* 查询文件分片记录
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @param chunkNumber 分片序号
|
||||
* @return 文件分片记录
|
||||
*/
|
||||
FileUploadChunk selectByUploadIdAndChunkNumber(@Param("uploadId") String uploadId, @Param("chunkNumber") Integer chunkNumber);
|
||||
|
||||
/**
|
||||
* 查询文件分片记录列表
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @return 文件分片记录集合
|
||||
*/
|
||||
List<FileUploadChunk> selectByUploadId(String uploadId);
|
||||
|
||||
/**
|
||||
* 查询文件分片记录列表
|
||||
*
|
||||
* @param fileUploadChunk 文件分片记录
|
||||
* @return 文件分片记录集合
|
||||
*/
|
||||
List<FileUploadChunk> selectChunkList(FileUploadChunk fileUploadChunk);
|
||||
|
||||
/**
|
||||
* 新增文件分片记录
|
||||
*
|
||||
* @param fileUploadChunk 文件分片记录
|
||||
* @return 结果
|
||||
*/
|
||||
int insertChunk(FileUploadChunk fileUploadChunk);
|
||||
|
||||
/**
|
||||
* 批量新增文件分片记录
|
||||
*
|
||||
* @param chunks 文件分片记录集合
|
||||
* @return 结果
|
||||
*/
|
||||
int batchInsertChunks(List<FileUploadChunk> chunks);
|
||||
|
||||
/**
|
||||
* 修改文件分片记录
|
||||
*
|
||||
* @param fileUploadChunk 文件分片记录
|
||||
* @return 结果
|
||||
*/
|
||||
int updateChunk(FileUploadChunk fileUploadChunk);
|
||||
|
||||
/**
|
||||
* 删除文件分片记录
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @return 结果
|
||||
*/
|
||||
int deleteByUploadId(String uploadId);
|
||||
|
||||
/**
|
||||
* 批量删除文件分片记录
|
||||
*
|
||||
* @param uploadIds 需要删除的上传ID集合
|
||||
* @return 结果
|
||||
*/
|
||||
int deleteByUploadIds(String[] uploadIds);
|
||||
|
||||
/**
|
||||
* 统计已上传的分片数
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @return 已上传分片数
|
||||
*/
|
||||
int countUploadedChunks(String uploadId);
|
||||
|
||||
/**
|
||||
* 查询指定状态的分片列表
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @param status 状态
|
||||
* @return 文件分片记录集合
|
||||
*/
|
||||
List<FileUploadChunk> selectByUploadIdAndStatus(@Param("uploadId") String uploadId, @Param("status") String status);
|
||||
|
||||
/**
|
||||
* 批量更新分片状态
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @param status 状态
|
||||
* @return 结果
|
||||
*/
|
||||
int updateStatusByUploadId(@Param("uploadId") String uploadId, @Param("status") String status);
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package com.microservices.file.mapper;
|
||||
|
||||
import com.microservices.file.domain.FileUploadSession;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件分片上传会话Mapper接口
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Mapper
|
||||
public interface FileUploadSessionMapper {
|
||||
|
||||
/**
|
||||
* 查询文件分片上传会话
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @return 文件分片上传会话
|
||||
*/
|
||||
FileUploadSession selectByUploadId(String uploadId);
|
||||
|
||||
/**
|
||||
* 查询文件分片上传会话列表
|
||||
*
|
||||
* @param fileUploadSession 文件分片上传会话
|
||||
* @return 文件分片上传会话集合
|
||||
*/
|
||||
List<FileUploadSession> selectSessionList(FileUploadSession fileUploadSession);
|
||||
|
||||
/**
|
||||
* 新增文件分片上传会话
|
||||
*
|
||||
* @param fileUploadSession 文件分片上传会话
|
||||
* @return 结果
|
||||
*/
|
||||
int insertSession(FileUploadSession fileUploadSession);
|
||||
|
||||
/**
|
||||
* 修改文件分片上传会话
|
||||
*
|
||||
* @param fileUploadSession 文件分片上传会话
|
||||
* @return 结果
|
||||
*/
|
||||
int updateSession(FileUploadSession fileUploadSession);
|
||||
|
||||
/**
|
||||
* 删除文件分片上传会话
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @return 结果
|
||||
*/
|
||||
int deleteByUploadId(String uploadId);
|
||||
|
||||
/**
|
||||
* 批量删除文件分片上传会话
|
||||
*
|
||||
* @param uploadIds 需要删除的上传ID集合
|
||||
* @return 结果
|
||||
*/
|
||||
int deleteByUploadIds(String[] uploadIds);
|
||||
|
||||
/**
|
||||
* 查询过期的会话
|
||||
*
|
||||
* @return 过期的会话列表
|
||||
*/
|
||||
List<FileUploadSession> selectExpiredSessions();
|
||||
|
||||
/**
|
||||
* 根据文件签名查询已完成的会话
|
||||
*
|
||||
* @param fileSignature 文件签名(可能是完整MD5或采样点哈希)
|
||||
* @return 文件分片上传会话
|
||||
*/
|
||||
FileUploadSession selectCompletedByFileSignature(String fileSignature);
|
||||
|
||||
/**
|
||||
* 根据文件哈希和哈希类型查询已完成的会话
|
||||
*
|
||||
* @param fileSignature 文件哈希
|
||||
* @param hashType 哈希类型
|
||||
* @return 文件分片上传会话
|
||||
*/
|
||||
FileUploadSession selectCompletedByFileSignatureAndType(@Param("fileSignature") String fileSignature, @Param("hashType") String hashType);
|
||||
|
||||
/**
|
||||
* 更新会话状态
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @param status 状态
|
||||
* @return 结果
|
||||
*/
|
||||
int updateStatus(@Param("uploadId") String uploadId, @Param("status") String status);
|
||||
|
||||
/**
|
||||
* 增加已上传分片数
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @return 结果
|
||||
*/
|
||||
int incrementUploadedChunks(String uploadId);
|
||||
|
||||
FileUploadSession selectUploadingOrMergingByFileSignatureAndType(@Param("fileSignature") String fileSignature, @Param("hashType") String hashType);
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
package com.microservices.file.service;
|
||||
|
||||
import com.microservices.file.domain.FileUploadSession;
|
||||
import com.microservices.file.domain.vo.CheckChunkVo;
|
||||
import com.microservices.file.domain.vo.CheckInstantUploadVo;
|
||||
import com.microservices.file.domain.vo.InitChunkedUploadVo;
|
||||
import com.microservices.file.domain.vo.UploadProgressVo;
|
||||
import com.microservices.file.domain.vo.input.InitChunkedUploadInputVo;
|
||||
import com.microservices.system.api.domain.SysFileInfo;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 分片上传服务接口
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
public interface IChunkedUploadService {
|
||||
|
||||
/**
|
||||
* 初始化分片上传
|
||||
* <p>
|
||||
* 功能:
|
||||
* 1. 创建上传会话
|
||||
* 2. 根据文件大小判断哈希类型(FULL/SAMPLE)
|
||||
* 3. 返回采样点信息(SAMPLE类型)
|
||||
*
|
||||
* @param input 初始化分片输入对象
|
||||
* @return 初始化结果,包含 uploadId、totalChunks、chunkSize、hashType、sampleChunks
|
||||
*/
|
||||
InitChunkedUploadVo initChunkedUpload(InitChunkedUploadInputVo input);
|
||||
|
||||
/**
|
||||
* 检查秒传(简化版)
|
||||
* <p>
|
||||
* 功能:
|
||||
* 1. 校验上传会话和时间窗口
|
||||
* 2. 查询是否存在相同的 file_hash 且状态为已完成
|
||||
* 3. 秒传成功:删除当前会话,返回已存在文件信息
|
||||
* 4. 不能秒传:存储 fileHash,返回继续上传
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @param fileHash 文件哈希(完整文件MD5或采样点哈希)
|
||||
* @return 检查结果
|
||||
*/
|
||||
CheckInstantUploadVo checkInstantUpload(String uploadId, String fileHash);
|
||||
|
||||
/**
|
||||
* 检查分片是否已上传(断点续传)
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @param chunkNumber 分片序号
|
||||
* @return 分片检查结果
|
||||
*/
|
||||
CheckChunkVo checkChunk(String uploadId, Integer chunkNumber);
|
||||
|
||||
/**
|
||||
* 上传分片
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @param chunkNumber 分片序号
|
||||
* @param chunk 分片文件
|
||||
* @return 上传结果
|
||||
*/
|
||||
Map<String, Object> uploadChunk(String uploadId, Integer chunkNumber, MultipartFile chunk);
|
||||
|
||||
/**
|
||||
* 合并分片
|
||||
* <p>
|
||||
* 功能:
|
||||
* 1. 合并所有分片为完整文件
|
||||
* 2. 根据哈希类型校验文件完整性(FULL-完整MD5,SAMPLE-采样点哈希)
|
||||
* 3. 调用现有文件服务保存文件
|
||||
* 4. 清理临时分片文件
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @return 合并后的文件信息
|
||||
*/
|
||||
SysFileInfo mergeChunks(String uploadId);
|
||||
|
||||
/**
|
||||
* 取消上传
|
||||
* <p>
|
||||
* 功能:
|
||||
* 1. 删除上传会话记录
|
||||
* 2. 清理已上传的分片文件
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @return 取消结果
|
||||
*/
|
||||
boolean cancelUpload(String uploadId);
|
||||
|
||||
/**
|
||||
* 获取上传进度
|
||||
*
|
||||
* @param uploadId 上传ID
|
||||
* @return 上传进度
|
||||
*/
|
||||
UploadProgressVo getProgress(String uploadId);
|
||||
|
||||
/**
|
||||
* 清理过期的上传会话
|
||||
* <p>
|
||||
* 定时任务调用,清理:
|
||||
* 1. 过期未完成的会话
|
||||
* 2. 对应的临时分片文件
|
||||
*
|
||||
* @return 清理的会话数量
|
||||
*/
|
||||
int cleanExpiredSessions();
|
||||
void cleanupTempFiles(FileUploadSession session);
|
||||
List<Integer> parseSampleChunks(String sampleChunksStr);
|
||||
String calculateSampleHashFromChunks(String tempPath, List<Integer> sampleChunks);
|
||||
String mergeChunkFiles(String baseFilePath,FileUploadSession session);
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.microservices.file.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件Service异步接口
|
||||
*
|
||||
* @author otto
|
||||
*/
|
||||
public interface IFileCommonAsyncService {
|
||||
public void executeMergeAsync(String uploadId);
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.microservices.file.service;
|
||||
|
||||
import com.microservices.file.domain.FileUploadSession;
|
||||
import com.microservices.system.api.domain.SysFileInfo;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
|
|
@ -104,6 +105,7 @@ public interface ISysFileInfoService {
|
|||
* @return 返回上传成功的文件名
|
||||
*/
|
||||
SysFileInfo upload(MultipartFile file, String type, String hierarchy);
|
||||
SysFileInfo uploadChunkedFile(FileUploadSession session, String type, String hierarchy);
|
||||
|
||||
/**
|
||||
* 根据文件标识获取文件详情
|
||||
|
|
|
|||
|
|
@ -0,0 +1,745 @@
|
|||
package com.microservices.file.service.impl;
|
||||
|
||||
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.core.utils.file.FileUtils;
|
||||
import com.microservices.common.core.utils.uuid.IdUtils;
|
||||
import com.microservices.common.security.utils.SecurityUtils;
|
||||
import com.microservices.file.domain.FileUploadChunk;
|
||||
import com.microservices.file.domain.FileUploadSession;
|
||||
import com.microservices.file.domain.vo.CheckChunkVo;
|
||||
import com.microservices.file.domain.vo.CheckInstantUploadVo;
|
||||
import com.microservices.file.domain.vo.InitChunkedUploadVo;
|
||||
import com.microservices.file.domain.vo.UploadProgressVo;
|
||||
import com.microservices.file.domain.vo.input.InitChunkedUploadInputVo;
|
||||
import com.microservices.file.mapper.FileUploadChunkMapper;
|
||||
import com.microservices.file.mapper.FileUploadSessionMapper;
|
||||
import com.microservices.file.service.IChunkedUploadService;
|
||||
import com.microservices.file.service.IFileCommonAsyncService;
|
||||
import com.microservices.file.service.ISysFileInfoService;
|
||||
import com.microservices.file.utils.FileUploadUtils;
|
||||
import com.microservices.file.utils.Md5Utils;
|
||||
import com.microservices.system.api.domain.SysFileInfo;
|
||||
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.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 分片上传服务实现
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Service
|
||||
public class ChunkedUploadServiceImpl implements IChunkedUploadService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ChunkedUploadServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private FileUploadSessionMapper sessionMapper;
|
||||
|
||||
@Autowired
|
||||
private FileUploadChunkMapper chunkMapper;
|
||||
|
||||
@Autowired
|
||||
@Lazy
|
||||
private ISysFileInfoService fileInfoService;
|
||||
|
||||
@Autowired
|
||||
@Lazy
|
||||
private IFileCommonAsyncService fileCommonAsyncService;
|
||||
|
||||
/**
|
||||
* 文件存储根路径
|
||||
*/
|
||||
@Value("${file.path}")
|
||||
private String filePath;
|
||||
|
||||
/**
|
||||
* 最小分片大小(默认:1MB)- 安全校验边界
|
||||
*/
|
||||
@Value("${chunked.upload.minChunkSize:1048576}")
|
||||
private Long minChunkSize;
|
||||
|
||||
/**
|
||||
* 最大分片大小(默认:50MB)- 安全校验边界
|
||||
*/
|
||||
@Value("${chunked.upload.maxChunkSize:52428800}")
|
||||
private Long maxChunkSize;
|
||||
|
||||
/**
|
||||
* 会话过期时间(默认:24小时)
|
||||
*/
|
||||
@Value("${chunked.upload.sessionExpireHours:24}")
|
||||
private Integer sessionExpireHours = 24;
|
||||
|
||||
/**
|
||||
* 最大并发上传分片数(安全边界)
|
||||
*/
|
||||
@Value("${chunked.upload.maxConcurrentChunks:10}")
|
||||
private Integer maxConcurrentChunks = 10;
|
||||
|
||||
/**
|
||||
* 最大文件大小(字节,默认50GB = 1024 * 50MB)
|
||||
*/
|
||||
@Value("${chunked.upload.maxFileSize:53687091200}")
|
||||
private Long maxFileSize;
|
||||
|
||||
/**
|
||||
* 完整哈希文件大小阈值(小于此值使用完整文件MD5,默认100MB)
|
||||
*
|
||||
*/
|
||||
@Value("${chunked.upload.fullHashThreshold:104857600}")
|
||||
private Long fullHashThreshold;
|
||||
|
||||
/**
|
||||
* 哈希采样点数量
|
||||
*/
|
||||
@Value("${chunked.upload.sampleCount:5}")
|
||||
private Integer sampleCount;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public InitChunkedUploadVo initChunkedUpload(InitChunkedUploadInputVo input) {
|
||||
// 1. 参数校验
|
||||
if (StringUtils.isEmpty(input.getFileName())) {
|
||||
throw new ServiceException("文件名不能为空");
|
||||
}
|
||||
if (input.getFileSize() == null || input.getFileSize() <= 0) {
|
||||
throw new ServiceException("文件大小无效");
|
||||
}
|
||||
|
||||
// 校验文件大小
|
||||
if (input.getFileSize() > maxFileSize) {
|
||||
throw new ServiceException("文件大小超过限制: %s(%d字节) ", FileUtils.calcFileSize(maxFileSize), maxFileSize);
|
||||
}
|
||||
|
||||
// 2. 计算分片信息(使用服务端动态计算的分片大小)
|
||||
Long chunkSize = calculateRecommendedChunkSize(input.getFileSize());
|
||||
int totalChunks = (int) Math.ceil((double) input.getFileSize() / chunkSize);
|
||||
Integer concurrent = calculateRecommendedConcurrent(totalChunks);
|
||||
|
||||
// 3. 根据文件大小判断哈希类型
|
||||
String hashType = (input.getFileSize() < fullHashThreshold) ? "FULL" : "SAMPLE";
|
||||
List<Integer> sampleChunks = null;
|
||||
|
||||
if ("SAMPLE".equals(hashType)) {
|
||||
// 计算采样点
|
||||
sampleChunks = calculateSampleChunks(totalChunks, sampleCount);
|
||||
}
|
||||
|
||||
// 4. 创建上传会话
|
||||
String uploadId = IdUtils.fastSimpleUUID();
|
||||
FileUploadSession session = new FileUploadSession();
|
||||
session.setUploadId(uploadId);
|
||||
session.setFileName(input.getFileName());
|
||||
session.setFileSize(input.getFileSize());
|
||||
session.setChunkSize(chunkSize);
|
||||
session.setTotalChunks(totalChunks);
|
||||
session.setUploadedChunks(0);
|
||||
session.setUploadType(input.getType());
|
||||
session.setHierarchy(input.getHierarchy());
|
||||
session.setStatus(FileUploadSession.Status.INIT.getCode());
|
||||
session.setTempFilePath(getTempSessionPath(uploadId));
|
||||
session.setCreateBy(SecurityUtils.getUsername());
|
||||
session.setCreateTime(DateUtils.getNowDate());
|
||||
session.setExpireTime(DateUtils.addHours(DateUtils.getNowDate(), sessionExpireHours));
|
||||
session.setHashType(hashType);
|
||||
if (sampleChunks != null && !sampleChunks.isEmpty()) {
|
||||
session.setSampleChunks(sampleChunks.stream().map(String::valueOf).reduce((a, b) -> a + "," + b).orElse(""));
|
||||
}
|
||||
|
||||
sessionMapper.insertSession(session);
|
||||
|
||||
// 5. 创建分片记录
|
||||
List<FileUploadChunk> chunks = new ArrayList<>();
|
||||
for (int i = 0; i < totalChunks; i++) {
|
||||
FileUploadChunk chunk = new FileUploadChunk();
|
||||
chunk.setUploadId(uploadId);
|
||||
chunk.setChunkNumber(i);
|
||||
chunk.setChunkSize(chunkSize);
|
||||
chunk.setStatus(FileUploadChunk.Status.PENDING.getCode());
|
||||
chunks.add(chunk);
|
||||
}
|
||||
chunkMapper.batchInsertChunks(chunks);
|
||||
|
||||
// 6. 创建临时目录
|
||||
createTempDirectory(session.getTempFilePath());
|
||||
|
||||
// 7. 返回结果
|
||||
InitChunkedUploadVo result = new InitChunkedUploadVo();
|
||||
result.setUploadId(uploadId);
|
||||
result.setTotalChunks(totalChunks);
|
||||
result.setChunkSize(chunkSize);
|
||||
result.setConcurrent(concurrent);
|
||||
result.setHashType(hashType);
|
||||
result.setSampleChunks(sampleChunks);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CheckInstantUploadVo checkInstantUpload(String uploadId, String fileSignature) {
|
||||
// 1. 校验上传会话
|
||||
FileUploadSession session = sessionMapper.selectByUploadId(uploadId);
|
||||
if (session == null) {
|
||||
throw new ServiceException("上传会话不存在");
|
||||
}
|
||||
|
||||
// 2. 查询数据库是否存在相同的 file_hash 且状态为已完成
|
||||
FileUploadSession completedSession = sessionMapper.selectCompletedByFileSignatureAndType(fileSignature, session.getHashType());
|
||||
|
||||
if (completedSession != null && completedSession.getFileId() != null) {
|
||||
// ========== 秒传成功 ==========
|
||||
SysFileInfo fileInfo = fileInfoService.selectSysFileInfoByFileId(completedSession.getFileId());
|
||||
|
||||
if (fileInfo != null) {
|
||||
// 删除当前会话及其分片记录
|
||||
chunkMapper.deleteByUploadId(uploadId);
|
||||
sessionMapper.deleteByUploadId(uploadId);
|
||||
|
||||
// 清理临时文件
|
||||
cleanupTempFiles(session);
|
||||
|
||||
// 返回已存在文件信息
|
||||
return CheckInstantUploadVo.instantSuccess(
|
||||
fileInfo.getFileId(),
|
||||
fileInfo.getFileOriginName(),
|
||||
fileInfo.getFilePath(),
|
||||
fileInfo.getDownloadUrl(),
|
||||
fileInfo.getFileSizeInfo()
|
||||
);
|
||||
}
|
||||
}
|
||||
// 3. 检查断点续传:查询数据库是否存在相同的 fileSignature 且状态为上传中或者合并中
|
||||
FileUploadSession uploadingOrMergingSession = sessionMapper.selectUploadingOrMergingByFileSignatureAndType(fileSignature, session.getHashType());
|
||||
if (uploadingOrMergingSession != null && uploadingOrMergingSession.getFileSignature() != null) {
|
||||
// ========== 断点续传成功 ==========
|
||||
// 删除当前会话及其分片记录
|
||||
chunkMapper.deleteByUploadId(uploadId);
|
||||
sessionMapper.deleteByUploadId(uploadId);
|
||||
// 清理临时文件
|
||||
cleanupTempFiles(session);
|
||||
List<Integer> completedUploadChunkedIdList = null;
|
||||
if (FileUploadSession.Status.UPLOADING.getCode().equals(uploadingOrMergingSession.getStatus())) {
|
||||
List<FileUploadChunk> chunkList = chunkMapper.selectByUploadId(uploadingOrMergingSession.getUploadId());
|
||||
completedUploadChunkedIdList = chunkList.stream()
|
||||
.filter(x -> FileUploadChunk.Status.UPLOADED.getCode().equals(x.getStatus()))
|
||||
.map(FileUploadChunk::getChunkNumber).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// 返回已存在文件信息
|
||||
return CheckInstantUploadVo.resumingSuccess(
|
||||
uploadingOrMergingSession.getUploadId(),
|
||||
completedUploadChunkedIdList,
|
||||
uploadingOrMergingSession.getStatus());
|
||||
}
|
||||
|
||||
// ========== 不能秒传,存储哈希值 ==========
|
||||
session.setFileSignature(fileSignature);
|
||||
sessionMapper.updateSession(session);
|
||||
|
||||
return CheckInstantUploadVo.needUpload("未找到相同文件,请继续上传分片");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CheckChunkVo checkChunk(String uploadId, Integer chunkNumber) {
|
||||
// 1. 校验上传会话
|
||||
FileUploadSession session = sessionMapper.selectByUploadId(uploadId);
|
||||
if (session == null) {
|
||||
throw new ServiceException("上传会话不存在");
|
||||
}
|
||||
|
||||
// 2. 校验分片序号
|
||||
if (chunkNumber == null || chunkNumber < 0 || chunkNumber >= session.getTotalChunks()) {
|
||||
throw new ServiceException("分片序号无效");
|
||||
}
|
||||
|
||||
// 3. 查询分片记录
|
||||
FileUploadChunk chunk = chunkMapper.selectByUploadIdAndChunkNumber(uploadId, chunkNumber);
|
||||
if (chunk == null) {
|
||||
return CheckChunkVo.notExists(chunkNumber);
|
||||
}
|
||||
|
||||
// 4. 检查分片文件是否存在
|
||||
if (FileUploadChunk.Status.UPLOADED.getCode().equals(chunk.getStatus())) {
|
||||
File chunkFile = new File(chunk.getChunkPath());
|
||||
if (chunkFile.exists()) {
|
||||
return CheckChunkVo.exists(chunkNumber, chunk.getChunkSize());
|
||||
}
|
||||
}
|
||||
|
||||
return CheckChunkVo.notExists(chunkNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Map<String, Object> uploadChunk(String uploadId, Integer chunkNumber, MultipartFile chunk) {
|
||||
// 1. 校验上传会话
|
||||
FileUploadSession session = sessionMapper.selectByUploadId(uploadId);
|
||||
if (session == null) {
|
||||
throw new ServiceException("上传会话不存在");
|
||||
}
|
||||
|
||||
// 2. 校验分片序号
|
||||
if (chunkNumber == null || chunkNumber < 0 || chunkNumber >= session.getTotalChunks()) {
|
||||
throw new ServiceException("分片序号无效");
|
||||
}
|
||||
|
||||
// 3. 校验文件
|
||||
if (chunk == null || chunk.isEmpty()) {
|
||||
throw new ServiceException("分片文件不能为空");
|
||||
}
|
||||
|
||||
// 4. 更新会话状态
|
||||
if (FileUploadSession.Status.INIT.getCode().equals(session.getStatus())) {
|
||||
sessionMapper.updateStatus(uploadId, FileUploadSession.Status.UPLOADING.getCode());
|
||||
}
|
||||
|
||||
// 5. 查询分片记录
|
||||
FileUploadChunk chunkRecord = chunkMapper.selectByUploadIdAndChunkNumber(uploadId, chunkNumber);
|
||||
if (chunkRecord == null) {
|
||||
throw new ServiceException("分片记录不存在");
|
||||
}
|
||||
|
||||
// 6. 检查是否已上传
|
||||
if (FileUploadChunk.Status.UPLOADED.getCode().equals(chunkRecord.getStatus())) {
|
||||
File existingFile = new File(chunkRecord.getChunkPath());
|
||||
if (existingFile.exists()) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("success", true);
|
||||
result.put("message", "分片已存在");
|
||||
result.put("chunkNumber", chunkNumber);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 保存分片文件
|
||||
String chunkFilePath = getChunkFilePath(session.getTempFilePath(), chunkNumber);
|
||||
File chunkFile = new File(chunkFilePath);
|
||||
|
||||
try {
|
||||
chunk.transferTo(chunkFile);
|
||||
} catch (IOException e) {
|
||||
logger.error("保存分片文件失败: uploadId={}, chunkNumber={}", uploadId, chunkNumber, e);
|
||||
throw new ServiceException("保存分片文件失败");
|
||||
}
|
||||
|
||||
// 8. 更新分片记录
|
||||
chunkRecord.setChunkPath(chunkFilePath);
|
||||
chunkRecord.setStatus(FileUploadChunk.Status.UPLOADED.getCode());
|
||||
chunkRecord.setUploadTime(DateUtils.getNowDate());
|
||||
chunkMapper.updateChunk(chunkRecord);
|
||||
|
||||
// 9. 更新会话已上传分片数
|
||||
sessionMapper.incrementUploadedChunks(uploadId);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("success", true);
|
||||
result.put("message", "分片上传成功");
|
||||
result.put("chunkNumber", chunkNumber);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public SysFileInfo mergeChunks(String uploadId) {
|
||||
// 1. 校验上传会话
|
||||
FileUploadSession session = sessionMapper.selectByUploadId(uploadId);
|
||||
if (session == null) {
|
||||
throw new ServiceException("上传会话不存在");
|
||||
}
|
||||
|
||||
// 2. 幂等性:已完成的直接返回
|
||||
if (FileUploadSession.Status.COMPLETED.getCode().equals(session.getStatus())
|
||||
&& session.getFileId() != null) {
|
||||
SysFileInfo fileInfo = fileInfoService.selectSysFileInfoByFileId(session.getFileId());
|
||||
if (fileInfo != null) {
|
||||
return fileInfo;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 检查所有分片是否已上传
|
||||
int uploadedCount = chunkMapper.countUploadedChunks(uploadId);
|
||||
if (uploadedCount < session.getTotalChunks()) {
|
||||
throw new ServiceException("还有分片未上传,无法合并");
|
||||
}
|
||||
|
||||
// 4. 状态检查:防止重复提交
|
||||
String currentStatus = session.getStatus();
|
||||
if (FileUploadSession.Status.MERGING.getCode().equals(currentStatus)) {
|
||||
// 已在合并中,直接返回 null
|
||||
return null;
|
||||
}
|
||||
if (FileUploadSession.Status.MERGE_FAILED.getCode().equals(currentStatus)) {
|
||||
throw new ServiceException("上次合并失败:" + session.getErrorMessage());
|
||||
}
|
||||
|
||||
// 5. 更新状态为 MERGING
|
||||
session.setStatus(FileUploadSession.Status.MERGING.getCode());
|
||||
sessionMapper.updateSession(session);
|
||||
|
||||
// 6. 异步执行合并
|
||||
fileCommonAsyncService.executeMergeAsync(uploadId);
|
||||
|
||||
// 7. 立即返回
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并分片文件
|
||||
*/
|
||||
@Override
|
||||
public String mergeChunkFiles(String baseFilePath, FileUploadSession session) {
|
||||
String fileName = FileUploadUtils.extractFilename(session.getFileName());
|
||||
String mergedFilePath = FileUploadUtils.getAbsoluteFile(baseFilePath, fileName).getAbsolutePath();
|
||||
|
||||
try (FileOutputStream fos = new FileOutputStream(mergedFilePath)) {
|
||||
List<FileUploadChunk> chunks = chunkMapper.selectByUploadId(session.getUploadId());
|
||||
for (FileUploadChunk chunk : chunks) {
|
||||
File chunkFile = new File(chunk.getChunkPath());
|
||||
if (!chunkFile.exists()) {
|
||||
throw new ServiceException("分片文件不存在: " + chunk.getChunkPath());
|
||||
}
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(chunkFile)) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
while ((bytesRead = fis.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("合并分片文件失败: uploadId={}", session.getUploadId(), e);
|
||||
throw new ServiceException("合并分片文件失败");
|
||||
}
|
||||
// 哈希校验(FULL类型进行后置校验)
|
||||
validateFullFileHash(mergedFilePath, session);
|
||||
|
||||
return FileUploadUtils.getPathFileName(fileName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean cancelUpload(String uploadId) {
|
||||
// 1. 查询会话
|
||||
FileUploadSession session = sessionMapper.selectByUploadId(uploadId);
|
||||
if (session == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 删除分片记录
|
||||
chunkMapper.deleteByUploadId(uploadId);
|
||||
|
||||
// 3. 删除会话记录
|
||||
sessionMapper.deleteByUploadId(uploadId);
|
||||
|
||||
// 4. 清理临时文件
|
||||
cleanupTempFiles(session);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UploadProgressVo getProgress(String uploadId) {
|
||||
FileUploadSession session = sessionMapper.selectByUploadId(uploadId);
|
||||
if (session == null) {
|
||||
throw new ServiceException("上传会话不存在");
|
||||
}
|
||||
|
||||
UploadProgressVo progress = new UploadProgressVo();
|
||||
progress.setUploadId(uploadId);
|
||||
progress.setFileName(session.getFileName());
|
||||
progress.setFileSize(session.getFileSize());
|
||||
progress.setTotalChunks(session.getTotalChunks());
|
||||
progress.setUploadedChunks(session.getUploadedChunks());
|
||||
progress.setStatus(session.getStatus());
|
||||
|
||||
// 计算上传进度
|
||||
if (session.getTotalChunks() > 0) {
|
||||
int progressPercent = (int) ((session.getUploadedChunks() * 100) / session.getTotalChunks());
|
||||
progress.setProgress(progressPercent);
|
||||
} else {
|
||||
progress.setProgress(0);
|
||||
}
|
||||
|
||||
// 合并状态下的额外信息
|
||||
if (FileUploadSession.Status.COMPLETED.getCode().equals(session.getStatus())
|
||||
&& session.getFileId() != null) {
|
||||
// 获取文件信息
|
||||
SysFileInfo fileInfo = fileInfoService.selectSysFileInfoByFileId(session.getFileId());
|
||||
if (fileInfo != null) {
|
||||
progress.setFileId(fileInfo.getFileId());
|
||||
progress.setDownloadUrl(fileInfo.getDownloadUrl());
|
||||
progress.setFilePath(fileInfo.getFilePath());
|
||||
}
|
||||
progress.setMergeProgress(100);
|
||||
} else if (FileUploadSession.Status.MERGING.getCode().equals(session.getStatus())) {
|
||||
// 合并中
|
||||
progress.setMergeProgress(50); // 简化处理,可以后续细化
|
||||
} else if (FileUploadSession.Status.MERGE_FAILED.getCode().equals(session.getStatus())) {
|
||||
// 合并失败
|
||||
progress.setErrorMessage(session.getErrorMessage());
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int cleanExpiredSessions() {
|
||||
// 1. 查询过期会话
|
||||
List<FileUploadSession> expiredSessions = sessionMapper.selectExpiredSessions();
|
||||
|
||||
if (expiredSessions.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
logger.info("开始清理过期会话,共{}个", expiredSessions.size());
|
||||
|
||||
int cleanedCount = 0;
|
||||
for (FileUploadSession session : expiredSessions) {
|
||||
try {
|
||||
// 删除分片记录
|
||||
chunkMapper.deleteByUploadId(session.getUploadId());
|
||||
|
||||
// 删除会话记录
|
||||
sessionMapper.deleteByUploadId(session.getUploadId());
|
||||
|
||||
// 清理临时文件
|
||||
cleanupTempFiles(session);
|
||||
|
||||
cleanedCount++;
|
||||
} catch (Exception e) {
|
||||
logger.error("清理过期会话失败: uploadId={}", session.getUploadId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("清理过期会话完成,成功清理{}个", cleanedCount);
|
||||
return cleanedCount;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建临时目录
|
||||
*/
|
||||
private void createTempDirectory(String tempPath) {
|
||||
try {
|
||||
Path path = Paths.get(tempPath);
|
||||
if (!Files.exists(path)) {
|
||||
Files.createDirectories(path);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("创建临时目录失败: {}", tempPath, e);
|
||||
throw new ServiceException("创建临时目录失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理临时文件
|
||||
*/
|
||||
public void cleanupTempFiles(FileUploadSession session) {
|
||||
if (StringUtils.isNotEmpty(session.getTempFilePath())) {
|
||||
File tempDir = new File(session.getTempFilePath());
|
||||
if (tempDir.exists()) {
|
||||
deleteDirectory(tempDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归删除目录
|
||||
*/
|
||||
private void deleteDirectory(File directory) {
|
||||
if (directory.isDirectory()) {
|
||||
File[] files = directory.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
deleteDirectory(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
directory.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取临时会话路径
|
||||
*/
|
||||
private String getTempSessionPath(String uploadId) {
|
||||
return filePath + "/chunks/" + uploadId + "/";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分片文件路径
|
||||
*/
|
||||
private String getChunkFilePath(String tempPath, Integer chunkNumber) {
|
||||
return tempPath + "chunk_" + chunkNumber;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据文件大小动态计算推荐的分片大小
|
||||
* 使用对数插值算法,基于配置的 minChunkSize 和 maxChunkSize
|
||||
*
|
||||
* @param fileSize 文件大小(字节)
|
||||
* @return 推荐的分片大小(字节)
|
||||
*/
|
||||
private Long calculateRecommendedChunkSize(long fileSize) {
|
||||
long minChunk = minChunkSize;
|
||||
long maxChunk = maxChunkSize;
|
||||
long maxFile = maxFileSize;
|
||||
|
||||
// 小于最小分片大小,直接返回最小值
|
||||
if (fileSize <= minChunk) {
|
||||
return minChunk;
|
||||
}
|
||||
|
||||
// 对数比例:小文件敏感度高,大文件敏感度低
|
||||
double ratio = Math.log((double) fileSize / minChunk) /
|
||||
Math.log((double) maxFile / minChunk);
|
||||
ratio = Math.max(0.0, Math.min(1.0, ratio));
|
||||
|
||||
// 线性插值
|
||||
long recommendedChunk = (long) (minChunk + ratio * (maxChunk - minChunk));
|
||||
recommendedChunk = Math.max(minChunk, Math.min(maxChunk, recommendedChunk));
|
||||
|
||||
// 向上取整到 1MB 边界
|
||||
long mb = 1024 * 1024L;
|
||||
recommendedChunk = ((recommendedChunk + mb - 1) / mb) * mb;
|
||||
|
||||
return Math.max(minChunk, Math.min(maxChunk, recommendedChunk));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据分片数量动态计算推荐的并发数
|
||||
* 使用平方根缩放算法,基于配置的 maxConcurrentChunks
|
||||
*
|
||||
* @param totalChunks 分片数量
|
||||
* @return 推荐的并发数
|
||||
*/
|
||||
private Integer calculateRecommendedConcurrent(int totalChunks) {
|
||||
int maxConcurrent = maxConcurrentChunks;
|
||||
int minConcurrent = 2;
|
||||
|
||||
// 分片数量少时,使用最小并发
|
||||
if (totalChunks <= minConcurrent) {
|
||||
return minConcurrent;
|
||||
}
|
||||
|
||||
// 平方根缩放:避免过度并发
|
||||
int recommendedConcurrent = (int) Math.ceil(Math.sqrt(totalChunks));
|
||||
recommendedConcurrent = Math.max(minConcurrent, Math.min(maxConcurrent, recommendedConcurrent));
|
||||
|
||||
return recommendedConcurrent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算采样点分片序号
|
||||
*
|
||||
* @param totalChunks 总分片数
|
||||
* @param sampleCount 采样点数量
|
||||
* @return 采样点分片序号列表
|
||||
*/
|
||||
private List<Integer> calculateSampleChunks(int totalChunks, int sampleCount) {
|
||||
List<Integer> samples = new ArrayList<>();
|
||||
if (totalChunks <= sampleCount) {
|
||||
for (int i = 0; i < totalChunks; i++) {
|
||||
samples.add(i);
|
||||
}
|
||||
} else {
|
||||
int interval = totalChunks / sampleCount;
|
||||
for (int i = 0; i < sampleCount; i++) {
|
||||
if (i == sampleCount - 1) {
|
||||
samples.add(totalChunks - 1);
|
||||
} else {
|
||||
samples.add(i * interval);
|
||||
}
|
||||
}
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析采样点字符串
|
||||
*
|
||||
* @param sampleChunksStr 采样点字符串(逗号分隔)
|
||||
* @return 采样点分片序号列表
|
||||
*/
|
||||
public List<Integer> parseSampleChunks(String sampleChunksStr) {
|
||||
if (StringUtils.isEmpty(sampleChunksStr)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return Arrays.stream(sampleChunksStr.split(","))
|
||||
.map(Integer::parseInt)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从已上传的分片中计算采样点哈希
|
||||
* <p>
|
||||
* 计算方式:
|
||||
* 1. 对采样点分片序号进行排序
|
||||
* 2. 逐个计算每个采样分片的 MD5 值
|
||||
* 3. 将所有 MD5 值拼接成一个字符串
|
||||
* 4. 计算拼接字符串的 MD5 值作为最终签名
|
||||
*
|
||||
* @param tempPath 临时文件路径
|
||||
* @param sampleChunks 采样点分片序号列表
|
||||
* @return 采样点哈希值
|
||||
*/
|
||||
public String calculateSampleHashFromChunks(String tempPath, List<Integer> sampleChunks) {
|
||||
// 1. 对采样点排序
|
||||
Collections.sort(sampleChunks);
|
||||
|
||||
// 2. 逐个计算每个采样分片的 MD5
|
||||
StringBuilder md5Concat = new StringBuilder();
|
||||
for (Integer chunkNumber : sampleChunks) {
|
||||
String chunkPath = getChunkFilePath(tempPath, chunkNumber);
|
||||
File chunkFile = new File(chunkPath);
|
||||
if (!chunkFile.exists()) {
|
||||
throw new ServiceException("采样分片不存在: " + chunkNumber);
|
||||
}
|
||||
String chunkMd5 = Md5Utils.calculateMd5(chunkFile);
|
||||
md5Concat.append(chunkMd5);
|
||||
}
|
||||
|
||||
// 3. 计算拼接后的 MD5 值作为最终签名
|
||||
return Md5Utils.calculateMd5(md5Concat.toString().getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验文件哈希
|
||||
*
|
||||
* @param filePath 合并后的文件路径
|
||||
* @param session 上传会话
|
||||
* @throws ServiceException 校验失败时抛出异常
|
||||
*/
|
||||
private void validateFullFileHash(String filePath, FileUploadSession session) {
|
||||
File mergedFile = new File(filePath);
|
||||
String hashType = session.getHashType();
|
||||
String expectedHash = session.getFileSignature();
|
||||
|
||||
if ("FULL".equals(hashType) || hashType == null) {
|
||||
String actualMd5 = Md5Utils.calculateMd5(mergedFile);
|
||||
if (!actualMd5.equalsIgnoreCase(expectedHash)) {
|
||||
mergedFile.delete();
|
||||
throw new ServiceException("文件MD5校验失败:期望=" + expectedHash + ",实际=" + actualMd5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
package com.microservices.file.service.impl;
|
||||
|
||||
import com.microservices.common.core.exception.ServiceException;
|
||||
import com.microservices.file.domain.FileUploadChunk;
|
||||
import com.microservices.file.domain.FileUploadSession;
|
||||
import com.microservices.file.mapper.FileUploadChunkMapper;
|
||||
import com.microservices.file.mapper.FileUploadSessionMapper;
|
||||
import com.microservices.file.service.IChunkedUploadService;
|
||||
import com.microservices.file.service.IFileCommonAsyncService;
|
||||
import com.microservices.file.service.ISysFileInfoService;
|
||||
import com.microservices.file.utils.CustomExecutorFactory;
|
||||
import com.microservices.file.utils.Md5Utils;
|
||||
import com.microservices.system.api.domain.SysFileInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件Service业务层处理
|
||||
*
|
||||
* @author otto
|
||||
*/
|
||||
@Service
|
||||
public class FileCommonAsyncServiceImpl implements IFileCommonAsyncService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(FileCommonAsyncServiceImpl.class);
|
||||
|
||||
|
||||
@Autowired
|
||||
private FileUploadSessionMapper sessionMapper;
|
||||
|
||||
@Autowired
|
||||
private FileUploadChunkMapper chunkMapper;
|
||||
|
||||
@Autowired
|
||||
private ISysFileInfoService fileInfoService;
|
||||
|
||||
@Autowired
|
||||
@Lazy
|
||||
private IChunkedUploadService chunkedUploadService;
|
||||
|
||||
public void executeMergeAsync(String uploadId) {
|
||||
CustomExecutorFactory.threadPoolExecutor.execute(() -> {
|
||||
FileUploadSession session = null;
|
||||
try {
|
||||
// 1. 获取会话
|
||||
session = sessionMapper.selectByUploadId(uploadId);
|
||||
if (session == null) {
|
||||
logger.error("合并任务失败:会话不存在, uploadId={}", uploadId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 状态校验
|
||||
if (!FileUploadSession.Status.MERGING.getCode().equals(session.getStatus())) {
|
||||
logger.warn("合并任务跳过:状态不是MERGING, uploadId={}, status={}",
|
||||
uploadId, session.getStatus());
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 检查所有分片是否已上传
|
||||
int uploadedCount = chunkMapper.countUploadedChunks(uploadId);
|
||||
if (uploadedCount < session.getTotalChunks()) {
|
||||
throw new ServiceException("还有分片未上传,无法合并");
|
||||
}
|
||||
// 4. 哈希校验(SAMPLE类型进行前置哈希校验,FULL类型进行合并后校验)
|
||||
validateSampleFileHash( session);
|
||||
|
||||
// 5. 执行合并
|
||||
SysFileInfo fileInfo = fileInfoService.uploadChunkedFile(session, session.getUploadType(), session.getHierarchy());
|
||||
|
||||
|
||||
// // 6. 保存文件
|
||||
// MultipartFile mergedMultipartFile = createMultipartFile(
|
||||
// new File(mergedFilePath), session.getFileName());
|
||||
// SysFileInfo fileInfo = fileInfoService.upload(
|
||||
// mergedMultipartFile, session.getUploadType(), session.getHierarchy());
|
||||
|
||||
// 7. 更新会话状态
|
||||
session.setStatus(FileUploadSession.Status.COMPLETED.getCode());
|
||||
session.setFileId(fileInfo.getFileId());
|
||||
sessionMapper.updateSession(session);
|
||||
|
||||
// 8. 更新分片状态
|
||||
chunkMapper.updateStatusByUploadId(uploadId,
|
||||
FileUploadChunk.Status.MERGED.getCode());
|
||||
|
||||
// 9. 清理临时文件
|
||||
cleanupTempFilesAsync(session);
|
||||
|
||||
logger.info("文件合并成功: uploadId={}, fileId={}", uploadId, fileInfo.getFileId());
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("文件合并失败: uploadId={}", uploadId, e);
|
||||
|
||||
// 更新状态为合并失败
|
||||
if (session != null) {
|
||||
session.setStatus(FileUploadSession.Status.MERGE_FAILED.getCode());
|
||||
session.setErrorMessage(e.getMessage());
|
||||
sessionMapper.updateSession(session);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步清理临时文件
|
||||
*/
|
||||
private void cleanupTempFilesAsync(FileUploadSession session) {
|
||||
new Thread(() -> chunkedUploadService.cleanupTempFiles(session)).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验文件哈希
|
||||
*
|
||||
* @param session 上传会话
|
||||
* @throws ServiceException 校验失败时抛出异常
|
||||
*/
|
||||
private void validateSampleFileHash(FileUploadSession session) {
|
||||
String hashType = session.getHashType();
|
||||
String expectedHash = session.getFileSignature();
|
||||
|
||||
if ("SAMPLE".equals(hashType)) {
|
||||
List<Integer> sampleChunks = chunkedUploadService.parseSampleChunks(session.getSampleChunks());
|
||||
String actualSampleHash = chunkedUploadService.calculateSampleHashFromChunks(
|
||||
session.getTempFilePath(), sampleChunks);
|
||||
if (!actualSampleHash.equalsIgnoreCase(expectedHash)) {
|
||||
throw new ServiceException("采样点哈希校验失败:期望=" + expectedHash + ",实际=" + actualSampleHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建MultipartFile对象
|
||||
*/
|
||||
private MultipartFile createMultipartFile(File file, String originalFilename) {
|
||||
return new MultipartFile() {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "file";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOriginalFilename() {
|
||||
return originalFilename;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return file.length() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return file.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getBytes() throws IOException {
|
||||
return Files.readAllBytes(file.toPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return new FileInputStream(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferTo(File dest) throws IOException, IllegalStateException {
|
||||
Files.copy(file.toPath(), dest.toPath());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,10 @@ import com.microservices.common.httpClient.domain.GitLinkRequestUrl;
|
|||
import com.microservices.common.httpClient.util.GitLinkRequestHelper;
|
||||
import com.microservices.common.redis.service.RedisService;
|
||||
import com.microservices.common.security.utils.SecurityUtils;
|
||||
import com.microservices.file.domain.FileUploadSession;
|
||||
import com.microservices.file.mapper.SysFileInfoMapper;
|
||||
import com.microservices.file.service.IChunkedUploadService;
|
||||
import com.microservices.file.service.IFileCommonAsyncService;
|
||||
import com.microservices.file.service.ISysFileInfoAsyncService;
|
||||
import com.microservices.file.service.ISysFileInfoService;
|
||||
import com.microservices.file.utils.FileUploadUtils;
|
||||
|
|
@ -57,6 +60,8 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
private GitLinkRequestHelper gitLinkRequestHelper;
|
||||
@Autowired
|
||||
private ISysFileInfoAsyncService fileInfoAsyncService;
|
||||
@Autowired
|
||||
private IChunkedUploadService chunkedUploadService;
|
||||
/**
|
||||
* 上传文件存储在本地的根路径
|
||||
*/
|
||||
|
|
@ -91,6 +96,11 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
/** 积分异步任务 Redis 队列 Key */
|
||||
private static final String ZONE_POINTS_ASYNC_QUEUE_KEY = "zone:points:async:queue";
|
||||
/** 资源下载积分任务 sourceType */
|
||||
private static final String DOWNLOAD_RESOURCE_SOURCE_TYPE = "downloadResource";
|
||||
|
||||
@Override
|
||||
public SysFileInfo selectSysFileInfoByFileObjectName(String fileObjectName) {
|
||||
return sysFileInfoMapper.selectSysFileInfoByFileObjectName(fileObjectName);
|
||||
|
|
@ -108,6 +118,12 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
if (sysFileInfo == null) {
|
||||
throw new ServiceException("该文件不存在(文件Id[%d])", fileId);
|
||||
}
|
||||
// 设置下载URL
|
||||
if (StringUtils.isNotEmpty(sysFileInfo.getFileIdentifier())) {
|
||||
sysFileInfo.setDownloadUrl(EscapeUtil.removeExtraSlashOfUrl(domain + "/common/downloadByIdentifier/" + sysFileInfo.getFileIdentifier()));
|
||||
} else {
|
||||
sysFileInfo.setDownloadUrl(EscapeUtil.removeExtraSlashOfUrl(domain + "/open/download/" + fileId));
|
||||
}
|
||||
return sysFileInfo;
|
||||
}
|
||||
|
||||
|
|
@ -358,8 +374,19 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
|
||||
@Override
|
||||
public SysFileInfo upload(MultipartFile file, String fileType, String hierarchy) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new ServiceException("不允许上传空文件");
|
||||
return commonUpload(false, file, null, fileType, hierarchy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SysFileInfo uploadChunkedFile(FileUploadSession session, String fileType, String hierarchy) {
|
||||
return commonUpload(true, null, session, fileType, hierarchy);
|
||||
}
|
||||
|
||||
public SysFileInfo commonUpload(Boolean isChunkedUpload, MultipartFile file, FileUploadSession session, String fileType, String hierarchy) {
|
||||
if (!isChunkedUpload) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new ServiceException("不允许上传空文件");
|
||||
}
|
||||
}
|
||||
SysFileInfo sysFileInfo = new SysFileInfo();
|
||||
// 上传文件路径
|
||||
|
|
@ -379,6 +406,9 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
switch (fileType) {
|
||||
case "pms-gitlink":
|
||||
case "gitlink": {
|
||||
if (isChunkedUpload) {
|
||||
throw new ServiceException("当前平台不支持分片上传");
|
||||
}
|
||||
isLocalUpload = false;
|
||||
JSONObject uploadRes = gitLinkRequestHelper.doPostFile(GitLinkRequestUrl.UPLOAD_ATTACHMENTS(), file);
|
||||
sysFileInfo.setFilePath(uploadRes.getString("title"));
|
||||
|
|
@ -433,7 +463,9 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
if (StringUtils.isNotEmpty(cmsAllowedExtension)) {
|
||||
allowedExtensions = cmsAllowedExtension.split(",");
|
||||
}
|
||||
assertAllowed(file, allowedExtensions);
|
||||
if (!isChunkedUpload) {
|
||||
assertAllowed(file, allowedExtensions);
|
||||
}
|
||||
|
||||
baseFilePath += "/" + fileType + "/";
|
||||
isCommonFileType = false;
|
||||
|
|
@ -445,13 +477,40 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
if (StringUtils.isNotEmpty(allowedExtension)) {
|
||||
allowedExtensions = allowedExtension.split(",");
|
||||
}
|
||||
assertAllowed(file, allowedExtensions);
|
||||
if (!isChunkedUpload) {
|
||||
assertAllowed(file, allowedExtensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isLocalUpload) {
|
||||
String filePath;
|
||||
|
||||
// 上传并返回新文件名称
|
||||
String filePath = upload(baseFilePath, file);
|
||||
if (!isChunkedUpload) {
|
||||
filePath = upload(baseFilePath, file);
|
||||
sysFileInfo.setFileSizeInfo(FileUtils.calcFileSize(file.getSize()));
|
||||
sysFileInfo.setFileOriginName(file.getOriginalFilename());
|
||||
// 获取文件内容信息
|
||||
try {
|
||||
ContentInfoUtil util = new ContentInfoUtil();
|
||||
ContentInfo contentInfo = util.findMatch(file.getInputStream());
|
||||
if (contentInfo != null) {
|
||||
sysFileInfo.setContentType(contentInfo.getContentType().name());
|
||||
sysFileInfo.setContentTypeName(contentInfo.getName());
|
||||
sysFileInfo.setContentTypeMessage(contentInfo.getMessage());
|
||||
sysFileInfo.setMimeType(contentInfo.getMimeType());
|
||||
sysFileInfo.setIsPartialMatch(contentInfo.isPartial() ? 1 : 0);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("获取文件内容信息出现异常:{0}", e);
|
||||
throw new ServiceException("获取文件内容信息出现异常(文件名[%s])", filePath);
|
||||
}
|
||||
} else {
|
||||
filePath = chunkedUploadService.mergeChunkFiles(baseFilePath, session);
|
||||
sysFileInfo.setFileSizeInfo(FileUtils.calcFileSize(session.getFileSize()));
|
||||
sysFileInfo.setFileOriginName(session.getFileName());
|
||||
}
|
||||
if (!isCommonFileType) {
|
||||
if ("pms".equals(fileType)) {
|
||||
filePath = EscapeUtil.removeExtraSlashOfUrl("/" + fileType + "/" + hierarchy + "/" + filePath);
|
||||
|
|
@ -467,23 +526,6 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
sysFileInfo.setFileObjectName(newFileName);
|
||||
sysFileInfo.setFileSuffix(newFileName.substring(separatorIndex + 1).toLowerCase());
|
||||
|
||||
sysFileInfo.setFileSizeInfo(FileUtils.calcFileSize(file.getSize()));
|
||||
// 获取文件内容信息
|
||||
try {
|
||||
ContentInfoUtil util = new ContentInfoUtil();
|
||||
ContentInfo contentInfo = util.findMatch(file.getInputStream());
|
||||
if (contentInfo != null) {
|
||||
sysFileInfo.setContentType(contentInfo.getContentType().name());
|
||||
sysFileInfo.setContentTypeName(contentInfo.getName());
|
||||
sysFileInfo.setContentTypeMessage(contentInfo.getMessage());
|
||||
sysFileInfo.setMimeType(contentInfo.getMimeType());
|
||||
sysFileInfo.setIsPartialMatch(contentInfo.isPartial() ? 1 : 0);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("获取文件内容信息出现异常:{0}", e);
|
||||
throw new ServiceException("获取文件内容信息出现异常(文件名[%s])", filePath);
|
||||
}
|
||||
sysFileInfo.setFileOriginName(file.getOriginalFilename());
|
||||
sysFileInfo.setDelFlag("N");
|
||||
insertSysFileInfo(sysFileInfo);
|
||||
fileId = sysFileInfo.getFileId();
|
||||
|
|
@ -579,6 +621,15 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
|
|||
sysFileInfo.setDownloadCount(downloadCount);
|
||||
updateSysFileInfo(sysFileInfo);
|
||||
|
||||
// 触发资源下载积分任务(直接写入 Redis 异步队列)
|
||||
try {
|
||||
JSONObject task = new JSONObject();
|
||||
task.put("sourceType", DOWNLOAD_RESOURCE_SOURCE_TYPE);
|
||||
task.put("sourceId", sysFileInfo.getFileId());
|
||||
redisService.leftPush(ZONE_POINTS_ASYNC_QUEUE_KEY, JSONObject.toJSONString(task));
|
||||
} catch (Exception e) {
|
||||
logger.warn("触发资源下载积分任务失败 fileId={} error={}", sysFileInfo.getFileId(), e.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException("文件下载异常:%s", e.getMessage());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
package com.microservices.file.task;
|
||||
|
||||
import com.microservices.file.service.IChunkedUploadService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 分片上传清理任务
|
||||
* <p>
|
||||
* 定时清理过期的上传会话和临时文件
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
@Component
|
||||
public class ChunkedUploadCleanupTask {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ChunkedUploadCleanupTask.class);
|
||||
|
||||
@Autowired
|
||||
private IChunkedUploadService chunkedUploadService;
|
||||
|
||||
/**
|
||||
* 每小时执行一次清理过期会话
|
||||
* <p>
|
||||
* 清理规则:
|
||||
* 1. 会话状态为INIT/UPLOADING/FAILED且已过期(超过24小时)
|
||||
* 2. 清理对应的临时分片文件
|
||||
*/
|
||||
@Scheduled(cron = "0 0 * * * ?")
|
||||
public void cleanExpiredSessions() {
|
||||
logger.info("开始清理过期的分片上传会话");
|
||||
try {
|
||||
int cleanedCount = chunkedUploadService.cleanExpiredSessions();
|
||||
if (cleanedCount > 0) {
|
||||
logger.info("清理过期的分片上传会话完成,共清理{}个会话", cleanedCount);
|
||||
} else {
|
||||
logger.debug("没有需要清理的过期会话");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("清理过期的分片上传会话失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -60,6 +60,15 @@ public class FileUploadUtils {
|
|||
FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), FileTypeUtils.getExtension(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码文件名
|
||||
*/
|
||||
public static final String extractFilename(String originalFilename)
|
||||
{
|
||||
return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
|
||||
FilenameUtils.getBaseName(originalFilename), Seq.getId(Seq.uploadSeqType), FileTypeUtils.getFileType(originalFilename));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件路径上传
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,148 @@
|
|||
package com.microservices.file.utils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* MD5工具类
|
||||
*
|
||||
* @author microservices
|
||||
*/
|
||||
public class Md5Utils {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(Md5Utils.class);
|
||||
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
|
||||
/**
|
||||
* 计算文件的MD5值
|
||||
*
|
||||
* @param file 文件
|
||||
* @return MD5字符串
|
||||
*/
|
||||
public static String calculateMd5(File file) {
|
||||
if (file == null || !file.exists()) {
|
||||
throw new IllegalArgumentException("文件不存在");
|
||||
}
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
return calculateMd5(fis);
|
||||
} catch (IOException e) {
|
||||
logger.error("计算文件MD5失败: {}", file.getAbsolutePath(), e);
|
||||
throw new RuntimeException("计算文件MD5失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算MultipartFile的MD5值
|
||||
*
|
||||
* @param file MultipartFile对象
|
||||
* @return MD5字符串
|
||||
*/
|
||||
public static String calculateMd5(MultipartFile file) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new IllegalArgumentException("文件为空");
|
||||
}
|
||||
|
||||
try (InputStream is = file.getInputStream()) {
|
||||
return calculateMd5(is);
|
||||
} catch (IOException e) {
|
||||
logger.error("计算文件MD5失败: {}", file.getOriginalFilename(), e);
|
||||
throw new RuntimeException("计算文件MD5失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算输入流的MD5值
|
||||
*
|
||||
* @param inputStream 输入流
|
||||
* @return MD5字符串
|
||||
*/
|
||||
public static String calculateMd5(InputStream inputStream) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
md.update(buffer, 0, bytesRead);
|
||||
}
|
||||
|
||||
byte[] digest = md.digest();
|
||||
return bytesToHex(digest);
|
||||
} catch (NoSuchAlgorithmException | IOException e) {
|
||||
logger.error("计算MD5失败", e);
|
||||
throw new RuntimeException("计算MD5失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算字节数组的MD5值
|
||||
*
|
||||
* @param data 字节数组
|
||||
* @return MD5字符串
|
||||
*/
|
||||
public static String calculateMd5(byte[] data) {
|
||||
if (data == null || data.length == 0) {
|
||||
throw new IllegalArgumentException("数据为空");
|
||||
}
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
byte[] digest = md.digest(data);
|
||||
return bytesToHex(digest);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
logger.error("计算MD5失败", e);
|
||||
throw new RuntimeException("计算MD5失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节数组转十六进制字符串
|
||||
*
|
||||
* @param bytes 字节数组
|
||||
* @return 十六进制字符串
|
||||
*/
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
char[] result = new char[bytes.length * 2];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
int b = bytes[i] & 0xFF;
|
||||
result[i * 2] = HEX_DIGITS[b >>> 4];
|
||||
result[i * 2 + 1] = HEX_DIGITS[b & 0x0F];
|
||||
}
|
||||
return new String(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证文件的MD5值
|
||||
*
|
||||
* @param file 文件
|
||||
* @param expectMd5 期望的MD5值
|
||||
* @return 是否匹配
|
||||
*/
|
||||
public static boolean verifyMd5(File file, String expectMd5) {
|
||||
String actualMd5 = calculateMd5(file);
|
||||
return actualMd5.equalsIgnoreCase(expectMd5);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证MultipartFile的MD5值
|
||||
*
|
||||
* @param file MultipartFile对象
|
||||
* @param expectMd5 期望的MD5值
|
||||
* @return 是否匹配
|
||||
*/
|
||||
public static boolean verifyMd5(MultipartFile file, String expectMd5) {
|
||||
String actualMd5 = calculateMd5(file);
|
||||
return actualMd5.equalsIgnoreCase(expectMd5);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
-- =============================================
|
||||
-- 分片上传采样点哈希方案 - 数据库迁移脚本
|
||||
-- =============================================
|
||||
|
||||
-- 1. 新增字段到 sys_file_upload_session 表
|
||||
ALTER TABLE sys_file_upload_session
|
||||
ADD COLUMN hash_type VARCHAR(16) DEFAULT 'FULL' COMMENT '哈希类型:FULL=完整文件MD5, SAMPLE=采样点哈希';
|
||||
|
||||
ALTER TABLE sys_file_upload_session
|
||||
ADD COLUMN sample_chunks VARCHAR(256) COMMENT '采样点分片序号列表,逗号分隔:0,200,400,600,999(仅SAMPLE类型)';
|
||||
|
||||
-- 2. 添加索引优化查询
|
||||
CREATE INDEX idx_file_hash_type ON sys_file_upload_session(file_md5, hash_type, status);
|
||||
|
||||
-- 3. 更新现有数据的 hash_type(可选)
|
||||
-- UPDATE sys_file_upload_session SET hash_type = 'FULL' WHERE hash_type IS NULL;
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
-- =============================================
|
||||
-- 文件分片上传会话表 - 完整建表语句
|
||||
-- =============================================
|
||||
|
||||
DROP TABLE IF EXISTS `sys_file_upload_session`;
|
||||
|
||||
CREATE TABLE `sys_file_upload_session` (
|
||||
`session_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '会话ID',
|
||||
`upload_id` VARCHAR(64) NOT NULL COMMENT '上传ID(唯一标识)',
|
||||
`file_id` BIGINT(20) DEFAULT NULL COMMENT '文件ID(关联sys_file_info表)',
|
||||
`file_name` VARCHAR(255) NOT NULL COMMENT '原始文件名',
|
||||
`file_size` BIGINT(20) NOT NULL COMMENT '文件总大小(字节)',
|
||||
`file_signature` VARCHAR(32) DEFAULT NULL COMMENT '文件签名(用于秒传校验,可能是完整MD5或采样点哈希)',
|
||||
`chunk_size` BIGINT(20) NOT NULL COMMENT '分片大小(字节)',
|
||||
`total_chunks` INT(11) NOT NULL COMMENT '总分片数',
|
||||
`uploaded_chunks` INT(11) NOT NULL DEFAULT '0' COMMENT '已上传分片数',
|
||||
`upload_type` VARCHAR(32) DEFAULT NULL COMMENT '上传类型(cms/dms/resource/pms等)',
|
||||
`hierarchy` VARCHAR(255) DEFAULT NULL COMMENT '层级路径',
|
||||
`status` VARCHAR(16) NOT NULL DEFAULT 'INIT' COMMENT '状态:INIT-初始化,UPLOADING-上传中,COMPLETED-已完成,FAILED-失败',
|
||||
`temp_file_path` VARCHAR(500) DEFAULT NULL COMMENT '临时文件存储路径',
|
||||
`error_message` VARCHAR(500) DEFAULT NULL COMMENT '错误信息',
|
||||
`hash_type` VARCHAR(16) DEFAULT 'FULL' COMMENT '哈希类型:FULL=完整文件MD5, SAMPLE=采样点哈希',
|
||||
`sample_chunks` VARCHAR(256) DEFAULT NULL COMMENT '采样点分片序号列表,逗号分隔:0,200,400,600,999(仅SAMPLE类型)',
|
||||
`create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
|
||||
`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
|
||||
`expire_time` DATETIME NOT NULL COMMENT '过期时间(24小时)',
|
||||
PRIMARY KEY (`session_id`),
|
||||
UNIQUE KEY `uk_upload_id` (`upload_id`),
|
||||
KEY `idx_file_hash_type` (`file_signature`, `hash_type`, `status`),
|
||||
KEY `idx_status_expire` (`status`, `expire_time`),
|
||||
KEY `idx_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件分片上传会话表';
|
||||
|
||||
-- =============================================
|
||||
-- 文件分片上传记录表 - 完整建表语句
|
||||
-- =============================================
|
||||
|
||||
DROP TABLE IF EXISTS `sys_file_upload_chunk`;
|
||||
|
||||
CREATE TABLE `sys_file_upload_chunk` (
|
||||
`chunk_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '分片ID',
|
||||
`upload_id` VARCHAR(64) NOT NULL COMMENT '上传ID',
|
||||
`chunk_number` INT(11) NOT NULL COMMENT '分片序号',
|
||||
`chunk_size` BIGINT(20) NOT NULL COMMENT '分片大小(字节)',
|
||||
`chunk_path` VARCHAR(500) DEFAULT NULL COMMENT '分片文件存储路径',
|
||||
`status` VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '状态:PENDING-待上传,UPLOADED-已上传,MERGED-已合并',
|
||||
`upload_time` DATETIME DEFAULT NULL COMMENT '上传时间',
|
||||
`create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`chunk_id`),
|
||||
UNIQUE KEY `uk_upload_chunk` (`upload_id`, `chunk_number`),
|
||||
KEY `idx_upload_id` (`upload_id`),
|
||||
KEY `idx_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件分片上传记录表';
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
<?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.FileUploadChunkMapper">
|
||||
|
||||
<resultMap type="com.microservices.file.domain.FileUploadChunk" id="FileUploadChunkResult">
|
||||
<result property="chunkId" column="chunk_id"/>
|
||||
<result property="uploadId" column="upload_id"/>
|
||||
<result property="chunkNumber" column="chunk_number"/>
|
||||
<result property="chunkSize" column="chunk_size"/>
|
||||
<result property="chunkPath" column="chunk_path"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="uploadTime" column="upload_time"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectFileUploadChunkVo">
|
||||
select chunk_id,
|
||||
upload_id,
|
||||
chunk_number,
|
||||
chunk_size,
|
||||
chunk_path,
|
||||
status,
|
||||
upload_time
|
||||
from sys_file_upload_chunk
|
||||
</sql>
|
||||
|
||||
<select id="selectByUploadIdAndChunkNumber" resultMap="FileUploadChunkResult">
|
||||
<include refid="selectFileUploadChunkVo"/>
|
||||
where upload_id = #{uploadId}
|
||||
and chunk_number = #{chunkNumber}
|
||||
</select>
|
||||
|
||||
<select id="selectByUploadId" parameterType="String" resultMap="FileUploadChunkResult">
|
||||
<include refid="selectFileUploadChunkVo"/>
|
||||
where upload_id = #{uploadId}
|
||||
order by chunk_number
|
||||
</select>
|
||||
|
||||
<select id="selectChunkList" parameterType="com.microservices.file.domain.FileUploadChunk"
|
||||
resultMap="FileUploadChunkResult">
|
||||
<include refid="selectFileUploadChunkVo"/>
|
||||
<where>
|
||||
<if test="uploadId != null and uploadId != ''">and upload_id = #{uploadId}</if>
|
||||
<if test="chunkNumber != null">and chunk_number = #{chunkNumber}</if>
|
||||
<if test="status != null and status != ''">and status = #{status}</if>
|
||||
</where>
|
||||
ORDER BY chunk_number
|
||||
</select>
|
||||
|
||||
<select id="countUploadedChunks" parameterType="String" resultType="int">
|
||||
select count(*)
|
||||
from sys_file_upload_chunk
|
||||
where upload_id = #{uploadId}
|
||||
and status = 'UPLOADED'
|
||||
</select>
|
||||
|
||||
<select id="selectByUploadIdAndStatus" resultMap="FileUploadChunkResult">
|
||||
<include refid="selectFileUploadChunkVo"/>
|
||||
where upload_id = #{uploadId}
|
||||
and status = #{status}
|
||||
order by chunk_number
|
||||
</select>
|
||||
|
||||
<insert id="insertChunk" parameterType="com.microservices.file.domain.FileUploadChunk"
|
||||
useGeneratedKeys="true" keyProperty="chunkId">
|
||||
insert into sys_file_upload_chunk
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="uploadId != null and uploadId != ''">upload_id,</if>
|
||||
<if test="chunkNumber != null">chunk_number,</if>
|
||||
<if test="chunkSize != null">chunk_size,</if>
|
||||
<if test="chunkPath != null and chunkPath != ''">chunk_path,</if>
|
||||
<if test="status != null and status != ''">status,</if>
|
||||
<if test="uploadTime != null">upload_time,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="uploadId != null and uploadId != ''">#{uploadId},</if>
|
||||
<if test="chunkNumber != null">#{chunkNumber},</if>
|
||||
<if test="chunkSize != null">#{chunkSize},</if>
|
||||
<if test="chunkPath != null and chunkPath != ''">#{chunkPath},</if>
|
||||
<if test="status != null and status != ''">#{status},</if>
|
||||
<if test="uploadTime != null">#{uploadTime},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<insert id="batchInsertChunks">
|
||||
insert into sys_file_upload_chunk (upload_id, chunk_number, chunk_size, status)
|
||||
values
|
||||
<foreach collection="list" item="item" separator=",">
|
||||
(#{item.uploadId}, #{item.chunkNumber}, #{item.chunkSize}, #{item.status})
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<update id="updateChunk" parameterType="com.microservices.file.domain.FileUploadChunk">
|
||||
update sys_file_upload_chunk
|
||||
<trim prefix="SET" suffixOverrides=",">
|
||||
<if test="chunkSize != null">chunk_size = #{chunkSize},</if>
|
||||
<if test="chunkPath != null and chunkPath != ''">chunk_path = #{chunkPath},</if>
|
||||
<if test="status != null and status != ''">status = #{status},</if>
|
||||
<if test="uploadTime != null">upload_time = #{uploadTime},</if>
|
||||
</trim>
|
||||
where chunk_id = #{chunkId}
|
||||
</update>
|
||||
|
||||
<update id="updateStatusByUploadId">
|
||||
update sys_file_upload_chunk
|
||||
set status = #{status}
|
||||
where upload_id = #{uploadId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteByUploadId" parameterType="String">
|
||||
delete
|
||||
from sys_file_upload_chunk
|
||||
where upload_id = #{uploadId}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteByUploadIds" parameterType="String">
|
||||
delete from sys_file_upload_chunk where upload_id in
|
||||
<foreach item="uploadId" collection="array" open="(" separator="," close=")">
|
||||
#{uploadId}
|
||||
</foreach>
|
||||
</delete>
|
||||
</mapper>
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
<?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.FileUploadSessionMapper">
|
||||
|
||||
<resultMap type="com.microservices.file.domain.FileUploadSession" id="FileUploadSessionResult">
|
||||
<result property="sessionId" column="session_id"/>
|
||||
<result property="uploadId" column="upload_id"/>
|
||||
<result property="fileId" column="file_id"/>
|
||||
<result property="fileName" column="file_name"/>
|
||||
<result property="fileSize" column="file_size"/>
|
||||
<result property="fileSignature" column="file_signature"/>
|
||||
<result property="chunkSize" column="chunk_size"/>
|
||||
<result property="totalChunks" column="total_chunks"/>
|
||||
<result property="uploadedChunks" column="uploaded_chunks"/>
|
||||
<result property="uploadType" column="upload_type"/>
|
||||
<result property="hierarchy" column="hierarchy"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="tempFilePath" column="temp_file_path"/>
|
||||
<result property="errorMessage" column="error_message"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
<result property="expireTime" column="expire_time"/>
|
||||
<result property="hashType" column="hash_type"/>
|
||||
<result property="sampleChunks" column="sample_chunks"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectFileUploadSessionVo">
|
||||
select session_id,
|
||||
upload_id,
|
||||
file_id,
|
||||
file_name,
|
||||
file_size,
|
||||
file_signature,
|
||||
chunk_size,
|
||||
total_chunks,
|
||||
uploaded_chunks,
|
||||
upload_type,
|
||||
hierarchy,
|
||||
status,
|
||||
temp_file_path,
|
||||
error_message,
|
||||
create_by,
|
||||
create_time,
|
||||
update_time,
|
||||
expire_time,
|
||||
hash_type,
|
||||
sample_chunks
|
||||
from sys_file_upload_session
|
||||
</sql>
|
||||
|
||||
<select id="selectByUploadId" parameterType="String" resultMap="FileUploadSessionResult">
|
||||
<include refid="selectFileUploadSessionVo"/>
|
||||
where upload_id = #{uploadId}
|
||||
</select>
|
||||
|
||||
<select id="selectSessionList" parameterType="com.microservices.file.domain.FileUploadSession"
|
||||
resultMap="FileUploadSessionResult">
|
||||
<include refid="selectFileUploadSessionVo"/>
|
||||
<where>
|
||||
<if test="fileName != null and fileName != ''">and file_name like concat('%', #{fileName}, '%')</if>
|
||||
<if test="fileSignature != null and fileSignature != ''">and file_signature = #{fileSignature}</if>
|
||||
<if test="uploadType != null and uploadType != ''">and upload_type = #{uploadType}</if>
|
||||
<if test="status != null and status != ''">and status = #{status}</if>
|
||||
<if test="hierarchy != null and hierarchy != ''">and hierarchy = #{hierarchy}</if>
|
||||
</where>
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectExpiredSessions" resultMap="FileUploadSessionResult">
|
||||
<include refid="selectFileUploadSessionVo"/>
|
||||
where status IN ('INIT', 'UPLOADING', 'FAILED')
|
||||
and expire_time < NOW()
|
||||
</select>
|
||||
|
||||
<select id="selectCompletedByFileSignature" parameterType="String" resultMap="FileUploadSessionResult">
|
||||
<include refid="selectFileUploadSessionVo"/>
|
||||
where file_signature = #{fileSignature}
|
||||
and status = 'COMPLETED'
|
||||
and file_id IS NOT NULL
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="selectCompletedByFileSignatureAndType" resultMap="FileUploadSessionResult">
|
||||
<include refid="selectFileUploadSessionVo"/>
|
||||
where file_signature = #{fileSignature}
|
||||
and hash_type = #{hashType}
|
||||
and status = 'COMPLETED'
|
||||
and file_id IS NOT NULL
|
||||
limit 1
|
||||
</select>
|
||||
<select id="selectUploadingOrMergingByFileSignatureAndType" resultMap="FileUploadSessionResult">
|
||||
<include refid="selectFileUploadSessionVo"/>
|
||||
where file_signature = #{fileSignature}
|
||||
and hash_type = #{hashType}
|
||||
and (status = 'UPLOADING' or status = 'MERGING')
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<insert id="insertSession" parameterType="com.microservices.file.domain.FileUploadSession"
|
||||
useGeneratedKeys="true" keyProperty="sessionId">
|
||||
insert into sys_file_upload_session
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="uploadId != null and uploadId != ''">upload_id,</if>
|
||||
<if test="fileId != null">file_id,</if>
|
||||
<if test="fileName != null and fileName != ''">file_name,</if>
|
||||
<if test="fileSize != null">file_size,</if>
|
||||
<if test="fileSignature != null and fileSignature != ''">file_signature,</if>
|
||||
<if test="chunkSize != null">chunk_size,</if>
|
||||
<if test="totalChunks != null">total_chunks,</if>
|
||||
<if test="uploadedChunks != null">uploaded_chunks,</if>
|
||||
<if test="uploadType != null and uploadType != ''">upload_type,</if>
|
||||
<if test="hierarchy != null">hierarchy,</if>
|
||||
<if test="status != null and status != ''">status,</if>
|
||||
<if test="tempFilePath != null">temp_file_path,</if>
|
||||
<if test="errorMessage != null">error_message,</if>
|
||||
<if test="createBy != null">create_by,</if>
|
||||
<if test="createTime != null">create_time,</if>
|
||||
<if test="expireTime != null">expire_time,</if>
|
||||
<if test="hashType != null and hashType != ''">hash_type,</if>
|
||||
<if test="sampleChunks != null">sample_chunks,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="uploadId != null and uploadId != ''">#{uploadId},</if>
|
||||
<if test="fileId != null">#{fileId},</if>
|
||||
<if test="fileName != null and fileName != ''">#{fileName},</if>
|
||||
<if test="fileSize != null">#{fileSize},</if>
|
||||
<if test="fileSignature != null and fileSignature != ''">#{fileSignature},</if>
|
||||
<if test="chunkSize != null">#{chunkSize},</if>
|
||||
<if test="totalChunks != null">#{totalChunks},</if>
|
||||
<if test="uploadedChunks != null">#{uploadedChunks},</if>
|
||||
<if test="uploadType != null and uploadType != ''">#{uploadType},</if>
|
||||
<if test="hierarchy != null">#{hierarchy},</if>
|
||||
<if test="status != null and status != ''">#{status},</if>
|
||||
<if test="tempFilePath != null">#{tempFilePath},</if>
|
||||
<if test="errorMessage != null">#{errorMessage},</if>
|
||||
<if test="createBy != null">#{createBy},</if>
|
||||
<if test="createTime != null">#{createTime},</if>
|
||||
<if test="expireTime != null">#{expireTime},</if>
|
||||
<if test="hashType != null and hashType != ''">#{hashType},</if>
|
||||
<if test="sampleChunks != null">#{sampleChunks},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<update id="updateSession" parameterType="com.microservices.file.domain.FileUploadSession">
|
||||
update sys_file_upload_session
|
||||
<trim prefix="SET" suffixOverrides=",">
|
||||
<if test="fileId != null">file_id = #{fileId},</if>
|
||||
<if test="status != null and status != ''">status = #{status},</if>
|
||||
<if test="uploadedChunks != null">uploaded_chunks = #{uploadedChunks},</if>
|
||||
<if test="tempFilePath != null">temp_file_path = #{tempFilePath},</if>
|
||||
<if test="errorMessage != null">error_message = #{errorMessage},</if>
|
||||
<if test="updateTime != null">update_time = #{updateTime},</if>
|
||||
<if test="fileSignature != null and fileSignature != ''">file_signature = #{fileSignature},</if>
|
||||
<if test="hashType != null and hashType != ''">hash_type = #{hashType},</if>
|
||||
<if test="sampleChunks != null">sample_chunks = #{sampleChunks},</if>
|
||||
</trim>
|
||||
where upload_id = #{uploadId}
|
||||
</update>
|
||||
|
||||
<update id="updateStatus">
|
||||
update sys_file_upload_session
|
||||
set status = #{status},
|
||||
update_time = NOW()
|
||||
where upload_id = #{uploadId}
|
||||
</update>
|
||||
|
||||
<update id="incrementUploadedChunks">
|
||||
update sys_file_upload_session
|
||||
set uploaded_chunks = uploaded_chunks + 1,
|
||||
update_time = NOW()
|
||||
where upload_id = #{uploadId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteByUploadId" parameterType="String">
|
||||
delete
|
||||
from sys_file_upload_session
|
||||
where upload_id = #{uploadId}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteByUploadIds" parameterType="String">
|
||||
delete from sys_file_upload_session where upload_id in
|
||||
<foreach item="uploadId" collection="array" open="(" separator="," close=")">
|
||||
#{uploadId}
|
||||
</foreach>
|
||||
</delete>
|
||||
</mapper>
|
||||
|
|
@ -26,6 +26,7 @@ import com.microservices.pms.utils.PmsConstants;
|
|||
import com.microservices.pms.utils.PmsGitLinkRequestUrl;
|
||||
import com.microservices.pms.utils.PmsUtils;
|
||||
import com.microservices.system.api.RemoteFileService;
|
||||
import com.microservices.system.api.domain.SimpleSysUser;
|
||||
import com.microservices.system.api.domain.SysUser;
|
||||
import com.microservices.system.api.utils.FeignUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import io.swagger.annotations.ApiModel;
|
|||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.Size;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
|
@ -71,12 +72,18 @@ public class PmsProjectTestcaseDataVo extends BaseEntityVo {
|
|||
@ApiModelProperty(value = "关联产品需求")
|
||||
private PmsProductRequirement productReqSpecs;
|
||||
|
||||
@ApiModelProperty(value = "测试追踪")
|
||||
private String testTracking;
|
||||
|
||||
/**
|
||||
* 用例索引
|
||||
*/
|
||||
@ApiModelProperty(value = "用例索引")
|
||||
private Long index;
|
||||
|
||||
@ApiModelProperty(value = "测试用例标识")
|
||||
private String identifier;
|
||||
|
||||
|
||||
public PmsProjectTestcaseDetailVo toPmsProjectTestcaseDetailVo() {
|
||||
PmsProjectTestcaseDetailVo target = new PmsProjectTestcaseDetailVo();
|
||||
|
|
|
|||
|
|
@ -476,7 +476,7 @@ public class PmsGitLinkRequestUrl extends GitLinkRequestUrl {
|
|||
}
|
||||
|
||||
public static GitLinkRequestUrl GET_ORGANIZATION_PUBLIC_REPO_LIST(String enterpriseIdentifier, JSONObject projectSearchInputVo) throws URISyntaxException {
|
||||
String path = String.format("/api/v1/%s/projects.json", enterpriseIdentifier);
|
||||
String path = String.format("/api/v1/organizations/%s/projects.json", enterpriseIdentifier);
|
||||
return getGitLinkRequestUrl(buildUri(path, projectSearchInputVo));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
<result property="testMethod" column="test_method"/>
|
||||
<result property="terminationConditions" column="termination_conditions"/>
|
||||
<result property="identifier" column="identifier"/>
|
||||
<result property="testTracking" column="test_track"/>
|
||||
<result property="testTracking" column="test_tracking"/>
|
||||
<result property="productReqSpecsId" column="product_req_specs_id"/>
|
||||
<result property="isImport" column="is_import"/>
|
||||
</resultMap>
|
||||
|
|
@ -33,6 +33,7 @@
|
|||
<sql id="selectPmsProjectTestcaseVo">
|
||||
select id,
|
||||
title,
|
||||
test_tracking,
|
||||
assignee_gitlink_id,
|
||||
pms_project_id,
|
||||
type_id,
|
||||
|
|
@ -186,7 +187,7 @@
|
|||
<if test="testMethod != null">test_method = #{testMethod},</if>
|
||||
<if test="terminationConditions != null">termination_conditions = #{terminationConditions},</if>
|
||||
<if test="identifier != null">identifier = #{identifier},</if>
|
||||
<if test="testTracking != null">#{testTracking},</if>
|
||||
<if test="testTracking != null">test_tracking = #{testTracking},</if>
|
||||
<if test="productReqSpecsId != null">#{productReqSpecsId},</if>
|
||||
<if test="isImport != null">#{isImport},</if>
|
||||
</trim>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.microservices.system.controller;
|
||||
|
||||
import com.microservices.common.core.constant.SecurityConstants;
|
||||
import com.microservices.common.core.domain.R;
|
||||
import com.microservices.common.core.exception.ServiceException;
|
||||
import com.microservices.common.core.utils.StringUtils;
|
||||
|
|
@ -203,6 +204,26 @@ public class SysUserController extends BaseController {
|
|||
return R.ok(sysUser);
|
||||
}
|
||||
|
||||
@InnerAuth
|
||||
@GetMapping("/getSysUserListByUserIds")
|
||||
R<List<SysUser>> getSysUserListByUserIds(@RequestParam("userIds") Long[] userIds) {
|
||||
List<SysUser> sysUserList = userService.selectUserListFromUserIds(userIds);
|
||||
return R.ok(sysUserList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过用户ID数组批量获取用户信息(POST方法,解决URL长度限制)
|
||||
*
|
||||
* @param userIds 用户ID数组
|
||||
* @return 用户列表
|
||||
*/
|
||||
@InnerAuth
|
||||
@PostMapping("/getSysUserListByUserIds")
|
||||
R<List<SysUser>> postSysUserListByUserIds(@RequestBody Long[] userIds) {
|
||||
List<SysUser> sysUserList = userService.selectUserListFromUserIds(userIds);
|
||||
return R.ok(sysUserList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过GitLinkToken获取本系统Token
|
||||
*
|
||||
|
|
|
|||
|
|
@ -289,6 +289,24 @@ public class SysUserDeptRoleController extends BaseController {
|
|||
return R.ok(userInDeptHasRole);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否在指定部门或父级部门下具有指定菜单权限
|
||||
*
|
||||
* @param deptId 组织id
|
||||
* @param userId 用户id
|
||||
* @param perms 菜单权限
|
||||
* @return 用户是否指定部门或父级部门下具有菜单权限 是true 否false
|
||||
*/
|
||||
@ApiOperation(value = "检查用户是否在指定部门或父级部门下具有指定菜单权限", hidden = true)
|
||||
@InnerAuth
|
||||
@GetMapping("/checkUserInDeptOrParentDeptHasPerms")
|
||||
public R<Boolean> checkUserInDeptOrParentDeptHasPerms(@ApiParam(name = "deptId", value = "组织id", required = true) Long deptId,
|
||||
@ApiParam(name = "userId", value = "用户id", required = true) Long userId,
|
||||
@ApiParam(name = "perms", value = "菜单权限", required = true) String perms) {
|
||||
Boolean userInDeptHasRole = sysUserDeptRoleService.checkUserInDeptOrParentDeptHasPerms(deptId, userId, perms);
|
||||
return R.ok(userInDeptHasRole);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否在指定部门下
|
||||
*
|
||||
|
|
@ -304,4 +322,12 @@ public class SysUserDeptRoleController extends BaseController {
|
|||
Boolean userInDept = sysUserDeptRoleService.checkUserInDept(currentDeptId, userId);
|
||||
return R.ok(userInDept);
|
||||
}
|
||||
@ApiOperation(value = "用户是否在指定部门下", hidden = true)
|
||||
@InnerAuth
|
||||
@GetMapping("/getSysUserDeptRoleByDeptIdAndUserId")
|
||||
public R<SysUserDeptRole> getSysUserDeptRoleByDeptIdAndUserId(@ApiParam(name = "deptId", value = "组织id", required = true) Long deptId,
|
||||
@ApiParam(name = "userId", value = "用户id", required = true) Long userId) {
|
||||
SysUserDeptRole userIdentifier = sysUserDeptRoleService.getSysUserDeptRoleByDeptIdAndUserId(deptId, userId);
|
||||
return R.ok(userIdentifier);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,4 +151,6 @@ public interface SysUserDeptRoleMapper {
|
|||
* @param state 状态
|
||||
*/
|
||||
void updateUserIdentifyStateByUserId(@Param("userId") Long userId, @Param("state") String state);
|
||||
|
||||
int selectUserInDeptOrParentDeptHasPermsCount(@Param("deptId") Long deptId, @Param("userId") Long userId, @Param("perms") String perms);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,4 +131,6 @@ public interface SysUserMapper
|
|||
public SysUser checkEmailUnique(String email);
|
||||
|
||||
SysUser selectUserByGitLinkUserId(Long gitLinkUserId);
|
||||
|
||||
List<SysUser> selectUserListFromUserIds(Long[] ids);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,4 +160,8 @@ public interface ISysUserDeptRoleService {
|
|||
* @param userId 用户Id
|
||||
*/
|
||||
void deleteSysUserDeptRoleByDeleteUser(@Param("userId") Long userId);
|
||||
|
||||
Boolean checkUserInDeptOrParentDeptHasPerms(Long deptId, Long userId, String perms);
|
||||
|
||||
SysUserDeptRole getSysUserDeptRoleByDeptIdAndUserId(Long deptId, Long userId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -222,4 +222,6 @@ public interface ISysUserService
|
|||
String getUserNameByGitLinkToken(String gitLinkToken);
|
||||
|
||||
SysUser gitlinkUserToUpdateSysUser(SysUser sysUser, ForgeUser forgeUser);
|
||||
|
||||
List<SysUser> selectUserListFromUserIds(Long[] userIds);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,8 +83,8 @@ public class SysUserDeptRoleServiceImpl implements ISysUserDeptRoleService {
|
|||
* 查询用户与部门及部门内角色关联列表
|
||||
*
|
||||
* @param userName 用户名
|
||||
* @param deptId 组织Id
|
||||
* @param roleId 角色Id
|
||||
* @param deptId 组织Id
|
||||
* @param roleId 角色Id
|
||||
* @return 用户与部门及部门内角色关联
|
||||
*/
|
||||
@Override
|
||||
|
|
@ -124,7 +124,10 @@ public class SysUserDeptRoleServiceImpl implements ISysUserDeptRoleService {
|
|||
}
|
||||
for (Long userId : userIds) {
|
||||
SysUserDeptRole userIdentify = sysUserDeptRoleMapper.selectSysUserDeptRoleByUserIdAndDeptId(userId, deptId);
|
||||
updateUserGitLinkRole(userId, deptId, userIdentify.getRoleId(), null, OPERATE_REMOVE);
|
||||
if (userIdentify != null) {
|
||||
updateUserGitLinkRole(userId, deptId, userIdentify.getRoleId(), null, OPERATE_REMOVE);
|
||||
}
|
||||
|
||||
}
|
||||
int success = deleteUserIdentityByDeptIdAndUserIds(sysDept, userIds);
|
||||
for (Long userId : userIds) {
|
||||
|
|
@ -166,8 +169,11 @@ public class SysUserDeptRoleServiceImpl implements ISysUserDeptRoleService {
|
|||
if (sysUser == null) {
|
||||
return;
|
||||
}
|
||||
String redisKey = CacheConstants.getUserIdentifyListKey(sysUser.getUserName());
|
||||
// 删除缓存中该用户身份列表信息
|
||||
redisService.deleteObject(CacheConstants.getUserIdentifyListKey(sysUser.getUserName()));
|
||||
redisService.deleteObject(CacheConstants.getUserIdentifyListKey(redisKey));
|
||||
List<SysUserDeptRole> userIdentifyList = selectSysUserDeptRoleListByUserName(sysUser.getUserName());
|
||||
redisService.setCacheObject(redisKey, userIdentifyList);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -231,8 +237,13 @@ public class SysUserDeptRoleServiceImpl implements ISysUserDeptRoleService {
|
|||
if (secondaryDeptIds == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!secondaryDeptIds.contains(dept.getDeptId())) {
|
||||
SysDept parentDept = sysDeptMapper.selectDeptById(dept.getParentId());
|
||||
if (parentDept == null) {
|
||||
return;
|
||||
}
|
||||
if (dept.getParentId() == 0L || (parentDept.getParentId() == 0L && dept.getDeptType() == CacheConstants.DEPT_TYPE_TSZQ)) {
|
||||
remoteCmsService.changeUserOrgRole(userId, operate, SecurityConstants.INNER);
|
||||
} else if (!secondaryDeptIds.contains(dept.getDeptId())) {
|
||||
if (secondaryDeptIds.contains(dept.getParentId())) {
|
||||
switch (dept.getDeptType()) {
|
||||
case CacheConstants.DEPT_TYPE_TSZQ: {
|
||||
|
|
@ -568,6 +579,17 @@ public class SysUserDeptRoleServiceImpl implements ISysUserDeptRoleService {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean checkUserInDeptOrParentDeptHasPerms(Long deptId, Long userId, String perms) {
|
||||
int hasPermsCount = sysUserDeptRoleMapper.selectUserInDeptOrParentDeptHasPermsCount(deptId, userId, perms);
|
||||
return hasPermsCount > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SysUserDeptRole getSysUserDeptRoleByDeptIdAndUserId(Long deptId, Long userId) {
|
||||
return sysUserDeptRoleMapper.selectSysUserDeptRoleByUserIdAndDeptId(userId, deptId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean checkUserInDept(Long currentDeptId, Long userId) {
|
||||
SysUserDeptRole sysUserDeptRole = sysUserDeptRoleMapper.selectSysUserDeptRoleByUserIdAndDeptId(userId, currentDeptId);
|
||||
|
|
|
|||
|
|
@ -37,10 +37,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import javax.validation.Validator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
|
|
@ -684,4 +681,9 @@ public class SysUserServiceImpl implements ISysUserService {
|
|||
return sysUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SysUser> selectUserListFromUserIds(Long[] userIds) {
|
||||
return userMapper.selectUserListFromUserIds(userIds);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,19 +121,24 @@ public class SyncGitLinkUserInfoJob {
|
|||
for (ForgeUserAction forgeUserAction : forgeUserActionList) {
|
||||
String userActionDataBank = forgeUserAction.getDataBank();
|
||||
if (StringUtils.isNotEmpty(userActionDataBank)) {
|
||||
JSONObject jsonObject = JSON.parseObject(userActionDataBank);
|
||||
if (jsonObject != null) {
|
||||
Long userId = jsonObject.getLong("id");
|
||||
if (userId != null) {
|
||||
SysUser sysUser = sysUserMapper.selectUserByGitLinkUserId(forgeUserAction.getUserId());
|
||||
if (sysUser != null && UserConstants.DEL_FLAG_FALSE.equals(sysUser.getDelFlag())) {
|
||||
log.info("检测到用户{}在Forge端已注销,将该用户在微服务平台的所有身份标记为已注销同时逻辑删除该用户", sysUser.getUserName());
|
||||
// 更新用户所有身份为已注销
|
||||
sysUserDeptRoleService.deleteSysUserDeptRoleByDeleteUser(sysUser.getUserId());
|
||||
// 逻辑删除该用户
|
||||
sysUserService.deleteUserById(sysUser.getUserId());
|
||||
try {
|
||||
JSONObject jsonObject = JSON.parseObject(userActionDataBank);
|
||||
if (jsonObject != null) {
|
||||
Long userId = jsonObject.getLong("id");
|
||||
if (userId != null) {
|
||||
SysUser sysUser = sysUserMapper.selectUserByGitLinkUserId(forgeUserAction.getUserId());
|
||||
if (sysUser != null && UserConstants.DEL_FLAG_FALSE.equals(sysUser.getDelFlag())) {
|
||||
log.info("检测到用户{}在Forge端已注销,将该用户在微服务平台的所有身份标记为已注销同时逻辑删除该用户", sysUser.getUserName());
|
||||
// 更新用户所有身份为已注销
|
||||
sysUserDeptRoleService.deleteSysUserDeptRoleByDeleteUser(sysUser.getUserId());
|
||||
// 逻辑删除该用户
|
||||
sysUserService.deleteUserById(sysUser.getUserId());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("解析Forge用户操作JSON失败,forgeUserActionId: {}, userId: {}, dataBank: {}, 错误信息: {}",
|
||||
forgeUserAction.getId(), forgeUserAction.getUserId(), userActionDataBank, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -234,6 +234,18 @@
|
|||
and udr.dept_id = #{deptId}
|
||||
</where>
|
||||
</select>
|
||||
<select id="selectUserInDeptOrParentDeptHasPermsCount" resultType="java.lang.Integer">
|
||||
SELECT count(DISTINCT sm.menu_id)
|
||||
FROM sys_menu sm
|
||||
INNER JOIN sys_role_menu srm ON sm.menu_id = srm.menu_id
|
||||
INNER JOIN sys_user_dept_role sudr ON srm.role_id = sudr.role_id
|
||||
CROSS JOIN (SELECT CONCAT(ancestors, ',', dept_id) AS dept_ancestors
|
||||
FROM sys_dept
|
||||
WHERE dept_id = #{deptId}) vars
|
||||
WHERE sudr.user_id = #{userId}
|
||||
AND FIND_IN_SET(sudr.dept_id, vars.dept_ancestors)
|
||||
AND sm.perms = #{perms};
|
||||
</select>
|
||||
|
||||
<insert id="insertSysUserDeptRole" parameterType="com.microservices.system.api.domain.SysUserDeptRole">
|
||||
insert into sys_user_dept_role
|
||||
|
|
|
|||
|
|
@ -200,8 +200,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
where u.gitlink_user_id = #{gitLinkUserId}
|
||||
limit 1
|
||||
</select>
|
||||
<select id="selectUserListFromUserIds" resultMap="SysUserResult">
|
||||
<include refid="selectOnlyUserVo"/>
|
||||
where user_id in
|
||||
<foreach item="id" collection="array" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</select>
|
||||
|
||||
<insert id="insertUser" parameterType="SysUser" useGeneratedKeys="true" keyProperty="userId">
|
||||
<insert id="insertUser" parameterType="SysUser" useGeneratedKeys="true" keyProperty="userId">
|
||||
insert into sys_user(
|
||||
<if test="userId != null and userId != 0">user_id,</if>
|
||||
<if test="gitlinkUserId != null and gitlinkUserId != 0">gitlink_user_id,</if>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue