Java 实现 图片 添加 文字水印、图片水印 工具类

一、话不多说,直接上代码

1.1,水印类型枚举

import lombok.AllArgsConstructor;
import lombok.Getter;/*** @author: wangjing* @createTime: 2023-12-05 15:01* @version: 1.0.0* @Description: 水印类型枚举*/
@Getter
@AllArgsConstructor
@SuppressWarnings("AlibabaEnumConstantsMustHaveComment")
public enum WatermarkTypeEnum {TEXT_WATERMARK(1, "文字水印"),IMAGE_WATERMARK(2, "图片水印");private final Integer code;private final String msg;public static WatermarkTypeEnum matchCode(Integer code) {for (WatermarkTypeEnum watermarkTypeEnum : WatermarkTypeEnum.values()) {if (code.equals(watermarkTypeEnum.getCode())) {return watermarkTypeEnum;}}return null;}}

1.2,图片水印位置类型枚举

import lombok.AllArgsConstructor;
import lombok.Getter;/*** @author: wangjing* @createTime: 2023-12-05 15:01* @version: 1.0.0* @Description: 图片水印位置类型枚举*/
@Getter
@AllArgsConstructor
@SuppressWarnings("AlibabaEnumConstantsMustHaveComment")
public enum ImageWatermarkPositionTypeEnum {/*** 九方位*/CENTER(1, "居中"),LEFT_SIDE(2, "左侧"),RIGHT_SIDE(3, "右侧"),DIRECTLY_ABOVE(4, "正上方"),DIRECTLY_BELOW(5, "正下方"),UPPER_LEFT(6, "左上角"),LOWER_LEFT(7, "左下角"),UPPER_RIGHT(8, "右上方"),LOWER_RIGHT(9, "右下方"),/*** 平铺*/TILE(10, "平铺水印"),TILT_TILE(11, "倾斜平铺");private final Integer code;private final String msg;public static ImageWatermarkPositionTypeEnum matchCode(Integer code) {for (ImageWatermarkPositionTypeEnum imageWatermarkPositionTypeEnum : ImageWatermarkPositionTypeEnum.values()) {if (code.equals(imageWatermarkPositionTypeEnum.getCode())) {return imageWatermarkPositionTypeEnum;}}return null;}}

1.3,图片水印处理类

import cn.piesat.space.watermark.enums.ImageWatermarkPositionTypeEnum;
import cn.piesat.space.watermark.enums.WatermarkTypeEnum;
import lombok.Data;import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;/*** @author: wangjing* @createTime: 2023-12-05 15:01* @version: 1.0.0* @Description: 图片水印处理类*/
public class ImageWatermarkUtil {/*** 水印信息对象*/@Datastatic class WatermarkInfo {BufferedImage watermarkBufferedImage;Integer watermarkWidth;Integer watermarkHeight;}/*** 获取水印信息并对画布做基础配置** @param graphics* @param watermarkType* @param watermark* @param font* @param color* @return*/public static WatermarkInfo getWatermarkInfo(Graphics2D graphics, Integer watermarkType, String watermark,Font font, Color color) {WatermarkInfo watermarkInfo = new WatermarkInfo();// 文字水印if (WatermarkTypeEnum.TEXT_WATERMARK.getCode().equals(watermarkType)) {// 设置字体graphics.setFont(font);// 设置颜色graphics.setColor(color);watermarkInfo.setWatermarkWidth(getWatermarkLength(watermark, graphics));watermarkInfo.setWatermarkHeight(font.getSize());} else {// 图片水印BufferedImage对象BufferedImage watermarkBufferedImage = readPicture(watermark);watermarkInfo.setWatermarkBufferedImage(watermarkBufferedImage);watermarkInfo.setWatermarkWidth(watermarkBufferedImage.getWidth());watermarkInfo.setWatermarkHeight(watermarkBufferedImage.getHeight());}return watermarkInfo;}/*** 添加水印** @param bufferedImage              源文件* @param font                       字体* @param color                      颜色* @param watermarkType              水印类型(具体见 WatermarkTypeEnum 枚举类)* @param imageWatermarkPositionType 水印位置(具体见 ImageWatermarkPositionType)* @param watermark                  水印(文字或图片地址)* @return*/public static BufferedImage addWatermark(BufferedImage bufferedImage, Font font, Color color, Integer watermarkType,Integer imageWatermarkPositionType, String watermark) {// 图片宽度Integer imageWidth = bufferedImage.getWidth();// 图片高度Integer imageHeight = bufferedImage.getHeight();// 创建画笔Graphics2D graphics = bufferedImage.createGraphics();WatermarkInfo watermarkInfo = getWatermarkInfo(graphics, watermarkType, watermark, font, color);switch (ImageWatermarkPositionTypeEnum.matchCode(imageWatermarkPositionType)) {// 居中case CENTER:centerCompute(graphics, imageWidth, imageHeight, watermarkInfo, watermarkType, watermark);break;// 左侧case LEFT_SIDE:leftSideCompute(graphics, imageHeight, watermarkInfo, watermarkType, watermark);break;// 右侧case RIGHT_SIDE:rightSideCompute(graphics, imageWidth, imageHeight, watermarkInfo, watermarkType, watermark);break;// 正上方case DIRECTLY_ABOVE:directlyAboveCompute(graphics, imageWidth, watermarkInfo, watermarkType, watermark);break;// 正下方case DIRECTLY_BELOW:directlyBelowCompute(graphics, imageWidth, imageHeight, watermarkInfo, watermarkType, watermark);break;// 左上角case UPPER_LEFT:upperLeftCompute(graphics, watermarkInfo, watermarkType, watermark);break;// 左下角case LOWER_LEFT:lowerLeftCompute(graphics, imageHeight, watermarkInfo, watermarkType, watermark);break;// 右上角case UPPER_RIGHT:upperRightCompute(graphics, imageWidth, watermarkInfo, watermarkType, watermark);break;// 右下角case LOWER_RIGHT:lowerRightCompute(graphics, imageWidth, imageHeight, watermarkInfo, watermarkType, watermark);break;// 右下角case TILE:tileDrawCompute(graphics, imageWidth, imageHeight, watermarkInfo, watermarkType, watermark);break;// 右下角case TILT_TILE:// 倾斜度graphics.rotate(0.2);tileDrawCompute(graphics, imageWidth, imageHeight, watermarkInfo, watermarkType, watermark);break;default: // 错误的操作类型throw new RuntimeException("错误的操作类型");}graphics.dispose();return bufferedImage;}/*** 居中 配置** @param graphics* @param imageWidth* @param imageHeight* @param watermarkInfo* @param watermarkType* @param watermark*/public static void centerCompute(Graphics2D graphics, Integer imageWidth, Integer imageHeight,WatermarkInfo watermarkInfo, Integer watermarkType, String watermark) {// 设置水印的坐标(为原图片中间位置)Integer x = (imageWidth - watermarkInfo.getWatermarkWidth()) / 2;Integer y = imageHeight / 2 + (watermarkInfo.getWatermarkHeight() / 2);drawTestOrImage(graphics, x, y, watermarkType, watermark, watermarkInfo);}/*** 左侧 配置** @param graphics* @param imageHeight* @param watermarkInfo* @param watermarkType* @param watermark*/public static void leftSideCompute(Graphics2D graphics, Integer imageHeight, WatermarkInfo watermarkInfo,Integer watermarkType, String watermark) {// 设置水印的坐标(为原图片中间位置)Integer x = 0;Integer y = (imageHeight + watermarkInfo.getWatermarkHeight()) / 2;drawTestOrImage(graphics, x, y, watermarkType, watermark, watermarkInfo);}/*** 右侧配置** @param graphics* @param imageWidth* @param imageHeight* @param watermarkInfo* @param watermarkType* @param watermark*/public static void rightSideCompute(Graphics2D graphics, Integer imageWidth, Integer imageHeight,WatermarkInfo watermarkInfo, Integer watermarkType, String watermark) {// 设置水印的坐标(为原图片中间位置)Integer x = imageWidth - watermarkInfo.getWatermarkWidth();Integer y = (imageHeight + watermarkInfo.getWatermarkHeight()) / 2;drawTestOrImage(graphics, x, y, watermarkType, watermark, watermarkInfo);}/*** 正上方配置** @param graphics* @param imageWidth* @param watermarkInfo* @param watermarkType* @param watermark*/public static void directlyAboveCompute(Graphics2D graphics, Integer imageWidth, WatermarkInfo watermarkInfo,Integer watermarkType, String watermark) {// 设置水印的坐标(为原图片中间位置)Integer x = (imageWidth - watermarkInfo.getWatermarkWidth()) / 2;Integer y = watermarkInfo.getWatermarkHeight();drawTestOrImage(graphics, x, y, watermarkType, watermark, watermarkInfo);}/*** 正下方** @param graphics* @param imageWidth* @param imageHeight* @param watermarkInfo* @param watermarkType* @param watermark*/public static void directlyBelowCompute(Graphics2D graphics, Integer imageWidth, Integer imageHeight,WatermarkInfo watermarkInfo, Integer watermarkType, String watermark) {// 设置水印的坐标(为原图片中间位置)Integer x = (imageWidth - watermarkInfo.getWatermarkWidth()) / 2;Integer y = imageHeight;drawTestOrImage(graphics, x, y, watermarkType, watermark, watermarkInfo);}/*** 左上角** @param graphics* @param watermarkInfo* @param watermarkType* @param watermark*/public static void upperLeftCompute(Graphics2D graphics, WatermarkInfo watermarkInfo, Integer watermarkType,String watermark) {// 设置水印的坐标(为原图片中间位置)Integer x = 0;Integer y = watermarkInfo.getWatermarkHeight();drawTestOrImage(graphics, x, y, watermarkType, watermark, watermarkInfo);}/*** 左下角** @param graphics* @param imageHeight* @param watermarkInfo* @param watermarkType* @param watermark*/public static void lowerLeftCompute(Graphics2D graphics, Integer imageHeight, WatermarkInfo watermarkInfo,Integer watermarkType, String watermark) {// 设置水印的坐标(为原图片中间位置)Integer x = 0;Integer y = imageHeight;drawTestOrImage(graphics, x, y, watermarkType, watermark, watermarkInfo);}/*** 右上方** @param graphics* @param imageWidth* @param watermarkInfo* @param watermarkType* @param watermark*/public static void upperRightCompute(Graphics2D graphics, Integer imageWidth, WatermarkInfo watermarkInfo,Integer watermarkType, String watermark) {// 设置水印的坐标(为原图片中间位置)Integer x = imageWidth - watermarkInfo.getWatermarkWidth();Integer y = watermarkInfo.getWatermarkHeight();drawTestOrImage(graphics, x, y, watermarkType, watermark, watermarkInfo);}/*** 右下方** @param graphics* @param imageWidth* @param imageHeight* @param watermarkInfo* @param watermarkType* @param watermark*/public static void lowerRightCompute(Graphics2D graphics, Integer imageWidth, Integer imageHeight,WatermarkInfo watermarkInfo, Integer watermarkType, String watermark) {// 设置水印的坐标(为原图片中间位置)Integer x = imageWidth - watermarkInfo.getWatermarkWidth();Integer y = imageHeight;drawTestOrImage(graphics, x, y, watermarkType, watermark, watermarkInfo);}/*** 平铺** @param graphics* @param imageWidth* @param imageHeight* @param watermarkInfo* @param watermarkType* @param watermark*/public static void tileDrawCompute(Graphics2D graphics, Integer imageWidth, Integer imageHeight,WatermarkInfo watermarkInfo, Integer watermarkType, String watermark) {Integer watermarkHeight = watermarkInfo.getWatermarkHeight();Integer watermarkWidth = watermarkInfo.getWatermarkWidth();// 间隔Integer split = watermarkHeight * 2;// x,y可以绘制的数量,多加一个补充空白int xCanNum = imageWidth / watermarkWidth + 1;int yCanNum = imageHeight / watermarkHeight + 1;for (int i = 1; i <= yCanNum; i++) {int y = watermarkHeight * i + split * i;for (int j = 0; j < xCanNum; j++) {int x = watermarkWidth * j + split * j;drawTestOrImage(graphics, x, y - (watermarkHeight + split) * j, watermarkType, watermark,watermarkInfo);}}}/*** 画文字水印或图片水印** @param graphics* @param x* @param y* @param watermarkType* @param watermark* @param watermarkInfo*/public static void drawTestOrImage(Graphics2D graphics, int x, int y, Integer watermarkType, String watermark,WatermarkInfo watermarkInfo) {// 文字水印if (WatermarkTypeEnum.TEXT_WATERMARK.getCode().equals(watermarkType)) {graphics.drawString(watermark, x, y);} else {// 图片水印graphics.drawImage(watermarkInfo.getWatermarkBufferedImage(), x, y - watermarkInfo.getWatermarkHeight(), watermarkInfo.getWatermarkWidth(),watermarkInfo.getWatermarkHeight(), null);}}/*** 获取水印文字的长度** @param watermarkContent 文字水印内容* @param graphics         图像类* @return*/private static Integer getWatermarkLength(String watermarkContent, Graphics2D graphics) {return graphics.getFontMetrics(graphics.getFont()).charsWidth(watermarkContent.toCharArray(), 0,watermarkContent.length());}/*** 将图片写入到指定位置** @param bufImg* @param tarImgPath*/public static void writeImage(BufferedImage bufImg, String tarImgPath) {// 输出图片try {FileOutputStream outImgStream = new FileOutputStream(tarImgPath);ImageIO.write(bufImg, "png", outImgStream);outImgStream.flush();outImgStream.close();} catch (IOException e) {throw new RuntimeException(e);}}/*** 读取图片** @param path* @return*/public static BufferedImage readPicture(String path) {try {// 尝试获取本地return readLocalPicture(path);} catch (Exception e) {// 尝试获取网路return readNetworkPicture(path);}}/*** 读取本地图片** @param path* @return*/public static BufferedImage readLocalPicture(String path) {if (null == path) {throw new RuntimeException("本地图片路径不能为空");}// 读取原图片信息 得到文件File srcImgFile = new File(path);try {// 将文件对象转化为图片对象return ImageIO.read(srcImgFile);} catch (IOException e) {throw new RuntimeException(e);}}/*** 读取网络图片** @param path 网络图片地址*/public static BufferedImage readNetworkPicture(String path) {if (null == path) {throw new RuntimeException("网络图片路径不能为空");}try {// 创建一个URL对象,获取网络图片的地址信息URL url = new URL(path);// 将URL对象输入流转化为图片对象 (url.openStream()方法,获得一个输入流)BufferedImage bugImg = ImageIO.read(url.openStream());if (null == bugImg) {throw new RuntimeException("网络图片地址不正确");}return bugImg;} catch (IOException e) {throw new RuntimeException(e);}}/*** 文字类型测试* @param args*/public static void main(String[] args) {// 要处理的图片路径:String localPath = "/Users/wangjing/Desktop/watermark/image/test1.jpg";// 定义存储的地址String tarImgPath = "/Users/wangjing/Desktop/watermark/image/";// 文字水印内容String textWatermark = "刘亦菲";// 根据图片的背景设置水印颜色Color color = new Color(255, 255, 255, 128);// 设置字体  画笔字体样式为微软雅黑,加粗,文字大小为45ptFont font = new Font("微软雅黑", Font.BOLD, 45);for (int i = 1; i < 12; i++) {BufferedImage bufferedImage = readPicture(localPath);BufferedImage test = addWatermark(bufferedImage, font, color, WatermarkTypeEnum.TEXT_WATERMARK.getCode(),i, textWatermark);writeImage(test, tarImgPath + "watermark_text_" + i + ".jpg");}}/*** 图片水印测试** @param args*/
//    public static void main(String[] args) {
//
//        // 要处理的图片路径:
//        String localPath = "https://vrar-obs-production.obs.cn-north-4.myhuaweicloud.com/survey/watermark/image.jpg";
//
//        // 定义存储的地址
//        String tarImgPath = "/Users/wangjing/Desktop/watermark/image/";
//
//        // 图片水印内容
//        String textWatermark = "https://vrar-obs-production.obs.cn-north-4.myhuaweicloud" +
//                ".com/survey/watermark/watermark.png";
//
//
//        for (int i = 1; i < 12; i++) {
//            BufferedImage bufferedImage = readPicture(localPath);
//            BufferedImage test = addWatermark(bufferedImage, null, null, WatermarkTypeEnum.IMAGE_WATERMARK.getCode(), i,
//                    textWatermark);
//            writeImage(test, tarImgPath + "watermark_image_" + i + ".jpg");
//        }
//    }}

二、看效果

2.1,原图

2.2,文字水印生成效果

2.3,水印图片

2.4,图片水印生成效果

注:以上内容仅提供参考和交流,请勿用于商业用途,如有侵权联系本人删除!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/204514.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

「Verilog学习笔记」状态机-重叠序列检测

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 读入数据移位寄存&#xff0c;寄存后的数据与序列数做对比&#xff0c;相等则flag为1&#xff0c;不等则为0 timescale 1ns/1nsmodule sequence_test2(input wire clk ,in…

java单人聊天

服务端 package 单人聊天;import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import…

拼多多商品详情API:构建高效电子商务解决方案

一、引言 随着互联网的快速发展&#xff0c;电子商务行业正在迅速崛起&#xff0c;其中拼多多以其独特的商业模式和创新的商品详情API&#xff0c;成为了行业内的佼佼者。本文将深入探讨拼多多商品详情API的技术特点、实现方式及其在电子商务解决方案中的应用。 二、拼多多商…

【PyTorch】模型选择、欠拟合和过拟合

文章目录 1. 理论介绍2. 实例解析2.1. 实例描述2.2. 代码实现2.2.1. 完整代码2.2.2. 输出结果 1. 理论介绍 将模型在训练数据上拟合的比在潜在分布中更接近的现象称为过拟合&#xff0c; 用于对抗过拟合的技术称为正则化。训练误差和验证误差都很严重&#xff0c; 但它们之间差…

跨地域组网挑战之如何实现数据传输无缝连接?

全球化是当今商业领域的主要趋势&#xff0c;许多行业都迅速扩展到跨国和跨地区运营。软件开发领域也不例外&#xff0c;为了利用全球资源和降低成本&#xff0c;越来越多的软件开发公司将团队分布在不同地理位置&#xff0c;而这也面临着实时回传开发数据至总部服务器的企业组…

要求CHATGPT高质量回答的艺术:提示工程技术的完整指南—第 7 章:让我们想一想 提示语

要求CHATGPT高质量回答的艺术&#xff1a;提示工程技术的完整指南—第 7 章&#xff1a;"让我们想一想 "提示语 让我们想一想 "提示语是一种用于鼓励 ChatGPT 生成具有反思性和沉思性文字的技巧。 让我们想一想 "提示语的提示公式是 “让我们想一想”&am…

【C++】Boost库LexicalCast模块介绍与使用

Boost库LexicalCast模块 文章目录 Boost库LexicalCast模块介绍使用基础API自定义类型转换 介绍 lexical_cast库进行”字面值“之间的通用转换 头文件 #include<boost/lexical_cast.hpp>使用 基础API lexical_cast boost::lexical_cast可以在各种基本类型中转换 #i…

计算两个结构的差

平面上有6个点&#xff0c;以6a1的方式运动 1 1 1 1 - - - 1 - - - 1 现在有一个点逃逸&#xff0c;剩下的5个点将如何运动&#xff1f; 2 2 2 3 - - - 3 - - - 3 将6a1的6个点减去1个点&#xff0c;只有两种可能&#xff0c;或者变成5a2&#xff0c…

SpringBoot的配置加载优先级

目录 一、背景分析 二、学习资源 三、具体使用 四、一些小技巧 方式一 方式二 一、背景分析 SpringBoot项目在打包之后&#xff0c;其配置文件就在jar包内&#xff0c;如果没有<配置文件优先级>这个机制&#xff0c;那么项目打成jar包之后&#xff0c;如果启动项目…

视频监控管理平台/智能监测/检测系统EasyCVR智能地铁监控方案,助力地铁高效运营

近日&#xff0c;关于全国44座城市开通地铁&#xff0c;却只有5座城市赚钱的新闻冲上热搜。地铁作为城市交通的重要枢纽&#xff0c;是人们出行必不可少的一种方式&#xff0c;但随着此篇新闻的爆出&#xff0c;大家也逐渐了解到城市运营的不易&#xff0c;那么&#xff0c;如何…

HTML 块级元素与行内元素有哪些以及注意、总结

行内元素和块级元素是HTML中的两种元素类型&#xff0c;它们在页面中的显示方式和行为有所不同。 块级元素&#xff08;Block-level Elements&#xff09;&#xff1a; 常见的块级元素有div、p、h1-h6、ul、ol、li、table、form等。 块级元素会独占一行&#xff0c;即使没有…

Linux下超轻量级Rust开发环境搭建:一、安装Rust

Rust语言在国内逐步开始流行&#xff0c;但开发环境的不成熟依然困扰不少小伙伴。 结合我个人的使用体验&#xff0c;推荐一种超轻量级的开发环境&#xff1a;Rust Helix Editor。运行环境需求很低&#xff0c;可以直接在Linux终端里进行代码开发。对于工程不是太过庞大的Rus…

目标检测——SPPNet算法解读

论文&#xff1a;Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition 作者&#xff1a;Kaiming He, Xiangyu Zhang, Shaoqing Ren, and Jian Sun 链接&#xff1a;https://arxiv.org/abs/1406.4729 目录 1、算法概述2、Deep Networks with Spatia…

share pool的组成

share pool的组成 3块区域&#xff1a;free,library cache,row cache 通过查看v$librarycache视图&#xff0c;可以监控library cache的活动情况&#xff0c;进一步衡量share pool设置是否合理; 其中reloads列&#xff0c;表示对象被重新加载的次数&#xff0c;在一个设置合…

代码随想录算法训练营第30天|● 332.重新安排行程 ● 51. N皇后 ● 37. 解数独 ● 总结

332. 重新安排行程 困难 相关标签 相关企业 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [from(i), to(i)] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从 JFK&#xff08;肯尼迪国际机场&#xff09;出发的先生&#…

logback日志打印操作人

logback日志打印操作人 自定义拦截器 package com.demo.dv.net.config;import com.demo.dv.net.common.domain.UserInfo; import com.demo.dv.net.common.utils.CurrentUserUtil; import org.slf4j.MDC; import org.springframework.stereotype.Component; import org.spring…

适用于 Windows 的 10 个顶级分区管理器软件

您想要对硬盘驱动器或 USB 驱动器进行分区的原因可能有多种。许多用户希望对其外部和内部硬盘驱动器进行分区以有效地管理数据。为了处理分区&#xff0c;Windows为用户提供了一个内置的分区管理工具。 Windows 用户可以通过磁盘管理面板对任何驱动器进行分区。然而&#xff0…

哪种超声波清洗眼镜不伤镜片?超声波什么牌子好?眼镜清洗机推荐

戴眼镜的朋友就能够感同身受&#xff0c;眼镜佩戴一天真的特别容易脏&#xff0c;灰尘或者是脸部出汗出油等&#xff0c;让耳机惨不忍睹&#xff01;对于眼镜清洗的方式也有很多种&#xff0c;那么到底哪种清洗方式才是真正有效果并且不伤眼镜的呢&#xff1f;跟随笔者的脚步来…

电商控制台系统商品模块的处理

电商项目&#xff08;前台&#xff09;&#xff1a; 登陆 注册&#xff08;安全&#xff0c;网页版&#xff09; &#xff08;审核机制&#xff09; 外部功能&#xff1a; check审核机制 Uncheck未通过机制 findUserByCheck()一条一条展示用户 findUser() 批量获取用户(分页…

安装Kuboard管理K8S集群

目录 第一章.安装Kuboard管理K8S集群 1.安装kuboard 2.绑定K8S集群&#xff0c;完成信息设定 3.内网安装 第二章.kuboard-spray安装K8S 2.1.先拉镜像下来 2.2.之后打开后&#xff0c;先熟悉功能&#xff0c;注意版本 2.3.打开资源包管理&#xff0c;选择符合自己服务器…