一、开发环境
- JDK 1.8
- MySQL 5.7
- springboot 2.2.4
- mybatis 1.1.1
- redis 3.2.8
可能有的人的MySQL版本不到5.7,在下面创建数据库的时候可能会报错。我的解决办法使用Docker创建一个合适版本的MySQL容器,参考:使用docker创建MySQL容器,并在springboot中使用。
二、开发工具
- IDEA
- Notepad++
- VirtualBox
- SQLyog
三、项目简单分析
该项目的角色主要分为买家端、买家端。买家端主要实现的功能有商品的查询和订单查询、创建、取消等,而卖家端主要是对订单的管理、商品的管理、类目的管理等。
四、在数据库中创建四张数据表
创建一个sell数据库,并在其中创建四张数据表,创建语句如下:
1 CREATE TABLE product_info( 2 product_id VARCHAR(32) NOT NULL, 3 product_name VARCHAR(64) NOT NULL COMMENT "商品名称", 4 product_price DECIMAL(8,2) NOT NULL COMMENT "单价", 5 product_stock INT NOT NULL COMMENT "库存", 6 product_description VARCHAR(64) COMMENT "描述", 7 product_icon VARCHAR(512) COMMENT "小图", 8 category_type INT NOT NULL COMMENT "类目编号", 9 product_status TINYINT(3), 10 create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT "创建时间", 11 update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT "修改时间", 12 PRIMARY KEY(product_id) 13 ) COMMENT "商品表"; 14 15 CREATE TABLE product_category( 16 category_id INT NOT NULL AUTO_INCREMENT, 17 category_name VARCHAR(64) NOT NULL COMMENT ‘类目名称‘, 18 category_type INT NOT NULL COMMENT ‘类目编号‘, 19 create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间‘, 20 update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘修改时间‘, 21 PRIMARY KEY (category_id), 22 UNIQUE KEY uqe_category_type (category_type) 23 ) COMMENT ‘类目表‘; 24 25 CREATE TABLE order_master ( 26 order_id VARCHAR(32) NOT NULL, 27 buyer_name VARCHAR(32) NOT NULL COMMENT ‘买家名字‘, 28 buyer_phone VARCHAR(32) NOT NULL COMMENT ‘买家电话‘, 29 buyer_address VARCHAR(128) NOT NULL COMMENT ‘买家地址‘, 30 buyer_openid VARCHAR(64) NOT NULL COMMENT ‘买家微信openid‘, 31 order_amount DECIMAL(8,2) NOT NULL COMMENT ‘订单总金额‘, 32 order_status TINYINT(3) NOT NULL DEFAULT 0 COMMENT ‘订单状态,默认0新下单‘, 33 pay_status TINYINT(3) NOT NULL DEFAULT 0 COMMENT ‘支付状态,默认0未支付‘, 34 create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间‘, 35 update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘修改时间‘, 36 PRIMARY KEY (order_id), 37 KEY idx_buyer_openid (buyer_openid) 38 ) COMMENT ‘订单表‘; 39 40 CREATE TABLE order_detail ( 41 detail_id VARCHAR(32) NOT NULL, 42 order_id VARCHAR(32) NOT NULL, 43 product_id VARCHAR(32) NOT NULL, 44 product_name VARCHAR(64) NOT NULL COMMENT ‘商品名称‘, 45 product_price DECIMAL(8,2) NOT NULL COMMENT ‘商品价格‘, 46 product_quantity INT NOT NULL COMMENT ‘商品数量‘, 47 product_icon VARCHAR(512) COMMENT ‘商品小图‘, 48 create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间‘, 49 update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘修改时间‘, 50 PRIMARY KEY (detail_id), 51 KEY idx_order_id (order_id) 52 ) COMMENT ‘订单详情表‘;
还差一张买家信息表,但是暂时还用不到就没创建。本文主要是买家商品list的实现就先介绍其中两张,product_category是商品的类目表,product_info是商品信息表。
五.根据API文档在vo包下创建需要返回数据的类
商品列表的API文档如下:
1 ###商品列表 2 3 ``` 4 GET /sell/buyer/product/list 5 ``` 6 7 参数 8 9 ``` 10 无 11 ``` 12 13 返回 14 15 ``` 16 { 17 "code": 0, 18 "msg": "成功", 19 "data": [ 20 { 21 "name": "热榜", 22 "type": 1, 23 "foods": [ 24 { 25 "id": "123456", 26 "name": "皮蛋粥", 27 "price": 1.2, 28 "description": "好吃的皮蛋粥", 29 "icon": "http://xxx.com", 30 } 31 ] 32 }, 33 { 34 "name": "好吃的", 35 "type": 2, 36 "foods": [ 37 { 38 "id": "123457", 39 "name": "慕斯蛋糕", 40 "price": 10.9, 41 "description": "美味爽口", 42 "icon": "http://xxx.com", 43 } 44 ] 45 } 46 ] 47 } 48 ```
根据商品列表文档可知需要返回数据最外层有"code","msg","data"字段,由于各个API"data"内内容不一致,将"data"类型指定为泛型T
因此创建一个ResultVO类
1 package club.nipengfei.VO; 2 3 import lombok.Data; 4 5 @Data 6 public class ResultVO<T> { 7 8 /** 错误码 */ 9 private Integer code; 10 11 /** 提示信息 */ 12 private String msg; 13 14 /** 具体内容 */ 15 private T data; 16 17 }
根据上述的文档可知"data"内是一个列表,其中一个元素的"name"表示"category_name","type"表示"category_type","foods"表示商品信息列表。
根据这些字段在vo包下创建一个ProductVO类。@Data注解:lombok插件的一个注解自动生成get,set等方法。@JsonProperty注解:可以让返回前端例如"categoryName"变成"name",如果字段直接使用"name"不能方便获取该字段含义。
1 package club.nipengfei.VO; 2 3 import com.fasterxml.jackson.annotation.JsonProperty; 4 import lombok.Data; 5 6 import java.util.List; 7 8 /** 9 * 商品(包含类目) 10 */ 11 @Data 12 public class ProductVO { 13 14 @JsonProperty("name") 15 private String categoryName; 16 17 @JsonProperty("type") 18 private Integer categoryType; 19 20 @JsonProperty("foods") 21 private List<ProductInfoVO> productInfoVOList; 22 23 }
"foods"列表中表示属于该类目"category"的商品,使用ProductInfoVO类封装。类型BigDecimal对应数据表中的"DECIMAL"一般钱都用这个类型。
1 package club.nipengfei.VO; 2 3 import com.fasterxml.jackson.annotation.JsonProperty; 4 import lombok.Data; 5 6 import java.math.BigDecimal; 7 8 /** 9 * 商品详情 10 */ 11 @Data 12 public class ProductInfoVO { 13 14 @JsonProperty("id") 15 private String productId; 16 17 @JsonProperty("name") 18 private String productName; 19 20 @JsonProperty("price") 21 private BigDecimal productPrice; 22 23 @JsonProperty("description") 24 private String productDescription; 25 26 @JsonProperty("icon") 27 private String productIcon; 28 }
六、开发repository层(dao层)
在这之前需要根据数据库中的数据表建立相应映射类,ProductCategory类、ProductInfo类。
1 package club.nipengfei.dataobject; 2 3 import lombok.Data; 4 5 import java.util.Date; 6 7 /** 8 * 类目 9 */ 10 @Data 11 public class ProductCategory { 12 13 /** 14 * 类目id 15 */ 16 private Integer category_id; 17 18 /** 19 * 类目名字 20 */ 21 private String category_name; 22 23 /** 24 * 类目编号 25 */ 26 private Integer category_type; 27 28 private Date create_time; 29 30 private Date update_time; 31 32 }
1 package club.nipengfei.dataobject; 2 3 import lombok.Data; 4 5 import java.math.BigDecimal; 6 7 @Data 8 public class ProductInfo { 9 10 private String product_id; 11 12 private String product_name; 13 14 private BigDecimal product_price; 15 16 private Integer product_stock; 17 18 private String product_description; 19 20 private String product_icon; 21 22 /** 状态,0正常 1下架*/ 23 private Integer product_status; 24 25 /** 类目编号*/ 26 private Integer category_type; 27 }
在pom文件中引入相应依赖
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
并在配置文件中配置数据库的信息
1 spring.datasource.url=jdbc:mysql://192.168.1.199/sell?characterEncoding=utf-8&useSSL=FALSE 2 spring.datasource.username=root 3 spring.datasource.password=123456
在ProductInfoRepository接口内写一个findUpAll抽象方法,将product_info表中上架的商品全部查出来,放入List<ProductInfo>中。然后在ProductCategoryRepository接口中写一个findByCategoryTypeIn抽象方法,根据List<Integer>参数查询出List<ProductCategory>。
@Mapper注解会将注解的接口交给Mybatis处理,如果觉得麻烦可以在启动类上加上@MapperScan("club.nipengfei.repository")代替,该注解可以自动扫描repository包下的类
@Repository注解将其交给spring处理,自动注册为spring bean
1 package club.nipengfei.repository; 2 3 import club.nipengfei.dataobject.ProductInfo; 4 import com.github.pagehelper.Page; 5 import org.apache.ibatis.annotations.Insert; 6 import org.apache.ibatis.annotations.Mapper; 7 import org.apache.ibatis.annotations.Select; 8 import org.apache.ibatis.annotations.Update; 9 import org.springframework.stereotype.Repository; 10 11 import java.util.List; 12 13 @Repository 14 @Mapper 15 public interface ProductInfoRepository { 16 17 @Select("select * from product_info where product_status=0") 18 List<ProductInfo> findUpAll(); 19 20 }
下面的select语句中使用了mybatis的动态SQL,参考:https://mybatis.org/mybatis-3/zh/dynamic-sql.html
注意在注解中使用动态SQL需要使用<script>
1 package club.nipengfei.repository; 2 3 import club.nipengfei.dataobject.ProductCategory; 4 import org.apache.ibatis.annotations.Insert; 5 import org.apache.ibatis.annotations.Mapper; 6 import org.apache.ibatis.annotations.Param; 7 import org.apache.ibatis.annotations.Select; 8 import org.springframework.stereotype.Repository; 9 10 import java.util.List; 11 12 @Mapper 13 @Repository 14 public interface ProductCategoryRepository { 15 16 17 @Select("<script>select * from product_category " + 18 "<where>" + 19 "<if test=‘list != null and list.size()>0‘ >"+ 20 "<foreach collection=‘list‘ open=‘and category_type in (‘ close=‘)‘ item=‘id‘ separator=‘,‘> #{id}</foreach>"+ 21 "</if>"+ 22 "</where>"+ 23 "</script>") 24 List<ProductCategory> findByCategoryTypeIn(@Param("list") List<Integer> list); 25 }
七、service层开发
@Service注解与上面的@Repository一样将其注册为spring bean
@Autowired注解,bean的自动注入
1 package club.nipengfei.service; 2 3 import club.nipengfei.dataobject.ProductInfo; 4 import club.nipengfei.dto.CartDTO; 5 import com.github.pagehelper.Page; 6 7 import java.util.List; 8 9 public interface ProductService { 10 11 /** 12 * 查询所有在架商品列表 13 * @return 14 */ 15 List<ProductInfo> findUpAll(); 16 17 }
1 package club.nipengfei.service.impl; 2 3 import club.nipengfei.dataobject.ProductInfo; 4 import club.nipengfei.dto.CartDTO; 5 import club.nipengfei.enums.ResultEnum; 6 import club.nipengfei.exception.SellException; 7 import club.nipengfei.repository.ProductInfoRepository; 8 import club.nipengfei.service.ProductService; 9 import com.github.pagehelper.Page; 10 import org.springframework.beans.factory.annotation.Autowired; 11 import org.springframework.stereotype.Service; 12 import org.springframework.transaction.annotation.Transactional; 13 14 import java.util.List; 15 16 @Service 17 public class ProductServiceImpl implements ProductService { 18 19 @Autowired 20 private ProductInfoRepository repository; 21 22 @Override 23 public List<ProductInfo> findUpAll() { 24 return repository.findUpAll(); 25 } 26 27 }
1 package club.nipengfei.service; 2 3 import club.nipengfei.dataobject.ProductCategory; 4 5 import java.util.List; 6 7 public interface CategoryService { 8 9 List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList); 10 11 }
1 package club.nipengfei.service.impl; 2 3 import club.nipengfei.dataobject.ProductCategory; 4 import club.nipengfei.repository.ProductCategoryRepository; 5 import club.nipengfei.service.CategoryService; 6 import org.springframework.beans.factory.annotation.Autowired; 7 import org.springframework.stereotype.Service; 8 9 import java.util.List; 10 11 @Service 12 public class CategoryServiceImpl implements CategoryService { 13 14 @Autowired 15 private ProductCategoryRepository repository; 16 17 @Override 18 public List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList) { 19 return repository.findByCategoryTypeIn(categoryTypeList); 20 } 21 22 }
八、controller层的开发
根据API文档请求方法和路径,在配置文件中写入一个项目路径"/sell",新建一个BuyerProductController类在这上面加上注解@RequestMapping指定值为"/buyer/product",在list方法上使用@GetMapping注解,指定值为"list"。@RestController注解@ResponseBody和@Controller的组合注解,@ResponseBody注解是返回json,@Controller注解处理http请求。
1 server.servlet.context-path=/sell
1 package club.nipengfei.controller; 2 3 import club.nipengfei.VO.ProductInfoVO; 4 import club.nipengfei.VO.ProductVO; 5 import club.nipengfei.VO.ResultVO; 6 import club.nipengfei.dataobject.ProductCategory; 7 import club.nipengfei.dataobject.ProductInfo; 8 import club.nipengfei.service.CategoryService; 9 import club.nipengfei.service.ProductService; 10 import club.nipengfei.utils.ResultVOUtil; 11 import org.springframework.beans.BeanUtils; 12 import org.springframework.beans.factory.annotation.Autowired; 13 import org.springframework.web.bind.annotation.GetMapping; 14 import org.springframework.web.bind.annotation.RequestMapping; 15 import org.springframework.web.bind.annotation.RestController; 16 17 import java.util.ArrayList; 18 import java.util.Arrays; 19 import java.util.List; 20 21 @RestController 22 @RequestMapping("/buyer/product") 23 public class BuyerProductController { 24 25 @Autowired 26 private ProductService productService; 27 28 @Autowired 29 private CategoryService categoryService; 30 31 32 @GetMapping("/list") 33 public ResultVO list() { 34 35 // 1.查询所有上架商品 36 List<ProductInfo> productInfoList = productService.findUpAll(); 37 38 // 2.查询类目(一次查询) 39 List<Integer> categoryTypeList = new ArrayList<>(); 40 for (ProductInfo productInfo : productInfoList) { 41 categoryTypeList.add(productInfo.getCategory_type()); 42 } 43 /** 当categoryTypeList为空时,即商品都没有上架,程序会报错 */ 44 List<ProductCategory> productCategoryList = categoryService.findByCategoryTypeIn( categoryTypeList); 45 46 // 3.数据拼接 47 List<ProductVO> productVOList = new ArrayList<>(); 48 for (ProductCategory productCategory : productCategoryList) { 49 ProductVO productVO = new ProductVO(); 50 productVO.setCategoryType(productCategory.getCategory_type()); 51 productVO.setCategoryName(productCategory.getCategory_name()); 52 53 List<ProductInfoVO> productInfoVOList = new ArrayList<>(); 54 for (ProductInfo productInfo : productInfoList) { 55 if (productInfo.getCategory_type().equals(productCategory.getCategory_type())){ 56 ProductInfoVO productInfoVO = new ProductInfoVO(); 57 58 // BeanUtils.copyProperties(productInfo,productInfoVO); 59 // 由于我的productInfo和productInfoVO的字段名不一致,不能使用上面的工具,而使用下面的 60 productInfoVO.setProductId(productInfo.getProduct_id()); 61 productInfoVO.setProductName(productInfo.getProduct_name()); 62 productInfoVO.setProductPrice(productInfo.getProduct_price()); 63 productInfoVO.setProductDescription(productInfo.getProduct_description()); 64 productInfoVO.setProductIcon(productInfo.getProduct_icon()); 65 66 productInfoVOList.add(productInfoVO); 67 } 68 } 69 productVO.setProductInfoVOList(productInfoVOList); 70 productVOList.add(productVO); 71 } 72 73 return ResultVOUtil.success(productVOList); 74 } 75 }
1 package club.nipengfei.utils; 2 3 import club.nipengfei.VO.ResultVO; 4 5 public class ResultVOUtil { 6 7 public static ResultVO success(Object object){ 8 ResultVO resultVO = new ResultVO(); 9 resultVO.setData(object); 10 resultVO.setCode(0); 11 resultVO.setMsg("成功"); 12 return resultVO; 13 } 14 15 public static ResultVO success(){ 16 return success(null); 17 } 18 19 public static ResultVO error(Integer code,String msg){ 20 ResultVO resultVO = new ResultVO(); 21 resultVO.setCode(code); 22 resultVO.setMsg(msg); 23 return resultVO; 24 } 25 }
在本地浏览器或者Postman中输入http://127.0.0.1:8080/sell/buyer/product/list,就可以发现返回json格式与API文档一致
九、存在的问题
@Mapper,@Repository,@Autowired,@RestController等注解具体是怎么工作的还是不太了解。mybatis中动态sql使用有点困难。