????作者简介:大家好,我是小童,Java开发工程师,博客博主,Java领域新星创作者
????系列专栏:前端、Java、Java中间件大全、微信小程序、微信支付、若依框架、Spring全家桶
????如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步????
????如果感觉博主的文章还不错的话,请????三连支持????一下博主哦
????博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人专栏:高并发项目
编写购物车服务接口
用户购买商品的流程为: 搜索商品 > 查看商品详情 > 添加到购物车 > 生成商品订单 > 支 付 ,搜索商品和查看商品详情功能已经完成,接下来我们编写购物 车服务。购物车数据属于临时数据,为了节约数据库开销,我们会 将其存放到redis中。
在通用模块编写购物车服务接口:
// 购物车服务
public interface CartService {
// 新增商品到购物车
void addCart(Long userId, CartGoods cartGoods);
// 修改购物车商品数量
void handleCart(Long userId, Long goodId, Integer num);
// 删除购物车商品
void deleteCartOption(Long userId, Long goodId);
// 获取用户购物车
List<CartGoods> findCartList(Long userId);
// 更新redis中的商品数据,在管理员更新商品后执行
void refreshCartGoods(CartGoods cartGoods);
// 删除redis中的商品数据,在管理员下架商品后执行
void deleteCartGoods(CartGoods cartGoods);
}
创建购物车服务模块
1、创建名为 shopping_cart_service 的SpringBoot工程,添加相关依赖。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.itbaizhan</groupId>
<artifactId>shopping_common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.8</version>
</dependency>
<!-- 操作zookeeper -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
2、设置该工程的父工程为 shopping 。
<parent>
<groupId>com.ittxc</groupId>
<artifactId>shopping</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
3、给 shopping 工程设置子模块
<!-- 子模块 -->
<modules>
<!-- 购物车服务 -->
<module>shopping_cart_service</module>
</modules>
4、编写配置文件 application.yml
# 端口号
server:
port: 9009
# 日志格式
logging:
pattern:
console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
spring:
# redis
redis:
host: 192.168.0.159
port: 6379
timeout: 30000
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
dubbo:
application:
name: shopping_cart_service # 项目名
registry:
address: zookeeper://192.168.0.159 #注册中心地址
port: 2181 # 注册中心的端口
timeout: 10000 # 注册到zk上超时时间,ms
protocol:
name: dubbo # dubbo使用的协议
port: -1 # dubbo自动分配端口
scan:
base-packages: com.itbaizhan.shopping_cart_service.service # 包扫描
5、启动类忽略数据源自动配置
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
public class ShoppingCartServiceApplication {
public static void main(String[] args)
{
SpringApplication.run(ShoppingCartServiceApplication.class, args);
}
}
创建购物车Api模块
1、创建名为 shopping_cart_customer_api 的SpringBoot工程,添加相关依赖。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.8</version>
</dependency>
<!-- 操作zookeeper -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>com.itbaizhan</groupId>
<artifactId>shopping_common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-startertest</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2、设置该工程的父工程为 shopping 。
<parent>
<groupId>com.ittxc</groupId>
<artifactId>shopping</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
3、给 shopping 工程设置子模块
<!-- 子模块 -->
<modules>
<!-- 购物车api -->
<module>shopping_cart_customer_api</module>
</modules>
4、编写配置文件 application.yml
# 端口号
server:
port: 8005
# 日志格式
logging:
pattern:
console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
dubbo:
application:
name: shopping_cart_customer_api # 项目名
registry:
address: zookeeper://192.168.0.159 #注册中心地址
port: 2181 # 注册中心的端口
timeout: 10000 # 注册到zk上超时时间,ms
protocol:
name: dubbo # dubbo使用的协议
port: -1 # dubbo自动分配端口
5、启动类忽略数据源自动配置
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
public class ShoppingUserCustomerApiApplication {
public static void main(String[] args)
{
SpringApplication.run(ShoppingUserCustomerApiApplication.class, args);
}
}
编写查询用户购物车功能
我们将用户的购物车信息保存到redis当中,所有用户的购物车作为 一个hash类型的数据保存,hash的键是用户id,hash的值是购物车商品列表。
在购物车服务模块创建购物车服务接口的实现类,重写查询用户购物车功能
@DubboService
public class CartServiceImpl implements CartService {
@Autowired
private RedisTemplate redisTemplate;
@Override
public List<CartGoods> findCartList(Long userId) {
Object cartList = redisTemplate.boundHashOps("cartList").get(userId);
if (cartList == null) {
return new ArrayList<CartGoods>();
} else {
return (List<CartGoods>)cartList;
}
}
}
编写添加商品到购物车方法
@Override
public void addCart(Long userId, CartGoods cartGoods) {
// 1.根据用户id获取用户购物车列表
List<CartGoods> cartList = findCartList(userId);
// 2.查询购物车是否有该商品,如果有商品,添加商品数量
for (CartGoods cartGoods1 : cartList) {
if(cartGoods.getGoodId().equals(cartGoods1.getGoodId())){
int newNum = cartGoods1.getNum() + cartGoods.getNum();
cartGoods1.setNum(newNum);
redisTemplate.boundHashOps("cartList").put(userId,cartList);
return;
}
}
// 3.如果购物车没有该商品,将商品添加到购物车列表
cartList.add(cartGoods);
redisTemplate.boundHashOps("cartList").put(userId,cartList);
}
编写修改购物车商品数量方法
@Override
public void handleCart(Long userId, Long goodId, Integer num) {
// 获取用户购物车列表
List<CartGoods> cartList = findCartList(userId);
// 遍历列表找到对应商品
for (CartGoods cartGoods : cartList) {
if(goodId.equals(cartGoods.getGoodId())) {
// 改变商品数量
cartGoods.setNum(num);
break;
}
}
// 将新的购物车列表保存到redis中
redisTemplate.boundHashOps("cartList").put(userId, cartList);
}
编写删除购物车商品方法
@Override
public void deleteCartOption(Long userId,Long goodId) {
// 获取用户购物车列表
List<CartGoods> cartList = findCartList(userId);
// 将商品移出列表
for (CartGoods cartGoods : cartList) {
if (goodId.equals(cartGoods.getGoodId())) {
cartList.remove(cartGoods);
break;
}
}
// 将新的购物车列表保存到redis中
redisTemplate.boundHashOps("cartList").put(userId, cartList);
}
编写购物车控制器
在进行购物车操作之前,要先获取用户的Id,而用户的令牌中只保存了用户名,所以我们要修改JWT工具类,让令牌中也保存用户的 Id:
1、修改JWT工具类
public class JWTUtil {
//token过期时间,一天
private static final Long EXPIRE_DATE = 1000*60*60*24L;
// 秘钥
private static final String SECRET = "xiaotong";
// 签发者
private static final String ISSUER = "XIAOTONG";
/**
* 签名生成
* @param shoppingUser
* @return
*/
public static String sign(ShoppingUser shoppingUser){
String token = JWT.create()
.withIssuer(ISSUER) // 签发者
.withIssuedAt(new Date())// 签发时间
.withExpiresAt(new Date(new Date().getTime() + EXPIRE_DATE))// 过期时间
.withSubject(shoppingUser.getUsername())// 保存用户名
.withClaim("userId",shoppingUser.getId())// 保存用户id
.sign(Algorithm.HMAC256(SECRET)); // 秘钥
return token;
}
/**
* 签名解析
* @param token 签名字符串
* @return 解析得出的用户名
*/
public static String verify(String token){
try {
String username = JWT
.require(Algorithm.HMAC256(SECRET))
.withIssuer(ISSUER)
.build()
.verify(token)
.getSubject();
return username;
} catch (Exception e){
throw new BusException(CodeEnum.VERIFY_TOKEN_ERROR);
}
}
/**
* 签名解析,获取用户id
* @param token 签名字符串
* @return 用户id
*/
public static Long getId(String token)
{
try {
Long userId = JWT
.require(Algorithm.HMAC256(SECRET))
.withIssuer(ISSUER)
.build()
.verify(token)
.getClaim("userId")
.asLong();
return userId;
} catch (Exception e){
throw new BusException(CodeEnum.VERIFY_TOKEN_ERROR);
}
}
}
2、在购物车Api模块编写购物车控制器
/**
* 购物车
*/
@RestController
@RequestMapping("/user/cart")
public class CartController {
@DubboReference
private CartService cartService;
/**
* 查询用户购物车
* @param token 用户令牌
* @return 用户购物车列表
*/
@GetMapping("/findCartList")
public BaseResult<List<CartGoods>> findCartList(@RequestHeader String token){
Long userId = JWTUtil.getId(token); // 获取用户id
List<CartGoods> cartList = cartService.findCartList(userId);
return BaseResult.ok(cartList);
}
/**
* 新增商品到购物车
* @param cartGoods 购物车商品
* @param token 用户令牌
* @return 操作结果
*/
@PostMapping("/addCart")
public BaseResult addCart(@RequestBody CartGoods cartGoods,@RequestHeader String token){
Long userId = JWTUtil.getId(token); // 获取用户id
cartService.addCart(userId,cartGoods);
return BaseResult.ok();
}
/**
* 修改购物车商品数量
* @param token 用户令牌
* @param goodId 商品id
* @param num 修改后的数量
* @return 操作结果
*/
@GetMapping("/handleCart")
public BaseResult addCart(@RequestHeader String token,Long goodId,Integer num){
Long userId = JWTUtil.getId(token); // 获取用户id
cartService.handleCart(userId,goodId,num);
return BaseResult.ok();
}
/**
* 删除购物车商品
* @param token 用户令牌
* @param goodId 商品id
* @return 操作结果
*/
@DeleteMapping("/deleteCart")
public BaseResult addCart(@RequestHeader String token,Long goodId){
Long userId = JWTUtil.getId(token); // 获取用户id
cartService.deleteCartOption(userId,goodId);
return BaseResult.ok();
}
}
编写修改所有用户购物车商品方法
当管理员修改了商品的价格等数据后,需要将数据同步到用户购物车中,所以我们要编写给所有用户的购物车中修改某件商品的方法。
@Override
public void refreshCartGoods(CartGoods
cartGoods) {
// 获取所有用户购物车商品
BoundHashOperations cartList = redisTemplate.boundHashOps("cartList");
Map<Long,List<CartGoods>> allCartGoods = cartList.entries();
Set<Map.Entry<Long, List<CartGoods>>> entries = allCartGoods.entrySet();
// 遍历所有用户的购物车
for (Map.Entry<Long, List<CartGoods>> entry : entries) {
List<CartGoods> goodsList = entry.getValue();
// 遍历一个用户购物车的所有商品
for (CartGoods goods : goodsList) {
// 如果该商品是被更新的商品,修改商品数据
if(cartGoods.getGoodId().equals(goods.getGoodId())){
goods.setGoodsName(cartGoods.getGoodsName());
goods.setHeaderPic(cartGoods.getHeaderPic());
goods.setPrice(cartGoods.getPrice());
}
}
}
// 将改变后所有用户购物车重新放入redis
redisTemplate.delete("cartList");
redisTemplate.boundHashOps("cartList").putAll(allCartGoods);
}
编写删除所有用户购物车商品方法
当管理员下架某件商品后,所有用户的购物车中该商品也应该被删除,所以我们要编写给所有用户的购物车中删除某件商品的方法。
@Override
public void deleteCartGoods(CartGoods cartGoods) {
BoundHashOperations cartList = redisTemplate.boundHashOps("cartList");
// 所有用户的购物车
Map<String,List<CartGoods>> allCartGoods = cartList.entries();
Set<Map.Entry<String, List<CartGoods>>> entries = allCartGoods.entrySet();
// 遍历所有用户的购物车
for (Map.Entry<String, List<CartGoods>> entry : entries) {
List<CartGoods> goodsList = entry.getValue();
// 遍历一个用户购物车的所有商品
for (CartGoods goods : goodsList) {
// 如果该商品是被删除的商品
if (cartGoods.getGoodId().equals(goods.getGoodId())){
goodsList.remove(goods);
break;
}
}
}
// 将改变后的map重新放入redis
redisTemplate.delete("cartList");
redisTemplate.boundHashOps("cartList").putAll(allCartGoods);
}
优化修改商品、下架商品方法
接下来我们修改商品服务模块代码,在修改商品后发送消息同步修 改购物车数据;在下架商品后发送消息同步删除购物车数据:
1、在MQ配置文件创建队列,绑定队列到交换机:
@Configuration
public class RabbitConfig {
// 交换机
private final String GOODS_EXCHANGE = "goods_exchange";
// 同步商品数据队列
private final String SYNC_GOODS_QUEUE = "sync_goods_queue";
// 删除商品数据队列
private final String DEL_GOODS_QUEUE = "del_goods_queue";
// 向购物车同步商品队列
private final String SYNC_CART_QUEUE = "sync_cart_queue";
// 向购物车删除商品队列
private final String DEL_CART_QUEUE = "del_cart_queue";
// 创建交换机
@Bean(GOODS_EXCHANGE)
public Exchange getExchange() {
return ExchangeBuilder
.topicExchange(GOODS_EXCHANGE) // 交换机类型
.durable(true) // 是否持久化
.build();
}
// 创建队列
@Bean(SYNC_GOODS_QUEUE)
public Queue getQueue1() {
return new Queue(SYNC_GOODS_QUEUE); // 队列名
}
@Bean(DEL_GOODS_QUEUE)
public Queue getQueue2() {
return new Queue(DEL_GOODS_QUEUE);
// 队列名
}
@Bean(SYNC_CART_QUEUE)
public Queue getQueue3() {
return new Queue(SYNC_CART_QUEUE);
// 队列名
}
@Bean(DEL_CART_QUEUE)
public Queue getQueue4() {
return new Queue(DEL_CART_QUEUE);
// 队列名
}
// 交换机绑定队列
@Bean
public Binding bindQueue1(@Qualifier(GOODS_EXCHANGE) Exchange exchange,
@Qualifier(SYNC_GOODS_QUEUE) Queue queue)
{
return BindingBuilder
.bind(queue)
.to(exchange)
.with("#.sync_goods.#")
.noargs();
}
@Bean
public Binding bindQueue2(@Qualifier(GOODS_EXCHANGE) Exchange exchange,
@Qualifier(DEL_GOODS_QUEUE) Queue queue) {
return BindingBuilder
.bind(queue)
.to(exchange)
.with("#.del_goods.#")
.noargs();
}
@Bean
public Binding
bindQueue3(@Qualifier(GOODS_EXCHANGE)
Exchange exchange,
@Qualifier(SYNC_CART_QUEUE) Queue queue)
{
return BindingBuilder
.bind(queue)
.to(exchange)
.with("#.sync_cart.#")
.noargs();
}
@Bean
public Binding bindQueue4(@Qualifier(GOODS_EXCHANGE) Exchange exchange,
@Qualifier(DEL_CART_QUEUE) Queue queue) {
return BindingBuilder
.bind(queue)
.to(exchange)
.with("#.del_cart.#")
.noargs();
}
}
@DubboService
public class GoodsServiceImpl implements
GoodsService {
@Autowired
private GoodsMapper goodsMapper;
@Autowired
private GoodsImageMapper goodsImageMapper;
@Autowired
private RabbitTemplate rabbitTemplate;
@Override
public void update(Goods goods) {
// 删除旧图片数据
Long goodsId = goods.getId(); //商品id
QueryWrapper<GoodsImage> queryWrapper = new QueryWrapper();
queryWrapper.eq("goodsId",goodsId);
goodsImageMapper.delete(queryWrapper);
// 删除旧规格项数据
goodsMapper.deleteGoodsSpecificationOption(goodsId);
// 插入商品数据
goodsMapper.updateById(goods);
// 插入图片数据
List<GoodsImage> images = goods.getImages(); // 商品图片
for (GoodsImage image : images) {
image.setGoodsId(goodsId); // 给图片设置商品id
goodsImageMapper.insert(image); // 插入图片
}
// 插入商品_规格项数据
List<Specification> specifications = goods.getSpecifications(); // 获取规格
List<SpecificationOption> options = new ArrayList(); // 规格项集合
// 遍历规格,获取规格中的所有规格项
for (Specification specification : specifications) {
options.addAll(specification.getSpecificationOptions());
}
// 遍历规格项,插入商品_规格项数据
for (SpecificationOption option : options) {
goodsMapper.addGoodsSpecificationOption(goodsId,option.getId());
}
rabbitTemplate.convertAndSend("goods_exchange","sync_goods",findById(goodsId));
// 将商品修改数据同步到用户购物车
CartGoods cartGoods = new CartGoods();
cartGoods.setGoodId(goods.getId());
cartGoods.setGoodsName(goods.getGoodsName());
cartGoods.setHeaderPic(goods.getHeaderPic());
cartGoods.setPrice(goods.getPrice());
rabbitTemplate.convertAndSend("goods_exchange","sync_cart",cartGoods);
}
@Override
public void putAway(Long id, Boolean isMarketable) {
goodsMapper.putAway(id,isMarketable);
if (isMarketable){
// 上架时同步到ES
rabbitTemplate.convertAndSend("goods_exchange","sync_goods",findById(id));
}else{
// 下架删除ES数据
rabbitTemplate.convertAndSend("goods_exchange","del_goods",id);
// 商品下架删除用户购物车
CartGoods cartGoods = new CartGoods();
cartGoods.setGoodId(id);
rabbitTemplate.convertAndSend("goods_exchange","del_cart",cartGoods);
}
}
}
购物车模块监听修改商品、删除商品消息
1、在购物车服务模块添加RabbitMQ依赖
<!-- RabbitMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2、添加RabbitMQ配置
spring:
# redis
redis:
host: 192.168.0.159
port: 6379
timeout: 30000
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
# rabbitmq
rabbitmq:
host: 192.168.0.159
port: 5672
username: guest
password: guest
virtual-host: /
3、修改购物车服务接口实现类,监听队列
@DubboService
@Service
public class CartServiceImpl implements CartService {
@Autowired
private RedisTemplate redisTemplate;
// 监听修改购物车商品队列
@RabbitListener(queues = "sync_cart_queue")
public void listenSyncQueue(CartGoods cartGoods){
refreshCartGoods(cartGoods);
}
// 监听删除购物车商品队列
@RabbitListener(queues = "del_cart_queue")
public void listenDelQueue(CartGoods cartGoods){
deleteCartGoods(cartGoods);
}
}
4、测试管理员修改商品、下架商品,看用户购物车是否发生改变。