Compare commits

..

1 Commits

Author SHA1 Message Date
wanjia 89cf31f69a feat(dms):excel导入校企成果,支持图片 2026-03-03 15:36:03 +08:00
220 changed files with 1091 additions and 14374 deletions

View File

@ -9,8 +9,6 @@ import com.microservices.system.api.factory.RemoteCmsFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 用户服务
*
@ -160,22 +158,4 @@ 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);
}

View File

@ -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,17 +143,6 @@ 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);
/**
* 检查用户是否在指定部门下
*
@ -163,7 +152,4 @@ 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);
}

View File

@ -14,6 +14,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 文件服务
@ -34,6 +35,17 @@ public interface RemoteFileService {
@RequestParam("hierarchy") String hierarchy,
@RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* Base64 图片上传HashMap 版本
*/
@PostMapping(value = "/upload/base64")
R<SysFileInfo> uploadBase64(
@RequestBody Map<String, Object> params,
@RequestParam("type") String type,
@RequestParam("hierarchy") String hierarchy,
@RequestHeader(SecurityConstants.FROM_SOURCE) String source
);
/**
* 根据文件Id列表获取文件列表
*

View File

@ -57,15 +57,6 @@ 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);
/**
* 组织下新增用户身份
@ -73,7 +64,7 @@ public interface RemoteUserService {
* @param deptId
* @param userId
* @param roleKey
* @param source 请求来源
* @param source 请求来源
* @return 结果
*/
@PostMapping("/SysUserDeptRole/dept/{deptId}/user/{userId}/role/{roleKey}")
@ -83,13 +74,14 @@ 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}")
@ -99,16 +91,18 @@ 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
*
@ -129,6 +123,7 @@ public interface RemoteUserService {
@GetMapping("/SysUserDeptRole/userByUserName/{userName}/list")
public R<List<SysUserDeptRole>> getSysUserDeptRoleListByUserName(@PathVariable("userName") String userName, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 通过用户名查询用户下第一个身份
*
@ -200,7 +195,7 @@ public interface RemoteUserService {
/**
* 通过GitLink用户Id创建用户
*
* @param gitLinkUserId GitLinkUserId
* @param gitLinkUserId GitLink用户Id
* @param source 请求来源
* @return 系统用户
*/
@ -211,7 +206,7 @@ public interface RemoteUserService {
* 根据角色Key获取菜单权限集合
*
* @param roleKey 角色Key
* @param source 请求来源
* @param source 请求来源
* @return 菜单权限集合
*/
@GetMapping("/menu/roleMenuPermissionByRoleKey/{roleKey}")

View File

@ -9,8 +9,6 @@ import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 内容管理服务降级处理
*
@ -85,16 +83,6 @@ 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());
}
};
}
}

View File

@ -83,20 +83,10 @@ 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());
}
};
}
}

View File

@ -14,6 +14,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 文件服务降级处理
@ -36,6 +37,11 @@ public class RemoteFileFallbackFactory implements FallbackFactory<RemoteFileServ
return R.fail("上传文件失败:" + throwable.getMessage());
}
@Override
public R<SysFileInfo> uploadBase64(Map<String, Object> params, String type, String hierarchy, String source) {
return R.fail("上传base64文件失败:" + throwable.getMessage());
}
@Override
public R<List<SysFileInfo>> getFileList(String fileIds) {
return R.fail("获取文件列表失败:" + throwable.getMessage());

View File

@ -52,12 +52,6 @@ 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());

View File

@ -0,0 +1,62 @@
package com.microservices.system.api.utils;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
public class StandardMultipartFile implements MultipartFile {
private final byte[] content;
private final String name;
private final String originalFilename;
private final String contentType;
public StandardMultipartFile(byte[] content, String name, String originalFilename, String contentType) {
this.content = content != null ? content : new byte[0];
this.name = name;
this.originalFilename = originalFilename;
this.contentType = contentType;
}
@Override
public String getName() {
return name;
}
@Override
public String getOriginalFilename() {
return originalFilename;
}
@Override
public String getContentType() {
return contentType;
}
@Override
public boolean isEmpty() {
return content.length == 0;
}
@Override
public long getSize() {
return content.length;
}
@Override
public byte[] getBytes() throws IOException {
return content;
}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(content);
}
@Override
public void transferTo(File dest) throws IOException, IllegalStateException {
try (FileOutputStream fos = new FileOutputStream(dest)) {
fos.write(content);
}
}
}

View File

@ -80,10 +80,6 @@ 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";
/**
* 内容管理同步缓存前缀
*/
@ -249,18 +245,6 @@ 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代表打包失败需要重新打包
*/

View File

@ -5,7 +5,8 @@ package com.microservices.common.core.constant;
*
* @author microservices
*/
public class UserConstants {
public class UserConstants
{
/**
* 平台内系统用户的唯一标志
*/
@ -27,89 +28,55 @@ 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;
@ -155,28 +122,4 @@ 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";
}

View File

@ -8,7 +8,7 @@ package com.microservices.common.core.enums;
public enum DocAndZoneOperation
{
OPERATION_CREATE("创建", "create"), OPERATION_UPDATE("更新", "update"),
OPERATION_DELETE("删除", "delete"), OPERATION_NON_REPO_UPDATE("非仓库更新", "nonRepoUpdate");
OPERATION_DELETE("删除", "delete");
private final String operationName;
private final String operationCode;

View File

@ -13,17 +13,19 @@ import com.microservices.common.core.utils.reflect.ReflectUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDataValidation;
import org.apache.poi.xssf.usermodel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
@ -1458,4 +1460,288 @@ public class ExcelUtil<T>
}
return method;
}
// 存储Excel中的图片key: 行号_列号value: MultipartFile
private final Map<String, MultipartFile> excelImageMap = new HashMap<>();
/**
* 获取解析后的Excel图片映射表行号_列号 -> MultipartFile
* <p>对外返回不可修改的Map避免外部篡改同时让静态检查识别到该集合被查询</p>
* @return 图片映射表
*/
public Map<String, MultipartFile> getExcelImageMap() {
// 返回不可修改的Map提升代码安全性同时让静态检查识别到集合被读取
return Collections.unmodifiableMap(excelImageMap);
}
/**
* 解析Excel中的图片并转换为MultipartFile存入{@link #excelImageMap}
* @param sheet Excel工作表
* @throws IOException 解析图片失败时抛出
*/
private void parseExcelImages(Sheet sheet) throws IOException {
if (wb instanceof XSSFWorkbook) {
parseXSSFImages((XSSFSheet) sheet);
} else if (wb instanceof HSSFWorkbook) {
parseHSSFImages((HSSFWorkbook) wb, sheet);
}
}
/**
* 解析XSSF格式xlsx图片
*/
private void parseXSSFImages(XSSFSheet sheet) {
XSSFDrawing drawing = sheet.createDrawingPatriarch();
List<XSSFShape> shapes = drawing.getShapes();
for (XSSFShape shape : shapes) {
if (shape instanceof XSSFPicture) {
XSSFPicture picture = (XSSFPicture) shape;
XSSFPictureData pictureData = picture.getPictureData();
XSSFClientAnchor anchor = picture.getPreferredSize();
// 获取图片所在单元格位置
int row = anchor.getRow1();
int col = anchor.getCol1();
String cellKey = row + "_" + col;
// 转换为MultipartFile并存储
MultipartFile multipartFile = bytesToMultipartFile(pictureData.getData(), "xlsx_image.png");
excelImageMap.put(cellKey, multipartFile);
}
}
}
/**
* 解析HSSF格式xls图片
*/
private void parseHSSFImages(HSSFWorkbook workbook, Sheet sheet) {
List<HSSFPictureData> pictureDataList = workbook.getAllPictures();
for (int i = 0; i < pictureDataList.size(); i++) {
HSSFPictureData pictureData = pictureDataList.get(i);
HSSFPatriarch patriarch = (HSSFPatriarch) sheet.createDrawingPatriarch();
List<HSSFShape> shapes = patriarch.getChildren();
for (HSSFShape shape : shapes) {
if (shape instanceof HSSFPicture) {
HSSFPicture picture = (HSSFPicture) shape;
if (picture.getPictureIndex() == i) {
HSSFClientAnchor anchor = picture.getClientAnchor();
int row = anchor.getRow1();
int col = anchor.getCol1();
String cellKey = row + "_" + col;
// 转换为MultipartFile并存储
MultipartFile multipartFile = bytesToMultipartFile(pictureData.getData(), "xls_image.png");
excelImageMap.put(cellKey, multipartFile);
break;
}
}
}
}
}
/**
* 自定义MultipartFile实现通用版无外部依赖
*/
public static class CustomMultipartFile implements MultipartFile {
private final byte[] content;
private final String filename;
private final String contentType;
public CustomMultipartFile(byte[] content, String filename, String contentType) {
this.content = content;
this.filename = filename;
this.contentType = contentType;
}
@Override
public String getName() {
return "file";
}
@Override
public String getOriginalFilename() {
return filename;
}
@Override
public String getContentType() {
return contentType;
}
@Override
public boolean isEmpty() {
return content == null || content.length == 0;
}
@Override
public long getSize() {
return content.length;
}
@Override
public byte[] getBytes() throws IOException {
return content;
}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(content);
}
@Override
public void transferTo(java.io.File dest) throws IOException, IllegalStateException {
try (java.io.OutputStream os = new java.io.FileOutputStream(dest)) {
os.write(content);
}
}
}
/**
* 将字节数组转换为MultipartFile核心工具方法
*/
public MultipartFile bytesToMultipartFile(byte[] bytes, String fileName) {
try {
String suffix = "png";
if (fileName != null && fileName.contains(".")) {
suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
}
String finalFileName = UUID.randomUUID().toString() + "." + suffix;
String contentType = "image/" + suffix;
return new CustomMultipartFile(bytes, finalFileName, contentType);
} catch (Exception e) {
throw new RuntimeException("转换图片为MultipartFile失败", e);
}
}
// 导入包含图片的excel
public List<T> importExcelWithImages(String sheetName, InputStream is, int titleNum) throws Exception {
this.type = Type.IMPORT;
this.wb = WorkbookFactory.create(is);
List<T> list = new ArrayList<T>();
Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0);
if (sheet == null) {
throw new IOException("文件sheet不存在");
}
// 1. 解析Excel中的图片生成imageMap行号_列号 -> MultipartFile
parseExcelImages(sheet);
int rows = sheet.getLastRowNum();
if (rows > 0) {
// ========== 参考原有逻辑构建表头列名和下标映射 ==========
Map<String, Integer> cellMap = new HashMap<>();
Row heard = sheet.getRow(titleNum);
for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) {
Cell cell = heard.getCell(i);
if (StringUtils.isNotNull(cell)) {
String columnName = this.getCellValue(heard, i).toString().trim();
cellMap.put(columnName, i);
} else {
cellMap.put(null, i);
}
}
// ========== 参考原有逻辑构建字段映射通过Excel注解匹配列名 ==========
List<Object[]> fields = this.getFields();
Map<Integer, Object[]> fieldsMap = new HashMap<Integer, Object[]>();
for (Object[] objects : fields) {
Excel attr = (Excel) objects[1];
Integer column = cellMap.get(attr.name());
if (column != null) {
fieldsMap.put(column, objects);
}
}
// 找到图片列的下标关键和Excel模板的列名对应
Integer imageColumnIndex = cellMap.get("图片");
// ========== 遍历数据行参考原有逻辑 ==========
for (int i = titleNum + 1; i <= rows; i++) {
Row row = sheet.getRow(i);
if (isRowEmpty(row)) {
continue;
}
T entity = null;
// 生成当前行的图片key行号_图片列下标i是数据行号对应Excel的行
String imageCellKey = i + "_" + imageColumnIndex;
// ========== 参考原有逻辑遍历字段映射赋值 ==========
for (Map.Entry<Integer, Object[]> entry : fieldsMap.entrySet()) {
Integer columnIndex = entry.getKey();
Object val = this.getCellValue(row, columnIndex);
// 如果不存在实例则新建
entity = (entity == null ? clazz.newInstance() : entity);
// 从map中得到对应列的field和Excel注解
Field field = (Field) entry.getValue()[0];
Excel attr = (Excel) entry.getValue()[1];
// ========== 关键修改如果是图片替换值为图片key ==========
if (imageColumnIndex != null && columnIndex.equals(imageColumnIndex)) {
val = imageCellKey; // 将单元格原始值替换为图片key如1_12
}
// ========== 原有类型转换逻辑完全复用 ==========
Class<?> fieldType = field.getType();
if (String.class == fieldType) {
String s = Convert.toStr(val);
if (StringUtils.endsWith(s, ".0")) {
val = StringUtils.substringBefore(s, ".0");
} else {
String dateFormat = field.getAnnotation(Excel.class).dateFormat();
if (StringUtils.isNotEmpty(dateFormat)) {
val = parseDateToStr(dateFormat, val);
} else {
val = Convert.toStr(val);
}
}
} else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) {
val = Convert.toInt(val);
} else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) {
val = Convert.toLong(val);
} else if (Double.TYPE == fieldType || Double.class == fieldType) {
val = Convert.toDouble(val);
} else if (Float.TYPE == fieldType || Float.class == fieldType) {
val = Convert.toFloat(val);
} else if (BigDecimal.class == fieldType) {
val = Convert.toBigDecimal(val);
} else if (Date.class == fieldType) {
if (val instanceof String) {
val = DateUtils.parseDate(val);
} else if (val instanceof Double) {
val = DateUtil.getJavaDate((Double) val);
}
} else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) {
val = Convert.toBool(val, false);
}
// ========== 原有反射赋值逻辑完全复用 ==========
if (StringUtils.isNotNull(fieldType)) {
String propertyName = field.getName();
if (StringUtils.isNotEmpty(attr.targetAttr())) {
propertyName = field.getName() + "." + attr.targetAttr();
} else if (StringUtils.isNotEmpty(attr.readConverterExp())) {
val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
} else if (!attr.handler().equals(ExcelHandlerAdapter.class)) {
val = dataFormatHandlerAdapter(val, attr);
}
// 使用原有工具类反射赋值核心替换自定义setEntityFieldValue
ReflectUtils.invokeSetter(entity, propertyName, val);
}
}
if (entity != null) {
list.add(entity);
}
}
}
return list;
}
}

View File

@ -211,17 +211,6 @@ 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请求
*

View File

@ -115,7 +115,8 @@ public class AuthLogic {
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean hasPermi(String permission) {
public boolean hasPermi(String permission)
{
return hasPermi(tokenService.getMenuPermissions(), permission);
}
@ -125,8 +126,10 @@ 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);
}
}
@ -150,10 +153,13 @@ 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);
}
}
@ -164,10 +170,13 @@ 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;
}
}
@ -182,7 +191,8 @@ public class AuthLogic {
* @param role 角色标识
* @return 用户是否具备某角色
*/
public boolean hasRole(String role) {
public boolean hasRole(String role)
{
return hasRole(tokenService.getRolePermissions(), role);
}
@ -215,10 +225,13 @@ 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);
}
}
@ -229,14 +242,18 @@ 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);
}
}
@ -306,9 +323,6 @@ 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)) {

View File

@ -11,7 +11,6 @@ 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;
@ -39,8 +38,6 @@ public class TokenService {
private RedisService redisService;
@Autowired
private RemoteUserService remoteUserService;
@Autowired
private RemoteDeptService remoteDeptService;
protected static final long MILLIS_SECOND = 1000;
@ -332,16 +329,4 @@ 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;
}
}

View File

@ -3,7 +3,6 @@ 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;
@ -18,7 +17,6 @@ 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;
/**
@ -108,13 +106,23 @@ public class CmsDocController extends BaseController {
@ApiImplicitParam(name = "pageNum", value = "当前记录起始索引", paramType = "query", dataType = "Integer"),
@ApiImplicitParam(name = "pageSize", value = "每页显示记录数", paramType = "query", dataType = "Integer"),
})
public GenericsTableDataInfo<CmsDocAllDto> docListByZone(
public TableDataInfo docListByZone(
@ApiParam(name = "zoneId", value = "专区Id") @PathVariable("zoneId") Long zoneId
, CmsDocSearchVo cmsDocSearchVo) {
Long deptId = cmsDocService.getDeptIdByZoneId(zoneId);
cmsDocSearchVo.setIsOpen(true);
cmsDocSearchVo.setIsOpen(false);
startPage();
return cmsDocService.selectAllDocList(deptId, cmsDocSearchVo);
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);
}
/**
@ -156,7 +164,7 @@ public class CmsDocController extends BaseController {
/**
* 新增文章信息
*/
@RequiresPermissions(UserConstants.CMS_PERMS_DOCS_ADD)
@RequiresPermissions("cms:doc:add")
@Log(title = "文章信息", businessType = BusinessType.INSERT)
@PostMapping("/dir/{dirId}")
@ApiOperation("在栏目下创建文章")
@ -193,7 +201,7 @@ public class CmsDocController extends BaseController {
/**
* 删除文章
*/
@RequiresPermissions(UserConstants.CMS_PERMS_DOCS_DELETE)
@RequiresPermissions("cms:doc:remove")
@Log(title = "文章信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
@ApiOperation("删除文章/栏目")
@ -204,13 +212,13 @@ public class CmsDocController extends BaseController {
/**
* 修改文章信息
*/
@RequiresPermissions(UserConstants.CMS_PERMS_DOCS_EDIT)
@RequiresPermissions("cms:doc: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 success(cmsDocService.updateCmsDoc(id, cmsDocDto));
return toAjax(cmsDocService.updateCmsDoc(id, cmsDocDto));
}
/**
@ -223,18 +231,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(UserConstants.CMS_PERMS_DOCS_AUDIT)
@RequiresPermissions("cms:doc: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));
}
@ -307,24 +315,6 @@ 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获取专区下文章阅读数量
*

View File

@ -5,17 +5,12 @@ 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.*;
@ -40,22 +35,6 @@ 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());
}
/**
* 查询栏目列表
*/
@ -89,7 +68,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.selectSetDefaultHeadImgCmsDocList(dirId, cmsDocSearchVo);
List<CmsDocBaseDto> list = cmsDocService.selectCmsDocList(dirId, cmsDocSearchVo);
return getAllGenericsDataTableToPage(list);
}
@ -121,19 +100,6 @@ 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);
}
/**
* 查询首页文章列表
*/
@ -209,9 +175,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("同步消息发送成功");
}

View File

@ -41,9 +41,6 @@ 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,

View File

@ -21,6 +21,9 @@ public class CmsDirUpdateVo {
@ApiModelProperty(value = "顺序")
private Integer order;
@ApiModelProperty(value = "文章头图")
private String headImg;
public CmsDir toCmsDir() {
CmsDir cmsDir = new CmsDir();
cmsDir.setName(name);
return cmsDir;
}
}

View File

@ -3,7 +3,6 @@ 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;
@ -37,27 +36,19 @@ 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;
@ -171,16 +162,10 @@ 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;
@ -224,12 +209,30 @@ public class CmsDoc extends BaseSortEntity {
}
private CmsDocBaseDto getCmsDocBaseDto(CmsDocBaseDto cmsDocDto) {
BeanUtils.copyProperties(this, cmsDocDto);
cmsDocDto.setId(id);
cmsDocDto.setName(name);
cmsDocDto.setHeadImg(headImg);
cmsDocDto.setIsHomepage(isHomepage);
cmsDocDto.setSummary(summary);
cmsDocDto.setEditorType(editorType);
cmsDocDto.setDeptId(deptId);
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;
}

View File

@ -20,10 +20,4 @@ public class CmsDocAuditVo {
*/
@ApiModelProperty(value = "是否重新编辑: 0不允许重新编辑 1允许重新编辑")
private Integer reedit;
/**
* 备注
*/
@ApiModelProperty(value = "审核备注")
private String remark;
}

View File

@ -101,10 +101,6 @@ public class CmsDocBaseDto {
*/
@ApiModelProperty("关键词")
private String keywords;
/**
* 备注
*/
private String remark;
public void setPublishTime(Date publishTime) {
if (publishTime != null) {

View File

@ -83,10 +83,4 @@ public class CmsDocSearchVo {
*/
@ApiModelProperty("关键词")
private String keywords;
/**
* 排除文章Id
*/
@ApiModelProperty(value = "排除文章Id", hidden = true)
private Long excludeId;
}

View File

@ -1,23 +0,0 @@
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;
}

View File

@ -1,7 +1,5 @@
package com.microservices.cms.doc.domain.gitlink;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.exception.ServiceException;
import lombok.Data;
/**
@ -24,12 +22,4 @@ 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();
}
}

View File

@ -2,7 +2,6 @@ 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;
@ -24,14 +23,6 @@ public interface CmsDocMapper {
*/
public CmsDoc selectCmsDocById(Long id);
/**
* 批量查询文章信息
*
* @param ids 文章ID列表
* @return 文章信息列表
*/
List<CmsDoc> selectCmsDocByIds(@Param("ids") List<Long> ids);
/**
* 查询文章信息列表
*
@ -40,17 +31,6 @@ public interface CmsDocMapper {
*/
public List<CmsDoc> selectCmsDocList(CmsDocSearchVo cmsDocSearchVo);
/**
* 查询文章信息列表(设置默认头图为栏目头图)
*
* @param cmsDocSearchVo 文章信息
* @return 文章信息集合
*/
List<CmsDoc> selectCmsDocListSetDefaultHeadImg(CmsDocSearchVo cmsDocSearchVo);
/**
* 新增文章信息
*
@ -91,11 +71,6 @@ 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);
@ -197,50 +172,4 @@ 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
);
}

View File

@ -91,7 +91,7 @@ public interface ICmsAsyncService {
void asyncCreateBranchAndDeleteFileAndCreatePR(CmsDoc newDocWithAudit, CmsProject cmsProject);
void asyncMoveFileToNewDir(CmsProject cmsProject, CmsDoc oldCmsDir, String newCmsDirName);
void asyncMoveFileToNewDir(CmsProject cmsProject, CmsDoc oldCmsDir, CmsDoc newCmsDir);
/**
* 推送文章数据到搜索引擎
@ -99,38 +99,4 @@ 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);
}

View File

@ -5,7 +5,6 @@ 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;
@ -57,9 +56,9 @@ public interface ICmsDocService {
*
* @param id
* @param cmsDocDto 文章信息
* @return 结果 文章id
* @return 结果
*/
public Long updateCmsDoc(Long id, CmsDocDto cmsDocDto);
public boolean updateCmsDoc(Long id, CmsDocDto cmsDocDto);
/**
* 批量删除文章信息
@ -145,15 +144,6 @@ 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);
/**
* 初始化栏目
*
@ -208,8 +198,6 @@ public interface ICmsDocService {
*/
List<CmsDocAllDto> selectCmsDocListByDept(Long deptId, CmsDocSearchVo cmsDocSearchVo);
GenericsTableDataInfo<CmsDocAllDto> selectAllDocList(Long deptId, CmsDocSearchVo cmsDocSearchVo);
/**
* 查询专区下文章数量
*
@ -300,12 +288,4 @@ 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);
}

View File

@ -16,18 +16,14 @@ 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;
@ -35,7 +31,6 @@ 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;
@ -52,7 +47,6 @@ 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;
@ -98,10 +92,7 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
@Autowired
private RemoteZoneService remoteZoneService;
@Autowired
private RemoteUserService remoteUserService;
@Autowired
private RequestConfig config;
/**
* 异步设置文章更新时间
*
@ -118,7 +109,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) {
@ -159,7 +150,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
@ -240,7 +231,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) {
@ -256,27 +247,18 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
}
CmsDoc cmsDoc = cmsDocMapper.selectCmsDocByNameAndDeptId(CmsUtils.getFileNameNoSuffix(entry.getName()), cmsProject.getDeptId(), dirName);
if (cmsDoc == null) {
// 防重按文章名+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());
}
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) {
@ -375,7 +357,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());
//获取需要新增的文章
@ -401,16 +383,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) {
@ -420,41 +402,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);
}
@ -487,12 +469,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));
@ -502,14 +484,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);
}
@ -518,7 +500,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);
@ -632,12 +614,11 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
100, "..."), tmpBranchName);
} catch (ServiceException e) {
//忽略重复创建PR异常
if (!e.getMessage().contains("合并请求已存在")) {
if (!"请求GitLink失败(合并请求已存在)".equals(e.getMessage())) {
throw new ServiceException(e.getMessage());
}
}
cmsDocService.updateCmsCommonDatabase(cmsDoc, tmpBranchName);
}
@ -697,7 +678,6 @@ 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) {
@ -738,8 +718,8 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
if (isChangeName || isChangeDir) {
oldCmsDoc = renameAndChangeDirCmsDoc(oldCmsDoc, cmsProject, newName, newDirName, branchName);
// 立即更新数据库中的dirName/filePath避免GitLink Webhook回调时查不到记录导致重复INSERT
cmsDocMapper.updateCmsDoc(oldCmsDoc);
//todo 此处是否需要更新数据库待讨论
//updateCmsCommonDatabase(oldCmsDoc);
}
boolean isChangeContent = false;
@ -891,87 +871,27 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
}
@Override
public void asyncMoveFileToNewDir(CmsProject cmsProject, CmsDoc oldCmsDir, String newCmsDirName) {
@Async
public void asyncMoveFileToNewDir(CmsProject cmsProject, CmsDoc oldCmsDir, CmsDoc newCmsDir) {
try {
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);
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(), newCmsDirName, e.getMessage());
throw new ServiceException("重命名栏目失败(原栏目名称[" + oldCmsDir.getName() + "],新栏目名称[" + newCmsDirName + "])");
logger.error("重命名栏目失败(原栏目名称[{}],新栏目名称[{}]){}", oldCmsDir.getName(), newCmsDir.getName(), e.getMessage());
throw new ServiceException("重命名栏目失败(原栏目名称[" + oldCmsDir.getName() + "],新栏目名称[" + newCmsDir.getName() + "])");
} finally {
remoteZoneService.lockCmsByDeptId(cmsProject.getDeptId(), 0, SecurityConstants.INNER);
}
// 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) {
@ -1018,23 +938,6 @@ 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);
@ -1087,127 +990,4 @@ 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);
}
}
}

View File

@ -15,7 +15,6 @@ 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;
@ -24,12 +23,10 @@ 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;
@ -50,11 +47,7 @@ import javax.servlet.http.HttpServletRequest;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@ -86,8 +79,6 @@ public class CmsDocServiceImpl implements ICmsDocService {
@Autowired
private RemoteDeptService remoteDeptService;
@Autowired
private TokenService tokenService;
@Autowired
@Lazy
private ICmsDocCommentService cmsDocCommentService;
@Value("${cms.detailPath:/newdetail}")
@ -97,7 +88,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
* 查询文章信息
*
* @param id 文章信息主键
* @param isOpen 是否公开访问
* @param isOpen
* @return 文章信息
*/
@Override
@ -112,14 +103,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(cmsDoc.getDeptId());
// 确定查询文件的分支已发布文章或非仓库改动使用 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();
}
String branchName = DocAuditStatus.AUDIT_PASS.getAuditCode().equals(cmsDoc.getAuditStatus()) ? CmsConstants.BRANCH_MASTER : cmsDoc.getAuditTmpBranchName();
FileEntryDto fileEntryDto = getFileEntryDto(cmsProject, cmsDoc.getFilePath(), branchName);
EntryDto entryDto = fileEntryDto.getEntries();
@ -130,7 +114,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
if (cmsDoc.getPublishTime() != null && cmsDoc.getPublishTime().after(cmsDoc.getCreateTime())) {
cmsDocDto.setIsFirstPublish(false);
}
if (isOpen && DocAuditStatus.AUDIT_PASS.getAuditCode().equals(cmsDoc.getAuditStatus())) {
if (isOpen) {
updateVisits(cmsDocDto.getId());
}
return cmsDocDto;
@ -186,21 +170,6 @@ 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);
@ -228,21 +197,20 @@ 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());
String newCmsDirName = cmsDirUpdateVo.getName();
CmsDoc newCmsDir = oldCmsDir.copyToRenameCmsDir(cmsDirUpdateVo.getName());
if (StringUtils.isNotEmpty(newCmsDirName) && !newCmsDirName.equals(oldCmsDir.getName())) {
CmsDoc cmsDir = cmsDocMapper.selectCmsDocDirByNameAndDeptId(newCmsDirName, oldCmsDir.getDeptId());
if (StringUtils.isNotEmpty(newCmsDir.getName()) && !newCmsDir.getName().equals(oldCmsDir.getName())) {
CmsDoc cmsDir = cmsDocMapper.selectCmsDocDirByNameAndDeptId(newCmsDir.getName(), oldCmsDir.getDeptId());
if (cmsDir != null) {
throw new ServiceException("栏目名称已存在,不允许进行重命名(新栏目名称[" + newCmsDirName + "])");
throw new ServiceException("栏目名称已存在,不允许进行重命名(新栏目名称[" + newCmsDir.getName() + "])");
}
cmsAsyncService.asyncMoveFileToNewDir(cmsProject, oldCmsDir, newCmsDir);
}
// 异步修改栏目名称将仓库内原文件夹下所有文章移动到新文件夹下
cmsAsyncService.asyncMoveFileToNewDir(cmsProject, oldCmsDir, newCmsDirName);
}
}
@Override
@ -263,14 +231,14 @@ public class CmsDocServiceImpl implements ICmsDocService {
@Override
public List<CmsDocDetailDto> selectCmsDocDetailList(Long dirId) {
List<CmsDocDetailDto> cmsDocDetailDtoList = new ArrayList<>();
CmsDoc cmsDir = selectCmsDir(dirId);
CmsDoc cmsDir = cmsDocMapper.selectCmsDocDirById(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.selectCmsDocListSetDefaultHeadImg(cmsDocSearchVo);
List<CompletableFuture<CmsDocDetailDto>> futures = new ArrayList<>();
List<CmsDoc> cmsDocList = cmsDocMapper.selectCmsDocList(cmsDocSearchVo);
List<CompletableFuture<CmsDocDetailDto>> futures = new ArrayList();
for (CmsDoc cmsDoc : cmsDocList) {
CompletableFuture<CmsDocDetailDto> future = CompletableFuture.supplyAsync(() -> {
CmsDocDetailDto cmsDocDetailDto = cmsDoc.copyToCmsDocDetailDto();
@ -315,21 +283,6 @@ 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);
@ -342,25 +295,6 @@ 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());
}
/**
* 检查专区资讯是否处于锁定状态若处于则抛出异常
*
@ -390,16 +324,12 @@ public class CmsDocServiceImpl implements ICmsDocService {
checkCmsLockStatus(dirDoc.getDeptId());
CmsDoc cmsDoc = cmsDocInputDto.toCmsDoc(dirDoc);
if (isNoAuditRequired(dirDoc.getDeptId()) || tokenService.hasDeptPerms(dirDoc.getDeptId(), UserConstants.CMS_PERMS_DOCS_AUDIT)) {
if (isNoAuditRequired(dirDoc.getDeptId()) || isCurrentUserZoneAdmin(dirDoc.getDeptId())) {
//文章自动置为审核通过状态值
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
@ -437,9 +367,7 @@ 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())) {
if (StringUtils.isNotEmpty(cmsDoc.getAuditTmpBranchName())) {
branchName = cmsDoc.getAuditTmpBranchName();
}
branchName = cmsDoc.getAuditTmpBranchName();
}
//当删除文章为文件夹时判断该文件夹下是否存在文章若存在需先删除所有文章
JSONObject getSubEntriesResult = gitLinkRequestHelper.doGet(
@ -459,34 +387,9 @@ 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)) {
@ -508,16 +411,8 @@ 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合并请求
String gitLinkPullRequestNumber = cmsDoc.getAuditPrNumber().split("-")[0];
JSONObject result = gitLinkRequestHelper.doPost(
CmsGitLinkRequestUrl.REFUSE_MERGE_PULL_REQUEST(cmsProject, gitLinkPullRequestNumber), null);
JSONObject result = refuseMergePullRequestByGitLink(cmsDoc, cmsProject);
if (result.containsKey(GitLinkConstants.STATUS_KEY)) {
//2.若拒绝GitLink合并请求操作返回正确的状态值,更新数据库
if (result.getInteger(GitLinkConstants.STATUS_KEY).equals(1)) {
@ -564,10 +459,8 @@ public class CmsDocServiceImpl implements ICmsDocService {
if (cmsDocAuditVo.getReedit().equals(0)) {
//删除待审核文章
cmsDocMapper.deleteCmsDocById(cmsDoc.getId());
// 删除分支如果有
if (StringUtils.isNotEmpty(cmsDoc.getAuditTmpBranchName())) {
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
}
//删除分支
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
return;
}
}
@ -579,7 +472,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
int success = deleteCmsDocDatabaseById(ids);
if (success == ids.length) {
//删除临时分支
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
}
}
@ -599,7 +492,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
int success = cmsDocMapper.deleteCmsDocById(newDocId);
if (success > 0) {
//删除临时分支
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
}
}
@ -613,13 +506,11 @@ public class CmsDocServiceImpl implements ICmsDocService {
int success = cmsDocMapper.updateCmsDoc(cmsDoc);
if (success > 0) {
//删除临时分支
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
}
// 推送文章地址到搜索引擎
cmsAsyncService.pushDocDataSearchEngine(cmsDoc);
// 文章审核通过后给作者增加积分异步执行不影响主事务
cmsAsyncService.addPublishDocPointsAsync(cmsDoc.getDeptId(), cmsDoc.getCreateBy(), cmsDoc.getId(), cmsDoc.getName());
}
private JSONObject mergePullRequestByGitLink(CmsProject cmsProject, CmsDoc cmsDoc) {
@ -634,6 +525,12 @@ 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");
@ -655,7 +552,6 @@ public class CmsDocServiceImpl implements ICmsDocService {
throw new ServiceException("计算字符串Sha出错");
}
}
@Override
public void setCmsSort(Integer order, CmsDoc oldCmsDoc, boolean isChangeOnDir, String isHomePage) {
//调整文章/栏目顺序
@ -775,47 +671,11 @@ public class CmsDocServiceImpl implements ICmsDocService {
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Long updateCmsDoc(Long id, CmsDocDto cmsDocDto) {
public boolean updateCmsDoc(Long id, CmsDocDto cmsDocDto) {
CmsDoc oldCmsDoc = cmsDocMapper.selectCmsDocById(id);
checkCmsLockStatus(oldCmsDoc.getDeptId());
if (!hasRepoRelatedChanged(oldCmsDoc, cmsDocDto)) {
// 更新非仓库字段到原文章对象
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();
return cmsDocMapper.updateCmsDoc(oldCmsDoc) > 0;
}
// 当文章名称发生更改时判断文章名是否在当前栏目下已存在
if (StringUtils.isNotEmpty(cmsDocDto.getName()) && !cmsDocDto.getName().equals(oldCmsDoc.getName())) {
@ -828,16 +688,8 @@ 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 (tokenService.hasDeptPerms(oldCmsDoc.getDeptId(), UserConstants.CMS_PERMS_DOCS_AUDIT) || isNoAuditRequired(oldCmsDoc.getDeptId())) {
if (isCurrentUserZoneAdmin(oldCmsDoc.getDeptId()) || isNoAuditRequired(oldCmsDoc.getDeptId())) {
//专区管理员重新编辑并提交审核未通过文章 重新编辑并提交待审核文章时专区不需审核机制
if (!oldCmsDoc.getAuditStatus().equals(DocAuditStatus.AUDIT_PASS.getAuditCode())) {
//文章审核类型update-原文章id 取出原文章id直接更新删除待审核更新类型文章
@ -845,15 +697,14 @@ 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);
directlyInsertAuditedCmsDocWhenZoneHasNoAuditRequired(cmsDocDto, oldCmsDoc);
return oldCmsDoc.getId();
return directlyInsertAuditedCmsDocWhenZoneHasNoAuditRequired(cmsDocDto, oldCmsDoc);
}
}
String branchName = CmsConstants.BRANCH_MASTER;
@ -861,62 +712,25 @@ public class CmsDocServiceImpl implements ICmsDocService {
oldCmsDoc.setAuditStatus(DocAuditStatus.AUDIT_PASS.getAuditCode());
oldCmsDoc.setAuditTmpBranchName(null);
oldCmsDoc.setKeywords(cmsDocDto.getKeywords());
updateCmsCommonDatabase(oldCmsDoc, CmsConstants.BRANCH_MASTER);
return oldCmsDoc.getId();
return updateCmsCommonDatabase(oldCmsDoc, CmsConstants.BRANCH_MASTER) > 0;
} 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 newDocWithAudit.getId();
return true;
}
}
@ -968,10 +782,10 @@ public class CmsDocServiceImpl implements ICmsDocService {
private void deleteTmpBranchIfBranchExist(CmsDoc cmsDoc) {
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(cmsDoc.getDeptId());
try {
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
} catch (ServiceException e) {
//忽略分支不存在异常
if (!"分支不存在!".equals(e.getMessage())) {
if(!"分支不存在!".equals(e.getMessage())) {
throw new ServiceException(e.getMessage());
}
}
@ -999,29 +813,15 @@ 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());
@ -1063,7 +863,6 @@ 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(), "");
}
@ -1150,7 +949,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
List<CmsDoc> cmsDocList = cmsDocMapper.selectHotCmsDocList(cmsDocInput);
if (cmsDocList != null) {
//todo:需检查该文章是否在GitLink中存在
return cmsDocList.stream().map(CmsDoc::copyToHotCmsDocDto).collect(Collectors.toList());
return cmsDocList.stream().map(x -> x.copyToHotCmsDocDto()).collect(Collectors.toList());
}
return new ArrayList<>();
@ -1167,13 +966,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
visits = visits + cmsDoc.getVisits();
}
cmsDoc.setVisits(visits);
int rows = cmsDocMapper.updateCmsDoc(cmsDoc);
if (rows > 0) {
cmsAsyncService.addVisitsDocPointsAsync(cmsDoc.getDeptId(), cmsDoc.getCreateBy(),
cmsDoc.getId(), cmsDoc.getName(), cmsDoc.getVisits());
return true;
}
return false;
return cmsDocMapper.updateCmsDoc(cmsDoc) > 0;
}
@Override
@ -1197,7 +990,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
for (CmsDoc cmsDir : cmsDirList) {
CmsDocSearchVo cmsDocSearchVo = new CmsDocSearchVo();
cmsDocSearchVo.setIsOpen(isOpen);
List<CmsDocBaseDto> cmsDocBaseDtoList = selectSetDefaultHeadImgCmsDocList(cmsDir.getId(), cmsDocSearchVo);
List<CmsDocBaseDto> cmsDocBaseDtoList = selectCmsDocList(cmsDir.getId(), cmsDocSearchVo);
CmsDocOverviewDto cmsDocOverviewDto = new CmsDocOverviewDto();
cmsDocOverviewDto.setId(cmsDir.getId());
cmsDocOverviewDto.setName(cmsDir.getName());
@ -1297,9 +1090,6 @@ 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);
}
@ -1314,7 +1104,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
CmsDocSearchVo cmsDocSearch = new CmsDocSearchVo();
cmsDocSearch.setDeptId(deptId);
cmsDocSearch.setIsHomepage(CmsConstants.TRUE);
List<CmsDoc> cmsDocList = cmsDocMapper.selectCmsDocListSetDefaultHeadImg(cmsDocSearch);
List<CmsDoc> cmsDocList = cmsDocMapper.selectCmsDocList(cmsDocSearch);
List<CmsDocBaseDto> cmsDocBaseDtoList = new ArrayList<>();
for (CmsDoc cmsDoc : cmsDocList) {
CmsDocBaseDto cmsDocBaseDto = cmsDoc.copyToCmsDocBaseDto();
@ -1323,41 +1113,6 @@ 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) {
@ -1504,17 +1259,6 @@ 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);
@ -1524,42 +1268,22 @@ 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())) {
// 非仓库改动类型直接删除
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 (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.AUDIT_FAIL.getAuditCode().equals(cmsDoc.getAuditStatus())) {
return cmsDocMapper.deleteCmsDocById(cmsDoc.getId());
}
// 判读当前用户是否拥有文章直接删除权限
if (tokenService.hasDeptPerms(cmsDoc.getDeptId(), UserConstants.CMS_PERMS_DOCS_AUDIT) //当前用户为专区管理员
if (isCurrentUserZoneAdmin(cmsDoc.getDeptId()) //当前用户为专区管理员
|| isNoAuditRequired(cmsDoc.getDeptId()) //当前专区文章发布无需审核机制
) {
String branchName = CmsConstants.BRANCH_MASTER;
boolean deleteResult = cmsAsyncService.deleteCmsDocForGitLink(cmsDoc, cmsProject, branchName);
if (deleteResult) {
// 拒绝相关合并请求并清理所有临时分支同时删除所有临时申请
List<CmsDoc> tmpDocList = getTmpDocList(cmsDoc);
cmsAsyncService.clearTmpDoc(cmsProject, tmpDocList);
cmsDoc.setAuditStatus(DocAuditStatus.AUDIT_FAIL.getAuditCode());
cmsDoc.setRemark("管理员删除");
return cmsDocMapper.updateCmsDoc(cmsDoc);
return deleteCmsDocDatabaseById(cmsDoc.getId());
}
} else {
//1.创建一条auditType="delete-原文章id",audit_status=2其余字段与待删除文章一致的新文章数据
CmsDoc newDocWithAudit = insertDeletedDocWithAudit(cmsDoc, id);
@ -1569,18 +1293,6 @@ 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()) {

View File

@ -49,16 +49,6 @@ 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);
}
/**
* 查询专区对应项目列表
*/
@ -105,12 +95,8 @@ 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.selectCmsProjectByDeptIdAndNoException(deptId);
if (cmsProject == null) {
return R.ok(null);
} else {
return R.ok(JSONObject.toJSONString(cmsProject));
}
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(deptId);
return R.ok(JSONObject.toJSONString(cmsProject));
}
/**
@ -184,16 +170,4 @@ 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));
}
}

View File

@ -1,21 +0,0 @@
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;
}

View File

@ -2,7 +2,6 @@ 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;
@ -65,25 +64,4 @@ 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);
}

View File

@ -78,10 +78,4 @@ 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);
}

View File

@ -1,24 +1,21 @@
package com.microservices.cms.project.service.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.cms.doc.domain.*;
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.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;
@ -26,7 +23,6 @@ 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;
@ -41,7 +37,6 @@ import java.util.List;
* @author otto
* @date 2023-03-27
*/
@Slf4j
@Service
public class CmsProjectServiceImpl implements ICmsProjectService {
@Autowired
@ -233,75 +228,6 @@ 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, " ", "-");
@ -316,45 +242,6 @@ 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));
@ -373,10 +260,9 @@ public class CmsProjectServiceImpl implements ICmsProjectService {
try {
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.REMOVE_USER_FROM_PROJECT(oldCmsProject), requestBody);
} catch (ServiceException e) {
log.error("将用户从原项目中移除发生异常:{}", e.getMessage());
// if (!e.getMessage().contains("用户为组织成员,请到组织下操作")||!e.getMessage().contains("还不是项目成员")) {
// throw new ServiceException(e.getMessage());
// }
if (!e.getMessage().contains("用户为组织成员,请到组织下操作")) {
throw new ServiceException(e.getMessage());
}
}
}
//将用户加入新的项目

View File

@ -1,43 +0,0 @@
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;
}

View File

@ -1,63 +0,0 @@
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;
}

View File

@ -1,65 +0,0 @@
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;
}

View File

@ -1,20 +0,0 @@
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);
}

View File

@ -1,215 +0,0 @@
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>");
}
}

View File

@ -1,4 +1,4 @@
package com.microservices.cms.service.impl;
package com.microservices.cms.common.service.impl;
import com.microservices.cms.common.service.ISysUserService;
import com.microservices.common.core.constant.SecurityConstants;

View File

@ -25,24 +25,4 @@ 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";
}

View File

@ -12,18 +12,6 @@ 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));
}
/**
* 创建项目
*/
@ -78,7 +66,7 @@ public class CmsGitLinkRequestUrl extends GitLinkRequestUrl {
public static GitLinkRequestUrl DELETE_BRANCH(CmsProject cmsProject, String branchName) {
return getGitLinkRequestUrl(
String.format("/api/v1/%s/%s/branches/delete.json?branch_name=%s", cmsProject.getUsername(), cmsProject.getIdentifier(), branchName)
String.format("/api/v1/%s/%s/branches/%s.json", cmsProject.getUsername(), cmsProject.getIdentifier(), branchName)
);
}
@ -88,12 +76,6 @@ 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)
@ -119,7 +101,7 @@ public class CmsGitLinkRequestUrl extends GitLinkRequestUrl {
}
public static GitLinkRequestUrl ADD_USER_FROM_PROJECT(CmsProject cmsProject) {
return getAdminGitLinkRequestUrl(
return getGitLinkRequestUrl(
String.format("/api/%s/%s/collaborators.json", cmsProject.getUsername(), cmsProject.getIdentifier())
);
}
@ -166,19 +148,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);
@ -215,12 +197,4 @@ 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));
}
}

View File

@ -1,37 +0,0 @@
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("目前处理的人太多了,请稍后再试");
}
);
}

View File

@ -37,7 +37,6 @@
<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">
@ -72,8 +71,7 @@
audit_type,
audit_tmp_branch_name,
keywords,
audit_pr_number,
remark
audit_pr_number
from cms_doc
</sql>
@ -105,103 +103,10 @@
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
cd.is_dir
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"/>
@ -217,19 +122,10 @@
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>
</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">
@ -241,10 +137,6 @@
</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
@ -258,13 +150,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="selectCmsDocAliasSetDefaultHeadImgVo"/>
<include refid="selectListWhereAlias"/>
<include refid="selectCmsDocVo"/>
<include refid="selectListWhere"/>
ORDER BY
cd.visits DESC,
cd.sort DESC,
cd.update_time DESC,
cd.create_time DESC
visits DESC,
sort DESC,
update_time DESC,
create_time DESC
</select>
<select id="selectCmsDocCount" resultType="java.lang.Integer">
@ -285,24 +177,6 @@
<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>
@ -409,15 +283,6 @@
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">
@ -523,7 +388,6 @@
<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>
@ -541,12 +405,6 @@
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
@ -560,70 +418,4 @@
#{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>

View File

@ -159,38 +159,4 @@
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>

View File

@ -7,6 +7,7 @@ import com.microservices.common.core.web.domain.AjaxResult;
import com.microservices.common.core.web.page.TableDataInfo;
import com.microservices.common.log.annotation.Log;
import com.microservices.common.log.enums.BusinessType;
import com.microservices.common.security.utils.SecurityUtils;
import com.microservices.dms.achievementLibrary.domain.SchoolEnterpriseAchievements;
import com.microservices.dms.achievementLibrary.service.ISchoolEnterpriseAchievementsService;
import io.swagger.annotations.Api;
@ -15,9 +16,11 @@ import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
/**
* 校企成果Controller
@ -90,6 +93,21 @@ public class SchoolEnterpriseAchievementsController extends BaseController
return AjaxResult.success(schoolEnterpriseAchievementsService.insertSchoolEnterpriseAchievements(schoolEnterpriseAchievements));
}
@PostMapping("/importData")
@Log(title = "校企成果", businessType = BusinessType.INSERT)
public AjaxResult importData(@RequestPart("file") MultipartFile file,
@RequestParam(value = "updateSupport", defaultValue = "false") boolean updateSupport) throws Exception {
ExcelUtil<SchoolEnterpriseAchievements> util = new ExcelUtil<>(SchoolEnterpriseAchievements.class);
// 1. 解析Excel获取实体列表和图片Map
List<SchoolEnterpriseAchievements> list = util.importExcelWithImages(StringUtils.EMPTY, file.getInputStream(), 0);
Map<String, MultipartFile> imageMap = util.getExcelImageMap();
// 2. 调用业务层处理传入实体列表和图片Map
String operName = SecurityUtils.getUsername();
String message = schoolEnterpriseAchievementsService.batchInsertSchoolEnterpriseAchievements(list, imageMap, updateSupport, operName);
return AjaxResult.success(message);
}
/**
* 修改校企成果
*/

View File

@ -167,7 +167,7 @@ public class Achievements extends BaseEntity {
/**
* 成果图片
*/
@Excel(name = "成果图片")
@Excel(name = "图片")
private String images;
/**

View File

@ -32,7 +32,10 @@ public class SchoolEnterpriseAchievements extends BaseEntity {
/**
* 成果领域1
*/
@Excel(name = "成果领域1")
@Excel(
name = "成果领域",
readConverterExp = "1=理论研究,2=政策法规,3=医学,4=电子信息,5=通信工程,6=计算机科学,7=软件工程,9=人工智能,10=知识图谱,12=大数据,23=机器人,24=无人系统,25=创意征集,26=文献翻译,27=成果转化,28=科技协同,29=科研组织模式,30=新兴领域,31=其他,32=电子元器件,33=测试仪器,34=军事历史,35=计算机网络"
)
private String field1;
/**
@ -50,7 +53,7 @@ public class SchoolEnterpriseAchievements extends BaseEntity {
/**
* 成果类型
*/
@Excel(name = "成果类型")
@Excel(name = "成果类型", readConverterExp = "1=理论成果,2=技术成果,3=应用成果,4=软科学成果,5=文化艺术成果,6=社会服务成果,7=交叉领域成果")
private String achievementType;
/**
@ -74,7 +77,10 @@ public class SchoolEnterpriseAchievements extends BaseEntity {
/**
* 成果标签
*/
@Excel(name = "成果标签")
@Excel(
name = "成果标签",
readConverterExp = "1=高校,2=企业,3=其他"
)
private String tags;
/**
@ -159,7 +165,7 @@ public class SchoolEnterpriseAchievements extends BaseEntity {
/**
* 成果图片
*/
@Excel(name = "成果图片")
@Excel(name = "图片")
private String images;
/**
@ -169,8 +175,15 @@ public class SchoolEnterpriseAchievements extends BaseEntity {
private String attachments;
private Integer achievementStatus;
//成果小类型
@Excel(
name = "成果小类型",
readConverterExp = "7=学术论文,8=学术专著,9=理论模型,10=发明专利,11=技术标准,12=软件系统,13=技术原型,14=产品设备,15=解决方案,16=示范工程,17=政策建议,18=咨询报告,19=行业标准,20=管理办法,21=文学作品,22=艺术作品,23=公益项目,24=社会调查报告,25=教育培训成果,26=理论模型+产品设备,27=学术论文+技术原型"
)
private String achievementLittleType;
//成果状态
@Excel(
name = "成果状态",
readConverterExp = "1=已有阶段性成果,2=已有成熟成果,3=已公开发表,4=已对外发布,5=已开展应用,6=已形成商业化产品"
)
private String achievementCurStatus;
//团队名称
private String teamName;

View File

@ -1,7 +1,10 @@
package com.microservices.dms.achievementLibrary.service;
import java.util.List;
import java.util.Map;
import com.microservices.dms.achievementLibrary.domain.SchoolEnterpriseAchievements;
import org.springframework.web.multipart.MultipartFile;
/**
* 校企成果Service接口
@ -64,4 +67,6 @@ public interface ISchoolEnterpriseAchievementsService
List<SchoolEnterpriseAchievements> listFront(SchoolEnterpriseAchievements schoolEnterpriseAchievements);
boolean getCheckSchoolAchName(SchoolEnterpriseAchievements schoolEnterpriseAchievements);
String batchInsertSchoolEnterpriseAchievements(List<SchoolEnterpriseAchievements> list, Map<String, MultipartFile> imageMap, boolean updateSupport, String operName);
}

View File

@ -1,25 +1,34 @@
package com.microservices.dms.achievementLibrary.service.impl;
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.constant.SecurityConstants;
import com.microservices.common.core.constant.UserConstants;
import com.microservices.common.core.domain.R;
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.datasource.annotation.Slave;
import com.microservices.common.security.utils.SecurityUtils;
import com.microservices.dms.achievementLibrary.domain.AchievementTeam;
import com.microservices.dms.achievementLibrary.domain.Achievements;
import com.microservices.dms.achievementLibrary.mapper.AchievementTeamMapper;
import com.microservices.dms.achievementLibrary.mapper.AchievementsMapper;
import com.microservices.dms.achievementLibrary.service.IAchievementsService;
import com.microservices.system.api.domain.SysUser;
import com.microservices.system.api.RemoteFileService;
import com.microservices.system.api.model.LoginUser;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.microservices.dms.achievementLibrary.mapper.SchoolEnterpriseAchievementsMapper;
import com.microservices.dms.achievementLibrary.domain.SchoolEnterpriseAchievements;
import com.microservices.dms.achievementLibrary.service.ISchoolEnterpriseAchievementsService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
/**
* 校企成果Service业务层处理
@ -41,6 +50,9 @@ public class SchoolEnterpriseAchievementsServiceImpl implements ISchoolEnterpris
@Autowired
private AchievementsMapper achievementsMapper;
@Autowired
private RemoteFileService remoteFileService;
/**
* 查询校企成果
*
@ -124,6 +136,144 @@ public class SchoolEnterpriseAchievementsServiceImpl implements ISchoolEnterpris
return UserConstants.UNIQUE;
}
/**
* 导入成果数据包含图片处理
*/
@Override
@Transactional
@Slave
public String batchInsertSchoolEnterpriseAchievements(List<SchoolEnterpriseAchievements> list,
Map<String, MultipartFile> imageMap,
boolean updateSupport,
String operName) {
if (list == null || list.isEmpty()) {
throw new RuntimeException("导入成果数据不能为空!");
}
int successNum = 0;
int failureNum = 0;
int imageErrorNum = 0; // 新增图片处理失败数
StringBuilder successMsg = new StringBuilder();
StringBuilder failureMsg = new StringBuilder();
StringBuilder imageErrorMsg = new StringBuilder(); // 新增图片错误信息
for (SchoolEnterpriseAchievements schoolEnterpriseAchievement : list) {
try {
// 处理图片字段优化后返回处理结果不抛出异常
String imageHandleResult = handleAchievementImage(schoolEnterpriseAchievement, imageMap);
if (imageHandleResult.contains("失败") || imageHandleResult.contains("异常")) {
imageErrorNum++;
imageErrorMsg.append("<br/>").append(imageErrorNum).append("、成果 ").append(schoolEnterpriseAchievement.getAchievementName()).append(" 图片处理失败:").append(imageHandleResult);
}
// 原有查重和入库逻辑不受图片处理影响
SchoolEnterpriseAchievements exist = schoolEnterpriseAchievementsMapper.selectSchoolEnterpriseAchievementsByName(schoolEnterpriseAchievement.getAchievementName());
if (exist == null) {
schoolEnterpriseAchievement.setCreateBy(operName);
schoolEnterpriseAchievement.setCreateTime(new Date());
schoolEnterpriseAchievement.setUpdateBy(operName);
schoolEnterpriseAchievement.setUpdateTime(new Date());
schoolEnterpriseAchievement.setStatus("0");
schoolEnterpriseAchievement.setAchievementStatus(0);
schoolEnterpriseAchievementsMapper.insertSchoolEnterpriseAchievements(schoolEnterpriseAchievement);
successNum++;
successMsg.append("<br/>").append(successNum).append("、成果 ").append(schoolEnterpriseAchievement.getAchievementName()).append(" 导入成功");
} else {
if (updateSupport) {
schoolEnterpriseAchievement.setId(exist.getId());
schoolEnterpriseAchievement.setCreateBy(exist.getCreateBy());
schoolEnterpriseAchievement.setCreateTime(exist.getCreateTime());
schoolEnterpriseAchievement.setUpdateBy(operName);
schoolEnterpriseAchievement.setUpdateTime(new Date());
schoolEnterpriseAchievementsMapper.updateSchoolEnterpriseAchievements(schoolEnterpriseAchievement);
successNum++;
successMsg.append("<br/>").append(successNum).append("、成果 ").append(schoolEnterpriseAchievement.getAchievementName()).append(" 更新成功");
} else {
failureNum++;
failureMsg.append("<br/>").append(failureNum).append("、成果 ").append(schoolEnterpriseAchievement.getAchievementName()).append(" 已存在(未开启更新)");
}
}
} catch (Exception e) {
failureNum++;
String msg = "<br/>" + failureNum + "、成果 " + schoolEnterpriseAchievement.getAchievementName() + " 导入失败:";
failureMsg.append(msg).append(e.getMessage());
}
}
// 组装最终提示信息包含图片处理错误
StringBuilder finalMsg = new StringBuilder();
if (successNum > 0) {
finalMsg.append("").append(successNum).append(" 条数据导入/更新成功:").append(successMsg);
}
if (failureNum > 0) {
finalMsg.append("<br/>共 ").append(failureNum).append(" 条数据导入失败:").append(failureMsg);
}
if (imageErrorNum > 0) {
finalMsg.append("<br/>共 ").append(imageErrorNum).append(" 条数据图片处理失败:").append(imageErrorMsg);
}
return finalMsg.toString();
}
/**
* 处理成果图片上传并格式化存储FastJSON解析版
* @param achievement 成果实体
* @param imageMap Excel解析出的图片映射表
* @return 图片处理结果提示成功/失败信息
*/
private String handleAchievementImage(SchoolEnterpriseAchievements achievement, Map<String, MultipartFile> imageMap) {
String imageCellKey = achievement.getImages();
if (StringUtils.isEmpty(imageCellKey) || !imageMap.containsKey(imageCellKey)) {
return "无图片,跳过处理";
}
try {
MultipartFile file = imageMap.get(imageCellKey);
byte[] fileBytes = file.getBytes();
if (fileBytes == null || fileBytes.length == 0) {
return "图片文件为空,跳过处理";
}
// 构建 HashMap 参数
String originalFilename = StringUtils.isEmpty(file.getOriginalFilename())
? "excel_import_image_" + System.currentTimeMillis() + ".png"
: file.getOriginalFilename();
Map<String, Object> params = new HashMap<>();
params.put("base64Data", Base64.getEncoder().encodeToString(fileBytes));
params.put("fileName", originalFilename);
params.put("contentType", file.getContentType());
params.put("size", fileBytes.length);
// 调用 Base64 上传接口
R<?> fileResult = remoteFileService.uploadBase64(params, "dms", "import", SecurityConstants.INNER);
// 解析结果与原逻辑相同
if (StringUtils.isNull(fileResult) || StringUtils.isNull(fileResult.getData())) {
String errorMsg = !StringUtils.isNull(fileResult) ? fileResult.getMsg() : "接口返回异常";
return "图片上传失败:" + errorMsg;
}
JSONObject resultJson = JSONObject.parseObject(JSONObject.toJSONString(fileResult.getData()));
String fileOriginName = resultJson.getString("fileOriginName");
String url = resultJson.getString("downloadUrl");
Integer fileId = resultJson.getInteger("fileId");
if (StringUtils.isEmpty(fileOriginName) || StringUtils.isEmpty(url) || fileId == null) {
return "图片上传成功,但返回数据不完整";
}
String imageJson = String.format("[{\"k\":\"%s\",\"v\":\"%s\",\"id\":%d}]",
fileOriginName, url, fileId);
achievement.setImages(imageJson);
return "图片处理成功";
} catch (Exception e) {
return "图片处理异常:" + e.getMessage();
}
}
/**
* 修改校企成果

View File

@ -5,7 +5,6 @@ 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;
/**
* 文件服务
@ -16,7 +15,6 @@ import org.springframework.scheduling.annotation.EnableScheduling;
@EnableCustomConfig
@SpringBootApplication
@EnableRyFeignClients
@EnableScheduling
public class MicroservicesFileApplication {
public static void main(String[] args) {
SpringApplication.run(MicroservicesFileApplication.class, args);

View File

@ -1,176 +0,0 @@
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 初始化结果包含 uploadIdtotalChunkschunkSizehashTypesampleChunks
*/
@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));
}
}

View File

@ -1,7 +1,6 @@
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;
@ -121,20 +120,19 @@ 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) {
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;
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;
}
}

View File

@ -1,20 +1,26 @@
package com.microservices.file.controller;
import com.microservices.common.core.domain.R;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.core.web.controller.BaseController;
import com.microservices.common.security.annotation.InnerAuth;
import com.microservices.file.service.ISysFileInfoService;
import com.microservices.system.api.domain.SysFile;
import com.microservices.system.api.domain.SysFileInfo;
import com.microservices.system.api.utils.StandardMultipartFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import springfox.documentation.annotations.ApiIgnore;
import java.util.Base64;
import java.util.Map;
/**
* 文件请求处理
*
@ -49,4 +55,56 @@ public class SysFileController extends BaseController {
return R.fail(e.getMessage());
}
}
@InnerAuth
@PostMapping("/upload/base64")
public R<SysFileInfo> uploadBase64(@RequestBody Map<String, Object> params,
@RequestParam("type") String type,
@RequestParam("hierarchy") String hierarchy) {
try {
// 1. 提取参数
String base64Data = (String) params.get("base64Data");
String fileName = (String) params.get("fileName");
String contentType = (String) params.get("contentType");
// 2. 参数校验
if (StringUtils.isEmpty(base64Data)) {
return R.fail("base64Data不能为空");
}
// 3. Base64解码
byte[] fileBytes;
try {
fileBytes = Base64.getDecoder().decode(base64Data);
} catch (IllegalArgumentException e) {
log.error("Base64解码失败", e);
return R.fail("图片数据格式错误");
}
if (fileBytes.length == 0) {
return R.fail("解码后文件为空");
}
// 4. 构造MultipartFile
String originalFilename = StringUtils.isEmpty(fileName)
? "base64_image_" + System.currentTimeMillis() + ".png"
: fileName;
MultipartFile multipartFile = new StandardMultipartFile(
fileBytes,
"file", // 字段名与原接口一致
originalFilename,
contentType
);
// 5. 复用原有上传逻辑
SysFileInfo fileInfo = sysFileInfoService.upload(multipartFile, type, hierarchy);
return R.ok(fileInfo);
} catch (Exception e) {
log.error("Base64上传文件失败", e);
return R.fail(e.getMessage());
}
}
}

View File

@ -1,91 +0,0 @@
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;
}
}
}

View File

@ -1,156 +0,0 @@
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;
}
}
}

View File

@ -1,52 +0,0 @@
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;
}
}

View File

@ -1,77 +0,0 @@
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;
}
}

View File

@ -1,58 +0,0 @@
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;
}

View File

@ -1,103 +0,0 @@
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;
}

View File

@ -1,29 +0,0 @@
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;
}

View File

@ -1,23 +0,0 @@
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;
}

View File

@ -1,35 +0,0 @@
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;
}

View File

@ -1,24 +0,0 @@
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;
}

View File

@ -1,45 +0,0 @@
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;
}
}

View File

@ -1,40 +0,0 @@
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;
}
}

View File

@ -1,65 +0,0 @@
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;
}
}

View File

@ -1,55 +0,0 @@
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;
}
}

View File

@ -1,56 +0,0 @@
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;
}
}

View File

@ -1,50 +0,0 @@
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;
}
}

View File

@ -1,84 +0,0 @@
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;
}
}

View File

@ -1,107 +0,0 @@
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);
}

View File

@ -1,107 +0,0 @@
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);
}

View File

@ -1,117 +0,0 @@
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 初始化结果包含 uploadIdtotalChunkschunkSizehashTypesampleChunks
*/
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-完整MD5SAMPLE-采样点哈希
* 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);
}

View File

@ -1,12 +0,0 @@
package com.microservices.file.service;
import java.util.List;
/**
* 文件Service异步接口
*
* @author otto
*/
public interface IFileCommonAsyncService {
public void executeMergeAsync(String uploadId);
}

View File

@ -1,6 +1,5 @@
package com.microservices.file.service;
import com.microservices.file.domain.FileUploadSession;
import com.microservices.system.api.domain.SysFileInfo;
import org.springframework.web.multipart.MultipartFile;
@ -105,7 +104,6 @@ public interface ISysFileInfoService {
* @return 返回上传成功的文件名
*/
SysFileInfo upload(MultipartFile file, String type, String hierarchy);
SysFileInfo uploadChunkedFile(FileUploadSession session, String type, String hierarchy);
/**
* 根据文件标识获取文件详情

View File

@ -1,745 +0,0 @@
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);
}
}
}
}

View File

@ -1,185 +0,0 @@
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());
}
};
}
}

View File

@ -17,10 +17,7 @@ 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;
@ -60,8 +57,6 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
private GitLinkRequestHelper gitLinkRequestHelper;
@Autowired
private ISysFileInfoAsyncService fileInfoAsyncService;
@Autowired
private IChunkedUploadService chunkedUploadService;
/**
* 上传文件存储在本地的根路径
*/
@ -96,11 +91,6 @@ 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);
@ -118,12 +108,6 @@ 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;
}
@ -374,19 +358,8 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
@Override
public SysFileInfo upload(MultipartFile file, String fileType, String hierarchy) {
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("不允许上传空文件");
}
if (file == null || file.isEmpty()) {
throw new ServiceException("不允许上传空文件");
}
SysFileInfo sysFileInfo = new SysFileInfo();
// 上传文件路径
@ -406,9 +379,6 @@ 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"));
@ -463,9 +433,7 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
if (StringUtils.isNotEmpty(cmsAllowedExtension)) {
allowedExtensions = cmsAllowedExtension.split(",");
}
if (!isChunkedUpload) {
assertAllowed(file, allowedExtensions);
}
assertAllowed(file, allowedExtensions);
baseFilePath += "/" + fileType + "/";
isCommonFileType = false;
@ -477,40 +445,13 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
if (StringUtils.isNotEmpty(allowedExtension)) {
allowedExtensions = allowedExtension.split(",");
}
if (!isChunkedUpload) {
assertAllowed(file, allowedExtensions);
}
assertAllowed(file, allowedExtensions);
}
}
if (isLocalUpload) {
String filePath;
// 上传并返回新文件名称
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());
}
String filePath = upload(baseFilePath, file);
if (!isCommonFileType) {
if ("pms".equals(fileType)) {
filePath = EscapeUtil.removeExtraSlashOfUrl("/" + fileType + "/" + hierarchy + "/" + filePath);
@ -526,6 +467,23 @@ 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();
@ -621,15 +579,6 @@ 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());
}

View File

@ -1,46 +0,0 @@
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);
}
}
}

View File

@ -60,15 +60,6 @@ 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));
}
/**
* 根据文件路径上传
*

View File

@ -1,148 +0,0 @@
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);
}
}

View File

@ -1,16 +0,0 @@
-- =============================================
-- 分片上传采样点哈希方案 - 数据库迁移脚本
-- =============================================
-- 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;

View File

@ -1,54 +0,0 @@
-- =============================================
-- 文件分片上传会话表 - 完整建表语句
-- =============================================
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='文件分片上传记录表';

View File

@ -1,123 +0,0 @@
<?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>

View File

@ -1,189 +0,0 @@
<?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 &lt; 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>

View File

@ -26,7 +26,6 @@ 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;

View File

@ -11,7 +11,6 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.Size;
import java.util.List;
/**
@ -72,18 +71,12 @@ 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();

View File

@ -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/organizations/%s/projects.json", enterpriseIdentifier);
String path = String.format("/api/v1/%s/projects.json", enterpriseIdentifier);
return getGitLinkRequestUrl(buildUri(path, projectSearchInputVo));
}

View File

@ -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_tracking"/>
<result property="testTracking" column="test_track"/>
<result property="productReqSpecsId" column="product_req_specs_id"/>
<result property="isImport" column="is_import"/>
</resultMap>
@ -33,7 +33,6 @@
<sql id="selectPmsProjectTestcaseVo">
select id,
title,
test_tracking,
assignee_gitlink_id,
pms_project_id,
type_id,
@ -187,7 +186,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">test_tracking = #{testTracking},</if>
<if test="testTracking != null">#{testTracking},</if>
<if test="productReqSpecsId != null">#{productReqSpecsId},</if>
<if test="isImport != null">#{isImport},</if>
</trim>

View File

@ -1,6 +1,5 @@
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;
@ -204,26 +203,6 @@ 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
*

View File

@ -289,24 +289,6 @@ 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);
}
/**
* 检查用户是否在指定部门下
*
@ -322,12 +304,4 @@ 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);
}
}

View File

@ -151,6 +151,4 @@ 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);
}

View File

@ -131,6 +131,4 @@ public interface SysUserMapper
public SysUser checkEmailUnique(String email);
SysUser selectUserByGitLinkUserId(Long gitLinkUserId);
List<SysUser> selectUserListFromUserIds(Long[] ids);
}

View File

@ -160,8 +160,4 @@ 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);
}

View File

@ -222,6 +222,4 @@ public interface ISysUserService
String getUserNameByGitLinkToken(String gitLinkToken);
SysUser gitlinkUserToUpdateSysUser(SysUser sysUser, ForgeUser forgeUser);
List<SysUser> selectUserListFromUserIds(Long[] userIds);
}

View File

@ -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,10 +124,7 @@ public class SysUserDeptRoleServiceImpl implements ISysUserDeptRoleService {
}
for (Long userId : userIds) {
SysUserDeptRole userIdentify = sysUserDeptRoleMapper.selectSysUserDeptRoleByUserIdAndDeptId(userId, deptId);
if (userIdentify != null) {
updateUserGitLinkRole(userId, deptId, userIdentify.getRoleId(), null, OPERATE_REMOVE);
}
updateUserGitLinkRole(userId, deptId, userIdentify.getRoleId(), null, OPERATE_REMOVE);
}
int success = deleteUserIdentityByDeptIdAndUserIds(sysDept, userIds);
for (Long userId : userIds) {
@ -169,11 +166,8 @@ public class SysUserDeptRoleServiceImpl implements ISysUserDeptRoleService {
if (sysUser == null) {
return;
}
String redisKey = CacheConstants.getUserIdentifyListKey(sysUser.getUserName());
// 删除缓存中该用户身份列表信息
redisService.deleteObject(CacheConstants.getUserIdentifyListKey(redisKey));
List<SysUserDeptRole> userIdentifyList = selectSysUserDeptRoleListByUserName(sysUser.getUserName());
redisService.setCacheObject(redisKey, userIdentifyList);
redisService.deleteObject(CacheConstants.getUserIdentifyListKey(sysUser.getUserName()));
}
@ -237,13 +231,8 @@ public class SysUserDeptRoleServiceImpl implements ISysUserDeptRoleService {
if (secondaryDeptIds == null) {
return;
}
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.getDeptId())) {
if (secondaryDeptIds.contains(dept.getParentId())) {
switch (dept.getDeptType()) {
case CacheConstants.DEPT_TYPE_TSZQ: {
@ -579,17 +568,6 @@ 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);

View File

@ -37,7 +37,10 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.validation.Validator;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@ -681,9 +684,4 @@ public class SysUserServiceImpl implements ISysUserService {
return sysUser;
}
@Override
public List<SysUser> selectUserListFromUserIds(Long[] userIds) {
return userMapper.selectUserListFromUserIds(userIds);
}
}

Some files were not shown because too many files have changed in this diff Show More