浏览代码

feat(project): 主项目详情资源列表增加「涉及的子系统」字段

- ResourceVO 新增 subProjectName(资源所属子项目/子系统名称, 多个以、分隔)
- ProjectServiceImpl.getDetail: 主项目聚合资源时, 按 project_resource 关联
  回填每条资源所属子项目名(仅主项目填充, 子项目自身详情留空)
- 前端 ProjectDetail.vue 关联资源表格新增「涉及的子系统」列(subProjectName)
- 离线重编译重打包为 resource-platform-v6.jar 并重启(8080); 端到端验证通过
  (主项目'快递1.0系统'聚合资源显示 涉及的子系统='堡昕德小程序')
- build_v6.py(全量编译+重打包)/ extract_deps.py(抽依赖) 入库为无 Maven 环境重部署工具
C 2 月之前
父节点
当前提交
ffab4b36d3

+ 69 - 0
backend/build_v6.py

@@ -0,0 +1,69 @@
+import zipfile, os, subprocess, glob, sys
+
+BASE = os.path.dirname(os.path.abspath(__file__))
+SRC = os.path.join(BASE, "src", "main", "java")
+OUT = os.path.join(BASE, "build_tmp_classes")
+LIB = os.path.join(BASE, "lib")
+JAVAC = r"D:\Program Files\Java\jdk-17\bin\javac.exe"
+SRC_JAR = os.path.join(BASE, "target", "resource-platform-v5.jar")
+DST_JAR = os.path.join(BASE, "target", "resource-platform-v6.jar")
+
+# 1) 收集依赖 classpath
+dep_jars = sorted(glob.glob(os.path.join(LIB, "*.jar")))
+cp = ";".join(dep_jars)
+print("classpath entries:", len(dep_jars))
+
+# 2) 编译全部源码
+sources = []
+for root, _, files in os.walk(SRC):
+    for f in files:
+        if f.endswith(".java"):
+            sources.append(os.path.join(root, f))
+print("source files:", len(sources))
+
+os.makedirs(OUT, exist_ok=True)
+cmd = [JAVAC, "-parameters", "-encoding", "UTF-8", "-cp", cp, "-d", OUT] + sources
+r = subprocess.run(cmd, capture_output=True)
+print("javac returncode:", r.returncode)
+if r.returncode != 0:
+    err = (r.stderr or b"").decode("utf-8", "replace")
+    print("=== STDERR (first 40 lines) ===")
+    print("\n".join(err.splitlines()[:40]))
+    sys.exit(1)
+print("compiled classes:", len(glob.glob(os.path.join(OUT, "**", "*.class"), recursive=True)))
+
+# 3) 重打包: 以 v5 为基底, 替换 BOOT-INF/classes 下新编译的 class
+with zipfile.ZipFile(SRC_JAR) as zin:
+    infos = zin.infolist()
+    names = set(zin.namelist())
+
+    def class_local(entry_name):
+        # 仅替换真正的 .class 文件; 目录条目(以/结尾)直接沿用原 jar
+        if entry_name.startswith("BOOT-INF/classes/") and entry_name.endswith(".class"):
+            rel = entry_name[len("BOOT-INF/classes/"):]
+            local = os.path.join(OUT, rel)
+            if os.path.isfile(local):
+                return local
+        return None
+
+    # MANIFEST 必须第一个写入
+    manifest_name = "META-INF/MANIFEST.MF"
+    with zipfile.ZipFile(DST_JAR, "w", compression=zipfile.ZIP_DEFLATED) as zout:
+        mi = next(i for i in infos if i.filename == manifest_name)
+        zout.writestr(mi, zin.read(manifest_name))
+        for i in infos:
+            if i.filename == manifest_name:
+                continue
+            local = class_local(i.filename)
+            if local:
+                with open(local, "rb") as lf:
+                    data = lf.read()
+                zi = zipfile.ZipInfo(i.filename)
+                zi.external_attr = i.external_attr
+                zi.date_time = i.date_time
+                zout.writestr(zi, data)
+            else:
+                zout.writestr(i, zin.read(i.filename))
+
+print("built:", DST_JAR, "exists:", os.path.exists(DST_JAR))
+print("manifest first:", zipfile.ZipFile(DST_JAR).namelist()[0] == manifest_name)

+ 14 - 0
backend/extract_deps.py

@@ -0,0 +1,14 @@
+import zipfile, os
+
+jar = "target/resource-platform-v5.jar"
+out_dir = "lib"
+os.makedirs(out_dir, exist_ok=True)
+z = zipfile.ZipFile(jar)
+n = 0
+for name in z.namelist():
+    if name.startswith("BOOT-INF/lib/") and name.endswith(".jar"):
+        out = os.path.join(out_dir, os.path.basename(name))
+        with open(out, "wb") as f:
+            f.write(z.read(name))
+        n += 1
+print("extracted", n, "dependency jars into", out_dir)

+ 3 - 0
backend/src/main/java/com/example/resourceplatform/dto/ResourceVO.java

@@ -53,6 +53,9 @@ public class ResourceVO {
     /** 关联的项目简要信息 */
     private List<SimpleProject> projects;
 
+    /** 资源所属子项目(子系统)名称, 多个以、分隔; 仅主项目聚合详情填充, 子项目自身详情留空 */
+    private String subProjectName;
+
     @Data
     public static class SimpleProject {
         private Long id;

+ 23 - 3
backend/src/main/java/com/example/resourceplatform/service/impl/ProjectServiceImpl.java

@@ -177,10 +177,30 @@ public class ProjectServiceImpl implements ProjectService {
             }).toList());
         }
 
-        List<Long> resIds = projectResourceMapper.selectList(
-                        new LambdaQueryWrapper<ProjectResource>().in(ProjectResource::getProjectId, leafIds))
-                .stream().map(ProjectResource::getResourceId).toList();
+        List<ProjectResource> rels = projectResourceMapper.selectList(
+                new LambdaQueryWrapper<ProjectResource>().in(ProjectResource::getProjectId, leafIds));
+        List<Long> resIds = rels.stream().map(ProjectResource::getResourceId).distinct().toList();
+
+        // 资源 -> 所属子项目(子系统)名称, 多个以、分隔; 仅主项目(有 children)才填充
+        Map<Long, String> projNameMap = children.stream()
+                .collect(Collectors.toMap(Project::getId, Project::getName, (a, b) -> a));
+        Map<Long, String> resSubSystem = new HashMap<>();
+        if (!projNameMap.isEmpty()) {
+            rels.stream()
+                    .collect(Collectors.groupingBy(ProjectResource::getResourceId,
+                            Collectors.mapping(ProjectResource::getProjectId, Collectors.toSet())))
+                    .forEach((rid, pids) -> {
+                        String names = pids.stream()
+                                .map(projNameMap::get)
+                                .filter(Objects::nonNull)
+                                .distinct()
+                                .collect(Collectors.joining("、"));
+                        resSubSystem.put(rid, names);
+                    });
+        }
+
         List<ResourceVO> resources = resourceService.listByIds(resIds);
+        resources.forEach(r -> r.setSubProjectName(resSubSystem.getOrDefault(r.getId(), "")));
         vo.setResources(resources);
         vo.setResourceCount(resources.size());
         return vo;

+ 1 - 0
frontend/src/views/project/ProjectDetail.vue

@@ -41,6 +41,7 @@
         <el-table-column prop="name" label="资源名称" min-width="140" />
         <el-table-column prop="typeName" label="类型" width="110" />
         <el-table-column prop="providerName" label="服务商" width="100" />
+        <el-table-column prop="subProjectName" label="涉及的子系统" min-width="120" show-overflow-tooltip />
         <el-table-column prop="purchaseDurationText" label="购买时长" width="90" />
         <el-table-column prop="ownerName" label="负责人" width="90" />
         <el-table-column label="到期日期" width="110" class-name="mono-num">