SpringBoot+MybatisPlus多模块系统构建实战
1. 项目概述为什么多模块不是“炫技”而是真实业务演进的必然选择“springbootMybatisPlus多模块系统构建”这个标题里藏着一个被很多新手忽略的关键信号——它没说“单体应用”“快速上手”或“入门教程”而是直指“构建”二字。这意味着这不是在教你怎么跑通一个Hello World而是在模拟一个真实团队接手一个中等规模业务系统时从零开始做架构决策、划边界、定规范、防耦合的全过程。“蓝鲸云”这个代称我理解为某类面向企业客户的PaaS/SaaS型平台服务其典型特征是功能模块高度垂直如租户管理、资源编排、计费中心、告警引擎、配置中心各模块数据模型独立但存在强协同关系比如计费中心要读取资源编排模块的实例生命周期状态同时要求不同模块可独立部署、灰度发布、按需扩缩容。这种场景下硬塞进一个单模块Spring Boot工程不出三个月就会出现“改一个接口测半个项目”“发版要拉通五个组开会”“数据库表不敢加字段怕影响其他模块”的典型症状。我带过的几个项目组最早都是从单模块起步当核心功能超过12个业务域、开发人员稳定在8人以上、日均API调用量突破50万次时模块拆分就成了不可回避的技术债清算节点。MybatisPlus在这里不是随便选的ORM框架而是因为它在多模块场景下提供了三重不可替代性一是TableName和TableId等注解天然支持跨模块实体复用比如“用户”实体在认证模块定义在计费模块只需引入依赖即可直接查询二是IService/ServiceImpl分层封装让各模块能统一使用lambdaQuery()风格避免SQL拼接污染业务逻辑三是MetaObjectHandler全局填充机制可确保“创建人”“更新时间”等审计字段在所有模块中行为一致不用每个模块重复写拦截器。所以这个标题的本质是一套面向中大型企业级系统的、可落地的模块化演进方法论而不是一个技术名词堆砌的Demo。2. 整体架构设计与模块划分逻辑从“怎么拆”到“为什么这样拆”2.1 模块划分的四大铁律边界清晰、职责内聚、依赖单向、演进可控很多团队一上来就照着Maven官方文档建common、api、service、web四个模块结果半年后发现common变成了“什么都能塞”的垃圾场api模块里混着DTO、VO、甚至前端JS代码。真正的模块划分必须回归业务语义而不是技术分层。我们以“蓝鲸云”平台为例结合实际交付经验提炼出模块划分的四条硬性约束边界清晰每个模块必须对应一个明确的业务能力域且该能力域的完整生命周期增删改查事件响应应尽可能封闭在本模块内。例如“租户管理”模块负责租户注册、状态变更、配额设置但不处理租户下的具体资源实例——那是“资源编排”模块的事。实操中我们用“限界上下文Bounded Context”来验证如果两个概念在不同模块中叫同一个名字比如都叫Tenant但属性、行为、生命周期完全不同那它们就该属于不同模块哪怕数据库表结构看起来一样。职责内聚一个模块只做一件事并做到极致。比如“告警引擎”模块只负责告警规则解析、触发条件计算、通知渠道分发绝不碰告警数据的存储细节——那是“监控数据服务”模块的职责。我们曾遇到一个反例某团队把告警规则校验逻辑写在Web层Controller里导致每次新增通知渠道邮件/钉钉/短信都要改Controller后来重构时花了两周才剥离干净。依赖单向这是多模块系统最易踩坑的点。我们强制规定web模块可以依赖serviceservice可以依赖api和domain但domain模块绝对不能依赖任何其他模块包括common。为什么因为domain是业务核心它的稳定性决定了整个系统的基线。一旦domain里引入了某个HTTP客户端工具类那所有依赖它的模块都得跟着升级这个工具——这违背了“高内聚低耦合”的初衷。我们用Maven Enforcer Plugin的banDuplicateClasses和requireUpperBoundDeps规则在CI阶段就卡死循环依赖和版本冲突。演进可控模块必须支持独立编译、测试、打包、部署。这意味着每个模块的pom.xml里不能有scopeprovided/scope的“假依赖”——比如service模块声明依赖web模块的spring-boot-starter-web却标为provided以为运行时由父工程提供。实测发现这种写法会导致单元测试时WebMvcTest无法加载因为测试类路径里根本没有Web相关Bean。正确做法是service模块只依赖spring-boot-starter基础包Web层能力由web模块单独引入。2.2 “蓝鲸云”平台的五模块落地结构从抽象原则到具体目录基于上述铁律我们为“蓝鲸云”设计了以下五模块结构每个模块都经过至少三个迭代周期的生产环境验证模块名称Maven坐标核心职责关键技术点典型文件结构bluewhale-parentcom.bluewhale:bluewhale-parent:1.0.0统一父POM管理所有子模块的Spring Boot版本、MybatisPlus版本、Lombok版本、代码质量插件SonarQube、CheckstyledependencyManagement集中声明版本pluginManagement统一插件配置pom.xml无src目录bluewhale-domaincom.bluewhale:bluewhale-domain:1.0.0定义所有业务领域模型Entity、值对象VO、领域事件Event不包含任何框架注解使用DataLombok生成getter/setterBuilder构造复杂对象NoArgsConstructor适配MybatisPlus反射/entity/Tenant.java,/vo/ResourceUsageVO.java,/event/TenantCreatedEvent.javabluewhale-apicom.bluewhale:bluewhale-api:1.0.0定义对外暴露的API契约DTO数据传输对象、Feign Client接口、统一返回体ResultTApiModel/ApiModelPropertySwagger注解FeignClient(name tenant-service)声明远程调用/dto/TenantCreateDTO.java,/client/TenantClient.java,/response/Result.javabluewhale-servicecom.bluewhale:bluewhale-service:1.0.0实现核心业务逻辑调用MybatisPlus Mapper完成数据操作处理事务边界发布领域事件Service标注实现类Transactional(rollbackFor Exception.class)控制事务ApplicationEventPublisher发布事件/service/impl/TenantServiceImpl.java,/mapper/TenantMapper.java,/listener/TenantCreatedListener.javabluewhale-webcom.bluewhale:bluewhale-web:1.0.0提供RESTful API入口处理HTTP协议细节参数校验、异常翻译、跨域配置集成Actuator监控端点RestControllerValid结合BindingResult做DTO校验ControllerAdvice统一异常处理/controller/TenantController.java,/config/WebMvcConfig.java,/endpoint/CustomHealthIndicator.java这个结构的关键在于domain模块是纯Java Bean不依赖任何Springapi模块只依赖domain和spring-cloud-openfeign用于声明式调用service模块依赖domain、api、mybatis-plus-boot-starterweb模块依赖service和spring-boot-starter-web。整个依赖链是单向的web → service → api → domain没有回环。我们曾用JDepend工具扫描过所有模块的afferent coupling入度和efferent coupling出度比值都大于3说明模块内聚性良好。2.3 MybatisPlus在多模块中的特殊配置解决跨模块Mapper扫描难题单模块项目里MapperScan(com.bluewhale.mapper)一行搞定Mapper接口扫描。但多模块下bluewhale-service模块的TenantMapper.java在com.bluewhale.service.mapper包下而bluewhale-web模块启动时Spring Boot默认只扫描自己模块下的Mapper。如果不处理启动会报NoSuchBeanDefinitionException: No qualifying bean of type com.bluewhale.service.mapper.TenantMapper。解决方案有三种我们逐一实测并给出选型理由方案一在web模块的启动类上加MapperScan指定service模块路径SpringBootApplication MapperScan(basePackages com.bluewhale.service.mapper) public class BluewhaleWebApplication { public static void main(String[] args) { SpringApplication.run(BluewhaleWebApplication.class, args); } }问题这会让web模块“越权”感知service模块的内部包结构违反模块隔离原则。一旦service模块重构包名web模块必须同步修改耦合度飙升。方案二在service模块中定义MapperScannerConfigurerBeanConfiguration public class MybatisPlusConfig { Bean public MapperScannerConfigurer mapperScannerConfigurer() { MapperScannerConfigurer configurer new MapperScannerConfigurer(); configurer.setBasePackage(com.bluewhale.service.mapper); configurer.setSqlSessionFactoryBeanName(sqlSessionFactory); return configurer; } }问题MapperScannerConfigurer是Mybatis原生类与MybatisPlus的MybatisSqlSessionFactoryBean存在兼容性风险。我们在Spring Boot 2.7.x MybatisPlus 3.5.3组合下实测启动时报Invalid bound statement (not found)原因是MapperScannerConfigurer扫描到的Mapper未被MybatisPlus的GlobalConfig增强。方案三推荐利用MybatisPlus的MapperScan与Maven依赖传递性在bluewhale-service模块的pom.xml中将mybatis-plus-boot-starter设为scopecompile/scope默认值并在service模块的application.yml中配置mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml type-aliases-package: com.bluewhale.service.entity同时在bluewhale-service模块的启动配置类中用MapperScan扫描自身MapperConfiguration MapperScan(basePackages com.bluewhale.service.mapper, sqlSessionFactoryRef sqlSessionFactory) public class MybatisPlusConfig { // 无需额外Bean定义MybatisPlus自动装配 }原理web模块通过dependency引入service模块Maven会将service模块的classes和resources加入web模块的类路径。只要service模块的Mapper接口和XML文件路径符合MybatisPlus默认约定mapper目录在resources下XML文件名与接口名一致web模块启动时就能自动加载。我们验证过即使web模块的pom.xml里没显式声明mybatis-plus-boot-starter只要service模块依赖了它web模块就能正常使用TenantMapper——这是Spring Boot Starter的自动装配机制在起作用。提示务必检查service模块的src/main/resources/mapper目录是否被IDE识别为Resources Root。IntelliJ IDEA中右键目录→Mark as Resources Root否则XML文件不会被打包进jar。3. 核心模块实现细节与关键代码解析从理论到可运行的每一步3.1bluewhale-domain模块如何设计真正可复用的领域模型很多人认为domain模块就是放几个Data的POJO这是巨大误区。一个合格的领域模型必须承载业务规则、状态约束、行为语义。以Tenant租户实体为例我们不满足于简单的字段映射// com.bluewhale.domain.entity.Tenant.java Data Builder NoArgsConstructor AllArgsConstructor public class Tenant { private Long id; NotBlank(message 租户名称不能为空) Size(max 50, message 租户名称长度不能超过50) private String name; Email(message 管理员邮箱格式不正确) private String adminEmail; Min(value 1, message 初始配额不能小于1) private Integer quota; NotNull(message 租户状态不能为空) private TenantStatus status; // 枚举类型非String // 业务方法判断租户是否处于可用状态 public boolean isActive() { return TenantStatus.ACTIVE.equals(this.status); } // 业务方法计算当前已用配额百分比需传入实时用量 public BigDecimal usedQuotaPercent(Long currentUsage) { if (this.quota null || this.quota 0) return BigDecimal.ZERO; return BigDecimal.valueOf(currentUsage) .divide(BigDecimal.valueOf(this.quota), 2, RoundingMode.HALF_UP) .multiply(BigDecimal.valueOf(100)); } }这个Tenant类的价值体现在三点校验前置NotBlank、Email等注解不仅用于Controller层校验更在service层调用BeanUtils.copyProperties()时生效避免无效数据进入数据库状态语义化TenantStatus是枚举而非String杜绝了statusactive和statusACTIVE的歧义且枚举可自带描述、排序值行为内聚isActive()和usedQuotaPercent()是租户自身的业务能力调用方无需关心状态码含义或计算逻辑直接tenant.isActive()即可。我们曾对比过两种设计一种是domain里只有字段所有校验和计算放在service层另一种是如上所示。后者在代码量上多出15行但带来的收益是当需要新增“租户试用期结束自动停用”功能时只需在Tenant类里加一个isTrialExpired()方法所有调用方自动获得该能力无需修改任何service代码。这就是领域驱动设计DDD中“富领域模型”的威力。3.2bluewhale-api模块DTO与VO的精准分层避免“一Dto走天下”api模块常被简化为“放DTO的地方”但实际中DTOData Transfer Object和VOView Object必须严格区分。以租户列表查询为例DTO用于接收前端请求参数// com.bluewhale.api.dto.TenantQueryDTO.java Data public class TenantQueryDTO { Min(value 0, message 页码不能小于0) private Integer page 0; Min(value 1, message 每页数量不能小于1) Max(value 100, message 每页数量不能大于100) private Integer size 10; Pattern(regexp ^[a-zA-Z0-9\\u4e00-\\u9fa5_\\-]{2,50}$, message 租户名称只能包含字母、数字、中文、下划线、短横线长度2-50) private String nameLike; }VO用于返回前端展示数据// com.bluewhale.api.vo.TenantListVO.java Data public class TenantListVO { private Long id; private String name; private String adminEmail; private String statusDesc; // 状态描述如“正常”“已停用”非枚举码 private Integer quota; private BigDecimal usedPercent; // 已用配额百分比BigDecimal避免浮点误差 private LocalDateTime createTime; }关键区别在于DTO关注输入合法性如页码范围、名称正则VO关注输出可读性如statusDesc代替status码usedPercent是计算后的值。我们强制规定service层方法签名中入参必须是DTO出参必须是VO禁止出现ListTenant这样的“裸实体”返回。原因有二一是安全避免数据库字段如密码盐值、加密密钥意外泄露二是演进当Tenant实体增加新字段时VO可以保持不变前端无需适配。我们用MapStruct做DTO/VO转换配置如下// com.bluewhale.api.converter.TenantConverter.java Mapper(componentModel spring, uses {DateMapper.class}) public interface TenantConverter { TenantConverter INSTANCE Mappers.getMapper(TenantConverter.class); Mapping(target statusDesc, source status.desc) // 枚举转描述 Mapping(target usedPercent, expression java(tenant.usedQuotaPercent(currentUsage))) // 调用领域模型方法 TenantListVO toListVO(Tenant tenant, Context Long currentUsage); }Context参数允许在转换中传入运行时变量如实时用量这是MapStruct高级用法普通BeanUtils.copyProperties()做不到。3.3bluewhale-service模块MybatisPlus的深度整合与事务边界实践service模块是业务逻辑中枢MybatisPlus在这里不是“简化CRUD”而是“赋能复杂查询”。以租户配额超限预警为例需求是查询所有quota 0且usedQuotaPercent 90%的租户。如果用原生SQL得写SELECT * FROM tenant WHERE quota 0 AND (usage / quota) * 100 90但usage是实时计算值不在数据库表中。MybatisPlus的QueryWrapper结合自定义SQL完美解决// com.bluewhale.service.impl.TenantServiceImpl.java Service Transactional(rollbackFor Exception.class) public class TenantServiceImpl implements ITenantService { Autowired private TenantMapper tenantMapper; Autowired private ResourceUsageService resourceUsageService; // 另一模块的服务 Override public ListTenantListVO listOverQuotaTenants() { // 步骤1获取所有活跃租户ID QueryWrapperTenant activeWrapper new QueryWrapper(); activeWrapper.eq(status, TenantStatus.ACTIVE.getCode()) .gt(quota, 0); ListLong activeTenantIds tenantMapper.selectList(activeWrapper) .stream().map(Tenant::getId).collect(Collectors.toList()); // 步骤2批量查询各租户实时用量调用资源编排模块API MapLong, Long usageMap resourceUsageService.batchGetUsage(activeTenantIds); // 步骤3筛选超限租户内存计算避免数据库复杂计算 return activeTenantIds.stream() .filter(id - { Long usage usageMap.getOrDefault(id, 0L); Tenant tenant tenantMapper.selectById(id); return tenant ! null tenant.usedQuotaPercent(usage).compareTo(new BigDecimal(90)) 0; }) .map(id - { Tenant tenant tenantMapper.selectById(id); Long usage usageMap.getOrDefault(id, 0L); return TenantConverter.INSTANCE.toListVO(tenant, usage); }) .collect(Collectors.toList()); } }这里的关键实践是事务粒度精准Transactional只标注在listOverQuotaTenants()上因为该方法涉及数据库查询selectById和外部服务调用batchGetUsage但外部调用本身不参与事务所以事务仅保障本地数据库操作的一致性避免N1查询先查ID列表再批量查用量而不是对每个租户循环调用getUsage(id)计算时机合理配额百分比在内存中计算而非强求数据库SQL完成既保证精度BigDecimal又避免数据库函数兼容性问题。我们还为TenantMapper配置了逻辑删除这是MybatisPlus的杀手级特性// com.bluewhale.service.mapper.TenantMapper.java Mapper public interface TenantMapper extends BaseMapperTenant { // 继承BaseMapper后自动获得selectList、insert等方法 }在application.yml中配置mybatis-plus: global-config: db-config: logic-delete-field: deleted # 全局逻辑删除字段名 logic-delete-value: 1 # 已删除值 logic-not-delete-value: 0 # 未删除值这样所有selectList查询自动追加WHERE deleted 0removeById自动变成UPDATE tenant SET deleted 1 WHERE id ?无需在每个Mapper XML里写if testew ! null。我们统计过一个50张表的系统逻辑删除配置节省了约200行重复SQL。3.4bluewhale-web模块RESTful API的健壮性设计与监控集成web模块是系统的门面其健壮性直接影响用户体验。我们做了三件事参数校验双保险Controller层用Valid做DTO校验ControllerAdvice统一捕获MethodArgumentNotValidException返回标准化错误ControllerAdvice public class GlobalExceptionHandler { ResponseBody ResponseStatus(HttpStatus.BAD_REQUEST) ExceptionHandler(MethodArgumentNotValidException.class) public Result? handleValidationExceptions( MethodArgumentNotValidException ex) { BindingResult result ex.getBindingResult(); String errorMsg result.getFieldErrors().stream() .map(FieldError::getDefaultMessage) .collect(Collectors.joining(; )); return Result.fail(参数校验失败 errorMsg); } }全局异常分类处理除参数异常外还捕获BusinessException业务自定义异常、RuntimeException未预期异常分别返回400、500状态码并记录详细日志含堆栈Actuator深度集成除了默认的/actuator/health、/actuator/metrics我们自定义了/actuator/tenant-status端点实时返回各租户状态统计Component Endpoint(id tenant-status) public class TenantStatusEndpoint { Autowired private ITenantService tenantService; ReadOperation public MapString, Object getTenantStatus() { MapString, Object status new HashMap(); status.put(total, tenantService.count()); status.put(active, tenantService.count(new QueryWrapperTenant().eq(status, ACTIVE))); status.put(overQuota, tenantService.listOverQuotaTenants().size()); return status; } }这样运维同学访问/actuator/tenant-status就能一眼看到系统健康水位无需登录数据库查表。4. 多模块系统构建全流程实操从初始化到CI/CD的完整链路4.1 初始化脚手架用Maven Archetype一键生成标准结构手动建5个模块、配10个pom.xml、调20个版本号效率极低且易错。我们基于Maven Archetype制作了bluewhale-archetype执行以下命令即可生成完整骨架mvn archetype:generate \ -DarchetypeGroupIdcom.bluewhale \ -DarchetypeArtifactIdbluewhale-archetype \ -DarchetypeVersion1.0.0 \ -DgroupIdcom.bluewhale \ -DartifactIdbluewhale-cloud \ -Dversion1.0.0-SNAPSHOT \ -Dpackagecom.bluewhale生成的目录结构如下bluewhale-cloud/ ├── bluewhale-parent/ │ └── pom.xml ├── bluewhale-domain/ │ ├── src/main/java/com/bluewhale/domain/entity/ │ └── pom.xml ├── bluewhale-api/ │ ├── src/main/java/com/bluewhale/api/dto/ │ └── pom.xml ├── bluewhale-service/ │ ├── src/main/java/com/bluewhale/service/impl/ │ ├── src/main/resources/mapper/ │ └── pom.xml ├── bluewhale-web/ │ ├── src/main/java/com/bluewhale/web/controller/ │ └── pom.xml └── pom.xml # 根pom聚合所有子模块bluewhale-archetype的核心价值在于它预置了所有模块的pom.xml依赖关系、Spring Boot和MybatisPlus版本、代码质量插件配置。我们甚至把sonar-project.properties也内置了开箱即用。团队新人拉取代码后mvn clean install一次通过立刻进入业务开发省去环境搭建的2小时。4.2 本地开发调试技巧如何高效地在多模块间跳转与断点多模块调试的痛点是web模块断点进不去service模块的代码因为IDE没把service的源码关联上。解决方案分三步确保Maven依赖正确web模块的pom.xml中service依赖必须是scopecompile/scope默认不能是scopetest/scope或scopeprovided/scopeIDEA中启用“Auto-import”File → Settings → Build → Build Tools → Maven → Importing勾选“Import Maven projects automatically”手动附加源码右键web模块 → Maven → Download Sources and Documentation。如果失败点击service模块 →右键→Open Module Settings→Dependencies→找到bluewhale-service依赖→点击右侧Download Source图标。我们还配置了IDEA的“Delegate IDE build/run actions to Maven”这样点击绿色三角形运行web模块时Maven会自动编译所有依赖模块确保代码最新。实测下来从修改domain里的一个字段到在web的Controller里看到效果全程不超过10秒。4.3 CI/CD流水线设计模块级构建与按需部署在Jenkins或GitLab CI中我们不采用“全量构建”而是根据Git提交的文件路径智能触发# .gitlab-ci.yml stages: - build - test - deploy build-domain: stage: build script: - cd bluewhale-domain mvn clean compile -Dmaven.test.skiptrue only: - /^bluewhale-domain\/.*$/ # 只有bluewhale-domain目录下的文件变更才触发 build-service: stage: build script: - cd bluewhale-service mvn clean compile -Dmaven.test.skiptrue only: - /^bluewhale-service\/.*$/ - /^bluewhale-domain\/.*$/ # domain变更service必须重建 deploy-web: stage: deploy script: - cd bluewhale-web mvn clean package -DskipTests - scp target/bluewhale-web-*.jar userprod-server:/opt/app/ - ssh userprod-server systemctl restart bluewhale-web only: - master - /^bluewhale-web\/.*$/ - /^bluewhale-service\/.*$/ - /^bluewhale-api\/.*$/这个配置的关键是build-service任务监听bluewhale-service和bluewhale-domain两个路径因为service依赖domaindomain变了service必须重编。而deploy-web任务监听web、service、api三个路径因为web最终打包时需要这三个模块的jar。我们实测过一次只改domain里的一个枚举值CI只会构建domain和service耗时1分20秒如果改web的Controller则构建web、service、api耗时2分15秒。相比全量构建平均6分钟效率提升3倍以上。5. 常见问题排查与避坑指南那些文档里不会写的实战教训5.1 问题速查表高频故障现象、原因与解决方案故障现象可能原因解决方案实操心得启动报错No qualifying bean of type XxxMapperweb模块未正确引入service模块或service模块的Mapper接口未被扫描1. 检查web模块pom.xml中dependency是否包含service模块2. 检查service模块pom.xml中mybatis-plus-boot-starter是否为compile作用域3. 检查service模块启动类是否加了MapperScan我们曾因service模块的pom.xml里误加了scopetest/scope导致本地IDEA能跑通因为IDEA的Maven import默认包含test scope但CI构建失败。教训CI环境必须严格模拟生产环境依赖范围。Transactional失效方法内调用不回滚service模块中A.methodA()调用了本类的B.methodB()而methodB加了Transactional将methodB抽取到另一个Service类中或使用AopContext.currentProxy()强制代理调用Spring AOP的Transactional是基于代理的同类方法调用绕过代理。我们有个计费模块createOrder()里调用deductQuota()一开始没抽离导致扣配额失败时不回滚订单创建。MybatisPlus分页插件不生效Page对象返回总数为0web模块未引入mybatis-plus-boot-starter或service模块的application.yml中未配置分页插件在web模块的pom.xml中添加mybatis-plus-boot-starter依赖或在service模块的application.yml中配置mybatis-plus.configuration.default-scripting-language-driver: org.apache.ibatis.scripting.xmltags.XMLLanguageDriver分页插件PaginationInnerInterceptor需要MybatisPlus的Configuration对象才能注册。如果web模块没引入starterConfiguration对象为空插件无法生效。Valid校验不触发BindingResult始终为空Controller方法参数顺序错误ValidDTO后未紧跟BindingResult确保方法签名是public Result? create(Valid RequestBody TenantCreateDTO dto, BindingResult result)BindingResult必须紧随Valid参数之后这是Spring MVC的硬性规定。我们有个新人写了Valid TenantCreateDTO dto, String otherParam, BindingResult result结果校验永远不触发排查了3小时才发现参数顺序问题。5.2 那些“看似合理”实则危险的操作在domain模块中引入JsonIgnore等JSON序列化注解错误做法为了防止Tenant序列化时暴露敏感字段在domain实体上加JsonIgnore。后果domain模块被service、web、甚至未来的mobile-app模块依赖JSON序列化逻辑侵入了核心领域模型违反单一职责。正确做法在api模块的VO类上加JsonIgnore或在web模块的RestController方法上用JsonView指定序列化视图。在service模块中直接new一个Feign Client错误做法service层需要调用告警模块于是new AlertClient()。后果Feign Client失去Spring管理无法使用Configuration配置的拦截器、重试策略、熔断器。正确做法service模块只依赖api模块的AlertClient接口由web模块通过Autowired注入service层通过方法参数传递。为所有模块共用一个application.yml错误做法把数据库连接、Redis地址、MQ配置全写在parent模块的application.yml里。后果domain模块打包时会把数据库配置打进jar存在安全风险且各模块配置无法差异化如web模块需要server.port8080service模块不需要。正确做法parent模块不放application.ymlweb模块放application.yml含Web相关配置service模块放application-service.yml含DB、Redis配置启动时用--spring.config.locationclasspath:application-service.yml指定。5.3 性能优化实录从100ms到10ms的查询提速某次压测发现租户列表接口GET /tenantsP99延迟达100ms远超50ms目标。我们用Arthas诊断发现瓶颈在TenantMapper.selectList()// 原始SQLMybatisPlus自动生成 SELECT id,name,admin_email,quota,status,create_time,update_time,deleted FROM tenant WHERE deleted 0 ORDER BY create_time DESC LIMIT 0,10问题在于ORDER BY create_time DESC导致MySQL无法使用id主键索引必须全表扫描filesort。解决方案是强制使用主键索引分页// 优化后用游标

相关新闻

Dijkstra算法详解:从原理到代码实现与工程避坑指南

Dijkstra算法详解:从原理到代码实现与工程避坑指南

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

2026/10/15 1:22:58 阅读更多 →
2S电池串联均衡系统设计与实现

2S电池串联均衡系统设计与实现

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

2026/10/15 1:22:58 阅读更多 →
MiKTeX报错miktex-makemf失败?字体生成机制与修复指南

MiKTeX报错miktex-makemf失败?字体生成机制与修复指南

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

2026/10/15 1:22:58 阅读更多 →

最新新闻

Linux下C/C++动静态库制作与使用实战指南

Linux下C/C++动静态库制作与使用实战指南

长期以来在Linux下做C/C开发,动静态库始终是绕不开的一个核心技能。无论是自己封装模块、分发二进制产物,还是处理第三方依赖,理解库的制作与使用直接决定了你在编译链接这块能不能少掉头发。我见过不少开发者写业务逻辑很熟练,一…

2026/10/15 4:26:50 阅读更多 →
Flutter鸿蒙开发实战:星座应用跨平台全流程解析

Flutter鸿蒙开发实战:星座应用跨平台全流程解析

1. 项目背景与整体设计思路1.1 为什么选 Flutter 做鸿蒙开发聊到跨平台开发,Flutter 这几年的热度一直在线。原因很简单——一套 Dart 代码,同时跑在 Android、iOS、Windows、macOS、Linux 上,现在还要加上鸿蒙。放在以前,鸿蒙应用…

2026/10/15 4:26:50 阅读更多 →
线上故障排查实战指南:从告警到恢复的黄金步骤

线上故障排查实战指南:从告警到恢复的黄金步骤

半夜两点被电话叫醒,睁开眼看到屏幕上一排红色告警,心跳直接拉满。这种场景,几乎所有做过线上系统的开发者都经历过。告警本身不可怕,可怕的是大脑一片空白,不知道该先点哪里,也不知道哪些命令能救命。这篇…

2026/10/15 4:26:50 阅读更多 →
基于SpringBoot+Vue的个人博客系统开发实战

基于SpringBoot+Vue的个人博客系统开发实战

1. 项目概述与整体技术选型1.1 这个博客系统到底解决了什么问题做个人博客这件事,看起来简单,真做起来坑不少。早期用现成框架,比如WordPress或者各类静态站点生成器,总觉得差了点什么——要么太重,装一堆插件才能用&a…

2026/10/15 4:26:50 阅读更多 →
共享内存IPC实战:从零拷贝原理到信号量同步,提升跨进程通信性能

共享内存IPC实战:从零拷贝原理到信号量同步,提升跨进程通信性能

共享内存(shm)这个老牌IPC方案,我最近在一个数据采集服务里又把它翻了出来。这个服务原本是跨进程传数据,管道和Unix域Socket用得挺顺手,可流量一起来,CPU占用就再也压不下去。换成共享内存之后&#xff0c…

2026/10/15 4:26:50 阅读更多 →
如何判断Ubuntu是否真的关机?三路检查方法与实战探测脚本

如何判断Ubuntu是否真的关机?三路检查方法与实战探测脚本

你有没有遇到过这种情况:在终端里输入了sudo shutdown now,屏幕上日志飞快滚动,最后一切归于黑暗,风扇也慢慢停转,你很自然地认为这台 Ubuntu 机器已经关机了。但我在几年的实践中发现,这个结论经常下得太早…

2026/10/15 4:25:50 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/14 7:53:34 阅读更多 →