作为一名在Java领域深耕多年的开发者,我最近完成了一个基于SpringBoot的校园失物招领系统。这个项目源于我在大学时期经常看到同学们在群里发布寻物启事的经历,当时就想如果能有一个专门的平台来统一管理这些信息该多好。现在终于有机会把这个想法落地,下面就来详细分享这个项目的完整实现过程。
校园失物招领系统主要解决以下几个实际问题:
系统采用前后端分离架构,前端使用Vue.js+ElementUI,后端基于SpringBoot+MyBatis,数据库选用MySQL 8.0。整个开发周期约3周,经过多次迭代优化后,系统已在某高校试运行2个月,累计处理失物信息300+条,成功匹配率达78%。
后端选择SpringBoot 2.7.x版本主要基于以下考虑:
数据库层选用MySQL而非SQLServer的原因:
前端技术选型:
系统采用经典的三层架构:
code复制表现层:Vue前端 → 业务逻辑层:SpringBoot → 数据访问层:MyBatis
关键架构决策:
数据库ER图核心实体:
采用JWT+Spring Security实现安全认证,关键代码如下:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationFilter jwtFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/lost-items/**").authenticated()
.anyRequest().authenticated();
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
}
密码存储采用BCrypt强哈希算法:
java复制@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
核心实体设计:
java复制@Entity
@Table(name = "lost_item")
public class LostItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String description;
@Enumerated(EnumType.STRING)
private ItemCategory category;
private LocalDateTime lostTime;
private String lostLocation;
@ManyToOne
@JoinColumn(name = "user_id")
private User reporter;
@Enumerated(EnumType.STRING)
private ItemStatus status;
// Getters and setters
}
采用MyBatis动态SQL实现复杂查询:
xml复制<select id="searchLostItems" resultType="LostItem">
SELECT * FROM lost_item
<where>
<if test="category != null">
AND category = #{category}
</if>
<if test="location != null">
AND lost_location LIKE CONCAT('%',#{location},'%')
</if>
<if test="startTime != null">
AND lost_time >= #{startTime}
</if>
<if test="endTime != null">
AND lost_time <= #{endTime}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
ORDER BY lost_time DESC
</select>
实现基于TF-IDF的文本相似度匹配:
java复制public class MatchService {
private final TfidfVectorizer vectorizer = new TfidfVectorizer();
public List<MatchResult> findMatches(LostItem lostItem) {
List<FoundItem> candidates = foundItemRepo.findByCategoryAndStatus(
lostItem.getCategory(), ItemStatus.UNCLAIMED);
String lostText = lostItem.getTitle() + " " + lostItem.getDescription();
double[] lostVector = vectorizer.transform(lostText);
return candidates.stream()
.map(found -> {
String foundText = found.getTitle() + " " + found.getDescription();
double similarity = cosineSimilarity(
lostVector,
vectorizer.transform(foundText)
);
return new MatchResult(found, similarity);
})
.filter(r -> r.getSimilarity() > 0.5)
.sorted(Comparator.comparingDouble(MatchResult::getSimilarity).reversed())
.limit(5)
.collect(Collectors.toList());
}
private double cosineSimilarity(double[] v1, double[] v2) {
// 实现余弦相似度计算
}
}
初期采用本地文件存储遇到的挑战:
最终解决方案:
java复制@Configuration
public class StorageConfig {
@Bean
@Profile("!prod")
public StorageService localStorage() {
return new LocalStorageService("uploads");
}
@Bean
@Profile("prod")
public StorageService ossStorage() {
return new AliyunOSSStorage(
"your-endpoint",
"your-access-key",
"your-secret-key",
"your-bucket-name"
);
}
}
压力测试发现的问题:
优化措施:
java复制@Cacheable(value = "lostItems", key = "#id")
public LostItem getLostItemById(Long id) {
return lostItemRepo.findById(id).orElseThrow();
}
sql复制CREATE INDEX idx_lost_item_category_status ON lost_item(category, status);
java复制@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES));
return manager;
}
Docker Compose编排文件示例:
yaml复制version: '3.8'
services:
app:
image: your-registry/lost-and-found:1.0.0
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_URL=jdbc:mysql://db:3306/lostfound
- REDIS_HOST=redis
depends_on:
- db
- redis
db:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=yourpassword
- MYSQL_DATABASE=lostfound
volumes:
- db_data:/var/lib/mysql
redis:
image: redis:6.2
ports:
- "6379:6379"
volumes:
db_data:
集成Prometheus + Grafana监控方案:
java复制@Configuration
@EnablePrometheusEndpoint
public class MonitoringConfig implements MeterRegistryCustomizer<PrometheusMeterRegistry> {
@Override
public void customize(PrometheusMeterRegistry registry) {
registry.config().commonTags("application", "lost-and-found");
}
}
关键监控指标:
目前系统已在生产环境稳定运行,后续计划从以下几个方向进行优化:
在开发过程中最大的体会是,一个看似简单的校园应用背后需要考虑的技术细节非常多。从数据库设计到缓存策略,从安全认证到性能优化,每个环节都需要仔细权衡。特别是处理用户上传的图片信息时,如何平衡存储成本和访问效率是个值得深入探讨的问题。