目录
一、背景
既然我们已经搭建了配置中心了,也实现了自定义的 db starter 和公共的 API starter ,现在我们就把它整合到一起,完成一个库存系统的设计,你就可以感受到简单了。
二、数据库
1.1、创建数据库
#创建库
create databases stock;
CREATE DATABASE IF NOT EXISTS stock
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_general_ci;
字符集推荐使用: utf8mb4
1.2、创建表
#创建一个库存表
CREATE TABLE `tb_goods_stock` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`goods_id` int unsigned NOT NULL DEFAULT '0' COMMENT '商品id',
`inventory` int unsigned NOT NULL DEFAULT '0' COMMENT '库存',
`create_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
`update_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_goods_id` (`goods_id`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COMMENT='库存表';
1.3、初始数据
#插入商品编号为10001和10002的库存分别为100,200
insert into tb_goods_stock(goods_id,inventory) values(10001,100);
insert into tb_goods_stock(goods_id,inventory) values(10002,200);
1.4、配置中心数据
三、domain-stock
3.1、实体类
GoodsStock.java
package cn.alian.domain.stock.domain;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
@Setter
@Getter
@Entity
@Table(name = "tb_goods_stock")
public class GoodsStock implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", insertable = false, nullable = false)
private Integer id;
/**
* 商品id
*/
@Column(name = "goods_id", nullable = false)
private int goodsId;
/**
* 库存数量
*/
@Column(name = "inventory", nullable = false)
private int inventory;
/**
* 创建时间
*/
@Column(name = "create_time", nullable = false, insertable = false, updatable = false)
private LocalDateTime createTime;
/**
* 更新时间
*/
@Column(name = "update_time", nullable = false, insertable = false, updatable = false)
private LocalDateTime updateTime;
}
3.2、持久层
GoodsStockRepository.java
package cn.alian.domain.stock.repository;
import cn.alian.domain.stock.domain.GoodsStock;
import org.springframework.data.ebean.repository.EbeanRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface GoodsStockRepository extends EbeanRepository<GoodsStock, Integer> {
}
3.3、打包到私服
通过maven自动生成Ebean QueryBean,之前的文章说了,就不再列出了,具体可以参考:(四)Alian 的 Spring Cloud Ebean自动生成Query Bean
四、maven依赖
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.alian.microservice</groupId>
<artifactId>parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<groupId>cn.alian.mall</groupId>
<artifactId>stock</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>stock</name>
<description>库存服务</description>
<dependencies>
<!--自定义的db starter-->
<dependency>
<groupId>cn.alian.microservice</groupId>
<artifactId>common-db</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<!--自定义的API starter-->
<dependency>
<groupId>cn.alian.microservice</groupId>
<artifactId>common-api</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<!--domain Query Bean-->
<dependency>
<groupId>cn.alian.domain</groupId>
<artifactId>domain-stock</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<!--配置中心-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!--注册中心-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--sql日志输出-->
<dependency>
<groupId>com.googlecode.log4jdbc</groupId>
<artifactId>log4jdbc</artifactId>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<repositories>
<repository>
<id>nexus-repos</id>
<name>Team Nexus Repository</name>
<url>http://192.168.0.210:8081/nexus/content/groups/public</url>
</repository>
<repository>
<id>nexus-repos-snapshots</id>
<name>Team Nexus Repository Snapshots</name>
<url>http://192.168.0.210:8081/nexus/content/groups/public-snapshots</url>
</repository>
<repository>
<id>aliyun-maven-repo</id>
<url>https://maven.aliyun.com/repository/public</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>nexus-repos</id>
<name>Team Nexus Repository</name>
<url>http://192.168.0.210:8081/nexus/content/groups/public</url>
</pluginRepository>
<pluginRepository>
<id>nexus-repos-snapshots</id>
<name>Team Nexus Repository Snapshots</name>
<url>http://192.168.0.210:8081/nexus/content/groups/public-snapshots</url>
</pluginRepository>
</pluginRepositories>
</project>
五、编码
5.1、controller
StockController.java
package cn.alian.mall.stock.controller;
import cn.alian.mall.stock.dto.DeductInventoryRequestDto;
import cn.alian.mall.stock.dto.DeductInventoryResponseDto;
import cn.alian.mall.stock.service.StockService;
import cn.alian.microservice.common.dto.ApiResponseDto;
import com.alibaba.fastjson.JSON;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@Slf4j
@Validated
@RestController
@RequestMapping({"/api/v1/stock"})
@Api(description = "库存相关接口")
public class StockController {
@Autowired
private StockService stockService;
@ApiOperation("扣减商品库存")
@PostMapping("/deductInventory")
public ApiResponseDto<DeductInventoryResponseDto> request(@ApiParam @Valid @RequestBody DeductInventoryRequestDto dto) {
log.info("扣减商品库存请求信息:{}", JSON.toJSONString(dto));
DeductInventoryResponseDto responseDto = new DeductInventoryResponseDto();
Pair<Boolean, String> pair = stockService.deductInventory(dto.getGoodsId(), dto.getNum());
log.info("扣减库存返回的结果:{}", pair.getRight());
responseDto.setResultCode(pair.getLeft() ? "0000" : "9999");
responseDto.setMessage(pair.getRight());
return ApiResponseDto.success(responseDto);
}
}
5.2、service
StockService.java
package cn.alian.mall.stock.service;
import io.ebean.DB;
import io.ebean.EbeanServer;
import io.ebean.SqlUpdate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class StockService {
@Autowired
private EbeanServer ebeanServer;
public Pair<Boolean, String> deductInventory(int goodsId, int num) {
String sql = "update tb_goods_stock set inventory=inventory - " + num + " where goods_id =" + goodsId;
SqlUpdate sqlUpdate = DB.sqlUpdate(sql);
int execute = sqlUpdate.execute();
log.info("商品【{}】库存操作完成", goodsId);
if (execute != 0) {
return Pair.of(true, "库存扣减成功");
}
return Pair.of(false, "库存扣减失败");
}
}
5.3、dto
DeductInventoryRequestDto.java
package cn.alian.mall.stock.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
@ApiModel(description = "扣减库存请求信息")
public class DeductInventoryRequestDto {
@NotNull
@ApiModelProperty("订单id")
private Integer orderId;
@NotNull
@ApiModelProperty("商品id")
private Integer goodsId;
@NotNull
@ApiModelProperty("数量")
private Integer num = 1;
}
DeductInventoryResponseDto.java
package cn.alian.mall.stock.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
@ApiModel(description = "扣减库存返回信息")
public class DeductInventoryResponseDto {
@NotNull
@ApiModelProperty("结果码")
private String resultCode;
@NotNull
@ApiModelProperty("结果说明")
private String message;
}
六、配置
6.1、主类
StockApplication.java
package cn.alian.mall.stock;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableDiscoveryClient
@SpringBootApplication
public class StockApplication {
public static void main(String[] args) {
SpringApplication.run(StockApplication.class, args);
}
}
6.2、bootstrap.properties
bootstrap.properties
#应用名
spring.application.name=stock
#开发环境
spring.profiles.active=dev
#配置标签(根据配置中心,本文全是采用master)
spring.cloud.config.label=master
#配置中心地址,多个用逗号分隔
spring.cloud.config.uri=http://10.130.3.222:6666
#设置为true时,如果服务无法连接到配置中心服务器,则服务启动失败
spring.cloud.config.fail-fast=true
#日志配置,通过配置中心获取
logging.config=${spring.cloud.config.uri}/logback/${spring.application.name}.xml
6.3、ebean.properties
ebean.properties
ebean.packages=cn.alian.domain.stock
6.4、build.properties
build.properties
build.groupId=${pom.groupId}
build.artifactId=${pom.artifactId}
build.version=${pom.version}
# 生成的文档说明
build.description=${pom.description}
# spring-boot:run -Ppack时方便生成api客户端
build.pack=cn.alian.mall.stock
6.5、dozerMapping.xml
dozerMapping.xml
<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozermapper.github.io/schema/bean-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://dozermapper.github.io/schema/bean-mapping http://dozermapper.github.io/schema/bean-mapping.xsd">
</mappings>
七、验证
我们公共的API已经完成了swagger的整合,我们启动项目使用swagger进行测试
返回结果:
{
"code": "0000",
"msg": "success",
"content": {
"resultCode": "0000",
"message": "库存扣减成功"
},
"timestamp": 1649741434665,
"bizCode": null,
"bizMsg": null,
"success": true
}
数据库结果:
mysql> select * from tb_goods_stock;
+----+----------+-----------+-------------------------+-------------------------+
| id | goods_id | inventory | create_time | update_time |
+----+----------+-----------+-------------------------+-------------------------+
| 1 | 10001 | 99 | 2022-04-06 11:25:51.626 | 2022-04-12 05:30:35.662 |
| 2 | 10002 | 200 | 2022-04-12 01:56:39.974 | 2022-04-12 01:56:39.974 |
+----+----------+-----------+-------------------------+-------------------------+
2 rows in set (0.00 sec)
从结果上来说吗,我们的系统已经通过测试了。后续我们会生成接口调用,供订单系统调用。