瑞吉外卖【用户移动端】

2023-11-04

一、手机验证码登录

在这里插入图片描述

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 Token
        DefaultProfile 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****");//短信模板CODE
        request.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 {
    @Autowired
    private 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);//生成随机数,最大为9999
            if(code < 1000){
                code = code + 1000;//保证随机数为4位数字
            }
        }else if(length == 6){
            code = new Random().nextInt(999999);//生成随机数,最大为999999
            if(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;


    //用户id
    private 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 {

    @Autowired
    private AddressBookService addressBookService;

    /**
     * 新增一个地直薄
     */
    @PostMapping
    public 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 = 1
        AddressBook 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 desc
        return 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;

    //用户id
    private Long userId;

    //菜品id
    private Long dishId;

    //套餐id
    private 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 {

    @Autowired
    private 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 {
            // 如果不存在,添加到购物车,数量默认就是1
            shoppingCart.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) {
        //菜品id
        Long dishId = shoppingCart.getDishId();
        //套餐id
        Long 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;


    //下单用户id
    private Long userId;

    //地址id
    private 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;

    //订单id
    private Long orderId;


    //菜品id
    private Long dishId;


    //套餐id
    private 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 {

    @Autowired
    private OrderService orderService;
}

@RestController
@RequestMapping("/orderDetail")
public class OrderDetailController {
    @Autowired
    private OrderDetailService orderDetailService;
}

业务实现层代码:OrderServiceImpl

@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Orders> implements OrderService {

    @Autowired
    private OrderDetailService orderDetailService;

    @Autowired
    private ShoppingCartService shoppingCartService;

    @Autowired
    private UserService userService;

    @Autowired
    private AddressBookService addressBookService;

    /**
     * 用户下单
     *
     * @param orders
     */
    @Override
    @Transactional
    public void subimt(Orders orders) {
        //获取当前用户id
        Long 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 {

    @Autowired
    private 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
     */
    @Override
    public 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属性赋值
            //获取订单ID
            Long 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
     */
    @Override
    public 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中保存的当前登录用户的id
        request.getSession().removeAttribute("user");
        return R.success("退出成功");
    }

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

瑞吉外卖【用户移动端】 的相关文章

  • 如何克服原语按值传递的事实

    我有一段很长的代码来计算两个值 doubles 对我来说 我在几个地方使用了这段代码 为了坚持 DRY 原则 我应该将这段代码重构为一个很好的单元测试方法 但是我不能让它返回两个双精度数 而双精度数是原始的 因此不能按值传递和操作 我能想到
  • 如何使用 Java 中的 Web 服务(例如 Axis2)发送复杂对象的数组或集合?

    我对 SOAP Web 服务还比较陌生 虽然我完成了一些较小的 Web 服务项目 但我偶然从来不需要返回 或用作参数 复杂 对象的数组或集合 当我尝试这样做时 根据我的 SOAP 绑定风格 我会得到不同的奇怪行为 当我使用RPC 文字 我可
  • 在文本文件中写入多行(java)

    下面的代码是运行命令cmd并使用命令行的输出生成一个文本文件 下面的代码在 Eclipse 的输出窗口中显示了正确的信息 但在文本文件中只打印了最后一行 谁能帮我这个 import java io public class TextFile
  • 插入最大日期(独立于数据库)

    在我的本地设置中 我使用一个简单的 H2 数据库 托管 解决方案将有另一个 类似但不相同 数据库 我需要将最大可能日期插入到日期时间列中 我尝试使用 Instant MAX 但是 这会导致列中出现 169104626 12 11 20 08
  • 比较两个文本文件的最快方法是什么,不将移动的行视为不同

    我有两个文件非常大 每个文件有 50000 行 我需要比较这两个文件并识别更改 然而 问题是如果一条线出现在不同的位置 它不应该显示为不同的 例如 考虑这个文件A txt xxxxx yyyyy zzzzz 文件B txt zzzzz xx
  • java中如何连接字符串

    这是我的字符串连接代码 StringSecret java public class StringSecret public static void main String args String s new String abc s co
  • 运行具有外部依赖项的 Scala 脚本

    我在 Users joe scala lib 下有以下 jar commons codec 1 4 jar httpclient 4 1 1 jar httpcore 4 1 jar commons logging 1 1 1 jar ht
  • 按第一列排序二维数组,然后按第二列排序

    int arrs 1 100 11 22 1 11 2 12 Arrays sort arrs a b gt a 0 b 0 上面的数组已排序为 1 100 1 11 2 12 11 22 我希望它们按以下方式排序a 0 b 0 首先 如果
  • hibernate锁等待超时超时;

    我正在使用 Hibernate 尝试模拟对数据库中同一行的 2 个并发更新 编辑 我将 em1 getTransaction commit 移至 em1 flush 之后我没有收到任何 StaleObjectException 两个事务已成
  • Calendar.getInstance(TimeZone.getTimeZone("UTC")) 不返回 UTC 时间

    我对得到的结果真的很困惑Calendar getInstance TimeZone getTimeZone UTC 方法调用 它返回 IST 时间 这是我使用的代码 Calendar cal Two Calendar getInstance
  • Java 8 流 - 合并共享相同 ID 的对象集合

    我有一系列发票 class Invoice int month BigDecimal amount 我想合并这些发票 这样我每个月都会收到一张发票 金额是本月发票金额的总和 例如 invoice 1 month 1 amount 1000
  • 将 SignedHash 插入 PDF 中以进行外部签名过程 -workingSample

    遵循电子书第 4 3 3 节 PDF 文档的数字签名 https jira nuxeo com secure attachment 49931 digitalsignatures20130304 pdf 我正在尝试创建一个工作示例 其中 客
  • 使用 SQLITE 按最近的纬度和经度坐标排序

    我必须获得一个 SQLite SQL 语句 以便在给定初始位置的情况下按最近的纬度和经度坐标进行排序 这是我在 sqlite 数据库中的表的例句 SELECT id name lat lng FROM items EXAMPLE RESUL
  • 以编程方式在java的resources/source文件夹中创建文件?

    我有两个资源文件夹 src 这是我的 java 文件 资源 这是我的资源文件 图像 properties 组织在文件夹 包 中 有没有办法以编程方式在该资源文件夹中添加另一个 properties 文件 我尝试过这样的事情 public s
  • Java整数双除法混淆[重复]

    这个问题在这里已经有答案了 方案1 int sum 30 double avg sum 4 result is 7 0 not 7 5 VS 方案2 int sum 30 double avg sum 4 0 Prints lns 7 5
  • Jersey 客户端请求中未设置 Content-Length-Header

    我正在使用 Jersey Client 访问网络服务 如下所示 response r accept MediaType TEXT PLAIN TYPE header content length 0 post String class 其中
  • Trie 数据结构 - Java [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 是否有任何库或文档 链接提供了在 java 中实现 Trie 数据结构的更多信息 任何帮助都会很棒 Thanks 你可以阅读Java特里树
  • 在 RESTful Web 服务中实现注销

    我正在开发一个需要注销服务的移动应用程序 登录服务是通过数据库验证来完成的 现在我陷入了注销状态 退一步 您没有提供有关如何在应用程序中执行身份验证的详细信息 并且很难猜测您在做什么 但是 需要注意的是 在 REST 应用程序中 不能有会话
  • GUI Java 程序 - 绘图程序

    我一直试图找出我的代码有什么问题 这个想法是创建一个小的 Paint 程序并具有红色 绿色 蓝色和透明按钮 我拥有我能想到的让它工作的一切 但无法弄清楚代码有什么问题 该程序打开 然后立即关闭 import java awt import
  • 如何修复:“无法解析类型 java.lang.CharSequence。它是从所需的 .class 文件间接引用的”消息? [复制]

    这个问题在这里已经有答案了 我正在尝试使用这个字符串 amountStr amountStr replace replace replace 但我收到一条错误消息 我知道我收到的错误消息是因为我刚刚发布的字符串已过时 所以我想知道该字符串的

随机推荐

  • Elasticsearch multi-index join实践

    Elasticsearch 多索引 join 实践 注 本文采用的实现语言是python 用到了python中的第三方库 作者 aideny 前言 博主近期在开发一个解析引擎 把我们定义的json格式查询语言解析成多种数据源的查询语言 然后
  • uni-app开发微信小程序,动态修改底部tabbar文字及顶部导航栏文字方法

    修改顶部导航文字 uni setNavigationBarTitle title res data data name 修改底部tabbar文字 uni setTabBarItem index 2 title res data data n
  • 恢复目录数据库发生 ORACLE 错误: ORA-00955: 名称已由现有对象使用

    author skate time 2009 02 19 在创建catalog的时候 如果报如下的错误 可以采用 drop catalog 然后再创建的方法解决 或直接运行spdrop sql 原因可能是以前创建过catalog RMAN
  • c语言程序 输入一个四位数,用c语言编程:输入一个四位数,求出它的个位、十位、百位、千位...

    满意答案 QQ89748770 推荐于 2018 02 26 采纳率 45 等级 13 已帮助 7318人 C代码 int a scanf d a printf 个位 d 十位 d 百位 d 千位 d a 10 a 100 10 a 100
  • python 使用exchange发送邮件

    安装库exchangelib pip install exchangelib 脚本内容 coding utf 8 Created on 2018 2 from exchangelib import DELEGATE Account Cred
  • 2.1矩阵的概念

    矩阵有什么用 矩阵可以用于表示复杂的信息 关系 下图左边是航班图 用 1 表示有航班 0 为无航班信息 这样就可以把左图转化到一张表 这里我联想到了自然语言处理中的词文档共现矩阵 图像处理中每一副数字图像也是一个矩阵 矩阵的定义 直观印象
  • MC9S12XEP100的ATD模块(ADC12B16CV1)

    网上的各种示例基本都是用同步 轮询的方式来使用ATD模块的 自己封装ATD模块时想利用中断改成异步的方式 结果出现了莫名其妙的问题 我明明没有开启比较中断的 结果还是跳到了比较中断里头去了 一气之下 把整个文档翻译了一遍 顺带给大家分享了
  • 什么是mAP(mean average Precision)

    Mean Average Precision 即 平均AP值 AP Average precision 单类标签平均 各个召回率中最大精确率的平均数 的精确率 AP PR Precision Recall 曲线下面积 mAP Mean Av
  • Sharding-JDBC 自定义一致性哈希算法 + 虚拟节点 实现数据库分片策略

    1 Sharding JDBC 分片策略 分片操作是分片键 分片算法 也就是分片策略 目前Sharding JDBC 支持多种分片策略 标准分片策略 对应StandardShardingStrategy 提供对SQL语句中的 IN和BETW
  • YOLOv7默默更新了Anchor-Free

    作者 小书童 编辑 集智书童 点击下方卡片 关注 自动驾驶之心 公众号 ADAS巨卷干货 即可获取 点击进入 自动驾驶之心 目标检测 技术交流群 首先恭喜YOLOv7登录CVPR2023的顶会列车 YOLOv7 u6分支的实现是基于Yolo
  • python画饼图加牵引线_python 用 matplotlib 绘制圆环形嵌套饼图步骤详解

    原博文 2020 05 09 22 23 1 加载库 import matplotlib as mpl import matplotlib pyplot as plt 2 单层圆环饼图 配置字体 显示中文 mpl rcParams font
  • js常规的循环方法

    JavaScript 的常规循环方法有以下几种 1 for 循环 最常用的一种循环方法 可以指定循环的起始值 结束值和步长 for let i 0 i lt array length i 循环体 2 while 循环 只要条件为真 就会一直
  • C++中如何使函数返回数组

    C 中如何使函数返回数组 以前使用java返回数组这些类型都比较方便 用c 的时候突然发现c 不支持返回数组 我就找了下应该怎么实现这这种返回 在C 中 数组不是一种类型 因此不能被直接返回 一般有两种方法来返回一个数组 返回一个指向数组的
  • 终于Github App支持中文简体了!

    GitHub 于 2008 年 4 月 10 日正式上线 目前 其注册用户已经超过 350 万 托管版本数量也是非常之多 2018 年 6 月 4 日 微软宣布 通过 75 亿美元的股票交易收购代码托管平台 GitHub Github作为互
  • 2022年03月青少年软件编程(C语言)等级考试试卷(一级) 试题解析

    1 本题20分 双精度浮点数的输入输出 输入一个双精度浮点数 保留8位小数 输出这个浮点数 时间限制 1000 内存限制 65536 输入 只有一行 一个双精度浮点数 输出
  • 编码的几种实现

    几个概念 Unicode是一种 编码 所谓编码就是一个编号 数字 到字符的一种映射关系 就仅仅是一种一对一的映射而已 Unicode只是一个符号集 它只规定了符号的二进制代码 却没有规定这个二进制代码应该如何存储 GBK UTF 8是一种
  • HTML注释

    目录 HTML 注释标签 实例 实例 实例 条件注释 软件程序标签 一个完整的实例 注释标签 用于在 HTML 插入注释 HTML 注释标签 您能够通过如下语法向 HTML 源代码添加注释 实例 注释 在开始标签中有一个惊叹号 但是结束标签
  • LeetCode:1625. 执行操作后字典序最小的字符串

    题目链接 1625 执行操作后字典序最小的字符串 力扣 LeetCode 题目信息 给你一个字符串 s 以及两个整数 a 和 b 其中 字符串 s 的长度为偶数 且仅由数字 0 到 9 组成 你可以在 s 上按任意顺序多次执行下面两个操作之
  • 用大数乘法计算阶乘

    在比较小的范围内阶乘可以递归实现 而求更大的数的阶乘一般用到long long长整形数 不过 即使这样 在耗时和再大些的阶乘上力有不逮 所以 在输入比较大的情况下 用大数乘法计算阶乘是最好的选择 计算过程分2步 1 输入字符串s 将它的值保
  • 瑞吉外卖【用户移动端】

    用户移动端 一 手机验证码登录 1 短信发送 1 1 短信服务介绍 1 2 阿里云短信服务 2 手机验证码登录 2 1 需求分析 2 2 数据模型 2 3 代码开发 二 菜品展示 购物车 下单 1 用户地址薄 1 1 需求分析 1 2 数据