MDX博客
Blog posts support Docusaurus Markdown features, such as MDX.
Use the power of React to create interactive blog posts.
Blog posts support Docusaurus Markdown features, such as MDX.
Use the power of React to create interactive blog posts.
| 建议 | 效果 |
|---|---|
| 运动 | 可以解决 80% 的疾病 |
| 读书 | 可以决定 60% 的命运 |
| 冷漠 | 可以省去 80% 的麻烦 |
| 有钱 | 可以解决 95% 的问题 |
| 行动 | 可以实现 80% 的目标 |
| 早睡 | 可以养足 90% 的元气 |
| 复盘 | 可以规避 70% 的弯路 |
| 自律 | 可以掌控 90% 的节奏 |
| 闭嘴 | 可以降低 70% 的纷争 |
| 拒绝 | 可以减少 90% 的内耗 |
| 练习 | 可以获得 80% 的能力 |
就是你银行卡里,那笔能让你在不做任何事的情况下,也能体面地活一年的钱——这是你所有安全感的基石。
就是你那套能让你看清世界真相,并做出正确决策的思维模型。
就是你那副能支撑你打完这场硬仗的身体和精神。
这是你人生前半场最重要,也最残酷的阶段。你需要像一个苦行僧一样,去完成三件事:
第一,把自己当成苦行僧。
你必须过一种极度专注,甚至有点无趣的生活。你要拒绝所有消耗你时间和金钱的无效社交和廉价娱乐。你要把别人刷短视频、喝大酒的时间,都用来投资到你的三大资本上去。
这个过程很苦,但这是你作为一个普通人唯一的出人头地的路。
第二,把工作当成你的练级场。
千万不要对你的第一份,甚至第二份工作抱有任何情感上的幻想。它不是你的家,它只是你免费的(或者说付费)的练级场。
你要像一块海绵一样,疯狂地从你现在的工作中吸收养分:
工资只是你在这家公司练级,他们付给你的学费。
第三,把薪水看作你的第一桶金。
每个月,强制性地把你收入的一半,甚至更多存下来。这笔钱不是用来消费的,它是你未来所有事业的启动资金,一分一毫都不能乱动。
当你通过几年的苦修,完成了原始资本的积累后,你就要立刻启动你人生的下半场:搭建你的个人商业模式。
第一步:找到你的生态位
你不需要跟所有人竞争,你只需要找到一个非常细分的、你比 90% 的人都做得好一点点的领域。
这个生态位,最好是你热爱、擅长和市场需求的交集。
第二步:打造你的产品
把你在这个生态位上的能力产品化,可以是:
你必须有一个可以清晰地向市场报价的价值单元。
第三步:建立你的流量渠道
你要开始在一个平台(比如小红书或者视频号)持续地输出你的价值,让那些需要你这个产品的人能找到你。
一个健康的个人商业模式,就是你找到了一个你能填补的市场缺口,然后用你的内容建了一座桥,连接了你和你的客户。
这条路,很难,也很孤独。没有攻略,没有捷径,甚至连为你鼓掌的人都很少。
但这恰恰是它的迷人之处,因为你走的每一步,都是在为你自己的人生绘制地图;你吃的每一份苦,都是在为你未来的家族打下地基。
百分百死亡率的人生,你还在犹豫什么?犹豫怎么死吗?
上班最可怕的地方是:
它居然让我因为盼着退休而期待衰老,让我完全不珍惜我剩余人生里最年轻的每一天。总想着周一到周五赶紧过去,可是一周又一周,突然反应过来——
这流逝掉的是我的生命。
所以,想做什么就去做吧。毕竟,我们唯一拥有的就是现在。
防御性编程是一种编程哲学,强调编写能够预见并处理各种异常情况的代码。它不是为了增加复杂度让开发者不可替代,而是为了让系统更加健壮、安全和易于维护。
永远不要信任外部输入,包括用户输入、API参数、配置文件等。
public class UserService {
public User createUser(UserCreateRequest request) {
// 参数验证
Objects.requireNonNull(request, "请求对象不能为空");
if (request.getEmail() == null || !isValidEmail(request.getEmail())) {
throw new IllegalArgumentException("邮箱格式不正确");
}
if (request.getPassword() == null || request.getPassword().length() < 8) {
throw new IllegalArgumentException("密码长度至少8位");
}
// 业务逻辑
return userRepository.save(toEntity(request));
}
private boolean isValidEmail(String email) {
return email != null && email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$");
}
}
合理的异常处理机制,避免程序崩溃。
@Service
public class PaymentService {
public PaymentResult processPayment(PaymentRequest request) {
try {
validateRequest(request);
return executePayment(request);
} catch (ValidationException e) {
log.error("支付请求验证失败: {}", e.getMessage(), e);
return PaymentResult.failure("参数错误: " + e.getMessage());
} catch (PaymentProcessingException e) {
log.error("支付处理失败: {}", e.getMessage(), e);
return PaymentResult.failure("支付失败,请稍后重试");
} catch (Exception e) {
log.error("支付处理发生未知错误: {}", e.getMessage(), e);
return PaymentResult.systemError();
}
}
}
预防空指针异常,使用Optional等工具。
@Service
public class OrderService {
public OrderDetail getOrderDetail(Long orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException("订单不存在: " + orderId));
Customer customer = customerRepository.findById(order.getCustomerId())
.orElse(null);
// 使用Optional处理可能为空的对象
String customerName = Optional.ofNullable(customer)
.map(Customer::getName)
.orElse("匿名用户");
return new OrderDetail(order, customerName);
}
}
确保资源得到正确释放,避免内存泄漏。
@Service
public class FileService {
public String readFile(String filePath) {
// 使用try-with-resources确保资源自动释放
try (BufferedReader reader = Files.newBufferedReader(Paths.get(filePath))) {
return reader.lines()
.collect(Collectors.joining("\n"));
} catch (IOException e) {
log.error("读取文件失败: {}", filePath, e);
throw new FileOperationException("读取文件失败", e);
}
}
}
在多线程环境下保证数据一致性。
@Service
public class CounterService {
private final ConcurrentHashMap<String, AtomicLong> counters = new ConcurrentHashMap<>();
public long increment(String key) {
return counters.computeIfAbsent(key, k -> new AtomicLong(0))
.incrementAndGet();
}
// 使用ConcurrentHashMap避免并发修改异常
public Map<String, Long> getAllCounters() {
Map<String, Long> result = new HashMap<>();
counters.forEach((key, value) -> result.put(key, value.get()));
return Collections.unmodifiableMap(result);
}
}
防止SQL注入和其他数据库安全问题。
@Repository
public class UserRepository {
@Autowired
private JdbcTemplate jdbcTemplate;
// 使用参数化查询防止SQL注入
public List<User> findUsersByName(String name) {
String sql = "SELECT * FROM users WHERE name LIKE ?";
return jdbcTemplate.query(sql,
new Object[]{"%" + escapeLikePattern(name) + "%"},
new BeanPropertyRowMapper<>(User.class));
}
// 转义LIKE语句中的特殊字符
private String escapeLikePattern(String pattern) {
return pattern.replace("\\", "\\\\")
.replace("%", "\\%")
.replace("_", "\\_");
}
}
统一处理应用中的异常。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleIllegalArgument(IllegalArgumentException e) {
log.warn("参数错误: {}", e.getMessage());
return new ErrorResponse("PARAM_ERROR", e.getMessage());
}
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFound(ResourceNotFoundException e) {
log.info("资源未找到: {}", e.getMessage());
return new ErrorResponse("RESOURCE_NOT_FOUND", e.getMessage());
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleGeneric(Exception e) {
log.error("系统内部错误", e);
return new ErrorResponse("SYSTEM_ERROR", "系统繁忙,请稍后重试");
}
}
使用Bean Validation进行参数校验。
public class UserCreateRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度应在2-20之间")
private String username;
@Email(message = "邮箱格式不正确")
@NotBlank(message = "邮箱不能为空")
private String email;
@NotBlank(message = "密码不能为空")
@Size(min = 8, max = 20, message = "密码长度应在8-20之间")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d@$!%*?&]{8,}$",
message = "密码必须包含大小写字母和数字")
private String password;
// getters and setters...
}
保护系统免受过载影响。
@Service
public class ExternalApiService {
// 使用Resilience4j进行熔断
private final CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("external-api");
// 限流
private final RateLimiter rateLimiter = RateLimiter.ofDefaults("api-call");
public ApiResponse callExternalApi(String param) {
Supplier<CompletableFuture<ApiResponse>> decoratedSupplier =
CircuitBreaker.decorateCompletionStage(
circuitBreaker,
() -> RateLimiter.decorateCompletionStage(
rateLimiter,
() -> CompletableFuture.supplyAsync(() -> doActualCall(param))
).get()
);
return decoratedSupplier.get().join();
}
private ApiResponse doActualCall(String param) {
// 实际的外部API调用
return externalApiClient.call(param);
}
}
防御性编程的目标是构建健壮、可靠的系统,而不是制造复杂度。通过合理的防御措施,我们可以提高代码质量,降低系统故障率,提升用户体验。
真正的专业开发者应该致力于编写清晰、简洁且健壮的代码,使系统易于理解和维护,这样才能真正成为团队中不可或缺的人才。
Java 的强类型本意是安全,但你可以将其变成迷宫。
Map<String, Object>。双括号初始化 + 匿名内部类:
List<String> list = new ArrayList<String>() {{
add(new String(new char[]{'h','e','l','l','o'}));
}};
Map<Object, Object> 传参,取值全靠强转,谁接手谁崩溃。过时的注释:代码改了,注释留着旧逻辑。
废话注释:i++; // i加一。
嘲讽式注释:// 这里有个坑,但我现在没时间填,祝你好运。
请谨慎使用此类代码。
这种写法会产生额外的 .class 文件,增加内存泄漏风险,且极难阅读。 利用枚举实现单例并挂载业务逻辑:将复杂的业务算法写在 Enum 的构造函数里。
简单理解: 架构级项目 ≠ 功能多的项目 而是指:从第一天就考虑长远发展,代码结构清晰,容易维护和扩展的项目
核心特点:
| 场景 | 普通项目(学生/初级开发) | 架构级项目(企业级) |
|---|---|---|
| 用户登录 | Controller直接查数据库,密码明文存储 | 分层架构:Controller → Service → AuthProvider 密码BCrypt加密 + 登录失败限流 + 支持OAuth2扩展 |
| 新增功能 | 在原有类里疯狂加if-else | 通过策略模式/插件机制 不改核心代码就能扩展新功能 |
| 系统崩溃 | 日志全是System.out.println 出问题找不到原因 | 全链路TraceID + 结构化日志 监控告警 + 自动定位问题 |
| 流量突增 | 直接宕机,服务不可用 | 熔断降级 + 缓存机制 异步处理 + 负载均衡 |
| 团队协作 | 所有人改同一个包 代码冲突频繁 | 模块解耦 + 接口契约 并行开发互不影响 |
就像盖房子一样,每层都有明确职责:
表现层 (Controller) ← 接收用户请求
↓
应用层 (Service) ← 处理业务流程
↓
领域层 (Domain) ← 核心业务逻辑
↓
基础设施层 (Infra) ← 数据库、缓存等技术实现
好处: 高内聚低耦合,业务逻辑不依赖具体技术框架
| 需求 | 架构体现 | 简单理解 |
|---|---|---|
| 可扩展性 | 策略模式、插件机制 | 加功能不改老代码 |
| 可靠性 | 重试机制、熔断器 | 出错能自动恢复 |
| 可观测性 | 链路追踪、监控告警 | 出问题能快速发现 |
| 安全性 | 权限控制、数据脱敏 | 保护系统和数据安全 |
| 可部署性 | Docker + CI/CD | 一键部署,自动发布 |
常见的架构模式:
@PostMapping("/order")
public Result createOrder(OrderDTO dto) {
// 1. 校验库存
if (stockService.get(dto.getSkuId()) < dto.getCount()) {
return error("库存不足");
}
// 2. 扣减库存(直接调 DB)
stockMapper.decrease(dto.getSkuId(), dto.getCount());
// 3. 创建订单
orderMapper.insert(dto);
// 4. 发送短信
smsService.send("下单成功");
return success();
}
问题:
// 1. 应用服务层 - 处理业务流程
@Transactional
public OrderId createOrder(CreateOrderCommand cmd) {
// 领域校验
Product product = productRepository.findById(cmd.getSkuId());
product.checkStock(cmd.getCount());
// 创建订单
Order order = Order.create(cmd);
orderRepository.save(order);
// 发布事件(解耦)
eventPublisher.publish(new OrderCreatedEvent(order.getId()));
return order.getId();
}
// 2. 基础设施层 - 监听事件发短信
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
smsClient.sendAsync("下单成功"); // 异步处理
}
优势:
看开源项目时,重点检查这些特征:
✅ 代码结构清晰
✅ 工程化完善
✅ 技术选型成熟
先理解概念
看优秀源码
实践DDD
学习微服务
架构设计
团队协作
一句话理解:
架构级项目 = 好的代码结构 + 完善的工程化 + 成熟的设计思想
核心价值:
记住: 代码是死的,架构思想是活的。好的架构能让复杂系统变得简单可控!
1、虾皮,它是补钙补肾王
第一步去盐去砂,留下咸味入肾的性;
第二步滤干水分,晾干;
第三步,用砂锅或不锈钢,不用铁锅,一定要炒,把寒邪气逼出,通过火的炮制把它的阳气逼出来;
第四步,观色闻香,待有点烤面包的颜色,香味散发满厨房程度,手一捻就成粉末即好;
第五步,研末封存,装在密封罐内,这就是还魂补肾粉,每天早上舀一勺拌在粥或蛋羹里吃。
2、腐竹白果汤
腐竹要买颜色豆黄的,不要买漂白过的毒腐竹,放7~10粒去毒芯的白果杏仁,加几块猪瘦肉或排骨,不要放有毒的淋巴肉,炖熟即可,这个汤补肺健脾,生精健身,清血管。
3、醋泡黑豆
黑皮绿芯的黑豆
洗净炒制泡粮食醋49天
晚饭后吃10~15粒。
4、鹌鹑蛋补益五脏,少胆固醇,补脑强记忆力,手不抖。
鹌鹑补脑方,3~5个打散,配伍枸杞子一小把,滋补肝肾,黑芝麻粉补肾黑发,蒸成蛋羹,出锅前加点香油即可。
《原子习惯》(Atomic Habits)是由 James Clear 所著的一本全球畅销书,被誉为"习惯养成领域的圣经"。它不是讲大道理,而是提供了一套科学、可操作、可持续的行为改变系统。
微小的、持续的改进(1% 的提升),在长期复利下会产生惊人的结果。
"你不是要达成目标,而是要成为那种能自然达成目标的人。"
James Clear 提出:习惯 = 提示(Cue) → 渴望(Craving) → 反应(Response) → 奖赏(Reward)
围绕这个循环,他提出 4 条反向设计原则:
| 法则 | 目标 | 具体策略 |
|---|---|---|
| 1. 让提示显而易见 (Make it obvious) | 建立好习惯 | - 习惯叠加("做完 A 之后,我就做 B") - 设计环境(把水果放桌上,把手机放抽屉) |
| 2. 让渴望有吸引力 (Make it attractive) | 增强动机 | - 绑定喜好("只有在跑步时才能听播客") - 加入文化认同("我是健康生活的人") |
| 3. 让行动简便易行 (Make it easy) | 降低启动门槛 | - 两分钟规则("新习惯不超过 2 分钟") - 减少摩擦(提前准备好运动服) |
| 4. 让奖赏令人满足 (Make it satisfying) | 强化正反馈 | - 即时奖励(打卡打钩、存"习惯币") - 不要连续错过两次 |
✅ 反过来,想戒掉坏习惯?就做相反的事:隐藏提示、让它没吸引力、增加难度、制造痛苦后果。
"赢球的球队不只盯着冠军奖杯,而是优化训练体系。"
行为改变的最高境界,是身份的转变。
微小的选择,长期决定人生轨迹。
任何习惯都可以从"两分钟版本"开始:
关键是先开始,行动会带动状态。
| 场景 | 用《原子习惯》怎么做? |
|---|---|
| 想坚持写作 | - 环境:书桌只放电脑和笔记本(无手机) - 习惯叠加:"喝完早咖啡后,写 200 字" - 两分钟版:"打开文档,写一个标题" |
| 想少吃零食 | - 隐藏提示:不买零食,或藏在高处 - 增加摩擦:用密封罐装,贴上"吃前思考 10 秒"标签 - 替代奖赏:准备切好的水果 |
| 想早睡 | - 提示:晚上 10 点自动调暗灯光 - 身份认同:"我是作息规律的人" - 奖赏:睡前读一本轻松的书(非手机) |
如果你只记住一点,请记住:
不要追求"大改变",而是设计一个让你"不知不觉变好"的系统。
改变,从原子大小开始。 🚀
“You do not rise to the level of your goals. You fall to the level of your systems.”
(你不会达到目标的高度,你会跌到系统所允许的水平。)
而减少琐事干扰,就是优化你的认知系统。
客户端(浏览器)
↓
[1] HTTP 请求(例如:GET /user/profile 或 /static/logo.png)
↓
[2] Web 容器(Tomcat / Jetty / Undertow)
↓
[3] 所有已注册的 Filter.doFilter()(按 @Order 顺序执行)
│ ← 包括:CharacterEncodingFilter、CorsFilter、自定义 Filter 等
│ ← **静态资源(如 /css/app.css)也会经过此处**
│
│ ┌───────────────────────────────┐
│ │ 在 Filter 中调用 chain.doFilter() │
│ └───────────────────────────────┘
│ ↓
│ [4] DispatcherServlet(Spring MVC 前端控制器)
│ ↓
│ [5] HandlerMapping
│ │ → 尝试匹配 @Controller 中的 @RequestMapping
│ │
│ ├─ 若匹配成功(如 /user/profile)→ 进入 Spring MVC 流程
│ │
│ └─ 若未匹配(如 /static/logo.png)→
│ 直接由 ResourceHttpRequestHandler 处理,
│ **跳过所有 Interceptor**,但仍会走后续 Filter
│
│ ↓(仅对匹配到 Controller 的请求)
│ [6] 执行已注册的 Interceptor.preHandle()
│ │ → 按 addInterceptor() 注册顺序调用
│ │ → 若任一返回 false,则中断流程,**不调用 Controller**
│ │ (但仍会执行后续 Filter 的后半部分和 afterCompletion)
│ ↓
│ [7] 调用目标 Controller 方法
│ │ → 参数绑定(@RequestParam, @RequestBody 等)
│ │ → 执行业务逻辑
│ ↓
│ [8] Controller 返回结果
│ │ → ModelAndView(视图) 或 JSON(@RestController)
│ ↓
│ [9] 执行 Interceptor.postHandle()
│ │ → 可修改 ModelAndView(仅对视图有效)
│ │ → **若 Controller 抛异常,此方法不会执行!**
│ ↓
│ [10] 视图渲染(ViewResolver → Thymeleaf/JSP)
│ │ → 或直接写 JSON 到 HttpServletResponse(REST)
│ ↓
│ [11] 执行 Interceptor.afterCompletion()
│ │ → **无论成功或异常,都会执行**
│ │ → 可清理 ThreadLocal、记录日志、关闭资源等
│
└──────────────────────↑
│
[12] 继续执行 Filter.doFilter() 的后半部分(响应返回阶段)
│ ← 所有 Filter 的 doFilter() 方法中 chain.doFilter() 之后的代码
│ ← 例如:记录响应时间、修改响应头、GZIP 压缩等
↓
[13] HttpServletResponse 完全写回客户端
↓
客户端收到 HTML / JSON / 静态文件等响应
/**
* 登录拦截器:拦截需要登录的接口
*/
@Component
public static class LoginInterceptor implements HandlerInterceptor { // 改为 static
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws IOException {
String uri = request.getRequestURI();
log.info("LoginInterceptor 拦截请求: {}", uri);
// 只处理 Controller 请求
if (!(handler instanceof HandlerMethod)) {
return true;
}
// 模拟未登录
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
log.warn("未登录,拒绝访问: {}", uri);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("{\"code\":401, \"msg\":\"请先登录\"}");
return false;
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("LoginInterceptor 拦截请求完成: {}", response.getStatus());
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
@Configuration
public static class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 拦截所有,但排除 /public/**
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/public/**");
}
}
/**
* 自定义 Filter 1:记录所有请求(包括静态资源)
*/
@Component
@Order(1) // 优先级高(数字小)
public static class MyCustomFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
log.info(">>> MyCustomFilter 开始: {}", req.getRequestURI());
String token = req.getHeader("Authorization");
if (token != null){
// 进入程序
chain.doFilter(request, response);
}
else{
// 模拟未登录
log.warn("未登录,拒绝访问: {}", req.getRequestURI());
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.getWriter().write("{\"code\":401, \"msg\":\"请先登录\"}");
}
log.info("<<< MyCustomFilter 结束: {}", req.getRequestURI());
}
}
@Slf4j
@SpringBootApplication
public class MvcTestApplication {
public static void main(String[] args) {
SpringApplication.run(MvcTestApplication.class, args);
}
@Slf4j
@RestController
public static class MyController {
@GetMapping("/test")
public String index1() {
log.info("访问 /test");
return "Hello MyController!";
}
@GetMapping("/public/test")
public String index2() {
log.info("访问 /public/test");
return "Hello Public!";
}
@PostMapping("/test2")
public String index3() {
log.info("访问 /test2 (POST)");
return "Hello POST!";
}
}
/**
* 登录拦截器:拦截需要登录的接口
*/
@Component
public static class LoginInterceptor implements HandlerInterceptor { // 改为 static
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws IOException {
String uri = request.getRequestURI();
log.info("LoginInterceptor 拦截请求: {}", uri);
// 只处理 Controller 请求
if (!(handler instanceof HandlerMethod)) {
return true;
}
// 模拟未登录
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
log.warn("未登录,拒绝访问: {}", uri);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("{\"code\":401, \"msg\":\"请先登录\"}");
return false;
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("LoginInterceptor 拦截请求完成: {}", response.getStatus());
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
@Component
public static class LoginInterceptor2 implements HandlerInterceptor { // 改为 static
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws IOException {
String uri = request.getRequestURI();
log.info("LoginInterceptor2 拦截请求: {}", uri);
// 只处理 Controller 请求
if (!(handler instanceof HandlerMethod)) {
return true;
}
// 模拟未登录
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
log.warn("未登录,拒绝访问: {}", uri);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("{\"code\":401, \"msg\":\"请先登录\"}");
return false;
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("LoginInterceptor2 拦截请求完成: {}", response.getStatus());
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
@Configuration
public static class WebConfig implements WebMvcConfigurer { // 改为 static
@Autowired
private LoginInterceptor loginInterceptor;
@Autowired
private LoginInterceptor2 loginInterceptor2;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 拦截所有,但排除 /public/**
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/public/**");
registry.addInterceptor(loginInterceptor2)
.addPathPatterns("/**")
.excludePathPatterns("/public/**");
}
}
/**
* 自定义 Filter 1:记录所有请求(包括静态资源)
*/
@Component
@Order(1) // 优先级高(数字小)
public static class MyCustomFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
log.info(">>> MyCustomFilter 开始: {}", req.getRequestURI());
String token = req.getHeader("Authorization");
if (token != null){
// 进入程序
chain.doFilter(request, response);
}
else{
// 模拟未登录
log.warn("未登录,拒绝访问: {}", req.getRequestURI());
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.getWriter().write("{\"code\":401, \"msg\":\"请先登录\"}");
}
log.info("<<< MyCustomFilter 结束: {}", req.getRequestURI());
}
}
/**
* 自定义 Filter 2
*/
@Component
@Order(2)
public static class MyCustomFilter2 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
log.info(">>> MyCustomFilter2 开始: {}", req.getRequestURI());
chain.doFilter(request, response);
log.info("<<< MyCustomFilter2 结束: {}", req.getRequestURI());
}
}
}
.github/workflows/deploy.yml,格式如下# .github/workflows/deploy.yml
name: Deploy to GitHub Pages
on:
push:
branches: [ master ] # 当 master 分支有推送时触发
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4 # 👈 关键!官方 pnpm 安装器
with:
version: 10 # 可选:指定版本,不写则用最新
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'pnpm'
- name: Install dependencies
run: pnpm i
- name: Build
run: pnpm run build
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist