zoukankan      html  css  js  c++  java
  • 【分布式】-- 基于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

  • 相关阅读:
    如何只通过Sandboxed Solution启动一个定时执行的操作
    创建与SharePoint 2010风格一致的下拉菜单 (续) 整合Feature Custom Action框架
    创建与SharePoint 2010风格一致的下拉菜单
    《SharePoint 2010 应用程序开发指南》第二章预览
    SharePoint 2013 App 开发 (1) 什么是SharePoint App?
    使用Jscex增强SharePoint 2010 JavaScript Client Object Model (JSOM)
    搜索范围的管理
    SharePoint 2010 服务应用程序(Service Application)架构(1)
    SharePoint 2010 服务应用程序(Service Application)架构(2)
    SharePoint 2013 App 开发 (2) 建立开发环境
  • 原文地址:https://www.cnblogs.com/yif0118/p/14827602.html
Copyright © 2011-2022 走看看