[瑞吉外卖]-05菜品模块

文件上传下载

介绍

文件上传也称为upload,是指将本地图片、视频、音频等文件上传到服务器上, 可以供其他用户浏览或下载

前端组件库提供了上传组件,但是底层原理还是基于form表单的文件上传。

服务端要接收客户端上传的文件,通常都会使用Apache的两个组件: commons-fileupload 和 commons-io

Spring框架在spring-web包中对文件上传进行了封装,大大简化了服务端代码,我们只需要在Controller的方法中声明-个MultipartFile类型的参数即可接收上传的文件

文件下载,也称为download,是指将文件从服务器传输到本地计算机的过程。

  1. 通过浏览器进行文件下载,通常有两种表现形式:
  • 以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录
  • 直接在浏览器中打开
  1. 通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程。

文件上传

资料中提供了前端文件上传页面, 直接只用即可

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>文件上传</title><!-- 引入样式 --><link rel="stylesheet" href="../../plugins/element-ui/index.css" /><link rel="stylesheet" href="../../styles/common.css" /><link rel="stylesheet" href="../../styles/page.css" />
</head>
<body><div class="addBrand-container" id="food-add-app"><div class="container"><el-upload class="avatar-uploader"action="/common/upload":show-file-list="false":on-success="handleAvatarSuccess":before-upload="beforeUpload"ref="upload"><img v-if="imageUrl" :src="imageUrl" class="avatar"></img><i v-else class="el-icon-plus avatar-uploader-icon"></i></el-upload></div></div><!-- 开发环境版本,包含了有帮助的命令行警告 --><script src="../../plugins/vue/vue.js"></script><!-- 引入组件库 --><script src="../../plugins/element-ui/index.js"></script><!-- 引入axios --><script src="../../plugins/axios/axios.min.js"></script><script src="../../js/index.js"></script><script>new Vue({el: '#food-add-app',data() {return {imageUrl: ''}},methods: {handleAvatarSuccess (response, file, fileList) {this.imageUrl = `/common/download?name=${response.data}`},beforeUpload (file) {if(file){const suffix = file.name.split('.')[1]const size = file.size / 1024 / 1024 < 2if(['png','jpeg','jpg'].indexOf(suffix) < 0){this.$message.error('上传图片只支持 png、jpeg、jpg 格式!')this.$refs.upload.clearFiles()return false}if(!size){this.$message.error('上传文件大小不能超过 2MB!')return false}return file}}}})</script>
</body>
</html>

前端代码调试

  1. 前端文件上传规范
  • 请求方式post
  • 文件以二进制的格式存储在From Date中
  • name是input表单的名字, 后端接受数据时要使用这个名字
  • filename是文件的名字

在过滤中把文件上传的路径进行排除, 不然文件上传会进行登录校验

/*** 过滤器: 检测用户是否登录* 拦截的路径: urlPatterns = "/*"*/
@WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {... ...//定义不需要处理的请求路径String[] urls = new String[]{"/employee/login","/employee/logout","/backend/**","/front/**","/common/**"};... ...
}

处理文件上传:

... ...
reggie:path: D:\img
/*** 通用controller*/
@RestController
@RequestMapping("/common")
@Slf4j
public class CommonController {@Value("${reggie.path}")private String basePath;@PostMapping("/upload")public R<String> upload(MultipartFile file) {// file是一个临时文件, 需要转存到指定位置, 否则本次请求完成后临时文件会删除log.info(file.toString());// 获取原始文件名String originalFilename = file.getOriginalFilename();// 截取原始文件类型后缀String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));// 生成新文件名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);}
}

文件下载

文件下载, 页面端可以使用<img>标签展示下载的图片

/*** 通用controller*/
@RestController
@RequestMapping("/common")
@Slf4j
public class CommonController {@Value("${reggie.path}")private String basePath;/*** 文件下载* @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 (IOException e) {e.printStackTrace();}}
}

新增菜品

需求分析

后台系统中可以管理菜品信息,通过新增功能来添加一个新的菜品,在添加菜品时需要选择当前菜品所属的菜品分类并且需要上传菜品图片,在移动端会按照菜品分类来展示对应的菜品信息。

新增菜品,其实就是将新增页面录入的菜品信息插入到dish表,如果添加了口味做法,还需要向dish flavor表插入数据所以在新增菜品时,涉及到两个表

代码开发

准备工作: 业务功能开发前,先把要用到的类和接口基本结构创建好

/**
菜品口味*/
@Data
public class DishFlavor implements Serializable {private static final long serialVersionUID = 1L;private Long id;//菜品idprivate Long dishId;//口味名称private String name;//口味数据listprivate String value;@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;}
  1. Dish实体之前已经导入过了

@Mapper
public interface DishFlavorMapper extends BaseMapper<DishFlavor> {
}
public interface DishFlavorService extends IService<DishFlavor> {
}
@Service
public class DishFlavorServiceImpl extends ServiceImpl<DishFlavorMapper, DishFlavor> implements DishFlavorService {
}
/*** 菜品管理*/
@Slf4j
@RestController
@RequestMapping("/dish")
public class DishController {@Autowiredprivate DishService dishService;@Autowiredprivate DishFlavorService dishFlavorService;}

在开发代码之前,需要梳理一下新增菜品时前端页面和服务端的交互过程:

  1. 页面(backend/page/food/add.html)发送ajax请求,请求服务端获取菜品分类数据并展示到下拉框中
  2. 页面发送请求进行图片上传,请求服务端将图片保存到服务器
  3. 页面发送请求进行图片下载,将上传的图片进行回显
  4. 点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端
  5. 开发新增菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可

菜品分类查询接口

/*** 分类管理*/
@Slf4j
@RestController
@RequestMapping("/category")
public class CategoryController {@Autowiredprivate CategoryService categoryService;/*** 根据条件查询分类数据* @param category* @return*/@GetMapping("/list")public R<List<Category>> list(Category category) {// 条件构造器LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();// 添加条件queryWrapper.eq(category.getType() != null, Category::getType, category.getType());// 添加排序条件queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);List<Category> list = categoryService.list(queryWrapper);return R.success(list);}}

导入DishDTO, 用于封装页面提交的参数

@Data
public class DishDto extends Dish {private List<DishFlavor> flavors = new ArrayList<>();private String categoryName;private Integer copies;
}
  1. 当实体类和前端参数无法匹配时, 就要设计DTO对象来接收前端参数
  2. 当前DTO是在Dish实体类的基础上, 扩展了 flavors 口味等字段

新增菜品接口

/*** 菜品管理*/
@Slf4j
@RestController
@RequestMapping("/dish")
public class DishController {@Autowiredprivate DishService dishService;@Autowiredprivate DishFlavorService dishFlavorService;/*** 新增菜品* @param dishDto* @return*/@PostMappingpublic R<String> save(@RequestBody DishDto dishDto) {log.info(dishDto.toString());dishService.saveWithFlavor(dishDto);return R.success("新增菜品成功");}}
public interface DishService extends IService<Dish> {//新增菜品,同时插入菜品对应的口味数据,需要操作两张表:dish、dish_flavorpublic void saveWithFlavor(DishDto dishDto);
}
@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {@Autowiredprivate DishFlavorService dishFlavorService;/*** 新增菜品,同时插入菜品对应的口味数据* @param dishDto*/@Transactionalpublic void saveWithFlavor(DishDto dishDto) {// 保存菜品的基本信息到菜品表dishthis.save(dishDto);// 菜品基本信息保存后就可以拿到菜品id了Long dishId = dishDto.getId();// 菜品口味List<DishFlavor> flavors = dishDto.getFlavors();// 给菜品口味设置菜品idflavors = flavors.stream().map((item) -> {item.setDishId(dishId);return item;}).collect(Collectors.toList());// 保存菜品口味数据到菜品口味表dish_flavordishFlavorService.saveBatch(flavors);}
}
// 开启事务支持
@EnableTransactionManagement
public class reggieApplication {public static void main(String[] args) {SpringApplication.run(reggieApplication.class, args);log.info("项目启动成功...");}
}

菜品分页查询

需求分析

系统中的菜品数据很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。

在开发代码之前,需要梳理一下菜品分页查询时前端页面和服务端的交互过程

  1. 页面(backend/page/food/list.html)发送aja提交到服务端,将分页查询参数(page、pageSize、name)提交到服务器, 获取分页数据
  2. 页面发送请求,请求服务端进行图片下载,用于页面图片展示
  3. 开发菜品信息分页查询功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。

代码开发

/*** 菜品管理*/
@Slf4j
@RestController
@RequestMapping("/dish")
public class DishController {@Autowiredprivate DishService dishService;@Autowiredprivate DishFlavorService dishFlavorService;@Autowiredprivate CategoryService categoryService;/*** 菜品信息分页查询* @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::getUpdateTime);dishService.page(pageInfo,queryWrapper);//对象宝贝//拷贝过程中排除records属性, 因为我们要设置categoryName属性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);// 分类idLong 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);}}

修改菜品

需求分析

在菜品管理列表页面点击修改按钮,跳转到修改菜品页面,在修改页面回显菜品相关信息并进行修改,最后点击确定按钮完成修改操作

在开发代码之前,需要梳理一下修改菜品时前端页面(add.html)和服务端的交互过程

  1. 页面发送ajax请求,请求服务端获取分类数据,用于菜品分类下拉框中数据展示
  2. 页面发送ajax请求,请求服务端,根据id查询当前菜品信息,用于菜品信息回显
  3. 页面发送请求,:请求服务端进行图片下载,用于页图片回显
  4. 点击保存按钮,页面发送ajax请求,将修改后的菜品相关数据以ison形式提交到服务端
  5. 开发修改菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可

代码开发

根据id查询当前菜品信息,用于菜品信息回显

/*** 菜品管理*/
@Slf4j
@RestController
@RequestMapping("/dish")
public class DishController {@Autowiredprivate DishService dishService;/*** 根据id查询菜品信息和对应的口味信息* @param id* @return*/@GetMapping("/{id}")public R<DishDto> get(@PathVariable Long id) {DishDto dishDto = dishService.getByIdWithFlavor(id);return R.success(dishDto);}}
public interface DishService extends IService<Dish> {//根据id查询菜品信息,同时查询对应的口味信息public DishDto getByIdWithFlavor(Long id);
}
@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {@Autowiredprivate DishFlavorService dishFlavorService;/*** 根据id查询菜品信息和对应的口味信息* @param id* @return*/@Overridepublic DishDto getByIdWithFlavor(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;}
}

将修改后的菜品相关数据更新到数据库

/*** 菜品管理*/
@Slf4j
@RestController
@RequestMapping("/dish")
public class DishController {@Autowiredprivate DishService dishService;/*** 修改菜品* @param dishDto* @return*/@PutMappingpublic R<String> update(@RequestBody DishDto dishDto) {log.info(dishDto.toString());dishService.updateWithFlavor(dishDto);return R.success("修改菜品成功");}}
public interface DishService extends IService<Dish> {// 修改菜品信息,同时修改对应的口味信息public void updateWithFlavor(DishDto dishDto);
}
@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {@Autowiredprivate DishFlavorService dishFlavorService;/*** 修改菜品信息,同时修改对应的口味信息* @param dishDto*/@Override@Transactionalpublic void updateWithFlavor(DishDto dishDto) {// 更新dish表基本信息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);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/447980.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一次Fegin CPU占用过高导致的事故

记录一下 一次应用事故分析、排查、处理 背景介绍 9号上午收到CPU告警&#xff0c;同时业务反馈依赖该服务的上游服务接口响应耗时太长 应用告警-CPU使用率 告警变更 【WARNING】项目XXX,集群qd-aliyun,分区bbbb-prod,应用customer,实例customer-6fb6448688-m47jz, POD实例CP…

Web集群服务-Nginx

1. web服务 1. WEB服务:网站服务,部署并启动了这个服务,你就可以搭建一个网站 2. WEB中间件: 等同于WEB服务 3. 中间件:范围更加广泛,指的负载均衡之后的服务 4. 数据库中间件:数据库缓存,消息对列 2. 极速上手指南 nginx官网: nginx documentation 2.1 配置yum源 vim /etc/…

HTML基础知识

介绍 HTML&#xff08;HyperText Markup Language&#xff0c;超文本标记语言&#xff09;是一种用于创建网页的标准标记语言。它描述了一个网站的结构骨架&#xff0c;使得浏览器能够展示具有特定格式的文本、链接、图片和其他内容。以下是HTML的一些基础知识&#xff1a; HT…

骨传导耳机哪个牌子好?自费测评5大爆款骨传导耳机,高能不断!

随着科技的飞速发展&#xff0c;耳机市场也迎来了一次又一次的革新。从有线到无线&#xff0c;从入耳式到头戴式&#xff0c;每一次技术的突破都为用户带来了全新的听觉体验。近年来&#xff0c;骨传导耳机以其独特的传声方式和健康舒适的佩戴体验&#xff0c;逐渐成为运动爱好…

初识Linux之指令(二)

一&#xff1a;head指令 head 与 tail 就像它的名字一样的浅显易懂&#xff0c;它是用来显示开头或结尾某个数量的文字区块&#xff0c;head 用来显示档案的 开头至标准输出中&#xff0c;而 tail 想当然尔就是看档案的结尾。 语法&#xff1a;head 【参数】 【文件】 功能&…

docker (desktopcompose) download

docker docker-compose download 百度网盘获取离线包链接release-notes 参考dockerdocker-composewlspowershell

Loss:Objects as Points

目录 3. 预备知识4. 物体作为点4.1. 3D 检测4.2. 人体姿态估计4-(1). 物体作为点的核心概念4-(2). 从点到边界框的推理过程4-(3). 3D 检测4-(4). 人体姿态估计5. 实现细节目录 3. 预备知识4. 物体作为点4.1. 3D 检测4.2. 人体姿态估计4-(1). 物体作为点的核心概念4-(2). 从点到…

微知-Mellanox提供的一个不错的测试rdma_cm方式建链的工具软件ucmatose?(ucmatose; ucmatose -s 1.1.1.1)

文章目录 快速命令获取背景实验server端客户端一个错误的情况无法建链&#xff1a; rpm安装包&#xff1a;librdmacm-utils-48.0-1.0.1.an8.x86_64详细介绍综述 快速命令获取 #server端 ucmatose# client端 ucmatose -s 1.1.1.1背景 平时使用rdma cm建链的测试一般使用ib_wri…

算法:974.和可以被K整除的子数组

题目 链接:leetcode链接 思路分析&#xff08;前缀和 同余定理&#xff09; 首先&#xff0c;我们要了解一下什么是同余定理 同余定理&#xff1a; 如果&#xff08;a - b&#xff09;/ p k …… 0 则 a % p b % p 证明我写在草稿纸上&#xff0c;如下图&#xff1a; 初…

Ubuntu QT 交叉编译环境搭建

文章目录 下载安装qtCreatornot a valid identifier 的错误 安装g下载并安装交叉编译器下载交叉编译器安装交叉编译器 下载编译 ARM 的Qt平台源码配置arm的QT平台 下载安装qtCreator 去QT下载官网下载对应需要的QT软件。 这里下载5.12.96版本的 改变安装包权限&#xff0c;…

红海云首届粤港澳大湾区人力资源服务创新创业大赛斩获殊荣

10月11日&#xff0c;由广东省人力资源和社会保障厅、广州市人民政府主办&#xff0c;广州市人力资源和社会保障局承办的“首届粤港澳大湾区人力资源服务创新创业大赛”决赛落下帷幕&#xff0c;红海云凭借领先的HR数字化产品创新实践斩获大赛三等奖。 本次大赛以“激活人力创…

21c RAC CLSRSC-143 CLSRSC-150 crsgpnp.pm line 2063

CLSRSC-143:Failed to create a peer profile for Oracle Cluster gpnp using ‘gpnptool’(error code 1) 21c RAC GI安装时&#xff0c;在跑root.sh时报错如下&#xff1a; 查看了下crsgpnp.pm line 2063为如下原代码 第一感觉是主机名太长了&#xff0c;将主机名缩减后…

基础IO -- 理解文件(1)

目录 一&#xff1a;回顾文件 二&#xff1a;加深对文件的理解 1.概念 2.以w写方式打开 3.以a追加方式打开 4.重定向 一&#xff1a;回顾文件 以前学习过在C语言中的文件操作&#xff0c; 但那根本是不足以理解文件的&#xff0c;即站在语言角度是不可能理解文件的 我们要…

3. 单例模式唯一性问题—构造函数

1. 构造函数带来的唯一性问题指什么&#xff1f; 对于不继承MonoBehaviour的单例模式基类 我们要避免在外部 new 单例模式类对象 例如 &#xff08;完整单例模式定义在上一节&#xff09; public class Main : MonoBehaviour {void Start(){// 破坏单例模式的唯一性&#xf…

电能表预付费系统-标准传输规范(STS)(6)

6. POSToTokenCarrierInterface 应用层协议 6.1 APDU: ApplicationProtocolDataUnit 应用协议数据单元 6.1 .1 Data elements in the APDU The APDU is the data interface between the POSApplicationProcess and the application layer protocol and comprises the data e…

新装ubuntu22.04必做两件事,不然可能没法用

一、换服务源 在全部里面找到软件和安装&#xff1b;打开后 在更多里面匹配一下最适合自己的软件源&#xff1b;这个过程比较漫长&#xff1b;要耐心等待 二、换软件安装中心 先执行&#xff1a; sudo apt upgrade 后执行&#xff1a; sudo apt install plasma-discover…

Rust默认使用UTF-8编码来解析源代码文件。如果在代码中包含无法用UTF-8编码表示的字符,编译器会报错!

文章目录 Rust默认编码示例在ANSI编码下中文显示正常的代码在UTF-8编码下将显示不正常在编译时&#xff0c;Rust使用UTF-8编码来解析代码&#xff0c;发现无法用UTF-8编码表示的字符&#xff0c;于是编译器报错 Rust默认编码 Rust 语言默认使用 UTF-8 编码来解析源代码文件。如…

免费Excel工作表同类数据合并工具

下载地址&#xff1a;https://pan.quark.cn/s/81b1aeb45e4c 在 Excel 表格里&#xff0c;当我们试图手动将多行同类数据合并为一行时&#xff0c;会遭遇诸多棘手的困难以及繁杂的操作流程。在确定哪些数据属于可合并的同类数据时&#xff0c;单纯依靠人工进行对比&#xff0c;极…

10.12学习日志

一.期望与方差 接上篇 2.方差 方差是统计学中一个重要的概念&#xff0c;用于衡量随机变量或一组数据的离散程度。它反映了数据点与其平均值之间的偏离程度。方差越大&#xff0c;数据点越分散&#xff1b;方差越小&#xff0c;数据点越集中。 对于一个随机变量 X&#xff…

JavaSE——集合7:Set接口实现类—TreeSet

目录 一、TreeSet基本介绍 二、TreeSet核心方法 三、TreeSet排序方法 四、TreeSet源码解析 1.无参构造时&#xff0c;底层是创建TreeMap对象 2.有参构造时&#xff0c;底层也创建TreeMap对象 3.执行add方法 4.执行put方法 一、TreeSet基本介绍 TreeSet是 Java 集合框架…