├── docs
├── home.png
├── icon.png
├── history.png
├── welcome.png
├── collections.png
├── functional.png
├── networklog.png
├── performance.png
├── script-post.png
├── script-pre.png
├── workspaces.png
├── environments.png
├── functional_1.png
├── functional_2.png
├── history-events.png
├── history-timeline.png
├── script-snippets.png
├── collections-import.png
├── functional-results.png
├── performance-report.png
├── performance-trend.png
├── workspaces-gitcommit.png
├── performance-resultTree.png
├── performance-threadgroup-fixed.png
├── performance-threadgroup-spike.png
├── performance-threadgroup-rampup.png
├── performance-threadgroup-stairs.png
└── FQA.MD
├── assets
├── linux
│ └── EasyPostman.png
├── mac
│ └── EasyPostman.icns
├── win
│ └── EasyPostman.ico
└── readme.md
├── .gitignore
├── src
├── main
│ ├── resources
│ │ └── icons
│ │ │ ├── icon.png
│ │ │ ├── nocheck.svg
│ │ │ ├── git-commit.svg
│ │ │ ├── git-pull.svg
│ │ │ ├── arrow-down.svg
│ │ │ ├── arrow-up.svg
│ │ │ ├── git-push.svg
│ │ │ ├── security.svg
│ │ │ ├── hash.svg
│ │ │ ├── ws-receive.svg
│ │ │ ├── ws-send.svg
│ │ │ ├── code.svg
│ │ │ ├── words.svg
│ │ │ ├── ws-connect.svg
│ │ │ ├── wordsHovered.svg
│ │ │ ├── wordsSelected.svg
│ │ │ ├── detail.svg
│ │ │ ├── ws-close.svg
│ │ │ ├── start.svg
│ │ │ ├── ws-info.svg
│ │ │ ├── check.svg
│ │ │ ├── connect.svg
│ │ │ ├── binary.svg
│ │ │ ├── connect-white.svg
│ │ │ ├── warning.svg
│ │ │ ├── plus.svg
│ │ │ ├── performance.svg
│ │ │ ├── cancel.svg
│ │ │ ├── cancel-white.svg
│ │ │ ├── edit.svg
│ │ │ ├── fail.svg
│ │ │ ├── pass.svg
│ │ │ ├── save.svg
│ │ │ ├── info.svg
│ │ │ ├── computer.svg
│ │ │ ├── switch.svg
│ │ │ ├── file.svg
│ │ │ ├── console.svg
│ │ │ ├── download.svg
│ │ │ ├── search.svg
│ │ │ ├── send.svg
│ │ │ ├── group.svg
│ │ │ ├── http.svg
│ │ │ ├── send-white.svg
│ │ │ ├── workspace.svg
│ │ │ ├── git.svg
│ │ │ ├── sse.svg
│ │ │ ├── refresh.svg
│ │ │ ├── stop.svg
│ │ │ ├── cookie.svg
│ │ │ ├── functional.svg
│ │ │ ├── time.svg
│ │ │ ├── eye-open.svg
│ │ │ ├── close.svg
│ │ │ ├── idea-http.svg
│ │ │ ├── har.svg
│ │ │ ├── close-white.svg
│ │ │ ├── copy.svg
│ │ │ ├── local.svg
│ │ │ ├── eye-close.svg
│ │ │ ├── websocket.svg
│ │ │ ├── expand.svg
│ │ │ ├── history.svg
│ │ │ ├── clear.svg
│ │ │ ├── matchCase.svg
│ │ │ ├── sidebar-toggle.svg
│ │ │ ├── matchCaseHovered.svg
│ │ │ ├── matchCaseSelected.svg
│ │ │ ├── duplicate.svg
│ │ │ ├── paste.svg
│ │ │ ├── users.svg
│ │ │ ├── user-group.svg
│ │ │ ├── collapse.svg
│ │ │ ├── root_group.svg
│ │ │ ├── load.svg
│ │ │ ├── curl.svg
│ │ │ ├── help.svg
│ │ │ ├── import.svg
│ │ │ ├── easy.svg
│ │ │ ├── export.svg
│ │ │ ├── collections.svg
│ │ │ ├── request.svg
│ │ │ ├── csv.svg
│ │ │ ├── tools.svg
│ │ │ ├── format.svg
│ │ │ ├── postman.svg
│ │ │ └── environments.svg
│ └── java
│ │ └── com
│ │ └── laker
│ │ └── postman
│ │ ├── model
│ │ ├── WorkspaceType.java
│ │ ├── GitAuthType.java
│ │ ├── GitRepoSource.java
│ │ ├── Category.java
│ │ ├── CookieInfo.java
│ │ ├── RemoteStatus.java
│ │ ├── RedirectInfo.java
│ │ ├── VariableSegment.java
│ │ ├── AssertionResult.java
│ │ ├── script
│ │ │ ├── TestResult.java
│ │ │ ├── Cookie.java
│ │ │ └── TemporaryVariablesApi.java
│ │ ├── HttpParam.java
│ │ ├── HttpHeader.java
│ │ ├── CurlRequest.java
│ │ ├── EnvironmentVariable.java
│ │ ├── Snippet.java
│ │ ├── ConflictBlock.java
│ │ ├── HttpFormUrlencoded.java
│ │ ├── TabInfo.java
│ │ ├── EnvironmentItem.java
│ │ ├── RequestHistoryItem.java
│ │ ├── RequestItemProtocolEnum.java
│ │ ├── GitOperation.java
│ │ ├── Workspace.java
│ │ ├── UpdateCheckFrequency.java
│ │ ├── MessageType.java
│ │ ├── RequestResult.java
│ │ ├── HttpResponse.java
│ │ ├── IterationResult.java
│ │ ├── BatchExecutionHistory.java
│ │ ├── RequestGroup.java
│ │ ├── NotificationPosition.java
│ │ ├── HttpRequestItem.java
│ │ ├── PreparedRequest.java
│ │ ├── AuthType.java
│ │ ├── UpdateInfo.java
│ │ ├── HttpFormData.java
│ │ ├── HttpEventInfo.java
│ │ └── ClientCertificate.java
│ │ ├── panel
│ │ ├── performance
│ │ │ ├── timer
│ │ │ │ ├── TimerData.java
│ │ │ │ └── TimerPropertyPanel.java
│ │ │ ├── model
│ │ │ │ ├── NodeType.java
│ │ │ │ ├── RequestResult.java
│ │ │ │ ├── ResultNodeInfo.java
│ │ │ │ └── JMeterTreeNode.java
│ │ │ ├── assertion
│ │ │ │ └── AssertionData.java
│ │ │ ├── component
│ │ │ │ ├── ResultTreeCellRenderer.java
│ │ │ │ └── JMeterTreeCellRenderer.java
│ │ │ └── threadgroup
│ │ │ │ └── ThreadGroupData.java
│ │ ├── MainPanel.java
│ │ ├── sidebar
│ │ │ └── cookie
│ │ │ │ └── CookieManagerDialog.java
│ │ ├── functional
│ │ │ └── table
│ │ │ │ └── RunnerRowData.java
│ │ ├── collections
│ │ │ ├── RequestCollectionsPanel.java
│ │ │ ├── right
│ │ │ │ └── request
│ │ │ │ │ └── sub
│ │ │ │ │ └── SyntaxType.java
│ │ │ └── left
│ │ │ │ └── action
│ │ │ │ └── TreeNodeCloner.java
│ │ └── workspace
│ │ │ └── components
│ │ │ └── ProgressPanel.java
│ │ ├── common
│ │ ├── exception
│ │ │ ├── GetInstanceException.java
│ │ │ ├── WorkspaceCreateException.java
│ │ │ └── CancelException.java
│ │ ├── constants
│ │ │ └── Icons.java
│ │ ├── window
│ │ │ └── SplashWindowInitializationException.java
│ │ ├── component
│ │ │ ├── button
│ │ │ │ ├── CSVButton.java
│ │ │ │ ├── StopButton.java
│ │ │ │ ├── ClearButton.java
│ │ │ │ ├── StartButton.java
│ │ │ │ └── RefreshButton.java
│ │ │ ├── tab
│ │ │ │ └── PlusTabComponent.java
│ │ │ ├── combobox
│ │ │ │ ├── EnvironmentItemRenderer.java
│ │ │ │ └── WorkspaceItemRenderer.java
│ │ │ ├── table
│ │ │ │ ├── EasyPostmanTextFieldCellEditor.java
│ │ │ │ ├── EasyPostmanTextFieldCellRenderer.java
│ │ │ │ └── TableUIConstants.java
│ │ │ └── list
│ │ │ │ └── EnvironmentListCellRenderer.java
│ │ ├── SingletonBasePanel.java
│ │ └── SingletonBaseMenuBar.java
│ │ ├── ioc
│ │ ├── PreDestroy.java
│ │ ├── PostConstruct.java
│ │ ├── Component.java
│ │ ├── ObjectFactory.java
│ │ ├── BeanException.java
│ │ ├── DisposableBean.java
│ │ ├── Autowired.java
│ │ ├── InitializingBean.java
│ │ ├── NoSuchBeanException.java
│ │ ├── Scope.java
│ │ ├── BeanCreationException.java
│ │ ├── BeanDefinition.java
│ │ └── BeanFactory.java
│ │ ├── service
│ │ ├── http
│ │ │ ├── sse
│ │ │ │ ├── SseResEventListener.java
│ │ │ │ └── SseUiCallback.java
│ │ │ ├── HttpSingleRequestExecutor.java
│ │ │ ├── ssl
│ │ │ │ ├── SSLException.java
│ │ │ │ ├── CertificateErrorParser.java
│ │ │ │ └── SSLValidationResult.java
│ │ │ └── okhttp
│ │ │ │ └── LogWebSocketListener.java
│ │ ├── js
│ │ │ ├── ScriptExecutionException.java
│ │ │ ├── ScriptFragment.java
│ │ │ └── ScriptExecutionResult.java
│ │ ├── update
│ │ │ ├── source
│ │ │ │ ├── GiteeUpdateSource.java
│ │ │ │ ├── GitHubUpdateSource.java
│ │ │ │ └── UpdateSource.java
│ │ │ ├── version
│ │ │ │ └── VersionComparator.java
│ │ │ ├── changelog
│ │ │ │ └── ChangelogService.java
│ │ │ └── asset
│ │ │ │ └── AssetFinder.java
│ │ ├── UpdateService.java
│ │ ├── ExitService.java
│ │ └── collections
│ │ │ └── RequestsTabsService.java
│ │ └── util
│ │ ├── FontsUtil.java
│ │ ├── TimeDisplayUtil.java
│ │ ├── FileSizeDisplayUtil.java
│ │ ├── JComponentUtils.java
│ │ ├── JsonPathUtil.java
│ │ └── XmlUtil.java
└── test
│ └── java
│ └── com
│ └── laker
│ └── postman
│ ├── ioc
│ └── test
│ │ ├── ServiceA.java
│ │ └── ServiceB.java
│ └── EditMenuDemo.java
└── .github
├── ISSUE_TEMPLATE
└── config.yml
├── workflows
├── sync-labels.yml
└── codeql-analysis.yml
├── PULL_REQUEST_TEMPLATE.md
└── dependabot.yml
/docs/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/home.png
--------------------------------------------------------------------------------
/docs/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/icon.png
--------------------------------------------------------------------------------
/docs/history.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/history.png
--------------------------------------------------------------------------------
/docs/welcome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/welcome.png
--------------------------------------------------------------------------------
/docs/collections.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/collections.png
--------------------------------------------------------------------------------
/docs/functional.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/functional.png
--------------------------------------------------------------------------------
/docs/networklog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/networklog.png
--------------------------------------------------------------------------------
/docs/performance.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/performance.png
--------------------------------------------------------------------------------
/docs/script-post.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/script-post.png
--------------------------------------------------------------------------------
/docs/script-pre.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/script-pre.png
--------------------------------------------------------------------------------
/docs/workspaces.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/workspaces.png
--------------------------------------------------------------------------------
/docs/environments.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/environments.png
--------------------------------------------------------------------------------
/docs/functional_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/functional_1.png
--------------------------------------------------------------------------------
/docs/functional_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/functional_2.png
--------------------------------------------------------------------------------
/docs/history-events.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/history-events.png
--------------------------------------------------------------------------------
/docs/history-timeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/history-timeline.png
--------------------------------------------------------------------------------
/docs/script-snippets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/script-snippets.png
--------------------------------------------------------------------------------
/assets/linux/EasyPostman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/assets/linux/EasyPostman.png
--------------------------------------------------------------------------------
/assets/mac/EasyPostman.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/assets/mac/EasyPostman.icns
--------------------------------------------------------------------------------
/assets/win/EasyPostman.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/assets/win/EasyPostman.ico
--------------------------------------------------------------------------------
/docs/collections-import.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/collections-import.png
--------------------------------------------------------------------------------
/docs/functional-results.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/functional-results.png
--------------------------------------------------------------------------------
/docs/performance-report.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/performance-report.png
--------------------------------------------------------------------------------
/docs/performance-trend.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/performance-trend.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /dependency-reduced-pom.xml
2 | /target/
3 | /dist/
4 | /.idea/
5 | /.vscode/
6 | .DS_Store
7 | **/.DS_Store
--------------------------------------------------------------------------------
/docs/workspaces-gitcommit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/workspaces-gitcommit.png
--------------------------------------------------------------------------------
/docs/performance-resultTree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/performance-resultTree.png
--------------------------------------------------------------------------------
/src/main/resources/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/src/main/resources/icons/icon.png
--------------------------------------------------------------------------------
/docs/performance-threadgroup-fixed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/performance-threadgroup-fixed.png
--------------------------------------------------------------------------------
/docs/performance-threadgroup-spike.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/performance-threadgroup-spike.png
--------------------------------------------------------------------------------
/docs/performance-threadgroup-rampup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/performance-threadgroup-rampup.png
--------------------------------------------------------------------------------
/docs/performance-threadgroup-stairs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakernote/EasyPostman/HEAD/docs/performance-threadgroup-stairs.png
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/WorkspaceType.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | // 工作空间类型枚举
4 | public enum WorkspaceType {
5 | LOCAL, GIT
6 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/GitAuthType.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | // 认证类型枚举
4 | public enum GitAuthType {
5 | NONE, PASSWORD, TOKEN, SSH_KEY
6 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/panel/performance/timer/TimerData.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.panel.performance.timer;
2 |
3 | public class TimerData {
4 | public int delayMs = 1000;
5 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/GitRepoSource.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | // 枚举定义
4 | public enum GitRepoSource {
5 | INITIALIZED, // 本地初始化后推送到远程
6 | CLONED // 从远程仓库克隆
7 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/panel/performance/model/NodeType.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.panel.performance.model;
2 |
3 | // 节点类型定义
4 | public enum NodeType {ROOT, THREAD_GROUP, REQUEST, ASSERTION, TIMER}
--------------------------------------------------------------------------------
/src/main/resources/icons/nocheck.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/git-commit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/git-pull.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/arrow-down.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/arrow-up.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/git-push.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/security.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/hash.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/ws-receive.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/ws-send.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/Category.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | public enum Category {
4 | PRE_SCRIPT, ASSERT, EXTRACT, LOCAL_VAR, ENV_VAR, ENCODE,
5 | ENCRYPT, STRING, ARRAY, JSON, DATE, REGEX, LOG, CONTROL, TOKEN, COOKIES, OTHER
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/resources/icons/code.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/words.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/ws-connect.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/wordsHovered.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/wordsSelected.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/detail.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/ws-close.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/start.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/common/exception/GetInstanceException.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.common.exception;
2 |
3 | public class GetInstanceException extends RuntimeException {
4 | public GetInstanceException(String message, Throwable cause) {
5 | super(message, cause);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/resources/icons/ws-info.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/common/exception/WorkspaceCreateException.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.common.exception;
2 |
3 | public class WorkspaceCreateException extends RuntimeException {
4 | public WorkspaceCreateException(String message, Throwable cause) {
5 | super(message, cause);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/resources/icons/check.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/connect.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/binary.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/connect-white.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/ioc/PreDestroy.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.ioc;
2 |
3 | import java.lang.annotation.*;
4 |
5 | /**
6 | * 标记一个方法在Bean销毁前执行
7 | * 该方法会在容器关闭时自动调用
8 | */
9 | @Target(ElementType.METHOD)
10 | @Retention(RetentionPolicy.RUNTIME)
11 | @Documented
12 | public @interface PreDestroy {
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/panel/performance/assertion/AssertionData.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.panel.performance.assertion;
2 |
3 | public class AssertionData {
4 | public String type = "Response Code";
5 | public String content = "";
6 | public String operator = "=";
7 | public String value = "200";
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/resources/icons/warning.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/ioc/PostConstruct.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.ioc;
2 |
3 |
4 | import java.lang.annotation.*;
5 |
6 | /* 标记一个方法在Bean初始化后执行
7 | * 该方法会在依赖注入完成后自动调用
8 | */
9 | @Target(ElementType.METHOD)
10 | @Retention(RetentionPolicy.RUNTIME)
11 | @Documented
12 | public @interface PostConstruct {
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/CookieInfo.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | public class CookieInfo {
4 | public String name;
5 | public String value;
6 | public String domain;
7 | public String path;
8 | public long expires;
9 | public boolean secure;
10 | public boolean httpOnly;
11 | }
--------------------------------------------------------------------------------
/src/main/resources/icons/plus.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/performance.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/common/exception/CancelException.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.common.exception;
2 |
3 | public class CancelException extends RuntimeException {
4 |
5 | public CancelException() {
6 | super();
7 | }
8 |
9 | public CancelException(String message) {
10 | super(message);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/RemoteStatus.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | /**
4 | * 远程状态信息封装
5 | */
6 | public class RemoteStatus {
7 | public boolean hasRemote = false;
8 | public boolean hasUpstream = false;
9 | public String remoteUrl;
10 | public String currentBranch;
11 | public String upstreamBranch;
12 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/service/http/sse/SseResEventListener.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.service.http.sse;
2 |
3 | import com.laker.postman.model.HttpResponse;
4 | import okhttp3.internal.sse.ServerSentEventReader;
5 |
6 | public interface SseResEventListener extends ServerSentEventReader.Callback {
7 | void onOpen(HttpResponse response);
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/resources/icons/cancel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/common/constants/Icons.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.common.constants;
2 |
3 | import javax.swing.*;
4 | import java.util.Objects;
5 |
6 | public class Icons {
7 | private Icons() {
8 | }
9 |
10 | public static final ImageIcon LOGO = new ImageIcon(Objects.requireNonNull(Icons.class.getResource("/icons/icon.png")));
11 | }
--------------------------------------------------------------------------------
/assets/readme.md:
--------------------------------------------------------------------------------
1 | - https://www.svgviewer.dev/
2 | - https://icon-icons.com/
3 | - https://anyconv.com/png-to-icns-converter/
4 | - https://www.flaticon.com/
5 | - https://www.svgrepo.com/
6 | - https://freesvgicons.com/search?q=http
7 | - https://flowbite.com/icons/
8 | - https://gitee.com/api/v5/swagger
9 | - https://www.remove.bg/zh 去除图片背景
10 | - https://www.photopea.com 调整大小
--------------------------------------------------------------------------------
/src/main/resources/icons/cancel-white.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/edit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/fail.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/pass.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/save.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/RedirectInfo.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 |
6 | // 重定向信息结构体
7 | public class RedirectInfo {
8 | public String url;
9 | public int statusCode;
10 | public Map> headers;
11 | public String location;
12 | public String responseBody;
13 | }
--------------------------------------------------------------------------------
/src/main/resources/icons/info.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/computer.svg:
--------------------------------------------------------------------------------
1 |
7 |
8 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/ioc/Component.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.ioc;
2 |
3 | import java.lang.annotation.*;
4 |
5 | /**
6 | * 标记一个类为组件,会被IOC容器管理
7 | */
8 | @Target(ElementType.TYPE)
9 | @Retention(RetentionPolicy.RUNTIME)
10 | @Documented
11 | public @interface Component {
12 | /**
13 | * Bean名称,默认为类名首字母小写
14 | */
15 | String value() default "";
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/ioc/ObjectFactory.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.ioc;
2 |
3 | /**
4 | * 对象工厂接口
5 | * 用于延迟创建对象,支持循环依赖解决
6 | */
7 | @FunctionalInterface
8 | public interface ObjectFactory {
9 |
10 | /**
11 | * 获取对象实例
12 | *
13 | * @return 对象实例
14 | * @throws Exception 如果获取失败
15 | */
16 | T getObject() throws Exception;
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/src/main/resources/icons/switch.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/VariableSegment.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | public class VariableSegment {
4 | public final int start;
5 | public final int end;
6 | public final String name;
7 |
8 | public VariableSegment(int start, int end, String name) {
9 | this.start = start;
10 | this.end = end;
11 | this.name = name;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/main/resources/icons/file.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/ioc/BeanException.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.ioc;
2 |
3 | /**
4 | * IOC容器相关异常的基类
5 | */
6 | public class BeanException extends RuntimeException {
7 |
8 | public BeanException(String message) {
9 | super(message);
10 | }
11 |
12 | public BeanException(String message, Throwable cause) {
13 | super(message, cause);
14 | }
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/src/main/resources/icons/console.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/AssertionResult.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import lombok.Getter;
4 |
5 | @Getter
6 | public enum AssertionResult {
7 | PASS("✅"),
8 |
9 | FAIL("❌"),
10 |
11 | NO_TESTS("💨");
12 |
13 | private final String displayValue;
14 |
15 | AssertionResult(String displayValue) {
16 | this.displayValue = displayValue;
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/ioc/DisposableBean.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.ioc;
2 |
3 | /**
4 | * Bean销毁接口
5 | * 实现此接口的Bean会在容器关闭时自动调用destroy方法
6 | * 这是@PreDestroy注解的接口方式替代
7 | */
8 | public interface DisposableBean {
9 |
10 | /**
11 | * Bean销毁前调用
12 | * 可以用于释放资源、关闭连接等清理工作
13 | *
14 | * @throws Exception 如果销毁过程中出现错误
15 | */
16 | void destroy() throws Exception;
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/src/main/resources/icons/download.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/send.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/group.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/http.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/send-white.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/workspace.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/ioc/Autowired.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.ioc;
2 |
3 | import java.lang.annotation.*;
4 |
5 | /**
6 | * 标记需要自动注入的字段
7 | */
8 | @Target({ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD})
9 | @Retention(RetentionPolicy.RUNTIME)
10 | @Documented
11 | public @interface Autowired {
12 | /**
13 | * 是否必须,如果为true且找不到对应的bean则抛出异常
14 | */
15 | boolean required() default true;
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/ioc/InitializingBean.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.ioc;
2 |
3 | /**
4 | * Bean初始化接口
5 | * 实现此接口的Bean会在属性注入完成后自动调用afterPropertiesSet方法
6 | * 这是@PostConstruct注解的接口方式替代
7 | */
8 | public interface InitializingBean {
9 |
10 | /**
11 | * Bean的所有属性被设置后调用
12 | * 可以用于初始化资源、验证配置等
13 | *
14 | * @throws Exception 如果初始化失败
15 | */
16 | void afterPropertiesSet() throws Exception;
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/resources/icons/git.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/common/window/SplashWindowInitializationException.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.common.window;
2 |
3 | import java.io.Serial;
4 |
5 | public class SplashWindowInitializationException extends RuntimeException {
6 | @Serial
7 | private static final long serialVersionUID = 1L;
8 |
9 | public SplashWindowInitializationException(String message, Throwable cause) {
10 | super(message, cause);
11 | }
12 | }
--------------------------------------------------------------------------------
/src/main/resources/icons/sse.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/service/http/sse/SseUiCallback.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.service.http.sse;
2 |
3 | import com.laker.postman.model.HttpResponse;
4 |
5 | // SSE UI回调接口
6 | public interface SseUiCallback {
7 | void onOpen(HttpResponse resp, String headersText);
8 |
9 | void onEvent(String id, String type, String data);
10 |
11 | void onClosed(HttpResponse resp);
12 |
13 | void onFailure(String errorMsg, HttpResponse resp);
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/resources/icons/refresh.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/common/component/button/CSVButton.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.common.component.button;
2 |
3 | import com.formdev.flatlaf.extras.FlatSVGIcon;
4 |
5 | import javax.swing.*;
6 |
7 | /**
8 | * 通用开始按钮,带图标和统一样式。
9 | */
10 | public class CSVButton extends JButton {
11 | public CSVButton() {
12 | super(" CSV ");
13 | setIcon(new FlatSVGIcon("icons/csv.svg", 20, 20));
14 | setFocusable(false); // 去掉按钮的焦点边框
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/resources/icons/stop.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/script/TestResult.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model.script;
2 |
3 | public class TestResult {
4 | /**
5 | * 测试唯一标识
6 | */
7 | public String id;
8 | public final String name;
9 | public final boolean passed;
10 | public final String message;
11 |
12 | public TestResult(String name, boolean passed, String message) {
13 | this.name = name;
14 | this.passed = passed;
15 | this.message = message;
16 | }
17 | }
--------------------------------------------------------------------------------
/src/main/resources/icons/cookie.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/panel/performance/model/RequestResult.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.panel.performance.model;
2 |
3 | // 用于统计每个请求的结束时间和成功状态
4 | public class RequestResult {
5 | public long endTime;
6 | public boolean success;
7 | public long responseTime; // 添加实际响应时间字段
8 |
9 | public RequestResult(long endTime, boolean success, long responseTime) {
10 | this.endTime = endTime;
11 | this.success = success;
12 | this.responseTime = responseTime;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/main/resources/icons/functional.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/ioc/NoSuchBeanException.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.ioc;
2 |
3 | /**
4 | * Bean未找到异常
5 | */
6 | public class NoSuchBeanException extends BeanException {
7 |
8 | public NoSuchBeanException(String beanName) {
9 | super("No bean named '" + beanName + "' found in the container");
10 | }
11 |
12 | public NoSuchBeanException(Class> requiredType) {
13 | super("No bean of type '" + requiredType.getName() + "' found in the container");
14 | }
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/src/main/resources/icons/time.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/icons/eye-open.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/close.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/idea-http.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/har.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/main/resources/icons/close-white.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/HttpParam.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Data;
5 | import lombok.NoArgsConstructor;
6 |
7 | import java.io.Serializable;
8 |
9 | /**
10 | * HTTP Parameter model with enabled state
11 | */
12 | @Data
13 | @NoArgsConstructor
14 | @AllArgsConstructor
15 | public class HttpParam implements Serializable {
16 | private static final long serialVersionUID = 1L;
17 |
18 | private boolean enabled = true;
19 | private String key = "";
20 | private String value = "";
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/HttpHeader.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Data;
5 | import lombok.NoArgsConstructor;
6 |
7 | import java.io.Serializable;
8 |
9 | /**
10 | * HTTP Header model with enabled state
11 | */
12 | @Data
13 | @NoArgsConstructor
14 | @AllArgsConstructor
15 | public class HttpHeader implements Serializable {
16 | private static final long serialVersionUID = 1L;
17 |
18 | private boolean enabled = true;
19 | private String key = "";
20 | private String value = "";
21 |
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/src/main/resources/icons/copy.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: 💬 社区讨论区 | Community Discussions
4 | url: https://github.com/lakernote/easy-postman/discussions
5 | about: 使用问答、功能建议讨论、分享经验 | Q&A, feature discussions, and sharing experiences
6 | - name: 📖 中文文档 | Chinese Documentation
7 | url: https://github.com/lakernote/easy-postman/blob/master/README_zh.md
8 | about: 查看中文文档 | View Chinese documentation
9 | - name: 📖 English Documentation
10 | url: https://github.com/lakernote/easy-postman/blob/master/README.md
11 | about: View English documentation
12 |
13 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/common/component/button/StopButton.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.common.component.button;
2 |
3 | import com.formdev.flatlaf.extras.FlatSVGIcon;
4 | import com.laker.postman.util.I18nUtil;
5 | import com.laker.postman.util.MessageKeys;
6 |
7 | import javax.swing.*;
8 |
9 | /**
10 | * 通用停止按钮,带图标和统一样式。
11 | */
12 | public class StopButton extends JButton {
13 | public StopButton() {
14 | super(I18nUtil.getMessage(MessageKeys.BUTTON_STOP));
15 | setIcon(new FlatSVGIcon("icons/stop.svg", 20, 20));
16 | setFocusable(false); // 去掉按钮的焦点边框
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/resources/icons/local.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/common/component/button/ClearButton.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.common.component.button;
2 |
3 | import com.formdev.flatlaf.extras.FlatSVGIcon;
4 | import com.laker.postman.util.I18nUtil;
5 | import com.laker.postman.util.MessageKeys;
6 |
7 | import javax.swing.*;
8 |
9 | /**
10 | * 通用开始按钮,带图标和统一样式。
11 | */
12 | public class ClearButton extends JButton {
13 | public ClearButton() {
14 | super(I18nUtil.getMessage(MessageKeys.BUTTON_CLEAR));
15 | setIcon(new FlatSVGIcon("icons/clear.svg", 20, 20));
16 | setFocusable(false); // 去掉按钮的焦点边框
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/common/component/button/StartButton.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.common.component.button;
2 |
3 | import com.formdev.flatlaf.extras.FlatSVGIcon;
4 | import com.laker.postman.util.I18nUtil;
5 | import com.laker.postman.util.MessageKeys;
6 |
7 | import javax.swing.*;
8 |
9 | /**
10 | * 通用开始按钮,带图标和统一样式。
11 | */
12 | public class StartButton extends JButton {
13 | public StartButton() {
14 | super(I18nUtil.getMessage(MessageKeys.BUTTON_START));
15 | setIcon(new FlatSVGIcon("icons/start.svg", 20, 20));
16 | setFocusable(false); // 去掉按钮的焦点边框
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/resources/icons/eye-close.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/common/component/button/RefreshButton.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.common.component.button;
2 |
3 | import com.formdev.flatlaf.extras.FlatSVGIcon;
4 | import com.laker.postman.util.I18nUtil;
5 | import com.laker.postman.util.MessageKeys;
6 |
7 | import javax.swing.*;
8 |
9 | /**
10 | * 通用刷新按钮,带图标和统一样式。
11 | */
12 | public class RefreshButton extends JButton {
13 | public RefreshButton() {
14 | super(I18nUtil.getMessage(MessageKeys.BUTTON_REFRESH));
15 | setIcon(new FlatSVGIcon("icons/refresh.svg", 20, 20));
16 | setFocusable(false); // 去掉按钮的焦点边框
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/resources/icons/websocket.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/CurlRequest.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * 临时数据结构,用于解析 curl 命令
7 | */
8 | public class CurlRequest {
9 | public String url;
10 | public String method;
11 | public List headersList;
12 | public String body;
13 | public List paramsList;
14 | // 用于存储解析出的表单数据 (multipart/form-data)
15 | public List formDataList;
16 | // 用于存储 application/x-www-form-urlencoded 类型的数据
17 | public List urlencodedList;
18 | public boolean followRedirects = false; // 是否跟随重定向
19 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/EnvironmentVariable.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Data;
5 | import lombok.NoArgsConstructor;
6 |
7 | import java.io.Serializable;
8 |
9 | /**
10 | * Environment Variable model with enabled state
11 | * 用于环境变量数据,支持启用/禁用状态
12 | */
13 | @Data
14 | @NoArgsConstructor
15 | @AllArgsConstructor
16 | public class EnvironmentVariable implements Serializable {
17 | private static final long serialVersionUID = 1L;
18 |
19 | private boolean enabled = true;
20 | private String key = "";
21 | private String value = "";
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/src/main/resources/icons/expand.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/main/resources/icons/history.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/clear.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/ioc/Scope.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.ioc;
2 |
3 | import java.lang.annotation.*;
4 |
5 | /**
6 | * 指定Bean的作用域
7 | * 默认为单例(singleton)
8 | */
9 | @Target(ElementType.TYPE)
10 | @Retention(RetentionPolicy.RUNTIME)
11 | @Documented
12 | public @interface Scope {
13 |
14 | /**
15 | * Bean的作用域
16 | * - "singleton": 单例模式(默认)
17 | * - "prototype": 原型模式(每次获取都创建新实例)
18 | */
19 | String value() default "singleton";
20 |
21 | /**
22 | * 常量:单例
23 | */
24 | String SINGLETON = "singleton";
25 |
26 | /**
27 | * 常量:原型
28 | */
29 | String PROTOTYPE = "prototype";
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/src/test/java/com/laker/postman/ioc/test/ServiceA.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.ioc.test;
2 |
3 | import com.laker.postman.ioc.Autowired;
4 | import com.laker.postman.ioc.Component;
5 | import lombok.Getter;
6 | import lombok.Setter;
7 | import lombok.ToString;
8 |
9 | /**
10 | * 测试循环依赖 - ServiceA
11 | */
12 | @Component
13 | @Getter
14 | @Setter
15 | @ToString(exclude = "serviceB") // 排除循环引用字段,避免 toString 栈溢出
16 | public class ServiceA {
17 |
18 | @Autowired
19 | private ServiceB serviceB;
20 |
21 | public void doSomething() {
22 | System.out.println("ServiceA doing something, has ServiceB: " + (serviceB != null));
23 | }
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/src/test/java/com/laker/postman/ioc/test/ServiceB.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.ioc.test;
2 |
3 | import com.laker.postman.ioc.Autowired;
4 | import com.laker.postman.ioc.Component;
5 | import lombok.Getter;
6 | import lombok.Setter;
7 | import lombok.ToString;
8 |
9 | /**
10 | * 测试循环依赖 - ServiceB
11 | */
12 | @Component
13 | @Getter
14 | @Setter
15 | @ToString(exclude = "serviceA") // 排除循环引用字段,避免 toString 栈溢出
16 | public class ServiceB {
17 |
18 | @Autowired
19 | private ServiceA serviceA;
20 |
21 | public void doSomething() {
22 | System.out.println("ServiceB doing something, has ServiceA: " + (serviceA != null));
23 | }
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/src/main/resources/icons/matchCase.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/sidebar-toggle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/matchCaseHovered.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/matchCaseSelected.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/duplicate.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/Snippet.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import com.laker.postman.util.I18nUtil;
4 |
5 | /**
6 | * 代码片段数据结构
7 | */
8 | public class Snippet {
9 | public String title;
10 | public String code;
11 | public String desc;
12 | public SnippetType type;
13 |
14 | public Snippet(SnippetType type) {
15 | this.type = type;
16 | this.title = I18nUtil.getMessage(type.titleKey);
17 | this.desc = I18nUtil.getMessage(type.descKey);
18 | this.code = type.code;
19 | }
20 |
21 | public String toString() {
22 | return title + (desc != null && !desc.isEmpty() ? (" - " + desc) : "");
23 | }
24 | }
--------------------------------------------------------------------------------
/src/main/resources/icons/paste.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/ioc/BeanCreationException.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.ioc;
2 |
3 | /**
4 | * Bean创建失败异常
5 | */
6 | public class BeanCreationException extends BeanException {
7 |
8 | public BeanCreationException(String beanName, String message) {
9 | super("Failed to create bean '" + beanName + "': " + message);
10 | }
11 |
12 | public BeanCreationException(String beanName, Throwable cause) {
13 | super("Failed to create bean '" + beanName + "'", cause);
14 | }
15 |
16 | public BeanCreationException(String beanName, String message, Throwable cause) {
17 | super("Failed to create bean '" + beanName + "': " + message, cause);
18 | }
19 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/ConflictBlock.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import lombok.Getter;
4 |
5 | import java.util.List;
6 |
7 | @Getter
8 | public class ConflictBlock {
9 | public int begin;
10 | public int end;
11 | public List baseLines;
12 | public List localLines;
13 | public List remoteLines;
14 |
15 | public ConflictBlock(int begin, int end, List baseLines, List localLines, List remoteLines) {
16 | this.begin = begin;
17 | this.end = end;
18 | this.baseLines = baseLines;
19 | this.localLines = localLines;
20 | this.remoteLines = remoteLines;
21 | }
22 | }
--------------------------------------------------------------------------------
/src/main/resources/icons/users.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/HttpFormUrlencoded.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Data;
5 | import lombok.NoArgsConstructor;
6 |
7 | import java.io.Serial;
8 | import java.io.Serializable;
9 |
10 | /**
11 | * HTTP Form Urlencoded model with enabled state
12 | * 用于 application/x-www-form-urlencoded 类型的表单数据
13 | */
14 | @Data
15 | @NoArgsConstructor
16 | @AllArgsConstructor
17 | public class HttpFormUrlencoded implements Serializable {
18 | @Serial
19 | private static final long serialVersionUID = 1L;
20 |
21 | private boolean enabled = true;
22 | private String key = "";
23 | private String value = "";
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/service/js/ScriptExecutionException.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.service.js;
2 |
3 | import lombok.Getter;
4 |
5 | /**
6 | * 脚本执行异常
7 | */
8 | @Getter
9 | public class ScriptExecutionException extends RuntimeException {
10 | private final ScriptExecutionContext.ScriptType scriptType;
11 |
12 | public ScriptExecutionException(String message, Throwable cause) {
13 | super(message, cause);
14 | this.scriptType = null;
15 | }
16 |
17 | public ScriptExecutionException(String message, Throwable cause, ScriptExecutionContext.ScriptType scriptType) {
18 | super(message, cause);
19 | this.scriptType = scriptType;
20 | }
21 |
22 | }
--------------------------------------------------------------------------------
/src/main/resources/icons/user-group.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/collapse.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/src/main/resources/icons/root_group.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/TabInfo.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import javax.swing.*;
4 | import java.util.function.Supplier;
5 |
6 | // Tab元数据结构,便于维护和扩展
7 | public class TabInfo {
8 | public String title;
9 | public Icon icon;
10 | public Supplier panelSupplier; // 用于懒加载面板
11 | public JPanel panel;
12 |
13 | public TabInfo(String title, Icon icon, Supplier panelSupplier) {
14 | this.title = title;
15 | this.icon = icon;
16 | this.panelSupplier = panelSupplier;
17 | }
18 |
19 | public JPanel getPanel() { // 懒加载面板
20 | if (panel == null) {
21 | panel = panelSupplier.get();
22 | }
23 | return panel;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/main/resources/icons/load.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/curl.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/help.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/import.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/easy.svg:
--------------------------------------------------------------------------------
1 |
11 |
12 |
--------------------------------------------------------------------------------
/src/main/resources/icons/export.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/ioc/BeanDefinition.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.ioc;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * Bean定义,描述一个Bean的元数据
7 | */
8 | @Data
9 | public class BeanDefinition {
10 | /**
11 | * Bean名称
12 | */
13 | private String name;
14 |
15 | /**
16 | * Bean的Class类型
17 | */
18 | private Class> beanClass;
19 |
20 | /**
21 | * 是否单例(默认为单例)
22 | */
23 | private boolean singleton = true;
24 |
25 |
26 | public BeanDefinition(String name, Class> beanClass) {
27 | this.name = name;
28 | this.beanClass = beanClass;
29 | }
30 |
31 | public BeanDefinition(String name, Class> beanClass, boolean singleton) {
32 | this.name = name;
33 | this.beanClass = beanClass;
34 | this.singleton = singleton;
35 | }
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/panel/MainPanel.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.panel;
2 |
3 | import com.laker.postman.common.SingletonFactory;
4 | import com.laker.postman.common.SingletonBasePanel;
5 | import com.laker.postman.panel.sidebar.SidebarTabPanel;
6 | import lombok.extern.slf4j.Slf4j;
7 |
8 | import java.awt.*;
9 |
10 | /**
11 | * 包含了左侧的标签页面板和右侧的请求编辑面板。
12 | * 左侧标签页面板包含了集合、环境变量、压测三个标签页,
13 | */
14 | @Slf4j
15 | public class MainPanel extends SingletonBasePanel {
16 |
17 | @Override
18 | protected void initUI() {
19 | setLayout(new BorderLayout()); // 设置布局为 BorderLayout
20 | // 中间SidebarTabPanel + 底部控制台面板
21 | add(SingletonFactory.getInstance(SidebarTabPanel.class), BorderLayout.CENTER);
22 | }
23 |
24 | @Override
25 | protected void registerListeners() {
26 | // no-op
27 | }
28 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/common/component/tab/PlusTabComponent.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.common.component.tab;
2 |
3 | import com.formdev.flatlaf.extras.FlatSVGIcon;
4 |
5 | import javax.swing.*;
6 | import java.awt.*;
7 |
8 | /**
9 | * PlusTabComponent 用于显示一个加号图标的标签组件
10 | */
11 | public class PlusTabComponent extends JPanel {
12 | public PlusTabComponent() {
13 | setOpaque(false); // 设置为透明背景
14 | setFocusable(false); // 不可获取焦点
15 | setLayout(new BorderLayout()); // 使用 BorderLayout 布局
16 |
17 | JLabel plusLabel = new JLabel();
18 | plusLabel.setIcon(new FlatSVGIcon("icons/plus.svg", 20, 20)); // 使用SVG图标
19 | plusLabel.setHorizontalAlignment(SwingConstants.CENTER);
20 | plusLabel.setVerticalAlignment(SwingConstants.CENTER);
21 | add(plusLabel, BorderLayout.CENTER);
22 | }
23 | }
--------------------------------------------------------------------------------
/.github/workflows/sync-labels.yml:
--------------------------------------------------------------------------------
1 | # 工作流名称:同步标签
2 | name: Sync Labels
3 |
4 | # 触发条件
5 | on:
6 | # 当推送到 main 分支,且 .github/labels.yml 文件发生变化时触发
7 | push:
8 | branches:
9 | - main
10 | paths:
11 | - '.github/labels.yml'
12 | # 允许手动触发工作流
13 | workflow_dispatch:
14 |
15 | jobs:
16 | sync-labels:
17 | # 运行环境:使用最新版本的 Ubuntu
18 | runs-on: ubuntu-latest
19 | # 权限设置:允许写入 issues(包括标签管理)
20 | permissions:
21 | issues: write
22 |
23 | steps:
24 | # 第一步:检出代码仓库
25 | - name: Checkout
26 | uses: actions/checkout@v4
27 |
28 | # 第二步:同步标签配置到 GitHub 仓库
29 | - name: Sync labels
30 | uses: EndBug/label-sync@v2
31 | with:
32 | # 标签配置文件路径
33 | config-file: .github/labels.yml
34 | # 使用 GitHub 提供的访问令牌进行身份验证
35 | token: ${{ secrets.GITHUB_TOKEN }}
36 |
37 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/EnvironmentItem.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import lombok.Getter;
4 |
5 | import java.util.Objects;
6 |
7 | /**
8 | * 环境项封装类,用于在下拉框中显示
9 | */
10 | @Getter
11 | public class EnvironmentItem {
12 | private final Environment environment;
13 |
14 | public EnvironmentItem(Environment environment) {
15 | this.environment = environment;
16 | }
17 |
18 | @Override
19 | public boolean equals(Object o) {
20 | if (!(o instanceof EnvironmentItem that)) return false;
21 | return Objects.equals(getEnvironment().getId(), that.getEnvironment().getId());
22 | }
23 |
24 | @Override
25 | public int hashCode() {
26 | return Objects.hashCode(environment.getId());
27 | }
28 |
29 | @Override
30 | public String toString() {
31 | return environment.getName();
32 | }
33 |
34 |
35 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/common/component/combobox/EnvironmentItemRenderer.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.common.component.combobox;
2 |
3 | import com.laker.postman.model.Environment;
4 | import com.laker.postman.model.EnvironmentItem;
5 |
6 | import javax.swing.*;
7 | import java.awt.*;
8 |
9 | /**
10 | * 环境下拉框的渲染器
11 | */
12 | public class EnvironmentItemRenderer extends DefaultListCellRenderer {
13 | @Override
14 | public Component getListCellRendererComponent(JList> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
15 | // 使用默认渲染器获取基础组件
16 | JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
17 | if (value instanceof EnvironmentItem item) {
18 | Environment env = item.getEnvironment();
19 | label.setText(env.getName());
20 | }
21 | return label;
22 | }
23 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/RequestHistoryItem.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | /**
4 | * 请求历史项,包含请求和响应的简要信息
5 | */
6 | public class RequestHistoryItem {
7 | public final String method;
8 | public final String url;
9 | public final int responseCode; // 响应状态码
10 | public final long requestTime; // 请求时间戳
11 | public PreparedRequest request; // 原始请求对象
12 | public HttpResponse response; // 响应对象
13 |
14 | public RequestHistoryItem(PreparedRequest request, HttpResponse response, long requestTime) {
15 | this.method = request.method;
16 | this.url = request.url;
17 | this.responseCode = response.code;
18 | this.request = request;
19 | this.response = response;
20 | this.requestTime = requestTime;
21 | }
22 |
23 | @Override
24 | public String toString() {
25 | return String.format("[%s] %s", method, url);
26 | }
27 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/common/component/combobox/WorkspaceItemRenderer.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.common.component.combobox;
2 |
3 | import com.laker.postman.model.Workspace;
4 |
5 | import javax.swing.*;
6 | import java.awt.*;
7 |
8 | /**
9 | * 工作区下拉框的渲染器
10 | */
11 | public class WorkspaceItemRenderer extends DefaultListCellRenderer {
12 | @Override
13 | public Component getListCellRendererComponent(JList> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
14 | JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
15 |
16 | if (value instanceof Workspace workspace) {
17 | label.setText(workspace.getName());
18 | // 如果名称太长,使用tooltip显示完整名称
19 | if (workspace.getName().length() > 15) {
20 | label.setToolTipText(workspace.getName());
21 | }
22 | }
23 | return label;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/main/resources/icons/collections.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/request.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/common/component/table/EasyPostmanTextFieldCellEditor.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.common.component.table;
2 |
3 | import com.laker.postman.common.component.EasyPostmanTextField;
4 |
5 | import javax.swing.*;
6 | import javax.swing.table.TableCellEditor;
7 | import java.awt.*;
8 |
9 | public class EasyPostmanTextFieldCellEditor extends AbstractCellEditor implements TableCellEditor {
10 | private final EasyPostmanTextField textField = new EasyPostmanTextField(1);
11 |
12 | public EasyPostmanTextFieldCellEditor() {
13 | textField.setBorder(null);
14 | }
15 |
16 | @Override
17 | public Object getCellEditorValue() {
18 | return textField.getText();
19 | }
20 |
21 | @Override
22 | public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
23 | textField.setText(value == null ? "" : value.toString());
24 | return textField;
25 | }
26 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/util/FontsUtil.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.util;
2 |
3 | import lombok.experimental.UtilityClass;
4 |
5 | import javax.swing.*;
6 | import java.awt.*;
7 |
8 | /**
9 | * 字体工具类,提供系统默认字体,保留完整的字体降级链以支持 emoji 等特殊字符
10 | */
11 | @UtilityClass
12 | public class FontsUtil {
13 |
14 | /**
15 | * 获取默认字体,从 UIManager 派生以保留降级链,支持 emoji 等特殊字符
16 | *
17 | * @param style 字体样式 (Font.PLAIN, Font.BOLD, Font.ITALIC)
18 | * @param size 字体大小
19 | * @return Font 对象,从系统默认字体派生
20 | */
21 | public static Font getDefaultFont(int style, int size) {
22 | // 从 UIManager 获取默认字体,使用 deriveFont 派生,保留降级链
23 | Font baseFont = UIManager.getFont("Label.font");
24 | if (baseFont == null) {
25 | // 如果 UIManager 中没有,使用系统默认字体
26 | baseFont = new JLabel().getFont();
27 | }
28 | // 使用 deriveFont 派生新字体,保留原字体的所有属性和降级链
29 | return baseFont.deriveFont(style, (float) size);
30 | }
31 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/RequestItemProtocolEnum.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 |
4 | import com.formdev.flatlaf.extras.FlatSVGIcon;
5 | import lombok.Getter;
6 |
7 | import javax.swing.*;
8 |
9 | @Getter
10 | public enum RequestItemProtocolEnum {
11 | HTTP("HTTP", new FlatSVGIcon("icons/http.svg", 24, 24)),
12 | WEBSOCKET("WebSocket", new FlatSVGIcon("icons/websocket.svg", 24, 24)),
13 | SSE("SSE", new FlatSVGIcon("icons/sse.svg", 24, 24));
14 |
15 | private final String protocol;
16 | private final Icon icon;
17 |
18 | RequestItemProtocolEnum(String protocol, Icon icon) {
19 | this.protocol = protocol;
20 | this.icon = icon;
21 | }
22 |
23 | public boolean isWebSocketProtocol() {
24 | return this == WEBSOCKET;
25 | }
26 |
27 | public boolean isHttpProtocol() {
28 | return this == HTTP;
29 | }
30 |
31 | public boolean isSseProtocol() {
32 | return this == SSE;
33 | }
34 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/GitOperation.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import com.laker.postman.common.constants.ModernColors;
4 | import com.laker.postman.util.I18nUtil;
5 | import com.laker.postman.util.MessageKeys;
6 | import lombok.Getter;
7 |
8 | import java.awt.*;
9 |
10 | @Getter
11 | public enum GitOperation {
12 | COMMIT(I18nUtil.getMessage(MessageKeys.GIT_OPERATION_COMMIT), "icons/git-commit.svg", ModernColors.GIT_COMMIT),
13 | PUSH(I18nUtil.getMessage(MessageKeys.GIT_OPERATION_PUSH), "icons/git-push.svg", ModernColors.GIT_PUSH),
14 | PULL(I18nUtil.getMessage(MessageKeys.GIT_OPERATION_PULL), "icons/git-pull.svg", ModernColors.GIT_PULL);
15 |
16 | private final String displayName;
17 | private final String iconName;
18 | private final Color color;
19 |
20 | GitOperation(String displayName, String iconName, Color color) {
21 | this.displayName = displayName;
22 | this.iconName = iconName;
23 | this.color = color;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/main/resources/icons/csv.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/util/TimeDisplayUtil.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.util;
2 |
3 | import lombok.experimental.UtilityClass;
4 |
5 | /**
6 | * 时间显示工具类
7 | * 提供将毫秒时间转换为更友好的显示格式
8 | */
9 | @UtilityClass
10 | public class TimeDisplayUtil {
11 |
12 | /**
13 | * 将毫秒时间转换为更友好的显示格式
14 | * - 小于1秒显示为 "xx ms"
15 | * - 小于1分钟显示为 "x.xx s"
16 | * - 大于等于1分钟显示为 "xm xs"
17 | *
18 | * @param elapsedTimeMillis 毫秒时间
19 | * @return 格式化后的时间字符串
20 | */
21 | public static String formatElapsedTime(long elapsedTimeMillis) {
22 | if (elapsedTimeMillis < 1000) {
23 | return elapsedTimeMillis + " ms";
24 | } else if (elapsedTimeMillis < 60000) {
25 | return String.format("%.2f s", elapsedTimeMillis / 1000.0);
26 | } else {
27 | int minutes = (int) (elapsedTimeMillis / 60000);
28 | int seconds = (int) ((elapsedTimeMillis % 60000) / 1000);
29 | return minutes + "m " + seconds + "s";
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/service/update/source/GiteeUpdateSource.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.service.update.source;
2 |
3 | /**
4 | * Gitee 更新源实现
5 | */
6 | public class GiteeUpdateSource extends AbstractUpdateSource {
7 |
8 | private static final String API_URL = "https://gitee.com/api/v5/repos/lakernote/easy-postman/releases/latest";
9 | private static final String ALL_RELEASES_API_URL = "https://gitee.com/api/v5/repos/lakernote/easy-postman/releases";
10 | private static final String WEB_URL = "https://gitee.com/lakernote/easy-postman/releases";
11 | private static final String NAME = "Gitee";
12 |
13 | @Override
14 | public String getName() {
15 | return NAME;
16 | }
17 |
18 | @Override
19 | public String getApiUrl() {
20 | return API_URL;
21 | }
22 |
23 | @Override
24 | public String getAllReleasesApiUrl() {
25 | return ALL_RELEASES_API_URL;
26 | }
27 |
28 | @Override
29 | public String getWebUrl() {
30 | return WEB_URL;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/service/update/source/GitHubUpdateSource.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.service.update.source;
2 |
3 | /**
4 | * GitHub 更新源实现
5 | */
6 | public class GitHubUpdateSource extends AbstractUpdateSource {
7 |
8 | private static final String API_URL = "https://api.github.com/repos/lakernote/easy-postman/releases/latest";
9 | private static final String ALL_RELEASES_API_URL = "https://api.github.com/repos/lakernote/easy-postman/releases";
10 | private static final String WEB_URL = "https://github.com/lakernote/easy-postman/releases";
11 | private static final String NAME = "GitHub";
12 |
13 | @Override
14 | public String getName() {
15 | return NAME;
16 | }
17 |
18 | @Override
19 | public String getApiUrl() {
20 | return API_URL;
21 | }
22 |
23 | @Override
24 | public String getAllReleasesApiUrl() {
25 | return ALL_RELEASES_API_URL;
26 | }
27 |
28 | @Override
29 | public String getWebUrl() {
30 | return WEB_URL;
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/Workspace.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import lombok.Data;
4 |
5 | // Workspace模型类
6 | @Data
7 | public class Workspace {
8 | private String id; // UUID
9 | private String name; // 工作空间名称
10 | private String description; // 工作空间描述
11 | private WorkspaceType type; // LOCAL/GIT
12 | private GitRepoSource gitRepoSource; // 仓库来源:INITIALIZED(本地初始化)/ CLONED(远程克隆)
13 | private String path; // 本地路径
14 | private String gitRemoteUrl; // Git远程仓库地址
15 | private String currentBranch; // 当前分支名称
16 | private String remoteBranch; // 当前跟踪的远程分支名称
17 | private String lastCommitId; // 最后提交ID
18 | private long createdAt;
19 | private long updatedAt;
20 | private String gitUsername; // Git 用户名
21 | private String gitPassword; // Git 密码(加密存储)
22 | private String gitToken; // Git 令牌(替代密码,优先使用)
23 | private GitAuthType gitAuthType; // 认证类型
24 | private String sshPrivateKeyPath; // SSH 私钥文件路径
25 | private String sshPassphrase; // SSH 私钥密码(可选)
26 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/panel/sidebar/cookie/CookieManagerDialog.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.panel.sidebar.cookie;
2 |
3 | import com.laker.postman.panel.collections.right.request.sub.CookieTablePanel;
4 | import com.laker.postman.util.I18nUtil;
5 | import com.laker.postman.util.MessageKeys;
6 |
7 | import javax.swing.*;
8 | import java.awt.*;
9 |
10 | /**
11 | * Cookie 管理器对话框
12 | * 全局 Cookie 管理界面,显示和管理所有域名的 Cookie
13 | */
14 | public class CookieManagerDialog extends JDialog {
15 |
16 | public CookieManagerDialog(Window owner) {
17 | super(owner, I18nUtil.getMessage(MessageKeys.COOKIES_MANAGER_TITLE), ModalityType.MODELESS);
18 | initUI();
19 | }
20 |
21 | private void initUI() {
22 | setLayout(new BorderLayout());
23 |
24 | // 使用现有的 CookieTablePanel
25 | CookieTablePanel cookiePanel = new CookieTablePanel();
26 | add(cookiePanel, BorderLayout.CENTER);
27 |
28 | // 设置对话框属性
29 | setSize(900, 500);
30 | setLocationRelativeTo(getOwner());
31 | setDefaultCloseOperation(DISPOSE_ON_CLOSE);
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/UpdateCheckFrequency.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | /**
4 | * 更新检查频率枚举
5 | */
6 | public enum UpdateCheckFrequency {
7 | STARTUP("startup", 0), // 每次启动
8 | DAILY("daily", 1), // 每日
9 | WEEKLY("weekly", 7), // 每周
10 | MONTHLY("monthly", 30); // 每月
11 |
12 | private final String code;
13 | private final int days;
14 |
15 | UpdateCheckFrequency(String code, int days) {
16 | this.code = code;
17 | this.days = days;
18 | }
19 |
20 | public String getCode() {
21 | return code;
22 | }
23 |
24 | public int getDays() {
25 | return days;
26 | }
27 |
28 | /**
29 | * 是否每次启动都检查
30 | */
31 | public boolean isAlwaysCheck() {
32 | return this == STARTUP;
33 | }
34 |
35 | public static UpdateCheckFrequency fromCode(String code) {
36 | for (UpdateCheckFrequency frequency : values()) {
37 | if (frequency.code.equals(code)) {
38 | return frequency;
39 | }
40 | }
41 | return DAILY; // 默认每日
42 | }
43 | }
--------------------------------------------------------------------------------
/src/main/resources/icons/tools.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/common/component/table/EasyPostmanTextFieldCellRenderer.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.common.component.table;
2 |
3 | import com.laker.postman.common.component.EasyPostmanTextField;
4 |
5 | import javax.swing.*;
6 | import javax.swing.table.TableCellRenderer;
7 | import java.awt.*;
8 |
9 | public class EasyPostmanTextFieldCellRenderer extends EasyPostmanTextField implements TableCellRenderer {
10 | public EasyPostmanTextFieldCellRenderer() {
11 | super(1);
12 | setBorder(null);
13 | setOpaque(true);
14 | }
15 |
16 | @Override
17 | public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
18 | setText(value == null ? "" : value.toString());
19 | if (value == null || value.toString().isEmpty()) {
20 | setBackground(Color.WHITE);
21 | } else if (isSelected) {
22 | setBackground(table.getSelectionBackground());
23 | } else {
24 | setBackground(table.getBackground());
25 | }
26 | return this;
27 | }
28 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/panel/performance/component/ResultTreeCellRenderer.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.panel.performance.component;
2 |
3 | import com.laker.postman.panel.performance.model.ResultNodeInfo;
4 |
5 | import javax.swing.*;
6 | import javax.swing.tree.DefaultMutableTreeNode;
7 | import javax.swing.tree.DefaultTreeCellRenderer;
8 | import java.awt.*;
9 |
10 | // 结果树渲染
11 | public class ResultTreeCellRenderer extends DefaultTreeCellRenderer {
12 | @Override
13 | public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
14 | JLabel label = (JLabel) super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
15 | Object userObj = ((DefaultMutableTreeNode) value).getUserObject();
16 | if (userObj instanceof ResultNodeInfo info) {
17 | if (info.success) {
18 | label.setForeground(new Color(0, 128, 0));
19 | } else {
20 | label.setForeground(Color.RED);
21 | }
22 | }
23 | return label;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/panel/performance/model/ResultNodeInfo.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.panel.performance.model;
2 |
3 | import cn.hutool.core.util.StrUtil;
4 | import com.laker.postman.model.HttpResponse;
5 | import com.laker.postman.model.PreparedRequest;
6 | import com.laker.postman.model.script.TestResult;
7 |
8 | import java.util.List;
9 |
10 | // 结果树节点信息
11 | public class ResultNodeInfo {
12 | public String name;
13 | public boolean success;
14 | public String errorMsg;
15 | public PreparedRequest req;
16 | public HttpResponse resp;
17 | public List testResults;
18 |
19 | public ResultNodeInfo(String name, boolean success, String errorMsg, PreparedRequest req, HttpResponse resp, List testResults) {
20 | this.name = name;
21 | this.success = success;
22 | this.errorMsg = errorMsg;
23 | this.req = req;
24 | this.resp = resp;
25 | this.testResults = testResults;
26 | }
27 |
28 | @Override
29 | public String toString() {
30 | return name + (StrUtil.isBlank(errorMsg) ? "" : " - 错误: " + errorMsg);
31 | }
32 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/service/UpdateService.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.service;
2 |
3 | import com.laker.postman.ioc.Autowired;
4 | import com.laker.postman.ioc.Component;
5 | import com.laker.postman.ioc.PreDestroy;
6 | import com.laker.postman.service.update.AutoUpdateManager;
7 | import lombok.extern.slf4j.Slf4j;
8 |
9 | /**
10 | * 版本更新服务
11 | */
12 | @Slf4j
13 | @Component
14 | public class UpdateService {
15 |
16 | @Autowired
17 | private AutoUpdateManager autoUpdateManager;
18 |
19 | /**
20 | * 启动时异步检查更新
21 | */
22 | public void checkUpdateOnStartup() {
23 | // 启动后台更新检查
24 | autoUpdateManager.startBackgroundCheck();
25 | }
26 |
27 | /**
28 | * 手动检查更新(用于菜单调用)
29 | */
30 | public void checkUpdateManually() {
31 | autoUpdateManager.checkForUpdateManually()
32 | .thenAccept(updateInfo ->
33 | autoUpdateManager.handleUpdateCheckResult(updateInfo, true))
34 | .exceptionally(throwable -> {
35 | log.error("Manual update check failed", throwable);
36 | return null;
37 | });
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/panel/functional/table/RunnerRowData.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.panel.functional.table;
2 |
3 | import com.laker.postman.model.*;
4 | import com.laker.postman.model.script.TestResult;
5 |
6 | import java.util.List;
7 |
8 | public class RunnerRowData {
9 | public boolean selected;
10 | public String name;
11 | public String url;
12 | public String method;
13 | public long cost;
14 | public String status;
15 | public AssertionResult assertion;
16 | public HttpRequestItem requestItem;
17 | public PreparedRequest preparedRequest;
18 | public HttpResponse response;
19 | public List testResults;
20 |
21 | public RunnerRowData(HttpRequestItem item, PreparedRequest prepared) {
22 | this.selected = true;
23 | this.name = item.getName();
24 | this.url = item.getUrl();
25 | this.method = item.getMethod();
26 | this.cost = 0;
27 | this.status = "";
28 | this.assertion = AssertionResult.NO_TESTS;
29 | this.requestItem = item;
30 | this.preparedRequest = prepared;
31 | this.response = null;
32 | this.testResults = null;
33 | }
34 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/ioc/BeanFactory.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.ioc;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 |
5 | /**
6 | * IOC容器工具类,提供便捷的访问方法
7 | */
8 | @Slf4j
9 | public class BeanFactory {
10 |
11 | private static final ApplicationContext context = ApplicationContext.getInstance();
12 |
13 | /**
14 | * 初始化IOC容器,扫描指定包
15 | */
16 | public static void init(String... basePackages) {
17 | log.info("Initializing IOC container, scanning packages: {}", (Object) basePackages);
18 | context.scan(basePackages);
19 | log.info("IOC container initialized successfully");
20 | }
21 |
22 | /**
23 | * 根据名称获取Bean
24 | */
25 | public static T getBean(String beanName) {
26 | return context.getBean(beanName);
27 | }
28 |
29 | /**
30 | * 根据类型获取Bean
31 | */
32 | public static T getBean(Class requiredType) {
33 | return context.getBean(requiredType);
34 | }
35 |
36 | /**
37 | * 销毁所有Bean,调用@PreDestroy方法
38 | * 建议在应用程序关闭时调用此方法
39 | */
40 | public static void destroy() {
41 | log.info("Destroying IOC container...");
42 | context.destroy();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/resources/icons/format.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/script/Cookie.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model.script;
2 |
3 | /**
4 | * Cookie 数据对象
5 | *
6 | * 表示一个 HTTP Cookie,包含所有标准 Cookie 属性。
7 | *
8 | */
9 | public class Cookie {
10 | /**
11 | * Cookie 名称
12 | */
13 | public String name;
14 |
15 | /**
16 | * Cookie 值
17 | */
18 | public String value;
19 |
20 | /**
21 | * Cookie 所属域名
22 | */
23 | public String domain;
24 |
25 | /**
26 | * Cookie 路径
27 | */
28 | public String path;
29 |
30 | /**
31 | * Cookie 过期时间
32 | */
33 | public String expires;
34 |
35 | /**
36 | * Cookie 最大生存时间(秒)
37 | */
38 | public Integer maxAge;
39 |
40 | /**
41 | * 是否仅在 HTTPS 连接中发送
42 | */
43 | public Boolean secure;
44 |
45 | /**
46 | * 是否禁止 JavaScript 访问
47 | */
48 | public Boolean httpOnly;
49 |
50 | /**
51 | * SameSite 属性 (Strict/Lax/None)
52 | */
53 | public String sameSite;
54 |
55 | @Override
56 | public String toString() {
57 | return String.format("Cookie{name='%s', value='%s', domain='%s', path='%s'}",
58 | name, value, domain, path);
59 | }
60 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/service/http/HttpSingleRequestExecutor.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.service.http;
2 |
3 | import com.laker.postman.model.HttpResponse;
4 | import com.laker.postman.model.PreparedRequest;
5 | import com.laker.postman.service.http.sse.SseResEventListener;
6 | import lombok.experimental.UtilityClass;
7 | import lombok.extern.slf4j.Slf4j;
8 | import okhttp3.WebSocketListener;
9 | import okhttp3.sse.EventSource;
10 | import okhttp3.sse.EventSourceListener;
11 |
12 | @Slf4j
13 | @UtilityClass
14 | public class HttpSingleRequestExecutor {
15 |
16 | public static HttpResponse executeHttp(PreparedRequest req) throws Exception {
17 | return HttpService.sendRequest(req, null);
18 | }
19 |
20 | public static HttpResponse executeHttp(PreparedRequest req, SseResEventListener callback) throws Exception {
21 | return HttpService.sendRequest(req, callback);
22 | }
23 |
24 | public static EventSource executeSSE(PreparedRequest req, EventSourceListener listener) {
25 | return HttpService.sendSseRequest(req, listener);
26 | }
27 |
28 | public static void executeWebSocket(PreparedRequest req, WebSocketListener listener) {
29 | HttpService.sendWebSocket(req, listener);
30 | }
31 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/MessageType.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import com.formdev.flatlaf.extras.FlatSVGIcon;
4 | import com.laker.postman.util.I18nUtil;
5 | import com.laker.postman.util.MessageKeys;
6 |
7 | import javax.swing.*;
8 |
9 | // 消息类型
10 | public enum MessageType {
11 | SENT(I18nUtil.getMessage(MessageKeys.WEBSOCKET_TYPE_SENT), new FlatSVGIcon("icons/ws-send.svg", 16, 16)),
12 | RECEIVED(I18nUtil.getMessage(MessageKeys.WEBSOCKET_TYPE_RECEIVED), new FlatSVGIcon("icons/ws-receive.svg", 16, 16)),
13 | CONNECTED(I18nUtil.getMessage(MessageKeys.WEBSOCKET_TYPE_CONNECTED), new FlatSVGIcon("icons/ws-connect.svg", 16, 16)),
14 | CLOSED(I18nUtil.getMessage(MessageKeys.WEBSOCKET_TYPE_CLOSED), new FlatSVGIcon("icons/ws-close.svg", 16, 16)),
15 | WARNING(I18nUtil.getMessage(MessageKeys.WEBSOCKET_TYPE_WARNING), new FlatSVGIcon("icons/warning.svg", 16, 16)),
16 | INFO(I18nUtil.getMessage(MessageKeys.WEBSOCKET_TYPE_INFO), new FlatSVGIcon("icons/ws-info.svg", 16, 16)),
17 | BINARY(I18nUtil.getMessage(MessageKeys.WEBSOCKET_TYPE_BINARY), new FlatSVGIcon("icons/binary.svg", 16, 16));
18 | public final String display;
19 | public final Icon icon;
20 |
21 | MessageType(String display, Icon icon) {
22 | this.display = display;
23 | this.icon = icon;
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/common/SingletonBasePanel.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.common;
2 |
3 | import javax.swing.*;
4 |
5 | /**
6 | * 面板基础抽象类,统一UI初始化和监听注册结构
7 | * 提供了initUI和registerListeners两个抽象方法,
8 | * 子类需要实现这两个方法来完成具体的UI组件初始化和事件监听注册。
9 | * 所有子类必须通过SingletonFactory获取实例,禁止直接new创建实例。
10 | */
11 | public abstract class SingletonBasePanel extends JPanel {
12 | // 使用静态线程局部变量直接控制创建状态
13 | private static final ThreadLocal CREATING_ALLOWED = ThreadLocal.withInitial(() -> Boolean.FALSE);
14 |
15 | // 构造函数设为protected,限制外部直接new
16 | protected SingletonBasePanel() {
17 | // 验证是否允许创建实例
18 | if (!CREATING_ALLOWED.get()) {
19 | throw new IllegalStateException(
20 | "SingletonBasePanel子类必须通过SingletonFactory.getInstance()获取实例,禁止直接new创建: " +
21 | this.getClass().getName());
22 | }
23 | }
24 |
25 | public void safeInit() {
26 | initUI();
27 | registerListeners();
28 | }
29 |
30 | /**
31 | * 初始化UI组件
32 | */
33 | protected abstract void initUI();
34 |
35 | /**
36 | * 注册事件监听
37 | */
38 | protected abstract void registerListeners();
39 |
40 | /**
41 | * 设置是否允许创建实例,供SingletonFactory使用
42 | */
43 | public static void setCreatingAllowed(boolean allowed) {
44 | CREATING_ALLOWED.set(allowed);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/common/SingletonBaseMenuBar.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.common;
2 |
3 | import javax.swing.*;
4 |
5 | /**
6 | * 面板基础抽象类,统一UI初始化和监听注册结构
7 | * 提供了initUI和registerListeners两个抽象方法,
8 | * 子类需要实现这两个方法来完成具体的UI组件初始化和事件监听注册。
9 | * 所有子类必须通过SingletonFactory获取实例,禁止直接new创建实例。
10 | */
11 | public abstract class SingletonBaseMenuBar extends JMenuBar {
12 | // 使用静态线程局部变量直接控制创建状态
13 | private static final ThreadLocal CREATING_ALLOWED = ThreadLocal.withInitial(() -> Boolean.FALSE);
14 |
15 | // 构造函数设为protected,限制外部直接new
16 | protected SingletonBaseMenuBar() {
17 | // 验证是否允许创建实例
18 | if (!CREATING_ALLOWED.get()) {
19 | throw new IllegalStateException(
20 | "SingletonBasePanel子类必须通过SingletonFactory.getInstance()获取实例,禁止直接new创建: " +
21 | this.getClass().getName());
22 | }
23 | }
24 |
25 | public void safeInit() {
26 | initUI();
27 | registerListeners();
28 | }
29 |
30 | /**
31 | * 初始化UI组件
32 | */
33 | protected abstract void initUI();
34 |
35 | /**
36 | * 注册事件监听
37 | */
38 | protected abstract void registerListeners();
39 |
40 | /**
41 | * 设置是否允许创建实例,供SingletonFactory使用
42 | */
43 | public static void setCreatingAllowed(boolean allowed) {
44 | CREATING_ALLOWED.set(allowed);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/service/update/source/UpdateSource.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.service.update.source;
2 |
3 | import cn.hutool.json.JSONArray;
4 | import cn.hutool.json.JSONObject;
5 |
6 | /**
7 | * 更新源接口 - 定义统一的版本检查和发布信息获取规范
8 | *
9 | * 不同的更新源(GitHub、Gitee 等)需要实现此接口,提供统一的访问方式。
10 | */
11 | public interface UpdateSource {
12 |
13 | /**
14 | * 获取更新源的名称
15 | *
16 | * @return 更新源名称(如 "GitHub", "Gitee")
17 | */
18 | String getName();
19 |
20 | /**
21 | * 获取最新版本的 API URL
22 | *
23 | * @return API 地址
24 | */
25 | String getApiUrl();
26 |
27 | /**
28 | * 获取所有版本的 API URL
29 | *
30 | * @return 所有版本的 API 地址
31 | */
32 | String getAllReleasesApiUrl();
33 |
34 | /**
35 | * 获取网页版发布页面 URL
36 | *
37 | * @return 网页版发布页面地址
38 | */
39 | String getWebUrl();
40 |
41 | /**
42 | * 测试更新源的连接速度
43 | *
44 | * @return 响应时间(毫秒),如果连接失败返回 Long.MAX_VALUE
45 | */
46 | long testConnectionSpeed();
47 |
48 | /**
49 | * 获取最新版本发布信息
50 | *
51 | * @return 发布信息的 JSON 对象,如果获取失败返回 null
52 | */
53 | JSONObject fetchLatestReleaseInfo();
54 |
55 | /**
56 | * 获取所有版本发布信息
57 | *
58 | * @param limit 限制返回的版本数量,0 表示不限制
59 | * @return 发布信息的 JSON 数组,如果获取失败返回 null
60 | */
61 | JSONArray fetchAllReleases(int limit);
62 | }
63 |
64 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/RequestResult.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import com.laker.postman.model.script.TestResult;
4 | import lombok.Data;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | /**
10 | * 单个请求的执行结果
11 | */
12 | @Data
13 | public class RequestResult {
14 | private final String requestName;
15 | private final String method;
16 | private final String url;
17 | private final PreparedRequest req;
18 | private final HttpResponse response;
19 | private final long cost;
20 | private final String status;
21 | private final AssertionResult assertion;
22 | private final List testResults;
23 | private final long timestamp;
24 |
25 | public RequestResult(String requestName, String method, String url,
26 | PreparedRequest req, HttpResponse response, long cost, String status,
27 | AssertionResult assertion, List testResults) {
28 | this.requestName = requestName;
29 | this.method = method;
30 | this.url = url;
31 | this.req = req;
32 | this.response = response;
33 | this.cost = cost;
34 | this.status = status;
35 | this.assertion = assertion;
36 | this.testResults = testResults != null ? new ArrayList<>(testResults) : new ArrayList<>();
37 | this.timestamp = System.currentTimeMillis();
38 | }
39 | }
--------------------------------------------------------------------------------
/src/main/resources/icons/postman.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/HttpResponse.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 |
6 | /**
7 | * HTTP 响应类
8 | */
9 | public class HttpResponse {
10 | public Map> headers;
11 | public String message;
12 | public String body;
13 | public int code; // 添加响应状态码字段
14 | public String threadName; // 添加线程名称字段
15 | public String filePath; // 临时文件下载路径字段
16 | public String fileName; // 如果是文件下载,从响应头中获取的文件名字段
17 | public long costMs; // 请求耗时,单位毫秒
18 | public long endTime; // 响应结束时间,单位毫秒
19 | public String protocol; // 协议类型字段,例如 HTTP/1.1 或 HTTP/2
20 | public int idleConnectionCount; // 空闲连接数
21 | public int connectionCount; // 连接总数
22 | public HttpEventInfo httpEventInfo;
23 | public long bodySize; // 响应体字节数
24 | public long headersSize; // 响应头字节数
25 | public boolean isSse = false; // 是否为SSE响应
26 |
27 | public void addHeader(String name, List value) {
28 | if (headers == null) {
29 | return;
30 | }
31 | if ("Easy-Content-Encoding".equalsIgnoreCase(name)) {
32 | headers.put("Content-Encoding", value);
33 | headers.remove("Easy-Content-Encoding");
34 | } else if ("Easy-Content-Length".equalsIgnoreCase(name)) {
35 | headers.put("Content-Length", value);
36 | headers.remove("Easy-Content-Length");
37 | } else {
38 | headers.put(name, value);
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/IterationResult.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.Map;
6 |
7 | /**
8 | * 单次迭代的执行结果
9 | */
10 | public class IterationResult {
11 | private final int iterationIndex;
12 | private final Map csvData;
13 | private final List requestResults = new ArrayList<>();
14 | private final long startTime;
15 | private long endTime;
16 |
17 | public IterationResult(int iterationIndex, Map csvData) {
18 | this.iterationIndex = iterationIndex;
19 | this.csvData = csvData;
20 | this.startTime = System.currentTimeMillis();
21 | }
22 |
23 | public void addRequestResult(RequestResult result) {
24 | requestResults.add(result);
25 | }
26 |
27 | public void complete() {
28 | this.endTime = System.currentTimeMillis();
29 | }
30 |
31 | public int getIterationIndex() {
32 | return iterationIndex;
33 | }
34 |
35 | public Map getCsvData() {
36 | return csvData;
37 | }
38 |
39 | public List getRequestResults() {
40 | return new ArrayList<>(requestResults);
41 | }
42 |
43 | public long getExecutionTime() {
44 | return endTime - startTime;
45 | }
46 |
47 | public long getStartTime() {
48 | return startTime;
49 | }
50 |
51 | public long getEndTime() {
52 | return endTime;
53 | }
54 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/BatchExecutionHistory.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | /**
7 | * 批量执行的历史记录
8 | * 记录所有轮次的执行结果
9 | */
10 | public class BatchExecutionHistory {
11 | private final List iterations = new ArrayList<>();
12 | private int totalIterations;
13 | private int totalRequests;
14 | private long startTime;
15 | private long endTime;
16 |
17 | public BatchExecutionHistory() {
18 | this.startTime = System.currentTimeMillis();
19 | }
20 |
21 | public void addIteration(IterationResult iteration) {
22 | iterations.add(iteration);
23 | }
24 |
25 | public void complete() {
26 | this.endTime = System.currentTimeMillis();
27 | }
28 |
29 | public List getIterations() {
30 | return new ArrayList<>(iterations);
31 | }
32 |
33 | public int getTotalIterations() {
34 | return totalIterations;
35 | }
36 |
37 | public void setTotalIterations(int totalIterations) {
38 | this.totalIterations = totalIterations;
39 | }
40 |
41 | public int getTotalRequests() {
42 | return totalRequests;
43 | }
44 |
45 | public void setTotalRequests(int totalRequests) {
46 | this.totalRequests = totalRequests;
47 | }
48 |
49 | public long getExecutionTime() {
50 | return endTime - startTime;
51 | }
52 |
53 | public long getStartTime() {
54 | return startTime;
55 | }
56 |
57 | public long getEndTime() {
58 | return endTime;
59 | }
60 |
61 |
62 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/RequestGroup.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 |
6 | import java.io.Serializable;
7 | import java.util.UUID;
8 |
9 | /**
10 | * RequestGroup 类表示一个请求分组的配置项
11 | * 包含分组的名称、认证、脚本等信息
12 | * 分组级别的认证和脚本会被其下的请求继承
13 | */
14 | @Getter
15 | @Setter
16 | public class RequestGroup implements Serializable {
17 | private String id = ""; // 唯一标识符
18 | private String name = ""; // 分组名称
19 | private String authType = AuthType.NONE.getConstant(); // 认证类型(none/basic/bearer),分组默认无认证
20 | private String authUsername = ""; // Basic用户名
21 | private String authPassword = ""; // Basic密码
22 | private String authToken = ""; // Bearer Token
23 | // 前置脚本(请求前执行)
24 | private String prescript = "";
25 | // 后置脚本(响应后执行)
26 | private String postscript = "";
27 |
28 | public RequestGroup() {
29 | this.id = UUID.randomUUID().toString();
30 | }
31 |
32 | public RequestGroup(String name) {
33 | this.id = UUID.randomUUID().toString();
34 | this.name = name;
35 | }
36 |
37 | /**
38 | * 判断分组是否有认证配置
39 | */
40 | public boolean hasAuth() {
41 | return authType != null && !AuthType.NONE.getConstant().equals(authType);
42 | }
43 |
44 | /**
45 | * 判断分组是否有前置脚本
46 | */
47 | public boolean hasPreScript() {
48 | return prescript != null && !prescript.trim().isEmpty();
49 | }
50 |
51 | /**
52 | * 判断分组是否有后置脚本
53 | */
54 | public boolean hasPostScript() {
55 | return postscript != null && !postscript.trim().isEmpty();
56 | }
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/NotificationPosition.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import com.laker.postman.util.MessageKeys;
4 | import lombok.Getter;
5 | import lombok.RequiredArgsConstructor;
6 |
7 | /**
8 | * 通知位置枚举
9 | * 包含位置的顺序索引和国际化键
10 | */
11 | @Getter
12 | @RequiredArgsConstructor
13 | public enum NotificationPosition {
14 | TOP_RIGHT(0, MessageKeys.SETTINGS_GENERAL_NOTIFICATION_POSITION_TOP_RIGHT),
15 | TOP_CENTER(1, MessageKeys.SETTINGS_GENERAL_NOTIFICATION_POSITION_TOP_CENTER),
16 | TOP_LEFT(2, MessageKeys.SETTINGS_GENERAL_NOTIFICATION_POSITION_TOP_LEFT),
17 | BOTTOM_RIGHT(3, MessageKeys.SETTINGS_GENERAL_NOTIFICATION_POSITION_BOTTOM_RIGHT),
18 | BOTTOM_CENTER(4, MessageKeys.SETTINGS_GENERAL_NOTIFICATION_POSITION_BOTTOM_CENTER),
19 | BOTTOM_LEFT(5, MessageKeys.SETTINGS_GENERAL_NOTIFICATION_POSITION_BOTTOM_LEFT),
20 | CENTER(6, MessageKeys.SETTINGS_GENERAL_NOTIFICATION_POSITION_CENTER);
21 |
22 | private final int index;
23 | private final String i18nKey;
24 |
25 | /**
26 | * 根据索引获取位置
27 | */
28 | public static NotificationPosition fromIndex(int index) {
29 | for (NotificationPosition position : values()) {
30 | if (position.index == index) {
31 | return position;
32 | }
33 | }
34 | return BOTTOM_RIGHT; // 默认右下角
35 | }
36 |
37 | /**
38 | * 根据名称获取位置
39 | */
40 | public static NotificationPosition fromName(String name) {
41 | try {
42 | return valueOf(name);
43 | } catch (IllegalArgumentException e) {
44 | return BOTTOM_RIGHT; // 默认右下角
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/HttpRequestItem.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 |
6 | import java.io.Serializable;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | /**
11 | * HttpRequestItem 类表示一个HTTP请求的配置项
12 | * 包含请求的基本信息、头部、参数、认证等
13 | * 每个参数都要求有默认值,便于前端展示和编辑
14 | */
15 | @Setter
16 | @Getter
17 | public class HttpRequestItem implements Serializable {
18 | private String id = ""; // 唯一标识符
19 | private String name = ""; // 请求名称
20 | private String url = ""; // 请求URL
21 | private String method = "GET"; // 请求方法(GET, POST, PUT, DELETE等)
22 | private RequestItemProtocolEnum protocol = RequestItemProtocolEnum.HTTP; // 协议类型,默认HTTP
23 | private List headersList = new ArrayList<>();
24 | private String bodyType = ""; // 请求体类型
25 | private String body = ""; // 请求体内容(如JSON、表单数据等)
26 | private List paramsList = new ArrayList<>();
27 | private List formDataList = new ArrayList<>();
28 | private List urlencodedList = new ArrayList<>();
29 | private String authType = AuthType.INHERIT.getConstant(); // 认证类型(inherit/none/basic/bearer),默认继承
30 | private String authUsername = ""; // Basic用户名
31 | private String authPassword = ""; // Basic密码
32 | private String authToken = ""; // Bearer Token
33 | // 前置脚本(请求前执行)
34 | private String prescript = "";
35 | // 后置脚本(响应后执行)
36 | private String postscript = "";
37 |
38 | /**
39 | * 判断该请求是否为新建(未命名)请求
40 | */
41 | public boolean isNewRequest() {
42 | return name == null || name.trim().isEmpty();
43 | }
44 |
45 |
46 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/panel/collections/RequestCollectionsPanel.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.panel.collections;
2 |
3 | import com.laker.postman.common.SingletonFactory;
4 | import com.laker.postman.common.SingletonBasePanel;
5 | import com.laker.postman.panel.collections.left.RequestCollectionsLeftPanel;
6 | import com.laker.postman.panel.collections.right.RequestEditPanel;
7 | import lombok.extern.slf4j.Slf4j;
8 |
9 | import javax.swing.*;
10 | import java.awt.*;
11 |
12 | /**
13 | * 请求集合面板,包含左侧的请求集合列表和右侧的请求编辑面板
14 | */
15 | @Slf4j
16 | public class RequestCollectionsPanel extends SingletonBasePanel {
17 | @Override
18 | protected void initUI() {
19 | // 设置布局为 BorderLayout
20 | setLayout(new BorderLayout()); // 设置布局为 BorderLayout
21 | setBorder(BorderFactory.createMatteBorder(0, 1, 0, 0, Color.LIGHT_GRAY));
22 | // 1.创建左侧的请求集合面板
23 | RequestCollectionsLeftPanel requestCollectionsLeftPanel = SingletonFactory.getInstance(RequestCollectionsLeftPanel.class);
24 | // 2. 创建右侧的请求编辑面板
25 | RequestEditPanel rightRequestEditPanel = SingletonFactory.getInstance(RequestEditPanel.class); // 创建请求编辑面板实例
26 | // 创建水平分割面板,将左侧的集合面板和右侧的请求编辑面板放入其中
27 | JSplitPane mainSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, requestCollectionsLeftPanel, rightRequestEditPanel);
28 | mainSplit.setContinuousLayout(true); // 分割条拖动时实时更新布局
29 | mainSplit.setDividerLocation(250); // 设置初始分割位置
30 | mainSplit.setDividerSize(2); // 设置分割条的宽度
31 |
32 | // 将分割面板添加到主面板
33 | add(mainSplit, BorderLayout.CENTER); // 将分割面板添加到主面板的中心位置
34 | }
35 |
36 | @Override
37 | protected void registerListeners() {
38 | // no op
39 | }
40 | }
--------------------------------------------------------------------------------
/src/main/resources/icons/environments.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/PreparedRequest.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 |
4 | import okhttp3.Headers;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * 准备好的请求对象,包含请求的所有必要信息以及替换变量后的内容。
10 | */
11 | public class PreparedRequest {
12 | public String id;
13 | public String url;
14 | public String method;
15 |
16 | public String body;
17 | public String bodyType;
18 |
19 | public List headersList;
20 | public List formDataList;
21 | public List urlencodedList;
22 | public List paramsList;
23 |
24 | public boolean isMultipart;
25 | public boolean followRedirects = true; // 默认自动重定向
26 | public boolean logEvent = false; // 默认不记录事件日志
27 |
28 | public Headers okHttpHeaders; // OkHttp 特有的 Headers 对象
29 | public String okHttpRequestBody; // 真实OkHttp请求体内容
30 |
31 | /**
32 | * 创建当前对象的浅拷贝
33 | * 注意:List 和 Map 对象本身不会被深拷贝,只是引用复制
34 | */
35 | public PreparedRequest shallowCopy() {
36 | PreparedRequest copy = new PreparedRequest();
37 | copy.id = this.id;
38 | copy.url = this.url;
39 | copy.method = this.method;
40 | copy.okHttpHeaders = this.okHttpHeaders;
41 | copy.body = this.body;
42 | copy.bodyType = this.bodyType;
43 | copy.okHttpRequestBody = this.okHttpRequestBody;
44 | copy.isMultipart = this.isMultipart;
45 | copy.followRedirects = this.followRedirects;
46 | copy.logEvent = this.logEvent;
47 | copy.headersList = this.headersList;
48 | copy.formDataList = this.formDataList;
49 | copy.urlencodedList = this.urlencodedList;
50 | copy.paramsList = this.paramsList;
51 | return copy;
52 | }
53 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/service/ExitService.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.service;
2 |
3 | import com.laker.postman.common.SingletonFactory;
4 | import com.laker.postman.common.exception.CancelException;
5 | import com.laker.postman.frame.MainFrame;
6 | import com.laker.postman.ioc.Component;
7 | import com.laker.postman.panel.functional.FunctionalPanel;
8 | import com.laker.postman.panel.performance.PerformancePanel;
9 | import com.laker.postman.service.collections.OpenedRequestsService;
10 | import lombok.extern.slf4j.Slf4j;
11 |
12 | @Slf4j
13 | @Component
14 | public class ExitService {
15 |
16 | /**
17 | * 显示退出确认对话框,处理未保存内容。
18 | */
19 | public void exit() {
20 |
21 | // 保存所有打开的请求(包括未保存的和已保存的)
22 | try {
23 | OpenedRequestsService.save();
24 | } catch (CancelException e) {
25 | // 用户取消了保存操作,终止退出
26 | return;
27 | }
28 |
29 | // 保存功能测试配置
30 | try {
31 | FunctionalPanel functionalPanel = SingletonFactory.getInstance(FunctionalPanel.class);
32 | functionalPanel.save();
33 | } catch (Exception e) {
34 | log.error("Failed to save functional test config on exit", e);
35 | }
36 |
37 | // 保存性能测试配置
38 | try {
39 | PerformancePanel performancePanel = SingletonFactory.getInstance(PerformancePanel.class);
40 | performancePanel.save();
41 | } catch (Exception e) {
42 | log.error("Failed to save performance test config on exit", e);
43 | }
44 |
45 | // 没有未保存内容,或已处理完未保存内容,直接退出
46 | log.info("User chose to exit application");
47 | SingletonFactory.getInstance(MainFrame.class).dispose();
48 | System.exit(0);
49 | }
50 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/util/FileSizeDisplayUtil.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.util;
2 |
3 | import lombok.experimental.UtilityClass;
4 |
5 | /**
6 | * 文件大小显示工具类
7 | * 提供将字节数转换为更友好的显示格式
8 | */
9 | @UtilityClass
10 | public class FileSizeDisplayUtil {
11 |
12 | /**
13 | * 格式化文件大小显示,自动选择单位,保留两位小数,去除无意义的.00
14 | *
15 | * @param sizeBytes 字节数
16 | * @return 友好的显示字符串,如 1.23 MB
17 | */
18 | public static String formatSize(int sizeBytes) {
19 | String unit;
20 | double value;
21 | if (sizeBytes >= 1024 * 1024 * 1024) {
22 | unit = "GB";
23 | value = sizeBytes / (1024.0 * 1024 * 1024);
24 | } else if (sizeBytes >= 1024 * 1024) {
25 | unit = "MB";
26 | value = sizeBytes / (1024.0 * 1024);
27 | } else if (sizeBytes >= 1024) {
28 | unit = "KB";
29 | value = sizeBytes / 1024.0;
30 | } else {
31 | unit = "B";
32 | value = sizeBytes;
33 | }
34 | if (unit.equals("B")) {
35 | return String.format("%d %s", (int) value, unit);
36 | } else {
37 | return String.format("%.2f %s", value, unit);
38 | }
39 | }
40 |
41 | /**
42 | * 格式化文件下载进度显示
43 | *
44 | * @param totalBytes 已下载字节数
45 | * @param contentLength 总字节数
46 | * @return 友好的显示字符串
47 | */
48 | public static String formatDownloadSize(int totalBytes, int contentLength) {
49 | if (contentLength > 0) {
50 | return String.format("Downloaded: %s / %s", formatSize(totalBytes), formatSize(contentLength));
51 | } else {
52 | return String.format("Downloaded: %s", formatSize(totalBytes));
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/service/update/version/VersionComparator.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.service.update.version;
2 |
3 | /**
4 | * 版本比较工具
5 | */
6 | public class VersionComparator {
7 |
8 | /**
9 | * 比较两个版本号
10 | *
11 | * @param v1 版本1(可以带 'v' 前缀)
12 | * @param v2 版本2(可以带 'v' 前缀)
13 | * @return 正数表示 v1 > v2,0 表示相等,负数表示 v1 < v2
14 | */
15 | public static int compare(String v1, String v2) {
16 | if (v1 == null || v2 == null) {
17 | return 0;
18 | }
19 |
20 | String s1 = removeVersionPrefix(v1);
21 | String s2 = removeVersionPrefix(v2);
22 |
23 | String[] arr1 = s1.split("\\.");
24 | String[] arr2 = s2.split("\\.");
25 | int len = Math.max(arr1.length, arr2.length);
26 |
27 | for (int i = 0; i < len; i++) {
28 | int n1 = i < arr1.length ? parseIntSafely(arr1[i]) : 0;
29 | int n2 = i < arr2.length ? parseIntSafely(arr2[i]) : 0;
30 | if (n1 != n2) {
31 | return Integer.compare(n1, n2);
32 | }
33 | }
34 | return 0;
35 | }
36 |
37 | /**
38 | * 移除版本号前缀(如 'v')
39 | */
40 | private static String removeVersionPrefix(String version) {
41 | return version.startsWith("v") ? version.substring(1) : version;
42 | }
43 |
44 | /**
45 | * 安全解析整数(移除非数字字符)
46 | */
47 | private static int parseIntSafely(String s) {
48 | try {
49 | return Integer.parseInt(s.replaceAll("\\D", ""));
50 | } catch (Exception e) {
51 | return 0;
52 | }
53 | }
54 |
55 | /**
56 | * 判断版本1是否大于版本2
57 | */
58 | public static boolean isNewer(String v1, String v2) {
59 | return compare(v1, v2) > 0;
60 | }
61 | }
62 |
63 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/service/http/ssl/SSLException.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.service.http.ssl;
2 |
3 | /**
4 | * SSL相关异常的统一封装
5 | */
6 | public class SSLException extends RuntimeException {
7 |
8 | private final SSLErrorType errorType;
9 |
10 | public SSLException(String message, SSLErrorType errorType) {
11 | super(message);
12 | this.errorType = errorType;
13 | }
14 |
15 | public SSLException(String message, Throwable cause, SSLErrorType errorType) {
16 | super(message, cause);
17 | this.errorType = errorType;
18 | }
19 |
20 | public SSLErrorType getErrorType() {
21 | return errorType;
22 | }
23 |
24 | /**
25 | * SSL错误类型枚举
26 | */
27 | public enum SSLErrorType {
28 | /** 证书过期 */
29 | CERTIFICATE_EXPIRED("Certificate expired"),
30 | /** 证书未生效 */
31 | CERTIFICATE_NOT_YET_VALID("Certificate not yet valid"),
32 | /** 不受信任的证书 */
33 | UNTRUSTED_CERTIFICATE("Untrusted certificate"),
34 | /** 主机名不匹配 */
35 | HOSTNAME_MISMATCH("Hostname mismatch"),
36 | /** 证书链不完整 */
37 | INCOMPLETE_CHAIN("Incomplete certificate chain"),
38 | /** 自签名证书 */
39 | SELF_SIGNED_CERTIFICATE("Self-signed certificate"),
40 | /** 证书路径验证失败 */
41 | PATH_VALIDATION_FAILED("Certificate path validation failed"),
42 | /** SSL配置错误 */
43 | CONFIGURATION_ERROR("SSL configuration error"),
44 | /** 未知SSL错误 */
45 | UNKNOWN("Unknown SSL error");
46 |
47 | private final String description;
48 |
49 | SSLErrorType(String description) {
50 | this.description = description;
51 | }
52 |
53 | public String getDescription() {
54 | return description;
55 | }
56 | }
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/util/JComponentUtils.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.util;
2 |
3 | import lombok.experimental.UtilityClass;
4 |
5 | import javax.swing.*;
6 | import java.awt.*;
7 |
8 | @UtilityClass
9 | public class JComponentUtils {
10 |
11 | /**
12 | * 超出宽度显示省略号(...)。默认icon/padding宽度为30px,最小宽度为200px。
13 | *
14 | * @param text 原始文本
15 | * @param component 用于计算宽度的组件(如JList、JLabel等)
16 | * @return 适配宽度的文本,超出部分自动省略并加...,否则原文
17 | */
18 | public static String ellipsisText(String text, JComponent component) {
19 | return ellipsisText(text, component, 30, 200);
20 | }
21 |
22 | /**
23 | * 超出宽度显示省略号(...),可自定义icon/padding宽度和最小宽度。
24 | *
25 | * @param text 原始文本
26 | * @param component 用于计算宽度的组件(如JList、JLabel等)
27 | * @param reservedWidth 预留宽度(如icon和padding,单位px)在计算文本最大宽度时,会减去30像素,剩下的空间才用于显示文本
28 | * @param minWidth 最小宽度(单位px),表示即使组件宽度为0,也假定有200像素可用来显示文本。
29 | * @return 适配宽度的文本,超出部分自动省略并加...,否则原文
30 | */
31 | public static String ellipsisText(String text, JComponent component, int reservedWidth, int minWidth) {
32 | if (text == null) return null;
33 | FontMetrics fm = component.getFontMetrics(component.getFont()); // 获取字体度量
34 | int maxWidth = component.getWidth() > 0 ? component.getWidth() - reservedWidth : minWidth; // 预留icon和padding
35 | if (maxWidth <= 0) return text;
36 | String ellipsis = "...";
37 | int width = fm.stringWidth(text);
38 | if (width <= maxWidth) return text;
39 | for (int i = text.length() - 1; i > 0; i--) {
40 | String sub = text.substring(0, i) + ellipsis;
41 | if (fm.stringWidth(sub) <= maxWidth) {
42 | return sub;
43 | }
44 | }
45 | return text;
46 | }
47 | }
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## 📝 PR 描述 | Description
2 |
3 |
4 |
5 | ## 🔗 相关 Issue | Related Issue
6 |
7 |
8 | Fixes #(issue number)
9 |
10 | ## 🎯 改动类型 | Type of Change
11 |
12 |
13 |
14 | - [ ] 🐛 Bug 修复 | Bug fix
15 | - [ ] ✨ 新功能 | New feature
16 | - [ ] 📝 文档更新 | Documentation update
17 | - [ ] 🎨 代码优化 | Code refactoring
18 | - [ ] ⚡️ 性能优化 | Performance improvement
19 | - [ ] 🔧 配置变更 | Configuration change
20 | - [ ] 🧪 测试相关 | Test related
21 | - [ ] 🔨 构建相关 | Build related
22 | - [ ] 🌐 国际化 | Internationalization
23 |
24 | ## 📋 改动内容 | Changes Made
25 |
26 |
27 |
28 | -
29 |
30 | ## 🧪 测试 | Testing
31 |
32 |
33 |
34 | - [ ] 本地编译通过 | Local build passed
35 | - [ ] 功能测试通过 | Functional tests passed
36 | - [ ] 在以下环境测试 | Tested on:
37 | - [ ] Windows
38 | - [ ] macOS
39 | - [ ] Linux
40 |
41 | ## 📸 截图 | Screenshots
42 |
43 |
44 |
45 | ## ✅ 检查清单 | Checklist
46 |
47 |
48 |
49 | - [ ] 代码遵循项目编码规范 | Code follows the project's coding standards
50 | - [ ] 已添加必要的注释 | Added necessary comments
51 | - [ ] 文档已更新(如需要)| Documentation updated (if needed)
52 | - [ ] 没有引入新的警告 | No new warnings introduced
53 | - [ ] 改动不影响现有功能 | Changes do not affect existing functionality
54 | - [ ] 已在本地完整测试 | Fully tested locally
55 |
56 | ## 💡 其他说明 | Additional Notes
57 |
58 |
59 |
60 | ---
61 |
62 | 感谢你的贡献! | Thank you for your contribution! 🎉
63 |
64 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/AuthType.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import com.laker.postman.util.I18nUtil;
4 | import com.laker.postman.util.MessageKeys;
5 | import lombok.Getter;
6 |
7 | /**
8 | * Authentication type enum
9 | */
10 | @Getter
11 | public enum AuthType {
12 | INHERIT("Inherit auth from parent", MessageKeys.AUTH_TYPE_INHERIT),
13 | NONE("No Auth", MessageKeys.AUTH_TYPE_NONE),
14 | BASIC("Basic Auth", MessageKeys.AUTH_TYPE_BASIC),
15 | BEARER("Bearer Token", MessageKeys.AUTH_TYPE_BEARER);
16 |
17 | private final String constant;
18 | private final String i18nKey;
19 |
20 | AuthType(String constant, String i18nKey) {
21 | this.constant = constant;
22 | this.i18nKey = i18nKey;
23 | }
24 |
25 | public String getDisplayText() {
26 | return I18nUtil.getMessage(i18nKey);
27 | }
28 |
29 | /**
30 | * Get AuthType from constant string
31 | */
32 | public static AuthType fromConstant(String constant) {
33 | if (constant == null) {
34 | return INHERIT;
35 | }
36 | for (AuthType type : values()) {
37 | if (type.constant.equals(constant)) {
38 | return type;
39 | }
40 | }
41 | return INHERIT;
42 | }
43 |
44 | /**
45 | * Get AuthType from display text (for backward compatibility)
46 | */
47 | public static AuthType fromDisplayText(String displayText) {
48 | if (displayText == null) {
49 | return INHERIT;
50 | }
51 | for (AuthType type : values()) {
52 | if (type.getDisplayText().equals(displayText)) {
53 | return type;
54 | }
55 | }
56 | return INHERIT;
57 | }
58 |
59 | @Override
60 | public String toString() {
61 | return getDisplayText();
62 | }
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # 工作流名称:CodeQL 代码安全分析
2 | # CodeQL 是什么?
3 | # - GitHub 的代码安全扫描工具,用于自动检测代码中的安全漏洞和编码错误
4 | # - 支持多种编程语言(Java、JavaScript、Python、C/C++等)
5 | # - 可以发现:SQL 注入、XSS、路径遍历、不安全的反序列化等安全问题
6 | name: "CodeQL"
7 |
8 | # 触发条件
9 | on:
10 | # 当代码推送到 master 分支时触发(检测新代码的安全问题)
11 | push:
12 | branches: [ master ]
13 | # 当创建针对 master 分支的 PR 时触发(在合并前发现安全问题)
14 | pull_request:
15 | branches: [ master ]
16 | # 定时扫描:每周日 UTC 02:00(北京时间 10:00)运行一次
17 | # 作用:即使代码没有变化,也能检测到新发现的安全漏洞模式
18 | schedule:
19 | - cron: '0 2 * * 0'
20 |
21 | # 权限设置:允许将安全问题写入 GitHub Security 标签页
22 | permissions:
23 | security-events: write
24 | # 添加 actions 读取权限(某些环境需要)
25 | actions: read
26 | # 添加内容读取权限
27 | contents: read
28 |
29 | jobs:
30 | analyze:
31 | name: Analyze (CodeQL)
32 | runs-on: ubuntu-latest
33 |
34 | # 超时设置:避免分析任务运行过久
35 | timeout-minutes: 360
36 |
37 | steps:
38 | # 第一步:检出代码仓库
39 | - name: Checkout repository
40 | uses: actions/checkout@v4
41 |
42 | # 第二步:初始化 CodeQL 分析环境
43 | - name: Initialize CodeQL
44 | uses: github/codeql-action/init@v3
45 | with:
46 | # 指定要分析的编程语言
47 | languages: java
48 | # 查询级别:security-extended(推荐)
49 | # - default: 基础安全检查
50 | # - security-extended: 扩展安全检查(推荐,覆盖更多安全问题)
51 | # - security-and-quality: 安全+代码质量检查(最全面但耗时更长)
52 | queries: security-extended
53 |
54 | # 第三步:自动构建项目(CodeQL 需要编译后的代码进行分析)
55 | # 对于 Java/Maven 项目,会自动执行 mvn compile
56 | - name: Autobuild
57 | uses: github/codeql-action/autobuild@v3
58 |
59 | # 第四步:执行 CodeQL 安全分析
60 | - name: Perform CodeQL Analysis
61 | uses: github/codeql-action/analyze@v3
62 | with:
63 | # 分析类别标识(用于在 Security 标签页中区分不同类型的扫描)
64 | category: "security"
65 | # 上传结果到 GitHub Security 标签页
66 | # 可以在仓库的 Security -> Code scanning alerts 中查看发现的问题
67 |
68 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/service/update/changelog/ChangelogService.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.service.update.changelog;
2 |
3 | import cn.hutool.json.JSONArray;
4 | import com.laker.postman.service.update.source.UpdateSource;
5 | import com.laker.postman.service.update.source.UpdateSourceSelector;
6 | import lombok.Getter;
7 | import lombok.extern.slf4j.Slf4j;
8 |
9 | /**
10 | * 更新日志服务 - 负责获取和格式化更新日志
11 | */
12 | @Slf4j
13 | public class ChangelogService {
14 |
15 | /**
16 | * -- GETTER --
17 | * 获取更新源选择器(用于获取 Web URL)
18 | */
19 | @Getter
20 | private final UpdateSourceSelector sourceSelector;
21 | private final ChangelogFormatter formatter;
22 | private static final int DEFAULT_LIMIT = 10;
23 |
24 | public ChangelogService() {
25 | this.sourceSelector = new UpdateSourceSelector();
26 | this.formatter = new ChangelogFormatter();
27 | }
28 |
29 | /**
30 | * 获取格式化的更新日志
31 | *
32 | * @return 格式化后的更新日志文本,失败返回 null
33 | */
34 | public String getChangelog() {
35 | return getChangelog(DEFAULT_LIMIT);
36 | }
37 |
38 | /**
39 | * 获取格式化的更新日志
40 | *
41 | * @param limit 限制返回的版本数量
42 | * @return 格式化后的更新日志文本,失败返回 null
43 | */
44 | public String getChangelog(int limit) {
45 | // 选择最佳更新源
46 | UpdateSource primarySource = sourceSelector.selectBestSource();
47 | JSONArray releases = primarySource.fetchAllReleases(limit);
48 |
49 | if (releases == null) {
50 | // 尝试备用源
51 | UpdateSource fallbackSource = sourceSelector.getFallbackSource(primarySource);
52 | log.info("Primary source failed, trying fallback source: {}", fallbackSource.getName());
53 | releases = fallbackSource.fetchAllReleases(limit);
54 | }
55 |
56 | if (releases == null) {
57 | return null;
58 | }
59 |
60 | return formatter.format(releases);
61 | }
62 |
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/UpdateInfo.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import cn.hutool.json.JSONObject;
4 | import lombok.Data;
5 |
6 | /**
7 | * 更新信息封装类
8 | */
9 | @Data
10 | public class UpdateInfo {
11 |
12 | public enum Status {
13 | UPDATE_AVAILABLE, // 有新版本可用
14 | NO_UPDATE, // 没有更新
15 | CHECK_FAILED // 检查失败
16 | }
17 |
18 | private final Status status;
19 | private final String currentVersion;
20 | private final String latestVersion;
21 | private final String message;
22 | private final JSONObject releaseInfo;
23 |
24 | private UpdateInfo(Status status, String currentVersion, String latestVersion, String message, JSONObject releaseInfo) {
25 | this.status = status;
26 | this.currentVersion = currentVersion;
27 | this.latestVersion = latestVersion;
28 | this.message = message;
29 | this.releaseInfo = releaseInfo;
30 | }
31 |
32 | /**
33 | * 创建有更新可用的信息
34 | */
35 | public static UpdateInfo updateAvailable(String currentVersion, String latestVersion, JSONObject releaseInfo) {
36 | return new UpdateInfo(Status.UPDATE_AVAILABLE, currentVersion, latestVersion,
37 | "New version available: " + latestVersion, releaseInfo);
38 | }
39 |
40 | /**
41 | * 创建无更新的信息
42 | */
43 | public static UpdateInfo noUpdateAvailable(String message) {
44 | return new UpdateInfo(Status.NO_UPDATE, null, null, message, null);
45 | }
46 |
47 | /**
48 | * 创建检查失败的信息
49 | */
50 | public static UpdateInfo checkFailed(String errorMessage) {
51 | return new UpdateInfo(Status.CHECK_FAILED, null, null, errorMessage, null);
52 | }
53 |
54 | /**
55 | * 是否有更新可用
56 | */
57 | public boolean isUpdateAvailable() {
58 | return status == Status.UPDATE_AVAILABLE;
59 | }
60 |
61 | /**
62 | * 检查是否失败
63 | */
64 | public boolean isCheckFailed() {
65 | return status == Status.CHECK_FAILED;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/HttpFormData.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Data;
5 | import lombok.NoArgsConstructor;
6 |
7 | import java.io.Serializable;
8 |
9 | /**
10 | * Form-Data Parameter model with enabled state
11 | * Supports both text and file types
12 | */
13 | @Data
14 | @NoArgsConstructor
15 | @AllArgsConstructor
16 | public class HttpFormData implements Serializable {
17 | private static final long serialVersionUID = 1L;
18 |
19 | /**
20 | * Type constants for form-data
21 | */
22 | public static final String TYPE_TEXT = "Text";
23 | public static final String TYPE_FILE = "File";
24 |
25 | private boolean enabled = true;
26 | private String key = "";
27 | private String type = TYPE_TEXT; // "Text" or "File"
28 | private String value = "";
29 |
30 | /**
31 | * Normalize the type to ensure consistent capitalization
32 | *
33 | * @param type the type string (case-insensitive)
34 | * @return normalized type ("Text" or "File")
35 | */
36 | public static String normalizeType(String type) {
37 | if (type == null || type.trim().isEmpty()) {
38 | return TYPE_TEXT;
39 | }
40 | String normalized = type.trim();
41 | if (TYPE_FILE.equalsIgnoreCase(normalized)) {
42 | return TYPE_FILE;
43 | }
44 | return TYPE_TEXT;
45 | }
46 |
47 | /**
48 | * Check if this is a text type form-data
49 | *
50 | * @return true if type is Text
51 | */
52 | public boolean isText() {
53 | return TYPE_TEXT.equalsIgnoreCase(type);
54 | }
55 |
56 | /**
57 | * Check if this is a file type form-data
58 | *
59 | * @return true if type is File
60 | */
61 | public boolean isFile() {
62 | return TYPE_FILE.equalsIgnoreCase(type);
63 | }
64 |
65 | /**
66 | * Set the type with automatic normalization
67 | */
68 | public void setType(String type) {
69 | this.type = normalizeType(type);
70 | }
71 | }
72 |
73 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/HttpEventInfo.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import lombok.Data;
4 | import okhttp3.Protocol;
5 |
6 | import java.security.cert.Certificate;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | /**
11 | * 采集 HTTP 全流程事件信息
12 | */
13 | @Data
14 | public class HttpEventInfo {
15 | // 连接信息
16 | private String localAddress;
17 | private String remoteAddress;
18 | // 各阶段时间戳
19 | private long queueStart; // newCall前的时间戳 自己额外定义的发起请求时间
20 | private long callStart;
21 | private long proxySelectStart;
22 | private long proxySelectEnd;
23 | private long dnsStart;
24 | private long dnsEnd;
25 | private long connectStart;
26 | private long secureConnectStart;
27 | private long secureConnectEnd;
28 | private long connectEnd;
29 | private long connectionAcquired;
30 | private long requestHeadersStart;
31 | private long requestHeadersEnd;
32 | private long requestBodyStart;
33 | private long requestBodyEnd;
34 | private long responseHeadersStart;
35 | private long responseHeadersEnd;
36 | private long responseBodyStart;
37 | private long responseBodyEnd;
38 | private long connectionReleased;
39 | private long callEnd;
40 | private long callFailed;
41 | private long canceled;
42 |
43 | // 耗时统计
44 | private long queueingCost; // 排队耗时
45 | private long stalledCost; // 阻塞耗时
46 | // 协议
47 | private Protocol protocol;
48 | // TLS/证书
49 | private List peerCertificates = new ArrayList<>();
50 | private List localCertificates = new ArrayList<>();
51 | private String tlsVersion;
52 | // 加密套件
53 | private String cipherName;
54 | // SSL 证书验证警告信息(如过期、域名不匹配、自签名等)
55 | private String sslCertWarning;
56 | // 异常
57 | private String errorMessage;
58 | private Throwable error;
59 | // 其他
60 | private String threadName;
61 |
62 | private long bodyBytesSent;
63 | private long bodyBytesReceived;
64 | private long headerBytesSent;
65 | private long headerBytesReceived;
66 | }
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # Dependabot 配置文件
2 | #
3 | # Dependabot 是什么?
4 | # - GitHub 的自动依赖更新工具,帮助保持项目依赖的安全性和时效性
5 | # - 自动检测过时的依赖包和安全漏洞(CVE)
6 | # - 自动创建 Pull Request 来更新依赖版本
7 | #
8 | # 主要作用:
9 | # 1. 🔒 安全防护:及时发现并修复依赖包中的安全漏洞
10 | # 2. 📦 版本管理:自动更新依赖到最新版本
11 | # 3. ⏰ 节省时间:无需手动检查和更新依赖
12 | # 4. 📊 可追溯性:通过 PR 记录每次依赖更新
13 | #
14 | # 如何查看?
15 | # - 仓库 Security -> Dependabot alerts:查看安全漏洞警告
16 | # - Pull requests:查看 Dependabot 创建的更新 PR
17 |
18 | version: 2
19 | updates:
20 | # Maven 项目依赖更新配置
21 | - package-ecosystem: "maven"
22 | # 扫描目录:根目录的 pom.xml
23 | directory: "/"
24 | # 更新频率:每周检查一次
25 | # 可选值:daily(每天)、weekly(每周)、monthly(每月)
26 | schedule:
27 | interval: "weekly"
28 | # 可选:指定检查时间(UTC 时区)
29 | # day: "monday"
30 | # time: "02:00"
31 |
32 | # 限制同时打开的 PR 数量,避免一次性创建太多 PR
33 | # 推荐值:3-10,根据项目活跃度调整
34 | open-pull-requests-limit: 3
35 |
36 | # 允许更新的依赖类型
37 | # - dependency-type: "all" 表示更新所有依赖(直接依赖和间接依赖)
38 | # - 其他选项:production(生产依赖)、development(开发依赖)
39 | allow:
40 | - dependency-type: "all"
41 |
42 | # 可选:将 Dependabot PR 自动分配给特定审核人员
43 | # reviewers:
44 | # - "your-github-username"
45 |
46 | # 可选:为 Dependabot PR 自动添加标签,便于管理
47 | # labels:
48 | # - "dependencies"
49 | # - "automated"
50 |
51 | # 可选:设置提交信息前缀,便于识别
52 | # commit-message:
53 | # prefix: "chore"
54 | # include: "scope"
55 |
56 | # 忽略某些依赖的更新
57 | # 使用场景:某些依赖版本需要保持稳定,不希望自动更新
58 | ignore:
59 | # 忽略 okhttp 相关依赖的更新
60 | - dependency-name: "com.squareup.okhttp3:okhttp"
61 | - dependency-name: "com.squareup.okhttp3:okhttp-*" # 忽略所有 okhttp 子模块
62 | - dependency-name: "com.squareup.okhttp3:logging-interceptor"
63 | # - dependency-name: "org.example:unwanted"
64 | # update-types: ["version-update:semver-major"] # 忽略主版本更新
65 |
66 | # 注意事项:
67 | # 1. Dependabot 会自动处理 GitHub Advisory 数据库中的安全修复
68 | # 2. 需在仓库设置中启用 "Dependabot alerts" 和 "Dependabot security updates"
69 | # 3. 路径:Settings -> Security -> Code security and analysis
70 | # 4. 安全更新的 PR 会被标记为 "security" 优先级
71 | # 5. 可以手动触发更新:在 Insights -> Dependency graph -> Dependabot 中点击 "Check for updates"
72 |
73 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/service/http/okhttp/LogWebSocketListener.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.service.http.okhttp;
2 |
3 | import com.laker.postman.panel.sidebar.ConsolePanel;
4 | import okhttp3.Response;
5 | import okhttp3.WebSocket;
6 | import okhttp3.WebSocketListener;
7 |
8 | // 日志增强WebSocketListener
9 | public class LogWebSocketListener extends WebSocketListener {
10 | private final WebSocketListener delegate;
11 |
12 | public LogWebSocketListener(WebSocketListener delegate) {
13 | this.delegate = delegate;
14 | }
15 |
16 | @Override
17 | public void onOpen(WebSocket webSocket, Response response) {
18 | ConsolePanel.appendLog("[WebSocket] onOpen: " + response, ConsolePanel.LogType.SUCCESS);
19 | delegate.onOpen(webSocket, response);
20 | }
21 |
22 | @Override
23 | public void onMessage(WebSocket webSocket, String text) {
24 | // ConsolePanel.appendLog("[WebSocket] onMessage: " + text, ConsolePanel.LogType.INFO);
25 | delegate.onMessage(webSocket, text);
26 | }
27 |
28 | @Override
29 | public void onMessage(WebSocket webSocket, okio.ByteString bytes) {
30 | // ConsolePanel.appendLog("[WebSocket] onMessage(bytes): " + bytes.hex(), ConsolePanel.LogType.INFO);
31 | delegate.onMessage(webSocket, bytes);
32 | }
33 |
34 | @Override
35 | public void onClosing(WebSocket webSocket, int code, String reason) {
36 | ConsolePanel.appendLog("[WebSocket] onClosing: code=" + code + ", reason=" + reason, ConsolePanel.LogType.WARN);
37 | delegate.onClosing(webSocket, code, reason);
38 | }
39 |
40 | @Override
41 | public void onClosed(WebSocket webSocket, int code, String reason) {
42 | ConsolePanel.appendLog("[WebSocket] onClosed: code=" + code + ", reason=" + reason, ConsolePanel.LogType.DEBUG);
43 | delegate.onClosed(webSocket, code, reason);
44 | }
45 |
46 | @Override
47 | public void onFailure(WebSocket webSocket, Throwable t, Response response) {
48 | ConsolePanel.appendLog("[WebSocket] onFailure: " + (t != null ? t.getMessage() : "Unknown error"), ConsolePanel.LogType.ERROR);
49 | delegate.onFailure(webSocket, t, response);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/ClientCertificate.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model;
2 |
3 | import lombok.Data;
4 |
5 | import java.io.Serializable;
6 |
7 | /**
8 | * 客户端证书配置
9 | * 用于支持 mTLS (mutual TLS) 认证
10 | */
11 | @Data
12 | public class ClientCertificate implements Serializable {
13 | private String id; // 唯一标识符
14 | private String name; // 证书配置名称(可选)
15 | private String host; // 主机名或域名匹配模式(支持通配符,如 *.example.com)
16 | private int port; // 端口号,0 表示匹配所有端口
17 | private String certPath; // 证书文件路径(PFX/P12 或 PEM 格式)
18 | private String certType; // 证书类型:PFX, PEM
19 | private String certPassword; // 证书密码(用于 PFX/P12)
20 | private String keyPath; // 私钥文件路径(PEM 格式时使用)
21 | private String keyPassword; // 私钥密码(可选)
22 | private boolean enabled; // 是否启用此证书配置
23 | private long createdAt; // 创建时间
24 | private long updatedAt; // 更新时间
25 |
26 | // 证书类型常量
27 | public static final String CERT_TYPE_PFX = "PFX";
28 | public static final String CERT_TYPE_PEM = "PEM";
29 |
30 | public ClientCertificate() {
31 | this.enabled = true;
32 | this.port = 0; // 默认匹配所有端口
33 | this.certType = CERT_TYPE_PFX;
34 | this.createdAt = System.currentTimeMillis();
35 | this.updatedAt = System.currentTimeMillis();
36 | }
37 |
38 | /**
39 | * 检查此证书是否匹配指定的主机和端口
40 | */
41 | public boolean matches(String targetHost, int targetPort) {
42 | if (!enabled) {
43 | return false;
44 | }
45 |
46 | // 检查端口匹配
47 | if (port != 0 && port != targetPort) {
48 | return false;
49 | }
50 |
51 | // 检查主机名匹配
52 | if (host == null || host.trim().isEmpty()) {
53 | return false;
54 | }
55 |
56 | String pattern = host.trim().toLowerCase();
57 | String target = targetHost.toLowerCase();
58 |
59 | // 支持通配符匹配
60 | if (pattern.startsWith("*.")) {
61 | String domain = pattern.substring(2);
62 | // 匹配 "api.example.com" 或 "example.com" 本身
63 | return target.endsWith("." + domain) || target.equals(domain);
64 | }
65 |
66 | // 精确匹配
67 | return pattern.equals(target);
68 | }
69 | }
70 |
71 |
--------------------------------------------------------------------------------
/docs/FQA.MD:
--------------------------------------------------------------------------------
1 | ## OkHttpClient 连接池参数说明
2 |
3 | 在 Easy Postman 的 OkHttpClientManager 中,使用如下方式为每个 baseUri 配置连接池:
4 |
5 | ```java
6 | .connectionPool(new ConnectionPool(maxIdleConnections, keepAliveDuration, TimeUnit.SECONDS))
7 | ```
8 |
9 | ### 参数解释
10 | - **maxIdleConnections**:最大空闲连接数。表示连接池中允许保持空闲状态的最大连接数量。
11 | - **keepAliveDuration**:空闲连接的最大存活时间(单位由第三个参数指定)。超过该时间未被使用的空闲连接将被关闭。
12 | - **TimeUnit.SECONDS**:指定 keepAliveDuration 的时间单位为秒。
13 |
14 | ### maxIdleConnections 详解
15 | maxIdleConnections 控制同一主机(baseUri)下,连接池中最多可以有多少个空闲连接被复用。
16 |
17 | #### 举例说明
18 | 假设 maxIdleConnections 设置为 6:
19 | - 如果你对同一个服务端(如 https://api.example.com)并发发起 10 个请求,连接池最多只会保留 6 个空闲连接。
20 | - 其余的连接在空闲后会被关闭,避免资源浪费。
21 | - 这样可以提升高并发下的性能,同时防止过多无用连接占用系统资源。
22 |
23 | #### 适用场景
24 | - **接口调试/开发环境**:通常 6 足够。
25 | - **性能压测/高并发场景**:可通过 setConnectionPoolConfig 动态调大 maxIdleConnections,例如设置为 50 或 100,以适应更多并发连接需求。
26 |
27 | ### maxIdleConnections 详解(更详细流程说明)
28 |
29 | 假设 maxIdleConnections 设置为 6,keepAliveDuration 设置为 90 秒,场景如下:
30 |
31 | #### 并发发起 10 个请求的全过程
32 |
33 | 1. **请求发起阶段**
34 | 客户端(如 Easy Postman)同时向同一服务端(如 https://api.example.com)并发发起 10 个 HTTP 请求。
35 | OkHttp 会为每个请求分配一个连接(如果连接池中有可用空闲连接,则复用,否则新建连接)。
36 |
37 | 2. **请求处理与连接归还**
38 | 10 个请求处理完毕后,这 10 个连接会被归还到连接池。
39 | 连接池会检查当前空闲连接数是否超过 maxIdleConnections(本例为 6)。
40 |
41 | 3. **空闲连接管理**
42 | 如果归还后空闲连接数超过 6,连接池会立即关闭多余的空闲连接,只保留 6 个。
43 | 哪些连接被保留、哪些被关闭由 OkHttp 内部策略决定(通常是最近最活跃的连接被保留)。
44 |
45 | 4. **keepAliveDuration 的作用**
46 | 被保留的 6 个空闲连接会在连接池中等待复用。
47 | 如果在 keepAliveDuration(如 90 秒)内没有新的请求复用这些连接,这些空闲连接会被自动关闭,释放资源。
48 | 也就是说,**maxIdleConnections** 决定了“最多能保留多少个空闲连接”,**keepAliveDuration** 决定了“空闲连接最多能保留多久”。
49 |
50 | #### 关键参数的作用
51 |
52 | - **maxIdleConnections**:决定连接池中最多能保留多少个空闲连接。超过这个数,多余的空闲连接会被立即关闭。
53 | - **keepAliveDuration**:决定空闲连接最多能保留多长时间。超过这个时间未被复用的空闲连接会被自动关闭。
54 |
55 | #### 过程示意
56 |
57 | 1. 并发 10 个请求,建立 10 个连接。
58 | 2. 请求结束,10 个连接归还到连接池。
59 | 3. 连接池只保留 6 个空闲连接,立即关闭多余的 4 个。
60 | 4. 这 6 个空闲连接在 90 秒内如果没有被新请求复用,也会被自动关闭。
61 |
62 | #### 这样做的好处
63 |
64 | - 避免了大量无用连接长期占用系统资源。
65 | - 保证高并发下的性能和资源利用率。
66 | - 连接池参数可根据实际并发量灵活调整。
67 |
68 | ### 参考
69 | - OkHttp 官方文档:[ConnectionPool](https://square.github.io/okhttp/4.x/okhttp/okhttp3/-connection-pool/)
70 | - Chrome 浏览器默认每 host 6 个连接,Easy Postman 默认采用相同策略。
71 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/panel/performance/component/JMeterTreeCellRenderer.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.panel.performance.component;
2 |
3 | import com.formdev.flatlaf.extras.FlatSVGIcon;
4 | import com.laker.postman.panel.performance.model.JMeterTreeNode;
5 |
6 | import javax.swing.*;
7 | import javax.swing.tree.DefaultMutableTreeNode;
8 | import javax.swing.tree.DefaultTreeCellRenderer;
9 | import java.awt.*;
10 |
11 | public class JMeterTreeCellRenderer extends DefaultTreeCellRenderer {
12 | @Override
13 | public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
14 | JLabel label = (JLabel) super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
15 | DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
16 | Object userObj = node.getUserObject();
17 | if (userObj instanceof JMeterTreeNode jtNode) {
18 | // 设置图标
19 | switch (jtNode.type) {
20 | case THREAD_GROUP -> label.setIcon(new FlatSVGIcon("icons/user-group.svg", 16, 16));
21 | case REQUEST -> label.setIcon(new FlatSVGIcon("icons/http.svg", 16, 16));
22 | case ASSERTION -> label.setIcon(new FlatSVGIcon("icons/warning.svg", 16, 16));
23 | case TIMER -> label.setIcon(new FlatSVGIcon("icons/time.svg", 16, 16));
24 | case ROOT -> label.setIcon(new FlatSVGIcon("icons/computer.svg", 16, 16));
25 | }
26 |
27 | // 根据启用状态设置样式
28 | String text = jtNode.name;
29 | Font font = label.getFont();
30 |
31 | if (!jtNode.enabled) {
32 | Color disabledColor = new Color(150, 150, 150);
33 | if (!sel) {
34 | label.setForeground(disabledColor);
35 | }
36 | label.setFont(font.deriveFont(Font.ITALIC));
37 | label.setText("" + text + "");
38 | } else {
39 | // 启用状态:恢复正常样式
40 | label.setFont(font.deriveFont(Font.PLAIN));
41 | label.setText(text);
42 | }
43 | }
44 | return label;
45 | }
46 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/panel/performance/model/JMeterTreeNode.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.panel.performance.model;
2 |
3 | import com.laker.postman.model.HttpRequestItem;
4 | import com.laker.postman.panel.performance.assertion.AssertionData;
5 | import com.laker.postman.panel.performance.threadgroup.ThreadGroupData;
6 | import com.laker.postman.panel.performance.timer.TimerData;
7 |
8 | public class JMeterTreeNode {
9 | public String name;
10 | public NodeType type;
11 | public HttpRequestItem httpRequestItem; // 仅REQUEST节点用
12 | public ThreadGroupData threadGroupData; // 线程组数据
13 | public AssertionData assertionData; // 断言数据
14 | public TimerData timerData; // 定时器数据
15 | public boolean enabled = true; // 是否启用,默认启用
16 |
17 | public JMeterTreeNode(String name, NodeType type) {
18 | this.name = name;
19 | this.type = type;
20 | }
21 |
22 | public JMeterTreeNode(String name, NodeType type, Object data) {
23 | this.name = name;
24 | this.type = type;
25 | switch (type) {
26 | case THREAD_GROUP -> this.threadGroupData = (ThreadGroupData) data;
27 | case REQUEST -> this.httpRequestItem = (HttpRequestItem) data;
28 | case ASSERTION -> this.assertionData = (AssertionData) data;
29 | case TIMER -> this.timerData = (TimerData) data;
30 | }
31 | }
32 |
33 | public Object getNodeData() {
34 | return switch (type) {
35 | case THREAD_GROUP -> threadGroupData;
36 | case REQUEST -> httpRequestItem;
37 | case ASSERTION -> assertionData;
38 | case TIMER -> timerData;
39 | default -> null;
40 | };
41 | }
42 |
43 | public void setNodeData(Object data) {
44 | switch (type) {
45 | case THREAD_GROUP -> this.threadGroupData = (ThreadGroupData) data;
46 | case REQUEST -> this.httpRequestItem = (com.laker.postman.model.HttpRequestItem) data;
47 | case ASSERTION -> this.assertionData = (AssertionData) data;
48 | case TIMER -> this.timerData = (TimerData) data;
49 | }
50 | }
51 |
52 | @Override
53 | public String toString() {
54 | return name;
55 | }
56 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/service/collections/RequestsTabsService.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.service.collections;
2 |
3 | import cn.hutool.core.text.CharSequenceUtil;
4 | import com.laker.postman.common.SingletonFactory;
5 | import com.laker.postman.common.component.tab.ClosableTabComponent;
6 | import com.laker.postman.model.HttpRequestItem;
7 | import com.laker.postman.panel.collections.right.RequestEditPanel;
8 | import com.laker.postman.panel.collections.right.request.RequestEditSubPanel;
9 | import com.laker.postman.util.I18nUtil;
10 | import com.laker.postman.util.MessageKeys;
11 | import lombok.extern.slf4j.Slf4j;
12 |
13 | import javax.swing.*;
14 | import java.awt.*;
15 |
16 | @Slf4j
17 | public class RequestsTabsService {
18 |
19 | private RequestsTabsService() {
20 | // no-op
21 | }
22 |
23 |
24 | public static RequestEditSubPanel addTab(HttpRequestItem item) {
25 | String id = item.getId();
26 | if (id == null || id.isEmpty()) {
27 | throw new IllegalArgumentException("Request item ID cannot be null or empty");
28 | }
29 | RequestEditSubPanel subPanel = new RequestEditSubPanel(id, item.getProtocol());
30 | subPanel.initPanelData(item);
31 | String tabTitle = CharSequenceUtil.isNotBlank(item.getName()) ? item.getName() : I18nUtil.getMessage(MessageKeys.NEW_REQUEST);
32 | JTabbedPane tabbedPane = SingletonFactory.getInstance(RequestEditPanel.class).getTabbedPane();
33 | tabbedPane.addTab(tabTitle, subPanel);
34 | tabbedPane.setTabComponentAt(tabbedPane.getTabCount() - 1, new ClosableTabComponent(tabTitle, item.getProtocol()));
35 | tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
36 | return subPanel;
37 | }
38 |
39 | public static void updateTabNew(RequestEditSubPanel panel, boolean isNew) {
40 | JTabbedPane tabbedPane = SingletonFactory.getInstance(RequestEditPanel.class).getTabbedPane();
41 | int idx = tabbedPane.indexOfComponent(panel);
42 | if (idx < 0) return;
43 | Component tabComp = tabbedPane.getTabComponentAt(idx);
44 | if (tabComp instanceof ClosableTabComponent closable) {
45 | closable.setNewRequest(isNew);
46 | }
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/util/JsonPathUtil.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.util;
2 |
3 | import cn.hutool.json.JSONArray;
4 | import cn.hutool.json.JSONObject;
5 | import cn.hutool.json.JSONUtil;
6 | import lombok.experimental.UtilityClass;
7 |
8 | @UtilityClass
9 | public class JsonPathUtil {
10 |
11 | /**
12 | * 从JSON对象中提取指定路径的值,支持简单的$.a.b[0].c格式
13 | *
14 | * @param jsonObj 可以是JSONObject/JSONArray/String
15 | * @param path 形如$.a.b[0].c
16 | * @return 提取到的值,找不到返回null
17 | */
18 | public static String extractJsonPath(Object jsonObj, String path) {
19 | if (jsonObj == null || path == null || path.isEmpty()) return null;
20 | if (jsonObj instanceof String str) {
21 | if (!JSONUtil.isTypeJSON(str)) return null;
22 | jsonObj = JSONUtil.parse(str);
23 | }
24 | if (path.startsWith("$.")) path = path.substring(2);
25 | String[] segments = path.split("\\.");
26 | Object current = jsonObj;
27 | for (String segment : segments) {
28 | if (current == null) return null;
29 | if (segment.contains("[") && segment.contains("]")) {
30 | String arrayName = segment.substring(0, segment.indexOf("["));
31 | String indexStr = segment.substring(segment.indexOf("[") + 1, segment.indexOf("]"));
32 | int index;
33 | try {
34 | index = Integer.parseInt(indexStr);
35 | } catch (Exception e) {
36 | return null;
37 | }
38 | if (current instanceof JSONObject obj) {
39 | Object arr = obj.get(arrayName);
40 | if (arr instanceof JSONArray jsonArr) {
41 | if (index >= 0 && index < jsonArr.size()) {
42 | current = jsonArr.get(index);
43 | } else return null;
44 | } else return null;
45 | } else return null;
46 | } else {
47 | if (current instanceof JSONObject obj) {
48 | current = obj.get(segment);
49 | } else return null;
50 | }
51 | }
52 | return current != null ? current.toString() : null;
53 | }
54 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/common/component/list/EnvironmentListCellRenderer.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.common.component.list;
2 |
3 | import com.formdev.flatlaf.extras.FlatSVGIcon;
4 | import com.laker.postman.model.Environment;
5 | import com.laker.postman.model.EnvironmentItem;
6 | import com.laker.postman.service.EnvironmentService;
7 |
8 | import javax.swing.*;
9 | import java.awt.*;
10 |
11 | import static com.laker.postman.util.JComponentUtils.ellipsisText;
12 |
13 | // 环境列表渲染器
14 | public class EnvironmentListCellRenderer extends DefaultListCellRenderer {
15 | @Override
16 | public Component getListCellRendererComponent(JList> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
17 | JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
18 | if (value instanceof EnvironmentItem item) {
19 | String envName = item.getEnvironment().getName(); // 获取环境名称
20 | label.setText(ellipsisText(envName, list)); // 超出宽度显示省略号
21 | label.setToolTipText(envName); // 超出显示tip
22 | Environment active = EnvironmentService.getActiveEnvironment();
23 |
24 | // 判断是否是激活环境
25 | boolean isActive = active != null && active.getId().equals(item.getEnvironment().getId());
26 |
27 | // 设置图标和样式
28 | if (isActive) {
29 | label.setIcon(new FlatSVGIcon("icons/check.svg", 16, 16));
30 | // 激活环境使用加粗字体
31 | label.setFont(label.getFont().deriveFont(Font.BOLD));
32 | } else {
33 | label.setIcon(new FlatSVGIcon("icons/nocheck.svg", 16, 16));
34 | // 普通环境使用常规字体
35 | label.setFont(label.getFont().deriveFont(Font.PLAIN));
36 | }
37 |
38 | // 设置边距和对齐
39 | label.setBorder(BorderFactory.createEmptyBorder(4, 8, 4, 8));
40 | label.setIconTextGap(10); // 增大icon和文字间距
41 | label.setHorizontalAlignment(SwingConstants.LEFT);
42 | label.setHorizontalTextPosition(SwingConstants.RIGHT);
43 |
44 | // 选中状态的样式增强
45 | if (isSelected) {
46 | label.setOpaque(true);
47 | }
48 | }
49 |
50 | return label;
51 | }
52 |
53 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/panel/collections/right/request/sub/SyntaxType.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.panel.collections.right.request.sub;
2 |
3 | import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
4 |
5 | /**
6 | * 语法类型枚举,统一管理语法高亮类型
7 | * 添加新的语法类型只需在此枚举中添加一项即可
8 | */
9 | public enum SyntaxType {
10 | AUTO_DETECT("Auto Detect", null),
11 | JSON("JSON", SyntaxConstants.SYNTAX_STYLE_JSON),
12 | XML("XML", SyntaxConstants.SYNTAX_STYLE_XML),
13 | HTML("HTML", SyntaxConstants.SYNTAX_STYLE_HTML),
14 | JAVASCRIPT("JavaScript", SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT),
15 | CSS("CSS", SyntaxConstants.SYNTAX_STYLE_CSS),
16 | PLAIN_TEXT("Plain Text", SyntaxConstants.SYNTAX_STYLE_NONE);
17 |
18 | private final String displayName;
19 | private final String syntaxStyle;
20 |
21 | SyntaxType(String displayName, String syntaxStyle) {
22 | this.displayName = displayName;
23 | this.syntaxStyle = syntaxStyle;
24 | }
25 |
26 | public String getDisplayName() {
27 | return displayName;
28 | }
29 |
30 | public String getSyntaxStyle() {
31 | return syntaxStyle;
32 | }
33 |
34 | /**
35 | * 获取所有显示名称数组(用于下拉框)
36 | */
37 | public static String[] getDisplayNames() {
38 | SyntaxType[] values = values();
39 | String[] names = new String[values.length];
40 | for (int i = 0; i < values.length; i++) {
41 | names[i] = values[i].displayName;
42 | }
43 | return names;
44 | }
45 |
46 | /**
47 | * 根据索引获取语法类型
48 | */
49 | public static SyntaxType getByIndex(int index) {
50 | SyntaxType[] values = values();
51 | if (index >= 0 && index < values.length) {
52 | return values[index];
53 | }
54 | return AUTO_DETECT;
55 | }
56 |
57 | /**
58 | * 根据语法样式获取语法类型
59 | */
60 | public static SyntaxType getBySyntaxStyle(String syntaxStyle) {
61 | if (syntaxStyle == null) {
62 | return AUTO_DETECT;
63 | }
64 | for (SyntaxType type : values()) {
65 | if (syntaxStyle.equals(type.syntaxStyle)) {
66 | return type;
67 | }
68 | }
69 | return AUTO_DETECT;
70 | }
71 |
72 | /**
73 | * 获取索引位置
74 | */
75 | public int getIndex() {
76 | return ordinal();
77 | }
78 | }
79 |
80 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/service/http/ssl/CertificateErrorParser.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.service.http.ssl;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 |
5 | import java.security.cert.CertificateException;
6 |
7 | /**
8 | * 证书错误信息解析器
9 | * 负责将各种证书异常转换为易读的错误信息
10 | */
11 | @Slf4j
12 | public class CertificateErrorParser {
13 |
14 | private CertificateErrorParser() {
15 | // 工具类,隐藏构造函数
16 | }
17 |
18 | /**
19 | * 解析证书异常,返回错误类型
20 | */
21 | public static SSLException.SSLErrorType parseErrorType(CertificateException e) {
22 | String message = e.getMessage();
23 | if (message == null) {
24 | return SSLException.SSLErrorType.UNKNOWN;
25 | }
26 |
27 | String lowerMessage = message.toLowerCase();
28 |
29 | if (lowerMessage.contains("certificate expired") || lowerMessage.contains("notafter")) {
30 | return SSLException.SSLErrorType.CERTIFICATE_EXPIRED;
31 | } else if (lowerMessage.contains("notbefore") || lowerMessage.contains("not yet valid")) {
32 | return SSLException.SSLErrorType.CERTIFICATE_NOT_YET_VALID;
33 | } else if (lowerMessage.contains("unable to find valid certification path") ||
34 | lowerMessage.contains("pkix path building failed")) {
35 | return SSLException.SSLErrorType.UNTRUSTED_CERTIFICATE;
36 | } else if (lowerMessage.contains("pkix path validation failed")) {
37 | return SSLException.SSLErrorType.PATH_VALIDATION_FAILED;
38 | }
39 |
40 | return SSLException.SSLErrorType.UNKNOWN;
41 | }
42 |
43 | /**
44 | * 提取用户友好的错误信息
45 | */
46 | public static String extractUserFriendlyMessage(CertificateException e) {
47 | SSLException.SSLErrorType errorType = parseErrorType(e);
48 |
49 | return switch (errorType) {
50 | case CERTIFICATE_EXPIRED -> "Certificate has expired";
51 | case CERTIFICATE_NOT_YET_VALID -> "Certificate is not yet valid";
52 | case UNTRUSTED_CERTIFICATE -> "Certificate is not trusted (untrusted root certificate)";
53 | case PATH_VALIDATION_FAILED -> "Certificate path validation failed";
54 | default -> {
55 | String message = e.getMessage();
56 | yield message != null ? message : "Certificate validation failed";
57 | }
58 | };
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/util/XmlUtil.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.util;
2 |
3 | import lombok.experimental.UtilityClass;
4 | import org.w3c.dom.Document;
5 | import javax.xml.parsers.DocumentBuilder;
6 | import javax.xml.parsers.DocumentBuilderFactory;
7 | import javax.xml.transform.OutputKeys;
8 | import javax.xml.transform.Transformer;
9 | import javax.xml.transform.TransformerFactory;
10 | import javax.xml.transform.dom.DOMSource;
11 | import javax.xml.transform.stream.StreamResult;
12 | import java.io.StringReader;
13 | import java.io.StringWriter;
14 | import org.xml.sax.InputSource;
15 |
16 | @UtilityClass
17 | public class XmlUtil {
18 | /**
19 | * 判断字符串是否为合法 XML
20 | */
21 | public static boolean isXml(String str) {
22 | if (str == null || str.trim().isEmpty()) return false;
23 | try {
24 | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
25 | factory.setNamespaceAware(true);
26 | DocumentBuilder builder = factory.newDocumentBuilder();
27 | builder.parse(new InputSource(new StringReader(str)));
28 | return true;
29 | } catch (Exception e) {
30 | return false;
31 | }
32 | }
33 |
34 | /**
35 | * 格式化 XML 字符串,避免重复插入空白行
36 | */
37 | public static String formatXml(String xml) {
38 | if (!isXml(xml)) return xml;
39 | try {
40 | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
41 | factory.setIgnoringElementContentWhitespace(true);
42 | DocumentBuilder builder = factory.newDocumentBuilder();
43 | Document document = builder.parse(new InputSource(new StringReader(xml)));
44 |
45 | TransformerFactory tf = TransformerFactory.newInstance();
46 | Transformer transformer = tf.newTransformer();
47 | transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
48 | transformer.setOutputProperty(OutputKeys.INDENT, "yes");
49 | transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
50 |
51 | StringWriter writer = new StringWriter();
52 | transformer.transform(new DOMSource(document), new StreamResult(writer));
53 | // 去除多余空白行
54 | String result = writer.toString().replaceAll("(?m)^\s*$", "");
55 | return result.trim();
56 | } catch (Exception e) {
57 | return xml;
58 | }
59 | }
60 | }
61 |
62 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/service/http/ssl/SSLValidationResult.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.service.http.ssl;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collections;
5 | import java.util.List;
6 |
7 | /**
8 | * SSL验证结果封装类
9 | */
10 | public class SSLValidationResult {
11 | private final boolean valid;
12 | private final List warnings;
13 | private final List errors;
14 |
15 | private SSLValidationResult(boolean valid, List warnings, List errors) {
16 | this.valid = valid;
17 | this.warnings = warnings != null ? new ArrayList<>(warnings) : new ArrayList<>();
18 | this.errors = errors != null ? new ArrayList<>(errors) : new ArrayList<>();
19 | }
20 |
21 | public static SSLValidationResult success() {
22 | return new SSLValidationResult(true, Collections.emptyList(), Collections.emptyList());
23 | }
24 |
25 | public static SSLValidationResult successWithWarnings(List warnings) {
26 | return new SSLValidationResult(true, warnings, Collections.emptyList());
27 | }
28 |
29 | public static SSLValidationResult failure(List errors) {
30 | return new SSLValidationResult(false, Collections.emptyList(), errors);
31 | }
32 |
33 | public static SSLValidationResult failureWithWarnings(List errors, List warnings) {
34 | return new SSLValidationResult(false, warnings, errors);
35 | }
36 |
37 | public boolean isValid() {
38 | return valid;
39 | }
40 |
41 | public boolean hasWarnings() {
42 | return !warnings.isEmpty();
43 | }
44 |
45 | public boolean hasErrors() {
46 | return !errors.isEmpty();
47 | }
48 |
49 | public List getWarnings() {
50 | return Collections.unmodifiableList(warnings);
51 | }
52 |
53 | public List getErrors() {
54 | return Collections.unmodifiableList(errors);
55 | }
56 |
57 | public String getSummary() {
58 | List messages = new ArrayList<>();
59 | if (!errors.isEmpty()) {
60 | messages.addAll(errors);
61 | }
62 | if (!warnings.isEmpty()) {
63 | messages.addAll(warnings);
64 | }
65 | return String.join("; ", messages);
66 | }
67 |
68 | @Override
69 | public String toString() {
70 | return "SSLValidationResult{valid=" + valid +
71 | ", warnings=" + warnings.size() +
72 | ", errors=" + errors.size() + "}";
73 | }
74 | }
75 |
76 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/panel/workspace/components/ProgressPanel.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.panel.workspace.components;
2 |
3 | import com.laker.postman.util.FontsUtil;
4 | import com.laker.postman.util.I18nUtil;
5 | import com.laker.postman.util.MessageKeys;
6 | import lombok.Getter;
7 |
8 | import javax.swing.*;
9 | import javax.swing.border.TitledBorder;
10 | import java.awt.*;
11 |
12 | /**
13 | * 进度显示面板公共组件
14 | * 用于显示操作进度和状态信息
15 | */
16 | public class ProgressPanel extends JPanel {
17 |
18 | @Getter
19 | private JProgressBar progressBar;
20 | @Getter
21 | private JLabel statusLabel;
22 |
23 | public ProgressPanel(String title) {
24 | initComponents();
25 | setupLayout(title);
26 | }
27 |
28 | private void initComponents() {
29 | progressBar = new JProgressBar();
30 | progressBar.setStringPainted(true);
31 | progressBar.setString(I18nUtil.getMessage(MessageKeys.PROGRESS_PANEL_READY));
32 |
33 | statusLabel = new JLabel(I18nUtil.getMessage(MessageKeys.PROGRESS_PANEL_FILL_CONFIG));
34 | statusLabel.setFont(FontsUtil.getDefaultFont(Font.ITALIC, 11));
35 | }
36 |
37 | private void setupLayout(String title) {
38 | setLayout(new BorderLayout());
39 | setBorder(BorderFactory.createTitledBorder(
40 | BorderFactory.createEtchedBorder(),
41 | title,
42 | TitledBorder.LEFT,
43 | TitledBorder.TOP,
44 | FontsUtil.getDefaultFont(Font.BOLD, 12)
45 | ));
46 |
47 | // 状态标签
48 | JPanel statusPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
49 | statusPanel.add(statusLabel);
50 | add(statusPanel, BorderLayout.NORTH);
51 |
52 | // 进度条
53 | add(progressBar, BorderLayout.CENTER);
54 | }
55 |
56 | /**
57 | * 更新进度
58 | */
59 | public void updateProgress(int progress, String message) {
60 | progressBar.setValue(progress);
61 | if (message != null) {
62 | statusLabel.setText(message);
63 | }
64 | }
65 |
66 | /**
67 | * 设置进度文本
68 | */
69 | public void setProgressText(String text) {
70 | progressBar.setString(text);
71 | }
72 |
73 | /**
74 | * 重置进度面板状态
75 | */
76 | public void reset() {
77 | progressBar.setValue(0);
78 | progressBar.setString(I18nUtil.getMessage(MessageKeys.PROGRESS_PANEL_READY));
79 | statusLabel.setText(I18nUtil.getMessage(MessageKeys.PROGRESS_PANEL_FILL_CONFIG));
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/panel/performance/threadgroup/ThreadGroupData.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.panel.performance.threadgroup;
2 |
3 | import com.laker.postman.util.I18nUtil;
4 | import com.laker.postman.util.MessageKeys;
5 |
6 | /**
7 | * 线程组数据模型,支持多种线程模式
8 | */
9 | public class ThreadGroupData {
10 | // 线程组类型
11 | public enum ThreadMode {
12 | FIXED(MessageKeys.THREADGROUP_MODE_FIXED), // 固定线程数
13 | RAMP_UP(MessageKeys.THREADGROUP_MODE_RAMP_UP), // 递增线程数
14 | SPIKE(MessageKeys.THREADGROUP_MODE_SPIKE), // 尖刺模式
15 | STAIRS(MessageKeys.THREADGROUP_MODE_STAIRS); // 阶梯模式
16 |
17 | private final String messageKey;
18 |
19 | ThreadMode(String messageKey) {
20 | this.messageKey = messageKey;
21 | }
22 |
23 | public String getDisplayName() {
24 | return I18nUtil.getMessage(messageKey);
25 | }
26 |
27 | @Override
28 | public String toString() {
29 | return getDisplayName();
30 | }
31 | }
32 |
33 | // 公共属性
34 | public ThreadMode threadMode = ThreadMode.FIXED; // 默认固定线程数
35 | public int numThreads = 20; // 固定模式-默认用户数
36 | public int duration = 60; // 所有模式-默认持续时间(秒)
37 | public int loops = 1; // 固定模式-默认循环次数
38 | public boolean useTime = true; // 是否使用时间而不是循环次数
39 |
40 | // 递增模式属性
41 | public int rampUpStartThreads = 1; // 递增起始线程数
42 | public int rampUpEndThreads = 20; // 递增最终线程数
43 | public int rampUpTime = 30; // 递增时间(秒)
44 | public int rampUpDuration = 60; // 递增模式总测试持续时间(秒)
45 |
46 | // 尖刺模式属性
47 | public int spikeMinThreads = 1; // 尖刺最小线程数
48 | public int spikeMaxThreads = 20; // 尖刺最大线程数
49 | public int spikeRampUpTime = 20; // 尖刺上升时间(秒)
50 | public int spikeHoldTime = 15; // 尖刺保持时间(秒)
51 | public int spikeRampDownTime = 20; // 尖刺下降时间(秒)
52 | public int spikeDuration = 60; // 尖刺模式总测试持续时间(秒)
53 |
54 |
55 | // 阶梯模式属性
56 | public int stairsStartThreads = 5; // 阶梯起始线程数
57 | public int stairsEndThreads = 20; // 阶梯最终线程数
58 | public int stairsStep = 5; // 阶梯步长
59 | public int stairsHoldTime = 15; // 每阶段保持时间(秒)
60 | public int stairsDuration = 60; // 阶梯模式总测试持续时间(秒)
61 | }
--------------------------------------------------------------------------------
/src/test/java/com/laker/postman/EditMenuDemo.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman;
2 |
3 | import java.awt.*;
4 | import javax.swing.*;
5 | import org.fife.ui.rtextarea.*;
6 | import org.fife.ui.rsyntaxtextarea.*;
7 |
8 | /**
9 | * A simple example showing how create an "Edit" menu from the same
10 | * actions used by RSyntaxTextArea.
11 | *
12 | * This example uses RSyntaxTextArea 3.0.5.
13 | */
14 | public class EditMenuDemo extends JFrame {
15 |
16 | public EditMenuDemo() {
17 |
18 | JPanel cp = new JPanel(new BorderLayout());
19 |
20 | RSyntaxTextArea textArea = new RSyntaxTextArea(20, 60);
21 | textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA);
22 | textArea.setCodeFoldingEnabled(true);
23 | RTextScrollPane sp = new RTextScrollPane(textArea);
24 | cp.add(sp);
25 |
26 | setJMenuBar(createMenuBar(textArea));
27 |
28 | setContentPane(cp);
29 | setTitle("Edit Menu Demo");
30 | setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
31 | pack();
32 | setLocationRelativeTo(null);
33 |
34 | }
35 |
36 | private static JMenuBar createMenuBar(RSyntaxTextArea textArea) {
37 |
38 | JMenuBar menuBar = new JMenuBar();
39 |
40 | JMenu editMenu = new JMenu("Edit");
41 | editMenu.add(createMenuItem(RTextArea.getAction(RTextArea.UNDO_ACTION)));
42 | editMenu.add(createMenuItem(RTextArea.getAction(RTextArea.REDO_ACTION)));
43 | editMenu.addSeparator();
44 | editMenu.add(createMenuItem(RTextArea.getAction(RTextArea.CUT_ACTION)));
45 | editMenu.add(createMenuItem(RTextArea.getAction(RTextArea.COPY_ACTION)));
46 | editMenu.add(createMenuItem(RTextArea.getAction(RTextArea.PASTE_ACTION)));
47 | editMenu.add(createMenuItem(RTextArea.getAction(RTextArea.DELETE_ACTION)));
48 | editMenu.addSeparator();
49 | editMenu.add(createMenuItem(RTextArea.getAction(RTextArea.SELECT_ALL_ACTION)));
50 | menuBar.add(editMenu);
51 |
52 | return menuBar;
53 | }
54 |
55 | private static JMenuItem createMenuItem(Action action) {
56 | JMenuItem item = new JMenuItem(action);
57 | item.setToolTipText(null); // Swing annoyingly adds tool tip text to the menu item
58 | return item;
59 | }
60 |
61 | public static void main(String[] args) {
62 | // Start all Swing applications on the EDT.
63 | SwingUtilities.invokeLater(new Runnable() {
64 | public void run() {
65 | new EditMenuDemo().setVisible(true);
66 | }
67 | });
68 | }
69 |
70 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/service/js/ScriptFragment.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.service.js;
2 |
3 | import lombok.Getter;
4 |
5 | /**
6 | * 脚本片段
7 | *
8 | * 表示一个可执行的脚本片段,包含脚本内容和来源标识。
9 | * 主要用于脚本继承场景,区分脚本来自请求本身还是从父分组继承。
10 | *
11 | *
12 | * 使用场景:
13 | *
14 | * - 分组级别的脚本继承
15 | * - 合并多个来源的脚本
16 | * - 脚本来源追踪和调试
17 | *
18 | *
19 | * 示例:
20 | * {@code
21 | * // 创建请求自身的脚本片段
22 | * ScriptFragment requestScript = new ScriptFragment(
23 | * "pm.environment.set('token', 'abc123');",
24 | * "Request: /api/login"
25 | * );
26 | *
27 | * // 创建继承的脚本片段
28 | * ScriptFragment inheritedScript = new ScriptFragment(
29 | * "pm.request.headers.add({key: 'X-API-Key', value: pm.environment.get('apiKey')});",
30 | * "Group: API Requests"
31 | * );
32 | *
33 | * // 合并脚本
34 | * List fragments = Arrays.asList(inheritedScript, requestScript);
35 | * String mergedScript = ScriptMerger.merge(fragments);
36 | * }
37 | *
38 | * @see ScriptMerger
39 | * @author laker
40 | */
41 | @Getter
42 | public class ScriptFragment {
43 | /**
44 | * 脚本内容(JavaScript 代码)
45 | */
46 | private final String content;
47 |
48 | /**
49 | * 脚本来源标识
50 | *
51 | * 用于标识脚本来源,便于调试和追踪。例如:
52 | *
53 | * - "Request: /api/users"
54 | * - "Group: API Requests"
55 | * - "Inherited: Root Collection"
56 | *
57 | *
58 | */
59 | private final String source;
60 |
61 | /**
62 | * 构造脚本片段(带来源标识)
63 | *
64 | * @param source 脚本来源标识
65 | * @param content 脚本内容
66 | */
67 | public ScriptFragment(String source, String content) {
68 | this.source = source;
69 | this.content = content;
70 | }
71 |
72 | /**
73 | * 构造脚本片段(无来源标识)
74 | *
75 | * @param content 脚本内容
76 | */
77 | public ScriptFragment(String content) {
78 | this(null, content);
79 | }
80 |
81 | /**
82 | * 工厂方法:创建带来源标识的脚本片段
83 | *
84 | * @param source 脚本来源标识
85 | * @param content 脚本内容
86 | * @return 脚本片段实例
87 | */
88 | public static ScriptFragment of(String source, String content) {
89 | return new ScriptFragment(source, content);
90 | }
91 |
92 | /**
93 | * 工厂方法:创建无来源标识的脚本片段
94 | *
95 | * @param content 脚本内容
96 | * @return 脚本片段实例
97 | */
98 | public static ScriptFragment of(String content) {
99 | return new ScriptFragment(content);
100 | }
101 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/model/script/TemporaryVariablesApi.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.model.script;
2 |
3 | import java.util.LinkedHashMap;
4 | import java.util.Map;
5 |
6 | /**
7 | * 临时变量 API (pm.variables)
8 | *
9 | * 用于存储请求生命周期内的临时变量,这些变量不会被持久化,
10 | * 只在当前请求执行过程中有效。支持从 CSV 数据驱动测试等场景注入数据。
11 | *
12 | *
13 | * 使用场景:
14 | *
15 | * - 存储中间计算结果
16 | * - 在前置和后置脚本之间传递数据
17 | * - 访问 CSV 数据驱动测试的当前行数据
18 | *
19 | */
20 | public class TemporaryVariablesApi {
21 | /**
22 | * 变量存储 Map
23 | */
24 | private final Map variablesMap = new LinkedHashMap<>();
25 |
26 | /**
27 | * 设置临时变量
28 | *
29 | * @param key 变量名
30 | * @param value 变量值
31 | */
32 | public void set(String key, String value) {
33 | if (key != null && value != null) {
34 | variablesMap.put(key, value);
35 | }
36 | }
37 |
38 | /**
39 | * 设置临时变量(支持任意类型,自动转换为字符串)
40 | * 解决JavaScript中传入数字等非String类型的问题
41 | *
42 | * @param key 变量名
43 | * @param value 变量值(任意类型)
44 | */
45 | public void set(String key, Object value) {
46 | if (key != null && value != null) {
47 | variablesMap.put(key, String.valueOf(value));
48 | }
49 | }
50 |
51 | /**
52 | * 获取临时变量
53 | *
54 | * @param key 变量名
55 | * @return 变量值,不存在则返回 null
56 | */
57 | public String get(String key) {
58 | return variablesMap.get(key);
59 | }
60 |
61 | /**
62 | * 检查变量是否存在
63 | *
64 | * @param key 变量名
65 | * @return 是否存在
66 | */
67 | public boolean has(String key) {
68 | return variablesMap.containsKey(key);
69 | }
70 |
71 | /**
72 | * 删除临时变量
73 | *
74 | * @param key 变量名
75 | */
76 | public void unset(String key) {
77 | variablesMap.remove(key);
78 | }
79 |
80 | /**
81 | * 清空所有临时变量
82 | */
83 | public void clear() {
84 | variablesMap.clear();
85 | }
86 |
87 | /**
88 | * 批量设置临时变量(用于 CSV 数据注入)
89 | *
90 | * @param variables 变量 Map
91 | */
92 | public void replaceAll(Map variables) {
93 | if (variables != null) {
94 | variablesMap.clear();
95 | variablesMap.putAll(variables);
96 | }
97 | }
98 |
99 | /**
100 | * 获取所有临时变量
101 | *
102 | * @return 变量 Map 的副本
103 | */
104 | public Map toObject() {
105 | return new java.util.LinkedHashMap<>(variablesMap);
106 | }
107 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/panel/performance/timer/TimerPropertyPanel.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.panel.performance.timer;
2 |
3 | import com.laker.postman.panel.performance.model.JMeterTreeNode;
4 |
5 | import javax.swing.*;
6 | import java.awt.*;
7 |
8 | public class TimerPropertyPanel extends JPanel {
9 | private final JSpinner delaySpinner;
10 | private JMeterTreeNode currentNode;
11 |
12 | public TimerPropertyPanel() {
13 | setLayout(new GridBagLayout());
14 | setMaximumSize(new Dimension(420, 120));
15 | setPreferredSize(new Dimension(380, 100));
16 | setBorder(BorderFactory.createEmptyBorder(18, 24, 18, 24));
17 | GridBagConstraints gbc = new GridBagConstraints();
18 | gbc.insets = new Insets(6, 6, 6, 6);
19 | gbc.anchor = GridBagConstraints.CENTER;
20 | gbc.fill = GridBagConstraints.NONE;
21 | gbc.gridx = 0;
22 | gbc.gridy = 0;
23 | gbc.gridwidth = 1;
24 | JLabel label = new JLabel("定时(ms):");
25 | add(label, gbc);
26 | gbc.gridx = 1;
27 | gbc.insets = new Insets(6, 0, 6, 6); // 左间距为0,右间距为6
28 | delaySpinner = new JSpinner(new SpinnerNumberModel(1000, 0, 60000, 100));
29 | delaySpinner.setPreferredSize(new Dimension(100, 28));
30 | add(delaySpinner, gbc);
31 | // 帮助说明
32 | gbc.gridx = 0;
33 | gbc.gridy = 1;
34 | gbc.gridwidth = 2;
35 | gbc.insets = new Insets(6, 6, 6, 6);
36 | gbc.anchor = GridBagConstraints.CENTER;
37 | gbc.fill = GridBagConstraints.HORIZONTAL;
38 | JLabel helpLabel = new JLabel("定时器会在请求前延迟指定毫秒数,适用于接口限流、节奏控制等场景。");
39 | helpLabel.setFont(helpLabel.getFont().deriveFont(Font.PLAIN, 12f));
40 | add(helpLabel, gbc);
41 | // 占位撑满高度
42 | gbc.gridy = 2;
43 | gbc.weighty = 1.0;
44 | gbc.fill = GridBagConstraints.BOTH;
45 | gbc.gridx = 0;
46 | gbc.gridy = 2;
47 | add(Box.createVerticalGlue(), gbc);
48 | }
49 |
50 | public void setTimerData(JMeterTreeNode node) {
51 | this.currentNode = node;
52 | TimerData data = node.timerData;
53 | if (data == null) {
54 | data = new TimerData();
55 | node.timerData = data;
56 | }
57 | delaySpinner.setValue(data.delayMs);
58 | }
59 |
60 | public void saveTimerData() {
61 | if (currentNode == null) return;
62 | TimerData data = currentNode.timerData;
63 | if (data == null) {
64 | data = new TimerData();
65 | currentNode.timerData = data;
66 | }
67 | data.delayMs = (Integer) delaySpinner.getValue();
68 | }
69 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/service/update/asset/AssetFinder.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.service.update.asset;
2 |
3 | import cn.hutool.json.JSONArray;
4 | import cn.hutool.json.JSONObject;
5 | import lombok.extern.slf4j.Slf4j;
6 |
7 | /**
8 | * 资源查找器 - 从发布信息中提取合适的下载链接
9 | */
10 | @Slf4j
11 | public class AssetFinder {
12 |
13 | private static final String BROWSER_DOWNLOAD_URL = "browser_download_url";
14 |
15 |
16 | /**
17 | * 根据扩展名查找资源
18 | *
19 | * @param assets 资源列表
20 | * @param extension 文件扩展名(如 ".dmg", ".exe")
21 | * @return 下载 URL,未找到返回 null
22 | */
23 | public String findByExtension(JSONArray assets, String extension) {
24 | for (int i = 0; i < assets.size(); i++) {
25 | JSONObject asset = assets.getJSONObject(i);
26 | String name = asset.getStr("name");
27 | if (name != null && name.endsWith(extension)) {
28 | String url = asset.getStr(BROWSER_DOWNLOAD_URL);
29 | log.debug("Found asset: {} -> {}", name, url);
30 | return url;
31 | }
32 | }
33 | return null;
34 | }
35 |
36 | /**
37 | * 根据模式查找资源
38 | *
39 | * @param assets 资源列表
40 | * @param pattern 匹配模式(如 "-portable.zip")
41 | * @return 下载 URL,未找到返回 null
42 | */
43 | public String findByPattern(JSONArray assets, String pattern) {
44 | for (int i = 0; i < assets.size(); i++) {
45 | JSONObject asset = assets.getJSONObject(i);
46 | String name = asset.getStr("name");
47 | if (name != null && name.contains(pattern)) {
48 | String url = asset.getStr(BROWSER_DOWNLOAD_URL);
49 | log.debug("Found asset by pattern '{}': {} -> {}", pattern, name, url);
50 | return url;
51 | }
52 | }
53 | return null;
54 | }
55 |
56 | /**
57 | * 查找通用 DMG 文件(不包含架构后缀)
58 | *
59 | * @param assets 资源列表
60 | * @return 下载 URL,未找到返回 null
61 | */
62 | public String findGenericDmg(JSONArray assets) {
63 | for (int i = 0; i < assets.size(); i++) {
64 | JSONObject asset = assets.getJSONObject(i);
65 | String name = asset.getStr("name");
66 | if (name != null && name.endsWith(".dmg") &&
67 | !name.endsWith("-intel.dmg") &&
68 | !name.endsWith("-arm64.dmg") &&
69 | !name.endsWith("-macos-x86_64.dmg") &&
70 | !name.endsWith("-macos-arm64.dmg")) {
71 | String url = asset.getStr(BROWSER_DOWNLOAD_URL);
72 | log.debug("Found generic DMG (without architecture suffix): {} -> {}", name, url);
73 | return url;
74 | }
75 | }
76 | return null;
77 | }
78 | }
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/common/component/table/TableUIConstants.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.common.component.table;
2 |
3 | import com.laker.postman.common.constants.ModernColors;
4 |
5 | import javax.swing.*;
6 | import javax.swing.border.Border;
7 | import java.awt.*;
8 |
9 | /**
10 | * 表格UI常量类
11 | * 集中管理表格相关的UI常量,便于统一修改和维护
12 | */
13 | public class TableUIConstants {
14 | // 文本常量
15 | public static final String SELECT_FILE_TEXT = "Select File";
16 | public static final String FILE_TYPE = "File";
17 |
18 | // 颜色常量 - 基础色
19 | public static final Color PRIMARY_COLOR = new Color(66, 133, 244); // 主题色
20 |
21 | // 背景色
22 | public static final Color ZEBRA_LIGHT = new Color(250, 252, 255);
23 | public static final Color ZEBRA_DARK = Color.WHITE;
24 |
25 | // 文本颜色
26 | public static final Color TEXT_SECONDARY = new Color(95, 99, 104); // 次要文本
27 | public static final Color TEXT_DISABLED = new Color(155, 155, 155); // 禁用文本
28 |
29 | // 边框和交互颜色
30 | public static final Color BORDER_COLOR = new Color(220, 225, 230);
31 | public static final Color HOVER_COLOR = new Color(230, 240, 255);
32 |
33 | // 特定用途颜色
34 | public static final Color FILE_BUTTON_TEXT_COLOR = PRIMARY_COLOR;
35 | public static final Color FILE_SELECTED_TEXT_COLOR = new Color(76, 130, 206);
36 | public static final Color FILE_EMPTY_TEXT_COLOR = TEXT_SECONDARY;
37 |
38 | // 图标大小
39 | public static final int ICON_SIZE = 14;
40 |
41 | // 边距
42 | public static final int PADDING_LEFT = 8;
43 | public static final int PADDING_RIGHT = 8;
44 | public static final int PADDING_TOP = 2;
45 | public static final int PADDING_BOTTOM = 2;
46 |
47 | /**
48 | * 创建标准按钮边框
49 | */
50 | public static Border createButtonBorder() {
51 | return BorderFactory.createCompoundBorder(
52 | BorderFactory.createLineBorder(BORDER_COLOR),
53 | BorderFactory.createEmptyBorder(
54 | PADDING_TOP, PADDING_LEFT, PADDING_BOTTOM, PADDING_RIGHT));
55 | }
56 |
57 | /**
58 | * 创建标准标签边框
59 | */
60 | public static Border createLabelBorder() {
61 | return BorderFactory.createEmptyBorder(
62 | PADDING_TOP, PADDING_LEFT, PADDING_BOTTOM, PADDING_RIGHT);
63 | }
64 |
65 | /**
66 | * 获取单元格背景色
67 | */
68 | public static Color getCellBackground(boolean isSelected, boolean isHovered, boolean isEmpty, int row,
69 | JTable table) {
70 | if (isSelected) {
71 | return table.getSelectionBackground();
72 | } else if (isHovered) {
73 | return HOVER_COLOR;
74 | } else if (isEmpty) {
75 | return ModernColors.EMPTY_CELL;
76 | } else {
77 | return table.getBackground();
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/service/js/ScriptExecutionResult.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.service.js;
2 |
3 | import com.laker.postman.model.script.TestResult;
4 | import lombok.Builder;
5 | import lombok.Getter;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | /**
11 | * 脚本执行结果
12 | * 封装脚本执行的结果信息,包括成功状态、测试结果、错误信息等
13 | */
14 | @Getter
15 | @Builder
16 | public class ScriptExecutionResult {
17 | /**
18 | * 是否执行成功
19 | */
20 | private final boolean success;
21 |
22 | /**
23 | * 测试结果列表(仅后置脚本)
24 | */
25 | @Builder.Default
26 | private final List testResults = new ArrayList<>();
27 |
28 | /**
29 | * 错误信息(如果执行失败)
30 | */
31 | private final String errorMessage;
32 |
33 | /**
34 | * 异常对象(如果执行失败)
35 | */
36 | private final Exception exception;
37 |
38 | /**
39 | * 创建成功的结果
40 | */
41 | public static ScriptExecutionResult success() {
42 | return ScriptExecutionResult.builder()
43 | .success(true)
44 | .build();
45 | }
46 |
47 | /**
48 | * 创建成功的结果(带测试结果)
49 | */
50 | public static ScriptExecutionResult success(List testResults) {
51 | return ScriptExecutionResult.builder()
52 | .success(true)
53 | .testResults(testResults != null ? new ArrayList<>(testResults) : new ArrayList<>())
54 | .build();
55 | }
56 |
57 | /**
58 | * 创建失败的结果
59 | */
60 | public static ScriptExecutionResult failure(String errorMessage, Exception exception) {
61 | return ScriptExecutionResult.builder()
62 | .success(false)
63 | .errorMessage(errorMessage)
64 | .exception(exception)
65 | .build();
66 | }
67 |
68 | /**
69 | * 创建失败的结果(带测试结果)
70 | */
71 | public static ScriptExecutionResult failure(String errorMessage, Exception exception, List testResults) {
72 | return ScriptExecutionResult.builder()
73 | .success(false)
74 | .errorMessage(errorMessage)
75 | .exception(exception)
76 | .testResults(testResults != null ? new ArrayList<>(testResults) : new ArrayList<>())
77 | .build();
78 | }
79 |
80 | /**
81 | * 是否有测试结果
82 | */
83 | public boolean hasTestResults() {
84 | return !testResults.isEmpty();
85 | }
86 |
87 | /**
88 | * 所有测试是否通过
89 | * 注意:如果没有测试,返回true(没有失败的测试)
90 | */
91 | public boolean allTestsPassed() {
92 | // 如果没有测试,视为通过
93 | if (!hasTestResults()) {
94 | return true;
95 | }
96 | // 如果有测试,检查是否全部通过
97 | return testResults.stream().allMatch(test -> test.passed);
98 | }
99 | }
100 |
101 |
--------------------------------------------------------------------------------
/src/main/java/com/laker/postman/panel/collections/left/action/TreeNodeCloner.java:
--------------------------------------------------------------------------------
1 | package com.laker.postman.panel.collections.left.action;
2 |
3 | import com.laker.postman.model.HttpRequestItem;
4 | import com.laker.postman.util.JsonUtil;
5 | import lombok.experimental.UtilityClass;
6 |
7 | import javax.swing.tree.DefaultMutableTreeNode;
8 |
9 | import static com.laker.postman.panel.collections.left.RequestCollectionsLeftPanel.GROUP;
10 | import static com.laker.postman.panel.collections.left.RequestCollectionsLeftPanel.REQUEST;
11 |
12 | /**
13 | * 树节点克隆工具类
14 | */
15 | @UtilityClass
16 | public class TreeNodeCloner {
17 |
18 | /**
19 | * 深拷贝分组节点及其所有子节点
20 | */
21 | public static DefaultMutableTreeNode deepCopyGroupNode(DefaultMutableTreeNode node) {
22 | Object userObj = node.getUserObject();
23 | Object[] obj = userObj instanceof Object[] ? ((Object[]) userObj).clone() : null;
24 | DefaultMutableTreeNode copy = new DefaultMutableTreeNode(obj);
25 |
26 | for (int i = 0; i < node.getChildCount(); i++) {
27 | DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i);
28 | copy.add(copyChildNode(child));
29 | }
30 |
31 | return copy;
32 | }
33 |
34 | /**
35 | * 拷贝子节点
36 | */
37 | private static DefaultMutableTreeNode copyChildNode(DefaultMutableTreeNode child) {
38 | Object childUserObj = child.getUserObject();
39 | if (!(childUserObj instanceof Object[] childObj)) {
40 | return new DefaultMutableTreeNode(childUserObj);
41 | }
42 |
43 | if (GROUP.equals(childObj[0])) {
44 | return deepCopyGroupNode(child);
45 | } else if (REQUEST.equals(childObj[0])) {
46 | return copyRequestNode(childObj);
47 | }
48 |
49 | return new DefaultMutableTreeNode(childUserObj);
50 | }
51 |
52 | /**
53 | * 深拷贝请求节点
54 | */
55 | private static DefaultMutableTreeNode copyRequestNode(Object[] childObj) {
56 | HttpRequestItem item = (HttpRequestItem) childObj[1];
57 | HttpRequestItem copyItem = JsonUtil.deepCopy(item, HttpRequestItem.class);
58 | copyItem.setId(java.util.UUID.randomUUID().toString());
59 | Object[] reqObj = new Object[]{REQUEST, copyItem};
60 | return new DefaultMutableTreeNode(reqObj);
61 | }
62 |
63 | /**
64 | * 递归克隆树节点(用于创建选择树)
65 | */
66 | public static DefaultMutableTreeNode cloneTreeNode(DefaultMutableTreeNode node) {
67 | Object userObj = node.getUserObject();
68 | DefaultMutableTreeNode copy = new DefaultMutableTreeNode(
69 | userObj instanceof Object[] ? ((Object[]) userObj).clone() : userObj
70 | );
71 |
72 | for (int i = 0; i < node.getChildCount(); i++) {
73 | DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i);
74 | copy.add(cloneTreeNode(child));
75 | }
76 |
77 | return copy;
78 | }
79 | }
80 |
81 |
--------------------------------------------------------------------------------