Compare commits

...

4 Commits

Author SHA1 Message Date
Y1NanPing 34cb7047bb Merge remote-tracking branch 'Y1NanPing/master'
# Conflicts:
#	service-client/service-album-client/src/main/java/com/atguigu/tingshu/album/AlbumFeignClient.java
#	service-client/service-album-client/src/main/java/com/atguigu/tingshu/album/impl/AlbumDegradeFeignClient.java
#	service-client/service-album-client/target/classes/com/atguigu/tingshu/album/AlbumFeignClient.class
#	service-client/service-album-client/target/classes/com/atguigu/tingshu/album/impl/AlbumDegradeFeignClient.class
#	service/service-album/src/main/java/com/atguigu/tingshu/album/api/BaseCategoryApiController.java
#	service/service-album/src/main/java/com/atguigu/tingshu/album/api/TrackInfoApiController.java
#	service/service-album/src/main/java/com/atguigu/tingshu/album/mapper/TrackInfoMapper.java
#	service/service-album/src/main/java/com/atguigu/tingshu/album/service/TrackInfoService.java
#	service/service-album/src/main/java/com/atguigu/tingshu/album/service/impl/AlbumInfoServiceImpl.java
#	service/service-album/src/main/java/com/atguigu/tingshu/album/service/impl/BaseCategoryServiceImpl.java
#	service/service-album/src/main/java/com/atguigu/tingshu/album/service/impl/TrackInfoServiceImpl.java
#	service/service-album/src/main/resources/mapper/TrackInfoMapper.xml
#	service/service-album/target/classes/com/atguigu/tingshu/album/api/AlbumInfoApiController.class
#	service/service-album/target/classes/com/atguigu/tingshu/album/api/BaseCategoryApiController.class
#	service/service-album/target/classes/com/atguigu/tingshu/album/api/TrackInfoApiController.class
#	service/service-album/target/classes/com/atguigu/tingshu/album/mapper/TrackInfoMapper.class
#	service/service-album/target/classes/com/atguigu/tingshu/album/service/AlbumInfoService.class
#	service/service-album/target/classes/com/atguigu/tingshu/album/service/TrackInfoService.class
#	service/service-album/target/classes/com/atguigu/tingshu/album/service/impl/AlbumInfoServiceImpl.class
#	service/service-album/target/classes/com/atguigu/tingshu/album/service/impl/BaseCategoryServiceImpl.class
#	service/service-album/target/classes/com/atguigu/tingshu/album/service/impl/TrackInfoServiceImpl.class
#	service/service-album/target/classes/mapper/TrackInfoMapper.xml
#	service/service-search/src/main/java/com/atguigu/tingshu/search/api/SearchApiController.java
#	service/service-search/src/main/java/com/atguigu/tingshu/search/service/SearchService.java
#	service/service-search/src/main/java/com/atguigu/tingshu/search/service/impl/SearchServiceImpl.java
#	service/service-search/target/classes/com/atguigu/tingshu/search/api/SearchApiController.class
#	service/service-search/target/classes/com/atguigu/tingshu/search/service/SearchService.class
#	service/service-search/target/classes/com/atguigu/tingshu/search/service/impl/SearchServiceImpl.class
#	service/service-user/target/classes/com/atguigu/tingshu/user/service/impl/UserInfoServiceImpl.class
2025-07-30 08:41:30 +08:00
Y1NanPing 38e090bed4 第10天 redis 2025-07-30 08:40:30 +08:00
Y1NanPing 4bd206c1a1 第10天 redis 2025-07-30 08:40:00 +08:00
Y1NanPing 0924a97aac 第10天 redis 2025-07-30 08:39:51 +08:00
63 changed files with 1091 additions and 33 deletions

View File

@ -51,11 +51,11 @@
<artifactId>spring-boot-starter-data-redis</artifactId> <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> </dependency>
<!-- redisson -->
<!-- <dependency>--> <dependency>
<!-- <groupId>org.redisson</groupId>--> <groupId>org.redisson</groupId>
<!-- <artifactId>redisson-spring-boot-starter</artifactId>--> <artifactId>redisson-spring-boot-starter</artifactId>
<!-- </dependency>--> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@ -87,6 +87,32 @@
<groupId>com.alibaba.cloud</groupId> <groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
<version>2.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
<version>12.5</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -0,0 +1,30 @@
package com.atguigu.tingshu.common.cache;
import com.atguigu.tingshu.common.constant.RedisConstant;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface GuiGuCache {
/**
* 缓存业务数据或锁前缀
* @return
*/
String prefix() default "";
/**
* 缓存时间
* @return
*/
long ttl() default RedisConstant.ALBUM_TIMEOUT;
/**
* 时间单位
* @return
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
}

View File

@ -0,0 +1,81 @@
package com.atguigu.tingshu.common.cache;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import com.atguigu.tingshu.common.constant.RedisConstant;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.atguigu.tingshu.common.constant.RedisConstant.ALBUM_LOCK_EXPIRE_PX2;
import static com.atguigu.tingshu.common.constant.RedisConstant.ALBUM_LOCK_WAIT_PX1;
@Aspect
@Slf4j
@Component
public class GuiGuCacheAspect {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private RedissonClient redissonClient;
@Around("@annotation(guiGuCache)")
public Object doBasicProfiling(ProceedingJoinPoint pjp, GuiGuCache guiGuCache) throws Throwable {
try {
//1.优先从Redis缓存中获取业务数据,命中缓存响应即可
//1.1 构建缓存业务数据Key 形式=前缀+目标方法参数采用_拼接
List<Object> argsList = Arrays.asList(pjp.getArgs());
String args = "none";
if (CollUtil.isNotEmpty(argsList)) {
//将argsList转换成字符串,用_拼接
args = argsList.stream()
.map(Object::toString)
.collect(Collectors.joining("_"));
}
//用写这个注解的prefix属性拼接上参数args得到缓存数据Key
String dataKey = guiGuCache.prefix() + args;
Object result = redisTemplate.opsForValue().get(dataKey);
if (result != null) {
return result;
}
//2.尝试获取分布式锁
//2.1 构建锁Key
String lockKey = dataKey + RedisConstant.CACHE_LOCK_SUFFIX;
RLock lock = redissonClient.getLock(lockKey);
boolean flag= lock.tryLock(ALBUM_LOCK_WAIT_PX1, ALBUM_LOCK_EXPIRE_PX2, TimeUnit.SECONDS);
//3.获取锁成功执行目标方法将结果缓存到Redis中业务执行结束释放锁
if(flag){
try {
//3.1 执行查询数据库方法
result = pjp.proceed();
//设置超时时间,防止缓存雪崩
long ttl = RedisConstant.ALBUM_TIMEOUT + RandomUtil.randomInt(600);
return result;
} finally {
//3.3 释放锁
lock.unlock();
}
}else{
//4.获取锁失败自旋
TimeUnit.MILLISECONDS.sleep(30);
return this.doBasicProfiling(pjp, guiGuCache);
}
} catch (Throwable e) {
log.error("Redis服务不可用请检查Redis服务是否正常");
return pjp.proceed();
}
}
}

View File

@ -1,6 +1,9 @@
package com.atguigu.tingshu.common.config.pool; package com.atguigu.tingshu.common.config.pool;
import com.atguigu.tingshu.common.zipkin.ZipkinHelper;
import com.atguigu.tingshu.common.zipkin.ZipkinTaskDecorator;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@ -15,6 +18,8 @@ import java.util.concurrent.*;
@Configuration @Configuration
public class ThreadPoolConfig { public class ThreadPoolConfig {
@Autowired(required = false)
ZipkinHelper zipkinHelper;
/** /**
* 基于JDKJUC提供线程池Class * 基于JDKJUC提供线程池Class
*/ */
@ -65,6 +70,8 @@ public class ThreadPoolConfig {
taskExecutor.setAwaitTerminationSeconds(300); taskExecutor.setAwaitTerminationSeconds(300);
// 线程不够用时由调用的线程处理该任务 // 线程不够用时由调用的线程处理该任务
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//设置解决zipkin链路追踪不完整装饰器对象
taskExecutor.setTaskDecorator(new ZipkinTaskDecorator(zipkinHelper));
return taskExecutor; return taskExecutor;
} }
} }

View File

@ -52,6 +52,9 @@ public class RedisConstant {
//更新声音统计前缀 //更新声音统计前缀
public static final String USER_TRACK_REPEAT_STAT_PREFIX = "user:track:"; public static final String USER_TRACK_REPEAT_STAT_PREFIX = "user:track:";
public static final String USER_SEARCH_REPEAT_STAT_PREFIX = "user:search:";
//重复锁定账户 //重复锁定账户

View File

@ -0,0 +1,147 @@
package com.atguigu.tingshu.common.zipkin;
import brave.Span;
import brave.Tracer;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.Callable;
@Component
public class ZipkinHelper {
@Autowired(required = false)
private Tracer tracer;
public ZipkinHelper() {
}
public Runnable wrap(Runnable runnable) {
Span currentSpan = this.tracer.currentSpan();
return () -> {
Tracer.SpanInScope scope = this.tracer.withSpanInScope(currentSpan);
Throwable var4 = null;
try {
Span span = this.tracer.nextSpan();
MDC.put("X-B3-TraceId", span.context().traceIdString());
MDC.put("X-B3-SpanId", span.context().spanIdString());
MDC.put("X-B3-ParentSpanId", span.context().parentIdString());
span.name("new_thread_started").kind(Span.Kind.SERVER).tag("thread_id", Thread.currentThread().getId() + "").tag("thread_name", Thread.currentThread().getName() + "");
span.start();
try {
Tracer.SpanInScope ws = this.tracer.withSpanInScope(span);
Throwable var7 = null;
try {
runnable.run();
} catch (Throwable var44) {
var7 = var44;
throw var44;
} finally {
if (ws != null) {
if (var7 != null) {
try {
ws.close();
} catch (Throwable var43) {
var7.addSuppressed(var43);
}
} else {
ws.close();
}
}
}
} catch (Error | RuntimeException var46) {
span.error(var46);
throw var46;
} finally {
span.finish();
}
} catch (Throwable var48) {
var4 = var48;
throw var48;
} finally {
if (scope != null) {
if (var4 != null) {
try {
scope.close();
} catch (Throwable var42) {
var4.addSuppressed(var42);
}
} else {
scope.close();
}
}
}
};
}
public <T> Callable<T> wrap(Callable<T> callable) {
Span currentSpan = this.tracer.currentSpan();
return () -> {
Tracer.SpanInScope scope = this.tracer.withSpanInScope(currentSpan);
Throwable var4 = null;
T var8;
try {
Span span = this.tracer.nextSpan();
MDC.put("X-B3-TraceId", span.context().traceIdString());
MDC.put("X-B3-SpanId", span.context().spanIdString());
MDC.put("X-B3-ParentSpanId", span.context().parentIdString());
span.name("new_thread_started").kind(Span.Kind.SERVER).tag("thread_id", Thread.currentThread().getId() + "").tag("thread_name", Thread.currentThread().getName() + "");
span.start();
try {
Tracer.SpanInScope ws = this.tracer.withSpanInScope(span);
Throwable var7 = null;
try {
var8 = callable.call();
} catch (Throwable var45) {
var8 = (T) var45;
var7 = var45;
throw var45;
} finally {
if (ws != null) {
if (var7 != null) {
try {
ws.close();
} catch (Throwable var44) {
var7.addSuppressed(var44);
}
} else {
ws.close();
}
}
}
} catch (Error | RuntimeException var47) {
span.error(var47);
throw var47;
} finally {
span.finish();
}
} catch (Throwable var49) {
var4 = var49;
throw var49;
} finally {
if (scope != null) {
if (var4 != null) {
try {
scope.close();
} catch (Throwable var43) {
var4.addSuppressed(var43);
}
} else {
scope.close();
}
}
}
return var8;
};
}
}

View File

@ -0,0 +1,36 @@
package com.atguigu.tingshu.common.zipkin;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.task.TaskDecorator;
/**
* @Author: chenyangu
* @Date: 2021/8/16 17:05
* @Description: zipkin装饰器
*/
@Slf4j
public class ZipkinTaskDecorator implements TaskDecorator {
private ZipkinHelper zipkinHelper;
public ZipkinTaskDecorator(ZipkinHelper zipkinHelper) {
this.zipkinHelper = zipkinHelper;
}
@Override
public Runnable decorate(Runnable runnable) {
return zipkinHelper.wrap(runnable);
}
}

View File

@ -3,9 +3,13 @@ package com.atguigu.tingshu.vo.album;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import java.io.Serializable;
@Data @Data
@Schema(description = "TrackStatMqVo") @Schema(description = "TrackStatMqVo")
public class TrackStatMqVo { public class TrackStatMqVo implements Serializable {
//防止 生产消息 消费消息使用对象不是同一个
private static final long serialVersionUID = 1L;
@Schema(description = "业务编号:去重使用") @Schema(description = "业务编号:去重使用")
private String businessNo; private String businessNo;

View File

@ -0,0 +1,27 @@
package com.atguigu.tingshu.vo.search;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
@Data
@Schema(description = "SearchStatMqVo")
public class SearchStatMqVo implements Serializable {
//防止 生产消息 消费消息使用对象不是同一个
private static final long serialVersionUID = 1L;
@Schema(description = "业务编号:去重使用")
private String businessNo;
@Schema(description = "专辑id")
private Long albumId;
@Schema(description = "声音id")
private Long trackId;
@Schema(description = "统计类型")
private String statType;
@Schema(description = "更新数目")
private Integer count;
}

View File

@ -3,6 +3,7 @@ package com.atguigu.tingshu.album;
import com.atguigu.tingshu.album.impl.AlbumDegradeFeignClient; import com.atguigu.tingshu.album.impl.AlbumDegradeFeignClient;
import com.atguigu.tingshu.common.result.Result; import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.model.album.AlbumInfo; import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.BaseCategory1;
import com.atguigu.tingshu.model.album.BaseCategory3; import com.atguigu.tingshu.model.album.BaseCategory3;
import com.atguigu.tingshu.model.album.BaseCategoryView; import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.atguigu.tingshu.vo.album.AlbumStatVo; import com.atguigu.tingshu.vo.album.AlbumStatVo;
@ -45,6 +46,9 @@ public interface AlbumFeignClient {
@GetMapping("/albumInfo/getAlbumStatVo/{albumId}") @GetMapping("/albumInfo/getAlbumStatVo/{albumId}")
public Result<AlbumStatVo>getAlbumStatVo(@PathVariable Long albumId); public Result<AlbumStatVo>getAlbumStatVo(@PathVariable Long albumId);
@GetMapping("/category/findAllCategory1")
Result<List<BaseCategory1>> findAllCategory1();
} }

View File

@ -4,6 +4,7 @@ package com.atguigu.tingshu.album.impl;
import com.atguigu.tingshu.album.AlbumFeignClient; import com.atguigu.tingshu.album.AlbumFeignClient;
import com.atguigu.tingshu.common.result.Result; import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.model.album.AlbumInfo; import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.BaseCategory1;
import com.atguigu.tingshu.model.album.BaseCategory3; import com.atguigu.tingshu.model.album.BaseCategory3;
import com.atguigu.tingshu.model.album.BaseCategoryView; import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.atguigu.tingshu.vo.album.AlbumStatVo; import com.atguigu.tingshu.vo.album.AlbumStatVo;
@ -40,4 +41,10 @@ public class AlbumDegradeFeignClient implements AlbumFeignClient {
log.error("[专辑服务]提供远程调用方法getAlbumStatVo执行服务降级"); log.error("[专辑服务]提供远程调用方法getAlbumStatVo执行服务降级");
return null; return null;
} }
@Override
public Result<List<BaseCategory1>> findAllCategory1() {
log.error("[专辑服务]提供远程调用方法findAllCategory1执行服务降级");
return null;
}
} }

View File

@ -71,7 +71,7 @@ public class AlbumInfoApiController {
@Operation(summary = "查询专辑信息,包含专辑标签列表") @Operation(summary = "查询专辑信息,包含专辑标签列表")
@GetMapping("/albumInfo/getAlbumInfo/{id}") @GetMapping("/albumInfo/getAlbumInfo/{id}")
public Result<AlbumInfo> getAlbumInfo(@PathVariable Long id) { public Result<AlbumInfo> getAlbumInfo(@PathVariable Long id) {
AlbumInfo albumInfo = albumInfoService.getAlbumInfo(id); AlbumInfo albumInfo = albumInfoService.getAlbumInfoFromDB(id);
return Result.ok(albumInfo); return Result.ok(albumInfo);
} }
@Operation(summary = "更新专辑信息") @Operation(summary = "更新专辑信息")

View File

@ -4,8 +4,10 @@ import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.service.BaseCategoryService; import com.atguigu.tingshu.album.service.BaseCategoryService;
import com.atguigu.tingshu.common.result.Result; import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.model.album.BaseAttribute; import com.atguigu.tingshu.model.album.BaseAttribute;
import com.atguigu.tingshu.model.album.BaseCategory1;
import com.atguigu.tingshu.model.album.BaseCategory3; import com.atguigu.tingshu.model.album.BaseCategory3;
import com.atguigu.tingshu.model.album.BaseCategoryView; import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -66,6 +68,16 @@ public class BaseCategoryApiController {
return Result.ok(jsonObject); return Result.ok(jsonObject);
} }
@Operation(summary = "查询所有一级分类集合")
@GetMapping("/category/findAllCategory1")
public Result<List<BaseCategory1>> findAllCategory1(){
List<BaseCategory1> list=baseCategoryService.list(
new LambdaQueryWrapper<BaseCategory1>()
.select(BaseCategory1::getId,BaseCategory1::getName)
);
return Result.ok(list);
}

View File

@ -2,6 +2,7 @@ package com.atguigu.tingshu.album.api;
import com.atguigu.tingshu.album.service.TrackInfoService; import com.atguigu.tingshu.album.service.TrackInfoService;
import com.atguigu.tingshu.album.service.VodService; import com.atguigu.tingshu.album.service.VodService;
import com.atguigu.tingshu.common.cache.GuiGuCache;
import com.atguigu.tingshu.common.login.GuiGuLogin; import com.atguigu.tingshu.common.login.GuiGuLogin;
import com.atguigu.tingshu.common.result.Result; import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.util.AuthContextHolder; import com.atguigu.tingshu.common.util.AuthContextHolder;
@ -10,6 +11,8 @@ import com.atguigu.tingshu.query.album.TrackInfoQuery;
import com.atguigu.tingshu.vo.album.AlbumTrackListVo; import com.atguigu.tingshu.vo.album.AlbumTrackListVo;
import com.atguigu.tingshu.vo.album.TrackInfoVo; import com.atguigu.tingshu.vo.album.TrackInfoVo;
import com.atguigu.tingshu.vo.album.TrackListVo; import com.atguigu.tingshu.vo.album.TrackListVo;
import com.atguigu.tingshu.vo.album.TrackStatVo;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@ -18,6 +21,7 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.Map; import java.util.Map;
@Tag(name = "声音管理") @Tag(name = "声音管理")
@ -34,6 +38,7 @@ public class TrackInfoApiController {
@Autowired @Autowired
private VodService vodService; private VodService vodService;
//用MultipartFile来接收文件 //用MultipartFile来接收文件
@Operation(summary = "将音频文件上传到腾讯云点播平台") @Operation(summary = "将音频文件上传到腾讯云点播平台")
@PostMapping("/trackInfo/uploadTrack") @PostMapping("/trackInfo/uploadTrack")
@ -90,6 +95,7 @@ public class TrackInfoApiController {
* @return * @return
*/ */
@Operation(summary = "查询声音信息") @Operation(summary = "查询声音信息")
@GuiGuCache(prefix = "album:trackInfo:")
@GetMapping("/trackInfo/getTrackInfo/{id}") @GetMapping("/trackInfo/getTrackInfo/{id}")
public Result<TrackInfo> getTrackInfo(@PathVariable Long id){ public Result<TrackInfo> getTrackInfo(@PathVariable Long id){
TrackInfo trackInfo = trackInfoService.getById(id); TrackInfo trackInfo = trackInfoService.getById(id);
@ -144,6 +150,20 @@ public class TrackInfoApiController {
//4.响应结果 //4.响应结果
return Result.ok(pageInfo); return Result.ok(pageInfo);
} }
/**
* 获取声音统计信息
* @param trackId
* @return
*/
@Operation(summary = "获取声音统计信息")
@GetMapping("/trackInfo/getTrackStatVo/{trackId}")
public Result<TrackStatVo> getTrackStatVo(@PathVariable Long trackId) {
TrackStatVo trackStatVo = trackInfoService.getTrackStatVo(trackId);
return Result.ok(trackStatVo);
}
} }

View File

@ -4,6 +4,7 @@ import com.atguigu.tingshu.model.album.TrackInfo;
import com.atguigu.tingshu.query.album.TrackInfoQuery; import com.atguigu.tingshu.query.album.TrackInfoQuery;
import com.atguigu.tingshu.vo.album.AlbumTrackListVo; import com.atguigu.tingshu.vo.album.AlbumTrackListVo;
import com.atguigu.tingshu.vo.album.TrackListVo; import com.atguigu.tingshu.vo.album.TrackListVo;
import com.atguigu.tingshu.vo.album.TrackStatVo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
@ -16,4 +17,6 @@ public interface TrackInfoMapper extends BaseMapper<TrackInfo> {
Page<TrackListVo> findUserTrackPage(Page<TrackListVo> pageInfo,@Param("vo")TrackInfoQuery trackInfoQuery); Page<TrackListVo> findUserTrackPage(Page<TrackListVo> pageInfo,@Param("vo")TrackInfoQuery trackInfoQuery);
Page<AlbumTrackListVo> findAlbumTrackPage(Page<AlbumTrackListVo> pageInfo, Long albumId); Page<AlbumTrackListVo> findAlbumTrackPage(Page<AlbumTrackListVo> pageInfo, Long albumId);
TrackStatVo getTrackStatVo(@Param("trackId")Long trackId);
} }

View File

@ -0,0 +1,68 @@
package com.atguigu.tingshu.album.receiver;
import com.atguigu.tingshu.album.service.TrackInfoService;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.vo.album.TrackStatMqVo;
import com.rabbitmq.client.Channel;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* @author: atguigu
* @create: 2025-07-28 14:14
*/
@Slf4j
@Component
public class AlbumReceiver {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private TrackInfoService trackInfoService;
/**
* 监听更新声音/专辑统计信息
*
* @param mqVo
* @param message
* @param channel
*/
@SneakyThrows
@RabbitListener(bindings = @QueueBinding(
exchange = @Exchange(value = MqConst.EXCHANGE_TRACK, durable = "true"),
value = @Queue(value = MqConst.QUEUE_TRACK_STAT_UPDATE, durable = "true"),
key = MqConst.ROUTING_TRACK_STAT_UPDATE
))
public void updateStat(TrackStatMqVo mqVo, Message message, Channel channel) {
if (mqVo != null) {
log.info("更新声音/专辑统计信息:{}", mqVo);
//1.幂等性处理
//1.1 从MQ消息对象中获取消息唯一标识
String key = RedisConstant.BUSINESS_PREFIX + "db:" + mqVo.getBusinessNo();
//1.2 尝试采用set nx写入Redis
Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 5, TimeUnit.MINUTES);
//1.3 写入成功消息才进行业务处理写入失败消息重复被MQ服务器投递忽略消息
if (flag) {
//2.业务处理
trackInfoService.updateStat(mqVo);
}
}
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}

View File

@ -23,7 +23,7 @@ public interface AlbumInfoService extends IService<AlbumInfo> {
void removeAlbumInfo(Long id); void removeAlbumInfo(Long id);
AlbumInfo getAlbumInfo(Long id); AlbumInfo getAlbumInfoFromDB(Long id);
void updateAlbumInfo(Long id, AlbumInfoVo albumInfoVo); void updateAlbumInfo(Long id, AlbumInfoVo albumInfoVo);

View File

@ -2,9 +2,7 @@ package com.atguigu.tingshu.album.service;
import com.atguigu.tingshu.model.album.TrackInfo; import com.atguigu.tingshu.model.album.TrackInfo;
import com.atguigu.tingshu.query.album.TrackInfoQuery; import com.atguigu.tingshu.query.album.TrackInfoQuery;
import com.atguigu.tingshu.vo.album.AlbumTrackListVo; import com.atguigu.tingshu.vo.album.*;
import com.atguigu.tingshu.vo.album.TrackInfoVo;
import com.atguigu.tingshu.vo.album.TrackListVo;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
@ -36,4 +34,7 @@ public interface TrackInfoService extends IService<TrackInfo> {
Page<AlbumTrackListVo> findAlbumTrackPage(Page<AlbumTrackListVo> pageInfo, Long userId, Long albumId); Page<AlbumTrackListVo> findAlbumTrackPage(Page<AlbumTrackListVo> pageInfo, Long userId, Long albumId);
TrackStatVo getTrackStatVo(Long trackId);
void updateStat(TrackStatMqVo mqVo);
} }

View File

@ -8,6 +8,8 @@ import com.atguigu.tingshu.album.mapper.TrackInfoMapper;
import com.atguigu.tingshu.album.service.AlbumAttributeValueService; import com.atguigu.tingshu.album.service.AlbumAttributeValueService;
import com.atguigu.tingshu.album.service.AlbumInfoService; import com.atguigu.tingshu.album.service.AlbumInfoService;
import com.atguigu.tingshu.album.service.AuditService; import com.atguigu.tingshu.album.service.AuditService;
import com.atguigu.tingshu.common.cache.GuiGuCache;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant; import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException; import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.rabbit.constant.MqConst; import com.atguigu.tingshu.common.rabbit.constant.MqConst;
@ -26,7 +28,9 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -51,6 +55,11 @@ public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo
private AuditService auditService; private AuditService auditService;
@Autowired @Autowired
private RabbitService rabbitService; private RabbitService rabbitService;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private RedissonClient redissonClient;
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@ -151,7 +160,8 @@ public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo
} }
@Override @Override
public AlbumInfo getAlbumInfo(Long id) { @GuiGuCache(prefix = RedisConstant.ALBUM_INFO_PREFIX)
public AlbumInfo getAlbumInfoFromDB(Long id) {
//1.根据专辑ID查询专辑信息 //1.根据专辑ID查询专辑信息
AlbumInfo albumInfo = albumInfoMapper.selectById(id); AlbumInfo albumInfo = albumInfoMapper.selectById(id);
@ -227,6 +237,7 @@ public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo
} }
@Override @Override
@GuiGuCache(prefix = RedisConstant.ALBUM_INFO_PREFIX + "stat:")
public AlbumStatVo getAlbumStatVo(Long albumId) { public AlbumStatVo getAlbumStatVo(Long albumId) {
return albumInfoMapper.getAlbumStatVo(albumId); return albumInfoMapper.getAlbumStatVo(albumId);
} }

View File

@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.mapper.*; import com.atguigu.tingshu.album.mapper.*;
import com.atguigu.tingshu.album.service.BaseCategoryService; import com.atguigu.tingshu.album.service.BaseCategoryService;
import com.atguigu.tingshu.common.cache.GuiGuCache;
import com.atguigu.tingshu.model.album.*; import com.atguigu.tingshu.model.album.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@ -13,6 +14,7 @@ import org.springframework.stereotype.Service;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@ -36,6 +38,7 @@ public class BaseCategoryServiceImpl extends ServiceImpl<BaseCategory1Mapper, Ba
@Override @Override
@GuiGuCache(prefix = "album:category:all")
public List<JSONObject> getBaseCategoryList() { public List<JSONObject> getBaseCategoryList() {
//1.创建响应结果集合对象-用于封装所有一级分类对象 //1.创建响应结果集合对象-用于封装所有一级分类对象
//创建一个新的List对象 //创建一个新的List对象
@ -99,6 +102,7 @@ public class BaseCategoryServiceImpl extends ServiceImpl<BaseCategory1Mapper, Ba
} }
@Override @Override
@GuiGuCache(prefix = "album:category:attribute:")
public List<BaseAttribute> findAttribute(Long category1Id) { public List<BaseAttribute> findAttribute(Long category1Id) {
List<BaseAttribute> list=baseAttributeMapper.findAttribute(category1Id); List<BaseAttribute> list=baseAttributeMapper.findAttribute(category1Id);
return list; return list;
@ -110,11 +114,13 @@ public class BaseCategoryServiceImpl extends ServiceImpl<BaseCategory1Mapper, Ba
* @return * @return
*/ */
@Override @Override
@GuiGuCache(prefix="alblum:categoryview:",ttl=3600, timeUnit= TimeUnit.SECONDS)
public BaseCategoryView getCategoryView(Long category3Id) { public BaseCategoryView getCategoryView(Long category3Id) {
return baseCategoryViewMapper.selectById(category3Id); return baseCategoryViewMapper.selectById(category3Id);
} }
@Override @Override
@GuiGuCache(prefix = "album:category:top3:")
public List<BaseCategory3> findTopBaseCategory3(Long category1Id) { public List<BaseCategory3> findTopBaseCategory3(Long category1Id) {
List<BaseCategory2> baseCategory2List = baseCategory2Mapper.selectList( new LambdaQueryWrapper<BaseCategory2>() List<BaseCategory2> baseCategory2List = baseCategory2Mapper.selectList( new LambdaQueryWrapper<BaseCategory2>()
.eq(BaseCategory2::getCategory1Id, category1Id) .eq(BaseCategory2::getCategory1Id, category1Id)
@ -134,6 +140,7 @@ public class BaseCategoryServiceImpl extends ServiceImpl<BaseCategory1Mapper, Ba
} }
@Override @Override
@GuiGuCache(prefix = "album:category:list")
public JSONObject getBaseCategoryListByCategory1Id(Long category1Id) { public JSONObject getBaseCategoryListByCategory1Id(Long category1Id) {
//1.处理1级分类 //1.处理1级分类
//1.1 根据1级分类ID查询分类视图得到"1级"分类列表获取列表第一条记录即可 //1.1 根据1级分类ID查询分类视图得到"1级"分类列表获取列表第一条记录即可

View File

@ -3,21 +3,21 @@ package com.atguigu.tingshu.album.service.impl;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import com.atguigu.tingshu.album.mapper.AlbumInfoMapper; import com.atguigu.tingshu.album.mapper.AlbumInfoMapper;
import com.atguigu.tingshu.album.mapper.AlbumStatMapper;
import com.atguigu.tingshu.album.mapper.TrackInfoMapper; import com.atguigu.tingshu.album.mapper.TrackInfoMapper;
import com.atguigu.tingshu.album.mapper.TrackStatMapper; import com.atguigu.tingshu.album.mapper.TrackStatMapper;
import com.atguigu.tingshu.album.service.AuditService; import com.atguigu.tingshu.album.service.AuditService;
import com.atguigu.tingshu.album.service.TrackInfoService; import com.atguigu.tingshu.album.service.TrackInfoService;
import com.atguigu.tingshu.album.service.VodService; import com.atguigu.tingshu.album.service.VodService;
import com.atguigu.tingshu.common.cache.GuiGuCache;
import com.atguigu.tingshu.common.constant.SystemConstant; import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.model.album.AlbumInfo; import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.AlbumStat;
import com.atguigu.tingshu.model.album.TrackInfo; import com.atguigu.tingshu.model.album.TrackInfo;
import com.atguigu.tingshu.model.album.TrackStat; import com.atguigu.tingshu.model.album.TrackStat;
import com.atguigu.tingshu.query.album.TrackInfoQuery; import com.atguigu.tingshu.query.album.TrackInfoQuery;
import com.atguigu.tingshu.user.client.UserFeignClient; import com.atguigu.tingshu.user.client.UserFeignClient;
import com.atguigu.tingshu.vo.album.AlbumTrackListVo; import com.atguigu.tingshu.vo.album.*;
import com.atguigu.tingshu.vo.album.TrackInfoVo;
import com.atguigu.tingshu.vo.album.TrackListVo;
import com.atguigu.tingshu.vo.album.TrackMediaInfoVo;
import com.atguigu.tingshu.vo.user.UserInfoVo; import com.atguigu.tingshu.vo.user.UserInfoVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@ -55,6 +55,8 @@ public class TrackInfoServiceImpl extends ServiceImpl<TrackInfoMapper, TrackInfo
private TrackStatMapper trackStatMapper; private TrackStatMapper trackStatMapper;
@Autowired @Autowired
private UserFeignClient userFeignClient; private UserFeignClient userFeignClient;
@Autowired
private AlbumStatMapper albumStatMapper;
@Override @Override
@ -294,8 +296,51 @@ public class TrackInfoServiceImpl extends ServiceImpl<TrackInfoMapper, TrackInfo
return pageInfo; return pageInfo;
} }
@Override
@GuiGuCache(prefix = "album:trackInfo:stat:")
public TrackStatVo getTrackStatVo(Long trackId) {
return trackInfoMapper.getTrackStatVo(trackId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateStat(TrackStatMqVo mqVo) {
//1.先更新声音统计数值
trackStatMapper.update(
null,
new LambdaUpdateWrapper<TrackStat>()
.eq(TrackStat::getTrackId, mqVo.getTrackId())
.eq(TrackStat::getStatType, mqVo.getStatType())
.setSql("stat_num = stat_num +" + mqVo.getCount())
);
//2.如果是声音播放评论还需要更新声音所属专辑统计数值
if(SystemConstant.TRACK_STAT_PLAY.equals(mqVo.getStatType())){
albumStatMapper.update(
null,
new LambdaUpdateWrapper<AlbumStat>()
.eq(AlbumStat::getAlbumId, mqVo.getAlbumId())
.eq(AlbumStat::getStatType, SystemConstant.ALBUM_STAT_PLAY)
.setSql("stat_num = stat_num +" + mqVo.getCount())
);
}
if(SystemConstant.TRACK_STAT_COMMENT.equals(mqVo.getStatType())){
albumStatMapper.update(
null,
new LambdaUpdateWrapper<AlbumStat>()
.eq(AlbumStat::getAlbumId, mqVo.getAlbumId())
.eq(AlbumStat::getStatType, SystemConstant.ALBUM_STAT_COMMENT)
.setSql("stat_num = stat_num +" + mqVo.getCount())
);
}
} }
}

View File

@ -50,5 +50,15 @@
order by ti.order_num order by ti.order_num
</select> </select>
<select id="getTrackStatVo" resultType="com.atguigu.tingshu.vo.album.TrackStatVo">
select
max(if(stat_type = '0701', stat_num, 0)) playStatNum,
max(if(stat_type = '0702', stat_num, 0)) collectStatNum,
max(if(stat_type = '0703', stat_num, 0)) praiseStatNum,
max(if(stat_type = '0704', stat_num, 0)) commentStatNum
from track_stat where track_id = #{trackId} and is_deleted = 0
</select>
</mapper> </mapper>

View File

@ -50,5 +50,15 @@
order by ti.order_num order by ti.order_num
</select> </select>
<select id="getTrackStatVo" resultType="com.atguigu.tingshu.vo.album.TrackStatVo">
select
max(if(stat_type = '0701', stat_num, 0)) playStatNum,
max(if(stat_type = '0702', stat_num, 0)) collectStatNum,
max(if(stat_type = '0703', stat_num, 0)) praiseStatNum,
max(if(stat_type = '0704', stat_num, 0)) commentStatNum
from track_stat where track_id = #{trackId} and is_deleted = 0
</select>
</mapper> </mapper>

View File

@ -36,6 +36,21 @@
<groupId>org.apache.kafka</groupId> <groupId>org.apache.kafka</groupId>
<artifactId>kafka-streams</artifactId> <artifactId>kafka-streams</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.github.biezhi</groupId>
<artifactId>TinyPinyin</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.belerweb</groupId>
<artifactId>pinyin4j</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>com.github.stuxuhai</groupId>
<artifactId>jpinyin</artifactId>
<version>1.1.8</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -3,6 +3,7 @@ package com.atguigu.tingshu.search.api;
import com.atguigu.tingshu.common.result.Result; import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.query.search.AlbumIndexQuery; import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.service.SearchService; import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo; import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@ -69,6 +70,34 @@ public class SearchApiController {
return Result.ok(list); return Result.ok(list);
} }
/**
* 根据用户已录入字符展示提示词下拉列表最多10个
* @param keyword
* @return
*/
@Operation(summary = "根据用户已录入字符展示提示词下拉列表最多10个")
@GetMapping("/albumInfo/completeSuggest/{keyword}")
public Result<List<String>> completeSuggest(@PathVariable String keyword) {
List<String> list = searchService.completeSuggest(keyword);
return Result.ok(list);
}
/**
* 更新Redis小时榜数据
* @return
*/
@Operation(summary = "更新Redis小时榜数据")
@GetMapping("/albumInfo/updateLatelyAlbumRanking/{top}")
public Result updateLatelyAlbumRanking(@PathVariable("top") Integer top){
searchService.updateLatelyAlbumRanking(top);
return Result.ok();
}
@Operation(summary = "获取不同分类下不同排行维度TOP20")
@GetMapping("/albumInfo/findRankingList/{category1Id}/{dimension}")
public Result<List<AlbumInfoIndexVo>> getRankingList(@PathVariable Long category1Id, @PathVariable String dimension){
List<AlbumInfoIndexVo> list = searchService.getRankingList(category1Id, dimension);
return Result.ok(list);
}
} }

View File

@ -1,7 +1,9 @@
package com.atguigu.tingshu.search.receiver; package com.atguigu.tingshu.search.receiver;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.rabbit.constant.MqConst; import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.search.service.SearchService; import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.vo.album.TrackStatMqVo;
import com.rabbitmq.client.Channel; import com.rabbitmq.client.Channel;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -11,8 +13,11 @@ import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding; import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/** /**
* @author: atguigu * @author: atguigu
* @create: 2025-07-22 15:22 * @create: 2025-07-22 15:22
@ -23,6 +28,8 @@ public class SearchReciever {
@Autowired @Autowired
private SearchService searchService; private SearchService searchService;
@Autowired
private RedisTemplate redisTemplate;
/** /**
* 监听专辑上架消息 * 监听专辑上架消息
@ -63,4 +70,38 @@ public class SearchReciever {
} }
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} }
}
// /**
// * 监听更新声音/搜索统计信息
// *
// * @param mqVo
// * @param message
// * @param channel
// */
//
// @RabbitListener(bindings = @QueueBinding(
// exchange = @Exchange(value = MqConst.EXCHANGE_TRACK, durable = "true"),
// value = @Queue(value = MqConst.QUEUE_ALBUM_ES_STAT_UPDATE, durable = "true"),
// key = MqConst.ROUTING_TRACK_STAT_UPDATE)
// )
// @SneakyThrows
// public void updateStat(TrackStatMqVo mqVo, Message message, Channel channel){
// if (mqVo != null)log.info("更新声音/专辑统计信息:{}", mqVo);
// //1.幂等性处理
// //1.1 从MQ消息对象中获取消息唯一标识
// String key = RedisConstant.BUSINESS_PREFIX + "db:" + mqVo.getBusinessNo();
// //1.2 尝试采用set nx写入Redis
// Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 5, TimeUnit.MINUTES);
//
// //1.3 写入成功消息才进行业务处理写入失败消息重复被MQ服务器投递忽略消息
// if (flag) {
// //2.业务处理
// searchService.upperAlbum(mqVo.getAlbumId());
//
// }
// channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
// }
}

View File

@ -2,6 +2,6 @@ package com.atguigu.tingshu.search.repository;
import com.atguigu.tingshu.model.search.AlbumInfoIndex; import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
//当启动这个服务的启动类的时候 当检测到有这个接口,可以自动创建索引库
public interface AlbumInfoIndexRepository extends ElasticsearchRepository<AlbumInfoIndex, Long> { public interface AlbumInfoIndexRepository extends ElasticsearchRepository<AlbumInfoIndex, Long> {
} }

View File

@ -0,0 +1,7 @@
package com.atguigu.tingshu.search.repository;
import com.atguigu.tingshu.model.search.SuggestIndex;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
public interface SuggestIndexRepository extends ElasticsearchRepository<SuggestIndex, String> {
}

View File

@ -1,8 +1,13 @@
package com.atguigu.tingshu.search.service; package com.atguigu.tingshu.search.service;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery; import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo; import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -18,5 +23,18 @@ public interface SearchService {
List<Map<String, Object>> channel(Long category1Id); List<Map<String, Object>> channel(Long category1Id);
List<String> completeSuggest(String keyword);
/**
* 解析自动补全建议结果
* @param searchResponse 检索响应结果对象
* @param suggestName 建议器名称
* @return
*/
Collection<String> parseSuggestResult(SearchResponse<SuggestIndex> searchResponse, String suggestName);
void saveSuggestIndex(AlbumInfoIndex albumInfoIndex);
void updateLatelyAlbumRanking(Integer top);
List<AlbumInfoIndexVo> getRankingList(Long category1Id, String dimension);
} }

View File

@ -2,9 +2,10 @@ package com.atguigu.tingshu.search.service.impl;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.pinyin.PinyinUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue; import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder; import co.elastic.clients.elasticsearch._types.SortOrder;
@ -13,18 +14,20 @@ import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery; import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest; import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse; import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.Hit; import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.album.AlbumFeignClient; import com.atguigu.tingshu.album.AlbumFeignClient;
import com.atguigu.tingshu.common.result.Result; import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
import com.atguigu.tingshu.model.album.AlbumInfo; import com.atguigu.tingshu.model.album.*;
import com.atguigu.tingshu.model.album.BaseCategory3;
import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.atguigu.tingshu.model.search.AlbumInfoIndex; import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex; import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery; import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.repository.AlbumInfoIndexRepository; import com.atguigu.tingshu.search.repository.AlbumInfoIndexRepository;
import com.atguigu.tingshu.search.repository.SuggestIndexRepository;
import com.atguigu.tingshu.search.service.SearchService; import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserFeignClient; import com.atguigu.tingshu.user.client.UserFeignClient;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo; import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
@ -34,14 +37,16 @@ import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.HashMap; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -63,6 +68,11 @@ public class SearchServiceImpl implements SearchService {
private UserFeignClient userFeignClient; private UserFeignClient userFeignClient;
@Autowired @Autowired
private ElasticsearchClient elasticsearchClient; private ElasticsearchClient elasticsearchClient;
private static final String SUGGEST_INDEX = "suggestinfo";
@Autowired
private SuggestIndexRepository suggestIndexRepository;
@Autowired
private RedisTemplate redisTemplate;
@Override @Override
@ -132,6 +142,8 @@ public class SearchServiceImpl implements SearchService {
//7.保存专辑到索引库 //7.保存专辑到索引库
albumInfoIndexRepository.save(albumInfoIndex); albumInfoIndexRepository.save(albumInfoIndex);
//8.将专辑标题作为提示词文档存入提示词索引库
this.saveSuggestIndex(albumInfoIndex);
} }
@ -144,10 +156,11 @@ public class SearchServiceImpl implements SearchService {
@Override @Override
public void lowerAlbum(Long albumId) { public void lowerAlbum(Long albumId) {
albumInfoIndexRepository.deleteById(albumId); albumInfoIndexRepository.deleteById(albumId);
suggestIndexRepository.deleteById(albumId.toString());
} }
/*可以将这个想成kibana中开发工具 buildDsl可以想象成左侧我们写的数据 我们在写 /*可以将这个想成kibana中开发工具 buildDsl可以想象成左侧我们写的数据 我们在写
buildDSL这个方法的时候就是在拼接这个dsl语句 buildDSL这个方法的时候就是在拼接这个dsl语句
第二步执行检索的时候就是在执行那个播放键 第二步执行检索的时候就是在执行那个播放键
第三步就是在看右边的数据 我们要对这个数据进行分析*/ 第三步就是在看右边的数据 我们要对这个数据进行分析*/
@ -374,4 +387,169 @@ public class SearchServiceImpl implements SearchService {
log.error("ES检索异常{}", e.getMessage()); log.error("ES检索异常{}", e.getMessage());
throw new RuntimeException(e); throw new RuntimeException(e);
} }
}} }
/**
* 根据用户已录入字符展示提示词下拉列表最多10个
*
* @param keyword
* @return
*/
@Override
public List<String> completeSuggest(String keyword) {
try {
//1.优先进行检索提示词索引库
SearchResponse<SuggestIndex> searchResponse = elasticsearchClient.search(s -> s.index(SUGGEST_INDEX).suggest(
s1 -> s1.suggesters("keywordSuggest", suggest -> suggest.prefix(keyword).completion(c -> c.field("keyword").skipDuplicates(true)))
.suggesters("pinyinSuggest", suggest -> suggest.prefix(keyword).completion(c -> c.field("keywordPinyin").skipDuplicates(true)))
.suggesters("sequenceSuggest", suggest -> suggest.prefix(keyword).completion(c -> c.field("keywordSequence").skipDuplicates(true)))
), SuggestIndex.class);
//2.解析自动补全结果将提示词列表放入hashset集合中(去重)
Set<String> hashSet = new HashSet<>();
hashSet.addAll(this.parseSuggestResult(searchResponse, "keywordSuggest"));
hashSet.addAll(this.parseSuggestResult(searchResponse, "pinyinSuggest"));
hashSet.addAll(this.parseSuggestResult(searchResponse, "sequenceSuggest"));
//3.如果上面结果数量小于10个尝试采用全文检索补齐到10个
if (hashSet.size() < 10) {
SearchResponse<AlbumInfoIndex> search = elasticsearchClient.search(
s -> s.index(INDEX_NAME)
.query(q -> q.match(m -> m.field("albumTitle").query(keyword)))
.source(s1 -> s1.filter(f -> f.includes("albumTitle")))
.size(10), AlbumInfoIndex.class);
List<Hit<AlbumInfoIndex>> hitList = search.hits().hits();
if (CollUtil.isNotEmpty(hitList)) {
for (Hit<AlbumInfoIndex> hit : hitList) {
String albumTitle = hit.source().getAlbumTitle();
hashSet.add(albumTitle);
if (hashSet.size() >= 10) {
break;
}
}
}
}
//4.将所有提示词列表返回
if (hashSet.size() >= 10) {
return new ArrayList<>(hashSet).subList(0, 10);
}
return new ArrayList<>(hashSet);
} catch (IOException e) {
log.error("建议词自动补全异常", e);
throw new RuntimeException(e);
}
}
/**
* 解析自动补全建议结果
*
* @param searchResponse 检索响应结果对象
* @param suggestName 建议器名称
* @return
*/
@Override
public Collection<String> parseSuggestResult(SearchResponse<SuggestIndex> searchResponse, String suggestName) {
List<String> list = new ArrayList<>();
List<Suggestion<SuggestIndex>> suggestionList = searchResponse.suggest().get(suggestName);
if (CollUtil.isNotEmpty(suggestionList)) {
for (Suggestion<SuggestIndex> suggestion : suggestionList) {
for (CompletionSuggestOption<SuggestIndex> option : suggestion.completion().options()) {
String title = option.source().getTitle();
list.add(title);
}
}
}
return list;
}
@Override
public void saveSuggestIndex(AlbumInfoIndex albumInfoIndex) {
SuggestIndex suggestIndex = new SuggestIndex();
suggestIndex.setId(albumInfoIndex.getId().toString());
//原始内容
String albumTitle = albumInfoIndex.getAlbumTitle();
suggestIndex.setTitle(albumTitle);
//用于汉字自动补全字段
suggestIndex.setKeyword(new Completion(new String[]{albumTitle}));
//用于汉语拼音自动补全字段
String pinyin = PinyinUtil.getPinyin(albumTitle, "");
suggestIndex.setKeywordPinyin(new Completion(new String[]{pinyin}));
//用于汉语拼音首字母自动补全字段
String firstLetter = PinyinUtil.getFirstLetter(albumTitle, "");
suggestIndex.setKeywordSequence(new Completion(new String[]{firstLetter}));
suggestIndexRepository.save(suggestIndex);
}
@Override
public void updateLatelyAlbumRanking(Integer top) {
try {
//1.从Redis检索获取分类下五种排序维度专辑TOPN(n=top)记录
//1.1 远程调用专辑服务获取所有1级分类列表
List<BaseCategory1> baseCategory1List = albumFeignClient.findAllCategory1().getData();
Assert.notNull(baseCategory1List, "1级分类列表为空");
//1.2 遍历1级分类
for (BaseCategory1 baseCategory1 : baseCategory1List) {
//从内容中找出id 设定一个key
Long category1Id = baseCategory1.getId();
String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
//绑定大key
BoundHashOperations<String, String, List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(key);
//1.3 遍历5种排序维度
String[] rankingDimensionArray
= new String[]{"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"};
for (String dimension : rankingDimensionArray) {
//1.4 根据1级分类ID+排序字段检索ES
SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(s ->
s.index(INDEX_NAME)
.query(q -> q.term(t -> t.field("category1Id").value(category1Id)))
.sort(s1 -> s1.field(f -> f.field(dimension).order(SortOrder.Desc)))
.size(top)
.source(s1 -> s1.filter(f -> f.excludes("attributeValueIndexList",
"hotScore",
"commentStatNum",
"buyStatNum",
"subscribeStatNum",
"announcerName"))), AlbumInfoIndex.class);
//2.解析ES检索结果 获取专辑TOPN记录
List<Hit<AlbumInfoIndex>> hits = searchResponse.hits().hits();
if (CollUtil.isNotEmpty(hits)) {
List<AlbumInfoIndex> topNList
= hits.stream().map(hit -> hit.source())
.collect(Collectors.toList());
//3.将TOPN记录存入Redis中hash小时排行中
//3.1 构建Hash排行榜中field 当前排序方式
String field = dimension;
//redisTemplate.opsForHash().put(key, field, topNList);
hashOps.put(field, topNList);
}
}
}
} catch (IOException e) {
log.error("更新Redis小时排行数据异常", e);
throw new RuntimeException(e);
}
}
@Override
public List<AlbumInfoIndexVo> getRankingList(Long category1Id, String dimension) {
//基于1级分类ID构建Redis中hash结构的Key
String key =RedisConstant.RANKING_KEY_PREFIX+category1Id;
BoundHashOperations<String,String,List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(key);
//构建Hash排行榜中field 当前排序方式
String field = dimension;
Boolean flag = hashOps.hasKey(field);
if(flag){
//List<AlbumInfoIndex> list = (List<AlbumInfoIndex>) redisTemplate.opsForHash().get(key, field);
List<AlbumInfoIndex> list = hashOps.get(field);
if (CollUtil.isNotEmpty(list)) {
List<AlbumInfoIndexVo> albumInfoIndexVoList = list.stream()
.map(albumInfoIndex -> BeanUtil.copyProperties(albumInfoIndex, AlbumInfoIndexVo.class))
.collect(Collectors.toList());
return albumInfoIndexVoList;
}
}
return null;
}
}

View File

@ -1,10 +1,17 @@
package com.atguigu.tingshu.user.api; package com.atguigu.tingshu.user.api;
import com.atguigu.tingshu.common.login.GuiGuLogin;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.user.service.UserListenProcessService; import com.atguigu.tingshu.user.service.UserListenProcessService;
import com.atguigu.tingshu.vo.user.UserListenProcessVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.Map;
@Tag(name = "用户声音播放进度管理接口") @Tag(name = "用户声音播放进度管理接口")
@RestController @RestController
@ -15,5 +22,50 @@ public class UserListenProcessApiController {
@Autowired @Autowired
private UserListenProcessService userListenProcessService; private UserListenProcessService userListenProcessService;
@GuiGuLogin
@Operation(summary = "获取声音播放进度")
@GetMapping("/userListenProcess/getTrackBreakSecond/{trackId}")
public Result<BigDecimal> getTrackBreakSecond (Long trackId){
Long userId = AuthContextHolder.getUserId();
BigDecimal second =userListenProcessService.getTrackBreakSecond(userId, trackId);
return Result.ok(second);
}
/**
* 更新用户对于指定声音播放进度
*
* @param userListenProcessVo
* @return
*/
@GuiGuLogin(required = false)
@Operation(summary = "更新用户对于指定声音播放进度")
@PostMapping("/userListenProcess/updateListenProcess")
public Result updateListenProcess(@RequestBody UserListenProcessVo userListenProcessVo) {
//1.尝试获取用户ID
Long userId = AuthContextHolder.getUserId();
//2.更新用户对于指定声音播放进度
if (userId != null) {
//3.返回
userListenProcessService.updateListenProcess(userListenProcessVo, userId);
}
//3.返回
return Result.ok(null);
}
/**
* 获取用户最近播放声音
*
* @return
*/
@GuiGuLogin
@Operation(summary = "获取用户最近播放声音")
@GetMapping("/userListenProcess/getLatelyTrack")
public Result<Map<String, Long>> getLatelyTrack() {
//1.尝试获取用户ID
Long userId = AuthContextHolder.getUserId();
//2.查询用户对于指定声音播放进度
Map<String, Long> map = userListenProcessService.getLatelyTrack(userId);
return Result.ok(map);
}
} }

View File

@ -1,5 +1,16 @@
package com.atguigu.tingshu.user.service; package com.atguigu.tingshu.user.service;
import com.atguigu.tingshu.vo.user.UserListenProcessVo;
import java.math.BigDecimal;
import java.util.Map;
public interface UserListenProcessService { public interface UserListenProcessService {
BigDecimal getTrackBreakSecond(Long userId, Long trackId);
void updateListenProcess(UserListenProcessVo userListenProcessVo, Long userId);
Map<String, Long> getLatelyTrack(Long userId);
} }

View File

@ -6,6 +6,7 @@ import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
import com.atguigu.tingshu.common.cache.GuiGuCache;
import com.atguigu.tingshu.common.constant.RedisConstant; import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.rabbit.constant.MqConst; import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.common.rabbit.service.RabbitService; import com.atguigu.tingshu.common.rabbit.service.RabbitService;
@ -97,7 +98,8 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
} }
@Override @Override
public UserInfoVo getUserInfo(Long userId) { @GuiGuCache(prefix = "user:info:")
public UserInfoVo getUserInfo(Long userId) {
UserInfo userInfo = userInfoMapper.selectById(userId); UserInfo userInfo = userInfoMapper.selectById(userId);
return BeanUtil.copyProperties(userInfo, UserInfoVo.class); return BeanUtil.copyProperties(userInfo, UserInfoVo.class);

View File

@ -1,15 +1,161 @@
package com.atguigu.tingshu.user.service.impl; package com.atguigu.tingshu.user.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.common.rabbit.service.RabbitService;
import com.atguigu.tingshu.common.util.MongoUtil;
import com.atguigu.tingshu.model.user.UserListenProcess;
import com.atguigu.tingshu.user.service.UserListenProcessService; import com.atguigu.tingshu.user.service.UserListenProcessService;
import com.atguigu.tingshu.vo.album.TrackStatMqVo;
import com.atguigu.tingshu.vo.search.SearchStatMqVo;
import com.atguigu.tingshu.vo.user.UserListenProcessVo;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Service @Service
@SuppressWarnings({"all"}) @SuppressWarnings({"all"})
public class UserListenProcessServiceImpl implements UserListenProcessService { public class UserListenProcessServiceImpl implements UserListenProcessService {
@Autowired @Autowired
private MongoTemplate mongoTemplate; private MongoTemplate mongoTemplate;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private RabbitService rabbitService;
@Override
public BigDecimal getTrackBreakSecond(Long userId, Long trackId) {
//确定一下集合名称
String collectionName = this.getCollectionName(userId);
//构建查询条件
Query query =new Query();
query.addCriteria(Criteria.where("trackId").is(trackId));
//执行mongodb
UserListenProcess userListenProcess = mongoTemplate.findOne(query, UserListenProcess.class, collectionName);
//如果有值 则从当前值去返回
if (userListenProcess != null) {
return userListenProcess.getBreakSecond();
}
//如果没有值 则从第0秒开始
return BigDecimal.valueOf(0);
}
@Override
public void updateListenProcess(UserListenProcessVo userListenProcessVo, Long userId) {
//1.确定用户播放进度集合名称
String collectionName = this.getCollectionName(userId);
//2.构建查询条件声音ID
Query query = new Query(Criteria.where("trackId").is(userListenProcessVo.getTrackId()));
UserListenProcess userListenProcess = mongoTemplate.findOne(query, UserListenProcess.class, collectionName);
//3.如果vo中有断的时间,将其拿出来
//从vo视图中拿出来的时间进行删除小数点.
BigDecimal breakSeconds = userListenProcessVo.getBreakSecond().setScale(0, RoundingMode.DOWN);
//如果userListenProcess有值 我们就将其进行修改
if(userListenProcess!=null){
userListenProcess.setBreakSecond(breakSeconds);
userListenProcess.setUpdateTime(new Date());
}else{
//如果里面没有值 说明没有断的时间 所以我们进行新建
userListenProcess = new UserListenProcess();
userListenProcess.setAlbumId(userListenProcessVo.getAlbumId());
userListenProcess.setTrackId(userListenProcessVo.getTrackId());
userListenProcess.setBreakSecond(breakSeconds);
userListenProcess.setCreateTime(new Date());
userListenProcess.setUpdateTime(new Date());
}
mongoTemplate.save(userListenProcess, collectionName);
//TODO 设置rabbitmq发送消息去更新声音的统计情况 分别要给专辑服务和搜索服务发
//要保证幂等性情况,我们要单独在专辑服务和搜索服务中设置一个特殊的业务逻辑
//这是为了给专辑服务去发送一个更新声音的统计信息
//定义一个key为user:track:userid_albumid_trackid
String userKey =
RedisConstant.USER_TRACK_REPEAT_STAT_PREFIX + userId + "_" + userListenProcessVo.getAlbumId() + "_" + userListenProcessVo.getTrackId();
//设置一个超时时间,方法是每一天的23:59:59去减修改的时间 这样可以做到一天只更新一次
long time = DateUtil.endOfDay(new Date()).getTime() - System.currentTimeMillis();
//将其存到redis中 作为一个锁
Boolean flag = redisTemplate.opsForValue().setIfAbsent(userKey, 1, time, TimeUnit.MILLISECONDS);
//如果存进去了 我们就在rabbitmq中发送数据
if(flag){
//5.2 创建更新统计数值MQ消息对象 注意MQ消息如果是对象必须实现序列化接口生成序列化ID
TrackStatMqVo mqVo = new TrackStatMqVo();
//业务消息不足以为消费者提供幂等处理固设置消息唯一标识用于消费者幂等性处理
mqVo.setBusinessNo(IdUtil.randomUUID());
mqVo.setAlbumId(userListenProcessVo.getAlbumId());
mqVo.setTrackId(userListenProcessVo.getTrackId());
mqVo.setStatType(SystemConstant.TRACK_STAT_PLAY);
mqVo.setCount(1);
//5.3 发送MQ消息到RabbitMQ
rabbitService.sendMessage(MqConst.EXCHANGE_TRACK, MqConst.ROUTING_TRACK_STAT_UPDATE,mqVo);
}
//反之不处理即可
// //这是为了给搜索服务去更新声音的统计信息
// String searchKey =
// RedisConstant.USER_SEARCH_REPEAT_STAT_PREFIX+userId+"_"+ userListenProcessVo.getAlbumId() + "_" + userListenProcessVo.getTrackId();
// Boolean searchFlag = redisTemplate.opsForValue().setIfAbsent(userKey, 1, time, TimeUnit.MILLISECONDS);
// if(searchFlag){
// TrackStatMqVo mqVo = new TrackStatMqVo();
// //业务消息不足以为消费者提供幂等处理固设置消息唯一标识用于消费者幂等性处理
// mqVo.setBusinessNo(IdUtil.randomUUID());
// mqVo.setAlbumId(userListenProcessVo.getAlbumId());
// mqVo.setTrackId(userListenProcessVo.getTrackId());
// mqVo.setStatType(SystemConstant.TRACK_STAT_PLAY);
// mqVo.setCount(1);
// //发送MQ消息到RabbitMQ
// rabbitService.sendMessage(MqConst.EXCHANGE_TRACK,MqConst.ROUTING_TRACK_STAT_UPDATE,mqVo);
// }
// //反之不处理即可
}
@Override
public Map<String, Long> getLatelyTrack(Long userId) {
//1.确定用户播放进度集合名称
String collectionName = this.getCollectionName(userId);
//2.构建查询条件-按照更新时间排序
Query query = new Query();
query.with(Sort.by(Sort.Direction.DESC, "updateTime"));
query.limit(1);
//3.查询MongoDB获取一条记录
UserListenProcess listenProcess = mongoTemplate.findOne(query, UserListenProcess.class, collectionName);
if (listenProcess != null) {
return Map.of("trackId", listenProcess.getTrackId(), "albumId", listenProcess.getAlbumId());
}
return null;
}
private String getCollectionName(Long userId) {
String collectionName = MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_LISTEN_PROCESS, userId);
return collectionName;
}
} }