第12天 下订单
This commit is contained in:
parent
34cb7047bb
commit
e287bcea12
|
@ -38,41 +38,47 @@ public class GuiGuCacheAspect {
|
||||||
List<Object> argsList = Arrays.asList(pjp.getArgs());
|
List<Object> argsList = Arrays.asList(pjp.getArgs());
|
||||||
String args = "none";
|
String args = "none";
|
||||||
if (CollUtil.isNotEmpty(argsList)) {
|
if (CollUtil.isNotEmpty(argsList)) {
|
||||||
//将argsList转换成字符串,用_拼接
|
|
||||||
args = argsList.stream()
|
args = argsList.stream()
|
||||||
.map(Object::toString)
|
.map(Object::toString)
|
||||||
.collect(Collectors.joining("_"));
|
.collect(Collectors.joining("_"));
|
||||||
}
|
}
|
||||||
//用写这个注解的prefix属性拼接上参数args,得到缓存数据Key
|
|
||||||
String dataKey = guiGuCache.prefix() + args;
|
String dataKey = guiGuCache.prefix() + args;
|
||||||
|
//1.2 从Redis中获取缓存数据
|
||||||
Object result = redisTemplate.opsForValue().get(dataKey);
|
Object result = redisTemplate.opsForValue().get(dataKey);
|
||||||
|
//1.3 命中缓存直接返回即可
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
//2.尝试获取分布式锁
|
//2.尝试获取分布式锁
|
||||||
//2.1 构建锁Key
|
//2.1 构建锁Key
|
||||||
String lockKey = dataKey + RedisConstant.CACHE_LOCK_SUFFIX;
|
String lockKey = dataKey + RedisConstant.CACHE_LOCK_SUFFIX;
|
||||||
|
//2.2 基于RedissonClient创建锁对象
|
||||||
RLock lock = redissonClient.getLock(lockKey);
|
RLock lock = redissonClient.getLock(lockKey);
|
||||||
boolean flag= lock.tryLock(ALBUM_LOCK_WAIT_PX1, ALBUM_LOCK_EXPIRE_PX2, TimeUnit.SECONDS);
|
//2.3 尝试获取锁-设置锁过期时间
|
||||||
|
boolean flag =
|
||||||
|
lock.tryLock(ALBUM_LOCK_WAIT_PX1, ALBUM_LOCK_EXPIRE_PX2, TimeUnit.SECONDS);
|
||||||
|
|
||||||
//3.获取锁成功,执行目标方法,将结果缓存到Redis中,业务执行结束,释放锁
|
//3.获取锁成功,执行目标方法,将结果缓存到Redis中,业务执行结束,释放锁
|
||||||
if(flag){
|
if (flag) {
|
||||||
|
//3.1 执行查询数据库方法
|
||||||
try {
|
try {
|
||||||
//3.1 执行查询数据库方法
|
|
||||||
result = pjp.proceed();
|
result = pjp.proceed();
|
||||||
//设置超时时间,防止缓存雪崩
|
//3.2 将查询结果放入缓存中,将结果返回
|
||||||
long ttl = RedisConstant.ALBUM_TIMEOUT + RandomUtil.randomInt(600);
|
long ttl = RedisConstant.ALBUM_TIMEOUT + RandomUtil.randomInt(600);
|
||||||
|
redisTemplate.opsForValue().set(dataKey, result, ttl, TimeUnit.SECONDS);
|
||||||
return result;
|
return result;
|
||||||
} finally {
|
} finally {
|
||||||
//3.3 释放锁
|
//3.3 释放锁
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
//4.获取锁失败,自旋
|
//4.获取锁失败,自旋
|
||||||
TimeUnit.MILLISECONDS.sleep(30);
|
TimeUnit.MILLISECONDS.sleep(30);
|
||||||
return this.doBasicProfiling(pjp, guiGuCache);
|
return this.doBasicProfiling(pjp, guiGuCache);
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
//5.如果Redis服务可不用,兜底处理:直接执行目标方法
|
||||||
log.error("Redis服务不可用,请检查Redis服务是否正常!");
|
log.error("Redis服务不可用,请检查Redis服务是否正常!");
|
||||||
return pjp.proceed();
|
return pjp.proceed();
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,29 +20,29 @@ public class ThreadPoolConfig {
|
||||||
|
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
ZipkinHelper zipkinHelper;
|
ZipkinHelper zipkinHelper;
|
||||||
/**
|
// /**
|
||||||
* 基于JDK(JUC)提供线程池Class
|
// * 基于JDK(JUC)提供线程池Class
|
||||||
*/
|
// */
|
||||||
@Bean
|
// @Bean
|
||||||
public Executor threadPoolExecutor() {
|
// public Executor threadPoolExecutor() {
|
||||||
//1.获取当前服务器核心数确定核心线程数
|
// //1.获取当前服务器核心数确定核心线程数
|
||||||
int cpuCoreCount = Runtime.getRuntime().availableProcessors();
|
// int cpuCoreCount = Runtime.getRuntime().availableProcessors();
|
||||||
int threadCount = cpuCoreCount * 2;
|
// int threadCount = cpuCoreCount * 2;
|
||||||
//2.通过构造方法创建线程池对象
|
// //2.通过构造方法创建线程池对象
|
||||||
ThreadPoolExecutor threadPoolExecutor =
|
// ThreadPoolExecutor threadPoolExecutor =
|
||||||
new ThreadPoolExecutor(
|
// new ThreadPoolExecutor(
|
||||||
threadCount,
|
// threadCount,
|
||||||
threadCount,
|
// threadCount,
|
||||||
0,
|
// 0,
|
||||||
TimeUnit.SECONDS,
|
// TimeUnit.SECONDS,
|
||||||
new ArrayBlockingQueue<>(200),
|
// new ArrayBlockingQueue<>(200),
|
||||||
Executors.defaultThreadFactory(),
|
// Executors.defaultThreadFactory(),
|
||||||
new ThreadPoolExecutor.CallerRunsPolicy()
|
// new ThreadPoolExecutor.CallerRunsPolicy()
|
||||||
);
|
// );
|
||||||
//3.可选:提交创建核心线程
|
// //3.可选:提交创建核心线程
|
||||||
threadPoolExecutor.prestartCoreThread();
|
// threadPoolExecutor.prestartCoreThread();
|
||||||
return threadPoolExecutor;
|
// return threadPoolExecutor;
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package com.atguigu.tingshu.common.config.redis;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.redisson.Redisson;
|
||||||
|
import org.redisson.api.RedissonClient;
|
||||||
|
import org.redisson.config.Config;
|
||||||
|
import org.redisson.config.SingleServerConfig;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* redisson配置信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties("spring.data.redis")
|
||||||
|
public class RedissonConfig {
|
||||||
|
|
||||||
|
private String host;
|
||||||
|
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
private String port;
|
||||||
|
|
||||||
|
private int timeout = 3000;
|
||||||
|
private static String ADDRESS_PREFIX = "redis://";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动装配
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
RedissonClient redissonSingle() {
|
||||||
|
Config config = new Config();
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(host)) {
|
||||||
|
throw new RuntimeException("host is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
//设置看门狗时间=锁释放时间
|
||||||
|
config.setLockWatchdogTimeout(10000);
|
||||||
|
SingleServerConfig serverConfig =
|
||||||
|
config.useSingleServer()
|
||||||
|
.setAddress(ADDRESS_PREFIX + this.host + ":" + port)
|
||||||
|
.setTimeout(this.timeout);
|
||||||
|
if (StringUtils.isNotBlank(this.password)) {
|
||||||
|
serverConfig.setPassword(this.password);
|
||||||
|
}
|
||||||
|
return Redisson.create(config);
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
|
@ -2,16 +2,14 @@ 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.*;
|
||||||
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;
|
import com.atguigu.tingshu.vo.album.AlbumStatVo;
|
||||||
import org.springframework.cloud.openfeign.FeignClient;
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -30,9 +28,11 @@ public interface AlbumFeignClient {
|
||||||
*/
|
*/
|
||||||
@GetMapping("/albumInfo/getAlbumInfo/{id}")
|
@GetMapping("/albumInfo/getAlbumInfo/{id}")
|
||||||
public Result<AlbumInfo> getAlbumInfo(@PathVariable Long id);
|
public Result<AlbumInfo> getAlbumInfo(@PathVariable Long id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 后续改为Redis缓存
|
* 后续改为Redis缓存
|
||||||
* 根据三级分类ID查询分类视图
|
* 根据三级分类ID查询分类视图
|
||||||
|
*
|
||||||
* @param category3Id
|
* @param category3Id
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
|
@ -44,11 +44,13 @@ 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")
|
@GetMapping("/category/findAllCategory1")
|
||||||
Result<List<BaseCategory1>> findAllCategory1();
|
Result<List<BaseCategory1>> findAllCategory1();
|
||||||
|
|
||||||
|
@GetMapping("/trackInfo/findPaidTrackInfoList/{trackId}/{trackCount}")
|
||||||
|
public Result<List<TrackInfo>> findWaitBuyTrackList(@PathVariable Long trackId, @PathVariable Integer trackCount);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,15 +3,13 @@ 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.*;
|
||||||
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;
|
import com.atguigu.tingshu.vo.album.AlbumStatVo;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -47,4 +45,11 @@ public class AlbumDegradeFeignClient implements AlbumFeignClient {
|
||||||
log.error("[专辑服务]提供远程调用方法findAllCategory1执行服务降级");
|
log.error("[专辑服务]提供远程调用方法findAllCategory1执行服务降级");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<List<TrackInfo>> findWaitBuyTrackList(Long trackId, Integer trackCount) {
|
||||||
|
log.error("[专辑服务]提供远程调用方法findWaitBuyTrackList执行服务降级");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
|
@ -1,6 +1,7 @@
|
||||||
package com.atguigu.tingshu.user.client;
|
package com.atguigu.tingshu.user.client;
|
||||||
|
|
||||||
import com.atguigu.tingshu.common.result.Result;
|
import com.atguigu.tingshu.common.result.Result;
|
||||||
|
import com.atguigu.tingshu.model.user.VipServiceConfig;
|
||||||
import com.atguigu.tingshu.user.client.impl.UserDegradeFeignClient;
|
import com.atguigu.tingshu.user.client.impl.UserDegradeFeignClient;
|
||||||
import com.atguigu.tingshu.vo.user.UserInfoVo;
|
import com.atguigu.tingshu.vo.user.UserInfoVo;
|
||||||
import org.springframework.cloud.openfeign.FeignClient;
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
|
@ -19,10 +20,11 @@ import java.util.Map;
|
||||||
*
|
*
|
||||||
* @author atguigu
|
* @author atguigu
|
||||||
*/
|
*/
|
||||||
@FeignClient(value = "service-user",path = "/api/user",fallback = UserDegradeFeignClient.class)
|
@FeignClient(value = "service-user", path = "/api/user", fallback = UserDegradeFeignClient.class)
|
||||||
public interface UserFeignClient {
|
public interface UserFeignClient {
|
||||||
/**
|
/**
|
||||||
* 根据用户ID查询用户信息
|
* 根据用户ID查询用户信息
|
||||||
|
*
|
||||||
* @param userId
|
* @param userId
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
|
@ -35,4 +37,14 @@ public interface UserFeignClient {
|
||||||
@RequestBody List<Long> needCheckPayStateTrackIdList
|
@RequestBody List<Long> needCheckPayStateTrackIdList
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/vipServiceConfig/getVipServiceConfig/{id}")
|
||||||
|
public Result<VipServiceConfig> getVipServiceConfig(@PathVariable Long id);
|
||||||
|
|
||||||
|
@GetMapping("/userInfo/isPaidAlbum/{albumId}")
|
||||||
|
public Result<Boolean> isPaidAlbum(@PathVariable Long albumId);
|
||||||
|
|
||||||
|
@GetMapping("/userInfo/findUserPaidTrackList/{albumId}")
|
||||||
|
public Result<List<Long>> findUserPaidTrackList(@PathVariable Long albumId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.atguigu.tingshu.user.client.impl;
|
||||||
|
|
||||||
|
|
||||||
import com.atguigu.tingshu.common.result.Result;
|
import com.atguigu.tingshu.common.result.Result;
|
||||||
|
import com.atguigu.tingshu.model.user.VipServiceConfig;
|
||||||
import com.atguigu.tingshu.user.client.UserFeignClient;
|
import com.atguigu.tingshu.user.client.UserFeignClient;
|
||||||
import com.atguigu.tingshu.vo.user.UserInfoVo;
|
import com.atguigu.tingshu.vo.user.UserInfoVo;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -25,4 +26,22 @@ public class UserDegradeFeignClient implements UserFeignClient {
|
||||||
log.error("[用户服务]提供远程调用方法userIsPaidTrack执行服务降级");
|
log.error("[用户服务]提供远程调用方法userIsPaidTrack执行服务降级");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<VipServiceConfig> getVipServiceConfig(Long id) {
|
||||||
|
log.error("[用户服务]提供远程调用方法userIsPaidTrack执行服务降级");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Boolean> isPaidAlbum(Long albumId) {
|
||||||
|
log.error("[用户服务]提供远程调用方法isPaidAlbum执行服务降级");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<List<Long>> findUserPaidTrackList(Long albumId) {
|
||||||
|
log.error("[用户服务]提供远程调用方法findUserPaidTrackList执行服务降级");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
|
@ -20,6 +20,7 @@
|
||||||
<module>service-order</module>
|
<module>service-order</module>
|
||||||
<module>service-payment</module>
|
<module>service-payment</module>
|
||||||
<module>service-user</module>
|
<module>service-user</module>
|
||||||
|
<module>service-cdc</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
package com.atguigu.tingshu.account.api;
|
package com.atguigu.tingshu.account.api;
|
||||||
|
|
||||||
import com.atguigu.tingshu.account.service.UserAccountService;
|
import com.atguigu.tingshu.account.service.UserAccountService;
|
||||||
|
import com.atguigu.tingshu.common.login.GuiGuLogin;
|
||||||
|
import com.atguigu.tingshu.common.result.Result;
|
||||||
|
import com.atguigu.tingshu.common.util.AuthContextHolder;
|
||||||
|
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.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
@Tag(name = "用户账户管理")
|
@Tag(name = "用户账户管理")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("api/account")
|
@RequestMapping("api/account")
|
||||||
|
@ -14,6 +21,16 @@ public class UserAccountApiController {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserAccountService userAccountService;
|
private UserAccountService userAccountService;
|
||||||
|
@GuiGuLogin
|
||||||
|
@Operation(summary = "查询当前用户账户可用余额")
|
||||||
|
@GetMapping("/userAccount/getAvailableAmount")
|
||||||
|
public Result<BigDecimal> getAvailableAmount(){
|
||||||
|
Long userId = AuthContextHolder.getUserId();
|
||||||
|
BigDecimal Amount = userAccountService.getAvailableAmount(userId);
|
||||||
|
return Result.ok(Amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,4 +22,6 @@ public interface UserAccountService extends IService<UserAccount> {
|
||||||
* @param orderNo 订单编号
|
* @param orderNo 订单编号
|
||||||
*/
|
*/
|
||||||
void saveUserAccountDetail(Long userId, String title, BigDecimal amount, String orderNo);
|
void saveUserAccountDetail(Long userId, String title, BigDecimal amount, String orderNo);
|
||||||
|
|
||||||
|
BigDecimal getAvailableAmount(Long userId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import com.atguigu.tingshu.account.service.UserAccountService;
|
||||||
import com.atguigu.tingshu.common.constant.SystemConstant;
|
import com.atguigu.tingshu.common.constant.SystemConstant;
|
||||||
import com.atguigu.tingshu.model.account.UserAccount;
|
import com.atguigu.tingshu.model.account.UserAccount;
|
||||||
import com.atguigu.tingshu.model.account.UserAccountDetail;
|
import com.atguigu.tingshu.model.account.UserAccountDetail;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
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.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
@ -58,4 +59,14 @@ public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserA
|
||||||
userAccountDetail.setOrderNo(orderNo);
|
userAccountDetail.setOrderNo(orderNo);
|
||||||
userAccountDetailMapper.insert(userAccountDetail);
|
userAccountDetailMapper.insert(userAccountDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getAvailableAmount(Long userId) {
|
||||||
|
UserAccount userAccount = userAccountMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<UserAccount>().eq(UserAccount::getUserId, userId)
|
||||||
|
);
|
||||||
|
BigDecimal availableAmount = userAccount.getAvailableAmount();
|
||||||
|
return availableAmount;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
package com.atguigu.tingshu;
|
package com.atguigu.tingshu;
|
||||||
|
|
||||||
|
import com.atguigu.tingshu.common.constant.RedisConstant;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.redisson.api.RBloomFilter;
|
||||||
|
import org.redisson.api.RedissonClient;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||||
|
@ -10,10 +16,21 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
@EnableDiscoveryClient
|
@EnableDiscoveryClient
|
||||||
@EnableFeignClients
|
@EnableFeignClients
|
||||||
@EnableScheduling //开启定时任务功能
|
@EnableScheduling //开启定时任务功能
|
||||||
public class ServiceAlbumApplication {
|
@Slf4j
|
||||||
|
public class ServiceAlbumApplication implements CommandLineRunner {
|
||||||
|
@Autowired
|
||||||
|
private RedissonClient redissonClient;
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(ServiceAlbumApplication.class, args);
|
SpringApplication.run(ServiceAlbumApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(String... args) throws Exception {
|
||||||
|
RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER);
|
||||||
|
if (!bloomFilter.isExists()){
|
||||||
|
log.info("初始化布隆过滤器,将布隆过滤器配置信息写入redis中采用hash结构存储");
|
||||||
|
bloomFilter.tryInit(10000, 0.03);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ 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;
|
||||||
import com.atguigu.tingshu.model.album.TrackInfo;
|
import com.atguigu.tingshu.model.album.TrackInfo;
|
||||||
|
import com.atguigu.tingshu.model.user.UserInfo;
|
||||||
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.TrackInfoVo;
|
import com.atguigu.tingshu.vo.album.TrackInfoVo;
|
||||||
|
@ -162,6 +163,37 @@ public class TrackInfoApiController {
|
||||||
return Result.ok(trackStatVo);
|
return Result.ok(trackStatVo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于用户选择声音作为起始标准获取未购买声音数量,得到分集购买列表
|
||||||
|
*
|
||||||
|
* @param trackId
|
||||||
|
* @return [{name:"本集",price:0.1,trackCount:1},{name:"后10集",price:1,trackCount:10},...,{name:"全集",price:4.3,trackCount:43},]
|
||||||
|
*/
|
||||||
|
@GuiGuLogin
|
||||||
|
@Operation(summary = "基于用户选择声音作为起始标准获取未购买声音数量,得到分集购买列表")
|
||||||
|
@GetMapping("/trackInfo/findUserTrackPaidList/{trackId}")
|
||||||
|
public Result<List<Map<String, Object>>> findUserTrackPaidList(@PathVariable Long trackId){
|
||||||
|
Long userId = AuthContextHolder.getUserId();
|
||||||
|
List<Map<String, Object>> list =trackInfoService.findUserTrackPaidList(trackId,userId);
|
||||||
|
return Result.ok(list);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 基于选择购买声音ID作为起始获取指定数量未购买声音列表
|
||||||
|
*
|
||||||
|
* @param trackId
|
||||||
|
* @param trackCount
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GuiGuLogin
|
||||||
|
@Operation(summary = "基于选择购买声音ID作为起始获取指定数量未购买声音列表")
|
||||||
|
@GetMapping("/trackInfo/findPaidTrackInfoList/{trackId}/{trackCount}")
|
||||||
|
public Result<List<TrackInfo>> findWaitBuyTrackList(@PathVariable Long trackId, @PathVariable Integer trackCount){
|
||||||
|
Long userId = AuthContextHolder.getUserId();
|
||||||
|
List<TrackInfo> list =trackInfoService.findWaitBuyTrackList(trackId,trackCount,userId);
|
||||||
|
return Result.ok( list);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -30,4 +30,10 @@ public interface AlbumInfoService extends IService<AlbumInfo> {
|
||||||
List<AlbumInfo> findUserAllAlbumList(Long userId);
|
List<AlbumInfo> findUserAllAlbumList(Long userId);
|
||||||
|
|
||||||
AlbumStatVo getAlbumStatVo(Long albumId);
|
AlbumStatVo getAlbumStatVo(Long albumId);
|
||||||
|
/**
|
||||||
|
* 查询指定状态专辑ID列表
|
||||||
|
* @param status
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<Long> findAlbumListByStatus(String status);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import com.atguigu.tingshu.vo.album.*;
|
||||||
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;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public interface TrackInfoService extends IService<TrackInfo> {
|
public interface TrackInfoService extends IService<TrackInfo> {
|
||||||
|
@ -37,4 +38,9 @@ public interface TrackInfoService extends IService<TrackInfo> {
|
||||||
TrackStatVo getTrackStatVo(Long trackId);
|
TrackStatVo getTrackStatVo(Long trackId);
|
||||||
|
|
||||||
void updateStat(TrackStatMqVo mqVo);
|
void updateStat(TrackStatMqVo mqVo);
|
||||||
|
|
||||||
|
List<Map<String, Object>> findUserTrackPaidList(Long trackId, Long userId);
|
||||||
|
|
||||||
|
List<TrackInfo> findWaitBuyTrackList(Long trackId, Integer trackCount, Long userId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -241,4 +241,21 @@ public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo
|
||||||
public AlbumStatVo getAlbumStatVo(Long albumId) {
|
public AlbumStatVo getAlbumStatVo(Long albumId) {
|
||||||
return albumInfoMapper.getAlbumStatVo(albumId);
|
return albumInfoMapper.getAlbumStatVo(albumId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//通过状态查询所有的专辑
|
||||||
|
@Override
|
||||||
|
public List<Long> findAlbumListByStatus(String status) {
|
||||||
|
List<AlbumInfo> albumInfos = albumInfoMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<AlbumInfo>()
|
||||||
|
.eq(AlbumInfo::getStatus, status)
|
||||||
|
.select(AlbumInfo::getId)
|
||||||
|
);
|
||||||
|
if(CollUtil.isNotEmpty(albumInfos)){
|
||||||
|
return albumInfos.stream().map(AlbumInfo::getId).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.atguigu.tingshu.album.service.impl;
|
package com.atguigu.tingshu.album.service.impl;
|
||||||
|
|
||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
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.AlbumStatMapper;
|
||||||
|
@ -15,11 +16,13 @@ import com.atguigu.tingshu.model.album.AlbumInfo;
|
||||||
import com.atguigu.tingshu.model.album.AlbumStat;
|
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.model.user.UserInfo;
|
||||||
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.*;
|
import com.atguigu.tingshu.vo.album.*;
|
||||||
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.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
@ -30,9 +33,7 @@ import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Date;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.atguigu.tingshu.common.constant.SystemConstant.ALBUM_PAY_TYPE_REQUIRE;
|
import static com.atguigu.tingshu.common.constant.SystemConstant.ALBUM_PAY_TYPE_REQUIRE;
|
||||||
|
@ -290,7 +291,7 @@ public class TrackInfoServiceImpl extends ServiceImpl<TrackInfoMapper, TrackInfo
|
||||||
pageInfo.getRecords()
|
pageInfo.getRecords()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(track -> track.getOrderNum() > tracksForFree)
|
.filter(track -> track.getOrderNum() > tracksForFree)
|
||||||
.forEach(track-> track.setIsShowPaidMark(payStatusMap.get(track.getTrackId()) == 0));
|
.forEach(track -> track.setIsShowPaidMark(payStatusMap.get(track.getTrackId()) == 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pageInfo;
|
return pageInfo;
|
||||||
|
@ -299,7 +300,7 @@ public class TrackInfoServiceImpl extends ServiceImpl<TrackInfoMapper, TrackInfo
|
||||||
@Override
|
@Override
|
||||||
@GuiGuCache(prefix = "album:trackInfo:stat:")
|
@GuiGuCache(prefix = "album:trackInfo:stat:")
|
||||||
public TrackStatVo getTrackStatVo(Long trackId) {
|
public TrackStatVo getTrackStatVo(Long trackId) {
|
||||||
return trackInfoMapper.getTrackStatVo(trackId);
|
return trackInfoMapper.getTrackStatVo(trackId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,9 +315,8 @@ public class TrackInfoServiceImpl extends ServiceImpl<TrackInfoMapper, TrackInfo
|
||||||
.eq(TrackStat::getStatType, mqVo.getStatType())
|
.eq(TrackStat::getStatType, mqVo.getStatType())
|
||||||
.setSql("stat_num = stat_num +" + mqVo.getCount())
|
.setSql("stat_num = stat_num +" + mqVo.getCount())
|
||||||
);
|
);
|
||||||
|
|
||||||
//2.如果是声音播放、评论。还需要更新声音所属专辑统计数值
|
//2.如果是声音播放、评论。还需要更新声音所属专辑统计数值
|
||||||
if(SystemConstant.TRACK_STAT_PLAY.equals(mqVo.getStatType())){
|
if (SystemConstant.TRACK_STAT_PLAY.equals(mqVo.getStatType())) {
|
||||||
albumStatMapper.update(
|
albumStatMapper.update(
|
||||||
null,
|
null,
|
||||||
new LambdaUpdateWrapper<AlbumStat>()
|
new LambdaUpdateWrapper<AlbumStat>()
|
||||||
|
@ -325,8 +325,7 @@ public class TrackInfoServiceImpl extends ServiceImpl<TrackInfoMapper, TrackInfo
|
||||||
.setSql("stat_num = stat_num +" + mqVo.getCount())
|
.setSql("stat_num = stat_num +" + mqVo.getCount())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (SystemConstant.TRACK_STAT_COMMENT.equals(mqVo.getStatType())) {
|
||||||
if(SystemConstant.TRACK_STAT_COMMENT.equals(mqVo.getStatType())){
|
|
||||||
albumStatMapper.update(
|
albumStatMapper.update(
|
||||||
null,
|
null,
|
||||||
new LambdaUpdateWrapper<AlbumStat>()
|
new LambdaUpdateWrapper<AlbumStat>()
|
||||||
|
@ -339,7 +338,83 @@ public class TrackInfoServiceImpl extends ServiceImpl<TrackInfoMapper, TrackInfo
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Map<String, Object>> findUserTrackPaidList(Long trackId, Long userId) {
|
||||||
|
//根据声音id去查询声音的属性
|
||||||
|
TrackInfo trackInfo = trackInfoMapper.selectById(trackId);
|
||||||
|
//查出来声音是哪个专辑的
|
||||||
|
Long albumId = trackInfo.getAlbumId();
|
||||||
|
//获取声音的排序值
|
||||||
|
Integer orderNum = trackInfo.getOrderNum();
|
||||||
|
//2.根据"起始"声音序号+专辑ID获取所有“待购买”声音列表(可能包含已购买声音)
|
||||||
|
List<TrackInfo> waitBuyTrackList = trackInfoMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<TrackInfo>()
|
||||||
|
.eq(TrackInfo::getAlbumId, albumId)
|
||||||
|
.ge(TrackInfo::getOrderNum, orderNum)
|
||||||
|
.select(TrackInfo::getId)
|
||||||
|
);
|
||||||
|
//3.远程调用用户服务获取已购买声音ID列表
|
||||||
|
List<Long> userPaidTrackIdList = userFeignClient.findUserPaidTrackList(albumId).getData();
|
||||||
|
if (CollUtil.isNotEmpty(userPaidTrackIdList)) {
|
||||||
|
//4.如果存在已购声音ID,则将已购声音ID从待购买声音列表中移除,得到未购买声音数量
|
||||||
|
waitBuyTrackList = waitBuyTrackList.stream()
|
||||||
|
.filter(f -> !userPaidTrackIdList.contains(f.getId()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
//5.基于未购买声音数量,生成分集购买列表
|
||||||
|
//查出来未存在的专辑数量
|
||||||
|
int size = waitBuyTrackList.size();
|
||||||
|
AlbumInfo albumInfo = albumInfoMapper.selectById(albumId);
|
||||||
|
BigDecimal price = albumInfo.getPrice();
|
||||||
|
//5.1 创建分集购买集合List
|
||||||
|
List<Map<String, Object>> list = new ArrayList<>();
|
||||||
|
//5.2 构建"本集"分集购买对象
|
||||||
|
Map<String, Object> currMap = new HashMap<>();
|
||||||
|
currMap.put("name", "本集");
|
||||||
|
currMap.put("price", price);
|
||||||
|
currMap.put("trackCount", 1);
|
||||||
|
list.add(currMap);
|
||||||
|
//5.3 构建其他"后*集"分集购买对象 未购买数量:23 43
|
||||||
|
for (int i = 10; i <= 50; i += 10) {
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
if (size >= i) {
|
||||||
|
map.put("name", "后" + i + "集");
|
||||||
|
map.put("price", price.multiply(new BigDecimal(i)));
|
||||||
|
map.put("trackCount", i);
|
||||||
|
list.add(map);
|
||||||
|
} else {
|
||||||
|
map.put("name", "全" + size + "集");
|
||||||
|
map.put("price", price.multiply(new BigDecimal(size)));
|
||||||
|
map.put("trackCount", size);
|
||||||
|
list.add(map);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TrackInfo> findWaitBuyTrackList(Long trackId, Integer trackCount, Long userId) {
|
||||||
|
//1.根据选择声音ID查询声音,得到专辑ID跟序号
|
||||||
|
TrackInfo trackInfo = trackInfoMapper.selectById(trackId);
|
||||||
|
Long albumId = trackInfo.getAlbumId();
|
||||||
|
Integer orderNum = trackInfo.getOrderNum();
|
||||||
|
|
||||||
|
//2.远程调用用户服务获取已购买声音ID列表
|
||||||
|
List<Long> userPaidTrackIdList = userFeignClient.findUserPaidTrackList(albumId).getData();
|
||||||
|
|
||||||
|
//3.获取指定数量未购买声音列表
|
||||||
|
LambdaQueryWrapper<TrackInfo> queryWrapper = new LambdaQueryWrapper<TrackInfo>()
|
||||||
|
.eq(TrackInfo::getAlbumId, albumId)
|
||||||
|
.eq(TrackInfo::getOrderNum, orderNum)
|
||||||
|
.last("limit " + trackCount)
|
||||||
|
.select(TrackInfo::getId, TrackInfo::getTrackTitle, TrackInfo::getCoverUrl, TrackInfo::getAlbumId);
|
||||||
|
if (CollUtil.isNotEmpty(userPaidTrackIdList)) {
|
||||||
|
queryWrapper.notIn(TrackInfo::getId, userPaidTrackIdList);
|
||||||
|
}
|
||||||
|
List<TrackInfo> trackInfoList = trackInfoMapper.selectList(queryWrapper);
|
||||||
|
return trackInfoList;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
package com.atguigu.tingshu.album.task;
|
||||||
|
|
||||||
|
import com.atguigu.tingshu.album.service.AlbumInfoService;
|
||||||
|
import com.atguigu.tingshu.common.constant.RedisConstant;
|
||||||
|
import com.atguigu.tingshu.common.constant.SystemConstant;
|
||||||
|
import com.atguigu.tingshu.model.album.AlbumInfo;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.redisson.Redisson;
|
||||||
|
import org.redisson.api.RBloomFilter;
|
||||||
|
import org.redisson.api.RedissonClient;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class RebuildBloomFilterTask {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedissonClient redissonClient;
|
||||||
|
@Autowired
|
||||||
|
private AlbumInfoService albumInfoService;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Scheduled(cron = "0 0 2 1 * ?")
|
||||||
|
public void rebuildBloomFilter() {
|
||||||
|
//1.获取到原有布隆过滤器对象 得到配置信息:期望数据量,现有数量预估值,误判率
|
||||||
|
RBloomFilter<Long> oldBloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER);
|
||||||
|
long count = oldBloomFilter.count();
|
||||||
|
long expectedInsertions = oldBloomFilter.getExpectedInsertions();
|
||||||
|
double falseProbability = oldBloomFilter.getFalseProbability();
|
||||||
|
//2.如果现有数量预估值大于期望数据规模,进行扩容
|
||||||
|
if(count>=expectedInsertions){
|
||||||
|
log.info("当前布隆过滤器已满,开始扩容");
|
||||||
|
//2.1 新建布隆过滤器对象,初始化配置信息不变
|
||||||
|
RBloomFilter<Object> newBloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER + ":new");
|
||||||
|
newBloomFilter.tryInit(expectedInsertions * 2, falseProbability);
|
||||||
|
//2.2 将现有过审专辑ID添加到新布隆过滤器对象中
|
||||||
|
List<Long> albumList = albumInfoService.findAlbumListByStatus(SystemConstant.ALBUM_STATUS_PASS);
|
||||||
|
for (Long albumId : albumList) {
|
||||||
|
newBloomFilter.add(albumId);
|
||||||
|
}
|
||||||
|
log.info("新布隆过滤器已创建完成");
|
||||||
|
//2.3 删除旧布隆过滤器对象(配置&位图)
|
||||||
|
oldBloomFilter.delete();
|
||||||
|
//2.4 重命名布隆过滤器,改为原有名称
|
||||||
|
newBloomFilter.rename(RedisConstant.ALBUM_BLOOM_FILTER);
|
||||||
|
log.info("扩容结束");
|
||||||
|
}else{
|
||||||
|
log.info("开始重建布隆过滤器");
|
||||||
|
//3.如果现有数量预估值小于期望数据规模,进行重建
|
||||||
|
//3.1 新建布隆过滤器对象,初始化配置信息不变
|
||||||
|
RBloomFilter<Long> newBloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER + ":new");
|
||||||
|
newBloomFilter.tryInit(expectedInsertions, falseProbability);
|
||||||
|
try {
|
||||||
|
//3.2 将现有过审专辑ID添加到新布隆过滤器对象中
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
List<Long> albumIdList = albumInfoService.findAlbumListByStatus(SystemConstant.ALBUM_STATUS_PASS);
|
||||||
|
for (Long albumId : albumIdList) {
|
||||||
|
newBloomFilter.add(albumId);
|
||||||
|
}
|
||||||
|
//3.3 删除旧布隆过滤器对象(配置&位图)
|
||||||
|
oldBloomFilter.delete();
|
||||||
|
|
||||||
|
//3.4 重命名布隆过滤器,改为原有名称
|
||||||
|
newBloomFilter.rename(RedisConstant.ALBUM_BLOOM_FILTER);
|
||||||
|
log.info("重建布隆过滤器结束");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,8 @@ import com.atguigu.tingshu.model.album.TrackInfo;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.redisson.api.RLock;
|
||||||
|
import org.redisson.api.RedissonClient;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
@ -27,34 +29,41 @@ public class ReviewTask {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private AuditService auditService;
|
private AuditService auditService;
|
||||||
|
@Autowired
|
||||||
|
private RedissonClient redissonClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 每5秒检查一次审核结果
|
* 每5秒检查一次审核结果
|
||||||
*/
|
*/
|
||||||
@Scheduled(cron = "0/5 * * * * ?")
|
@Scheduled(cron = "0/5 * * * * ?")
|
||||||
public void checkReviewResultTask() {
|
public void checkReviewResultTask() {
|
||||||
log.info("开始检查审核任务");
|
|
||||||
//1.查询审核状态为"审核中"声音列表
|
RLock lock = redissonClient.getLock("reviewtask:lock");
|
||||||
List<TrackInfo> trackInfoList = trackInfoMapper.selectList(
|
boolean b = lock.tryLock();
|
||||||
new LambdaQueryWrapper<TrackInfo>()
|
if (b) {
|
||||||
.eq(TrackInfo::getStatus, SystemConstant.TRACK_STATUS_REVIEWING)
|
log.info("开始检查审核任务");
|
||||||
.select(TrackInfo::getId, TrackInfo::getReviewTaskId)
|
//1.查询审核状态为"审核中"声音列表
|
||||||
.last("limit 100")
|
List<TrackInfo> trackInfoList = trackInfoMapper.selectList(
|
||||||
);
|
new LambdaQueryWrapper<TrackInfo>()
|
||||||
//2.遍历列表,查询审核结果
|
.eq(TrackInfo::getStatus, SystemConstant.TRACK_STATUS_REVIEWING)
|
||||||
if (CollUtil.isNotEmpty(trackInfoList)) {
|
.select(TrackInfo::getId, TrackInfo::getReviewTaskId)
|
||||||
for (TrackInfo trackInfo : trackInfoList) {
|
.last("limit 100")
|
||||||
String suggestion = auditService.getReviewResult(trackInfo.getReviewTaskId());
|
);
|
||||||
if (StringUtils.isNotBlank(suggestion)) {
|
//2.遍历列表,查询审核结果
|
||||||
//3.根据审核任务结果更新声音审核状态
|
if (CollUtil.isNotEmpty(trackInfoList)) {
|
||||||
if ("pass".equals(suggestion)) {
|
for (TrackInfo trackInfo : trackInfoList) {
|
||||||
trackInfo.setStatus(SystemConstant.TRACK_STATUS_PASS);
|
String suggestion = auditService.getReviewResult(trackInfo.getReviewTaskId());
|
||||||
} else if ("block".equals(suggestion)) {
|
if (StringUtils.isNotBlank(suggestion)) {
|
||||||
trackInfo.setStatus(SystemConstant.TRACK_STATUS_NO_PASS);
|
//3.根据审核任务结果更新声音审核状态
|
||||||
} else if ("review".equals(suggestion)) {
|
if ("pass".equals(suggestion)) {
|
||||||
trackInfo.setStatus(SystemConstant.TRACK_STATUS_ARTIFICIAL);
|
trackInfo.setStatus(SystemConstant.TRACK_STATUS_PASS);
|
||||||
|
} else if ("block".equals(suggestion)) {
|
||||||
|
trackInfo.setStatus(SystemConstant.TRACK_STATUS_NO_PASS);
|
||||||
|
} else if ("review".equals(suggestion)) {
|
||||||
|
trackInfo.setStatus(SystemConstant.TRACK_STATUS_ARTIFICIAL);
|
||||||
|
}
|
||||||
|
trackInfoMapper.updateById(trackInfo);
|
||||||
}
|
}
|
||||||
trackInfoMapper.updateById(trackInfo);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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.
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.atguigu.tingshu</groupId>
|
||||||
|
<artifactId>service</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>service-cdc</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.xizixuejie</groupId>
|
||||||
|
<artifactId>canal-spring-boot-starter</artifactId>
|
||||||
|
<version>0.0.17</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.atguigu.tingshu;
|
||||||
|
|
||||||
|
import io.xzxj.canal.spring.annotation.EnableCanalListener;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: atguigu
|
||||||
|
* @create: 2025-03-21 15:46
|
||||||
|
*/
|
||||||
|
@EnableCanalListener
|
||||||
|
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
|
||||||
|
public class CDCApplicaiton {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(CDCApplicaiton.class, args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.atguigu.tingshu.listener;
|
||||||
|
|
||||||
|
import com.atguigu.tingshu.model.album.AlbumInfo;
|
||||||
|
import io.xzxj.canal.core.annotation.CanalListener;
|
||||||
|
import io.xzxj.canal.core.listener.EntryListener;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: atguigu
|
||||||
|
* @create: 2025-03-22 08:46
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@CanalListener(destination = "tingshuTopic", schemaName = "tingshu_album", tableName = "album_info")
|
||||||
|
public class AlbumListener implements EntryListener<AlbumInfo> {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisTemplate redisTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听到转机信息表更新
|
||||||
|
* @param before 变更前数据
|
||||||
|
* @param after 变更后的数据
|
||||||
|
* @param fields 变更字段
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void update(AlbumInfo before, AlbumInfo after, Set<String> fields) {
|
||||||
|
log.info("[cdc]监听到变更数据,变更前:{},变更后:{}", before, after);
|
||||||
|
Long id = after.getId();
|
||||||
|
String redisKey = "album:info:"+id;
|
||||||
|
redisTemplate.delete(redisKey);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.atguigu.tingshu.listener;
|
||||||
|
|
||||||
|
import com.atguigu.tingshu.model.user.UserInfo;
|
||||||
|
import io.xzxj.canal.core.annotation.CanalListener;
|
||||||
|
import io.xzxj.canal.core.listener.EntryListener;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: atguigu
|
||||||
|
* @create: 2025-03-21 15:47
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@CanalListener(destination = "tingshuTopic", schemaName = "tingshu_user", tableName = "user_info")
|
||||||
|
public class UserListener implements EntryListener<UserInfo> {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisTemplate redisTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听用户表更新回调方法
|
||||||
|
* @param before
|
||||||
|
* @param after
|
||||||
|
* @param fields
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void update(UserInfo before, UserInfo after, Set<String> fields) {
|
||||||
|
log.info("[cdc]监听到变更数据:{}", after);
|
||||||
|
String redisKey = "user:info:"+after.getId();
|
||||||
|
redisTemplate.delete(redisKey);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.atguigu.tingshu.listener;
|
||||||
|
|
||||||
|
import com.atguigu.tingshu.model.user.VipServiceConfig;
|
||||||
|
import io.xzxj.canal.core.annotation.CanalListener;
|
||||||
|
import io.xzxj.canal.core.listener.EntryListener;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: atguigu
|
||||||
|
* @create: 2025-03-22 08:46
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@CanalListener(destination = "tingshuTopic", schemaName = "tingshu_user", tableName = "vip_service_config")
|
||||||
|
public class VipServiceConfigListener implements EntryListener<VipServiceConfig> {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisTemplate redisTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听到VIP套餐信息表更新
|
||||||
|
* @param before 变更前数据
|
||||||
|
* @param after 变更后的数据
|
||||||
|
* @param fields 变更字段
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void update(VipServiceConfig before, VipServiceConfig after, Set<String> fields) {
|
||||||
|
log.info("[cdc]监听到变更数据,变更前:{},变更后:{}", before, after);
|
||||||
|
Long id = after.getId();
|
||||||
|
String redisKey = "user:vipconfig:"+id;
|
||||||
|
redisTemplate.delete(redisKey);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
spring.application.name=service-canal
|
||||||
|
spring.profiles.active=dev
|
||||||
|
spring.main.allow-bean-definiton-overriding=true
|
||||||
|
spring.cloud.nacos.discovery.server-addr=192.168.200.6:8848
|
||||||
|
spring.cloud.nacos.config.server-addr=192.168.200.6:8848
|
||||||
|
spring.cloud.nacos.config.prefix=${spring.application.name}
|
||||||
|
spring.cloud.nacos.config.file-extension=yaml
|
||||||
|
spring.cloud.nacos.config.shared-configs[0].data-id=common.yaml
|
|
@ -1,8 +1,15 @@
|
||||||
package com.atguigu.tingshu.order.api;
|
package com.atguigu.tingshu.order.api;
|
||||||
|
|
||||||
|
import com.atguigu.tingshu.common.login.GuiGuLogin;
|
||||||
|
import com.atguigu.tingshu.common.result.Result;
|
||||||
import com.atguigu.tingshu.order.service.OrderInfoService;
|
import com.atguigu.tingshu.order.service.OrderInfoService;
|
||||||
|
import com.atguigu.tingshu.vo.order.OrderInfoVo;
|
||||||
|
import com.atguigu.tingshu.vo.order.TradeVo;
|
||||||
|
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.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@ -16,5 +23,14 @@ public class OrderInfoApiController {
|
||||||
private OrderInfoService orderInfoService;
|
private OrderInfoService orderInfoService;
|
||||||
|
|
||||||
|
|
||||||
|
@GuiGuLogin
|
||||||
|
@Operation(summary = "三种商品(VIP会员、专辑、声音)订单结算")
|
||||||
|
@PostMapping("/orderInfo/trade")
|
||||||
|
public Result<OrderInfoVo> trade(@RequestBody TradeVo tradeVo) {
|
||||||
|
OrderInfoVo orderInfoVo =orderInfoService.trade(tradeVo);
|
||||||
|
return Result.ok(orderInfoVo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
package com.atguigu.tingshu.order.service;
|
package com.atguigu.tingshu.order.service;
|
||||||
|
|
||||||
import com.atguigu.tingshu.model.order.OrderInfo;
|
import com.atguigu.tingshu.model.order.OrderInfo;
|
||||||
|
import com.atguigu.tingshu.vo.order.OrderInfoVo;
|
||||||
|
import com.atguigu.tingshu.vo.order.TradeVo;
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
|
||||||
public interface OrderInfoService extends IService<OrderInfo> {
|
public interface OrderInfoService extends IService<OrderInfo> {
|
||||||
|
|
||||||
|
|
||||||
|
OrderInfoVo trade(TradeVo tradeVo);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,36 @@
|
||||||
package com.atguigu.tingshu.order.service.impl;
|
package com.atguigu.tingshu.order.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import com.atguigu.tingshu.album.AlbumFeignClient;
|
||||||
|
import com.atguigu.tingshu.common.constant.SystemConstant;
|
||||||
|
import com.atguigu.tingshu.common.result.Result;
|
||||||
|
import com.atguigu.tingshu.common.util.AuthContextHolder;
|
||||||
|
import com.atguigu.tingshu.model.album.AlbumInfo;
|
||||||
|
import com.atguigu.tingshu.model.album.TrackInfo;
|
||||||
import com.atguigu.tingshu.model.order.OrderInfo;
|
import com.atguigu.tingshu.model.order.OrderInfo;
|
||||||
|
import com.atguigu.tingshu.model.user.VipServiceConfig;
|
||||||
import com.atguigu.tingshu.order.mapper.OrderInfoMapper;
|
import com.atguigu.tingshu.order.mapper.OrderInfoMapper;
|
||||||
import com.atguigu.tingshu.order.service.OrderInfoService;
|
import com.atguigu.tingshu.order.service.OrderInfoService;
|
||||||
|
import com.atguigu.tingshu.user.client.UserFeignClient;
|
||||||
|
import com.atguigu.tingshu.vo.order.OrderDerateVo;
|
||||||
|
import com.atguigu.tingshu.vo.order.OrderDetailVo;
|
||||||
|
import com.atguigu.tingshu.vo.order.OrderInfoVo;
|
||||||
|
import com.atguigu.tingshu.vo.order.TradeVo;
|
||||||
|
import com.atguigu.tingshu.vo.user.UserInfoVo;
|
||||||
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.aspectj.weaver.ast.Or;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@SuppressWarnings({"all"})
|
@SuppressWarnings({"all"})
|
||||||
|
@ -15,6 +38,165 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private OrderInfoMapper orderInfoMapper;
|
private OrderInfoMapper orderInfoMapper;
|
||||||
|
@Autowired
|
||||||
|
private UserFeignClient userFeignClient;
|
||||||
|
@Qualifier("com.atguigu.tingshu.album.AlbumFeignClient")
|
||||||
|
@Autowired
|
||||||
|
private AlbumFeignClient albumFeignClient;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OrderInfoVo trade(TradeVo tradeVo) {
|
||||||
|
//从线程中取出userid来
|
||||||
|
Long userId = AuthContextHolder.getUserId();
|
||||||
|
//1.创建订单VO对象,创建初始三个金额,两个集合(商品、优惠)
|
||||||
|
OrderInfoVo orderInfoVo = new OrderInfoVo();
|
||||||
|
//1.1 初始化原价金额
|
||||||
|
// 必须是BigDecimal类型且必须是字符串
|
||||||
|
BigDecimal originalAmount = new BigDecimal("0.00");
|
||||||
|
//1.2 初始化减免金额
|
||||||
|
BigDecimal derateAmount = new BigDecimal("0.00");
|
||||||
|
//1.3 初始化订单金额
|
||||||
|
BigDecimal orderAmount = new BigDecimal("0.00");
|
||||||
|
|
||||||
|
//1.4.初始化订单明细
|
||||||
|
List<OrderDetailVo> orderDetailVoList = new ArrayList<>();
|
||||||
|
//1.5.初始化订单减免明细列表
|
||||||
|
List<OrderDerateVo> orderDerateVoList = new ArrayList<>();
|
||||||
|
//todo 流水号 签名之类的还未添加
|
||||||
|
//1.6 获取购买项目类型 付款项目类型: 1001-专辑 1002-声音 1003-vip会员
|
||||||
|
String itemType = tradeVo.getItemType();
|
||||||
|
//2. 处理购买项目类型:VIP套餐
|
||||||
|
if (SystemConstant.ORDER_ITEM_TYPE_VIP.equals(itemType)) {
|
||||||
|
//2.1 远程调用用户服务得到VIP套餐信息
|
||||||
|
VipServiceConfig vipServiceConfig = userFeignClient.getVipServiceConfig(tradeVo.getItemId()).getData();
|
||||||
|
Assert.notNull(vipServiceConfig, "VIP套餐{}不存在", tradeVo.getItemId());
|
||||||
|
//2.2 计算原金额、订单金额、减免金额
|
||||||
|
originalAmount = vipServiceConfig.getPrice();
|
||||||
|
orderAmount = vipServiceConfig.getDiscountPrice();
|
||||||
|
//2.3 封装商品信息列表
|
||||||
|
OrderDetailVo orderDetailVo = new OrderDetailVo();
|
||||||
|
//套餐的ID
|
||||||
|
orderDetailVo.setItemId(tradeVo.getItemId());
|
||||||
|
//套餐的名字
|
||||||
|
orderDetailVo.setItemName("VIP套餐:" + vipServiceConfig.getName());
|
||||||
|
//套餐图片的url
|
||||||
|
orderDetailVo.setItemUrl(vipServiceConfig.getImageUrl());
|
||||||
|
//套餐的折后价
|
||||||
|
orderDetailVo.setItemPrice(orderAmount);
|
||||||
|
orderDetailVoList.add(orderDetailVo);
|
||||||
|
//2.4 如果存在优惠,封装优惠列表
|
||||||
|
//bigdecimal是不能用equals判断的 因为1.10和1.1不相等
|
||||||
|
//compareTo(orderAmount)==0是相等
|
||||||
|
//compareTo(orderAmount)==-1小于
|
||||||
|
//compareTo(orderAmount)==1是大于 如果大于的话说明有了折扣
|
||||||
|
if (originalAmount.compareTo(orderAmount) == 1) {
|
||||||
|
derateAmount = originalAmount.subtract(orderAmount);
|
||||||
|
//存在优惠 我们要封装优惠列表
|
||||||
|
OrderDerateVo orderDerateVo = new OrderDerateVo();
|
||||||
|
orderDerateVo.setDerateAmount(derateAmount);
|
||||||
|
orderDerateVo.setDerateType(SystemConstant.ORDER_DERATE_VIP_SERVICE_DISCOUNT);
|
||||||
|
orderDerateVo.setRemarks("VIP套餐限时优惠:" + derateAmount);
|
||||||
|
orderDerateVoList.add(orderDerateVo);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (SystemConstant.ORDER_ITEM_TYPE_ALBUM.equals(itemType)) {
|
||||||
|
//3. 处理购买项目类型:专辑
|
||||||
|
//3.1 远程调用用户服务,判断是否重复购买专辑,如果已购过,则业务终止
|
||||||
|
Long albumId = tradeVo.getItemId();
|
||||||
|
Boolean data = userFeignClient.isPaidAlbum(albumId).getData();
|
||||||
|
//如果data为true的话就走断言
|
||||||
|
Assert.isFalse(data, "用户已购买专辑{}", albumId);
|
||||||
|
//3.2 远程调用专辑服务,获取专辑价格以及折扣(普通用户折扣,VIP会员折扣)
|
||||||
|
AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData();
|
||||||
|
BigDecimal price = albumInfo.getPrice();
|
||||||
|
BigDecimal discount = albumInfo.getDiscount();
|
||||||
|
BigDecimal vipDiscount = albumInfo.getVipDiscount();
|
||||||
|
//判断用户是否为vip 如果是vip就走vip折扣 如果不是就走普通用户的折扣
|
||||||
|
Boolean isVIP = false;
|
||||||
|
UserInfoVo userInfoVo = userFeignClient.getUserInfoVo(userId).getData();
|
||||||
|
Assert.notNull(userInfoVo, "用户{}不存在", userId);
|
||||||
|
//判断一下用户是否为vip以及vip的过期时间是否超过了当时的时间
|
||||||
|
if (userInfoVo.getIsVip().intValue() == 1
|
||||||
|
&& userInfoVo.getVipExpireTime().after(new Date())) {
|
||||||
|
isVIP = true;
|
||||||
|
}
|
||||||
|
//3.4 计算相关价格
|
||||||
|
//3.4.1 暂时将订单价=原价
|
||||||
|
originalAmount = price;
|
||||||
|
orderAmount = originalAmount;
|
||||||
|
//当你是普通客户 但是专辑是有普通用户折扣的时候 计算一下专辑价格
|
||||||
|
//discount.doubleValue()!=-1
|
||||||
|
//BigDecimal.doubleValue() 是将 BigDecimal 转换为 double 类型的操作
|
||||||
|
|
||||||
|
//如果是普通用户且有折扣,则订单价=原价 100 *折扣 6
|
||||||
|
//..divide(new BigDecimal("10"), 2, RoundingMode.HALF_UP)是保留两位小数的标准写法
|
||||||
|
if (!isVIP && discount.doubleValue() != -1) {
|
||||||
|
orderAmount =originalAmount.multiply(discount)
|
||||||
|
.divide(new BigDecimal("10"), 2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
//如果是会员,则按照会员折扣走
|
||||||
|
if (isVIP && vipDiscount.doubleValue() != -1) {
|
||||||
|
orderAmount = originalAmount.multiply(vipDiscount)
|
||||||
|
.divide(new BigDecimal("10"), 2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
//3.5 封装商品信息列表
|
||||||
|
OrderDetailVo orderDetailVo = new OrderDetailVo();
|
||||||
|
orderDetailVo.setItemId(albumId);
|
||||||
|
orderDetailVo.setItemName("专辑:"+albumInfo.getAlbumTitle());
|
||||||
|
orderDetailVo.setItemUrl(albumInfo.getCoverUrl());
|
||||||
|
orderDetailVo.setItemPrice(originalAmount);
|
||||||
|
orderDetailVoList.add(orderDetailVo);
|
||||||
|
|
||||||
|
//2.4 如果存在优惠,封装优惠列表
|
||||||
|
if(originalAmount.compareTo(orderAmount)==1){
|
||||||
|
OrderDerateVo orderDerateVo = new OrderDerateVo();
|
||||||
|
orderDerateVo.setRemarks(SystemConstant.ORDER_DERATE_VIP_SERVICE_DISCOUNT);
|
||||||
|
orderDerateVo.setDerateType("VIP套餐限时优惠:" + derateAmount);
|
||||||
|
orderDerateVo.setDerateAmount(originalAmount.subtract(orderAmount));
|
||||||
|
orderDerateVoList.add(orderDerateVo);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (SystemConstant.ORDER_ITEM_TYPE_TRACK.equals(itemType)) {
|
||||||
|
//4.处理购买项目类型:声音
|
||||||
|
//4.1 远程调用专辑服务,获取未购买声音列表
|
||||||
|
Long trackId = tradeVo.getItemId();
|
||||||
|
List<TrackInfo> trackInfoList =
|
||||||
|
albumFeignClient.findWaitBuyTrackList(trackId, tradeVo.getTrackCount()).getData();
|
||||||
|
Assert.notNull(trackInfoList, "不存在待结算声音",trackId);
|
||||||
|
//4.2 远程调用专辑服务获取,专辑价格(声音单价)声音不支持折扣
|
||||||
|
AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(trackInfoList.get(0).getAlbumId()).getData();
|
||||||
|
Assert.notNull(albumInfo, "专辑{}不存在", albumInfo.getId());
|
||||||
|
BigDecimal price = albumInfo.getPrice();
|
||||||
|
//4.3 计算相关价格
|
||||||
|
originalAmount = price.multiply(new BigDecimal(trackInfoList.size()));
|
||||||
|
orderAmount = originalAmount;
|
||||||
|
//4.4 封装订单明细列表
|
||||||
|
orderDetailVoList = trackInfoList.stream().map(
|
||||||
|
trackInfo -> {
|
||||||
|
OrderDetailVo orderDetailVo = new OrderDetailVo();
|
||||||
|
orderDetailVo.setItemId(trackInfo.getId());
|
||||||
|
orderDetailVo.setItemName("声音:" + trackInfo.getTrackTitle());
|
||||||
|
orderDetailVo.setItemUrl(trackInfo.getCoverUrl());
|
||||||
|
orderDetailVo.setItemPrice(price);
|
||||||
|
return orderDetailVo;
|
||||||
|
}
|
||||||
|
).collect(Collectors.toList());
|
||||||
|
|
||||||
|
}
|
||||||
|
//5.封装订单VO对象
|
||||||
|
//5.1 封装相关价格信息
|
||||||
|
orderInfoVo.setOriginalAmount(originalAmount);
|
||||||
|
orderInfoVo.setOrderAmount(orderAmount);
|
||||||
|
orderInfoVo.setDerateAmount(derateAmount);
|
||||||
|
//5.2 封装商品相关集合
|
||||||
|
orderInfoVo.setOrderDetailVoList(orderDetailVoList);
|
||||||
|
orderInfoVo.setOrderDerateVoList(orderDerateVoList);
|
||||||
|
//5.3 TODO 封装其他信息:流水号、时间戳及签名、项目类型等
|
||||||
|
|
||||||
|
//6.响应订单VO对象
|
||||||
|
return orderInfoVo;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
|
@ -28,14 +28,6 @@
|
||||||
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
|
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.kafka</groupId>
|
|
||||||
<artifactId>kafka-clients</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.kafka</groupId>
|
|
||||||
<artifactId>kafka-streams</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.github.biezhi</groupId>
|
<groupId>io.github.biezhi</groupId>
|
||||||
<artifactId>TinyPinyin</artifactId>
|
<artifactId>TinyPinyin</artifactId>
|
||||||
|
|
|
@ -71,37 +71,45 @@ public class SearchReciever {
|
||||||
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
|
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * 监听更新声音/搜索统计信息
|
* 监听更新声音/搜索统计信息
|
||||||
// *
|
*
|
||||||
// * @param mqVo
|
* @param mqVo
|
||||||
// * @param message
|
* @param message
|
||||||
// * @param channel
|
* @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);
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
@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.USER_TRACK_REPEAT_STAT_PREFIX + "es:" + mqVo.getBusinessNo();
|
||||||
|
//1.2 尝试采用set nx写入Redis
|
||||||
|
Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.MINUTES);
|
||||||
|
|
||||||
|
//1.3 写入成功消息才进行业务处理,写入失败(消息重复被MQ服务器投递)忽略消息
|
||||||
|
if (flag) {
|
||||||
|
try {
|
||||||
|
//2.业务处理
|
||||||
|
searchService.updateAlbumStat(mqVo);
|
||||||
|
} catch (Exception e) {
|
||||||
|
redisTemplate.delete(key);
|
||||||
|
//如果异常将无法处理消息发送到死信(异常)交换机->死信队列->消费者处理进入存入消费者异常消息表->人工处理
|
||||||
|
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import co.elastic.clients.elasticsearch.core.SearchResponse;
|
||||||
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
|
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
|
||||||
import com.atguigu.tingshu.model.search.SuggestIndex;
|
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.album.TrackStatMqVo;
|
||||||
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
|
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
|
||||||
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
|
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
|
||||||
|
|
||||||
|
@ -37,4 +38,6 @@ public interface SearchService {
|
||||||
void updateLatelyAlbumRanking(Integer top);
|
void updateLatelyAlbumRanking(Integer top);
|
||||||
|
|
||||||
List<AlbumInfoIndexVo> getRankingList(Long category1Id, String dimension);
|
List<AlbumInfoIndexVo> getRankingList(Long category1Id, String dimension);
|
||||||
|
|
||||||
|
void updateAlbumStat(TrackStatMqVo mqVo);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.atguigu.tingshu.user.client.UserFeignClient;
|
||||||
import com.atguigu.tingshu.vo.album.AlbumStatVo;
|
import com.atguigu.tingshu.vo.album.AlbumStatVo;
|
||||||
import com.atguigu.tingshu.vo.user.UserInfoVo;
|
import com.atguigu.tingshu.vo.user.UserInfoVo;
|
||||||
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.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@ -28,6 +29,8 @@ public class ItemServiceImpl implements ItemService {
|
||||||
private AlbumFeignClient albumFeignClient;
|
private AlbumFeignClient albumFeignClient;
|
||||||
@Autowired
|
@Autowired
|
||||||
private Executor threadPoolTaskExecutor;
|
private Executor threadPoolTaskExecutor;
|
||||||
|
@Autowired
|
||||||
|
private RedissonClient redissonClient;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -17,10 +17,12 @@ import co.elastic.clients.elasticsearch.core.SearchResponse;
|
||||||
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
|
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 co.elastic.clients.elasticsearch.core.search.Suggestion;
|
||||||
|
import co.elastic.clients.json.JsonData;
|
||||||
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.constant.RedisConstant;
|
import com.atguigu.tingshu.common.constant.RedisConstant;
|
||||||
|
|
||||||
|
import com.atguigu.tingshu.common.constant.SystemConstant;
|
||||||
import com.atguigu.tingshu.model.album.*;
|
import com.atguigu.tingshu.model.album.*;
|
||||||
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;
|
||||||
|
@ -30,12 +32,15 @@ import com.atguigu.tingshu.search.repository.AlbumInfoIndexRepository;
|
||||||
import com.atguigu.tingshu.search.repository.SuggestIndexRepository;
|
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.album.TrackStatMqVo;
|
||||||
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
|
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
|
||||||
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
|
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
|
||||||
import com.atguigu.tingshu.vo.user.UserInfoVo;
|
import com.atguigu.tingshu.vo.user.UserInfoVo;
|
||||||
import jakarta.annotation.Resource;
|
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.redisson.api.RBloomFilter;
|
||||||
|
import org.redisson.api.RedissonClient;
|
||||||
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.elasticsearch.core.suggest.Completion;
|
||||||
|
|
||||||
|
@ -68,17 +73,21 @@ 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
|
@Autowired
|
||||||
private SuggestIndexRepository suggestIndexRepository;
|
private SuggestIndexRepository suggestIndexRepository;
|
||||||
@Autowired
|
@Autowired
|
||||||
private RedisTemplate redisTemplate;
|
private RedisTemplate redisTemplate;
|
||||||
|
@Autowired
|
||||||
|
private RedissonClient redissonClient;
|
||||||
|
|
||||||
|
private static final String SUGGEST_INDEX = "suggestinfo";
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void upperAlbum(Long albumId) {
|
public void upperAlbum(Long albumId) {
|
||||||
//1.创建专辑索引对象
|
//1.创建专辑索引对象
|
||||||
AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
|
AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
|
||||||
|
|
||||||
//2.远程调用专辑服务查询专辑信息,封装专辑以及专辑标签列表
|
//2.远程调用专辑服务查询专辑信息,封装专辑以及专辑标签列表
|
||||||
CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
|
CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
|
||||||
AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData();
|
AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData();
|
||||||
|
@ -137,14 +146,18 @@ public class SearchServiceImpl implements SearchService {
|
||||||
BaseCategoryViewCompletableFuture,
|
BaseCategoryViewCompletableFuture,
|
||||||
userInfoCompletableFuture,
|
userInfoCompletableFuture,
|
||||||
statCompletableFuture
|
statCompletableFuture
|
||||||
).orTimeout(5, TimeUnit.SECONDS)
|
).orTimeout(1, TimeUnit.SECONDS)
|
||||||
.join();
|
.join();
|
||||||
|
|
||||||
//7.保存专辑到索引库
|
//7.保存专辑到索引库
|
||||||
albumInfoIndexRepository.save(albumInfoIndex);
|
albumInfoIndexRepository.save(albumInfoIndex);
|
||||||
|
|
||||||
//8.将专辑标题作为提示词文档存入提示词索引库
|
//8.将专辑标题作为提示词文档存入提示词索引库
|
||||||
this.saveSuggestIndex(albumInfoIndex);
|
this.saveSuggestIndex(albumInfoIndex);
|
||||||
|
|
||||||
|
//9.将专辑ID存入布隆过滤器,用于解决缓存穿透问题
|
||||||
|
RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER);
|
||||||
|
bloomFilter.add(albumId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -483,16 +496,18 @@ public class SearchServiceImpl implements SearchService {
|
||||||
@Override
|
@Override
|
||||||
public void updateLatelyAlbumRanking(Integer top) {
|
public void updateLatelyAlbumRanking(Integer top) {
|
||||||
try {
|
try {
|
||||||
//1.从Redis检索获取分类下五种排序维度专辑TOPN(n=top)记录
|
//1.从Redis检索获取分类下五种排序维度专辑TOPN记录
|
||||||
//1.1 远程调用专辑服务获取所有1级分类列表
|
//1.1 远程调用专辑服务获取所有1级分类列表
|
||||||
List<BaseCategory1> baseCategory1List = albumFeignClient.findAllCategory1().getData();
|
List<BaseCategory1> baseCategory1List = albumFeignClient.findAllCategory1().getData();
|
||||||
Assert.notNull(baseCategory1List, "1级分类列表为空");
|
Assert.notNull(baseCategory1List, "1级分类列表为空");
|
||||||
|
|
||||||
|
|
||||||
//1.2 遍历1级分类
|
//1.2 遍历1级分类
|
||||||
for (BaseCategory1 baseCategory1 : baseCategory1List) {
|
for (BaseCategory1 baseCategory1 : baseCategory1List) {
|
||||||
//从内容中找出id 设定一个key
|
|
||||||
Long category1Id = baseCategory1.getId();
|
Long category1Id = baseCategory1.getId();
|
||||||
|
//基于基于1级分类ID构建Redis中hash结构的Key
|
||||||
String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
|
String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
|
||||||
//绑定大key
|
//基于Key创建绑定hash操作对象
|
||||||
BoundHashOperations<String, String, List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(key);
|
BoundHashOperations<String, String, List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(key);
|
||||||
//1.3 遍历5种排序维度
|
//1.3 遍历5种排序维度
|
||||||
String[] rankingDimensionArray
|
String[] rankingDimensionArray
|
||||||
|
@ -525,31 +540,67 @@ public class SearchServiceImpl implements SearchService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("更新Redis小时排行数据异常", e);
|
log.error("更新Redis小时榜数据异常", e);
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<AlbumInfoIndexVo> getRankingList(Long category1Id, String dimension) {
|
public List<AlbumInfoIndexVo> getRankingList(Long category1Id, String dimension) {
|
||||||
//基于1级分类ID构建Redis中hash结构的Key
|
//基于基于1级分类ID构建Redis中hash结构的Key
|
||||||
String key =RedisConstant.RANKING_KEY_PREFIX+category1Id;
|
String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
|
||||||
BoundHashOperations<String,String,List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(key);
|
BoundHashOperations<String, String, List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(key);
|
||||||
//构建Hash排行榜中field 当前排序方式
|
//构建Hash排行榜中field 当前排序方式
|
||||||
String field = dimension;
|
String field = dimension;
|
||||||
Boolean flag = hashOps.hasKey(field);
|
Boolean flag = hashOps.hasKey(field);
|
||||||
if(flag){
|
if (flag) {
|
||||||
//List<AlbumInfoIndex> list = (List<AlbumInfoIndex>) redisTemplate.opsForHash().get(key, field);
|
//List<AlbumInfoIndex> list = (List<AlbumInfoIndex>) redisTemplate.opsForHash().get(key, field);
|
||||||
List<AlbumInfoIndex> list = hashOps.get(field);
|
List<AlbumInfoIndex> list = hashOps.get(field);
|
||||||
if (CollUtil.isNotEmpty(list)) {
|
if (CollUtil.isNotEmpty(list)) {
|
||||||
List<AlbumInfoIndexVo> albumInfoIndexVoList = list.stream()
|
List<AlbumInfoIndexVo> albumInfoIndexVoList = list.stream()
|
||||||
.map(albumInfoIndex -> BeanUtil.copyProperties(albumInfoIndex, AlbumInfoIndexVo.class))
|
.map(albumInfoIndex -> BeanUtil.copyProperties(albumInfoIndex,AlbumInfoIndexVo.class))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return albumInfoIndexVoList;
|
return albumInfoIndexVoList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* #增量更新
|
||||||
|
* POST /albuminfo/_update/1
|
||||||
|
* {
|
||||||
|
* "script": {
|
||||||
|
* "source": "ctx._source.playStatNum += params.increment",
|
||||||
|
* "lang": "painless",
|
||||||
|
* "params": {
|
||||||
|
* "increment": 1
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateAlbumStat(TrackStatMqVo mqVo) {
|
||||||
|
try {
|
||||||
|
Long albumId = mqVo.getAlbumId();
|
||||||
|
String incrementField = "";
|
||||||
|
if (SystemConstant.TRACK_STAT_PLAY.equals(mqVo.getStatType())) {
|
||||||
|
incrementField = "playStatNum";
|
||||||
|
|
||||||
|
} else if (SystemConstant.TRACK_STAT_COMMENT.equals(mqVo.getStatType())) {
|
||||||
|
incrementField = "commentStatNum";
|
||||||
|
}
|
||||||
|
String finalIncrementField = incrementField;
|
||||||
|
elasticsearchClient.update(
|
||||||
|
u -> u.index(INDEX_NAME).id(albumId.toString())
|
||||||
|
.script(s -> s.inline(i -> i.source("ctx._source." + finalIncrementField + " += params.increment").lang("painless").params(Map.of("increment", JsonData.of(mqVo.getCount())))))
|
||||||
|
, AlbumInfoIndex.class
|
||||||
|
);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package com.atguigu.tingshu.search;
|
package com.atguigu.tingshu.search;
|
||||||
|
|
||||||
|
import com.atguigu.tingshu.common.constant.RedisConstant;
|
||||||
import com.atguigu.tingshu.search.service.SearchService;
|
import com.atguigu.tingshu.search.service.SearchService;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.redisson.api.RBloomFilter;
|
||||||
|
import org.redisson.api.RedissonClient;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
@ -10,6 +13,8 @@ import org.springframework.boot.test.context.SpringBootTest;
|
||||||
public class test01 {
|
public class test01 {
|
||||||
@Autowired
|
@Autowired
|
||||||
private SearchService searchService;
|
private SearchService searchService;
|
||||||
|
@Autowired
|
||||||
|
private RedissonClient redissonClient;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test01() {
|
public void test01() {
|
||||||
|
@ -19,8 +24,17 @@ public class test01 {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void test02() {
|
||||||
|
RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER);
|
||||||
|
for (long i = 1; i <= 1629; i++) {
|
||||||
|
try {
|
||||||
|
bloomFilter.add(i);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -19,38 +19,72 @@ import java.util.Map;
|
||||||
@SuppressWarnings({"all"})
|
@SuppressWarnings({"all"})
|
||||||
public class UserInfoApiController {
|
public class UserInfoApiController {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserInfoService userInfoService;
|
private UserInfoService userInfoService;
|
||||||
/**
|
|
||||||
* 根据用户ID查询用户信息
|
/**
|
||||||
* @param userId
|
* 根据用户ID查询用户信息
|
||||||
* @return
|
*
|
||||||
*/
|
* @param userId
|
||||||
@Operation(summary = "根据用户ID查询用户信息")
|
* @return
|
||||||
@GetMapping("/userInfo/getUserInfoVo/{userId}")
|
*/
|
||||||
public Result<UserInfoVo> getUserInfoVo(@PathVariable Long userId){
|
@Operation(summary = "根据用户ID查询用户信息")
|
||||||
UserInfoVo userInfoVo = userInfoService.getUserInfo(userId);
|
@GetMapping("/userInfo/getUserInfoVo/{userId}")
|
||||||
return Result.ok(userInfoVo);
|
public Result<UserInfoVo> getUserInfoVo(@PathVariable Long userId) {
|
||||||
}
|
UserInfoVo userInfoVo = userInfoService.getUserInfo(userId);
|
||||||
|
return Result.ok(userInfoVo);
|
||||||
/**
|
}
|
||||||
* 获取每一页声音列表(提交声音ID集合)对应购买状态
|
|
||||||
*
|
/**
|
||||||
* @param userId 用户ID
|
* 获取每一页声音列表(提交声音ID集合)对应购买状态
|
||||||
* @param albumId 专辑ID
|
*
|
||||||
* @param needCheckPayStateTrackIdList 需要检查购买状态的声音ID集合
|
* @param userId 用户ID
|
||||||
* @return {声音ID:1(已购),声音ID:0(未购)}
|
* @param albumId 专辑ID
|
||||||
*/
|
* @param needCheckPayStateTrackIdList 需要检查购买状态的声音ID集合
|
||||||
@GuiGuLogin //对内微服务访问接口,上游系统调用是否只要携带token 下游系统就可以获取用户ID
|
* @return {声音ID:1(已购),声音ID:0(未购)}
|
||||||
@Operation(summary = "获取每一页声音列表(提交声音ID集合)对应购买状态")
|
*/
|
||||||
@PostMapping("/userInfo/userIsPaidTrack/{albumId}")
|
@GuiGuLogin //对内微服务访问接口,上游系统调用是否只要携带token 下游系统就可以获取用户ID
|
||||||
public Result<Map<Long, Integer>> userIsPaidTrack(
|
@Operation(summary = "获取每一页声音列表(提交声音ID集合)对应购买状态")
|
||||||
@PathVariable("albumId") Long albumId,
|
@PostMapping("/userInfo/userIsPaidTrack/{albumId}")
|
||||||
@RequestBody List<Long> needCheckPayStateTrackIdList
|
public Result<Map<Long, Integer>> userIsPaidTrack(
|
||||||
) {
|
@PathVariable("albumId") Long albumId,
|
||||||
Long userId = AuthContextHolder.getUserId();
|
@RequestBody List<Long> needCheckPayStateTrackIdList
|
||||||
Map<Long, Integer> map = userInfoService.userIsPaidTrack(userId, albumId, needCheckPayStateTrackIdList);
|
) {
|
||||||
return Result.ok(map);
|
Long userId = AuthContextHolder.getUserId();
|
||||||
}
|
Map<Long, Integer> map = userInfoService.userIsPaidTrack(userId, albumId, needCheckPayStateTrackIdList);
|
||||||
|
return Result.ok(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断当前用户是否已购买指定专辑
|
||||||
|
*
|
||||||
|
* @param albumId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GuiGuLogin
|
||||||
|
@Operation(summary = "判断当前用户是否已购买指定专辑")
|
||||||
|
@GetMapping("/userInfo/isPaidAlbum/{albumId}")
|
||||||
|
public Result<Boolean> isPaidAlbum(@PathVariable Long albumId) {
|
||||||
|
Long userId = AuthContextHolder.getUserId();
|
||||||
|
Boolean flag = userInfoService.isPaidAlbum(userId, albumId);
|
||||||
|
return Result.ok(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询当前用户指定专辑下已购声音ID列表
|
||||||
|
*
|
||||||
|
* @param albumId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GuiGuLogin
|
||||||
|
@Operation(summary = "查询当前用户指定专辑下已购声音ID列表")
|
||||||
|
@GetMapping("/userInfo/findUserPaidTrackList/{albumId}")
|
||||||
|
public Result<List<Long>> findUserPaidTrackList(@PathVariable Long albumId) {
|
||||||
|
Long userId = AuthContextHolder.getUserId();
|
||||||
|
List<Long> list = userInfoService.findUserPaidTrackList(userId, albumId);
|
||||||
|
return Result.ok(list);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,16 @@ public class UserListenProcessApiController {
|
||||||
@Operation(summary = "获取声音播放进度")
|
@Operation(summary = "获取声音播放进度")
|
||||||
@GetMapping("/userListenProcess/getTrackBreakSecond/{trackId}")
|
@GetMapping("/userListenProcess/getTrackBreakSecond/{trackId}")
|
||||||
public Result<BigDecimal> getTrackBreakSecond (Long trackId){
|
public Result<BigDecimal> getTrackBreakSecond (Long trackId){
|
||||||
|
//1.尝试获取用户ID
|
||||||
Long userId = AuthContextHolder.getUserId();
|
Long userId = AuthContextHolder.getUserId();
|
||||||
BigDecimal second =userListenProcessService.getTrackBreakSecond(userId, trackId);
|
//2.查询用户对于指定声音播放进度
|
||||||
return Result.ok(second);
|
if (userId != null) {
|
||||||
|
//3.返回
|
||||||
|
BigDecimal breakSecond = userListenProcessService.getTrackBreakSecond(userId, trackId);
|
||||||
|
return Result.ok(breakSecond);
|
||||||
|
}
|
||||||
|
//3.返回
|
||||||
|
return Result.ok(BigDecimal.valueOf(0));
|
||||||
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,11 +1,19 @@
|
||||||
package com.atguigu.tingshu.user.api;
|
package com.atguigu.tingshu.user.api;
|
||||||
|
|
||||||
|
import com.atguigu.tingshu.common.cache.GuiGuCache;
|
||||||
|
import com.atguigu.tingshu.common.result.Result;
|
||||||
|
import com.atguigu.tingshu.model.user.VipServiceConfig;
|
||||||
import com.atguigu.tingshu.user.service.VipServiceConfigService;
|
import com.atguigu.tingshu.user.service.VipServiceConfigService;
|
||||||
|
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.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Tag(name = "VIP服务配置管理接口")
|
@Tag(name = "VIP服务配置管理接口")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("api/user")
|
@RequestMapping("api/user")
|
||||||
|
@ -15,5 +23,23 @@ public class VipServiceConfigApiController {
|
||||||
@Autowired
|
@Autowired
|
||||||
private VipServiceConfigService vipServiceConfigService;
|
private VipServiceConfigService vipServiceConfigService;
|
||||||
|
|
||||||
|
@Operation(summary = "获取平台套餐列表")
|
||||||
|
@GetMapping("/vipServiceConfig/findAll")
|
||||||
|
@GuiGuCache(prefix = "user:vipconfig:list")
|
||||||
|
public Result<List<VipServiceConfig>> findAll(){
|
||||||
|
List<VipServiceConfig> list = vipServiceConfigService.list();
|
||||||
|
return Result.ok(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "根据id获取套餐内容")
|
||||||
|
@GuiGuCache(prefix = "user:vipconfig:")
|
||||||
|
@GetMapping("/vipServiceConfig/getVipServiceConfig/{id}")
|
||||||
|
|
||||||
|
public Result<VipServiceConfig> getVipServiceConfig(@PathVariable Long id){
|
||||||
|
VipServiceConfig content = vipServiceConfigService.getById(id);
|
||||||
|
return Result.ok(content);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,4 +17,7 @@ public interface UserInfoService extends IService<UserInfo> {
|
||||||
|
|
||||||
Map<Long, Integer> userIsPaidTrack(Long userId, Long albumId, List<Long> needCheckPayStateTrackIdList);
|
Map<Long, Integer> userIsPaidTrack(Long userId, Long albumId, List<Long> needCheckPayStateTrackIdList);
|
||||||
|
|
||||||
|
Boolean isPaidAlbum(Long userId, Long albumId);
|
||||||
|
|
||||||
|
List<Long> findUserPaidTrackList(Long userId, Long albumId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,124 +46,161 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
|
||||||
private RedisTemplate redisTemplate;
|
private RedisTemplate redisTemplate;
|
||||||
@Autowired
|
@Autowired
|
||||||
private RabbitService rabbitService;
|
private RabbitService rabbitService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserPaidAlbumMapper userPaidAlbumMapper;
|
private UserPaidAlbumMapper userPaidAlbumMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserPaidTrackMapper userPaidTrackMapper;
|
private UserPaidTrackMapper userPaidTrackMapper;
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, String> wxLogin(String code) {
|
|
||||||
try {
|
|
||||||
//1.根据小程序提交的临时票据code调用微信接口服务,获取当前微信用户唯一标识openid
|
|
||||||
WxMaJscode2SessionResult sessionInfo = wxMaService.getUserService().getSessionInfo(code);
|
|
||||||
String wxOpenid = sessionInfo.getOpenid();
|
|
||||||
//2.根据openid查询数据库用户表,判断微信账户是否关联用户
|
|
||||||
UserInfo userInfo = userInfoMapper.selectOne(
|
|
||||||
new LambdaQueryWrapper<UserInfo>()
|
|
||||||
.eq(UserInfo::getWxOpenId, wxOpenid)
|
|
||||||
);//3.如果不存在用户记录,说明该微信是首次登录
|
|
||||||
if (userInfo == null) {
|
|
||||||
//3.1 新增用户记录
|
|
||||||
userInfo = new UserInfo();
|
|
||||||
userInfo.setWxOpenId(wxOpenid);
|
|
||||||
userInfo.setNickname("听友" + IdUtil.nanoId());
|
|
||||||
userInfo.setAvatarUrl("http://192.168.200.6:9000/tingshu/2025-07-21/1ce194e3-4212-4ff8-948b-babd9a7b8a02.jpg");
|
|
||||||
userInfoMapper.insert(userInfo);
|
|
||||||
//3.2 TODO 隐式初始化账户记录,用于后续订单消费,采用MQ
|
|
||||||
//3.2.1 创建初始化账户MQ消息对象 注意:如果使用的是VO对象,该对象必须得实现序列化接口 Serializable
|
|
||||||
Map<String, Object> mqData = new HashMap<>();
|
|
||||||
mqData.put("userId", userInfo.getId());
|
|
||||||
mqData.put("amount", BigDecimal.valueOf(100));
|
|
||||||
mqData.put("orderNo", "ZS"+ DateUtil.today().replaceAll("-", "")+IdUtil.getSnowflakeNextId());
|
|
||||||
mqData.put("title", "首次注册,赠送体验金活动");
|
|
||||||
|
|
||||||
//3.2.2 发送消息到RabbitMQ交换机 todo 使用RabbitService
|
|
||||||
rabbitService.sendMessage(MqConst.EXCHANGE_USER, MqConst.ROUTING_USER_REGISTER, mqData);
|
|
||||||
|
|
||||||
}
|
|
||||||
//4.无论是否首次登录,都需要生成令牌,返回给小程序
|
|
||||||
//4.1 生成随机令牌
|
|
||||||
String token = IdUtil.randomUUID();
|
|
||||||
//4.2 将用户令牌作为Key,用户基本信息UserInfoVo作为Value,保存到Redis中
|
|
||||||
String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token;
|
|
||||||
UserInfoVo userInfoVo = BeanUtil.copyProperties(userInfo, UserInfoVo.class);
|
|
||||||
redisTemplate.opsForValue().set(loginKey, userInfoVo, RedisConstant.USER_LOGIN_KEY_TIMEOUT, TimeUnit.SECONDS);
|
|
||||||
//4.3 返回用户令牌
|
|
||||||
return Map.of("token", token);
|
|
||||||
} catch (WxErrorException e) {
|
|
||||||
log.error("微信登录失败:{}", e.getMessage());
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@GuiGuCache(prefix = "user:info:")
|
public Map<String, String> wxLogin(String code) {
|
||||||
public UserInfoVo getUserInfo(Long userId) {
|
try {
|
||||||
UserInfo userInfo = userInfoMapper.selectById(userId);
|
//1.根据小程序提交的临时票据code调用微信接口服务,获取当前微信用户唯一标识openid
|
||||||
return BeanUtil.copyProperties(userInfo, UserInfoVo.class);
|
WxMaJscode2SessionResult sessionInfo = wxMaService.getUserService().getSessionInfo(code);
|
||||||
|
String wxOpenid = sessionInfo.getOpenid();
|
||||||
|
//2.根据openid查询数据库用户表,判断微信账户是否关联用户
|
||||||
|
UserInfo userInfo = userInfoMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<UserInfo>()
|
||||||
|
.eq(UserInfo::getWxOpenId, wxOpenid)
|
||||||
|
);//3.如果不存在用户记录,说明该微信是首次登录
|
||||||
|
if (userInfo == null) {
|
||||||
|
//3.1 新增用户记录
|
||||||
|
userInfo = new UserInfo();
|
||||||
|
userInfo.setWxOpenId(wxOpenid);
|
||||||
|
userInfo.setNickname("听友" + IdUtil.nanoId());
|
||||||
|
userInfo.setAvatarUrl("http://192.168.200.6:9000/tingshu/2025-07-21/1ce194e3-4212-4ff8-948b-babd9a7b8a02.jpg");
|
||||||
|
userInfoMapper.insert(userInfo);
|
||||||
|
//3.2 TODO 隐式初始化账户记录,用于后续订单消费,采用MQ
|
||||||
|
//3.2.1 创建初始化账户MQ消息对象 注意:如果使用的是VO对象,该对象必须得实现序列化接口 Serializable
|
||||||
|
Map<String, Object> mqData = new HashMap<>();
|
||||||
|
mqData.put("userId", userInfo.getId());
|
||||||
|
mqData.put("amount", BigDecimal.valueOf(100));
|
||||||
|
mqData.put("orderNo", "ZS" + DateUtil.today().replaceAll("-", "") + IdUtil.getSnowflakeNextId());
|
||||||
|
mqData.put("title", "首次注册,赠送体验金活动");
|
||||||
|
|
||||||
|
//3.2.2 发送消息到RabbitMQ交换机 todo 使用RabbitService
|
||||||
|
rabbitService.sendMessage(MqConst.EXCHANGE_USER, MqConst.ROUTING_USER_REGISTER, mqData);
|
||||||
|
|
||||||
|
}
|
||||||
|
//4.无论是否首次登录,都需要生成令牌,返回给小程序
|
||||||
|
//4.1 生成随机令牌
|
||||||
|
String token = IdUtil.randomUUID();
|
||||||
|
//4.2 将用户令牌作为Key,用户基本信息UserInfoVo作为Value,保存到Redis中
|
||||||
|
String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token;
|
||||||
|
UserInfoVo userInfoVo = BeanUtil.copyProperties(userInfo, UserInfoVo.class);
|
||||||
|
redisTemplate.opsForValue().set(loginKey, userInfoVo, RedisConstant.USER_LOGIN_KEY_TIMEOUT, TimeUnit.SECONDS);
|
||||||
|
//4.3 返回用户令牌
|
||||||
|
return Map.of("token", token);
|
||||||
|
} catch (WxErrorException e) {
|
||||||
|
log.error("微信登录失败:{}", e.getMessage());
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@GuiGuCache(prefix = "user:info:")
|
||||||
|
public UserInfoVo getUserInfo(Long userId) {
|
||||||
|
UserInfo userInfo = userInfoMapper.selectById(userId);
|
||||||
|
return BeanUtil.copyProperties(userInfo, UserInfoVo.class);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateUser(UserInfoVo userInfoVo, Long userId) {
|
public void updateUser(UserInfoVo userInfoVo, Long userId) {
|
||||||
UserInfo userInfo = new UserInfo();
|
UserInfo userInfo = new UserInfo();
|
||||||
userInfo.setId(userId);
|
userInfo.setId(userId);
|
||||||
userInfo.setAvatarUrl(userInfoVo.getAvatarUrl());
|
//更换头像
|
||||||
userInfo.setNickname(userInfoVo.getNickname());
|
userInfo.setAvatarUrl(userInfoVo.getAvatarUrl());
|
||||||
userInfoMapper.updateById(userInfo);
|
//更换昵称
|
||||||
|
userInfo.setNickname(userInfoVo.getNickname());
|
||||||
|
userInfoMapper.updateById(userInfo);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<Long, Integer> userIsPaidTrack(Long userId, Long albumId, List<Long> needCheckPayStateTrackIdList) {
|
public Map<Long, Integer> userIsPaidTrack(Long userId, Long albumId, List<Long> needCheckPayStateTrackIdList) {
|
||||||
Map<Long, Integer> payStatusMap = new HashMap<>();
|
Map<Long, Integer> payStatusMap = new HashMap<>();
|
||||||
//1.根据用户ID+专辑ID查询已购专辑记录,如果存在专辑购买记录,将提交声音ID购买状态全部设置为1,返回
|
//1.根据用户ID+专辑ID查询已购专辑记录,如果存在专辑购买记录,将提交声音ID购买状态全部设置为1,返回
|
||||||
Long count = userPaidAlbumMapper.selectCount(
|
Long count = userPaidAlbumMapper.selectCount(
|
||||||
new LambdaQueryWrapper<UserPaidAlbum>()
|
new LambdaQueryWrapper<UserPaidAlbum>()
|
||||||
.eq(UserPaidAlbum::getUserId, userId)
|
.eq(UserPaidAlbum::getUserId, userId)
|
||||||
.eq(UserPaidAlbum::getAlbumId, albumId)
|
.eq(UserPaidAlbum::getAlbumId, albumId)
|
||||||
);
|
);
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
for (Long trackId : needCheckPayStateTrackIdList) {
|
for (Long trackId : needCheckPayStateTrackIdList) {
|
||||||
payStatusMap.put(trackId, 1);
|
payStatusMap.put(trackId, 1);
|
||||||
}
|
}
|
||||||
return payStatusMap;
|
return payStatusMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
//2.如果未购买专辑,根据用户ID+专辑ID查询已购声音记录
|
//2.如果未购买专辑,根据用户ID+专辑ID查询已购声音记录
|
||||||
List<UserPaidTrack> userPaidTrackList = userPaidTrackMapper.selectList(
|
//查询出来的是购买的声音的id
|
||||||
new LambdaQueryWrapper<UserPaidTrack>()
|
List<UserPaidTrack> userPaidTrackList = userPaidTrackMapper.selectList(
|
||||||
.eq(UserPaidTrack::getUserId, userId)
|
new LambdaQueryWrapper<UserPaidTrack>()
|
||||||
.eq(UserPaidTrack::getAlbumId, albumId)
|
.eq(UserPaidTrack::getUserId, userId)
|
||||||
.select(UserPaidTrack::getTrackId)
|
.eq(UserPaidTrack::getAlbumId, albumId)
|
||||||
);
|
.select(UserPaidTrack::getTrackId)
|
||||||
|
);
|
||||||
|
|
||||||
//2.1 如果不存在购买声音记录,将提交声音ID购买状态全部设置为0,返回
|
//2.1 如果不存在购买声音记录,将提交声音ID购买状态全部设置为0,返回
|
||||||
if (CollUtil.isEmpty(userPaidTrackList)) {
|
if (CollUtil.isEmpty(userPaidTrackList)) {
|
||||||
for (Long trackID : needCheckPayStateTrackIdList) {
|
for (Long trackID : needCheckPayStateTrackIdList) {
|
||||||
payStatusMap.put(trackID, 0);
|
payStatusMap.put(trackID, 0);
|
||||||
}
|
}
|
||||||
return payStatusMap;
|
return payStatusMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
//2.2 如果存在购买声音记录,将提交声音ID已购设置1,未购买设置为0返回
|
//2.2 如果存在购买声音记录,将提交声音ID已购设置1,未购买设置为0返回
|
||||||
//2.2.1 获取已购声音ID集合
|
//2.2.1 获取已购声音ID集合
|
||||||
List<Long> userPaidTrackIdList =
|
//这个stream流就是将userPaidTrackList这个中的id单独取出来然后做成一个List
|
||||||
userPaidTrackList.stream().map(UserPaidTrack::getTrackId).collect(Collectors.toList());
|
List<Long> userPaidTrackIdList =
|
||||||
//2.2.2 判断哪些是已购设置为1,哪些是未购买设置为0
|
userPaidTrackList.stream().map(UserPaidTrack::getTrackId).collect(Collectors.toList());
|
||||||
for (Long trackId : needCheckPayStateTrackIdList) {
|
//2.2.2 判断哪些是已购设置为1,哪些是未购买设置为0
|
||||||
if(userPaidTrackIdList.contains(trackId)){
|
for (Long trackId : needCheckPayStateTrackIdList) {
|
||||||
//已购买
|
if (userPaidTrackIdList.contains(trackId)) {
|
||||||
payStatusMap.put(trackId, 1);
|
//已购买
|
||||||
}else{
|
payStatusMap.put(trackId, 1);
|
||||||
//未购买
|
} else {
|
||||||
payStatusMap.put(trackId, 0);
|
//未购买
|
||||||
}
|
payStatusMap.put(trackId, 0);
|
||||||
}
|
}
|
||||||
return payStatusMap;
|
}
|
||||||
}
|
return payStatusMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean isPaidAlbum(Long userId, Long albumId) {
|
||||||
|
//根据用户的id去查询购买的专辑 返回一个数值,如果有的话说明买过了
|
||||||
|
Long count = userPaidAlbumMapper.selectCount(
|
||||||
|
new LambdaQueryWrapper<UserPaidAlbum>()
|
||||||
|
.eq(UserPaidAlbum::getUserId, userId)
|
||||||
|
.eq(UserPaidAlbum::getAlbumId, albumId)
|
||||||
|
);
|
||||||
|
return count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
/**
|
||||||
|
* 查询指定用户指定专辑下已购声音ID列表
|
||||||
|
*
|
||||||
|
* @param userId
|
||||||
|
* @param albumId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<Long> findUserPaidTrackList(Long userId, Long albumId) {
|
||||||
|
List<UserPaidTrack> userPaidTracks = userPaidTrackMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<UserPaidTrack>()
|
||||||
|
.eq(UserPaidTrack::getUserId, userId)
|
||||||
|
.eq(UserPaidTrack::getAlbumId, albumId)
|
||||||
|
.select(UserPaidTrack::getTrackId)
|
||||||
|
);
|
||||||
|
if (CollUtil.isNotEmpty(userPaidTracks)) {
|
||||||
|
List<Long> userPaidTrackIdList = userPaidTracks.stream()
|
||||||
|
.map(u -> u.getTrackId())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return userPaidTrackIdList;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue