在建筑行业摸爬滚打多年,我见过太多中小施工企业被材料管理问题折磨得焦头烂额。这些企业往往面临一个尴尬局面:大型ERP系统用不起,Excel表格又管不住。去年帮朋友公司做咨询时,就遇到一个典型案例——某工地因为材料领用记录混乱,最终盘点时发现价值20多万的电缆"不翼而飞",而类似问题在这些年经手的项目中屡见不鲜。
这个毕设项目正是瞄准了这个行业痛点,试图用SSM+Vue技术栈打造一个轻量级解决方案。相比市面上动辄几十万的商业系统,我们的设计有三大差异化优势:
作为传统JavaEE技术的拥趸,我最初考虑过纯Spring Boot方案。但经过三个技术方案的对比测试,最终拍板SSM+Vue的架构,主要基于以下考量:
特别要说明的是Vue 2.x的选择——虽然3.0已发布,但考虑到:
在MySQL 5.7的表结构设计中,我们突破了传统ERP的范式约束,做了几个大胆创新:
sql复制CREATE TABLE `material_flow` (
`flow_id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`material_id` VARCHAR(20) NOT NULL COMMENT '材料身份证号',
`operator_id` INT NOT NULL COMMENT '必须关联考勤打卡ID',
`project_id` INT NOT NULL,
`quantity` DECIMAL(10,2) NOT NULL,
`flow_type` ENUM('IN','OUT','RETURN') NOT NULL,
`gps_location` POINT NOT NULL COMMENT '领用地点坐标',
`offline_flag` TINYINT DEFAULT 0 COMMENT '离线操作标记',
`sync_version` INT DEFAULT 0 COMMENT '乐观锁版本号',
KEY `idx_chain` (`material_id`,`project_id`) USING BTREE,
SPATIAL KEY `idx_gps` (`gps_location`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
这个设计的精妙之处在于:
传统系统的致命缺陷是领料人与实际操作人分离。我们的解决方案是:
java复制// 领料前校验逻辑
public boolean checkOperatorValid(int operatorId, String materialType) {
// 因子1:考勤状态验证
Attendance attendance = attendanceMapper.selectLatest(operatorId);
if(attendance == null || !attendance.isOnDuty()) {
throw new BizException("未打卡人员禁止领料");
}
// 因子2:资质验证
String certType = materialCertMapper.selectCertType(materialType);
return operatorCertMapper.existsCert(operatorId, certType);
}
配合前端的活体检测:
vue复制<template>
<el-dialog :visible.sync="showFaceVerify">
<face-recognition
@success="onVerifySuccess"
@failure="onVerifyFailed"
:liveness-level="3"/>
</el-dialog>
</template>
施工现场经常没信号?我们这样解决:
javascript复制workbox.precaching.precacheAndRoute([
'/static/js/material-offline.js',
'/static/css/offline.css',
{url: '/offline.html', revision: 'v1'}
]);
javascript复制function saveOfflineRecord(record) {
return new Promise((resolve) => {
const tx = db.transaction('material_flows', 'readwrite');
const store = tx.objectStore('material_flows');
store.put(record).onsuccess = resolve;
});
}
java复制@Scheduled(fixedDelay = 300000)
public void syncOfflineData() {
List<OfflineRecord> records = offlineService.getPendingRecords();
records.forEach(record -> {
try {
materialService.processOfflineFlow(record);
offlineService.markAsSynced(record.getId());
} catch (Exception e) {
logger.error("同步失败记录ID:{}", record.getId());
}
});
}
初期采用简单循环插入,200条记录要8秒。优化方案:
xml复制<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO material_flow(...) VALUES
<foreach collection="list" item="item" separator=",">
(#{item.materialId},#{item.operatorId},...)
</foreach>
ON DUPLICATE KEY UPDATE version=version+1
</insert>
配合JDBC参数优化:
properties复制spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.data-source-properties=rewriteBatchedStatements=true
最终性能提升40倍!
当材料清单超过500条时,页面明显卡顿。解决方案:
vue复制<virtual-list :size="60" :remain="20">
<material-row v-for="item in list" :key="item.id"/>
</virtual-list>
javascript复制function chunkRender(data) {
let index = 0;
const chunk = () => {
const piece = data.slice(index, index + 50);
this.list.push(...piece);
if(index < data.length) {
requestIdleCallback(chunk);
}
};
chunk();
}
针对中小企业预算,推荐以下配置:
| 项目 | 最低配置 | 推荐配置 |
|---|---|---|
| 服务器 | 2核4G(年费800) | 4核8G(年费1500) |
| 数据库 | 阿里云RDS MySQL基础版 | 自建MySQL主从 |
| 监控方案 | Spring Boot Actuator | Prometheus+Grafana |
| 备份策略 | 每日自动导出 | 双机热备+OSS归档 |
在application.yml中配置:
yaml复制management:
endpoints:
web:
exposure:
include: "*"
metrics:
tags:
application: ${spring.application.name}
export:
prometheus:
enabled: true
重点监控:
这个毕设只是起点,后续可扩展:
我在实现过程中最大的体会是:好的管理系统不在于技术多先进,而在于对业务痛点的精准把握。比如那个强制关联考勤的设计,看似简单,却解决了施工行业十年未解的代领难题。