feat(extapi): 新增外部接口清单注解扫描与展示导出

新增 @ExternalApi/@ExternalCall 方法级注解,启动时扫描生成内存注册表。
提供 /sys/extApi/list 展示、exportXls(AutoPoi) 与 exportMarkdown 导出;
给 TaskApiController.countTaskforporter 打 @ExternalApi 示范并补菜单 SQL。
This commit is contained in:
wsm
2026-08-11 15:14:38 +08:00
parent 64182840f3
commit 522f269f59
10 changed files with 543 additions and 0 deletions
@@ -4,6 +4,8 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.ExternalApi;
import org.jeecg.common.aspect.annotation.ExternalApiAuthType;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.openapi.service.ITaskApi;
import org.springframework.web.bind.annotation.GetMapping;
@@ -32,6 +34,7 @@ public class TaskApiController {
* <p>调用方(门户)通过 username 参数传入 sys_user.username(登录账号)。
*/
@Operation(summary = "门户查询待办数", description = "门户查询待办数")
@ExternalApi(name = "门户查询待办数", caller = "门户系统", auth = ExternalApiAuthType.NONE, dataScope = "仅返回待办数量,不涉及业务明细")
@GetMapping("/countTaskforporter")
public Result<Long> countTaskforporter(HttpServletRequest request) {
String username = request.getParameter("username");
@@ -0,0 +1,115 @@
package org.jeecg.modules.system.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.config.extapi.ExternalApiRegistry;
import org.jeecg.config.extapi.model.ExternalApiInfo;
import org.jeecg.config.extapi.model.ExternalApiType;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
/**
* 外部接口清单(对外提供 / 对外调用)展示与导出
* <p>数据来自 {@link ExternalApiRegistry} 内存注册表(启动时由注解扫描生成)。
*/
@Tag(name = "外部接口清单")
@RestController
@RequestMapping("/sys/extApi")
@Slf4j
public class ExternalApiController {
private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Operation(summary = "外部接口清单列表")
@GetMapping("/list")
public Result<List<ExternalApiInfo>> list() {
return Result.OK(ExternalApiRegistry.get());
}
@Operation(summary = "导出外部接口清单Excel")
@GetMapping("/exportXls")
public ModelAndView exportXls(HttpServletRequest request) {
// AutoPoi 导出时会 remove 已处理行(多sheet分页),必须传可变集合
List<ExternalApiInfo> data = new ArrayList<>(ExternalApiRegistry.get());
ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
String title = "外部接口清单";
mv.addObject(NormalExcelConstants.FILE_NAME, title);
mv.addObject(NormalExcelConstants.CLASS, ExternalApiInfo.class);
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
ExportParams exportParams = new ExportParams(title + "报表", "导出人:" + (sysUser == null ? "" : sysUser.getRealname()), title);
mv.addObject(NormalExcelConstants.PARAMS, exportParams);
mv.addObject(NormalExcelConstants.DATA_LIST, data);
return mv;
}
@Operation(summary = "导出外部接口清单Markdown")
@GetMapping("/exportMarkdown")
public void exportMarkdown(HttpServletResponse response) throws IOException {
String content = buildMarkdown();
String fileName = URLEncoder.encode("外部接口清单.md", StandardCharsets.UTF_8.name()).replaceAll("\\+", "%20");
response.setContentType("text/markdown;charset=UTF-8");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName);
OutputStream os = response.getOutputStream();
os.write(content.getBytes(StandardCharsets.UTF_8));
os.flush();
os.close();
}
private String buildMarkdown() {
List<ExternalApiInfo> all = ExternalApiRegistry.get();
StringBuilder sb = new StringBuilder();
sb.append("# 应用系统外部接口清单\n\n");
sb.append("> 生成时间:" + LocalDateTime.now().format(FMT) + " | 认证机制:无=裸调用 / 签名=X-Sign / JWT=登录态\n\n");
sb.append("## 一、对外提供的接口(外部系统调用本系统)\n\n");
sb.append(tableHeader());
all.stream().filter(i -> ExternalApiType.EXPOSE.name().equals(i.getType())).forEach(i -> sb.append(tableRow(i)));
sb.append("\n## 二、对外调用的接口(本系统调用外部系统)\n\n");
sb.append(tableHeader());
all.stream().filter(i -> ExternalApiType.CALL.name().equals(i.getType())).forEach(i -> sb.append(tableRow(i)));
return sb.toString();
}
private String tableHeader() {
return "| 接口名称 | 地址 | HTTP方法 | 调用方系统 | 被调系统 | 数据流向 | 数据范围 | 认证机制 | 代码位置 | 备注 |\n"
+ "|---|---|---|---|---|---|---|---|---|---|\n";
}
private String tableRow(ExternalApiInfo i) {
return String.join(" | ",
cell(i.getName()),
cell(i.getUrl()),
cell(i.getHttpMethod()),
cell(i.getCallerSystem()),
cell(i.getTargetSystem()),
cell(i.getDataFlow()),
cell(i.getDataScope()),
cell(i.getAuth()),
cell(i.getSource()),
cell(i.getRemark())) + "\n";
}
private String cell(String v) {
return StringUtils.hasText(v) ? v.replace("|", "\\|").replace("\n", " ") : " ";
}
}