# helper
**Repository Path**: luminion/helper
## Basic Information
- **Project Name**: helper
- **Description**: 封装api的一些常用方法, 提供常用静态工具集, 简化加速开发
- **Primary Language**: Unknown
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: https://github.com/luminion/helper
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2024-12-24
- **Last Updated**: 2026-07-18
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Helper Java Utilities
[](https://central.sonatype.com/artifact/io.github.luminion/helper)
[](LICENSE)
[](https://github.com/luminion/helper)
Helper 是一组兼容 Java 8 的常用工具类。
项目围绕集合、反射、JSON、HTTP、文件与分片上传、MinIO、FFmpeg、Excel、Spring 上下文和中国地理坐标等常见场景提供轻量能力。
## 功能特性
- 提供树结构构建、枚举映射、Bean 转换和 Lambda 属性解析
- 提供 Jackson JSON 序列化、反序列化和对象更新封装
- 提供链式 HTTP 请求,支持 query、form、JSON body 与响应流
- 提供本地文件复制、删除和安全的文件分片上传、合并能力
- 提供 MinIO / S3 对象上传、下载、预签名、复制和分片合并能力
- 提供 FFmpeg 播放、转码、裁剪、截图及音视频合并能力
- 提供 EasyExcel / FastExcel 常用类型 converter
- 提供 Spring 上下文、动态 Bean 和事件访问能力
- 提供中国境内 WGS-84、GCJ-02、BD-09 坐标转换和范围判断
---
## 总览
Helper 的基础模块可以直接使用;Spring、Jackson、MinIO、EasyExcel 和 FastExcel 均为 Maven 可选依赖,只有调用对应模块时才需要在业务项目中引入。
| 模块 | 主要能力 | 额外运行时依赖 |
| --- | --- | --- |
| Collection / Reflect | 树、枚举、Bean、Lambda | 无 |
| File / Upload | 文件操作、本地分片上传 | 无 |
| HTTP | JDK `HttpURLConnection` 链式封装 | 无 |
| Geo / Bit | 坐标、距离、位运算 | 无 |
| JSON | Jackson 读写与类型转换 | Jackson Databind、JSR-310 |
| Spring | 上下文、Bean、事件 | Spring Context 5.x |
| MinIO | S3 对象与分片操作 | MinIO Java SDK 8.x |
| Excel | 扩展 converter | EasyExcel 4.x 或 FastExcel 1.x |
| FFmpeg | 外部媒体命令封装 | 本机 FFmpeg / FFplay / FFprobe |
资源所有权总原则:明确执行上传的方法会关闭传入的 `InputStream`;MinIO `download` 不关闭调用方提供的 `OutputStream`;`responseStream()` 必须由调用方关闭。
## Maven 依赖
最新版本
[](https://central.sonatype.com/artifact/io.github.luminion/helper)
```xml
io.github.luminion
helper
1.3.0
```
项目本身兼容 Java 8。各集成模块的三方依赖在 Helper 中标记为 `optional`,业务项目需按实际使用的模块自行引入。
---
## 各功能细览
### 1. 集合与反射
`TreeHelper` 用函数描述节点关系,不要求实体实现固定接口;构树后所有节点的 children 都是可变列表。
```java
TreeHelper tree = TreeHelper.of(
Node::getId,
Node::getParentId,
Node::setChildren
);
List roots = tree.treeRootAuto(nodes);
List descendants = tree.findAllChildrenById(nodes, parentId);
```
`EnumHelper` 按枚举声明顺序维护 key 映射:
```java
EnumHelper statuses = EnumHelper.of(Status.class, Status::getCode);
Status status = statuses.requireEnum(1);
List all = statuses.enums();
Map labels = statuses.resolveMap(Status::getLabel);
```
Bean 与 Lambda 示例:
```java
Target target = BeanHelper.copyProperties(source, Target.class);
Target difference = BeanHelper.toDifference(source, target);
String property = LambdaHelper.resolvePropertyName(User::getName);
```
说明:
- `BeanHelper.toDifference` 逐属性比较,不依赖业务对象的 `equals` 实现
- `LambdaHelper.resolveSerializedLambda` 始终解析当前方法引用,不缓存捕获对象
- 空集合结果使用可变容器,便于调用方继续补充内容
### 2. JSON
额外依赖:
```xml
com.fasterxml.jackson.core
jackson-databind
com.fasterxml.jackson.datatype
jackson-datatype-jsr310
```
使用示例:
```java
ObjectMapperHelper json = ObjectMapperHelper.of();
String value = json.toJson(source);
Demo parsed = json.parseObject(value, Demo.class);
List list = json.parseArray(value, Demo.class);
```
默认配置:
- `long`、`double`、`BigInteger` 和 `BigDecimal` 序列化为字符串
- 日期时间格式为 `yyyy-MM-dd HH:mm:ss`
- 可通过 `ObjectMapperHelper.objectMapper(customMapper)` 替换全局实例
### 3. HTTP
```java
String response = HttpHelper.post("https://example.com/api")
.header("Authorization", "Bearer token")
.queryParam("page", "1")
.bodyParam("{\"name\":\"demo\"}")
.responseString();
```
说明:
- `bodyParam` 默认使用 `application/json`
- `formParam` 默认使用 `application/x-www-form-urlencoded`
- 调用方显式设置的 `Content-Type` 优先
- GET 和 HEAD 不接受请求体
- query 参数会正确追加到原 URL 的 query 中,不会落入 fragment
- `responseStream()` 返回的流必须关闭,关闭时会断开底层连接
- 调试日志和非 2xx 日志会原样记录 URL 与请求体,不做脱敏;不得传入不允许写入日志的敏感信息,或应关闭对应日志级别
### 4. 文件与本地分片上传
文件复制:
```java
FileHelper.copyFile(sourceFile, targetFile);
FileHelper.copyDir(sourceDir, targetDir);
```
同文件复制、将目录复制到自身或子目录会直接拒绝。单文件复制先写临时文件,完成后再替换目标。
分片上传:
```java
FileUploadHelper upload = new FileUploadHelper("D:/uploads");
upload.uploadChunk(inputStream, fileMd5, "0");
boolean completed = upload.mergeFile(fileMd5, "zip", chunkCount, chunkSize);
```
说明:
- 上传目录及其上级目录不能包含符号链接或目录联接
- `fileMD5` 只接受字母、数字、下划线和短横线
- 分片索引必须是非负整数,扩展名不得包含路径字符
- `uploadChunk` 会关闭传入的输入流
- 推荐使用带 `chunkSize` 的 `mergeFile` 重载校验分片完整性
- 同一 `fileMD5` 的上传、校验、合并和取消在单 JVM 内串行执行
- 多实例部署仍需由业务侧提供分布式协调
### 5. MinIO / S3
额外依赖:
```xml
io.minio
minio
```
创建实例:
```java
MinioHelper minio = MinioHelper.builder()
.client(minioClient)
.bucket("assets")
.urlResolver((bucket, objectPath) ->
"https://cdn.example.com/" + objectPath)
.build();
String url = minio.upload("images/demo.png", inputStream);
```
分片上传:
```java
MinioHelper.ChunkUploadSession session = minio.initChunkUpload("video/demo", "mp4");
minio.uploadChunk(session, 0, firstChunk);
minio.uploadChunk(session, 1, secondChunk);
String url = minio.completeChunkUpload(session, 2);
```
说明:
- Builder 必须接收已有 `MinioClient` 和 URL 解析函数;默认 bucket 可选
- 上传方法关闭传入的输入流,`download` 保持调用方输出流打开
- 未知长度上传使用 10 MiB 分片;S3 compose 除最后一片外通常要求每片至少 5 MiB
- 优先使用 `completeChunkUpload(session, expectedChunkCount)` 明确总片数
- 不带总片数的完成重载已废弃,因为无法判断尾部分片是否缺失
- 合并对象按最终文件名设置 `Content-Type`
- `urlResolver` 接收的对象路径已经逐段 URL 编码,不要再次整体编码
- 预签名有效期最短 1 秒、最长 7 天;对象名不允许以 `/` 结尾
### 6. FFmpeg
运行前需安装 FFmpeg,并提供三个可执行文件路径:
```java
FfmpegHelper ffmpeg = FfmpegHelper.of(
ffplayPath,
ffmpegPath,
ffprobePath,
tempDir
);
ffmpeg.videoFormatConversion(source, target);
```
说明:
- 命令启动失败、输出读取失败、超时或非零退出码会抛出 `IllegalStateException`
- 单文件输出先写入目标同目录临时目录,成功且产物非空后再替换目标
- 转换失败时保留旧目标文件,并清理临时产物
- `mergeVideos` 使用 UTF-8 ffconcat 清单并处理特殊字符路径
- `videoAllScreenshot` 会生成多个文件,不适用单文件原子发布;建议使用独立输出目录
- 输入路径和外部命令参数应来自可信来源
### 7. Excel
按实际使用的库引入依赖:
```xml
com.alibaba
easyexcel
```
```xml
cn.idev.excel
fastexcel
```
注册 converter:
```java
EasyExcelHelper.registerExtraConverters();
// 或
FastExcelHelper.registerExtraConverters();
```
说明:
- 默认日期格式为 `yyyy-MM-dd`
- 默认时间格式为 `HH:mm:ss`
- 默认日期时间格式为 `yyyy-MM-dd HH:mm:ss`
- 默认时区为 `GMT+8`
- Boolean 转换器将 `TRUE`、`TURE`、`1`、`Y`、`YES`、`是`(忽略大小写和首尾空格)识别为 `true`
- 其他非空 Boolean 文本恒为 `false`,空白单元格返回 `null`
### 8. Spring 上下文
额外依赖:
```xml
org.springframework
spring-context
```
使用示例:
```java
UserService service = SpringContextHelper.getBean(UserService.class);
OptionalService optional = SpringContextHelper.getBeanIfPresent(OptionalService.class);
SpringContextHelper.registerBean("runtimeBean", runtimeBean);
SpringContextHelper.publishEvent(new DemoEvent(source));
```
说明:
- `getBeanIfPresent` 只在 Bean 不存在时返回 `null`
- Bean 创建或依赖注入失败会继续抛出,不会伪装成“Bean 不存在”
- Spring 上下文关闭后会清理静态引用
- 动态 Bean 操作要求应用上下文已完成初始化且尚未关闭
### 9. 地理坐标
```java
GeoHelper wgs84 = GeoHelper.ofWGS84(116.397, 39.908);
GeoHelper gcj02 = wgs84.toGCJ02();
double distance = wgs84.getDistanceMeters(otherPoint);
boolean inside = wgs84.isInPolygon(boundaryPoints);
```
说明:
- 支持 WGS-84、GCJ-02 与 BD-09 转换
- 支持距离、圆形、矩形和多边形范围判断
- 范围判断只面向中国境内坐标,不支持跨越 `+180/-180` 反经线的区域
- GCJ-02 / BD-09 逆转换存在约 1~2 米误差
---
## 异常与资源约定
- 参数错误通常抛出 `IllegalArgumentException`
- 外部系统、文件或进程调用失败通常包装为 `IllegalStateException`
- 上传方法会关闭传入的 `InputStream`
- MinIO 下载不关闭调用方提供的 `OutputStream`
- HTTP 响应流必须由调用方关闭
- HTTP 原文日志按当前设计不脱敏,生产环境需控制日志级别和输入内容
- 文件上传并发锁只在单 JVM 内有效
---
## 故障排查 FAQ
### Q1:引入依赖后某个 Helper 报 `ClassNotFoundException`?
Helper 的集成依赖是可选依赖。使用 JSON、Spring、MinIO、EasyExcel 或 FastExcel 时,需要在业务项目中显式引入对应三方库。
### Q2:为什么 GET / HEAD 设置 body 会失败?
JDK `HttpURLConnection` 可能在写 GET 请求体时把请求隐式改成 POST。为避免方法语义被静默改变,Helper 明确拒绝 GET 和 HEAD 请求体;请改用 query 参数或合适的 HTTP 方法。
### Q3:为什么本地文件分片上传在多实例部署中仍需加锁?
`FileUploadHelper` 的锁是当前 JVM 内锁,只能协调单进程内的上传、校验、合并和取消。多个应用实例共享同一磁盘时,需要在业务侧增加分布式锁或按实例隔离上传目录。
### Q4:为什么 MinIO 分片完成建议传 `expectedChunkCount`?
仅根据已经列出的连续分片,无法判断最后是否还缺少一片。带总片数的重载可以同时校验数量和索引连续性,避免发布尾部缺失的对象。
### Q5:FFmpeg 失败后旧目标文件还在吗?
单文件输出方法会先生成临时产物,成功且产物非空后才替换目标。启动失败、超时、非零退出或无有效产物时,旧目标文件保持不变。
### Q6:Excel 中哪些值会转换为 `true`?
只有 `TRUE`、`TURE`、`1`、`Y`、`YES`、`是` 会转换为 `true`;比较忽略大小写和首尾空格。其他非空值统一转换为 `false`。
### Q7:为什么地理范围判断不支持跨反经线区域?
该工具面向中国境内坐标,矩形和多边形按普通经纬度平面边界计算,不处理 `+180/-180` 经度解缠。全球或跨反经线场景应使用球面几何库。
---
完整版本变更见 [CHANGELOG.md](CHANGELOG.md)。