// win本地hosts
// 将cart的静态资源上传到nginx的cart文件夹下
// 将cartList.html和success.html复制到模块中,并修改静态资源路径
192.168.56.10 gulimall.com
192.168.56.10 search.gulimall.com
192.168.56.10 item.gulimall.com
192.168.56.10 auth.gulimall.com
192.168.56.10 cart.gulimall.com
// gateway添加以下路由
- id: gulimall_cart_route
uri: lb://gulimall-cart
predicates:
- Host=cart.gulimall.com
// application.properties
server.port=14000
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.application.name=gulimall-cart
spring.redis.host=192.168.56.10
gulimall.thread.core-size=20
gulimall.thread.keep-alive-time=10
gulimall.thread.max-size=200
用户可以在登录
状态下将商品添加到购物车【用户购物车/在线购物车】
问题: redis是一个内存数据库,一旦宕机则数据丢失。后面我们可以修改持久化策略来解决,即使这样会损失一部分吞吐量,但仍然比mysql性能高很多。
用户可以在未登录
状态下将商品添加到购物车【游客购物车/离线购物车/临时购物车】
添加商品
修改购买商品的数量
。删除商品
。选中不选中
商品展示商品优惠信息
商品价格变化
每一个请求进来,tomcat会开一个线程给我们处理,从拦截器的执行,到controller,service,dao一直到请求结束给浏览器响应,从始至终都是同一个线程。
所以在同一个线程期间,所以下一个流程要共享上一个流程的数据,就可以使用ThreadLocal。
ThreadLocal的核心原理,其实就是一个map,map的key时thread(当前线程),值就是当前线程要共享的数据。
key不一样,值也就不一样,所以我们每一个线程是互不干扰的。
package com.atlinxi.gulimall.cart;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@EnableRedisHttpSession
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class GulimallCartApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallCartApplication.class, args);
}
}
package com.atlinxi.gulimall.cart.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
@Configuration
public class GulimallSessionConfig {
@Bean
public CookieSerializer cookieSerializer(){
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setDomainName("gulimall.com");
cookieSerializer.setCookieName("GULISESSION");
return cookieSerializer;
}
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
}
package com.atlinxi.gulimall.cart.config;
import com.atlinxi.gulimall.cart.interceptor.CartInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class GulimallWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 将我们写好的拦截器添加到注册列表中,并指定拦截所有请求
registry.addInterceptor(new CartInterceptor()).addPathPatterns("/**");
}
}
package com.atlinxi.gulimall.cart.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
// ThreadPoolConfigProperties 已经被注入容器了,就不需要这样配置了
//@EnableConfigurationProperties(ThreadPoolConfigProperties.class)
@Configuration
public class MyThreadConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool){
return new ThreadPoolExecutor(pool.getCoreSize(),pool.getMaxSize(),
pool.getKeepAliveTime(), TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100000),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()
);
}
}
package com.atlinxi.gulimall.cart.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@ConfigurationProperties("gulimall.thread")
@Component
@Data
public class ThreadPoolConfigProperties {
private Integer coreSize;
private Integer maxSize;
private Integer keepAliveTime;
}
package com.atlinxi.gulimall.cart.interceptor;
import com.atlinxi.common.constant.AuthServerConstant;
import com.atlinxi.common.constant.CartConstant;
import com.atlinxi.common.vo.MemberRespVo;
import com.atlinxi.gulimall.cart.vo.UserInfoTo;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.UUID;
/**
* 在执行目标方法之前(controller中的方法),判断用户的登录状态,并封装传递给controller目标请求
*/
public class CartInterceptor implements HandlerInterceptor {
public static ThreadLocal<UserInfoTo> threadLocal = new ThreadLocal<>();
/**
* 目标方法执行之前
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
UserInfoTo userInfoTo = new UserInfoTo();
HttpSession session = request.getSession();
MemberRespVo member = (MemberRespVo) session.getAttribute(AuthServerConstant.LOGIN_USER);
if (member != null){
// 用户登录
userInfoTo.setUserId(member.getId());
}
Cookie[] cookies = request.getCookies();
if (cookies!=null && cookies.length > 0){
for (Cookie cookie : cookies) {
String name = cookie.getName();
if (name.equals(CartConstant.TEMP_USER_COOKIE_NAME)){
userInfoTo.setUserKey(cookie.getValue());
userInfoTo.setTempUser(true);
}
}
}
// 如果没有临时用户一定分配一个临时用户
if (StringUtils.isEmpty(userInfoTo.getUserKey())){
String uuid = UUID.randomUUID().toString();
userInfoTo.setUserKey(uuid);
}
// 目标方法执行之前
threadLocal.set(userInfoTo);
return true;
}
/**
* 业务执行之后:分配临时用户,让浏览器保存
*
* 购物车无论是否登录都需要user-key的存在,响应之后可能会跳转页面,我们需要设置一下cookie
*
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
UserInfoTo userInfoTo = threadLocal.get();
// 如果没有临时用户一定保存一个临时用户
if (!userInfoTo.isTempUser()){
Cookie cookie = new Cookie(CartConstant.TEMP_USER_COOKIE_NAME, userInfoTo.getUserKey());
cookie.setDomain("gulimall.com");
// 一个月
cookie.setMaxAge(CartConstant.TEMP_USER_COOKIE_TIMEOUT);
response.addCookie(cookie);
}
}
}
SELECT CONCAT(attr_name,":",attr_value) FROM pms_sku_sale_attr_value WHERE sku_id=#{skuId}
package com.atlinxi.gulimall.cart.controller;
import com.atlinxi.gulimall.cart.feign.ProductFeignService;
import com.atlinxi.gulimall.cart.service.CartService;
import com.atlinxi.gulimall.cart.vo.Cart;
import com.atlinxi.gulimall.cart.vo.CartItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.concurrent.ExecutionException;
@Controller
public class CartController {
@Autowired
CartService cartService;
@Autowired
ProductFeignService productFeignService;
@GetMapping("/deleteItem")
public String countItem(@RequestParam("skuId") Long skuId){
cartService.deleteItem(skuId);
return "redirect:http://cart.gulimall.com/cart.html";
}
@GetMapping("/countItem")
public String countItem(@RequestParam("skuId") Long skuId,@RequestParam("num") Integer num){
cartService.changeItemCount(skuId,num);
return "redirect:http://cart.gulimall.com/cart.html";
}
@GetMapping("/checkItem")
public String checkItem(@RequestParam("skuId") Long skuId,@RequestParam("check") Integer check){
cartService.checkItem(skuId,check);
return "redirect:http://cart.gulimall.com/cart.html";
}
/**
* 浏览器有一个cookie:user-key:标识用户身份,一个月后过期
* 如果第一次使用购物车功能,都会给一个临时的用户身份
* 浏览器以后保存,每次访问都会带上这个cookie
*
* 登录:session有
* 没登录:按照cookie里面带来user-key来做
* 第一次:如果没有临时用户,则创建一个临时用户
* @return
*/
@GetMapping("/cart.html")
public String cartListPage(Model model) throws ExecutionException, InterruptedException {
Cart cart = cartService.getCart();
model.addAttribute("cart",cart);
return "cartList";
}
/**
* 添加商品到购物车
* @return
*
* 使用重定向解决重复提交的问题
* 在这儿直接返回success的话,浏览器再次刷新该页面,则会重复提交
* 之前这个商品买了几个,刷新一遍就会又买一遍,
* 添加成功之后重定向到其他页面就可以避免这个问题
*
*
* RedirectAttributes
*
* addFlashAttribute()
* 将数据放在session里面,可以在页面取出,但是只能取一次
* addAttribute()
* 将数据放在url后面
*
*
*
*/
@GetMapping("/addToCart")
public String addToCart(@RequestParam("skuId") Long skuId,
@RequestParam("num") Integer num,
RedirectAttributes redirectAttributes) throws ExecutionException, InterruptedException {
cartService.addToCart(skuId,num);
redirectAttributes.addAttribute("skuId",skuId);
return "redirect:http://cart.gulimall.com/addToCartSuccess.html";
}
/**
* 跳转到成功页
* @param skuId
* @param model
* @return
*/
@GetMapping("/addToCartSuccess.html")
public String addToCartSuccessPage(@RequestParam("skuId") Long skuId,Model model){
// 重定向到成功页面。再次查询购物车数据即可
CartItem cartItem = cartService.getCartItem(skuId);
model.addAttribute("item",cartItem);
return "success";
}
}
package com.atlinxi.gulimall.cart.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atlinxi.common.utils.R;
import com.atlinxi.gulimall.cart.feign.ProductFeignService;
import com.atlinxi.gulimall.cart.interceptor.CartInterceptor;
import com.atlinxi.gulimall.cart.service.CartService;
import com.atlinxi.gulimall.cart.vo.Cart;
import com.atlinxi.gulimall.cart.vo.CartItem;
import com.atlinxi.gulimall.cart.vo.SkuInfoVo;
import com.atlinxi.gulimall.cart.vo.UserInfoTo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;
@Slf4j
@Service
public class CartServiceImpl implements CartService {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Autowired
ProductFeignService productFeignService;
@Autowired
ThreadPoolExecutor threadPoolExecutor;
private final String CART_PREFIX = "gulimall:cart:";
@Override
public CartItem addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
String res = (String) cartOps.get(skuId.toString());
if (StringUtils.isEmpty(res)){
// cartItem 不能抽取到上面,异步任务可能不能让对象改变
// 具体也不是很明白
CartItem cartItem = new CartItem();
// 购物车无此商品
// 2. 新商品添加到购物车
CompletableFuture<Void> getSkuInfoTask = CompletableFuture.runAsync(() -> {
// 1. 远程查询当前要添加的商品的信息
R skuInfo = productFeignService.getSkuInfo(skuId);
SkuInfoVo data = skuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {
});
cartItem.setCheck(true);
cartItem.setCount(num);
cartItem.setImage(data.getSkuDefaultImg());
cartItem.setTitle(data.getSkuTitle());
cartItem.setPrice(data.getPrice());
cartItem.setSkuId(data.getSkuId());
},threadPoolExecutor);
// 3. 远程查询sku的组合信息
CompletableFuture<Void> getSukSaleAttrValues = CompletableFuture.runAsync(() -> {
List<String> values = productFeignService.getSkuSaleAttrValues(skuId);
cartItem.setSkuAttr(values);
}, threadPoolExecutor);
CompletableFuture.allOf(getSkuInfoTask,getSukSaleAttrValues).get();
String s = JSON.toJSONString(cartItem);
cartOps.put(skuId.toString(),s);
return cartItem;
}else {
CartItem cartItem = new CartItem();
// 购物车有此商品,修改数量即可
cartItem = JSON.parseObject(res, CartItem.class);
cartItem.setCount(cartItem.getCount()+num);
cartOps.put(skuId.toString(),JSON.toJSONString(cartItem));
return cartItem;
}
}
@Override
public CartItem getCartItem(Long skuId) {
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
String str = (String) cartOps.get(skuId.toString());
CartItem cartItem = JSON.parseObject(str, CartItem.class);
return cartItem;
}
@Override
public Cart getCart() throws ExecutionException, InterruptedException {
Cart cart = new Cart();
UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
if (userInfoTo.getUserId()!=null){
// 1. 登录
String cartKey = CART_PREFIX + userInfoTo.getUserId();
String tempCartKey = CART_PREFIX + userInfoTo.getUserKey();
// 2. 如果临时购物车的数据还没有进行合并
List<CartItem> tempCartItems = getCartItems(tempCartKey);
if (tempCartItems!=null){
// 临时购物车有数据,需要合并
for (CartItem item : tempCartItems) {
addToCart(item.getSkuId(), item.getCount());
}
// 清除临时购物车的数据
clearCart(tempCartKey);
}
// 3. 获取登录后的购物车数据【包含合并过来的临时购物车的数据,和登陆后的购物车的数据】
List<CartItem> cartItems = getCartItems(cartKey);
cart.setItems(cartItems);
}else {
// 2. 没登录
String cartKey = CART_PREFIX + userInfoTo.getUserKey();
// 获取临时购物车的所有购物项
List<CartItem> cartItems = getCartItems(cartKey);
cart.setItems(cartItems);
}
return cart;
}
/**
*
* 获取到我们要操作的购物车(临时/已登录)
*
* @return
*/
private BoundHashOperations<String, Object, Object> getCartOps() {
UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
//
String cartKey = "";
if (userInfoTo.getUserId()!=null){
// gulimall:cart:1
cartKey = CART_PREFIX + userInfoTo.getUserId();
}else {
cartKey = CART_PREFIX + userInfoTo.getUserKey();
}
// boundHashOps,这种方式的话,以下的操作都是围绕这个key的
BoundHashOperations<String, Object, Object> operations = stringRedisTemplate.boundHashOps(cartKey);
return operations;
}
/**
* 获取购物车中所有的购物项
* @param cartKey
* @return
*/
private List<CartItem> getCartItems(String cartKey){
BoundHashOperations<String, Object, Object> hashOps = stringRedisTemplate.boundHashOps(cartKey);
// 获取所有值,key是skuId,val是CartItem
List<Object> values = hashOps.values();
if (values!=null && values.size() > 0){
List<CartItem> collect = values.stream().map((obj) -> {
String str = (String) obj;
return JSON.parseObject(str, CartItem.class);
}).collect(Collectors.toList());
return collect;
}
return null;
}
@Override
public void clearCart(String cartKey){
stringRedisTemplate.delete(cartKey);
}
@Override
public void checkItem(Long skuId, Integer check) {
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
CartItem cartItem = getCartItem(skuId);
cartItem.setCheck(check==1?true:false);
String s = JSON.toJSONString(cartItem);
cartOps.put(skuId.toString(),s);
}
@Override
public void changeItemCount(Long skuId, Integer num) {
CartItem cartItem = getCartItem(skuId);
cartItem.setCount(num);
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
cartOps.put(skuId.toString(),JSON.toJSONString(cartItem));
}
@Override
public void deleteItem(Long skuId) {
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
cartOps.delete(skuId.toString());
}
}
package com.atlinxi.gulimall.cart.vo;
import java.math.BigDecimal;
import java.util.List;
/**
* 整个购物车
*
* 需要计算的属性,必须重写它的get方法,保证每次获取属性都会进行计算
*/
public class Cart {
List<CartItem> items;
private Integer countNum; // 商品数量
private Integer countType; // 商品类型数量
private BigDecimal totalAmount; // 商品总价
private BigDecimal reduce = new BigDecimal("0"); // 减免价格
public List<CartItem> getItems() {
return items;
}
public void setItems(List<CartItem> items) {
this.items = items;
}
public Integer getCountNum() {
int count = 0;
if (items!=null && items.size() > 0){
for (CartItem item : items) {
count+= item.getCount();
}
}
return count;
}
public void setCountNum(Integer countNum) {
this.countNum = countNum;
}
public Integer getCountType() {
int count = 0;
if (items!=null && items.size() > 0){
for (CartItem item : items) {
count+= 1;
}
}
return count;
}
public void setCountType(Integer countType) {
this.countType = countType;
}
public BigDecimal getTotalAmount() {
BigDecimal amount = new BigDecimal("0");
if (items!=null && items.size() > 0){
for (CartItem item : items) {
BigDecimal totalPrice = item.getTotalPrice();
amount = amount.add(totalPrice);
}
}
// 2. 减去优惠总价
BigDecimal subtract = amount.subtract(getReduce());
return subtract;
}
public void setTotalAmount(BigDecimal totalAmount) {
this.totalAmount = totalAmount;
}
public BigDecimal getReduce() {
return reduce;
}
public void setReduce(BigDecimal reduce) {
this.reduce = reduce;
}
}
package com.atlinxi.gulimall.cart.vo;
import java.math.BigDecimal;
import java.util.List;
/**
* 购物项内容
*/
public class CartItem {
private Long skuId;
// 是否被选中
private Boolean check = true;
private String title;
private String image;
// 内存 cpu
private List<String> skuAttr;
private BigDecimal price;
private Integer count;
private BigDecimal totalPrice;
/**
* 计算当前总价
* @return
*/
public BigDecimal getTotalPrice() {
// multiply 乘以
return this.price.multiply(new BigDecimal(this.count));
}
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<String> getSkuAttr() {
return skuAttr;
}
public void setSkuAttr(List<String> skuAttr) {
this.skuAttr = skuAttr;
}
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 void setTotalPrice(BigDecimal totalPrice) {
this.totalPrice = totalPrice;
}
}
package com.atlinxi.gulimall.cart.vo;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class SkuInfoVo {
/**
* skuId
*/
@TableId
private Long skuId;
/**
* spuId
*/
private Long spuId;
/**
* sku名称
*/
private String skuName;
/**
* sku介绍描述
*/
private String skuDesc;
/**
* 所属分类id
*/
private Long catalogId;
/**
* 品牌id
*/
private Long brandId;
/**
* 默认图片
*/
private String skuDefaultImg;
/**
* 标题
*/
private String skuTitle;
/**
* 副标题
*/
private String skuSubtitle;
/**
* 价格
*/
private BigDecimal price;
/**
* 销量
*/
private Long saleCount;
}
package com.atlinxi.gulimall.cart.vo;
import lombok.Data;
@Data
public class UserInfoTo {
private Long userId;
// 临时登录时存放用户的唯一标识,获取临时登录时的购物车数据
private String userKey;
private boolean tempUser = false;
}
package com.atlinxi.common.constant;
public class CartConstant {
public static final String TEMP_USER_COOKIE_NAME = "user-key";
public static final int TEMP_USER_COOKIE_TIMEOUT = 60 * 60 * 24 * 30;
}
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.5version>
<relativePath/>
parent>
<groupId>com.atlinxi.gulimallgroupId>
<artifactId>gulimall-cartartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>gulimall-cartname>
<description>购物车description>
<properties>
<java.version>1.8java.version>
<spring-cloud.version>2020.0.4spring-cloud.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>com.atlinxi.gulimallgroupId>
<artifactId>gulimall-commonartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-loadbalancerartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.springframework.sessiongroupId>
<artifactId>spring-session-data-redisartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
只有在淋浴间,哭声才不会走出去,说闲话。
房思琪的初恋乐园
林奕含