用户业务
短信发送业务
注册之前输入手机号,请求后端getOtp接口。接口生成验证码后,发送
到用户手机,并且用Map将验证码和手机绑定起来。企业级开发将Map放
到分布式Redis里面,这里直接放到Session里面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @RequestMapping(value="courseselect/getotp",method = RequestMethod.POST) @ResponseBody public CommonReturnType getOtp(@RequestParam(value="stuno",required = false)String stuno){ Random random = new Random(); int randomInt = random.nextInt(90000); randomInt += 10000; String otpCode = String.valueOf(randomInt); httpServletRequest.getSession().setAttribute(stuno, otpCode); String infomation=(String) this.httpServletRequest.getSession() .getAttribute(stuno); return CommonReturnType.create(otpCode); }
|
注册业务
注册请求后端StuController.register接口,先进行短信验证,然后
将注册信息封装到StuModel,调用StuServiceImpl.register(),先
对注册信息进行入参校验,再将StuModel转成StuDO、StuPasswordDO
存入到数据库。同时需要注意的是StuServiceImpl.register() 方法
,设计到了数据库写操作,需要加上@Transactional注解,以事务的
方式进行处理
1 2
| controller.StuController.register() service.impl.StuServiceImpl.register()
|
添加数据时使用insertselective而不用insert,insertselective
会首先判断字段是否为null,如果为null就跳过,也就是不insert这
个字段,完全依赖于数据库提供的默认值。null 字段对于前端的展示
没有意义
登录业务
登录请求后端UserController.login接口,前端传过来手机号和密码。
判空之后,调用UserServiceImpl.validateLogin方法,这个方法先
通过手机号查询user_info表,看是否存在该用户返回UserDO 对象,
再根据UserDO.id去user_password表中查询密码。如果密码匹配则返
回UserModel对象给login方法,最后login方法将UserModel对象存
放到Session里面,即完成了登录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public UserModel validateLogin(String telphone,String encrptPassword) throws BusinessException{ UserDO userDO = userDOMapper.selectByTelphone(telphone); if (userDO == null) { throw new BusinessException(EmBusinessError.USER_LOGIN_FAIL); } userPasswordDO userPasswordDO = userPasswordDOMapper.selectByUserId (userDO.getId()); UserModel userModel = convertFromDataObject(userDO, userPasswordDO); UserSaltDO userSaltDO= userSaltDOMapper.selectByUserId(userDO.getId()); String password=DigestUtils.md5Hex(encrptPassword+userSaltDO.getSalt()); if (!StringUtils.equals(password, userModel.getEncrptPassword())) { throw new BusinessException(EmBusinessError.USER_LOGIN_FAIL); } return userModel; }
|
课程业务
课程添加业务
请求后端ItemController.create接口,传入商品创建的各种信息,封装
到ItemModel对象,调用ItemServiceImpl.createItem方法,进行入参
校验,然后将ItemModel转换成ItemDO和ItemStockDO对象,分别写入数
据库
获取课程业务
请求后端ItemController.get接口传入一个Id,通过ItemServiceImpl
.getItemById先查询出ItemDO对象,再根据这个对象查出ItemStockDO
对象,最后两个对象封装成一个ItemModel对象返回
查询所有课程
请求后端ItemController.list接口,跟上面类似查询所有课程
交易业务
下单业务
请求后端OrderController.createOrder接口,传入商品IdItemId和下
单数量amount。接着在Session中获取用户登录信息,如果用户没有登录
,直接抛异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @GetMapping(value = "soquick/item/createorder") @ResponseBody public CommonReturnType createOrder(@RequestParam(value = "itemId") Integer itemId,@RequestParam(value = "amount") Integer amount, @RequestParam(value = "promoId",required = false) Integer promoId) throws BusinessException {
Boolean isLogin = (Boolean) httpServletRequest.getSession(). getAttribute("IS_LOGIN"); if (isLogin == null || !isLogin.booleanValue()) { throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "用户还未登录,不能下单"); } HttpSession session=httpServletRequest.getSession(); UserModel userModel = (UserModel) httpServletRequest.getSession(). getAttribute("LOGIN_USER"); OrderModel orderModel = orderService.createOrder(userModel.getId(), itemId,promoId, amount); return CommonReturnType.create(null); }
|
在将订单存入库之前,先要调用OrderServiceImpl.createOrder方法
,对课程信息、学生信息、下单数量进行校验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ItemModel itemModel = itemService.getItemById(itemId); if (itemModel == null) { throw new BusinessException(EmBusinessError. PARAMETER_VALIDATION_ERROR, "商品信息不存在"); } UserModel userModel = userService.getUserById(userId); if (userModel == null) { throw new BusinessException(EmBusinessError. PARAMETER_VALIDATION_ERROR, "用户信息不存在"); } if (amount <= 0 || amount > 99) { throw new BusinessException(EmBusinessError. PARAMETER_VALIDATION_ERROR, "数量信息不存在"); }
|
此外还需要校验库存是否足够,最后将订单入库,再让销量增加
1 2 3 4 5 6
|
boolean result = itemService.decreaseStock(itemId, amount); if (!result) { throw new BusinessException(EmBusinessError.STOCK_NOT_ENOUGH); }
|
订单ID的生成
订单ID不能是简单的自增长,而是要符合一定的规则,比如前8位是年月
日,中间6位为自增序列,最后2位为分库分表信息
- 前8位比较好实现,使用LocalDateTime,处理一下格式即可
- 中间6位自增序列,需要新建一个sequence_info表,里面包含name、
current_value、step三个字段。这个表及其对应的DO专门用来产生自增
序列
- generatorOrderNo方法需要将序列更新信息写入到sequence_info
表,而且该方法封装在OrderServiceImpl.createOrder 方法中。如果
createOrder执行失败会进行回滚,默认情况下,generatorOrderNo
也会回滚。而我们希望生成ID的事务不受影响,就算订单创建失败,ID
还是继续生成,所以generatorOrderNo方法使用了REQUIRES_NEW事
务传播方式
1 2 3 4 5 6
| orderModel.setId(generateOrderNo()); OrderDO orderDO=convertFromOrderModel(orderModel); orderDOMapper.insertSelective(orderDO); itemService.increaseSales(itemId, amount);
|
秒杀业务
秒杀DO/Model和VO
PromoDO包含活动名称、起始、结束时间、参与活动的商品id、参与活动的、
价格。而我们希望在前端显示活动的状态,是开始?还是结束?还是正在
进行中?所以PromoModel对象新加一个status字段,通过从数据库的
start_time和end_time字段,与当前系统时间做比较,设置状态
1 2 3 4 5 6 7 8
| if(promoModel.getStartDate().isAfterNow()) { promoModel.setStatus(1); }else if(promoModel.getEndDate().isBeforeNow()){ promoModel.setStatus(3); }else{ promoModel.setStatus(2); }
|
对于ItemModel,需要将PromoModel属性添加进去,这样就完成了商品和
活动信息的关联,在ItemServiceImpl.getItemById中,除了要查询商
品信息ItemDO、库存信息ItemStockDO外,还需要查询出PromoModel
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public ItemModel getItemById(Integer id) { ItemDO itemDO=itemDOMapper.selectByPrimaryKey(id); if(itemDO==null) return null; ItemStockDO itemStockDO=itemStockDOMapper.selectByItemId(itemDO.getId()); ItemModel itemModel=convertModelFromDataObject(itemDO,itemStockDO); PromoModel promoModel= promoService.getPromoByItemId(itemModel.getId()); if(promoModel!=null&&promoModel.getStatus()!=3){ itemModel.setPromoModel(promoModel); } return itemModel; }
|
对于ItemVO,也是一样的,我们需要把活动的信息(活动进行信息、活
动价格等)显示给前端,所以需要在ItemVO 里面添加promoStatus、
promoPrice等属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| private String imgUrl;
private Integer promoStatus; private BigDecimal promoPrice; private Integer promoId;
private String startDate;
private ItemVO convertVOFromModel(ItemModel itemModel){ if(itemModel==null) return null; ItemVO itemVO=new ItemVO(); BeanUtils.copyProperties(itemModel,itemVO); if(itemModel.getPromoModel()!=null){ itemVO.setPromoStatus(itemModel.getPromoModel().getStatus()); itemVO.setPromoId(itemModel.getPromoModel().getId()); itemVO.setStartDate(itemModel.getPromoModel().getStartDate(). toString(DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"))); itemVO.setPromoPrice(itemModel.getPromoModel(). getPromoItemPrice()); }else{ itemVO.setPromoStatus(0); } }
|
升级获取商品业务
之前获取的商品不包含秒杀活动信息,现在需要把活动信息添加进去。还
是先请求ItemController.list 接口,获取所有商品信息。然后通过点
击的商品Id,请求ItemController.get接口,查询商品详细信息。
首先根据Id调用ItemServiceImpl.getItemById查询出商品信息、库
存信息、秒杀活动信息,一并封装到ItemModel中。然后再调用上面的
convertVOFromModel,将这个ItemModel对象转换成ItemVO对象,
包含了秒杀活动的信息,最后返回给前端以供显示
活动商品下单业务
秒杀活动商品的下单需要单独处理,以“秒杀价格”入下单库。所以OrderDO
也需要添加promoId属性
1 2
| private BigDecimal orderPrice; private Integer promoId;
|
之前活动商品的下单附带itemId、amount请求OrderController.createOrder
接口,现在会附带一个promoId请求接口,这个参数会作为OrderServiceImpl
.createOrder的参数,进行参数校验
1 2 3 4 5 6 7 8 9 10 11 12
| if(promoId!=null){ if(promoId.intValue()!=itemModel.getPromoModel().getId()){ throw new BizException(EmBizError.PARAMETER_VALIDATION_ERROR, "活动信息不存在"); }else if (itemModel.getPromoModel().getStatus()!=2){ throw new BizException(EmBizError.PARAMETER_VALIDATION_ERROR, "活动还未开始"); } }
|
最后如果promoId不为空,那么订单的价格就以活动价格为准
1 2 3 4 5 6 7 8
| if(promoId!=null){ orderModel.setItemPrice(itemModel.getPromoModel().getPromoItemPrice()); }else{ orderModel.setItemPrice(itemModel.getPrice()); } orderModel.setPromoId(promoId);
|