1. 项目背景与需求分析
历史文化资源的数字化管理已成为当今博物馆、图书馆等机构的重要课题。传统的人工记录和纸质档案管理方式存在效率低下、易丢失、难追溯等问题。我们团队开发的线上历史馆藏系统,正是为了解决这些痛点而生。
这个系统最核心的价值在于:
- 实现了文物信息的全生命周期数字化管理
- 提供了高效的多条件检索功能
- 支持图片、文档等多媒体资料的存储与展示
- 通过权限控制确保数据安全
从技术架构来看,我们选择了目前企业级开发中最主流的SpringBoot2+Vue3前后端分离架构。后端采用SpringBoot2提供RESTful API,前端使用Vue3实现响应式交互,数据库选用MySQL8.0,ORM框架则是MyBatis-Plus。这种技术组合既保证了系统的性能,又具有良好的可维护性和扩展性。
2. 系统架构设计
2.1 技术选型解析
后端技术栈:
- SpringBoot2:简化了Spring应用的初始搭建和开发过程,内置Tomcat服务器,开箱即用
- MyBatis-Plus:在MyBatis基础上只做增强不做改变,简化了CRUD操作
- MySQL8.0:支持JSON数据类型、窗口函数等新特性,性能较5.7提升明显
- JWT:轻量级的认证方案,适合前后端分离架构
前端技术栈:
- Vue3:组合式API更灵活,性能较Vue2提升显著
- Element Plus:基于Vue3的UI组件库,提供了丰富的现成组件
- Axios:处理HTTP请求,与后端API交互
- Vue Router:实现前端路由管理
2.2 系统架构图
整个系统采用经典的三层架构:
code复制表示层(Vue3) → 业务逻辑层(SpringBoot) → 数据访问层(MyBatis-Plus+MySQL)
前后端通过RESTful API进行通信,接口数据格式统一为JSON。考虑到安全性,所有API请求都需要携带JWT token进行身份验证。
3. 数据库设计与实现
3.1 核心表结构
系统主要包含三张核心表:馆藏文物表、用户权限表和借阅记录表。每张表的设计都遵循了数据库设计范式,并考虑了实际业务需求。
馆藏文物表(relic_info)
sql复制CREATE TABLE `relic_info` (
`relic_id` bigint NOT NULL AUTO_INCREMENT COMMENT '文物ID',
`relic_name` varchar(100) NOT NULL COMMENT '文物名称',
`relic_category` varchar(50) NOT NULL COMMENT '文物类别',
`relic_era` varchar(30) NOT NULL COMMENT '文物年代',
`relic_desc` text COMMENT '文物描述',
`relic_image` varchar(255) DEFAULT NULL COMMENT '图片路径',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`relic_id`),
KEY `idx_category` (`relic_category`),
KEY `idx_era` (`relic_era`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
用户权限表(user_info)
sql复制CREATE TABLE `user_info` (
`user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(50) NOT NULL COMMENT '用户名',
`password_hash` varchar(255) NOT NULL COMMENT '加密密码',
`user_role` varchar(20) NOT NULL DEFAULT 'normal' COMMENT '用户角色',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`last_login` datetime DEFAULT NULL COMMENT '最后登录时间',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`user_id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
3.2 索引优化策略
为了提高查询性能,我们在表中设置了适当的索引:
- 文物表按类别和年代建立了复合索引,加速分类检索
- 用户表的用户名设置了唯一索引,避免重复注册
- 借阅记录表建立了文物ID和用户ID的外键索引
注意:对于TEXT类型的字段(如文物描述)不要建立索引,MySQL对这类字段建立索引效率很低。
4. 后端核心功能实现
4.1 SpringBoot应用配置
首先创建SpringBoot项目,添加必要依赖:
xml复制<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
配置application.yml:
yaml复制server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/relic_db?useSSL=false&serverTimezone=UTC
username: root
password: yourpassword
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: deleted # 逻辑删除字段名
logic-delete-value: 1 # 逻辑已删除值
logic-not-delete-value: 0 # 逻辑未删除值
4.2 MyBatis-Plus高级应用
利用MyBatis-Plus的代码生成器可以快速生成实体类、Mapper和Service:
java复制public class CodeGenerator {
public static void main(String[] args) {
AutoGenerator generator = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
gc.setOutputDir(System.getProperty("user.dir") + "/src/main/java");
gc.setAuthor("YourName");
gc.setOpen(false);
generator.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/relic_db?useSSL=false");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("yourpassword");
generator.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("relic");
pc.setParent("com.example");
generator.setPackageInfo(pc);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setInclude("relic_info", "user_info", "borrow_record");
generator.setStrategy(strategy);
generator.execute();
}
}
4.3 JWT认证实现
创建JWT工具类处理token生成和验证:
java复制public class JwtUtil {
private static final String SECRET_KEY = "your-secret-key";
private static final long EXPIRATION_TIME = 86400000; // 24小时
public static String generateToken(String username, String role) {
return Jwts.builder()
.setSubject(username)
.claim("role", role)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public static Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
}
}
实现登录接口:
java复制@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private UserService userService;
@PostMapping("/login")
public Result login(@RequestBody LoginDTO dto) {
User user = userService.findByUsername(dto.getUsername());
if(user == null || !passwordEncoder.matches(dto.getPassword(), user.getPasswordHash())) {
return Result.error("用户名或密码错误");
}
String token = JwtUtil.generateToken(user.getUsername(), user.getRole());
return Result.success(token);
}
}
5. 前端Vue3实现
5.1 项目初始化
使用Vite创建Vue3项目:
bash复制npm init vite@latest relic-frontend --template vue
cd relic-frontend
npm install
npm install element-plus axios vue-router
配置路由router/index.js:
javascript复制import { createRouter, createWebHistory } from 'vue-router'
import Login from '../views/Login.vue'
import RelicList from '../views/RelicList.vue'
const routes = [
{ path: '/', redirect: '/login' },
{ path: '/login', component: Login },
{
path: '/relics',
component: RelicList,
meta: { requiresAuth: true }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeEach((to, from, next) => {
if(to.meta.requiresAuth && !localStorage.getItem('token')) {
next('/login')
} else {
next()
}
})
export default router
5.2 文物列表页面实现
使用Element Plus的表格组件展示文物列表:
vue复制<template>
<div class="relic-container">
<el-table :data="relicList" style="width: 100%">
<el-table-column prop="relicId" label="ID" width="80" />
<el-table-column prop="relicName" label="文物名称" />
<el-table-column prop="relicCategory" label="类别" />
<el-table-column prop="relicEra" label="年代" />
<el-table-column label="操作" width="180">
<template #default="scope">
<el-button size="small" @click="handleDetail(scope.row)">详情</el-button>
<el-button size="small" type="primary" @click="handleEdit(scope.row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
background
layout="prev, pager, next"
:total="total"
:page-size="pageSize"
@current-change="handlePageChange"
/>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
const relicList = ref([])
const total = ref(0)
const pageSize = ref(10)
const fetchRelics = async (page = 1) => {
try {
const res = await axios.get('/api/relics', {
params: { page, size: pageSize.value },
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
})
relicList.value = res.data.list
total.value = res.data.total
} catch (error) {
console.error('获取文物列表失败:', error)
}
}
onMounted(() => {
fetchRelics()
})
const handlePageChange = (page) => {
fetchRelics(page)
}
</script>
5.3 文件上传组件
实现文物图片上传功能:
vue复制<template>
<el-upload
class="upload-demo"
action="/api/upload"
:headers="headers"
:on-success="handleSuccess"
:before-upload="beforeUpload"
>
<el-button type="primary">点击上传</el-button>
<template #tip>
<div class="el-upload__tip">
只能上传jpg/png文件,且不超过2MB
</div>
</template>
</el-upload>
</template>
<script setup>
import { ref } from 'vue'
const headers = ref({
Authorization: `Bearer ${localStorage.getItem('token')}`
})
const beforeUpload = (file) => {
const isImage = file.type === 'image/jpeg' || file.type === 'image/png'
const isLt2M = file.size / 1024 / 1024 < 2
if (!isImage) {
ElMessage.error('只能上传图片文件!')
}
if (!isLt2M) {
ElMessage.error('图片大小不能超过2MB!')
}
return isImage && isLt2M
}
const handleSuccess = (response) => {
ElMessage.success('上传成功')
// 更新文物图片路径
}
</script>
6. 系统部署与运维
6.1 后端部署
使用Docker部署SpringBoot应用:
dockerfile复制# Dockerfile
FROM openjdk:11-jre-slim
WORKDIR /app
COPY target/relic-system-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
构建并运行容器:
bash复制mvn clean package
docker build -t relic-system .
docker run -d -p 8080:8080 --name relic-app relic-system
6.2 前端部署
使用Nginx部署Vue3应用:
nginx复制server {
listen 80;
server_name yourdomain.com;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
6.3 性能优化建议
-
数据库优化:
- 对常用查询字段建立合适索引
- 对大文本字段考虑分表存储
- 定期执行
ANALYZE TABLE更新统计信息
-
缓存策略:
- 使用Redis缓存热点数据
- 对文物列表实现分页缓存
- 设置合理的缓存过期时间
-
前端优化:
- 使用懒加载加载图片
- 实现组件级代码分割
- 使用CDN加速静态资源
7. 常见问题与解决方案
7.1 跨域问题
在SpringBoot中配置CORS:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.maxAge(3600);
}
}
7.2 文件上传大小限制
调整SpringBoot文件上传限制:
yaml复制spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 20MB
7.3 MyBatis-Plus逻辑删除不生效
确保实体类字段添加了@TableLogic注解:
java复制public class RelicInfo {
// ...
@TableLogic
private Integer deleted;
}
7.4 Vue3页面刷新后路由丢失
修改Nginx配置添加try_files:
nginx复制location / {
try_files $uri $uri/ /index.html;
}
8. 项目扩展方向
-
多语言支持:
- 使用i18n实现前端多语言切换
- 后端API支持根据Accept-Language头返回不同语言数据
-
3D文物展示:
- 集成Three.js实现文物3D模型展示
- 支持360度旋转查看功能
-
大数据分析:
- 使用Elasticsearch实现全文检索
- 基于文物数据生成可视化报表
-
移动端适配:
- 开发微信小程序版本
- 使用uniapp框架实现多端兼容
在实际开发过程中,我们遇到了不少挑战,比如文物图片的高清展示问题、复杂检索条件的优化等。通过引入图片懒加载、分片上传、数据库索引优化等技术手段,最终都得到了很好的解决。这个项目不仅实现了基本功能,还在性能和使用体验上做了很多优化,可以作为同类系统开发的一个参考样板。