Day1
实现了登录模块:
利用Session实现会话登录
具体如下:
- 将页面提交的密码password进行md5加密处理
- 匹配用户名并取出用户信息进行判断
- 如果没有查询到则返回登录失败结果
- 密码比对,如果不一致则返回登录失败结果
- 查看员工状态,如果为已禁用状态,则返回员工已禁用结果
- 登录成功,将员工id存入Session并返回登录成功结果
- 用户退出直接清除Session中保存的当前登录员工的id
Day2
完善了登录模块
添加了拦截器
实现了增加用户接口
拦截器Filter:
- 实现Filter接口,重写doFilter方法
- 获取URI并定义不需要处理的请求路径
- 判断本次请求是否需要处理(路径匹配,检查本次请求是否需要放行)
- 不需要处理 ==> 放行
- 已登录(Session中有用户信息) ==> 放行
- 未登录 ==> 通过输出流方式向用户客户端发送数据
增加用户接口:
- 定义全局异常处理(防止用户重复)
- @ControllerAdvice(annotations = {RestController.class, Controller.class})
- @ExceptionHandler(发生异常的字节码)
Day3
实现了员工分页查询接口
- 添加MP的分页查询组件(MybatisPlusInterceptor)
- 定义分页构造器
- 定义条件构造器
- 分析查询条件
- 执行查询(调用Service.page(pageInfo, queryWrapper))
实现了启用、禁用员工账号接口
问题解决:
- 利用消息转换器将前端传来的时间格式对应成时分秒
- 利用消息转换器将用户id由JSON格式转换成Java格式(防止id溢出)
消息转换器的使用:
extendMessageConverters:
- 创建消息转换器对象
- 设置对象转换器,底层使用Jackson将Java对象转为json
- 将上面的消息转换器对象追加到mvc框架的转换器集合中
代码如下:
/**
* 扩展mvc框架的消息转换器
* @param converters
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器...");
// 创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
// 设置对象转换器,底层使用Jackson将Java对象转为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
// 将上面的消息转换器对象追加到mvc框架的转换器集合中
converters.add(0, messageConverter);
}
其中JacksonObjectMapper
是我们自定义的消息转换器。
实现了编辑员工信息接口和公共字段自动填充
MP的公共字段自动填充:
- 在需要自动填充的字段添加注解@TableField(fill = FieldFill.INSERT)
- 实现MetaObjectHandler接口并重写insertFill和updateFill方法
- 使用metaObject.setValue(“字段名称”, 修改后的值)
问题:如果获取用户id以自动填充createUser和updateUser信息呢?
- 每个用户都是一个线程
- 使用ThreadLocal保存和获取当前用户id并封装在BaseContext中
- 在Longin的时候拿到Session中的用户id并set在BaseContext中
什么是ThreadLocal?
- Threadlocal并不是一个Thread,而是Thread的局部变量,当使用Threadlocal维护变量时,Threadlocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
ThreadLocal的常用方法:
- public void set(Tvalue) 设置当前线程的线程局部变量的值
- public T get() 返回当前线程所对应的线程局部变量的值
Day4
实现了新增分类,分类信息分页查询,删除分类接口以及修改分类接口
分页查询复习:
- 定义分页构造器
- 定义条件构造器
- 分析查询条件
- 执行查询(调用Service接口)
重难点:删除分类接口
- 当前分类下关联了菜品,不能删除
- 当前分类下关联了套餐,不能删除
因此我们只需要根据id查询当前分类下关联的菜品(套餐)count 是否 > 0
如果 count > 0,则不能删除 –> 抛异常
抛异常的处理方法:
- 定义自定义异常处理器(CustomException)
- 定义全局异常处理器(GlobalExpectionHandler)
- @ExceptionHandler(RuntimeException.class)
代码如下:
@ExceptionHandler(RuntimeException.class)
public R<String> ExceptionHandler(RuntimeException ex) {
log.error(ex.getMessage());
return R.error(ex.getMessage());
}
实现了文件上传和文件下载接口
文件上传:
- 在properties中定义好reggie.path,在类中加入
@Value("${reggie.path}")
注解
@Value
注解是SpringFramework
中用于注入外部配置值的一个重要注解。它的主要作用是将配置文件(如application.properties
或 application.yml
)中的属性值注入到Spring
管理的 Bean
中。
- 代码如下:
/**
* 文件上传
* @param file
* @return
*/
@PostMapping("/upload")
public R<String> upload(MultipartFile file) {
log.info(file.toString());
String originalFilename = file.getOriginalFilename();
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
// 使用UUID重新生成文件名,防止文件名称重复造成文件覆盖
String fileName = UUID.randomUUID().toString() + suffix;
// 创建一个目录对象
File dir = new File(basePath);
// 判断当前目录是否存在
if (!dir.exists()) {
// 目录不存在,需要创建
dir.mkdirs();
}
try {
//将临时文件转存到指定位置
file.transferTo(new File(basePath + fileName));
} catch (IOException e) {
e.printStackTrace();
}
return R.success(fileName);
}
文件下载:
- 代码如下:
/**
* 文件下载
* @param name
* @param response
*/
@GetMapping("/download")
public void download(String name, HttpServletResponse response) {
try {
FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));
ServletOutputStream outputStream = response.getOutputStream();
response.setContentType("image/jpeg");
int len = 0;
byte[] bytes = new byte[1024];
while ((len = fileInputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, len);
outputStream.flush();
}
outputStream.close();
fileInputStream.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Day5
实现了新增菜品接口
重难点:
flavor属性存在于DishFlavor中,其余的属性存在于Dish中,因此我们需要连接两张表进行查询
Dto:
- 用于封装页面提交的数据
- 全称为
Data Transfer Object
,即数据传输对象,一般用于展示层与服务层之间的数据传输。
这里就封装一个DishDto
并继承Dish
,这样就能接收前端发送的Dish所有的属性
前端传回来的数据flavors
中是一个列表,因此我们需要一个List[HTML_REMOVED]类型的属性。
代码如下:
@Data
public class DishDto extends Dish {
private List<DishFlavor> flavors = new ArrayList<>();
private String categoryName;
private Integer copies;
}
实现方法:
- 在方法上添加事务注解
@Transactional
- 启动类上添加注解
@EnableTransactionManagement
- 通过
id
连接dish
和dishFlavors
两张表
saveBatch()
:批量添加数据,flavors
的数据是列表所以用saveBatch
。
代码如下:
@Autowired
private DishFlavorService dishFlavorService;
/**
* 新增菜品,同时保存对应的口味数据
*
* @param dishDto
*/
@Override
@Transactional
public void saveWithFlavor(DishDto dishDto) {
this.save(dishDto);
Long dishId = dishDto.getId();
List<DishFlavor> flavors = dishDto.getFlavors();
flavors = flavors.stream().map((item) -> {
item.setDishId(dishId);
return item;
}).collect(Collectors.toList());
dishFlavorService.saveBatch(flavors);
}
实现了分页查询菜品信息接口
总结:
这个接口是非常有难度的。
由上表可知,dish
表中除了categoryName
字段,其余字段均存在。
我们之前封装的dishDto
中有categoryName
字段,但是我们不能直接返回DishDto
,因为dishDto
中包含了不需要返回的信息,只需要在dish
的基础上多返回一个categoryName
。
思路:
- 将
dish
表中的数据拷贝到dishDto
中(BeanUtils.copyProperties(pageInfo, dishDtoPage, "records"
) - 因为我们要求返回的泛型是
dishDto
,因此我们在拷贝的时候要忽略records
- 根据
id
查询分类对象,分类对象不为空的话,就setCategoryName
- 最后把
list
封装进Page
当中(dishDtoPage.setRecords(list)
)
代码如下:
/**
* 分页查询菜品信息
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name) {
Page<Dish> pageInfo = new Page<>(page, pageSize);
Page<DishDto> dishDtoPage = new Page<>();
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(name != null, Dish::getName, name);
queryWrapper.orderByDesc(Dish::getSort);
dishService.page(pageInfo, queryWrapper);
BeanUtils.copyProperties(pageInfo, dishDtoPage, "records");
List<Dish> records = pageInfo.getRecords();
List<DishDto> list = records.stream().map((item) -> {
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(item, dishDto);
Long categoryId = item.getCategoryId();//分类id
//根据id查询分类对象
Category category = categoryService.getById(categoryId);
if (category != null) {
String categoryName = category.getName();
dishDto.setCategoryName(categoryName);
}
return dishDto;
}).collect(Collectors.toList());
dishDtoPage.setRecords(list);
return R.success(dishDtoPage);
}
Day6
实现了修改菜品接口
具体如下:
- 根据id查询菜品信息
- 更新菜品信息
根据id查询菜品信息:
- 将
Dish
表中的信息拷贝到DishDto
中 - 根据
Dish
表中的id
查询DishFlavor
中的flavor
信息 - 将
flavor
保存在DishDto
表中并返回DishDto
更新菜品信息:
- 更新
DishDto
表中的信息 - 根据
id
删除DishFlavor
表中对应的flavor
信息 - 根据
DishDto
表中的id
给当前DishFlavor
表设置Dish_id
信息 - 更新
DishFlavor
表中的flavor
信息
代码如下:
/**
* 根据id查询菜品信息
*
* @param id
* @return
*/
@Override
public DishDto getByWithFlavor(Long id) {
Dish dish = this.getById(id);
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(dish, dishDto);
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DishFlavor::getDishId, dish.getId());
List<DishFlavor> flavors = dishFlavorService.list(queryWrapper);
dishDto.setFlavors(flavors);
return dishDto;
}
/**
* 更新菜品信息
* @param dishDto
*/
@Override
@Transactional
public void updateWithFlavor(DishDto dishDto) {
this.updateById(dishDto);
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DishFlavor::getDishId, dishDto.getId());
dishFlavorService.remove(queryWrapper);
List<DishFlavor> flavors = dishDto.getFlavors();
flavors = flavors.stream().map((item) -> {
item.setDishId(dishDto.getId());
return item;
}).collect(Collectors.toList());
dishFlavorService.saveBatch(flavors);
}
实现了新增菜品,同时保存套餐和菜品的关联信息接口
总结:
- 同时需要操作多张表的时候需要开启事务
@Transactional
- 设置菜品基本信息
this.save(setmealDto)
- 保存套餐和菜品的关联信息(操作
SetmealDish
表设置SetmealId
属性)
代码如下:
/**
* 新增菜品,同时保存套餐和菜品的关联信息
* @param setmealDto
*/
@Transactional
@Override
public void saveWithDish(SetmealDto setmealDto) {
this.save(setmealDto);
List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
setmealDishes.stream().map((item) -> {
item.setSetmealId(setmealDto.getId());
return item;
}).collect(Collectors.toList());
setmealDishService.saveBatch(setmealDishes);
}
实现了套餐信息分页查询,增加套餐,更新套餐状态接口
套餐信息分页查询:
- 分页构造器对象
- 添加查询条件,根据name进行like模糊查询
- 添加排序条件,根据更新时间降序排列
- 对象拷贝
- 根据分类id查询分类对象
- set分类名称
代码如下:
/**
* 套餐信息分页查询
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name) {
Page<Setmeal> pageInfo = new Page<>(page, pageSize);
Page<SetmealDto> dtoPage = new Page<>();
LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(name != null, Setmeal::getName, name);
queryWrapper.orderByDesc(Setmeal::getUpdateTime);
setmealService.page(pageInfo, queryWrapper);
BeanUtils.copyProperties(pageInfo, dtoPage, "records");
List<Setmeal> records = pageInfo.getRecords();
List<SetmealDto> list = records.stream().map((item) -> {
SetmealDto setmealDto = new SetmealDto();
BeanUtils.copyProperties(item, setmealDto);
Long categoryId = item.getCategoryId();
Category category = categoryService.getById(categoryId);
if (category != null) {
String categoryName = category.getName();
setmealDto.setCategoryName(categoryName);
}
return setmealDto;
}).collect(Collectors.toList());
dtoPage.setRecords(list);
return R.success(dtoPage);
}
删除套餐:
/**
* 删除套餐,同时删除套餐和菜品的关联信息
* @param ids
*/
@Transactional
@Override
public void removeWithDish(List<Long> ids) {
LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(Setmeal::getId, ids);
queryWrapper.eq(Setmeal::getStatus, 1);
int count = this.count(queryWrapper);
if (count > 0) {
throw new CustomException("套餐正在售卖中,不能删除");
}
this.removeByIds(ids);
LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.in(SetmealDish::getSetmealId, ids);
setmealDishService.remove(lambdaQueryWrapper);
}
更新套餐状态:
/**
* 修改销售状态
* @param status
* @param ids
* @return
*/
@PostMapping("/status/{status}")
public R<String> changeStatus(@PathVariable int status, String ids) {
String[] idList = ids.split(",");
for (String id : idList) {
Setmeal setmeal = new Setmeal();
setmeal.setId(Long.parseLong(id));
setmeal.setStatus(status);
setmealService.updateById(setmeal);
}
return R.success("更新状态成功");
}
完成发送手机验证码和移动端手机登录接口
发送手机验证码:
- 获取手机号
- 生成随机的4位验证码
- 调用阿里云提供的短信服务
API
完成发送短信 - 需要将生成的验证码保存到
Session
准备工作:
导入Maven
坐标,添加SMSUtil
工具包
- Pom:
<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>2.1.0</version>
</dependency>
- SMSUtil:
/**
* 短信发送工具类
*/
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", "LTAI5t8MtuEqoYHwmgdAXFcE", "ePxL4Qx9iavetJGEIUW53xvpXRXA8Y");
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();
}
}
}
业务代码如下:
/**
* 发送手机短信验证码
*
* @param user
* @return
*/
@PostMapping("/sendMsg")
public R<String> sendMsg(@RequestBody User user, HttpSession session) {
// 获取手机号
String phone = user.getPhone();
if (StringUtils.isNotEmpty(phone)) {
// 生成随机的4位验证码
String code = ValidateCodeUtils.generateValidateCode(4).toString();
log.info("code={}", code);
// 调用阿里云提供的短信服务API完成发送短信
//SMSUtils.sendMessage("瑞吉外卖", "19137161239", phone, code);
// 需要将生成的验证码保存到Session
session.setAttribute(phone, code);
return R.success("手机验证码短信发送成功");
}
return R.error("短信发送失败");
}
移动端手机登录:
- 使用
Map
存储前端传来的phone
和code
- 进行验证码比对(页面上的验证码与Session中的验证码比对)
-
判断当前手机号对应的用户是否为新用户,如果是新用户就自动完成注册
-
过滤器要取消的拦截接口:
"/user/login",
"/user/sendMsg"
- 拦截器放行代码:
if (request.getSession().getAttribute("user") != null) {
log.info("用户已登录,用户id为:{}", request.getSession().getAttribute("user"));
Long userId = (Long) request.getSession().getAttribute("user");
BaseContext.setCurrentId(userId);
filterChain.doFilter(request, response);
return;
}
- 随机生成验证码工具类:
/**
* 随机生成验证码工具类
*/
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;
}
}
业务代码如下:
/**
* 移动端手机登录
* @param map
* @param session
* @return
*/
@PostMapping("/login")
public R<User> login(@RequestBody Map map, HttpSession session) {
log.info(map.toString());
String phone = map.get("phone").toString();
String code = map.get("code").toString();
Object codeInSession = session.getAttribute(phone);
// 进行验证码比对(页面上的验证码与Session中的验证码比对)
if (codeInSession != null && codeInSession.equals(code)) {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getPhone, phone);
User user = userService.getOne(queryWrapper);
// 判断当前手机号对应的用户是否为新用户,如果是新用户就自动完成注册
if (user == null) {
user = new User();
user.setPhone(phone);
user.setStatus(1);
userService.save(user);
}
session.setAttribute("user", user.getId());
return R.success(user);
}
return R.error("登录失败");
}
Day7
完成购物车相关接口
代码如下:
@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 userId = BaseContext.getCurrentId();
shoppingCart.setUserId(userId);
Long dishId = shoppingCart.getDishId();
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ShoppingCart::getUserId, userId);
if (dishId != null) {
// 添加到购物车里的是菜品
queryWrapper.eq(ShoppingCart::getDishId, dishId);
} else {
// 添加到购物车的是套餐
queryWrapper.eq(ShoppingCart::getSetmealId, shoppingCart.getSetmealId());
}
// 查询当前菜品或套餐是否在购物车中
ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper);
if (cartServiceOne != null) {
// 如果已经存在,就在原来数量基础上加1
Integer number = cartServiceOne.getNumber();
cartServiceOne.setNumber(number + 1);
shoppingCartService.updateById(cartServiceOne);
} else {
// 如果不存在,则添加到购物车,数量默认就是一
shoppingCart.setNumber(1);
shoppingCart.setCreateTime(LocalDateTime.now());
shoppingCartService.save(shoppingCart);
cartServiceOne = shoppingCart;
}
return R.success(cartServiceOne);
}
/**
* 查看购物车
*
* @return
*/
@GetMapping("list")
public R<List<ShoppingCart>> list() {
log.info("查询购物车...");
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());
queryWrapper.orderByAsc(ShoppingCart::getCreateTime);
List<ShoppingCart> list = shoppingCartService.list(queryWrapper);
return R.success(list);
}
/**
* 清空购物车
*
* @return
*/
@DeleteMapping("/clean")
public R<String> clean() {
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());
shoppingCartService.remove(queryWrapper);
return R.success("清空购物车成功");
}
/**
* 客户端的套餐或者是菜品数量减少设置
* 没必要设置返回值
* @param shoppingCart
*/
@PostMapping("/sub")
@Transactional
public R<ShoppingCart> sub(@RequestBody ShoppingCart shoppingCart) {
Long dishId = shoppingCart.getDishId();
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
//代表数量减少的是菜品数量
if (dishId != null) {
//通过dishId查出购物车对象
queryWrapper.eq(ShoppingCart::getDishId, dishId);
//这里必须要加两个条件,否则会出现用户互相修改对方与自己购物车中相同套餐或者是菜品的数量
queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());
ShoppingCart cart1 = shoppingCartService.getOne(queryWrapper);
cart1.setNumber(cart1.getNumber() - 1);
Integer LatestNumber = cart1.getNumber();
if (LatestNumber > 0) {
//对数据进行更新操作
shoppingCartService.updateById(cart1);
} else if (LatestNumber == 0) {
//如果购物车的菜品数量减为0,那么就把菜品从购物车删除
shoppingCartService.removeById(cart1.getId());
} else if (LatestNumber < 0) {
return R.error("操作异常");
}
return R.success(cart1);
}
Long setmealId = shoppingCart.getSetmealId();
if (setmealId != null) {
//代表是套餐数量减少
queryWrapper.eq(ShoppingCart::getSetmealId, setmealId).eq(ShoppingCart::getUserId, BaseContext.getCurrentId());
ShoppingCart cart2 = shoppingCartService.getOne(queryWrapper);
cart2.setNumber(cart2.getNumber() - 1);
Integer LatestNumber = cart2.getNumber();
if (LatestNumber > 0) {
//对数据进行更新操作
shoppingCartService.updateById(cart2);
} else if (LatestNumber == 0) {
//如果购物车的套餐数量减为0,那么就把套餐从购物车删除
shoppingCartService.removeById(cart2.getId());
} else if (LatestNumber < 0) {
return R.error("操作异常");
}
return R.success(cart2);
}
//如果两个大if判断都进不去
return R.error("操作异常");
}
}
Day8
完成了用户下单相关接口
代码如下:
package com.itheima.reggie.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.common.BaseContext;
import com.itheima.reggie.common.CustomException;
import com.itheima.reggie.entity.*;
import com.itheima.reggie.mapper.OrdersMapper;
import com.itheima.reggie.service.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.ParameterResolutionDelegate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.util.pattern.PathPattern;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@Service
@Slf4j
public class OrdersServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements OrdersService {
@Autowired
private ShoppingCartService shoppingCartService;
@Autowired
private UserService userService;
@Autowired
private AddressBookService addressBookService;
@Autowired
private OrderDetailService orderDetailService;
/**
* 用户下单
*
* @param orders
*/
@Transactional
@Override
public void submit(Orders orders) {
Long userId = BaseContext.getCurrentId();
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ShoppingCart::getUserId, userId);
List<ShoppingCart> shoppingCarts = shoppingCartService.list(queryWrapper);
if (shoppingCarts == null || shoppingCarts.size() == 0) {
throw new CustomException("购物车为空,不能下单");
}
User user = userService.getById(userId);
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.setId(orderId);
orders.setOrderTime(LocalDateTime.now());
orders.setCheckoutTime(LocalDateTime.now());
orders.setStatus(2);
orders.setAmount(new BigDecimal(amount.get()));//总金额
orders.setUserId(userId);
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(queryWrapper);
}
}