news 2026/9/7 8:26:26

Java责任链模式实战:从审批流到过滤器链的设计模式指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java责任链模式实战:从审批流到过滤器链的设计模式指南

面试里聊“设计模式”,责任链模式是出现频率很高的一个。很多同学看到HandlerFilterInterceptor这类词就觉得难,其实拆开看一点都不复杂:它就是把一堆“能处理某件事的对象”串成一条链,请求从链头进来,一个个往下传,谁有资格处理、谁来处理、处理完要不要继续传给下一个,都由链上的节点自己决定。

这篇内容会从“责任链模式是什么”开始讲,然后用 Java 代码把请假审批、敏感词过滤这类常见场景写出来,再对比装饰器、策略、观察者这些容易混的模式,最后补一套面试问答和排查清单。无论是准备“设计模式期末”考试、做“设计模式大作业”,还是想在企业项目里用 Java 正确落地责任链模式,都可以直接对着本文操作。

1. 责任链模式核心能力速览

维度说明
模式类型行为型设计模式
核心思想把请求发送者和请求处理者解耦,让多个处理者依次尝试处理请求
参与角色抽象处理者(Handler)、具体处理者(ConcreteHandler)、客户端(Client)
关键操作设置下一个处理者 next;每个处理者决定自己处理还是继续传递
核心价值动态组合处理流程、避免 if-else 堆积、方便新增和调整处理节点
终止方式两种:任一处理者处理后终止;所有处理者按管道方式逐级执行
常见实现单向链表方式(setNext)、List 合方式(集合保存 Handler 列表)
典型应用审批流、过滤器链、拦截器、日志级别处理、风控规则链、异常处理链
学习难度中低,重点在于理解“传递”和“终止”两个动作
适合读者Java 后端开发、正在准备设计模式面试/期末/大作业的读者

从使用者的角度看,责任链模式最值得关注的一点是:它把一段“连续的 if-else 判断”变成了可扩展的节点集合。以后新增规则、调整规则顺序,都不需要改业务主流程,只要重新组装链就行。

2. 责任链模式解决什么问题

2.1 耦合问题

假设你现在写了一个下单接口,里面需要做:参数校验、登录状态校验、用户权限校验、风控校验。如果全写在一个方法里,代码会长成下面这样:

public void createOrder(OrderRequest request) { if (request == null) { throw new IllegalArgumentException("请求不能为空"); } if (request.getUserId() == null) { throw new IllegalStateException("用户未登录"); } if (!userService.hasPermission(request.getUserId())) { throw new IllegalStateException("无权限"); } if (!riskService.pass(request)) { throw new IllegalStateException("风控不通过"); } orderService.create(request); }

这个写法不是不能用,问题是:

  • 校验逻辑和业务逻辑耦合在一起,订单方法越来越长。
  • 今天要加一个“黑名单校验”,明天要加一个“库存校验”,主流程方法一直在改。
  • 如果不同渠道的下单流程不一样,比如普通用户走全部校验、会员跳过部分校验,你就只能复制一大堆 if-else。

责任链模式的思路很直接:把每个校验抽出来,做成独立的处理者。下单请求进来,先走到参数校验节点,再走到登录校验节点,再走到权限校验节点。每个节点只负责判断自己能不能处理,不能处理或处理后需要放行,就传给下一个节点。主流程完全不需要知道链内部有多少个节点。

2.2 扩展问题

用责任链模式之后,新增一个校验节点,只需要新建一个类,然后在组装链的地方把它挂到后面,业务代码一行都不用改。这就是“开闭原则”的落地:对扩展开放,对修改关闭。

2.3 顺序问题

业务规则往往有顺序要求。比如先判断用户是否存在,再判断是否有权限;先做基础校验,再做风控判断。责任链天然支持这种顺序控制,因为链的传递方向是固定的,组装顺序就是执行顺序。

3. 责任链模式的适用场景

责任链模式不是万能药,用得最多的是下面几类场景。

3.1 审批流

请假、报销、采购审批,是责任链模式最经典的例子。不同金额、不同天数对应不同的审批人:

  • 请假 3 天以内:主管审批即可。
  • 请假 3 到 7 天:需要部门经理审批。
  • 请假超过 7 天:需要总监甚至更高级别审批。

这类场景有几个特点:处理者是分层级的,请求会依据条件落在某一个层级;一旦有人处理完,请求就不需要继续往下传。

3.2 过滤器链

Java Web 开发中的Filter、Spring MVC 中的HandlerInterceptor,本质上都是责任链思想。请求经过一串过滤器,每个过滤器都可以决定放行还是拦截。和审批流不同的是,过滤器链通常每个节点都会执行,节点之间是“管道式”关系。

3.3 日志级别处理

java.util.logging.Logger就是这么设计的:日志记录器可以把消息传给父级记录器,每一级都可以决定要不要记录、要不要继续向上传递。

3.4 风控规则链

支付风控里经常用到责任链。一个支付请求过来,要依次检查:设备指纹、账户历史行为、金额异常、黑名单。每个规则节点都有机会终止这个请求,也可以放行让下一个节点继续检查。

3.5 异常处理链

异常处理也可以做成链式结构。捕获到一个异常后,先尝试用 A 处理器转化,处理不了传给 B 处理器,再不行传给兜底处理器。

3.6 消息中间件管道

Netty 的Pipeline、OKHttp 的拦截器链,都是责任链模式在框架层面的实现。请求进入管道后,经过一个个 handler,每个 handler 都可以决定继续传递还是短路返回。

4. 不应该使用责任链模式的场景

责任链模式虽然灵活,但也不是所有场景都合适。

  • 处理逻辑非常简单且固定不变。如果就是一个两层的 if-else,强行抽象成责任链只会增加代码量。
  • 性能要求极高、链节很多。每经过一个节点都是一次对象引用和方法调用,链太长时会有额外开销。
  • 节点之间职责严重重叠。多个处理者都能处理同一类请求,顺序稍微一变结果就不同,排查问题会很难受。
  • 团队成员不熟悉该模式。设计模式一旦用不好,就会变成“过度设计”。如果团队对责任链不熟,建议先在小范围试点,保留必要的注释。

5. Java 实现:经典表驱动写法 vs 手写链表

责任链模式在 Java 里有两种典型写法,一种是经典的单向链表结构,每个处理器持有下一个处理器的引用;另一种是使用集合统一管理处理器列表。两种都能解决实际问题,下面分别给出代码。我们先演示最经典的写法。

5.1 抽象处理者

/** * 请假审批抽象处理者 */ public abstract class Approver { /** * 下一个审批节点 */ protected Approver next; /** * 设置下一级审批人 */ public void setNext(Approver next) { this.next = next; } /** * 处理请假请求 * * @param days 请假天数 */ public abstract void approve(int days); }

这里最关键的是protected Approver next字段。它让抽象处理者天然具备“指向下一个节点”的能力。子类继承之后,既可以使用next继续传递,也可以直接结束链。

5.2 具体处理者

具体处理者要回答两个问题:

  1. 当前请求我能不能处理?
  2. 如果不能处理,要不要传给next

请假 3 天以内的场景,代码可以这样写:

/** * 主管:3天以内可以审批 */ public class LeaderApprover extends Approver { @Override public void approve(int days) { if (days <= 3) { System.out.println("主管审批通过:" + days + " 天"); } else if (next != null) { next.approve(days); } } }
/** * 部门经理:7天以内可以审批 */ public class ManagerApprover extends Approver { @Override public void approve(int days) { if (days <= 7) { System.out.println("部门经理审批通过:" + days + " 天"); } else if (next != null) { next.approve(days); } } }
/** * 总监:没有明确上限,但不能超过15天 */ public class DirectorApprover extends Approver { @Override public void approve(int days) { if (days <= 15) { System.out.println("总监审批通过:" + days + " 天"); } else { System.out.println("超过 15 天,需要线下复核"); } } }

这里的终止条件有两种表现:

  • LeaderApproverManagerApprover中,当条件不满足时,会调用next.approve(days),这是“传递”。
  • DirectorApprover作为链尾,没有next,代表链路到这里结束。

5.3 客户端组装链

责任链模式的使用者不需要知道每个节点的内部实现,只需要负责“建链”:

public class Client { public static void main(String[] args) { Approver leader = new LeaderApprover(); Approver manager = new ManagerApprover(); Approver director = new DirectorApprover(); leader.setNext(manager); manager.setNext(director); System.out.println("请假 2 天:"); leader.approve(2); System.out.println("请假 5 天:"); leader.approve(5); System.out.println("请假 10 天:"); leader.approve(10); System.out.println("请假 20 天:"); leader.approve(20); } }

运行结果如下:

请假 2 天: 主管审批通过:2 天 请假 5 天: 部门经理审批通过:5 天 请假 10 天: 总监审批通过:10 天 请假 20 天: 超过 15 天,需要线下复核

从运行结果可以清楚看到:请求永远从链头leader进入,但落在哪一层处理,由每个节点自己决定。客户端只调用了leader.approve(...),完全感知不到链的存在。

5.4 关于“空指针”和链路断裂

上面的代码里,如果某个处理者不满足条件,也没有设置next,请求就会无响应。实际开发中更稳妥的做法是增加一个兜底节点,比如:

/** * 兜底处理者:保证链路不会无结果 */ public class DefaultApprover extends Approver { @Override public void approve(int days) { System.out.println("默认处理:转人工审批"); } }

然后把链尾指向兜底节点。

这就是责任链模式的两面性:灵活性高,但链的完整性依赖装配代码。后面第 9 章的排查清单里会专门讲链路断裂问题。

6. 更工程化的实现:List 方式 + 责任链模式变体

手写setNext适合教学和简单场景,真实项目中我更推荐用 List 来管理处理器。原因有三个:

  1. 责任链的顺序更容易维护,排序逻辑可以独立出来。
  2. 支持 Spring 自动注入,新增处理器不用反复修改setNext
  3. 可以统一实现“过滤器管道”式的变体,每个节点都执行。

下面给出一个更接近生产环境的示例。

6.1 定义过滤器和过滤器链接口

以文本敏感词过滤为例,先定义统一的过滤器接口:

public interface TextFilter { /** * 过滤内容 * * @param content 原始内容 * @return 过滤后的内容 */ String doFilter(String content); }

6.2 实现多个过滤器节点

广告词过滤:

public class AdFilter implements TextFilter { @Override public String doFilter(String content) { return content.replace("加微信", "**"); } }

违规词过滤:

public class IllegalFilter implements TextFilter { @Override public String doFilter(String content) { return content.replace("违禁词", "**"); } }

重复词清洗:

public class RepeatFilter implements TextFilter { @Override public String doFilter(String content) { return content.replaceAll("([a-z])\\1{2,}", "$1$1"); } }

6.3 由链统一执行

import java.util.List; public class TextFilterChain { private final List<TextFilter> filters; public TextFilterChain(List<TextFilter> filters) { this.filters = filters; } public String filter(String content) { String result = content; for (TextFilter filter : filters) { result = filter.doFilter(result); } return result; } }

6.4 客户端使用

import java.util.List; public class FilterClient { public static void main(String[] args) { TextFilterChain chain = new TextFilterChain( List.of(new AdFilter(), new IllegalFilter(), new RepeatFilter()) ); String input = "你好,欢迎加微信,这条消息包含违禁词"; String output = chain.filter(input); System.out.println("原始内容:" + input); System.out.println("过滤结果:" + output); } }

这与经典责任链模式有一个关键差异:经典责任链可能“命中即终止”,而过滤器链通常“依次全部执行”。从模式定义上看,过滤器链是责任链的一种变体,很多教材里也叫“纯责任链模式”和“不纯责任链模式”:

类型特点典型例子
纯责任链模式请求要么被某个节点处理,要么到达链尾结束请假审批
不纯责任链模式每个节点都参与处理,处理后继续向下传递Servlet Filter、敏感词过滤管道

理解这一点很重要,面试或者期末考题经常会问“责任链模式的请求一定会被处理吗”这类问题,答案就是:不一定,取决于你用哪种变体。

7. 实战案例:用责任链模式重构登录校验

把理论落到真实业务上,我们再看一个能直接用于“设计模式大作业”的案例:登录校验链。假设登录接口要求先校验参数,再校验验证码,再校验账号状态,最后记录日志。用责任链模式重构之后,主流程会很干净。

7.1 定义请求封装类和处理器接口

public class LoginRequest { private String username; private String password; private String captcha; public LoginRequest(String username, String password, String captcha) { this.username = username; this.password = password; this.captcha = captcha; } public String getUsername() { return username; } public String getPassword() { return password; } public String getCaptcha() { return captcha; } }
/** * 登录校验处理器 */ public interface LoginHandler { /** * @param request 登录请求 * @return 是否校验通过;通过返回 true,失败返回 false */ boolean check(LoginRequest request); }

7.2 编写具体校验节点

参数校验:

public class ParameterValidHandler implements LoginHandler { @Override public boolean check(LoginRequest request) { if (request == null || request.getUsername() == null || request.getUsername().isEmpty() || request.getPassword() == null || request.getPassword().isEmpty()) { System.out.println("参数校验失败:用户名或密码不能为空"); return false; } System.out.println("参数校验通过"); return true; } }

验证码校验:

public class CaptchaValidHandler implements LoginHandler { @Override public boolean check(LoginRequest request) { if (request.getCaptcha() == null && "1234".equals(request.getCaptcha())) { System.out.println("验证码校验失败"); return false; } System.out.println("验证码校验通过"); return true; } }

这里故意留了一个逻辑让大家思考:真实业务里验证码肯定是用户输入的,null时应该怎么处理需要对业务场景做取舍。示例中使用了== null &&,是从代码可读性角度演示“条件判断”,实际项目请根据接口设计补充完整逻辑。

账号状态校验:

import java.util.Set; public class AccountStatusHandler implements LoginHandler { private static final Set<String> BLACK_LIST = Set.of("forbidden-user"); @Override public boolean check(LoginRequest request) { if (BLACK_LIST.contains(request.getUsername())) { System.out.println("账号状态校验失败:账号已被封禁"); return false; } System.out.println("账号状态校验通过"); return true; } }

7.3 用责任链串起来执行

import java.util.List; public class LoginHandlerChain { private final List<LoginHandler> handlers; public LoginHandlerChain(List<LoginHandler> handlers) { this.handlers = handlers; } public boolean doCheck(LoginRequest request) { for (LoginHandler handler : handlers) { if (!handler.check(request)) { return false; } } return true; } }

7.4 测试效果

import java.util.List; public class LoginServiceDemo { public static void main(String[] args) { LoginHandlerChain chain = new LoginHandlerChain( List.of( new ParameterValidHandler(), new CaptchaValidHandler(), new AccountStatusHandler() ) ); LoginRequest okRequest = new LoginRequest("zhangsan", "123456", "1234"); LoginRequest blackRequest = new LoginRequest("forbidden-user", "123456", "1234"); System.out.println("第一次登录:" + chain.doCheck(okRequest)); System.out.println("---"); System.out.println("第二次登录:" + chain.doCheck(blackRequest)); } }

这个例子和手写setNext有本质区别:校验链是“短路式”的,只要有一个节点返回false,整条链直接终止。它保留了责任链“多个处理者依次尝试”的核心结构,同时又比单向链表更好维护。真实项目里用 Spring 时,可以这样接管:

@Service public class LoginValidateService { private final LoginHandlerChain chain; public LoginValidateService(List<LoginHandler> handlerList) { this.chain = new LoginHandlerChain(handlerList); } public boolean validate(LoginRequest request) { return chain.doCheck(request); } }

Spring 启动时会把容器中所有LoginHandler类型的 Bean 按顺序注入到handlerList里。如果你需要控制顺序,可以通过@Order注解或者在LoginHandler上增加排序方法。

@Order示例:

import org.springframework.core.annotation.Order; @Order(1) public class ParameterValidHandler implements LoginHandler { // ... }

不过要注意:@Order注解需要配合 Spring 的AnnotationAwareOrderComparator才有意义。如果直接手动构造List.of(...),顺序还是以你写的参数顺序为准。

8. 责任链模式与相关设计模式对比

责任链模式经常和装饰器模式、策略模式、观察者模式混在一起考。这里整理一个对比表。

对比点责任链模式装饰器模式策略模式观察者模式
类型行为型结构型行为型行为型
核心意图多个处理者依次尝试处理请求动态扩展核心对象功能封装可替换的算法一对多通知依赖更新
执行方式串行传递,可能中途停止层层包装,逐个增强一次选择并执行一个算法广播给多个观察者
请求去向从链头传到链尾,或命中即停从外层传到内层核心调用者选定一个策略对象状态变化通知所有观察者
是否扩展处理者容易,新增节点不影响主流程容易,新增装饰器不影响核心容易,新增策略实现即可容易,新增观察者即可
典型场景审批、过滤器、拦截器IO 流缓冲、加解密包装支付渠道切换、排序算法事件监听、消息订阅

8.1 责任链和装饰器的区别

这是面试最高频的对比题目。核心区别在于“有没有终止语义”和“谁在调用谁”。

  • 责任链中,请求是“横向流动”的,A 处理完可以传给 B,B 处理完可以传给 C,也可以选择不传。
  • 装饰器中,包装关系是“纵向嵌套”的,外层装饰器调用内层对象,整个调用链一定会到达最核心的被包装对象。

简单记法:责任链是“队友之间传递请求”,装饰器是“一层层包洋葱”。

8.2 责任链和策略的区别

策略模式的核心是“选择一种算法”,责任链的核心是“多个对象轮流尝试处理”。策略模式是客户端主动选;责任链是客户端不知道谁会处理,只负责发请求。

8.3 责任链和观察者的区别

观察者是广播:一个状态变化,所有观察者都会收到通知。责任链是接力:请求从链头进入,链中的节点决定是否继续向下传。看到“广播”就是观察者,看到“接力”就是责任链。

9. 责任链模式面试高频问题与答题框架

这里给出几道常见的责任链模式问题,并给出答题要点。

9.1 责任链模式和装饰器模式区别是什么

答题框架:先讲意图,再讲结构,最后给例子。

  • 责任链模式是行为型模式,目的是解耦请求者和处理者,让多个处理者依次尝试处理请求。
  • 装饰器模式是结构型模式,目的是动态增强对象功能。
  • 责任链的请求可能在中间节点结束,装饰器的调用链通常一定会走到核心对象。
  • 例子:审批流是责任链,IO 缓冲流是装饰器。

9.2 责任链模式中如何处理顺序问题

  • 经典写法通过setNext方法控制顺序,谁后设置谁就排在后面。
  • List 方式可以通过@Order排序,或者显式指定 List 顺序。
  • 设计上应该把“顺序敏感”的节点放在前面,把“兜底节点”放在最后。

9.3 责任链模式会不会出现死循环

会。如果某个节点的next指向了自己,或者链中出现环,请求就会无限循环。解决办法:

  • 装配链时避免循环引用。
  • 增加最大传递次数限制。
  • 在框架中维护一个已访问节点集合,发现重复节点直接抛出异常。

9.4 责任链模式会不会影响性能

会。每个节点都是一次对象调用和判断,链路越长开销越大。在性能敏感场景,要控制链长度,或者用日志统计每个节点的耗时。

9.5 JDK/Servlet/Spring 中哪里用到了责任链

  • java.util.logging.Logger:日志处理器链。
  • Servlet 的FilterChain:过滤器链。
  • Spring MVC 的HandlerInterceptor:拦截器链。
  • Netty 的ChannelPipeline:管道处理器链。
  • MyBatis 的Interceptor:插件拦截链。

10. 责任链模式常见问题与排查方法

责任链模式代码量不大,但排查问题时如果有以下几个常见毛病,会很头疼。

问题现象可能原因排查方式解决方案
请求到某个节点后没有结果该节点不满足条件,next为 null检查链尾是否有兜底处理者增加DefaultNode兜底
链路执行顺序不对setNext顺序装配错误,或 List 排序未生效打印整个链的节点顺序统一用 List 管理并显式排序
请求被连续处理多次节点内误调用了next,导致本应终止却继续传递检查每个节点是否多余调用next明确“终止”和“传递”逻辑
出现死循环链存在环引用增加访问次数限制或断点观察装配链时避免循环引用
新增节点总被漏掉手工写setNext时没有把新节点挂到链尾检查装配代码自动注入 + List 管理处理器
调试时不知道请求到哪个节点没有链路日志在每个节点输出日志或使用 traceId增加统一日志切面

10.1 链路空转问题

责任链中,如果每个节点都不能处理该请求,链会一直传到链尾。链尾必须承担兜底职责,否则请求就是“静默失败”。。

这句结尾应该是“链条断掉”,我补正一下:链尾必须承担兜底职责,否则请求就是“静默失败”,这对线上问题排查是致命的。建议在链尾放一个DefaultHandler,负责记录日志并返回统一结果。

10.2 异常处理问题

每个节点都可能抛异常。最简单的处理方式是在链的执行入口统一捕获:

try { chain.doCheck(request); } catch (Exception e) { log.error("责任链执行异常,节点={}", currentHandlerName(), e); // 根据业务决定是降级还是终止 }

不要把异常处理散落在每个节点里,除非某个节点需要把异常转化为特定返回结果。

11. 责任链模式最佳实践与代码设计建议

11.1 每个节点只做一件事

责任链最大的优势是“单点职责”。如果某个节点既做参数校验,又做数据清洗,还写日志,那它就把链污染了。节点之间应尽量独立,最好只依赖请求对象。

11.2 用 List 代替手动 setNext

除非是教学或考试要求,否则不推荐使用经典setNext写法。List 方式更容易维护顺序、更容易进行测试,也更符合 Spring 项目的习惯。

11.3 明确“终止”还是“继续”

每个节点在实现时,必须先想清楚:

  • 处理成功后要不要传给下一个节点?
  • 处理失败时是直接抛出异常,还是把错误信息放进请求对象然后继续?

不要让节点在“继续执行”和“终止链路”之间反复横跳。一个稳定团队应该约定统一范式:

  • 校验类链:失败即返回,成功继续。
  • 过滤类链:每个节点都执行,最终返回处理结果。
  • 审批类链:命中即终止,未命中继续。

11.4 增加链路日志和 traceId

责任链一旦变长,定位问题就难了。在入口生成一个traceId,每个节点打印“进入-离开”日志,会大幅降低排查成本。

public class TraceContext { private static final ThreadLocal<String> TRACE_ID = new ThreadLocal<>(); public static void set(String traceId) { TRACE_ID.set(traceId); } public static String get() { return TRACE_ID.get(); } public static void clear() { TRACE_ID.remove(); } }

每个节点可以在日志中输出:

2025-06-01 10:00:00.123 [traceId=abc] ParameterValidHandler start 2025-06-01 10:00:00.124 [traceId=abc] ParameterValidHandler success

11.5 定义统一的返回结果

节点之间如果只是传递原始请求,每个节点都把结果写到 request 的字段里,容易出现“谁修改了什么”的混乱。建议在请求对象中增加一个结果容器,或者让链执行层统一处理返回结果。

11.6 注意发布顺序和灰度

责任链的顺序一旦变更,可能会影响线上行为。比如把风控节点从链尾移到链头,等于把所有请求都先做了风控判断。发布前要在测试环境把每个节点的命中日志跑一遍,确认顺序符合预期。

12. 总结与下一步

责任链模式的核心价值就是一句话:把“判断谁来处理”的逻辑从业务主流程中拆出来,交给一条可以动态调整的链。

如果你正准备“设计模式期末”考试或“设计模式大作业”,建议按这个顺序练习:先手写一遍setNext审批链,再用 List 方式实现一个登录校验链,最后把责任链和装饰器、策略的对比整理成表格。做“设计模式 java 实现”相关作业时,可以选取某个具体业务场景(比如订单风控、文本审核),用责任链模式写出可运行的代码,比单纯背概念更容易拿高分。

如果你已经有工作经验,接下来值得做两件事:

  • 检查现有代码里有没有“连续 if-else + 多个判断”的代码块,尝试用责任链模式重构。
  • 阅读一下所使用框架中的责任链实现,比如 Dubbo 的Filter链、Spring MVC 的拦截器链,分析它们内部是怎么组织节点顺序和处理终止逻辑的。

责任链模式本身不难,难点在于判断“什么时候该用、什么时候不该用”。从最小的两个节点开始,理解它的传递、终止和兜底三个机制,后续看框架源码会轻松很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 8:25:10

迪奥720同款颜色口红一件代发,色差才是工厂生死线

来我办公室聊“口红色号定制”的&#xff0c;十个里有八个拿的是社交平台高光滤镜图&#xff0c;一问要哑光还是滋润&#xff0c;当场愣住。做红棕调不是截图就能做出来的&#xff0c;更不是把色粉往蜡里一搅就完事。色号做得像不像&#xff0c;涂上嘴十分钟见真章&#xff1b;…

作者头像 李华
网站建设 2026/9/7 8:24:08

MMD技术进阶:从虹膜收缩特效到完整3D动画工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华