扫描项目代码结构
----------------------------------------------------------------1原版------------------------------------------------------------------------------------
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
/**
* Controller + 实体类 统计导出模板
*/
@Data
public class ControllerEntityExcel {
@ExcelProperty(value = "接口模块名称", index = 0)
private String moduleName;
@ExcelProperty(value = "Controller接口前缀", index = 1)
private String requestMappingPath;
@ExcelProperty(value = "Controller名称", index = 2)
private String controllerName;
@ExcelProperty(value = "Controller全类名", index = 3)
private String controllerClass;
@ExcelProperty(value = "关联实体类", index = 4)
private String entityName;
@ExcelProperty(value = "对应数据库表", index = 5)
private String tableName;
}
import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.*; public class ControllerEntityStats { // ================== 改成你的项目包名 ================== private static final String BASE_PACKAGE = "xxx"; // ==================================================== private static Map<String, Class<?>> entityClassMap = new HashMap<>(); private static Set<String> entitySimpleNames = new HashSet<>(); public static List<ControllerEntityExcel> scan() { List<ControllerEntityExcel> result = new ArrayList<>(); scanMyBatisPlusEntities(); Set<Class<?>> controllers = scanControllers(); for (Class<?> controller : controllers) { // 1. 模块名称 String moduleName = "无模块"; if (controller.isAnnotationPresent(Tag.class)) { moduleName = controller.getAnnotation(Tag.class).name(); } // 2. 请求路径 String reqPath = "无"; if (controller.isAnnotationPresent(RequestMapping.class)) { String[] paths = controller.getAnnotation(RequestMapping.class).value(); if (paths.length > 0) { reqPath = String.join(",", paths); } } // 3. 智能匹配关联实体(终极增强) Set<String> relatedEntities = intelligentMatchEntity(controller); if (relatedEntities.isEmpty()) { ControllerEntityExcel excel = new ControllerEntityExcel(); excel.setModuleName(moduleName); excel.setRequestMappingPath(reqPath); excel.setControllerName(controller.getSimpleName()); excel.setControllerClass(controller.getName()); excel.setEntityName("无"); excel.setTableName("无"); result.add(excel); } else { for (String entityName : relatedEntities) { ControllerEntityExcel excel = new ControllerEntityExcel(); excel.setModuleName(moduleName); excel.setRequestMappingPath(reqPath); excel.setControllerName(controller.getSimpleName()); excel.setControllerClass(controller.getName()); excel.setEntityName(entityName); excel.setTableName(getTableName(entityName)); result.add(excel); } } } return result; } // ========================= 核心:智能匹配实体(支持Controller名匹配实体名) ========================= private static Set<String> intelligentMatchEntity(Class<?> controller) { Set<String> matchSet = new HashSet<>(); // 方式1:深度解析方法返回值/参数(泛型全支持) deepScanMethodEntities(controller, matchSet); // 方式2:按 Controller 名字智能匹配实体类(UserCollectController → UserCollect) String controllerName = controller.getSimpleName(); String entityCandidate = controllerName.replace("Controller", ""); if (entitySimpleNames.contains(entityCandidate)) { matchSet.add(entityCandidate); } return matchSet; } private static void deepScanMethodEntities(Class<?> controller, Set<String> matchSet) { for (Method method : controller.getDeclaredMethods()) { extractRealType(method.getGenericReturnType(), matchSet); for (Type type : method.getGenericParameterTypes()) { extractRealType(type, matchSet); } } } private static void extractRealType(Type type, Set<String> realEntities) { if (type instanceof ParameterizedType) { Type[] args = ((ParameterizedType) type).getActualTypeArguments(); for (Type t : args) { extractRealType(t, realEntities); } } else if (type instanceof Class) { // JDK8 传统写法,无报错 Class<?> clazz = (Class<?>) type; if (entitySimpleNames.contains(clazz.getSimpleName())) { realEntities.add(clazz.getSimpleName()); } // 兼容 DTO/VO/BO String className = clazz.getSimpleName(); if (className.endsWith("DTO") || className.endsWith("VO") || className.endsWith("BO")) { String entityName = className.replaceAll("(DTO|VO|BO)$", ""); if (entitySimpleNames.contains(entityName)) { realEntities.add(entityName); } } } } // 扫描 MyBatis-Plus @TableName 实体 private static void scanMyBatisPlusEntities() { ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); scanner.addIncludeFilter(new AnnotationTypeFilter(TableName.class)); Set<Class<?>> set = new HashSet<>(); for (BeanDefinition bd : scanner.findCandidateComponents(BASE_PACKAGE)) { try { Class<?> clazz = Class.forName(bd.getBeanClassName()); entityClassMap.put(clazz.getSimpleName(), clazz); entitySimpleNames.add(clazz.getSimpleName()); } catch (Exception ignored) {} } } // 扫描 Controller private static Set<Class<?>> scanControllers() { ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); scanner.addIncludeFilter(new AnnotationTypeFilter(RestController.class)); scanner.addIncludeFilter(new AnnotationTypeFilter(Controller.class)); Set<Class<?>> set = new HashSet<>(); for (BeanDefinition bd : scanner.findCandidateComponents(BASE_PACKAGE)) { try { set.add(Class.forName(bd.getBeanClassName())); } catch (Exception ignored) {} } return set; } // 获取表名 private static String getTableName(String entitySimpleName) { Class<?> clazz = entityClassMap.get(entitySimpleName); if (clazz == null) { return "无表名"; } TableName tableName = clazz.getAnnotation(TableName.class); return tableName != null ? tableName.value() : clazz.getSimpleName(); } }
import com.alibaba.excel.EasyExcel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.util.List;
@RestController
@RequestMapping("/admin/stats")
public class StatsExportController {
/**
* 导出 Controller 与 实体类 统计 Excel
*/
@GetMapping("/export-controller-entity")
public void export(HttpServletResponse response) throws Exception {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("Controller_实体类统计.xlsx", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
// 核心统计
List<ControllerEntityExcel> list = ControllerEntityStats.scan();
// 写出到浏览器
EasyExcel.write(response.getOutputStream(), ControllerEntityExcel.class)
.sheet("Controller统计")
.doWrite(list);
}
}
----------------------------------------------------------------2新版------------------------------------------------------------------------------------
import com.alibaba.excel.annotation.ExcelProperty; import lombok.Data; //汇总版:http://127.0.0.1:7011/admin/stats/export-final-7column //明细版:http://127.0.0.1:7011/admin/stats/export-detail-7column @Data public class Detail7ColumnExcel { @ExcelProperty(value = "接口模块名称", index = 0) private String moduleName; @ExcelProperty(value = "Controller接口前缀", index = 1) private String controllerBasePath; @ExcelProperty(value = "Controller名称", index = 2) private String controllerName; @ExcelProperty(value = "Controller全类名", index = 3) private String controllerFullClassName; @ExcelProperty(value = "Service名称", index = 4) private String serviceName; @ExcelProperty(value = "引入Service名称", index = 5) private String nestedServiceName; @ExcelProperty(value = "关联实体类", index = 6) private String entityName; @ExcelProperty(value = "对应数据库表", index = 7) private String tableName; } import com.alibaba.excel.annotation.ExcelProperty; import lombok.Data; @Data public class Final7ColumnExcel { @ExcelProperty(value = "接口模块名称", index = 0) private String moduleName; @ExcelProperty(value = "Controller接口前缀", index = 1) private String controllerBasePath; @ExcelProperty(value = "Controller名称", index = 2) private String controllerName; @ExcelProperty(value = "Controller全类名", index = 3) private String controllerFullClassName; @ExcelProperty(value = "Service名称", index = 4) private String serviceList; @ExcelProperty(value = "引入Service名称", index = 5) private String nestedServiceList; @ExcelProperty(value = "关联实体类", index = 6) private String entityList; @ExcelProperty(value = "对应数据库表", index = 7) private String tableList; } import com.alibaba.excel.EasyExcel; import com.alibaba.excel.support.ExcelTypeEnum; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; import java.net.URLEncoder; import java.util.List; @RestController @RequestMapping("/admin/stats") public class Final7ColumnExportController { /** * 导出7列完整架构表 */ @GetMapping("/export-final-7column") public void exportFinal7Column(HttpServletResponse response) throws Exception { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("UTF-8"); String fileName = URLEncoder.encode("微服务7列架构统计表.xlsx", "UTF-8"); response.setHeader("Content-Disposition", "attachment;filename=" + fileName); List<Final7ColumnExcel> dataList = Final7ColumnScanner.scan(); EasyExcel.write(response.getOutputStream(), Final7ColumnExcel.class) .sheet("导出服务数据汇总") .doWrite(dataList); } /** * 明细版导出:一行一个 Service + 实体 + 表 完整组合 */ /*@GetMapping("/export-detail-7column") public void exportDetail7Column(HttpServletResponse response) throws Exception { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("UTF-8"); String fileName = URLEncoder.encode("微服务7列架构明细表.xlsx", "UTF-8"); response.setHeader("Content-Disposition", "attachment;filename=" + fileName); List<Detail7ColumnExcel> dataList = Final7ColumnScanner.scanDetail(); EasyExcel.write(response.getOutputStream(), Detail7ColumnExcel.class) .sheet("7列架构明细") .doWrite(dataList); }*/ @GetMapping("/export-detail-7column") public void exportDetail7Column(HttpServletResponse response) { try { // ========= 强制禁用缓存,防止断开连接 ========= response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("utf-8"); response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control", "no-store"); response.addHeader("Access-Control-Expose-Headers", "Content-Disposition"); String fileName = URLEncoder.encode("七列关系导出", "UTF-8"); response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ExcelTypeEnum.XLSX.getValue()); // ========= 获取数据(你现有的方法) ========= //List<Final7ColumnExcel> dataList = Final7ColumnScanner.scanDetail(); List<Detail7ColumnExcel> dataList = Final7ColumnScanner.scanDetail(); // ========= 写入 Excel(不关闭流,交给Tomcat) ========= EasyExcel.write(response.getOutputStream(), Final7ColumnExcel.class) .excelType(ExcelTypeEnum.XLSX) .autoCloseStream(false) // ✅ 关键:禁止自动关闭流 .sheet("导出服务数据明细") .doWrite(dataList); } catch (Exception e) { // 忽略客户端断开异常,防止报错 if (!e.getMessage().contains("Connection reset by peer") && !e.getMessage().contains("已建立的连接")) { e.printStackTrace(); } } } } import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import io.swagger.v3.oas.annotations.tags.Tag; import org.apache.ibatis.annotations.Mapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.stereotype.Controller; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.Resource; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.*; public final class Final7ColumnScanner { private static final String BASE_PACKAGE = "xxx"; private static final Map<String, Class<?>> ENTITY_MAP = new HashMap<>(); private static final Set<String> ENTITY_SIMPLE_SET = new HashSet<>(); public static List<Final7ColumnExcel> scan() { List<Final7ColumnExcel> resultList = new ArrayList<>(); scanAllEntities(); Set<Class<?>> controllerSet = scanAllControllers(); for (Class<?> controllerCls : controllerSet) { String moduleName = getTagModuleName(controllerCls); String basePath = getClassRequestMapping(controllerCls); String controllerName = controllerCls.getSimpleName(); String fullClassName = controllerCls.getName(); Set<String> mainServices = getDirectServiceList(controllerCls); Set<String> allNested = new LinkedHashSet<>(); Set<String> allEntities = new LinkedHashSet<>(); // ====== 遍历主Service,收集 内部嵌套Service/Mapper + 实体 ====== for (String s : mainServices) { Class<?> itf = findServiceInterface(controllerCls, s); if (itf == null) { itf = getClassByName(s); } Set<String> nested = getInsideServiceAndMapper(itf); allNested.addAll(nested); // 解析主Service自身的实体 String mainEnt = getEntityCommon(itf); if (mainEnt == null) { mainEnt = s.replace("Service", "").replace("Impl", ""); if (mainEnt.startsWith("I")) mainEnt = mainEnt.substring(1); } if (ENTITY_SIMPLE_SET.contains(mainEnt)) { allEntities.add(mainEnt); } // ====== 【关键】解析内部嵌套的 Mapper/Service 实体 ====== for (String nestedName : nested) { Class<?> nestedClazz = getClassByName(nestedName); String nestedEnt = getEntityCommon(nestedClazz); if (nestedEnt != null && ENTITY_SIMPLE_SET.contains(nestedEnt)) { allEntities.add(nestedEnt); } } } Final7ColumnExcel row = new Final7ColumnExcel(); row.setModuleName(moduleName); row.setControllerBasePath(basePath); row.setControllerName(controllerName); row.setControllerFullClassName(fullClassName); row.setServiceList(String.join("、", mainServices)); row.setNestedServiceList(String.join("、", allNested)); row.setEntityList(String.join("、", allEntities)); row.setTableList(getTableNames(allEntities)); resultList.add(row); } return resultList; } public static List<Detail7ColumnExcel> scanDetail() { List<Detail7ColumnExcel> resultList = new ArrayList<>(); scanAllEntities(); Set<Class<?>> controllerSet = scanAllControllers(); for (Class<?> controllerCls : controllerSet) { String moduleName = getTagModuleName(controllerCls); String basePath = getClassRequestMapping(controllerCls); String controllerName = controllerCls.getSimpleName(); String fullClassName = controllerCls.getName(); Set<String> mainServices = getDirectServiceList(controllerCls); if (mainServices.isEmpty()) { addEmpty(resultList, moduleName, basePath, controllerName, fullClassName); continue; } for (String mainService : mainServices) { Class<?> mainItf = findServiceInterface(controllerCls, mainService); if (mainItf == null) { mainItf = getClassByName(mainService); } Set<String> insideList = getInsideServiceAndMapper(mainItf); // 【主Service自身】 String mainEntity = getEntityCommon(mainItf); String mainTable = getTableNameByEntity(mainEntity); Detail7ColumnExcel mainRow = new Detail7ColumnExcel(); mainRow.setModuleName(moduleName); mainRow.setControllerBasePath(basePath); mainRow.setControllerName(controllerName); mainRow.setControllerFullClassName(fullClassName); mainRow.setServiceName(mainService); mainRow.setNestedServiceName(mainService); // <-- 显示自身Service名称 mainRow.setEntityName(mainEntity == null ? "" : mainEntity); mainRow.setTableName(mainTable == null ? "" : mainTable); resultList.add(mainRow); if (insideList.isEmpty()) { Detail7ColumnExcel row = new Detail7ColumnExcel(); row.setModuleName(moduleName); row.setControllerBasePath(basePath); row.setControllerName(controllerName); row.setControllerFullClassName(fullClassName); row.setServiceName(mainService); row.setNestedServiceName("无"); row.setEntityName(mainEntity == null ? "" : mainEntity); row.setTableName(mainTable == null ? "" : mainTable); resultList.add(row); } else { // ============================================== // 【核心修复】遍历内部的 Mapper,强制解析它的实体! // ============================================== for (String nested : insideList) { Class<?> nestedClazz = getClassByName(nested); // 【强制解析:不管Service有没有,都用Mapper自己的实体】 String entity = getEntityCommon(nestedClazz); String table = getTableNameByEntity(entity); Detail7ColumnExcel row = new Detail7ColumnExcel(); row.setModuleName(moduleName); row.setControllerBasePath(basePath); row.setControllerName(controllerName); row.setControllerFullClassName(fullClassName); row.setServiceName(mainService); row.setNestedServiceName(nested); row.setEntityName(entity == null ? "" : entity); row.setTableName(table == null ? "" : table); resultList.add(row); } } } } return resultList; } private static Set<String> getDirectServiceList(Class<?> controllerCls) { Set<String> set = new LinkedHashSet<>(); for (Field f : controllerCls.getDeclaredFields()) { if (f.isAnnotationPresent(javax.annotation.Resource.class) || f.isAnnotationPresent(Autowired.class)) { String name = f.getType().getSimpleName(); if (name.endsWith("Service") || name.endsWith("Mapper")) set.add(name); } } return set; } private static Class<?> findServiceInterface(Class<?> controllerCls, String serviceName) { for (Field f : controllerCls.getDeclaredFields()) { if (f.getType().getSimpleName().equals(serviceName)) { return f.getType(); } } return null; } // ===================== 【核心】同时获取 引入的 Service + Mapper ===================== private static Set<String> getInsideServiceAndMapper(Class<?> serviceInterface) { Set<String> result = new LinkedHashSet<>(); if (serviceInterface == null) return result; try { ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(true); scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class)); Set<BeanDefinition> beans = scanner.findCandidateComponents(BASE_PACKAGE); for (BeanDefinition bd : beans) { Class<?> implClass = Class.forName(bd.getBeanClassName()); if (serviceInterface.isAssignableFrom(implClass)) { for (Field f : implClass.getDeclaredFields()) { if (f.isAnnotationPresent(javax.annotation.Resource.class) || f.isAnnotationPresent(Autowired.class)) { String name = f.getType().getSimpleName(); // 只保留 Service 和 Mapper boolean isService = name.endsWith("Service") || name.endsWith("ServiceImpl"); boolean isMapper = name.endsWith("Mapper"); if (isService || isMapper) { result.add(name); } } } } } } catch (Exception e) { e.printStackTrace(); } return result; } private static String getTableNames(Set<String> entities) { List<String> list = new ArrayList<>(); for (String e : entities) { String t = getTableNameByEntity(e); if (t != null) list.add(t); } return list.isEmpty() ? "无" : String.join("、", list); } private static String getTableNameByEntity(String entity) { if (entity == null) return null; Class<?> c = ENTITY_MAP.get(entity); if (c == null) return null; TableName tn = c.getAnnotation(TableName.class); return tn == null ? entity : tn.value(); } private static void addEmpty(List<Detail7ColumnExcel> list, String m, String b, String c, String f) { Detail7ColumnExcel row = new Detail7ColumnExcel(); row.setModuleName(m); row.setControllerBasePath(b); row.setControllerName(c); row.setControllerFullClassName(f); row.setServiceName("无"); row.setNestedServiceName("无"); row.setEntityName(""); row.setTableName(""); list.add(row); } private static Set<Class<?>> scanAllControllers() { Set<Class<?>> set = new HashSet<>(); ClassPathScanningCandidateComponentProvider scan = new ClassPathScanningCandidateComponentProvider(false); scan.addIncludeFilter(new AnnotationTypeFilter(RestController.class)); scan.addIncludeFilter(new AnnotationTypeFilter(Controller.class)); for (BeanDefinition bd : scan.findCandidateComponents(BASE_PACKAGE)) { try { set.add(Class.forName(bd.getBeanClassName())); } catch (Exception e) {} } return set; } /* private static Set<Class<?>> scanAllControllers() { Set<Class<?>> set = new HashSet<>(); try { // 【只加载 UserInfoController,其他全部不扫】 Class<?> targetController = Class.forName("com.cnpc.riped.itms.controller.sysmanage.UserInfoController"); set.add(targetController); } catch (Exception e) {} return set; }*/ private static void scanAllEntities() { ClassPathScanningCandidateComponentProvider scan = new ClassPathScanningCandidateComponentProvider(false); scan.addIncludeFilter(new AnnotationTypeFilter(TableName.class)); for (BeanDefinition bd : scan.findCandidateComponents(BASE_PACKAGE)) { try { Class<?> c = Class.forName(bd.getBeanClassName()); ENTITY_MAP.put(c.getSimpleName(), c); ENTITY_SIMPLE_SET.add(c.getSimpleName()); } catch (Exception e) {} } } private static String getTagModuleName(Class<?> c) { return c.isAnnotationPresent(Tag.class) ? c.getAnnotation(Tag.class).name() : "无模块"; } private static String getClassRequestMapping(Class<?> c) { if (c.isAnnotationPresent(RequestMapping.class)) { String[] v = c.getAnnotation(RequestMapping.class).value(); return v != null && v.length > 0 ? v[0] : "无"; } return "无"; } private static Class<?> getClassByName(String className) { try { List<Class<?>> allClasses = new ArrayList<>(); // 1. 扫描所有 Service(正常) ClassPathScanningCandidateComponentProvider scanService = new ClassPathScanningCandidateComponentProvider(true); scanService.addIncludeFilter(new AnnotationTypeFilter(Service.class)); for (BeanDefinition bd : scanService.findCandidateComponents(BASE_PACKAGE)) { allClasses.add(Class.forName(bd.getBeanClassName())); } // 2. 【多模块终极版】扫描所有 Mapper(跨模块生效) try { ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); String path = "classpath*:com/cnpc/riped/itms/**/*.class"; Resource[] resources = resolver.getResources(path); MetadataReaderFactory factory = new CachingMetadataReaderFactory(); for (Resource resource : resources) { if (!resource.isReadable()) continue; MetadataReader mr = factory.getMetadataReader(resource); String clsName = mr.getClassMetadata().getClassName(); if (clsName.endsWith("Mapper")) { allClasses.add(Class.forName(clsName)); } } } catch (Exception e) { e.printStackTrace(); } // 3. 匹配查找 for (Class<?> c : allClasses) { if (c.getSimpleName().equals(className)) { return c; } for (Class<?> itf : c.getInterfaces()) { if (itf.getSimpleName().equals(className)) { return itf; } } } } catch (Exception e) {} return null; } // 【新增】统一获取实体:Service / Mapper 都支持 private static String getEntityCommon(Class<?> clazz) { if (clazz == null) return null; // 解析 IService try { for (Type type : clazz.getGenericInterfaces()) { if (type instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) type; if (pt.getRawType().getTypeName().contains("IService")) { Type arg = pt.getActualTypeArguments()[0]; if (arg instanceof Class) { return ((Class<?>) arg).getSimpleName(); } } } } } catch (Exception ignored) {} // 解析 ServiceImpl try { Type superType = clazz.getGenericSuperclass(); if (superType instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) superType; if (pt.getActualTypeArguments().length >= 2) { return ((Class<?>) pt.getActualTypeArguments()[1]).getSimpleName(); } } } catch (Exception ignored) {} // ========================================== // 【终极支持】直接解析所有 Mapper 泛型: // MPJBaseMapper<T> BaseMapper<T> 全部支持 // ========================================== try { Type[] genericInterfaces = clazz.getGenericInterfaces(); for (Type type : genericInterfaces) { if (type instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) type; Class<?> rawType = (Class<?>) pt.getRawType(); // 只要是 MyBatis 的 Mapper 接口,直接取第一个泛型 if (BaseMapper.class.isAssignableFrom(rawType) || rawType.getName().contains("MPJBaseMapper") || rawType.getName().endsWith("Mapper")) { Type arg = pt.getActualTypeArguments()[0]; if (arg instanceof Class) { return ((Class<?>) arg).getSimpleName(); } } } } } catch (Exception ignored) {} return null; } }
----------------------------------------------------------------3优化版------------------------------------------------------------------------------------
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
//汇总版:http://127.0.0.1:7011/admin/stats/export-final-7column
//明细版:http://127.0.0.1:7011/admin/stats/export-detail-7column
@Data
public class Detail7ColumnExcel {
@ExcelProperty(value = "接口模块名称", index = 0)
private String moduleName;
@ExcelProperty(value = "Controller接口前缀", index = 1)
private String controllerBasePath;
@ExcelProperty(value = "Controller名称", index = 2)
private String controllerName;
@ExcelProperty(value = "Controller全类名", index = 3)
private String controllerFullClassName;
@ExcelProperty(value = "Service名称", index = 4)
private String serviceName;
@ExcelProperty(value = "引入Service名称", index = 5)
private String nestedServiceName;
@ExcelProperty(value = "关联实体类", index = 6)
private String entityName;
@ExcelProperty(value = "对应数据库表", index = 7)
private String tableName;
}
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
@Data
public class Final7ColumnExcel {
@ExcelProperty(value = "接口模块名称", index = 0)
private String moduleName;
@ExcelProperty(value = "Controller接口前缀", index = 1)
private String controllerBasePath;
@ExcelProperty(value = "Controller名称", index = 2)
private String controllerName;
@ExcelProperty(value = "Controller全类名", index = 3)
private String controllerFullClassName;
@ExcelProperty(value = "Service名称", index = 4)
private String serviceList;
@ExcelProperty(value = "引入Service名称", index = 5)
private String nestedServiceList;
@ExcelProperty(value = "关联实体类", index = 6)
private String entityList;
@ExcelProperty(value = "对应数据库表", index = 7)
private String tableList;
}
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.util.List;
@RestController
@RequestMapping("/admin/stats")
public class Final7ColumnExportController {
/**
* 导出7列完整架构表
*/
@GetMapping("/export-final-7column")
public void exportFinal7Column(HttpServletResponse response) throws Exception {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("UTF-8");
String fileName = URLEncoder.encode("微服务7列架构统计表.xlsx", "UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
List<Final7ColumnExcel> dataList = Final7ColumnScanner.scan();
EasyExcel.write(response.getOutputStream(), Final7ColumnExcel.class)
.sheet("导出服务数据汇总")
.doWrite(dataList);
}
/**
* 明细版导出:一行一个 Service + 实体 + 表 完整组合
*/
/*@GetMapping("/export-detail-7column")
public void exportDetail7Column(HttpServletResponse response) throws Exception {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("UTF-8");
String fileName = URLEncoder.encode("微服务7列架构明细表.xlsx", "UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
List<Detail7ColumnExcel> dataList = Final7ColumnScanner.scanDetail();
EasyExcel.write(response.getOutputStream(), Detail7ColumnExcel.class)
.sheet("7列架构明细")
.doWrite(dataList);
}*/
@GetMapping("/export-detail-7column")
public void exportDetail7Column(HttpServletResponse response) {
try {
// ========= 强制禁用缓存,防止断开连接 =========
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-store");
response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
String fileName = URLEncoder.encode("七列关系导出", "UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ExcelTypeEnum.XLSX.getValue());
// ========= 获取数据(你现有的方法) =========
//List<Final7ColumnExcel> dataList = Final7ColumnScanner.scanDetail();
List<Detail7ColumnExcel> dataList = Final7ColumnScanner.scanDetail();
// ========= 写入 Excel(不关闭流,交给Tomcat) =========
EasyExcel.write(response.getOutputStream(), Final7ColumnExcel.class)
.excelType(ExcelTypeEnum.XLSX)
.autoCloseStream(false) // ✅ 关键:禁止自动关闭流
.sheet("导出服务数据明细")
.doWrite(dataList);
} catch (Exception e) {
// 忽略客户端断开异常,防止报错
if (!e.getMessage().contains("Connection reset by peer") &&
!e.getMessage().contains("已建立的连接")) {
e.printStackTrace();
}
}
}
}
import org.springframework.core.annotation.AnnotationUtils;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
public final class Final7ColumnScanner {
private static final String BASE_PACKAGE = "xxx";
private static final Map<String, Class<?>> ENTITY_MAP = new HashMap<>();
private static final Set<String> ENTITY_SIMPLE_SET = new HashSet<>();
private static final Set<Class<?>> CONTROLLER_SET = new HashSet<>();
private static final Map<String, Class<?>> SERVICE_CLASS_CACHE = new HashMap<>();
private static final Map<String, Class<?>> MAPPER_CLASS_CACHE = new HashMap<>();
private static final Map<String, Class<?>> SIMPLE_NAME_TO_CLASS = new HashMap<>();
static {
// 全局只扫描一次!!!
initAllClass();
}
private static void initAllClass() {
scanAllEntities();
scanAllControllers();
scanAllServices();
scanAllMappers();
buildSimpleNameCache();
}
// ========================= 【全局缓存】 =========================
private static void scanAllServices() {
ClassPathScanningCandidateComponentProvider scan = new ClassPathScanningCandidateComponentProvider(true);
scan.addIncludeFilter(new AnnotationTypeFilter(Service.class));
for (BeanDefinition bd : scan.findCandidateComponents(BASE_PACKAGE)) {
try {
Class<?> cls = Class.forName(bd.getBeanClassName());
SERVICE_CLASS_CACHE.put(cls.getName(), cls);
for (Class<?> itf : cls.getInterfaces()) {
SERVICE_CLASS_CACHE.put(itf.getName(), itf);
}
} catch (Exception ignored) {}
}
}
private static void scanAllMappers() {
try {
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
String path = "classpath*:com/cnpc/riped/itms/**/*.class";
Resource[] resources = resolver.getResources(path);
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
for (Resource resource : resources) {
if (!resource.isReadable()) continue;
MetadataReader mr = factory.getMetadataReader(resource);
String clsName = mr.getClassMetadata().getClassName();
if (clsName.endsWith("Mapper")) {
try {
Class<?> cls = Class.forName(clsName);
MAPPER_CLASS_CACHE.put(clsName, cls);
} catch (Exception ignored) {}
}
}
} catch (Exception ignored) {}
}
private static void buildSimpleNameCache() {
for (Class<?> cls : SERVICE_CLASS_CACHE.values()) {
SIMPLE_NAME_TO_CLASS.put(cls.getSimpleName(), cls);
}
for (Class<?> cls : MAPPER_CLASS_CACHE.values()) {
SIMPLE_NAME_TO_CLASS.put(cls.getSimpleName(), cls);
}
for (Class<?> cls : ENTITY_MAP.values()) {
SIMPLE_NAME_TO_CLASS.put(cls.getSimpleName(), cls);
}
}
// ========================= 工具方法(O(1)) =========================
private static Class<?> getClassByName(String simpleName) {
return SIMPLE_NAME_TO_CLASS.get(simpleName);
}
// ========================= 原逻辑(不变,但速度爆炸) =========================
public static List<Final7ColumnExcel> scan() {
List<Final7ColumnExcel> resultList = new ArrayList<>();
for (Class<?> controllerCls : CONTROLLER_SET) {
String moduleName = getTagModuleName(controllerCls);
String basePath = getClassRequestMapping(controllerCls);
String controllerName = controllerCls.getSimpleName();
String fullClassName = controllerCls.getName();
Set<String> mainServices = getDirectServiceList(controllerCls);
Set<String> allNested = new LinkedHashSet<>();
Set<String> allEntities = new LinkedHashSet<>();
for (String s : mainServices) {
Class<?> itf = getClassByName(s);
if (itf == null) continue;
Set<String> nested = getInsideServiceAndMapper(itf);
allNested.addAll(nested);
String mainEnt = getEntityCommon(itf);
if (mainEnt == null) {
mainEnt = s.replace("Service", "").replace("Impl", "");
if (mainEnt.startsWith("I")) mainEnt = mainEnt.substring(1);
}
if (ENTITY_SIMPLE_SET.contains(mainEnt)) {
allEntities.add(mainEnt);
}
for (String nestedName : nested) {
Class<?> nestedClazz = getClassByName(nestedName);
if (nestedClazz == null) continue;
String nestedEnt = getEntityCommon(nestedClazz);
if (nestedEnt != null && ENTITY_SIMPLE_SET.contains(nestedEnt)) {
allEntities.add(nestedEnt);
}
}
}
Final7ColumnExcel row = new Final7ColumnExcel();
row.setModuleName(moduleName);
row.setControllerBasePath(basePath);
row.setControllerName(controllerName);
row.setControllerFullClassName(fullClassName);
row.setServiceList(String.join("、", mainServices));
row.setNestedServiceList(String.join("、", allNested));
row.setEntityList(String.join("、", allEntities));
row.setTableList(getTableNames(allEntities));
resultList.add(row);
}
return resultList;
}
public static List<Detail7ColumnExcel> scanDetail() {
List<Detail7ColumnExcel> resultList = new ArrayList<>();
for (Class<?> controllerCls : CONTROLLER_SET) {
String moduleName = getTagModuleName(controllerCls);
String basePath = getClassRequestMapping(controllerCls);
String controllerName = controllerCls.getSimpleName();
String fullClassName = controllerCls.getName();
Set<String> mainServices = getDirectServiceList(controllerCls);
if (mainServices.isEmpty()) {
addEmpty(resultList, moduleName, basePath, controllerName, fullClassName);
continue;
}
for (String mainService : mainServices) {
Class<?> mainItf = getClassByName(mainService);
if (mainItf == null) continue;
Set<String> insideList = getInsideServiceAndMapper(mainItf);
String mainEntity = getEntityCommon(mainItf);
String mainTable = getTableNameByEntity(mainEntity);
Detail7ColumnExcel mainRow = new Detail7ColumnExcel();
mainRow.setModuleName(moduleName);
mainRow.setControllerBasePath(basePath);
mainRow.setControllerName(controllerName);
mainRow.setControllerFullClassName(fullClassName);
mainRow.setServiceName(mainService);
mainRow.setNestedServiceName(mainService);
mainRow.setEntityName(mainEntity == null ? "" : mainEntity);
mainRow.setTableName(mainTable == null ? "" : mainTable);
resultList.add(mainRow);
if (insideList.isEmpty()) {
Detail7ColumnExcel row = new Detail7ColumnExcel();
row.setModuleName(moduleName);
row.setControllerBasePath(basePath);
row.setControllerName(controllerName);
row.setControllerFullClassName(fullClassName);
row.setServiceName(mainService);
row.setNestedServiceName("无");
row.setEntityName(mainEntity == null ? "" : mainEntity);
row.setTableName(mainTable == null ? "" : mainTable);
resultList.add(row);
} else {
for (String nested : insideList) {
Class<?> nestedClazz = getClassByName(nested);
if (nestedClazz == null) continue;
String entity = getEntityCommon(nestedClazz);
String table = getTableNameByEntity(entity);
Detail7ColumnExcel row = new Detail7ColumnExcel();
row.setModuleName(moduleName);
row.setControllerBasePath(basePath);
row.setControllerName(controllerName);
row.setControllerFullClassName(fullClassName);
row.setServiceName(mainService);
row.setNestedServiceName(nested);
row.setEntityName(entity == null ? "" : entity);
row.setTableName(table == null ? "" : table);
resultList.add(row);
}
}
}
}
return resultList;
}
// ========================= 以下方法基本不变 =========================
private static Set<String> getDirectServiceList(Class<?> controllerCls) {
Set<String> set = new LinkedHashSet<>();
for (Field f : controllerCls.getDeclaredFields()) {
if (f.isAnnotationPresent(javax.annotation.Resource.class) || f.isAnnotationPresent(Autowired.class)) {
String name = f.getType().getSimpleName();
if (name.endsWith("Service") || name.endsWith("Mapper")) {
set.add(name);
}
}
}
return set;
}
/*private static Set<String> getInsideServiceAndMapper(Class<?> serviceInterface) {
Set<String> result = new LinkedHashSet<>();
if (serviceInterface == null) return result;
try {
for (Class<?> implClass : SERVICE_CLASS_CACHE.values()) {
if (serviceInterface.isAssignableFrom(implClass)) {
for (Field f : implClass.getDeclaredFields()) {
if (f.isAnnotationPresent(javax.annotation.Resource.class) || f.isAnnotationPresent(Autowired.class)) {
String name = f.getType().getSimpleName();
boolean isService = name.endsWith("Service") || name.endsWith("ServiceImpl");
boolean isMapper = name.endsWith("Mapper");
if (isService || isMapper) {
result.add(name);
}
}
}
break;
}
}
} catch (Exception ignored) {}
return result;
}*/
private static Set<String> getInsideServiceAndMapper(Class<?> serviceInterface) {
Set<String> result = new LinkedHashSet<>();
if (serviceInterface == null) return result;
try {
// 拿到接口简单名称:AgileRequirementSubmissionService
String interfaceName = serviceInterface.getSimpleName();
for (Class<?> implClass : SERVICE_CLASS_CACHE.values()) {
String implClassName = implClass.getSimpleName();
// ==========================================
// 核心兼容逻辑:只要是 接口名 + Impl 就认定是实现类
// 无视包名、无视是否 implements、无视拼写错误
// ==========================================
if (implClassName.equals(interfaceName + "Impl")) {
// 开始扫描字段
for (Field f : implClass.getDeclaredFields()) {
// 判断是否注入注解
if (f.isAnnotationPresent(javax.annotation.Resource.class)
|| f.isAnnotationPresent(org.springframework.beans.factory.annotation.Autowired.class)) {
String name = f.getType().getSimpleName();
boolean isService = name.endsWith("Service") || name.endsWith("ServiceImpl");
boolean isMapper = name.endsWith("Mapper");
if (isService || isMapper) {
result.add(name);
}
}
}
break;
}
}
} catch (Exception ignored) {
// 不抛异常
}
return result;
}
private static String getTableNames(Set<String> entities) {
List<String> list = new ArrayList<>();
for (String e : entities) {
String t = getTableNameByEntity(e);
if (t != null) list.add(t);
}
return list.isEmpty() ? "无" : String.join("、", list);
}
private static String getTableNameByEntity(String entity) {
if (entity == null) return null;
Class<?> c = ENTITY_MAP.get(entity);
if (c == null) return null;
TableName tn = AnnotationUtils.findAnnotation(c, TableName.class);
return tn == null ? entity : tn.value();
}
private static void addEmpty(List<Detail7ColumnExcel> list, String m, String b, String c, String f) {
Detail7ColumnExcel row = new Detail7ColumnExcel();
row.setModuleName(m);
row.setControllerBasePath(b);
row.setControllerName(c);
row.setControllerFullClassName(f);
row.setServiceName("无");
row.setNestedServiceName("无");
row.setEntityName("");
row.setTableName("");
list.add(row);
}
private static void scanAllControllers() {
ClassPathScanningCandidateComponentProvider scan = new ClassPathScanningCandidateComponentProvider(false);
scan.addIncludeFilter(new AnnotationTypeFilter(RestController.class));
scan.addIncludeFilter(new AnnotationTypeFilter(Controller.class));
for (BeanDefinition bd : scan.findCandidateComponents(BASE_PACKAGE)) {
try {
CONTROLLER_SET.add(Class.forName(bd.getBeanClassName()));
//CONTROLLER_SET.add(Class.forName("com.cnpc.riped.itms.controller.requirementsubmission.AgileRequirementSubmissionController"));
//CONTROLLER_SET.add(Class.forName("com.cnpc.riped.itms.controller.maintenanceproject.TMaintenanceprojectBasicinformationController"));
} catch (Exception ignored) {}
}
}
private static void scanAllEntities() {
ClassPathScanningCandidateComponentProvider scan = new ClassPathScanningCandidateComponentProvider(false);
scan.addIncludeFilter(new AnnotationTypeFilter(TableName.class));
for (BeanDefinition bd : scan.findCandidateComponents(BASE_PACKAGE)) {
try {
Class<?> c = Class.forName(bd.getBeanClassName());
ENTITY_MAP.put(c.getSimpleName(), c);
ENTITY_SIMPLE_SET.add(c.getSimpleName());
} catch (Exception ignored) {}
}
}
private static String getTagModuleName(Class<?> c) {
Tag tag = AnnotationUtils.findAnnotation(c, Tag.class);
return tag != null ? tag.name() : "无模块";
}
private static String getClassRequestMapping(Class<?> c) {
RequestMapping rm = AnnotationUtils.findAnnotation(c, RequestMapping.class);
if (rm == null) return "无";
String[] v = rm.value();
return (v != null && v.length > 0) ? v[0] : "无";
}
private static String getEntityCommon(Class<?> clazz) {
if (clazz == null) return null;
try {
for (Type type : clazz.getGenericInterfaces()) {
if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
if (pt.getRawType().getTypeName().contains("IService")) {
return ((Class<?>) pt.getActualTypeArguments()[0]).getSimpleName();
}
}
}
} catch (Exception ignored) {}
try {
Type superType = clazz.getGenericSuperclass();
if (superType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) superType;
if (pt.getActualTypeArguments().length >= 2) {
return ((Class<?>) pt.getActualTypeArguments()[1]).getSimpleName();
}
}
} catch (Exception ignored) {}
try {
for (Type type : clazz.getGenericInterfaces()) {
if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
Class<?> rawType = (Class<?>) pt.getRawType();
if (BaseMapper.class.isAssignableFrom(rawType) || rawType.getName().endsWith("Mapper")) {
return ((Class<?>) pt.getActualTypeArguments()[0]).getSimpleName();
}
}
}
} catch (Exception ignored) {}
return null;
}
}

浙公网安备 33010602011771号