SpringBoot+MyBatis实战:手把手教你从零搭建一个企业级CRM系统(附完整源码)

张开发
2026/4/17 19:12:37 15 分钟阅读

分享文章

SpringBoot+MyBatis实战:手把手教你从零搭建一个企业级CRM系统(附完整源码)
SpringBootMyBatis实战从零构建企业级CRM系统全流程解析当技术学习者从基础语法过渡到实际项目开发时往往面临一个关键瓶颈——如何将分散的技术点整合成可落地的商业系统。本文将带你完整经历一个企业级CRM系统的构建过程不仅涵盖SpringBoot和MyBatis的技术整合更着重分享工程化实践中那些文档里找不到的实战经验。1. 项目架构设计与环境搭建在开始编码之前合理的项目架构设计能避免后期大量的重构工作。我们采用经典的三层架构但针对CRM系统的特性做了特殊优化crm-system ├── src/main/java │ ├── config # 配置层拦截器、Swagger等 │ ├── controller # 控制层 │ ├── service # 业务层 │ │ ├── impl # 实现类 │ ├── dao # 数据访问层 │ ├── model # 实体类 │ ├── util # 工具类 │ ├── exception # 异常处理 │ └── vo # 视图对象 ├── src/main/resources │ ├── mapper # MyBatis映射文件 │ ├── static # 静态资源 │ └── templates # 模板文件关键依赖配置pom.xml精选片段dependencies !-- SpringBoot Starter系列 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.2.0/version /dependency !-- 数据库相关 -- dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.2.8/version /dependency !-- 工具类 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies提示使用Druid连接池而非默认HikariCP因其提供的SQL监控功能在CRM这类数据密集型应用中非常实用数据库配置示例application.ymlspring: datasource: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://localhost:3306/crm_db?useSSLfalseserverTimezoneAsia/Shanghai username: crm_user password: 加密密码建议使用Jasypt druid: filters: stat,wall stat-view-servlet: enabled: true login-username: admin login-password: admin2. 核心模块实现精要2.1 用户认证与权限控制CRM系统的安全体系需要兼顾便捷性与安全性。我们采用改良版的RBAC模型增加数据权限维度// 数据权限注解示例 Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface DataPermission { DataScopeType value() default DataScopeType.SELF; enum DataScopeType { SELF, // 仅自己 DEPARTMENT, // 本部门 ALL // 全部 } }权限拦截器的核心逻辑public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 获取当前用户权限上下文 UserContext user getCurrentUser(request); if (handler instanceof HandlerMethod) { HandlerMethod method (HandlerMethod)handler; // 检查方法级权限注解 RequiredPermission permission method.getMethodAnnotation( RequiredPermission.class); if (permission ! null !user.hasPermission(permission.value())) { throw new BusinessException(403, 权限不足); } // 检查数据权限注解 DataPermission dataPermission method.getMethodAnnotation( DataPermission.class); if (dataPermission ! null) { request.setAttribute(DATA_SCOPE, dataPermission.value().apply(user)); } } return true; }记住我功能实现要点采用Token机制而非直接存储密码Token应包含过期时间和校验码服务端需要维护Token白名单CREATE TABLE t_login_token ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, token varchar(64) NOT NULL, expire_time datetime NOT NULL, create_time datetime NOT NULL, update_time datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY idx_token (token) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;2.2 客户关系管理模块客户数据模型设计是CRM的核心我们采用主表扩展表的设计模式// 客户主表实体 Data public class Customer { private Long id; private String name; private Integer source; // 客户来源 private Integer level; // 客户等级 // 其他基础字段... } // 客户扩展信息垂直分表 Data public class CustomerExt { private Long customerId; private String industry; private BigDecimal annualRevenue; private Integer employeeCount; // 其他扩展字段... }客户生命周期状态机设计状态允许操作业务规则POTENTIAL建档、分配新录入客户默认状态DEVELOPING跟进、升级需记录每次跟进内容COOPERATED签单、服务生成客户健康度评分LOST分析、归档触发流失预警机制使用MyBatis实现复杂查询时推荐使用注解式SQL提高可维护性SelectProvider(type CustomerSqlBuilder.class, method buildQuery) ListCustomerVO queryByCondition(CustomerQuery query); // SQL构建器 public class CustomerSqlBuilder { public String buildQuery(CustomerQuery query) { return new SQL() {{ SELECT(c.*, e.industry); FROM(t_customer c); LEFT_OUTER_JOIN(t_customer_ext e ON c.id e.customer_id); if (query.getLevel() ! null) { WHERE(c.level #{level}); } if (StringUtils.isNotBlank(query.getIndustry())) { WHERE(e.industry #{industry}); } ORDER_BY(c.create_time DESC); }}.toString(); } }2.3 服务工单系统服务工单的状态流转采用策略模式实现// 状态处理接口 public interface ServiceStateHandler { void handle(ServiceTicket ticket, String operator); } // 具体状态实现 Service(assignedHandler) public class AssignedStateHandler implements ServiceStateHandler { Override public void handle(ServiceTicket ticket, String operator) { if (!ASSIGNED.equals(ticket.getStatus())) { throw new IllegalStateException(当前状态不能执行分配操作); } ticket.setHandler(operator); ticket.setProcessTime(new Date()); // 发送通知 notifyService.notifyUser(operator, 您有新的工单待处理 ticket.getTitle()); } }工单表设计考虑到了操作审计需求CREATE TABLE t_service_ticket ( id bigint(20) NOT NULL AUTO_INCREMENT, title varchar(100) NOT NULL, content text, status varchar(20) NOT NULL, creator varchar(50) NOT NULL, handler varchar(50) DEFAULT NULL, create_time datetime NOT NULL, update_time datetime NOT NULL, PRIMARY KEY (id), KEY idx_status (status), KEY idx_handler (handler) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 操作日志表 CREATE TABLE t_service_log ( id bigint(20) NOT NULL AUTO_INCREMENT, ticket_id bigint(20) NOT NULL, operation varchar(50) NOT NULL, operator varchar(50) NOT NULL, content text, create_time datetime NOT NULL, PRIMARY KEY (id), KEY idx_ticket (ticket_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;3. 高级功能实现3.1 数据统计与分析使用ECharts实现可视化时后端需要提供特定格式的数据GetMapping(/stats/customer) public CustomerStatsVO getCustomerStats( RequestParam(required false) String timeRange) { CustomerStatsVO vo new CustomerStatsVO(); // 客户来源分布饼图数据 vo.setSourceStats(customerMapper.countBySource()); // 月度增长趋势折线图数据 vo.setGrowthTrend(customerMapper.countMonthlyGrowth( parseTimeRange(timeRange))); // 客户价值矩阵散点图数据 vo.setValueMatrix(customerMapper.analyzeValueMatrix()); return vo; }MyBatis动态SQL处理复杂统计select idcountMonthlyGrowth resultTypemap SELECT DATE_FORMAT(create_time, %Y-%m) AS month, COUNT(*) AS total, SUM(CASE WHEN level VIP THEN 1 ELSE 0 END) AS vip_count FROM t_customer WHERE create_time BETWEEN #{begin} AND #{end} GROUP BY DATE_FORMAT(create_time, %Y-%m) ORDER BY month /select3.2 定时任务与异步处理SpringBoot的定时任务配置Configuration EnableScheduling public class ScheduleConfig implements SchedulingConfigurer { Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(taskExecutor()); } Bean(destroyMethod shutdown) public Executor taskExecutor() { return Executors.newScheduledThreadPool(5); } } // 客户流失检测任务 Component public class CustomerChurnJob { Autowired private CustomerService customerService; // 每天凌晨2点执行 Scheduled(cron 0 0 2 * * ?) public void checkChurnCustomers() { ListLong churnIds customerService.detectChurnCustomers(); if (!churnIds.isEmpty()) { customerService.markChurnStatus(churnIds); // 异步发送通知 notificationService.asyncSendChurnAlert(churnIds); } } }注意涉及批量数据操作的任务建议添加Transactional注解并考虑分页处理避免大事务问题4. 性能优化与生产实践4.1 MyBatis高级优化二级缓存配置需谨慎使用settings setting namecacheEnabled valuetrue/ setting namelocalCacheScope valueSTATEMENT/ /settings !-- 在Mapper XML中 -- cache typeorg.mybatis.caches.ehcache.EhcacheCache evictionLRU flushInterval60000 size1000 readOnlytrue/批量插入优化public void batchInsert(ListCustomer list) { SqlSession session sqlSessionTemplate.getSqlSessionFactory() .openSession(ExecutorType.BATCH, false); try { CustomerMapper mapper session.getMapper(CustomerMapper.class); for (int i 0; i list.size(); i) { mapper.insert(list.get(i)); if (i % 500 0 || i list.size() - 1) { session.commit(); session.clearCache(); } } } finally { session.close(); } }4.2 生产环境配置建议连接池参数调优spring: datasource: druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 validation-query: SELECT 1 test-while-idle: true test-on-borrow: false test-on-return: falseMyBatis日志输出控制logging.level.org.mybatisDEBUG logging.level.com.yourpackage.mapperTRACEAPI文档集成Swagger3配置Configuration public class SwaggerConfig { Bean public OpenAPI customOpenAPI() { return new OpenAPI() .info(new Info() .title(CRM系统API文档) .version(1.0) .contact(new Contact() .name(技术支持) .email(techexample.com))) .externalDocs(new ExternalDocumentation() .description(CRM系统开发手册) .url(https://docs.example.com)); } }5. 项目扩展与演进方向当系统发展到一定规模后可以考虑以下架构演进模块化拆分将客户管理、营销活动、服务支持拆分为独立微服务采用Spring Cloud Alibaba体系集成数据中台建设// 客户统一视图服务 Service public class CustomerViewService { DubboReference private BasicCustomerService basicService; DubboReference private InteractionService interactionService; public Customer360View getCustomerView(Long id) { Customer360View view new Customer360View(); view.setBaseInfo(basicService.getById(id)); view.setInteractions(interactionService.getRecentInteractions(id)); view.setValueScore(calculateValueScore(id)); return view; } }智能化升级集成机器学习模型预测客户流失风险使用NLP处理客服工单自动分类在开发过程中我们发现MyBatis Generator生成的代码往往需要手动调整因此封装了自定义模板!-- generatorConfig.xml片段 -- table tableNamet_customer generatedKey columnid sqlStatementMySql identitytrue/ columnOverride columnremarks propertycomments javaTypeString jdbcTypeVARCHAR/ /table实际项目中遇到的典型问题及解决方案问题场景客户列表页需要展示关联的订单数量直接JOIN查询在大数据量时性能极差解决方案采用冗余计数字段定期任务更新使用Redis缓存热门客户的订单数复杂场景下考虑Elasticsearch聚合查询// 优化后的查询示例 public PageInfoCustomerVO queryWithOrderCount(CustomerQuery query) { // 先查分页基础数据 PageHelper.startPage(query.getPageNum(), query.getPageSize()); ListCustomerVO list customerMapper.selectByCondition(query); // 批量查询关联订单数减少N1查询 if (!list.isEmpty()) { MapLong, Integer orderCounts orderService.batchQueryOrderCount( list.stream().map(CustomerVO::getId).collect(Collectors.toList())); list.forEach(vo - vo.setOrderCount( orderCounts.getOrDefault(vo.getId(), 0))); } return new PageInfo(list); }

更多文章