From 51a23c217898fb497b199a61bec78c593f07de0c Mon Sep 17 00:00:00 2001 From: Timi Date: Mon, 14 Jul 2025 11:50:02 +0800 Subject: [PATCH] Initial project --- .gitignore | 128 ++++---------- .idea/.gitignore | 5 + .idea/encodings.xml | 7 + .idea/misc.xml | 14 ++ .idea/uiDesigner.xml | 124 +++++++++++++ .idea/vcs.xml | 6 + pom.xml | 31 ++++ src/main/java/com/imyeyu/network/FormMap.java | 25 +++ src/main/java/com/imyeyu/network/Network.java | 164 ++++++++++++++++++ .../imyeyu/network/ProgressiveRequest.java | 81 +++++++++ 10 files changed, 491 insertions(+), 94 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/encodings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/uiDesigner.xml create mode 100644 .idea/vcs.xml create mode 100644 pom.xml create mode 100644 src/main/java/com/imyeyu/network/FormMap.java create mode 100644 src/main/java/com/imyeyu/network/Network.java create mode 100644 src/main/java/com/imyeyu/network/ProgressiveRequest.java diff --git a/.gitignore b/.gitignore index c6d98d1..5ff6309 100644 --- a/.gitignore +++ b/.gitignore @@ -1,98 +1,38 @@ -# ---> JetBrains -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# AWS User-specific -.idea/**/aws.xml - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# SonarLint plugin -.idea/sonarlint/ - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -# ---> Maven target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties -# https://github.com/takari/maven-wrapper#usage-without-binary-jar -.mvn/wrapper/maven-wrapper.jar +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ -# Eclipse m2e generated files -# Eclipse Core -.project -# JDT-specific (Eclipse Java Development Tools) +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated .classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..0f8d55a --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml + +CopilotChatHistory.xml \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..a0e1fc4 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..2de2f3a --- /dev/null +++ b/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + com.imyeyu.network + timi-network + 0.0.1 + jar + + + true + 21 + 21 + UTF-8 + + + + + com.imyeyu.io + timi-io + 0.0.1 + + + org.apache.httpcomponents.client5 + httpclient5-fluent + 5.2.1 + + + diff --git a/src/main/java/com/imyeyu/network/FormMap.java b/src/main/java/com/imyeyu/network/FormMap.java new file mode 100644 index 0000000..6a45e9e --- /dev/null +++ b/src/main/java/com/imyeyu/network/FormMap.java @@ -0,0 +1,25 @@ +package com.imyeyu.network; + +import org.apache.hc.client5.http.fluent.Form; +import org.apache.hc.core5.http.NameValuePair; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * HashMap 构建 {@link org.apache.hc.client5.http.fluent.Form} 参数列表 + * + * @author 夜雨 + * @since 2025-06-26 15:41 + */ +public class FormMap extends HashMap { + + public List build() { + Form form = Form.form(); + for (Map.Entry item : entrySet()) { + form.add(item.getKey().toString(), item.getValue().toString()); + } + return form.build(); + } +} diff --git a/src/main/java/com/imyeyu/network/Network.java b/src/main/java/com/imyeyu/network/Network.java new file mode 100644 index 0000000..1d2b445 --- /dev/null +++ b/src/main/java/com/imyeyu/network/Network.java @@ -0,0 +1,164 @@ +package com.imyeyu.network; + +import com.imyeyu.java.bean.CallbackArg; +import com.imyeyu.utils.Calc; +import com.imyeyu.utils.Encoder; + +import java.awt.Desktop; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +/** + * @author 夜雨 + * @version 2024-03-29 17:22 + */ +public class Network { + + /** + * 使用默认浏览器打开 URL 地址 + * + * @param url 地址 + */ + public static void openURIInBrowser(String url) { + try { + Desktop dp = Desktop.getDesktop(); + if (dp.isSupported(Desktop.Action.BROWSE)) { + dp.browse(URI.create(Encoder.url(url))); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * TCP 测试 IP 连接延时(异步) + * + * @param ip IP + * @param callback 结果回调,单位毫秒(超时 8 秒或失败回参为 -1) + */ + public static void pingAsync(String ip, CallbackArg callback) { + pingAsync(ip, 8000, callback); + } + + /** + * TCP 测试 IP 连接延时(异步) + * + * @param ip IP + * @param timeout 超时限制,单位毫秒 + * @param callback 结果回调,单位毫秒(超时或失败回参为 -1) + */ + public static void pingAsync(String ip, int timeout, CallbackArg callback) { + new Thread(() -> callback.handler(ping(ip, timeout))).start(); + } + + /** + * TCP 测试 IP 连接延时(同步),8 秒连接失败返回 -1 + * + * @param ip IP + * @return 延时值,毫秒 + */ + public static int ping(String ip) { + return ping(ip, 8000); + } + + /** + * TCP 测试 IP 连接延时(同步) + * + * @param ip IP + * @param timeout 超时限制,单位毫秒 + * @return 延时值,毫秒 + */ + public static int ping(String ip, int timeout) { + try { + long s = System.currentTimeMillis(); + InetAddress address = InetAddress.getByName(ip); + boolean isReachable = address.isReachable(timeout); + if (isReachable) { + return Calc.floor((System.currentTimeMillis() - s) * .5); + } + return -1; + } catch (Exception e) { + return -1; + } + } + + /** + * 检测一个端口是否被占用 + * + * @param port 端口 + * @return 为 true 时表示已被占用 + */ + public static boolean isBusyPort(int port) { + if (port < 1 || 65535 < port) { + throw new IllegalArgumentException("port must between with [1, 65535]"); + } + try { + Socket socket = new Socket(); + socket.connect(new InetSocketAddress("127.0.0.1", port), 500); + socket.close(); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * 获取 URI 文件名 + * + * @param uri URI 路径 + * @return 文件名 + */ + public static String uriFileName(String uri) { + int parSp = uri.indexOf("?"); + if (parSp != -1) { + uri = uri.substring(0, parSp); + } + int sp = uri.lastIndexOf("/"); + if (sp == -1) { + return uri; + } + return uri.substring(sp + 1); + } + + /** + * 获取 URI 简易的文件名,不含格式 + * + * @param uri URI 路径 + * @return 文件名 + */ + public static String simpleURIFileName(String uri) { + String fileName = uriFileName(uri); + int dot = fileName.lastIndexOf("."); + if (dot != -1) { + return fileName.substring(0, dot); + } + return fileName; + } + + /** + * 获取 URI 文件扩展名 + * + * @param uri URI 路径 + * @return 扩展名 + */ + public static String uriFileExtension(String uri) { + String fileName = uriFileName(uri); + int dot = fileName.lastIndexOf("."); + if (dot != -1) { + return fileName.substring(dot + 1); + } + return ""; + } + + public static String getFileDownloadHeader(String fileName) throws UnsupportedEncodingException { + String fallbackFileName = fileName.replaceAll("[^\\x00-\\x7F]", "_"); + String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replace("+", "%20"); + return "attachment; filename=\"%s\"; filename*=UTF-8''%s".formatted(fallbackFileName, encodedFileName); + } +} diff --git a/src/main/java/com/imyeyu/network/ProgressiveRequest.java b/src/main/java/com/imyeyu/network/ProgressiveRequest.java new file mode 100644 index 0000000..8552330 --- /dev/null +++ b/src/main/java/com/imyeyu/network/ProgressiveRequest.java @@ -0,0 +1,81 @@ +package com.imyeyu.network; + +import com.imyeyu.io.IO; +import org.apache.hc.client5.http.fluent.Request; +import org.apache.hc.client5.http.fluent.Response; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; +import org.apache.hc.core5.http.io.entity.EntityUtils; + +import javax.naming.NoPermissionException; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Path; + +/** + * 封装 {@link org.apache.hc.client5.http.fluent.Request} 计算返回进度 + * + * @author 夜雨 + * @since 2025-06-26 13:03 + */ +public class ProgressiveRequest { + + private final Request request; + private final ProgressiveCallback callback; + + private ProgressiveRequest(Request request, ProgressiveCallback callback) { + this.request = request; + this.callback = callback; + } + + public static ProgressiveRequest wrap(Request request, ProgressiveCallback callback) { + return new ProgressiveRequest(request, callback); + } + + public void toFile(Path outputPath) throws IOException, NoPermissionException { + processResponse(request.execute(), IO.getOutputStream(outputPath.toFile())); + } + + public byte[] asBytes() throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + processResponse(request.execute(), os); + return os.toByteArray(); + } + + private void processResponse(Response response, OutputStream os) throws IOException { + response.handleResponse((HttpClientResponseHandler) resp -> { + HttpEntity entity = resp.getEntity(); + if (entity == null) { + throw new IOException("not found response entity"); + } + int code = resp.getCode(); + if (400 <= code) { + throw new IOException("response error: %s".formatted(code)); + } + long length = entity.getContentLength(); + + IO.toOutputStream(entity.getContent(), os, new IO.OnWriteCallback() { + + @Override + public boolean handler(long total, long now) { + + return callback.handler(length, total, now); + } + }); + EntityUtils.consume(entity); + return null; + }); + } + + /** + * + * + * @author 夜雨 + * @since 2025-06-26 15:18 + */ + public interface ProgressiveCallback { + + boolean handler(long total, long read, long now); + } +}