1. 项目背景与核心价值
宽带业务管理系统是电信运营商和互联网服务提供商的核心业务支撑平台。随着光纤入户普及和千兆宽带升级,传统基于Excel和纸质工单的管理方式已经无法满足现代业务需求。这个SpringBoot+Vue全栈项目正是为解决以下痛点而生:
- 业务复杂度激增:套餐组合从早期的5-10种发展到现在的200+种(含叠加包、合约计划等)
- 工单流转低效:安装、移机、故障处理等环节平均耗时超过48小时
- 数据孤岛问题:CRM系统、计费系统、运维系统间缺乏实时数据同步
我在某省级运营商实施类似系统时,通过这套技术方案将业务开通时效从72小时压缩到4小时以内。系统核心功能模块包括:
- 客户信息管理(含实名认证对接)
- 套餐配置中心(支持动态定价策略)
- 工单智能调度(基于GIS的地理围栏分配)
- 设备资源管理(OLT、ONU序列号追踪)
- 数据分析看板(用户离网预警模型)
2. 技术架构设计解析
2.1 前后端分离架构优势
采用SpringBoot+Vue的组合主要基于以下考量:
- 迭代效率:前端团队可独立开发界面原型,后端专注业务逻辑
- 性能优化:Nginx静态资源托管+Vue路由懒加载,首屏加载<1s
- 安全隔离:JWT令牌机制避免Session跨域问题
实测对比传统JSP方案:
| 指标 | JSP方案 | Vue+SpringBoot |
|---|---|---|
| 页面响应速度 | 800ms | 210ms |
| 并发处理能力 | 300TPS | 1200TPS |
| 需求变更周期 | 2周 | 3天 |
2.2 数据库设计关键点
MySQL表结构设计遵循电信行业TM Forum标准模型,核心表包括:
CREATE TABLE `broadband_account` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `customer_id` varchar(32) NOT NULL COMMENT '客户编码', `account_status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态(1:正常 2:停机)', `bandwidth` int(11) NOT NULL COMMENT '带宽(Mbps)', `olt_port` varchar(64) DEFAULT NULL COMMENT 'OLT端口', `install_address` geometry NOT NULL COMMENT 'GIS安装位置', PRIMARY KEY (`id`), SPATIAL KEY `idx_address` (`install_address`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宽带账户表';特别注意:
- 使用GEOMETRY类型存储安装坐标,支持半径查询(5km内待装机客户)
- 账户状态采用位运算存储复合状态(如0b000101表示正常+欠费+合约期内)
2.3 MyBatis动态SQL实践
套餐查询模块需要处理多达20+的筛选条件组合:
<select id="selectPlans" resultType="PlanVO"> SELECT * FROM broadband_plan <where> <if test="minPrice != null"> AND price >= #{minPrice} </if> <if test="maxBandwidth != null"> AND bandwidth <= #{maxBandwidth} </if> <choose> <when test="ispType == 'residential'"> AND plan_type IN (1,2,3) </when> <when test="ispType == 'enterprise'"> AND plan_type IN (4,5) </when> </choose> </where> ORDER BY create_time DESC </select>经验:复杂查询建议配合PageHelper分页插件,避免内存溢出。实测10万级数据量下,分页查询耗时稳定在200ms内。
3. 核心功能实现细节
3.1 工单智能调度算法
工单分配逻辑采用加权评分策略:
public class DispatchStrategy { // 技术员评分要素权重 private static final double SKILL_WEIGHT = 0.4; private static final double DISTANCE_WEIGHT = 0.3; private static final double WORKLOAD_WEIGHT = 0.3; public Technician selectTechnician(WorkOrder order) { return technicianList.stream() .map(t -> new ScoreWrapper(t, calculateScore(t, order))) .max(Comparator.comparingDouble(ScoreWrapper::getScore)) .orElseThrow().getTechnician(); } private double calculateScore(Technician t, WorkOrder o) { double skillScore = t.getSkillLevel(o.getWorkType()) * SKILL_WEIGHT; double distanceScore = (1 - normalize(distance(t, o))) * DISTANCE_WEIGHT; double workloadScore = (1 - t.getCurrentWorkload()) * WORKLOAD_WEIGHT; return skillScore + distanceScore + workloadScore; } }关键参数说明:
- 技能匹配度:根据历史完工率动态计算(维护周期30天)
- 距离因子:采用Haversine公式计算球面距离
- 工作负载:当前未完成工单数/日均处理能力
3.2 Vue动态表单生成
套餐配置界面需要动态渲染200+种参数组合:
<template> <div v-for="(group,index) in paramGroups" :key="index"> <h3>{{ group.groupName }}</h3> <component :is="getComponentType(param.controlType)" v-for="param in group.params" :key="param.code" v-model="formData[param.code]" :config="param" /> </div> </template> <script> export default { methods: { getComponentType(type) { const components = { 'input': 'DynamicInput', 'select': 'DynamicSelect', 'switch': 'DynamicSwitch' } return components[type] || 'DynamicInput' } } } </script>避坑指南:动态组件需要预注册(在components属性声明),否则生产环境可能报错
4. 性能优化实战记录
4.1 二级缓存设计
采用Redis+Caffeine多级缓存方案:
@Configuration @EnableCaching public class CacheConfig { @Bean public CacheManager cacheManager(RedisConnectionFactory factory) { CaffeineCacheManager caffeineManager = new CaffeineCacheManager(); caffeineManager.setCaffeine(Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(10, TimeUnit.MINUTES)); RedisCacheManager redisManager = RedisCacheManager.builder(factory) .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofHours(1))) .build(); return new TieredCacheManager(caffeineManager, redisManager); } }缓存策略说明:
- 本地缓存:10分钟过期,应对高频访问的静态数据(如套餐列表)
- Redis缓存:1小时过期,保证集群环境数据一致性
- 数据库:最终数据源,通过@CacheEvict保证数据更新后缓存失效
4.2 大文件导出优化
客户账单导出采用分片流式处理:
@GetMapping("/export/bills") public void exportBills(HttpServletResponse response) { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); try (SXSSFWorkbook workbook = new SXSSFWorkbook(100)) { Sheet sheet = workbook.createSheet("账单"); // 分批查询(每次5000条) int page = 0; while (true) { List<Bill> bills = billMapper.selectByPage(page, 5000); if (bills.isEmpty()) break; // 流式写入 for (Bill bill : bills) { Row row = sheet.createRow(sheet.getLastRowNum() + 1); row.createCell(0).setCellValue(bill.getAccountNo()); // 其他字段... } page++; } workbook.write(response.getOutputStream()); } }实测数据:
- 传统方式(全量加载):导出10万条数据消耗1.2GB内存,耗时45秒
- 流式处理:内存占用稳定在200MB以内,耗时38秒
5. 部署与监控方案
5.1 Docker Compose编排
生产环境部署配置示例:
version: '3.8' services: app: image: broadband-app:1.0 ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=prod depends_on: - redis - mysql mysql: image: mysql:8.0 volumes: - mysql_data:/var/lib/mysql environment: - MYSQL_ROOT_PASSWORD=${DB_PASSWORD} redis: image: redis:6.2 ports: - "6379:6379" volumes: mysql_data:关键参数:
- 使用Alpine基础镜像,最终镜像大小<150MB
- 通过env_file管理敏感配置
- 配置健康检查接口:/actuator/health
5.2 Prometheus监控指标
自定义业务指标采集:
@RestController public class MetricsController { private final Counter orderCounter = Counter.build() .name("broadband_orders_total") .help("Total work orders") .register(); @PostMapping("/order") public Result createOrder(@RequestBody Order order) { orderCounter.inc(); // 业务逻辑... } }Grafana监控看板应包含:
- 业务指标:工单创建速率、套餐订购分布
- 系统指标:JVM内存、数据库连接池使用率
- 告警规则:API错误率>1%持续5分钟触发PagerDuty
6. 源码结构说明
完整项目目录树:
broadband-system ├── broadband-admin # Vue管理端 │ ├── src │ │ ├── api # Axios接口封装 │ │ ├── components # 业务组件 │ │ └── views # 页面路由 ├── broadband-app # SpringBoot主模块 │ ├── src/main/java │ │ ├── config # 配置类 │ │ ├── controller # 控制层 │ │ ├── service # 业务逻辑 │ │ └── mapper # MyBatis接口 ├── broadband-common # 公共模块 │ ├── exception # 异常处理 │ ├── utils # 工具类 │ └── validator # 参数校验 └── sql ├── init.sql # 初始化脚本 └── demo-data.sql # 测试数据开发环境启动步骤:
- MySQL执行init.sql建表
- IDEA启动SpringBoot应用(默认端口8080)
- 进入admin目录执行:npm install && npm run dev
- 访问 http://localhost:9528 (默认账号admin/123456)
7. 典型问题排查指南
7.1 跨域问题深度解决
尽管配置了标准CORS,但文件上传仍报错:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("*") .allowedHeaders("*") // 必须显式配置暴露的Header .exposedHeaders("Content-Disposition"); } }关键点:当需要前端读取自定义Header(如文件下载时的文件名)时,必须通过exposedHeaders声明
7.2 MyBatis批量插入优化
传统循环插入方式(性能差):
@Insert("<script>" + "INSERT INTO broadband_device(serial_no, type) VALUES " + "<foreach collection='list' item='item' separator=','>" + "(#{item.serialNo}, #{item.type})" + "</foreach>" + "</script>") void batchInsert(List<Device> devices);改进方案(使用rewriteBatchedStatements):
- 在jdbc url添加参数:
jdbc:mysql://localhost:3306/broadband?rewriteBatchedStatements=true - 使用ExecutorType.BATCH模式:
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) { DeviceMapper mapper = session.getMapper(DeviceMapper.class); for (Device device : deviceList) { mapper.insert(device); } session.commit(); // 批量提交 }实测10万条数据插入时间从120秒降至8秒