- 个人主页:i笨笨i
- 版权:本文由【i笨笨i】原创,需要转载联系博主
- 欢迎关注、点赞、收藏(一键三连)和订阅专栏哦!
- 一个系列哟
- 瑞吉外卖开发笔记一
- 瑞吉外卖开发笔记二
- 瑞吉外卖开发笔记三
- 瑞吉外卖开发笔记四
- 瑞吉外卖开发笔记五
文件上传,也称为upload,是指将本地图片、视频、音频等文件上传到服务器上,可以供其他用户浏览或者下载的过程。文件上传在项目中应用非常广泛,并且经常使用。
文件上传时,对页面的form表单有如下要求:
目前一些前端组件库也提供了相应的上传组件,但是底层原理还是基于form表单的文件上传。
服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件:
Spring框架在spring-web包中对文件上传进行了封装,大大简化了服务端,我们只需要在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件。
文件下载,也称download,是指将文件从服务器传输到本地计算机的过程。
通过浏览器进行文件下载,通常有两种表现形式:
通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程
文件上传,页面端可以使用ElementUI提供的上传组件。可以直接使用资料中提供的上传页面,位置:资料/文件上传下载页面/upload.html。
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" />
<link rel="shortcut icon" href="../../favicon.ico">
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>
<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 < 2
if(['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️⃣LoginCheckFilter过滤器中放行上传页面,即未登录也可以上传图片
String[] urls = new String[]{
"/employee/login",
"/employee/out",
"/backend/**",
"/front/**",
"/common/**"
};
2️⃣在application.yml文件中设置上传图片后保存到那个位置。
reggie:
path: D:\\images\
3️⃣代码实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nWd9uCCk-1667089785275)(./images/Snipaste_2022-10-25_11-01-39.png)]
@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {
@Value("${reggie.path}")
private String basePath;
/**
* 文件上传
*
* @param file
* @return
*/
@PostMapping("/upload")
public R<String> upload(MultipartFile file) {
log.info(file.toString());
//原始文件名
String originalFilename = file.getOriginalFilename();
//截取后缀,例:.png
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) {
throw new RuntimeException(e);
}
return R.success(fileName);
}
}
文件下载,页面端可以使用标签展示下载的图片
/**
* 文件下载
* CommonController类中
* @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);
}
}
后台系统中可以管理菜品信息,通过新增功能来添加一个新的菜品,在添加菜品时需要选择当前所属的菜品分类,并且需要上传菜品图片,在移动端会按照菜品分类来展示对应的菜品信息。
新增菜品,其实就是将新增页面录入的菜品信息插入到dish表,如果添加了口味做法,还需要向dish_flavor表插入数据。所以在新增菜品时涉及两张表:
在开发代码功能前,先将需要用到的类和接口基本结构创建好
1️⃣实体类DishFlavor
/**
* 菜品口味
* @属性:
* 1、dishId,菜品id
* 2、name,口味名字
* 3、value,口味数据list
* 4、isDeleted,是否删除
*/
@Data
public class DishFlavor {
private static final long serialVersionUID = 1L;
private Long id;
private Long dishId;
private String name;
private 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;
}
2️⃣Mapper接口DishFlavorMapper
@Mapper
public interface DishFlavorMapper extends BaseMapper<DishFlavor> {
}
3️⃣业务层接口DishFlavorService
public interface DishFlavorService extends IService<DishFlavor> {
}
4️⃣业务层实现类DishFlavorServiceImpl
@Service
public class DishFlavorServiceImpl extends ServiceImpl<DishFlavorMapper, DishFlavor> implements DishFlavorService {
}
5️⃣控制层DishController
/**
* 处理菜品和菜品口味
*/
@RestController
@RequestMapping("/dish")
public class DishController {
@Autowired
private DishService dishService;
@Autowired
private DishFlavorService dishFlavorService;
}
在开发代码之前,需要梳理一下新增菜品时前端页面和服务端的交互过程:
开发新增菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可
1️⃣点击新增菜品时,会查询菜品分类数据,数据展示在菜品分类下拉框
/**
* 根据条件查询分类数据
* CategoryController类中
* @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());
//优先使用Category::getSort排序,在sort属性相同的情况下再根据updateTime属性排序
queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);
List<Category> list = categoryService.list(queryWrapper);
return R.success(list);
}
2️⃣导入DishDto,用于封装页面提交的数据
@Data
public class DishDto extends Dish {
private List<DishFlavor> flavors = new ArrayList<>();
private String categoryName;
private Integer copies;
}
DTO,全称为Data Transfer object,即数据传输对象,一般用于展示层与服务层之间的数据传输。
3️⃣新增菜品同时插入菜品对应的口味数据
新增菜品同时插入菜品对应的口味数据,需要操作两张表:dish、dishflavor
在DishService接口中添加方法saveWithFlavor,在DishServiceImpl实现
public interface DishService extends IService<Dish> {
/**
* 新增菜品同时插入菜品对应的口味数据,需要同时操作两张表:dish,dish_flavor
* @param dishDto
*/
void saveWithFlavor(DishDto dishDto);
}
saveWithFlavor方法涉及到多张表,所以在方法上加入
@Transactional
注解,并且在启动类添加@EnableTransactionManagement
注解开启事务支持。
@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
@Autowired
private DishFlavorService dishFlavorService;
/**
* 新增菜品同时插入菜品对应的口味数据,需要同时操作两张表:dish,dish_flavor
* @param dishDto
*/
@Override
@Transactional
public void saveWithFlavor(DishDto dishDto) {
//保存菜品的基本信息到菜品表dish
this.save(dishDto);
//菜品id
Long dishId = dishDto.getId();
//菜品口味
List<DishFlavor> flavors = dishDto.getFlavors();
flavors = flavors.stream().map((item)->{
item.setDishId(dishId);
return item;
}).collect(Collectors.toList());
//保存菜品口味数据到菜品口味表dish_flavor,saveBatch批量保存
dishFlavorService.saveBatch(flavors);
}
}
4️⃣新增菜品
/**
* 新增菜品
* DishController类中
*/
@PostMapping
public R<String> save(@RequestBody DishDto dishDto){
dishService.saveWithFlavor(dishDto);
return R.success("新增菜品成功");
}
系统中的菜品数据很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会已分页的方式来展示列表数据。
在开发代码之前,需要梳理一下菜品分页查询时前端页面和服务端的交互过程:
开发菜品信息分页查询功能,其实就是在服务端编写代码去处理发送的这2次请求即可。
/**
* 菜品信息分页查询
* @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);
//对象拷贝(源,目的)
BeanUtils.copyProperties(pageInfo,dishDtoPage,"records");
List<Dish> records = pageInfo.getRecords();
//Steam流将Dish对象转换成DishDto对象(页面需要categoryName属性,但Dish内没有,所以需要转换)
List<DishDto> list = records.stream().map((item)->{
DishDto dishDto = new DishDto();
//除过categoryName其余属性的拷贝
BeanUtils.copyProperties(item,dishDto);
//分类id
Long categoryId = item.getCategoryId();
//根据id查询分类对象
Category category = categoryService.getById(categoryId);
String categoryName = category.getName();
dishDto.setCategoryName(categoryName);
return dishDto;
}).collect(Collectors.toList());
dishDtoPage.setRecords(list);
return R.success(dishDtoPage);
}
在菜单管理列表页面点击修改按钮,转跳到修改菜品页面,在修改页面回显菜品相关信息进行修改,最后点击确认按钮完成修改操作。
在开发代码之前,需要梳理一下修改菜品时前端页面(add.html)和服务端的交互过程:
开发修改菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可
1️⃣根据id查询菜品信息和对应的口味信息
/**
* 根据id查询菜品信息和对应的口味信息
* DishController类中
* @param id
* @return
*/
@GetMapping("/{id}")
public R<DishDto> getById(@PathVariable Long id){
DishDto dishDto = dishService.getByIdWithFlavor(id);
return R.success(dishDto);
}
2️⃣在DishServiceImpl添加getByIdWithFlavor方法
/**
* 根据id查询菜品信息和对应的口味信息
* @param id
* @return
*/
@Override
public DishDto getByIdWithFlavor(Long id) {
//1.查询菜品基本信息,从dish表查询
Dish dish = this.getById(id);
//拷贝除过口味属性
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(dish,dishDto);
//2.查询当前菜品的口味信息,从dish_flavor
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DishFlavor::getDishId,dish.getId());
//填入口味信息
List<DishFlavor> flavors = dishFlavorService.list(queryWrapper);
dishDto.setFlavors(flavors);
return dishDto;
}
3️⃣修改菜品
/**
* 修改菜品
* @param dishDto
* @return
*/
@PutMapping
public R<String> update(@RequestBody DishDto dishDto){
dishService.updateWithFlavor(dishDto);
return R.success("新增菜品成功");
}
4️⃣在DishServiceImpl类中添加
/**
* 更新菜品信息,同时更新口味信息
* @param dishDto
*/
@Override
@Transactional
public void updateWithFlavor(DishDto dishDto) {
//1.更新dish表信息
this.updateById(dishDto);
//2.更新dish_flavor表信息
//清理当前菜品对应的口味数据
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);
}
在商品买卖过程,商品停售、起售可以更加方便的让用户知道店家有什么菜品。
删除方法可以管理菜品
/**
* 停售/起售菜品
* @param status
* @param ids
* @return
*/
@PostMapping("/status/{status}")
public R<String> sale(@PathVariable int status,String[] ids){
for (String id : ids){
Dish dish = dishService.getById(id);
dish.setStatus(status);
dishService.updateById(dish);
}
return R.success("修改成功");
}
/**
* 删除菜品
* @param ids
* @return
*/
@DeleteMapping
public R<String> delete(String[] ids){
for (String id : ids){
dishService.removeById(ids);
}
return R.success("删除成功");
}