Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@ build/
/.idea/
/.idea/
/.idea/
.specstory/
.cursorindexingignore
67 changes: 60 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,67 @@
# static-chain-analysis-web

1.创建数据表,参考Analysis.sql
## 快速开始

2.修改application配置,参考application-dev.yml
1. 创建数据表(**建议使用包含链路表的最新版 DDL**):
- `static-chain-analysis-admin/src/main/resources/mybatis/ddl/Analysis.sql`

3.打包,运行static-chain-analysis-admin-1.0-SNAPSHOT.jar
2. 修改 application 配置,参考 `static-chain-analysis-admin/src/main/resources/application-dev.yml`

4.网页默认运行在8089端口: http://localhost:8089
3. 打包并运行 `static-chain-analysis-admin-1.0-SNAPSHOT.jar`

注意:
4. 网页默认运行在 8089 端口:`http://localhost:8089`

* 所有分析的代码会clone并保存在 tmp/ 目录下
* 关于凭证:如果项目的git url是http格式,则会使用 [username-password] 凭证拉取,如果是ssh链接(比如 git@github.com:xxxxxx)则必须使用公钥+私钥拉取代码,注意是公钥和私钥都要填
## 调用链路(ChainLink)说明

- **数据表**
- `analysis_chain_link`:一条“改动接口 → 受影响接口”的链路元信息(按 `task_id` 归档)
- `analysis_chain_node`:链路上的节点(含 `node_type` 与 `sequence_num`)
- `analysis_chain_edge`:节点之间的有向边(调用关系)

- **后端接口**
- `POST /report/chainLinks?taskId=<任务ID>`:返回某次分析任务的链路列表(包含 link/nodes/edges)

- **前端页面**
- 路由:`/#/chainLink`
- 支持 query 参数自动查询:`/#/chainLink?taskId=<任务ID>`

## 前端打包部署

前端代码在 `website/static-chain-analysis-web/` 目录下。

### 手动部署

```bash
# 1. 进入前端目录
cd website/static-chain-analysis-web

# 2. 安装依赖(首次需要)
npm install

# 3. 打包
npm run build

# 4. 复制打包文件到 admin 模块
cd ../..
cp -r website/static-chain-analysis-web/dist/* static-chain-analysis-admin/src/main/resources/static/
```

### 使用自动化脚本

```bash
# 在项目根目录执行
./deploy-frontend.sh
```

脚本会自动完成:

- 检查并安装依赖
- 打包前端项目
- 清理旧的静态文件
- 复制新的静态文件到 admin 模块

## 注意事项

- 所有分析的代码会 clone 并保存在 `tmp/` 目录下
- 关于凭证:如果项目的 git url 是 http 格式,则会使用 [username-password] 凭证拉取;如果是 ssh 链接(比如 `git@github.com:xxxxxx`)则必须使用公钥+私钥拉取代码(注意公钥和私钥都要填)
- 修改前端代码后,需要重新打包并部署到 admin 模块的静态资源目录才能生效
41 changes: 41 additions & 0 deletions deploy-frontend.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash

# 前端打包并部署到 admin 模块静态资源目录的脚本

echo "开始打包前端项目..."
cd website/static-chain-analysis-web

# 安装依赖(如果需要)
if [ ! -d "node_modules" ]; then
echo "检测到缺少 node_modules,开始安装依赖..."
npm install
fi

# 执行打包
echo "执行构建..."
npm run build

if [ $? -ne 0 ]; then
echo "构建失败!"
exit 1
fi

echo "构建成功!"

# 返回项目根目录
cd ../..

# 清理旧的静态文件
echo "清理旧的静态文件..."
rm -rf static-chain-analysis-admin/src/main/resources/static/assets
rm -f static-chain-analysis-admin/src/main/resources/static/index.html

# 复制新文件
echo "复制新的静态文件..."
cp -r website/static-chain-analysis-web/dist/* static-chain-analysis-admin/src/main/resources/static/

echo "部署完成!"
echo "新的静态文件已复制到: static-chain-analysis-admin/src/main/resources/static/"



6 changes: 5 additions & 1 deletion static-chain-analysis-admin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.hsf.core</groupId>
<groupId>com.hsf.static.analysis</groupId>
<artifactId>static-chain-analysis-core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
Expand Down Expand Up @@ -74,6 +74,10 @@
<artifactId>static-chain-analysis-core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.analysis.admin.Code.Response;
import com.analysis.admin.Pojo.Entities.AnalysisSimpleReport;
import com.analysis.admin.Pojo.Entities.TaskInfo;
import com.analysis.admin.Pojo.Responses.ChainLinkResponse;
import com.analysis.admin.Service.ReportService;
import com.analysis.admin.Service.TaskService;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -37,4 +38,9 @@ public Response<List<AnalysisSimpleReport>> getReports(@RequestParam Integer tas
public Response<List<TaskInfo>> getDiffTaskInfos(@RequestParam Integer nodeId){
return new Response<>(taskService.getDiffTasks(nodeId));
}

@PostMapping("chainLinks")
public Response<List<ChainLinkResponse>> getChainLinks(@RequestParam Integer taskId){
return new Response<>(reportService.getChainLinks(taskId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.analysis.admin.Mapper;

import com.analysis.admin.Pojo.Entities.AnalysisChainLink;
import com.analysis.admin.Pojo.Entities.AnalysisChainNodeEntity;
import com.analysis.admin.Pojo.Entities.AnalysisChainEdge;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface AnalysisChainLinkMapper {
/**
* 插入链路记录
*/
Integer insertChainLink(@Param("link") AnalysisChainLink link);

/**
* 批量插入链路节点
*/
Integer insertChainNodes(@Param("nodes") List<AnalysisChainNodeEntity> nodes);

/**
* 批量插入链路边
*/
Integer insertChainEdges(@Param("edges") List<AnalysisChainEdge> edges);

/**
* 根据任务ID查询链路
*/
List<AnalysisChainLink> getChainLinksByTaskId(@Param("taskId") Integer taskId);

/**
* 根据链路ID、类名和方法名查询节点ID
*/
Integer getNodeIdByLinkAndMethod(@Param("linkId") Integer linkId,
@Param("className") String className,
@Param("methodName") String methodName);

/**
* 单个插入节点(用于获取生成的ID)
*/
Integer insertChainNode(@Param("node") AnalysisChainNodeEntity node);

/**
* 根据链路ID查询节点列表
*/
List<AnalysisChainNodeEntity> getChainNodesByLinkId(@Param("linkId") Integer linkId);

/**
* 根据链路ID查询边列表
*/
List<AnalysisChainEdge> getChainEdgesByLinkId(@Param("linkId") Integer linkId);
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.analysis.admin.Pojo.Entities;

import lombok.Data;

import java.util.Date;

@Data
public class AnalysisChainEdge {
private Integer id;
private Integer linkId;
private Integer fromNodeId;
private Integer toNodeId;
private Date createTime;
}



Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.analysis.admin.Pojo.Entities;

import lombok.Data;

import java.util.Date;

@Data
public class AnalysisChainLink {
private Integer id;
private Integer taskId;
private String changedMethod; // 改动的接口(类名#方法名)
private String affectedApi; // 受影响的接口(类名#方法名)
private String apiType; // 接口类型:http/dubbo/kafka/grpc
private String apiUri; // 接口URI(HTTP的URL或Dubbo的方法名等)
private Date createTime;
private Date updateTime;
}



Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.analysis.admin.Pojo.Entities;

import lombok.Data;

import java.util.Date;

@Data
public class AnalysisChainNodeEntity {
private Integer id;
private Integer linkId;
private String className;
private String methodName;
private Integer nodeType; // 0=中间节点,1=改动的接口,2=受影响的接口
private Integer sequenceNum; // 在链路中的顺序
private Date createTime;
}



Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.analysis.admin.Pojo.Responses;

import com.analysis.admin.Pojo.Entities.AnalysisChainLink;
import lombok.Data;

import java.util.List;

@Data
public class ChainLinkResponse {
private AnalysisChainLink link;
private List<ChainNodeResponse> nodes;
private List<ChainEdgeResponse> edges;

@Data
public static class ChainNodeResponse {
private Integer id;
private String className;
private String methodName;
private Integer nodeType; // 0=中间节点,1=改动的接口,2=受影响的接口
private Integer sequenceNum;
}

@Data
public static class ChainEdgeResponse {
private Integer fromNodeId;
private Integer toNodeId;
}
}



Original file line number Diff line number Diff line change
@@ -1,19 +1,69 @@
package com.analysis.admin.Service;

import com.analysis.admin.Mapper.AnalysisChainLinkMapper;
import com.analysis.admin.Mapper.AnalysisSimpleReportMapper;
import com.analysis.admin.Pojo.Entities.AnalysisChainEdge;
import com.analysis.admin.Pojo.Entities.AnalysisChainLink;
import com.analysis.admin.Pojo.Entities.AnalysisChainNodeEntity;
import com.analysis.admin.Pojo.Entities.AnalysisSimpleReport;
import com.analysis.admin.Pojo.Responses.ChainLinkResponse;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class ReportService {

@Resource
private AnalysisSimpleReportMapper analysisSimpleReportMapper;

@Resource
private AnalysisChainLinkMapper analysisChainLinkMapper;

public List<AnalysisSimpleReport> getReports(Integer taskId){
return analysisSimpleReportMapper.getReportByTaskId(taskId);
}

/**
* 获取调用链路详情(包含节点和边)
*/
public List<ChainLinkResponse> getChainLinks(Integer taskId) {
List<AnalysisChainLink> links = analysisChainLinkMapper.getChainLinksByTaskId(taskId);
List<ChainLinkResponse> result = new ArrayList<>();

for (AnalysisChainLink link : links) {
ChainLinkResponse response = new ChainLinkResponse();
response.setLink(link);

// 查询节点
List<AnalysisChainNodeEntity> nodes = analysisChainLinkMapper.getChainNodesByLinkId(link.getId());
List<ChainLinkResponse.ChainNodeResponse> nodeResponses = nodes.stream().map(node -> {
ChainLinkResponse.ChainNodeResponse nodeResponse = new ChainLinkResponse.ChainNodeResponse();
nodeResponse.setId(node.getId());
nodeResponse.setClassName(node.getClassName());
nodeResponse.setMethodName(node.getMethodName());
nodeResponse.setNodeType(node.getNodeType());
nodeResponse.setSequenceNum(node.getSequenceNum());
return nodeResponse;
}).collect(Collectors.toList());
response.setNodes(nodeResponses);

// 查询边
List<AnalysisChainEdge> edges = analysisChainLinkMapper.getChainEdgesByLinkId(link.getId());
List<ChainLinkResponse.ChainEdgeResponse> edgeResponses = edges.stream().map(edge -> {
ChainLinkResponse.ChainEdgeResponse edgeResponse = new ChainLinkResponse.ChainEdgeResponse();
edgeResponse.setFromNodeId(edge.getFromNodeId());
edgeResponse.setToNodeId(edge.getToNodeId());
return edgeResponse;
}).collect(Collectors.toList());
response.setEdges(edgeResponses);

result.add(response);
}

return result;
}
}
Loading