1. 添加ZXing依赖到Spring Boot项目场景描述: 在需要快速、高效地生成二维码或条形码的应用中，如电子票务、产品追踪、个人身份验证等，集成ZXing库至Spring Boot项目是至关重要的一步。在项目的pom.xml文件中添加以下依赖以引入ZXing库：xmlCopy code<dependencies> <dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.5.2</version> </dependency> </dependencies>使用最新版本的ZXing库可以确保你的应用具备最新的二维码和条形码生成能力，包括更好的性能和支持更多格式。2. 创建二维码生成服务场景描述: 此服务类适用于需要将URL、文本信息或其他数据快速转换为二维码图像的场合，如商业广告、社交媒体共享、安全验证等。代码示例与注释:import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.MultiFormatWriter; import com.google.zxing.common.BitMatrix; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.util.HashMap; import java.util.Map; /** * 生成二维码图像类 * * @author songzixian */ public class QRCodeService { /** * 生成二维码图像 * * @param text 要编码的文本 * @param width 二维码图像宽度 * @param height 二维码图像高度 * @param filePath 文件存储路径 * @return 生成的二维码图像的字节数组 */ public void generateQRCode(String text, int width, int height, String filePath) { try { // 定义二维码参数 Map<EncodeHintType, Object> hints = new HashMap<>(); // 设置字符编码 hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); // 错误纠正级别 hints.put(EncodeHintType.ERROR_CORRECTION, com.google.zxing.qrcode.decoder.ErrorCorrectionLevel.H); // 二维码边距 hints.put(EncodeHintType.MARGIN, 1); // 使用QRCodeWriter生成二维码矩阵信息 MultiFormatWriter writer = new MultiFormatWriter(); BitMatrix bitMatrix = writer.encode(text, BarcodeFormat.QR_CODE, width, height, hints); // 将二维码矩阵信息转换为图像 BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { image.setRGB(x, y, bitMatrix.get(x, y) ? Color.BLACK.getRGB() : Color.WHITE.getRGB()); } } // 江二维码保存成文件 File qrCodeFile = new File(filePath); ImageIO.write(image, "png", qrCodeFile); System.out.println("二维码生成成功: " + filePath); } catch (Exception e) { e.printStackTrace(); } } /** * 测试 */ public static void main(String[] args) { QRCodeService qrCodeGenerator = new QRCodeService(); // 要存储在二维码中的文本数据 String text = "https://songzixian.com"; // 二维码的宽度 int width = 300; // 二维码的高度 int height = 300; // 二维码存储路径 String filePath = "D:\\data\\qrcode.png"; qrCodeGenerator.generateQRCode(text, width, height, filePath); } }此服务类提供了一个generateQRCode方法，该方法可以将任意文本（如URL或文本信息）转换为指定大小的二维码图像，并保存至指定路径。3. 创建条形码生成服务场景描述: 适用于零售、库存管理、物流追踪等需要快速生成条形码的业务场景。代码示例与注释:import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.MultiFormatWriter; import com.google.zxing.common.BitMatrix; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * 生成条形图像类 * * @author songzixian */ public class barCodeService { /** * 生成条形码图像 * * @param text 要编码的数据 * @param width 条形码图像宽度 * @param height 条形码图像高度 * @param filePath 文件存储路径 * @return 生成的条形码图像的字节数组 * @throws IOException 当图像写入过程中出现问题时抛出 */ public void generateBarcode(String text, int width, int height, String filePath) { try { // 定义二维码参数 Map<EncodeHintType, Object> hints = new HashMap<>(); // 设置字符编码 hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); // 使用QRCodeWriter生成条形码码矩阵信息 MultiFormatWriter writer = new MultiFormatWriter(); BitMatrix bitMatrix = writer.encode(text, BarcodeFormat.CODE_128, width, height, hints); // 创建BufferedImage对象来表示条形码 BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { // 生成黑色条和白色背景的条形码 image.setRGB(x, y, bitMatrix.get(x, y) ? 0 : 0xFFFFFF); } } // 将条形码保存到文件 File barcodeFile = new File(filePath); ImageIO.write(image, "png", barcodeFile); System.out.println("条形码已生成并保存到: " + filePath); } catch (Exception e) { e.printStackTrace(); } } /** * 测试 */ public static void main(String[] args) { barCodeService barcodeGenerator = new barCodeService(); // 要存储在条形码中的数据 String text = "1234567890"; // 条形码的宽度 int width = 200; // 条形码的高度 int height = 30; String filePath = "D:\\data\\barCode.png"; barcodeGenerator.generateBarcode(text, width, height, filePath); } }此服务类通过generateBarcode方法生成条形码，支持将数字或文本信息转换为条形码，非常适合产品编码和库存管理系统。把上面的代码复制到你的Spring Boot的Service代码中,就成功地在Spring Boot应用中整合了ZXing库，可以生成二维码和条形码了。
在子线程中通过join()方法指定顺序通过join()方法使当前线程“阻塞”，等待指定线程执行完毕后继续执行。举例：在线程thread2中，加上一句thread1.join()，其意义在于，当前线程2运行到此行代码时会进入阻塞状态，直到线程thread1执行完毕后，线程thread2才会继续运行，这就保证了线程thread1与线程thread2的运行顺序。/** * * Description: 线程测试Demo * * * @author songzixian * @version v2.0.0 * @create 2022-05-08 0:50 * @see com.songzixian.constant */ public class ThreadJoinDemo { public static void main(String[] args) throws InterruptedException { final Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("访问宋子宪博客！"); } }); final Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { thread1.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("成功访问中！"); } }); final Thread thread3 = new Thread(new Runnable() { @Override public void run() { try { thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("关闭博客！"); } }); //下面三行代码顺序可随意调整，程序运行结果不受影响，因为我们在子线程中通过“join()方法”已经指定了运行顺序。 thread3.start(); thread2.start(); thread1.start(); } }控制台运行结果：访问宋子宪博客！ 成功访问中！ 关闭博客！在主线程中通过join()方法指定顺序简单说一下子线程与主线程的区别，子线程指的是发生在Thread内部的代码，主线程指的是发生在main函数中的代码，我们可以在main函数中通过join()方法让主线程阻塞等待以达到指定顺序执行的目的。/** * * Description: 线程测试Demo * * * @author songzixian * @version v2.0.0 * @create 2022-05-08 0:50 * @see com.songzixian.constant */ public class ThreadJoinDemo { public static void main(String[] args) throws InterruptedException { final Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("访问宋子宪博客！"); } }); final Thread thread2 = new Thread(new Runnable() { @Override public void run() { System.out.println("成功访问中！"); } }); final Thread thread3 = new Thread(new Runnable() { @Override public void run() { System.out.println("关闭博客！"); } }); thread1.start(); thread1.join(); thread2.start(); thread2.join(); thread3.start(); } }通过倒数计时器CountDownLatch实现public class ThreadJoinDemo { private static CountDownLatch countDownLatch1 = new CountDownLatch(1); private static CountDownLatch countDownLatch2 = new CountDownLatch(1); public static void main(String[] args) { final Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("访问宋子宪博客！"); countDownLatch1.countDown(); } }); final Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { countDownLatch1.await(); System.out.println("成功访问中！"); countDownLatch2.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } }); final Thread thread3 = new Thread(new Runnable() { @Override public void run() { try { countDownLatch2.await(); System.out.println("关闭博客！"); } catch (InterruptedException e) { e.printStackTrace(); } } }); //下面三行代码顺序可随意调整，程序运行结果不受影响 thread3.start(); thread1.start(); thread2.start(); } }通过创建单一化线程池newSingleThreadExecutor()实现CountDownLatch通过计数器提供了更灵活的控制，只要检测到计数器为0当前线程就可以往下执行而不用管相应的thread是否执行完毕。public class ThreadJoinDemo { static ExecutorService executorService = Executors.newSingleThreadExecutor(); public static void main(String[] args) { final Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("访问宋子宪博客！"); } }); final Thread thread2 =new Thread(new Runnable() { @Override public void run() { System.out.println("成功访问中！！"); } }); final Thread thread3 = new Thread(new Runnable() { @Override public void run() { System.out.println("关闭博客！"); } }); executorService.submit(thread1); executorService.submit(thread2); executorService.submit(thread3); executorService.shutdown(); //使用完毕记得关闭线程池 } }
Hutool是一个小而全的Java工具类库，通过静态方法封装，降低相关API的学习成本，提高工作效率，使Java拥有函数式语言般的优雅引入Hutool依赖<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.0.M2</version> </dependency> 第一种方式/** * @author songzixian */ public class TestHutoolTree { public static void main(String[] args) { // 构建node列表(数据源) List<TreeNode<String>> nodeList = CollUtil.newArrayList(); nodeList.add(new TreeNode<>("1", "0", "系统管理", 5)); nodeList.add(new TreeNode<>("11", "1", "用户管理", 222222)); nodeList.add(new TreeNode<>("111", "11", "用户添加", 0)); nodeList.add(new TreeNode<>("2", "0", "店铺管理", 1)); nodeList.add(new TreeNode<>("21", "2", "商品管理", 44)); nodeList.add(new TreeNode<>("221", "2", "商品管理2", 2)); // 0表示最顶层的id是0 List<Tree<String>> treeList = TreeUtil.build(nodeList, "0"); System.out.println(JSONUtil.toJsonStr(treeList)); } }输出三级Json第二种方式准备测试表和数据SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for system_classification -- ---------------------------- DROP TABLE IF EXISTS `system_classification`; CREATE TABLE `system_classification` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分类id', `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '分类名称', `parent_id` bigint(20) NULL DEFAULT 0 COMMENT '父分类id', `sort` int(11) NOT NULL DEFAULT 0 COMMENT '显示顺序', `level` int(11) NULL DEFAULT NULL COMMENT '层级', `status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '分类状态（0正常 1停用）', `deth` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '纵深', `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '部门表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of system_classification -- ---------------------------- INSERT INTO `system_classification` VALUES (1, '根节点', 0, 0, 1, 0, '1', 'admin', '2021-01-05 17:03:47', '1', '2022-03-22 10:18:56', b'0', 1); INSERT INTO `system_classification` VALUES (2, '王二', 1, 1024, 2, 0, '1', '1', '2022-03-18 11:35:43', '1', '2022-03-22 10:19:00', b'0', 1); INSERT INTO `system_classification` VALUES (3, '王1二', 1, 1024, 2, 0, '1', '1', '2022-03-18 11:38:05', '1', '2022-03-22 10:19:02', b'0', 1); INSERT INTO `system_classification` VALUES (4, '一级目', 0, 1, 1, 0, NULL, '1', '2022-03-22 10:09:25', '1', '2022-03-22 10:09:25', b'0', 1); INSERT INTO `system_classification` VALUES (5, '一级目2', 0, 1, 1, 0, NULL, '1', '2022-03-22 10:09:32', '1', '2022-03-22 10:09:32', b'0', 1); INSERT INTO `system_classification` VALUES (6, '二级目2', 5, 1, 2, 0, '5', '1', '2022-03-22 10:09:40', '1', '2022-03-22 10:14:17', b'0', 1); INSERT INTO `system_classification` VALUES (7, 'san级目2', 6, 1, 3, 0, '5,6', '1', '2022-03-22 10:19:28', '1', '2022-03-22 10:19:28', b'0', 1); SET FOREIGN_KEY_CHECKS = 1; 创建测试数据对应实体类public class ClassificationDO { /** * 分级ID */ @TableId private Long id; /** * 分级名称 */ private String name; /** * 父分级ID * * 关联 {@link #id} */ private Long parentId; /** * 显示顺序 */ private Integer sort; /** * 层级 */ private Integer level; /** * 纵深 * */ private String deth; /** * 分级状态 * * 枚举 {@link CommonStatusEnum} */ private Integer status; /** * 多租户编号 */ private Long tenantId; } 四、实现细节根据pid与id的逻辑关系构建tree;​public static void main(String[] args) { //配置 TreeNodeConfig treeNodeConfig = new TreeNodeConfig(); // 自定义属性名 都要默认值的 treeNodeConfig.setWeightKey("order"); treeNodeConfig.setIdKey("rid"); // 最大递归深度 treeNodeConfig.setDeep(3); //数据源 List<Student> nodeList = list(); //转换器 List<Tree<String>> treeNodes = TreeUtil.build(nodeList, "0", treeNodeConfig, (treeNode, tree) -> { tree.setId(treeNode.getId()); tree.setParentId(treeNode.getParentId()); tree.setWeight(treeNode.getWeight()); tree.setName(treeNode.getName()); // 扩展属性 ... tree.putExtra("extraField", 666); tree.putExtra("other", new Object()); System.out.println(JSON.toJSONString(treeNodes)); } 输出3级树形Json格式
Stream是Java 8非常使用的新特性，这个版本新增的Stream，配合同版本出现的 Lambda ，给我们操作集合（Collection）提供了极大的便利。Stream可以由数组或集合创建，对流的操作分为两种：中间操作，每次返回一个新的流，可以有多个。终端操作，每个流只能进行一次终端操作，终端操作结束后流无法再次使用。终端操作会产生一个新的集合或值。另外，Stream有几个特性：stream不存储数据，而是按照特定的规则对数据进行计算，一般会输出结果。stream不会改变数据源，通常情况下会产生一个新的集合或一个值。stream具有延迟执行特性，只有调用终端操作时，中间操作才会执行。Stream的创建Stream可以通过集合数组创建。第一种方式1、通过 java.util.Collection.stream() 方法用集合创建流List<String> list = Arrays.asList("a", "b", "c","e", "f", "g"); // 创建一个顺序流 Stream<String> stream = list.stream(); // 创建一个并行流 Stream<String> parallelStream = list.parallelStream(); 第二种方式2.使用java.util.Arrays.stream(T[] array)方法用数组创建流int[] array={1,3,5,6,8}; IntStream stream = Arrays.stream(array); 第三种方式3.使用Stream的静态方法：of()、iterate()、generate() Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8); Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 5).limit(4); stream2.forEach(System.out::println); Stream<Double> stream3 = Stream.generate(Math::random).limit(3); stream3.forEach(System.out::println); 控制台输出结果0 5 10 15 0.7814735299587631 0.3986400508445669 0.4224148166774161如果流中的数据量足够大，并行流可以加快处速度。除了直接创建并行流，还可以通过parallel()把顺序流转换成并行流Optional findFirst = list.stream().parallel().filter(x->x>6).findFirst();Stream的使用在使用stream之前，先理解一个概念：Optional 。Optional类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true，调用get()方法会返回该对象。更详细说明请见：菜鸟教程Java 8 Optional类案例演示准备案例数据案例使用的员工类Person类/** * @author songzixian */ @NoArgsConstructor //无参构造 @AllArgsConstructor //有参构造 @Data public class Person { private String name; // 姓名 private int salary; // 薪资 private int age; // 年龄 private String sex; //性别 private String area; // 地区 } 模拟数据库查询List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900, 18,"male", "New York")); personList.add(new Person("Jack", 7000,22, "male", "Washington")); personList.add(new Person("Lily", 7800,30, "female", "Washington")); personList.add(new Person("Anni", 8200, 26,"female", "New York")); personList.add(new Person("Owen", 9500,45, "male", "New York")); personList.add(new Person("Alisa", 7900, 35,"female", "New York")); 筛选（filter）筛选，是按照一定的规则校验流中的元素，将符合条件的元素提取到新的流中的操作。案例一：筛选出Integer集合中大于7的元素，并打印出来public class StreamTest { public static void main(String[] args) { List<Integer> list = Arrays.asList(6, 7, 3, 8, 1, 2, 9); Stream<Integer> stream = list.stream(); stream.filter(x -> x > 7).forEach(System.out::println); } }输出结果8 9案例二： 筛选员工中工资高于8000的人，并形成新的集合。 形成新集合依赖collect（收集）/** * @author songzixian */ public class StreamTest02 { public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900, 23, "male", "New York")); personList.add(new Person("Jack", 7000, 25, "male", "Washington")); personList.add(new Person("Lily", 8000, 21, "female", "Washington")); personList.add(new Person("Anni", 8200, 24, "female", "New York")); personList.add(new Person("Owen", 9500, 25, "male", "New York")); personList.add(new Person("Alisa", 7900, 26, "female", "New York")); List<String> fiterList = personList.stream().filter(x -> x.getSalary() > 8000).map(Person::getName) .collect(Collectors.toList()); List<String> collect = personList.stream().filter(x -> x.getSalary() >= 800) //过滤出工资等于活动大于的员工 .map(Person::getName) //获取员工性能 .collect(Collectors.toList()); //返回一个新的list集合 System.out.print("高于8000的员工姓名：" + fiterList); } } 控制台打印结果：高于或等于8000的员工姓名：[Lily,Tom, Anni, Owen]聚合（max/min/count)max、min、count这些字眼你一定不陌生，没错，在mysql中我们常用它们进行数据统计。Java stream中也引入了这些概念和用法，极大地方便了我们对集合、数组的数据统计工作。案例一：获取String集合中最长的元素。public class StreamTest { public static void main(String[] args) { List<String> list = Arrays.asList("adnm", "admmt", "pot", "xbangd", "weoujgsd"); Optional<String> max = list.stream().max(Comparator.comparing(String::length)); System.out.println("最长的字符串：" + max.get()); } } 案例一：获取String集合中最长的元素。public class StreamTest02 { public static void main(String[] args) { List<String> list = Arrays.asList("admin", "admmt", "poot", "xxxxaaa", "asadasd"); //max代表最大 Optional<String> max = list.stream().max(Comparator.comparing(String::length)); System.out.println("集合种最长的字符串是:"+max.get()); } } 控制台打印结果集合种最长的字符串是:xxxxaaa 案例二：获取Integer集合中的最大值。public class StreamTest02 {public static void main(String[] args) { List<Integer> list = Arrays.asList(1,7, 6, 9, 4, 11, 6,10,2,3,8); //自然排序 max(最大) compareTo(对两个Integer对象进行数值比较) Optional<Integer> max1 = list.stream().max(Integer::compareTo); //自定义排序 Optional<Integer> max2 = list.stream().max(new Comparator<Integer>() { //重写compare方法 @Override public int compare(Integer o1, Integer o2) { return o1.compareTo(o2); } }); System.out.println("自然排序的最大值：" + max1.get()); System.out.println("自定义排序的最大值：" + max2.get()); }}控制台打印结果自然排序的最大值：11 自定义排序的最大值：11 案例三：获取员工工资最高的人。public class StreamTest02 { public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900, 23, "male", "New York")); personList.add(new Person("Jack", 7000, 25, "male", "Washington")); personList.add(new Person("Lily", 7800, 21, "female", "Washington")); personList.add(new Person("Anni", 8200, 24, "female", "New York")); personList.add(new Person("Owen", 9500, 25, "male", "New York")); personList.add(new Person("Alisa", 7900, 26, "female", "New York")); Optional<Person> max = personList.stream().max(Comparator.comparing(Person::getSalary)); System.out.println("员工工资最大值：" + max.get().getSalary()); } } 控制台输出结果员工工资最大值：9500 案例四：计算Integer集合中大于6的元素的个数。/** * @author songzixian */ public class StreamTest02 { public static void main(String[] args) { List<Integer> list = Arrays.asList(7, 6, 4, 8, 2, 11, 9); //统计集合种大于6数字有几个 long count = list.stream().filter(x -> x > 6).count(); System.out.println("list中大于6的元素个数：" + count); } } 控制台输出list中大于6的元素个数：4 映射(map/flatMap)映射，可以将一个流的元素按照一定的映射规则映射到另一个流中。分为map和flatMap：map：接收一个函数作为参数，该函数会被应用到每个元素上，并将其映射成一个新的元素。flatMap：接收一个函数作为参数，将流中的每个值都换成另一个流，然后把所有流连接成一个流。案例一：英文字符串数组的元素全部改为大写。整数数组每个元素+3。 public class StreamTest02 { public static void main(String[] args) { String[] strArr = { "abcd", "bcdd", "defde", "fTr" }; //toUpperCase(将所有字符串转为大写)并返回新的流 List<String> strList = Arrays.stream(strArr).map(String::toUpperCase).collect(Collectors.toList()); System.out.println("每个元素大写：" + strList); List<Integer> intList = Arrays.asList(1, 3, 5, 7, 9, 11); //每个元素都+3 并返回新的流 List<Integer> intListNew = intList.stream().map(x -> x + 3).collect(Collectors.toList()); System.out.println("每个元素+3：" + intListNew); } }控制台输出结果 每个元素大写：[ABCD, BCDD, DEFDE, FTR] 每个元素+3：[4, 6, 8, 10, 12, 14] 案例二：将员工的薪资全部增加1000。public class StreamTest02 { public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900, 23, "male", "New York")); personList.add(new Person("Jack", 7000, 25, "male", "Washington")); personList.add(new Person("Lily", 7800, 21, "female", "Washington")); personList.add(new Person("Anni", 8200, 24, "female", "New York")); personList.add(new Person("Owen", 9500, 25, "male", "New York")); personList.add(new Person("Alisa", 7900, 26, "female", "New York")); // 不改变原来员工集合的方式 List<Person> newPersonList = personList.stream().map(person -> { //保持原有的格式 Person newPersion = new Person(person.getName(), 0 ,0,null ,null); //为每个员工添加 100工资 newPersion.setSalary(person.getSalary() + 100); return newPersion; }).collect(Collectors.toList()); //打印第一个员工工资变动 System.out.println("一次改动前：" + personList.get(0).getName() + "-->" + personList.get(0).getSalary()); System.out.println("一次改动后：" + newPersonList.get(0).getName() + "-->" + newPersonList.get(0).getSalary()); System.out.println("所有员工：" + personList.toString()); // 改变原来员工集合的方式 List<Person> new2personList = personList.stream().map(person -> { //传入一个person,每个person工资都+100 person.setSalary(person.getSalary() + 1000); return person; }).collect(Collectors.toList()); System.out.println("二次改动前：" + newPersonList.get(0).getName() + "-->" + newPersonList.get(0).getSalary()); System.out.println("二次改动后：" + new2personList.get(0).getName() + "-->" + new2personList.get(0).getSalary()); System.out.println("所有员工：" + personList.toString()); } } 不改变原来员工集合的方式打印结果一次改动前：Tom-->8900 一次改动后：Tom-->9000 所有员工：[Person(name=Tom, salary=8900, age=23, sex=male, area=New York), Person(name=Jack, salary=7000, age=25, sex=male, area=Washington), Person(name=Lily, salary=7800, age=21, sex=female, area=Washington), Person(name=Anni, salary=8200, age=24, sex=female, area=New York), Person(name=Owen, salary=9500, age=25, sex=male, area=New York), Person(name=Alisa, salary=7900, age=26, sex=female, area=New York)] 改变原来员工集合的方式二次改动前：Tom-->9000 二次改动后：Tom-->9900 所有员工：[Person(name=Tom, salary=9900, age=23, sex=male, area=New York), Person(name=Jack, salary=8000, age=25, sex=male, area=Washington), Person(name=Lily, salary=8800, age=21, sex=female, area=Washington), Person(name=Anni, salary=9200, age=24, sex=female, area=New York), Person(name=Owen, salary=10500, age=25, sex=male, area=New York), Person(name=Alisa, salary=8900, age=26, sex=female, area=New York)] 归约(reduce)归约，也称缩减，顾名思义，是把一个流缩减成一个值，能实现对集合求和、求乘积和求最值操作。案例一：求Integer集合的元素之和、乘积和最大值。public class StreamTest01 { public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 13, 22, 10, 11, 6); // 求和方式1 Optional<Integer> sum = list.stream().reduce((x, y) -> x + y); // 求和方式2 Optional<Integer> sum2 = list.stream().reduce(Integer::sum); // 求和方式3 Integer sum3 = list.stream().reduce(0, Integer::sum); System.out.println("list求和：" + sum.get() + "," + sum2.get() + "," + sum3); // 求乘积 Optional<Integer> product = list.stream().reduce((x, y) -> x * y); System.out.println("list求积：" + product.get()); // 求最大值方式1 Optional<Integer> max = list.stream().reduce((x, y) -> x > y ? x : y); // 求最大值写法2 Integer max2 = list.stream().reduce(1, Integer::max); System.out.println("list求和：" + max.get() + "," + max2); } }控制台输入结果list求和：63,63,63 list求积：188760 list求和：22,22 收集(collect)collect，收集，可以说是内容最繁多、功能最丰富的部分了。从字面上去理解，就是把一个流收集起来，最终可以是收集成一个值也可以收集成一个新的集合。collect主要依赖java.util.stream.Collectors类内置的静态方法。3.6.1 归集(toList/toSet/toMap)因为流不存储数据，那么在流中的数据完成处理后，需要将流中的数据重新归集到新的集合里。toList、toSet和toMap比较常用，另外还有toCollection、toConcurrentMap等复杂一些的用法。下面用一个案例演示toList、toSet和toMap：public class StreamTest01 { public static void main(String[] args) { List<Integer> list = Arrays.asList(1,5,3,4,6,11,15,22,63); System.out.println("list:" + list); // 取余数 返回 list List<Integer> listNew = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toList()); System.out.println("listNew" + listNew); // 取余数 返回 set Set<Integer> set = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toSet()); System.out.println("set:" + set); List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900, 23, "male", "New York")); personList.add(new Person("Jack", 7000, 25, "male", "Washington")); personList.add(new Person("Lily", 7800, 21, "female", "Washington")); personList.add(new Person("Anni", 8200, 24, "female", "New York")); // 查询工资大于8000的员工 并返回map Map<String, Person> map = personList.stream().filter(p -> p.getSalary() > 8000).collect(Collectors.toMap(Person::getName, p -> p)); System.out.println("toMap:" + map); } } 案例二：求所有员工的工资之和和最高工资。public class StreamTest01 { public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900, 23, "male", "New York")); personList.add(new Person("Jack", 7000, 25, "male", "Washington")); personList.add(new Person("Lily", 7800, 21, "female", "Washington")); personList.add(new Person("Anni", 8200, 24, "female", "New York")); personList.add(new Person("Owen", 9500, 25, "male", "New York")); personList.add(new Person("Alisa", 7900, 26, "female", "New York")); // 求工资之和方式1： Optional<Integer> sumSalary = personList.stream().map(Person::getSalary).reduce(Integer::sum); // 求工资之和方式2： Integer sumSalary2 = personList.stream().reduce(0, (sum, p) -> sum += p.getSalary(), (sum1, sum2) -> sum1 + sum2); Integer sumSalary3 = personList.stream().reduce(0, (sum, p) -> sum += p.getSalary(), Integer::sum); // 求工资之和方式3： Integer maxSalary1 = personList.stream().reduce(0 ,(max ,p ) -> max > p.getSalary() ? max : p.getSalary() ,Integer::max); // 求最高工资方式1： Integer maxSalary2 = personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(), (max1, max2) -> max1 > max2 ? max1 : max2); System.out.println("工资之和：" + sumSalary.get() + "," + sumSalary2 + "," + sumSalary3); System.out.println("最高工资：" + maxSalary1 + "," + maxSalary2); } } 输出结果：工资之和：49300,49300,49300 最高工资：9500,9500 统计(count/averaging)Collectors提供了一系列用于数据统计的静态方法：计数：count平均值：averagingInt、averagingLong、averagingDouble最值：maxBy、minBy求和：summingInt、summingLong、summingDouble统计以上所有：summarizingInt、summarizingLong、summarizingDouble案例：统计员工人数、平均工资、工资总额、最高工资。public class StreamTest01 { public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900, 23, "male", "New York")); personList.add(new Person("Jack", 7000, 25, "male", "Washington")); personList.add(new Person("Lily", 7800, 21, "female", "Washington")); // 求总数 Long count = personList.stream().collect(Collectors.counting()); System.out.println("员工总数：" + count); // 求平均工资 Double average = personList.stream().collect(Collectors.averagingDouble((Person::getSalary))); System.out.println("员工平均工资：" + average); // 求最高工资 Optional<Integer> max = personList.stream().map(Person::getSalary).collect(Collectors.maxBy(Integer::compareTo)); System.out.println("员工最高工资:" + max); // 求工资之和 Integer sum = personList.stream().collect(Collectors.summingInt(Person::getSalary)); System.out.println("员工工资总和：" + sum); DoubleSummaryStatistics collect = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary)); System.out.println("员工工资所有统计：" + collect); } } 控制台输出员工总数：3 员工平均工资：7900.0 员工最高工资:Optional[8900] 员工工资总和：23700 员工工资所有统计：DoubleSummaryStatistics{count=3, sum=23700.000000, min=7000.000000, average=7900.000000, max=8900.000000} 分组(partitioningBy/groupingBy)public class StreamTest01 { public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900, 18,"male", "New York")); personList.add(new Person("Jack", 7000,22, "male", "Washington")); personList.add(new Person("Lily", 7800,30, "female", "Washington")); personList.add(new Person("Anni", 8200, 26,"female", "New York")); personList.add(new Person("Owen", 9500,45, "male", "New York")); personList.add(new Person("Alisa", 7900, 35,"female", "New York")); // 根据员工薪资查询是否高于8000并分组 Map<Boolean, List<Person>> maxPart = personList.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 8000)); System.out.println("员工按薪资是否大于8000进行分组" + maxPart); // 根据性别分组 Map<String, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getSex)); System.out.println("根据性别分组" + group); /// 将员工先按性别分组，再按地区分组 Map<String, Map<String, List<Person>>> group2 = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea))); System.out.println("将员工先按性别分组，再按地区分组：" + group2); } } 控制台打印员工按薪资是否大于8000进行分组{false=[Person(name=Jack, salary=7000, age=22, sex=male, area=Washington), Person(name=Lily, salary=7800, age=30, sex=female, area=Washington), Person(name=Alisa, salary=7900, age=35, sex=female, area=New York)], true=[Person(name=Tom, salary=8900, age=18, sex=male, area=New York), Person(name=Anni, salary=8200, age=26, sex=female, area=New York), Person(name=Owen, salary=9500, age=45, sex=male, area=New York)]} 根据性别分组{female=[Person(name=Lily, salary=7800, age=30, sex=female, area=Washington), Person(name=Anni, salary=8200, age=26, sex=female, area=New York), Person(name=Alisa, salary=7900, age=35, sex=female, area=New York)], male=[Person(name=Tom, salary=8900, age=18, sex=male, area=New York), Person(name=Jack, salary=7000, age=22, sex=male, area=Washington), Person(name=Owen, salary=9500, age=45, sex=male, area=New York)]} 将员工先按性别分组，再按地区分组：{female={New York=[Person(name=Anni, salary=8200, age=26, sex=female, area=New York), Person(name=Alisa, salary=7900, age=35, sex=female, area=New York)], Washington=[Person(name=Lily, salary=7800, age=30, sex=female, area=Washington)]}, male={New York=[Person(name=Tom, salary=8900, age=18, sex=male, area=New York), Person(name=Owen, salary=9500, age=45, sex=male, area=New York)], Washington=[Person(name=Jack, salary=7000, age=22, sex=male, area=Washington)]}} 接合(joining)joining可以将stream中的元素用特定的连接符（没有的话，则直接连接）连接成一个字符串。public class StreamTest01 { public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900, 18,"male", "New York")); personList.add(new Person("Jack", 7000,22, "male", "Washington")); personList.add(new Person("Lily", 7800,30, "female", "Washington")); personList.add(new Person("Anni", 8200, 26,"female", "New York")); personList.add(new Person("Owen", 9500,45, "male", "New York")); personList.add(new Person("Alisa", 7900, 35,"female", "New York")); String names = personList.stream().map(p -> p.getName()).collect(Collectors.joining(",")); System.out.println("所有员工的工资:" +names); List<String> strings = Arrays.asList("A", "B", "C"); String stringJoin = strings.stream().collect(Collectors.joining("-")); System.out.println("拼接后的字符串：" +stringJoin); } } 控制台输出结果所有员工的工资:Tom,Jack,Lily,Anni,Owen,Alisa 拼接后的字符串：A-B-C 3.6.5 归约(reducing)Collectors类提供的reducing方法，相比于stream本身的reduce方法，增加了对自定义归约的支持。 public class StreamTest01 { public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900, 23, "male", "New York")); personList.add(new Person("Jack", 7000, 25, "male", "Washington")); personList.add(new Person("Lily", 7800, 21, "female", "Washington")); // 每个员工减去起征点后的薪资之和（这个例子并不严谨，但一时没想到好的例子） Integer sum = personList.stream().collect(Collectors.reducing(0, Person::getSalary, (i, j) -> (i + j) - 5000)); System.out.println("员工扣税薪资总和：" +sum); Optional<Integer> sum2 = personList.stream().map(Person::getSalary).reduce(Integer::sum); System.out.println("员工薪资总和：" + sum2.get()); } } 控制台输出结果员工扣税薪资总和：8700 员工薪资总和：23700 排序(sorted)sorted，中间操作。有两种排序：sorted()：自然排序，流中元素需实现Comparable接口sorted(Comparator com)：Comparator排序器自定义排序public class StreamTest01 { public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900, 18,"male", "New York")); personList.add(new Person("Jack", 7000,22, "male", "Washington")); personList.add(new Person("Lily", 7800,30, "female", "Washington")); personList.add(new Person("Anni", 8200, 26,"female", "New York")); personList.add(new Person("Owen", 9500,45, "male", "New York")); personList.add(new Person("Alisa", 7900, 35,"female", "New York")); // 按员工工资升降序(自然排序)-> 从小到大 List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName).collect(Collectors.toList()); System.out.println("按工资升序排序：" + newList); //按员工工资的倒车 List<String > newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed()).map(Person::getName).collect(Collectors.toList()); System.out.println("按工资降序排序：" + newList2); Stream<String> newList3 = personList.stream().sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName); System.out.println("先按工资再按年龄升序排序：" + newList3); // 先按工资再按年龄自定义排序（降序） List<String> newList4 = personList.stream().sorted((p1 , p2) -> { if ( p1.getSalary() == p2.getSalary()){ return p2.getAge() - p1.getAge(); }else { return p2.getSalary() - p1.getSalary(); } }).map(Person::getName).collect(Collectors.toList()); System.out.println("先按工资再按年龄自定义降序排序：" + newList4); } } 控制台打印按工资升序排序：[Jack, Lily, Alisa, Anni, Tom, Owen] 按工资降序排序：[Owen, Tom, Anni, Alisa, Lily, Jack] 先按工资再按年龄升序排序：java.util.stream.ReferencePipeline$3@1ddc4ec2 先按工资再按年龄自定义降序排序：[Owen, Tom, Anni, Alisa, Lily, Jack] 提取/组合流也可以进行合并、去重、限制、跳过等操作。去重是指将（集合）流中重复的元素去除，通过 hashcode 和 equals 函数来判断是否是重复元素。去重操作实现了类似于 HashSet 的运算，对于对象元素流去重，需要重写 hashcode 和 equals 方法。如果流中泛型对象使用 Lombok 插件，使用@Data注解默认重写了 hashcode 和 equals 方法，字段相同并且属性相同，则对象相等demo演示public class StreamTest01 { public static void main(String[] args) { String[] arr1 = { "a", "b", "c", "d" }; String[] arr2 = { "d", "e", "f", "g" }; Stream<String> stream1 = Stream.of(arr1); Stream<String> stream2 = Stream.of(arr2); // concat:合并两个流 distinct：去重 List<String> newList = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList()); System.out.println("流合并：" + newList); // limit：限制从流中获得前n个数据 List<Integer> collect = Stream.iterate(1, x -> x + 2).limit(10).collect(Collectors.toList()); System.out.println("limit：" + collect); // skip：跳过前n个数据 List<Integer> collect2 = Stream.iterate(1, x -> x + 2).skip(1).limit(5).collect(Collectors.toList()); System.out.println("skip：" + collect2); } }
创建一个模拟MenuTree 对象/** * @author songzixian */ @Data @Builder public class MenuTree { /** * id */ public Integer id; /** * 名称 */ public String name; /** * 父id ，根节点为0 */ public Integer parentId; /** * 子节点信息 */ public List<MenuTree> childList; public MenuTree(Integer id, String name, Integer parentId) { this.id = id; this.name = name; this.parentId = parentId; } public MenuTree(Integer id, String name, Integer parentId, List<MenuTree> childList) { this.id = id; this.name = name; this.parentId = parentId; this.childList = childList; } } 创建TestTree测试类public class TestTree { @Test public void testtree() { //模拟从数据库查询出来 List<MenuTree> menus = Arrays.asList( new MenuTree(1, "根节点", 0), new MenuTree(2, "子节点1", 1), new MenuTree(3, "子节点1.1", 2), new MenuTree(4, "子节点1.2", 2), new MenuTree(5, "根节点1.3", 2), new MenuTree(6, "根节点2", 1), new MenuTree(7, "根节点2.1", 6), new MenuTree(8, "根节点2.2", 6), new MenuTree(9, "根节点2.2.1", 7), new MenuTree(10, "根节点2.2.2", 7), new MenuTree(11, "根节点3", 1), new MenuTree(12, "根节点3.1", 11) ); //获取父节点 List<MenuTree> collect = menus.stream().filter(m -> m.getParentId() == 0). map( (m) -> { m.setChildList(getChildrens(m, menus)); return m; } ).collect(Collectors.toList()); System.out.println("-------转json输出结果-------"); System.out.println(JSONUtil.toJsonStr(collect)); } /** * 递归查询子节点 * * @param root 根节点 * @param all 所有节点 * @return 根节点信息 */ private List<MenuTree> getChildrens(MenuTree root, List<MenuTree> all) { List<MenuTree> children = all.stream().filter(m -> { return Objects.equals(m.getParentId(), root.getId()); }).map( (m) -> { m.setChildList(getChildrens(m, all)); return m; } ).collect(Collectors.toList()); return children; } }运行输出一个Json树形结构
使用screw一键生成数据库文档、支持生成多种格式！
推荐一款简洁好用的数据库表结构文档生成工具screw数据库支持 MySQL MariaDB TIDB Oracle SqlServer PostgreSQL Cache DB（2016） 文档生成支持 html word markdown 有两种方式,选择其中一种即可,个人觉得第二种更方便第一种方式 整合代码生成引入依赖 <dependency> <groupId>cn.smallbun.screw</groupId> <artifactId>screw-core</artifactId> <version>1.0.5</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.4.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.20</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>RELEASE</version> <scope>compile</scope> </dependency> 代码类** * 生成数据库表文档 * @author songzixian */ public class GenerateDbDoc { /** * 文档生成 */ @Test public void documentGeneration() { //数据源 HikariConfig hikariConfig = new HikariConfig(); hikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver"); hikariConfig.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/szx_blog"); hikariConfig.setUsername("root"); hikariConfig.setPassword("12345678"); //设置可以获取tables remarks信息 hikariConfig.addDataSourceProperty("useInformationSchema", "true"); hikariConfig.setMinimumIdle(2); hikariConfig.setMaximumPoolSize(5); DataSource dataSource = new HikariDataSource(hikariConfig); //生成配置 EngineConfig engineConfig = EngineConfig.builder() //生成文件路径 .fileOutputDir("D:\\doc") //打开目录 .openOutputDir(true) //文件类型 .fileType(EngineFileType.HTML) //生成模板实现 .produceType(EngineTemplateType.freemarker) //自定义文件名称 .fileName("自定义文件名称").build(); //忽略表 ArrayList<String> ignoreTableName = new ArrayList<>(); ignoreTableName.add("test_user"); ignoreTableName.add("test_group"); //忽略表前缀 ArrayList<String> ignorePrefix = new ArrayList<>(); ignorePrefix.add("test_"); //忽略表后缀 ArrayList<String> ignoreSuffix = new ArrayList<>(); ignoreSuffix.add("_test"); ProcessConfig processConfig = ProcessConfig.builder() //指定生成逻辑、当存在指定表、指定表前缀、指定表后缀时，将生成指定表，其余表不生成、并跳过忽略表配置 //根据名称指定表生成 .designatedTableName(new ArrayList<>()) //根据表前缀生成 .designatedTablePrefix(new ArrayList<>()) //根据表后缀生成 .designatedTableSuffix(new ArrayList<>()) //忽略表名 .ignoreTableName(ignoreTableName) //忽略表前缀 .ignoreTablePrefix(ignorePrefix) //忽略表后缀 .ignoreTableSuffix(ignoreSuffix).build(); //配置 Configuration config = Configuration.builder() //版本 .version("1.0.0") //描述 .description("数据库设计文档生成") //数据源 .dataSource(dataSource) //生成配置 .engineConfig(engineConfig) //生成配置 .produceConfig(processConfig) .build(); //执行生成 new DocumentationExecute(config).execute(); } } 第二种方式 Maven插件生成<build> <plugins> <plugin> <groupId>cn.smallbun.screw</groupId> <artifactId>screw-maven-plugin</artifactId> <version>1.0.5</version> <dependencies> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.4.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.20</version> </dependency> </dependencies> <configuration> <username>root</username> <password>12345678</password> <driverClassName>com.mysql.cj.jdbc.Driver</driverClassName> <jdbcUrl>jdbc:mysql://127.0.0.1:3306/szx_blog</jdbcUrl> <fileType>MD</fileType> <openOutputDir>false</openOutputDir> <produceType>freemarker</produceType> <fileName>测试文档名称</fileName> <description>数据库文档生成</description> <version>${project.version}</version> 数据库文档 </configuration> <executions> <execution> <phase>compile</phase> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin> </plugins> </build>在pmx种添加之后,IDEA种刷新Maven,然后点击compile执行即可生成
依赖<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.4.6</version> </dependency>代码示例// https://www.hutool.cn/aaa/bbb?ie=UTF-8&wd=test// https://www.hutool.cn/aaa/bbb?ie=UTF-8&wd=test String buildUrl = UrlBuilder.create() .setScheme("https") .setHost("www.hutool.cn") .addPath("/aaa").addPath("bbb") .addQuery("ie", "UTF-8") .addQuery("wd", "test") .build();
Java实现阿里云视频点播视本地视频上传
官方demo代码:https://help.aliyun.com/document_detail/106648.html?spm=a2c4g.11186623.6.1044.4227192bgI8IxVpom依赖 <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <aliyun-java-sdk-core.version>4.5.3</aliyun-java-sdk-core.version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-vod</artifactId> <aliyun-java-sdk-vod>2.15.10</aliyun-java-sdk-vod> </dependency> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <gson.version>2.8.6</gson.version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.72</version> </dependency> <dependency> <groupId>org.json</groupId> <artifactId>json</artifactId> <version>20200518</version> </dependency>手动把aliyun-java-vod-upload-1.4.12.jar打进本地仓库mvn install:install-file -DgroupId=com.aliyun -DartifactId=aliyun-sdk-vod-upload -Dversion=1.4.12 -Dpackaging=jar -Dfile=aliyun-java-vod-upload-1.4.12.jar <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-sdk-vod-upload</artifactId> <version>1.4.12</version> </dependency> 本地文件上传本地文件上传SDK下载 参考UploadVideoDemo类中的testUploadVideo本地文件上传接口 方法 /** * 本地文件上传接口 * * @param accessKeyId * @param accessKeySecret * @param title * @param fileName */ @RequestMapping("testUploadVideo") private static void testUploadVideo(String accessKeyId, String accessKeySecret, String title, String fileName) { UploadVideoRequest request = new UploadVideoRequest(accessKeyId, accessKeySecret, title, fileName); /* 可指定分片上传时每个分片的大小，默认为2M字节 */ request.setPartSize(2 * 1024 * 1024L); /* 可指定分片上传时的并发线程数，默认为1，(注：该配置会占用服务器CPU资源，需根据服务器情况指定）*/ request.setTaskNum(1); /* 是否开启断点续传, 默认断点续传功能关闭。当网络不稳定或者程序崩溃时，再次发起相同上传请求，可以继续未完成的上传任务，适用于超时3000秒仍不能上传完成的大文件。 注意: 断点续传开启后，会在上传过程中将上传位置写入本地磁盘文件，影响文件上传速度，请您根据实际情况选择是否开启*/ //request.setEnableCheckpoint(false); /* OSS慢请求日志打印超时时间，是指每个分片上传时间超过该阈值时会打印debug日志，如果想屏蔽此日志，请调整该阈值。单位: 毫秒，默认为300000毫秒*/ //request.setSlowRequestsThreshold(300000L); /* 可指定每个分片慢请求时打印日志的时间阈值，默认为300s*/ //request.setSlowRequestsThreshold(300000L); /* 是否显示水印(可选)，指定模板组ID时，根据模板组配置确定是否显示水印*/ //request.setIsShowWaterMark(true); /* 设置上传完成后的回调URL(可选)，建议您通过点播控制台配置事件通知，参见文档 https://help.aliyun.com/document_detail/55627.html */ //request.setCallback("http://callback.sample.com"); /* 自定义消息回调设置(可选)，参数说明参考文档 https://help.aliyun.com/document_detail/86952.html#UserData */ // request.setUserData("{\"Extend\":{\"test\":\"www\",\"localId\":\"xxxx\"},\"MessageCallback\":{\"CallbackURL\":\"http://test.test.com\"}}"); /* 视频分类ID(可选) */ //request.setCateId(0); /* 视频标签,多个用逗号分隔(可选) */ //request.setTags("标签1,标签2"); /* 视频描述(可选) */ //request.setDescription("视频描述"); /* 封面图片(可选) */ //request.setCoverURL("http://cover.sample.com/sample.jpg"); /* 模板组ID(可选) */ //request.setTemplateGroupId("8c4792cbc8694e7084fd5330e56a33d"); /* 工作流ID(可选) */ //request.setWorkflowId("d4430d07361f0*be1339577859b0177b"); /* 存储区域(可选) */ //request.setStorageLocation("in-201703232118266-5sejdln9o.oss-cn-shanghai.aliyuncs.com"); /* 开启默认上传进度回调 */ //request.setPrintProgress(false); /* 设置自定义上传进度回调 (必须继承 VoDProgressListener) */ //request.setProgressListener(new PutObjectProgressListener()); /* 设置您实现的生成STS信息的接口实现类*/ // request.setVoDRefreshSTSTokenListener(new RefreshSTSTokenImpl()); /* 设置应用ID*/ //request.setAppId("app-1000000"); /* 点播服务接入点 */ //request.setApiRegionId("cn-shanghai"); /* ECS部署区域*/ // request.setEcsRegionId("cn-shanghai"); UploadVideoImpl uploader = new UploadVideoImpl(); UploadVideoResponse response = uploader.uploadVideo(request); System.out.print("RequestId=" + response.getRequestId() + "\n"); //请求视频点播服务的请求ID if (response.isSuccess()) { System.out.print("VideoId=" + response.getVideoId() + "\n"); } else { /* 如果设置回调URL无效，不影响视频上传，可以返回VideoId同时会返回错误码。其他情况上传失败时，VideoId为空，此时需要根据返回错误码分析具体错误原因 */ System.out.print("VideoId=" + response.getVideoId() + "\n"); System.out.print("ErrorCode=" + response.getCode() + "\n"); System.out.print("ErrorMessage=" + response.getMessage() + "\n"); } }测试testUploadVideo方法,实现本地视频上传//账号AK信息请填写(必选) private static final String accessKeyId = "LTAI4FqLipWEZ5hWL198qKo1"; //账号AK信息请填写(必选) private static final String accessKeySecret = "W51CzGrwKuMrWl6wUm4qyEfVdN6m3q"; //设置上传完成后的名称 private static final String title="爱笑的眼睛"; //填写需要上传的视频路径+名称 private static final String fileName="D:\\aaaa\\爱笑的眼睛test.mp4"; /** * 功能描述: 本地视频上传测试 * 〈〉 * @Param: [] * @Return: {@link String} */ @RequestMapping("testUpload") public String testUpload(){ testUploadVideo( accessKeyId, accessKeySecret, title, fileName); return "视频上传成功!"; }查看IDEA控制台然后进入阿里云视频点播控制台,发现文件已经上传成功
@Data使用这个注解，就不用再去手写Getter,Setter,equals,canEqual,hasCode,toString等方法了，注解后在编译时会自动加进去。@AllArgsConstructor使用后添加一个构造函数，该构造函数含有所有已声明字段属性参数@NoArgsConstructor使用后创建一个无参构造函数@Builder关于Builder较为复杂一些，Builder的作用之一是为了解决在某个类有很多构造函数的情况，也省去写很多构造函数的麻烦，在设计模式中的思想是：用一个内部类去实例化一个对象，避免一个类出现过多构造函数，@Data //生成getter,setter等函数 @AllArgsConstructor //生成全参数构造函数 @NoArgsConstructor//生成无参构造函数 public class User { }
首先需要到银联注册帐号,然后下载需要的证书,如图然后下载jdkhttps://open.unionpay.com/tjweb/acproduct/list?apiSvcId=448&index=5把SDK中的com.unionpay.acp.sdk包下的类复制到开发项目中把SDK中的com.unionpay.acp.demo包下的DemoBase类复制到开发项目com.unionpay.acp.sdk包中,改名为UnionpayBese把src下的配置文件复制到项目中打开acp_sdk.properties配置修改证书路径地址编写支付共同行为接口public interface PayStrategyService { /** * 共同算法实现 */ public String toPayHtml(PaymentChannelEntity pymentChannel, PayMentTransacDTO payMentTransacDTO); } 编写支付共同行为实现类@Slf4j public class UnionPayStrategyServiceImpl implements PayStrategyService { @Override public String toPayHtml(PaymentChannelEntity paymentChannel, PayMentTransacDTO payMentTransacDTO) { log.info(">>>>>>>>银联支付组装参数开始<<<<<<<<<<<<"); Map<String, String> requestData = new HashMap<String, String>(); /*** 银联全渠道系统，产品参数，除了encoding自行选择外其他不需修改 ***/ requestData.put("version", UnionPayBase.version); // 版本号，全渠道默认值 requestData.put("encoding", UnionPayBase.encoding); // 字符集编码，可以使用UTF-8,GBK两种方式 requestData.put("signMethod", SDKConfig.getConfig().getSignMethod()); // 签名方法 requestData.put("txnType", "01"); // 交易类型 ，01：消费 requestData.put("txnSubType", "01"); // 交易子类型， 01：自助消费 requestData.put("bizType", "000201"); // 业务类型，B2C网关支付，手机wap支付 requestData.put("channelType", "07"); // 渠道类型，这个字段区分B2C网关支付和手机wap支付；07：PC,平板 // 08：手机 /*** 商户接入参数 ***/ String merchantId = paymentChannel.getMerchantId(); requestData.put("merId", merchantId); // 商户号码，请改成自己申请的正式商户号或者open上注册得来的777测试商户号 requestData.put("accessType", "0"); // 接入类型，0：直连商户 String paymentId = payMentTransacDTO.getPaymentId(); // 在微服务电商项目中 订单系统(orderId) 支付系统 支付id requestData.put("orderId", paymentId); // 商户订单号，8-40位数字字母，不能含“-”或“_”，可以自行定制规则 requestData.put("txnTime", format(payMentTransacDTO.getCreatedTime())); // 订单发送时间，取系统时间，格式为YYYYMMDDhhmmss，必须取当前时间，否则会报txnTime无效 requestData.put("currencyCode", "156"); // 交易币种（境内商户一般是156 人民币） Long payAmount = payMentTransacDTO.getPayAmount(); requestData.put("txnAmt", payAmount + ""); // 交易金额，单位分，不要带小数点 // requestData.put("reqReserved", "透传字段"); // //请求方保留域，如需使用请启用即可；透传字段（可以实现商户自定义参数的追踪）本交易的后台通知,对本交易的交易状态查询交易、对账文件中均会原样返回，商户可以按需上传，长度为1-1024个字节。出现&={}[]符号时可能导致查询接口应答报文解析失败，建议尽量只传字母数字并使用|分割，或者可以最外层做一次base64编码(base64编码之后出现的等号不会导致解析失败可以不用管)。 //商品名称 或者商品描述 requestData.put("riskRateInfo", "{commodityName=测试商品名称}"); // // 前台通知地址 （需设置为外网能访问 http https均可），支付成功后的页面 点击“返回商户”按钮的时候将异步通知报文post到该地址 // 如果想要实现过几秒中自动跳转回商户页面权限，需联系银联业务申请开通自动返回商户权限 // 异步通知参数详见open.unionpay.com帮助中心 下载 产品接口规范 网关支付产品接口规范 消费交易 商户通知\ String syncUrl = paymentChannel.getSyncUrl(); requestData.put("frontUrl", syncUrl); // 后台通知地址（需设置为【外网】能访问 http // https均可），支付成功后银联会自动将异步通知报文post到商户上送的该地址，失败的交易银联不会发送后台通知 // 后台通知参数详见open.unionpay.com帮助中心 下载 产品接口规范 网关支付产品接口规范 消费交易 商户通知 // 注意:1.需设置为外网能访问，否则收不到通知 2.http https均可 3.收单后台通知后需要10秒内返回http200或302状态码 // 4.如果银联通知服务器发送通知后10秒内未收到返回状态码或者应答码非http200，那么银联会间隔一段时间再次发送。总共发送5次，每次的间隔时间为0,1,2,4分钟。 // 5.后台通知地址如果上送了带有？的参数，例如：http://abc/web?a=b&c=d // 在后台通知处理程序验证签名之前需要编写逻辑将这些字段去掉再验签，否则将会验签失败 String asynUrl = paymentChannel.getAsynUrl(); requestData.put("backUrl", asynUrl); // 订单超时时间。 // 超过此时间后，除网银交易外，其他交易银联系统会拒绝受理，提示超时。 // 跳转银行网银交易如果超时后交易成功，会自动退款，大约5个工作日金额返还到持卡人账户。 // 此时间建议取支付时的北京时间加15分钟。 // 超过超时时间调查询接口应答origRespCode不是A6或者00的就可以判断为失败。 requestData.put("payTimeout", new SimpleDateFormat("yyyyMMddHHmmss").format(new Date().getTime() + 15 * 60 * 1000)); ////////////////////////////////////////////////// // // 报文中特殊用法请查看 PCwap网关跳转支付特殊用法.txt // ////////////////////////////////////////////////// /** 请求参数设置完毕，以下对请求参数进行签名并生成html表单，将表单写入浏览器跳转打开银联页面 **/ Map<String, String> submitFromData = AcpService.sign(requestData, UnionPayBase.encoding); // 报文中certId,signature的值是在signData方法中获取并自动赋值的，只要证书配置正确即可。 String requestFrontUrl = SDKConfig.getConfig().getFrontRequestUrl(); // 获取请求银联的前台地址：对应属性文件acp_sdk.properties文件中的acpsdk.frontTransUrl String html = AcpService.createAutoFormHtml(requestFrontUrl, submitFromData, UnionPayBase.encoding); // 生成自动跳转的Html表单 LogUtil.writeLog("打印请求HTML，此为请求报文，为联调排查问题的依据：" + html); // 将生成的html写到浏览器中完成自动跳转打开银联支付页面；这里调用signData之后，将html写到浏览器跳转到银联页面之前均不能对html中的表单项的名称和值进行修改，如果修改会导致验签不通过 return html; } private String format(Date timeDate) { String date = new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(timeDate); return date; } } 在szx-shop-unionpay-plugin中创建com.songzixian.pay.init包,创建InitUnionPayProject类,让项目启动时自动加载证书@Component public class InitUnionPayProject implements ApplicationRunner { // springboot 项目启动的时候 执行该方法 @Override public void run(ApplicationArguments args) throws Exception { SDKConfig.getConfig().loadPropertiesFromSrc(); } } 证书加载完毕启动成功访问localhost:8600/toPayHtml?chnnelId=yinlian_pay&payToken=pay_607d328eb99a4655a45652929ed68d41返回一个thml复制下了,把\"全部替换为",新建一个html文档放进去,访问,打开跳转到支付页面测试账号信息:卡号：6216261000000000018姓名：全渠道身份证号码: 341126197709218366提交支付