1.公共工具类速查
base-plugin 公共工具类速查
QuickMedia 各插件的公共基础库,本文按分类整理 base-plugin 内所有工具类的典型用法
base-plugin 在 QuickMedia 里的定位是「内部依赖库」:image-plugin、qrcode-plugin、markdown-plugin 等插件对文件、图片、gif、http 的操作,底层都是通过它完成的,日常大概率是随其他插件间接引入,基本不会单独依赖。
不过这套工具类本身写得挺通用,想在业务里直接用也没问题,maven 坐标如下(也可以直接看源码,每个类都很短):
<dependency>
<groupId>com.github.liuyueyi.media</groupId>
<artifactId>base-plugin</artifactId>
<version>3.1.0</version>
</dependency>
下面按分类逐个过一遍,所有方法签名均以源码为准。
1. 文件读取:FileReadUtil
最核心的一个方法,getStreamByFileName 统一了四种文件来源,传什么路径它自己判断:
// 根据路径获取输入流:网络地址 / 绝对路径 / ~/用户目录 / classpath 相对路径
public static InputStream getStreamByFileName(String fileName) throws IOException
判断规则(源码):
http开头 → 走HttpUtil.downFile从网络下载- 绝对路径(win 下含
:或\\开头,其他系统/开头)→ 直接打开本地文件 ~开头 → 替换成用户主目录后打开- 其余 → 作为 classpath 相对路径,从
ClassLoader里找资源
在这个基础之上有几个常用封装:
// 整个文件读成一个字符串(按行读取后用 \n 拼接)
public static String readAll(String fileName) throws IOException
// 分别以字节/字符/行为单位打开读取器,均支持上面四种路径
public static InputStream createByteRead(String fileName) throws IOException
public static Reader createCharRead(String fileName) throws IOException
public static BufferedReader createLineRead(String fileName) throws IOException
典型用法,比如在 qrcode 插件里读取模板文件:
String content = FileReadUtil.readAll("svg/愤怒小猪svg.template");
这里传的是相对路径,实际就是从 src/main/resources(或 jar 包内)读取。
还有一个可能用得上的小能力——魔数识别:
// 读取文件头 28 字节,转成 16 进制字符串
public static String getMagicNum(String file)
public static String getMagicNum(ByteArrayInputStream inputStream)
jpeg 的头是 FFD8FF,png 是 89504E47,gif 是 47494638,webp 是 52494646。想验证一个文件是不是真的图片(而非改了后缀的假货),靠后缀靠不住,靠魔数才靠谱。
2. 文件写入:FileWriteUtil
设计上有点意思的是它的临时目录约定:
// 临时目录:/tmp/quickmedia/(windows 为 C://tmp/quickmedia/)+ yyyyMMdd
// 优先读取系统属性 quick.media.tmp.path,配置了就用它
public static String getTmpPath()
也就是说 quick-media 各插件落地的临时文件,默认在 /tmp/quickmedia/20261112/ 这类目录下;想换地方,启动时加 -Dquick.media.tmp.path=/data/quickmedia/ 即可,不用改代码。
保存文件走 saveFile,支持三种入参类型:
// src 支持 String(路径,含网络url) / URI / InputStream,统一保存并返回文件信息
public static <T> FileInfo saveFile(T src, String inputType) throws Exception
// 流保存到临时目录,文件名 = 时间戳_随机数
public static FileInfo saveFileByStream(InputStream inputStream, String fileType) throws Exception
// 文本保存
public static void saveContent(FileInfo fileInfo, String content) throws FileNotFoundException
public static void saveContent(File file, String content)
// 递归创建目录(各插件输出文件前都会先调它)
public static void mkDir(File path) throws FileNotFoundException
// 解析绝对路径,得到 目录 + 文件名 + 后缀
public static FileInfo parseAbsFileToFileInfo(String absFile)
FileInfo 内部就三个字段 path / filename / fileType,用 getAbsFile() 可以拼回全路径。
3. 文件路径与类型:BasicFileUtil
// 是否绝对路径(自动区分 win/mac/linux)
public static boolean isAbsFile(String fileName)
// ~/xxx 转换为绝对路径
public static String parseHomeDir2AbsDir(String path)
// 根据文件魔数判断文件类型,返回 MediaType 枚举
public static MediaType getMediaType(String path)
MediaType(constants 包下)是个枚举,定义了 jpg/gif/png/webp/mp3/wav/mp4 等常见格式的 后缀、mime、魔数、data-uri 前缀 四件套,配合 FileReadUtil.getMagicNum 就能实现「只认内容不认后缀」的文件类型识别。
4. 图片加载:ImageLoadUtil
// 按路径加载图片,路径规则与 FileReadUtil 一致(本地/网络/classpath),ico 格式走 image4j 解析
public static BufferedImage getImageByPath(String path) throws IOException
// 按路径加载 gif,返回 GifDecoder
public static GifDecoder getGifByPath(String path) throws IOException
这个方法在各个插件里出场率极高,比如 qrcode 插件里设置 logo:
BufferedImage logo = ImageLoadUtil.getImageByPath("logo.jpg"); // classpath
BufferedImage netImg = ImageLoadUtil.getImageByPath("https://blog.hhui.top/hexblog/images/avatar.jpg"); // 网络
注意:加载 ico 需要 image4j 依赖,它在 base-plugin 的 pom 里是 provided scope(不会传递),需要解析 ico 的项目要自己显式引入 org.jclarion:image4j:0.7。
5. 图片操作:ImageOperateUtil
主要服务于「logo 处理」这类场景:
// 矩形图变正圆图(自动居中裁剪出最大的正方形区域再转圆),可加圆形边框
public static BufferedImage makeRoundImg(BufferedImage image, boolean borderEnable, Color borderColor)
// 按指定区域 + 圆角弧度生成圆角图
public static BufferedImage makeRoundImg(BufferedImage image, Rectangle rectangle, int cornerRadius)
// 生成圆角图(整图圆角,常用于二维码圆角)
public static BufferedImage makeRoundedCorner(BufferedImage image, int cornerRadius)
// 生成带圆角边框的图片
public static BufferedImage makeRoundBorder(BufferedImage image, int cornerRadius, Color color)
cornerRadius 的取值,源码注释里给的实测建议是图片宽度的 1/4,传 0 表示直角。
6. 绘图基础:GraphicUtil
这个类在 QuickMedia 里几乎是「绘图画布」的标配入口,最常用的是 getG2d:
// 获取 Graphics2D,内部已经配好了抗锯齿、高质量渲染等 RenderingHints
// 手绘时强烈建议用它拿画笔,否则画出来的线条边缘会有明显的锯齿感
public static Graphics2D getG2d(BufferedImage bf)
// 创建画布,可选填充底色、在 (offsetX, offsetY) 位置贴一张底图
public static BufferedImage createImg(int w, int h, int type)
public static BufferedImage createImg(int w, int h, BufferedImage img)
public static BufferedImage createImg(int w, int h, int offsetX, int offsetY, BufferedImage img)
public static BufferedImage createImg(int w, int h, int offsetX, int offsetY, BufferedImage img, Color fillColor)
public static BufferedImage createImg(int w, int h, int offsetX, int offsetY, int imgType, BufferedImage img, Color fillColor)
// 缩放
public static BufferedImage scaleImg(int w, int h, BufferedImage img) // 拉伸到指定宽高
public static Image smoothScale(BufferedImage img, int w, int h) // 平滑缩放(尺寸相同则原样返回)
// png 转 jpg(透明区域用指定底色填充,避免 jpg 不支持透明出现异常色块)
public static BufferedImage pngToJpg(BufferedImage img, Color bg)
pngToJpg 是个容易忽略但很实用的方法:jpg 不支持透明通道,直接把带透明的 png 写成 jpg,透明区域往往会渲染成奇怪的偏红色调,先用它填个底色再输出就正常了。
7. 颜色处理:ColorUtil
QuickMedia 的插件支持多种传参格式的颜色(Color 对象、int、字符串),互转逻辑都收在这个类里:
// int 颜色转 Color:0xffffffff,高两位透明度,后面依次 R G B
public static Color int2color(int color)
// html 风格颜色转 Color,支持 #rgb / #rgba / #rrggbb / #rrggbbaa 四种格式
public static Color html2color(String color)
// 字符串颜色统一入口:# 开头走 html,0x 开头走十六进制,纯数字走十进制
public static Color str2color(String color)
// 反向转换:Color 转 html 格式字符串
public static String int2htmlColor(int color)
public static String color2htmlColor(Color color)
// 两个预置颜色
public static Color OPACITY; // 全透明
public static Color OFF_WHITE; // 米黄色 0xFFF7EED6
举个对得上的测试 case(ColorUtilTest#testHtml2Color):
ColorUtil.html2color("#a10"); // r=0xaa, g=0x11, b=0x00
ColorUtil.html2color("#aa1100ff"); // 八位,最后两位是透明度 0xff
ColorUtil.int2color(0xffcfccc9); // int 写法,注意别丢掉 0xff 的透明度位
一个老生常谈但确实容易踩的坑:用 int 表示颜色时,最高两位是透明度,如果写成 0x2e317c 这种六位的,实际透明度是 0,画出来会「看不见」,记得带上 0xff 前缀,或者干脆用 # 字符串格式。
8. gif 编解码:GifHelper / GifDecoder / GifEncoder
gif 相关的能力都封装在 GifHelper 这个门面里,日常用它就够了:
// 加载 gif(会先校验文件魔数,不是 gif 直接抛异常)
public static GifDecoder loadGif(String gif) throws IOException
// 加载 gif,把每一帧塞进 list,返回帧间最小延时(毫秒)
public static int loadGif(String gif, List<BufferedImage> list) throws IOException
public static int loadGif(InputStream stream, List<BufferedImage> list)
// 把一组帧合成 gif 输出到文件或流
public static void saveGif(List<BufferedImage> frames, int delay, String out) throws FileNotFoundException
public static void saveGif(List<BufferedImage> frames, int delay, OutputStream out)
// 每帧可以单独指定延时的输出方式
public static void saveGif(List<ImmutablePair<BufferedImage, Integer>> frames, OutputStream out)
组合起来就能实现「拆帧改帧再合成」这种操作,quick-media 里动图相关插件的底层就是这套:
List<BufferedImage> frames = new ArrayList<>();
int delay = GifHelper.loadGif("demo.gif", frames);
// ... 对每一帧做些处理 ...
GifHelper.saveGif(frames, delay, "/tmp/out.gif");
如果想更细粒度地控制,也可以直接用底层的两个类:
// GifDecoder:逐帧读取
GifDecoder decoder = new GifDecoder();
decoder.read(inputStream); // 返回状态码:STATUS_OK / STATUS_FORMAT_ERROR / STATUS_OPEN_ERROR
decoder.getFrameCount(); // 总帧数
decoder.getFrame(0); // 第 n 帧
decoder.getDelay(0); // 第 n 帧延时(毫秒)
decoder.getLoopCount(); // 循环次数,0 表示无限循环
// GifEncoder:逐帧编码(经典写法见类头注释)
GifEncoder encoder = new GifEncoder();
encoder.start(outputStream);
encoder.setRepeat(0); // 0 = 无限循环
encoder.setDelay(100); // 帧延时,毫秒
encoder.addFrame(img); // 逐帧添加
encoder.finish();
9. http 下载:HttpUtil
就一个能力:把网络资源下成输入流。
public static InputStream downFile(String src) throws IOException
public static InputStream downFile(URI uri) throws IOException
实现上用的是 httpclient 的 fluent API,GET 请求,自动带上了浏览器 UA;非 200 响应直接抛 FileNotFoundException。FileReadUtil.getStreamByFileName 里识别到 http 开头的路径,最终就是落到这个方法上。
10. IO 小工具:IoUtil
// 把任意输入流转成字节数组流,好处是流可以重复消费(mark/reset 不支持的场景也 ok)
public static ByteArrayInputStream toByteArrayInputStream(InputStream inputStream) throws IOException
// 流内容读成字符串,方便打印日志
public static String readToString(InputStream inputStream) throws IOException
toByteArrayInputStream 在「一个流要读多次」的场景很常用,比如 GifHelper.loadGif 里先用流读魔数、再用同一个流解码 gif,中间就是靠它做的流复制。
11. base64 与 data-uri:Base64Util / DomUtil
// BufferedImage 编码为 base64 字符串(imgType 如 "png"、"jpg")
public static String encode(BufferedImage bufferedImage, String imgType) throws IOException
public static String encode(ByteArrayOutputStream outputStream)
// base64 字符串解码回 BufferedImage
public static BufferedImage decode2Img(String base64) throws IOException
DomUtil 只有一个方法,把 base64 拼成 html 能直接用的 data-uri:
DomUtil.toDomSrc(Base64Util.encode(img, "png"), MediaType.ImagePng);
// 得到 data:image/png;base64,xxx...,可以直接塞进 <img src="..."> 或 json 响应里
这套组合在 phantom-plugin 的 html 转图片、qrcode 插件的 base64 二维码输出里都有实际应用。
12. 系统与进程:OSUtil / NumUtil / ProcessUtil
// OSUtil:判断是否 windows 系统(各插件区分临时目录、路径分隔符时用)
public static boolean isWinOS()
// NumUtil:进制转换
// 支持十进制/八进制/十六进制字符串转 int(内部用 Long.decode,避免 0xffee0011 这类大数在 Integer 上溢出)
public static Integer decode2int(String str, Integer defaultValue)
public static String toHex(int hex) // int 转 16 进制字符串,单位数补 0
// ProcessUtil:执行外部命令
public boolean process(String cmd) throws Exception
// 用法:ProcessUtil.instance().process("ffmpeg -i in.mp3 out.wav");
ProcessUtil 内部是用线程池并行消费子进程的 stdout/stderr(防止大量输出导致死锁,参考 im4java 的实现),并给进程等待加了 2 秒的超时,防止外部命令卡死拖垮主流程。audio-plugin 调 ffmpeg 转码走的就是它。如果你的业务也需要 Java 里执行外部命令,这个类的实现值得抄一抄。
13. 小结
base-plugin 没有什么黑科技,就是一个个短小、独立、以静态方法为主的工具类集合。理解它的最好方式是带着场景去翻对应的方法:
| 想做什么 | 找谁 |
|---|---|
| 读本地/网络/classpath 的文件 | FileReadUtil |
| 保存流/文本,建目录 | FileWriteUtil |
| 判断文件类型(不看后缀) | FileReadUtil.getMagicNum + MediaType |
| 加载图片/ico/gif | ImageLoadUtil |
| 圆角、圆形 logo、边框 | ImageOperateUtil |
| 拿高质量画笔画布、缩放、png 转 jpg | GraphicUtil |
| 各种格式的颜色互转 | ColorUtil |
| gif 拆帧、合成 | GifHelper(底层 GifDecoder/GifEncoder) |
| 下载网络资源 | HttpUtil |
| 流重复消费 | IoUtil |
| base64、data-uri | Base64Util + DomUtil |
| 执行外部命令 | ProcessUtil |
相关文档: