• 【分布式】-- 基于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

  • 相关阅读:
    java cp命令
    Ubuntu相关IP配置(转)
    (转)Linux操作系统下VMware的多网卡桥接转换
    Linux问题FAQ1
    hadoop运行常见问题FAQ
    hadoop运行故障问题解决1——datanode节点启动后自动关闭
    Java程序设计9——泛型
    一道灵活的css笔试题
    inherit与auto
    再谈visibility:hidden和display:none
  • 原文地址:https://www.cnblogs.com/yif0118/p/14827602.html
Copyright © 2020-2023  润新知