宋子宪博客 - Java实战
type: channel
link: https://songzixian.com/tag/Java实战
items: Spring Boot整合ZXing实现二维码和条形码生成
items: Java多线程按着指定顺序执行
items: Java使用Hutool工具类构建树形结构
items: Java8 Stream之筛选、归约、分组、聚合 案例进阶学习
items: Java8 Stream流递归实现树形结构
items: 使用screw一键生成数据库文档、支持生成多种格式！
items: Java实现URL参数拼接案例
items: Java实现阿里云视频点播视本地视频上传
items: lombok常用的基本注解使用
items: SpringBoot对接银联支付系统

Spring Boot整合ZXing实现二维码和条形码生成
type: item
date: 2023-12-18T11:30:00+08:00
description: 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) { QRCodeServi…

Java多线程按着指定顺序执行
type: item
date: 2022-05-08T00:49:00+08:00
description: 在子线程中通过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 CountDo…

Java使用Hutool工具类构建树形结构
type: item
date: 2022-04-02T23:53:00+08:00
description: 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' …

Java8 Stream之筛选、归约、分组、聚合 案例进阶学习
type: item
date: 2022-03-29T22:13:00+08:00
description: 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 Per…

Java8 Stream流递归实现树形结构
type: item
date: 2022-03-28T00:39:00+08:00
description: 创建一个模拟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一键生成数据库文档、支持生成多种格式！
type: item
date: 2022-03-25T21:53:00+08:00
description: 推荐一款简洁好用的数据库表结构文档生成工具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<>()) //根据表前缀生成 .designatedTablePr…

Java实现URL参数拼接案例
type: item
date: 2020-10-30T16:04:00+08:00
description: 依赖<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实现阿里云视频点播视本地视频上传
type: item
date: 2020-07-14T09:56:00+08:00
description: 官方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慢请求日志打印超…

lombok常用的基本注解使用
type: item
date: 2020-05-19T16:50:00+08:00
description: @Data使用这个注解，就不用再去手写Getter,Setter,equals,canEqual,hasCode,toString等方法了，注解后在编译时会自动加进去。@AllArgsConstructor使用后添加一个构造函数，该构造函数含有所有已声明字段属性参数@NoArgsConstructor使用后创建一个无参构造函数@Builder关于Builder较为复杂一些，Builder的作用之一是为了解决在某个类有很多构造函数的情况，也省去写很多构造函数的麻烦，在设计模式中的思想是：用一个内部类去实例化一个对象，避免一个类出现过多构造函数，@Data //生成getter,setter等函数 @AllArgsConstructor //生成全参数构造函数 @NoArgsConstructor//生成无参构造函数 public class User { }

SpringBoot对接银联支付系统
type: item
date: 2019-10-27T22:00:00+08:00
description: 首先需要到银联注册帐号,然后下载需要的证书,如图然后下载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", "透传字段"); // //请求方保留域，如需使用请启用即可；透传字段…