1. 项目背景与核心价值
大学城水电管理系统是一个典型的校园基础设施管理平台,它解决了传统人工抄表、纸质账单带来的效率低下问题。我在参与某高校智慧校园建设时,曾亲眼目睹后勤部门每月需要投入3名专职人员花费5个工作日完成整个校区的数据采集和费用计算,而采用这套系统后,整个流程缩短到2小时内自动完成。
这个2025版系统采用SpringBoot+Vue的前后端分离架构,相比早期单体应用具有明显优势:
- 前端采用Vue 3.2+组合式API,实现响应式数据绑定
- 后端基于SpringBoot 3.1.x,默认支持GraalVM原生镜像编译
- 持久层使用MyBatis-Plus 3.5.3,减少90%的基础CRUD代码
- 数据库采用MySQL 8.0的JSON字段存储动态扩展属性
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计解析
2.1 技术栈选型依据
选择SpringBoot+Vue的组合主要基于以下实际考量:
- 开发效率:我校信息中心仅有5人开发团队,需要快速迭代多个管理系统
- 运维成本:采用Docker Compose部署方案,降低服务器资源占用
- 扩展需求:预留了第三方支付接口和智能电表数据采集协议
技术栈版本选择特别注意了长期支持(LTS)版本:
- SpringBoot 3.1.x (支持到2026年)
- Vue 3.2.x (生态兼容性好)
- MySQL 8.0.33 (高校普遍采用的稳定版本)
2.2 核心模块划分
系统包含6个关键模块:
- 计量采集模块:通过Modbus TCP协议与智能电表通信
- 费用计算引擎:支持阶梯电价、季节浮动等15种计费规则
- 账单管理:生成PDF账单并支持微信推送
- 故障报修:集成高德地图API实现定位
- 数据看板:ECharts实现能耗热力图
- 权限控制:RBAC模型+JWT认证
3. 数据库设计与优化
3.1 关键表结构
sql复制CREATE TABLE `meter_reading` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`room_id` VARCHAR(20) NOT NULL COMMENT '房间编号',
`meter_type` TINYINT NOT NULL COMMENT '1-电表 2-水表',
`current_value` DECIMAL(10,2) NOT NULL,
`reading_time` DATETIME NOT NULL,
`operator_id` BIGINT DEFAULT NULL,
`ext_data` JSON DEFAULT NULL COMMENT '扩展字段',
PRIMARY KEY (`id`),
INDEX `idx_room_meter` (`room_id`, `meter_type`),
INDEX `idx_time` (`reading_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
3.2 性能优化实践
在20000+房间规模的实际部署中,我们遇到过分页查询慢的问题。通过以下方案解决:
- 采用覆盖索引优化:
SELECT id,room_id FROM meter_reading WHERE ... - 大表分库分表:按楼栋进行水平分片
- 冷热数据分离:3个月前的数据自动归档
特别注意:MySQL 8.0的JSON字段虽然方便,但频繁更新的JSON属性应考虑拆分成独立字段
4. 前后端交互关键实现
4.1 API设计规范
采用RESTful风格,但针对批量操作做了特殊处理:
java复制@PostMapping("/readings/batch")
public Result batchInsert(@Valid @RequestBody List<MeterReadingDTO> dtos) {
// 使用MyBatis的BatchExecutor
readingService.insertBatch(dtos);
return Result.success();
}
4.2 Vue前端封装技巧
在src/api/目录下创建统一的请求封装:
javascript复制// request.js
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 30000,
withCredentials: true
})
// 响应拦截器中处理业务错误码
service.interceptors.response.use(response => {
const res = response.data
if (res.code !== 200) {
ElMessage.error(res.message || 'Error')
return Promise.reject(new Error(res.message || 'Error'))
}
return res
})
5. 典型业务场景实现
5.1 阶梯电价计算
采用策略模式实现计费规则:
java复制public interface BillingStrategy {
BigDecimal calculate(BigDecimal usage);
}
@Service
public class TieredElectricityStrategy implements BillingStrategy {
@Override
public BigDecimal calculate(BigDecimal usage) {
if (usage.compareTo(new BigDecimal("200")) <= 0) {
return usage.multiply(new BigDecimal("0.6"));
} else if (usage.compareTo(new BigDecimal("400")) <= 0) {
return usage.multiply(new BigDecimal("0.8"));
} else {
return usage.multiply(new BigDecimal("1.2"));
}
}
}
5.2 微信推送集成
使用WxJava SDK实现模板消息推送:
java复制@Scheduled(cron = "0 0 8 1 * ?") // 每月1号8点执行
public void sendMonthlyBills() {
List<WechatUser> users = userMapper.selectSubscribedUsers();
users.forEach(user -> {
String content = buildBillContent(user.getRoomId());
wxMpService.getTemplateMsgService()
.sendTemplateMsg(WxMpTemplateMessage.builder()
.toUser(user.getOpenId())
.templateId("BILL_TPL_ID")
.data(buildTemplateData(content))
.build());
});
}
6. 部署与监控方案
6.1 容器化部署
docker-compose.yml关键配置:
yaml复制services:
app:
image: openjdk:17-jdk
ports:
- "8080:8080"
volumes:
- ./logs:/app/logs
depends_on:
- redis
- mysql
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
6.2 监控配置
采用SpringBoot Actuator+Prometheus的方案:
properties复制# application.properties
management.endpoints.web.exposure.include=health,metrics,prometheus
management.metrics.export.prometheus.enabled=true
7. 开发中的典型问题解决
7.1 MyBatis动态表名问题
在分表场景下,需要通过拦截器实现:
java复制@Intercepts({
@Signature(type= StatementHandler.class,
method="prepare",
args={Connection.class, Integer.class})
})
public class DynamicTableInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 解析SQL替换表名
String newSql = originalSql.replaceAll("meter_reading", "meter_reading_" + tableSuffix);
metaStatementHandler.setValue("delegate.boundSql.sql", newSql);
return invocation.proceed();
}
}
7.2 Vue路由缓存问题
在账单详情页需要强制刷新:
javascript复制const router = createRouter({
routes: [
{
path: '/bill/:id',
component: BillDetail,
meta: {
noCache: true
}
}
]
})
router.beforeEach((to, from, next) => {
if (to.meta.noCache) {
store.dispatch('clearCache')
}
next()
})
8. 项目二次开发建议
根据实际部署经验,建议在以下方向进行扩展:
- 设备预警:当用电量突增50%时触发微信通知
- 移动端适配:基于Uniapp开发跨平台应用
- 数据可视化:接入GIS系统展示管线网络
- 离线模式:支持PWA技术在断网时记录数据
在代码结构方面,建议将领域模型拆分为独立模块:
code复制src/
├── domain/
│ ├── billing/
│ ├── meter/
│ └── user/
├── infrastructure/
└── application/
