【分布式】-- 基於Nacos、OpenFeign搭建的微服務抽獎系統后台小案例


1.項目介紹

最近入項目之前要求熟悉一下SpringCloud Nacos微服務基於Feign接口調用並整合Swagger2進行接口文檔展示給前端,所以自己按照要求來編寫並整合了一套基於SpringCloudAlibaba NacosFeignMyBatisSwagger2的簡單微服務抽獎系統,並結合數據庫數據進行數據返回。

框架提供了基礎的微服務注冊與發現,接口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>
View Code

 

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>
View Code

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>
View Code

 

③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/",
                        "hyfmailsave@163.com"))
                .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
        );
    }
}
View Code

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();
}
View Code

暴露給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;
    }
}
View Code

 

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來計算真實的抽獎概率,本人這里實現的比較簡單,具體的抽獎概率可以

根據實際情況進行優化,也歡迎博友提出相對應的算法建議。

 

 2.4.抽獎接口(離散算法實現)

最終晚上把抽獎接口的算法進行了實現,使用的是正態分布的離散算法,算法參考博文:抽獎概率--三種算法

下面是這種算法的具體Java代碼實現:

/**
  * 抽獎接口二:離散算法,具有較好的正態分布隨機性
  * 竟然1-20都是靴子,21-45都是披風,那抽象成小於等於20的是靴子,大於20且小於等於45是披風,
  * 就變成幾個點[20,45,55,60,100],然后也是從1到99隨機取一個數R,按順序在這些點進行比較,
  * 知道找到第一個比R大的數的下標,比一般算法減少占用空間,
  * 還可以采用二分法找出R,這樣,預處理O(N),隨機數生成O(logN),空間復雜度O(N)
  * @param products
  * @return
  */
 public static Product discreteDraw(List<Product> products){
     List<Integer> integers = products.stream()
             .map(product -> new BigDecimal(product.getWinRate() * 100).intValue())
             .collect(Collectors.toList());
     //1.划分區間,將概率划分為與概率值對應的幾個點的概率區間
     Integer[] arr = new Integer[integers.size()];
     for (int i = 0; i < integers.size(); i++) {
         int sum = 0;
         for (int j = 0; j < i+1; j++) {
             sum += integers.get(j);
         }
         arr[i] = sum;
     }
     //2.最后arr就變成了0-100對應的商品的概率區間,如:[20,45,55,60,100]
     System.out.println("原抽獎商品的概率(%)為:" + integers);
     integers.forEach(System.out::println);
     //3.從1到99隨機取一個數R,按照順序在對這些點進行比較,找出第一個比R大的數的下標,這個下標對應就是數組中
     //的抽到的商品的下標值
     int probabilityCount = 100;  //產生1-100的下標值
     int randomNum = (int) (Math.random()* probabilityCount);  //生成0-100的隨機數
     int maxIndex = getMaxIndex(arr, randomNum);
     //4.根據索引值去查詢出中獎商品
     Product target = maxIndex == -1 ? null : products.get(maxIndex);
     return target;
 }

 /**
  *
  * @param arr  傳入的分區后的0-100區間的概率數組arr
  * @param randomNum  隨機數
  * @return  成功返回索引值,不成功返回-1
  */
 private static int getMaxIndex(Integer[] arr, int randomNum) {
     for (int index = 0; index < arr.length; index++) {
         if(arr[index] >= randomNum){
             return index;
         }
     }

     return -1;
 }

另外在新增抽獎商品的接口上也增加了插入判斷概率是否大於1的容錯處理:

 /**
     * 添加商品接口:使用Product參數進行JSON數據插入傳遞參數
     *
     * @param product
     * @return
     */
    @PostMapping(value = "/provider/insert")
    public Integer insertProduct(@RequestBody Product product) {
        int sum = productService.getProductList()
                .stream()
                .map(p -> new BigDecimal(p.getWinRate() * 100).intValue()).mapToInt(p -> p).sum();
        int pVal = new BigDecimal(product.getWinRate() * 100).intValue();
        int newRes = sum + pVal;
        //結果相加大於1,概率超過100%,返回-2,表示概率超過限制
        if((pVal + newRes) > 100){
            return -2;
        }
        return productService.insertProduct(product);
    }

這樣接口的實現就完整了。

 

博客示例及相關代碼已上傳至GitHub:

LotteryDraw

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM