VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > 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
 View Code

 

springcloudnacos-provider-product6700

 View Code

springcloudnacos-consumer-product7800

 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接口:

 View Code

springcloudnacos-consumer-product7800

服务消费方Servcie Feign接口:

 View Code

暴露给Swagger2访问的Controller外部接口:

 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);
    }
复制代码

这样接口的实现就完整了。
 

来源:https://www.cnblogs.com/yif0118/p/14827602.html

相关教程