最近做项目遇到了一些问题,于是手搓了一个框架。

    在当今的 Java 开发领域,我们似乎总是面临两个极端的选择:要么是维护成本极高、牵一发而动全身的传统“巨型单体”;要么是虽然灵活但运维极其复杂、调用链路漫长的“微服务架构”。

有没有一种架构,既能保持单体应用的开发便捷性,又能拥有微服务的模块化边界?

     今天我们要介绍的开源项目 LiHe ,就是一个基于 Spring Boot 3 + Spring Modulith + React 18 的全栈开发框架。它采用“模块化单体(Modular Monolith)”的设计理念,试图为现代企业级开发提供一个“刚刚好”的解决方案。

    项目地址

    https://gitee.com/blackhold/LiHe

🏛️ 架构哲学:回归理性的“模块化单体”
LiHe 的核心理念在于“平衡”。它试图解决传统单体“代码耦合严重”的痛点,同时避免微服务“运维复杂”的深坑。

什么是 Spring Modulith?
LiHe 引入了 Spring 生态中备受瞩目的 Spring Modulith 项目。这不仅仅是一个依赖库,更是一种架构约束。

- 逻辑分离,物理统一 :在 LiHe 中,用户模块、部门模块、系统监控模块在代码结构上是严格隔离的。Spring Modulith 会在编译期和测试期检查模块间的依赖关系,禁止循环依赖和非法调用。
- 事件驱动架构 :模块之间通过 Spring Event 进行异步交互。例如,当用户创建一个新账号(User 模块),系统需要记录日志(Log 模块)。在 LiHe 中,这不是直接的方法调用,而是抛出一个事件。这种设计使得各个模块高度解耦,未来如果业务量级突破单机瓶颈,你可以轻松地将某个模块剥离出来独立部署。

🎨 前端架构:React 18 与 Arco Design 的美学
前端部分,LiHe 同样拒绝平庸。

1. 字节跳动 Arco Design
LiHe 选用了字节跳动开源的 Arco Design 作为 UI 组件库。相比于大家看腻了的 Ant Design,Arco 的设计语言更加年轻、扁平,配色更加清爽。它提供了极其丰富的组件和开箱即用的“工作台”模板,让中后台系统也能拥有 C 端产品的精致感。

2. React 18 的并发能力
基于 React 18 构建,利用其并发渲染特性(Concurrent Features),在处理大量数据表格渲染或复杂交互时,界面响应更加流畅,极大地提升了用户体验。

📦 开箱即用的企业级功能
LiHe 不是一个空的脚手架,它内置了企业级开发所需的“基础设施”,让你能专注于业务逻辑:

- 组织架构管理 :精细到部门、岗位、用户的树形管理。
- 动态权限控制 :支持菜单级、按钮级的权限控制,甚至支持数据权限(例如:只能看本部门的数据)。
- 全方位监控 :
  - 服务监控 :实时查看 CPU、内存、磁盘使用率。
  - 缓存监控 :Redis 的命中率、内存占用一目了然。
  - 在线用户 :谁在线,谁在操作,尽在掌握。
- 异步日志系统 :基于事件驱动的操作日志和登录日志,记录详尽且不阻塞主线程。

🛠️ 后端技术栈:硬核与前沿的碰撞
LiHe 的后端选型非常激进且富有远见,它直接跳过了过渡版本,拥抱了 Java 生态的最新标准。

1. JDK 21+:不仅仅是语法糖
LiHe 强制要求 JDK 21。这意味着你不仅能使用 var 、 record 等语法糖来精简代码,更能享受到 虚拟线程 (Virtual Threads) 带来的性能红利。在高并发 I/O 密集型场景下(如大量数据库查询、Web 请求),虚拟线程能以极低的资源消耗实现惊人的吞吐量,这让 LiHe 在单体模式下也能拥有强悍的性能表现。

2. Spring Data JPA + QueryDSL:类型安全的优雅
在国内 MyBatis 统治的背景下,LiHe 选择了 JPA + QueryDSL 的组合。

- JPA :负责基础的 CRUD,开发效率极高,一行 SQL 都不用写。
- QueryDSL :解决了 JPA 处理复杂动态查询的痛点。它让你用 Java 代码来构建 SQL,最大的好处是 类型安全 。字段名写错了?编译器直接报错,而不是等到运行时抛出 SQL 异常。这对于长期维护和重构来说,价值千金。
3. Spring Security + JWT:无状态的护城河
采用了标准的 RBAC(基于角色的访问控制)模型,配合 JWT 实现无状态认证。支持多终端认证,无论是 Web 端、小程序还是移动端,都能一套鉴权逻辑搞定。

/**
 * 分页查询 (QueryDSL 版)
 */
public Page<SysOperLogVo> pageSysOperLogByParam(SysOperLogDto queryDto, Pageable pageable) {
        // 1. 获取 Q 类单例 (类似 SQL 中的表别名)
        QSysOperLogEntity q = QSysOperLogEntity.sysOperLogEntity;

        // 2. 构建查询条件
        BooleanBuilder builder = new BooleanBuilder();

            // 自动生成 module 查询条件
            if (queryDto.getModule() != null) {
                if (StringUtils.hasText(queryDto.getModule())) {
                builder.and(q.module.containsIgnoreCase(queryDto.getModule()));
                }
            }
            // 自动生成 businessType 查询条件
            if (queryDto.getBusinessType() != null) {
                if (StringUtils.hasText(queryDto.getBusinessType())) {
                builder.and(q.businessType.containsIgnoreCase(queryDto.getBusinessType()));
                }
            }
            // 自动生成 operator 查询条件
            if (queryDto.getOperator() != null) {
                if (StringUtils.hasText(queryDto.getOperator())) {
                builder.and(q.operator.containsIgnoreCase(queryDto.getOperator()));
                }
            }
            // 自动生成 operatorId 查询条件
            if (queryDto.getOperatorId() != null) {
                if (StringUtils.hasText(queryDto.getOperatorId())) {
                builder.and(q.operatorId.containsIgnoreCase(queryDto.getOperatorId()));
                }
            }
            // 自动生成 operIp 查询条件
            if (queryDto.getOperIp() != null) {
                if (StringUtils.hasText(queryDto.getOperIp())) {
                builder.and(q.operIp.containsIgnoreCase(queryDto.getOperIp()));
                }
            }
            // 自动生成 costTime 查询条件
            if (queryDto.getCostTime() != null) {
                builder.and(q.costTime.eq(queryDto.getCostTime()));
            }
            // 自动生成 operTime 查询条件
            if (queryDto.getOperTime() != null) {
                builder.and(q.operTime.eq(queryDto.getOperTime()));
            }
            // 自动生成 operParam 查询条件
            if (queryDto.getOperParam() != null) {
                if (StringUtils.hasText(queryDto.getOperParam())) {
                builder.and(q.operParam.containsIgnoreCase(queryDto.getOperParam()));
                }
            }
            // 自动生成 jsonResult 查询条件
            if (queryDto.getJsonResult() != null) {
                if (StringUtils.hasText(queryDto.getJsonResult())) {
                builder.and(q.jsonResult.containsIgnoreCase(queryDto.getJsonResult()));
                }
            }
            // 自动生成 status 查询条件
            if (queryDto.getStatus() != null) {
                builder.and(q.status.eq(queryDto.getStatus()));
            }
            // 自动生成 errorMsg 查询条件
            if (queryDto.getErrorMsg() != null) {
                if (StringUtils.hasText(queryDto.getErrorMsg())) {
                builder.and(q.errorMsg.containsIgnoreCase(queryDto.getErrorMsg()));
                }
            }

        // 3. 执行查询 (Repository 原生支持 QueryDSL)
        return sysOperLogRepository.findAll(builder, pageable).map(this::toSysOperLogVo);
}

Entity 简单配置 

@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "sys_menu")
public class SysMenuEntity extends BaseEntity {

    @Comment("菜单名称")
    private String menuName;

    @Comment("父菜单ID")
    private String parentId; // 根节点通常为 "0"

    @Comment("显示顺序")
    private Integer orderNum;

    @Comment("路由地址")
    private String path;

    @Comment("组件路径 (vue组件路径)")
    private String component;

    @Comment("是否为外链 (1是 0否)")
    private Integer isFrame;

    @Comment("菜单类型 (M目录 C菜单 F按钮)")
    private String menuType;

    @Comment("菜单状态 (1显示 0隐藏)")
    private String visible;

    @Comment("权限标识 (e.g. system:user:list)")
    private String perms;

    @Comment("菜单图标")
    private String icon;

    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    @ManyToMany(mappedBy = "menus")
    private Set<SysRoleEntity> roles = new HashSet<>();

}

📦数据库

数据库使用的是 PostgreSQL 作为一款强大的开源关系型数据库,具有以下显著优点:

开源免费 - BSD协议,无商业限制

功能全面
• 最标准SQL支持
• JSON文档存储 + 关系型双模式
• 高级查询(CTE、窗口函数等)

性能强大
• MVCC高并发
• 多种索引优化
• 并行查询

可靠稳定
• 完整ACID事务
• 数据完整性保障
• 流复制高可用

高度可扩展
• 自定义函数/类型
• 丰富扩展生态(如PostGIS)
• 多语言支持

适用: 需要高可靠性、复杂查询、混合数据模型的企业应用

📦 开箱即用的企业级功能
LiHe 不是一个空的脚手架,它内置了企业级开发所需的“基础设施”,让你能专注于业务逻辑:

- 组织架构管理 :精细到部门、岗位、用户的树形管理。
- 动态权限控制 :支持菜单级、按钮级的权限控制,甚至支持数据权限(例如:只能看本部门的数据)。
- 全方位监控 :
  - 服务监控 :实时查看 CPU、内存、磁盘使用率。
  - 缓存监控 :Redis 的命中率、内存占用一目了然。
  - 在线用户 :谁在线,谁在操作,尽在掌握。
- 异步日志系统 :基于事件驱动的操作日志和登录日志,记录详尽且不阻塞主线程。

⚙️ 配置与启动:极简主义
LiHe 的落地门槛极低,只需几步即可从零启动。

server:
  port: 8080

spring:
  application:
    name: lihe-gateway
  config:
    import: "optional:nacos:lihe-gateway.yml"
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yaml
    gateway:
      discovery:
        locator:
          enabled: true # 使用配置来选择是否开启微服务
          lower-case-service-id: true
      routes:
        - id: lihe-server
          uri: lb://lihe-server
          predicates:
            - Path=/api/**
          filters:
            - StripPrefix=1 # 去掉 /api 前缀,转发给后端

Redis和Kafka 随意切换 还加入了 本地文件上传与Minio 的配置切换

lihe:
  microservice-enabled: false # 默认为单体模式
  service-name: lihe-server
  captcha:
    enabled: true  # 验证码开关
    type: math     # math(算术) 或 line(线段干扰)
    expiration: 120 # 过期时间(秒)
  mq:
    type: redis # 可选值: redis, kafka
  file:
    type: local # 默认本地存储
    local:
      # 默认存储在用户主目录下的 lihe/upload
      # path: ${user.home}/lihe/upload
      # 访问域名前缀
      domain: http://localhost:8099/api/profile
    # MinIO 配置示例 (需要时取消注释并修改 type: minio)
    # type: minio
    # minio:
    #   endpoint: http://localhost:9000
    #   access-key: minioadmin
    #   secret-key: minioadmin
    #   bucket-name: lihe

📝 总结与选型建议
LiHe 是一个非常务实的框架。它没有过度设计的复杂性,却拥有应对未来的扩展性。

它适合谁?

- 初创团队/中小企业 :业务处于快速验证期,需要极高的开发效率,且运维资源有限。
- 外包/私活开发者 :需要一个功能完备、界面美观的底座,能快速交付高质量的交付物。
- 技术探索者 :想学习 Spring Boot 3、JDK 21 新特性以及 React 18 最佳实践的开发者。
它不适合谁?

- 超大规模互联网应用 :如果你的系统日活千万,且团队有几百人维护,那么微服务架构可能更适合你。
- 极度依赖 MyBatis 生态 :如果你对 JPA 有强烈的抵触情绪,或者历史遗留了大量复杂的 MyBatis XML,迁移成本可能较高。
总的来说,LiHe 在“巨型单体”和“微服务”之间找到了一块 黄金平衡点 。如果你正在寻找一个既能快速起步,又不会在未来成为技术负债的框架,LiHe 值得你投入时间去尝试。

更多推荐