本文主要是介绍瑞吉外卖java项目实战(句句解析版),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
瑞吉外卖项目
Java项目实战《瑞吉外卖》,轻松掌握springboot + mybatis plus开发核心技术的真java实战项目
包含自己学习时候的理解 有可能不对欢迎指正,我会查看并且进行正确的修改,也有老师讲的时候的讲解
创建项目
创建项目,数据库 我就不多讲解了 (项目这里导入了backend 和front包 都是前端代码)
注意:这里导入了backend 和 front 包的时候,springboot并不知道这些是静态资源,所以要编写一个配置类,配置mvc静态资源的映射 如果不编写输入时url时不出现页面
以下是springboot默认的静态资源路径
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
编写配置类
告诉springboot 要访问backend 就是 resource下面那个backend 你直接输入
localhost:8080/backend/… 就去resource/backend/ 下面去找
另外:reources包下在建一个resources包装静态资源不能直接放在第一个reources下
这时直接输入网站就可以访问了
配置pom和appliciation.yml
导入依赖如果有问题可以私信我帮助解决一下 建议配置本地maven 能解决大部分问题
创建controller层 ,service层 ,mapper层
然后实现相应的Empoyee 类或接口 让他们继承对应mybatis plus 提供的类
这个类里有很多我们会经常用的实现方法,所以尽管表面看起来没有神什么代码但其实已经有很多实现方法了,感兴趣的同学们可以ctrl+b 看一下
创建common类 (包含通用类如通用返回结果类R)
注意R类里的success和error和add方法经常用到
编写员工登录逻辑
弱弱的说一句不知道为什么他要把方法写在controller类里,应该是service类里吧,我们这里尊重作者
@Slf4j
@RequestMapping("/employee")
// @ResponseBody 注解是将返回的数据结构转换为 Json 格式
@RestController
// @RestController 注解包含了原来的 @Controller 和 @ResponseBody 注解
//使用了 @RestController 注解即可将返回的数据结构转换成 Json 格式,
// Spring Boot 中默认使用的 Json 解析技术框架是 jackson。
public class EmployeeController {@Autowiredprivate EmployeeService employeeService;@PostMapping("/login")//@PostMapping是@RequestMapping(method = RequestMethod.POST)缩写的组合注解,// 用于将 HTTP 的post 请求映射到特定处理程序的方法注解。//RequestBody 前端点登录发送的请求会带来账号密码参数 但是是json形式 接受的时候要用这个注解//要与实体类里的username 一样//request 是因为要存一份在session里 request可以get一个session//这个session是服务器端共享,每个游览器(客户端)独享的。我们可以在session存储数据,实现数据共享。public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee) {String pwd = employee.getPassword();//md5解密pwd = DigestUtils.md5DigestAsHex(pwd.getBytes());//mybatis-plus 的方法LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();//和数据库进行判断queryWrapper.eq(Employee::getUsername, employee.getUsername());Employee emp = employeeService.getOne(queryWrapper);System.out.println(emp);if (emp == null) {return R.error("登陆失败");}if (!emp.getPassword().equals(pwd)) {return R.error("密码不对");}if (emp.getStatus() == 0) {return R.error("账号被锁");}System.out.println("登录成功");//这里设定employee的id的key为employeeSystem.out.println("----这个是empoyee---" + employee);System.out.println("----这个是emp---" + emp);request.getSession().setAttribute("employee", emp.getId());return R.success(emp);}
==注意:这里我直接加上了session 原视频开始是没有的,是后来完善的,另外 request.getSession().setAttribute(“employee”, emp.getId()); 这句话传递的是emp。getId()而不是employee.id(),employee这里id是空的 所以我在发现写错了之前session一直出错… ==
过滤器和拦截器
我们这里用的是过滤器
@Slf4j
@WebFilter(filterName = "LoginCheckfilter", urlPatterns = "/*")
// /* 所有的请求都拦截
// 过滤器要有这个注解
//添加过滤器和拦截器 防止直接输入网站 从而跳过 登陆界面
public class LoginCheckfilter implements Filter {/*有些路径后面带html,有些不带html用路径匹配器,支持通配符*/static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletResponse response = (HttpServletResponse) servletResponse;HttpServletRequest request = (HttpServletRequest) servletRequest;log.info("拦截到请求{}", request.getRequestURI());/*filterchain.dofilter() 方法用于执行过滤器链中的所有过滤器。它返回过滤器链处理后的结果。*/String requestURI = request.getRequestURI();//不需要处理的URI路径//backen和front 包里有些带.html后缀 有些不带 用路径通配符String[] URIS = new String[]{"/employee/login","/employee/logout","/backend/**","/front/**"};boolean check = check(requestURI, URIS);//如果不需要处理就直接放行if (check) {log.info("本次请求{}不需要处理",requestURI);filterChain.doFilter(request, response);return;}//如果session 有对象 说明就已经登陆了if (request.getSession().getAttribute("employee") != null) {//取得属性的名称之后,我们就可以用getAttribute()方法将它的属性值拿出来了。System.out.println("----session--- 不为空");log.info("用户登录,用户id为{}",request.getSession().getAttribute("employee"));filterChain.doFilter(request, response);return;}// 如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据//通过JSON.toJSONSting方法 将R对象转换成josn数据 然后调用write写回去//"NOTLOGIN" 对应 request.js 里的”NOTLOGIN“log.info("用户未登录");response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));return;}/*** 路径匹配看一下是否要放行** @param requestURI 传进来的uri* @return*/public static boolean check(String requestURI, String[] UIRS) {for (String url : UIRS) {//通配符boolean match = PATH_MATCHER.match(url, requestURI);if (match) {return true;}}return false;}}
另外我想起了一个面试题 拦截器和过滤器有什么不同?
有兴趣大家可以研究一下
编写员工退出
/*** 员工退出** @param request* @return*/@PostMapping("/logout")//@RequestMapping(method = RequestMethod.POST)的快捷方式public R<String> logout(HttpServletRequest request) {//清理session 传进去的叫employee 移除的也叫employeerequest.getSession().removeAttribute("employee");return R.success("退出成功");}
编写员工新增
注意这里密码用MD5加密
/*** 新增员工** @param employee* @return*/@PostMappingpublic R<String> save(@RequestBody Employee employee, HttpServletRequest request) {log.info("新增员工,输出员工信息:{}", employee.toString());//设定初始密码时123456 并且加密employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));employee.setCreateTime(LocalDateTime.now());employee.setUpdateTime(LocalDateTime.now());//获得当前用户idLong empId = (long) request.getSession().getAttribute("employee");employee.setCreateUser(empId);employee.setUpdateUser(empId);//mybatis plus 里的方法employeeService.save(employee);return R.success("新增员工成功");}
注意我们这里先不先不设置MetaObjectHandler类 我们在 公共字段填充代码里写,可以看目录。
编写全局异常
aop编程
/*** 全局异常处理*//*** @ControllerAdvice* annotation: 注解* 只要类上加了 RestController, Controller 这两个注解 就会被拦截到*/
@ControllerAdvice(annotations = {RestController.class, Controller.class})
/*** 因为要返回json数据所以用ResponsBody*/
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {/*** 异常处理方法* @return*///@ExceptionHandler注解中可以添加参数,参数是某个异常类的class,代表这个方法专门处理该类异常,@ExceptionHandler(SQLIntegrityConstraintViolationException.class)public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex) {log.error(ex.getMessage());if (ex.getMessage().contains("Duplicate entry")) {//用空格分开String[] split = ex.getMessage().split(" ");//split【2】得到重复的用户名 加已存在String msg = split[2] + "已存在";return R.error(msg);}return R.error("未知错误");}
}
员工分页查询
/*** 员工分页查询* @param page 页码* @param pageSize 一页的大小 10* @param name 搜索时输入的值*/@GetMapping("/page")//因为传过来的是get//在request.js 里 将json数据解析 然后拼接成url 并转换成key value形式 所以这里的参数不用加 @RequestBody//这里 参数要和 前端url 里的key 一样 前段是?page= 后端参数名字就要叫pagepublic R<Page> page(int page, int pageSize, String name) {log.info("page = {},pageSize = {},name = {}", page, pageSize, name);//构造分页构造器//Page也是Spring Data库中的一个接口,主要用于存储JPA查询数据库的结果集。Page pageInfo = new Page(page, pageSize);//构造条件构造器//mybatis puls 提供的 和数据库进行比较LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();//添加过滤条件//StringUtils已经提供了一个判断如果name为空的情况 就不需要自己判断了//如果空就不执行下面这句话 ,如果不为空再执行这句话queryWrapper.like(StringUtils.isNotEmpty(name), Employee::getName, name);//添加排序条件 根据更新时间排序queryWrapper.orderByDesc(Employee::getUpdateTime);//执行查询employeeService.page(pageInfo, queryWrapper);return R.success(pageInfo);}
根据id员工修改
注意这里是因为mybatis-puls 不考虑null 的值 前端只传过来 status还有id
其他的值都是空所以他只更新不为空的值 如果想让他更新null的值要改配置
在MyBatis-Plus配置文件中修改field-strategy字段验证的值为0即可
/*** 根据id修改员工信息* @param employee* @return*/@PutMappingpublic R<String> update(@RequestBody Employee employee,HttpServletRequest request){log.info(employee.toString());employee.setUpdateUser((long)request.getSession().getAttribute("employee"));employee.setUpdateTime(LocalDateTime.now());//代码简单是因为我们用的mybatis puls updateById 是继承Iservice这个接口employeeService.updateById(employee);return R.success("员工信息修改成功");}
注意这里update会失败因为json解析long型的数据时会改变精度 所以要进行修改
注意 update次数是0
完善员工修改 增加对象映射器
/*** 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]*/
public class JacksonObjectMapper extends ObjectMapper {public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";public JacksonObjectMapper() {super();//收到未知属性时不报异常this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);//反序列化时,属性不存在的兼容处理this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);SimpleModule simpleModule = new SimpleModule().addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))).addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))).addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))).addSerializer(BigInteger.class, ToStringSerializer.instance).addSerializer(Long.class, ToStringSerializer.instance).addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))).addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))).addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));//注册功能模块 例如,可以添加自定义序列化器和反序列化器this.registerModule(simpleModule);}
扩展mvc框架消息转换器
注意这里要控制下标把咱们的转换器放在0位置也就是先用咱们这个转换器而不是先用默认的转换器
/*** 扩展mvc框架的消息转换器* @param converters*/@Overrideprotected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {log.info("扩展消息转换器...");//创建消息转换器对象MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();//设置对象转换器,底层使用Jackson将Java对象转为jsonmessageConverter.setObjectMapper(new JacksonObjectMapper());//将上面的消息转换器对象追加到mvc框架的转换器集合中converters.add(0,messageConverter);}
MappingJackson2HttpMessageConverter是springboot中默认的Json消息转换器。
跟据id查询员工
/*** 根据id查询数字*/@GetMapping("/{id}")public R<Employee>getById(@PathVariable long id){Employee employee =employeeService.getById(id);return null;}
注意这里和之前员工分页不同!
这个是员工分页
这个是key-value 让括号里声明的参数和key值一样就可以
这个是根据id查询员工
这个是直接获取url上的值所以要用@PathVariable 注解
另外 编辑员工用的也是上一个update 方法所以根据id查完之后就可以直接编辑
公共字段填充代码
我们现在可以把操作员工那里的重复加入的代码(比如设置更新人是谁等等)删去了
我们这里先不得到 updateUser 因为我们在MyMetaObjectHandler类里是获取不到session的
ThreadLocal 获取session
注意:每个线程都只能看到自己线程的值 ,每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题
代码如下:
我们在设置婉工具类后就可以在BaseContxt 类里调用get方法获取session 里的id了
因为filter 先运行filter 里的 BaseContext.setCurrentId(empId); 然后运行MyMetaObjectHandler进行赋值
全局异常处理
先获取带字段里带Duplicate entry 说明这个是异常 然后根据空格分开
新增分类
这里我们后端只需要写一个方法就可以新增套餐分类,新增菜品分类,两个窗口的逻辑,因为请求的url路径和穿的json 参数都一样的
显示页面
@GetMapping("/page")public R<Page> page(int page, int pageSize) {log.info("page = {},pageSize = {}", page, pageSize);//构造分页构造器//Page也是Spring Data库中的一个接口,主要用于存储JPA查询数据库的结果集。Page pageInfo = new Page(page, pageSize);//构造条件构造器//mybatis puls 提供的 和数据库进行比较LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper();//添加排序条件 根据更新时间排序queryWrapper.orderByAsc(Category::getSort);//执行查询categoryService.page(pageInfo, queryWrapper);return R.success(pageInfo);}
删除分类(跟菜品和套餐)
接收ids
注意这里老师传的参数是ids 我开始写的id 报了id为空的异常。。。 大家要细心一点
/*** 根据id删除分类,删除之前要判断** @param id*/@Overridepublic void remove(Long id) {//查询是否关联了菜品,如果已经关联 ,抛出一个异常//LambdaQueryWrapper.eq 方法是 MyBatis Plus 中用于构建等于(equal)条件的方法。// 它接收两个参数,第一个参数是数据库表中的列名,第二个参数是要匹配的值。// 该方法返回一个 LambdaQueryWrapper 对象,可以配合其它条件继续构建复杂的查询语句。LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<Dish>();dishLambdaQueryWrapper.eq(Dish::getCategoryId, id);int count1 = dishService.count(dishLambdaQueryWrapper);//查询是否关联了菜品,如果已经关联 ,抛出一个异常if (count1 > 0) {throw new CustomException("当下分类关联了菜品,不能删啊0.0");}LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId, id);int count2 = dishService.count(dishLambdaQueryWrapper);//查询是否关联了套餐,如果已经关联 ,抛出一个异常if (count2 > 0) {throw new CustomException("当下分类关联了套餐,不能删啊0.0");}//正常删除分类super.removeById(id);}
设置全局异常处理
修改分类信息
前端来看修改和新增用的是一个窗口
文件上传
@RestController
@Slf4j
@RequestMapping("/common")
public class CommonController {/*** 那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。* 通常我们会将配置信息以key=value的形式存储在properties配置文件中。*/@Value("${takeout.path}")private String basePath;@PostMapping("/upload")public R<String> upload(MultipartFile file){//file 是一个临时文件,要转存到一个位置不然本次请求完成后就会消失log.info(file.toString());String originalFilename = file.getOriginalFilename();String substring = originalFilename.substring(originalFilename.lastIndexOf("."));String fileName = UUID.randomUUID().toString()+substring ;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);}@GetMapping("/download")public void download(String name, HttpServletResponse response){log.info(name);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) {e.printStackTrace();}}
}
配置spring.yml 配置文件
用到了输入输出流 javase学的忘记了的学生可以搜一下学一下
新建菜品页面
@GetMapping("/list")//点击新建菜品 传参为type=1 可以用String type 来接收 但是我们这里采用封装成category的方法public R<List<Category>> list(Category category){log.info("进入新建菜品页面");//条件构造器LambdaQueryWrapper<Category> categoryLambdaQueryWrapper = new LambdaQueryWrapper<>();//添加条件 categoryLambdaQueryWrapper.eq(category.getType()!=null,Category::getType,category.getType());//添加排序条件 优先用sort来排序 如果sort相同那么采用更新时间进行排序categoryLambdaQueryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);List<Category> list = categoryService.list(categoryLambdaQueryWrapper);return R.success(list);}
这个表里有两个id 第一个id是表本身自带的 一个是另一个表的菜品id这这里叫dish_id
@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {@Autowiredprivate DishFlavorservice dishFlavorservice;@Transactional//保证数据一致性@Overridepublic void saveWithFalver(DishDto dishDto) {//保存菜品的基本信息到菜品dishthis.save(dishDto);Long id = dishDto.getId();//菜品口味List<DishFlavor> flavors = dishDto.getFlavors();//lambda表达式 Java8 新特性//这里的id 是根据appliciation.yml 里的 id-type: ASSIGN_ID 注入的flavors=flavors.stream().map((dishFlavor) -> {dishFlavor.setDishId(id);return dishFlavor;}).collect(Collectors.toList());//变为一个链表并且把dish_id 赋值// //保存菜品口味数据到菜品口味表dish——flavordishFlavorservice.saveBatch(flavors);}
}
lambda 表达式这里大家可以搜索学习一下是新特性当然也可以用foreach
菜品分页查询
/*菜品信息分页查询*/@GetMapping("/page")public R<Page> page(int page, int pageSize, String name) {//构造分页构造器对象Page<Dish> dishPage = new Page<>(page, pageSize);Page<DishDto> dishDtoPage = new Page<>();//条件构造器LambdaQueryWrapper<Dish> QueryWrapper = new LambdaQueryWrapper<>();//添加过滤条件 用名字LambdaQueryWrapper<Dish> like = QueryWrapper.like(name != null, Dish::getName, name);//用更新时间进行排序QueryWrapper.orderByDesc(Dish::getUpdateTime);//执行分页 查询dishService.page(dishPage, QueryWrapper);//对象拷贝BeanUtils.copyProperties(dishPage, dishDtoPage, "records");List<Dish> records = dishPage.getRecords();List<DishDto> list = records.stream().map((item) -> {DishDto dishDto = new DishDto();//遍历出来的属性拷贝到dishDtoBeanUtils.copyProperties(item, dishDto);Long categoryId = item.getCategoryId();//分类//根据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);}
修改菜品
/*** 修改菜品* @param dishDto*/@Override@Transactionalpublic void updateWithFalvor(DishDto dishDto) {//更新dish表信息this.updateById(dishDto);//清理当前菜品对应口味数据--dish_falvor表的delete操作LambdaQueryWrapper<DishFlavor> QueryWrapper = new LambdaQueryWrapper();QueryWrapper.eq(DishFlavor::getDishId,dishDto.getId());dishFlavorservice.remove(QueryWrapper);//添加当前提交过来的口味数据,dish_flavor表的insert操作List<DishFlavor> flavors = dishDto.getFlavors();flavors=flavors.stream().map((item)->{log.info("item.tostring()={}",item.toString());log.info("dishDto.getId().toString()={}",dishDto.getId().toString());item.setDishId(dishDto.getId());return item;}).collect(Collectors.toList());}
根据菜品id查询
发现他传了一个菜品id key-value形式 我们这里可以用Long categoryid来接收
/*** 根据条件来查询对应的菜品数据* @param dish* @return*/@GetMapping("/list")public R<List<Dish>> list(Dish dish){//构造查询条件LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(dish.getCategoryId() != null ,Dish::getCategoryId,dish.getCategoryId());//添加条件,查询状态为1(起售状态)的菜品queryWrapper.eq(Dish::getStatus,1);//添加排序条件queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);List<Dish> list = dishService.list(queryWrapper);return R.success(list);}
新增套餐
/*** 新增套餐 同时保存套餐与菜品的关联关系**/@Override@Transactionalpublic void saveWithDish(SetmealDto setmealDto) {//保存套餐的基本信息,操作setmeal,执行insert操作this.save(setmealDto);List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();setmealDishes.stream().map((item) -> {item.setSetmealId(setmealDto.getId());return item;}).collect(Collectors.toList());//保存套餐和菜品的关联信息,操作setmeal_dish,执行insert操作setmealDishService.saveBatch(setmealDishes);}
套餐分页查询
/*** 套餐分页查询* @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<>();//添加查询条件,根据name进行like模糊查询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);//分类idLong categoryId = item.getCategoryId();//根据分类id查询分类对象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);}
删除套餐
删除一个和删除多个是一样的 无非是id传过来的个数不一样
/*** 删除套餐,同时需要删除套餐和菜品的关联数据* @param ids*/@Transactional@Overridepublic void removeWithDish(List<Long> ids) {//select count(*) from setmeal where id in (1,2,3) and status = 1//查询套餐状态,确定是否可用删除LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper();queryWrapper.in(Setmeal::getId,ids);System.out.println("-------------------------------------------------------------");queryWrapper.eq(Setmeal::getStatus,1);int count = this.count(queryWrapper);if(count > 0){//如果不能删除,抛出一个业务异常throw new CustomException("套餐正在售卖中,不能删除");}//如果可以删除,先删除套餐表中的数据---setmealthis.removeByIds(ids);//delete from setmeal_dish where setmeal_id in (1,2,3)LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.in(SetmealDish::getSetmealId,ids);//删除关系表中的数据----setmeal_dishsetmealDishService.remove(lambdaQueryWrapper);}
手机验证码登录
/*** 移动端用户登录* @param map* @param session* @return*/@PostMapping("/login")public R<User> login(@RequestBody Map map, HttpSession session){log.info(map.toString());log.info("进入方法--------------------");//获取手机号String phone = map.get("phone").toString();//获取验证码String code = map.get("code").toString();//从Session中获取保存的验证码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("登录失败");}
不要忘记加过滤器
我在做这里的时候遇到了找不到sendMsgApi的问题 我把target文件删掉然后重新启动项目就好了
front文件用的day6
地址
Slf4j
@RestController
@RequestMapping("/addressBook")
public class AddressBookController {@Autowiredprivate AddressBookService addressBookService;/*** 新增*/@PostMappingpublic R<AddressBook> save(@RequestBody AddressBook addressBook) {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<>();wrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());wrapper.set(AddressBook::getIsDefault, 0);//SQL:update address_book set is_default = 0 where user_id = ?addressBookService.update(wrapper);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<>();queryWrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());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) {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));}
}
菜品展示
/*** 根据条件来查询对应的菜品数据* @param dish* @return*/@GetMapping("/list")public R<List<DishDto>> list(Dish dish){//构造查询条件LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(dish.getCategoryId() != null ,Dish::getCategoryId,dish.getCategoryId());//添加条件,查询状态为1(起售状态)的菜品queryWrapper.eq(Dish::getStatus,1);//添加排序条件queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);List<Dish> list = dishService.list(queryWrapper);List<DishDto> dishDtoList = list.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);}//当前菜品的idLong dishId = item.getId();LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.eq(DishFlavor::getDishId,dishId);//SQL:select * from dish_flavor where dish_id = ?List<DishFlavor> dishFlavorList = dishFlavorservice.list(lambdaQueryWrapper);dishDto.setFlavors(dishFlavorList);return dishDto;}).collect(Collectors.toList());return R.success(dishDtoList);}
新增购物车
/*** 添加购物车* @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{//如果不存在,则添加到购物车,数量默认就是一shoppingCart.setNumber(1);shoppingCart.setCreateTime(LocalDateTime.now());shoppingCartService.save(shoppingCart);cartServiceOne = shoppingCart;}return R.success(cartServiceOne);}
查看购物车
@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(){//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("清空购物车成功");}
用户下单
@Autowiredprivate ShoppingCartService shoppingCartService;@Autowiredprivate UserService userService;@Autowiredprivate AddressBookService addressBookService;@Autowiredprivate OrderDetailService orderDetailService;/*** 用户下单* @param orders*/@Transactionalpublic void submit(Orders orders) {//获得当前用户idLong userId = BaseContext.getCurrentId();//查询当前用户的购物车数据LambdaQueryWrapper<ShoppingCart> wrapper = new LambdaQueryWrapper<>();wrapper.eq(ShoppingCart::getUserId,userId);List<ShoppingCart> shoppingCarts = shoppingCartService.list(wrapper);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(wrapper);}
基础篇完结散花 欢迎指正
我会在另一篇更新后面的内容
去玩星穹铁道了!
比心
这篇关于瑞吉外卖java项目实战(句句解析版)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!