用户移动端

  • 一、手机验证码登录
    • 1. 短信发送
      • 1.1 短信服务介绍
      • 1.2 阿里云短信服务
    • 2. 手机验证码登录
    • 2.1 需求分析
    • 2.2 数据模型
    • 2.3 代码开发
  • 二、菜品展示、购物车、下单
    • 1. 用户地址薄
      • 1.1 需求分析
      • 1.2 数据模型
      • 1.3 功能代码
    • 2.菜品展示
      • 2.1 需求分析
      • 2.2 代码开发
    • 3.购物车
      • 3.1 需求分析
      • 3.2 数据模型
      • 3.3 代码开发
    • 4.下单
      • 4.1 需求分析
      • 4.2 数据模型
      • 4.3 代码开发
    • 5.用户查看订单、再来一单、查看套餐、用户退出
      • 5.1 需求分析
      • 5.2 代码开发

一、手机验证码登录

1. 短信发送

1.1 短信服务介绍

市面上有很多第三方提供的短信服务,这些第三方短信服务和各个运营商(移动、联通、电信)对接,我么只需要注册成会员并且按照提供的开发文档进行调用就可以发送短信了。这些服务一般都是收费的。
常用的短信服务:(阿里云、华为云、腾讯云、京东、梦网、乐信)

1.2 阿里云短信服务

阿里云短信服务(Short Message Service)是广大企业客户快速触达手机用户所优选使用的通信能力。调用API或群发助手,即可发送验证码、通知类和营销类短信;国内验证码秒级触达,到达率最高可达99%;国际/港澳台短信覆盖200多个国家和地区,安全稳定,广受出海企业选用。

应用场景:(验证码,短信通知,推广短信 )

阿里云官网:https://www.aliyun.com/

进入短信服务管理页面,选择国内消菜单:

短信签名是短信发送者的署名,表示发送方的身份。

1、添加签名

2、切换到模板管理标签页:

短信模板包含短信发送内容、场景、变量信息。

添加模板:

设置签名,和模板后设置AccessKey
光标移动到用户头像上,在弹出的窗口中点击[AccessKey管理]:


点击后跳转到:


创建成功:




C
使用阿里云短信服务发送短信,参考官方提供的文档即可。
快速入门:链接

具体步骤如下:
1、导入Maven坐标

        <dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId><version>4.5.16</version></dependency><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-dysmsapi</artifactId><version>1.1.0</version></dependency>

2、调用API

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.profile.DefaultProfile;
import com.google.gson.Gson;
import java.util.*;
import com.aliyuncs.dysmsapi.model.v20170525.*;public class SendSms {public static void main(String[] args) {DefaultProfile profile = DefaultProfile.getProfile("cn-beijing", "<your-access-key-id>", "<your-access-key-secret>");/** use STS TokenDefaultProfile profile = DefaultProfile.getProfile("<your-region-id>",           // The region ID"<your-access-key-id>",       // The AccessKey ID of the RAM account"<your-access-key-secret>",   // The AccessKey Secret of the RAM account"<your-sts-token>");          // STS Token**/IAcsClient client = new DefaultAcsClient(profile);SendSmsRequest request = new SendSmsRequest();request.setPhoneNumbers("1368846****");//接收短信的手机号码request.setSignName("阿里云");//短信签名名称request.setTemplateCode("SMS_20933****");//短信模板CODErequest.setTemplateParam("张三");//短信模板变量对应的实际值try {SendSmsResponse response = client.getAcsResponse(request);System.out.println(new Gson().toJson(response));} catch (ServerException e) {e.printStackTrace();} catch (ClientException e) {System.out.println("ErrCode:" + e.getErrCode());System.out.println("ErrMsg:" + e.getErrMsg());System.out.println("RequestId:" + e.getRequestId());}}
}

2. 手机验证码登录

2.1 需求分析

为了方便用户登录,移动端通常会提供手机验证码登录的功能。
手机验证码登录的优点:

  • 方便快捷,无需注册,直接登录
  • 使用短信验证码作为登录凭证,无需记忆
  • 安全

登录流程:
输入手机号->获取验证码->输入验证码->点击登录->登录成功

注意:通过手机验证码登录,手机号是区分不同用户的标识。

2.2 数据模型

通过手机验证码登录时,涉及到的表为user表即,用户表,结构如下:

2.3 代码开发

前端页面和服务端的交互过程:
1、在登录页面(front/page/login.html2)输入手机号,点击【获取验证码】按钮,页面发送ajax请求,在服务端调用短信服务API给指定的手机号发送验证码短信
2、在登录页面输入验证码,点击登录,发送ajax请求,在服务端处理登录请求

开发手机验证码登录功能,就是在服务端编写代码去处理前端页面发送的这两次请求。

开发业务功能前,先将需要用到的类和接口基本结构创建好:

  • 实体类User
/*** 用户信息*/
@Data
public class User implements Serializable {private static final long serialVersionUID = 1L;private Long id;//姓名private String name;//手机号private String phone;//性别 0 女 1 男private String sex;//身份证号private String idNumber;//头像private String avatar;//状态 0:禁用,1:正常private Integer status;
}
  • Mapper接口UserMapper
@Mapper
public interface UserMapper extends BaseMapper<User> {}
  • 业务层接口UserService
public interface UserService extends IService<User> {}
  • 业务层实现接口UserServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {}
  • 控制层Usercontroller
@RestController
@RequestMapping("/user")
public class Usercontroller {@Autowiredprivate UserService userService;
}
  • 工具类SMSUtils、ValidateCodeUtils
/*** 短信发送工具类*/
public class SMSUtils {/*** 发送短信* @param signName 签名* @param templateCode 模板* @param phoneNumbers 手机号* @param param 参数*/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();}}}
/*** 随机生成验证码工具类*/
public class ValidateCodeUtils {/*** 随机生成验证码* @param length 长度为4位或者6位* @return*/public static Integer generateValidateCode(int length){Integer code =null;if(length == 4){code = new Random().nextInt(9999);//生成随机数,最大为9999if(code < 1000){code = code + 1000;//保证随机数为4位数字}}else if(length == 6){code = new Random().nextInt(999999);//生成随机数,最大为999999if(code < 100000){code = code + 100000;//保证随机数为6位数字}}else{throw new RuntimeException("只能生成4位或6位数字验证码");}return code;}/*** 随机生成指定长度字符串验证码* @param length 长度* @return*/public static String generateValidateCode4String(int length){Random rdm = new Random();String hash1 = Integer.toHexString(rdm.nextInt());String capstr = hash1.substring(0, length);return capstr;}
}

前面完成了LoginCheckFilter过滤器的开发,此过滤器用于检查用户的登录状态。我们在进行手机验证码登录时,发送的请求需要在此过滤处理时直接放行。

        //定义不需要处理的请求路径String[] urls = new String[]{"/employee/login","/employee/logout","/backend/**","/front/**","/common/**","/user/sendMsg",    //移动端发送短信"/user/login"       //移动端登录};

在LoginCheckFilter过滤器中扩展逻辑,判断移动用户登录状态:

        //4-2、判断用户登录状态,如果已经登录,则直接放行if (request.getSession().getAttribute("user") != null) {log.info("用户已经登录,用户id为:{}", request.getSession().getAttribute("user"));Long userId = (Long) request.getSession().getAttribute("user");BaseContext.setCurrentId(userId);long id = Thread.currentThread().getId();log.info("线程id:{}", id);filterChain.doFilter(request, response);return;}

二、菜品展示、购物车、下单

1. 用户地址薄

1.1 需求分析

地址薄,指的是移动端消费者用户的地址信息,用户登录成功后可以维护自己的地址信息。同一个用户可以有多个地址信息,但是默认的只能有一个。

1.2 数据模型

用户的地址信息会存储在address_book表,即地址薄表中,具体如下:

1.3 功能代码

  • 实体类AddressBook
/*** 地址簿*/
@Data
public class AddressBook implements Serializable {private static final long serialVersionUID = 1L;private Long id;//用户idprivate Long userId;//收货人private String consignee;//手机号private String phone;//性别 0 女 1 男private String sex;//省级区划编号private String provinceCode;//省级名称private String provinceName;//市级区划编号private String cityCode;//市级名称private String cityName;//区级区划编号private String districtCode;//区级名称private String districtName;//详细地址private String detail;//标签private String label;//是否默认 0 否 1是private Integer isDefault;//创建时间@TableField(fill = FieldFill.INSERT)private LocalDateTime createTime;//更新时间@TableField(fill = FieldFill.INSERT_UPDATE)private LocalDateTime updateTime;//创建人@TableField(fill = FieldFill.INSERT)private Long createUser;//修改人@TableField(fill = FieldFill.INSERT_UPDATE)private Long updateUser;//是否删除private Integer isDeleted;
}
  • Mapper接口 AddressBookMapper
@Mapper
public interface AddressBookMapper extends BaseMapper<AddressBook> {}
  • 业务层接口AddressBookService
public interface AddressBookService extends IService<AddressBook> {}
  • 业务层实现类AddressBookServiceimpl
@Service
public class AddressBookServiceImpl extends ServiceImpl<AddressBookMapper, AddressBook> implements AddressBookService {}
  • 控制层AddressBookController
/*** 地址簿管理*/
@Slf4j
@RestController
@RequestMapping("/addressBook")
public class AddressBookController {@Autowiredprivate AddressBookService addressBookService;/*** 新增一个地直薄*/@PostMappingpublic R<AddressBook> save(@RequestBody AddressBook addressBook) {//设置userId,知道当前这个地址是谁的addressBook.setUserId(BaseContext.getCurrentId());log.info("addressBook:{}", addressBook);addressBookService.save(addressBook);return R.success(addressBook);}/*** 设置默认地址*/@PutMapping("default")public R<AddressBook> setDefault(@RequestBody AddressBook addressBook) {log.info("addressBook:{}", addressBook);LambdaUpdateWrapper<AddressBook> wrapper = new LambdaUpdateWrapper<>();//根据当前登录用户id去匹配地址薄地址wrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());//设置当前默认地址状态为 0  (0不是默认地址)wrapper.set(AddressBook::getIsDefault, 0);//SQL:update address_book set is_default = 0 where user_id = ?addressBookService.update(wrapper);//把当前要改的这条数据的地址状态改成1(1默认地址)addressBook.setIsDefault(1);//SQL:update address_book set is_default = 1 where id = ?addressBookService.updateById(addressBook);return R.success(addressBook);}/*** 根据id查询地址*/@GetMapping("/{id}")public R get(@PathVariable Long id) {AddressBook addressBook = addressBookService.getById(id);if (addressBook != null) {return R.success(addressBook);} else {return R.error("没有找到该对象");}}/*** 查询默认地址*/@GetMapping("default")public R<AddressBook> getDefault() {LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();//根据当前登录的UserID来查queryWrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());//条件查询,查地址状态为1的数据queryWrapper.eq(AddressBook::getIsDefault, 1);//SQL:select * from address_book where user_id = ? and is_default = 1AddressBook addressBook = addressBookService.getOne(queryWrapper);if (null == addressBook) {return R.error("没有找到该对象");} else {return R.success(addressBook);}}/*** 查询指定用户的全部地址*/@GetMapping("/list")public R<List<AddressBook>> list(AddressBook addressBook) {//根据当前登录的UserID来查addressBook.setUserId(BaseContext.getCurrentId());log.info("addressBook:{}", addressBook);//条件构造器LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(null != addressBook.getUserId(), AddressBook::getUserId, addressBook.getUserId());queryWrapper.orderByDesc(AddressBook::getUpdateTime);//SQL:select * from address_book where user_id = ? order by update_time descreturn R.success(addressBookService.list(queryWrapper));}
}

2.菜品展示

2.1 需求分析

用户登录成功后跳转到系统首页,在首页需要根据分类来展示菜品和套餐,如果菜品设置了口味信息,需要展示选择规格按钮,否则显示+按钮。

2.2 代码开发

前端页面和服务端的交互过程:
1、页面(front/index.html)发送ajax请求,获取分类数据(菜品分类和套餐分类)
2、页面发送ajax请求,获取一个分类下的菜品或者套餐

开发菜品展示功能,就是在服务端编写代码去处理前端发送的这2次请求即可。

注意: 首页加载完成之后,还发送了一条ajax请求用于加载购物车数据,此次请求的地址暂时修改下,从静态json文件获取数据,等后续开发购物车功能时在修改过来。
main.js:

//获取购物车内商品的集合
function cartListApi(data) {return $axios({//'url': '/shoppingCart/list',          //原来的请求地址'url': '/front/cartData.json','method': 'get',params: {...data}})
}

3.购物车

3.1 需求分析

移动端用户可以将菜品或者套餐添加到购物车,对于菜品来说,如果设置了口味信息,则需要选择规格后才能加入购物车,对于套餐来说,可以直接点击加号将套餐加入购物车。在购物车中可以修改菜品和套餐的数量,也可以清空购物车

3.2 数据模型

购物车对应的数据表为shopping_cart表,结构如下:

3.3 代码开发

购物车操作时前端页面和服务端的交互过程:
1、点击加入购物车或者加号按钮,页面发送ajax请求,请求服务端,将菜品或者套餐添加到购物车
2、点击购物车图标,页面发送ajax请求,请求服务端查询购物车中的菜品和套餐
3、点击清空购物车按钮,页面发送ajax请求,请求服务端来执行清空购物车操作

开发购物车功能,就是在服务单编写代码去处理前端页面发送的3次请求即可。

开发购物车功能时需要把请求地址改回来:

//获取购物车内商品的集合
function cartListApi(data) {return $axios({'url': '/shoppingCart/list',          //原来的请求地址//'url': '/front/cartData.json','method': 'get',params: {...data}})
}

创建需要用的类和接口:

  • 实体类ShoppingCart
/*** 购物车*/
@Data
public class ShoppingCart implements Serializable {private static final long serialVersionUID = 1L;private Long id;//名称private String name;//用户idprivate Long userId;//菜品idprivate Long dishId;//套餐idprivate Long setmealId;//口味private String dishFlavor;//数量private Integer number;//金额private BigDecimal amount;//图片private String image;private LocalDateTime createTime;
}
  • Mapper接口 ShoppingCartMapper
@Mapper
public interface ShoppingCartMapper extends BaseMapper<ShoppingCart> {}
  • 业务层接口 ShoppingCartService
public interface ShoppingCartService extends IService<ShoppingCart> {}
  • 业务层实现类 ShoppingCartServiceImp
@Service
public class ShoppingCartServiceImp extends ServiceImpl<ShoppingCartMapper, ShoppingCart> implements ShoppingCartService {}
  • 控制层 ShoppingCartController
    添加购物车、购物车套餐或者是菜品数量减少设置、查看购物车、清空购物车功能的实现

/*** 购物车*/
@Slf4j
@RestController
@RequestMapping("/shoppingCart")
public class ShoppingCartController {@Autowiredprivate ShoppingCartService shoppingCartService;/*** 添加购物车** @param shoppingCart* @return*/@PostMapping("/add")public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart) {log.info("购物车数据:{}", shoppingCart);//设置用户id,指定当前是哪个用户的的购物车数据Long currentId = BaseContext.getCurrentId();shoppingCart.setUserId(currentId);//查询当前菜品或者套餐是否在购物车中Long dishId = shoppingCart.getDishId();LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(ShoppingCart::getUserId, currentId);if (dishId != null) {//添加到购物车的是菜品queryWrapper.eq(ShoppingCart::getDishId, dishId);} else {//添加到购物车的是套餐queryWrapper.eq(ShoppingCart::getSetmealId, shoppingCart.getSetmealId());}//SQL:select * from shopping_cart where user_id = ? and dish_id/setmeal_id = ?ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper);if (cartServiceOne != null) {//如果已经存在,就在原来数量基础上加一Integer number = cartServiceOne.getNumber();cartServiceOne.setNumber(number + 1);shoppingCartService.updateById(cartServiceOne);} else {// 如果不存在,添加到购物车,数量默认就是1shoppingCart.setNumber(1);shoppingCart.setCreateTime(LocalDateTime.now());shoppingCartService.save(shoppingCart);cartServiceOne = shoppingCart;}return R.success(cartServiceOne);}/*** 套餐或者是菜品数量减少设置* @param shoppingCart* @return*/@PostMapping("/sub")@Transactional//携带的参数可能是dish_id也可能是setmeal_id所以我们需要用shoppingCart接收public R<ShoppingCart> sub(@RequestBody ShoppingCart shoppingCart) {//菜品idLong dishId = shoppingCart.getDishId();//套餐idLong setmealId = shoppingCart.getSetmealId();LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();//代表数量减少的是菜品数量if (dishId != null) {//通过dishId查出购物车菜品queryWrapper.eq(ShoppingCart::getDishId, dishId);//查询当前用户对应的购物车queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());ShoppingCart cartDish = shoppingCartService.getOne(queryWrapper);cartDish.setNumber(cartDish.getNumber() - 1);Integer number = cartDish.getNumber();if (number > 0) {//对数据进行更新shoppingCartService.updateById(cartDish);} else if (number == 0) {//如果购物车的数量为0,那么就将菜品从购物车删除shoppingCartService.removeById(cartDish.getId());} else if (number < 0) {return R.error("操作异常");}return R.success(cartDish);}//代表数量减少的是套餐数量if (setmealId != null) {queryWrapper.eq(ShoppingCart::getSetmealId, setmealId);//查询当前用户对应的购物车queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());ShoppingCart cartSetmeal = shoppingCartService.getOne(queryWrapper);cartSetmeal.setNumber(cartSetmeal.getNumber() - 1);Integer number = cartSetmeal.getNumber();if (number > 0) {//对数据进行更新shoppingCartService.updateById(cartSetmeal);} else if (number == 0) {//如果购物车的数量为0,那么就将套餐从购物车删除shoppingCartService.removeById(cartSetmeal);} else if (number < 0) {return R.error("操作异常");}return R.success(cartSetmeal);}//如果菜品和套餐都进不去return R.error("操作异常");}/*** 查看购物车** @return*/@GetMapping("/list")public R<List<ShoppingCart>> list() {log.info("查看购物车...");LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();Long currentId = BaseContext.getCurrentId();queryWrapper.eq(ShoppingCart::getUserId, currentId);queryWrapper.orderByAsc(ShoppingCart::getCreateTime);List<ShoppingCart> list = shoppingCartService.list(queryWrapper);return R.success(list);}/*** 清空购物车** @return*/@DeleteMapping("/clean")public R<String> clean() {//SQL:Delete from shopping_cart where user_id = ?LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());shoppingCartService.remove(queryWrapper);return R.success("清空购物车成功");}
}

4.下单

4.1 需求分析

移动端用户将菜品或者套餐加入购物车后,可以点击购物车中的去结算按钮,页面跳转到订单确认页面,点击去支付按钮完成下单操作。

4.2 数据模型

用户下单业务对应的数据表为orders表和order_detail表:

  • orders:订单表

  • order_detail:订单明细表

4.3 代码开发

用户下单操作时前端页面和服务端的交互过程:
1、在购物车中点击,去结算按钮,页面跳转到订单确认页面
2、在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的默认地址(前面已实现)
3、在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的购物车数据(前面已实现)
4、在订单确认页面点击去支付按钮,发送ajax请求,请求服务端完成下单操作

开发用户下单功能,其实就是在服务端编写代码去处理前端页面发送的请求即可。

创建需要用到的类和接口:

  • 实体类Orders、OrderDetail
/*** 订单*/
@Data
public class Orders implements Serializable {private static final long serialVersionUID = 1L;private Long id;//订单号private String number;//订单状态 1待付款,2待派送,3已派送,4已完成,5已取消private Integer status;//下单用户idprivate Long userId;//地址idprivate Long addressBookId;//下单时间private LocalDateTime orderTime;//结账时间private LocalDateTime checkoutTime;//支付方式 1微信,2支付宝private Integer payMethod;//实收金额private BigDecimal amount;//备注private String remark;//用户名private String userName;//手机号private String phone;//地址private String address;//收货人private String consignee;
}
/*** 订单明细*/
@Data
public class OrderDetail implements Serializable {private static final long serialVersionUID = 1L;private Long id;//名称private String name;//订单idprivate Long orderId;//菜品idprivate Long dishId;//套餐idprivate Long setmealId;//口味private String dishFlavor;//数量private Integer number;//金额private BigDecimal amount;//图片private String image;
}
  • Mapper接口OrderMapper、OrderDetailMapper
@Mapper
public interface OrderMapper extends BaseMapper<Order> {}
@Mapper
public interface OrderDetailMapper extends BaseMapper<OrderDetail> {}
  • 业务层接口OrderService、OrderDetailService
public interface OrderService extends IService<Order> {}
public interface OrderDetailService extends IService<OrderDetail> {}
  • 业务层实现类OrderServiceImpl、OrderDetailServiceImpl
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {}
@Service
public class OrderDetailServiceImpl extends ServiceImpl<OrderDetailMapper, OrderDetail> implements OrderDetailService {}
  • 控制层OrderController、OrderDetailController
@RestController
@RequestMapping("/order")
public class OrderController {@Autowiredprivate OrderService orderService;
}
@RestController
@RequestMapping("/orderDetail")
public class OrderDetailController {@Autowiredprivate OrderDetailService orderDetailService;
}

业务实现层代码:OrderServiceImpl

@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Orders> implements OrderService {@Autowiredprivate OrderDetailService orderDetailService;@Autowiredprivate ShoppingCartService shoppingCartService;@Autowiredprivate UserService userService;@Autowiredprivate AddressBookService addressBookService;/*** 用户下单** @param orders*/@Override@Transactionalpublic void subimt(Orders orders) {//获取当前用户idLong currentId = BaseContext.getCurrentId();//查询当前用户的购物车数据LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(ShoppingCart::getUserId, currentId);List<ShoppingCart> shoppingCarts = shoppingCartService.list(queryWrapper);if (shoppingCarts == null || shoppingCarts.size() == 0) {throw new CustomException("购物车为空不能下单");}//查询用户数据User user = userService.getById(currentId);//查询地址数据Long addressBookId = orders.getAddressBookId();AddressBook addressBook = addressBookService.getById(addressBookId);if (addressBook == null) {throw new CustomException("用户地址不能为空");}long orderId = IdWorker.getId();//原子操作,保证线程安全AtomicInteger amount = new AtomicInteger(0);List<OrderDetail> orderDetails = shoppingCarts.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.setNumber(String.valueOf(orderId));orders.setId(orderId);orders.setOrderTime(LocalDateTime.now());orders.setCheckoutTime(LocalDateTime.now());orders.setStatus(2);orders.setAmount(new BigDecimal(amount.get()));//总金额orders.setUserId(currentId);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(queryWrapper);}}

控制层代码:

@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {@Autowiredprivate OrderService orderService;/*** 用户下单** @param orders* @return*///当前用户已经完成登录了,可以从session或者BaseContext上下文工具类获取当前用户id,所以用户id不需要传递@PostMapping("/submit")public R<String> submit(@RequestBody Orders orders) {log.info("订单数据:{}", orders);orderService.subimt(orders);return R.success("下单成功!"); }}

5.用户查看订单、再来一单、查看套餐、用户退出

5.1 需求分析

用户支付后通过点击查看订单,可以查看下单的菜品或者套餐的详细信息

5.2 代码开发

分析前端代码: 这个item是从order.orderDetails里面 获取到的,但是orders实体类里面并没有orderDetails这个属性,而且数据库中这个order表里面也没有这个字段,所以这里我使用的是dto来封装数据给前端,这就需要使用到dto对象的分页查询了。

创建需要用到的类和接口:

  • DTO:OrdersDto
@Data
public class OrdersDto extends Orders {private String userName;private String phone;private String address;private String consignee;private List<OrderDetail> orderDetails;}
  • 业务实现层
    /*** 用户个人中心订单信息查看** @param page* @param pageSize* @return*/@Overridepublic Page<OrdersDto> userPage(int page, int pageSize) {//分页构造器对象Page<Orders> pageInfo = new Page<>(page, pageSize);Page<OrdersDto> dtoPage = new Page<>();//创建条件查询对象LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper<>();//添加排序条件,根据更新时间降序排序queryWrapper.orderByDesc(Orders::getOrderTime);this.page(pageInfo, queryWrapper);//通过OrderId查寻对应的菜品/套餐LambdaQueryWrapper<OrderDetail> wrapper = new LambdaQueryWrapper<>();//对OrderDto进行需要的属性赋值List<Orders> records = pageInfo.getRecords();List<OrdersDto> list = records.stream().map((item) -> {OrdersDto ordersDto = new OrdersDto();//为orderDetails属性赋值//获取订单IDLong orderId = item.getId();//根据订单ID查询对应的订单明细wrapper.eq(OrderDetail::getOrderId, orderId);List<OrderDetail> orderDetailList = orderDetailService.list(wrapper);BeanUtils.copyProperties(item, ordersDto);//对ordersDto的OorderDetai属性进行赋值ordersDto.setOrderDetails(orderDetailList);return ordersDto;}).collect(Collectors.toList());BeanUtils.copyProperties(pageInfo, dtoPage, "records");dtoPage.setRecords(list);return dtoPage;}
  • 控制层
    /*** 用户个人中心订单信息查看** @param page* @param pageSize* @return*/@GetMapping("/userPage")public R<Page> userPage(int page, int pageSize) {Page<OrdersDto> dtoPage = orderService.userPage(page, pageSize);return R.success(dtoPage);}

再来一单:

  • 业务实现层
    /*** 再来一单** @param order*/@Overridepublic void again(Orders order) {//获取订单里订单号Orders orderId = this.getById(order.getId());String number = order.getNumber();//根据条件进行查询LambdaQueryWrapper<OrderDetail> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(OrderDetail::getOrderId, number);List<OrderDetail> orderDetailList = orderDetailService.list(queryWrapper);//根据查到的数据再次添加到购物车里List<ShoppingCart> list = orderDetailList.stream().map((item) -> {//把从order表中和order_details表中获取到的数据赋值给这个购物车对象ShoppingCart shoppingCart = new ShoppingCart();shoppingCart.setName(item.getName());shoppingCart.setImage(item.getImage());shoppingCart.setUserId(BaseContext.getCurrentId());shoppingCart.setDishId(item.getDishId());shoppingCart.setSetmealId(item.getSetmealId());shoppingCart.setDishFlavor(item.getDishFlavor());shoppingCart.setNumber(item.getNumber());shoppingCart.setAmount(item.getAmount());return shoppingCart;}).collect(Collectors.toList());shoppingCartService.saveBatch(list);}
  • 控制层
    /*** 再来一单* @param order* @return*/@PostMapping("/again")public R<String> again(@RequestBody Orders order) {orderService.again(order);return R.success("再来一单啊");}

查看套餐详情

控制层SetmealController

    /*** 查看套餐详情* @param SetmealId* @return*/@GetMapping("/dish/{id}")//使用路径来传值的public R<List<Dish>> dish(@PathVariable("id") Long SetmealId) {LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(SetmealDish::getSetmealId, SetmealId);List<SetmealDish> list = setmealDishService.list(queryWrapper);LambdaQueryWrapper<Dish> queryWrapper2 = new LambdaQueryWrapper<>();ArrayList<Long> dishIdList = new ArrayList<>();for (SetmealDish setmealDish : list) {Long dishId = setmealDish.getDishId();dishIdList.add(dishId);}queryWrapper2.in(Dish::getId, dishIdList);List<Dish> dishList = dishService.list(queryWrapper2);return R.success(dishList);}

Usercontroller控制层

    /*** 用户退出** @param request* @return*/@PostMapping("/loginout")public R<String> logout(HttpServletRequest request) {//清理Session中保存的当前登录用户的idrequest.getSession().removeAttribute("user");return R.success("退出成功");}

瑞吉外卖【用户移动端】相关推荐

  1. 黑马瑞吉外卖之移动端验证码登录使用qq邮箱

    黑马瑞吉外卖之移动端验证码登录 说明 用qq邮箱进行验证码发送 hi,我是兰舟千帆,本次更文还是将瑞吉外卖的笔记完善一下.每一篇都写得很认真哦,希望需要的人可以得到帮助,谢谢你们的阅读. 说明 pc端 ...

  2. 瑞吉外卖之移动端菜品数据的展示

    瑞吉外卖之移动端菜品数据的展示 界面分析 界面分析 上篇我们主要完成了了瑞吉外卖移动端界面登录的功能.完成界面登录自然要进入主界面. 我们和前面的登录界面进行衔接上. 于是我们跳转到这个界面,也就是我 ...

  3. 黑马瑞吉外卖用户端顾客手机端页面展示

    八.顾客手机端页面展示 0.地址管理 需求分析: 一个用户可以有多个地址 一个用户只能设置一个默认地址 设置默认地址时,先将该用户下所有的地址都设置为不默认.再设置默认地址. 将AdressBook相 ...

  4. 【SpringBoot项目实战+思维导图】瑞吉外卖⑥(用户地址簿功能、菜品展示、购物车、下单)

    文章目录 用户地址簿功能 需求分析 数据模型 导入功能代码 功能测试 思维导图总结 菜品展示 需求分析 前端页面分析 代码开发 查询菜品方法修改 根据分类ID查询套餐 功能测试 思维导图总结 购物车 ...

  5. 瑞吉外卖项目移动端再来一单功能

    我的前端页面是拷贝了资料中day6的前端页面 这里"/order/again"浏览器传来的数据是orders表中的id数据 注意:数据库中order_detail表中的order_ ...

  6. 瑞吉外卖-移动端菜品展示功能,购物车添加菜品及修改等功能,用户下单功能及历史订单派送功能

    整理记录下学习整个瑞吉外卖项目,详细代码可在我的Gitee仓库瑞吉外卖实战克隆下载学习使用! 12.菜品展示 12.1 需求分析 12.2 更改前台页面使其正常显示 由于购物车功能还未进行开发,所以修 ...

  7. 瑞吉外卖订单后台和用户端功能

    项目场景: 在编写瑞吉外卖的订单功能的时候,还是按照惯例,订单的一些功能是我们自己做,毕竟都看了一百多集视频了,自己应该能写一点东西了. 遇到的坑: 后台的订单列表功能,有一个输入订单号查询的功能,看 ...

  8. 猿创征文|瑞吉外卖——移动端_邮箱验证码登录

    个人名片: 博主:酒徒ᝰ. 专栏:瑞吉外卖 个人简介:沉醉在酒中,借着一股酒劲,去拼搏一个未来. 本篇励志:如果决意去做一件事了就不要再问自己和别人值不值得. 本项目基于B站黑马程序员Java项目实战 ...

  9. 猿创征文|瑞吉外卖——移动端_订单明细

    个人名片: 博主:酒徒ᝰ. 专栏:瑞吉外卖 个人简介:沉醉在酒中,借着一股酒劲,去拼搏一个未来. 本篇励志:一些伤口之所以总会痛,那是因为你总是去摸.有一种女人像贝壳一样,外面很硬,内在其实很软.心里 ...

  10. 瑞吉外卖--套餐的添加修改等功能,短信验证登录原理操作及用户地址管理功能

    整理记录下学习整个瑞吉外卖项目,详细代码可在我的Gitee仓库瑞吉外卖实战克隆下载学习使用! 9.套餐管理 9.1 新增套餐 9.1.1 需求分析 9.1.2 数据模型 新增套餐就是将新增页面录入的套 ...

最新文章

  1. FlasCC例子研究之Animation
  2. 如何有效提升网站转化率?从四个方面着手分析!
  3. java清除浏览器记录_Javascript脚本之清除浏览器历史数据
  4. 解决IDM“警告:您在以管理员身份运行IDM,在该模式下,IDM无法接管浏览器的下载事件”的问题。
  5. 深度学习(十二)——Winograd(2)
  6. Android中ScrollView嵌套WebView
  7. 排序算法(1)----选择排序算法
  8. 12. 我的第一个容器化应用
  9. 解决开始时cmd进入dos中ping会提示未出现在内部文件中
  10. Python Django之路由系统
  11. QT入门(六)——代码创建计算器界面|可以计算的简易计算器|按键的关联
  12. [转]论window和Linux之长短
  13. 雾霾都没走!尾气净化器就别来凑热闹了!
  14. OpenGL中的抗锯齿绘线(上)
  15. 游戏攻略 一 天堂W(韩)
  16. 人类历史的进程vs互联网的进程
  17. 非线性方程求根方法——二分法
  18. 使用内部RC的导致串口通讯错误率高甚至失败的原因及解决办法
  19. cocos如何在游戏中动态设置横竖屏
  20. 超级详细:公网环境下登录 Docker 仓库: Docker Hub 或 国内阿里镜像仓库!超级解惑!(推送镜像到docker hub 或 国内阿里云镜像仓库)

热门文章

  1. 2021年中国阴极铜产量及重点企业对比分析:江西铜业vs铜陵有色vs云南铜业[图]
  2. 关于java中getInstance()方法
  3. MySQL 8.0.23免安装版配置步骤
  4. ForkJoinPool的理解与使用
  5. 树莓派3 基于Ubuntu mate 16.04的调用中文输入法
  6. MySQL主从复制原理和使用
  7. bugku 输入密码查看flag
  8. 8、店铺分类 - 后端功能开发 - 微擎小程序模块应用开发
  9. 石墨烯能带matlab,锯齿石墨烯能带matlab计算程序
  10. vue 权限管理 动态路由(6)