Merge branch 'chaosuan_zone' into aiResourceMRCS

# Conflicts:
#	microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/common/controller/FrontController.java
#	microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/common/controller/OpenController.java
#	microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/domain/vo/ZoneResourceDataVo.java
#	microservices-modules/microservices-modules-zone/src/main/java/com/microservices/zone/resource/mapper/ZoneResourceMapper.java
#	microservices-modules/microservices-modules-zone/src/main/resources/mapper/zone/ZoneResourceMapper.xml
This commit is contained in:
欧涛 2026-05-09 08:58:56 +08:00
commit b3f5a4898f
84 changed files with 2952 additions and 199 deletions

View File

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

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,6 +143,17 @@ public interface RemoteDeptService {
@GetMapping("/SysUserDeptRole/checkUserInDeptHasPerms")
R<Boolean> checkUserInDeptHasPerms(@RequestParam("currentDeptId") Long currentDeptId, @RequestParam("userId") Long userId, @RequestParam("perms") String perms, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 检查用户是否在指定部门或指定部门的父级部门下具有指定菜单权限
*
* @param deptId 指定组织id
* @param userId 用户id
* @param perms 菜单权限
* @return 用户是否在指定部门下具有菜单权限 是true 否false
*/
@GetMapping("/SysUserDeptRole/checkUserInDeptOrParentDeptHasPerms")
R<Boolean> checkUserInDeptOrParentDeptHasPerms(@RequestParam("deptId") Long deptId, @RequestParam("userId") Long userId, @RequestParam("perms") String perms, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 检查用户是否在指定部门下
*
@ -152,4 +163,7 @@ public interface RemoteDeptService {
*/
@GetMapping("/SysUserDeptRole/checkUserInDept")
R<Boolean> checkUserInDept(@RequestParam("currentDeptId") Long currentDeptId, @RequestParam("userId") Long userId, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
@GetMapping("/SysUserDeptRole/getSysUserDeptRoleByDeptIdAndUserId")
R<SysUserDeptRole> getSysUserDeptRoleByDeptIdAndUserId(@RequestParam("deptId") Long deptId, @RequestParam("userId") Long userId, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
}

View File

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

View File

@ -83,10 +83,20 @@ public class RemoteDeptFallbackFactory implements FallbackFactory<RemoteDeptServ
return R.fail("检查用户是否在指定部门下具有指定菜单权限失败:" + throwable.getMessage());
}
@Override
public R<Boolean> checkUserInDeptOrParentDeptHasPerms(Long deptId, Long userId, String perms, String source) {
return R.fail("检查用户是否在指定部门或指定部门的父级部门下具有指定菜单权限失败:" + throwable.getMessage());
}
@Override
public R<Boolean> checkUserInDept(Long currentDeptId, Long userId, String source) {
return R.fail("检查用户是否在指定部门下失败:" + throwable.getMessage());
}
@Override
public R<SysUserDeptRole> getSysUserDeptRoleByDeptIdAndUserId(Long deptId, Long userId, String source) {
return R.fail("获取用户在指定部门下的角色失败:" + throwable.getMessage());
}
};
}
}

View File

@ -5,8 +5,7 @@ package com.microservices.common.core.constant;
*
* @author microservices
*/
public class UserConstants
{
public class UserConstants {
/**
* 平台内系统用户的唯一标志
*/
@ -28,55 +27,89 @@ public class UserConstants
*/
public static final String DEL_FLAG_FALSE = "0";
/** 正常状态 */
/**
* 正常状态
*/
public static final String NORMAL = "0";
/** 异常状态 */
/**
* 异常状态
*/
public static final String EXCEPTION = "1";
/** 用户封禁状态 */
/**
* 用户封禁状态
*/
public static final String USER_DISABLE = "1";
/** 角色封禁状态 */
/**
* 角色封禁状态
*/
public static final String ROLE_DISABLE = "1";
/** 部门正常状态 */
/**
* 部门正常状态
*/
public static final String DEPT_NORMAL = "0";
/** 部门停用状态 */
/**
* 部门停用状态
*/
public static final String DEPT_DISABLE = "1";
/** 字典正常状态 */
/**
* 字典正常状态
*/
public static final String DICT_NORMAL = "0";
/** 是否为系统默认(是) */
/**
* 是否为系统默认
*/
public static final String YES = "Y";
/** 是否菜单外链(是) */
/**
* 是否菜单外链
*/
public static final String YES_FRAME = "0";
/** 是否菜单外链(否) */
/**
* 是否菜单外链
*/
public static final String NO_FRAME = "1";
/** 菜单类型(目录) */
/**
* 菜单类型目录
*/
public static final String TYPE_DIR = "M";
/** 菜单类型(菜单) */
/**
* 菜单类型菜单
*/
public static final String TYPE_MENU = "C";
/** 菜单类型(按钮) */
/**
* 菜单类型按钮
*/
public static final String TYPE_BUTTON = "F";
/** Layout组件标识 */
/**
* Layout组件标识
*/
public final static String LAYOUT = "Layout";
/** ParentView组件标识 */
/**
* ParentView组件标识
*/
public final static String PARENT_VIEW = "ParentView";
/** InnerLink组件标识 */
/**
* InnerLink组件标识
*/
public final static String INNER_LINK = "InnerLink";
/** 校验是否唯一的返回标识 */
/**
* 校验是否唯一的返回标识
*/
public final static boolean UNIQUE = true;
public final static boolean NOT_UNIQUE = false;
@ -122,4 +155,20 @@ 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";
}

View File

@ -211,6 +211,17 @@ public class HttpAPIService {
return doRequest(httpDelete, headers);
}
/**
* 不带Header的get请求
*
* @param url
* @return
* @throws Exception
*/
public JSONObject doGet(String url) throws Exception {
return doGet(url, null);
}
/**
* 带Header的get请求
*

View File

@ -3,6 +3,7 @@ package com.microservices.cms.doc.controller;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.cms.doc.domain.*;
import com.microservices.cms.doc.service.ICmsDocService;
import com.microservices.common.core.constant.UserConstants;
import com.microservices.common.core.domain.R;
import com.microservices.common.core.web.controller.BaseController;
import com.microservices.common.core.web.domain.AjaxResult;
@ -164,7 +165,7 @@ public class CmsDocController extends BaseController {
/**
* 新增文章信息
*/
@RequiresPermissions("cms:doc:add")
@RequiresPermissions(UserConstants.CMS_PERMS_DOCS_ADD)
@Log(title = "文章信息", businessType = BusinessType.INSERT)
@PostMapping("/dir/{dirId}")
@ApiOperation("在栏目下创建文章")
@ -201,7 +202,7 @@ public class CmsDocController extends BaseController {
/**
* 删除文章
*/
@RequiresPermissions("cms:doc:remove")
@RequiresPermissions(UserConstants.CMS_PERMS_DOCS_DELETE)
@Log(title = "文章信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
@ApiOperation("删除文章/栏目")
@ -212,7 +213,7 @@ public class CmsDocController extends BaseController {
/**
* 修改文章信息
*/
@RequiresPermissions("cms:doc:edit")
@RequiresPermissions(UserConstants.CMS_PERMS_DOCS_EDIT)
@Log(title = "文章信息", businessType = BusinessType.UPDATE)
@PutMapping("/{id}")
@ApiOperation("更新文章")
@ -237,12 +238,12 @@ public class CmsDocController extends BaseController {
/**
* 审核文章信息
*/
@RequiresPermissions("cms:doc:audit")
@RequiresPermissions(UserConstants.CMS_PERMS_DOCS_AUDIT)
@Log(title = "文章信息", businessType = BusinessType.UPDATE)
@PutMapping("/{id}/audit")
@ApiOperation("审核文章")
public AjaxResult audit(@ApiParam(name = "id", value = "文章Id") @PathVariable Long id,
@ApiParam(name = "cmsDocAuditVo", value = "文章审核对象") @RequestBody CmsDocAuditVo cmsDocAuditVo) {
@ApiParam(name = "cmsDocAuditVo", value = "文章审核对象") @RequestBody CmsDocAuditVo cmsDocAuditVo) {
return toAjax(cmsDocService.auditCmsDoc(id, cmsDocAuditVo));
}

View File

@ -5,12 +5,17 @@ import com.microservices.cms.doc.domain.*;
import com.microservices.cms.doc.service.ICmsDocCommentService;
import com.microservices.cms.doc.service.ICmsDocService;
import com.microservices.cms.doc.service.ICmsWechatService;
import com.microservices.cms.search.dto.DocSearchResult;
import com.microservices.common.core.web.controller.BaseController;
import com.microservices.common.core.web.domain.AjaxResult;
import com.microservices.common.core.web.domain.GenericsAjaxResult;
import com.microservices.common.core.web.page.GenericsTableDataInfo;
import com.microservices.common.core.web.page.TableDataInfo;
import com.microservices.common.log.annotation.Log;
import com.microservices.common.log.enums.BusinessType;
import com.microservices.cms.search.dto.DocSearchRequest;
import com.microservices.cms.search.dto.DocSearchResponse;
import com.microservices.cms.search.service.IGlobalSearchService;
import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@ -35,6 +40,22 @@ public class OpenController extends BaseController {
@Autowired
private ICmsWechatService cmsWechatService;
@Autowired
private IGlobalSearchService globalSearchService;
/**
* 文章搜索接口
*
* @return 搜索结果
*/
@ApiOperation("文章搜索")
@PostMapping("/search")
public GenericsTableDataInfo<DocSearchResult> docSearch(@RequestBody DocSearchRequest docSearchRequest) {
logger.info("文章搜索请求: keyword={}", docSearchRequest.getKeyword());
DocSearchResponse response = globalSearchService.docSearch(docSearchRequest);
return new GenericsTableDataInfo<>(response.getResults(), response.getTotal());
}
/**
* 查询栏目列表
*/
@ -175,9 +196,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

@ -3,6 +3,7 @@ package com.microservices.cms.doc.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.microservices.cms.utils.CmsConstants;
import com.microservices.common.core.annotation.Excel;
import com.microservices.common.core.utils.bean.BeanUtils;
import com.microservices.common.core.web.domain.BaseSortEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -36,19 +37,27 @@ public class CmsDoc extends BaseSortEntity {
@Excel(name = "文件名称")
private String fileName;
/** 文件路径 */
/**
* 文件路径
*/
@Excel(name = "文件路径")
private String filePath;
/** 文件SHA */
/**
* 文件SHA
*/
@Excel(name = "文件SHA")
private String fileSha;
/** 文件大小 */
/**
* 文件大小
*/
@Excel(name = "文件大小")
private Long fileSize;
/** 文件URL */
/**
* 文件URL
*/
@Excel(name = "文件URL")
private String fileUrl;
@ -162,6 +171,11 @@ public class CmsDoc extends BaseSortEntity {
*/
private String keywords;
/**
* 备注
*/
private String remark;
public CmsDir copyToCmsDir() {
CmsDir cmsDir = new CmsDir();
cmsDir.setId(id);
@ -210,30 +224,12 @@ public class CmsDoc extends BaseSortEntity {
}
private CmsDocBaseDto getCmsDocBaseDto(CmsDocBaseDto cmsDocDto) {
cmsDocDto.setId(id);
cmsDocDto.setName(name);
cmsDocDto.setHeadImg(headImg);
cmsDocDto.setIsHomepage(isHomepage);
cmsDocDto.setSummary(summary);
cmsDocDto.setEditorType(editorType);
cmsDocDto.setDeptId(deptId);
BeanUtils.copyProperties(this, cmsDocDto);
if (visits == null) {
cmsDocDto.setVisits(0L);
} else {
cmsDocDto.setVisits(visits);
}
cmsDocDto.setPublishTime(publishTime);
cmsDocDto.setPublishUser(publishUser);
cmsDocDto.setPublishUserUrl(publishUserUrl);
cmsDocDto.setPublishUserImage(publishUserImage);
cmsDocDto.setPublishUserNickname(publishUserNickname);
cmsDocDto.setAuditStatus(auditStatus);
cmsDocDto.setAuditType(auditType);
cmsDocDto.setCreateBy(createBy);
cmsDocDto.setKeywords(keywords);
return cmsDocDto;
}

View File

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

View File

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

View File

@ -0,0 +1,23 @@
package com.microservices.cms.doc.domain;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
/**
* CmsDoc with full-text search score
* 用于全局搜索结果包含全文搜索相关度评分
*
* @author otto
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class CmsDocWithScore extends CmsDoc implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 全文搜索相关度评分来自 MySQL MATCH...AGAINST
*/
private Double searchScore;
}

View File

@ -2,6 +2,7 @@ package com.microservices.cms.doc.mapper;
import com.microservices.cms.doc.domain.CmsDoc;
import com.microservices.cms.doc.domain.CmsDocSearchVo;
import com.microservices.cms.doc.domain.CmsDocWithScore;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@ -192,4 +193,41 @@ public interface CmsDocMapper {
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

@ -99,4 +99,6 @@ public interface ICmsAsyncService {
* @param cmsDoc 文章
*/
void pushDocDataSearchEngine(CmsDoc cmsDoc);
void clearTmpDoc(CmsProject cmsProject,List<CmsDoc> tmpDocList);
}

View File

@ -294,4 +294,6 @@ public interface ICmsDocService {
List<CmsDoc> selectCmsDocAndDirListByDept(Long deptId);
void updateCmsDocShaByPathAndDeptId(String sha, String filePath, Long deptId);
List<CmsDoc> getTmpDocList(CmsDoc cmsDoc);
}

View File

@ -20,6 +20,8 @@ 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;
@ -618,11 +620,12 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
100, "..."), tmpBranchName);
} catch (ServiceException e) {
//忽略重复创建PR异常
if (!"请求GitLink失败(合并请求已存在)".equals(e.getMessage())) {
if (!e.getMessage().contains("合并请求已存在")) {
throw new ServiceException(e.getMessage());
}
}
cmsDocService.updateCmsCommonDatabase(cmsDoc, tmpBranchName);
}
@ -1003,6 +1006,23 @@ public class CmsAsyncServiceImpl implements ICmsAsyncService {
}
}
@Override
public void clearTmpDoc(CmsProject cmsProject, List<CmsDoc> tmpDocList) {
CustomExecutorFactory.threadPoolExecutor.execute(() -> {
for (CmsDoc tmpDoc : tmpDocList) {
//拒绝GitLink合并请求
String gitLinkPullRequestNumber = tmpDoc.getAuditPrNumber().split("-")[0];
gitLinkRequestHelper.doPost(
CmsGitLinkRequestUrl.REFUSE_MERGE_PULL_REQUEST(cmsProject, gitLinkPullRequestNumber), null);
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, tmpDoc.getAuditTmpBranchName()), null);
// 所有删除操作调整为将审核状态设置为已拒绝
tmpDoc.setAuditStatus(DocAuditStatus.AUDIT_FAIL.getAuditCode());
tmpDoc.setRemark("管理员删除");
cmsDocMapper.updateCmsDoc(tmpDoc);
}
});
}
private void moveCmsDocToNewCmsDir(CmsProject cmsProject, String oldDirName, String newDirName, String message) {
CmsDocSearchVo cmsDocSearch = new CmsDocSearchVo();
cmsDocSearch.setIsDir(false);

View File

@ -15,6 +15,7 @@ import com.microservices.cms.utils.CmsGitLinkRequestUrl;
import com.microservices.common.core.constant.CacheConstants;
import com.microservices.common.core.constant.HttpStatus;
import com.microservices.common.core.constant.SecurityConstants;
import com.microservices.common.core.constant.UserConstants;
import com.microservices.common.core.enums.DocAndZoneOperation;
import com.microservices.common.core.enums.DocAuditStatus;
import com.microservices.common.core.enums.SystemRole;
@ -296,7 +297,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
@Override
public void updateCmsDocShaByPathAndDeptId(String sha, String filePath, Long deptId) {
cmsDocMapper.updateCmsDocShaByPathAndDeptId(sha,filePath,deptId);
cmsDocMapper.updateCmsDocShaByPathAndDeptId(sha, filePath, deptId);
}
private List<CmsDocBaseDto> selectCmsDocListByDatabase(Long dirId, CmsDocSearchVo cmsDocSearchVo) {
@ -313,7 +314,8 @@ public class CmsDocServiceImpl implements ICmsDocService {
/**
* 设置文章默认头图为栏目头图
* @param dirId 栏目id
*
* @param dirId 栏目id
* @param cmsDocSearchVo 文章查询条件
* @return 文章列表
*/
@ -358,7 +360,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
checkCmsLockStatus(dirDoc.getDeptId());
CmsDoc cmsDoc = cmsDocInputDto.toCmsDoc(dirDoc);
if (isNoAuditRequired(dirDoc.getDeptId()) || isCurrentUserZoneAdmin(dirDoc.getDeptId())) {
if (isNoAuditRequired(dirDoc.getDeptId()) || hasDeptAdminPerms(dirDoc.getDeptId(), UserConstants.CMS_PERMS_DOCS_AUDIT)) {
//文章自动置为审核通过状态值
cmsDoc.setAuditStatus(DocAuditStatus.AUDIT_PASS.getAuditCode());
boolean success = insertCommonCmsDoc(cmsDoc,
@ -421,8 +423,15 @@ 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) {
//同意合并请求
JSONObject result = mergePullRequestByGitLink(cmsProject, cmsDoc);
@ -446,7 +455,9 @@ public class CmsDocServiceImpl implements ICmsDocService {
}
if (cmsDocAuditVo.getPass() == 0) {
//拒绝GitLink合并请求
JSONObject result = refuseMergePullRequestByGitLink(cmsDoc, cmsProject);
String gitLinkPullRequestNumber = cmsDoc.getAuditPrNumber().split("-")[0];
JSONObject result = gitLinkRequestHelper.doPost(
CmsGitLinkRequestUrl.REFUSE_MERGE_PULL_REQUEST(cmsProject, gitLinkPullRequestNumber), null);
if (result.containsKey(GitLinkConstants.STATUS_KEY)) {
//2.若拒绝GitLink合并请求操作返回正确的状态值,更新数据库
if (result.getInteger(GitLinkConstants.STATUS_KEY).equals(1)) {
@ -494,7 +505,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
//删除待审核文章
cmsDocMapper.deleteCmsDocById(cmsDoc.getId());
//删除分支
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
return;
}
}
@ -506,7 +517,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
int success = deleteCmsDocDatabaseById(ids);
if (success == ids.length) {
//删除临时分支
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
}
}
@ -526,7 +537,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
int success = cmsDocMapper.deleteCmsDocById(newDocId);
if (success > 0) {
//删除临时分支
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
}
}
@ -540,7 +551,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
int success = cmsDocMapper.updateCmsDoc(cmsDoc);
if (success > 0) {
//删除临时分支
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
}
// 推送文章地址到搜索引擎
@ -559,12 +570,6 @@ public class CmsDocServiceImpl implements ICmsDocService {
CmsGitLinkRequestUrl.MERGE_PULL_REQUEST(cmsProject, gitLinkPullRequestId), requestBody);
}
private JSONObject refuseMergePullRequestByGitLink(CmsDoc cmsDoc, CmsProject cmsProject) {
//1.调用gitlink拒绝合并请求接口
return gitLinkRequestHelper.doPost(
CmsGitLinkRequestUrl.REFUSE_MERGE_PULL_REQUEST(cmsProject, cmsDoc.getAuditPrNumber()), null);
}
public static String sha1(String data) {
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
@ -725,14 +730,14 @@ public class CmsDocServiceImpl implements ICmsDocService {
}
// 当用户更新栏目时需检查新栏目下是否已存在相同文章名
if(cmsDocDto.getDirId()!=null){
if (cmsDocDto.getDirId() != null) {
CmsDir cmsDir = selectCmsDirById(cmsDocDto.getDirId(), false);
if(cmsDir!=null&&!cmsDir.getName().equals(oldCmsDoc.getDirName())){
if (cmsDir != null && !cmsDir.getName().equals(oldCmsDoc.getDirName())) {
checkDocName(cmsDocDto.getName(), oldCmsDoc.getDeptId(), cmsDir.getName());
}
}
//用户为专区管理员或专区无审核机制时直接更新文章及仓库主分支
if (isCurrentUserZoneAdmin(oldCmsDoc.getDeptId()) || isNoAuditRequired(oldCmsDoc.getDeptId())) {
if (hasDeptAdminPerms(oldCmsDoc.getDeptId(), UserConstants.CMS_PERMS_DOCS_AUDIT) || isNoAuditRequired(oldCmsDoc.getDeptId())) {
//专区管理员重新编辑并提交审核未通过文章 重新编辑并提交待审核文章时专区不需审核机制
if (!oldCmsDoc.getAuditStatus().equals(DocAuditStatus.AUDIT_PASS.getAuditCode())) {
//文章审核类型update-原文章id 取出原文章id直接更新删除待审核更新类型文章
@ -764,7 +769,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
|| DocAuditStatus.TO_BE_AUDIT.getAuditCode().equals(oldCmsDoc.getAuditStatus())) {
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其余字段与待更新文章一致的新文章数据
@ -772,6 +777,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
DocAndZoneOperation.OPERATION_UPDATE.getOperationCode() + "-" + id,
DocAuditStatus.TO_BE_AUDIT.getAuditCode());
}
}
cmsAsyncService.asyncUpdateBranchAndFileAndCreatePR(newDocWithAudit, cmsDocDto);
return true;
}
@ -825,7 +831,7 @@ public class CmsDocServiceImpl implements ICmsDocService {
private void deleteTmpBranchIfBranchExist(CmsDoc cmsDoc) {
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(cmsDoc.getDeptId());
try {
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
} catch (ServiceException e) {
//忽略分支不存在异常
if (!"分支不存在!".equals(e.getMessage())) {
@ -856,6 +862,10 @@ 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);
@ -865,6 +875,10 @@ public class CmsDocServiceImpl implements ICmsDocService {
}
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());
@ -1303,6 +1317,18 @@ public class CmsDocServiceImpl implements ICmsDocService {
cmsAsyncService.updateCmsPublishDataByGitLink(cmsProject, cmsDoc, branchName);
}
private Boolean hasDeptAdminPerms(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;
}
@Override
public int deleteCmsDocById(Long id) {
CmsDoc cmsDoc = cmsDocMapper.selectCmsDocById(id);
@ -1314,20 +1340,33 @@ public class CmsDocServiceImpl implements ICmsDocService {
CmsProject cmsProject = cmsProjectService.selectCmsProjectByDeptId(cmsDoc.getDeptId());
//删除待审核审核未通过文章删除临时分支拒绝合并请求
if (DocAuditStatus.TO_BE_AUDIT.getAuditCode().equals(cmsDoc.getAuditStatus())
|| DocAuditStatus.AUDIT_FAIL.getAuditCode().equals(cmsDoc.getAuditStatus())) {
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()));
cmsDocMapper.deleteCmsDocById(id);
return 1;
if (DocAuditStatus.TO_BE_AUDIT.getAuditCode().equals(cmsDoc.getAuditStatus())) {
//拒绝GitLink合并请求
String gitLinkPullRequestNumber = cmsDoc.getAuditPrNumber().split("-")[0];
gitLinkRequestHelper.doPost(
CmsGitLinkRequestUrl.REFUSE_MERGE_PULL_REQUEST(cmsProject, gitLinkPullRequestNumber), null);
gitLinkRequestHelper.doPost(CmsGitLinkRequestUrl.DELETE_BRANCH(cmsProject, cmsDoc.getAuditTmpBranchName()), null);
return cmsDocMapper.deleteCmsDocById(cmsDoc.getId());
}
if (isCurrentUserZoneAdmin(cmsDoc.getDeptId()) //当前用户为专区管理员
if (DocAuditStatus.AUDIT_FAIL.getAuditCode().equals(cmsDoc.getAuditStatus())) {
return cmsDocMapper.deleteCmsDocById(cmsDoc.getId());
}
// 判读当前用户是否拥有文章直接删除权限
if (hasDeptAdminPerms(cmsDoc.getDeptId(), UserConstants.CMS_PERMS_DOCS_AUDIT) //当前用户为专区管理员
|| isNoAuditRequired(cmsDoc.getDeptId()) //当前专区文章发布无需审核机制
) {
String branchName = CmsConstants.BRANCH_MASTER;
boolean deleteResult = cmsAsyncService.deleteCmsDocForGitLink(cmsDoc, cmsProject, branchName);
if (deleteResult) {
return deleteCmsDocDatabaseById(cmsDoc.getId());
// 拒绝相关合并请求并清理所有临时分支同时删除所有临时申请
List<CmsDoc> tmpDocList = getTmpDocList(cmsDoc);
cmsAsyncService.clearTmpDoc(cmsProject, tmpDocList);
cmsDoc.setAuditStatus(DocAuditStatus.AUDIT_FAIL.getAuditCode());
cmsDoc.setRemark("管理员删除");
return cmsDocMapper.updateCmsDoc(cmsDoc);
}
} else {
//1.创建一条auditType="delete-原文章id",audit_status=2其余字段与待删除文章一致的新文章数据
CmsDoc newDocWithAudit = insertDeletedDocWithAudit(cmsDoc, id);
@ -1337,6 +1376,18 @@ public class CmsDocServiceImpl implements ICmsDocService {
throw new ServiceException("文件删除失败");
}
@Override
public List<CmsDoc> getTmpDocList(CmsDoc cmsDoc) {
CmsDocSearchVo cmsDocSearchVo = new CmsDocSearchVo();
cmsDocSearchVo.setName(cmsDoc.getName());
cmsDocSearchVo.setDeptId(cmsDoc.getDeptId());
cmsDocSearchVo.setIsDir(false);
cmsDocSearchVo.setDirName(cmsDoc.getDirName());
cmsDocSearchVo.setAuditStatus(DocAuditStatus.TO_BE_AUDIT.getAuditCode());
// 拒绝相关合并请求并清理所有临时分支同时删除所有临时申请
return cmsDocMapper.selectCmsDocList(cmsDocSearchVo);
}
public int deleteCmsDocDatabaseById(Long id) {
CmsDoc cmsDoc = cmsDocMapper.selectCmsDocById(id);
if (cmsDoc != null && !cmsDoc.getIsDir()) {

View File

@ -49,6 +49,16 @@ public class CmsProjectController extends BaseController {
return R.ok(changeRoleResult);
}
@InnerAuth
@PutMapping("/changeOrgRole/user/{userId}/operate/{operate}")
@ApiOperation(value = "修改用户所在GItLink组织中的权限", hidden = true)
public R<Boolean> changeUserOrgRole(@PathVariable("userId") Long userId, @PathVariable("operate") Long operate
) {
Boolean changeRoleResult = cmsProjectService.changeUserOrgRole(userId, operate);
return R.ok(changeRoleResult);
}
/**
* 查询专区对应项目列表
*/

View File

@ -0,0 +1,21 @@
package com.microservices.cms.project.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @author otto
*/
@Data
public class GitlinkOrganizationTeamVo implements Serializable {
@ApiModelProperty(value = "团队权限")
private String authorize;
@ApiModelProperty(value = "团队Id")
private Long id;
@ApiModelProperty(value = "团队名称")
private String name;
@ApiModelProperty(value = "团队昵称")
private String nickname;
}

View File

@ -2,6 +2,7 @@ package com.microservices.cms.project.mapper;
import com.microservices.cms.project.domain.CmsProject;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@ -64,4 +65,25 @@ public interface CmsProjectMapper {
CmsProject selectCmsProjectByDeptId(Long deptId);
CmsProject selectCmsProjectByIdentifier(String identifier);
/**
* 全局搜索 - 全文索引查询
*
* @param keyword 搜索关键词
* @param deptId 组织ID可选
* @param offset 偏移量
* @param pageSize 每页数量
* @return 搜索结果列表
*/
List<CmsProject> globalSearch(@Param("keyword") String keyword, @Param("deptId") Long deptId,
@Param("offset") int offset, @Param("pageSize") int pageSize);
/**
* 全局搜索结果计数
*
* @param keyword 搜索关键词
* @param deptId 组织ID可选
* @return 结果数量
*/
Long globalSearchCount(@Param("keyword") String keyword, @Param("deptId") Long deptId);
}

View File

@ -82,4 +82,6 @@ public interface ICmsProjectService {
boolean initZoneCmsProjectByDept(Long deptId);
CmsProject selectCmsProjectByDeptIdAndNoException(Long deptId);
Boolean changeUserOrgRole(Long userId, Long operate);
}

View File

@ -6,6 +6,7 @@ import com.microservices.cms.doc.domain.*;
import com.microservices.cms.doc.service.ICmsAsyncService;
import com.microservices.cms.doc.service.ICmsDocService;
import com.microservices.cms.project.domain.CmsProject;
import com.microservices.cms.project.domain.GitlinkOrganizationTeamVo;
import com.microservices.cms.project.mapper.CmsProjectMapper;
import com.microservices.cms.project.service.ICmsProjectService;
import com.microservices.cms.utils.CmsConstants;
@ -320,6 +321,40 @@ public class CmsProjectServiceImpl implements ICmsProjectService {
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));
@ -338,9 +373,10 @@ public class CmsProjectServiceImpl implements ICmsProjectService {
try {
gitLinkRequestHelper.doDelete(CmsGitLinkRequestUrl.REMOVE_USER_FROM_PROJECT(oldCmsProject), requestBody);
} catch (ServiceException e) {
if (!e.getMessage().contains("用户为组织成员,请到组织下操作")) {
throw new ServiceException(e.getMessage());
}
log.error("将用户从原项目中移除发生异常:{}", e.getMessage());
// if (!e.getMessage().contains("用户为组织成员,请到组织下操作")||!e.getMessage().contains("还不是项目成员")) {
// throw new ServiceException(e.getMessage());
// }
}
}
//将用户加入新的项目

View File

@ -0,0 +1,43 @@
package com.microservices.cms.search.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 文章搜索请求对象
*
* @author microservices
*/
@Data
@ApiModel("文章搜索请求对象")
public class DocSearchRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 搜索关键词
*/
@ApiModelProperty(value = "搜索关键词", required = true)
private String keyword;
/**
* 专区ID
*/
@ApiModelProperty(value = "专区ID")
private Long zoneId;
/**
* 页码
*/
@ApiModelProperty(value = "页码")
private Integer pageNum = 1;
/**
* 每页数量
*/
@ApiModelProperty(value = "每页数量")
private Integer pageSize = 20;
}

View File

@ -0,0 +1,63 @@
package com.microservices.cms.search.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* 全局搜索响应对象
*
* @author microservices
*/
@Data
@ApiModel("文章搜索响应对象")
public class DocSearchResponse implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 总结果数
*/
@ApiModelProperty(value = "总结果数")
private Long total;
/**
* 搜索结果列表
*/
@ApiModelProperty(value = "搜索结果列表")
private List<DocSearchResult> results;
/**
* 各类型结果数量统计
*/
@ApiModelProperty(value = "各类型结果数量统计")
private Map<String, Long> typeCounts;
/**
* 搜索耗时毫秒
*/
@ApiModelProperty(value = "搜索耗时(毫秒)")
private Long searchTime;
/**
* 搜索关键词
*/
@ApiModelProperty(value = "搜索关键词")
private String keyword;
/**
* 当前页码
*/
@ApiModelProperty(value = "当前页码")
private Integer pageNum;
/**
* 每页数量
*/
@ApiModelProperty(value = "每页数量")
private Integer pageSize;
}

View File

@ -0,0 +1,65 @@
package com.microservices.cms.search.dto;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 文章搜索结果对象
*
* @author microservices
*/
@Data
@ApiModel("文章搜索结果对象")
public class DocSearchResult implements Serializable {
private static final long serialVersionUID = 1L;
/**
* ID
*/
@ApiModelProperty(value = "ID")
private Long id;
/**
* 标题
*/
@ApiModelProperty(value = "标题")
private String title;
/**
* 内容摘要
*/
@ApiModelProperty(value = "内容摘要")
private String content;
/**
* 组织ID
*/
@ApiModelProperty(value = "组织ID")
private Long deptId;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty(value = "创建时间")
private Date createTime;
/**
* 相关度评分
*/
@ApiModelProperty(value = "相关度评分")
private Double score;
/**
* 扩展数据JSON格式
*/
@ApiModelProperty(value = "扩展数据")
private JSONObject extendData;
}

View File

@ -0,0 +1,20 @@
package com.microservices.cms.search.service;
import com.microservices.cms.search.dto.DocSearchRequest;
import com.microservices.cms.search.dto.DocSearchResponse;
/**
* 全局搜索服务接口
*
* @author microservices
*/
public interface IGlobalSearchService {
/**
* 全局搜索
*
* @param request 搜索请求
* @return 搜索响应
*/
DocSearchResponse docSearch(DocSearchRequest request);
}

View File

@ -0,0 +1,215 @@
package com.microservices.cms.search.service.impl;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.cms.doc.domain.CmsDoc;
import com.microservices.cms.doc.domain.CmsDocWithScore;
import com.microservices.cms.doc.mapper.CmsDocMapper;
import com.microservices.cms.doc.service.ICmsDocService;
import com.microservices.cms.search.service.IGlobalSearchService;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.redis.service.RedisService;
import com.microservices.cms.search.dto.DocSearchRequest;
import com.microservices.cms.search.dto.DocSearchResponse;
import com.microservices.cms.search.dto.DocSearchResult;
import com.microservices.common.security.utils.SecurityUtils;
import com.microservices.system.api.model.LoginUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
/**
* 全局搜索服务实现
*
* @author microservices
*/
@Service
public class GlobalSearchServiceImpl implements IGlobalSearchService {
private static final Logger logger = LoggerFactory.getLogger(GlobalSearchServiceImpl.class);
@Autowired
private CmsDocMapper cmsDocMapper;
@Autowired
private RedisService redisService;
@Autowired
private ICmsDocService cmsDocService;
private static final String SEARCH_CACHE_PREFIX = "search:cache:";
private static final Long CACHE_EXPIRE_SECONDS = 300L; // 5分钟
@Override
public DocSearchResponse docSearch(DocSearchRequest request) {
long startTime = System.currentTimeMillis();
// 参数校验
if (StringUtils.isEmpty(request.getKeyword())) {
return buildEmptyResponse(request);
}
// 1. 尝试从缓存获取仅第一页
if (request.getPageNum() == 1) {
String cacheKey = buildCacheKey(request);
DocSearchResponse cached = redisService.getCacheObject(SEARCH_CACHE_PREFIX + cacheKey);
if (cached != null) {
cached.setSearchTime(System.currentTimeMillis() - startTime);
return cached;
}
}
// 2. 并行搜索各类型
List<DocSearchResult> allResults = new ArrayList<>();
Map<String, Long> typeCounts = new HashMap<>();
// 获取当前用户信息
LoginUser loginUser = SecurityUtils.getLoginUser();
// 搜索CMS文章
List<DocSearchResult> docResults = searchDocs(request);
allResults.addAll(docResults);
typeCounts.put("doc", (long) docResults.size());
// 3. 按相关度评分排序
allResults.sort((a, b) -> {
if (b.getScore() != null && a.getScore() != null) {
return b.getScore().compareTo(a.getScore());
}
return 0;
});
// 4. 分页
int total = allResults.size();
int start = (request.getPageNum() - 1) * request.getPageSize();
int end = Math.min(start + request.getPageSize(), total);
List<DocSearchResult> pagedResults = start < total ?
allResults.subList(start, end) : new ArrayList<>();
// 5. 构建响应
DocSearchResponse response = new DocSearchResponse();
response.setTotal((long) total);
response.setResults(pagedResults);
response.setTypeCounts(typeCounts);
response.setKeyword(request.getKeyword());
response.setPageNum(request.getPageNum());
response.setPageSize(request.getPageSize());
response.setSearchTime(System.currentTimeMillis() - startTime);
// 6. 缓存结果仅第一页
if (request.getPageNum() == 1) {
String cacheKey = buildCacheKey(request);
redisService.setCacheObject(SEARCH_CACHE_PREFIX + cacheKey, response, CACHE_EXPIRE_SECONDS, TimeUnit.SECONDS);
}
// 7. 记录搜索日志
recordSearchLog(request, response, loginUser);
return response;
}
/**
* 记录搜索日志
*/
private void recordSearchLog(DocSearchRequest request, DocSearchResponse response, LoginUser loginUser) {
Long userId = loginUser != null ? loginUser.getUserId() : null;
String searchTypes = "文章";
Long deptId = cmsDocService.getDeptIdByZoneId(request.getZoneId());
logger.info("全局搜索 - 关键词: {}, 用户ID: {}, 耗时: {}ms, 结果数: {}, 类型: {}, 专区Id: {}, 组织ID: {}",
request.getKeyword(),
userId,
response.getSearchTime(),
response.getTotal(),
searchTypes,
request.getZoneId(),
deptId
);
}
/**
* 搜索CMS文章
*/
private List<DocSearchResult> searchDocs(DocSearchRequest request) {
List<DocSearchResult> results = new ArrayList<>();
try {
// 使用部门ID参数
Long deptId = cmsDocService.getDeptIdByZoneId(request.getZoneId());
// 使用新的带评分的查询方法
List<CmsDocWithScore> docs = cmsDocMapper.globalSearchWithScore(
request.getKeyword(),
deptId,
(request.getPageNum() - 1) * request.getPageSize(),
request.getPageSize()
);
for (CmsDocWithScore doc : docs) {
DocSearchResult result = new DocSearchResult();
result.setId(doc.getId());
result.setTitle(highlightKeyword(doc.getName(), request.getKeyword()));
result.setContent(highlightKeyword(doc.getSummary(), request.getKeyword()));
result.setDeptId(doc.getDeptId());
result.setCreateTime(doc.getCreateTime());
// 使用数据库返回的实际搜索评分
Double score = doc.getSearchScore();
if (score == null || score == 0.0) {
// 对于 LIKE 匹配无全文评分赋予最小评分
score = 0.1;
}
result.setScore(score);
JSONObject extendData = new JSONObject();
extendData.put("visits", doc.getVisits());
extendData.put("publishTime", doc.getPublishTime());
result.setExtendData(extendData);
results.add(result);
}
} catch (Exception e) {
// 记录错误但继续处理其他类型
System.err.println("搜索CMS文章失败: " + e.getMessage());
}
return results;
}
/**
* 构建缓存键
*/
private String buildCacheKey(DocSearchRequest request) {
Long deptId = cmsDocService.getDeptIdByZoneId(request.getZoneId());
StringBuilder key = new StringBuilder(request.getKeyword());
key.append(":").append(String.join(",", "Doc"));
if (deptId != null) {
key.append(":dept=").append(deptId);
}
return key.toString();
}
/**
* 构建空响应
*/
private DocSearchResponse buildEmptyResponse(DocSearchRequest request) {
DocSearchResponse response = new DocSearchResponse();
response.setTotal(0L);
response.setResults(new ArrayList<>());
response.setTypeCounts(new HashMap<>());
response.setKeyword(request.getKeyword());
response.setPageNum(request.getPageNum());
response.setPageSize(request.getPageSize());
response.setSearchTime(0L);
return response;
}
/**
* 将文本中的关键词用 <em> 标签包裹不区分大小写
*/
private String highlightKeyword(String text, String keyword) {
if (StringUtils.isEmpty(text) || StringUtils.isEmpty(keyword)) {
return text;
}
return text.replaceAll("(?i)(" + Pattern.quote(keyword) + ")", "<em>$1</em>");
}
}

View File

@ -12,6 +12,17 @@ import java.util.Map;
public class CmsGitLinkRequestUrl extends GitLinkRequestUrl {
public static GitLinkRequestUrl ADD_USER_TO_ORGANIZATION_TEAM(String orgIdentifier, Long teamId) {
return getAdminGitLinkRequestUrl(String.format("/api/organizations/%s/teams/%d/team_users.json", orgIdentifier, teamId));
}
public static String TEAMS_BY_ORGANIZATION_URL(Long orgId) {
return String.format("/api/organizations/%s/teams.json", orgId);
}
public static GitLinkRequestUrl DELETE_USER_FROM_ORGANIZATION_TEAM(String orgIdentifier, Long teamId, String username) {
return getAdminGitLinkRequestUrl(String.format("/api/organizations/%s/teams/%d/team_users/%s.json", orgIdentifier, teamId, username));
}
/**
* 创建项目
*/
@ -66,7 +77,7 @@ public class CmsGitLinkRequestUrl extends GitLinkRequestUrl {
public static GitLinkRequestUrl DELETE_BRANCH(CmsProject cmsProject, String branchName) {
return getGitLinkRequestUrl(
String.format("/api/v1/%s/%s/branches/%s.json", cmsProject.getUsername(), cmsProject.getIdentifier(), branchName)
String.format("/api/v1/%s/%s/branches/delete.json?branch_name=%s", cmsProject.getUsername(), cmsProject.getIdentifier(), branchName)
);
}

View File

@ -37,6 +37,7 @@
<result property="auditTmpBranchName" column="audit_tmp_branch_name"/>
<result property="auditPrNumber" column="audit_pr_number"/>
<result property="keywords" column="keywords"/>
<result property="remark" column="remark"/>
</resultMap>
<sql id="selectCmsDocVo">
@ -71,7 +72,8 @@
audit_type,
audit_tmp_branch_name,
keywords,
audit_pr_number
audit_pr_number,
remark
from cms_doc
</sql>
@ -103,7 +105,43 @@
cd.is_homepage,
cd.editor_type,
cd.keywords,
cd.is_dir
cd.is_dir,
cd.remark
from cms_doc cd
</sql>
<sql id="selectCmsDocWithScoreVo">
select cd.id,
cd.create_by,
cd.create_time,
cd.update_by,
cd.update_time,
cd.name,
cd.file_name,
cd.file_path,
cd.file_sha,
cd.file_size,
cd.file_url,
cd.html_url,
cd.visits,
cd.sort,
cd.publish_time,
cd.publish_user,
cd.publish_user_image,
cd.publish_user_url,
cd.publish_user_nickname,
cd.summary,
cd.head_img,
cd.dir_name,
cd.dept_id,
cd.issue_id,
cd.is_homepage,
cd.editor_type,
cd.keywords,
cd.is_dir,
-- 计算全文搜索相关度评分
MATCH(cd.name, cd.summary) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE) AS search_score,
cd.remark
from cms_doc cd
</sql>
@ -141,7 +179,8 @@
cd.is_homepage,
cd.editor_type,
cd.keywords,
cd.is_dir
cd.is_dir,
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>
@ -458,6 +497,7 @@
<if test="auditTmpBranchName != null">audit_tmp_branch_name = #{auditTmpBranchName},</if>
<if test="auditPrNumber != null">audit_pr_number = #{auditPrNumber},</if>
<if test="keywords != null">keywords = #{keywords},</if>
<if test="remark != null">remark = #{remark},</if>
</trim>
where id = #{id}
</update>
@ -494,4 +534,70 @@
#{id}
</foreach>
</delete>
<!-- 全局搜索 - 全文索引查询(含 LIKE 后备搜索) -->
<select id="globalSearch" resultMap="CmsDocResult">
<include refid="selectCmsDocAliasVo"/>
WHERE
cd.is_dir = 0
AND cd.audit_status = '1'
AND (
MATCH(cd.name, cd.summary) AGAINST(#{keyword} IN BOOLEAN MODE)
OR cd.name LIKE CONCAT('%', #{keyword}, '%')
OR cd.summary LIKE CONCAT('%', #{keyword}, '%')
)
<if test="deptId != null">
AND cd.dept_id = #{deptId}
</if>
ORDER BY
CASE
WHEN MATCH(cd.name, cd.summary) AGAINST(#{keyword} IN BOOLEAN MODE) > 0 THEN 1
ELSE 2
END,
cd.update_time DESC
LIMIT #{offset}, #{pageSize}
</select>
<select id="globalSearchCount" resultType="java.lang.Long">
SELECT COUNT(*)
FROM cms_doc cd
WHERE
cd.is_dir = 0
AND cd.audit_status = '1'
AND (
MATCH(cd.name, cd.summary) AGAINST(#{keyword} IN BOOLEAN MODE)
OR cd.name LIKE CONCAT('%', #{keyword}, '%')
OR cd.summary LIKE CONCAT('%', #{keyword}, '%')
)
<if test="deptId != null">
AND cd.dept_id = #{deptId}
</if>
</select>
<!-- ResultMap for CmsDocWithScore -->
<resultMap type="com.microservices.cms.doc.domain.CmsDocWithScore" id="CmsDocWithScoreResult" extends="CmsDocResult">
<result property="searchScore" column="search_score"/>
</resultMap>
<!-- 全局搜索 - 带评分的全文索引查询 -->
<select id="globalSearchWithScore" resultMap="CmsDocWithScoreResult">
<include refid="selectCmsDocWithScoreVo"/>
WHERE
cd.is_dir = 0
AND cd.audit_status = '1'
AND (
MATCH(cd.name, cd.summary) AGAINST(#{keyword} IN BOOLEAN MODE)
OR cd.name LIKE CONCAT('%', #{keyword}, '%')
OR cd.summary LIKE CONCAT('%', #{keyword}, '%')
)
<if test="deptId != null">
AND cd.dept_id = #{deptId}
</if>
ORDER BY
-- 主要排序:按相关度评分降序
search_score DESC,
-- 次要排序:更新时间降序
cd.update_time DESC
LIMIT #{offset}, #{pageSize}
</select>
</mapper>

View File

@ -159,4 +159,38 @@
item="id" collection="array" open="(" separator="," close=")">#{id}
</foreach>
</delete>
<!-- 全局搜索 - 全文索引查询 -->
<select id="globalSearch" resultMap="CmsProjectResult">
SELECT id,
name,
description,
user_id,
dept_id,
identifier,
username,
create_by,
create_time,
update_time
FROM cms_project cp
WHERE
MATCH(cp.name) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE)
<if test="deptId != null">
AND cp.dept_id = #{deptId}
</if>
ORDER BY
MATCH(cp.name) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE) DESC,
cp.update_time DESC
LIMIT #{offset}, #{pageSize}
</select>
<select id="globalSearchCount" resultType="java.lang.Long">
SELECT COUNT(*)
FROM cms_project cp
WHERE
MATCH(cp.name) AGAINST(#{keyword} IN NATURAL LANGUAGE MODE)
<if test="deptId != null">
AND cp.dept_id = #{deptId}
</if>
</select>
</mapper>

View File

@ -289,6 +289,24 @@ public class SysUserDeptRoleController extends BaseController {
return R.ok(userInDeptHasRole);
}
/**
* 检查用户是否在指定部门或父级部门下具有指定菜单权限
*
* @param deptId 组织id
* @param userId 用户id
* @param perms 菜单权限
* @return 用户是否指定部门或父级部门下具有菜单权限 是true 否false
*/
@ApiOperation(value = "检查用户是否在指定部门或父级部门下具有指定菜单权限", hidden = true)
@InnerAuth
@GetMapping("/checkUserInDeptOrParentDeptHasPerms")
public R<Boolean> checkUserInDeptOrParentDeptHasPerms(@ApiParam(name = "deptId", value = "组织id", required = true) Long deptId,
@ApiParam(name = "userId", value = "用户id", required = true) Long userId,
@ApiParam(name = "perms", value = "菜单权限", required = true) String perms) {
Boolean userInDeptHasRole = sysUserDeptRoleService.checkUserInDeptOrParentDeptHasPerms(deptId, userId, perms);
return R.ok(userInDeptHasRole);
}
/**
* 检查用户是否在指定部门下
*
@ -304,4 +322,12 @@ public class SysUserDeptRoleController extends BaseController {
Boolean userInDept = sysUserDeptRoleService.checkUserInDept(currentDeptId, userId);
return R.ok(userInDept);
}
@ApiOperation(value = "用户是否在指定部门下", hidden = true)
@InnerAuth
@GetMapping("/getSysUserDeptRoleByDeptIdAndUserId")
public R<SysUserDeptRole> getSysUserDeptRoleByDeptIdAndUserId(@ApiParam(name = "deptId", value = "组织id", required = true) Long deptId,
@ApiParam(name = "userId", value = "用户id", required = true) Long userId) {
SysUserDeptRole userIdentifier = sysUserDeptRoleService.getSysUserDeptRoleByDeptIdAndUserId(deptId, userId);
return R.ok(userIdentifier);
}
}

View File

@ -151,4 +151,6 @@ public interface SysUserDeptRoleMapper {
* @param state 状态
*/
void updateUserIdentifyStateByUserId(@Param("userId") Long userId, @Param("state") String state);
int selectUserInDeptOrParentDeptHasPermsCount(@Param("deptId") Long deptId, @Param("userId") Long userId, @Param("perms") String perms);
}

View File

@ -160,4 +160,8 @@ public interface ISysUserDeptRoleService {
* @param userId 用户Id
*/
void deleteSysUserDeptRoleByDeleteUser(@Param("userId") Long userId);
Boolean checkUserInDeptOrParentDeptHasPerms(Long deptId, Long userId, String perms);
SysUserDeptRole getSysUserDeptRoleByDeptIdAndUserId(Long deptId, Long userId);
}

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,7 +124,10 @@ public class SysUserDeptRoleServiceImpl implements ISysUserDeptRoleService {
}
for (Long userId : userIds) {
SysUserDeptRole userIdentify = sysUserDeptRoleMapper.selectSysUserDeptRoleByUserIdAndDeptId(userId, deptId);
updateUserGitLinkRole(userId, deptId, userIdentify.getRoleId(), null, OPERATE_REMOVE);
if (userIdentify != null) {
updateUserGitLinkRole(userId, deptId, userIdentify.getRoleId(), null, OPERATE_REMOVE);
}
}
int success = deleteUserIdentityByDeptIdAndUserIds(sysDept, userIds);
for (Long userId : userIds) {
@ -231,8 +234,13 @@ public class SysUserDeptRoleServiceImpl implements ISysUserDeptRoleService {
if (secondaryDeptIds == null) {
return;
}
if (!secondaryDeptIds.contains(dept.getDeptId())) {
SysDept parentDept = sysDeptMapper.selectDeptById(dept.getParentId());
if (parentDept == null) {
return;
}
if (dept.getParentId() == 0L || (parentDept.getParentId() == 0L && dept.getDeptType() == CacheConstants.DEPT_TYPE_TSZQ)) {
remoteCmsService.changeUserOrgRole(userId, operate, SecurityConstants.INNER);
} else if (!secondaryDeptIds.contains(dept.getDeptId())) {
if (secondaryDeptIds.contains(dept.getParentId())) {
switch (dept.getDeptType()) {
case CacheConstants.DEPT_TYPE_TSZQ: {
@ -568,6 +576,17 @@ public class SysUserDeptRoleServiceImpl implements ISysUserDeptRoleService {
return false;
}
@Override
public Boolean checkUserInDeptOrParentDeptHasPerms(Long deptId, Long userId, String perms) {
int hasPermsCount = sysUserDeptRoleMapper.selectUserInDeptOrParentDeptHasPermsCount(deptId, userId, perms);
return hasPermsCount > 0;
}
@Override
public SysUserDeptRole getSysUserDeptRoleByDeptIdAndUserId(Long deptId, Long userId) {
return sysUserDeptRoleMapper.selectSysUserDeptRoleByUserIdAndDeptId(userId, deptId);
}
@Override
public Boolean checkUserInDept(Long currentDeptId, Long userId) {
SysUserDeptRole sysUserDeptRole = sysUserDeptRoleMapper.selectSysUserDeptRoleByUserIdAndDeptId(userId, currentDeptId);

View File

@ -234,6 +234,18 @@
and udr.dept_id = #{deptId}
</where>
</select>
<select id="selectUserInDeptOrParentDeptHasPermsCount" resultType="java.lang.Integer">
SELECT count(DISTINCT sm.menu_id)
FROM sys_menu sm
INNER JOIN sys_role_menu srm ON sm.menu_id = srm.menu_id
INNER JOIN sys_user_dept_role sudr ON srm.role_id = sudr.role_id
CROSS JOIN (SELECT CONCAT(ancestors, ',', dept_id) AS dept_ancestors
FROM sys_dept
WHERE dept_id = #{deptId}) vars
WHERE sudr.user_id = #{userId}
AND FIND_IN_SET(sudr.dept_id, vars.dept_ancestors)
AND sm.perms = #{perms};
</select>
<insert id="insertSysUserDeptRole" parameterType="com.microservices.system.api.domain.SysUserDeptRole">
insert into sys_user_dept_role

View File

@ -1,5 +1,6 @@
package com.microservices.zone.common.controller;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.web.controller.BaseController;
import com.microservices.common.core.web.domain.AjaxResult;
import com.microservices.common.core.web.domain.GenericsAjaxResult;
@ -10,16 +11,25 @@ import com.microservices.common.security.annotation.RequiresPermissions;
import com.microservices.common.security.utils.SecurityUtils;
import com.microservices.zone.detail.domain.vo.ZoneFrontRoleVo;
import com.microservices.zone.detail.service.IZoneDetailService;
import com.microservices.zone.project.domain.ZoneProjectType;
import com.microservices.zone.project.domain.vo.*;
import com.microservices.zone.project.service.IZoneProjectService;
import com.microservices.zone.project.service.IZoneProjectTypeService;
import com.microservices.zone.resource.domain.ZoneResource;
import com.microservices.zone.resource.domain.vo.*;
import com.microservices.zone.resource.service.IAiSkillParseService;
import com.microservices.zone.resource.service.IZoneResourceService;
import io.swagger.annotations.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* @author otto
*/
@ -34,9 +44,15 @@ public class FrontController extends BaseController {
@Autowired
private IAiSkillParseService aiSkillParseService;
@Autowired
private IZoneProjectService zoneProjectService;
@Autowired
private IZoneDetailService zoneDetailService;
@Autowired
private IZoneProjectTypeService zoneProjectTypeService;
/**
* 查询我的资源列表
*/
@ -93,6 +109,72 @@ public class FrontController extends BaseController {
return toAjax(zoneResourceService.deleteMyZoneResourceByIds(ids));
}
/**
* 查询GitLink项目列表
*/
@GetMapping("/zone/{zoneId}/myGitlinkProjectList")
@ApiOperation("查询专区下我的GitLink项目列表")
public GenericsTableDataInfo<JSONObject> selectMyGitlinkProjectListByZone(
@ApiParam(name = "zoneId", value = "专区Id") @PathVariable Long zoneId
, MyGitLinkProjectSearchVo myGitLinkProjectSearchVo) {
return zoneProjectService.selectMyGitlinkProjectListByZone(zoneId, myGitLinkProjectSearchVo);
}
/**
* 查询我的项目列表
*/
@RequiresPermissions("zone:front:project:myList")
@GetMapping("/myProjectList")
@ApiOperation(value = "查询我的项目聚合列表")
public GenericsTableDataInfo<ZoneProjectDataVo> myProjectList(ZoneProjectSearchVo zoneProjectSearchVo) {
zoneProjectSearchVo.setCreateBy(SecurityUtils.getUsername());
return zoneProjectService.selectMyZoneProjectList(zoneProjectSearchVo);
}
/**
* 查询我的项目详情
*/
@RequiresPermissions("zone:front:project:query")
@GetMapping("/myProject/{id}")
@ApiOperation(value = "查询我的项目聚合详情")
public GenericsAjaxResult<ZoneProjectDataVo> myProject(@PathVariable("id") Long id) {
return genericsSuccess(zoneProjectService.selectZoneProjectDataById(id));
}
/**
* 新增项目
*/
@RequiresPermissions("zone:front:project:add")
@Log(title = "专区前台项目操作", businessType = BusinessType.INSERT)
@PostMapping("/addProject")
@ApiOperation(value = "新增项目聚合")
public AjaxResult addProject(@RequestBody @Validated List<ZoneProjectInputVo> zoneProjectInputVoList) {
return success(zoneProjectService.insertBatchZoneProject(zoneProjectInputVoList));
}
/**
* 编辑项目
*/
@RequiresPermissions("zone:front:project:edit")
@Log(title = "专区前台项目操作", businessType = BusinessType.UPDATE)
@PutMapping("/editProject/{projectId}")
@ApiOperation(value = "编辑项目聚合")
public AjaxResult editProject(@PathVariable("projectId") Long projectId,@RequestBody ZoneProjectFrontUpdateVo zoneProjectFrontUpdateVo) {
return success(zoneProjectService.updateMyZoneProject(projectId, zoneProjectFrontUpdateVo.toZoneProjectUpdateVo()));
}
/**
* 删除项目
*/
@RequiresPermissions("zone:front:project:remove")
@Log(title = "专区前台项目操作", businessType = BusinessType.DELETE)
@DeleteMapping("/removeProject/{ids}")
@ApiOperation(value = "删除项目聚合")
public AjaxResult removeProject(@PathVariable Long[] ids) {
return toAjax(zoneProjectService.deleteMyZoneProjectByIds(ids));
}
/**
* 检查当前登录用户在当前专区下角色
*/

View File

@ -32,7 +32,6 @@ import com.microservices.zone.project.domain.vo.ZoneProjectSearchVo;
import com.microservices.zone.project.domain.vo.ZoneStatisticsVo;
import com.microservices.zone.project.service.IZoneProjectService;
import com.microservices.zone.project.service.IZoneProjectTypeService;
import com.microservices.zone.resource.domain.ZoneResource;
import com.microservices.zone.resource.domain.ZoneResourceDomain;
import com.microservices.zone.resource.domain.ZoneResourceType;
import com.microservices.zone.resource.domain.vo.ZoneResourceDataVo;
@ -41,6 +40,10 @@ import com.microservices.zone.resource.enums.AiResourceType;
import com.microservices.zone.resource.service.IZoneResourceDomainService;
import com.microservices.zone.resource.service.IZoneResourceService;
import com.microservices.zone.resource.service.IZoneResourceTypeService;
import com.microservices.zone.search.dto.GlobalSearchRequest;
import com.microservices.zone.search.dto.GlobalSearchResponse;
import com.microservices.zone.search.dto.GlobalSearchResult;
import com.microservices.zone.search.service.IGlobalSearchService;
import com.microservices.zone.specialProject.domain.ZoneSpecialProject;
import com.microservices.zone.specialProject.domain.ZoneSpecialProjectRelevancy;
import com.microservices.zone.specialProject.domain.ZoneSpecialProjectReport;
@ -99,6 +102,9 @@ public class OpenController extends BaseController {
@Autowired
private IZoneVisitsService zoneVisitsService;
@Autowired
private IGlobalSearchService globalSearchService;
/**
* 新增特色专区用户浏览
*/
@ -416,6 +422,19 @@ public class OpenController extends BaseController {
return getGenericsDataTable(list);
}
/**
* 全局搜索接口POST方法
*
* @return 搜索结果
*/
@ApiOperation("全局搜索")
@PostMapping("/global")
public GenericsTableDataInfo<GlobalSearchResult> globalSearch(@RequestBody GlobalSearchRequest globalSearchRequest) {
logger.info("全局搜索请求: keyword={}, types={}", globalSearchRequest.getKeyword(), globalSearchRequest.getTypes());
GlobalSearchResponse response = globalSearchService.globalSearch(globalSearchRequest);
return new GenericsTableDataInfo<>(response.getResults(), response.getTotal());
}
// ==================== AI资源广场 - 开放接口 ====================
/**

View File

@ -1,9 +1,12 @@
package com.microservices.zone.common.service;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.zone.detail.domain.ZoneDetail;
import com.microservices.zone.project.domain.ZoneProject;
import com.microservices.zone.resource.domain.ZoneResourceType;
import java.util.List;
/**
* 特色专区异步Service接口
*
@ -51,4 +54,6 @@ public interface IZoneAsyncService {
void setProjectGitlinkInfoCache(ZoneProject zoneProject);
void clearZoneProjectSortByScoreCache(ZoneProject zoneProject);
List<JSONObject> getGitlinkProjectsDetail(List<String> gitLinkProjectIdList);
}

View File

@ -62,10 +62,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.*;
import java.util.stream.Collectors;
/**
@ -522,6 +519,41 @@ public class ZoneAsyncServiceImpl implements IZoneAsyncService {
});
}
@Override
public List<JSONObject> getGitlinkProjectsDetail(List<String> gitLinkProjectIdList) {
int queryCount = gitLinkProjectIdList.size() / 50 + 1;
CountDownLatch countDownLatch = new CountDownLatch(queryCount);
List<JSONObject> gitLinkProjectList = new ArrayList<>();
for (int i = 0; i < queryCount; i++) {
int finalI = i;
threadPoolExecutor.execute(() -> {
try {
int pageMaxCount = (finalI + 1) * 50;
if (pageMaxCount >= gitLinkProjectIdList.size()) {
pageMaxCount = gitLinkProjectIdList.size();
}
List<String> tmpIdList = gitLinkProjectIdList.subList(finalI * 50, pageMaxCount);
JSONObject result = gitLinkRequestHelper.doGet(
ZoneGitLinkRequestUrl.PROJECTS("?ids=" + String.join(ZoneConstants.ARRAY_SPLIT_SYMBOLS, tmpIdList))
);
gitLinkProjectList.addAll(result.getList(ZoneConstants.GITLINK_RESPONSE_PROJECTS_KEY, JSONObject.class));
} catch (Exception e) {
logger.error("异步批量获取项目详细信息失败:{}", e.getMessage());
} finally {
countDownLatch.countDown();
}
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
logger.error("根据gitlinkProjectId调用Gitlink接口批量获取项目详细信息失败{}", e.getMessage());
throw new ServiceException("获取平台项目详细信息失败");
}
return gitLinkProjectList;
}
private Double calcProjectScore(ZoneProject zoneProject, String startDate, String endDate) {
Double theWeekScore = forgeProjectStatisticsMapper
.selectLastWeekScoreByProjectId(zoneProject.getGitlinkProjectId(), startDate, endDate);

View File

@ -190,6 +190,11 @@ public class ZoneDetail extends BaseEntity {
@ApiModelProperty(value = "资源是否需要审核0:不需要审核1需要审核")
private Integer resourceNeedAudit;
/**
* 项目是否需要审核0不需要1需要
*/
@ApiModelProperty(value = "项目是否需要审核0:不需要审核1需要审核")
private Integer projectNeedAudit;
/**
* 资讯是否锁定中0未锁定1锁定

View File

@ -155,6 +155,12 @@ public class ZoneDetailDataVo extends BaseEntity {
@ApiModelProperty(value = "资源是否需要审核0:不需要审核1需要审核")
private Integer resourceNeedAudit;
/**
* 项目是否需要审核0不需要1需要
*/
@ApiModelProperty(value = "项目是否需要审核0:不需要审核1需要审核")
private Integer projectNeedAudit;
/**
* 资讯是否锁定中0未锁定1锁定
*/

View File

@ -123,6 +123,12 @@ public class ZoneDetailUpdateVo {
@ApiModelProperty(value = "资源是否需要审核0:不需要审核1需要审核")
private Integer resourceNeedAudit;
/**
* 项目是否需要审核0不需要1需要
*/
@ApiModelProperty(value = "项目是否需要审核0:不需要审核1需要审核")
private Integer projectNeedAudit;
/**
* 资讯是否锁定中0未锁定1锁定
*/

View File

@ -20,6 +20,12 @@ public class ZoneFrontRoleVo {
@ApiModelProperty(value = "资讯管理访问地址")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private String docManageUrl;
@ApiModelProperty(value = "项目管理访问地址")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private String projectManageUrl;
@ApiModelProperty(value = "用户访问组织Id")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private Long deptId;
}

View File

@ -24,6 +24,7 @@ import com.microservices.system.api.RemoteUserService;
import com.microservices.system.api.domain.SysDept;
import com.microservices.system.api.domain.SysDictData;
import com.microservices.system.api.domain.SysUserDeptRole;
import com.microservices.system.api.model.LoginUser;
import com.microservices.system.api.utils.FeignUtils;
import com.microservices.zone.common.service.IZoneAsyncService;
import com.microservices.zone.detail.domain.ZoneDetail;
@ -108,14 +109,16 @@ public class ZoneDetailServiceImpl implements IZoneDetailService {
private IZoneSpecialProjectService zoneSpecialProjectService;
@Value("${zone.zoneAdminUrl}")
private String zoneAdminUrl;
@Value("${zone.zoneConfigurationUrl}")
private String zoneConfigurationUrl;
@Value("${zone.zoneConfigurationPath:/zone/configuration}")
private String zoneConfigurationPath;
@Value("${zone.adminUrl}")
private String adminUrl;
@Value("${zone.resourceManageUrl}")
private String resourceManageUrl;
@Value("${zone.docManageUrl}")
private String docManageUrl;
@Value("${zone.resourceManagePath:/zone/resource/resource}")
private String resourceManagePath;
@Value("${zone.docManagePath:/zone/content/doc}")
private String docManagePath;
@Value("${zone.projectManagePath:/zone/project/list}")
private String projectManagePath;
@Autowired
private RedisService redisService;
@ -202,13 +205,13 @@ public class ZoneDetailServiceImpl implements IZoneDetailService {
Date[] lastWeek = getLastWeek();
//专区文章数统计
ZoneDetailDataVo articlesGroupByKey = zoneDetailMapper
.countArticlesGroupByKey(lastWeek[0],lastWeek[1],zoneDetail.getKey()) ;
.countArticlesGroupByKey(lastWeek[0], lastWeek[1], zoneDetail.getKey());
if (articlesGroupByKey != null) {
zoneDetailDataVo.setWeekArticles(articlesGroupByKey.getWeekArticles());
}
//专区浏览数据统计
ZoneDetailDataVo puVsGroupByKey = zoneDetailMapper
.countPUVsGroupByKey(lastWeek[0],lastWeek[1],zoneDetail.getKey()) ;
.countPUVsGroupByKey(lastWeek[0], lastWeek[1], zoneDetail.getKey());
if (puVsGroupByKey != null) {
zoneDetailDataVo.setWeekPV(puVsGroupByKey.getWeekPV());
zoneDetailDataVo.setWeekUV(puVsGroupByKey.getWeekUV());
@ -277,6 +280,16 @@ public class ZoneDetailServiceImpl implements IZoneDetailService {
throw new ServiceException("当前专区下存在待审核资源,需完成审核后才能将专区资源修改为不需要审核(专区名称[%s],待审核资源数量[%d])", oldZoneDetail.getName(), notAuditCount);
}
}
// 专区项目聚合审核设置由需要审核修改为不需要审核时需检查当前专区下是否存在待审核项目聚合如果存在需要处理完成后再修改配置
if (zoneDetail.getProjectNeedAudit() != null && (0 == zoneDetail.getProjectNeedAudit())) {
ZoneProjectSearchVo zoneProjectSearchVo = new ZoneProjectSearchVo();
zoneProjectSearchVo.setZoneId(zoneDetail.getId());
zoneProjectSearchVo.setAuditStatus(ZoneConstants.PROJECT_NOT_AUDIT);
Integer notAuditCount = zoneProjectMapper.selectZoneProjectListCount(zoneProjectSearchVo);
if (notAuditCount > 0) {
throw new ServiceException("当前专区下存在待审核项目聚合,需完成审核后才能将专区项目聚合修改为不需要审核(专区名称[%s],待审核项目聚合数量[%d])", oldZoneDetail.getName(), notAuditCount);
}
}
// 专区资讯审核设置由需要审核修改为不需要审核时需检查当前专区下是否存在待审核资讯如果存在需要处理完成后再修改配置
if (zoneDetail.getDocNeedAudit() != null && (0 == zoneDetail.getDocNeedAudit())) {
Long notAuditCount = FeignUtils.getReturnData(
@ -505,14 +518,14 @@ public class ZoneDetailServiceImpl implements IZoneDetailService {
.filter(x -> zoneDetail.getDeptId().equals(x.getDeptId()))
.findFirst().orElse(null);
if (userIdentify != null) {
return genChangeCurrentUserIdentityUrl(userIdentify, gitLinkToken, zoneId, notMemberParentUserIdentify);
return genChangeCurrentUserIdentityUrl(userIdentify, zoneId, notMemberParentUserIdentify);
}
// 当不存在当前专区对应的管理权限时查找在父级组织中是否存在身份
userIdentify = userIdentifyList.stream()
.filter(x -> deptAncestors.contains(String.valueOf(x.getDeptId())))
.findFirst().orElse(null);
if (userIdentify != null) {
return genChangeCurrentUserIdentityUrl(userIdentify, gitLinkToken, zoneId, notMemberParentUserIdentify);
return genChangeCurrentUserIdentityUrl(userIdentify, zoneId, notMemberParentUserIdentify);
}
}
return null;
@ -525,12 +538,11 @@ public class ZoneDetailServiceImpl implements IZoneDetailService {
* <a href="https://www.gitlink.org.cn/Gitlink/forgeplus/issues/3523">参照需求</a>
*
* @param userIdentify
* @param gitLinkToken
* @param zoneId
* @param notMemberParentUserIdentify
* @return
*/
private String genChangeCurrentUserIdentityUrl(SysUserDeptRole userIdentify, String gitLinkToken, Long zoneId, SysUserDeptRole notMemberParentUserIdentify) {
private String genChangeCurrentUserIdentityUrl(SysUserDeptRole userIdentify, Long zoneId, SysUserDeptRole notMemberParentUserIdentify) {
String roleKey = userIdentify.getSysRole().getRoleKey();
Long deptId = userIdentify.getDeptId();
if (SystemRole.ZONE_MEMBER.getRoleKey().equals(roleKey)) {
@ -541,10 +553,10 @@ public class ZoneDetailServiceImpl implements IZoneDetailService {
}
if (SystemRole.ZONE_ADMIN.getRoleKey().equals(roleKey)
|| SystemRole.ZONE_OPERATOR.getRoleKey().equals(roleKey)) {
return zoneConfigurationUrl + "?changeCurrentUserIdentity=" + deptId;
return adminUrl + zoneConfigurationPath + "?changeCurrentUserIdentity=" + deptId;
}
if (SystemRole.PLATFORM_ADMIN.getRoleKey().equals(roleKey)) {
return zoneConfigurationUrl + "/" + zoneId + "?changeCurrentUserIdentity=" + deptId;
return adminUrl + zoneConfigurationPath + "/" + zoneId + "?changeCurrentUserIdentity=" + deptId;
}
return null;
@ -635,19 +647,56 @@ public class ZoneDetailServiceImpl implements IZoneDetailService {
public ZoneFrontRoleVo checkCurrentRoleInZone(Long zoneId) {
ZoneFrontRoleVo zoneFrontRoleVo = new ZoneFrontRoleVo();
ZoneDetail zoneDetail = selectZoneDetailById(zoneId);
boolean isZoneAdmin = AuthUtil.hasUserIdentity(zoneDetail.getDeptId(), SystemRole.ZONE_ADMIN.getRoleKey());
boolean isZoneOperator = AuthUtil.hasUserIdentity(zoneDetail.getDeptId(), SystemRole.ZONE_OPERATOR.getRoleKey());
boolean isZoneMember = AuthUtil.hasUserIdentity(zoneDetail.getDeptId(), SystemRole.ZONE_MEMBER.getRoleKey());
if (isZoneAdmin || isZoneOperator) {
zoneFrontRoleVo.setRole(ZoneConstants.FRONT_ROLE_MANAGER);
zoneFrontRoleVo.setResourceManageUrl(resourceManageUrl);
zoneFrontRoleVo.setDocManageUrl(docManageUrl);
} else if (isZoneMember) {
zoneFrontRoleVo.setRole(ZoneConstants.FRONT_ROLE_MEMBER);
} else {
LoginUser loginUser = SecurityUtils.getLoginUser();
SysDept sysDept = FeignUtils.getReturnData(remoteDeptService.getDeptByDeptId(zoneDetail.getDeptId(), SecurityConstants.INNER));
List<SysUserDeptRole> userIdentifyList = FeignUtils.getReturnData(
remoteUserService.getSysUserDeptRoleListByUserName(loginUser.getUsername(), SecurityConstants.INNER)
);
if (sysDept == null || userIdentifyList == null) {
zoneFrontRoleVo.setRole(ZoneConstants.FRONT_ROLE_NONE);
return zoneFrontRoleVo;
} else {
List<String> deptIdList = Arrays.asList(sysDept.getAncestors().split(","));
SysUserDeptRole parentDeptUserIdentify = userIdentifyList
.stream()
.filter(x -> deptIdList.contains(String.valueOf(x.getDeptId())))
.filter(x -> !x.getSysRole().getRoleKey().equals(SystemRole.ZONE_MEMBER.getRoleKey()))
.findFirst().orElse(null);
SysUserDeptRole currentUserIdentify = userIdentifyList.stream().filter(x -> x.getDeptId().equals(zoneDetail.getDeptId())).findFirst().orElse(null);
String resourceManageUrl = adminUrl + resourceManagePath + "?changeCurrentUserIdentity=";
String docManageUrl = adminUrl + docManagePath + "?changeCurrentUserIdentity=";
String projectManageUrl = adminUrl + projectManagePath + "?changeCurrentUserIdentity=";
if (currentUserIdentify != null) {
if (currentUserIdentify.getSysRole().getRoleKey().equals(SystemRole.ZONE_MEMBER.getRoleKey())) {
if (parentDeptUserIdentify != null) {
zoneFrontRoleVo.setRole(ZoneConstants.FRONT_ROLE_MANAGER);
zoneFrontRoleVo.setResourceManageUrl(resourceManageUrl + parentDeptUserIdentify.getDeptId());
zoneFrontRoleVo.setDocManageUrl(docManageUrl + parentDeptUserIdentify.getDeptId());
zoneFrontRoleVo.setProjectManageUrl(projectManageUrl + parentDeptUserIdentify.getDeptId());
zoneFrontRoleVo.setDeptId(parentDeptUserIdentify.getDeptId());
} else {
zoneFrontRoleVo.setRole(ZoneConstants.FRONT_ROLE_MEMBER);
zoneFrontRoleVo.setDeptId(currentUserIdentify.getDeptId());
}
} else {
zoneFrontRoleVo.setRole(ZoneConstants.FRONT_ROLE_MANAGER);
zoneFrontRoleVo.setResourceManageUrl(resourceManageUrl + currentUserIdentify.getDeptId());
zoneFrontRoleVo.setDocManageUrl(docManageUrl + currentUserIdentify.getDeptId());
zoneFrontRoleVo.setProjectManageUrl(projectManageUrl + currentUserIdentify.getDeptId());
zoneFrontRoleVo.setDeptId(currentUserIdentify.getDeptId());
}
return zoneFrontRoleVo;
} else if (parentDeptUserIdentify != null) {
zoneFrontRoleVo.setRole(ZoneConstants.FRONT_ROLE_MANAGER);
zoneFrontRoleVo.setResourceManageUrl(resourceManageUrl + parentDeptUserIdentify.getDeptId());
zoneFrontRoleVo.setDocManageUrl(docManageUrl + parentDeptUserIdentify.getDeptId());
zoneFrontRoleVo.setProjectManageUrl(projectManageUrl + parentDeptUserIdentify.getDeptId());
zoneFrontRoleVo.setDeptId(parentDeptUserIdentify.getDeptId());
return zoneFrontRoleVo;
}
}
zoneFrontRoleVo.setRole(ZoneConstants.FRONT_ROLE_NONE);
return zoneFrontRoleVo;
}
@ -678,14 +727,14 @@ public class ZoneDetailServiceImpl implements IZoneDetailService {
}
if (SystemRole.ZONE_ADMIN.getRoleKey().equals(roleKey)
|| SystemRole.ZONE_OPERATOR.getRoleKey().equals(roleKey)) {
return zoneConfigurationUrl;
return adminUrl + zoneConfigurationPath;
}
if (SystemRole.PLATFORM_ADMIN.getRoleKey().equals(roleKey)) {
ZoneDetail zoneDetail = zoneDetailMapper.selectZoneDetailByDeptId(deptId);
if (zoneDetail == null) {
return zoneAdminUrl;
}
return zoneConfigurationUrl + "/" + zoneDetail.getId();
return adminUrl + zoneConfigurationPath + "/" + zoneDetail.getId();
}
return adminUrl;

View File

@ -0,0 +1,24 @@
package com.microservices.zone.member.domain;
import com.microservices.zone.member.domain.ZoneMember;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
/**
* ZoneMember with full-text search score
* 用于全局搜索结果包含全文搜索相关度评分
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ZoneMemberWithScore extends ZoneMember implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 全文搜索相关度评分来自 MySQL MATCH...AGAINST
*/
private Double searchScore;
private String memberTypeName;
}

View File

@ -154,4 +154,21 @@ public interface ZoneMemberMapper {
* @return 会员组织Id列表
*/
List<Long> checkUserIsMemberInDepts(@Param("userId") Long userId, @Param("deptIds") Long[] deptIds);
/**
* 全局搜索 - 会员名称和用户名搜索含评分
*
* @param keyword 搜索关键词
* @param zoneId 专区ID
* @param offset 偏移量
* @param pageSize 每页数量
* @return 搜索结果列表含评分
*/
List<com.microservices.zone.member.domain.ZoneMemberWithScore> globalSearchWithScore(
@Param("keyword") String keyword,
@Param("zoneId") Long zoneId,
@Param("offset") int offset,
@Param("pageSize") int pageSize);
Long globalSearchCount(@Param("keyword") String keyword, @Param("zoneId") Long zoneId);
}

View File

@ -100,7 +100,7 @@ public class ZoneProjectController extends BaseController {
@PostMapping
@ApiOperation("批量新增项目聚合")
public AjaxResult add(@RequestBody @Validated List<ZoneProjectInputVo> zoneProjectInputVoList) {
return toAjax(zoneProjectService.insertBatchZoneProject(zoneProjectInputVoList));
return success(zoneProjectService.insertBatchZoneProject(zoneProjectInputVoList));
}
/**
@ -135,4 +135,15 @@ public class ZoneProjectController extends BaseController {
public AjaxResult updateHomepageShow(@PathVariable Long id) {
return toAjax(zoneProjectService.updateZoneProjectHomePageShow(id));
}
/**
* 审核特色专区项目
*/
@RequiresPermissions("zone:project:audit")
@Log(title = "审核特色专区项目", businessType = BusinessType.UPDATE)
@PutMapping("/audit")
@ApiOperation("审核特色专区项目")
public AjaxResult audit(@RequestBody @Validated ZoneProjectAuditVo zoneProjectAuditVo) {
return toAjax(zoneProjectService.auditZoneProject(zoneProjectAuditVo));
}
}

View File

@ -35,7 +35,6 @@ public class ZoneProjectTypeController extends BaseController {
/**
* 查询特色专区项目分类列表
*/
@RequiresPermissions("zone:projectType:list")
@GetMapping("/list")
@ApiOperation("查询特色专区项目分类列表")
public GenericsTableDataInfo<ZoneProjectType> list(ZoneProjectType zoneProjectType) {

View File

@ -1,11 +1,15 @@
package com.microservices.zone.project.domain;
import com.microservices.common.core.annotation.Excel;
import com.microservices.common.core.utils.bean.BeanUtils;
import com.microservices.common.core.web.domain.BaseEntity;
import com.microservices.system.api.domain.SysDept;
import io.swagger.annotations.ApiModelProperty;
import com.microservices.zone.project.domain.vo.ZoneProjectDataVo;
import lombok.Data;
import java.util.Date;
/**
* 特色专区项目聚合对象 zone_project
*
@ -83,16 +87,18 @@ public class ZoneProject extends BaseEntity {
*/
private String isHomepage;
@ApiModelProperty(value = "审核状态0待审核1审核通过 2审核不通过")
private String auditStatus;
@ApiModelProperty(value = "审核时间")
private Date auditTime;
@ApiModelProperty(value = "审核人")
private String auditBy;
public ZoneProjectDataVo toZoneProjectDataVo() {
ZoneProjectDataVo zoneProjectDataVo = new ZoneProjectDataVo();
zoneProjectDataVo.setId(this.getId());
BeanUtils.copyProperties(this, zoneProjectDataVo);
zoneProjectDataVo.setDeptName(this.getDept().getDeptName());
zoneProjectDataVo.setProjectType(this.getZoneProjectType().getName());
zoneProjectDataVo.setProjectTypeId(this.getProjectTypeId());
zoneProjectDataVo.setIsHomepage(this.getIsHomepage());
zoneProjectDataVo.setZoneId(this.getZoneId());
zoneProjectDataVo.setSort(this.getSort());
zoneProjectDataVo.setGitlinkProjectId(this.getGitlinkProjectId());
return zoneProjectDataVo;
}
}

View File

@ -43,6 +43,18 @@ public class ZoneProjectType extends BaseEntity {
@Excel(name = "排序")
private Integer sort;
/**
* 类型详情JSON
*/
@Excel(name = "类型详情JSON")
private String detailJson;
/**
* 分类下项目总数
*/
@ApiModelProperty(value = "分类下项目总数")
private Integer projectCount;
/**
* 删除标志0代表存在 2代表删除
*/

View File

@ -0,0 +1,48 @@
package com.microservices.zone.project.domain;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.zone.project.domain.ZoneProject;
import com.microservices.zone.project.domain.vo.ZoneProjectDataVo;
import com.microservices.zone.search.dto.GlobalSearchResult;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
/**
* ZoneProject with full-text search score
* 用于全局搜索结果包含全文搜索相关度评分
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ZoneProjectWithScore extends ZoneProject implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 全文搜索相关度评分来自 MySQL MATCH...AGAINST
*/
private Double searchScore;
public GlobalSearchResult toGlobalSearchResult(ZoneProjectDataVo zoneProjectDataVo) {
GlobalSearchResult result = new GlobalSearchResult();
result.setType("zone_project");
result.setId(this.getId());
result.setTitle(this.getName());
result.setContent(this.getFullName());
result.setDeptId(this.getDeptId());
result.setZoneId(this.getZoneId());
result.setCreateTime(this.getCreateTime());
if (zoneProjectDataVo != null) {
result.setExtendData(zoneProjectDataVo.getProjectProperties());
}
// 使用数据库返回的实际搜索评分
Double score = this.getSearchScore();
if (score == null || score == 0.0) {
score = 0.1; // LIKE 匹配的最小评分
}
result.setScore(score);
return result;
}
}

View File

@ -11,37 +11,15 @@ import lombok.Data;
@Data
@ApiModel("GitLink项目搜索对象")
public class GitLinkProjectSearchVo {
public class GitLinkProjectSearchVo extends MyGitLinkProjectSearchVo {
@ApiModelProperty(value = "项目语言")
private Long languageId;
@ApiModelProperty(value = "项目排序")
private String sortBy;
@ApiModelProperty(value = "搜索关键字")
private String search;
@ApiModelProperty(value = "页码")
private Long page;
@ApiModelProperty(value = "每页数量")
private Long limit;
public String genQueryString() {
StringBuilder stringBuilder = new StringBuilder("?");
if (page == null) {
page = 1L;
}
if (limit == null) {
limit = 15L;
}
stringBuilder.append("page=").append(page);
stringBuilder.append("&limit=").append(limit);
StringBuilder queryString = super.genBaseQueryString();
if (languageId != null) {
stringBuilder.append("&language_id=").append(languageId);
queryString.append("&language_id=").append(languageId);
}
if (StringUtils.isNotEmpty(sortBy)) {
stringBuilder.append("&sort_by=").append(sortBy);
}
if (StringUtils.isNotEmpty(search)) {
stringBuilder.append("&search=").append(search);
}
return stringBuilder.toString();
return queryString.toString();
}
}

View File

@ -0,0 +1,48 @@
package com.microservices.zone.project.domain.vo;
import com.microservices.common.core.utils.StringUtils;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @author otto
*/
@Data
@ApiModel("GitLink项目搜索对象")
public class MyGitLinkProjectSearchVo {
@ApiModelProperty(value = "项目排序")
private String sortBy;
@ApiModelProperty(value = "搜索关键字")
private String search;
@ApiModelProperty(value = "页码")
private Long page;
@ApiModelProperty(value = "每页数量")
private Long limit;
protected StringBuilder genBaseQueryString() {
StringBuilder stringBuilder = new StringBuilder("?");
if (page == null) {
page = 1L;
}
if (limit == null) {
limit = 15L;
}
stringBuilder.append("page=").append(page);
stringBuilder.append("&limit=").append(limit);
if (StringUtils.isNotEmpty(sortBy)) {
stringBuilder.append("&sort_by=").append(sortBy);
}
if (StringUtils.isNotEmpty(search)) {
stringBuilder.append("&search=").append(search);
}
return stringBuilder;
}
public String myProjectsQueryString() {
StringBuilder stringBuilder = genBaseQueryString();
stringBuilder.append("&category=admin&is_public=public");
return stringBuilder.toString();
}
}

View File

@ -0,0 +1,22 @@
package com.microservices.zone.project.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Data
@ApiModel("项目审核对象")
public class ZoneProjectAuditVo {
@ApiModelProperty(value = "项目Id", required = true)
@NotNull
private Long id;
@ApiModelProperty(value = "审核状态1审核通过 2审核不通过", required = true)
@NotNull
private String auditStatus;
@ApiModelProperty(value = "备注")
private String remark;
}

View File

@ -6,6 +6,8 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
/**
* @author otto
*/
@ -40,6 +42,15 @@ public class ZoneProjectDataVo implements Comparable<ZoneProjectDataVo> {
@Excel(name = "gitlink项目Id")
private Long gitlinkProjectId;
@ApiModelProperty(value = "审核状态0待审核1审核通过 2审核不通过")
private String auditStatus;
@ApiModelProperty(value = "审核时间")
private Date auditTime;
@ApiModelProperty(value = "审核人")
private String auditBy;
@ApiModelProperty(value = "备注")
private String remark;
@Override
public int compareTo(ZoneProjectDataVo zoneProjectDataVo) {
// 重写Comparable接口的compareTo方法根据年龄升序排列降序修改相减顺序即可

View File

@ -0,0 +1,28 @@
package com.microservices.zone.project.domain.vo;
import com.microservices.common.core.utils.bean.BeanUtils;
import com.microservices.zone.project.domain.ZoneProject;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author otto
*/
@Data
@ApiModel("项目聚合前台更新对象")
public class ZoneProjectFrontUpdateVo {
@ApiModelProperty(value = "聚合项目类型")
@NotNull
protected Long projectTypeId;
public ZoneProjectUpdateVo toZoneProjectUpdateVo() {
ZoneProjectUpdateVo zoneProjectUpdateVo = new ZoneProjectUpdateVo();
BeanUtils.copyProperties(this, zoneProjectUpdateVo);
return zoneProjectUpdateVo;
}
}

View File

@ -14,19 +14,16 @@ import javax.validation.constraints.NotNull;
@Data
@ApiModel("项目聚合输入对象")
public class ZoneProjectInputVo {
public class ZoneProjectInputVo {
@ApiModelProperty(value = "gitlink项目Id", required = true)
@NotNull
private Long gitlinkProjectId;
protected Long gitlinkProjectId;
@ApiModelProperty(value = "聚合项目类型", required = true)
@NotNull
private Long projectTypeId;
protected Long projectTypeId;
@ApiModelProperty(value = "所属专区", required = true)
@NotNull
private Long zoneId;
@ApiModelProperty(value = "排序")
private Integer sort;
protected Long zoneId;
public ZoneProject toZoneProject() {
ZoneProject zoneProject = new ZoneProject();

View File

@ -4,6 +4,8 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author otto
*/
@ -11,6 +13,11 @@ import lombok.Data;
@Data
@ApiModel("项目聚合搜索对象")
public class ZoneProjectSearchVo extends ZoneProjectOpenSearchVo {
@ApiModelProperty(value = "所属专区")
@ApiModelProperty(value = "所属专区", required = true)
@NotNull
private Long zoneId;
@ApiModelProperty(value = "审核状态0待审核1审核通过 2审核不通过")
private String auditStatus;
@ApiModelProperty(value = "是否为我的项目", hidden = true)
private Boolean isMyProject;
}

View File

@ -27,11 +27,15 @@ public class ZoneProjectTypeInputVo extends BaseEntity {
@ApiModelProperty(value = "排序")
private Integer sort;
@ApiModelProperty(value = "类型详情JSON")
private String detailJson;
public ZoneProjectType toZoneProjectType() {
ZoneProjectType zoneProjectType = new ZoneProjectType();
zoneProjectType.setZoneId(zoneId);
zoneProjectType.setName(name);
zoneProjectType.setSort(sort);
zoneProjectType.setDetailJson(detailJson);
return zoneProjectType;
}

View File

@ -27,10 +27,14 @@ public class ZoneProjectTypeUpdateVo extends BaseEntity {
@ApiModelProperty(value = "顺序")
private Integer order;
@ApiModelProperty(value = "类型详情JSON")
private String detailJson;
public ZoneProjectType toZoneProjectType() {
ZoneProjectType zoneProjectType = new ZoneProjectType();
zoneProjectType.setId(id);
zoneProjectType.setName(name);
zoneProjectType.setDetailJson(detailJson);
return zoneProjectType;
}

View File

@ -13,18 +13,15 @@ import javax.validation.constraints.NotNull;
@Data
@ApiModel("项目聚合更新对象")
public class ZoneProjectUpdateVo {
@ApiModelProperty(value = "聚合项目类型")
@NotNull
private Long projectTypeId;
public class ZoneProjectUpdateVo extends ZoneProjectFrontUpdateVo{
@ApiModelProperty(value = "顺序")
private Integer order;
public ZoneProject toZoneProject(Long id) {
ZoneProject zoneProject = new ZoneProject();
zoneProject.setId(id);
zoneProject.setProjectTypeId(projectTypeId);
zoneProject.setProjectTypeId(super.projectTypeId);
zoneProject.setSort(order);
return zoneProject;
}
}

View File

@ -155,4 +155,39 @@ public interface ZoneProjectMapper {
* @return
*/
int deleteZoneProjectByZoneId(Long zoneId);
/**
* 全局搜索结果计数
*
* @param keyword 搜索关键词
* @param zoneId 专区ID可选
* @return 结果数量
*/
Long globalSearchCount(@Param("keyword") String keyword, @Param("zoneId") Long zoneId);
/**
* 全局搜索 - 全文索引查询含评分
*
* @param keyword 搜索关键词
* @param zoneId 专区ID
* @param offset 偏移量
* @param pageSize 每页数量
* @return 搜索结果列表含评分
*/
List<com.microservices.zone.project.domain.ZoneProjectWithScore> globalSearchWithScore(
@Param("keyword") String keyword,
@Param("zoneId") Long zoneId,
@Param("offset") int offset,
@Param("pageSize") int pageSize);
/**
* 获取专区下项目项目gitlink id列表
*
* @param zoneId 专区id
* @param projectTypeId 专区类型id
* @return 项目gitlink id列表
*/
List<String> selectZoneProjectGitlinkIdList(@Param("zoneId") Long zoneId, @Param("projectTypeId") Long projectTypeId);
List<ZoneProject> selectZoneProjectListFromGitlinkProjectIdList(@Param("zoneId") Long zoneId, @Param("gitlinkProjectIdList") List<Long> gitlinkProjectIdList);
}

View File

@ -2,6 +2,7 @@ package com.microservices.zone.project.service;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.web.page.GenericsTableDataInfo;
import com.microservices.zone.project.domain.ZoneProject;
import com.microservices.zone.project.domain.vo.*;
import java.util.List;
@ -112,4 +113,40 @@ public interface IZoneProjectService {
Integer selectHomepageCount(Long zoneId);
List<JSONObject> selectZoneProjectByScoreList(ZoneProjectSearchVo zoneProjectSearchVo, boolean isOpen);
/**
* 查询我的项目列表
*
* @param zoneProjectSearchVo 查询条件
* @return 项目列表
*/
GenericsTableDataInfo<ZoneProjectDataVo> selectMyZoneProjectList(ZoneProjectSearchVo zoneProjectSearchVo);
/**
* 批量删除我的项目校验创建者
*
* @param ids 需要删除的项目主键集合
* @return 结果
*/
int deleteMyZoneProjectByIds(Long[] ids);
/**
* 修改我的项目校验创建者
*
* @param id 项目主键
* @param zoneProjectUpdateVo 更新数据
* @return 结果
*/
int updateMyZoneProject(Long id, ZoneProjectUpdateVo zoneProjectUpdateVo);
/**
* 审核项目
*
* @param zoneProjectAuditVo 审核数据
* @return 结果
*/
int auditZoneProject(ZoneProjectAuditVo zoneProjectAuditVo);
GenericsTableDataInfo<JSONObject> selectMyGitlinkProjectListByZone(Long zoneId, MyGitLinkProjectSearchVo myGitLinkProjectSearchVo);
List<ZoneProjectDataVo> toZoneProjectDataVoList(List<ZoneProject> zoneProjectList);
}

View File

@ -1,5 +1,6 @@
package com.microservices.zone.project.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.constant.CacheConstants;
@ -12,8 +13,10 @@ import com.microservices.common.core.web.page.GenericsTableDataInfo;
import com.microservices.common.datascope.annotation.DataScope;
import com.microservices.common.httpClient.util.GitLinkRequestHelper;
import com.microservices.common.redis.service.RedisService;
import com.microservices.common.security.auth.AuthUtil;
import com.microservices.common.security.utils.DictUtils;
import com.microservices.common.security.utils.SecurityUtils;
import com.microservices.common.core.enums.SystemRole;
import com.microservices.system.api.domain.SysDictData;
import com.microservices.zone.common.service.IZoneAsyncService;
import com.microservices.zone.detail.domain.ZoneDetail;
@ -113,23 +116,24 @@ public class ZoneProjectServiceImpl implements IZoneProjectService {
throw new ServiceException("查询项目列表时,请指定专区Id");
}
zoneDetailService.selectZoneDetailById(zoneProjectSearchVo.getZoneId());
if (isOpen) {
zoneProjectSearchVo.setAuditStatus(ZoneConstants.PROJECT_AUDIT_PASS);
}
startPage();
zoneProjectSearchVo.setIsOpen(isOpen);
List<ZoneProject> zoneProjectList = zoneProjectMapper.selectZoneProjectList(zoneProjectSearchVo);
return PageUtils.toListPage(zoneProjectList, this::toZoneProjectDataVoList);
}
private List<ZoneProjectDataVo> toZoneProjectDataVoList(List<ZoneProject> zoneProjectList) {
@Override
public List<ZoneProjectDataVo> toZoneProjectDataVoList(List<ZoneProject> zoneProjectList) {
List<ZoneProjectDataVo> zoneProjectDataVoList = new ArrayList<>();
// 原方案为遍历项目聚合再逐一从GitLink获取对应项目详情此方案延时过高调整为根据项目Id批量获取GitLink项目信息
List<String> gitLinkProjectIdList = zoneProjectList
.stream()
.map(x -> String.valueOf(x.getGitlinkProjectId()))
.collect(Collectors.toList());
JSONObject result = gitLinkRequestHelper.doGet(
ZoneGitLinkRequestUrl.PROJECTS("?ids=" + String.join(ZoneConstants.ARRAY_SPLIT_SYMBOLS, gitLinkProjectIdList))
);
List<JSONObject> gitLinkProjectList = result.getList(ZoneConstants.GITLINK_RESPONSE_PROJECTS_KEY, JSONObject.class);
List<JSONObject> gitLinkProjectList = zoneAsyncService.getGitlinkProjectsDetail(gitLinkProjectIdList);
List<String> resultGitLinkProjectIdList = gitLinkProjectList
.stream()
.map(x -> x.getString("id"))
@ -210,6 +214,24 @@ public class ZoneProjectServiceImpl implements IZoneProjectService {
}
}
private static String extractFirstPathSegment(String url) {
if (url == null || url.isEmpty()) return null;
// 找到协议后的第一个 '/' 的位置路径开始
int schemeSep = url.indexOf("://");
if (schemeSep == -1) return null;
int hostStart = schemeSep + 3;
int pathStart = url.indexOf('/', hostStart);
if (pathStart == -1) return null; // 没有路径
// 找到下一个 '/' 或结尾
int pathEnd = url.indexOf('/', pathStart + 1);
if (pathEnd == -1) pathEnd = url.length();
return url.substring(pathStart + 1, pathEnd);
}
private ZoneProjectDataVo zoneProjectToZoneProjectDataVo(ZoneProject zoneProject, JSONObject gitLinkProject) {
//每次从GitLink查询项目时自动更新项目名称和项目标识
updateProjectNameAndFullName(gitLinkProject, zoneProject);
@ -229,6 +251,11 @@ public class ZoneProjectServiceImpl implements IZoneProjectService {
projectKeyName.put("name", "项目名称");
projectPropertiesVo.put("fullName", author.getString("login") + "/" + gitLinkProject.getString("identifier"));
projectKeyName.put("fullName", "项目完整地址");
projectPropertiesVo.put("fromOwner", author.getString("login"));
}
String mirrorUrl = gitLinkProject.getString("mirror_url");
if (StringUtils.isNotEmpty(mirrorUrl)) {
projectPropertiesVo.put("fromOwner", extractFirstPathSegment(mirrorUrl));
}
String description = gitLinkProject.getString("description");
projectPropertiesVo.put("description", description);
@ -385,9 +412,20 @@ public class ZoneProjectServiceImpl implements IZoneProjectService {
public int insertZoneProject(ZoneProjectInputVo zoneProjectInputVo) {
ZoneDetail zoneDetail = zoneDetailService.selectZoneDetailById(zoneProjectInputVo.getZoneId());
zoneProjectTypeService.selectZoneProjectTypeByIdAndZoneId(zoneProjectInputVo.getProjectTypeId(), zoneProjectInputVo.getZoneId());
ZoneProject existingProject = zoneProjectMapper.selectZoneProjectByGitLinkProjectIdAndZoneId(
zoneProjectInputVo.getGitlinkProjectId(), zoneProjectInputVo.getZoneId());
if (existingProject != null) {
if (existingProject.getAuditStatus().equals(ZoneConstants.PROJECT_NOT_AUDIT)) {
throw new ServiceException("该项目正处于于专区审核中,不可重复添加");
}
throw new ServiceException("该项目已存在于专区中,不可重复添加");
}
JSONObject gitLinkProject = getGitLinkProjectById(zoneProjectInputVo.getGitlinkProjectId());
ZoneProject zoneProject = zoneProjectInputVo.toZoneProject();
zoneProject.setCreateTime(DateUtils.getNowDate());
if (StringUtils.isEmpty(zoneProject.getCreateBy())) {
zoneProject.setCreateBy(SecurityUtils.getUsername());
}
zoneProject.setDeptId(zoneDetail.getDeptId());
zoneProject.setDeptId(zoneDetail.getDeptId());
JSONObject author = gitLinkProject.getJSONObject("author");
@ -395,6 +433,12 @@ public class ZoneProjectServiceImpl implements IZoneProjectService {
zoneProject.setName(author.getString("name") + "/" + gitLinkProject.getString("name"));
zoneProject.setFullName(author.getString("login") + "/" + gitLinkProject.getString("identifier"));
}
// 专区开启项目审核后专区会员和专区运营人员新增项目都需要审核
if (projectNeedAudit(zoneDetail)) {
zoneProject.setAuditStatus(ZoneConstants.PROJECT_NOT_AUDIT);
} else {
zoneProject.setAuditStatus(ZoneConstants.PROJECT_AUDIT_PASS);
}
//设置排序值为当前最大的排序值+1
Integer maxSort = zoneProjectMapper.selectMaxSortByZoneId(zoneProjectInputVo.getZoneId());
if (maxSort == null) {
@ -548,27 +592,10 @@ public class ZoneProjectServiceImpl implements IZoneProjectService {
JSONObject result = gitLinkRequestHelper.doGet(
ZoneGitLinkRequestUrl.PROJECTS(gitLinkProjectSearchVo.genQueryString())
);
List<JSONObject> list = result.getList(ZoneConstants.GITLINK_RESPONSE_PROJECTS_KEY, JSONObject.class);
if (list == null) {
throw new ServiceException("Gitlink项目列表获取失败");
}
ZoneProjectSearchVo zoneProjectSearchVo = new ZoneProjectSearchVo();
zoneProjectSearchVo.setZoneId(zoneId);
List<ZoneProject> zoneProjectList = zoneProjectMapper.selectZoneProjectList(zoneProjectSearchVo);
List<JSONObject> gitlinkProjectList = new ArrayList<>();
for (JSONObject gitlinkProject : list) {
if (zoneId != null) {
boolean isChoose = zoneProjectList.stream().anyMatch(x -> x.getGitlinkProjectId().equals(gitlinkProject.getLong("id")));
if (isChoose) {
gitlinkProject.put("isChoose", true);
} else {
gitlinkProject.put("isChoose", false);
}
}
gitlinkProjectList.add(gitlinkProject);
}
Long size = result.getLong(ZoneConstants.GITLINK_RESPONSE_TOTAL_COUNT_KEY);
return PageUtils.toPage(gitlinkProjectList, size);
return execGitlinkProjectsSearch(zoneId, result, ZoneConstants.GITLINK_RESPONSE_TOTAL_COUNT_KEY, zoneProjectList);
}
@Override
@ -582,7 +609,12 @@ public class ZoneProjectServiceImpl implements IZoneProjectService {
ZoneProject zoneProject = zoneProjectMapper
.selectZoneProjectByGitLinkProjectIdAndZoneId(gitlinkProjectId, zoneId);
if (zoneProject != null) {
throw new ServiceException("该GitLink项目已存在于专区中不可重复添加(专区Id[" + zoneId + "],GitLink项目Id[" + gitlinkProjectId + "])");
if (zoneProject.getAuditStatus().equals(ZoneConstants.PROJECT_NOT_AUDIT)) {
throw new ServiceException("该项目正处于于专区审核中,不可重复添加(专区Id[" + zoneId + "],项目Id[" + gitlinkProjectId + "])");
}
throw new ServiceException("该项目已存在于专区中,不可重复添加(专区Id[" + zoneId + "],项目Id[" + gitlinkProjectId + "])");
// logger.warn("该GitLink项目已存在于专区中不可重复添加(专区Id[{}],GitLink项目Id[{}])", zoneId, gitlinkProjectId);
// break;
}
success += insertZoneProject(zoneProjectInputVo);
}
@ -633,6 +665,55 @@ public class ZoneProjectServiceImpl implements IZoneProjectService {
return zoneProjectMapper.selectHomepageCount(zoneId);
}
@Override
public GenericsTableDataInfo<ZoneProjectDataVo> selectMyZoneProjectList(ZoneProjectSearchVo zoneProjectSearchVo) {
if (zoneProjectSearchVo == null || zoneProjectSearchVo.getZoneId() == null) {
throw new ServiceException("获取我的项目列表时必须传入专区Id");
}
zoneDetailService.selectZoneDetailById(zoneProjectSearchVo.getZoneId());
startPage();
List<ZoneProject> zoneProjectList = zoneProjectMapper.selectZoneProjectList(zoneProjectSearchVo);
return PageUtils.toListPage(zoneProjectList, this::toZoneProjectDataVoList);
}
@Override
public int deleteMyZoneProjectByIds(Long[] ids) {
for (Long id : ids) {
ZoneProject zoneProject = zoneProjectMapper.selectZoneProjectById(id);
if (zoneProject == null) {
throw new ServiceException("该项目不存在(项目Id[" + id + "])");
}
if (!zoneProject.getCreateBy().equals(SecurityUtils.getUsername())) {
throw new ServiceException("仅允许删除自身创建的项目");
}
}
int success = 0;
for (Long id : ids) {
success += deleteZoneProjectById(id);
}
return success;
}
@Override
public int updateMyZoneProject(Long id, ZoneProjectUpdateVo zoneProjectUpdateVo) {
ZoneProject zoneProject = zoneProjectMapper.selectZoneProjectById(id);
if (zoneProject == null) {
throw new ServiceException("该项目不存在(项目Id[" + id + "])");
}
if (!zoneProject.getCreateBy().equals(SecurityUtils.getUsername())) {
throw new ServiceException("仅允许编辑自身创建的项目");
}
ZoneDetail zoneDetail = zoneDetailService.selectZoneDetailById(zoneProject.getZoneId());
int result = updateZoneProject(id, zoneProjectUpdateVo);
if (projectNeedAudit(zoneDetail)) {
ZoneProject updateProject = new ZoneProject();
updateProject.setId(id);
updateProject.setAuditStatus(ZoneConstants.PROJECT_NOT_AUDIT);
zoneProjectMapper.updateZoneProject(updateProject);
}
return result;
}
@Override
public List<JSONObject> selectZoneProjectByScoreList(ZoneProjectSearchVo zoneProjectSearchVo, boolean isOpen) {
if (zoneProjectSearchVo.getProjectTypeId() == null) {
@ -680,4 +761,78 @@ public class ZoneProjectServiceImpl implements IZoneProjectService {
}
return zoneProjectJSONList;
}
private boolean projectNeedAudit(ZoneDetail zoneDetail) {
if (1 == zoneDetail.getProjectNeedAudit()) {
boolean isOperator = AuthUtil.hasUserIdentity(zoneDetail.getDeptId(), SystemRole.ZONE_OPERATOR.getRoleKey());
boolean isMember = AuthUtil.hasUserIdentity(zoneDetail.getDeptId(), SystemRole.ZONE_MEMBER.getRoleKey());
return isOperator || isMember;
}
return false;
}
@Override
public int auditZoneProject(ZoneProjectAuditVo zoneProjectAuditVo) {
ZoneProject zoneProject = zoneProjectMapper.selectZoneProjectById(zoneProjectAuditVo.getId());
if (zoneProject == null) {
throw new ServiceException("该项目不存在(项目Id[" + zoneProjectAuditVo.getId() + "])");
}
zoneDetailService.checkZoneDataScope(zoneProject.getZoneId());
if (!ZoneConstants.PROJECT_AUDIT_PASS.equals(zoneProjectAuditVo.getAuditStatus())
&& !ZoneConstants.PROJECT_AUDIT_FAILED.equals(zoneProjectAuditVo.getAuditStatus())) {
throw new ServiceException("审核状态仅允许设置为审核通过和审核不通过");
}
if (!ZoneConstants.PROJECT_NOT_AUDIT.equals(zoneProject.getAuditStatus())) {
throw new ServiceException("该项目当前未处于待审核状态,不允许进行审核操作");
}
zoneProject.setAuditTime(DateUtils.getNowDate());
zoneProject.setAuditBy(SecurityUtils.getUsername());
zoneProject.setAuditStatus(zoneProjectAuditVo.getAuditStatus());
zoneProject.setRemark(zoneProjectAuditVo.getRemark());
return zoneProjectMapper.updateZoneProject(zoneProject);
}
@Override
public GenericsTableDataInfo<JSONObject> selectMyGitlinkProjectListByZone(Long zoneId, MyGitLinkProjectSearchVo gitLinkProjectSearchVo) {
JSONObject result = gitLinkRequestHelper.doGet(
ZoneGitLinkRequestUrl.MY_OWNER_PROJECTS(SecurityUtils.getUsername(), gitLinkProjectSearchVo.myProjectsQueryString())
);
ZoneProjectSearchVo zoneProjectSearchVo = new ZoneProjectSearchVo();
zoneProjectSearchVo.setZoneId(zoneId);
zoneProjectSearchVo.setIsMyProject(true);
List<ZoneProject> zoneProjectList = zoneProjectMapper.selectZoneProjectList(zoneProjectSearchVo);
return execGitlinkProjectsSearch(zoneId, result, ZoneConstants.GITLINK_RESPONSE_COUNT_KEY, zoneProjectList);
}
private GenericsTableDataInfo<JSONObject> execGitlinkProjectsSearch(Long zoneId, JSONObject result, String totalKey, List<ZoneProject> zoneProjectList) {
List<JSONObject> list = result.getList(ZoneConstants.GITLINK_RESPONSE_PROJECTS_KEY, JSONObject.class);
if (list == null) {
throw new ServiceException("Gitlink项目列表获取失败");
}
List<JSONObject> gitlinkProjectList = new ArrayList<>();
for (JSONObject gitlinkProject : list) {
if (zoneId != null) {
ZoneProject isChooseProject = zoneProjectList
.stream()
.filter(x -> x.getGitlinkProjectId().equals(gitlinkProject.getLong("id")))
.findFirst()
.orElse(null);
if (isChooseProject == null) {
gitlinkProject.put("isChoose", false);
gitlinkProject.put("isAudited", false);
} else {
gitlinkProject.put("isChoose", true);
if (isChooseProject.getAuditStatus().equals(ZoneConstants.PROJECT_NOT_AUDIT)) {
gitlinkProject.put("isAudited", false);
} else {
gitlinkProject.put("isAudited", true);
}
}
}
gitlinkProjectList.add(gitlinkProject);
}
Long size = result.getLong(totalKey);
return PageUtils.toPage(gitlinkProjectList, size);
}
}

View File

@ -0,0 +1,24 @@
package com.microservices.zone.resource.domain;
import com.microservices.zone.resource.domain.ZoneResource;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
/**
* ZoneResource with full-text search score
* 用于全局搜索结果包含全文搜索相关度评分
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ZoneResourceWithScore extends ZoneResource implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 全文搜索相关度评分来自 MySQL MATCH...AGAINST
*/
private Double searchScore;
private Long downloadCount;
}

View File

@ -75,6 +75,10 @@ public class ZoneResourceDataVo {
private Date auditTime;
@ApiModelProperty(value = "审核人")
private String auditBy;
@ApiModelProperty(value = "拓展数据")
private String extendData;
@ApiModelProperty(value = "备注")
private String remark;
@ApiModelProperty(value = "拓展数据JSON格式")
private JSONObject extendData;

View File

@ -5,6 +5,8 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author otto
*/
@ -12,7 +14,8 @@ import lombok.Data;
@ApiModel("资源搜索对象")
public class ZoneResourceSearchVo extends ZoneResourceOpenSearchVo {
@ApiModelProperty(value = "所属专区")
@ApiModelProperty(value = "所属专区",required = true)
@NotNull
private Long zoneId;
@ApiModelProperty(value = "审核状态0待审核1审核通过 2审核不通过")

View File

@ -96,6 +96,30 @@ public interface ZoneResourceMapper {
*/
void deleteZoneResourceByZoneId(Long zoneId);
/**
* 全局搜索结果计数
*
* @param keyword 搜索关键词
* @param zoneId 专区ID可选
* @return 结果数量
*/
Long globalSearchCount(@Param("keyword") String keyword, @Param("zoneId") Long zoneId);
/**
* 全局搜索 - 全文索引查询含评分
*
* @param keyword 搜索关键词
* @param zoneId 专区ID
* @param offset 偏移量
* @param pageSize 每页数量
* @return 搜索结果列表含评分
*/
List<com.microservices.zone.resource.domain.ZoneResourceWithScore> globalSearchWithScore(
@Param("keyword") String keyword,
@Param("zoneId") Long zoneId,
@Param("offset") int offset,
@Param("pageSize") int pageSize);
/**
* 通过名称查询Skill资源resource_category='SKILL'
*

View File

@ -0,0 +1,13 @@
package com.microservices.zone.search.dto;
import lombok.Data;
import java.util.List;
@Data
public class GitlinkSearchDataVo {
private Long total=0L;
private List<GitlinkSearchVo> gitlinkSearchVoList;
}

View File

@ -0,0 +1,73 @@
package com.microservices.zone.search.dto;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.zone.project.domain.vo.ZoneProjectDataVo;
import com.microservices.zone.search.utils.SearchUtils;
import lombok.Data;
import java.util.List;
@Data
public class GitlinkSearchVo {
private String id;
private Long instanceId;
private String title;
private String content;
private String userName;
private Long type;
private Long star;
private Long fork;
private Long watch;
private String createTime;
private String updateTime;
private List<String> topics;
private Long praisesCount;
private Long forkedCount;
private Long watchersCount;
private String url;
private Double score;
public GlobalSearchResult toGlobalSearchResult(ZoneProjectDataVo zoneProjectDataVo, String topic) {
GlobalSearchResult globalSearchResult = new GlobalSearchResult();
globalSearchResult.setTitle(replaceHighlightGitlinkSearch(this.title));
globalSearchResult.setContent(replaceHighlightGitlinkSearch(this.content));
globalSearchResult.setScore(this.score);
globalSearchResult.setId(zoneProjectDataVo.getId());
globalSearchResult.setType("zone_project");
globalSearchResult.setDeptId(zoneProjectDataVo.getZoneId());
globalSearchResult.setCreateTime(globalSearchResult.getCreateTime());
globalSearchResult.setZoneId(zoneProjectDataVo.getZoneId());
JSONObject extendData = zoneProjectDataVo.getProjectProperties();
String topics = extendData.getString("topics");
if (StringUtils.isNotEmpty(topics)) {
extendData.put("topics", SearchUtils.highlightKeyword(topics, topic));
}
globalSearchResult.setExtendData(extendData);
return globalSearchResult;
}
private String replaceHighlightGitlinkSearch(String text) {
if (StringUtils.isNotEmpty(text)) {
text = text.replace("<span class=\"highlightByGitlinkSearch\">", "<em>").replace("</span>", "</em>");
}
return text;
}
}

View File

@ -0,0 +1,72 @@
package com.microservices.zone.search.dto;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.utils.StringUtils;
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 GlobalSearchRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 搜索关键词
*/
@ApiModelProperty(value = "搜索关键词", required = true)
private String keyword;
/**
* 搜索类型列表zone_project, resource, member
* 为空则搜索所有类型
*/
@ApiModelProperty(value = "搜索类型列表")
private List<String> types;
/**
* 专区ID
*/
@ApiModelProperty(value = "专区ID")
private Long zoneId;
/**
* 页码
*/
@ApiModelProperty(value = "页码")
private Integer pageNum = 1;
/**
* 每页数量
*/
@ApiModelProperty(value = "每页数量")
private Integer pageSize = 20;
@ApiModelProperty(value = "额外搜索参数项目类型projectTypeId,topic")
private JSONObject extraSearchParam;
public String toProjectEsSearchString(String gitlinkSearchUrl, String gitlinkProjectIds, String topic) {
String esSearchString= gitlinkSearchUrl +
"/search" +
"?" +
"page=" + this.pageNum +
"&size=" + this.pageSize +
"&term=" + this.keyword +
"&type=" + 1 +
"&instanceIds=" + gitlinkProjectIds;
if(StringUtils.isNotEmpty(topic)){
esSearchString+="&topic=" + topic;
}
return esSearchString;
}
}

View File

@ -0,0 +1,63 @@
package com.microservices.zone.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 GlobalSearchResponse implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 总结果数
*/
@ApiModelProperty(value = "总结果数")
private Long total;
/**
* 搜索结果列表
*/
@ApiModelProperty(value = "搜索结果列表")
private List<GlobalSearchResult> 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

@ -0,0 +1,77 @@
package com.microservices.zone.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 GlobalSearchResult implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 结果类型zone_project, resource, member
*/
@ApiModelProperty(value = "结果类型")
private String type;
/**
* ID
*/
@ApiModelProperty(value = "ID")
private Long id;
/**
* 标题
*/
@ApiModelProperty(value = "标题")
private String title;
/**
* 内容摘要
*/
@ApiModelProperty(value = "内容摘要")
private String content;
/**
* 组织ID
*/
@ApiModelProperty(value = "组织ID")
private Long deptId;
/**
* 专区ID
*/
@ApiModelProperty(value = "专区ID")
private Long zoneId;
/**
* 创建时间
*/
@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

@ -0,0 +1,29 @@
package com.microservices.zone.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;
import java.util.List;
/**
* 全局搜索结果对象
*
* @author microservices
*/
@Data
@ApiModel("全局搜索结果对象")
public class GlobalSearchResultDataVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "总数")
private Long total;
@ApiModelProperty(value = "搜索结果")
private List<GlobalSearchResult> globalSearchResultList;
}

View File

@ -0,0 +1,20 @@
package com.microservices.zone.search.service;
import com.microservices.zone.search.dto.GlobalSearchRequest;
import com.microservices.zone.search.dto.GlobalSearchResponse;
/**
* 全局搜索服务接口
*
* @author microservices
*/
public interface IGlobalSearchService {
/**
* 全局搜索
*
* @param request 搜索请求
* @return 搜索响应
*/
GlobalSearchResponse globalSearch(GlobalSearchRequest request);
}

View File

@ -0,0 +1,432 @@
package com.microservices.zone.search.service.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.domain.R;
import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.httpClient.service.HttpAPIService;
import com.microservices.common.redis.service.RedisService;
import com.microservices.common.security.utils.SecurityUtils;
import com.microservices.system.api.RemoteFileService;
import com.microservices.system.api.domain.SysFileInfo;
import com.microservices.system.api.model.LoginUser;
import com.microservices.zone.project.domain.ZoneProject;
import com.microservices.zone.project.domain.ZoneProjectWithScore;
import com.microservices.zone.project.domain.vo.GitLinkProjectSearchVo;
import com.microservices.zone.project.domain.vo.ZoneProjectDataVo;
import com.microservices.zone.project.mapper.ZoneProjectMapper;
import com.microservices.zone.project.service.IZoneProjectService;
import com.microservices.zone.resource.domain.ZoneResourceWithScore;
import com.microservices.zone.resource.mapper.ZoneResourceMapper;
import com.microservices.zone.member.domain.ZoneMemberWithScore;
import com.microservices.zone.member.mapper.ZoneMemberMapper;
import com.microservices.zone.search.dto.*;
import com.microservices.zone.search.service.IGlobalSearchService;
import com.microservices.zone.search.utils.SearchUtils;
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.cloud.commons.config.DefaultsBindHandlerAdvisor;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
import java.util.regex.Pattern;
/**
* 全局搜索服务实现
*
* @author microservices
*/
@Service
public class GlobalSearchServiceImpl implements IGlobalSearchService {
private static final Logger logger = LoggerFactory.getLogger(GlobalSearchServiceImpl.class);
@Autowired
private ZoneProjectMapper zoneProjectMapper;
@Autowired
private ZoneResourceMapper zoneResourceMapper;
@Autowired
private ZoneMemberMapper zoneMemberMapper;
@Autowired
private RemoteFileService remoteFileService;
@Autowired
private RedisService redisService;
@Autowired
private IZoneProjectService projectService;
@Autowired
private HttpAPIService httpAPIService;
@Value("${http.gitLinkSearchUrl:}")
public String gitLinkSearchUrl;
private static final String SEARCH_CACHE_PREFIX = "search:cache:";
private static final long CACHE_EXPIRE_SECONDS = 300; // 5分钟
@Autowired
private DefaultsBindHandlerAdvisor.MappingsProvider mappingsProvider;
@Override
public GlobalSearchResponse globalSearch(GlobalSearchRequest request) {
long startTime = System.currentTimeMillis();
if (request.getZoneId() == null) {
throw new ServiceException("专区id不能为空");
}
// 2. 并行搜索各类型
List<GlobalSearchResult> allResults = new ArrayList<>();
Map<String, Long> typeCounts = new HashMap<>();
// 获取当前用户信息
LoginUser loginUser = SecurityUtils.getLoginUser();
// 确定要搜索的类型
List<String> searchTypes = request.getTypes();
if (searchTypes == null || searchTypes.isEmpty()) {
searchTypes = Arrays.asList("zone_project", "resource", "member");
}
Long searchTotal = 0L;
// 搜索Zone项目
if (searchTypes.contains("zone_project")) {
GlobalSearchResultDataVo resultDataVo = searchZoneProjects(request);
allResults.addAll(resultDataVo.getGlobalSearchResultList());
typeCounts.put("zone_project", resultDataVo.getTotal());
searchTotal += resultDataVo.getTotal();
}
// 搜索Zone资源
if (searchTypes.contains("resource")) {
List<GlobalSearchResult> resourceResults = searchResources(request);
allResults.addAll(resourceResults);
Long searchResourceCount = zoneResourceMapper.globalSearchCount(request.getKeyword(), request.getZoneId());
typeCounts.put("resource", searchResourceCount);
searchTotal += searchResourceCount;
}
// 搜索Zone会员
if (searchTypes.contains("member")) {
List<GlobalSearchResult> memberResults = searchZoneMembers(request);
allResults.addAll(memberResults);
Long searchMemberCount = zoneMemberMapper.globalSearchCount(request.getKeyword(), request.getZoneId());
typeCounts.put("member", searchMemberCount);
searchTotal += searchMemberCount;
}
// 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<GlobalSearchResult> pagedResults = start < total ?
allResults.subList(start, end) : new ArrayList<>();
// 5. 构建响应
GlobalSearchResponse response = new GlobalSearchResponse();
response.setTotal(searchTotal);
response.setResults(pagedResults);
response.setTypeCounts(typeCounts);
response.setKeyword(request.getKeyword());
response.setPageNum(request.getPageNum());
response.setPageSize(request.getPageSize());
response.setSearchTime(System.currentTimeMillis() - startTime);
// 7. 异步记录搜索日志
recordSearchLog(request, response, loginUser);
return response;
}
/**
* 异步记录搜索日志
*/
@Async
public void recordSearchLog(GlobalSearchRequest request, GlobalSearchResponse response, LoginUser loginUser) {
try {
logger.info("全局搜索 - 关键词: {}, 用户ID: {}, 耗时: {}ms, 结果数: {}, 搜索类型: {}, 专区ID: {}",
request.getKeyword(),
loginUser != null ? loginUser.getUserId() : null,
response.getSearchTime(),
response.getTotal(),
request.getTypes() != null ? String.join(",", request.getTypes()) : null,
request.getZoneId()
);
} catch (Exception e) {
logger.error("记录搜索日志失败: {}", e.getMessage());
}
}
/**
* 搜索Zone项目
*/
private GlobalSearchResultDataVo searchZoneProjects(GlobalSearchRequest request) {
GlobalSearchResultDataVo globalSearchResultDataVo = new GlobalSearchResultDataVo();
List<GlobalSearchResult> results = new ArrayList<>();
Long total = 0L;
try {
if (StringUtils.isNotEmpty(gitLinkSearchUrl)) {
Long projectTypeId = null;
String topic = null;
if (request.getExtraSearchParam() != null) {
projectTypeId = request.getExtraSearchParam().getLong("projectTypeId");
topic = request.getExtraSearchParam().getString("topic");
}
List<String> gitlinkProjectIds = zoneProjectMapper.selectZoneProjectGitlinkIdList(request.getZoneId(), projectTypeId);
JSONObject searchRes = httpAPIService.doGet(request.toProjectEsSearchString(gitLinkSearchUrl, String.join(",", gitlinkProjectIds), topic));
GitlinkSearchDataVo gitlinkSearchDataVos = gitlinkSearchResToGitlinkSearchDataVo(searchRes);
total = gitlinkSearchDataVos.getTotal();
if (gitlinkSearchDataVos.getTotal() > 0) {
List<GitlinkSearchVo> gitlinkSearchVoList = gitlinkSearchDataVos.getGitlinkSearchVoList();
List<Long> gitlinkProjectIdList = gitlinkSearchVoList.stream().map(GitlinkSearchVo::getInstanceId).collect(Collectors.toList());
List<ZoneProject> zoneProjectList = zoneProjectMapper.selectZoneProjectListFromGitlinkProjectIdList(request.getZoneId(), gitlinkProjectIdList);
List<ZoneProjectDataVo> projectDataVoList = projectService.toZoneProjectDataVoList(zoneProjectList);
for (GitlinkSearchVo gitlinkSearchVo : gitlinkSearchVoList) {
ZoneProjectDataVo zoneProjectDataVo = projectDataVoList.stream().filter(x -> x.getGitlinkProjectId().equals(gitlinkSearchVo.getInstanceId())).findFirst().orElse(null);
if (zoneProjectDataVo != null) {
GlobalSearchResult result = gitlinkSearchVo.toGlobalSearchResult(zoneProjectDataVo, topic);
results.add(result);
}
}
}
} else {
List<ZoneProjectWithScore> projects = zoneProjectMapper.globalSearchWithScore(
request.getKeyword(),
request.getZoneId(),
(request.getPageNum() - 1) * request.getPageSize(),
request.getPageSize()
);
total = zoneProjectMapper.globalSearchCount(request.getKeyword(), request.getZoneId());
List<ZoneProject> zoneProjectList = projects.stream().map(x -> (ZoneProject) x).collect(Collectors.toList());
List<ZoneProjectDataVo> projectDataVoList = projectService.toZoneProjectDataVoList(zoneProjectList);
for (ZoneProjectWithScore project : projects) {
ZoneProjectDataVo zoneProjectDataVo = projectDataVoList.stream().filter(x -> x.getId().equals(project.getId())).findFirst().orElse(null);
GlobalSearchResult result = project.toGlobalSearchResult(zoneProjectDataVo);
result.setTitle(SearchUtils.highlightKeyword(result.getTitle(), request.getKeyword()));
result.setContent(SearchUtils.highlightKeyword(result.getContent(), request.getKeyword()));
results.add(result);
}
}
} catch (Exception e) {
System.err.println("搜索Zone项目失败: " + e.getMessage());
}
globalSearchResultDataVo.setTotal(total);
globalSearchResultDataVo.setGlobalSearchResultList(results);
return globalSearchResultDataVo;
}
private GitlinkSearchDataVo gitlinkSearchResToGitlinkSearchDataVo(JSONObject gitlinkSearchRes) {
GitlinkSearchDataVo gitlinkSearchDataVo = new GitlinkSearchDataVo();
List<GitlinkSearchVo> results = new ArrayList<>();
if (gitlinkSearchRes != null && gitlinkSearchRes.getJSONObject("data") != null) {
JSONObject data = gitlinkSearchRes.getJSONObject("data");
if (data.getLong("total") == null || data.getLong("total") == 0) {
gitlinkSearchDataVo.setGitlinkSearchVoList(results);
return gitlinkSearchDataVo;
}
gitlinkSearchDataVo.setTotal(data.getLong("total"));
JSONArray rows = data.getJSONArray("rows");
for (int i = 0; i < rows.size(); i++) {
JSONObject row = rows.getJSONObject(i);
results.add(row.toJavaObject(GitlinkSearchVo.class));
}
gitlinkSearchDataVo.setGitlinkSearchVoList(results);
}
return gitlinkSearchDataVo;
}
/**
* 搜索Zone资源
*/
private List<GlobalSearchResult> searchResources(GlobalSearchRequest request) {
List<GlobalSearchResult> results = new ArrayList<>();
try {
List<ZoneResourceWithScore> resources = zoneResourceMapper.globalSearchWithScore(
request.getKeyword(),
request.getZoneId(),
(request.getPageNum() - 1) * request.getPageSize(),
request.getPageSize()
);
for (ZoneResourceWithScore resource : resources) {
GlobalSearchResult result = new GlobalSearchResult();
result.setType("resource");
result.setId(resource.getId());
result.setTitle(SearchUtils.highlightKeyword(resource.getName(), request.getKeyword()));
result.setContent(SearchUtils.highlightKeyword(resource.getSummary(), request.getKeyword()));
result.setDeptId(resource.getDeptId());
result.setZoneId(resource.getZoneId());
result.setCreateTime(resource.getCreateTime());
// 使用数据库返回的实际搜索评分
Double score = resource.getSearchScore();
if (score == null || score == 0.0) {
score = 0.1; // LIKE 匹配的最小评分
}
result.setScore(score);
// 查询并过滤匹配的附件
if (StringUtils.isNotEmpty(resource.getFileIds())) {
try {
JSONObject extendData = new JSONObject();
extendData.put("downloadCount", resource.getDownloadCount());
if (resource.getFileIds() != null) {
String[] fileIds = resource.getFileIds().split(",");
extendData.put("fileCount", fileIds.length);
}
R<List<SysFileInfo>> fileResponse = remoteFileService.getFileList(resource.getFileIds());
if (fileResponse != null && fileResponse.getCode() == 200 && fileResponse.getData() != null) {
List<JSONObject> matchedFiles = fileResponse.getData().stream()
.filter(file -> StringUtils.isNotEmpty(file.getFileOriginName())
&& file.getFileOriginName().toLowerCase().contains(request.getKeyword().toLowerCase()))
.map(file -> {
JSONObject fileInfo = new JSONObject();
fileInfo.put("fileId", file.getFileId());
fileInfo.put("fileName", SearchUtils.highlightKeyword(file.getFileOriginName(), request.getKeyword()));
fileInfo.put("fileSize", file.getFileSizeInfo());
return fileInfo;
})
.collect(Collectors.toList());
if (!matchedFiles.isEmpty()) {
extendData.put("matchedFiles", matchedFiles);
}
}
result.setExtendData(extendData);
} catch (Exception e) {
logger.warn("查询附件信息失败: resourceId={}, error={}", resource.getId(), e.getMessage());
}
}
results.add(result);
}
} catch (Exception e) {
logger.error("搜索Zone资源失败: {}", e.getMessage());
}
return results;
}
/**
* 搜索Zone会员
*/
private List<GlobalSearchResult> searchZoneMembers(GlobalSearchRequest request) {
List<GlobalSearchResult> results = new ArrayList<>();
try {
List<ZoneMemberWithScore> members = zoneMemberMapper.globalSearchWithScore(
request.getKeyword(),
request.getZoneId(),
(request.getPageNum() - 1) * request.getPageSize(),
request.getPageSize()
);
for (ZoneMemberWithScore member : members) {
GlobalSearchResult result = new GlobalSearchResult();
result.setType("member");
result.setId(member.getId());
result.setTitle(SearchUtils.highlightKeyword(member.getName(), request.getKeyword()));
// 构建内容用户名 + 会员介绍
StringBuilder content = new StringBuilder();
if (member.getSysUser() != null && StringUtils.isNotEmpty(member.getSysUser().getUserName())) {
content.append("用户名: ").append(member.getSysUser().getUserName());
}
if (StringUtils.isNotEmpty(member.getIntroduction())) {
if (content.length() > 0) {
content.append(" | ");
}
content.append(member.getIntroduction());
}
result.setContent(SearchUtils.highlightKeyword(content.toString(), request.getKeyword()));
result.setDeptId(member.getDeptId());
result.setZoneId(member.getZoneId());
result.setCreateTime(member.getCreateTime());
// 使用数据库返回的实际搜索评分
Double score = member.getSearchScore();
if (score == null || score == 0.0) {
score = 0.1; // LIKE 匹配包括用户名匹配的最小评分
}
result.setScore(score);
// 添加扩展数据
if (StringUtils.isNotEmpty(member.getImageUrl()) || member.getMemberLevelId() != null) {
try {
JSONObject extendData = new JSONObject();
if (StringUtils.isNotEmpty(member.getImageUrl())) {
extendData.put("imageUrl", member.getImageUrl());
}
if (StringUtils.isNotEmpty(member.getMemberTypeName())) {
extendData.put("memberTypeName", member.getMemberTypeName());
}
if (member.getSysUser() != null) {
extendData.put("username", member.getSysUser().getUserName());
}
result.setExtendData(extendData);
} catch (Exception e) {
logger.warn("构建会员扩展数据失败: memberId={}, error={}", member.getId(), e.getMessage());
}
}
results.add(result);
}
} catch (Exception e) {
logger.error("搜索Zone会员失败: {}", e.getMessage());
}
return results;
}
/**
* 构建缓存键
*/
private String buildCacheKey(GlobalSearchRequest request) {
StringBuilder key = new StringBuilder(request.getKeyword());
if (request.getTypes() != null && !request.getTypes().isEmpty()) {
key.append(":").append(String.join(",", request.getTypes()));
}
if (request.getZoneId() != null) {
key.append(":zone=").append(request.getZoneId());
}
return key.toString();
}
/**
* 构建空响应
*/
private GlobalSearchResponse buildEmptyResponse(GlobalSearchRequest request) {
GlobalSearchResponse response = new GlobalSearchResponse();
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;
}
}

View File

@ -0,0 +1,19 @@
package com.microservices.zone.search.utils;
import com.microservices.common.core.utils.StringUtils;
import java.util.regex.Pattern;
public class SearchUtils {
/**
* 将文本中的关键词用 <em> 标签包裹不区分大小写
*/
public static 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

@ -28,6 +28,10 @@ public class ZoneConstants {
* GitLink响应总数Key
*/
public final static String GITLINK_RESPONSE_TOTAL_COUNT_KEY = "total_count";
/**
* GitLink响应总数Key
*/
public final static String GITLINK_RESPONSE_COUNT_KEY = "count";
/**
* GitLink项目列表Key
*/
@ -66,6 +70,19 @@ public class ZoneConstants {
* 资源审核状态-待审核
*/
public final static String RESOURCE_NOT_AUDIT = "0";
/**
* 项目审核状态-审核通过
*/
public final static String PROJECT_AUDIT_PASS = "1";
/**
* 项目审核状态-审核未通过
*/
public final static String PROJECT_AUDIT_FAILED = "2";
/**
* 项目审核状态-待审核
*/
public final static String PROJECT_NOT_AUDIT = "0";
/**
* 资源审核状态-已下架
*/

View File

@ -22,6 +22,13 @@ public class ZoneGitLinkRequestUrl extends GitLinkRequestUrl {
return getOpenGitLinkRequestUrl(String.format("/api/projects.json%s", query));
}
/**
* 搜索项目列表
*/
public static GitLinkRequestUrl MY_OWNER_PROJECTS(String username,String query) {
return getOpenGitLinkRequestUrl(String.format("/api/users/%s/projects.json%s",username, query));
}
/**
* 获取项目贡献者

View File

@ -44,6 +44,7 @@
<result property="helperTitle" column="helper_title"/>
<result property="docNeedAudit" column="doc_need_audit"/>
<result property="resourceNeedAudit" column="resource_need_audit"/>
<result property="projectNeedAudit" column="project_need_audit"/>
<result property="isLockCms" column="is_lock_cms"/>
<result property="sectionCmsShow" column="section_cms_show"/>
<result property="sectionProjectShow" column="section_project_show"/>
@ -101,6 +102,7 @@
template,
is_lock_cms,
resource_need_audit,
project_need_audit,
doc_need_audit,
section_cms_show,
section_project_show,
@ -159,6 +161,7 @@
z.template,
z.is_lock_cms,
z.resource_need_audit,
z.project_need_audit,
z.doc_need_audit,
z.section_cms_show,
z.section_project_show,
@ -273,6 +276,7 @@
<if test="helperShow != null">helper_show,</if>
<if test="docNeedAudit != null">doc_need_audit,</if>
<if test="resourceNeedAudit != null">resource_need_audit,</if>
<if test="projectNeedAudit != null">project_need_audit,</if>
<if test="isLockCms != null">is_lock_cms,</if>
<if test="sectionCmsShow != null">section_cms_show,</if>
<if test="sectionProjectShow != null">section_project_show,</if>
@ -327,6 +331,7 @@
<if test="helperShow != null">#{helperShow},</if>
<if test="docNeedAudit != null">#{docNeedAudit},</if>
<if test="resourceNeedAudit != null">#{resourceNeedAudit},</if>
<if test="projectNeedAudit != null">#{projectNeedAudit},</if>
<if test="isLockCms != null">#{isLockCms},</if>
<if test="sectionCmsShow != null">#{sectionCmsShow},</if>
<if test="sectionProjectShow != null">#{sectionProjectShow},</if>
@ -390,6 +395,7 @@
<if test="helperTitle != null">helper_title = #{helperTitle},</if>
<if test="docNeedAudit != null">doc_need_audit = #{docNeedAudit},</if>
<if test="resourceNeedAudit != null">resource_need_audit = #{resourceNeedAudit},</if>
<if test="projectNeedAudit != null">project_need_audit = #{projectNeedAudit},</if>
<if test="isLockCms != null">is_lock_cms = #{isLockCms},</if>
<if test="sectionCmsShow != null">section_cms_show = #{sectionCmsShow},</if>
<if test="sectionProjectShow != null">section_project_show = #{sectionProjectShow},</if>

View File

@ -61,6 +61,12 @@
<result property="name" column="levelName"/>
</resultMap>
<resultMap type="com.microservices.zone.member.domain.ZoneMemberWithScore"
id="ZoneMemberWithScoreResult" extends="ZoneMemberResult">
<result property="searchScore" column="search_score"/>
<result property="memberTypeName" column="typeName"/>
</resultMap>
<sql id="selectZoneMemberAliasVo">
select zm.id,
zm.user_id,
@ -109,6 +115,54 @@
left join zone_member_type zmt on zmt.id = zm.member_type_id
</sql>
<sql id="selectZoneMemberWithSearchVo">
select zm.id,
zm.user_id,
zm.name,
zm.image_url,
zm.introduction,
zm.member_type_id,
zm.member_level_id,
zm.is_audit,
zm.is_homepage,
zm.dept_id,
zm.del_flag,
zm.create_by,
zm.create_time,
zm.update_by,
zm.update_time,
zm.zone_id,
zm.sort,
zm.remark,
zm.audit_time,
zml.name as levelName,
zml.sort as levelSort,
zmt.name as typeName,
zmt.sort as typeSort,
u.user_id as userId,
u.gitlink_user_id,
u.user_name,
u.nick_name,
u.email,
u.avatar,
u.phonenumber,
u.sex,
u.status,
u.del_flag as userDelFlag,
d.dept_id as deptId,
d.parent_id,
d.ancestors,
d.dept_name,
d.order_num,
d.leader,
d.status as dept_status,
MATCH(zm.name) AGAINST(#{keyword} IN BOOLEAN MODE) AS search_score
from zone_member as zm
left join sys_dept d on zm.dept_id = d.dept_id
left join sys_user u on u.user_id = zm.user_id
left join zone_member_level zml on zml.id = zm.member_level_id
left join zone_member_type zmt on zmt.id = zm.member_type_id
</sql>
<sql id="selectZoneMemberVo">
select id,
user_id,
@ -363,4 +417,32 @@
from zone_member
where zone_id = #{zoneId}
</delete>
<!-- 全局搜索 - 带评分的全文索引查询 -->
<select id="globalSearchWithScore" resultMap="ZoneMemberWithScoreResult">
<include refid="selectZoneMemberWithSearchVo"/>
WHERE zm.zone_id = #{zoneId}
AND zm.is_audit = '1'
AND zm.del_flag = '0'
AND (
MATCH(zm.name) AGAINST(#{keyword} IN BOOLEAN MODE)
OR zm.name LIKE CONCAT('%', #{keyword}, '%')
OR u.user_name LIKE CONCAT('%', #{keyword}, '%')
)
ORDER BY search_score DESC, zm.create_time DESC
LIMIT #{offset}, #{pageSize}
</select>
<select id="globalSearchCount" resultType="java.lang.Long">
SELECT COUNT(*)
FROM zone_member zm
left join sys_user u on u.user_id = zm.user_id
WHERE zm.zone_id = #{zoneId}
AND zm.is_audit = '1'
AND zm.del_flag = '0'
AND (
MATCH(zm.name) AGAINST(#{keyword} IN BOOLEAN MODE)
OR zm.name LIKE CONCAT('%', #{keyword}, '%')
OR u.user_name LIKE CONCAT('%', #{keyword}, '%')
)
</select>
</mapper>

View File

@ -19,6 +19,10 @@
<result property="zoneId" column="zone_id"/>
<result property="sort" column="sort"/>
<result property="isHomepage" column="is_homepage"/>
<result property="auditStatus" column="audit_status"/>
<result property="auditTime" column="audit_time"/>
<result property="auditBy" column="audit_by"/>
<result property="remark" column="remark"/>
<association property="dept" column="dept_id" javaType="com.microservices.system.api.domain.SysDept"
resultMap="deptResult"/>
<association property="zoneProjectType" column="project_type_id"
@ -42,6 +46,11 @@
<result property="deptId" column="type_dept_id"/>
</resultMap>
<resultMap type="com.microservices.zone.project.domain.ZoneProjectWithScore"
id="ZoneProjectWithScoreResult" extends="ZoneProjectResult">
<result property="searchScore" column="search_score"/>
</resultMap>
<sql id="selectZoneProjectVo">
select id,
name,
@ -56,7 +65,11 @@
create_time,
update_by,
is_homepage,
update_time
update_time,
audit_status,
audit_time,
audit_by,
remark
from zone_project
</sql>
@ -75,6 +88,9 @@
zp.update_time,
zp.project_type_id,
zp.is_homepage,
zp.audit_status,
zp.audit_time,
zp.audit_by,
d.dept_id,
d.parent_id,
d.ancestors,
@ -84,7 +100,8 @@
d.status as dept_status,
zpt.id,
zpt.name as type_name,
zpt.dept_id as type_dept_id
zpt.dept_id as type_dept_id,
zp.remark
from zone_project as zp
left join sys_dept as d on zp.dept_id = d.dept_id
left join zone_project_type as zpt on zp.project_type_id = zpt.id
@ -114,6 +131,22 @@
</if>
<if test="projectTypeId != null ">and zp.project_type_id = #{projectTypeId}</if>
<if test="zoneId != null ">and zp.zone_id = #{zoneId}</if>
<if test="createBy != null and createBy != ''">and zp.create_by = #{createBy}</if>
<choose>
<when test="isMyProject != null and isMyProject">
and zp.audit_status != '2'
</when>
<otherwise>
<choose>
<when test="auditStatus != null and auditStatus !=''">
and zp.audit_status = #{auditStatus}
</when>
<otherwise>
and zp.audit_status = '1'
</otherwise>
</choose>
</otherwise>
</choose>
<if test="isHomepage != null ">and zp.is_homepage = #{isHomepage}</if>
<!-- 数据范围过滤 -->
@ -132,6 +165,21 @@
<if test="name != null and name != ''">and zp.name like concat('%', #{name}, '%')</if>
<if test="projectTypeId != null ">and zp.project_type_id = #{projectTypeId}</if>
<if test="zoneId != null ">and zp.zone_id = #{zoneId}</if>
<choose>
<when test="isMyProject != null and isMyProject">
and zp.audit_status != '2'
</when>
<otherwise>
<choose>
<when test="auditStatus != null and auditStatus !=''">
and zp.audit_status = #{auditStatus}
</when>
<otherwise>
and zp.audit_status = '1'
</otherwise>
</choose>
</otherwise>
</choose>
<!-- 数据范围过滤 -->
${params.dataScope}
</where>
@ -140,6 +188,7 @@
<include refid="selectZoneProjectAliasVo"/>
where zp.zone_id = #{zoneId}
and zp.gitlink_project_id = #{gitlinkProjectId}
and zp.audit_status != '2'
</select>
<select id="selectMaxSortByZoneId" resultType="java.lang.Integer">
select MAX(sort)
@ -187,6 +236,9 @@
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="auditStatus != null">audit_status,</if>
<if test="auditTime != null">audit_time,</if>
<if test="auditBy != null">audit_by,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="name != null and name != ''">#{name},</if>
@ -201,6 +253,9 @@
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="auditStatus != null">#{auditStatus},</if>
<if test="auditTime != null">#{auditTime},</if>
<if test="auditBy != null">#{auditBy},</if>
</trim>
</insert>
@ -218,6 +273,10 @@
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="sort != null">sort = #{sort},</if>
<if test="isHomepage != null">is_homepage = #{isHomepage},</if>
<if test="auditStatus != null">audit_status = #{auditStatus},</if>
<if test="auditTime != null">audit_time = #{auditTime},</if>
<if test="auditBy != null">audit_by = #{auditBy},</if>
<if test="remark != null">remark = #{remark},</if>
</trim>
where id = #{id}
</update>
@ -256,4 +315,54 @@
from zone_project
where zone_id = #{zoneId}
</delete>
<!-- 全局搜索 - 带评分的全文索引查询 -->
<select id="globalSearchWithScore" resultMap="ZoneProjectWithScoreResult">
SELECT
zp.id, zp.name, zp.sort, zp.full_name, zp.gitlink_project_id,
zp.dept_id, zp.zone_id, zp.del_flag, zp.create_by, zp.create_time,
zp.update_by, zp.update_time, zp.project_type_id, zp.is_homepage,
zp.audit_status, zp.audit_time, zp.audit_by,
MATCH(zp.name) AGAINST(#{keyword} IN BOOLEAN MODE) AS search_score
FROM zone_project zp
WHERE zp.zone_id = #{zoneId}
AND zp.audit_status = '1'
AND (
MATCH(zp.name) AGAINST(#{keyword} IN BOOLEAN MODE)
OR zp.name LIKE CONCAT('%', #{keyword}, '%')
)
ORDER BY search_score DESC, zp.update_time DESC
LIMIT #{offset}, #{pageSize}
</select>
<select id="globalSearchCount" resultType="java.lang.Long">
SELECT COUNT(*)
FROM zone_project zp
WHERE zp.audit_status = '1'
AND (
MATCH(zp.name) AGAINST(#{keyword} IN BOOLEAN MODE)
OR zp.name LIKE CONCAT('%', #{keyword}, '%')
)
<if test="zoneId != null">
AND zp.zone_id = #{zoneId}
</if>
<if test="deptId != null">
AND zp.dept_id = #{deptId}
</if>
</select>
<select id="selectZoneProjectGitlinkIdList" resultType="java.lang.String">
select gitlink_project_id from zone_project
where zone_id=#{zoneId}
<if test="projectTypeId != null">
AND project_type_id = #{projectTypeId}
</if>
</select>
<select id="selectZoneProjectListFromGitlinkProjectIdList" resultMap="ZoneProjectWithScoreResult">
<include refid="selectZoneProjectVo"/>
where zone_id=#{zoneId}
and gitlink_project_id in
<foreach item="gitlinkProjectId" collection="gitlinkProjectIdList" open="(" separator="," close=")">
#{gitlinkProjectId}
</foreach>
</select>
</mapper>

View File

@ -15,12 +15,15 @@
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="sort" column="sort"/>
<result property="detailJson" column="detail_json"/>
<result property="projectCount" column="projectCount"/>
</resultMap>
<sql id="selectZoneProjectTypeVo">
select id,
name,
sort,
detail_json,
dept_id,
zone_id,
del_flag,
@ -35,13 +38,15 @@
select zpt.id,
zpt.name,
zpt.sort,
zpt.detail_json,
zpt.dept_id,
zpt.zone_id,
zpt.del_flag,
zpt.create_by,
zpt.create_time,
zpt.update_by,
zpt.update_time
zpt.update_time,
(select count(1) from zone_project where project_type_id=zpt.id) as projectCount
from zone_project_type as zpt
</sql>
@ -124,6 +129,7 @@
<if test="deptId != null">dept_id,</if>
<if test="zoneId != null">zone_id,</if>
<if test="sort != null">sort,</if>
<if test="detailJson != null">detail_json,</if>
<if test="delFlag != null">del_flag,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
@ -135,6 +141,7 @@
<if test="deptId != null">#{deptId},</if>
<if test="zoneId != null">#{zoneId},</if>
<if test="sort != null">#{sort},</if>
<if test="detailJson != null">#{detailJson},</if>
<if test="delFlag != null">#{delFlag},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
@ -150,6 +157,7 @@
<if test="deptId != null">dept_id = #{deptId},</if>
<if test="zoneId != null">zone_id = #{zoneId},</if>
<if test="sort != null">sort = #{sort},</if>
<if test="detailJson != null">detail_json = #{detailJson},</if>
<if test="delFlag != null">del_flag = #{delFlag},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>

View File

@ -42,6 +42,12 @@
<result property="name" column="domain_name"/>
</resultMap>
<resultMap type="com.microservices.zone.resource.domain.ZoneResourceWithScore"
id="ZoneResourceWithScoreResult" extends="ZoneResourceResult">
<result property="searchScore" column="search_score"/>
<result property="downloadCount" column="download_count"/>
</resultMap>
<sql id="selectZoneResourceVo">
select id,
name,
@ -325,6 +331,53 @@
where zone_id = #{zoneId}
</delete>
<!-- 全局搜索 - 带评分的全文索引查询 -->
<select id="globalSearchWithScore" resultMap="ZoneResourceWithScoreResult">
SELECT
zr.id, zr.name, zr.file_ids, zr.domain_id, zr.sort,
zr.is_homepage, zr.zone_id, zr.dept_id, zr.del_flag,
zr.create_by, zr.create_time, zr.update_by, zr.update_time,
zr.editor_type, zr.audit_status, zr.audit_time, zr.audit_by,
zr.summary, zr.remark, zr.keywords, zr.extend_data,
(select sum(download_count) from sys_file_info sf where FIND_IN_SET(sf.file_id,zr.file_ids)) as download_count,
MATCH(zr.name, zr.keywords) AGAINST(#{keyword} IN BOOLEAN MODE) AS search_score
FROM zone_resource zr
WHERE zr.zone_id = #{zoneId}
AND zr.audit_status = '1'
AND (
MATCH(zr.name, zr.keywords) AGAINST(#{keyword} IN BOOLEAN MODE)
OR zr.name LIKE CONCAT('%', #{keyword}, '%')
OR zr.keywords LIKE CONCAT('%', #{keyword}, '%')
OR EXISTS (
SELECT 1 FROM sys_file_info sf
WHERE FIND_IN_SET(sf.file_id, zr.file_ids)
AND sf.file_origin_name LIKE CONCAT('%', #{keyword}, '%')
)
)
ORDER BY search_score DESC, zr.update_time DESC
LIMIT #{offset}, #{pageSize}
</select>
<select id="globalSearchCount" resultType="java.lang.Long">
SELECT COUNT(*)
FROM zone_resource zr
WHERE
zr.audit_status = '1'
AND (
MATCH(zr.name, zr.keywords) AGAINST(#{keyword} IN BOOLEAN MODE)
OR zr.name LIKE CONCAT('%', #{keyword}, '%')
OR zr.keywords LIKE CONCAT('%', #{keyword}, '%')
OR EXISTS (
SELECT 1 FROM sys_file_info sf
WHERE FIND_IN_SET(sf.file_id, zr.file_ids)
AND sf.file_origin_name LIKE CONCAT('%', #{keyword}, '%')
)
)
<if test="zoneId != null">
AND zr.zone_id = #{zoneId}
</if>
</select>
<select id="selectSkillResourceByName" resultType="com.microservices.zone.resource.domain.ZoneResource">
<include refid="selectZoneResourceVo"/>
where name = #{name} and resource_category = 'SKILL' and del_flag = '0'