1. SSM框架实战:hami音乐项目深度解析
作为一款基于SSM(Spring+SpringMVC+MyBatis)框架开发的音乐平台,hami音乐项目完整呈现了企业级Java Web应用的典型架构。我在实际开发中发现,这类项目最考验开发者对框架整合能力和业务逻辑解耦的把握。下面就以第三期开发为例,分享几个关键模块的实现细节。
提示:本文假设读者已掌握SSM基础配置,重点讲解业务层实现中的典型问题和优化方案
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心模块设计与实现
2.1 音乐推荐模块实现
推荐算法采用混合策略:
- 基于用户历史行为的协同过滤(占60%权重)
- 基于音乐标签的内容推荐(占30%权重)
- 热门歌曲补全(占10%权重)
具体实现代码示例:
java复制// RecommendationServiceImpl.java
public List<Music> recommend(Long userId) {
// 获取用户最近播放记录
List<PlayRecord> records = playRecordMapper.selectByUser(userId);
// 三种推荐策略并行执行
CompletableFuture<List<Music>> cf1 = CompletableFuture.supplyAsync(
() -> cfRecommender.recommend(records));
CompletableFuture<List<Music>> cf2 = CompletableFuture.supplyAsync(
() -> contentRecommender.recommend(userId));
CompletableFuture<List<Music>> cf3 = CompletableFuture.supplyAsync(
() -> hotMusicService.getTop100());
// 合并结果并加权
return CompletableFuture.allOf(cf1, cf2, cf3)
.thenApply(v -> {
List<Music> result = new ArrayList<>();
result.addAll(weight(cf1.join(), 0.6));
result.addAll(weight(cf2.join(), 0.3));
result.addAll(weight(cf3.join(), 0.1));
return result;
}).join();
}
2.2 高并发播放统计方案
音乐播放量统计面临的主要挑战:
- 高频写操作(每次播放都要更新)
- 数据一致性要求高
- 需要实时展示最新数据
我们最终采用的解决方案:
mermaid复制graph TD
A[客户端播放事件] --> B[Redis计数器incr]
B --> C{计数器达到100}
C -->|是| D[批量写入MySQL]
C -->|否| E[继续累积]
关键配置参数:
properties复制# redis计数器过期时间(秒)
music.playcount.expire=86400
# 批量写入阈值
music.playcount.batch-size=100
# 定时任务补偿间隔(分钟)
music.playcount.compensate=5
3. 性能优化实践
3.1 MyBatis二级缓存优化
在音乐列表查询场景中,我们遇到缓存命中率低的问题。通过以下改进显著提升性能:
- 细粒度缓存策略:
xml复制<!-- MusicMapper.xml -->
<cache eviction="LRU" flushInterval="3600000"
size="512" readOnly="true"/>
- 按业务维度拆分缓存:
- 热门音乐缓存:设置较短过期时间(10分钟)
- 分类音乐缓存:设置较长过期时间(2小时)
- 个性化推荐缓存:按用户ID隔离
3.2 Spring事务优化
在用户收藏音乐的业务中,原始实现存在事务范围过大的问题:
优化前:
java复制@Transactional
public void addFavorite(Long userId, Long musicId) {
// 1. 验证音乐存在
Music music = musicMapper.selectById(musicId);
if(music == null) throw new BizException("音乐不存在");
// 2. 检查是否已收藏
if(favoriteMapper.exists(userId, musicId)) {
return;
}
// 3. 添加收藏记录
favoriteMapper.insert(new Favorite(userId, musicId));
// 4. 更新音乐收藏数
musicMapper.incrementFavoriteCount(musicId);
// 5. 记录用户行为
userBehaviorService.log(userId, "FAVORITE", musicId);
}
优化后:
java复制public void addFavorite(Long userId, Long musicId) {
// 非事务操作前置检查
Music music = musicMapper.selectById(musicId);
if(music == null) throw new BizException("音乐不存在");
if(favoriteMapper.exists(userId, musicId)) {
return;
}
// 仅对核心操作添加事务
doAddFavorite(userId, musicId);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
protected void doAddFavorite(Long userId, Long musicId) {
favoriteMapper.insert(new Favorite(userId, musicId));
musicMapper.incrementFavoriteCount(musicId);
}
// 异步记录行为日志
@Async
public void logUserBehavior(Long userId, String action, Long musicId) {
userBehaviorService.log(userId, action, musicId);
}
4. 典型问题排查实录
4.1 音乐搜索性能问题
现象:当搜索关键词包含特殊符号时,响应时间从平均200ms飙升到2s+
排查过程:
- 检查MySQL慢查询日志,发现like查询未走索引
- 分析发现MyBatis生成的SQL未对特殊字符转义
- 确认是MySQL的like语法特性导致
解决方案:
java复制// 在Service层添加过滤处理
public List<Music> search(String keyword) {
// 过滤特殊字符
String safeKeyword = keyword.replaceAll("[%_]", "\\\\$0");
return musicMapper.searchByName(safeKeyword);
}
// MyBatis映射文件调整
<select id="searchByName" resultType="Music">
SELECT * FROM music
WHERE name LIKE CONCAT('%', #{keyword}, '%') ESCAPE '\\'
</select>
4.2 文件上传内存溢出
现象:上传大尺寸音乐文件时频繁出现OOM
根本原因:
Spring默认使用内存缓存上传文件,当并发上传大文件时会耗尽堆内存
优化方案:
java复制@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
// 临时目录存储阈值(超过1MB就写入磁盘)
factory.setMaxFileSize(DataSize.ofMegabytes(1));
factory.setLocation(System.getProperty("java.io.tmpdir"));
return factory.createMultipartConfig();
}
// 控制器添加流式处理
@PostMapping("/upload")
public String upload(@RequestParam MultipartFile file) {
try (InputStream is = file.getInputStream()) {
storageService.streamUpload(is, file.getOriginalFilename());
return "success";
}
}
5. 部署架构优化
5.1 生产环境配置建议
推荐采用以下部署架构:
code复制前端Nginx(负载均衡+静态资源)
↓
Spring应用集群(无状态部署)
↓
Redis集群(缓存+会话)
↓
MySQL主从(读写分离)
↓
Elasticsearch(搜索专用)
关键配置项:
yaml复制# application-prod.yml
spring:
datasource:
url: jdbc:mysql://master:3306,slave:3306/hami?useSSL=false
hikari:
readOnly: true # 从库连接标记
redis:
cluster:
nodes: redis1:6379,redis2:6379,redis3:6379
5.2 监控方案实施
我们集成的监控组件:
- Prometheus + Grafana(系统指标)
- SkyWalking(分布式追踪)
- ELK(日志分析)
关键监控指标示例:
java复制// 自定义业务指标
@RestController
public class MetricsController {
private final Counter playCounter;
public MetricsController(MeterRegistry registry) {
playCounter = registry.counter("music.play.count");
}
@PostMapping("/play")
public void recordPlay(@RequestParam Long musicId) {
playCounter.increment();
// ...业务逻辑
}
}
6. 项目演进建议
根据实际运营数据,后续可重点优化:
- 推荐算法升级:引入深度学习模型
- 缓存策略细化:按地域分级缓存
- 灰度发布方案:基于用户标签的AB测试
- 容灾方案完善:多活数据中心部署
在迭代过程中,特别要注意保持配置的灵活性。比如我们将所有算法参数都设计为可动态调整:
java复制// 配置中心管理算法参数
@RefreshScope
@Service
public class RecommendService {
@Value("${recommend.cf.weight:0.6}")
private double cfWeight;
@Value("${recommend.content.weight:0.3}")
private double contentWeight;
// 可随时通过配置中心调整权重
}
