温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系名片!
一、项目背景与意义
博物馆作为文化遗产的重要保存与展示机构,日常需要管理大量藏品信息,包括藏品的基本属性、入藏时间、来源、保存状态、展览记录等。传统依靠纸质台账或 Excel 表格的管理方式,存在信息分散、检索困难、数据易丢失、多人协作冲突等问题,难以满足现代博物馆数字化管理的需求。
基于 Spring Boot + Vue3 构建的博物馆藏品管理系统,采用前后端分离架构,能够实现藏品信息的集中化、结构化与可视化管理,提升藏品登记、查询、统计和借展管理的效率,同时为后续的数字化展陈、藏品研究提供可靠的数据基础。
二、系统需求分析
2.1 功能需求
- 用户与权限管理:支持管理员、藏品管理员、普通查询用户等角色,基于角色进行菜单与接口权限控制。
- 藏品信息管理:藏品的增删改查,支持编号、名称、类别、年代、材质、尺寸、来源、入藏时间、保存状态、图片等字段。
- 分类与年代管理:维护藏品类别(陶瓷、书画、青铜器、玉器等)与年代字典,便于统一规范录入。
- 藏品检索与统计:支持按名称、类别、年代、状态等多条件组合查询,并提供分类数量、状态分布等统计图表。
- 出入库与借展管理:记录藏品出库、入库、外借展览的申请与审批流程,保留完整操作日志。
- 图片与附件管理:支持藏品多图上传、预览与压缩存储。
2.2 非功能需求
- 系统响应时间控制在秒级,列表查询支持分页。
- 接口需具备鉴权与参数校验,防止越权访问与非法数据写入。
- 关键操作需记录日志,便于追溯。
- 前端界面简洁易用,适配常见桌面浏览器分辨率。
三、技术选型
| 层次 | 技术 | 说明 |
|---|---|---|
| 后端框架 | Spring Boot 3.x | 快速构建 RESTful 服务,内置 Tomcat |
| 持久层 | MyBatis-Plus | 简化 CRUD,支持分页与条件构造器 |
| 数据库 | MySQL 8.0 | 存储藏品、用户、日志等业务数据 |
| 鉴权 | Spring Security + JWT | 无状态登录认证与接口权限控制 |
| 前端框架 | Vue 3 + Vite | 组合式 API,构建速度快 |
| UI 组件库 | Element Plus | 表格、表单、弹窗等常用组件 |
| 状态与路由 | Pinia + Vue Router | 全局状态管理与前端路由守卫 |
| 图表 | ECharts | 藏品分类与状态统计可视化 |
四、系统架构设计
系统采用前后端分离架构,前端通过 Axios 调用后端 RESTful 接口,后端按 Controller、Service、Mapper 分层组织代码,数据库负责持久化存储。
flowchart LR
A[Vue3 前端] -->|HTTP/JSON| B[Spring Boot 后端]
B --> C[Controller 层]
C --> D[Service 层]
D --> E[Mapper 层]
E --> F[(MySQL 数据库)]
B --> G[文件存储]
A --> H[ECharts 统计展示]
4.1 后端分层说明
- Controller:接收请求、参数校验、返回统一响应体。
- Service:业务逻辑编排,如借展审批、状态流转。
- Mapper:基于 MyBatis-Plus 完成数据访问。
- Common:统一返回结果、异常处理、JWT 工具类等公共组件。
五、数据库设计
核心表包括用户表、角色表、藏品表、藏品分类表、出入库记录表与操作日志表。以下为藏品表的主要字段设计。
sql
CREATE TABLE collection_item (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
item_no VARCHAR(32) NOT NULL UNIQUE COMMENT '藏品编号',
name VARCHAR(128) NOT NULL COMMENT '藏品名称',
category_id BIGINT NOT NULL COMMENT '分类ID',
era VARCHAR(64) COMMENT '年代',
material VARCHAR(64) COMMENT '材质',
size_desc VARCHAR(128) COMMENT '尺寸描述',
origin VARCHAR(128) COMMENT '来源',
entry_date DATE COMMENT '入藏日期',
status TINYINT DEFAULT 1 COMMENT '状态:1在库 2展出 3修复 4外借',
cover_url VARCHAR(255) COMMENT '封面图地址',
description TEXT COMMENT '详细描述',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted TINYINT DEFAULT 0 COMMENT '逻辑删除标记'
) COMMENT '藏品信息表';
六、后端核心实现
6.1 统一响应与全局异常处理
java
@Data
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
Result<T> r = new Result<>();
r.setCode(200);
r.setMessage("success");
r.setData(data);
return r;
}
public static <T> Result<T> fail(String message) {
Result<T> r = new Result<>();
r.setCode(500);
r.setMessage(message);
return r;
}
}
6.2 藏品分页查询接口
java
@RestController
@RequestMapping("/api/collections")
@RequiredArgsConstructor
public class CollectionController {
private final CollectionService collectionService;
@GetMapping
public Result<Page<CollectionItem>> page(
@RequestParam(defaultValue = "1") long current,
@RequestParam(defaultValue = "10") long size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Long categoryId) {
return Result.success(collectionService.pageQuery(current, size, keyword, categoryId));
}
@PostMapping
public Result<Long> create(@RequestBody @Valid CollectionItem item) {
collectionService.save(item);
return Result.success(item.getId());
}
}
6.3 业务层分页实现
java
@Service
@RequiredArgsConstructor
public class CollectionServiceImpl extends ServiceImpl<CollectionMapper, CollectionItem>
implements CollectionService {
@Override
public Page<CollectionItem> pageQuery(long current, long size, String keyword, Long categoryId) {
LambdaQueryWrapper<CollectionItem> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.hasText(keyword), CollectionItem::getName, keyword)
.eq(categoryId != null, CollectionItem::getCategoryId, categoryId)
.orderByDesc(CollectionItem::getCreateTime);
return this.page(new Page<>(current, size), wrapper);
}
}
七、前端核心实现
7.1 请求封装与拦截器
javascript
import axios from 'axios'
import { ElMessage } from 'element-plus'
const request = axios.create({
baseURL: '/api',
timeout: 10000
})
request.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
request.interceptors.response.use(
res => {
const { code, message, data } = res.data
if (code === 200) return data
ElMessage.error(message || '请求失败')
return Promise.reject(new Error(message))
},
err => {
ElMessage.error(err.response?.data?.message || '网络异常')
return Promise.reject(err)
}
)
export default request
7.2 藏品列表页面
vue
<template>
<el-card>
<el-form :inline="true" :model="query">
<el-form-item label="关键词">
<el-input v-model="query.keyword" placeholder="藏品名称" clearable />
</el-form-item>
<el-form-item label="分类">
<el-select v-model="query.categoryId" placeholder="全部" clearable>
<el-option v-for="c in categories" :key="c.id" :label="c.name" :value="c.id" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadData">查询</el-button>
</el-form-item>
</el-form>
<el-table :data="list" v-loading="loading">
<el-table-column prop="itemNo" label="编号" width="140" />
<el-table-column prop="name" label="名称" />
<el-table-column prop="era" label="年代" width="120" />
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="statusType(row.status)">{{ statusText(row.status) }}</el-tag>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="query.current"
v-model:page-size="query.size"
:total="total"
layout="total, prev, pager, next"
@current-change="loadData"
/>
</el-card>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import request from '@/utils/request'
const list = ref([])
const total = ref(0)
const loading = ref(false)
const categories = ref([])
const query = reactive({ current: 1, size: 10, keyword: '', categoryId: null })
const loadData = async () => {
loading.value = true
try {
const page = await request.get('/collections', { params: query })
list.value = page.records
total.value = page.total
} finally {
loading.value = false
}
}
onMounted(loadData)
</script>
八、系统测试与部署
- 接口测试:使用 Postman 或 Apifox 验证登录、藏品增删改查、分页查询等接口。
- 权限测试:验证不同角色访问受限接口时返回 403。
- 部署方式 :前端执行
npm run build生成静态资源,交由 Nginx 托管;后端打包为 jar,通过java -jar运行,数据库使用 MySQL 8.0。
nginx
server {
listen 80;
server_name museum.example.com;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}









九、总结与展望
本文围绕博物馆藏品管理系统,完成了从需求分析、技术选型、架构设计到核心代码实现的完整梳理。系统采用 Spring Boot + Vue3 前后端分离方案,具备良好的可维护性与扩展性。
后续可在此基础上继续扩展:引入 Redis 缓存热点查询、使用 MinIO 或对象存储管理藏品图片、增加藏品二维码标签打印、结合 Elasticsearch 实现全文检索,以及对接数字展厅实现藏品的三维展示。