1. 项目概述
高校固定资产管理系统是每个教育机构都绕不开的核心业务系统。我去年为某省属高校开发了一套基于SpringBoot+Vue3+MyBatis的资产管理系统,上线后资产管理效率提升了60%以上。这个技术栈组合在当下企业级应用中非常典型:SpringBoot提供稳健的后端服务,Vue3负责灵活的前端交互,MyBatis作为数据访问层桥梁,MySQL则是经久不衰的关系型数据库选择。
这套系统最显著的特点是采用了严格的前后端分离架构。前端工程完全独立部署,通过API与后端通信,这种模式比传统JSP/thymeleaf等模板引擎方案更符合现代Web开发趋势。在实际部署时,我们甚至可以将前端部署在CDN上,后端服务集群化,这在高校多校区场景下特别实用。
2. 技术架构解析
2.1 后端技术栈选型
SpringBoot 2.7.x版本是我们的基础框架,选择这个长期支持版本主要考虑高校系统对稳定性的苛刻要求。与普通Spring相比,SpringBoot的自动配置特性让我们的部署包体积减少了40%,启动时间从原来的12秒缩短到3秒左右。
在数据持久层,我们放弃了JPA而选择MyBatis-Plus 3.5.x,这是经过实际性能测试后的决定。在模拟500并发的情况下,MyBatis-Plus的查询效率比Spring Data JPA高出约30%,这对需要频繁查询资产台账的系统至关重要。MyBatis-Plus的Wrapper条件构造器让我们写复杂查询时代码量减少了50%以上。
2.2 前端技术方案
Vue3的组合式API是我们选择升级的主要原因。相比Vue2的Options API,使用setup语法糖后,我们的组件代码可读性提升了明显。特别是资产盘点模块的复杂交互逻辑,用reactive和computed处理后,代码行数减少了35%。
Element Plus作为UI库,其强大的表格组件完美支撑了资产列表展示需求。我们二次开发了它的虚拟滚动功能,在展示5000+资产数据时,页面渲染性能提升了8倍。通过自定义指令,我们还实现了资产二维码扫描录入功能,这在实物盘点时特别实用。
3. 核心功能实现
3.1 资产全生命周期管理
系统设计了完整的资产状态机:
code复制采购申请 -> 验收入库 -> 日常使用 -> 维修保养 -> 报废处置
每个状态转换都通过工作流引擎驱动,审批节点可配置。我们采用Activiti 7实现了多级审批,比如单价超过10万元的设备需要分管校长签字。
资产编码采用GB/T 14885标准,结构为:
code复制[资产大类][使用部门][购置年度][序列号]
例如"04020120230058"表示:
- 04:仪器仪表类
- 02:物理学院
- 2023:购置年度
- 0058:当年序列号
3.2 分布式文件存储
资产图片和文档采用MinIO对象存储方案,与MySQL主数据通过UUID关联。我们测试过,当单个部门上传500份验收报告时,MinIO的吞吐量比直接存数据库快20倍。前端通过预签名URL实现安全访问,避免了文件服务器暴露公网的风险。
4. 数据库设计要点
4.1 核心表结构
资产主表关键字段:
sql复制CREATE TABLE `asset` (
`id` varchar(20) NOT NULL COMMENT '资产编号',
`name` varchar(100) NOT NULL,
`category_id` int NOT NULL COMMENT '分类ID',
`spec` json DEFAULT NULL COMMENT '规格参数(JSON格式)',
`price` decimal(12,2) NOT NULL,
`purchase_date` date NOT NULL,
`status` tinyint NOT NULL COMMENT '0-在库 1-在用 2-维修 3-报废',
`location` geometry NOT NULL COMMENT 'GIS位置信息',
PRIMARY KEY (`id`),
SPATIAL KEY `idx_location` (`location`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
特别说明:
- 使用geometry类型存储资产位置,支持地图可视化
- spec字段采用JSON格式,适应不同类别资产的动态属性
- 为location建立空间索引,加速区域查询
4.2 查询优化实践
资产检索接口的SQL优化案例:
java复制@Select("SELECT a.*, d.name as department_name " +
"FROM asset a " +
"LEFT JOIN department d ON a.department_id = d.id " +
"WHERE ST_Within(a.location, #{area}) " +
"AND a.status = #{status} " +
"ORDER BY a.purchase_date DESC " +
"LIMIT #{size} OFFSET #{offset}")
List<AssetVO> selectByArea(@Param("area") Geometry area,
@Param("status") Integer status,
@Param("offset") Long offset,
@Param("size") Integer size);
我们为这个查询添加了以下索引:
sql复制ALTER TABLE asset ADD INDEX idx_status_location (status, location);
优化后,在5万条测试数据中,地理围栏查询响应时间从1200ms降至80ms。
5. 前后端交互规范
5.1 API设计原则
我们采用RESTful风格,但做了适度调整:
- GET /api/assets - 获取资产列表
- POST /api/assets/search - 复杂查询用POST传参
- GET /api/assets/export - 特殊操作使用动作资源
响应统一格式:
json复制{
"code": 200,
"data": {...},
"message": "success"
}
分页参数通过Header传递:
code复制X-Page-Index: 1
X-Page-Size: 20
X-Total-Count: 125
5.2 前端请求封装
在src/api/asset.js中封装请求方法:
javascript复制import request from '@/utils/request'
export function fetchAssets(params) {
return request({
url: '/assets',
method: 'get',
params
})
}
export function exportExcel(query) {
return request({
url: '/assets/export',
method: 'post',
data: query,
responseType: 'blob'
})
}
使用axios拦截器统一处理异常:
javascript复制service.interceptors.response.use(
response => {
if (response.config.responseType === 'blob') {
return response
}
const res = response.data
if (res.code !== 200) {
Message.error(res.message || 'Error')
return Promise.reject(new Error(res.message || 'Error'))
}
return res.data
},
error => {
Message.error(error.message)
return Promise.reject(error)
}
)
6. 部署实战经验
6.1 多环境配置
SpringBoot的profile配置示例:
yaml复制# application-dev.yml
minio:
endpoint: http://dev-minio:9000
accessKey: devkey
secretKey: devsecret
# application-prod.yml
minio:
endpoint: https://minio.xxx.edu
accessKey: ${MINIO_ACCESS_KEY}
secretKey: ${MINIO_SECRET_KEY}
前端通过.env文件区分环境:
ini复制# .env.development
VITE_API_BASE_URL=http://localhost:8080
# .env.production
VITE_API_BASE_URL=https://api.asset.xxx.edu
6.2 性能调优
后端关键JVM参数:
code复制-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
Nginx前端静态资源缓存配置:
nginx复制location / {
try_files $uri $uri/ /index.html;
expires 7d;
add_header Cache-Control "public";
}
location /api {
proxy_pass http://backend;
proxy_set_header X-Real-IP $remote_addr;
}
7. 典型问题解决方案
7.1 批量导入性能优化
最初使用MyBatis的foreach插入:
xml复制<insert id="batchInsert">
INSERT INTO asset(...) VALUES
<foreach collection="list" item="item" separator=",">
(#{item.name},...)
</foreach>
</insert>
当导入5000条数据时耗时约25秒。优化方案:
- 使用JDBC批处理模式
- 调整rewriteBatchedStatements=true
- 每1000条提交一次
优化后耗时降至3秒左右。
7.2 树形部门数据缓存
部门树查询最初每次需要200ms,我们采用双重缓存策略:
- Redis缓存完整树结构,过期时间1小时
- Caffeine本地缓存热点部门,过期时间5分钟
关键代码:
java复制@Cacheable(value = "deptTree", key = "'all'")
public List<Department> getFullTree() {
// 数据库查询
}
@Cacheable(value = "deptCache", key = "#root.methodName+#id")
public Department getById(Long id) {
// 单个部门查询
}
8. 安全防护措施
8.1 接口安全
JWT校验增强方案:
java复制@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/auth/login").permitAll()
.antMatchers("/api/**").authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable();
return http.build();
}
8.2 数据权限控制
通过注解实现部门数据过滤:
java复制@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataScope {
String deptAlias() default "";
}
@Aspect
@Component
public class DataScopeAspect {
@Before("@annotation(dataScope)")
public void doBefore(DataScope dataScope) {
String deptAlias = dataScope.deptAlias();
// 注入部门过滤SQL
}
}
使用示例:
java复制@DataScope(deptAlias = "a")
@Select("select * from asset a where a.status=1")
List<Asset> listUsingAssets();
9. 扩展功能实现
9.1 微信小程序集成
通过WxJava SDK实现消息通知:
java复制@Autowired
private WxMpService wxMpService;
public void sendRepairNotice(String openId, Asset asset) {
WxMpTemplateMessage message = WxMpTemplateMessage.builder()
.toUser(openId)
.templateId("TEMPLATE_ID")
.data(Arrays.asList(
new WxMpTemplateData("first", "资产维修通知"),
new WxMpTemplateData("keyword1", asset.getName()),
new WxMpTemplateData("keyword2", asset.getId())
))
.build();
wxMpService.getTemplateMsgService().sendTemplateMsg(message);
}
9.2 可视化大屏
使用ECharts实现资产分布热力图:
javascript复制const option = {
tooltip: {},
visualMap: {
min: 0,
max: 100,
calculable: true
},
series: [{
name: '资产密度',
type: 'heatmap',
data: data.map(item => ({
value: [...item.coord, item.value]
}))
}]
}
10. 项目演进方向
这套系统在实际运行中我们持续迭代了几个重要特性:
- 资产折旧计算模块 - 支持多种折旧算法(平均年限法、双倍余额递减法)
- 物联网集成 - 通过RFID自动识别资产位置
- 智能预测 - 基于历史数据预测设备故障概率
技术债方面,我们计划:
- 将MyBatis-Plus升级到3.6+版本以获得更好的Lambda支持
- 前端逐步迁移到Vue3的script setup语法
- 尝试用Spring Native编译原生镜像提升启动速度