适合新手小白入门Java后端开发的Springboot + Mybatis Plus 项目。
后面我逐渐省略了一些功能的记录,主要是因为和前面的业务万变不离其宗,如果要看详细的代码可以到我的github仓库下拉reggie代码 。
业务实现(后台系统) 新增套餐 数据模型
表现层要素
请求类型:POST
请求路径:/setmeal
请求参数:json格式的数据,除了setmeal套餐的基本信息外,还有套餐内的菜品信息setmealDishes也封装成json数组的格式。(还有idType和dishList是什么,当请求参数json数据key大于dto对象的时候,可以正常封装吗?)
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 26 27 28 addSetmeal (prams) .then ((res ) => { if (res.code === 1 ) { this .$message .success ('套餐添加成功!' ) if (!st) { this .goBack () } else { this .$refs .ruleForm .resetFields () this .dishList = [] this .dishTable = [] this .ruleForm = { name : '' , categoryId : '' , price : '' , code : '' , image : '' , description : '' , dishList : [], status : true , id : '' , idType : '' , } this .imageUrl = '' } } else { this .$message .error (res.msg || '操作失败' ) } })
返回值类型:Result<String>
类型
核心业务思路
仍然是操作setmeal数据表和setmeal_dish数据表,要在Service类上加事务管理注释@Transactional
。
步骤1:在Service类自定义新增套餐的业务层方法,并在类上加事务管理注释@Transactional
;
步骤2:调用Service层原生的save方法将SetmealDto对象传入并保存;
步骤3:接下来要对setmeal_dish数据表进行添加多条数据,可以从dto对象中get到setmealDishes这个属性列表,但是不能直接将setmealDishes列表saveBatch
,要对其赋值setmeal_id值;
步骤4:通过dto对象get到id值(执行了save操作,在setmeal表已经通过雪花算法为dto对象生成了id属性值),将其赋给setmealDishes列表的setmealDish.setmealId套餐id值;
步骤5:调用Service层的saveBatch
将setmealDishes列表添加到setmeal_dish数据表中。
套餐信息分页查询 表现层要素
请求类型:GET
请求路径:/setmeal/page
请求参数:普通参数page、pageSize和可选参数name
返回值类型:Result<Page>
核心业务思路
跟前面的菜品信息分页查询一模一样。
删除套餐 表现层要素
请求类型:DELETE
请求路径:/setmeal
请求参数:普通参数ids,因为批量删除的id可能有多个,所以这里的方法形参是List<Long> ids
,要加@RequestParam
注解
1 2 3 4 5 6 7 8 deleteSetmeal (type === '批量' ? this .checkList .join (',' ) : id).then (res => { if (res.code === 1 ) { this .$message .success ('删除成功!' ) this .handleQuery () } else { this .$message .error (res.msg || '操作失败' ) } })
返回值类型:Result<String>
核心业务思路
首先操作setmeal表删除套餐信息,再操作setmeal_dish表删除setmeal_id字段 = ids的所有数据。
批量起售停售
1 2 3 4 5 6 7 8 @PostMapping("/status/{status}") public Result<String> changeStatus (@PathVariable Integer status, @RequestParam List<Long> ids) { LambdaUpdateWrapper<Setmeal> lambdaUpdateWrapper = new LambdaUpdateWrapper <>(); lambdaUpdateWrapper.in(Setmeal::getId, ids); lambdaUpdateWrapper.set(Setmeal::getStatus, status); setmealService.update(lambdaUpdateWrapper); return Result.success("状态修改成功" ); }
订单明细分页查询 跟前面的分页查询相比就是可选参数变多了,还有就是关注一下“时间”作为可选参数如何处理。
可选参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 @GetMapping("/page") public Result<Page> pageSelect (int page, int pageSize, String number,String beginTime, String endTime) { Page<Orders> pageInfo = new Page <>(page ,pageSize); LambdaQueryWrapper<Orders> lambdaQueryWrapper = new LambdaQueryWrapper <>(); lambdaQueryWrapper.like(StringUtils.hasText(number), Orders::getNumber, number); lambdaQueryWrapper.gt(StringUtils.hasText(beginTime), Orders::getOrderTime, beginTime) .lt(StringUtils.hasText(endTime), Orders::getOrderTime, endTime); lambdaQueryWrapper.orderByDesc(Orders::getOrderTime); orderService.page(pageInfo, lambdaQueryWrapper); return Result.success(pageInfo); }
业务实现(移动端系统) 手机验证码登录 流程分析
点击获取验证码,页面向服务端发送第一次请求,请求服务器随机生成要求的验证码并发送到表单输出的手机号中;
点击登录,页面向服务端发送第二次请求,携带表单输入的验证码和Session中储存的验证码进行比对,确定是否成功登录。
核心业务思路
导入手机验证的依赖坐标:
1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > com.aliyun</groupId > <artifactId > aliyun-java-sdk-core</artifactId > <version > 4.6.3</version > </dependency > <dependency > <groupId > com.aliyun</groupId > <artifactId > aliyun-java-sdk-dysmsapi</artifactId > <version > 2.2.1</version > </dependency >
编写一个工具类(根据阿里云短信服务的帮助文档改的),用于发送手机验证码:
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 26 27 28 29 30 public class SMSUtils { public static void sendMessage (String signName, String templateCode,String phoneNumbers,String param) { DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou" , "" , "" ); IAcsClient client = new DefaultAcsClient (profile); SendSmsRequest request = new SendSmsRequest (); request.setSysRegionId("cn-hangzhou" ); request.setPhoneNumbers(phoneNumbers); request.setSignName(signName); request.setTemplateCode(templateCode); request.setTemplateParam("{\"code\":\"" +param+"\"}" ); try { SendSmsResponse response = client.getAcsResponse(request); System.out.println("短信发送成功" ); }catch (ClientException e) { e.printStackTrace(); } } }
随机生成随机验证码的工具类:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class ValidateCodeUtils { public static Integer generateValidateCode (int length) { Integer code = null ; if (length == 4 ){ code = new Random ().nextInt(9999 ); if (code < 1000 ){ code = code + 1000 ; } }else if (length == 6 ){ code = new Random ().nextInt(999999 ); if (code < 100000 ){ code = code + 100000 ; } }else { throw new RuntimeException ("只能生成4位或6位数字验证码" ); } return code; } public static String generateValidateCode4String (int length) { Random rdm = new Random (); String hash1 = Integer.toHexString(rdm.nextInt()); String capstr = hash1.substring(0 , length); return capstr; } }
修改拦截器,对客户端登录相关的请求进行放行:
1 2 3 4 5 6 7 8 9 10 11 String[] urls = { "/employee/login" , "/employee/logout" , "/backend/**" , "/front/**" , "/" , "/common/**" , "/user/sendMsg" , "/user/login" };
表现层方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @PostMapping("/sendMsg") public Result<String> sendMsg (@RequestBody User user, HttpSession session) { String phone = user.getPhone(); if (StringUtils.hasText(phone)){ String code = ValidateCodeUtils.generateValidateCode(4 ).toString(); log.info("code -> {}" , code); SMSUtils.sendMessage("瑞吉外卖" , "您的验证码为:${code},请勿泄露于他人!" , phone, code); session.setAttribute(phone, code); return Result.success("手机短信验证码发送成功" ); } return Result.error("手机短信验证码发送失败" ); }
请求参数是json格式的code和phone,phone是User实体类的属性,但是验证码code不是。有两种解决方案:① 定义增强类UserDto; ② 用Map的key-value接收参数。
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 26 27 28 29 30 31 32 @PostMapping("/login") public Result<User> login (@RequestBody Map userMap, HttpSession session) { log.info(userMap.toString()); String phone = userMap.get("phone" ).toString(); String code = userMap.get("code" ).toString(); String codeInSession = session.getAttribute(phone).toString(); if (codeInSession != null && codeInSession.equals(code)){ LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper <>(); lambdaQueryWrapper.eq(User::getPhone, phone); User user = userService.getOne(lambdaQueryWrapper); if (user == null ){ user = new User (); user.setPhone(phone); userService.save(user); } session.setAttribute("user" , user.getId()); return Result.success(user); } return Result.error("登录失败" ); }
地址簿管理 address_book数据模型
表现层代码
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 @Slf4j @RestController @RequestMapping("/addressBook") public class AddressBookController { @Autowired private AddressBookService addressBookService; @PostMapping public Result<AddressBook> save (@RequestBody AddressBook addressBook) { addressBook.setUserId(BaseContext.getCurrentId()); log.info("addressBook:{}" , addressBook); addressBookService.save(addressBook); return Result.success(addressBook); } @PutMapping("/default") public Result<AddressBook> setDefault (@RequestBody AddressBook addressBook) { log.info("addressBook:{}" , addressBook); LambdaUpdateWrapper<AddressBook> wrapper = new LambdaUpdateWrapper <>(); wrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId()); wrapper.set(AddressBook::getIsDefault, 0 ); addressBookService.update(wrapper); addressBook.setIsDefault(1 ); addressBookService.updateById(addressBook); return Result.success(addressBook); } @GetMapping("/{id}") public Result get (@PathVariable Long id) { AddressBook addressBook = addressBookService.getById(id); if (addressBook != null ) { return Result.success(addressBook); } else { return Result.error("没有找到该对象" ); } } @GetMapping("/default") public Result<AddressBook> getDefault () { LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId()); queryWrapper.eq(AddressBook::getIsDefault, 1 ); AddressBook addressBook = addressBookService.getOne(queryWrapper); if (null == addressBook) { return Result.error("没有找到该对象" ); } else { return Result.success(addressBook); } } @GetMapping("/list") public Result<List<AddressBook>> list () { AddressBook addressBook = new AddressBook (); addressBook.setUserId(BaseContext.getCurrentId()); LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(null != addressBook.getUserId(), AddressBook::getUserId, addressBook.getUserId()); queryWrapper.orderByDesc(AddressBook::getUpdateTime); return Result.success(addressBookService.list(queryWrapper)); } }
核心业务思路
这里不需要请求参数,只需要用户userId,这个可以从Session中获取。
购物车 需求分析
移动端用户可以将菜品/套餐添加到购物车
对于菜品来说,如果设置了口味信息,则需要选择规格后才能加入购物车(前端实现)
对于套餐来说,可以直接点击当前套餐加入购物车
在购物车中可以修改菜品/套餐的数量,也可以清空购物车
表现层代码
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 @RestController @RequestMapping("/shoppingCart") @Slf4j public class ShoppingCartController { @Autowired private ShoppingCartService shoppingCartService; @RequestMapping("/add") public Result<ShoppingCart> add (@RequestBody ShoppingCart shoppingCart) { Long currentId = BaseContext.getCurrentId(); shoppingCart.setUserId(currentId); Long dishId = shoppingCart.getDishId(); LambdaQueryWrapper<ShoppingCart> lambdaQueryWrapper = new LambdaQueryWrapper <>(); lambdaQueryWrapper.eq(ShoppingCart::getUserId, currentId); if (dishId == null ){ lambdaQueryWrapper.eq(ShoppingCart::getSetmealId, shoppingCart.getSetmealId()); }else { lambdaQueryWrapper.eq(ShoppingCart::getDishId, shoppingCart.getDishId()); } ShoppingCart shoppingCartOne = shoppingCartService.getOne(lambdaQueryWrapper); if (shoppingCartOne == null ){ shoppingCart.setNumber(1 ); shoppingCart.setCreateTime(LocalDateTime.now()); shoppingCartService.save(shoppingCart); shoppingCartOne = shoppingCart; }else { Integer number = shoppingCartOne.getNumber(); shoppingCartOne.setNumber(++number); shoppingCartService.updateById(shoppingCartOne); } return Result.success(shoppingCartOne); } @RequestMapping("/sub") public Result<ShoppingCart> sub (@RequestBody ShoppingCart shoppingCart) { Long userId = BaseContext.getCurrentId(); shoppingCart.setUserId(userId); LambdaQueryWrapper<ShoppingCart> lambdaQueryWrapper = new LambdaQueryWrapper <>(); lambdaQueryWrapper.eq(ShoppingCart::getUserId, userId); if (shoppingCart.getDishId() != null ){ lambdaQueryWrapper.eq(ShoppingCart::getDishId, shoppingCart.getDishId()); } else { lambdaQueryWrapper.eq(ShoppingCart::getSetmealId, shoppingCart.getSetmealId()); } ShoppingCart one = shoppingCartService.getOne(lambdaQueryWrapper); if (one.getNumber() == 1 ){ shoppingCartService.remove(lambdaQueryWrapper); } else if (one.getNumber() > 1 ){ one.setNumber(one.getNumber() - 1 ); shoppingCartService.updateById(one); } return Result.success(one); } @GetMapping("/list") public Result<List<ShoppingCart>> list () { Long currentId = BaseContext.getCurrentId(); LambdaQueryWrapper<ShoppingCart> lambdaQueryWrapper = new LambdaQueryWrapper <>(); lambdaQueryWrapper.eq(ShoppingCart::getUserId, currentId); lambdaQueryWrapper.orderByAsc(ShoppingCart::getCreateTime); List<ShoppingCart> list = shoppingCartService.list(lambdaQueryWrapper); return Result.success(list); } @DeleteMapping("/clean") public Result<String> cleanShoppingCart () { Long currentId = BaseContext.getCurrentId(); LambdaQueryWrapper<ShoppingCart> lambdaQueryWrapper = new LambdaQueryWrapper <>(); lambdaQueryWrapper.eq(ShoppingCart::getUserId, currentId); shoppingCartService.remove(lambdaQueryWrapper); return Result.success("清空购物车成功" ); } }
用户下单 流程分析
在订单确认页面中,发送ajax请求,请求服务端,获取当前登录用户的默认地址
在订单确认页面,发送ajax请求,请求服务端,获取当前登录用户的购物车数据
在订单确认页面点击去支付按钮,发送ajax请求,请求服务端,完成下单操作
数据模型
orders数据表:
order_detail数据表:
“下单”核心业务思路
步骤1:查询当前用户的购物车,判断购物车是否为空,如果为空抛出业务异常无法下单;
步骤2:向订单表orders插入(1条)数据;
步骤3:向订单明细表order_detail插入(多条)数据;
步骤4:清空购物车数据
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 @Override public void submit (Orders orders) { Long currentId = BaseContext.getCurrentId(); LambdaQueryWrapper<ShoppingCart> cartLambdaQueryWrapper = new LambdaQueryWrapper <>(); cartLambdaQueryWrapper.eq(ShoppingCart::getUserId, currentId); List<ShoppingCart> cartList = shoppingCartService.list(cartLambdaQueryWrapper); if (cartList == null || cartList.size() == 0 ){ throw new ServiceException ("购物车为空,无法下单" ); } User user = userService.getById(currentId); Long addressBookId = orders.getAddressBookId(); AddressBook addressBook = addressBookService.getById(addressBookId); long orderId = IdWorker.getId(); AtomicInteger amount = new AtomicInteger (0 ); List<OrderDetail> orderDetails = cartList.stream().map((item) -> { OrderDetail orderDetail = new OrderDetail (); orderDetail.setOrderId(orderId); orderDetail.setNumber(item.getNumber()); orderDetail.setDishFlavor(item.getDishFlavor()); orderDetail.setDishId(item.getDishId()); orderDetail.setSetmealId(item.getSetmealId()); orderDetail.setName(item.getName()); orderDetail.setImage(item.getImage()); orderDetail.setAmount(item.getAmount()); amount.addAndGet(item.getAmount().multiply(new BigDecimal (item.getNumber())).intValue()); return orderDetail; }).collect(Collectors.toList()); orders.setId(orderId); orders.setOrderTime(LocalDateTime.now()); orders.setCheckoutTime(LocalDateTime.now()); orders.setStatus(2 ); orders.setAmount(new BigDecimal (amount.get())); orders.setUserId(currentId); orders.setNumber(String.valueOf(orderId)); orders.setUserName(user.getName()); orders.setConsignee(addressBook.getConsignee()); orders.setPhone(addressBook.getPhone()); orders.setAddress((addressBook.getProvinceName() == null ? "" : addressBook.getProvinceName()) + (addressBook.getCityName() == null ? "" : addressBook.getCityName()) + (addressBook.getDistrictName() == null ? "" : addressBook.getDistrictName()) + (addressBook.getDetail() == null ? "" : addressBook.getDetail())); this .save(orders); orderDetailService.saveBatch(orderDetails); shoppingCartService.remove(cartLambdaQueryWrapper); }