Grain Mall Twenty Order Service

Rabbitmq related knowledge

// 静态页面的引入,静态资源上传nginx等

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
192.168.56.10	order.gulimall.com



- id: gulimall_order_route
  uri: lb://gulimall-order
  predicates:
  	- Host=order.gulimall.com

It is necessary to introduce springSession, asynchronous thread pool, and display session login information in the imported page. These have been configured before, so I won’t go into details.

Basic concept of order

The e-commerce system involves three streams, which are information flow (commodity information, preferential information and other data queries), capital flow (payment, refund, etc.), logistics, and the order system as the center organically integrates the three.

The order module is the hub of the e-commerce system. In the link of order, it is necessary to obtain data and information of multiple modules. At the same time, the information is processed and then flows to the next link. This series constitutes the information flow of the order.

insert image description here

Order Status

  1. Pending payment
    Lock the inventory and configure the payment timeout period. After the timeout, the order will be automatically canceled, the order will be changed to closed status, and the inventory will be unlocked.
  2. Paid/to be shipped
    The order system records the payment time and payment order number; the warehouse system distributes goods, out of the warehouse, etc.
  3. To be received/delivered
    Order system to synchronize logistics information
  4. Completed
    The user confirms receipt, and the order transaction is completed.
  5. Canceled
    Order canceled before payment. Including overtime payment or cancellation of orders by users and merchants.
  6. After sale

Order Process

Order Generation –> Payment Order –> Seller Ships –> Confirm Receipt –> Transaction Successful
insert image description here

Order creation and payment

  1. Before creating an order, you need to preview the order, select the delivery information, etc.
  2. The order creation needs to lock the inventory, the inventory can only be created, otherwise it cannot be created
  3. After the order is created, the overtime payment needs to be unlocked
  4. After the payment is successful, it is necessary to disassemble the order, and dismantle the order according to the packaging method, warehouse, logistics, etc.
  5. Every transaction of payment needs to be recorded for auditing
  6. Order creation, payment success and other states need to send messages to MQ to facilitate other systems to perceive and subscribe

Order service login interception

package com.atlinxi.gulimall.order.interceptor;

import com.atlinxi.common.constant.AuthServerConstant;
import com.atlinxi.common.vo.MemberRespVo;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class LoginUserInterceptor implements HandlerInterceptor {
    
    


    public static ThreadLocal<MemberRespVo> loginUser = new ThreadLocal<>();


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    

        //

        //

        /**
         * 在库存服务远程调用订单服务时,请求是不携带session的,进入的时候会因为没有获取到登录信息而被拦截
         * 而我们在库存服务的时候已经判断了是否登录,所以这里是不需要判断登录的,放行就是
         *
         * uri 就是类似于上面那个路径,url是完整的请求地址
         *
         * order/order/status/{orderSn}  orderSn是动态的
         *
         *
         */
        boolean match = new AntPathMatcher().match("/order/order/status/**", request.getRequestURI());

        if (match){
    
    
            return true;
        }


        MemberRespVo attribute = (MemberRespVo) request.getSession().getAttribute(AuthServerConstant.LOGIN_USER);

        if (attribute!=null){
    
    

            loginUser.set(attribute);

            return true;
        }else {
    
    
            // 没登录就去登录
            request.getSession().setAttribute("msg","请先进行登录");
            response.sendRedirect("http://auth.gulimall.com/login.html");
            return  false;
        }

    }
}






package com.atlinxi.gulimall.order.config;

import com.atlinxi.gulimall.order.interceptor.LoginUserInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
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 OrderWebConfiguration implements WebMvcConfigurer {
    
    


    @Autowired
    LoginUserInterceptor loginUserInterceptor;


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        registry.addInterceptor(loginUserInterceptor).addPathPatterns("/**");
    }
}


vo

// 订单确认页
package com.atlinxi.gulimall.order.vo;

import lombok.Data;
import lombok.Getter;
import lombok.Setter;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;

// 订单确认页需要用的数据
public class OrderConfirmVo {
    
    

    // 收货地址,ums_member_receive_address 表
    @Getter @Setter
    private List<MemberAddressVo> addresses;

    // 购物清单,根据购物车页面传递过来的 skuIds 查询
    // 所有选中的购物项
    @Getter @Setter
    private List<OrderItemVo> orderItems;

    // 可用积分,ums_member 表中的 integration 字段
    @Getter @Setter
    private Integer bounds;

    // 订单令牌,防止重复提交
    @Getter @Setter
    private String orderToken;

    // 积分
    @Getter @Setter
    private Integer integration;

    @Getter @Setter
    Map<Long,Boolean> stocks;



    // 有get方法就相当于有count属性
    public Integer getCount(){
    
    
        Integer i = 0;
        if (orderItems!=null){
    
    
            for (OrderItemVo item: orderItems){
    
    
                i+= item.getCount();
            }
        }

        return i;
    }

    // 订单总额
//    BigDecimal total;

    // 应付价格
//    BigDecimal payPrice;

    // 订单总额
    public BigDecimal getTotal() {
    
    

        BigDecimal sum = new BigDecimal("0");

        if (orderItems!=null){
    
    
            for (OrderItemVo item: orderItems){
    
    
                BigDecimal multiply = item.getPrice().multiply(new BigDecimal(item.getCount() + ""));
                sum = sum.add(multiply);
            }
        }

        return sum;
    }


    // 应付价格
    // 暂时没有优惠信息
    public BigDecimal getPayPrice() {
    
    
        return getTotal();
    }


    // 发票记录。。。
    //优惠券信息。。。
}







package com.atlinxi.gulimall.order.vo;

import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;


// 会员的收货地址列表
@Data
public class MemberAddressVo {
    
    

    /**
     * id
     */
    @TableId
    private Long id;
    /**
     * member_id
     */
    private Long memberId;
    /**
     * 收货人姓名
     */
    private String name;
    /**
     * 电话
     */
    private String phone;
    /**
     * 邮政编码
     */
    private String postCode;
    /**
     * 省份/直辖市
     */
    private String province;
    /**
     * 城市
     */
    private String city;
    /**
     * 区
     */
    private String region;
    /**
     * 详细地址(街道)
     */
    private String detailAddress;
    /**
     * 省市区代码
     */
    private String areacode;
    /**
     * 是否默认
     */
    private Integer defaultStatus;
}










package com.atlinxi.gulimall.order.vo;


import lombok.Data;

import java.math.BigDecimal;
import java.util.List;

/**
 * 购物项内容
 */
@Data
public class OrderItemVo {
    
    

    private Long skuId;


    private String title;
    private String image;
    // 内存 cpu
    private List<String> skuAttr;
    private BigDecimal price;
    private Integer count;
    private BigDecimal totalPrice;


    private BigDecimal weight;

}



Feign remote call loses data

We obtain the member's address information remotely by passing the member id, and remotely query the shopping cart information without passing any parameters. The shopping cart itself judges whether the user is logged in, and returns the current user's shopping cart information if logged in.

At this point 购物车的拦截器没有从session中获取到用户信息, but we are indeed logged in.

If the browser directly accesses, it will carry a cookie, and the cookie will carry our jsessionId.

Simple analysis of feign remote call source code

Feign needs to construct a request before making a remote call, and calls many interceptors RequestInterceptor. We did not set up an interceptor, which means that feign thinks that the current request does not need to be enhanced, so it passes the default constructed request.

The browser initiates a request to submit an order and enters the controller and service, which must carry the request header, but feign created a new request template for us during the remote call process, and there is nothing in it.

// 如果是公共方法equals,hashcode,toString等就不需要远程调用
// 否则就调用,dispatch.get(method)).invoke(args),获取方法并执行
// public class ReflectiveFeign extends Feign
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
            if (!"equals".equals(method.getName())) {
    
    
                if ("hashCode".equals(method.getName())) {
    
    
                    return this.hashCode();
                } else {
    
    
                    return "toString".equals(method.getName()) ? this.toString() : ((MethodHandler)this.dispatch.get(method)).invoke(args);
                }
            } else {
    
    
                try {
    
    
                    Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
                    return this.equals(otherHandler);
                } catch (IllegalArgumentException var5) {
    
    
                    return false;
                }
            }
        }










// final class SynchronousMethodHandler implements MethodHandler 


Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    
    
		// 模板指定了我们请求的url地址,得到当前请求,利用客户端执行
        Request request = this.targetRequest(template);
        if (this.logLevel != Level.NONE) {
    
    
            this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
        }

        long start = System.nanoTime();

        Response response;
        try {
    
    
            response = this.client.execute(request, options);
            


// 拦截所有的请求拦截器并返回
Request targetRequest(RequestTemplate template) {
    
    
        Iterator var2 = this.requestInterceptors.iterator();

        while(var2.hasNext()) {
    
    
            RequestInterceptor interceptor = (RequestInterceptor)var2.next();
            interceptor.apply(template);
        }

        return this.target.apply(template);
    }

feign remote call lost request header

insert image description here

package com.atlinxi.gulimall.order.config;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Configuration
public class GuliFeignConfig {
    
    

    /**
     *
     * 浏览器发起提交订单的请求,肯定是携带请求头的,包含cookie,这些订单服务肯定是可以获取到的
     * 订单服务远程调用购物车服务获取当前用户的购物车信息,在调用过程中因为feign创建了新的请求模板,但是并没有携带旧的请求头
     *      导致购物车服务不能获取session,错误认为是用户没有登录
     *
     * feign在发起请求之前,会拦截所有RequestInterceptor,对当前请求进行增强
     *      Cookie:user-key=93827f16-f88a-480a-9552-a784fa80b372;GULISESSION=MGI2ZWJkYzktOTlhNC00MTY4LTk4ZWQtZmI0MzhiYWM1NDRk
     *
     *
     *
     * 解决feign远程调用丢失请求头的问题
     * @return
     */
    @Bean("requestInterceptor")
    public RequestInterceptor requestInterceptor(){
    
    

        return new RequestInterceptor() {
    
    
            @Override
            public void apply(RequestTemplate requestTemplate) {
    
    


                /**
                 *
                 * 把之前的请求头设置到feign的requestTemplate中
                 *
                 * 原生的办法
                 *
                 *      浏览器发送 /toTrade 请求,来到service,service远程调用feign,
                 *          feign创建新的请求对象来发请求,在创建对象的时候会调用拦截器
                 *
                 *          拦截器,controller,service其实都是同一个线程,
                 *          所以在拦截器中想获取到之前的请求,
                 *              在controller中使用HttpServletRequest获取到请求头并使用ThreadLocal共享给拦截器
                 *
                 */

                // 我们不用原生的,spring为我们提供了RequestContextHolder(上下文环境的保持器)
                // RequestContextHolder拿到刚进来的这个请求,这个实现其实也是上面原生的方法
                ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();


                /**
                 * 因为异步问题的原因,我们需要在service层使用RequestContextHolder来获取RequestAttributes设置到每一个异步任务中,
                 *      getRequestAttributes()在这儿才能被获取到
                 *
                 * 共有三个异步任务 会员服务查询收获地址,购物车服务查询当前用户的购物项,库存服务查询各购物项是否有库存
                 *
                 * 库存服务我们没有设置RequestAttributes(无需共享数据),所以我们在这儿获取不到,需要判断null
                 *
                 *
                 *
                 *
                 * 主线程获取requestAttributes,设置到每一个副线程中
                 * 虽然都是RequestContextHolder,但是是两个线程,
                 * RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
                 *
                 *         CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
                 *             // 每一个线程都来共享之前的请求数据
                 *             RequestContextHolder.setRequestAttributes(requestAttributes);
                 *             // 1. 远程查询所有的收获地址列表
                 *             List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
                 *             confirmVo.setAddresses(address);
                 *         }, executor);
                 *
                 *
                 */
                if (attributes!=null){
    
    
                    HttpServletRequest request = attributes.getRequest();   // 老请求

                    // 老师这块儿request是判断null的,对RequestContextHolder不太熟,但是凭感觉这儿不应该request为null
                    // 出现过一次,但再没复现过
                    requestTemplate.header("Cookie",request.getHeader("Cookie"));
                }



            }
        };
    }
}

feign asynchronous remote call loses context

Using asynchronous remote calls to the shopping cart service and membership service, it is found that RequestInterceptorthe current request attributes cannot be obtained.

To put it simply , RequestContextHolderthe underlying implementation provided by spring is that ThreadLocalwhen asynchronous is enabled, oderService and remote call address and cart are all different threads, so naturally the context cannot be obtained from ThreadLocal.

If asynchronous is not enabled, it must be available in the same thread.
insert image description here

Interface idempotency

After the order is submitted, there will be an order in the database, and the next step is to enter our payment process.
Assuming that the network speed is slow and the user submits orders multiple times, it may cause the same order to be saved in the database. Anti-duplicate submission is very important, the professional term is called order submission 幂等性.
Idempotency here means that the result of submitting an order once and multiple times is the same, and the database will only have one order.

In a distributed system, whether we submit data to a page, submit a form, or call each other in a distributed system, it is possible that the same method will be executed multiple times, for example, the user clicked multiple times to submit, or the call failed once For multiple calls, the idempotency of the interface must be guaranteed no matter how it is called.

What is idempotency

接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的, will not cause side effects due to multiple clicks;
for example, in the payment scenario, the user purchases the product and the payment is deducted successfully, but when the result is returned, the network is abnormal. At this time, the money has been deducted. The second deduction, the return result is successful, the user checks the balance and finds that more money has been deducted, and the transaction records have become two. , which does not guarantee the idempotency of the interface.

what needs to be prevented

  • User clicks button multiple times
  • The user page rolls back and submits again
  • The microservices call each other, and the requests fail due to network problems. Feign triggers retry mechanism
  • other business situations

When is idempotent required?

Taking SQL as an example, some operations are naturally idempotent.

  • SELECT * FROM table WHER id=?, no matter how many times it is executed, it will not change the state, and it is naturally idempotent.
  • UPDATE tab1 SET col1=1 WHERE col2=2, the state is consistent no matter how many times the execution is successful, and it is also an idempotent operation.
  • delete from user where userid=1, multiple operations, same result, idempotent
  • insert into user(userid,name) values(1,'a') If userid is the only primary key, that is, if the above business is repeated, only one piece of user data will be inserted, which is idempotent.

not idempotent

  • UPDATE tab1 SET col1=col1+1 WHERE col2=2, the result of each execution will change, it is not idempotent.
  • insert into user(userid,name) values(1,'a') If userid is not a primary key and can be repeated, then multiple operations on the above business will result in multiple new data entries, which is not idempotent.

At the database design level, we use order submission as a scenario, and we can set the order number to unique, so that it will not be submitted multiple times when inserting.

Idempotent solution

token mechanism

  1. The server provides an interface for sending tokens. When we analyze the business, which business has idempotent problems, we must first obtain the token before executing the business, and the server will save the token in redis.
  2. Then, when calling the business interface request, the token is carried over, usually placed in the header of the request.
  3. The server judges whether the token exists in redis, if it exists, it means the first request, then deletes the token, and continues to execute the business.
  4. If it is judged that the token does not exist in redis, it means that it is a repeated operation, and the repeated mark is returned to the client directly, so as to ensure that the business code will not be executed repeatedly.

Dangerousness:
delete the token first or delete the token later;

Let's say the user clicks twice quickly to submit the order

  • Deleting the token first may cause,
    assuming that two clicks are fast, the token is obtained from redis at the same time, the token is deleted at the same time, and the business logic is executed twice.

  • Deleting the token later may cause
    the first request to come in, obtain the token, and create an order (the token in redis has not been deleted at this time), and then the second request also comes in and obtains the token, and the business logic is executed twice .

Token acquisition, comparison and deletion must be atomic

  • redis.get(token), token.equals, and redis.del(token) If these two operations are not atomic, it may cause that, under high concurrency, they all get the same data, the judgment is successful, and the business continues to execute concurrently

  • This operation can be done in redis using lua script
    if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end

various locking mechanisms

  1. Database pessimistic lock
    select * from xxxx where id = 1 for update; (This record is locked during the query, and verification is required when the second request comes in)
    Pessimistic locks are generally used together with transactions, and the data locking time may be very slow Long, need to be selected according to the actual situation.
    In addition, it should be noted that the id field must be a primary key or a unique index, otherwise it may cause the result of locking the table, which will be very troublesome to deal with.

  2. Database Optimistic Lock
    This method is suitable for update scenarios,
    update t_goods set count = count -1 , version = version + 1 where good_id=2 and version = 1
    According to the version version, that is, to obtain the current product before operating the inventory version version number, and then bring this version number when operating.

    Let's sort it out. When we first operated the inventory, we got version 1, and the version called inventory service became 2; but there was a problem returning to the order service, and the order service initiated the call to the inventory service again. When the order service passed the version is still 1, when the above sql statement is executed again, it will not be executed; because the version has changed to 2, the where condition will not be established. This ensures that no matter how many times it is called, it will only be processed once.

    乐观锁主要使用于处理读多写少的问题

  3. Distributed locks at the business layer
    If multiple machines may process the same data at the same time, for example, if multiple machines receive the same data for timed tasks, we can add distributed locks to lock the data and release the locks after the processing is completed. Those who acquire the lock must first determine whether the data has been processed.

Various unique constraints

  1. Database unique constraints
    Insert data, should be inserted according to the unique index, such as the order number, the same order can not have two records inserted. We prevent duplication at the database level.
    This mechanism uses the unique constraint of the primary key of the database to solve the idempotent problem in the insert scenario. However, the requirement for the primary key is not an auto-incrementing primary key, which requires the business to generate a globally unique primary key.
    In the scenario of sub-database and sub-table, the routing rules must ensure that the same request lands in the same database and the same table, otherwise the database primary key constraint will not work, because the primary keys of different databases and tables are irrelevant.

  2. redis set anti-duplication
    A lot of data needs to be processed and can only be processed once. For example, we can calculate the MD5 of the data and put it into the redis set. Every time we process data, we first check whether the MD5 already exists, and do not process it if it exists.

  3. The anti-duplication table
    uses the order number orderNo as the unique index of the de-duplication table, inserts the unique index into the de-duplication table, and then performs business operations, and they are in the same transaction.
    This ensures that when the request is repeated, the request fails because the deduplication table has a unique constraint, avoiding the idempotent problem. It should be noted here that the deduplication table and the business table should be in the same library, so as to ensure that in the same transaction, even if the business operation fails, the data in the deduplication table will be rolled back. This is a good guarantee of data consistency.
    The redis anti-heavy mentioned earlier is also considered

  4. When a unique id is requested globally to
    call the interface, a unique id is generated, and redis saves the data in the collection (deduplication), and it is processed as it exists.
    You can use nginx to set the unique id of each request;
    proxy_set_header X-Request-Id $request_id;

Use the token mechanism to solve the idempotence problem of submitting orders

  1. Click to settle and enter the order page to create a token, save one for redis, and return one for the front end
  2. Carry the token when you click to submit the order, and compare it with the token in redis. If it is successful, it will be submitted. If it does not exist, it will be a repeated submission.

insert image description here

insert image description here

Create Order

Submit your order in the next post

public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
    
    
        OrderConfirmVo confirmVo = new OrderConfirmVo();

        MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();

        // 获取之前的请求,
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
    
    
            // 每一个线程都来共享之前的请求数据
            RequestContextHolder.setRequestAttributes(requestAttributes);
            // 1. 远程查询所有的收获地址列表
            List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
            confirmVo.setAddresses(address);
        }, executor);


        CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
    
    
            RequestContextHolder.setRequestAttributes(requestAttributes);
            // 2. 远程查询购物车所有选中的购物项
            List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
            confirmVo.setOrderItems(items);
        }, executor).thenRunAsync(()->{
    
    
            RequestContextHolder.setRequestAttributes(requestAttributes);

            List<OrderItemVo> items = confirmVo.getOrderItems();
            List<Long> collect = items.stream().map(item -> item.getSkuId()).collect(Collectors.toList());
            R hasStock = wmsFeignService.getSkusHasStock(collect);
            List<SkuStockVo> data = hasStock.getData(new TypeReference<List<SkuStockVo>>() {
    
    
            });

            if (data!=null){
    
    

                Map<Long, Boolean> map = data.stream()
                        .collect(Collectors.toMap(SkuStockVo::getSkuId, SkuStockVo::getHasStock));
                confirmVo.setStocks(map);
            }
        },executor);


        // 3. 查询用户积分
        Integer integration = memberRespVo.getIntegration();
        confirmVo.setIntegration(integration);

        // 4. 其他数据自动计算


        // 5. 防重令牌
        String token = UUID.randomUUID().toString().replace("-", "");
        redisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX+memberRespVo.getId(),token,30, TimeUnit.MINUTES);

        confirmVo.setOrderToken(token);

        CompletableFuture.allOf(getAddressFuture,cartFuture).get();
        return confirmVo;
    }

mvn

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.atlinxi.gulimall</groupId>
    <artifactId>gulimall-order</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gulimall-order</name>
    <description>谷粒商城-订单服务</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2020.0.4</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--        由于SpringCloud Feign高版本不使用Ribbon而是使用spring-cloud-loadbalancer,
           所以需要引用spring-cloud-loadbalancer或者降版本-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.atlinxi.gulimall</groupId>
            <artifactId>gulimall-common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--		整合springSession解决session共享问题-->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


Soft as a nurse's palm. Palms of quail eggs. The palm of the poetic eye. Maybe it's not necessarily the right way.

Fang Siqi's First Love Paradise
Yihan Lam

Guess you like

Origin blog.csdn.net/weixin_44431371/article/details/129780882