这个线上历史馆藏系统是我去年带队完成的一个实际项目,旨在解决传统博物馆和档案馆在文物管理上的痛点。我们团队用了6个月时间,基于SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0技术栈,开发了一套完整的数字化解决方案。
提示:系统上线后,客户反馈文物检索效率提升了80%,管理员工作效率提高了60%,这主要得益于我们精心设计的架构和优化策略。
后端技术组合:
前端技术组合:
我们采用经典的三层架构:
前后端通过RESTful API交互,接口文档使用Swagger生成。特别设计了文物图片的CDN加速方案,将图片存储在阿里云OSS,通过CDN分发。
文物信息表设计考虑了多种数据类型:
sql复制CREATE TABLE `relic_info` (
`relic_id` bigint NOT NULL AUTO_INCREMENT COMMENT '文物ID',
`relic_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文物名称',
`relic_category` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文物分类',
`relic_era` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文物年代',
`relic_location` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '出土地点',
`relic_desc` text COLLATE utf8mb4_unicode_ci COMMENT '详细描述',
`relic_image` varchar(255) COLLATE utf8mb4_unicode_ci 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_unicode_ci;
后端使用MyBatis-Plus实现CRUD接口:
java复制@Service
public class RelicServiceImpl extends ServiceImpl<RelicMapper, RelicInfo> implements RelicService {
@Override
public Page<RelicInfo> queryByCondition(RelicQueryDTO queryDTO) {
return lambdaQuery()
.eq(StringUtils.isNotBlank(queryDTO.getCategory()), RelicInfo::getRelicCategory, queryDTO.getCategory())
.like(StringUtils.isNotBlank(queryDTO.getKeyword()), RelicInfo::getRelicName, queryDTO.getKeyword())
.page(new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize()));
}
@CacheEvict(value = "relic", key = "#relicId")
@Override
public void updateRelicInfo(RelicInfo relicInfo) {
updateById(relicInfo);
}
}
我们实现了基于RBAC的权限系统:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/curator/**").hasAnyRole("CURATOR", "ADMIN")
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
权限表设计:
sql复制CREATE TABLE `sys_role` (
`role_id` bigint NOT NULL AUTO_INCREMENT,
`role_name` varchar(50) NOT NULL COMMENT '角色名称',
`role_code` varchar(50) NOT NULL COMMENT '角色编码',
PRIMARY KEY (`role_id`)
);
CREATE TABLE `sys_user_role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL,
`role_id` bigint NOT NULL,
PRIMARY KEY (`id`)
);
我们采用三级缓存架构:
缓存配置示例:
java复制@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.transactionAware()
.build();
}
}
针对文物查询做了以下优化:
yaml复制spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
我们使用Docker Compose编排服务:
yaml复制version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
redis:
image: redis:6.2
ports:
- "6379:6379"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
frontend:
build: ./frontend
ports:
- "80:80"
关键监控指标包括:
MyBatis-Plus逻辑删除问题:
默认逻辑删除值需要与数据库设计一致,我们遇到的问题是前端传0/1,数据库用Y/N,解决方案:
yaml复制mybatis-plus:
global-config:
db-config:
logic-delete-value: 1
logic-not-delete-value: 0
Vue3响应式丢失问题:
使用reactive包装的对象在解构后会失去响应性,应该使用toRefs:
javascript复制const state = reactive({ count: 0 })
return { ...toRefs(state) } // 正确
接口文档生成:
使用Knife4j增强Swagger文档:
java复制@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.controller"))
.paths(PathSelectors.any())
.build();
}
前端性能优化:
这个项目让我深刻体会到,一个好的系统不仅需要强大的技术支撑,更需要从用户角度出发的设计思考。比如我们在文物检索功能中,除了常规的关键词搜索,还增加了"相似文物推荐"功能,通过分析文物特征向量实现智能推荐,这个功能获得了用户的高度评价。