DAO 层设计
1.写OrderMaster和OrderDetail两个实体类,因为涉及到订单修改时间的更新,所以添加注解@DynamicUpdate,即在更新时候修改时间数据
2.写OrderStatusEnum和PayStatusEnum两个枚举类
//订单状态
@Getter
public enum OrderStatusEnum {
/**
* 新订单
*/
NEW(0, "新订单"),
/**
* 完结
*/
FINISHED(1, "完结"),
/**
* 已取消
*/
CANCEL(2, "已取消"),
;
private Integer code;
private String message;
OrderStatusEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
//支付状态
@Getter
public enum PayStatusEnum {
/**
* 等待支付
*/
WAIT(0, "等待支付"),
/**
* 支付成功
*/
SUCCESS(1, "支付成功");
private Integer code;
private String message;
PayStatusEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
3.编写DAO层的两个类OrderMasterRepository和OrderDetailRepository两个类
public interface OrderMasterRepository extends JpaRepository<OrderMaster,String> {
/**
* 分页显示,通过买家的openid查找他的订单信息
*/
Page<OrderMaster> findByBuyerOpenid(String buyerOpenid, Pageable pageable);
}
public interface OrderDetailRepository extends JpaRepository<OrderDetail,String> {
List<OrderDetail> findByOrderId(String orderId);
}
4.对持久层两个类进行单元测试
Service层设计
1.创建OrderService接口
@Service
public interface OrderService {
/**
* 创建订单
*/
OrderDTO create(OrderDTO orderDTO);
/**
* 根据订单ID查询订单
*/
OrderDTO findOne(String orderId);
/**
* 通过买家openid查询他的所有订单,分页返回
*/
Page<OrderDTO> findList(String buyerOpenid, Pageable pageable);
/**
* 取消订单
*/
OrderDTO cancel(OrderDTO orderDTO);
/**
* 完结订单
*/
OrderDTO finish(OrderDTO orderDTO);
/**
* 支付订单
*/
OrderDTO paid(OrderDTO orderDTO);
}
2.创建OrderDTO这个类,这个类包含了orderMaster所有数据,还加入了List集合
@Data
public class OrderDTO {
/**
* 订单id.
*/
private String orderId;
/**
* 买家名字.
*/
private String buyerName;
/**
* 买家手机号.
*/
private String buyerPhone;
/**
* 买家地址.
*/
private String buyerAddress;
/**
* 买家微信Openid.
*/
private String buyerOpenid;
/**
* 订单总金额.
*/
private BigDecimal orderAmount;
/**
* 订单状态, 默认为0新下单.
*/
private Integer orderStatus ;
/**
* 支付状态, 默认为0未支付.
*/
private Integer payStatus ;
/**
* 创建时间.
*/
private Date createTime;
/**
* 更新时间.
*/
private Date updateTime;
private List<OrderDetail> orderDetailList;
}
根据order_master和order_detail之间一对多的联系,本来应该在OrderMaster实体类中加入一个字段,进行一对多连接,但是这里为了不要太过混淆,于是创建了dto这个包(数据传输)
3.创建OrderServiceImpl类(代码略),实现OrderService接口
4.在OrderServiceImpl类中涉及到了自定义异常,所以我们在这里创建了一个SellException类放入Exception包中
public class SellException extends RuntimeException {
private Integer code;
public SellException(ResultEnum resultEnum) {
super(resultEnum.getMessage());
this.code = resultEnum.getCode();
}
}
5.因为异常有很多类型,所以我们建立一个枚举类ResultEnum,在这个枚举类中放入所有的能使用的异常然后进行调用,枚举类可以访问数据但是不能修改,所以我们使用@Getter标签
@Getter
public enum ResultEnum {
/**
* 商品不存在
*/
Product_not_exist(10,"商品不存在"),
;
private Integer code;
private String message;
ResultEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
6.因为订单ID是唯一的,所以创建一个工具类KeyUtil放入Utils包下,由于在创建唯一ID的时候一定会涉及到多线程重复的情况,所以要加上synchronized
public class KeyUtil {
/**
* 生成唯一的Key
*/
public static synchronized String genUniqueKey() {
Random random = new Random();
Integer a = random.nextInt(900000) + 10000;
return System.currentTimeMillis() + String.valueOf(a);
}
}
7.涉及到商品表中的库存的增加减少问题,返回ProductService中去完成这两个功能。加减库存涉及到了两个数据,即商品ID以及数量,所以我们可以创建一个新的类来存放这两个数据(CartDTO)
@Data
public class CartDTO {
/**
* 商品id.
*/
private String productId;
/**
* 商品数量.
*/
private Integer productQuantity;
}
Controller层设计
具体方法见:api的规范以及使用方式
1.API分析
创建订单
POST /sell/buyer/order/create
参数
name: "张三"
phone: "18868822111"
address: "慕课网总部"
openid: "ew3euwhd7sjw9diwkq" //用户的微信openid
items: [{
productId: "1423113435324",
productQuantity: 2 //购买数量
}]
返回
{
"code": 0,
"msg": "成功",
"data": {
"orderId": "147283992738221"
}
}
订单列表
GET /sell/buyer/order/list
参数
openid: 18eu2jwk2kse3r42e2e
page: 0 //从第0页开始
size: 10
返回
{
"code": 0,
"msg": "成功",
"data": [
{
"orderId": "161873371171128075",
"buyerName": "张三",
"buyerPhone": "18868877111",
"buyerAddress": "慕课网总部",
"buyerOpenid": "18eu2jwk2kse3r42e2e",
"orderAmount": 0,
"orderStatus": 0,
"payStatus": 0,
"createTime": 1490171219,
"updateTime": 1490171219,
"orderDetailList": null
}]
}
查询订单详情
GET /sell/buyer/order/detail
参数
openid: 18eu2jwk2kse3r42e2e
orderId: 161899085773669363
返回
{
"code": 0,
"msg": "成功",
"data": {
"orderId": "161899085773669363",
"buyerName": "李四",
"buyerPhone": "18868877111",
"buyerAddress": "慕课网总部",
"buyerOpenid": "18eu2jwk2kse3r42e2e",
"orderAmount": 18,
"orderStatus": 0,
"payStatus": 0,
"createTime": 1490177352,
"updateTime": 1490177352,
"orderDetailList": [
{
"detailId": "161899085974995851",
"orderId": "161899085773669363",
"productId": "157875196362360019",
"productName": "招牌奶茶",
"productPrice": 9,
"productQuantity": 2,
"productIcon": "http://xxx.com",
"productImage": "http://xxx.com"
}
]
}
}
取消订单
POST /sell/buyer/order/cancel
参数
openid: 18eu2jwk2kse3r42e2e
orderId: 161899085773669363
返回
{
"code": 0,
"msg": "成功",
"data": null
}
获取openid
重定向到 /sell/wechat/authorize
参数
returnUrl: http://xxx.com/abc //【必填】
返回
http://xxx.com/abc?openid=oZxSYw5ldcxv6H0EU67GgSXOUrVg
2.创建BuyerOrderController类,完成四个API方法
@RestController
@RequestMapping("/buyer/order")
@Slf4j
public class BuyerOrderController {
@Autowired
private OrderService orderService;
//创建订单
//@Valid:开启数据校验,假如字段验证不通过,信息绑定到后面定义的BindingResult;
//需要注意的是@Valid 和 BindingResult 是一 一对应的,如果有多个@Valid,那么每个@Valid后面都需要添加BindingResult用于接收bean中的校验信息
@GetMapping("/create")
public ResultVO<Map<String,String>> create(@Valid OrderForm orderForm,
BindingResult bindingResult){
if(bindingResult.hasErrors()){
log.error("【创建订单】 参数不正确,orderForm={}",orderForm);
throw new SellException(ResultEnum.PARAM_ERROR.getCode(),
bindingResult.getFieldError().getDefaultMessage());
}
//将orderForm转换为OrderDTO类型
OrderDTO orderDTO = OrderForm2OrderDTOConverter.convert(orderForm);
if(CollectionUtils.isEmpty(orderDTO.getOrderDetailList())){
log.error("【创建订单】 购物车不能为空");
throw new SellException(ResultEnum.CART_EMPTY);
}
OrderDTO createResult = orderService.create(orderDTO);
Map<String,String> map = new HashMap<>();
map.put("orderId",createResult.getOrderId());
return ResultVOUtil.success(map);
}
//订单列表
@GetMapping("/list")
public ResultVO<List<OrderDTO>> list(@RequestParam("openid") String openid,
@RequestParam(value = "page",defaultValue = "0") Integer page,
@RequestParam(value = "size",defaultValue = "10") Integer size){
if(StringUtils.isEmpty(openid)){
log.error("【查询订单列表】openid为空");
throw new SellException(ResultEnum.PARAM_ERROR);
}
PageRequest request = PageRequest.of(page,size);
Page<OrderDTO> orderDTOPage = orderService.findList(openid,request);
return ResultVOUtil.success(orderDTOPage.getContent());
}
//订单详情
@GetMapping("/detail")
public ResultVO<OrderDTO> detail(@RequestParam("openid") String openid,
@RequestParam("orderid") String orderid){
//TODO 不安全,改进
OrderDTO orderDTO = orderService.findOne(openid);
return ResultVOUtil.success(orderDTO);
}
//取消订单
@PostMapping("/cancel")
public ResultVO cancel (@RequestParam("openid") String openid,
@RequestParam("orderid") String orderid) {
//TODO 不安全
OrderDTO orderDTO = orderService.findOne(orderid);
orderService.cancel(orderDTO);
return ResultVOUtil.success();
}
}
3.前台传过来的是一个post是表单数据,于是我们可以创建一个OrderForm类放在form包下进行表单验证
详见:表单验证
这里使用到了Json转类的方法,所以引入Gson(json类型字符串通过Gson转换为对象)
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
4.进行Date和Long的转换,新建一个序列化类serializer(JsonSerializer:对输出到前端的属性做转换)
public class Date2LongSerializer extends JsonSerializer<Date> {
@Override
public void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeNumber(value.getTime()/1000);
}
}
然后再需要转换的属性上加上注解@JsonSerialize(using = Date2LongSerializer.class)即可
/**
* 创建时间.
*/
@JsonSerialize(using = Date2LongSerializer.class)
private Date createTime;
/**
* 更新时间.
*/
@JsonSerialize(using = Date2LongSerializer.class)
private Date updateTime;
5.使返回到前端的参数不能为null的多种解决方法
1)参数设置一个默认值
2)在类的前面加上标签
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class OrderDTO {
}
或者设置在application.yml中设置
spring:
jackson:
default-property-inclusion: non_null