BigBro`s Blog
type: channel
link: https://blog.bigbro.xin/
description: Just Code My Life
items: 点播测试总览
items: 离散数学
items: 记一次分区表查询的实践
items: IntelliJ IDEA远程Debug Linux的Java程序，找问题不要只会看日志了
items: Stream日常使用
items: 处理 Exception 的几种实践，很优雅，被很多团队采纳！
items: 推荐一个 Java 实体映射工具 MapStruct
items: Java 设计模式：实战工厂方法模式
items: SpringBoot 2.x 开发案例之优雅的处理异常
items: Java8 Stream 真香，赶紧来舔一舔

点播测试总览
type: item
date: 2026-06-10T08:07:00+00:00
description: 点播测试用例与结论汇总（五端）本文档分三章：一、测试矩阵与用例（测什么）；二、通过用例描述（哪些过了）；三、问题用例与异常现象（哪些有问题、现象是什么）。第一章 测试矩阵与用例§1.1 协议ID协议 / 容器reader 路径关联样本P1local file MP4（H.264 + AAC）ffmpeg_readerM1 / M3 / M4 / M9P2HTTPS progressive MP4http_progressive_reader（含 TLS）M1–M11 + M12~M18P3HLS（VOD + 时移已录完段，TS 分片）hls_readerM19~M23（M22 暂无）不测：FLV / RTMP / RTSP / HTTP-FLV（直播范畴）、纯 HTTP 明文（业务侧全站 HTTPS）。§1.2 基础场景 S1–S11组风险场景触发关键验证点L1 资源🟢S1 open / S2 closeprepare(url) / close()状态序列合法、onMediaInfoReady 阈值、含自然 EOS→ENDED→closeL2 播控🟢S3 play / S4 pausestart() / pause()READY→PLAYING + PAUSED→PLAYING；暂停后位置不增长L3 终止🟢S5 stopstop()（三态触发）任意非 IDLE → IDLE，不跳过中间态L4 seek🟡S6 前向 / S7 后向seek(pos±30s)目标 ±100ms；无音频持续丢弃 / 视频无 late drop（I5-A02 基线）L4 seek🔴S8 高频1s 内 5 次同上；竞态 / callback 队列错位L5 流切换🔴S9 switch_toswitchToUrl(new_url)新流自动 start、旧 config 保留；token/generation 失效（fef0764/a42bcda 修复区）L5 流切换🟡S10 reloadreload()起播定位清空、内部状态重置L6 循环🟡S11 loopsetLoop(true) 播至 EOSEOS→seek(0)→继续 PLAYING，至少 3 轮无异常基础矩阵 = 协议 × 场景 = 33 格；五端展开 165 cell。§1.3 扩展场景（业务回归补充）编号约定：U* UI 行为 / LC* 应用生命周期 / N* 网络中断 / E* 错误注入 / D* 长播稳定性 / NET* 倍速与弱网扩展。UI 行为（U 系列）ID风险场景关键判定U1🟡首帧预渲染显示首帧 + 停在暂停态 + 播放后丝滑、首帧无变动U2🟢全屏 / 取消全屏切换无重载、无黑边 / 拉伸、音视频连续U3🟡横竖屏切换自适应、无重载、控件重排U4 / U5🟢seek 到末尾 / 初始正确跳转、自然 EOS 或从头播U6 / U7🟢seeking / switching 状态显示UI 显示对应状态且不卡住应用生命周期（LC 系列）ID风险场景关键判定LC1🟢秒压后台恢复后正常播放、无崩溃LC2🔴长压后台 10min恢复后正常播放、位置 / 倍速 / 音量 config 保留LC3🟡锁屏解锁 1min恢复后正常播放、UI 状态正确网络中断（N 系列，拔网→恢复）ID风险场景关键判定N1🔴断网恢复重连显示 reconnecting、恢复后自动续播、续播首帧防花屏N2🟢断网 seek 恢复恢复后在 seek 目标位置续播倍速与弱网扩展（NET 系列）ID风险子类场景关键判定NET1🟡倍速单档 2x / 1.5x / 0.75x / 0.5x速度符合预期、无音画不同步、变速无顿挫NET2🔴倍速倍速时 seek正确跳转、继续保持倍速（经典坑：被 seek 重置回 1.0x）NET3🟢倍速倍速 + 高频 seek稳定、流畅、无崩溃NET4🟢倍速倍速 + 压后台保留返回后依旧倍速（config 保留）NET5🔴弱网弱网首次起播（限速 200kbps）能缓冲播放、不卡死 BUFFERING > 30sNET6🟡弱网网络链路切换 Wi-Fi ↔ 4G/5G丝滑、无卡顿、IP 切换不破坏 HTTPS sessionNET7🟢弱网弱网 + seek 恢复允许长缓冲，不卡 > 30s，最终回到 PLAYING错误注入（E 系列）ID风险场景关键判定E1🟢错误 URL无法播放、UI 不崩、错误提示友好E2🟢切换错误 URLURL2 切换失败、URL1 继续E3🟢切换空 URL切换被拒、URL1 继续E4🔴不解密播加密UI 正常、无法播放、不崩、不花屏长播稳定性（D 系列）ID风险场景关键判定D1🟡20 分钟连播末段音画同步无漂移、UI 无崩、内存稳定§1.4 弱网 profile 定义与验证profile 类别特…

离散数学
type: item
date: 2025-03-12T06:28:00+00:00
description: 离散数学基本概念数理逻辑 命题与联结词（否定￢、析取∨、合取∧、蕴涵→、等价↔） 析取∨ 类同于 或 合取∧ 类同于 且 蕴涵→ 如果就 等价 充分必要条件 命题(非真既假的陈述句) 复合命题(由简单命题通过联结词联结而成的命题) 计算规律双重否定律: A ⇔ ¬¬A 幂等律: A ∨ A ⇔ A, A ∧ A ⇔ A 交换律: A ∧ B ⇔ B ∧ A, A ∨ B ⇔ B ∨ A 结合律: (A ∨ B) ∨ C ⇔ A ∨ (B ∨ C), (A ∧ B) ∧ C ⇔ A ∧ (B ∧ C) 分配律: A ∨ (B ∧ C) ⇔ (A ∨ B) ∧ (A ∨ C), A ∧ (B ∨ C) ⇔ (A ∧ B) ∨ (A ∧ C) 德摩根律: ¬(A ∨ B) ⇔ ¬A ∧ ¬B, ¬(A ∧ B) ⇔ ¬A ∨ ¬B 吸收律: A ∨ (A ∧ B) ⇔ A, A ∧ (A ∨ B) ⇔ A 零律: A ∨ 1 ⇔ 1, A ∧ 0 ⇔ 0, 同一律: A ∧ 1 ⇔ A, A ∨ 0 ⇔ A 排中律: A ∨ ¬A ⇔ 1 矛盾律: A ∧ ¬A ⇔ 0 蕴涵等值式: A → B ⇔ ¬A ∨ B 等价等值式: A ↔ B ⇔ (A → B) ∧ (B → A) 假言易位: A → B ⇔ ¬B → ¬A 等价否定等值式: A ↔ B ⇔ ¬A ↔ ¬B 归谬论: (A → B) ∧ (A → ¬B) ⇔ ¬A 范式：析取范式、合取范式，极大（小）项，主析取范式、主合取范式 公式类型的判别方法：真值表法、等值演算法、主析取/合取范式法 命题逻辑的推理理论有一些重要的重言蕴涵式，称作推理定律。下面给出9条推理定律，它们是： $A \Rightarrow (A \lor B)$附加律 $(A \land B) \Rightarrow A$化简律 $(A \rightarrow B) \land A \Rightarrow B$假言推理 $(A \rightarrow B) \land \neg B \Rightarrow \neg A$拒取式 $(A \lor B) \land \neg B \Rightarrow A$析取三段论 $(A \rightarrow B) \land (B \rightarrow C) \Rightarrow (A \rightarrow C)$假言三段论 $(A \leftrightarrow B) \land (B \leftrightarrow C) \Rightarrow (A \leftrightarrow C)$等价三段论 $(A \rightarrow B) \land (C \rightarrow D) \land (A \lor C) \Rightarrow (B \lor D)$构造性二难$(A \rightarrow B) \land (\neg A \rightarrow B) \Rightarrow B$构造性二难（特殊形式） $(A \rightarrow B) \land (C \rightarrow D) \land (\neg B \lor \neg D) \Rightarrow (\neg A \lor \neg C)$破坏性二难 其中 $A, B, C, D$ 等是元语言符号，表示任意的命题公式。 集合 集合、元素、集合的表示方法（列元素法、谓词表示法）、子集、空集、全集、集合的包含、相等、幂集 定义 幂集 设 $A$ 为集合，把 $A$ 的全体子集构成的集合叫做 $A$ 的幂集，记作 $P(A)$（或 $2^A$），符号化表示为 $$P(A) = \{ x \mid x \subseteq A \}。$$ 若 $A$ 是 $n$ 元集，则 $P(A)$ 有 $2^n$ 个元素。例如：$A = \{ a, b, c \}$，$A$ 的幂集： $$P(A) = \{\emptyset, \{a\}, \{b\}, \{c\}, \{a, b\}, \{a, c\}, \{b, c\}, \{a, b, c\}\}。$$ 集合的交、并、差、补以及对称差等运算及有穷集的计数（文氏（Venn）图、包含排斥原理） 集合恒等式（幂等律、交换律、结合律、分配律、吸收律、矛盾律、德摩根律等）及应用 集合恒等式 名称 等式 恒等律 $A \cup \emptyset = A, \, A \cap E = A$​ 支配律 $A \cup E = E, \, A \cap \emptyset = \emptyset$​ 幂等律 $A \cup A = A, \, A \cap A = A$​ 双重否定律 $\sim(\sim A) = A$​ 交换律 $A \cup B = …

记一次分区表查询的实践
type: item
date: 2022-03-16T07:10:00+00:00
description: 前情提要:目前项目用的PgSql,订单库所有生产数据都会同步到汇聚库(kettle实现),导致目前汇聚库数据量巨大(目前主单亿级数据)所以就用上了PgSql的表分区(分区因子是数据的创建时间),项目Dao用的是Mybatis,so我得根据业务的相关条件指定查询走哪一个或者哪几个表分区.那就直接上Mybatis的拦截器@Intercepts( { @Signature( type = StatementHandler.class, method = "prepare", args = { Connection.class,Integer.class })}) public class SubTablePlugin implements Interceptor { private Logger logger = LoggerFactory.getLogger(getClass()); public static final String DELEGATE = "delegate"; public static final String MAPPED_STATEMENT = "mappedStatement"; @Override public Object intercept(Invocation ivk) throws Throwable { if(ivk.getTarget().getClass().isAssignableFrom(RoutingStatementHandler.class)) { final RoutingStatementHandler statementHandler = (RoutingStatementHandler) ivk.getTarget(); final BaseStatementHandler delegate = (BaseStatementHandler) Reflections.getFieldValue(statementHandler, DELEGATE); final MappedStatement mappedStatement = (MappedStatement) Reflections.getFieldValue(delegate, MAPPED_STATEMENT); final BoundSql boundSql = statementHandler.getBoundSql(); String sql = SqlConverterFactory.getGlobalInstance().convert(boundSql, mappedStatement); if(StringUtils.isNotBlank(sql)) { logger.debug("[Modified sql] " + sql); Reflections.setFieldValue(statementHandler.getBoundSql(), "sql", sql); } } return ivk.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } }再加上一个自定义的注解 实现功能

IntelliJ IDEA远程Debug Linux的Java程序，找问题不要只会看日志了
type: item
date: 2021-12-14T00:47:00+00:00
description: 1 前言我们习惯于在本地开发的时候debug，能快速定位与解决问题，那部署在服务器上是不是就没有办法了呢？只能通过查看日志来定位？不是的，在远端的服务器上，我们一样可以debug。2 IDEA的debug我们先来看一下在IntelliJ IDEA直接debug是怎样的。先准备一个简单的Java程序：package com.pkslow.basic; import java.util.Map; public class RemoteDebug { public static void main(String[] args) { System.out.println("------------------start------------------"); System.out.println("get all the system environment"); Map<String, String> envs = System.getenv(); System.out.println(" print out the contains `HOME`"); System.out.println("------env HOME------"); envs.entrySet().stream() .filter(env -> env.getKey().contains("HOME")) .forEach(env -> { System.out.println(env.getKey() + ":" + env.getValue()); }); System.out.println("------------------end------------------"); } }功能很简单，获取所有系统环境变量，并打印出含有HOME字段的。Debug很简单，直接点击以下按钮就可以：相信大家都知道这一点，但应该很多人都不会注意，IDEA究竟做了什么，为什么就可以调试了呢？我们看一下控制台的日志就明白了：/Library/Java/JavaVirtualMachines/jdk1.8.0_212.jdk/Contents/Home/bin/java -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:59313,suspend=y,server=n -javaagent:/Users/pkslow/Library/Caches/IntelliJIdea2019.3/captureAgent/debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "/Library/Java/JavaVirtualMachines/jdk1.8.0_212.jdk/Contents/Home/jre/lib/charsets.jar:" com.pkslow.basic.RemoteDebug Connected to the target VM, address: '127.0.0.1:59313', transport: 'socket'简化一下，不重要的参数去掉：java -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:59313,suspend=y,server=n com.pkslow.basic.RemoteDebug这就是可以Debug的原因，利用了Java Agent原理。这个功能很强大，类似一个AOP，代理了Java程序，可以利用它进行调试、热部署等。3 调试本地程序我们先试试如何可以调试本地程序，不是直接在IDEA上调试。先要编译出class文件RemoteDebug.class，然后按package结构放好。我通过mvn clean compile来编译。启动程序，在target/classes/目录执行：$ java -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:50050,suspend=y,server=y com.pkslow.basic.RemoteDebug Listening for transport dt_socket at address: 50050然后程序就会等待调试客户端的连接，不会往下执行。配置IDEA以进行调试：配置完成保存后，点击debug就可以了：程序已经进入debug模式：我们已经执行到了其中一行，现在看看服务端：与IDEA是同步的，并且确实已经控制了服务端Java的执行。4 远程调试Linux Java程序先把程序部署在Linux上：$ scp -P 22 ./com/pkslow/basi…

Stream日常使用
type: item
date: 2021-11-09T08:01:00+00:00
description: 1. Streams简介今天要讲的Stream指的是java.util.stream包中的诸多类。Stream可以方便的将之前的结合类以转换为Stream并以流式方式进行处理，大大的简化了我们的编程，Stream包中，最核心的就是interface Stream1.1 创建StreamStream的创建有很多方式，java引入Stream之后所有的集合类都添加了一个stream()方法，通过这个方法可以直接得到其对应的Stream。也可以通过Stream.of方法来创建：//Stream Creation String[] arr = new String[]{"a", "b", "c"}; Stream<String> stream = Arrays.stream(arr); stream = Stream.of("a", "b", "c");复制代码1.2 Streams多线程如果我们想使用多线程来处理集合类的数据，Stream提供了非常方便的多线程方法parallelStream()：//Multi-threading List<String> list =new ArrayList(); list.add("aaa"); list.add("bbb"); list.add("abc"); list.add("ccc"); list.add("ddd"); list.parallelStream().forEach(element -> doPrint(element));复制代码1.3 Stream的基本操作Stream的操作可以分为两类，一类是中间操作，中间操作返回Stream，因此可以级联调用。 另一类是终止操作，这类操作会返回Stream定义的类型。//Operations long count = list.stream().distinct().count();复制代码上面的例子中，distinct()返回一个Stream，所以可以级联操作，最后的count()是一个终止操作，返回最后的值。Matching Stream提供了anyMatch(), allMatch(), noneMatch()这三种match方式，我们看下怎么使用： //Matching boolean isValid = list.stream().anyMatch(element -> element.contains("h")); boolean isValidOne = list.stream().allMatch(element -> element.contains("h")); boolean isValidTwo = list.stream().noneMatch(element -> element.contains("h")); 复制代码Filteringfilter() 方法允许我们对Stream中的数据进行过滤，从而得到我们需要的：Stream filterStream = list.stream().filter(element -> element.contains("d"));复制代码上面的例子中我们从list中选出了包含“d”字母的String。Mappingmap就是对Stream中的值进行再加工，然后将加工过后的值作为新的Stream返回。//Mapping Stream<String> mappingStream = list.stream().map(element -> convertElement(element)); private static String convertElement(String element) { return "element"+"abc"; }复制代码上的例子中我们把list中的每个值都加上了“abc”然后返回一个新的Stream。FlatMapflatMap和Map很类似，但是他们两个又有不同，看名字我们可以看到flatMap意思是打平之后再做Map。怎么理解呢？假如我们有一个CustBook类：@Data public class CustBook { List<String> bookName; }复制代码CustBook定义了一个bookName字段。先看一下Map返回的结果：List<CustBook> users = new ArrayList<>(); users.add(new CustBook()); Stream<Stream<String>> userStreamMap = users.stream().map(user -> user.getBookName().stream());复制代码在上面的代码中，map将每一个user都转换成了s…

处理 Exception 的几种实践，很优雅，被很多团队采纳！
type: item
date: 2021-10-24T02:45:00+00:00
description: 在Java中处理异常并不是一个简单的事情。不仅仅初学者很难理解，即使一些有经验的开发者也需要花费很多时间来思考如何处理异常，包括需要处理哪些异常，怎样处理等等。这也是绝大多数开发团队都会制定一些规则来规范对异常的处理的原因。而团队之间的这些规范往往是截然不同的。本文给出几个被很多团队使用的异常处理最佳实践。在Finally块中清理资源或者使用try-with-resource语句当使用类似InputStream这种需要使用后关闭的资源时，一个常见的错误就是在try块的最后关闭资源。`public void doNotCloseResourceInTry() {` `FileInputStream inputStream = null;``try {` `File file = new File("./tmp.txt");` `inputStream = new FileInputStream(file);``// use the inputStream to read a file``// do NOT do this` `inputStream.close();` `} catch (FileNotFoundException e) {``log.error(e);` `} catch (IOException e) {``log.error(e);` `}``}`上述代码在没有任何exception的时候运行是没有问题的。但是当try块中的语句抛出异常或者自己实现的代码抛出异常，那么就不会执行最后的关闭语句，从而资源也无法释放。合理的做法则是将所有清理的代码都放到finally块中或者使用try-with-resource语句。`public void closeResourceInFinally() {` `FileInputStream inputStream = null;``try {` `File file = new File("./tmp.txt");` `inputStream = new FileInputStream(file);``// use the inputStream to read a file` `} catch (FileNotFoundException e) {``log.error(e);` `} finally {``if (inputStream != null) {``try {` `inputStream.close();` `} catch (IOException e) {``log.error(e);` `}` `}` `}``}``public void automaticallyCloseResource() {` `File file = new File("./tmp.txt");``try (FileInputStream inputStream = new FileInputStream(file);) {``// use the inputStream to read a file` `} catch (FileNotFoundException e) {``log.error(e);` `} catch (IOException e) {``log.error(e);` `}``}`指定具体的异常尽可能的使用最具体的异常来声明方法，这样才能使得代码更容易理解。`public void doNotDoThis() throws Exception {` `...``}``public void doThis() throws NumberFormatException {` `...``}`如上，NumberFormatException字面上即可以看出是数字格式化错误。对异常进行文档说明当在方法上声明抛出异常时，也需要进行文档说明。和前面的一点一样，都是为了给调用者提供尽可能多的信息，从而可以更好地避免/处理异常。异常处理的 10 个最佳实践，这篇也推荐看下。在Javadoc中加入throws声明，并且描述抛出异常的场景。`/**` `* This method does something extremely useful ...` `*` `* @param input` `* @throws MyBusinessException if ... happens` `*/``public void doSomething(String input) throws MyBusinessException {` `...``}`抛出异常的时候包含描述信息在抛出异常时，需要尽可能精确地描述问题和相关信息，这样无论是打印到日志中还是监控工具中，都能够更容易…

推荐一个 Java 实体映射工具 MapStruct
type: item
date: 2021-10-16T06:15:48+00:00
description: 背景在一个成熟可维护的工程中，细分模块后，domian工程最好不要被其他工程依赖，但是实体类一般存于domain之中，这样其他工程想获取实体类数据时就需要在各自工程写model，自定义model可以根据自身业务需要而并不需要映射整个实体属性。mapstruct这个插件就是用来处理domin实体类与model类的属性映射，定义mapper接口，mapstruct就会自动的帮我们实现这个映射接口，避免了麻烦复杂的映射实现。通过类似下面的代码声明一个Mapper，MapStruct会在编译时自动生成实现代码。除了基本的功能外，MapStruct提供很多高级的功能，全面支持业务中可能出现各种映射要求。示例代码如下如何使用？1.简单封装@Mapper public interface ContentConverter { ContentConverter INSTANCE = Mappers.getMapper(ContentConverter.class); @Mappings({ @Mapping(source = "status", target = "statusVo"), @Mapping(source = "isRemove", target = "isRemoveVo"), @Mapping(source = "urlImg", target = "imgUrl"), @Mapping(source = "onDate", target = "onDateVo"), }) Content.ContentVo domain2dto(Content content); List<Content.ContentVo> domains2dto(List<Content> content);简要说明如下 1 - 用Mapper注解标注Mapper接口（也可以是抽象类） 2 - 可以调用其他的Mapper，来转换复杂类型的字段 3 - 可以指定一个Decorator类对Mapper实现做定制 4 - 常用的practice：声明一个静态的INSTANCE，便于调用方引用 5 - 同名的字段会自动映射，不同名的通过Mapping注解指定映射关系和映射方法 6 - 嵌套对象映射，映射到字段的成员，或成员的成员等 7 - 通过源类型和目标类型不能确定mapper的情况下，可以给mapping方法命名，在使用的时候指定特定名字的mapping方法 8 - 用constant来指定映射为一个常量值 9 - 用@MappingTarget注解指定update方法的参数中代表目标对象的参数 10 - 通过@InheritConfiguration重用映射配置，避免重复配置2.整合进Maven<properties> <java.version>1.8</java.version> <mapstruct.version>1.3.1.Final</mapstruct.version> </properties> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-jdk8</artifactId> <version>${mapstruct.version}</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${mapstruct.version}</version> </dependency>3.为Maven compile plugin设置annotation processor(本文使用lombok) <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.0</version> <configuration> 1.8 <target>1.8</target> <encoding>UTF-8</encoding> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </path> <path> <groupId>org.mapstruct</groupId> <artifactId…

Java 设计模式：实战工厂方法模式
type: item
date: 2021-09-11T07:44:36+00:00
description: 一、前言好看的代码千篇一律，恶心的程序升职加薪。该说不说几乎是程序员就都知道或者了解设计模式，但大部分小伙伴写代码总是习惯于一把梭。无论多少业务逻辑就一个类几千行，这样的开发也可以归纳为三步；定义属性、创建方法、调用展示，Done！只不过开发一时爽，重构火葬场。好的代码不只为了完成现有功能，也会考虑后续扩展。在结构设计上松耦合易读易扩展，在领域实现上高内聚不对外暴漏实现细节不被外部干扰。而这就有点像家里三居(MVC)室、四居(DDD)室的装修，你不会允许几十万的房子把走线水管裸漏在外面，也不会允许把马桶放到厨房，炉灶安装到卫生间。谁发明了设计模式？ 设计模式的概念最早是由 克里斯托佛·亚历山大 在其著作 《建筑模式语言》 中首次提出的。 本书介绍了城市设计的 “语言”，提供了253个描述城镇、邻里、住宅、花园、房间及西部构造的模式， 而此类 “语言” 的基本单元就是模式。后来，埃里希·伽玛、 约翰·弗利赛德斯、 拉尔夫·约翰逊 和 理查德·赫尔姆 这四位作者接受了模式的概念。 1994 年， 他们出版了 《设计模式： 可复用面向对象软件的基础》 一书， 将设计模式的概念应用到程序开发领域中。其实有一部分人并没有仔细阅读过设计模式的相关书籍和资料，但依旧可以编写出优秀的代码。这主要是由于在经过众多项目的锤炼和对程序设计的不断追求，从而在多年编程历程上提炼出来的心得体会。而这份经验最终会与设计模式提到的内容几乎一致，同样会要求高内聚、低耦合、可扩展、可复用。你可能也遇到类似的经历，在学习一些框架的源码时，发现它里的某些设计和你在做开发时一样。我怎么学不会设计模式？ 钱也花了，书也买了。代码还是一坨一坨的！设计模式是由多年的经验提炼出来开发指导思想。就像我告诉你自行车怎么骑、汽车怎么开，但只要你没跑过几千公里，你能记住的只是理论，想上道依旧很慌！所以，本设计模式专题系列开始，会带着你使用设计模式的思想去优化代码。从而学习设计模式的心得并融入给自己。当然这里还需要多加练习，一定是人车合一，才能站在设计模式的基础上构建出更加合理的代码。二、工厂方法模式介绍工厂方法模式，图片来自 refactoringguru.cn工厂模式又称工厂方法模式，是一种创建型设计模式，其在父类中提供一个创建对象的方法， 允许子类决定实例化对象的类型。这种设计模式也是 Java 开发中最常见的一种模式，它的主要意图是定义一个创建对象的接口，让其子类自己决定实例化哪一个工厂类，工厂模式使其创建过程延迟到子类进行。简单说就是为了提供代码结构的扩展性，屏蔽每一个功能类中的具体实现逻辑。让外部可以更加简单的只是知道调用即可，同时，这也是去掉众多ifeslse的方式。当然这可能也有一些缺点，比如需要实现的类非常多，如何去维护，怎样减低开发成本。但这些问题都可以在后续的设计模式结合使用中，逐步降低。三、模拟发奖多种商品为了可以让整个学习的案例更加贴近实际开发，这里模拟互联网中在营销场景下的业务。由于营销场景的复杂、多变、临时的特性，它所需要的设计需要更加深入，否则会经常面临各种紧急CRUD操作，从而让代码结构混乱不堪，难以维护。在营销场景中经常会有某个用户做了一些操作；打卡、分享、留言、邀请注册等等，进行返利积分，最后通过积分在兑换商品，从而促活和拉新。那么在这里我们模拟积分兑换中的发放多种类型商品，假如现在我们有如下三种类型的商品接口；序号类型接口1优惠券CouponResult sendCoupon(String uId, String couponNumber, String uuid)2实物商品Boolean deliverGoods(DeliverReq req)3第三方爱奇艺兑换卡void grantToken(String bindMobileNumber, String cardId)从以上接口来看有如下信息：三个接口返回类型不同，有对象类型、布尔类型、还有一个空类型。入参不同，发放优惠券需要仿重、兑换卡需要卡ID、实物商品需要发货位置(对象中含有)。另外可能会随着后续的业务的发展，会新增其他种商品类型。因为你所有的开发需求都是随着业务对市场的拓展而带来的。四、用一坨坨代码实现ifelse实现需求public class PrizeController { private Logger logger = LoggerFactory.getLogger(PrizeController.class); public AwardRes awardToUser(AwardReq req) { String reqJson = JSON.toJSONString(req); AwardRes awardRes = null; try { logger.info("奖品…

SpringBoot 2.x 开发案例之优雅的处理异常
type: item
date: 2021-09-07T06:42:40+00:00
description: 前言异常怎么处理？撸主很久之前的项目都是在 Controller 层一个个 try 的，之后也曾自己写过AOP实现异常拦截处理。不过，这里给小伙伴推荐一款统一处理异常神器。代码案例微服务、前后端分离的时代，应该很少有小伙伴使用模板了吧，大多都是返回Json数据。墙裂推荐大家使用 @RestControllerAdvice，可以用于定义@ExceptionHandler、@InitBinder、@ModelAttribute，并应用到所有@RequestMapping中。异常处理器/** * 异常处理器 */ @RestControllerAdvice public class RrExceptionHandler { private Logger logger = LoggerFactory.getLogger(getClass()); @ExceptionHandler(Exception.class) public Result handleException(Exception e){ logger.error(e.getMessage(), e); return Result.error(); } /** * 自定义异常 */ @ExceptionHandler(RrException.class) public Result handleRRException(RrException e){ Result r = new Result(); r.put("code", e.getCode()); r.put("msg", e.getMessage()); return r; } @ExceptionHandler(DuplicateKeyException.class) public Result handleDuplicateKeyException(DuplicateKeyException e){ logger.error(e.getMessage(), e); return Result.error("数据库中已存在该记录"); } }自定义异常/** * 自定义异常 */ public class RrException extends RuntimeException { private static final long serialVersionUID = 1L; private String msg; private int code = 500; public RrException(String msg) { super(msg); this.msg = msg; } public RrException(String msg, Throwable e) { super(msg, e); this.msg = msg; } public RrException(String msg, int code) { super(msg); this.msg = msg; this.code = code; } public RrException(String msg, int code, Throwable e) { super(msg, e); this.msg = msg; this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } }页面响应/** * 页面响应 */ public class Result extends HashMap<String, Object> { private static final long serialVersionUID = 1L; public Result() { put("code", 0); } public static Result error() { return error(500, "未知异常，请联系管理员"); } public static Result error(String msg) { return error(500, msg); } public static Result error(int code, String msg) { Result r = new Result(); r.put("code", code); r.put("msg", msg); return r; } pub…

Java8 Stream 真香，赶紧来舔一舔
type: item
date: 2021-09-07T06:29:40+00:00
description: 如今Java14已经发布许久了，Java15也在路上跑着了，然鹅不少小伙伴Java8的API应该还没用溜吧！今天跟各位小伙伴们聊聊Java Stream API的具体使用方法以及应用场景。举哥简单的例子，定义一个数组：String[] users = new String[]{"张三","李四","王二"};我们来使用之前的方式遍历：for(int i=0;i<users.length;i++){ System.out.println(users[i]); }亦或者使用：for(String u:users){ System.out.println(u); }使用最新的API实现方式：Stream<String> stream = Stream.of(users); stream.forEach(user-> System.out.println(user));然鹅好像并没有多么方便，仅仅少撸了一行代码，下面跟大家分享一个稍微复杂一点的例子。首先定义一个学生类：@Data @Builder public class Student { private String name; private Integer age; private Integer sex; private Double score; }定义基础数据：List<Student> list = new ArrayList<>(); list.add(new Student("张三",28,1,78.9)); list.add(new Student("李四",20,1,98.0)); list.add(new Student("王二",29,0,100.0)); list.add(new Student("张三",28,1,78.5)); Stream<Student> stream = list.stream();遍历：stream.forEach(stu-> System.out.println(stu.getName()));过滤性别：stream.filter(stu -> stu.getSex()==1) .forEach(stu -> System.out.println(stu.getName()));名字去重：stream.distinct().forEach(stu -> System.out.println(stu.getName()));年龄排序：stream.sorted(Comparator.comparingInt(Student::getAge)) .forEach(stu -> System.out.println(stu.getName()));排序反转：stream.sorted(Comparator.comparingInt(Student::getAge).reversed()) .forEach(stu -> System.out.println(stu.getName()));姓名集合：List<String> userList = stream.map(stu -> stu.getName()) .collect(Collectors.toList());分数求和：Optional<Double> totalScore = stream.map(Student::getScore) .reduce((x, y) -> x+y); System.out.println(totalScore.get());求分数最大值：Optional<Double> maxScore = stream.map(Student::getScore).reduce(Double::max);求分数最小值：Optional<Double> minScore = stream.map(Student::getScore).reduce(Double::min);取前三名同学：stream.sorted(Comparator.comparingDouble(Student::getScore).reversed()) .limit(3) .forEach(System.out::println);扔了前三名同学：stream.sorted(Comparator.comparingDouble(Student::getScore).reversed()) .skip(3) .forEach(System.out::println);卧槽太多了，撸主举不过来了，但是基本上常用的就这么几个。函数式编程写出的代码更加简洁且意图明确，使用stream接口让你从此告别for循环，小伙伴们赶紧入坑享受吧！！！