Compare commits
4 Commits
ed419a4bee
...
34cb7047bb
Author | SHA1 | Date |
---|---|---|
|
34cb7047bb | |
|
38e090bed4 | |
|
4bd206c1a1 | |
|
0924a97aac |
|
@ -51,11 +51,11 @@
|
|||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- redisson -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.redisson</groupId>-->
|
||||
<!-- <artifactId>redisson-spring-boot-starter</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
@ -87,6 +87,32 @@
|
|||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||
</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>
|
||||
|
||||
</project>
|
||||
|
|
30
common/service-util/src/main/java/com/atguigu/tingshu/common/cache/GuiGuCache.java
vendored
Normal file
30
common/service-util/src/main/java/com/atguigu/tingshu/common/cache/GuiGuCache.java
vendored
Normal 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;
|
||||
}
|
81
common/service-util/src/main/java/com/atguigu/tingshu/common/cache/GuiGuCacheAspect.java
vendored
Normal file
81
common/service-util/src/main/java/com/atguigu/tingshu/common/cache/GuiGuCacheAspect.java
vendored
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
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 org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
@ -15,6 +18,8 @@ import java.util.concurrent.*;
|
|||
@Configuration
|
||||
public class ThreadPoolConfig {
|
||||
|
||||
@Autowired(required = false)
|
||||
ZipkinHelper zipkinHelper;
|
||||
/**
|
||||
* 基于JDK(JUC)提供线程池Class
|
||||
*/
|
||||
|
@ -65,6 +70,8 @@ public class ThreadPoolConfig {
|
|||
taskExecutor.setAwaitTerminationSeconds(300);
|
||||
// 线程不够用时由调用的线程处理该任务
|
||||
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
//设置解决zipkin链路追踪不完整装饰器对象
|
||||
taskExecutor.setTaskDecorator(new ZipkinTaskDecorator(zipkinHelper));
|
||||
return taskExecutor;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,9 @@ public class RedisConstant {
|
|||
|
||||
//更新声音统计前缀
|
||||
public static final String USER_TRACK_REPEAT_STAT_PREFIX = "user:track:";
|
||||
public static final String USER_SEARCH_REPEAT_STAT_PREFIX = "user:search:";
|
||||
|
||||
|
||||
|
||||
|
||||
//重复锁定账户
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
BIN
common/service-util/target/classes/com/atguigu/tingshu/common/cache/GuiGuCache.class
vendored
Normal file
BIN
common/service-util/target/classes/com/atguigu/tingshu/common/cache/GuiGuCache.class
vendored
Normal file
Binary file not shown.
BIN
common/service-util/target/classes/com/atguigu/tingshu/common/cache/GuiGuCacheAspect.class
vendored
Normal file
BIN
common/service-util/target/classes/com/atguigu/tingshu/common/cache/GuiGuCacheAspect.class
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -3,9 +3,13 @@ package com.atguigu.tingshu.vo.album;
|
|||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@Schema(description = "TrackStatMqVo")
|
||||
public class TrackStatMqVo {
|
||||
public class TrackStatMqVo implements Serializable {
|
||||
//防止 生产消息 消费消息使用对象不是同一个
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "业务编号:去重使用")
|
||||
private String businessNo;
|
||||
|
|
|
@ -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;
|
||||
}
|
Binary file not shown.
Binary file not shown.
|
@ -3,6 +3,7 @@ package com.atguigu.tingshu.album;
|
|||
import com.atguigu.tingshu.album.impl.AlbumDegradeFeignClient;
|
||||
import com.atguigu.tingshu.common.result.Result;
|
||||
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.BaseCategoryView;
|
||||
import com.atguigu.tingshu.vo.album.AlbumStatVo;
|
||||
|
@ -45,6 +46,9 @@ public interface AlbumFeignClient {
|
|||
@GetMapping("/albumInfo/getAlbumStatVo/{albumId}")
|
||||
public Result<AlbumStatVo>getAlbumStatVo(@PathVariable Long albumId);
|
||||
|
||||
@GetMapping("/category/findAllCategory1")
|
||||
Result<List<BaseCategory1>> findAllCategory1();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ package com.atguigu.tingshu.album.impl;
|
|||
import com.atguigu.tingshu.album.AlbumFeignClient;
|
||||
import com.atguigu.tingshu.common.result.Result;
|
||||
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.BaseCategoryView;
|
||||
import com.atguigu.tingshu.vo.album.AlbumStatVo;
|
||||
|
@ -40,4 +41,10 @@ public class AlbumDegradeFeignClient implements AlbumFeignClient {
|
|||
log.error("[专辑服务]提供远程调用方法getAlbumStatVo执行服务降级");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<List<BaseCategory1>> findAllCategory1() {
|
||||
log.error("[专辑服务]提供远程调用方法findAllCategory1执行服务降级");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -71,7 +71,7 @@ public class AlbumInfoApiController {
|
|||
@Operation(summary = "查询专辑信息,包含专辑标签列表")
|
||||
@GetMapping("/albumInfo/getAlbumInfo/{id}")
|
||||
public Result<AlbumInfo> getAlbumInfo(@PathVariable Long id) {
|
||||
AlbumInfo albumInfo = albumInfoService.getAlbumInfo(id);
|
||||
AlbumInfo albumInfo = albumInfoService.getAlbumInfoFromDB(id);
|
||||
return Result.ok(albumInfo);
|
||||
}
|
||||
@Operation(summary = "更新专辑信息")
|
||||
|
|
|
@ -4,8 +4,10 @@ import com.alibaba.fastjson.JSONObject;
|
|||
import com.atguigu.tingshu.album.service.BaseCategoryService;
|
||||
import com.atguigu.tingshu.common.result.Result;
|
||||
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.BaseCategoryView;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -66,6 +68,16 @@ public class BaseCategoryApiController {
|
|||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.atguigu.tingshu.album.api;
|
|||
|
||||
import com.atguigu.tingshu.album.service.TrackInfoService;
|
||||
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.result.Result;
|
||||
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.TrackInfoVo;
|
||||
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 io.swagger.v3.oas.annotations.Operation;
|
||||
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.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Tag(name = "声音管理")
|
||||
|
@ -34,6 +38,7 @@ public class TrackInfoApiController {
|
|||
@Autowired
|
||||
private VodService vodService;
|
||||
|
||||
|
||||
//用MultipartFile来接收文件
|
||||
@Operation(summary = "将音频文件上传到腾讯云点播平台")
|
||||
@PostMapping("/trackInfo/uploadTrack")
|
||||
|
@ -90,6 +95,7 @@ public class TrackInfoApiController {
|
|||
* @return
|
||||
*/
|
||||
@Operation(summary = "查询声音信息")
|
||||
@GuiGuCache(prefix = "album:trackInfo:")
|
||||
@GetMapping("/trackInfo/getTrackInfo/{id}")
|
||||
public Result<TrackInfo> getTrackInfo(@PathVariable Long id){
|
||||
TrackInfo trackInfo = trackInfoService.getById(id);
|
||||
|
@ -144,6 +150,20 @@ public class TrackInfoApiController {
|
|||
//4.响应结果
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.atguigu.tingshu.model.album.TrackInfo;
|
|||
import com.atguigu.tingshu.query.album.TrackInfoQuery;
|
||||
import com.atguigu.tingshu.vo.album.AlbumTrackListVo;
|
||||
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.extension.plugins.pagination.Page;
|
||||
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<AlbumTrackListVo> findAlbumTrackPage(Page<AlbumTrackListVo> pageInfo, Long albumId);
|
||||
|
||||
TrackStatVo getTrackStatVo(@Param("trackId")Long trackId);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -23,7 +23,7 @@ public interface AlbumInfoService extends IService<AlbumInfo> {
|
|||
|
||||
void removeAlbumInfo(Long id);
|
||||
|
||||
AlbumInfo getAlbumInfo(Long id);
|
||||
AlbumInfo getAlbumInfoFromDB(Long id);
|
||||
|
||||
void updateAlbumInfo(Long id, AlbumInfoVo albumInfoVo);
|
||||
|
||||
|
|
|
@ -2,9 +2,7 @@ package com.atguigu.tingshu.album.service;
|
|||
|
||||
import com.atguigu.tingshu.model.album.TrackInfo;
|
||||
import com.atguigu.tingshu.query.album.TrackInfoQuery;
|
||||
import com.atguigu.tingshu.vo.album.AlbumTrackListVo;
|
||||
import com.atguigu.tingshu.vo.album.TrackInfoVo;
|
||||
import com.atguigu.tingshu.vo.album.TrackListVo;
|
||||
import com.atguigu.tingshu.vo.album.*;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
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);
|
||||
|
||||
TrackStatVo getTrackStatVo(Long trackId);
|
||||
|
||||
void updateStat(TrackStatMqVo mqVo);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import com.atguigu.tingshu.album.mapper.TrackInfoMapper;
|
|||
import com.atguigu.tingshu.album.service.AlbumAttributeValueService;
|
||||
import com.atguigu.tingshu.album.service.AlbumInfoService;
|
||||
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.execption.GuiguException;
|
||||
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.extension.service.impl.ServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
@ -51,6 +55,11 @@ public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo
|
|||
private AuditService auditService;
|
||||
@Autowired
|
||||
private RabbitService rabbitService;
|
||||
@Autowired
|
||||
private RedisTemplate redisTemplate;
|
||||
|
||||
@Autowired
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
|
@ -151,7 +160,8 @@ public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo
|
|||
}
|
||||
|
||||
@Override
|
||||
public AlbumInfo getAlbumInfo(Long id) {
|
||||
@GuiGuCache(prefix = RedisConstant.ALBUM_INFO_PREFIX)
|
||||
public AlbumInfo getAlbumInfoFromDB(Long id) {
|
||||
//1.根据专辑ID查询专辑信息
|
||||
AlbumInfo albumInfo = albumInfoMapper.selectById(id);
|
||||
|
||||
|
@ -227,6 +237,7 @@ public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo
|
|||
}
|
||||
|
||||
@Override
|
||||
@GuiGuCache(prefix = RedisConstant.ALBUM_INFO_PREFIX + "stat:")
|
||||
public AlbumStatVo getAlbumStatVo(Long albumId) {
|
||||
return albumInfoMapper.getAlbumStatVo(albumId);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
|
|||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.atguigu.tingshu.album.mapper.*;
|
||||
import com.atguigu.tingshu.album.service.BaseCategoryService;
|
||||
import com.atguigu.tingshu.common.cache.GuiGuCache;
|
||||
import com.atguigu.tingshu.model.album.*;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
|
@ -13,6 +14,7 @@ import org.springframework.stereotype.Service;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
|
@ -36,6 +38,7 @@ public class BaseCategoryServiceImpl extends ServiceImpl<BaseCategory1Mapper, Ba
|
|||
|
||||
|
||||
@Override
|
||||
@GuiGuCache(prefix = "album:category:all")
|
||||
public List<JSONObject> getBaseCategoryList() {
|
||||
//1.创建响应结果集合对象-用于封装所有一级分类对象
|
||||
//创建一个新的List对象
|
||||
|
@ -99,6 +102,7 @@ public class BaseCategoryServiceImpl extends ServiceImpl<BaseCategory1Mapper, Ba
|
|||
}
|
||||
|
||||
@Override
|
||||
@GuiGuCache(prefix = "album:category:attribute:")
|
||||
public List<BaseAttribute> findAttribute(Long category1Id) {
|
||||
List<BaseAttribute> list=baseAttributeMapper.findAttribute(category1Id);
|
||||
return list;
|
||||
|
@ -110,11 +114,13 @@ public class BaseCategoryServiceImpl extends ServiceImpl<BaseCategory1Mapper, Ba
|
|||
* @return
|
||||
*/
|
||||
@Override
|
||||
@GuiGuCache(prefix="alblum:categoryview:",ttl=3600, timeUnit= TimeUnit.SECONDS)
|
||||
public BaseCategoryView getCategoryView(Long category3Id) {
|
||||
return baseCategoryViewMapper.selectById(category3Id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuiGuCache(prefix = "album:category:top3:")
|
||||
public List<BaseCategory3> findTopBaseCategory3(Long category1Id) {
|
||||
List<BaseCategory2> baseCategory2List = baseCategory2Mapper.selectList( new LambdaQueryWrapper<BaseCategory2>()
|
||||
.eq(BaseCategory2::getCategory1Id, category1Id)
|
||||
|
@ -134,6 +140,7 @@ public class BaseCategoryServiceImpl extends ServiceImpl<BaseCategory1Mapper, Ba
|
|||
}
|
||||
|
||||
@Override
|
||||
@GuiGuCache(prefix = "album:category:list")
|
||||
public JSONObject getBaseCategoryListByCategory1Id(Long category1Id) {
|
||||
//1.处理1级分类
|
||||
//1.1 根据1级分类ID查询分类视图,得到"1级"分类列表,获取列表第一条记录即可
|
||||
|
|
|
@ -3,21 +3,21 @@ package com.atguigu.tingshu.album.service.impl;
|
|||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
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.TrackStatMapper;
|
||||
import com.atguigu.tingshu.album.service.AuditService;
|
||||
import com.atguigu.tingshu.album.service.TrackInfoService;
|
||||
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.model.album.AlbumInfo;
|
||||
import com.atguigu.tingshu.model.album.AlbumStat;
|
||||
import com.atguigu.tingshu.model.album.TrackInfo;
|
||||
import com.atguigu.tingshu.model.album.TrackStat;
|
||||
import com.atguigu.tingshu.query.album.TrackInfoQuery;
|
||||
import com.atguigu.tingshu.user.client.UserFeignClient;
|
||||
import com.atguigu.tingshu.vo.album.AlbumTrackListVo;
|
||||
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.album.*;
|
||||
import com.atguigu.tingshu.vo.user.UserInfoVo;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
|
@ -55,6 +55,8 @@ public class TrackInfoServiceImpl extends ServiceImpl<TrackInfoMapper, TrackInfo
|
|||
private TrackStatMapper trackStatMapper;
|
||||
@Autowired
|
||||
private UserFeignClient userFeignClient;
|
||||
@Autowired
|
||||
private AlbumStatMapper albumStatMapper;
|
||||
|
||||
|
||||
@Override
|
||||
|
@ -294,6 +296,49 @@ public class TrackInfoServiceImpl extends ServiceImpl<TrackInfoMapper, TrackInfo
|
|||
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())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -50,5 +50,15 @@
|
|||
order by ti.order_num
|
||||
</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>
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -50,5 +50,15 @@
|
|||
order by ti.order_num
|
||||
</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>
|
||||
|
||||
|
|
|
@ -36,6 +36,21 @@
|
|||
<groupId>org.apache.kafka</groupId>
|
||||
<artifactId>kafka-streams</artifactId>
|
||||
</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>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.atguigu.tingshu.search.api;
|
|||
import com.atguigu.tingshu.common.result.Result;
|
||||
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
|
||||
import com.atguigu.tingshu.search.service.SearchService;
|
||||
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
|
||||
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
@ -69,6 +70,34 @@ public class SearchApiController {
|
|||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
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.search.service.SearchService;
|
||||
import com.atguigu.tingshu.vo.album.TrackStatMqVo;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import lombok.SneakyThrows;
|
||||
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.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-22 15:22
|
||||
|
@ -23,6 +28,8 @@ public class SearchReciever {
|
|||
|
||||
@Autowired
|
||||
private SearchService searchService;
|
||||
@Autowired
|
||||
private RedisTemplate redisTemplate;
|
||||
|
||||
/**
|
||||
* 监听专辑上架消息
|
||||
|
@ -63,4 +70,38 @@ public class SearchReciever {
|
|||
}
|
||||
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);
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,6 @@ package com.atguigu.tingshu.search.repository;
|
|||
|
||||
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
|
||||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
||||
|
||||
//当启动这个服务的启动类的时候 当检测到有这个接口,可以自动创建索引库
|
||||
public interface AlbumInfoIndexRepository extends ElasticsearchRepository<AlbumInfoIndex, Long> {
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
}
|
|
@ -1,8 +1,13 @@
|
|||
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.vo.search.AlbumInfoIndexVo;
|
||||
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -18,5 +23,18 @@ public interface SearchService {
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -2,9 +2,10 @@ package com.atguigu.tingshu.search.service.impl;
|
|||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.extra.pinyin.PinyinUtil;
|
||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||
import co.elastic.clients.elasticsearch._types.FieldValue;
|
||||
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.core.SearchRequest;
|
||||
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.Suggestion;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.atguigu.tingshu.album.AlbumFeignClient;
|
||||
import com.atguigu.tingshu.common.result.Result;
|
||||
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
|
||||
import com.atguigu.tingshu.model.album.AlbumInfo;
|
||||
import com.atguigu.tingshu.model.album.BaseCategory3;
|
||||
import com.atguigu.tingshu.model.album.BaseCategoryView;
|
||||
import com.atguigu.tingshu.common.constant.RedisConstant;
|
||||
|
||||
import com.atguigu.tingshu.model.album.*;
|
||||
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
|
||||
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.search.repository.AlbumInfoIndexRepository;
|
||||
import com.atguigu.tingshu.search.repository.SuggestIndexRepository;
|
||||
import com.atguigu.tingshu.search.service.SearchService;
|
||||
import com.atguigu.tingshu.user.client.UserFeignClient;
|
||||
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
|
||||
|
@ -34,14 +37,16 @@ import jakarta.annotation.Resource;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
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.util.CollectionUtils;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -63,6 +68,11 @@ public class SearchServiceImpl implements SearchService {
|
|||
private UserFeignClient userFeignClient;
|
||||
@Autowired
|
||||
private ElasticsearchClient elasticsearchClient;
|
||||
private static final String SUGGEST_INDEX = "suggestinfo";
|
||||
@Autowired
|
||||
private SuggestIndexRepository suggestIndexRepository;
|
||||
@Autowired
|
||||
private RedisTemplate redisTemplate;
|
||||
|
||||
|
||||
@Override
|
||||
|
@ -132,6 +142,8 @@ public class SearchServiceImpl implements SearchService {
|
|||
|
||||
//7.保存专辑到索引库
|
||||
albumInfoIndexRepository.save(albumInfoIndex);
|
||||
//8.将专辑标题作为提示词文档存入提示词索引库
|
||||
this.saveSuggestIndex(albumInfoIndex);
|
||||
|
||||
|
||||
}
|
||||
|
@ -144,10 +156,11 @@ public class SearchServiceImpl implements SearchService {
|
|||
@Override
|
||||
public void lowerAlbum(Long albumId) {
|
||||
albumInfoIndexRepository.deleteById(albumId);
|
||||
suggestIndexRepository.deleteById(albumId.toString());
|
||||
}
|
||||
|
||||
|
||||
/*可以将这个想成kibana中开发工具哪个 buildDsl可以想象成左侧我们写的数据 我们在写
|
||||
/*可以将这个想成kibana中开发工具那个 buildDsl可以想象成左侧我们写的数据 我们在写
|
||||
buildDSL这个方法的时候就是在拼接这个dsl语句
|
||||
第二步执行检索的时候就是在执行那个播放键
|
||||
第三步就是在看右边的数据 我们要对这个数据进行分析*/
|
||||
|
@ -374,4 +387,169 @@ public class SearchServiceImpl implements SearchService {
|
|||
log.error("ES检索异常:{}", e.getMessage());
|
||||
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;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,10 +1,17 @@
|
|||
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.vo.user.UserListenProcessVo;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
|
||||
@Tag(name = "用户声音播放进度管理接口")
|
||||
@RestController
|
||||
|
@ -15,5 +22,50 @@ public class UserListenProcessApiController {
|
|||
@Autowired
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,16 @@
|
|||
package com.atguigu.tingshu.user.service;
|
||||
|
||||
import com.atguigu.tingshu.vo.user.UserListenProcessVo;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
|
||||
public interface UserListenProcessService {
|
||||
|
||||
BigDecimal getTrackBreakSecond(Long userId, Long trackId);
|
||||
|
||||
void updateListenProcess(UserListenProcessVo userListenProcessVo, Long userId);
|
||||
|
||||
Map<String, Long> getLatelyTrack(Long userId);
|
||||
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import cn.hutool.core.bean.BeanUtil;
|
|||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
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.rabbit.constant.MqConst;
|
||||
import com.atguigu.tingshu.common.rabbit.service.RabbitService;
|
||||
|
@ -97,6 +98,7 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
|
|||
}
|
||||
|
||||
@Override
|
||||
@GuiGuCache(prefix = "user:info:")
|
||||
public UserInfoVo getUserInfo(Long userId) {
|
||||
UserInfo userInfo = userInfoMapper.selectById(userId);
|
||||
return BeanUtil.copyProperties(userInfo, UserInfoVo.class);
|
||||
|
|
|
@ -1,15 +1,161 @@
|
|||
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.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.data.domain.Sort;
|
||||
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 java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service
|
||||
@SuppressWarnings({"all"})
|
||||
public class UserListenProcessServiceImpl implements UserListenProcessService {
|
||||
|
||||
@Autowired
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue