畅购第10天项目总结(购物车与用户身份识别)
发布日期:2021-06-29 18:10:49 浏览次数:2 分类:技术文章

本文共 18959 字,大约阅读时间需要 63 分钟。

4. 购物车

购物车分为用户登录购物车和未登录购物车操作,国内知名电商京东用户登录和不登录都可以操作购物车,如果用户不登录,操作购物车可以将数据存储到Cookie或者WebSQL或者SessionStorage中,用户登录后购物车数据可以存储到Redis中,再将之前未登录加入的购物车合并到Redis中即可。

淘宝天猫则采用了另外一种实现方案,用户要想将商品加入购物车,必须先登录才能操作购物车。

我们今天实现的购物车是天猫解决方案,即用户必须先登录才能使用购物车功能。

4.1 购物车分析

1)需求分析

用户在商品详细页点击加入购物车,提交商品SKU编号和购买数量,添加到购物车。购物车展示页面如下:

在这里插入图片描述

2)购物车实现思路

在这里插入图片描述

我们实现的是用户登录后的购物车,用户将商品加入购物车的时候,直接将要加入购物车的详情存入到Redis即可。每次查看购物车的时候直接从Redis中获取。

3)表结构分析

用户登录后将商品加入购物车,需要存储商品详情以及购买数量,购物车详情表如下:

changgou_order数据中tb_order_item表:

CREATE TABLE `tb_order_item` (  `id` varchar(20) COLLATE utf8_bin NOT NULL COMMENT 'ID',  `category_id1` int(11) DEFAULT NULL COMMENT '1级分类',  `category_id2` int(11) DEFAULT NULL COMMENT '2级分类',  `category_id3` int(11) DEFAULT NULL COMMENT '3级分类',  `spu_id` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT 'SPU_ID',  `sku_id` bigint(20) NOT NULL COMMENT 'SKU_ID',  `order_id` bigint(20) NOT NULL COMMENT '订单ID',  `name` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '商品名称',  `price` int(20) DEFAULT NULL COMMENT '单价',  `num` int(10) DEFAULT NULL COMMENT '数量',  `money` int(20) DEFAULT NULL COMMENT '总金额',  `pay_money` int(11) DEFAULT NULL COMMENT '实付金额',  `image` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '图片地址',  `weight` int(11) DEFAULT NULL COMMENT '重量',  `post_fee` int(11) DEFAULT NULL COMMENT '运费',  `is_return` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '是否退货',  PRIMARY KEY (`id`),  KEY `item_id` (`sku_id`),  KEY `order_id` (`order_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

购物车详情表其实就是订单详情表结构,只是目前临时存储数据到Redis,等用户下单后才将数据从Redis取出存入到MySQL中。

在商城中一般都会有不同分类商品做折扣活动,下面有一张表记录了对应分类商品的折扣活动,每类商品只允许参与一次折扣活动。

changgou_goods数据中tb_pref表:

CREATE TABLE `tb_pref` (  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',  `cate_id` int(11) DEFAULT NULL COMMENT '分类ID',  `buy_money` int(11) DEFAULT NULL COMMENT '消费金额',  `pre_money` int(11) DEFAULT NULL COMMENT '优惠金额',  `start_time` date DEFAULT NULL COMMENT '活动开始日期',  `end_time` date DEFAULT NULL COMMENT '活动截至日期',  `type` char(1) DEFAULT NULL COMMENT '类型,1:普通订单,2:限时活动',  `state` char(1) DEFAULT NULL COMMENT '状态,1:有效,0:无效',  PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

4.2 订单购物车微服务

1)导入资源

搭建订单购物车微服务,工程名字changgou-service-order并搭建对应的api工程changgou-service-order-api,将生成好的mapper和相关文件拷贝到工程中,以及生成好的Pojo拷贝到API工程中。同时在changgou-service-order中引入changgou-service-order-api 依赖:

com.changgou
changgou-service-order-api
1.0-SNAPSHOT

changgou-service-order:

在这里插入图片描述

changgou-service-order-api:

在这里插入图片描述

2)application.yml配置

在changgou-service-order的resources中添加application.yml配置文件,代码如下:

server:  port: 8090spring:  application:    name: order  datasource:    driver-class-name: com.mysql.jdbc.Driver    url: jdbc:mysql://127.0.0.1:3306/changgou_order?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC    username: root    password: root  redis:    # Redis数据库索引(默认为0)    database: 0    # Redis服务器地址    host: 8.xxx.xx.136    # Redis服务器连接端口    port: 6379    # Redis服务器连接密码(默认为空)    password: csp1xxxxxx9  main:    allow-bean-definition-overriding: trueeureka:  client:    service-url:      defaultZone: http://127.0.0.1:7001/eureka  instance:    prefer-ip-address: truefeign:  hystrix:    enabled: true

3)创建启动类

在changgou-service-order的resources中创建启动类,代码如下:

/** * @Auther: csp1999 * @Date: 2021/01/28/12:39 * @Description: 订单购物车微服务启动类 */@SpringBootApplication@EnableEurekaClient@MapperScan(basePackages = {
"com.changgou.order.mapper"})public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class,args); }}

4.3 添加购物车

4.3.1 思路分析

在这里插入图片描述

用户添加购物车,只需要将要加入购物车的商品存入到Redis中即可。一个用户可以将多件商品加入购物车,存储到Redis中的数据可以采用Hash类型。

选Hash类型可以将用户的用户名作为namespace的一部分,将指定商品加入购物车,则往对应的namespace中增加一个key和value,key是商品ID,value是加入购物车的商品详情,如下图:

在这里插入图片描述

4.3.2 代码实现

1)feign创建

下订单需要调用feign查看商品信息,我们先创建feign分别根据ID查询Sku和Spu信息,在changgou-service-goods-api工程中的SkuFeign和SpuFeign根据ID查询方法如下:

com.changgou.goods.feign.SkuFeign

/*** * 根据ID查询SKU信息 * @param id : sku的ID */@GetMapping(value = "/{id}")public Result
findById(@PathVariable(value = "id", required = true) Long id);

com.changgou.goods.feign.SpuFeign

/*** * 根据SpuID查询Spu信息 * @param id * @return */@GetMapping("/{id}")public Result
findById(@PathVariable(name = "id") Long id);
2)业务层

业务层接口

在changgou-service-order微服务中创建com.changgou.order.service.CartService接口,代码如下:

public interface CartService {
/*** * 添加购物车 * @param num:购买商品数量 * @param id:购买ID * @param username:购买用户 * @return */ void add(Integer num, Long id, String username);}

业务层接口实现类

在changgou-service-order微服务中创建接口实现类com.changgou.order.service.impl.CartServiceImpl,代码如下:

@Servicepublic class CartServiceImpl implements CartService {
@Autowired private RedisTemplate redisTemplate; @Autowired private SkuFeign skuFeign; @Autowired private SpuFeign spuFeign; /*** * 加入购物车 * @param num:购买商品数量 * @param id:购买ID * @param username:购买用户 * @return */ @Override public void add(Integer num, Long id, String username) {
//查询SKU Result
resultSku = skuFeign.findById(id); if(resultSku!=null && resultSku.isFlag()){
//获取SKU Sku sku = resultSku.getData(); //获取SPU Result
resultSpu = spuFeign.findById(sku.getSpuId()); //将SKU转换成OrderItem OrderItem orderItem = sku2OrderItem(sku,resultSpu.getData(), num); /****** * 购物车数据存入到Redis * namespace = Cart_[username] * key=id(sku) * value=OrderItem */ redisTemplate.boundHashOps("Cart_"+username).put(id,orderItem); } } /*** * SKU转成OrderItem * @param sku * @param num * @return */ private OrderItem sku2OrderItem(Sku sku,Spu spu,Integer num){
OrderItem orderItem = new OrderItem(); orderItem.setSpuId(sku.getSpuId()); orderItem.setSkuId(sku.getId()); orderItem.setName(sku.getName()); orderItem.setPrice(sku.getPrice()); orderItem.setNum(num); orderItem.setMoney(num*orderItem.getPrice()); //单价*数量 orderItem.setPayMoney(num*orderItem.getPrice()); //实付金额 orderItem.setImage(sku.getImage()); orderItem.setWeight(sku.getWeight()*num); //重量=单个重量*数量 //分类ID设置 orderItem.setCategoryId1(spu.getCategory1Id()); orderItem.setCategoryId2(spu.getCategory2Id()); orderItem.setCategoryId3(spu.getCategory3Id()); return orderItem; }}
3)控制层

在changgou-service-order微服务中创建com.changgou.order.controller.CartController,代码如下:

@RestController@CrossOrigin@RequestMapping(value = "/cart")public class CartController {
@Autowired private CartService cartService; /*** * 加入购物车 * @param num:购买的数量 * @param id:购买的商品(SKU)ID * @return */ @RequestMapping(value = "/add") public Result add(Integer num, Long id){
//用户名 String username="szitheima"; //将商品加入购物车 cartService.add(num,id,username); return new Result(true, StatusCode.OK,"加入购物车成功!"); }}
4)feign配置

修改com.changgou.OrderApplication开启Feign客户端:

@SpringBootApplication@EnableEurekaClient@EnableFeignClients(basePackages = {
"com.changgou.goods.feign"})@MapperScan(basePackages = {
"com.changgou.order.dao"})public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class,args); }}
测试添加购物车

访问

在这里插入图片描述

查看redis中的数据:

在这里插入图片描述

4.4 购物车列表

4.4.1 思路分析

在这里插入图片描述

接着我们实现一次购物车列表操作。因为存的时候是根据用户名往Redis中存储用户的购物车数据的,所以我们这里可以将用户的名字作为key去Redis中查询对应的数据。

4.4.2 代码实现

1)业务层

业务层接口:

修改changgou-service-order微服务的com.changgou.order.service.CartService接口,添加购物车列表方法,代码如下:

/*** * 查询用户的购物车数据 * @param username * @return */List
list(String username);

业务层接口实现类:

修改changgou-service-order微服务的com.changgou.order.service.impl.CartServiceImpl类,添加购物车列表实现方法,代码如下:

/*** * 查询用户购物车数据 * @param username * @return */@Overridepublic List
list(String username) {
//查询所有购物车数据 List
orderItems = redisTemplate.boundHashOps("Cart_"+username).values(); return orderItems;}
2)控制层

修改changgou-service-order微服务的com.changgou.order.controller.CartController类,添加购物车列表查询方法,代码如下:

/*** * 查询用户购物车列表 * @return */@GetMapping(value = "/list")public Result list(){
// 用户名 String username="szitheima"; List
orderItems = cartService.list(username); return new Result(true,StatusCode.OK,"购物车列表查询成功!",orderItems);}
3)测试

Postman访问 ,效果如下:

在这里插入图片描述

4.4.3 问题处理

1)删除商品购物车

在这里插入图片描述

我们发现个问题,就是用户将商品加入购物车,无论数量是正负,都会执行添加购物车,如果数量如果<=0,应该移除该商品的。

如果需要删除购物车中的某个商品,或者减少某个商品的数量,只需要将参数num传递为负数即可

修改changgou-service-order的com.changgou.order.service.impl.CartServiceImpl的add方法,添加如下代码:

/** * 添加购物车 * * @param id       sku的ID * @param num      购买的数量 * @param username 购买的商品的用户名 */@Overridepublic void add(Long id, Integer num, String username) {
// 如果商品数量小于1删除商品 if (num <= 0) {
// 删除掉原来的商品 redisTemplate.boundHashOps("Cart_" + username).delete(id); return; } ... }}
2)数据精度丢失问题

SkuId是Long类型,在页面输出的时候会存在精度丢失问题,我们只需要在OrderItem的SkuId上加上字符串序列化类型就可以了,代码如下:

/** * SKU_ID */// 数字会存在精度丢失问题,这里用字符串替代数字!@JsonSerialize(using = ToStringSerializer.class)@Column(name = "sku_id")private Long skuId;

5. 购物车用户身份识别

5.1 购物车需求分析

在这里插入图片描述

购物车功能已经做完了,但用户我们都是硬编码写死的。用户要想将商品加入购物车,必须得先登录授权,登录授权后再经过微服务网关,微服务网关需要过滤判断用户请求是否存在令牌,如果存在令牌,才能再次访问微服务,此时网关会通过过滤器将令牌数据再次存入到头文件中,然后访问模板渲染服务,模板渲染服务再调用订单购物车微服务,此时也需要将令牌数据存入到头文件中,将令牌数据传递给购物车订单微服务,到了购物车订单微服务的时候,此时微服务需要校验令牌数据,如果令牌正确,才能使用购物车功能,并解析令牌数据获取用户信息。

5.2 微服务之间认证

在这里插入图片描述

如上图:因为微服务之间并没有传递头文件,所以我们可以定义一个拦截器,每次微服务调用之前都先检查下头文件,将请求的头文件中的令牌数据再放入到header中,再调用其他微服务即可。

1)common工程创建Feign拦截器

工具类抽取

  • 微服务之间相互认证的情况非常多,我们可以把Feign拦截器抽取出去,放到changgou-common的interceptor包中,其他工程需要用,直接在主启动类创建一个@Bean对象即可。

在changgou-common服务中创建一个com.changgou.common.interceptor.FeignInterceptor拦截器,并将所有头文件数据再次加入到Feign请求的微服务头文件中,代码如下:

/** * @Auther: csp1999 * @Date: 2021/01/28/16:24 * @Description: Feign拦截器:在微服务调用微服务时候,执行feign中方法之前进行拦截,将令牌传入Header */public class FeignInterceptor implements RequestInterceptor {
/** * Feign执行之前进行拦截: * 获取用户令牌 * 将令牌封装到header中 * * @param requestTemplate */ @Override public void apply(RequestTemplate requestTemplate) {
// 使用RequestContextHolder工具获取request对象(包含请求头header和请求参数等) // 用户当前请求时对应线程数据,如果开启熔断,默认是线程池隔离,会开启新的线程,导致获取不到requestAttributes(为空) // 解决方法:需要将熔断策略换成信号量隔离,此时不会开启新的线程 ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (requestAttributes != null) {
// 1.取出request请求对象 HttpServletRequest request = requestAttributes.getRequest(); // 2.获取所有header头信息中的key Enumeration
headerNames = request.getHeaderNames(); if (headerNames != null) {
while (headerNames.hasMoreElements()) {
// 3.header头的key(网关传递过来的) String name = headerNames.nextElement();// 头的名称(key) String value = request.getHeader(name);// 头名称对应的值(value) //System.out.println("name:" + name + "::::::::value:" + value); // 4.将头信息传递给fegin (restTemplate) requestTemplate.header(name, value); } } } }}

2)Order微服务启动类注入Feign拦截器

在changgou-web-order服务中启动类里创建对象实例

/** * @Auther: csp1999 * @Date: 2021/01/28/12:39 * @Description: 订单购物车微服务启动类 */@SpringBootApplication@EnableEurekaClient@MapperScan(basePackages = {
"com.changgou.order.mapper"})@EnableFeignClients(basePackages = {
"com.changgou.goods.feign"})public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class,args); } /** * 将Feign拦截器注入容器中 * @return */ @Bean public FeignInterceptor feignInterceptor(){
return new FeignInterceptor(); }}

3)debug测试

在这里插入图片描述

我们发现这块的ServletRequestAttributes始终为空,RequestContextHolder.getRequestAttributes()该方法是从ThreadLocal变量里面取得相应信息的,当hystrix断路器的隔离策略为THREAD(线程隔离)时,是无法取得ThreadLocal中的值因为当用户发送请求,获取对应线程数据时,如果开启熔断,默认是线程池隔离,会开启新的线程,导致获取不到原线程中的数据(为空)。

解决方案:hystrix隔离策略换为SEMAPHORE(信号量隔离)

修改changgou-web-order的application.yml配置文件,在application.yml中添加如下代码,代码如下:

# 开启Feign的熔断:默认是线程池隔离,会开启新的线程feign:  hystrix:    enabled: true# hystrix 配置(不加下面的配置,则熔断使用默认的线程池隔离)hystrix:  command:    default:      execution:        isolation:          thread:            timeoutInMilliseconds: 10000          # 信号量隔离          strategy: SEMAPHORE

再次测试,效果如下:

在这里插入图片描述

5.3 网关过滤

为了不给微服务带来一些无效的请求,我们可以在网关中过滤用户请求,先看看头文件中是否有Authorization,如果有再看看cookie中是否有Authorization,如果都通过了才允许请求到达微服务。

5.4 订单对接网关+oauth

1)application.yml配置

修改微服务网关changgou-gateway-web的application.yml配置文件,添加order的路由过滤配置,配置如下:

# 对接购物车订单order微服务路由相关配置- id: changgou_order_route  # lb:使用LoadBalancerClient 实现负载均衡,后面的changgou-order是微服务名称  uri: lb://changgou-order  # 路由断言,路由规则配置  predicates:    - Path=/api/cart/**,/api/categoryReport/**,/api/orderConfig/**,/api/order/**,/api/orderItem/**,/api/orderLog/**,/api/preferential/**,/api/returnCause/**,/api/returnOrder/**,/api/returnOrderItem/**  filters:    # user微服务真实请求中是没有/api的,所以这里StripPrefix=1    - StripPrefix=1

这里注意使用的是yml格式,所以上面代码中的空格也一并记得拷贝到application.yml文件中。

2)路由过滤配置

在微服务网关changgou-gateway-web中添加com.changgou.filter.URLFilter过滤类,用于过滤需要用户登录的地址,代码如下:

/** * @Auther: csp1999 * @Date: 2021/01/29/12:27 * @Description: 不需要认证就可以访问的路径校验 */public class URLFilter {
/** * 要放行的路径:登录和注册不需要拦截 */ private static final String noAuthorizeurls = "/api/user/add,/api/user/login"; /** * 判断当前的请求的地址中是否存在于已有的不拦截地址中, * 返回true表示:不拦截 * 返回false表示:需要拦截 * * @param uri 获取到的当前的请求的地址 * @return */ public static boolean hasAuthorize(String uri) {
String[] split = noAuthorizeurls.split(","); for (String s : split) {
if (s.equals(uri)) {
return true; } } return false; }}

3)全局过滤器修改

修改之前的com.changgou.filter.AuthorizeFilter的过滤方法,将是否需要用户登录过滤也加入其中,代码如下:

// 未登录状态下只放行登录login和搜索searchif (!URLFilter.hasAuthorize(path)){
// 直接放行 return chain.filter(exchange);}

4)集成OAuth进行安全校验

修改changgou-service-order的pom.xml,添加oauth的依赖:

org.springframework.cloud
spring-cloud-starter-oauth2

将公钥拷贝到changgou-service-order工程的resources中

在这里插入图片描述

在changgou-service-order工程中创建com.changgou.order.config.ResourceServerConfig,配置需要拦截的路径,这里需要拦截所有请求路径,代码如下:

/** * @Auther: csp1999 * @Date: 2021/01/28/12:39 * @Description:  资源(微服务)授权(令牌校验)相关配置 */@Configuration// 开启 资源服务器(标识他是一个oauth2中的资源服务器)@EnableResourceServer@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)//激活方法上的PreAuthorize注解public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//公钥 private static final String PUBLIC_KEY = "public.key"; /*** * 定义JwtTokenStore * @param jwtAccessTokenConverter * @return */ @Bean public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter); } /*** * 定义JJwtAccessTokenConverter 用来校验令牌 * @return */ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setVerifierKey(getPubKey()); return converter; } /** * 获取非对称加密公钥 Key * * @return 公钥 Key */ private String getPubKey() {
Resource resource = new ClassPathResource(PUBLIC_KEY); try {
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream()); BufferedReader br = new BufferedReader(inputStreamReader); return br.lines().collect(Collectors.joining("\n")); } catch (IOException ioe) {
return null; } } /*** * Http安全配置,对每个到达系统的http请求链接进行校验 * @param http * @throws Exception */ @Override public void configure(HttpSecurity http) throws Exception {
// 所有请求必须通过认证 http.authorizeRequests() .anyRequest() .authenticated(); //其他地址需要认证授权 }}

5)测试

使用浏览器访问 http://localhost:8001/api/cart/list,效果如下:

未登录:

在这里插入图片描述

使用Postman访问 http://localhost:8001/api/cart/list,效果如下:

已登录:

在这里插入图片描述

5.5 获取用户数据

5.5.1 数据分析

用户登录后,数据会封装到SecurityContextHolder.getContext().getAuthentication()里面,我们可以将数据从这里面取出,然后转换成OAuth2AuthenticationDetails,在这里面可以获取到令牌信息、令牌类型等,代码如下:

在这里插入图片描述

这里的tokenValue是加密之后的令牌数据,remoteAddress是用户的IP信息,tokenType是令牌类型。

我们可以获取令牌加密数据后,使用公钥对它进行解密,如果能解密说明说句无误,如果不能解密用户也没法执行到这一步。解密后可以从明文中获取用户信息。

5.5.2 代码实现

1)读取公钥,校验,解析,读取令牌数据

在changgou-service-order微服务中创建com.changgou.order.config.TokenDecode类,用于解密令牌信息,在类中读取公钥信息,代码如下:

/** * @Auther: csp1999 * @Date: 2021/01/28/12:39 * @Description: token解析 */@Componentpublic class TokenDecode {
private static final String PUBLIC_KEY = "public.key"; /*** * 获取用户信息 * @return */ public String getToken() {
OAuth2AuthenticationDetails authentication = (OAuth2AuthenticationDetails) SecurityContextHolder .getContext() .getAuthentication() .getDetails(); String tokenValue = authentication.getTokenValue(); return tokenValue; } /** * 获取当前登录用户的用户信息(读取令牌数据) * * @return */ public Map
getUserInfo() {
// 1.获取令牌 String token = getToken(); // 2.解析令牌 公钥 String pubKey = getPubKey(); Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(pubKey)); String claims = jwt.getClaims();//{} System.out.println(claims); // 3.返回 Map
map = JSON.parseObject(claims, Map.class); return map; } /** * 获取非对称加密公钥 Key * @return */ private String getPubKey() {
Resource resource = new ClassPathResource(PUBLIC_KEY); try {
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream()); BufferedReader br = new BufferedReader(inputStreamReader); return br.lines().collect(Collectors.joining("\n")); } catch (IOException ioe) {
return null; } }}
4)控制层获取用户数据

在CartController中注入TokenDecode,并调用TokenDecode的getUserInfo方法获取用户信息,代码如下:

注入TokenDecode:

@Autowiredprivate TokenDecode tokenDecode;

获取用户名:

在这里插入图片描述

5)测试

用户登录后测试http://localhost:8001/api/wcart/list

断点调试可以获取到用户名信息:

在这里插入图片描述

请求结果:

在这里插入图片描述

5.6 代码抽取

以后很有可能在很多微服务中都会用到该对象来获取用户信息,我们可以把它抽取出去。

在changgou-common工程中引入鉴权包

org.springframework.cloud
spring-cloud-starter-oauth2
provided
io.jsonwebtoken
jjwt
0.9.0
provided

将TokenDecode拷贝到entity包中,并且删除changgou-service-order中的TokenDecode,同时在changgou-service-order启动类中创建该对象,并交给SpringIOC容器管理。

在这里插入图片描述

转载地址:https://csp1999.blog.csdn.net/article/details/113383092 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:狂神JUC笔记(上)
下一篇:畅购第10天项目总结(资源服务器授权以及OAuth2对接微服务)

发表评论

最新留言

不错!
[***.144.177.141]2024年05月01日 12时06分09秒