1. 项目概述
校园失物招领系统是高校信息化建设中的重要组成部分。这个基于SpringBoot3+Vue3的全栈项目,旨在解决传统校园失物招领效率低下的痛点。我在实际开发中发现,很多高校仍在使用公告栏或微信群等原始方式,存在信息传播范围有限、查找困难、认领流程繁琐等问题。
这个系统采用前后端分离架构,前端使用Vue3组合式API开发,后端基于SpringBoot3构建RESTful API。特别值得一提的是,我们充分利用了Vue3的Composition API和SpringBoot3的新特性,如ProblemDetail错误处理机制,使系统在性能和开发体验上都有显著提升。
2. 技术选型与架构设计
2.1 前端技术栈
Vue3作为前端框架具有明显优势:
- Composition API使代码组织更灵活
- 更好的TypeScript支持
- 更小的打包体积
- 性能提升明显
我们搭配使用了以下关键技术:
- Pinia状态管理:替代Vuex,更简洁的API
- Element Plus组件库:提供丰富的UI组件
- Axios:处理HTTP请求
- Vue Router:实现前端路由
2.2 后端技术栈
SpringBoot3带来了多项改进:
- 原生支持GraalVM
- 改进的ProblemDetail错误处理
- 更好的Micrometer集成
- Java17基线要求
后端主要技术组件:
- Spring Security:认证授权
- Spring Data JPA:数据持久化
- Redis:缓存和会话管理
- MySQL:关系型数据库
- MinIO:对象存储(用于图片上传)
3. 核心功能实现
3.1 失物发布模块
前端实现要点:
vue复制<script setup>
import { ref } from 'vue'
import { useLostAndFoundStore } from '@/stores/lostAndFound'
const form = ref({
title: '',
category: '',
lostTime: '',
location: '',
description: '',
images: []
})
const store = useLostAndFoundStore()
const submitForm = async () => {
try {
await store.publishLostItem(form.value)
// 处理成功逻辑
} catch (error) {
// 处理错误
}
}
</script>
后端关键代码:
java复制@PostMapping("/lost-items")
public ResponseEntity<LostItemDTO> publishLostItem(
@Valid @RequestBody LostItemCreateRequest request,
@AuthenticationPrincipal UserDetails userDetails) {
LostItem lostItem = lostItemService.createLostItem(request, userDetails.getUsername());
return ResponseEntity
.created(URI.create("/api/lost-items/" + lostItem.getId()))
.body(lostItemMapper.toDTO(lostItem));
}
3.2 智能搜索功能
我们实现了基于Elasticsearch的全文检索:
- 配置ElasticsearchRepository
java复制public interface LostItemSearchRepository extends ElasticsearchRepository<LostItemES, Long> {
Page<LostItemES> findByTitleOrDescription(String title, String description, Pageable pageable);
}
- 实现搜索服务
java复制public Page<LostItemDTO> search(String keyword, Pageable pageable) {
return lostItemSearchRepository
.findByTitleOrDescription(keyword, keyword, pageable)
.map(lostItemMapper::toDTO);
}
4. 系统特色功能
4.1 图片识别匹配
我们集成了百度AI的图像识别服务:
- 前端上传图片
javascript复制const uploadImage = async (file) => {
const formData = new FormData()
formData.append('image', file)
const { data } = await axios.post('/api/image-recognition', formData)
return data.matches // 返回匹配结果
}
- 后端处理逻辑
java复制public List<LostItemDTO> recognizeAndMatch(MultipartFile image) {
// 调用百度AI接口
RecognitionResult result = baiduAIService.recognize(image);
// 在数据库中查找相似物品
return lostItemService.findSimilarItems(result);
}
4.2 实时通知系统
使用WebSocket实现实时通知:
- 配置WebSocket
java复制@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").setAllowedOrigins("*");
}
}
- 前端连接
javascript复制const socket = new SockJS('/ws')
const stompClient = Stomp.over(socket)
stompClient.connect({}, () => {
stompClient.subscribe('/topic/notifications', (message) => {
// 处理通知
})
})
5. 部署与优化
5.1 容器化部署
我们使用Docker Compose编排服务:
yaml复制version: '3.8'
services:
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
- elasticsearch
frontend:
build: ./frontend
ports:
- "80:80"
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: lost_and_found
redis:
image: redis:alpine
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.5.0
environment:
- discovery.type=single-node
5.2 性能优化措施
- 前端优化:
- 使用Vite替代Webpack
- 按需加载组件
- 图片懒加载
- 后端优化:
- 启用SpringBoot Actuator监控
- 配置HikariCP连接池
- 使用Redis缓存热点数据
6. 开发经验分享
6.1 跨域问题解决
在开发中遇到的CORS问题解决方案:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:8080")
.allowedMethods("*")
.allowedHeaders("*")
.allowCredentials(true);
}
}
6.2 文件上传处理
使用MinIO存储上传的图片:
java复制public String uploadImage(MultipartFile file) {
try {
String objectName = UUID.randomUUID() + getFileExtension(file.getOriginalFilename());
minioClient.putObject(
PutObjectArgs.builder()
.bucket("lost-and-found")
.object(objectName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build());
return objectName;
} catch (Exception e) {
throw new FileStorageException("Failed to store file", e);
}
}
7. 测试与质量保证
7.1 单元测试示例
后端服务层测试:
java复制@ExtendWith(MockitoExtension.class)
class LostItemServiceTest {
@Mock
private LostItemRepository lostItemRepository;
@InjectMocks
private LostItemService lostItemService;
@Test
void shouldReturnLostItemWhenValidId() {
// 准备测试数据
Long id = 1L;
LostItem mockItem = new LostItem();
mockItem.setId(id);
// 定义Mock行为
when(lostItemRepository.findById(id)).thenReturn(Optional.of(mockItem));
// 执行测试
LostItem result = lostItemService.getLostItemById(id);
// 验证结果
assertNotNull(result);
assertEquals(id, result.getId());
}
}
7.2 前端组件测试
使用Vitest测试Vue组件:
javascript复制import { mount } from '@vue/test-utils'
import LostItemCard from '@/components/LostItemCard.vue'
describe('LostItemCard', () => {
it('renders item title correctly', () => {
const wrapper = mount(LostItemCard, {
props: {
item: {
id: 1,
title: 'Test Item',
description: 'Test Description'
}
}
})
expect(wrapper.text()).toContain('Test Item')
})
})
8. 项目总结与展望
在实际开发过程中,我们发现以下几点特别值得注意:
- Vue3的Composition API确实大幅提升了代码的可维护性,特别是对于复杂组件
- SpringBoot3对Java17的支持使得我们可以使用record等新特性简化DTO定义
- 前后端分离架构在团队协作中优势明显,但也增加了接口管理的复杂度
未来可能的改进方向:
- 引入微服务架构拆分功能模块
- 增加移动端应用支持
- 集成更多AI能力提升匹配准确率
这个项目从技术选型到最终部署,完整实践了现代Web开发的各个环节。特别值得一提的是,我们在开发过程中积累的Docker和CI/CD经验,对于后续项目开发有很大帮助。
