[Business Functions 98] Microservices-springcloud-springboot-e-commerce order module-interface idempotence processing-order submission anti-duplication check-the problem and solution of request Header loss when Fegin calls remote services

Order module

1. Resource integration

  We need to copy the relevant static resources to nginx, then copy the dynamic template files to the templates directory of the order project, and then adjust the path of the resources. Just set the corresponding route in the gateway.

2. Integrate SpringSession

  Combined with the official website, import the corresponding dependencies, and then add the corresponding configuration information, redis configuration information, and cookie configuration first-level domain name and second-level domain name.

3. Order Center

  Modules involved in the order center

image.png

Order status:

  1. Pending payment: submit order, order pre-order
  2. Paid/to be shipped: After the payment is completed, the order system needs to record the payment time, the payment serial number is convenient for reconciliation, the order is transferred to the wms system, and the warehouse performs operations such as allocation, distribution, sorting, and delivery
  3. Pending payment/shipped: The warehouse ships the goods out of the warehouse and the order enters the logistics link.
  4. Completed: The user confirms receipt of the goods, the order transaction is completed, and subsequent settlement is carried out on the payment side. If there is a problem with the order, the after-sales status will be entered.
  5. Canceled: The order was canceled before payment was made.
  6. After-sales: The user applies for a refund after payment, or the user applies for a return or exchange after the merchant delivers the goods.

Order Process:

image.png

4. Authentication interception

  All requests in the order service must be processed in an authenticated state, so we need to add an interceptor to verify whether it is authenticated.

package com.msb.mall.order.interceptor;

import com.msb.common.constant.AuthConstant;
import com.msb.common.vo.MemberVO;
import org.springframework.web.servlet.HandlerInterceptor;

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

public class AuthInterceptor implements HandlerInterceptor {
    
    

    public static ThreadLocal threadLocal = new ThreadLocal();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        // 通过HttpSession获取当前登录的用户信息
        HttpSession session = request.getSession();
        Object attribute = session.getAttribute(AuthConstant.AUTH_SESSION_REDIS);
        if(attribute != null){
    
    
            MemberVO memberVO = (MemberVO) attribute;
            threadLocal.set(memberVO);
            return true;
        }
        // 如果 attribute == null 说明没有登录,那么我们就需要重定向到登录页面
        session.setAttribute(AuthConstant.AUTH_SESSION_MSG,"请先登录");
        response.sendRedirect("http://auth.msb.com/login.html");
        return false;
    }
}

Then register the interceptor

@Configuration
public class MyWebInterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/**");
    }

}

5. Order confirmation page

1. VO extraction on the order confirmation page

public class OrderConfirmVo {
    
    

    // 订单的收货人 及 收货地址信息
    @Getter @Setter
    List<MemberAddressVo> address;
    // 购物车中选中的商品信息
    @Getter @Setter
    List<OrderItemVo> items;
    // 支付方式
    // 发票信息
    // 优惠信息

    //Integer countNum;

    public Integer getCountNum(){
    
    
        int count = 0;
        if(items != null){
    
    
            for (OrderItemVo item : items) {
    
    
                count += item.getCount();
            }
        }
        return count;
    }

    // BigDecimal total ;// 总的金额
    public BigDecimal getTotal(){
    
    
        BigDecimal sum = new BigDecimal(0);
        if(items != null ){
    
    
            for (OrderItemVo item : items) {
    
    
                BigDecimal totalPrice = item.getPrice().multiply(new BigDecimal(item.getCount()));
                sum = sum.add(totalPrice);
            }
        }
        return sum;
    }
    // BigDecimal payTotal;// 需要支付的总金额
    public BigDecimal getPayTotal(){
    
    
        return getTotal();
    }
}

2. Confirm page data acquisition

  Call the corresponding service remotely through Fegin to obtain the member's data and product information in the shopping cart.

@Override
    public OrderConfirmVo confirmOrder() {
    
    
        OrderConfirmVo vo = new OrderConfirmVo();
        MemberVO memberVO = (MemberVO) AuthInterceptor.threadLocal.get();
        // 获取到 RequestContextHolder 的相关信息
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
    
    
            // 同步主线程中的 RequestContextHolder
            RequestContextHolder.setRequestAttributes(requestAttributes);
            // 1.查询当前登录用户对应的会员的地址信息
            Long id = memberVO.getId();
            List<MemberAddressVo> addresses = memberFeginService.getAddress(id);
            vo.setAddress(addresses);
        }, executor);
        CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
    
    
            RequestContextHolder.setRequestAttributes(requestAttributes);
            // 2.查询购物车中选中的商品信息
            List<OrderItemVo> userCartItems = cartFeginService.getUserCartItems();
            vo.setItems(userCartItems);
        }, executor);
        try {
    
    
            CompletableFuture.allOf(future1,future2).get();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        // 3.计算订单的总金额和需要支付的总金额 VO自动计算
        return vo;
    }

When Fegin calls the remote service, the request header will be lost.

image.png

image.png

First, we create RequestInterceptoran implementation to bind the Header information. At the same time, we need to obtain the Request information from the main thread during asynchronous processing, and then bind it in the sub-thread.

1. Add the corresponding RequestInterceptor configuration class to bind the Header information

package com.msb.mall.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 MallFeginConfig {
    
    

    @Bean
    public RequestInterceptor requestInterceptor(){
    
    
        return new RequestInterceptor() {
    
    
            @Override
            public void apply(RequestTemplate requestTemplate) {
    
    
                System.out.println("RequestInterceptor:"+Thread.currentThread().getName());
                ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                HttpServletRequest request = requestAttributes.getRequest();
                String cookie = request.getHeader("Cookie");
                requestTemplate.header("Cookie",cookie);
            }
        };
    }
}

2. Obtain the Request information in the main thread, and then bind it to the child thread.
image.png

Then render the data display on the order confirmation page

image.png

image.png

image.png

final page effect

image.png

6. Interface idempotency processing

Idempotence: Calling a method or interface multiple times will not change the business status, and it can ensure that the results of repeated calls are consistent with the results of a single call.

1. Natural idempotent behavior

Take the SQL statement as an example:

select * from t_user where id = 1;
update t_user set age = 18 where id = 2;
delete from t_user where id = 1
insert into (userid,username)values(1,'波哥') ; # userid 唯一主键

Does not have idempotent behavior

update t_user set age = age + 1 where id = 1;
insert into (userid,username)values(1,'波哥'); # userid 不是主键 可以重复

2. Need to use idempotent scenarios

Scenarios that require idempotence:

  • Front-end repeated submission
  • Interface timeout retry
  • Message queue repeated consumption

3.Solution

  1. Token mechanism : ①The client requests to obtain the token, and the server generates a unique ID as the token and stores it in redis; ②The client carries the token in the second request, and the server executes business operations and deletes the token if the token is successfully verified, and the server verifies the token. If the verification token fails, it means repeating the operation.
  2. Based on mysql : ①Create a new deduplication table; ②The server puts some information submitted by the client into the table, which has a unique index field; ③If the insertion is successful, there will be no repeated request, and if the insertion fails, the request will be repeated.
  3. Based on redis : ①The client requests the server to get the identification field of this request; ②The server stores the identification field in redis in the form of setnx and sets the expiration time; ③If the setting is successful, it means a non-repeated operation, and if the setting fails, it means a repeated operation.
  4. State machine, pessimistic lock, optimistic lock, etc.

7. Submit order

1. Prevent duplicate submissions

  When submitting an order, we ensure the idempotence of the request by preventing duplication of tokens.

image.png

2.Generate Token

  In the service that obtains the order settlement page data, we need to generate the corresponding Token, save it in Redis and bind it to the page.

image.png

Processing in the page

image.png

3. Submit order

  Then in the logic of submitting the order, we first create the corresponding VO

@Data
public class OrderSubmitVO {
    
    

    // 收获地址的id
    private Long addrId;

    // 支付方式
    private Integer payType;

    // 防重Token
    private String orderToken;

    // 买家备注
    private String note;
}

Then create the corresponding form on the order confirmation page

image.png

Then submit the data to the backend service.

image.png

4. Anti-reinspection

  The order data is submitted to the back-end service, and we need to perform verification to prevent re-submission before placing the order.

try{
    
    
            lock.lock();//加锁
            String redisToken = redisTemplate.opsForValue().get(key);
            if(redisToken != null && redisToken.equals(vo.getOrderToken())){
    
    
                // 表示是第一次提交
                // 需要删除Token
                redisTemplate.delete(key);
            }else{
    
    
                // 表示是重复提交
                return responseVO;
            }
        }finally {
    
    
            lock.unlock(); //释放锁
        }

Above we realized the atomicity of query and delete operations in Redis by means of locking, and we can also use scripts in Redis to realize atomic processing.

// 获取当前登录的用户信息
        MemberVO memberVO = (MemberVO) AuthInterceptor.threadLocal.get();
        // 1.验证是否重复提交  保证Redis中的token 的查询和删除是一个原子性操作
        String key = OrderConstant.ORDER_TOKEN_PREFIX+":"+memberVO.getId();
        String script = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0";
        Long result = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class)
                , Arrays.asList(key)
                , vo.getOrderToken());
        if(result == 0){
            // 表示验证失败 说明是重复提交
            return responseVO;
        }

Guess you like

Origin blog.csdn.net/studyday1/article/details/132710199