1.專案介紹
最近入專案之前要求熟悉一下SpringCloud Nacos微服務基於Feign介面呼叫並整合Swagger2進行介面文件展示給前端,所以自己按照要求來編寫並整合了一套基於SpringCloudAlibaba Nacos、Feign、MyBatis、Swagger2的簡單微服務抽獎系統,並結合資料庫資料進行資料返回。
框架提供了基礎的微服務註冊與發現,介面Swagger訪問、MyBatis註解實現介面Dao層資料訪問,可用於快速搭建一個微服務CRUD基礎框架。
抽獎介面主要包含:新增商品介面(包含商品名稱和中獎率);抽獎介面,對新增的商品進行抽獎返回中獎商品的功能。
1.1.專案框架搭建
①專案主要結構說明:
- common-api模組:用於存放公共的pojo類、介面返回值列舉類、公共的抽獎函式
- consumer-product7800模組:服務消費方
- provider-product6700模組:服務提供方
②pom.xml依賴說明:
父工程主要pom依賴包:
其中主要的pom依賴有
spring-cloud-alibaba-dependencies
mybatis-spring-boot-starter
lombok
springfox-swagger2
swagger-bootstrap-ui


<!--統一管理jar包版本-->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<springboot.test.version>2.5.0</springboot.test.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
<mysql.version>6.0.6</mysql.version>
<druid.version>1.1.16</druid.version>
<mybatis.spring.boot.version>2.1.4</mybatis.spring.boot.version>
<springboot.starter.web>2.4.3</springboot.starter.web>
</properties> <!--子模組繼承之後,提供作用:鎖定版本+子module不用寫groupId和version-->
<dependencyManagement>
<dependencies>
<!--spring boot 2.2.2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--springcloud Hoxton.SR1-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--springcloud alibaba 2.1.0.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--mysql資料庫連線驅動包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--web依賴包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${springboot.starter.web}</version>
</dependency>
<!--alibaba druid資料庫-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!--mybatis orm框架-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.version}</version>
</dependency>
<!--lombok外掛引入-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!--熱啟動部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>2.1.10.RELEASE</version>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--測試依賴包引入-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${springboot.test.version}</version>
<scope>test</scope>
</dependency>
<!--log4j日誌包-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<!--swagger2依賴-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<!--swagger第三方ui依賴-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency>
</dependencies>
</dependencyManagement> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--排除lombok jar在打包編譯期間-->
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<!--引入mybatis逆向工程外掛-->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<configuration>
<configurationFile>${basedir}/src/main/resources/mybatis-generator/generatorConfig.xml
</configurationFile>
<overwrite>true</overwrite>
<verbose>true</verbose>
</configuration>
</plugin>
</plugins>
</build>
springcloudnacos-provider-product6700


<dependencies>
<!--SpringCloud alibaba nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--SpringBoot整合Web元件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--服務註冊與發現-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency> <!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--api jar包引入-->
<dependency>
<groupId>com.fengye</groupId>
<artifactId>springcloudnacos-common-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--集合判空工具類-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.3</version>
</dependency>
<!--引入Swagger2元件-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!--mysql連線驅動包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--引入ui包-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<!--swagger第三方ui依賴-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency>
</dependencies>
springcloudnacos-consumer-product7800


<dependencies>
<!--springcloud openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--sentinel熔斷限流-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--SpringCloud alibaba nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--引入自定義的api通用包-->
<dependency>
<groupId>com.fengye</groupId>
<artifactId>springcloudnacos-common-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--SpringBoot整合Web元件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--引入Swagger2元件-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!--引入ui包-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<!--swagger第三方ui依賴-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.3</version>
</dependency>
</dependencies>
③application.yml配置
重點主要是在服務生產方provider-product6700進行資料庫連線、mybatis框架、nacos服務註冊相關的配置,具體如下:
server:
port: 6700 spring:
application:
name: nacos-product-provider-6700
cloud:
nacos:
discovery:
server-addr: localhost:8848
#資料庫連線池配置
datasource:
username: root
password: admin
#假如時區報錯,增加時區配置serverTimezone=UTC
url: jdbc:mysql://localhost:3306/nacosproduct?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver #整合mybatis配置
mybatis:
#config-location: classpath:mybatis/mybatis-config.xml 使用了configuration註解則無需再指定mybatis-config.xml檔案
mapper-locations: classpath:mybatis/mapper/*.xml
configuration: #指定mybatis全域性配置檔案中的相關配置項
map-underscore-to-camel-case: true
type-aliases-package: com.fengye.springcloud.entities #消費者將要去訪問的微服務名稱
server-url:
nacos-user-service: http://nacos-product-provider
1.2.專案分包結構說明
以一個服務提供方6700來說,就是簡單地controller、mapper、service/impl,mapper層使用xml與註解結合的方式都可以。這裡不再多說。
而在服務消費方7800來說,主要分為Feign介面呼叫與Swagger2Config配置類:
2.Swagger2/Feign介面/抽獎介面說明
2.1.Swagger2配置類
①這裡主要的就是在專案中會引入Swagger2的依賴包,以及基於國人UI風格的jar包。
<!--引入Swagger2元件-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!--swagger第三方ui依賴-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency>
②編寫Swagger2Config配置類:
//將此類交給Spring管理,表示一個配置類
@Configuration
//開啟Swagger2
@EnableSwagger2
public class Swagger2Config {
/**
* 建立API應用
* apiInfo() 增加API相關資訊
* 通過select()函式返回一個ApiSelectorBuilder例項,用來控制哪些介面暴露給Swagger來展現,
* 本例採用指定掃描的包路徑來定義指定要建立API的目錄
*
* @return 返回Swagger的Docket配置Bean例項
*/
@Bean
public Docket createRestApi(Environment environment) {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(true) //enable是否啟動swagger,如果為False,則swagger不能在瀏覽器中訪問
.select()
//指定API物件掃描哪個包下面的controller
//引數any():掃描全部; none():都不掃描
//withClassAnnotation:掃描類上的註解,引數是一個註解的反射物件
//withMethodAnnotation:掃描方法上的註解
.apis(RequestHandlerSelectors.basePackage("com.fengye.springcloud"))
//過濾什麼路徑
.paths(PathSelectors.any())
.build();
} /**
* 建立該API的基本資訊(這些基本資訊會展現在文件頁面中)
* 訪問地址:http://專案實際地址/swagger-ui.html
* @return 返回API基本資訊
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
//Swagger2展示介面的標題(重要)
.title("抽獎介面API文件")
//描述資訊(主要)
.description("抽獎介面API文件")
.version("1.0")
//.termsOfServiceUrl("https://swagger.io/docs")
//.license("Apache 2.0")
//.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0")
//作者資訊
.contact(new Contact("fengye", "https://www.cnblogs.com/yif0118/",
"[email protected]"))
.build();
}
}
啟動整體的專案之後,訪問:http://localhost:7800/doc.html,即可看到具體的UI風格的API文件介面:
2.2.Feign介面請求
Feign的介面請求呼叫方式主要是基於服務提供方的Controller介面,並在服務消費方編寫一個基於Controller介面一樣的Service介面層,根據服務名及對應一致的方法名進行呼叫。
springcloudnacos-provider-product6700
服務提供方Controller介面:


@RestController
@Slf4j
public class ProductController {
@Autowired
private ProductService productService; /**
* 測試Nacos資料介面
*
* @return
*/
@GetMapping(value = "/provider/test")
public CommonResult<Product> getProductTest() {
CommonResult<Product> result = new CommonResult<>();
result.setCode(ResultCodeEnum.SUCCESS.getCode());
result.setData(new Product(1, "iphone12Max", (float) 0.05));
result.setException(null);
result.setMsg("測試資料介面");
result.setUrl(null);
result.setSuccess(true);
return result;
} /**
* 根據id查詢商品返回商品介面
*
* @param id
* @return
*/
@GetMapping(value = "/provider/product/{id}")
public CommonResult<Product> getProductById(@PathVariable("id") Integer id) {
Product product = productService.getProductById(id);
if (product != null) {
return new CommonResult<Product>(
ResultCodeEnum.SUCCESS.getCode(),
product,
null,
"根據id查詢商品資訊成功!商品名稱為:" + product,
true,
null);
} return new CommonResult<Product>(
ResultCodeEnum.DATA_NOT_FOUND.getCode(),
null,
null,
"根據id查詢商品資訊失敗!",
false,
null);
} /**
* 獲取所有商品
*
* @return
*/
@GetMapping(value = "/provider/getAll")
public CommonResult<List<Product>> getAllProducts() {
List<Product> productList = productService.getProductList();
if (CollectionUtils.isEmpty(productList)) {
return new CommonResult<>(
ResultCodeEnum.DATA_NOT_FOUND.getCode(),
null,
null,
"查詢商品列表資訊失敗!",
false,
null
);
}
return new CommonResult<>(
ResultCodeEnum.SUCCESS.getCode(),
productList,
null,
"查詢商品資訊成功,所得到的的商品列表為:" + productList,
true,
null
);
} /**
* 新增商品介面:使用Product引數進行JSON資料插入傳遞引數
*
* @param product
* @return
*/
@PostMapping(value = "/provider/insert")
public Integer insertProduct(@RequestBody Product product) {
return productService.insertProduct(product);
} /**
* 抽獎介面:根據抽獎次數及抽獎商品的概率進行返回抽中的商品
*
* @return
*/
@GetMapping(value = "/provider/luckyDraw")
public CommonResult<Product> luckyDrawProduct() {
List<Product> productList = productService.getProductList();
Product product = LotteryUtil.luckyDraw(productList);
if (product == null) {
return new CommonResult<>(
ResultCodeEnum.PARAMS_NULL.getCode(),
null,
null,
"未抽取到商品,謝謝惠顧!",
false,
null
);
} return new CommonResult<>(
ResultCodeEnum.SUCCESS.getCode(),
product,
null,
"抽獎商品獲取成功!抽到的商品名稱為:" + product.getProductName(),
true,
null
);
}
}
springcloudnacos-consumer-product7800
服務消費方Servcie Feign介面:


@Component
@FeignClient(value = "nacos-product-provider-6700")
public interface ProductFeignService {
//測試整合Nacos服務介面
@GetMapping(value = "/provider/test")
public CommonResult<Product> getProductTest(); //介面名與url地址與服務生產者介面名稱相同
@GetMapping(value = "/provider/product/{id}")
public CommonResult<Product> getProductById(@PathVariable("id") Integer id); //獲取所有的商品資料
@GetMapping(value = "/provider/getAll")
public CommonResult<List<Product>> getAllProducts(); //編寫一個新增商品介面:包含商品名稱和中獎率
@PostMapping(value = "/provider/insert")
public CommonResult<Product> insertProduct(@RequestBody Product product); //編寫一個抽獎介面,對新增的商口進行抽獎返回中獎商品
@GetMapping(value = "/provider/luckyDraw")
public CommonResult<Product> luckyDrawProduct();
}
暴露給Swagger2訪問的Controller外部介面:


@RestController
@Api(value = "抽獎介面演示",description = "SpringCloud Nacos測試API介面")
public class ProductConsumerController {
@Value("${server.port}")
private String serverPort; @Autowired
private ProductFeignService productFeignService; @ApiOperation(value = "獲取所有抽獎商品資訊", notes = "獲取所有抽獎商品getAllProducts介面")
@GetMapping(value = "/consumer/getAll")
public CommonResult<List<Product>> getAllProducts(){
CommonResult<List<Product>> allProducts = productFeignService.getAllProducts();
String requestUrl = String.format("http://localhost:%s/consumer/getAll", serverPort);
allProducts.setUrl(requestUrl);
return allProducts;
} @ApiOperation(value = "獲取商品測試test介面", notes = "test介面")
@GetMapping(value = "/test")
public CommonResult<Product> getProductTest(){
CommonResult<Product> productTest = productFeignService.getProductTest();
String requestUrl = String.format("http://localhost:%s/test", serverPort);
productTest.setUrl(requestUrl);
return productTest;
} @RequestMapping(value = "/consumer/getProductById/{id}")
@ApiOperation(value = "獲取對應id商品介面", notes = "根據商品id獲取商品資訊")
@ApiImplicitParams({
@ApiImplicitParam(paramType="query", name = "id", value = "商品id", required = true, dataType = "Integer"),
})
public CommonResult<Product> getProductById(@PathVariable("id") Integer id){
CommonResult<Product> productRes = productFeignService.getProductById(id);
String requestUrl = String.format("http://localhost:%s/consumer/getProductById/%s", serverPort, id);
productRes.setUrl(requestUrl);
return productRes;
} @PostMapping(value = "/consumer/insert")
@ApiOperation(value = "插入抽獎商品insert介面", notes = "插入商品介面,包含商品資訊、商品抽獎概率")
@ApiImplicitParams({
@ApiImplicitParam(paramType="insert", name = "id", value = "商品", required = true, dataType = "Product"),
})
public CommonResult<Product> insertProduct(@RequestBody Product product){
CommonResult<Product> result = productFeignService.insertProduct(product);
String requestUrl = String.format("http://localhost:%s//consumer/insert", serverPort);
result.setUrl(requestUrl);
return result;
} //編寫一個抽獎介面,對新增的商口進行抽獎返回中獎商品
@GetMapping(value = "/consumer/luckyDraw")
@ApiOperation(value = "抽獎介面", notes = "抽獎介面,根據概率返回中獎商品")
public CommonResult<Product> luckyDrawProduct(){
CommonResult<Product> commonRes = productFeignService.luckyDrawProduct();
String requestUrl = String.format("http://localhost:%s//consumer/luckyDraw", serverPort);
commonRes.setUrl(requestUrl);
return commonRes;
}
}
2.3.抽獎介面實現
主要用到的抽獎商品類:
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel("使用者實體類")
public class Product
{
@ApiModelProperty("主鍵id")
private Integer id; //主鍵id
@ApiModelProperty("商品名稱")
private String productName; //商品名稱
@ApiModelProperty("中獎率")
private float winRate; //中獎率 -- 請使用者輸入小數點後兩位
}
公共介面返回值封裝類:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
private Integer code;
private T data;
private String exception;
private String msg;
private boolean success;
private String url;
}
返回結果列舉類:
@Getter
public enum ResultCodeEnum {
/**
* 返回結果列舉,每個列舉代表著一個狀態
*/
SUCCESS(200, "操作成功!"),
ERROR(400, "操作失敗!"),
DATA_NOT_FOUND(401, "查詢失敗!"), PARAMS_NULL(402, "引數不能為空!"), PARAMS_ERROR(405, "引數不合法!"), NOT_LOGIN(403, "當前賬號未登入!"); private Integer code;
private String msg; ResultCodeEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}
主要的抽獎介面實現工具類:
public class LotteryUtil {
/**
* 抽獎設計介面:
* 產生一個隨機數,0-5為一等獎商品,6-15為二等獎商品,16-40為三等獎商品,41-100為謝謝惠顧
* 在比較的時候,比較隨機數(百分比)與獲取商品的概率(百分比)的絕對值,40%以下的才中獎
* 之後計算隨機數與中獎概率的絕對值,選擇絕對值相差最小的那個為中獎商品
* @param products
* @return
*/
public static Product luckyDraw(List<Product> products) {
//1.產生一個隨機數
int probabilityCount = 100;
int randomNum = (int) (Math.random()* probabilityCount);
//2.41-100表示不中獎
if(randomNum > 40){
return null;
} Map<String, Product> map = new HashMap<>();
List<Integer> list = new ArrayList<>();
for (Product product : products) {
int intValue = new BigDecimal(product.getWinRate() * 100).intValue();
int absVal = Math.abs(randomNum - intValue);
list.add(absVal);
}
Integer min = Collections.min(list); for (Product product : products) {
int value = new BigDecimal(product.getWinRate() * 100).intValue();
if(Math.abs(randomNum - value) == min){
return product;
}
}
return null;
}
}
Nacos微服務註冊中心:
使用Swagger介面測試返回中獎結果:
抽獎演算法需求:抽獎介面按照新增商品介面的名稱和中獎率進行抽獎返回中獎商品,中獎率小於等於40%。即有可能按實際概率來抽獎返回不中獎情況。
抽獎演算法這裡實際的情況應該是按照插入獎品的實際概率0.15來計算真實的抽獎概率,本人這裡實現的比較簡單,具體的抽獎概率可以
根據實際情況進行優化,也歡迎博友提出相對應的演算法建議。
部落格示例及相關程式碼已上傳至GitHub: