“This article has participated in the good article call order activity, click to see: back end, big front end double track submission, 20,000 yuan prize pool for you to challenge!”

RocketMQ series is divided into three parts to introduce, the last part is the initial RocketMQ, this part is the second part, case practice, through the requirements of the case get on!

1. Case introduction

1.1 Service Analysis

Simulate the [order] and [payment] business in the shopping scene of e-commerce websites

1) place the order

  1. User request order system order
  2. The order system invokes the order service through RPC to place an order
  3. The order service invokes the coupon service and deducts the coupon
  4. The order service invocation invokes the inventory service to verify and subtract the inventory
  5. The order service invokes the user service and deducts the user balance
  6. Order service completes confirmation orders

2) payment

  1. The user requests the payment system
  2. The payment system calls the API of the third-party payment platform to initiate the payment process
  3. After the user successfully pays through the third-party payment platform, the third-party payment platform will call back and notify the payment system
  4. The payment system calls the order service to modify the order state
  5. The payment system calls the credits service to add credits
  6. The payment system calls the logging service to log

1.2 Problem Analysis

Question 1

After the user submits the order, the inventory is successfully deducted, the coupon is successfully deducted and the balance is successfully used. However, when the order confirmation operation fails, the inventory, inventory and balance need to be rolled back.

How to ensure data integrity?

Use MQ to ensure the integrity of system data after an order failure

Question 2

After the user successfully pays through the third-party payment platform (Alipay, wechat), the third-party payment platform shall asynchronously notify the merchant payment system of the user’s payment result through the callback API, and the payment system shall modify the order status, record the payment log and increase the points to the user according to the payment result.

How does the merchant payment system ensure that when receiving the asynchronous notification from the third-party payment platform, how does it respond quickly to the third-party payment voucher?

Data is distributed through MQ to improve system processing performance

2. Technical analysis

2.1 Technology Selection

  • SpringBoot
  • Dubbo
  • Zookeeper
  • RocketMQ
  • Mysql

2.2 SpringBoot integrates RocketMQ

Download the RocketMQ-Spring project

Install RocketMQ-Spring into your local repository

mvn install -Dmaven.skip.test=true
Copy the code

2.2.1 Message producer

1) Add dependencies

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> < version > 2.0.1. RELEASE < / version > < / parent > < properties > < rocketmq - spring - the boot - the starter version - > 2.0.3 < / rocketmq - spring - the boot - starter - version > < / properties > < dependencies > <dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-spring-boot-starter</artifactId> <version>${rocketmq-spring-boot-starter-version}</version> </dependency> <dependency> < the groupId > org. Projectlombok < / groupId > < artifactId > lombok < / artifactId > < version > 1.18.6 < / version > < / dependency > <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>Copy the code

2) Configuration file

# application. The properties s201 (192.168.85.201) s202 (192.168.85.202)
rocketmq.name-server=s201:9876; s202:9876
rocketmq.producer.group=my-group
Copy the code

3) Startup class

@SpringBootApplication
public class MQProducerApplication {
    public static void main(String[] args) { SpringApplication.run(MQSpringBootApplication.class); }}Copy the code

4) Test class

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {MQSpringBootApplication.class})
public class ProducerTest {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

        @Test 
    public void test1(a){
        rocketMQTemplate.convertAndSend("springboot-mq"."hello springboot rocketmq"); }}Copy the code

2.2.2 Message Consumer

1) Add dependencies

Same message producer

2) Configuration file

Same message producer

3) Startup class

@SpringBootApplication
public class MQConsumerApplication {
    public static void main(String[] args) { SpringApplication.run(MQSpringBootApplication.class); }}Copy the code

4) Message listener

@Slf4j
@Component
@RocketMQMessageListener(topic = "springboot-mq",consumerGroup = "springboot-mq-consumer-1")
public class Consumer implements RocketMQListener<String> {

    @Override
    public void onMessage(String message) {
        log.info("The Receive message:"+message); }}Copy the code

2.3 SpringBoot integrates Dubbo

Download the Dubbo-spring-boot-starter dependency package

Install the Dubbo-spring-boot-starter into the local repository

mvn install -Dmaven.skip.test=true
Copy the code

2.3.1 Setting up the Zookeeper Cluster

1) Preparation

  1. Install the JDK
  2. Upload Zookeeper to the server
  3. Decompress Zookeeper, create a data directory, and rename the zoo_sample. CFG file in conf to zoo.cfg
  4. To establish/usr/local/zookeeper-cluster, copy the decompressed Zookeeper to the following three directories

Note: For ease of operation, the ZK is configured as a pseudo cluster, nothing more.

/usr/local/zookeeper-cluster/zookeeper-1
/usr/local/zookeeper-cluster/zookeeper-2
/usr/local/zookeeper-cluster/zookeeper-3
Copy the code
  1. Set the dataDir (zoo.cfg) clientPort of each Zookeeper to 2181 2182 2183

Modify/usr/local/zookeeper cluster/zookeeper – 1 / conf/zoo. CFG

clientPort=2181
dataDir=/usr/local/zookeeper-cluster/zookeeper-1/data
Copy the code

Modify/usr/local/zookeeper cluster/zookeeper – 2 / conf/zoo. CFG

clientPort=2182
dataDir=/usr/local/zookeeper-cluster/zookeeper-2/data
Copy the code

Modify/usr/local/zookeeper cluster/zookeeper – 3 / conf/zoo. CFG

clientPort=2183
dataDir=/usr/local/zookeeper-cluster/zookeeper-3/data
Copy the code

2) Configure the cluster

  1. Create a myID file with contents 1, 2, and 3 in each ZooKeeper data directory. This file is the ID of each server
  2. Configure the client access port and cluster server IP address list in zookeeper’s zoo. CFG file.

The IP address list of cluster servers is as follows:

server.1=s201:2881:3881
server.2=s201:2882:3882
server.3=s201:2883:3883
Copy the code

Description: server. server ID= Server IP address: Communication port between servers: Voting port between servers

3) Start the cluster

Starting the cluster means starting each instance separately.

2.3.2 RPC service interface

public interface IUserService {
    public String sayHello(String name);
}
Copy the code

2.3.3 Service Provider

1) Add dependencies

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.01..RELEASE</version> </parent> <dependencies> <! --dubbo--> <dependency> <groupId>com.alibaba.spring.boot</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>2.0. 0</version> </dependency> <! --spring-boot-stater--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <artifactId>log4j-to-slf4j</artifactId> <groupId>org.apache.logging.log4j</groupId> </exclusion> </exclusions> </dependency> <! --zookeeper--> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.410.</version>
        <exclusions>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
            </exclusion>
            <exclusion>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>com.101tec</groupId>
        <artifactId>zkclient</artifactId>
        <version>0.9</version> <exclusions> <exclusion> <artifactId>slf4j-log4j12</artifactId> <groupId>org.slf4j</groupId> </exclusion> </exclusions> </dependency> <! --API--> <dependency> <groupId>com.moe.demo</groupId> <artifactId>dubbo-api</artifactId> <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>
Copy the code

2) Configuration file

# application.properties
spring.application.name=dubbo-demo-provider
spring.dubbo.application.id=dubbo-demo-provider
spring.dubbo.application.name=dubbo-demo-provider
spring.dubbo.registry.address=zookeeper://s201:2181; zookeeper://s201:2182; zookeeper://s201:2183
spring.dubbo.server=true
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20880
Copy the code

3) Startup class

@EnableDubboConfiguration
@SpringBootApplication
public class ProviderBootstrap {
    public static void main(String[] args) throws IOException { SpringApplication.run(ProviderBootstrap.class,args); }}Copy the code

4) Service implementation

@Component
@Service(interfaceClass = IUserService.class)
public class UserServiceImpl implements IUserService{
    @Override
    public String sayHello(String name) {
        return "hello:"+ name; }}Copy the code

2.3.4 Service consumer

1) Add dependencies

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> </parent> <dependencies> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <! --dubbo--> <dependency> <groupId>com.alibaba.spring.boot</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> < version > 2.0.0 < / version > < / dependency > < the dependency > < groupId > org. Springframework. Boot < / groupId > <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <artifactId>log4j-to-slf4j</artifactId> <groupId>org.apache.logging.log4j</groupId> </exclusion> </exclusions> </dependency> <! --zookeeper--> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.10</version> < Exclusions > < Exclusion > <groupId>org.slf4j</groupId> <artifactId> slf4J-log4j12 </artifactId> </exclusion> <exclusion> <groupId>log4j</groupId> <artifactId>log4j</artifactId> </exclusion> </exclusions> </dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.9< version> <exclusions> <exclusion> <artifactId>slf4j-log4j12</artifactId> <groupId>org.slf4j</groupId> </exclusion> </exclusions> </dependency> <! <dependency> <groupId>com.moe.demo</groupId> <artifactId>dubbo-api</artifactId> <version> 1.0-snapshot </version>  </dependency> </dependencies>Copy the code

2) Configuration file

# application.properties
spring.application.name=dubbo-demo-consumer
spring.dubbo.application.name=dubbo-demo-consumer
spring.dubbo.application.id=dubbo-demo-consumer
spring.dubbo.registry.address=zookeeper://s201:2181; zookeeper://s201:2182; zookeeper://s201:2183
Copy the code

3) Startup class

@EnableDubboConfiguration
@SpringBootApplication
public class ConsumerBootstrap {
    public static void main(String[] args) { SpringApplication.run(ConsumerBootstrap.class); }}Copy the code

4) Controller

@RestController
@RequestMapping("/user")
public class UserController {

    @Reference
    private IUserService userService;

    @RequestMapping("/sayHello")
    public String sayHello(String name){
        returnuserService.sayHello(name); }}Copy the code

3. Environment construction

3.1 database

1) Coupon list

Field Type Comment
coupon_id bigint(50) NOT NULL A coupon ID
coupon_price A decimal (10, 2) NULL Coupon amount
user_id bigint(50) NULL The user ID
order_id bigint(32) NULL The order ID
is_used int(1) NULL Yes No 0 Not used 1 Used
used_time timestamp NULL Use your time

2) Commodity list

Field Type Comment
goods_id bigint(50) NOT NULL A primary key
goods_name varchar(255) NULL Name of commodity
goods_number int(11) NULL inventory
goods_price A decimal (10, 2) NULL Commodity prices
goods_desc varchar(255) NULL Commodity description
add_time timestamp NULL Add the time

3) Order form

Field Type Comment
order_id bigint(50) NOT NULL The order ID
user_id bigint(50) NULL The user ID
order_status int(1) NULL Order status 0 unconfirmed 1 confirmed 2 cancelled 3 invalid 4 refund
pay_status int(1) NULL Payment status 0 unpaid 1 paid 2 paid
shipping_status int(1) NULL Delivery Status 0 Undelivered 1 Delivered 2 Returned
address varchar(255) NULL Shipping address
consignee varchar(255) NULL The consignee
goods_id bigint(50) NULL Product ID
goods_number int(11) NULL The number
goods_price A decimal (10, 2) NULL Commodity prices
goods_amount A decimal NULL (10, 0) Commodity price
shipping_fee A decimal (10, 2) NULL The freight
order_amount A decimal (10, 2) NULL The order price
coupon_id bigint(50) NULL A coupon ID
coupon_paid A decimal (10, 2) NULL coupons
money_paid A decimal (10, 2) NULL The amount paid
pay_amount A decimal (10, 2) NULL Pay the amount
add_time timestamp NULL Creation time
confirm_time timestamp NULL Order confirmation Time
pay_time timestamp NULL Payment time

4) Order commodity log table

Field Type Comment
goods_id int(11) NOT NULL Product ID
order_id varchar(32) NOT NULL The order ID
goods_number int(11) NULL Inventory quantity
log_time datetime NULL Recording time

5) User table

Field Type Comment
user_id bigint(50) NOT NULL The user ID
user_name varchar(255) NULL The user name
user_password varchar(255) NULL The user password
user_mobile varchar(255) NULL Mobile phone no.
user_score int(11) NULL integral
user_reg_time timestamp NULL Registration time
user_money A decimal NULL (10, 0) Balance of the user

6) User balance log table

Field Type Comment
user_id bigint(50) NOT NULL The user ID
order_id bigint(50) NOT NULL The order ID
money_log_type int(1) NOT NULL Log type 1 Order payment 2 Order refund
use_money A decimal (10, 2) NULL Operation amount
create_time timestamp NULL Log time

7) Order payment table

Field Type Comment
id varchar(100) NOT NULL A primary key
group_name varchar(100) NULL Producer group name
msg_topic varchar(100) NULL The message theme
msg_tag varchar(100) NULL Tag
msg_key varchar(100) NULL Key
msg_body varchar(500) NULL The message content
msg_status int(1) NULL 0: Untreated. 1: It has been processed
create_time timestamp NOT NULL Recording time

8) MQ message production table

Field Type Comment
id varchar(100) NOT NULL A primary key
group_name varchar(100) NULL Producer group name
msg_topic varchar(100) NULL The message theme
msg_tag varchar(100) NULL Tag
msg_key varchar(100) NULL Key
msg_body varchar(500) NULL The message content
msg_status int(1) NULL 0: Untreated. 1: It has been processed
create_time timestamp NOT NULL Recording time

9) MQ message consumption table

Field Type Comment
msg_id varchar(50) NULL Message ID
group_name varchar(100) NOT NULL Consumer Group name
msg_tag varchar(100) NOT NULL Tag
msg_key varchar(100) NOT NULL Key
msg_body varchar(500) NULL The message body
consumer_status int(1) NULL 0: Processing is in progress. 1: The processing is successful. 2: Processing fails
consumer_times int(1) NULL Number of consumption
consumer_timestamp timestamp NULL Spending time
remark varchar(500) NULL note

3.2 Project Initialization

The Shop system is based on Maven for project management

3.2.1 Project Browse

  • Parent project: shop-parent
  • Order system: shop-order-web
  • Payment system: shop-pay-web
  • Coupon service: shop-coupon service
  • Order service: shop-order-service
  • Payment service: shop-pay-service
  • Goods and services: shop-goods-service
  • User service: shop-user-service
  • Entity class: shop-pojo
  • Persistence layer: shop-DAO
  • Interface layer: shop-API
  • Tool project: shop-common

There are 12 systems

3.2.2 Engineering relationship

3.3 Use of Mybatis reverse engineering

1) Code generation

Using Mybatis reverse engineering to generate CURD persistence layer code for data table

2) Code import

  • Import the entity classes into the shop-pojo project
  • Import the corresponding Mapper class and corresponding configuration file in the service layer project

3.4 Introduction to Public Classes

  • ID generator

IDWorker: Twitter snowflake algorithm

  • Exception handling class

CustomerException: Custom exception class

CastException: Exception throwing class

  • Constant class

ShopCode: System status class

  • Response entity class

Result: Encapsulates the response status and response information

4. Order business

4.1 Basic process of placing orders

1) Interface definition

IOrderService

public interface IOrderService {
    /** * Confirm order *@param order
     * @return Result
     */
    Result confirmOrder(TradeOrder order);
}
Copy the code

2) Business class implementation

@Slf4j
@Component
@Service(interfaceClass = IOrderService.class)
public class OrderServiceImpl implements IOrderService {

    @Override
    public Result confirmOrder(TradeOrder order) {
        //1. Verify the order
       
        //2. Generate a reservation
       
        try {
            //3. Inventory deduction
            
            //4. Deduct the coupon
           
            //5. Use the balance
           
            //6. Confirmation of order
            
            //7. Return to the successful state
           
        } catch (Exception e) {
            //1. Confirm the order failed, send a message
            
            //2. Return to the failed state}}}Copy the code

3) Verify the order

private void checkOrder(TradeOrder order) {
        //1. Check whether the order exists
        if(order==null){
            CastException.cast(ShopCode.SHOP_ORDER_INVALID);
        }
        //2. Verify the existence of the goods in the order
        TradeGoods goods = goodsService.findOne(order.getGoodsId());
        if(goods==null){
            CastException.cast(ShopCode.SHOP_GOODS_NO_EXIST);
        }
        //3. Verify whether the single user exists
        TradeUser user = userService.findOne(order.getUserId());
        if(user==null){
            CastException.cast(ShopCode.SHOP_USER_NO_EXIST);
        }
        //4. Verify whether the unit price of goods is legal
        if(order.getGoodsPrice().compareTo(goods.getGoodsPrice())! =0){
            CastException.cast(ShopCode.SHOP_GOODS_PRICE_INVALID);
        }
        //5. Verify whether the quantity of goods ordered is legal
        if(order.getGoodsNumber()>=goods.getGoodsNumber()){
            CastException.cast(ShopCode.SHOP_GOODS_NUM_NOT_ENOUGH);
        }

        log.info("Check order approved");
}
Copy the code

4) Generate a reservation

private Long savePreOrder(TradeOrder order) {
        //1. Set the order status to invisible
        order.setOrderStatus(ShopCode.SHOP_ORDER_NO_CONFIRM.getCode());
        / / 2. The order ID
        order.setOrderId(idWorker.nextId());
        // Check whether the freight is correct
        BigDecimal shippingFee = calculateShippingFee(order.getOrderAmount());
        if(order.getShippingFee().compareTo(shippingFee) ! =0) {
            CastException.cast(ShopCode.SHOP_ORDER_SHIPPINGFEE_INVALID);
        }
        //3. Calculate the total price of the order correctly
        BigDecimal orderAmount = order.getGoodsPrice().multiply(new BigDecimal(order.getGoodsNumber()));
        orderAmount.add(shippingFee);
        if(orderAmount.compareTo(order.getOrderAmount()) ! =0) {
            CastException.cast(ShopCode.SHOP_ORDERAMOUNT_INVALID);
        }

        //4. Judge whether the coupon information is legal
        Long couponId = order.getCouponId();
        if(couponId ! =null) {
            TradeCoupon coupon = couponService.findOne(couponId);
            // The coupon does not exist
            if (coupon == null) {
                CastException.cast(ShopCode.SHOP_COUPON_NO_EXIST);
            }
            // The coupon has been used
            if ((ShopCode.SHOP_COUPON_ISUSED.getCode().toString())
                .equals(coupon.getIsUsed().toString())) {
                CastException.cast(ShopCode.SHOP_COUPON_INVALIED);
            }
            order.setCouponPaid(coupon.getCouponPrice());
        } else {
            order.setCouponPaid(BigDecimal.ZERO);
        }

        //5. Determine whether the balance is correct
        BigDecimal moneyPaid = order.getMoneyPaid();
        if(moneyPaid ! =null) {
            // Compare whether the balance is greater than 0
            int r = order.getMoneyPaid().compareTo(BigDecimal.ZERO);
            // The balance is less than 0
            if (r == -1) {
                CastException.cast(ShopCode.SHOP_MONEY_PAID_LESS_ZERO);
            }
            // The balance is greater than 0
            if (r == 1) {
                // Query user information
                TradeUser user = userService.findOne(order.getUserId());
                if (user == null) {
                    CastException.cast(ShopCode.SHOP_USER_NO_EXIST);
                }
            // Compare whether the balance is greater than the user account balance
            if (user.getUserMoney().compareTo(order.getMoneyPaid().longValue()) == -1) { CastException.cast(ShopCode.SHOP_MONEY_PAID_INVALID); } order.setMoneyPaid(order.getMoneyPaid()); }}else {
        order.setMoneyPaid(BigDecimal.ZERO);
    }
    // Calculate the total price paid for the order
    order.setPayAmount(orderAmount.subtract(order.getCouponPaid())
                       .subtract(order.getMoneyPaid()));
    // Set order adding time
    order.setAddTime(new Date());

    // Save the reservation
    int r = orderMapper.insert(order);
    if(ShopCode.SHOP_SUCCESS.getCode() ! = r) { CastException.cast(ShopCode.SHOP_ORDER_SAVE_ERROR); } log.info("Order: ["+order.getOrderId()+"] reservation form generated successfully");
    return order.getOrderId();
}
Copy the code

5) Inventory deduction

  • Dubbo invokes goods and services to complete the inventory reduction
private void reduceGoodsNum(TradeOrder order) {
    TradeGoodsNumberLog goodsNumberLog = new TradeGoodsNumberLog();
    goodsNumberLog.setGoodsId(order.getGoodsId());
    goodsNumberLog.setOrderId(order.getOrderId());
    goodsNumberLog.setGoodsNumber(order.getGoodsNumber());
    Result result = goodsService.reduceGoodsNum(goodsNumberLog);
    if(result.getSuccess().equals(ShopCode.SHOP_FAIL.getSuccess())) { CastException.cast(ShopCode.SHOP_REDUCE_GOODS_NUM_FAIL);  } log.info("Order: ["+order.getOrderId()+"] inventory deduction ["+order.getGoodsNumber()+"A] success");
}
Copy the code
  • Inventory deduction
@Override
public Result reduceGoodsNum(TradeGoodsNumberLog goodsNumberLog) {
    if (goodsNumberLog == null ||
            goodsNumberLog.getGoodsNumber() == null ||
            goodsNumberLog.getOrderId() == null ||
            goodsNumberLog.getGoodsNumber() == null ||
            goodsNumberLog.getGoodsNumber().intValue() <= 0) {
        CastException.cast(ShopCode.SHOP_REQUEST_PARAMETER_VALID);
    }
    TradeGoods goods = goodsMapper.selectByPrimaryKey(goodsNumberLog.getGoodsId());
    if(goods.getGoodsNumber()<goodsNumberLog.getGoodsNumber()){
        // Inventory is low
        CastException.cast(ShopCode.SHOP_GOODS_NUM_NOT_ENOUGH);
    }
    / / inventory reduction
    goods.setGoodsNumber(goods.getGoodsNumber()-goodsNumberLog.getGoodsNumber());
    goodsMapper.updateByPrimaryKey(goods);


    // Record the inventory operation log
    goodsNumberLog.setGoodsNumber(-(goodsNumberLog.getGoodsNumber()));
    goodsNumberLog.setLogTime(new Date());
    goodsNumberLogMapper.insert(goodsNumberLog);

    return new Result(ShopCode.SHOP_SUCCESS.getSuccess(),ShopCode.SHOP_SUCCESS.getMessage());
}
Copy the code

6) Discount coupons

  • Complete the coupon deduction through Dubbo
private void changeCoponStatus(TradeOrder order) {
    // Determine whether the user uses the coupon
    if(! StringUtils.isEmpty(order.getCouponId())) {// Encapsulate the coupon object
        TradeCoupon coupon = couponService.findOne(order.getCouponId());
        coupon.setIsUsed(ShopCode.SHOP_COUPON_ISUSED.getCode());
        coupon.setUsedTime(new Date());
        coupon.setOrderId(order.getOrderId());
        Result result = couponService.changeCouponStatus(coupon);
        // Judge the execution result
        if (result.getSuccess().equals(ShopCode.SHOP_FAIL.getSuccess())) {
            // Failed to use the coupon
            CastException.cast(ShopCode.SHOP_COUPON_USE_FAIL);
        }
        log.info("Order: ["+order.getOrderId()+"] use a discount coupon ["+coupon.getCouponPrice()+"Yuan] success"); }}Copy the code
  • CouponService changes the status of a coupon
@Override
public Result changeCouponStatus(TradeCoupon coupon) {
    try {
        // Check whether the request parameters are valid
        if (coupon == null || StringUtils.isEmpty(coupon.getCouponId())) {
            CastException.cast(ShopCode.SHOP_REQUEST_PARAMETER_VALID);
        }
		// Update the coupon status to used
        couponMapper.updateByPrimaryKey(coupon);
        return new Result(ShopCode.SHOP_SUCCESS.getSuccess(), ShopCode.SHOP_SUCCESS.getMessage());
    } catch (Exception e) {
        return newResult(ShopCode.SHOP_FAIL.getSuccess(), ShopCode.SHOP_FAIL.getMessage()); }}Copy the code

7) Deduct the balance of users

  • The balance is deducted through user services
private void reduceMoneyPaid(TradeOrder order) {
    // Determine whether the balance used in the order is valid
    if(order.getMoneyPaid() ! =null && order.getMoneyPaid().compareTo(BigDecimal.ZERO) == 1) {
        TradeUserMoneyLog userMoneyLog = new TradeUserMoneyLog();
        userMoneyLog.setOrderId(order.getOrderId());
        userMoneyLog.setUserId(order.getUserId());
        userMoneyLog.setUseMoney(order.getMoneyPaid());
        userMoneyLog.setMoneyLogType(ShopCode.SHOP_USER_MONEY_PAID.getCode());
        // Deduct the balance
        Result result = userService.changeUserMoney(userMoneyLog);
        if (result.getSuccess().equals(ShopCode.SHOP_FAIL.getSuccess())) {
            CastException.cast(ShopCode.SHOP_USER_MONEY_REDUCE_FAIL);
        }
        log.info("Order: ["+order.getOrderId()+"Balance deducted ["+order.getMoneyPaid()+"Yuan] success]"); }}Copy the code
  • UserService UserService updates the balance

@Override
public Result changeUserMoney(TradeUserMoneyLog userMoneyLog) {
    // Check whether the request parameters are valid
    if (userMoneyLog == null
            || userMoneyLog.getUserId() == null
            || userMoneyLog.getUseMoney() == null
            || userMoneyLog.getOrderId() == null
            || userMoneyLog.getUseMoney().compareTo(BigDecimal.ZERO) <= 0) {
        CastException.cast(ShopCode.SHOP_REQUEST_PARAMETER_VALID);
    }

    // Check whether the order has a payment record
    TradeUserMoneyLogExample userMoneyLogExample = new TradeUserMoneyLogExample();
    userMoneyLogExample.createCriteria()
            .andUserIdEqualTo(userMoneyLog.getUserId())
            .andOrderIdEqualTo(userMoneyLog.getOrderId());
   int count = userMoneyLogMapper.countByExample(userMoneyLogExample);
   TradeUser tradeUser = new TradeUser();
   tradeUser.setUserId(userMoneyLog.getUserId());
   tradeUser.setUserMoney(userMoneyLog.getUseMoney().longValue());
   // Determine the balance operation behavior
   // [payment operation]
   if (userMoneyLog.getMoneyLogType().equals(ShopCode.SHOP_USER_MONEY_PAID.getCode())) {
           // If the order has been paid, the exception will be thrown
           if (count > 0) {
                CastException.cast(ShopCode.SHOP_ORDER_PAY_STATUS_IS_PAY);
            }
       	   // The balance is deducted from the user account
           userMapper.reduceUserMoney(tradeUser);
       }
    // [Refund operation]
    if (userMoneyLog.getMoneyLogType().equals(ShopCode.SHOP_USER_MONEY_REFUND.getCode())) {
         // If the order is not paid, there will be no refund
         if (count == 0) {
         CastException.cast(ShopCode.SHOP_ORDER_PAY_STATUS_NO_PAY);
     }
     // Prevent multiple refunds
     userMoneyLogExample = new TradeUserMoneyLogExample();
     userMoneyLogExample.createCriteria()
             .andUserIdEqualTo(userMoneyLog.getUserId())
                .andOrderIdEqualTo(userMoneyLog.getOrderId())
                .andMoneyLogTypeEqualTo(ShopCode.SHOP_USER_MONEY_REFUND.getCode());
     count = userMoneyLogMapper.countByExample(userMoneyLogExample);
     if (count > 0) {
         CastException.cast(ShopCode.SHOP_USER_MONEY_REFUND_ALREADY);
     }
     	// Add balance to user account
        userMapper.addUserMoney(tradeUser);
    }


    // Record the user balance log
    userMoneyLog.setCreateTime(new Date());
    userMoneyLogMapper.insert(userMoneyLog);
    return new Result(ShopCode.SHOP_SUCCESS.getSuccess(),ShopCode.SHOP_SUCCESS.getMessage());
}
Copy the code

8) Confirm the order

private void updateOrderStatus(TradeOrder order) {
    order.setOrderStatus(ShopCode.SHOP_ORDER_CONFIRM.getCode());
    order.setPayStatus(ShopCode.SHOP_ORDER_PAY_STATUS_NO_PAY.getCode());
    order.setConfirmTime(new Date());
    int r = orderMapper.updateByPrimaryKey(order);
    if (r <= 0) {
        CastException.cast(ShopCode.SHOP_ORDER_CONFIRM_FAIL);
    }
    log.info("Order: ["+order.getOrderId()+"] status changed successfully");
}
Copy the code

9) summary

@Override
public Result confirmOrder(TradeOrder order) {
    //1. Verify the order
    checkOrder(order);
    //2. Generate a reservation
    Long orderId = savePreOrder(order);
    order.setOrderId(orderId);
    try {
        //3. Inventory deduction
        reduceGoodsNum(order);
        //4. Deduct the coupon
        changeCoponStatus(order);
        //5. Use the balance
        reduceMoneyPaid(order);
        //6. Confirmation of order
        updateOrderStatus(order);
        log.info("Order: ["+orderId+"] confirm success");
        return new Result(ShopCode.SHOP_SUCCESS.getSuccess(), ShopCode.SHOP_SUCCESS.getMessage());
    } catch (Exception e) {
        // Failed to confirm the order, send a message.return newResult(ShopCode.SHOP_FAIL.getSuccess(), ShopCode.SHOP_FAIL.getMessage()); }}Copy the code

4.2 Failure Compensation Mechanism

4.2.1 Message Sender

  • Configure the RocketMQ attribute value
rocketmq.name-server=s201:9876; s202:9876
rocketmq.producer.group=orderProducerGroup

mq.order.consumer.group.name=order_orderTopic_cancel_group
mq.order.topic=orderTopic
mq.order.tag.confirm=order_confirm
mq.order.tag.cancel=order_cancel
Copy the code
  • Inject template class and attribute value information
 @Autowired
 private RocketMQTemplate rocketMQTemplate;

 @Value("${mq.order.topic}")
 private String topic;

 @Value("${mq.order.tag.cancel}")
 private String cancelTag;
Copy the code
  • Sending the order failure message
@Override
public Result confirmOrder(TradeOrder order) {
    //1. Verify the order
    //2. Generate a reservation
    try {
        //3. Inventory deduction
        //4. Deduct the coupon
        //5. Use the balance
        //6. Confirmation of order
    } catch (Exception e) {
        // Failed to confirm the order, send a message
        CancelOrderMQ cancelOrderMQ = new CancelOrderMQ();
        cancelOrderMQ.setOrderId(order.getOrderId());
        cancelOrderMQ.setCouponId(order.getCouponId());
        cancelOrderMQ.setGoodsId(order.getGoodsId());
        cancelOrderMQ.setGoodsNumber(order.getGoodsNumber());
        cancelOrderMQ.setUserId(order.getUserId());
        cancelOrderMQ.setUserMoney(order.getMoneyPaid());
        try {
            sendMessage(topic, 
                        cancelTag, 
                        cancelOrderMQ.getOrderId().toString(), 
                    JSON.toJSONString(cancelOrderMQ));
    } catch (Exception e1) {
        e1.printStackTrace();
            CastException.cast(ShopCode.SHOP_MQ_SEND_MESSAGE_FAIL);
        }
        return newResult(ShopCode.SHOP_FAIL.getSuccess(), ShopCode.SHOP_FAIL.getMessage()); }}private void sendMessage(String topic, String tags, String keys, String body) throws Exception {
    // Determine whether Topic is null
    if (StringUtils.isEmpty(topic)) {
        CastException.cast(ShopCode.SHOP_MQ_TOPIC_IS_EMPTY);
    }
    // Check whether the message content is empty
    if (StringUtils.isEmpty(body)) {
        CastException.cast(ShopCode.SHOP_MQ_MESSAGE_BODY_IS_EMPTY);
    }
    / / the message body
    Message message = new Message(topic, tags, keys, body.getBytes());
    // Send the message
    rocketMQTemplate.getProducer().send(message);
}
Copy the code

4.2.2 Consuming the receiver

  • Configure the RocketMQ attribute value
rocketmq.name-server=s201:9876; s202:9876
mq.order.consumer.group.name=order_orderTopic_cancel_group
mq.order.topic=orderTopic
Copy the code
  • Create a listener class to consume the message
@Slf4j
@Component
@RocketMQMessageListener(topic = "${mq.order.topic}", consumerGroup = "${mq.order.consumer.group.name}", messageModel = MessageModel.BROADCASTING)
public class CancelOrderConsumer implements RocketMQListener<MessageExt>{

    @Override
    public void onMessage(MessageExt messageExt) {... }}Copy the code

1) Roll back the inventory

  • Process analysis

  • Message consumer
@Slf4j
@Component
@RocketMQMessageListener(topic = "${mq.order.topic}",consumerGroup = "${mq.order.consumer.group.name}",messageModel = MessageModel.BROADCASTING )
public class CancelMQListener implements RocketMQListener<MessageExt>{


    @Value("${mq.order.consumer.group.name}")
    private String groupName;

    @Autowired
    private TradeGoodsMapper goodsMapper;

    @Autowired
    private TradeMqConsumerLogMapper mqConsumerLogMapper;

    @Autowired
    private TradeGoodsNumberLogMapper goodsNumberLogMapper;

    @Override
    public void onMessage(MessageExt messageExt) {
        String msgId=null;
        String tags=null;
        String keys=null;
        String body=null;
        try {
            //1. Parse the message content
            msgId = messageExt.getMsgId();
            tags= messageExt.getTags();
            keys= messageExt.getKeys();
            body= new String(messageExt.getBody(),"UTF-8");

            log.info("Message received successfully");

            //2. Query message consumption records
            TradeMqConsumerLogKey primaryKey = new TradeMqConsumerLogKey();
            primaryKey.setMsgTag(tags);
            primaryKey.setMsgKey(keys);
            primaryKey.setGroupName(groupName);
            TradeMqConsumerLog mqConsumerLog = mqConsumerLogMapper.selectByPrimaryKey(primaryKey);

            if(mqConsumerLog! =null) {//3.
                //3.1 Obtain the message processing status
                Integer status = mqConsumerLog.getConsumerStatus();
                // Do... return
                if(ShopCode.SHOP_MQ_MESSAGE_STATUS_SUCCESS.getCode().intValue()==status.intValue()){
                    log.info("Message."+msgId+It's been dealt with.");
                    return;
                }

                // We are working on... return
                if(ShopCode.SHOP_MQ_MESSAGE_STATUS_PROCESSING.getCode().intValue()==status.intValue()){
                    log.info("Message."+msgId+"In process.");
                    return;
                }

                // Processing failed
                if(ShopCode.SHOP_MQ_MESSAGE_STATUS_FAIL.getCode().intValue()==status.intValue()){
                    // Get the number of times the message was processed
                    Integer times = mqConsumerLog.getConsumerTimes();
                    if(times>3){
                        log.info("Message."+msgId+", the message has been processed more than 3 times, no more processing.");
                        return;
                    }
                    mqConsumerLog.setConsumerStatus(ShopCode.SHOP_MQ_MESSAGE_STATUS_PROCESSING.getCode());

                    // Update with database optimistic locks
                    TradeMqConsumerLogExample example = new TradeMqConsumerLogExample();
                    TradeMqConsumerLogExample.Criteria criteria = example.createCriteria();
                    criteria.andMsgTagEqualTo(mqConsumerLog.getMsgTag());
                    criteria.andMsgKeyEqualTo(mqConsumerLog.getMsgKey());
                    criteria.andGroupNameEqualTo(groupName);
                    criteria.andConsumerTimesEqualTo(mqConsumerLog.getConsumerTimes());
                    int r = mqConsumerLogMapper.updateByExampleSelective(mqConsumerLog, example);
                    if(r<=0) {// Failed to modify, other threads concurrently modify
                        log.info("Concurrent modification, later processing."); }}}else{
                //4.
                mqConsumerLog = new TradeMqConsumerLog();
                mqConsumerLog.setMsgTag(tags);
                mqConsumerLog.setMsgKey(keys);
                mqConsumerLog.setGroupName(groupName);
                mqConsumerLog.setConsumerStatus(ShopCode.SHOP_MQ_MESSAGE_STATUS_PROCESSING.getCode());
                mqConsumerLog.setMsgBody(body);
                mqConsumerLog.setMsgId(msgId);
                mqConsumerLog.setConsumerTimes(0);

                // Add message processing information to the database
                mqConsumerLogMapper.insert(mqConsumerLog);
            }
            //5. Roll back the inventory
            MQEntity mqEntity = JSON.parseObject(body, MQEntity.class);
            Long goodsId = mqEntity.getGoodsId();
            TradeGoods goods = goodsMapper.selectByPrimaryKey(goodsId);
            goods.setGoodsNumber(goods.getGoodsNumber()+mqEntity.getGoodsNum());
            goodsMapper.updateByPrimaryKey(goods);

            // Record the inventory operation log
            TradeGoodsNumberLog goodsNumberLog = new TradeGoodsNumberLog();
            goodsNumberLog.setOrderId(mqEntity.getOrderId());
            goodsNumberLog.setGoodsId(goodsId);
            goodsNumberLog.setGoodsNumber(mqEntity.getGoodsNum());
            goodsNumberLog.setLogTime(new Date());
            goodsNumberLogMapper.insert(goodsNumberLog);

            //6. Change the processing status of the message to Successful
            mqConsumerLog.setConsumerStatus(ShopCode.SHOP_MQ_MESSAGE_STATUS_SUCCESS.getCode());
            mqConsumerLog.setConsumerTimestamp(new Date());
            mqConsumerLogMapper.updateByPrimaryKey(mqConsumerLog);
            log.info("Inventory rollback succeeded");
        } catch (Exception e) {
            e.printStackTrace();
            TradeMqConsumerLogKey primaryKey = new TradeMqConsumerLogKey();
            primaryKey.setMsgTag(tags);
            primaryKey.setMsgKey(keys);
            primaryKey.setGroupName(groupName);
            TradeMqConsumerLog mqConsumerLog = mqConsumerLogMapper.selectByPrimaryKey(primaryKey);
            if(mqConsumerLog==null) {// The database has no record
                mqConsumerLog = new TradeMqConsumerLog();
                mqConsumerLog.setMsgTag(tags);
                mqConsumerLog.setMsgKey(keys);
                mqConsumerLog.setConsumerStatus(ShopCode.SHOP_MQ_MESSAGE_STATUS_FAIL.getCode());
                mqConsumerLog.setMsgBody(body);
                mqConsumerLog.setMsgId(msgId);
                mqConsumerLog.setConsumerTimes(1);
                mqConsumerLogMapper.insert(mqConsumerLog);
            }else{
                mqConsumerLog.setConsumerTimes(mqConsumerLog.getConsumerTimes()+1); mqConsumerLogMapper.updateByPrimaryKeySelective(mqConsumerLog); }}}}Copy the code

2) Roll back coupons

@Slf4j
@Component
@RocketMQMessageListener(topic = "${mq.order.topic}",consumerGroup = "${mq.order.consumer.group.name}",messageModel = MessageModel.BROADCASTING )
public class CancelMQListener implements RocketMQListener<MessageExt>{

    @Autowired
    private TradeCouponMapper couponMapper;

    @Override
    public void onMessage(MessageExt message) {
        try {
            //1. Parse the message content
            String body = new String(message.getBody(), "UTF-8");
            MQEntity mqEntity = JSON.parseObject(body, MQEntity.class);
            log.info("Message received");
            //2. Query the coupon information
            TradeCoupon coupon = couponMapper.selectByPrimaryKey(mqEntity.getCouponId());
            //3. Change the coupon status
            coupon.setUsedTime(null);
            coupon.setIsUsed(ShopCode.SHOP_COUPON_UNUSED.getCode());
            coupon.setOrderId(null);
            couponMapper.updateByPrimaryKey(coupon);
            log.info("Coupon rollback successful");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            log.error("Failed to roll back coupon."); }}}Copy the code

3) Return the balance

@Slf4j
@Component
@RocketMQMessageListener(topic = "${mq.order.topic}",consumerGroup = "${mq.order.consumer.group.name}",messageModel = MessageModel.BROADCASTING )
public class CancelMQListener implements RocketMQListener<MessageExt>{

    @Autowired
    private IUserService userService;

    @Override
    public void onMessage(MessageExt messageExt) {

        try {
            //1. Parse the message
            String body = new String(messageExt.getBody(), "UTF-8");
            MQEntity mqEntity = JSON.parseObject(body, MQEntity.class);
            log.info("Message received");
            if(mqEntity.getUserMoney()! =null && mqEntity.getUserMoney().compareTo(BigDecimal.ZERO)>0) {//2. Call the business layer to modify the balance
                TradeUserMoneyLog userMoneyLog = new TradeUserMoneyLog();
                userMoneyLog.setUseMoney(mqEntity.getUserMoney());
                userMoneyLog.setMoneyLogType(ShopCode.SHOP_USER_MONEY_REFUND.getCode());
                userMoneyLog.setUserId(mqEntity.getUserId());
                userMoneyLog.setOrderId(mqEntity.getOrderId());
                userService.updateMoneyPaid(userMoneyLog);
                log.info("Balance rollback successful"); }}catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            log.error("Balance rollback failed"); }}}Copy the code

4) Cancel the order

@Override
public void onMessage(MessageExt messageExt) {
    String body = new String(messageExt.getBody(), "UTF-8");
    String msgId = messageExt.getMsgId();
    String tags = messageExt.getTags();
    String keys = messageExt.getKeys();
    log.info("CancelOrderProcessor receive message:"+messageExt);
    CancelOrderMQ cancelOrderMQ = JSON.parseObject(body, CancelOrderMQ.class);
    TradeOrder order = orderService.findOne(cancelOrderMQ.getOrderId());
    order.setOrderStatus(ShopCode.SHOP_ORDER_CANCEL.getCode());
    orderService.changeOrderStatus(order);
    log.info("Order: ["+order.getOrderId()+"] status set to Cancel");
    return order;
}
Copy the code

4.3 test

1) Prepare the test environment

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ShopOrderServiceApplication.class)
public class OrderTest {
    @Autowired
    private IOrderService orderService;
}
Copy the code

2) Prepare test data

  • User data
  • Product data
  • Coupon data

3) Test the order success process

@Test    
public void add(a){
    Long goodsId=XXXL;
    Long userId=XXXL;
    Long couponId=XXXL;

    TradeOrder order = new TradeOrder();
    order.setGoodsId(goodsId);
    order.setUserId(userId);
    order.setGoodsNumber(1);
    order.setAddress("Beijing");
    order.setGoodsPrice(new BigDecimal("5000"));
    order.setOrderAmount(new BigDecimal("5000"));
    order.setMoneyPaid(new BigDecimal("100"));
    order.setCouponId(couponId);
    order.setShippingFee(new BigDecimal(0));
    orderService.confirmOrder(order);
}
Copy the code

After execution, check the balance of the user in the database, coupon data, and order status data

4) Test the order failure process

Same code as above.

After the execution is completed, check whether the balance and coupon data of the user have changed, and whether the status of the order is cancelled.

5. Payment services

5.1 Creating a payment order

public Result createPayment(TradePay tradePay) {
    // Check the order payment status
    try {
        TradePayExample payExample = new TradePayExample();
        TradePayExample.Criteria criteria = payExample.createCriteria();
        criteria.andOrderIdEqualTo(tradePay.getOrderId());
        criteria.andIsPaidEqualTo(ShopCode.SHOP_ORDER_PAY_STATUS_IS_PAY.getCode());
        int count = tradePayMapper.countByExample(payExample);
        if (count > 0) {
            CastException.cast(ShopCode.SHOP_ORDER_PAY_STATUS_IS_PAY);
        }

        long payId = idWorker.nextId();
        tradePay.setPayId(payId);
        tradePay.setIsPaid(ShopCode.SHOP_ORDER_PAY_STATUS_NO_PAY.getCode());
        tradePayMapper.insert(tradePay);
        log.info("Payment order created successfully :" + payId);
    } catch (Exception e) {
        return new Result(ShopCode.SHOP_FAIL.getSuccess(), ShopCode.SHOP_FAIL.getMessage());
    }
    return new Result(ShopCode.SHOP_SUCCESS.getSuccess(), ShopCode.SHOP_SUCCESS.getMessage());
}
Copy the code

5.2 Payment callback

5.2.1 Process analysis

5.2.2 Code implementation

public Result callbackPayment(TradePay tradePay) {

    if (tradePay.getIsPaid().equals(ShopCode.SHOP_ORDER_PAY_STATUS_IS_PAY.getCode())) {
        tradePay = tradePayMapper.selectByPrimaryKey(tradePay.getPayId());
        if (tradePay == null) {
            CastException.cast(ShopCode.SHOP_PAYMENT_NOT_FOUND);
        }
        tradePay.setIsPaid(ShopCode.SHOP_ORDER_PAY_STATUS_IS_PAY.getCode());
        int i = tradePayMapper.updateByPrimaryKeySelective(tradePay);
        // If the update is successful, the payment is successful
        if (i == 1) {
            TradeMqProducerTemp mqProducerTemp = new TradeMqProducerTemp();
            mqProducerTemp.setId(String.valueOf(idWorker.nextId()));
            mqProducerTemp.setGroupName("payProducerGroup");
            mqProducerTemp.setMsgKey(String.valueOf(tradePay.getPayId()));
            mqProducerTemp.setMsgTag(topic);
            mqProducerTemp.setMsgBody(JSON.toJSONString(tradePay));
            mqProducerTemp.setCreateTime(new Date());
            mqProducerTempMapper.insert(mqProducerTemp);
            TradePay finalTradePay = tradePay;
            executorService.submit(new Runnable() {
                @Override
                public void run(a) {
                    try {
                        SendResult sendResult = sendMessage(topic, 
                                                            tag, 
                                                            finalTradePay.getPayId(), 
                                                            JSON.toJSONString(finalTradePay));
                        log.info(JSON.toJSONString(sendResult));
                        if (SendStatus.SEND_OK.equals(sendResult.getSendStatus())) {
                            mqProducerTempMapper.deleteByPrimaryKey(mqProducerTemp.getId());
                            System.out.println("Deletion of message table succeeded"); }}catch(Exception e) { e.printStackTrace(); }}}); }else{ CastException.cast(ShopCode.SHOP_PAYMENT_IS_PAID); }}return new Result(ShopCode.SHOP_SUCCESS.getSuccess(), ShopCode.SHOP_SUCCESS.getMessage());
}
Copy the code

Thread pool optimizes message sending logic

  • Create a thread pool object
@Bean
public ThreadPoolTaskExecutor getThreadPool(a) {

    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

    executor.setCorePoolSize(4);

    executor.setMaxPoolSize(8);

    executor.setQueueCapacity(100);

    executor.setKeepAliveSeconds(60);

    executor.setThreadNamePrefix("Pool-A");

    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

    executor.initialize();

    return executor;

}
Copy the code
  • Using thread pools
@Autowired
private ThreadPoolTaskExecutor executorService;

executorService.submit(new Runnable() {
    @Override
    public void run(a) {
        try {
            SendResult sendResult = sendMessage(topic, tag, finalTradePay.getPayId(), JSON.toJSONString(finalTradePay));
            log.info(JSON.toJSONString(sendResult));
            if (SendStatus.SEND_OK.equals(sendResult.getSendStatus())) {
                mqProducerTempMapper.deleteByPrimaryKey(mqProducerTemp.getId());
                System.out.println("Deletion of message table succeeded"); }}catch(Exception e) { e.printStackTrace(); }}});Copy the code

5.2.3 Processing messages

After successful payment, payment service payService sends MQ message, and order service, user service and log service need subscription message for processing

  1. The Order service changes the order status to paid
  2. The log service records payment logs
  3. Customer service is responsible for adding credits to users

The following uses the order service as an example to illustrate message processing

1) Configure the RocketMQ attribute value

mq.pay.topic=payTopic
mq.pay.consumer.group.name=pay_payTopic_group
Copy the code

2) Consume messages

  • In the order service, configure a common message processing class
public class BaseConsumer {

    public TradeOrder handleMessage(IOrderService orderService, MessageExt messageExt,Integer code) throws Exception {
        // Parse the message content
        String body = new String(messageExt.getBody(), "UTF-8");
        String msgId = messageExt.getMsgId();
        String tags = messageExt.getTags();
        String keys = messageExt.getKeys();
        OrderMQ orderMq = JSON.parseObject(body, OrderMQ.class);
        
        / / query
        TradeOrder order = orderService.findOne(orderMq.getOrderId());

        if(ShopCode.SHOP_ORDER_MESSAGE_STATUS_CANCEL.getCode().equals(code)){
            order.setOrderStatus(ShopCode.SHOP_ORDER_CANCEL.getCode());
        }

        if(ShopCode.SHOP_ORDER_MESSAGE_STATUS_ISPAID.getCode().equals(code)){
            order.setPayStatus(ShopCode.SHOP_ORDER_PAY_STATUS_IS_PAY.getCode());
        }
        orderService.changeOrderStatus(order);
        returnorder; }}Copy the code
  • Accept order payment success message
@Slf4j
@Component
@RocketMQMessageListener(topic = "${mq.pay.topic}", consumerGroup = "${mq.pay.consumer.group.name}")
public class PayConsumer extends BaseConsumer implements RocketMQListener<MessageExt> {

    @Autowired
    private IOrderService orderService;

    @Override
    public void onMessage(MessageExt messageExt) {
        try {
            log.info("CancelOrderProcessor receive message:"+messageExt);
            TradeOrder order = handleMessage(orderService, 
                                             messageExt, 
                                             ShopCode.SHOP_ORDER_MESSAGE_STATUS_ISPAID.getCode());
            log.info("Order: ["+order.getOrderId()+"] Payment successful");
        } catch (Exception e) {
            e.printStackTrace();
            log.error("Order payment failed"); }}}Copy the code

6. Overall alignment

The Rest client requests shop-order-web and shop-pay-web to complete the ordering and payment operations

6.1 Preparations

1) Configure the RestTemplate class

@Configuration
public class RestTemplateConfig {

    @Bean
    @ConditionalOnMissingBean({ RestOperations.class, RestTemplate.class })
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {

        RestTemplate restTemplate = new RestTemplate(factory);

        // Replace the default conver with the UTF-8 encoding set (the default string conver is "ISO-8859-1")List<HttpMessageConverter<? >> messageConverters = restTemplate.getMessageConverters(); Iterator<HttpMessageConverter<? >> iterator = messageConverters.iterator();while(iterator.hasNext()) { HttpMessageConverter<? > converter = iterator.next();if (converter instanceof StringHttpMessageConverter) {
                iterator.remove();
            }
        }
        messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));

        return restTemplate;
    }

    @Bean
    @ConditionalOnMissingBean({ClientHttpRequestFactory.class})
    public ClientHttpRequestFactory simpleClientHttpRequestFactory(a) {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        // ms
        factory.setReadTimeout(15000);
        // ms
        factory.setConnectTimeout(15000);
        returnfactory; }}Copy the code

2) Configure the request address

  • Order system
server.host=http://localhost
server.servlet.path=/order-web
server.port=8080
shop.order.baseURI=${server.host}:${server.port}${server.servlet.path}
shop.order.confirm=/order/confirm
Copy the code
  • The payment system
server.host=http://localhost
server.servlet.path=/pay-web
server.port=9090
shop.pay.baseURI=${server.host}:${server.port}${server.servlet.path}
shop.pay.createPayment=/pay/createPayment
shop.pay.callbackPayment=/pay/callbackPayment
Copy the code

6.2 Single test

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = ShopOrderWebApplication.class)
@TestPropertySource("classpath:application.properties")
public class OrderTest {

    @Autowired
    private RestTemplate restTemplate;

    @Value("${shop.order.baseURI}")
    private String baseURI;

    @Value("${shop.order.confirm}")
    private String confirmOrderPath;

    @Autowired
    private IDWorker idWorker;
   
   /** * order */
    @Test
    public void confirmOrder(a){
        Long goodsId=XXXL;
        Long userId=XXXL;
        Long couponId=XXXL;

        TradeOrder order = new TradeOrder();
        order.setGoodsId(goodsId);
        order.setUserId(userId);
        order.setGoodsNumber(1);
        order.setAddress("Beijing");
        order.setGoodsPrice(new BigDecimal("5000"));
        order.setOrderAmount(new BigDecimal("5000"));
        order.setMoneyPaid(new BigDecimal("100"));
        order.setCouponId(couponId);
        order.setShippingFee(new BigDecimal(0)); Result result = restTemplate.postForEntity(baseURI + confirmOrderPath, order, Result.class).getBody(); System.out.println(result); }}Copy the code

6.3 Payment test

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = ShopPayWebApplication.class)
@TestPropertySource("classpath:application.properties")
public class PayTest {

    @Autowired
    private RestTemplate restTemplate;

    @Value("${shop.pay.baseURI}")
    private String baseURI;

    @Value("${shop.pay.createPayment}")
    private String createPaymentPath;

    @Value("${shop.pay.callbackPayment}")
    private String callbackPaymentPath;

    @Autowired
    private IDWorker idWorker;

   /** * Create payment order */
    @Test
    public void createPayment(a){

        Long orderId = 346321587315814400L;
        TradePay pay = new TradePay();
        pay.setOrderId(orderId);
        pay.setPayAmount(new BigDecimal(4800));

        Result result = restTemplate.postForEntity(baseURI + createPaymentPath, pay, Result.class).getBody();
        System.out.println(result);
    }
   
    /** * Pay callback */
    @Test
    public void callbackPayment(a){
        Long payId = 346321891507720192L;
        TradePay pay = newTradePay(); pay.setPayId(payId); pay.setIsPaid(ShopCode.SHOP_ORDER_PAY_STATUS_IS_PAY.getCode()); Result result = restTemplate.postForEntity(baseURI + callbackPaymentPath, pay, Result.class).getBody(); System.out.println(result); }}Copy the code