第12天 下订单
This commit is contained in:
parent
34cb7047bb
commit
e287bcea12
|
@ -38,41 +38,47 @@ public class GuiGuCacheAspect {
|
|||
List<Object> argsList = Arrays.asList(pjp.getArgs());
|
||||
String args = "none";
|
||||
if (CollUtil.isNotEmpty(argsList)) {
|
||||
//将argsList转换成字符串,用_拼接
|
||||
args = argsList.stream()
|
||||
.map(Object::toString)
|
||||
.collect(Collectors.joining("_"));
|
||||
}
|
||||
//用写这个注解的prefix属性拼接上参数args,得到缓存数据Key
|
||||
String dataKey = guiGuCache.prefix() + args;
|
||||
//1.2 从Redis中获取缓存数据
|
||||
Object result = redisTemplate.opsForValue().get(dataKey);
|
||||
//1.3 命中缓存直接返回即可
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
//2.尝试获取分布式锁
|
||||
//2.1 构建锁Key
|
||||
String lockKey = dataKey + RedisConstant.CACHE_LOCK_SUFFIX;
|
||||
//2.2 基于RedissonClient创建锁对象
|
||||
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中,业务执行结束,释放锁
|
||||
if(flag){
|
||||
if (flag) {
|
||||
//3.1 执行查询数据库方法
|
||||
try {
|
||||
//3.1 执行查询数据库方法
|
||||
result = pjp.proceed();
|
||||
//设置超时时间,防止缓存雪崩
|
||||
//3.2 将查询结果放入缓存中,将结果返回
|
||||
long ttl = RedisConstant.ALBUM_TIMEOUT + RandomUtil.randomInt(600);
|
||||
redisTemplate.opsForValue().set(dataKey, result, ttl, TimeUnit.SECONDS);
|
||||
return result;
|
||||
} finally {
|
||||
//3.3 释放锁
|
||||
lock.unlock();
|
||||
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
//4.获取锁失败,自旋
|
||||
TimeUnit.MILLISECONDS.sleep(30);
|
||||
return this.doBasicProfiling(pjp, guiGuCache);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
//5.如果Redis服务可不用,兜底处理:直接执行目标方法
|
||||
log.error("Redis服务不可用,请检查Redis服务是否正常!");
|
||||
return pjp.proceed();
|
||||
}
|
||||
|
|
|
@ -20,29 +20,29 @@ public class ThreadPoolConfig {
|
|||
|
||||
@Autowired(required = false)
|
||||
ZipkinHelper zipkinHelper;
|
||||
/**
|
||||
* 基于JDK(JUC)提供线程池Class
|
||||
*/
|
||||
@Bean
|
||||
public Executor threadPoolExecutor() {
|
||||
//1.获取当前服务器核心数确定核心线程数
|
||||
int cpuCoreCount = Runtime.getRuntime().availableProcessors();
|
||||
int threadCount = cpuCoreCount * 2;
|
||||
//2.通过构造方法创建线程池对象
|
||||
ThreadPoolExecutor threadPoolExecutor =
|
||||
new ThreadPoolExecutor(
|
||||
threadCount,
|
||||
threadCount,
|
||||
0,
|
||||
TimeUnit.SECONDS,
|
||||
new ArrayBlockingQueue<>(200),
|
||||
Executors.defaultThreadFactory(),
|
||||
new ThreadPoolExecutor.CallerRunsPolicy()
|
||||
);
|
||||
//3.可选:提交创建核心线程
|
||||
threadPoolExecutor.prestartCoreThread();
|
||||
return threadPoolExecutor;
|
||||
}
|
||||
// /**
|
||||
// * 基于JDK(JUC)提供线程池Class
|
||||
// */
|
||||
// @Bean
|
||||
// public Executor threadPoolExecutor() {
|
||||
// //1.获取当前服务器核心数确定核心线程数
|
||||
// int cpuCoreCount = Runtime.getRuntime().availableProcessors();
|
||||
// int threadCount = cpuCoreCount * 2;
|
||||
// //2.通过构造方法创建线程池对象
|
||||
// ThreadPoolExecutor threadPoolExecutor =
|
||||
// new ThreadPoolExecutor(
|
||||
// threadCount,
|
||||
// threadCount,
|
||||
// 0,
|
||||
// TimeUnit.SECONDS,
|
||||
// new ArrayBlockingQueue<>(200),
|
||||
// Executors.defaultThreadFactory(),
|
||||
// new ThreadPoolExecutor.CallerRunsPolicy()
|
||||
// );
|
||||
// //3.可选:提交创建核心线程
|
||||
// threadPoolExecutor.prestartCoreThread();
|
||||
// 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.common.result.Result;
|
||||
import com.atguigu.tingshu.model.album.AlbumInfo;
|
||||
import com.atguigu.tingshu.model.album.BaseCategory1;
|
||||
import com.atguigu.tingshu.model.album.BaseCategory3;
|
||||
import com.atguigu.tingshu.model.album.BaseCategoryView;
|
||||
import com.atguigu.tingshu.model.album.*;
|
||||
import com.atguigu.tingshu.vo.album.AlbumStatVo;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
|
@ -30,9 +28,11 @@ public interface AlbumFeignClient {
|
|||
*/
|
||||
@GetMapping("/albumInfo/getAlbumInfo/{id}")
|
||||
public Result<AlbumInfo> getAlbumInfo(@PathVariable Long id);
|
||||
|
||||
/**
|
||||
* 后续改为Redis缓存
|
||||
* 根据三级分类ID查询分类视图
|
||||
*
|
||||
* @param category3Id
|
||||
* @return
|
||||
*/
|
||||
|
@ -44,11 +44,13 @@ public interface AlbumFeignClient {
|
|||
|
||||
|
||||
@GetMapping("/albumInfo/getAlbumStatVo/{albumId}")
|
||||
public Result<AlbumStatVo>getAlbumStatVo(@PathVariable Long albumId);
|
||||
public Result<AlbumStatVo> getAlbumStatVo(@PathVariable Long albumId);
|
||||
|
||||
@GetMapping("/category/findAllCategory1")
|
||||
Result<List<BaseCategory1>> findAllCategory1();
|
||||
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.common.result.Result;
|
||||
import com.atguigu.tingshu.model.album.AlbumInfo;
|
||||
import com.atguigu.tingshu.model.album.BaseCategory1;
|
||||
import com.atguigu.tingshu.model.album.BaseCategory3;
|
||||
import com.atguigu.tingshu.model.album.BaseCategoryView;
|
||||
import com.atguigu.tingshu.model.album.*;
|
||||
import com.atguigu.tingshu.vo.album.AlbumStatVo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
|
@ -47,4 +45,11 @@ public class AlbumDegradeFeignClient implements AlbumFeignClient {
|
|||
log.error("[专辑服务]提供远程调用方法findAllCategory1执行服务降级");
|
||||
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;
|
||||
|
||||
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.vo.user.UserInfoVo;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
|
@ -19,10 +20,11 @@ import java.util.Map;
|
|||
*
|
||||
* @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 {
|
||||
/**
|
||||
* 根据用户ID查询用户信息
|
||||
*
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
|
@ -35,4 +37,14 @@ public interface UserFeignClient {
|
|||
@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.model.user.VipServiceConfig;
|
||||
import com.atguigu.tingshu.user.client.UserFeignClient;
|
||||
import com.atguigu.tingshu.vo.user.UserInfoVo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -25,4 +26,22 @@ public class UserDegradeFeignClient implements UserFeignClient {
|
|||
log.error("[用户服务]提供远程调用方法userIsPaidTrack执行服务降级");
|
||||
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-payment</module>
|
||||
<module>service-user</module>
|
||||
<module>service-cdc</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
package com.atguigu.tingshu.account.api;
|
||||
|
||||
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 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.RestController;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Tag(name = "用户账户管理")
|
||||
@RestController
|
||||
@RequestMapping("api/account")
|
||||
|
@ -14,6 +21,16 @@ public class UserAccountApiController {
|
|||
|
||||
@Autowired
|
||||
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 订单编号
|
||||
*/
|
||||
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.model.account.UserAccount;
|
||||
import com.atguigu.tingshu.model.account.UserAccountDetail;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -58,4 +59,14 @@ public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserA
|
|||
userAccountDetail.setOrderNo(orderNo);
|
||||
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;
|
||||
|
||||
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.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
|
@ -10,10 +16,21 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
|||
@EnableDiscoveryClient
|
||||
@EnableFeignClients
|
||||
@EnableScheduling //开启定时任务功能
|
||||
public class ServiceAlbumApplication {
|
||||
|
||||
@Slf4j
|
||||
public class ServiceAlbumApplication implements CommandLineRunner {
|
||||
@Autowired
|
||||
private RedissonClient redissonClient;
|
||||
public static void main(String[] 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.util.AuthContextHolder;
|
||||
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.vo.album.AlbumTrackListVo;
|
||||
import com.atguigu.tingshu.vo.album.TrackInfoVo;
|
||||
|
@ -162,6 +163,37 @@ public class TrackInfoApiController {
|
|||
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);
|
||||
|
||||
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.service.IService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface TrackInfoService extends IService<TrackInfo> {
|
||||
|
@ -37,4 +38,9 @@ public interface TrackInfoService extends IService<TrackInfo> {
|
|||
TrackStatVo getTrackStatVo(Long trackId);
|
||||
|
||||
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) {
|
||||
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;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import com.atguigu.tingshu.album.mapper.AlbumInfoMapper;
|
||||
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.TrackInfo;
|
||||
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.user.client.UserFeignClient;
|
||||
import com.atguigu.tingshu.vo.album.*;
|
||||
import com.atguigu.tingshu.vo.user.UserInfoVo;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
|
@ -30,9 +33,7 @@ import org.springframework.stereotype.Service;
|
|||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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()
|
||||
.stream()
|
||||
.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;
|
||||
|
@ -299,7 +300,7 @@ public class TrackInfoServiceImpl extends ServiceImpl<TrackInfoMapper, TrackInfo
|
|||
@Override
|
||||
@GuiGuCache(prefix = "album:trackInfo:stat:")
|
||||
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())
|
||||
.setSql("stat_num = stat_num +" + mqVo.getCount())
|
||||
);
|
||||
|
||||
//2.如果是声音播放、评论。还需要更新声音所属专辑统计数值
|
||||
if(SystemConstant.TRACK_STAT_PLAY.equals(mqVo.getStatType())){
|
||||
if (SystemConstant.TRACK_STAT_PLAY.equals(mqVo.getStatType())) {
|
||||
albumStatMapper.update(
|
||||
null,
|
||||
new LambdaUpdateWrapper<AlbumStat>()
|
||||
|
@ -325,8 +325,7 @@ public class TrackInfoServiceImpl extends ServiceImpl<TrackInfoMapper, TrackInfo
|
|||
.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(
|
||||
null,
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
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.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -27,34 +29,41 @@ public class ReviewTask {
|
|||
|
||||
@Autowired
|
||||
private AuditService auditService;
|
||||
@Autowired
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
/**
|
||||
* 每5秒检查一次审核结果
|
||||
*/
|
||||
@Scheduled(cron = "0/5 * * * * ?")
|
||||
public void checkReviewResultTask() {
|
||||
log.info("开始检查审核任务");
|
||||
//1.查询审核状态为"审核中"声音列表
|
||||
List<TrackInfo> trackInfoList = trackInfoMapper.selectList(
|
||||
new LambdaQueryWrapper<TrackInfo>()
|
||||
.eq(TrackInfo::getStatus, SystemConstant.TRACK_STATUS_REVIEWING)
|
||||
.select(TrackInfo::getId, TrackInfo::getReviewTaskId)
|
||||
.last("limit 100")
|
||||
);
|
||||
//2.遍历列表,查询审核结果
|
||||
if (CollUtil.isNotEmpty(trackInfoList)) {
|
||||
for (TrackInfo trackInfo : trackInfoList) {
|
||||
String suggestion = auditService.getReviewResult(trackInfo.getReviewTaskId());
|
||||
if (StringUtils.isNotBlank(suggestion)) {
|
||||
//3.根据审核任务结果更新声音审核状态
|
||||
if ("pass".equals(suggestion)) {
|
||||
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);
|
||||
|
||||
RLock lock = redissonClient.getLock("reviewtask:lock");
|
||||
boolean b = lock.tryLock();
|
||||
if (b) {
|
||||
log.info("开始检查审核任务");
|
||||
//1.查询审核状态为"审核中"声音列表
|
||||
List<TrackInfo> trackInfoList = trackInfoMapper.selectList(
|
||||
new LambdaQueryWrapper<TrackInfo>()
|
||||
.eq(TrackInfo::getStatus, SystemConstant.TRACK_STATUS_REVIEWING)
|
||||
.select(TrackInfo::getId, TrackInfo::getReviewTaskId)
|
||||
.last("limit 100")
|
||||
);
|
||||
//2.遍历列表,查询审核结果
|
||||
if (CollUtil.isNotEmpty(trackInfoList)) {
|
||||
for (TrackInfo trackInfo : trackInfoList) {
|
||||
String suggestion = auditService.getReviewResult(trackInfo.getReviewTaskId());
|
||||
if (StringUtils.isNotBlank(suggestion)) {
|
||||
//3.根据审核任务结果更新声音审核状态
|
||||
if ("pass".equals(suggestion)) {
|
||||
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;
|
||||
|
||||
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.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 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.RestController;
|
||||
|
||||
|
@ -16,5 +23,14 @@ public class OrderInfoApiController {
|
|||
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;
|
||||
|
||||
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;
|
||||
|
||||
public interface OrderInfoService extends IService<OrderInfo> {
|
||||
|
||||
|
||||
OrderInfoVo trade(TradeVo tradeVo);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,36 @@
|
|||
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.user.VipServiceConfig;
|
||||
import com.atguigu.tingshu.order.mapper.OrderInfoMapper;
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.weaver.ast.Or;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
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
|
||||
@Service
|
||||
@SuppressWarnings({"all"})
|
||||
|
@ -15,6 +38,165 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
|
|||
|
||||
@Autowired
|
||||
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>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.kafka</groupId>
|
||||
<artifactId>kafka-clients</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.kafka</groupId>
|
||||
<artifactId>kafka-streams</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.biezhi</groupId>
|
||||
<artifactId>TinyPinyin</artifactId>
|
||||
|
|
|
@ -71,37 +71,45 @@ public class SearchReciever {
|
|||
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 监听更新声音/搜索统计信息
|
||||
// *
|
||||
// * @param mqVo
|
||||
// * @param message
|
||||
// * @param channel
|
||||
// */
|
||||
//
|
||||
// @RabbitListener(bindings = @QueueBinding(
|
||||
// exchange = @Exchange(value = MqConst.EXCHANGE_TRACK, durable = "true"),
|
||||
// value = @Queue(value = MqConst.QUEUE_ALBUM_ES_STAT_UPDATE, durable = "true"),
|
||||
// key = MqConst.ROUTING_TRACK_STAT_UPDATE)
|
||||
// )
|
||||
// @SneakyThrows
|
||||
// public void updateStat(TrackStatMqVo mqVo, Message message, Channel channel){
|
||||
// if (mqVo != null)log.info("更新声音/专辑统计信息:{}", mqVo);
|
||||
// //1.幂等性处理
|
||||
// //1.1 从MQ消息对象中获取消息唯一标识
|
||||
// String key = RedisConstant.BUSINESS_PREFIX + "db:" + mqVo.getBusinessNo();
|
||||
// //1.2 尝试采用set nx写入Redis
|
||||
// Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 5, TimeUnit.MINUTES);
|
||||
//
|
||||
// //1.3 写入成功消息才进行业务处理,写入失败(消息重复被MQ服务器投递)忽略消息
|
||||
// if (flag) {
|
||||
// //2.业务处理
|
||||
// searchService.upperAlbum(mqVo.getAlbumId());
|
||||
//
|
||||
// }
|
||||
// channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
|
||||
// }
|
||||
/**
|
||||
* 监听更新声音/搜索统计信息
|
||||
*
|
||||
* @param mqVo
|
||||
* @param message
|
||||
* @param channel
|
||||
*/
|
||||
|
||||
@RabbitListener(bindings = @QueueBinding(
|
||||
exchange = @Exchange(value = MqConst.EXCHANGE_TRACK, durable = "true"),
|
||||
value = @Queue(value = MqConst.QUEUE_ALBUM_ES_STAT_UPDATE, durable = "true"),
|
||||
key = MqConst.ROUTING_TRACK_STAT_UPDATE)
|
||||
)
|
||||
@SneakyThrows
|
||||
public void updateStat(TrackStatMqVo mqVo, Message message, Channel channel) {
|
||||
if (mqVo != null) {
|
||||
log.info("更新声音/专辑统计信息:{}", mqVo);
|
||||
//1.幂等性处理
|
||||
//1.1 从MQ消息对象中获取消息唯一标识
|
||||
String key = RedisConstant.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.SuggestIndex;
|
||||
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.AlbumSearchResponseVo;
|
||||
|
||||
|
@ -37,4 +38,6 @@ public interface SearchService {
|
|||
void updateLatelyAlbumRanking(Integer top);
|
||||
|
||||
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.user.UserInfoVo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
@ -28,6 +29,8 @@ public class ItemServiceImpl implements ItemService {
|
|||
private AlbumFeignClient albumFeignClient;
|
||||
@Autowired
|
||||
private Executor threadPoolTaskExecutor;
|
||||
@Autowired
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
|
||||
@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.Hit;
|
||||
import co.elastic.clients.elasticsearch.core.search.Suggestion;
|
||||
import co.elastic.clients.json.JsonData;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.atguigu.tingshu.album.AlbumFeignClient;
|
||||
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.search.AlbumInfoIndex;
|
||||
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.service.SearchService;
|
||||
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.AlbumSearchResponseVo;
|
||||
import com.atguigu.tingshu.vo.user.UserInfoVo;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.data.elasticsearch.core.suggest.Completion;
|
||||
|
||||
|
@ -68,17 +73,21 @@ public class SearchServiceImpl implements SearchService {
|
|||
private UserFeignClient userFeignClient;
|
||||
@Autowired
|
||||
private ElasticsearchClient elasticsearchClient;
|
||||
private static final String SUGGEST_INDEX = "suggestinfo";
|
||||
@Autowired
|
||||
private SuggestIndexRepository suggestIndexRepository;
|
||||
@Autowired
|
||||
private RedisTemplate redisTemplate;
|
||||
@Autowired
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
private static final String SUGGEST_INDEX = "suggestinfo";
|
||||
|
||||
|
||||
@Override
|
||||
public void upperAlbum(Long albumId) {
|
||||
//1.创建专辑索引对象
|
||||
AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
|
||||
|
||||
//2.远程调用专辑服务查询专辑信息,封装专辑以及专辑标签列表
|
||||
CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
|
||||
AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData();
|
||||
|
@ -137,14 +146,18 @@ public class SearchServiceImpl implements SearchService {
|
|||
BaseCategoryViewCompletableFuture,
|
||||
userInfoCompletableFuture,
|
||||
statCompletableFuture
|
||||
).orTimeout(5, TimeUnit.SECONDS)
|
||||
).orTimeout(1, TimeUnit.SECONDS)
|
||||
.join();
|
||||
|
||||
//7.保存专辑到索引库
|
||||
albumInfoIndexRepository.save(albumInfoIndex);
|
||||
|
||||
//8.将专辑标题作为提示词文档存入提示词索引库
|
||||
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
|
||||
public void updateLatelyAlbumRanking(Integer top) {
|
||||
try {
|
||||
//1.从Redis检索获取分类下五种排序维度专辑TOPN(n=top)记录
|
||||
//1.从Redis检索获取分类下五种排序维度专辑TOPN记录
|
||||
//1.1 远程调用专辑服务获取所有1级分类列表
|
||||
List<BaseCategory1> baseCategory1List = albumFeignClient.findAllCategory1().getData();
|
||||
Assert.notNull(baseCategory1List, "1级分类列表为空");
|
||||
|
||||
|
||||
//1.2 遍历1级分类
|
||||
for (BaseCategory1 baseCategory1 : baseCategory1List) {
|
||||
//从内容中找出id 设定一个key
|
||||
Long category1Id = baseCategory1.getId();
|
||||
//基于基于1级分类ID构建Redis中hash结构的Key
|
||||
String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
|
||||
//绑定大key
|
||||
//基于Key创建绑定hash操作对象
|
||||
BoundHashOperations<String, String, List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(key);
|
||||
//1.3 遍历5种排序维度
|
||||
String[] rankingDimensionArray
|
||||
|
@ -525,31 +540,67 @@ public class SearchServiceImpl implements SearchService {
|
|||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("更新Redis小时排行数据异常", e);
|
||||
log.error("更新Redis小时榜数据异常", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AlbumInfoIndexVo> getRankingList(Long category1Id, String dimension) {
|
||||
//基于1级分类ID构建Redis中hash结构的Key
|
||||
String key =RedisConstant.RANKING_KEY_PREFIX+category1Id;
|
||||
BoundHashOperations<String,String,List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(key);
|
||||
//基于基于1级分类ID构建Redis中hash结构的Key
|
||||
String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
|
||||
BoundHashOperations<String, String, List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(key);
|
||||
//构建Hash排行榜中field 当前排序方式
|
||||
String field = dimension;
|
||||
Boolean flag = hashOps.hasKey(field);
|
||||
if(flag){
|
||||
if (flag) {
|
||||
//List<AlbumInfoIndex> list = (List<AlbumInfoIndex>) redisTemplate.opsForHash().get(key, field);
|
||||
List<AlbumInfoIndex> list = hashOps.get(field);
|
||||
if (CollUtil.isNotEmpty(list)) {
|
||||
List<AlbumInfoIndexVo> albumInfoIndexVoList = list.stream()
|
||||
.map(albumInfoIndex -> BeanUtil.copyProperties(albumInfoIndex, AlbumInfoIndexVo.class))
|
||||
.map(albumInfoIndex -> BeanUtil.copyProperties(albumInfoIndex,AlbumInfoIndexVo.class))
|
||||
.collect(Collectors.toList());
|
||||
return albumInfoIndexVoList;
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
import com.atguigu.tingshu.common.constant.RedisConstant;
|
||||
import com.atguigu.tingshu.search.service.SearchService;
|
||||
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.boot.test.context.SpringBootTest;
|
||||
|
||||
|
@ -10,6 +13,8 @@ import org.springframework.boot.test.context.SpringBootTest;
|
|||
public class test01 {
|
||||
@Autowired
|
||||
private SearchService searchService;
|
||||
@Autowired
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
@Test
|
||||
public void test01() {
|
||||
|
@ -19,8 +24,17 @@ public class test01 {
|
|||
} catch (Exception e) {
|
||||
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"})
|
||||
public class UserInfoApiController {
|
||||
|
||||
@Autowired
|
||||
private UserInfoService userInfoService;
|
||||
/**
|
||||
* 根据用户ID查询用户信息
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
@Operation(summary = "根据用户ID查询用户信息")
|
||||
@GetMapping("/userInfo/getUserInfoVo/{userId}")
|
||||
public Result<UserInfoVo> getUserInfoVo(@PathVariable Long userId){
|
||||
UserInfoVo userInfoVo = userInfoService.getUserInfo(userId);
|
||||
return Result.ok(userInfoVo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取每一页声音列表(提交声音ID集合)对应购买状态
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param albumId 专辑ID
|
||||
* @param needCheckPayStateTrackIdList 需要检查购买状态的声音ID集合
|
||||
* @return {声音ID:1(已购),声音ID:0(未购)}
|
||||
*/
|
||||
@GuiGuLogin //对内微服务访问接口,上游系统调用是否只要携带token 下游系统就可以获取用户ID
|
||||
@Operation(summary = "获取每一页声音列表(提交声音ID集合)对应购买状态")
|
||||
@PostMapping("/userInfo/userIsPaidTrack/{albumId}")
|
||||
public Result<Map<Long, Integer>> userIsPaidTrack(
|
||||
@PathVariable("albumId") Long albumId,
|
||||
@RequestBody List<Long> needCheckPayStateTrackIdList
|
||||
) {
|
||||
Long userId = AuthContextHolder.getUserId();
|
||||
Map<Long, Integer> map = userInfoService.userIsPaidTrack(userId, albumId, needCheckPayStateTrackIdList);
|
||||
return Result.ok(map);
|
||||
}
|
||||
@Autowired
|
||||
private UserInfoService userInfoService;
|
||||
|
||||
/**
|
||||
* 根据用户ID查询用户信息
|
||||
*
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
@Operation(summary = "根据用户ID查询用户信息")
|
||||
@GetMapping("/userInfo/getUserInfoVo/{userId}")
|
||||
public Result<UserInfoVo> getUserInfoVo(@PathVariable Long userId) {
|
||||
UserInfoVo userInfoVo = userInfoService.getUserInfo(userId);
|
||||
return Result.ok(userInfoVo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取每一页声音列表(提交声音ID集合)对应购买状态
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param albumId 专辑ID
|
||||
* @param needCheckPayStateTrackIdList 需要检查购买状态的声音ID集合
|
||||
* @return {声音ID:1(已购),声音ID:0(未购)}
|
||||
*/
|
||||
@GuiGuLogin //对内微服务访问接口,上游系统调用是否只要携带token 下游系统就可以获取用户ID
|
||||
@Operation(summary = "获取每一页声音列表(提交声音ID集合)对应购买状态")
|
||||
@PostMapping("/userInfo/userIsPaidTrack/{albumId}")
|
||||
public Result<Map<Long, Integer>> userIsPaidTrack(
|
||||
@PathVariable("albumId") Long albumId,
|
||||
@RequestBody List<Long> needCheckPayStateTrackIdList
|
||||
) {
|
||||
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 = "获取声音播放进度")
|
||||
@GetMapping("/userListenProcess/getTrackBreakSecond/{trackId}")
|
||||
public Result<BigDecimal> getTrackBreakSecond (Long trackId){
|
||||
//1.尝试获取用户ID
|
||||
Long userId = AuthContextHolder.getUserId();
|
||||
BigDecimal second =userListenProcessService.getTrackBreakSecond(userId, trackId);
|
||||
return Result.ok(second);
|
||||
//2.查询用户对于指定声音播放进度
|
||||
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;
|
||||
|
||||
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 io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Tag(name = "VIP服务配置管理接口")
|
||||
@RestController
|
||||
@RequestMapping("api/user")
|
||||
|
@ -15,5 +23,23 @@ public class VipServiceConfigApiController {
|
|||
@Autowired
|
||||
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);
|
||||
|
||||
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;
|
||||
@Autowired
|
||||
private RabbitService rabbitService;
|
||||
@Autowired
|
||||
private UserPaidAlbumMapper userPaidAlbumMapper;
|
||||
@Autowired
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@Autowired
|
||||
private UserPaidAlbumMapper userPaidAlbumMapper;
|
||||
@Autowired
|
||||
private UserPaidTrackMapper userPaidTrackMapper;
|
||||
|
||||
@Override
|
||||
@GuiGuCache(prefix = "user:info:")
|
||||
public UserInfoVo getUserInfo(Long userId) {
|
||||
UserInfo userInfo = userInfoMapper.selectById(userId);
|
||||
return BeanUtil.copyProperties(userInfo, UserInfoVo.class);
|
||||
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
|
||||
@GuiGuCache(prefix = "user:info:")
|
||||
public UserInfoVo getUserInfo(Long userId) {
|
||||
UserInfo userInfo = userInfoMapper.selectById(userId);
|
||||
return BeanUtil.copyProperties(userInfo, UserInfoVo.class);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUser(UserInfoVo userInfoVo, Long userId) {
|
||||
UserInfo userInfo = new UserInfo();
|
||||
userInfo.setId(userId);
|
||||
userInfo.setAvatarUrl(userInfoVo.getAvatarUrl());
|
||||
userInfo.setNickname(userInfoVo.getNickname());
|
||||
userInfoMapper.updateById(userInfo);
|
||||
UserInfo userInfo = new UserInfo();
|
||||
userInfo.setId(userId);
|
||||
//更换头像
|
||||
userInfo.setAvatarUrl(userInfoVo.getAvatarUrl());
|
||||
//更换昵称
|
||||
userInfo.setNickname(userInfoVo.getNickname());
|
||||
userInfoMapper.updateById(userInfo);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Long, Integer> userIsPaidTrack(Long userId, Long albumId, List<Long> needCheckPayStateTrackIdList) {
|
||||
Map<Long, Integer> payStatusMap = new HashMap<>();
|
||||
//1.根据用户ID+专辑ID查询已购专辑记录,如果存在专辑购买记录,将提交声音ID购买状态全部设置为1,返回
|
||||
Long count = userPaidAlbumMapper.selectCount(
|
||||
new LambdaQueryWrapper<UserPaidAlbum>()
|
||||
.eq(UserPaidAlbum::getUserId, userId)
|
||||
.eq(UserPaidAlbum::getAlbumId, albumId)
|
||||
);
|
||||
if (count > 0) {
|
||||
for (Long trackId : needCheckPayStateTrackIdList) {
|
||||
payStatusMap.put(trackId, 1);
|
||||
}
|
||||
return payStatusMap;
|
||||
}
|
||||
@Override
|
||||
public Map<Long, Integer> userIsPaidTrack(Long userId, Long albumId, List<Long> needCheckPayStateTrackIdList) {
|
||||
Map<Long, Integer> payStatusMap = new HashMap<>();
|
||||
//1.根据用户ID+专辑ID查询已购专辑记录,如果存在专辑购买记录,将提交声音ID购买状态全部设置为1,返回
|
||||
Long count = userPaidAlbumMapper.selectCount(
|
||||
new LambdaQueryWrapper<UserPaidAlbum>()
|
||||
.eq(UserPaidAlbum::getUserId, userId)
|
||||
.eq(UserPaidAlbum::getAlbumId, albumId)
|
||||
);
|
||||
if (count > 0) {
|
||||
for (Long trackId : needCheckPayStateTrackIdList) {
|
||||
payStatusMap.put(trackId, 1);
|
||||
}
|
||||
return payStatusMap;
|
||||
}
|
||||
|
||||
//2.如果未购买专辑,根据用户ID+专辑ID查询已购声音记录
|
||||
List<UserPaidTrack> userPaidTrackList = userPaidTrackMapper.selectList(
|
||||
new LambdaQueryWrapper<UserPaidTrack>()
|
||||
.eq(UserPaidTrack::getUserId, userId)
|
||||
.eq(UserPaidTrack::getAlbumId, albumId)
|
||||
.select(UserPaidTrack::getTrackId)
|
||||
);
|
||||
//2.如果未购买专辑,根据用户ID+专辑ID查询已购声音记录
|
||||
//查询出来的是购买的声音的id
|
||||
List<UserPaidTrack> userPaidTrackList = userPaidTrackMapper.selectList(
|
||||
new LambdaQueryWrapper<UserPaidTrack>()
|
||||
.eq(UserPaidTrack::getUserId, userId)
|
||||
.eq(UserPaidTrack::getAlbumId, albumId)
|
||||
.select(UserPaidTrack::getTrackId)
|
||||
);
|
||||
|
||||
//2.1 如果不存在购买声音记录,将提交声音ID购买状态全部设置为0,返回
|
||||
if (CollUtil.isEmpty(userPaidTrackList)) {
|
||||
for (Long trackID : needCheckPayStateTrackIdList) {
|
||||
payStatusMap.put(trackID, 0);
|
||||
}
|
||||
return payStatusMap;
|
||||
}
|
||||
//2.1 如果不存在购买声音记录,将提交声音ID购买状态全部设置为0,返回
|
||||
if (CollUtil.isEmpty(userPaidTrackList)) {
|
||||
for (Long trackID : needCheckPayStateTrackIdList) {
|
||||
payStatusMap.put(trackID, 0);
|
||||
}
|
||||
return payStatusMap;
|
||||
}
|
||||
|
||||
//2.2 如果存在购买声音记录,将提交声音ID已购设置1,未购买设置为0返回
|
||||
//2.2.1 获取已购声音ID集合
|
||||
List<Long> userPaidTrackIdList =
|
||||
userPaidTrackList.stream().map(UserPaidTrack::getTrackId).collect(Collectors.toList());
|
||||
//2.2.2 判断哪些是已购设置为1,哪些是未购买设置为0
|
||||
for (Long trackId : needCheckPayStateTrackIdList) {
|
||||
if(userPaidTrackIdList.contains(trackId)){
|
||||
//已购买
|
||||
payStatusMap.put(trackId, 1);
|
||||
}else{
|
||||
//未购买
|
||||
payStatusMap.put(trackId, 0);
|
||||
}
|
||||
}
|
||||
return payStatusMap;
|
||||
}
|
||||
//2.2 如果存在购买声音记录,将提交声音ID已购设置1,未购买设置为0返回
|
||||
//2.2.1 获取已购声音ID集合
|
||||
//这个stream流就是将userPaidTrackList这个中的id单独取出来然后做成一个List
|
||||
List<Long> userPaidTrackIdList =
|
||||
userPaidTrackList.stream().map(UserPaidTrack::getTrackId).collect(Collectors.toList());
|
||||
//2.2.2 判断哪些是已购设置为1,哪些是未购买设置为0
|
||||
for (Long trackId : needCheckPayStateTrackIdList) {
|
||||
if (userPaidTrackIdList.contains(trackId)) {
|
||||
//已购买
|
||||
payStatusMap.put(trackId, 1);
|
||||
} else {
|
||||
//未购买
|
||||
payStatusMap.put(trackId, 0);
|
||||
}
|
||||
}
|
||||
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