1.项目介绍
最近入项目之前要求熟悉一下SpringCloud Nacos微服务基于Feign接口调用并整合Swagger2进行接口文档展示给前端,所以自己按照要求来编写并整合了一套基于SpringCloudAlibaba Nacos、Feign、MyBatis、Swagger2的简单微服务抽奖系统,并结合数据库数据进行数据返回。
框架提供了基础的微服务注册与发现,接口Swagger访问、MyBatis注解实现接口Dao层数据访问,可用于快速搭建一个微服务CRUD基础框架。
抽奖接口主要包含:添加商品接口(包含商品名称和中奖率);抽奖接口,对添加的商品进行抽奖返回中奖商品的功能。
1.1.项目框架搭建
①项目主要结构说明:
- common-api模块:用于存放公共的pojo类、接口返回值枚举类、公共的抽奖函数
- consumer-product7800模块:服务消费方
- provider-product6700模块:服务提供方
②pom.xml依赖说明:
父工程主要pom依赖包:
其中主要的pom依赖有
spring-cloud-alibaba-dependencies
mybatis-spring-boot-starter
lombok
springfox-swagger2
swagger-bootstrap-ui
<!--统一管理jar包版本--> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <junit.version>4.12</junit.version> <springboot.test.version>2.5.0</springboot.test.version> <log4j.version>1.2.17</log4j.version> <lombok.version>1.16.18</lombok.version> <mysql.version>6.0.6</mysql.version> <druid.version>1.1.16</druid.version> <mybatis.spring.boot.version>2.1.4</mybatis.spring.boot.version> <springboot.starter.web>2.4.3</springboot.starter.web> </properties> <!--子模块继承之后,提供作用:锁定版本+子module不用写groupId和version--> <dependencyManagement> <dependencies> <!--spring boot 2.2.2--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.2.2.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!--springcloud Hoxton.SR1--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR1</version> <type>pom</type> <scope>import</scope> </dependency> <!--springcloud alibaba 2.1.0.RELEASE--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!--mysql数据库连接驱动包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <!--web依赖包--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${springboot.starter.web}</version> </dependency> <!--alibaba druid数据库--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <!--mybatis orm框架--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring.boot.version}</version> </dependency> <!--lombok插件引入--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </dependency> <!--热启动部署--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <version>2.1.10.RELEASE</version> <scope>runtime</scope> <optional>true</optional> </dependency> <!--测试依赖包引入--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>${springboot.test.version}</version> <scope>test</scope> </dependency> <!--log4j日志包--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> <!--swagger2依赖--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version> </dependency> <!--swagger第三方ui依赖--> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.6</version> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <!--排除lombok jar在打包编译期间--> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> <!--引入mybatis逆向工程插件--> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.5</version> <configuration> <configurationFile>${basedir}/src/main/resources/mybatis-generator/generatorConfig.xml </configurationFile> <overwrite>true</overwrite> <verbose>true</verbose> </configuration> </plugin> </plugins> </build>
springcloudnacos-provider-product6700
<dependencies> <!--SpringCloud alibaba nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--SpringBoot整合Web组件--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--服务注册与发现--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--日常通用jar包配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--api jar包引入--> <dependency> <groupId>com.fengye</groupId> <artifactId>springcloudnacos-common-api</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <!--集合判空工具类--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.3</version> </dependency> <!--引入Swagger2组件--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!--mysql连接驱动包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <!--引入ui包--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version> </dependency> <!--swagger第三方ui依赖--> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.6</version> </dependency> </dependencies>
springcloudnacos-consumer-product7800
<dependencies> <!--springcloud openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--sentinel熔断限流--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--SpringCloud alibaba nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--引入自定义的api通用包--> <dependency> <groupId>com.fengye</groupId> <artifactId>springcloudnacos-common-api</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <!--SpringBoot整合Web组件--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--日常通用jar包配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--引入Swagger2组件--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!--引入ui包--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version> </dependency> <!--swagger第三方ui依赖--> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.6</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.3</version> </dependency> </dependencies>
③application.yml配置
重点主要是在服务生产方provider-product6700进行数据库连接、mybatis框架、nacos服务注册相关的配置,具体如下:
server: port: 6700 spring: application: name: nacos-product-provider-6700 cloud: nacos: discovery: server-addr: localhost:8848 #数据库连接池配置 datasource: username: root password: admin #假如时区报错,增加时区配置serverTimezone=UTC url: jdbc:mysql://localhost:3306/nacosproduct?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver #整合mybatis配置 mybatis: #config-location: classpath:mybatis/mybatis-config.xml 使用了configuration注解则无需再指定mybatis-config.xml文件 mapper-locations: classpath:mybatis/mapper/*.xml configuration: #指定mybatis全局配置文件中的相关配置项 map-underscore-to-camel-case: true type-aliases-package: com.fengye.springcloud.entities #消费者将要去访问的微服务名称 server-url: nacos-user-service: http://nacos-product-provider
1.2.项目分包结构说明
以一个服务提供方6700来说,就是简单地controller、mapper、service/impl,mapper层使用xml与注解结合的方式都可以。这里不再多说。
而在服务消费方7800来说,主要分为Feign接口调用与Swagger2Config配置类:
2.Swagger2/Feign接口/抽奖接口说明
2.1.Swagger2配置类
①这里主要的就是在项目中会引入Swagger2的依赖包,以及基于国人UI风格的jar包。
<!--引入Swagger2组件--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!--swagger第三方ui依赖--> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.6</version> </dependency>
②编写Swagger2Config配置类:
//将此类交给Spring管理,表示一个配置类 @Configuration //开启Swagger2 @EnableSwagger2 public class Swagger2Config { /** * 创建API应用 * apiInfo() 增加API相关信息 * 通过select()函数返回一个ApiSelectorBuilder实例,用来控制哪些接口暴露给Swagger来展现, * 本例采用指定扫描的包路径来定义指定要建立API的目录 * * @return 返回Swagger的Docket配置Bean实例 */ @Bean public Docket createRestApi(Environment environment) { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .enable(true) //enable是否启动swagger,如果为False,则swagger不能在浏览器中访问 .select() //指定API对象扫描哪个包下面的controller //参数any():扫描全部; none():都不扫描 //withClassAnnotation:扫描类上的注解,参数是一个注解的反射对象 //withMethodAnnotation:扫描方法上的注解 .apis(RequestHandlerSelectors.basePackage("com.fengye.springcloud")) //过滤什么路径 .paths(PathSelectors.any()) .build(); } /** * 创建该API的基本信息(这些基本信息会展现在文档页面中) * 访问地址:http://项目实际地址/swagger-ui.html * @return 返回API基本信息 */ private ApiInfo apiInfo() { return new ApiInfoBuilder() //Swagger2展示界面的标题(重要) .title("抽奖接口API文档") //描述信息(主要) .description("抽奖接口API文档") .version("1.0") //.termsOfServiceUrl("https://swagger.io/docs") //.license("Apache 2.0") //.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0") //作者信息 .contact(new Contact("fengye", "https://www.cnblogs.com/yif0118/", "[email protected]")) .build(); } }
启动整体的项目之后,访问:http://localhost:7800/doc.html,即可看到具体的UI风格的API文档界面:
2.2.Feign接口请求
Feign的接口请求调用方式主要是基于服务提供方的Controller接口,并在服务消费方编写一个基于Controller接口一样的Service接口层,根据服务名及对应一致的方法名进行调用。
springcloudnacos-provider-product6700
服务提供方Controller接口:
@RestController @Slf4j public class ProductController { @Autowired private ProductService productService; /** * 测试Nacos数据接口 * * @return */ @GetMapping(value = "/provider/test") public CommonResult<Product> getProductTest() { CommonResult<Product> result = new CommonResult<>(); result.setCode(ResultCodeEnum.SUCCESS.getCode()); result.setData(new Product(1, "iphone12Max", (float) 0.05)); result.setException(null); result.setMsg("测试数据接口"); result.setUrl(null); result.setSuccess(true); return result; } /** * 根据id查询商品返回商品接口 * * @param id * @return */ @GetMapping(value = "/provider/product/{id}") public CommonResult<Product> getProductById(@PathVariable("id") Integer id) { Product product = productService.getProductById(id); if (product != null) { return new CommonResult<Product>( ResultCodeEnum.SUCCESS.getCode(), product, null, "根据id查询商品信息成功!商品名称为:" + product, true, null); } return new CommonResult<Product>( ResultCodeEnum.DATA_NOT_FOUND.getCode(), null, null, "根据id查询商品信息失败!", false, null); } /** * 获取所有商品 * * @return */ @GetMapping(value = "/provider/getAll") public CommonResult<List<Product>> getAllProducts() { List<Product> productList = productService.getProductList(); if (CollectionUtils.isEmpty(productList)) { return new CommonResult<>( ResultCodeEnum.DATA_NOT_FOUND.getCode(), null, null, "查询商品列表信息失败!", false, null ); } return new CommonResult<>( ResultCodeEnum.SUCCESS.getCode(), productList, null, "查询商品信息成功,所得到的的商品列表为:" + productList, true, null ); } /** * 添加商品接口:使用Product参数进行JSON数据插入传递参数 * * @param product * @return */ @PostMapping(value = "/provider/insert") public Integer insertProduct(@RequestBody Product product) { return productService.insertProduct(product); } /** * 抽奖接口:根据抽奖次数及抽奖商品的概率进行返回抽中的商品 * * @return */ @GetMapping(value = "/provider/luckyDraw") public CommonResult<Product> luckyDrawProduct() { List<Product> productList = productService.getProductList(); Product product = LotteryUtil.luckyDraw(productList); if (product == null) { return new CommonResult<>( ResultCodeEnum.PARAMS_NULL.getCode(), null, null, "未抽取到商品,谢谢惠顾!", false, null ); } return new CommonResult<>( ResultCodeEnum.SUCCESS.getCode(), product, null, "抽奖商品获取成功!抽到的商品名称为:" + product.getProductName(), true, null ); } }
springcloudnacos-consumer-product7800
服务消费方Servcie Feign接口:
@Component @FeignClient(value = "nacos-product-provider-6700") public interface ProductFeignService { //测试集成Nacos服务接口 @GetMapping(value = "/provider/test") public CommonResult<Product> getProductTest(); //接口名与url地址与服务生产者接口名称相同 @GetMapping(value = "/provider/product/{id}") public CommonResult<Product> getProductById(@PathVariable("id") Integer id); //获取所有的商品数据 @GetMapping(value = "/provider/getAll") public CommonResult<List<Product>> getAllProducts(); //编写一个添加商品接口:包含商品名称和中奖率 @PostMapping(value = "/provider/insert") public CommonResult<Product> insertProduct(@RequestBody Product product); //编写一个抽奖接口,对添加的商口进行抽奖返回中奖商品 @GetMapping(value = "/provider/luckyDraw") public CommonResult<Product> luckyDrawProduct(); }
暴露给Swagger2访问的Controller外部接口:
@RestController @Api(value = "抽奖接口演示",description = "SpringCloud Nacos测试API接口") public class ProductConsumerController { @Value("${server.port}") private String serverPort; @Autowired private ProductFeignService productFeignService; @ApiOperation(value = "获取所有抽奖商品信息", notes = "获取所有抽奖商品getAllProducts接口") @GetMapping(value = "/consumer/getAll") public CommonResult<List<Product>> getAllProducts(){ CommonResult<List<Product>> allProducts = productFeignService.getAllProducts(); String requestUrl = String.format("http://localhost:%s/consumer/getAll", serverPort); allProducts.setUrl(requestUrl); return allProducts; } @ApiOperation(value = "获取商品测试test接口", notes = "test接口") @GetMapping(value = "/test") public CommonResult<Product> getProductTest(){ CommonResult<Product> productTest = productFeignService.getProductTest(); String requestUrl = String.format("http://localhost:%s/test", serverPort); productTest.setUrl(requestUrl); return productTest; } @RequestMapping(value = "/consumer/getProductById/{id}") @ApiOperation(value = "获取对应id商品接口", notes = "根据商品id获取商品信息") @ApiImplicitParams({ @ApiImplicitParam(paramType="query", name = "id", value = "商品id", required = true, dataType = "Integer"), }) public CommonResult<Product> getProductById(@PathVariable("id") Integer id){ CommonResult<Product> productRes = productFeignService.getProductById(id); String requestUrl = String.format("http://localhost:%s/consumer/getProductById/%s", serverPort, id); productRes.setUrl(requestUrl); return productRes; } @PostMapping(value = "/consumer/insert") @ApiOperation(value = "插入抽奖商品insert接口", notes = "插入商品接口,包含商品信息、商品抽奖概率") @ApiImplicitParams({ @ApiImplicitParam(paramType="insert", name = "id", value = "商品", required = true, dataType = "Product"), }) public CommonResult<Product> insertProduct(@RequestBody Product product){ CommonResult<Product> result = productFeignService.insertProduct(product); String requestUrl = String.format("http://localhost:%s//consumer/insert", serverPort); result.setUrl(requestUrl); return result; } //编写一个抽奖接口,对添加的商口进行抽奖返回中奖商品 @GetMapping(value = "/consumer/luckyDraw") @ApiOperation(value = "抽奖接口", notes = "抽奖接口,根据概率返回中奖商品") public CommonResult<Product> luckyDrawProduct(){ CommonResult<Product> commonRes = productFeignService.luckyDrawProduct(); String requestUrl = String.format("http://localhost:%s//consumer/luckyDraw", serverPort); commonRes.setUrl(requestUrl); return commonRes; } }
2.3.抽奖接口实现
主要用到的抽奖商品类:
@Data @AllArgsConstructor @NoArgsConstructor @ApiModel("用户实体类") public class Product { @ApiModelProperty("主键id") private Integer id; //主键id @ApiModelProperty("商品名称") private String productName; //商品名称 @ApiModelProperty("中奖率") private float winRate; //中奖率 -- 请用户输入小数点后两位 }
公共接口返回值封装类:
@Data @AllArgsConstructor @NoArgsConstructor public class CommonResult<T> { private Integer code; private T data; private String exception; private String msg; private boolean success; private String url; }
返回结果枚举类:
@Getter public enum ResultCodeEnum { /** * 返回结果枚举,每个枚举代表着一个状态 */ SUCCESS(200, "操作成功!"), ERROR(400, "操作失败!"), DATA_NOT_FOUND(401, "查询失败!"), PARAMS_NULL(402, "参数不能为空!"), PARAMS_ERROR(405, "参数不合法!"), NOT_LOGIN(403, "当前账号未登录!"); private Integer code; private String msg; ResultCodeEnum(Integer code, String msg) { this.code = code; this.msg = msg; } }
主要的抽奖接口实现工具类:
public class LotteryUtil { /** * 抽奖设计接口: * 产生一个随机数,0-5为一等奖商品,6-15为二等奖商品,16-40为三等奖商品,41-100为谢谢惠顾 * 在比较的时候,比较随机数(百分比)与获取商品的概率(百分比)的绝对值,40%以下的才中奖 * 之后计算随机数与中奖概率的绝对值,选择绝对值相差最小的那个为中奖商品 * @param products * @return */ public static Product luckyDraw(List<Product> products) { //1.产生一个随机数 int probabilityCount = 100; int randomNum = (int) (Math.random()* probabilityCount); //2.41-100表示不中奖 if(randomNum > 40){ return null; } Map<String, Product> map = new HashMap<>(); List<Integer> list = new ArrayList<>(); for (Product product : products) { int intValue = new BigDecimal(product.getWinRate() * 100).intValue(); int absVal = Math.abs(randomNum - intValue); list.add(absVal); } Integer min = Collections.min(list); for (Product product : products) { int value = new BigDecimal(product.getWinRate() * 100).intValue(); if(Math.abs(randomNum - value) == min){ return product; } } return null; } }
Nacos微服务注册中心:
使用Swagger接口测试返回中奖结果:
抽奖算法需求:抽奖接口按照添加商品接口的名称和中奖率进行抽奖返回中奖商品,中奖率小于等于40%。即有可能按实际概率来抽奖返回不中奖情况。
抽奖算法这里实际的情况应该是按照插入奖品的实际概率0.15来计算真实的抽奖概率,本人这里实现的比较简单,具体的抽奖概率可以
根据实际情况进行优化,也欢迎博友提出相对应的算法建议。
博客示例及相关代码已上传至GitHub: