SpringCloudday07

weixin_47562812發表於2020-12-29

RabbitMQ和Spring boot整合

建立環境:
在這裡插入圖片描述
新建springboot工程
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

spring:
  rabbitmq:
    host: 192.168.64.140
    username: admin
    password: admin

在這裡插入圖片描述
在這裡插入圖片描述
真正的分散式系統,生產者和消費者應該部署在不同的伺服器,現在測試將兩個模組放到一起

簡單模式

package cn.tedu.rabbitmqboot.m1;

import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import javax.annotation.PostConstruct;

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }

    @Bean
    public Queue helloworldQueue() {
        // 非持久佇列,spring 的 Queue,預設是持久佇列
        return new Queue("helloworld", false);
    }

    @Autowired
    private Producer p;

    /*
    在 spring 執行完掃描,建立完所有的物件,完成所有的注入後,
    會自動執行 @PostConstruct 方法
     */
    @PostConstruct
    public void test() {
        p.send();
    }
}

package cn.tedu.rabbitmqboot.m1;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/*
* 消費者自動啟動,註冊成為消費者
* 自動開始監聽佇列中的訊息
* 不需要手動呼叫
*
*
*  @RabbitListener
*     1 在類上註解,需要 @RabbitListener 配合指定處理的方法
      2 直接加在方法上
 */
@Component
public class Consumer {
    @RabbitListener(queues = "helloworld")
    public void recieve(String msg) {
        System.out.println("收到: "+msg);
    }

}
package cn.tedu.rabbitmqboot.m1;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Producer {
    @Autowired
    private AmqpTemplate amqpTemplate;
    //需要手動呼叫,傳送訊息
    public void send(){
     //字串自動轉成byte[] 陣列,在傳送helloworld佇列
        amqpTemplate.convertAndSend("helloworld", "Hello World!!");
        System.out.println("訊息已傳送");
    }
}

工作模式

package cn.tedu.rabbitmqboot.m2;

import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import javax.annotation.PostConstruct;

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }

    @Bean
    public Queue taskQueue() {
        // spring 的 Queue,預設是持久佇列
        return new Queue("task_queue", true);
    }

    @Autowired
    private Producer p;

    /*
    在 spring 執行完掃描,建立完所有的物件,完成所有的注入後,
    會自動執行 @PostConstruct 方法
     */
    @PostConstruct
    public void test() {
        /*
        p.send()中執行一個死迴圈,會阻塞spring主執行緒的執行,
        要啟動一個新的執行緒執行 send()
         */

        // new Thread(new Runnable() {
        //     @Override
        //     public void run() {
        //         p.send();
        //     }
        // }).start();

        // lambda
        new Thread(() -> p.send()).start();

    }
}
package cn.tedu.rabbitmqboot.m2;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/*
消費者自動啟動,註冊成為消費者,
自動開始監聽佇列中的訊息。
不需要手動呼叫

@RabbitListener
    1. 在類上註解,需要 @RabbitHandler 配合指定處理訊息的方法
    2. 直接載入方法上
 */
@Component
public class Consumer {
    @RabbitListener(queues = "task_queue")
    public void recieve1(String msg) {
        System.out.println("消費者1收到: "+msg);
    }

    @RabbitListener(queues = "task_queue")
    public void recieve2(String msg) {
        System.out.println("消費者2收到: "+msg);
    }
}
package cn.tedu.rabbitmqboot.m2;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Scanner;

@Component
public class Producer {
    @Autowired
    private AmqpTemplate amqpTemplate;

    // 需要手動呼叫,傳送訊息
    public void send() {
        while (true) {
            System.out.print("輸入: ");
            String msg = new Scanner(System.in).nextLine();
            // 預設是持久訊息
            // 如果要傳送非持久訊息,新增第三個引數 MessagePostProcessor
            amqpTemplate.convertAndSend("task_queue", msg);
        }
    }
}

在這裡插入圖片描述

釋出訂閱

package cn.tedu.rabbitmqboot.m3;

import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import javax.annotation.PostConstruct;

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }

    @Bean
    public FanoutExchange logsExchange() {
        // 第二個引數持久屬性,預設是 true
        return new FanoutExchange("logs", false, false);
    }

    @Autowired
    private Producer p;
    @PostConstruct
    public void test() {
        new Thread(() -> p.send()).start();
    }
}
package cn.tedu.rabbitmqboot.m3;

import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/*
消費者自動啟動,註冊成為消費者,
自動開始監聽佇列中的訊息。
不需要手動呼叫

@RabbitListener
    1. 在類上註解,需要 @RabbitHandler 配合指定處理訊息的方法
    2. 直接載入方法上
 */
@Component
public class Consumer {
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue, //由伺服器自動命名,false,true,true
            exchange = @Exchange(name = "logs",declare = "false") //不重複定義這個交換機
    ))
    public void recieve1(String msg) {
        System.out.println("消費者1收到: "+msg);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue, //由伺服器自動命名,false,true,true
            exchange = @Exchange(name = "logs",declare = "false") //不重複定義這個交換機
    ))
    public void recieve2(String msg) {
        System.out.println("消費者2收到: "+msg);
    }
}
package cn.tedu.rabbitmqboot.m3;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Scanner;

@Component
public class Producer {
    @Autowired
    private AmqpTemplate amqpTemplate;

    // 需要手動呼叫,傳送訊息
    public void send() {
        while (true) {
            System.out.print("輸入: ");
            String msg = new Scanner(System.in).nextLine();
            // 預設是持久訊息
            // 如果要傳送非持久訊息,新增第三個引數 MessagePostProcessor
            amqpTemplate.convertAndSend("logs", "", msg);
        }
    }
}

在這裡插入圖片描述

路由模式:

package cn.tedu.rabbitmqboot.m4;

import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import javax.annotation.PostConstruct;

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }

    @Bean
    public DirectExchange logsExchange() {
        // 第二個引數持久屬性,預設是 true
        return new DirectExchange("direct_logs", false, false);
    }

    @Autowired
    private Producer p;
    @PostConstruct
    public void test() {
        new Thread(() -> p.send()).start();
    }
}
package cn.tedu.rabbitmqboot.m4;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Scanner;

@Component
public class Producer {
    @Autowired
    private AmqpTemplate amqpTemplate;

    // 需要手動呼叫,傳送訊息
    public void send() {
        while (true) {
            System.out.print("輸入: ");
            String msg = new Scanner(System.in).nextLine();
            System.out.print("輸入路由鍵: ");
            String key = new Scanner(System.in).nextLine();

            // 預設是持久訊息
            // 如果要傳送非持久訊息,新增第三個引數 MessagePostProcessor
            amqpTemplate.convertAndSend("direct_logs", key, msg);
        }
    }
}
package cn.tedu.rabbitmqboot.m4;

import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/*
消費者自動啟動,註冊成為消費者,
自動開始監聽佇列中的訊息。
不需要手動呼叫

@RabbitListener
    1. 在類上註解,需要 @RabbitHandler 配合指定處理訊息的方法
    2. 直接載入方法上
 */
@Component
public class Consumer {
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue, //由伺服器自動命名,false,true,true
            exchange = @Exchange(name = "direct_logs",declare = "false"), //不重複定義這個交換機
            key={"error"}
    ))
    public void recieve1(String msg) {
        System.out.println("消費者1收到: "+msg);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue, //由伺服器自動命名,false,true,true
            exchange = @Exchange(name = "direct_logs",declare = "false"), //不重複定義這個交換機
            key = {"info","error","warning"}
    ))
    public void recieve2(String msg) {
        System.out.println("消費者2收到: "+msg);
    }
}

在這裡插入圖片描述

主題模式

package cn.tedu.rabbitmqboot.m5;

import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import javax.annotation.PostConstruct;

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }

    @Bean
    public TopicExchange logsExchange() {
        // 第二個引數持久屬性,預設是 true
        return new TopicExchange("topic_logs");
    }

    @Autowired
    private Producer p;
    @PostConstruct
    public void test() {
        new Thread(() -> p.send()).start();
    }
}
package cn.tedu.rabbitmqboot.m5;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Scanner;

@Component
public class Producer {
    @Autowired
    private AmqpTemplate amqpTemplate;

    // 需要手動呼叫,傳送訊息
    public void send() {
        while (true) {
            System.out.print("輸入: ");
            String msg = new Scanner(System.in).nextLine();
            System.out.print("輸入路由鍵: ");
            String key = new Scanner(System.in).nextLine();

            // 預設是持久訊息
            // 如果要傳送非持久訊息,新增第三個引數 MessagePostProcessor
            amqpTemplate.convertAndSend("topic_logs", key, msg);
        }
    }
}
package cn.tedu.rabbitmqboot.m5;

import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/*
消費者自動啟動,註冊成為消費者,
自動開始監聽佇列中的訊息。
不需要手動呼叫

@RabbitListener
    1. 在類上註解,需要 @RabbitHandler 配合指定處理訊息的方法
    2. 直接載入方法上
 */
@Component
public class Consumer {
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue, //由伺服器自動命名,false,true,true
            exchange = @Exchange(name = "topic_logs",declare = "false"), //不重複定義這個交換機
            key={"*.orange.*"}
    ))
    public void recieve1(String msg) {
        System.out.println("消費者1收到: "+msg);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue, //由伺服器自動命名,false,true,true
            exchange = @Exchange(name = "topic_logs",declare = "false"), //不重複定義這個交換機
            key = {"*.*.rabbit","lazy.#"}
    ))
    public void recieve2(String msg) {
        System.out.println("消費者2收到: "+msg);
    }
}

在這裡插入圖片描述

分散式事務

在這裡插入圖片描述
在微服務系統中,每個微服務應用都可能會有自己的資料庫,它們首先需要控制自己的本地事務。

一項業務操作可能會呼叫執行多個微服務。如何保證多個服務執行的多個資料庫的操作整體成功或整體失敗?這就是分散式事務要解決的問題。

在這裡插入圖片描述
資料庫初始化工具db-init ,seata控制分散式事務
在這裡插入圖片描述

分散式事務案例

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost/?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
    username: root
    password: root

seata-server.sql

drop database if exists `seata`;

CREATE DATABASE `seata` CHARSET utf8;
use `seata`;

-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid`                       VARCHAR(128) NOT NULL,
`transaction_id`            BIGINT,
`status`                    TINYINT      NOT NULL,
`application_id`            VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name`          VARCHAR(128),
`timeout`                   INT,
`begin_time`                BIGINT,
`application_data`          VARCHAR(2000),
`gmt_create`                DATETIME,
`gmt_modified`              DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;

-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id`         BIGINT       NOT NULL,
`xid`               VARCHAR(128) NOT NULL,
`transaction_id`    BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id`       VARCHAR(256),
`branch_type`       VARCHAR(8),
`status`            TINYINT,
`client_id`         VARCHAR(64),
`application_data`  VARCHAR(2000),
`gmt_create`        DATETIME(6),
`gmt_modified`      DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key`        VARCHAR(128) NOT NULL,
`xid`            VARCHAR(96),
`transaction_id` BIGINT,
`branch_id`      BIGINT       NOT NULL,
`resource_id`    VARCHAR(256),
`table_name`     VARCHAR(32),
`pk`             VARCHAR(36),
`gmt_create`     DATETIME,
`gmt_modified`   DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;

order.sql

drop database if exists `seata_order`;

CREATE DATABASE `seata_order` charset utf8;

use `seata_order`;


CREATE TABLE `order` (
  `id` bigint(11) NOT NULL,
  `user_id` bigint(11) DEFAULT NULL COMMENT '使用者id',
  `product_id` bigint(11) DEFAULT NULL COMMENT '產品id',
  `count` int(11) DEFAULT NULL COMMENT '數量',
  `money` decimal(11,0) DEFAULT NULL COMMENT '金額',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

ALTER TABLE `order` ADD COLUMN `status` int(1) DEFAULT NULL COMMENT '訂單狀態:0:建立中;1:已完結' AFTER `money` ;

-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT(20)   NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(100) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';

CREATE TABLE IF NOT EXISTS segment
(
    id            BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '自增主鍵',
    VERSION       BIGINT      DEFAULT 0  NOT NULL COMMENT '版本號',
    business_type VARCHAR(63) DEFAULT '' NOT NULL COMMENT '業務型別,唯一',
    max_id        BIGINT      DEFAULT 0  NOT NULL COMMENT '當前最大id',
    step          INT         DEFAULT 0  NULL COMMENT '步長',
    increment     INT         DEFAULT 1  NOT NULL COMMENT '每次id增量',
    remainder     INT         DEFAULT 0  NOT NULL COMMENT '餘數',
    created_at    BIGINT UNSIGNED        NOT NULL COMMENT '建立時間',
    updated_at    BIGINT UNSIGNED        NOT NULL COMMENT '更新時間',
    CONSTRAINT uniq_business_type UNIQUE (business_type)
) CHARSET = utf8mb4
  ENGINE INNODB COMMENT '號段表';


INSERT INTO segment
(VERSION, business_type, max_id, step, increment, remainder, created_at, updated_at)
VALUES (1, 'order_business', 1000, 1000, 1, 0, NOW(), NOW());

storage.sql

drop database  if exists `seata_storage`;

CREATE DATABASE `seata_storage` charset utf8;

use `seata_storage`;


CREATE TABLE `storage` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT,
  `product_id` bigint(11) DEFAULT NULL COMMENT '產品id',
  `total` int(11) DEFAULT NULL COMMENT '總庫存',
  `used` int(11) DEFAULT NULL COMMENT '已用庫存',
  `residue` int(11) DEFAULT NULL COMMENT '剩餘庫存',
  `frozen` int(11) DEFAULT '0' COMMENT 'TCC事務鎖定的庫存',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

INSERT INTO `seata_storage`.`storage` (`id`, `product_id`, `total`, `used`, `residue`) VALUES ('1', '1', '100', '0', '100');

-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT(20)   NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(100) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';

account.sql

drop database  if exists `seata_account`;

CREATE DATABASE `seata_account` charset utf8;

use `seata_account`;

CREATE TABLE `account` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `user_id` bigint(11) UNIQUE DEFAULT NULL COMMENT '使用者id',
  `total` decimal(10,0) DEFAULT NULL COMMENT '總額度',
  `used` decimal(10,0) DEFAULT NULL COMMENT '已用餘額',
  `residue` decimal(10,0) DEFAULT '0' COMMENT '剩餘可用額度',
  `frozen` decimal(10,0) DEFAULT '0' COMMENT 'TCC事務鎖定的金額',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

INSERT INTO `seata_account`.`account` (`id`, `user_id`, `total`, `used`, `residue`) VALUES ('1', '1', '1000', '0', '1000');

-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT(20)   NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(100) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';

建立sql資料夾
在這裡插入圖片描述

主程式中新增程式碼,執行sql指令碼

Spring 中提供了一個 jdbc 指令碼執行器,使用這個工具可以非常方便的執行一個 sql 指令碼檔案,下面是這個方法:

ScriptUtils.executeSqlScript()

只需要傳入它需要的引數即可。

下面程式碼執行 sql 目錄中的四個指令碼程式,每次執行都會刪除四個資料庫再重新建立,並初始化資料。

package cn.tedu.dbinit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.jdbc.datasource.init.ScriptUtils;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.sql.SQLException;

@SpringBootApplication
public class DbInitApplication {

	@Autowired
	private DataSource dataSource;

	public static void main(String[] args) {
		SpringApplication.run(DbInitApplication.class, args);
	}

	@PostConstruct
	public void init() throws SQLException {
		exec(dataSource, "sql/account.sql");
		exec(dataSource, "sql/storage.sql");
		exec(dataSource, "sql/order.sql");
		exec(dataSource, "sql/seata-server.sql");
	}

	private void exec(DataSource accountDatasource, String script) throws SQLException {
		ClassPathResource rc = new ClassPathResource(script, DbInitApplication.class.getClassLoader());
		EncodedResource er = new EncodedResource(rc, "utf-8");
		ScriptUtils.executeSqlScript(accountDatasource.getConnection(), er);
	}
}


package cn.tedu.dbinit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.jdbc.datasource.init.ScriptUtils;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.sql.SQLException;

@SpringBootApplication
public class DbInitApplication {

	public static void main(String[] args) {
		SpringApplication.run(DbInitApplication.class, args);
	}


	@Autowired
	private DataSource dataSource;
	@PostConstruct
	public void runSql() throws SQLException {
		run("sql/account.sql");
		run("sql/order.sql");
		run("sql/seata-server.sql");
		run("sql/storage.sql");

	}
	private void run(String sql) throws SQLException {
		ClassPathResource rc=new ClassPathResource(sql, DbInitApplication.class.getClassLoader());
		EncodedResource er = new EncodedResource(rc, "UTF-8");
		ScriptUtils.executeSqlScript(dataSource.getConnection(), er);
	}


}

執行程式資料庫出現響應檔案
在這裡插入圖片描述

註冊中心

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

spring:
  application:
    name: eureka-server

server:
  port: 8761

eureka:
  server:
    enable-self-preservation: false
  instance:
    hostname: eureka1
  client:
    register-with-eureka: false
    fetch-registry: false
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

	public static void main(String[] args) {
		SpringApplication.run(EurekaServerApplication.class, args);
	}

}

測試:localhost:8761
在這裡插入圖片描述
訂單,庫存,賬戶從父專案中繼承
新建maven專案
在這裡插入圖片描述

<?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 http://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.3.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.tedu</groupId>
    <artifactId>order-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>order-parent</name>


    <properties>
        <mybatis-plus.version>3.3.2</mybatis-plus.version>
        <druid-spring-boot-starter.version>1.1.23</druid-spring-boot-starter.version>
        <seata.version>1.3.0</seata.version>
        <spring-cloud-alibaba-seata.version>2.0.0.RELEASE</spring-cloud-alibaba-seata.version>
        <spring-cloud.version>Hoxton.SR6</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid-spring-boot-starter.version}</version>
        </dependency>
        <!--<dependency>-->
        <!--  <groupId>com.alibaba.cloud</groupId>-->
        <!--  <artifactId>spring-cloud-alibaba-seata</artifactId>-->
        <!--  <version>${spring-cloud-alibaba-seata.version}</version>-->
        <!--  <exclusions>-->
        <!--    <exclusion>-->
        <!--      <artifactId>seata-all</artifactId>-->
        <!--      <groupId>io.seata</groupId>-->
        <!--    </exclusion>-->
        <!--  </exclusions>-->
        <!--</dependency>-->
        <!--<dependency>-->
        <!--  <groupId>io.seata</groupId>-->
        <!--  <artifactId>seata-all</artifactId>-->
        <!--  <version>${seata.version}</version>-->
        <!--</dependency>-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </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>

在這裡插入圖片描述

account賬戶專案

在這個微服務專案中實現扣減賬戶金額的功能。

在這裡插入圖片描述
在這裡插入圖片描述

<?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">
    <parent>
        <artifactId>order-parent</artifactId>
        <groupId>cn.tedu</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.tedu</groupId>
    <artifactId>account</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>account</name>
    <description>Demo project for Spring Boot</description>
</project>

spring:
  application:
    name: account
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost/seata_account?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
    username: root
    password: root

server:
  port: 8081

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    prefer-ip-address: true

mybatis-plus:
  type-aliases-package: cn.tedu.account.entity
  mapper-locations:
    - classpath:mapper/*Mapper.xml
  configuration:
    map-underscore-to-camel-case: true

logging:
  level:
    cn.tedu.account.mapper: DEBUG

在這裡插入圖片描述

package cn.tedu.eurekaserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

	public static void main(String[] args) {
		SpringApplication.run(EurekaServerApplication.class, args);
	}

}

package cn.tedu.account.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
    private Long id;
    private Long userId;
    private BigDecimal total;   //總金額
    private BigDecimal used;   //以用金額
    private BigDecimal residue;//剩餘金額
    private BigDecimal frozen;//凍結金額

}

package cn.tedu.account.mapper;

import cn.tedu.account.entity.Account;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

import java.math.BigDecimal;


public interface AccountMapper extends BaseMapper<Account> {

    void decrease(Long userId, BigDecimal money);


}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="cn.tedu.account.mapper.AccountMapper" >
    <resultMap id="BaseResultMap" type="cn.tedu.account.entity.Account" >
        <id column="id" property="id" jdbcType="BIGINT" />
        <result column="user_id" property="userId" jdbcType="BIGINT" />
        <result column="total" property="total" jdbcType="DECIMAL" />
        <result column="used" property="used" jdbcType="DECIMAL" />
        <result column="residue" property="residue" jdbcType="DECIMAL"/>
        <result column="frozen" property="frozen" jdbcType="DECIMAL"/>
    </resultMap>
    <update id="decrease">
    UPDATE account SET residue = residue - #{money},used = used + #{money} where user_id = #{userId};
  </update>
</mapper>

package cn.tedu.account.controller;

import cn.tedu.account.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;

@RestController
public class AccountController {

    @Autowired
    private AccountService accountService;
    @GetMapping("/decrease")
    public String decrease(Long userId, BigDecimal money){
        accountService.decrease(userId, money);
        return "扣減賬戶金額成功!!!";
    }


}

訪問測試
在這裡插入圖片描述

http://localhost:8081/decrease?userId=1&money=100

在這裡插入圖片描述
在這裡插入圖片描述

Eureka客戶端向伺服器註冊正確IP

在這裡插入圖片描述
1.選擇網路卡
2.註冊ip,而不註冊主機名

1.選擇網路卡
配置bootstrap.yml檔案

spring:
  cloud:
    inetutils:
      ignored-interfaces: # 忽略的網路卡
        - VM.*
      preferred-networks: # 要是用的網路卡的網段
        - 192\.168\.7\..*

測試頁面:
在這裡插入圖片描述
account的yml檔案


spring:
  application:
    name: account
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3307/seata_account?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
    username: root
    password: root

server:
  port: 8081

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
      prefer-ip-address: true # 使用ip進行註冊
      instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} # 介面列表中顯示的格式也顯示ip

mybatis-plus:
  type-aliases-package: cn.tedu.account.entity
  mapper-locations:
    - classpath:mapper/*Mapper.xml
  configuration:
    map-underscore-to-camel-case: true

logging:
  level:
    cn.tedu.account.mapper: DEBUG

在這裡插入圖片描述

在這裡插入圖片描述