This commit is contained in:
六如
2024-11-25 00:18:47 +08:00
parent ba384660f1
commit 5e2c3fab46
37 changed files with 939 additions and 86 deletions

View File

@@ -0,0 +1,19 @@
package com.gitee.sop.adminbackend.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 来源类型,1-torna,2-自建
*
* @author 六如
*/
@Getter
@AllArgsConstructor
public enum DocSourceTypeEnum implements IntEnum {
TORNA(1, "Torna"),
CUSTOM(2, "自建");
private final Integer value;
private final String description;
}

View File

@@ -0,0 +1,21 @@
package com.gitee.sop.adminbackend.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 文档类型,0:http,1:dubbo,2:富文本,3:Markdown
*
* @author 六如
*/
@Getter
@AllArgsConstructor
public enum DocTypeEnum implements IntEnum {
HTTP(0, "HTTP"),
DUBBO(1, "dubbo"),
RICH_TEXT(2, "富文本"),
MARKDOWN(3, "Markdown");
private final Integer value;
private final String description;
}

View File

@@ -0,0 +1,24 @@
package com.gitee.sop.adminbackend.common.enums;
import java.io.Serializable;
/**
* @author 六如
*/
public interface IEnum<T extends Serializable> {
/**
* 获取值
*
* @return 返回枚举值
*/
T getValue();
/**
* 获取描述
*
* @return 返回枚举描述
*/
String getDescription();
}

View File

@@ -0,0 +1,24 @@
package com.gitee.sop.adminbackend.common.enums;
import java.util.Objects;
import java.util.Optional;
/**
* @author 六如
*/
public interface IntEnum extends IEnum<Integer> {
static <T extends IntEnum> Optional<T> of(T[] values, Integer value) {
for (IntEnum intEnum : values) {
if (Objects.equals(intEnum.getValue(), value)) {
return Optional.of((T) intEnum);
}
}
return Optional.empty();
}
static <T extends IntEnum> Optional<String> ofDescription(T[] values, Integer value) {
return of(values, value).map(IntEnum::getDescription);
}
}

View File

@@ -1,5 +1,6 @@
package com.gitee.sop.adminbackend.common.enums; package com.gitee.sop.adminbackend.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import java.util.Objects; import java.util.Objects;
@@ -8,25 +9,23 @@ import java.util.Objects;
* @author 六如 * @author 六如
*/ */
@Getter @Getter
public enum StatusEnum { @AllArgsConstructor
DISABLED((byte) 2), public enum StatusEnum implements IntEnum {
ENABLE((byte) 1), DISABLED(2, "禁用"),
SET_PWD((byte) 3); ENABLE(1, "启用"),
SET_PWD(3, "重置密码");
private final int status; private final Integer value;
private final String description;
public static StatusEnum of(Integer value) { public static StatusEnum of(Integer value) {
for (StatusEnum statusEnum : StatusEnum.values()) { for (StatusEnum statusEnum : StatusEnum.values()) {
if (Objects.equals(statusEnum.status, value)) { if (Objects.equals(statusEnum.value, value)) {
return statusEnum; return statusEnum;
} }
} }
return DISABLED; return DISABLED;
} }
StatusEnum(byte style) {
this.status = style;
}
} }

View File

@@ -0,0 +1,24 @@
package com.gitee.sop.adminbackend.common.enums;
import java.util.Objects;
import java.util.Optional;
/**
* @author 六如
*/
public interface StringEnum extends IEnum<String> {
static <T extends StringEnum> Optional<T> of(T[] values, String value) {
for (StringEnum intEnum : values) {
if (Objects.equals(intEnum.getValue(), value)) {
return Optional.of((T) intEnum);
}
}
return Optional.empty();
}
static <T extends StringEnum> Optional<String> ofDescription(T[] values, String value) {
return of(values, value).map(StringEnum::getDescription);
}
}

View File

@@ -10,14 +10,18 @@ import java.util.Objects;
*/ */
@AllArgsConstructor @AllArgsConstructor
@Getter @Getter
public enum YesOrNoEnum { public enum YesOrNoEnum implements IntEnum {
YES(1), YES(1, ""),
NO(0); NO(0, "");
private final int value; private final Integer value;
private final String description;
public static YesOrNoEnum of(Integer value) { public static YesOrNoEnum of(Number value) {
return Objects.equals(value, YES.value) ? YES : NO; if (value == null) {
return NO;
}
return Objects.equals(value.intValue(), YES.value) ? YES : NO;
} }
public static YesOrNoEnum of(Boolean value) { public static YesOrNoEnum of(Boolean value) {

View File

@@ -3,15 +3,20 @@ package com.gitee.sop.adminbackend.controller.doc;
import com.gitee.sop.adminbackend.common.resp.Result; import com.gitee.sop.adminbackend.common.resp.Result;
import com.gitee.sop.adminbackend.common.util.CopyUtil; import com.gitee.sop.adminbackend.common.util.CopyUtil;
import com.gitee.sop.adminbackend.controller.doc.param.DocAppAddParam; import com.gitee.sop.adminbackend.controller.doc.param.DocAppAddParam;
import com.gitee.sop.adminbackend.controller.doc.param.DocInfoUpdateParam;
import com.gitee.sop.adminbackend.controller.doc.vo.DocAppVO; import com.gitee.sop.adminbackend.controller.doc.vo.DocAppVO;
import com.gitee.sop.adminbackend.controller.doc.vo.DocInfoTreeVO;
import com.gitee.sop.adminbackend.service.doc.DocService; import com.gitee.sop.adminbackend.service.doc.DocService;
import com.gitee.sop.adminbackend.service.website.dto.DocAppDTO; import com.gitee.sop.adminbackend.service.doc.dto.DocAppDTO;
import com.gitee.sop.adminbackend.service.doc.dto.DocInfoTreeDTO;
import com.gitee.sop.adminbackend.service.doc.dto.DocInfoPublishUpdateDTO;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.List; import java.util.List;
@@ -34,9 +39,22 @@ public class DocController {
} }
@PostMapping("app/add") @PostMapping("app/add")
public Result<Integer> addApp(@Validated @RequestBody DocAppAddParam param) { public Result<Long> addApp(@Validated @RequestBody DocAppAddParam param) {
docService.addDocApp(param.getTornaToken()); Long docAppId = docService.addDocApp(param.getTornaToken());
return Result.ok(1); return Result.ok(docAppId);
}
@GetMapping("info/tree")
public Result<List<DocInfoTreeVO>> docTree(@RequestParam Long docAppId) {
List<DocInfoTreeDTO> docInfoTreeDTOS = docService.listDocTree(docAppId);
List<DocInfoTreeVO> docInfoTreeVOS = CopyUtil.deepCopyList(docInfoTreeDTOS, DocInfoTreeVO.class);
return Result.ok(docInfoTreeVOS);
}
@PostMapping("info/publish")
public Result<Integer> publish(@Validated @RequestBody DocInfoUpdateParam param) {
int cnt = docService.publish(CopyUtil.copyBean(param, DocInfoPublishUpdateDTO::new));
return Result.ok(cnt);
} }
} }

View File

@@ -0,0 +1,18 @@
package com.gitee.sop.adminbackend.controller.doc.param;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author 六如
*/
@Data
public class DocInfoUpdateParam {
@NotNull
private Long id;
private Integer isPublish;
}

View File

@@ -1,10 +1,12 @@
package com.gitee.sop.adminbackend.controller.website.vo; package com.gitee.sop.adminbackend.controller.doc.vo;
import com.gitee.fastmybatis.core.support.TreeNode; import com.gitee.fastmybatis.core.support.TreeNode;
import com.gitee.sop.adminbackend.common.enums.YesOrNoEnum;
import lombok.Data; import lombok.Data;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
@@ -30,6 +32,11 @@ public class DocInfoTreeVO implements TreeNode<DocInfoTreeVO, Long> {
*/ */
private Long docId; private Long docId;
/**
* 文档标题
*/
private String docTitle;
/** /**
* 文档code * 文档code
*/ */
@@ -95,7 +102,7 @@ public class DocInfoTreeVO implements TreeNode<DocInfoTreeVO, Long> {
@Override @Override
public Long takeId() { public Long takeId() {
return id; return docId;
} }
@Override @Override
@@ -103,4 +110,11 @@ public class DocInfoTreeVO implements TreeNode<DocInfoTreeVO, Long> {
return parentId; return parentId;
} }
public String getDocName() {
if (Objects.equals(isFolder, YesOrNoEnum.YES.getValue())) {
return "";
}
return docName;
}
} }

View File

@@ -4,10 +4,10 @@ import com.gitee.sop.adminbackend.common.annotation.NoToken;
import com.gitee.sop.adminbackend.common.resp.Result; import com.gitee.sop.adminbackend.common.resp.Result;
import com.gitee.sop.adminbackend.common.util.CopyUtil; import com.gitee.sop.adminbackend.common.util.CopyUtil;
import com.gitee.sop.adminbackend.controller.doc.vo.DocAppVO; import com.gitee.sop.adminbackend.controller.doc.vo.DocAppVO;
import com.gitee.sop.adminbackend.controller.website.vo.DocInfoTreeVO; import com.gitee.sop.adminbackend.controller.doc.vo.DocInfoTreeVO;
import com.gitee.sop.adminbackend.service.website.WebsiteService; import com.gitee.sop.adminbackend.service.website.WebsiteService;
import com.gitee.sop.adminbackend.service.website.dto.DocAppDTO; import com.gitee.sop.adminbackend.service.doc.dto.DocAppDTO;
import com.gitee.sop.adminbackend.service.website.dto.DocInfoTreeDTO; import com.gitee.sop.adminbackend.service.doc.dto.DocInfoTreeDTO;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;

View File

@@ -34,6 +34,11 @@ public class DocInfo {
*/ */
private Long docId; private Long docId;
/**
* 文档标题
*/
private String docTitle;
/** /**
* 文档code * 文档code
*/ */
@@ -54,6 +59,11 @@ public class DocInfo {
*/ */
private String docName; private String docName;
/**
* 版本号
*/
private String docVersion;
/** /**
* 描述 * 描述
*/ */

View File

@@ -0,0 +1,16 @@
package com.gitee.sop.adminbackend.service.doc;
import com.gitee.fastmybatis.core.support.LambdaService;
import com.gitee.sop.adminbackend.dao.entity.DocApp;
import com.gitee.sop.adminbackend.dao.mapper.DocAppMapper;
import org.springframework.stereotype.Service;
/**
* @author 六如
*/
@Service
public class DocAppService implements LambdaService<DocApp, DocAppMapper> {
}

View File

@@ -0,0 +1,107 @@
package com.gitee.sop.adminbackend.service.doc;
import com.gitee.fastmybatis.core.support.LambdaService;
import com.gitee.sop.adminbackend.common.enums.DocSourceTypeEnum;
import com.gitee.sop.adminbackend.common.enums.YesOrNoEnum;
import com.gitee.sop.adminbackend.dao.entity.DocApp;
import com.gitee.sop.adminbackend.dao.entity.DocInfo;
import com.gitee.sop.adminbackend.dao.mapper.DocAppMapper;
import com.gitee.sop.adminbackend.dao.mapper.DocInfoMapper;
import com.gitee.sop.adminbackend.service.doc.dto.TornaDocDTO;
import com.gitee.sop.adminbackend.service.doc.dto.TornaDocInfoDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author 六如
*/
@Service
public class DocInfoService implements LambdaService<DocInfo, DocInfoMapper> {
@Autowired
private TornaClient tornaClient;
@Autowired
private DocAppMapper docAppMapper;
public List<DocInfo> listChildDoc(Long parentId) {
return this.list(DocInfo::getParentId, parentId);
}
public void syncDocInfo(Long docAppId) {
Map<Object, DocInfo> nameVersionMap = this.list(DocInfo::getDocAppId, docAppId)
.stream()
.collect(Collectors.toMap(docInfo -> docInfo.getDocName() + ":" + docInfo.getDocVersion(), Function.identity(), (v1, v2) -> v2));
DocApp docApp = docAppMapper.getById(docAppId);
String token = docApp.getToken();
// add doc
TornaDocDTO tornaDocDTO = tornaClient.execute("doc.list", null, token, TornaDocDTO.class);
List<TornaDocInfoDTO> docList = tornaDocDTO.getDocList();
if (!CollectionUtils.isEmpty(docList)) {
List<DocInfo> updateList = new ArrayList<>();
for (TornaDocInfoDTO tornaDocInfoDTO : docList) {
String key = tornaDocInfoDTO.getUrl() + ":" + tornaDocInfoDTO.getVersion();
DocInfo docInfo = nameVersionMap.get(key);
// 需要修改的文档
if (docInfo != null) {
docInfo.setDocId(tornaDocInfoDTO.getId());
docInfo.setDocTitle(tornaDocInfoDTO.getName());
docInfo.setDocCode("");
if (YesOrNoEnum.of(tornaDocInfoDTO.getIsFolder()) == YesOrNoEnum.YES) {
docInfo.setDocName(tornaDocInfoDTO.getName());
}
docInfo.setDocId(tornaDocInfoDTO.getId());
docInfo.setDocType(tornaDocInfoDTO.getType().intValue());
docInfo.setDescription(tornaDocInfoDTO.getDescription());
docInfo.setIsFolder(tornaDocInfoDTO.getIsFolder().intValue());
docInfo.setParentId(tornaDocInfoDTO.getParentId());
updateList.add(docInfo);
}
}
for (DocInfo docInfo : updateList) {
this.update(docInfo);
}
// 新增的文档
List<DocInfo> saveList = docList.stream()
.filter(tornaDocInfoDTO -> {
String key = tornaDocInfoDTO.getUrl() + ":" + tornaDocInfoDTO.getVersion();
return !nameVersionMap.containsKey(key);
})
.map(tornaDocInfoDTO -> {
DocInfo docInfo = new DocInfo();
docInfo.setDocAppId(docAppId);
docInfo.setDocId(tornaDocInfoDTO.getId());
docInfo.setDocTitle(tornaDocInfoDTO.getName());
docInfo.setDocCode("");
docInfo.setDocType(tornaDocInfoDTO.getType().intValue());
docInfo.setSourceType(DocSourceTypeEnum.TORNA.getValue());
if (YesOrNoEnum.of(tornaDocInfoDTO.getIsFolder()) == YesOrNoEnum.YES) {
docInfo.setDocName(tornaDocInfoDTO.getName());
} else {
docInfo.setDocName(tornaDocInfoDTO.getUrl());
}
docInfo.setDocVersion(tornaDocInfoDTO.getVersion());
docInfo.setDescription(tornaDocInfoDTO.getDescription());
docInfo.setIsFolder(tornaDocInfoDTO.getIsFolder().intValue());
docInfo.setIsPublish(YesOrNoEnum.NO.getValue());
docInfo.setParentId(tornaDocInfoDTO.getParentId());
return docInfo;
})
.collect(Collectors.toList());
this.saveBatch(saveList);
}
}
}

View File

@@ -1,30 +1,27 @@
package com.gitee.sop.adminbackend.service.doc; package com.gitee.sop.adminbackend.service.doc;
import com.alibaba.fastjson2.JSON; import com.gitee.fastmybatis.core.util.TreeUtil;
import com.alibaba.fastjson2.JSONObject;
import com.gitee.httphelper.HttpHelper;
import com.gitee.sop.adminbackend.common.enums.ConfigKeyEnum; import com.gitee.sop.adminbackend.common.enums.ConfigKeyEnum;
import com.gitee.sop.adminbackend.common.exception.BizException; import com.gitee.sop.adminbackend.common.enums.YesOrNoEnum;
import com.gitee.sop.adminbackend.common.util.CopyUtil; import com.gitee.sop.adminbackend.common.util.CopyUtil;
import com.gitee.sop.adminbackend.dao.entity.DocApp; import com.gitee.sop.adminbackend.dao.entity.DocApp;
import com.gitee.sop.adminbackend.dao.mapper.DocAppMapper; import com.gitee.sop.adminbackend.dao.entity.DocInfo;
import com.gitee.sop.adminbackend.service.doc.dto.DocAppDTO;
import com.gitee.sop.adminbackend.service.doc.dto.DocInfoPublishUpdateDTO;
import com.gitee.sop.adminbackend.service.doc.dto.DocInfoTreeDTO;
import com.gitee.sop.adminbackend.service.doc.dto.DocSettingDTO; import com.gitee.sop.adminbackend.service.doc.dto.DocSettingDTO;
import com.gitee.sop.adminbackend.service.doc.dto.TornaModuleDTO; import com.gitee.sop.adminbackend.service.doc.dto.TornaModuleDTO;
import com.gitee.sop.adminbackend.service.sys.SysConfigService; import com.gitee.sop.adminbackend.service.sys.SysConfigService;
import com.gitee.sop.adminbackend.service.sys.dto.SystemConfigDTO; import com.gitee.sop.adminbackend.service.sys.dto.SystemConfigDTO;
import java.io.IOException; import org.springframework.beans.factory.annotation.Autowired;
import java.time.LocalDateTime; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Set;
import java.util.stream.Collectors;
import com.gitee.sop.adminbackend.service.website.dto.DocAppDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/** /**
@@ -37,10 +34,12 @@ public class DocService {
private SysConfigService sysConfigService; private SysConfigService sysConfigService;
@Autowired @Autowired
private DocAppMapper docAppMapper; private DocAppService docAppService;
@Autowired @Autowired
private TornaClient tornaClient; private TornaClient tornaClient;
@Autowired
private DocInfoService docInfoService;
public DocSettingDTO getDocSetting() { public DocSettingDTO getDocSetting() {
DocSettingDTO docSettingDTO = new DocSettingDTO(); DocSettingDTO docSettingDTO = new DocSettingDTO();
@@ -58,21 +57,55 @@ public class DocService {
sysConfigService.save(systemConfigDTOS); sysConfigService.save(systemConfigDTOS);
} }
public void addDocApp(String token) { public Long addDocApp(String token) {
if (docAppMapper.checkExist(DocApp::getToken, token)) {
throw new BizException("该应用已添加");
}
TornaModuleDTO tornaModuleDTO = tornaClient.execute("module.get", null, token, TornaModuleDTO.class); TornaModuleDTO tornaModuleDTO = tornaClient.execute("module.get", null, token, TornaModuleDTO.class);
DocApp docApp = new DocApp(); DocApp docApp = docAppService.get(DocApp::getToken, token);
if (docApp == null) {
docApp = new DocApp();
docApp.setAppName(tornaModuleDTO.getName()); docApp.setAppName(tornaModuleDTO.getName());
docApp.setToken(token); docApp.setToken(token);
docAppMapper.saveIgnoreNull(docApp); docAppService.save(docApp);
} else {
docApp.setAppName(tornaModuleDTO.getName());
docAppService.update(docApp);
}
// 同步文档
docInfoService.syncDocInfo(docApp.getId());
return docApp.getId();
} }
public List<DocAppDTO> listDocApp() { public List<DocAppDTO> listDocApp() {
List<DocApp> docApps = docAppMapper.listAll(); List<DocApp> docApps = docAppService.listAll();
return CopyUtil.copyList(docApps, DocAppDTO::new); return CopyUtil.copyList(docApps, DocAppDTO::new);
} }
public List<DocInfoTreeDTO> listDocTree(Long docAppId) {
List<DocInfo> list = docInfoService.list(DocInfo::getDocAppId, docAppId);
if (CollectionUtils.isEmpty(list)) {
return new ArrayList<>(0);
}
List<DocInfoTreeDTO> docInfoTreeDTOS = CopyUtil.copyList(list, DocInfoTreeDTO::new);
return TreeUtil.convertTree(docInfoTreeDTOS, 0L);
}
public int publish(DocInfoPublishUpdateDTO docInfoUpdateDTO) {
DocInfo docInfo = docInfoService.getById(docInfoUpdateDTO.getId());
// 如果是文件夹,发布下面所有的文档
if (YesOrNoEnum.of(docInfo.getIsFolder()) == YesOrNoEnum.YES) {
List<DocInfo> children = this.docInfoService.listChildDoc(docInfo.getDocId());
Set<Long> ids = children.stream().map(DocInfo::getId).collect(Collectors.toSet());
return docInfoService.query()
.in(DocInfo::getId, ids)
.set(DocInfo::getIsPublish, docInfoUpdateDTO.getIsPublish())
.update();
} else {
// 发布单个文档
return docInfoService.query()
.eq(DocInfo::getId, docInfoUpdateDTO.getId())
.set(DocInfo::getIsPublish, docInfoUpdateDTO.getIsPublish())
.update();
}
}
} }

View File

@@ -1,6 +1,7 @@
package com.gitee.sop.adminbackend.service.doc; package com.gitee.sop.adminbackend.service.doc;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.gitee.httphelper.HttpHelper; import com.gitee.httphelper.HttpHelper;
import com.gitee.sop.adminbackend.common.enums.ConfigKeyEnum; import com.gitee.sop.adminbackend.common.enums.ConfigKeyEnum;
@@ -10,6 +11,7 @@ import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import java.util.Objects; import java.util.Objects;
/** /**
@@ -19,6 +21,16 @@ import java.util.Objects;
public class TornaClient { public class TornaClient {
public <T> T execute(String name, Object param, String token, Class<T> respClass) { public <T> T execute(String name, Object param, String token, Class<T> respClass) {
JSONObject data = request(name, param, token).getJSONObject("data");
return data.toJavaObject(respClass);
}
public <T> List<T> executeList(String name, Object param, String token, Class<T> respClass) {
JSONArray data = request(name, param, token).getJSONArray("data");
return data.toList(respClass);
}
private JSONObject request(String name, Object param, String token) {
try { try {
HttpHelper httpHelper = HttpHelper.get(getTornaApiUrl()) HttpHelper httpHelper = HttpHelper.get(getTornaApiUrl())
.parameter("name", name) .parameter("name", name)
@@ -33,8 +45,7 @@ public class TornaClient {
if (!Objects.equals("0", jsonObject.getString("code"))) { if (!Objects.equals("0", jsonObject.getString("code"))) {
throw new BizException(jsonObject.getString("msg")); throw new BizException(jsonObject.getString("msg"));
} }
JSONObject data = jsonObject.getJSONObject("data"); return jsonObject;
return data.toJavaObject(respClass);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -1,11 +1,7 @@
package com.gitee.sop.adminbackend.service.website.dto; package com.gitee.sop.adminbackend.service.doc.dto;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import com.gitee.fastmybatis.annotation.Pk;
import com.gitee.fastmybatis.annotation.PkStrategy;
import com.gitee.fastmybatis.annotation.Table;
import lombok.Data; import lombok.Data;

View File

@@ -0,0 +1,18 @@
package com.gitee.sop.adminbackend.service.doc.dto;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author 六如
*/
@Data
public class DocInfoPublishUpdateDTO {
@NotNull
private Long id;
private Integer isPublish;
}

View File

@@ -1,4 +1,4 @@
package com.gitee.sop.adminbackend.service.website.dto; package com.gitee.sop.adminbackend.service.doc.dto;
import com.gitee.fastmybatis.core.support.TreeNode; import com.gitee.fastmybatis.core.support.TreeNode;
import lombok.Data; import lombok.Data;
@@ -30,6 +30,11 @@ public class DocInfoTreeDTO implements TreeNode<DocInfoTreeDTO, Long> {
*/ */
private Long docId; private Long docId;
/**
* 文档标题
*/
private String docTitle;
/** /**
* 文档code * 文档code
*/ */
@@ -50,6 +55,11 @@ public class DocInfoTreeDTO implements TreeNode<DocInfoTreeDTO, Long> {
*/ */
private String docName; private String docName;
/**
* 版本号
*/
private String docVersion;
/** /**
* 描述 * 描述
*/ */
@@ -95,7 +105,7 @@ public class DocInfoTreeDTO implements TreeNode<DocInfoTreeDTO, Long> {
@Override @Override
public Long takeId() { public Long takeId() {
return id; return docId;
} }
@Override @Override

View File

@@ -0,0 +1,15 @@
package com.gitee.sop.adminbackend.service.doc.dto;
import lombok.Data;
import java.util.List;
/**
* @author 六如
*/
@Data
public class TornaDocDTO {
private List<TornaDocInfoDTO> docList;
}

View File

@@ -0,0 +1,62 @@
package com.gitee.sop.adminbackend.service.doc.dto;
import lombok.Data;
/**
* @author tanghc
*/
@Data
public class TornaDocInfoDTO {
private Long id;
/**
* 文档名称
*/
private String name;
/**
* 文档概述
*/
private String description;
/**
* 访问URL
*/
private String url;
/**
* 版本号
*/
private String version;
/**
* http方法
*/
private String httpMethod;
/**
* contentType
*/
private String contentType;
/**
* 文档类型,0:http,1:dubbo,2:富文本,3:Markdown
*/
private Byte type;
/**
* 是否是分类0不是1
*/
private Byte isFolder;
/**
* 父节点
*/
private Long parentId;
/**
* 是否显示
*/
private Byte isShow;
}

View File

@@ -100,7 +100,7 @@ public class IsvInfoService implements LambdaService<IsvInfo, IsvInfoMapper> {
IsvInfo rec = CopyUtil.copyBean(isvInfoAddDTO, IsvInfo::new); IsvInfo rec = CopyUtil.copyBean(isvInfoAddDTO, IsvInfo::new);
String appKey = new SimpleDateFormat("yyyyMMdd").format(new Date()) + IdGen.nextId(); String appKey = new SimpleDateFormat("yyyyMMdd").format(new Date()) + IdGen.nextId();
rec.setAppId(appKey); rec.setAppId(appKey);
rec.setStatus(StatusEnum.ENABLE.getStatus()); rec.setStatus(StatusEnum.ENABLE.getValue());
this.save(rec); this.save(rec);
this.sendChangeEvent(rec.getId()); this.sendChangeEvent(rec.getId());
return rec.getId(); return rec.getId();

View File

@@ -122,7 +122,7 @@ public class LoginService {
userInfo.setPassword(GenerateUtil.getUUID()); userInfo.setPassword(GenerateUtil.getUUID());
userInfo.setNickname(loginResult.getNickname()); userInfo.setNickname(loginResult.getNickname());
userInfo.setAvatar(""); userInfo.setAvatar("");
userInfo.setStatus(StatusEnum.ENABLE.getStatus()); userInfo.setStatus(StatusEnum.ENABLE.getValue());
userInfo.setRegType(loginResult.getRegTypeEnum().getValue()); userInfo.setRegType(loginResult.getRegTypeEnum().getValue());
userInfo.setEmail(loginResult.getEmail()); userInfo.setEmail(loginResult.getEmail());
sysAdminUserService.save(userInfo); sysAdminUserService.save(userInfo);

View File

@@ -75,7 +75,7 @@ public class DefaultUserCacheManager implements UserCacheManager, InitializingBe
log.warn("登录用户不存在userId{}", id); log.warn("登录用户不存在userId{}", id);
return null; return null;
} }
if (userInfo.getStatus() == StatusEnum.DISABLED.getStatus()) { if (userInfo.getStatus() == StatusEnum.DISABLED.getValue()) {
log.warn("用户被禁用, userId:{}, username:{}, nickname:{}", userInfo.getId(), userInfo.getUsername(), userInfo.getNickname()); log.warn("用户被禁用, userId:{}, username:{}, nickname:{}", userInfo.getId(), userInfo.getUsername(), userInfo.getNickname());
return null; return null;
} }

View File

@@ -6,8 +6,8 @@ import com.gitee.sop.adminbackend.dao.entity.DocApp;
import com.gitee.sop.adminbackend.dao.entity.DocInfo; import com.gitee.sop.adminbackend.dao.entity.DocInfo;
import com.gitee.sop.adminbackend.dao.mapper.DocAppMapper; import com.gitee.sop.adminbackend.dao.mapper.DocAppMapper;
import com.gitee.sop.adminbackend.dao.mapper.DocInfoMapper; import com.gitee.sop.adminbackend.dao.mapper.DocInfoMapper;
import com.gitee.sop.adminbackend.service.website.dto.DocAppDTO; import com.gitee.sop.adminbackend.service.doc.dto.DocAppDTO;
import com.gitee.sop.adminbackend.service.website.dto.DocInfoTreeDTO; import com.gitee.sop.adminbackend.service.doc.dto.DocInfoTreeDTO;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;

View File

@@ -48,6 +48,7 @@
"url": "https://github.com/xiaoxian521" "url": "https://github.com/xiaoxian521"
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@pureadmin/descriptions": "1.2.1", "@pureadmin/descriptions": "1.2.1",
"@pureadmin/table": "3.2.0", "@pureadmin/table": "3.2.0",
"@pureadmin/utils": "2.4.8", "@pureadmin/utils": "2.4.8",

View File

@@ -8,6 +8,9 @@ importers:
.: .:
dependencies: dependencies:
'@element-plus/icons-vue':
specifier: ^2.3.1
version: 2.3.1(vue@3.4.38(typescript@5.5.4))
'@pureadmin/descriptions': '@pureadmin/descriptions':
specifier: 1.2.1 specifier: 1.2.1
version: 1.2.1(echarts@5.5.1)(element-plus@2.8.0(vue@3.4.38(typescript@5.5.4)))(typescript@5.5.4) version: 1.2.1(echarts@5.5.1)(element-plus@2.8.0(vue@3.4.38(typescript@5.5.4)))(typescript@5.5.4)

View File

@@ -4,7 +4,9 @@ import type { Result } from "@/model";
// 后端请求接口 // 后端请求接口
const apiUrl: any = createUrl({ const apiUrl: any = createUrl({
addApp: "/doc/app/add", addApp: "/doc/app/add",
listApp: "/doc/app/list" listApp: "/doc/app/list",
listDocTree: "/doc/info/tree",
publish: "/doc/info/publish"
}); });
interface DocApp { interface DocApp {
@@ -28,5 +30,19 @@ export const api: any = {
*/ */
addApp(data: object) { addApp(data: object) {
return http.post<Result<any>, any>(apiUrl.addApp, { data }); return http.post<Result<any>, any>(apiUrl.addApp, { data });
},
/**
* 发布
* @param data 表单内容
*/
publish(data: object) {
return http.post<Result<any>, any>(apiUrl.publish, { data });
},
/**
* 查询文档树
* @param data
*/
listDocTree(params: object) {
return http.get<Result<Array<any>>, any>(apiUrl.listDocTree, { params });
} }
}; };

View File

@@ -75,3 +75,9 @@ getPlatformConfig(app).then(async config => {
// .use(useEcharts); // .use(useEcharts);
app.mount("#app"); app.mount("#app");
}); });
// element-plus icon
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}

View File

@@ -1,6 +1,12 @@
import { ref } from "vue"; import { computed, ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus"; import { ElMessage, ElMessageBox } from "element-plus";
import { api } from "@/api/doc"; import { api } from "@/api/doc";
import {
type ButtonsCallBackParams,
type PlusColumn,
useTable
} from "plus-pro-components";
import { YesOrNoEnum } from "@/model/enums";
export const tabsData = ref<Array<any>>([ export const tabsData = ref<Array<any>>([
{ {
@@ -10,9 +16,163 @@ export const tabsData = ref<Array<any>>([
]); ]);
export const activeName = ref(0); export const activeName = ref(0);
const docAppId = ref(0);
// 表格对象
export const { tableData, buttons: actionButtons } = useTable<any[]>();
export const filterText = ref("");
export const tableRows = computed(() => {
let search = filterText.value;
if (!search) {
return tableData.value;
}
search = search.toLowerCase();
return searchRow(search, tableData.value, searchContent, isFolder);
});
const isFolder = row => {
return row.isFolder === 1;
};
const searchContent = (searchText, row) => {
return (
(row.docName && row.docName.toLowerCase().indexOf(searchText) > -1) ||
(row.docTitle && row.docTitle.toLowerCase().indexOf(searchText) > -1)
);
};
// 表格字段定义
export const tableColumns: PlusColumn[] = [
{
label: "文档标题",
prop: "docTitle"
},
{
label: "接口名",
prop: "docName"
},
{
label: "版本号",
prop: "docVersion"
},
{
label: "描述",
prop: "description"
},
{
label: "发布状态",
prop: "isPublish",
valueType: "select",
width: 100,
options: [
{
label: "已发布",
value: YesOrNoEnum.YES,
color: "green"
},
{
label: "未发布",
value: YesOrNoEnum.NO,
color: "red"
}
],
renderHTML: (value, { row }) => {
if (row.isFolder) {
return "";
}
return value
? `<div style="color:green;">已发布</div>`
: `<div style="color:gray;">未发布</div>`;
}
},
{
width: 160,
label: "添加时间",
prop: "addTime"
},
{
width: 160,
label: "修改时间",
prop: "updateTime"
}
];
// 表格按钮定义
actionButtons.value = [
{
text: row => (row.isPublish ? "下线" : "发布"),
confirm: {
options: { draggable: false },
popconfirmProps: { width: 300 },
message: params => {
const row = params.row;
const opt = row.isPublish ? "下线" : "发布";
const isFolder = row.isFolder;
return isFolder === 1
? `确定要${opt}[${row.docTitle}]下所有接口吗?`
: `确定要${opt}[${row.docTitle}]吗?`;
}
},
props: (row: any) => ({
type: row.isPublish === 1 ? "danger" : "success"
}),
onConfirm(params: ButtonsCallBackParams) {
const data = {
id: params.row.id,
isPublish: params.row.isPublish === 1 ? 0 : 1
};
api.publish(data).then(() => {
ElMessage.success("保存成功");
search();
});
}
},
{
text: "下线",
confirm: {
options: { draggable: false },
popconfirmProps: { width: 300 },
message: params => {
const row = params.row;
const opt = "下线";
return `确定要${opt}[${row.docTitle}]下所有接口吗?`;
}
},
props: {
type: "danger"
},
onConfirm(params: ButtonsCallBackParams) {
const data = {
id: params.row.id,
isPublish: 0
};
api.publish(data).then(() => {
ElMessage.success("下线成功");
search();
});
},
show: (row: any) => row.isFolder === 1
}
];
// 点击查询按钮
export const handleSearch = () => {
search();
};
// 查询
const search = async () => {
loadContent(docAppId.value);
};
export const handleClick = data => { export const handleClick = data => {
const id = data.props.name; const id = data.props.name;
activeTab(id);
};
const activeTab = id => {
docAppId.value = id;
loadContent(id); loadContent(id);
}; };
@@ -27,30 +187,78 @@ export const handleAddApp = () => {
const data = { const data = {
tornaToken: value tornaToken: value
}; };
api.addApp(data).then(() => { api.addApp(data).then(resp => {
ElMessage.success("添加成功"); ElMessage.success("添加成功");
loadTabs(true); loadTabs(resp.data);
}); });
}) })
.catch(() => {}); .catch(() => {});
}; };
const loadTabs = showLast => { const loadTabs = docAppId => {
api.listApp().then(resp => { api.listApp().then(resp => {
tabsData.value = resp.data; tabsData.value = resp.data;
const length = tabsData.value.length; const length = tabsData.value.length;
if (length > 0) { if (length > 0) {
const showData = showLast let targetId;
? tabsData.value[length - 1] for (const id of tabsData.value) {
: tabsData.value[0]; if (id === docAppId) {
activeName.value = showData.id; targetId = id;
loadContent(showData.id); break;
}
}
if (!targetId) {
targetId = tabsData.value[0].id;
}
activeName.value = targetId;
activeTab(targetId);
} }
}); });
}; };
const loadContent = id => { const searchRow = (search, rows, searchHandler, folderHandler) => {
console.log(id); if (!folderHandler) {
folderHandler = isFolder;
}
const ret = [];
for (const row of rows) {
// 找到分类
if (folderHandler(row)) {
if (searchHandler(search, row)) {
ret.push(row);
} else {
// 分类名字没找到,需要从子文档中找
const children = row.children || [];
const searchedChildren = searchRow(
search,
children,
searchHandler,
folderHandler
);
// 如果子文档中有
if (searchedChildren && searchedChildren.length > 0) {
const rowCopy = Object.assign({}, row);
rowCopy.children = searchedChildren;
ret.push(rowCopy);
}
}
} else {
// 不是分类且被找到
if (searchHandler(search, row)) {
ret.push(row);
}
}
}
return ret;
}; };
loadTabs(false); const loadContent = id => {
const data = {
docAppId: id
};
api.listDocTree(data).then(resp => {
tableData.value = resp.data;
});
};
loadTabs(0);

View File

@@ -1,14 +1,32 @@
<script setup lang="ts"> <script setup lang="ts">
import { activeName, handleClick, handleAddApp, tabsData } from "./index"; import {
activeName,
handleClick,
handleAddApp,
tabsData,
actionButtons,
tableColumns,
tableRows,
filterText,
handleSearch
} from "./index";
import { Search } from "@element-plus/icons-vue";
</script> </script>
<template> <template>
<el-card shadow="never"> <el-card shadow="never">
<div v-show="tabsData.length === 0">
<el-button type="primary" @click="handleAddApp">添加应用</el-button>
</div>
<el-tabs <el-tabs
v-show="tabsData.length > 0" v-show="tabsData.length > 0"
v-model="activeName" v-model="activeName"
type="card" addable
@tab-click="handleClick" @tab-click="handleClick"
@tab-add="handleAddApp"
> >
<template #add-icon>
<el-icon style="font-size: 20px"><CirclePlusFilled /></el-icon>
</template>
<el-tab-pane <el-tab-pane
v-for="item in tabsData" v-for="item in tabsData"
:key="item.id" :key="item.id"
@@ -16,8 +34,29 @@ import { activeName, handleClick, handleAddApp, tabsData } from "./index";
:name="item.id" :name="item.id"
/> />
</el-tabs> </el-tabs>
</el-card> <PlusTable
<div v-show="tabsData.length === 0" style="margin: 20px"> v-show="tabsData.length > 0"
<el-button type="primary" @click="handleAddApp">添加应用</el-button> :columns="tableColumns"
</div> :table-data="tableRows"
:action-bar="{
buttons: actionButtons,
confirmType: 'popconfirm',
width: 120
}"
adaptive
>
<template #title>
<el-input
v-model="filterText"
type="text"
placeholder="过滤,支持接口标题,接口名称"
style="width: 300px"
>
<template #append>
<el-button :icon="Search" @click="handleSearch" />
</template>
</el-input>
</template>
</PlusTable>
</el-card>
</template> </template>

View File

@@ -0,0 +1,32 @@
import { createUrl, http } from "@/utils/http";
import type { Result } from "@/model";
// 后端请求接口
const apiUrl: any = createUrl({
listApp: "/website/listDocApp",
listDocTree: "/doc/info/tree"
});
interface DocApp {
id: number;
appName: string;
}
/**
* 接口管理
*/
export const api: any = {
/**
* 分页查询
*/
listApp(): Promise<Result<Array<DocApp>>> {
return http.get<Result<Array<DocApp>>, any>(apiUrl.listApp, {});
},
/**
* 查询文档树
* @param data
*/
listDocTree(params: object) {
return http.get<Result<Array<any>>, any>(apiUrl.listDocTree, { params });
}
};

View File

@@ -0,0 +1,16 @@
export interface Result<T> {
success: boolean;
data: T;
msg: "";
code: "";
}
export interface PageResult {
success: boolean;
msg: "";
code: "";
data: {
total: 0;
list: Array<any>;
};
}

View File

@@ -14,6 +14,24 @@ import NProgress from "../progress";
import { getToken, formatToken } from "@/utils/auth"; import { getToken, formatToken } from "@/utils/auth";
import { useUserStoreHook } from "@/store/modules/user"; import { useUserStoreHook } from "@/store/modules/user";
export const baseUrl = (url: string) => {
if (!url) {
throw new Error("url不能为空");
}
if (url.startsWith("/")) {
url = url.substring(1);
}
return `/api/${url}`;
};
export const createUrl = (data: object) => {
const ret = {};
for (const dataKey in data) {
ret[dataKey] = baseUrl(data[dataKey]);
}
return ret;
};
// 相关配置请参考www.axios-js.com/zh-cn/docs/#axios-request-config-1 // 相关配置请参考www.axios-js.com/zh-cn/docs/#axios-request-config-1
const defaultConfig: AxiosRequestConfig = { const defaultConfig: AxiosRequestConfig = {
// 请求超时时间 // 请求超时时间

View File

@@ -1,4 +1,5 @@
import { ref } from "vue"; import { ref } from "vue";
import { api as docApi } from "@/api/doc";
export const defaultActive = ref("overview.md"); export const defaultActive = ref("overview.md");
export const activeIndex = ref("1"); export const activeIndex = ref("1");
@@ -11,6 +12,10 @@ export const api = ref<any>({});
export const requestParamsExample = ref({}); export const requestParamsExample = ref({});
export const responseParamsExample = ref({}); export const responseParamsExample = ref({});
export const docAppId = ref(0);
export const docAppList = ref([]);
export const docTree = ref([]);
/* /*
参数 类型 是否必填 最大长度 描述 示例值 参数 类型 是否必填 最大长度 描述 示例值
app_id String 是 32 支付宝分配给开发者的应用ID 2014072300007148 app_id String 是 32 支付宝分配给开发者的应用ID 2014072300007148
@@ -152,3 +157,32 @@ export function loadMarkdown(path) {
contentShow.value = true; contentShow.value = true;
}); });
} }
export function handleChangeDocApp(id) {
loadDocTree(id);
}
export function handleNodeClick(node) {
console.log(node);
}
function loadDocApp() {
docApi.listApp().then(resp => {
docAppList.value = resp.data;
if (docAppList.value.length > 0) {
loadDocTree(docAppList.value[0].id);
}
});
}
function loadDocTree(id) {
docAppId.value = id;
const params = {
docAppId: id
};
docApi.listDocTree(params).then(resp => {
docTree.value = resp.data;
});
}
loadDocApp();

View File

@@ -12,15 +12,45 @@ import {
responseParamsExample, responseParamsExample,
dataNodeType, dataNodeType,
resultData, resultData,
onMenuClick onMenuClick,
handleChangeDocApp,
docAppId,
docAppList,
docTree,
handleNodeClick
} from "./index"; } from "./index";
import { ApiParamTable } from "@/components/ApiParamTable"; import { ApiParamTable } from "@/components/ApiParamTable";
import MavonEditor from "mavon-editor"; import MavonEditor from "mavon-editor";
const defaultProps = {
children: "children",
label: "docTitle"
};
</script> </script>
<template> <template>
<el-container> <el-container>
<el-aside width="300px">left</el-aside> <el-aside width="300px">
<el-main> <el-select
v-show="docAppId > 0"
v-model="docAppId"
placeholder="Select"
size="large"
@change="handleChangeDocApp"
>
<el-option
v-for="item in docAppList"
:key="item.id"
:label="item.appName"
:value="item.id"
/>
</el-select>
<el-tree
:data="docTree"
:props="defaultProps"
style="margin-top: 20px"
@node-click="handleNodeClick"
/>
</el-aside>
<el-main style="padding-top: 0">
<div v-show="contentShow"> <div v-show="contentShow">
<MavonEditor <MavonEditor
v-model="content" v-model="content"

View File

@@ -24,7 +24,14 @@ export default ({ mode }: ConfigEnv): UserConfigExport => {
port: VITE_PORT, port: VITE_PORT,
host: "0.0.0.0", host: "0.0.0.0",
// 本地跨域代理 https://cn.vitejs.dev/config/server-options.html#server-proxy // 本地跨域代理 https://cn.vitejs.dev/config/server-options.html#server-proxy
proxy: {}, proxy: {
"/api": {
// 这里填写后端地址
target: "http://127.0.0.1:8082",
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, "")
}
},
// 预热文件以提前转换和缓存结果,降低启动期间的初始页面加载时长并防止转换瀑布 // 预热文件以提前转换和缓存结果,降低启动期间的初始页面加载时长并防止转换瀑布
warmup: { warmup: {
clientFiles: ["./index.html", "./src/{views,components}/*"] clientFiles: ["./index.html", "./src/{views,components}/*"]