Skip to main content
lianghchao
Java开发,服务器搭建,React
View all authors

鸡汤

· 4 min read
lianghchao
Java开发,服务器搭建,React

第一部分 小建议

建议效果
运动可以解决 80% 的疾病
读书可以决定 60% 的命运
冷漠可以省去 80% 的麻烦
有钱可以解决 95% 的问题
行动可以实现 80% 的目标
早睡可以养足 90% 的元气
复盘可以规避 70% 的弯路
自律可以掌控 90% 的节奏
闭嘴可以降低 70% 的纷争
拒绝可以减少 90% 的内耗
练习可以获得 80% 的能力

第二部分 普通人人生最重要的三件事

一、金融资本

就是你银行卡里,那笔能让你在不做任何事的情况下,也能体面地活一年的钱——这是你所有安全感的基石。

二、认知资本

就是你那套能让你看清世界真相,并做出正确决策的思维模型。

三、健康资本

就是你那副能支撑你打完这场硬仗的身体和精神。


人生前半场:像苦行僧一样修行

这是你人生前半场最重要,也最残酷的阶段。你需要像一个苦行僧一样,去完成三件事:

第一,把自己当成苦行僧。

你必须过一种极度专注,甚至有点无趣的生活。你要拒绝所有消耗你时间和金钱的无效社交和廉价娱乐。你要把别人刷短视频、喝大酒的时间,都用来投资到你的三大资本上去。

这个过程很苦,但这是你作为一个普通人唯一的出人头地的路。

第二,把工作当成你的练级场。

千万不要对你的第一份,甚至第二份工作抱有任何情感上的幻想。它不是你的家,它只是你免费的(或者说付费)的练级场。

你要像一块海绵一样,疯狂地从你现在的工作中吸收养分:

  • 去学技能
  • 去链接资源
  • 去看高手是怎么做的

工资只是你在这家公司练级,他们付给你的学费。

第三,把薪水看作你的第一桶金。

每个月,强制性地把你收入的一半,甚至更多存下来。这笔钱不是用来消费的,它是你未来所有事业的启动资金,一分一毫都不能乱动。


人生下半场:搭建个人商业模式

当你通过几年的苦修,完成了原始资本的积累后,你就要立刻启动你人生的下半场:搭建你的个人商业模式。

第一步:找到你的生态位

你不需要跟所有人竞争,你只需要找到一个非常细分的、你比 90% 的人都做得好一点点的领域。

这个生态位,最好是你热爱、擅长和市场需求的交集。

第二步:打造你的产品

把你在这个生态位上的能力产品化,可以是:

  • 一篇文章
  • 一节课
  • 一次咨询
  • 一个能解决小问题的工具

你必须有一个可以清晰地向市场报价的价值单元。

第三步:建立你的流量渠道

你要开始在一个平台(比如小红书或者视频号)持续地输出你的价值,让那些需要你这个产品的人能找到你。

一个健康的个人商业模式,就是你找到了一个你能填补的市场缺口,然后用你的内容建了一座桥,连接了你和你的客户。


写在最后

这条路,很难,也很孤独。没有攻略,没有捷径,甚至连为你鼓掌的人都很少。

但这恰恰是它的迷人之处,因为你走的每一步,都是在为你自己的人生绘制地图;你吃的每一份苦,都是在为你未来的家族打下地基。


第三部分 不要害怕

百分百死亡率的人生,你还在犹豫什么?犹豫怎么死吗?

上班最可怕的地方是:

它居然让我因为盼着退休而期待衰老,让我完全不珍惜我剩余人生里最年轻的每一天。总想着周一到周五赶紧过去,可是一周又一周,突然反应过来——

这流逝掉的是我的生命。


所以,想做什么就去做吧。毕竟,我们唯一拥有的就是现在。

防御性编程

· 9 min read
lianghchao
Java开发,服务器搭建,React

Java防御性编程:构建健壮系统的基石

防御性编程是一种编程哲学,强调编写能够预见并处理各种异常情况的代码。它不是为了增加复杂度让开发者不可替代,而是为了让系统更加健壮、安全和易于维护。

1. 防御性编程的核心原则

1.1 输入验证

永远不要信任外部输入,包括用户输入、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,}$");
}
}

1.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();
}
}
}

1.3 空值检查

预防空指针异常,使用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);
}
}

2. 实际应用场景

2.1 资源管理

确保资源得到正确释放,避免内存泄漏。

@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);
}
}
}

2.2 并发安全

在多线程环境下保证数据一致性。

@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);
}
}

2.3 数据库操作安全

防止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("_", "\\_");
}
}

3. Spring Boot中的防御性编程

3.1 全局异常处理器

统一处理应用中的异常。

@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", "系统繁忙,请稍后重试");
}
}

3.2 参数校验

使用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...
}

3.3 限流和熔断

保护系统免受过载影响。

@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);
}
}

4. 最佳实践总结

4.1 编码习惯

  • 始终验证输入参数
  • 合理使用异常处理
  • 采用防御性的集合操作
  • 使用不可变对象和线程安全的数据结构

4.2 测试保障

  • 编写边界条件测试
  • 模拟异常场景测试
  • 进行压力测试验证系统稳定性

4.3 监控和日志

  • 记录关键操作的日志
  • 设置适当的监控指标
  • 实现告警机制

防御性编程的目标是构建健壮、可靠的系统,而不是制造复杂度。通过合理的防御措施,我们可以提高代码质量,降低系统故障率,提升用户体验。

真正的专业开发者应该致力于编写清晰、简洁且健壮的代码,使系统易于理解和维护,这样才能真正成为团队中不可或缺的人才。

一、 破坏类型系统:让 IDE 失去作用

Java 的强类型本意是安全,但你可以将其变成迷宫。

  • 泛型擦除术:所有的接口返回全部设为 Object 或 Map<String, Object>
  • 反射依赖:永远不要直接调用方法。编写一个全局工具类,通过字符串名称和反射来调用方法。
  • 效果:同事搜索方法引用(Find Usages)时结果为 0,删代码时像排雷。
  • 动态代理层层包裹:为一个简单的 Bean 实现 3 层以上的动态代理,让 Debug 时的堆栈跟踪(Stack Trace)长达 50 行。

二、 逻辑原子化:将简单问题复杂化

  • 碎裂式重构:将一个 10 行的逻辑拆分成 10 个类,每个类只实现一个接口,接口里只有一个方法。
  • 效果:看逻辑需要横跨 10 个文件,在大脑里建立复杂的拓扑图,正常人 5 分钟内就会放弃。
  • 异常驱动开发:不要用 if-else 做逻辑判断,全部通过抛出和捕获自定义异常来控制流程。 例子:找不到用户不返回 null,而是抛出 UserNotFoundException,在外部 catch 块里写业务逻辑。

三、 状态隐秘化:消除确定性

  • ThreadLocal 滥用:将关键业务参数存入 ThreadLocal。
  • 效果:代码看起来干净整洁,但运行逻辑取决于前一个请求留在线程里的残余数据。这是调试者的终极噩梦。
  • 隐式副作用:在 Getter 方法里写修改逻辑(比如 getName() 的时候顺便更新一下数据库状态)。

四、 注释与文档的“降维打击”

  • 文学创作法:注释里不写技术实现,写诗、写心路历程、或者引用《金刚经》。
  • 诚实的谎言:写一段极其详尽的注释,描述一个三年前就已经被重构掉的算法。
  • 中文 Unicode 化:在某些关键的报错信息里,使用看起来像中文但其实是 Unicode 编码的字符。

五、 Java 语法糖的极端挖掘

双括号初始化 + 匿名内部类:

List<String> list = new ArrayList<String>() {{
add(new String(new char[]{'h','e','l','l','o'}));
}};

命名艺术(语义模糊化)

  • 拼音缩写+数字:使用 gn1, gn2(功能1、2)或者 sj(数据/时间/随机?让别人猜去吧)。
  • 迷惑性命名:明明是个 List,变量名叫 myString;明明是删除操作,函数名叫 updateInventory。
  • 利用相似字符:在大规模循环里混用 l (小写L)、1 (数字1) 和 I (大写i)。

逻辑黑洞(增加心理负担)

  • 多重否定:if (!(!isNotReady || !isError)),写这种代码能让人的大脑瞬间短路。
  • 嵌套地狱:保持 5 层以上的 if-else 或 for 循环嵌套。
  • 拒绝异常处理:所有的异常都用 catch(Exception e) 默默吞掉,让系统在死掉时保持优雅的沉默,无从排查。

Java 特性滥用(炫技式毁灭)

  • 万物皆 Object:拒绝泛型,全部用 Map<Object, Object> 传参,取值全靠强转,谁接手谁崩溃。
  • 反射黑魔法:能直接调用的方法非要用反射去调。甚至可以通过反射在运行时修改常量池里的 Integer 值(比如让 1 等于 2)。
  • Stream 流套娃:写一个 50 行的 Stream 流,中间混入各种复杂的 filter、flatMap 和带副作用的 peek,不写注释。

环境与配置陷阱

  • 硬编码 (Hardcoding):把数据库密码、API 密钥和环境判断逻辑直接写在类文件最深处的私有方法里。
  • 隐式依赖:代码的行为取决于某个特定的系统环境变量,或者必须在特定的操作系统目录下才能运行。

心理战:注释的最高境界

  • 过时的注释:代码改了,注释留着旧逻辑。

  • 废话注释:i++; // i加一。

  • 嘲讽式注释:// 这里有个坑,但我现在没时间填,祝你好运。

  • 请谨慎使用此类代码。

这种写法会产生额外的 .class 文件,增加内存泄漏风险,且极难阅读。 利用枚举实现单例并挂载业务逻辑:将复杂的业务算法写在 Enum 的构造函数里。

⚠️ 风险提示(非常重要)

  • 极致防御性编程是一把双刃剑:
  • 自缚手脚:Bug 发生时,由于逻辑过于扭曲,你自己排查的速度也会成倍增加。
  • 简历污点:如果因为代码质量太差被开除,你的代码风格将成为你在行业内臭名昭著的证据。
  • 凌晨 3 点的电话:因为只有你能修,所以你永远无法真正下班。
  • 真正的“大神级”防裁秘籍其实是:
  • 写出极高内聚、极低耦合、极致优雅的代码,并掌握核心业务链路的领域知识。当整个部门只有你能在 10 分钟内准确定位并修复生产事故时,你就成了真正的“不动产”。

架构级项目

· 5 min read
lianghchao
Java开发,服务器搭建,React

什么是架构级项目?

简单理解: 架构级项目 ≠ 功能多的项目 而是指:从第一天就考虑长远发展,代码结构清晰,容易维护和扩展的项目

核心特点:

  • 🎯 可扩展:加功能不改老代码
  • 🔧 易维护:新人能快速上手
  • 高性能:能应对流量增长
  • 🛡️ 高可靠:出问题能快速定位
  • 🔒 安全性:有完善的防护机制

📊 对比示例:普通项目 vs 架构级项目

场景普通项目(学生/初级开发)架构级项目(企业级)
用户登录Controller直接查数据库,密码明文存储分层架构:Controller → Service → AuthProvider
密码BCrypt加密 + 登录失败限流 + 支持OAuth2扩展
新增功能在原有类里疯狂加if-else通过策略模式/插件机制
不改核心代码就能扩展新功能
系统崩溃日志全是System.out.println
出问题找不到原因
全链路TraceID + 结构化日志
监控告警 + 自动定位问题
流量突增直接宕机,服务不可用熔断降级 + 缓存机制
异步处理 + 负载均衡
团队协作所有人改同一个包
代码冲突频繁
模块解耦 + 接口契约
并行开发互不影响

🔍 架构级项目的4大核心特征

1. 分层清晰(Layered Architecture)

就像盖房子一样,每层都有明确职责:

表现层 (Controller)     ← 接收用户请求

应用层 (Service) ← 处理业务流程

领域层 (Domain) ← 核心业务逻辑

基础设施层 (Infra) ← 数据库、缓存等技术实现

好处: 高内聚低耦合,业务逻辑不依赖具体技术框架

2. 关注非功能性需求

需求架构体现简单理解
可扩展性策略模式、插件机制加功能不改老代码
可靠性重试机制、熔断器出错能自动恢复
可观测性链路追踪、监控告警出问题能快速发现
安全性权限控制、数据脱敏保护系统和数据安全
可部署性Docker + CI/CD一键部署,自动发布

3. 使用成熟架构模式

常见的架构模式:

  • DDD(领域驱动设计):把复杂的业务逻辑组织得清清楚楚
  • CQRS:读写分离,查询和修改用不同的方式处理
  • 事件驱动:用消息队列解耦系统组件
  • 六边形架构:核心业务与外部技术解耦

4. 工程化能力完备

  • 自动化测试:单元测试、集成测试、端到端测试
  • 代码质量:SonarQube代码检查、代码规范
  • 配置管理:Nacos/Apollo统一配置中心
  • API网关:Spring Cloud Gateway统一入口
  • 分布式事务:Seata/Saga保证数据一致性

🌰 实战案例:订单创建功能

❌ 普通写法(面条代码)

@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("下单成功"); // 异步处理
}

优势:

  • ✅ 业务逻辑集中在领域层
  • ✅ 事务安全,自动回滚
  • ✅ 扩展点清晰(加积分?监听同一事件)
  • ✅ 可测试(Mock Repository即可)
  • ✅ 解耦合(发短信不影响下单主流程)

💡 如何识别架构级项目?

看开源项目时,重点检查这些特征:

代码结构清晰

  • 有domain包?→ 有DDD思想
  • 有config/aspect/exception包?→ 有全局处理
  • Repository用接口定义?→ 解耦良好

工程化完善

  • 有docker-compose.yml?→ 支持容器化部署
  • 有.github/workflows?→ 有CI/CD流水线
  • README有架构图?→ 设计文档完善

技术选型成熟

  • 用Spring Boot + Spring Cloud?
  • 有统一配置中心?
  • 有链路追踪和监控?

📚 学习路径推荐

🎯 入门阶段

  1. 先理解概念

    • 《企业应用架构模式》- Martin Fowler
    • 《Head First设计模式》- 简单易懂
  2. 看优秀源码

    • Spring Boot源码(分层结构清晰)
    • MyBatis-Plus源码(插件机制)

🚀 进阶阶段

  1. 实践DDD

    • 用Clean Architecture重构一个小项目
    • 从简单电商系统开始练习
  2. 学习微服务

    • Spring Cloud Alibaba全家桶
    • Eventuate Tram(分布式事务)
    • Moduliths(模块化单体)

🏆 高级阶段

  1. 架构设计

    • C4模型画架构图
    • 高并发系统设计
    • 分布式系统设计
  2. 团队协作

    • 架构评审流程
    • 技术方案设计
    • 代码规范制定

🎯 总结

一句话理解:

架构级项目 = 好的代码结构 + 完善的工程化 + 成熟的设计思想

核心价值:

  • 📈 业务增长:系统能支撑业务快速发展
  • 👥 团队扩张:新人能快速上手,协作效率高
  • 🔧 技术演进:能平滑升级技术栈
  • 💰 降本增效:减少维护成本,提高开发效率

记住: 代码是死的,架构思想是活的。好的架构能让复杂系统变得简单可控!

养生

· 2 min read
lianghchao
Java开发,服务器搭建,React

食疗养生

未验证

1、虾皮,它是补钙补肾王

第一步去盐去砂,留下咸味入肾的性;
第二步滤干水分,晾干;
第三步,用砂锅或不锈钢,不用铁锅,一定要炒,把寒邪气逼出,通过火的炮制把它的阳气逼出来;
第四步,观色闻香,待有点烤面包的颜色,香味散发满厨房程度,手一捻就成粉末即好;
第五步,研末封存,装在密封罐内,这就是还魂补肾粉,每天早上舀一勺拌在粥或蛋羹里吃。

2、腐竹白果汤

腐竹要买颜色豆黄的,不要买漂白过的毒腐竹,放7~10粒去毒芯的白果杏仁,加几块猪瘦肉或排骨,不要放有毒的淋巴肉,炖熟即可,这个汤补肺健脾,生精健身,清血管。

3、醋泡黑豆

黑皮绿芯的黑豆
洗净炒制泡粮食醋49天
晚饭后吃10~15粒。

4、鹌鹑蛋补益五脏,少胆固醇,补脑强记忆力,手不抖。

鹌鹑补脑方,3~5个打散,配伍枸杞子一小把,滋补肝肾,黑芝麻粉补肾黑发,蒸成蛋羹,出锅前加点香油即可。

认知带宽理论

· 2 min read
lianghchao
Java开发,服务器搭建,React

贫穷/忙碌的本质是“带宽被占用”。

  • 琐事(如账单、日程协调、家务安排)占据大脑 RAM,导致:
  • 智商暂时下降(实验显示贫困者 IQ 测试得分低 13 分)
  • 更难规划长期目标(如储蓄、学习)

如何减少琐事对决策力的侵蚀?

  1. 建立惯例(Routines) 例子:扎克伯格/乔布斯穿同款衣服 → 消除“穿什么”的决策。 行动建议: 固定起床/睡觉时间 周一到周五午餐吃固定几样 每天上午专注深度工作(不处理邮件)
  2. 批量处理琐事 把同类小决策集中处理(如每周日晚上规划下周菜单、统一回复非紧急消息)。
  3. 设置默认选项 自动缴费、订阅常用日用品、用模板回复邮件 → 减少“是否做”的判断。
  4. 保护高价值时段 把需要创造力/战略思考的工作放在决策精力最充沛时(通常是早晨),避免被琐事打断。
  5. 学会说“不” 拒绝无关会议、社交邀约、信息流干扰 → 本质是保护决策带宽。

💡 一句话总结

  • 你每天的“高质量决策次数”是有限的。把它们留给真正重要的事,而不是“选哪个滤镜”或“回哪个群消息”。

正如《原子习惯》作者 James Clear 所说:

  • “You do not rise to the level of your goals. You fall to the level of your systems.”

  • (你不会达到目标的高度,你会跌到系统所允许的水平。)

  • 而减少琐事干扰,就是优化你的认知系统。

灵光一闪

· One min read
lianghchao
Java开发,服务器搭建,React

记录一下偶尔的灵光一闪

惰性思想

放弃对于现实的进行一个艰难的思考,转而拥抱一个现成的简单粗暴的解释框架,并对此深信不疑。

滤镜幻觉

救世幻觉

房子结构有问题,直接推到重建。
鲁迅:“从来如此,便对么?”——对宏大叙事保持怀疑。

泰勒的科学管理法

泰勒的科学管理 = 用科学方法代替经验,用标准化动作+差别激励+计划执行分离,实现效率最大化。

宿命幻觉

把一切归咎于命运,实则是逃避选择与责任的自我麻痹。

过滤器&拦截器

· 5 min read
lianghchao
Java开发,服务器搭建,React

sping boot中的过滤器&拦截器

mvc交互流程


客户端(浏览器)

[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 Workflow

· One min read
lianghchao
Java开发,服务器搭建,React

GitHub Pages 静态网站托管服务

  • github自动化构建工作流
  • 在项目根目录下创建文件.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


内网穿透

· 3 min read
lianghchao
Java开发,服务器搭建,React

在查看服务器查看8001端口映射

netstat -anp|grep 8001

编辑配置文件内容

vi /usr/etc/ssh/sshd_config
Getwayports yes

重启sshd

systemctl restart sshd

ssh

  • 通过SSH将本地端口(192.168.1.5:8085)映射到服务器(@serverIP)所有IP端口(0.0.0.0:8001)
ssh -R 0.0.0.0:8001:192.168.1.5:8085 root@serverIP
ssh -R 0.0.0.0:8001:192.168.1.5:8085 root@serverIP
ssh -R 0.0.0.0:8005:192.168.1.7:8003 root@serverIP
ssh -p 8022 -R 0.0.0.0:8001:127.0.0.1:8001 root@serverIP

ssh -R 0.0.0.0:7022:0.0.0.0:7022 root@serverIP

echo 1 >/proc/sys/net/ipv4/ip_forward

iptables -t nat -A PREROUTING -d 【公网地址】 -p tcp -m tcp --dport 【公网端口】 -j DNAT --to-destination 【内网IP】:【内网端口】

iptables -t nat -A PREROUTING -d serverIP -p tcp -m tcp --dport 3306 -j DNAT --to-destination 192.168.1.141:3306

文件服务器映射

ssh -R 0.0.0.0:21:192.168.1.3:21 root@serverIP

frp服务器配置

bindAddr = "0.0.0.0"
bindPort = 7000
# HTTP 端口(用于内网 HTTP 代理穿透)
vhostHTTPPort = 80
# HTTPS 端口(用于内网 HTTPS 代理穿透)
vhostHTTPSPort = 443

# 监控界面监听地址(`0.0.0.0` 代表所有 IP 可访问)
webServer.addr = "0.0.0.0"
# Web 管理面板端口(可在浏览器访问,默认 7500)
# 你可以通过 `http://你的公网IP:7500` 访问 FRP 管理面板
webServer.port = 7500
# Web 控制台管理账号(可自定义)
webServer.user = "admin"
# Web 控制台密码(请自行修改)
webServer.password = "admin"
# =============================================
# 身份验证(Authentication)配置
# =============================================
# 认证方式(防止未经授权的客户端连接)
# 目前 FRP 支持 `token` 和 `oidc` 方式,我们选用token
auth.method = "token"

# Token 认证(客户端需要匹配相同 token 才能连接)
# 通俗来说就是密码,写一个你能记住的,尽量长一点
# 示例: 123-abc-123abc
auth.token = "frptoken" # 请自行修改,不要用我的

frp客户端配置

# 服务端地址(这里要填你有公网IP的服务器的IP或者是服务器的域名)
#serverAddr = "152.32.175.238"
serverAddr = "serverIp"
# 服务器端口(Frp 服务端监听的端口)
serverPort = 7000

# 连接协议
transport.protocol = "tcp"

# 认证方式
auth.method = "token"
# 认证所使用的 Token(要和你刚才配置的服务端token完全一样!)
auth.token = "frptoken"

# 代理配置
[[proxies]]
# 代理名称(标识该代理的名称,根据你的喜好填写)
name = "rocketcat"
# 代理类型(http、https、tcp等)
# 这里要根据你的需求来填写,如果你有域名,就用http
# 如果你没有域名,那就用IP直连,例如:165.0.0.1:8848,此时这里应该写tcp协议
# 如果你用tcp协议就必须把刚才服务端上subDomainHost = "xxxx.com"的配置删除!
# type = "tcp" # IP+端口直连用这个
type = "tcp"
# 本地 IP(Frp 客户端需要将流量转发到的本地地址)
localIP = "127.0.0.1"
# 本地端口(Frp 客户端需要将流量转发到的本地端口,根据你要穿透的端口来填写)
localPort = 8001
# 访问此代理的子域名
# 如果你没有域名要用IP直连,请把这句删除掉,否则会导致无法连接!
# subdomain = "rocket" # 子域名请根据你拥有的域名配置,配置后通过rocket.xxx.com格式访问

# 如果你不用域名,要用ip+端口直连,请必须加上这句!
# 并且删除 subdomain = "rocket"
remotePort = 8001 # 这个端口和localPort 配置的一模一样,这样才能正常访问!