- springboot初始化
- 新增本地域名cart.achangmall.com
- 修改对应pom配置,与此次服务间版本对应
- 引入公共依赖
- 修改springboot版本
- 修改springcloud版本
- 将提供的静态资源导入nginx服务,做到动静分离
- 修改页面对应的资源地址,修改只选nginx的静态地址,如:
- 主启动类com.achang.achangmall.cart.AchangmallCartApplication
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableFeignClients
@EnableDiscoveryClient
public class AchangmallCartApplication {
public static void main(String[] args) {
SpringApplication.run(AchangmallCartApplication.class, args);
}
}
- 配置文件achangmall-cart/src/main/resources/application.yaml
spring:
cloud:
loadbalancer:
ribbon:
enabled: false
nacos:
discovery:
server-addr: localhost:8848
thymeleaf:
cache: false
redis:
host: 192.168.109.101
port: 6379
application:
name: achangmall-cart
server:
port: 30000
- 网关配置路由映射achangmall-gateway/src/main/resources/application.yml
- id: cart_route
uri: lb://achangmall-cart
predicates:
- Host=cart.achangmall.com
- 启动服务测试,访问:http://cart.achangmall.com/
二、业务
- com.achang.achangmall.cart.vo.CartVo
public class CartVo {
List items;
private Integer countNum;
private Integer countType;
private BigDecimal totalAmount;
private BigDecimal reduce = new BigDecimal("0.00");;
public List getItems() {
return items;
}
public void setItems(List items) {
this.items = items;
}
public Integer getCountNum() {
int count = 0;
if (items != null && items.size() > 0) {
for (CartItemVo item : items) {
count += item.getCount();
}
}
return count;
}
public Integer getCountType() {
int count = 0;
if (items != null && items.size() > 0) {
for (CartItemVo item : items) {
count += 1;
}
}
return count;
}
public BigDecimal getTotalAmount() {
BigDecimal amount = new BigDecimal("0");
// 计算购物项总价
if (!CollectionUtils.isEmpty(items)) {
for (CartItemVo cartItem : items) {
if (cartItem.getCheck()) {
amount = amount.add(cartItem.getTotalPrice());
}
}
}
// 计算优惠后的价格
return amount.subtract(getReduce());
}
public BigDecimal getReduce() {
return reduce;
}
public void setReduce(BigDecimal reduce) {
this.reduce = reduce;
}
}
- com.achang.achangmall.cart.vo.CartItemVo
public class CartItemVo {
private Long skuId;
private Boolean check = true;
private String title;
private String image;
private List skuAttrValues;
private BigDecimal price;
private Integer count;
private BigDecimal totalPrice;
public Long getSkuId() {
return skuId;
}
public void setSkuId(Long skuId) {
this.skuId = skuId;
}
public Boolean getCheck() {
return check;
}
public void setCheck(Boolean check) {
this.check = check;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public List getSkuAttrValues() {
return skuAttrValues;
}
public void setSkuAttrValues(List skuAttrValues) {
this.skuAttrValues = skuAttrValues;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public BigDecimal getTotalPrice() {
return this.price.multiply(new BigDecimal("" + this.count));
}
public void setTotalPrice(BigDecimal totalPrice) {
this.totalPrice = totalPrice;
}
}
- com.achang.achangmall.cart.vo.SkuInfoVo
@Data
public class SkuInfoVo {
private Long skuId;
private Long spuId;
private String skuName;
private String skuDesc;
private Long catalogId;
private Long brandId;
private String skuDefaultImg;
private String skuTitle;
private String skuSubtitle;
private BigDecimal price;
private Long saleCount;
}
- 引入依赖
org.springframework.boot spring-boot-starter-data-redis org.springframework.session spring-session-data-redis
- com.achang.achangmall.cart.config.AchangmallSessionConfig
@Configuration
@EnableRedisHttpSession
public class AchangmallSessionConfig {
@Bean
public cookieSerializer cookieSerializer() {
DefaultcookieSerializer cookieSerializer = new DefaultcookieSerializer();
//放大作用域
cookieSerializer.setDomainName("achangmall.com");
cookieSerializer.setcookieName("ACHANGSESSION");
return cookieSerializer;
}
@Bean
public RedisSerializer
- com.achang.achangmall.cart.config.MyThreadConfig
@EnableConfigurationProperties(ThreadPoolConfigProperties.class)
@Configuration
public class MyThreadConfig {
@Bean
public ThreadPoolExecutor executor(ThreadPoolConfigProperties pool) {
return new ThreadPoolExecutor(
pool.getCoreSize(),
pool.getMaxSize(),
pool.getKeepAliveTime(),
TimeUnit.SECONDS,
new linkedBlockingDeque<>(100000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
}
}
- com.achang.achangmall.cart.config.ThreadPoolConfigProperties
@ConfigurationProperties(prefix = "achangmall.thread")
@Data
public class ThreadPoolConfigProperties {
private Integer coreSize;
private Integer maxSize;
private Integer keepAliveTime;
}
- com.achang.achangmall.cart.exception.RuntimeExceptionHandler
@ControllerAdvice
public class RuntimeExceptionHandler {
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public R handler(RuntimeException exception) {
return R.error(exception.getMessage());
}
@ExceptionHandler(CartExceptionHandler.class)
public R userHandler(CartExceptionHandler exception) {
return R.error("购物车无此商品");
}
}
- com.achang.achangmall.cart.exception.CartExceptionHandler
public class CartExceptionHandler extends RuntimeException {}
- com.achang.achangmall.cart.feign.ProductFeignService
@FeignClient("achangmall-product")
public interface ProductFeignService {
@RequestMapping("/product/skuinfo/info/{skuId}")
R getInfo(@PathVariable("skuId") Long skuId);
@GetMapping(value = "/product/skusaleattrvalue/stringList/{skuId}")
List getSkuSaleAttrValues(@PathVariable("skuId") Long skuId);
@GetMapping(value = "/product/skuinfo/{skuId}/price")
BigDecimal getPrice(@PathVariable("skuId") Long skuId);
}
- com.achang.achangmall.cart.intercept.CartIntercept
@Data
public class UserInfoTo {
private Long userId;
private String userKey;//一定存在
private Boolean tempUser = false;
}
- 拦截器com.achang.achangmall.cart.intercept.CartIntercept
@Component
public class CartIntercept implements HandlerInterceptor {
public static ThreadLocal toThreadLocal = new ThreadLocal<>();
//在目标方法执行之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
UserInfoTo userInfoTo = new UserInfoTo();
HttpSession session = request.getSession();
MemberResponseVo memberResponseVo = (MemberResponseVo) session.getAttribute(LOGIN_USER);
if (memberResponseVo != null) {
//用户登录了
userInfoTo.setUserId(memberResponseVo.getId());
}
cookie[] cookies = request.getcookies();
if (cookies != null && cookies.length > 0) {
for (cookie cookie : cookies) {
//user-key
String name = cookie.getName();
if (name.equals(TEMP_USER_cookie_NAME)) {
userInfoTo.setUserKey(cookie.getValue());
//标记为已是临时用户
userInfoTo.setTempUser(true);
}
}
}
//如果没有临时用户一定分配一个临时用户
if (StringUtils.isEmpty(userInfoTo.getUserKey())) {
String uuid = UUID.randomUUID().toString();
userInfoTo.setUserKey(uuid);
}
//目标方法执行之前
toThreadLocal.set(userInfoTo);
return true;
}
//业务执行之后,分配临时用户来浏览器保存
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//获取当前用户的值
UserInfoTo userInfoTo = toThreadLocal.get();
//如果没有临时用户一定保存一个临时用户
if (!userInfoTo.getTempUser()) {
//创建一个cookie
cookie cookie = new cookie(TEMP_USER_cookie_NAME, userInfoTo.getUserKey());
//扩大作用域
cookie.setDomain("achangmall.com");
//设置过期时间
cookie.setMaxAge(TEMP_USER_cookie_TIMEOUT);
response.addcookie(cookie);
}
toThreadLocal.remove();
}
}
- com.achang.achangmall.cart.to.UserInfoTo
@Data
public class UserInfoTo {
private Long userId;
private String userKey;//一定存在
private Boolean tempUser = false;
}
- com.achang.common.constant.CartConstant
public class CartConstant {
public final static String TEMP_USER_cookie_NAME = "user-key";
public final static int TEMP_USER_cookie_TIMEOUT = 60*60*24*30;
public final static String CART_PREFIX = "achangmall:cart:";
}
- com.achang.achangmall.cart.intercept.AchangmallWebConfig配置拦截器,让其生效
@Configuration
public class AchangmallWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CartIntercept())//注册拦截器
.addPathPatterns("
@GetMapping(value = "/currentUserCartItems")
@ResponseBody
public List getCurrentCartItems() {
List cartItemVoList = cartService.getUserCartItems();
return cartItemVoList;
}
@GetMapping(value = "/cart.html")
public String cartListPage(Model model) throws ExecutionException, InterruptedException {
//快速得到用户信息:id,user-key
// UserInfoTo userInfoTo = CartInterceptor.toThreadLocal.get();
CartVo cartVo = cartService.getCart();
model.addAttribute("cart",cartVo);
return "cartList";
}
@GetMapping(value = "/addCartItem")
public String addCartItem(@RequestParam("skuId") Long skuId,
@RequestParam("num") Integer num,
RedirectAttributes attributes) throws ExecutionException, InterruptedException {
cartService.addToCart(skuId,num);
attributes.addAttribute("skuId",skuId);
return "redirect:http://cart.achangmall.com/addToCartSuccessPage.html";
}
@GetMapping(value = "/addToCartSuccessPage.html")
public String addToCartSuccessPage(@RequestParam("skuId") Long skuId,Model model) {
//重定向到成功页面。再次查询购物车数据即可
CartItemVo cartItemVo = cartService.getCartItem(skuId);
model.addAttribute("cartItem",cartItemVo);
return "success";
}
@GetMapping(value = "/checkItem")
public String checkItem(@RequestParam(value = "skuId") Long skuId,
@RequestParam(value = "checked") Integer checked) {
cartService.checkItem(skuId,checked);
return "redirect:http://cart.achangmall.com/cart.html";
}
@GetMapping(value = "/countItem")
public String countItem(@RequestParam(value = "skuId") Long skuId,
@RequestParam(value = "num") Integer num) {
cartService.changeItemCount(skuId,num);
return "redirect:http://cart.achangmall.com/cart.html";
}
@GetMapping(value = "/deleteItem")
public String deleteItem(@RequestParam("skuId") Integer skuId) {
cartService.deleteIdCartInfo(skuId);
return "redirect:http://cart.achangmall.com/cart.html";
}
}
- com.achang.achangmall.cart.service.impl.CartServiceImpl
@Slf4j
@Service("cartService")
public class CartServiceImpl implements CartService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private ProductFeignService productFeignService;
@Autowired
private ThreadPoolExecutor executor;
@Override
public CartItemVo addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {
//拿到要操作的购物车信息
BoundHashOperations cartOps = getCartOps();
//判断Redis是否有该商品的信息
String productRedisValue = (String) cartOps.get(skuId.toString());
//如果没有就添加数据
if (StringUtils.isEmpty(productRedisValue)) {
//2、添加新的商品到购物车(redis)
CartItemVo cartItemVo = new CartItemVo();
//开启第一个异步任务
CompletableFuture getSkuInfoFuture = CompletableFuture.runAsync(() -> {
//1、远程查询当前要添加商品的信息
R productSkuInfo = productFeignService.getInfo(skuId);
SkuInfoVo skuInfo = productSkuInfo.getData("skuInfo", new TypeReference() {});
//数据赋值操作
cartItemVo.setSkuId(skuInfo.getSkuId());
cartItemVo.setTitle(skuInfo.getSkuTitle());
cartItemVo.setImage(skuInfo.getSkuDefaultImg());
cartItemVo.setPrice(skuInfo.getPrice());
cartItemVo.setCount(num);
}, executor);
//开启第二个异步任务
CompletableFuture getSkuAttrValuesFuture = CompletableFuture.runAsync(() -> {
//2、远程查询skuAttrValues组合信息
List skuSaleAttrValues = productFeignService.getSkuSaleAttrValues(skuId);
cartItemVo.setSkuAttrValues(skuSaleAttrValues);
}, executor);
//等待所有的异步任务全部完成
CompletableFuture.allOf(getSkuInfoFuture, getSkuAttrValuesFuture).get();
String cartItemJson = JSON.toJSONString(cartItemVo);
cartOps.put(skuId.toString(), cartItemJson);
return cartItemVo;
} else {
//购物车有此商品,修改数量即可
CartItemVo cartItemVo = JSON.parseObject(productRedisValue, CartItemVo.class);
cartItemVo.setCount(cartItemVo.getCount() + num);
//修改redis的数据
String cartItemJson = JSON.toJSONString(cartItemVo);
cartOps.put(skuId.toString(),cartItemJson);
return cartItemVo;
}
}
@Override
public CartItemVo getCartItem(Long skuId) {
//拿到要操作的购物车信息
BoundHashOperations cartOps = getCartOps();
String redisValue = (String) cartOps.get(skuId.toString());
CartItemVo cartItemVo = JSON.parseObject(redisValue, CartItemVo.class);
return cartItemVo;
}
@Override
public CartVo getCart() throws ExecutionException, InterruptedException {
CartVo cartVo = new CartVo();
UserInfoTo userInfoTo = CartIntercept.toThreadLocal.get();
if (userInfoTo.getUserId() != null) {
//1、登录
String cartKey = CART_PREFIX + userInfoTo.getUserId();
//临时购物车的键
String temptCartKey = CART_PREFIX + userInfoTo.getUserKey();
//2、如果临时购物车的数据还未进行合并
List tempCartItems = getCartItems(temptCartKey);
if (tempCartItems != null) {
//临时购物车有数据需要进行合并操作
for (CartItemVo item : tempCartItems) {
addToCart(item.getSkuId(),item.getCount());
}
//清除临时购物车的数据
clearCartInfo(temptCartKey);
}
//3、获取登录后的购物车数据【包含合并过来的临时购物车的数据和登录后购物车的数据】
List cartItems = getCartItems(cartKey);
cartVo.setItems(cartItems);
} else {
//没登录
String cartKey = CART_PREFIX + userInfoTo.getUserKey();
//获取临时购物车里面的所有购物项
List cartItems = getCartItems(cartKey);
cartVo.setItems(cartItems);
}
return cartVo;
}
private BoundHashOperations getCartOps() {
//先得到当前用户信息
UserInfoTo userInfoTo = CartIntercept.toThreadLocal.get();
String cartKey = "";
if (userInfoTo.getUserId() != null) {
//gulimall:cart:1
cartKey = CART_PREFIX + userInfoTo.getUserId();
} else {
cartKey = CART_PREFIX + userInfoTo.getUserKey();
}
//绑定指定的key操作Redis
BoundHashOperations operations = redisTemplate.boundHashOps(cartKey);
return operations;
}
private List getCartItems(String cartKey) {
//获取购物车里面的所有商品
BoundHashOperations operations = redisTemplate.boundHashOps(cartKey);
List



