1. 项目背景与核心需求
大学城水电管理系统是针对高校园区水电资源管理痛点设计的数字化解决方案。传统高校普遍存在水电数据采集滞后、费用核算不透明、设备维护响应慢等问题。以某985高校为例,其2024年审计报告显示,因计量误差和管理漏洞导致的水电浪费高达年度预算的12%。这套系统通过物联网+信息化的方式,实现水电数据的实时采集、智能分析和可视化管控。
系统采用前后端分离架构,后端基于SpringBoot 3.1.5构建RESTful API服务,前端使用Vue 3组合式API开发管理界面,数据持久层采用MyBatis-Plus 3.5.3增强CRUD操作,数据库选用MySQL 8.0的InnoDB集群方案。系统需满足以下核心场景:
- 宿舍/教学楼水电表的远程自动抄表(支持Modbus RTU/TCP协议)
- 用量异常检测(基于时间序列预测算法)
- 多维度费用统计(按楼栋/院系/个人分级核算)
- 移动端缴费与账单推送(集成微信支付SDK)
2. 技术架构设计解析
2.1 后端技术栈选型
SpringBoot框架的选择主要基于其嵌入式Tomcat和自动配置特性,特别适合需要快速迭代的管理系统开发。我们在pom.xml中配置了关键依赖:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>com.github.yitter</groupId>
<artifactId>yitter-idgenerator</artifactId>
<version>1.0.6</version>
</dependency>
采用雪花算法替代数据库自增ID,解决分库分表时的ID冲突问题。通过自定义MetaObjectHandler实现审计字段自动填充:
java复制public class AutoFillHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
}
}
2.2 前端工程化实践
Vue3项目使用Vite作为构建工具,相比Webpack启动速度提升80%。关键配置如下:
javascript复制// vite.config.js
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: tag => tag.startsWith('ion-') // 处理自定义元素
}
}
})
],
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
}
})
采用Pinia替代Vuex进行状态管理,配合axios拦截器实现JWT自动刷新:
javascript复制// auth.interceptor.js
instance.interceptors.response.use(response => {
if (response.data?.token) {
localStorage.setItem('token', response.data.token)
}
return response
}, error => {
if (error.response.status === 401) {
router.push('/login')
}
return Promise.reject(error)
})
3. 核心功能实现细节
3.1 水电数据采集模块
通过Spring Integration实现Modbus设备通信:
java复制@Bean
public MessageChannel modbusInputChannel() {
return new DirectChannel();
}
@Bean
@InboundChannelAdapter(channel = "modbusInputChannel", poller = @Poller(fixedDelay = "5000"))
public MessageSource<byte[]> modbusSource() {
ModbusPollingChannelAdapter source = new ModbusPollingChannelAdapter(tcpAdapter());
source.setFunctionCode(ModbusFunctionCode.READ_INPUT_REGISTERS);
source.setStartAddress(0);
source.setNumberOfRegisters(10);
return source;
}
数据解析采用策略模式,针对不同型号电表实现差异处理:
java复制public interface MeterDataParser {
MeterData parse(byte[] rawData);
}
@Service
public class DLTS645Parser implements MeterDataParser {
private static final int VOLTAGE_OFFSET = 6;
@Override
public MeterData parse(byte[] rawData) {
// 解析DL/T645-2007协议电表数据
}
}
3.2 费用计算引擎
采用规则引擎Drools处理阶梯计价:
drl复制rule "Tiered pricing - residential"
when
$record : ElectricityRecord(userType == "RESIDENTIAL",
monthlyUsage > 200)
then
$record.setUnitPrice(0.85);
end
批量计算使用Spring Batch优化性能:
java复制@Bean
public Step calculateStep() {
return stepBuilderFactory.get("calculateStep")
.<ElectricityRecord, Bill>chunk(1000)
.reader(recordReader())
.processor(priceProcessor())
.writer(billWriter())
.build();
}
4. 数据库设计与优化
4.1 关键表结构
电表读数表(meter_reading):
sql复制CREATE TABLE `meter_reading` (
`id` BIGINT NOT NULL COMMENT '雪花算法ID',
`meter_id` VARCHAR(20) NOT NULL COMMENT '电表编号',
`current_reading` DECIMAL(10,2) NOT NULL COMMENT '当前读数',
`reading_time` DATETIME NOT NULL COMMENT '抄表时间',
`device_status` TINYINT DEFAULT 0 COMMENT '设备状态(0正常1异常)',
PRIMARY KEY (`id`),
INDEX `idx_meter_time` (`meter_id`, `reading_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
费用账单表(energy_bill):
sql复制CREATE TABLE `energy_bill` (
`id` BIGINT NOT NULL,
`user_id` BIGINT NOT NULL COMMENT '关联用户ID',
`billing_cycle` DATE NOT NULL COMMENT '账期(YYYY-MM-01)',
`total_amount` DECIMAL(10,2) NOT NULL COMMENT '应缴金额',
`payment_status` TINYINT DEFAULT 0 COMMENT '支付状态',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_cycle` (`user_id`, `billing_cycle`)
) PARTITION BY RANGE (TO_DAYS(billing_cycle)) (
PARTITION p2023 VALUES LESS THAN (TO_DAYS('2024-01-01')),
PARTITION p2024 VALUES LESS THAN (TO_DAYS('2025-01-01'))
);
4.2 查询优化实践
对于高频访问的月度统计报表,采用物化视图+定时刷新策略:
sql复制CREATE MATERIALIZED VIEW mv_monthly_stats
REFRESH COMPLETE ON DEMAND
AS
SELECT
DATE_FORMAT(reading_time, '%Y-%m') AS month,
building_id,
SUM(current_reading) AS total_usage
FROM meter_reading
GROUP BY month, building_id;
5. 部署与运维方案
5.1 容器化部署
使用Docker Compose编排服务:
yaml复制version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping"]
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
mysql:
condition: service_healthy
5.2 监控告警配置
通过Prometheus+Grafana监控关键指标:
yaml复制# application.yml
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
自定义业务指标采集:
java复制@RestController
public class MeterController {
private final Counter readingErrorCounter = Metrics.counter("meter.reading.errors");
@PostMapping("/reading")
public void submitReading(@RequestBody ReadingDTO dto) {
try {
// 处理逻辑
} catch (Exception e) {
readingErrorCounter.increment();
}
}
}
6. 典型问题排查实录
6.1 电表数据不同步问题
现象:部分电表数据延迟超过1小时
排查步骤:
- 检查Modbus TCP连接池状态
bash复制netstat -ant | grep 502 | wc -l - 分析线程转储
java复制ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); ThreadInfo[] threads = threadBean.dumpAllThreads(true, true); - 发现阻塞在SerialPortEventListener的回调处理
解决方案:
- 增加Modbus请求超时配置
- 采用异步非阻塞的Reactor Netty实现
6.2 批量计费性能优化
原始方案:单线程处理5000条记录耗时28秒
优化措施:
- 启用MyBatis批处理模式
yaml复制mybatis-plus: global-config: db-config: logic-delete-field: isDeleted batch-size: 1000 - 添加复合索引
sql复制ALTER TABLE meter_reading ADD INDEX idx_calculation (user_id, reading_time, meter_type); - 引入Redis缓存费率规则
优化结果:处理时间降至3.2秒
7. 安全防护措施
7.1 接口安全设计
采用JWT+RBAC的双重验证:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()));
return http.build();
}
}
7.2 数据加密方案
敏感字段使用AES-GSM加密:
java复制public class CryptoUtils {
private static final String ALGORITHM = "AES/GCM/NoPadding";
public static String encrypt(String plaintext) {
GCMParameterSpec ivSpec = new GCMParameterSpec(128, SecureRandom.getSeed(12));
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] ciphertext = cipher.doFinal(plaintext.getBytes());
return Base64.getEncoder().encodeToString(ciphertext);
}
}
水电数据在传输层启用MQTT over TLS 1.3,采用双向证书认证。每个物联网终端设备部署唯一客户端证书,通过OCSP实现证书状态实时验证。数据存储层对用户隐私字段(如房间号、联系方式)进行字段级加密,密钥管理采用HSM硬件安全模块托管。
