目录
一 脏数据
新建数据没有parent_id,会导致页面商品分类显示不出来,后端报空指针异常
二 商品管理中商品列表新增操作
1 VUE 过滤器用法
/* 定义过滤器 */
/* Vue.filter("定义过滤器名称",function(参数){
过滤器需要添加return
}) */
Vue.filter("priceFormat",function(price){
//console.log(price)
return (price / 100).toFixed(2)
})
2 商品新增操作
2.1 页面跳转--index.js
根据用户请求地址,实现页面跳转.
import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '../components/Login.vue'
import ElementUI from '../components/ElementUI.vue'
import Home from '../components/Home.vue'
import User from '../components/user/user.vue'
import Item from '../components/items/Item.vue'
import Welcome from '../components/Welcome.vue'
import ItemCat from '../components/items/ItemCat.vue'
import AddItem from '../components/items/addItem.vue'
//使用路由机制 通过children实现路由嵌套, redirect重定向
Vue.use(VueRouter)
const routes = [
{path: '/', redirect: '/login'},
{path: '/login', component: Login},
{path: '/elementUI', component: ElementUI},
//children组件的跳转 在home组件内部进行填充
{path: '/home', component: Home, redirect: '/welcome', children:[
{path: '/welcome', component: Welcome},
{path: '/user', component: User},
{path: '/item', component: Item},
{path: '/itemCat', component: ItemCat},
{path: '/item/addItem', component: AddItem}
]}
]
2.2 商品新增业务说明(一)
商品分为(item/itemDesc), 目前只完成商品基本信息的提交即可.
2.3 页面JS分析--addItem.vue
/* 添加商品按钮 */
async addItemBtn(){
//console.log(this.addItemForm)
//1.完成表单校验
this.$refs.addItemFormRef.validate( valid => {
if(!valid) return this.$message.error("请输入商品必填项")
})
//2.完成商品参数的封装
//2.0 将商品价格扩大100倍
this.addItemForm.price = this.addItemForm.price * 100
//2.1 将商品图片的数据转化为字符串
this.addItemForm.images = this.addItemForm.images.join(",")
//2.5 实现商品数据提交 商品(item基本信息/商品详情信息)
let submitAddItem = {
item : this.addItemForm,
itemDesc: this.itemDesc
}
//console.log(submitAddItem)
let {data: result} = await this.$http.post("/item/saveItem",submitAddItem)
if(result.status !== 200) return this.$message.error("商品添加失败")
this.$message.success("商品添加成功")
//2.5添加完成之后,将数据重定向到商品展现页面
this.$router.push("/item")
}
2.4编辑ItemVO对象--VO层
/**
* @author 刘昱江
* 时间 2021/4/16
*/
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ItemVO { //该对象封装商品所有的参数信息
private Item item;
private ItemDesc itemDesc;
private ItemParam itemParam;
}
2.5 商品新增的业务接口文档分析
- 请求路径: http://localhost:8091/item/saveItem
- 请求类型: post
- 前端传递参数分析
{
item: {
images: "/2021/05/20/da0c1d4781c1499399f090da8b60f359.jpg,/2021/05/20/2ac1c34776a7465887eb019655354c3c.jpg"
itemCatId: 560
num: "100"
price: 718800
sellPoint: "【华为官方直供,至高12期免息0首付,原装正品】送华为原装无线充+运动蓝牙耳机+蓝牙音箱+三合一多功能数据线+钢化膜等!"
title: "华为P40 Pro 5G手机【12期免息可选送豪礼】全网通智能手机"
},
itemDesc: {
itemDesc: "<ul><li>品牌: <a href=https://list.jd.com/list.html"....... "
}
}
- 请求参数: 使用ItemVO对象接收
参数名称 | 参数类型 | 参数说明 | 备注 |
---|---|---|---|
item | Item | 商品基本信息对象封装 | 不能为null |
itemDesc | ItemDesc | 商品详情信息 | 不能为null |
- ItemVO参数详解:
- Item对象
- itemDesc 对象
- 为了降低商品提交代码的耦合性,将大字段信息详情,采用ItemDesc对象进行封装
参数名称 | 参数类型 | 参数说明 | 备注 |
---|---|---|---|
id | Integer | 商品Id信息 | 因为Item和ItemDesc是一对一关系 所以需要依赖Item对象的Id值 |
itemDesc | String | 商品详情信息 | 内部包含了大量的html语句 |
- 返回值结果:
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
3 商品详情说明
3.1 引入富文本编辑器
说明: 富文本用户操作的都是html代码片段
//1.导入JS
/* 导入富文本编辑器 */
import VueQuillEditor from 'vue-quill-editor'
/* 导入富文本编辑器对应的样式 */
import 'quill/dist/quill.core.css' // import styles
import 'quill/dist/quill.snow.css' // for snow theme
import 'quill/dist/quill.bubble.css' // for bubble theme
//2. 页面展现
<el-tab-pane label="商品详情" name="2">
<!-- 定义富文本编辑器-->
<quill-editor ref="myQuillEditor" v-model="itemDesc.itemDesc">
</quill-editor>
<!-- 定义添加商品按钮-->
<el-button type="primary" class="addItemBtnClass" @click="addItemBtn">添加商品</el-button>
</el-tab-pane>
3.2 Item和ItemDesc关系
- Item表: 主要封装了商品的基本信息.
- ItemDesc表: 主要封装商品详情信息(大字段—html代码片段)
- 原因: 如果用户频繁的查询大字段 则影响效率. 所以将商品信息分为item和itemDesc
- 网页起初只展现item基本信息,当用户选定了某个item就会进商品详情信息的页面,itemDesc才会展现
- 关联关系: item.id = itemDesc.id ID的值一致的.在ItemDesc类中添加主键注解@TableId
3.3 编辑ItemController
/**
* 完成商品新增操作
* 1.URL地址: http://localhost:8091/item/saveItem
* 2.参数: {item,itemDesc} 使用ItemVO进行接收
* 3.请求类型: post JSON
* 4.返回值: SysResult对象
*/
@PostMapping("/saveItem")
public SysResult saveItem(@RequestBody ItemVO itemVO){
itemService.saveItem(itemVO);
return SysResult.success();
}
3.4 编辑ItemServiceImpl
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jt.mapper.ItemDescMapper;
import com.jt.mapper.ItemMapper;
import com.jt.pojo.Item;
import com.jt.pojo.ItemDesc;
import com.jt.vo.ItemVO;
import com.jt.vo.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.List;
@Service
public class ItemServiceImpl implements ItemService {
//依赖注入商品基本信息的持久层接口
@Autowired
private ItemMapper itemMapper;
//依赖注入商品详情的持久层接口
@Autowired
private ItemDescMapper itemDescMapper;
/*商品列表的新增入库
*问题分析:
* 1.item入库之后,才会有主键信息
* 2.itemDesc入库时,必须获取与Item.id一样的数据
*解决方法:
* 设定主键的自动回显功能!
*如何设计:
* 开启主键自增 主键回显配置 Mybatis原生操作
* <insert id="xxx" useGeneratedKeys="true" keyColumn="主键字段" keyProperty="主键属性"></insert>
* 但是现在我们使用的是MP,它在完成入库时,自动实现了数据回显的功能,所以ID是有值的
* 知识:哪种情况会有自动回显的功能!!mybatis没有,mybatis-plus有
* BUG:由于测试数据可能出现重复的现象,需要提前删除多余的重复的表记录代码
* @Param itemVO
* */
@Override
@Transactional
public void saveItem(ItemVO itemVO) {
//1.获取item对象信息
Item item = itemVO.getItem();
item.setStatus(true);
//2.商品入库,主键自增,入库之后才能看到主键
itemMapper.insert(item);
//3.获取商品详情信息
ItemDesc itemDesc = itemVO.getItemDesc();
//如何保证item和itemDesc的主键id保持一致
itemDesc.setId(item.getId());
itemDescMapper.insert(itemDesc);
}
}
注意:
1)开启主键自增 主键回显配置 这是在mapper映射文件中的Mybatis原生操作:
* <insert id="xxx" useGeneratedKeys="true" keyColumn="主键字段" keyProperty="主键属性"></insert>*
2)但是现在我们使用的是MP,它在完成入库时,自动实现了数据回显的功能,所以ID是有值的
3)总结:哪种情况会有自动回显的功能!!
答:mybatis没有,mybatis-plus有;当我们使用的是mybatis依赖文件时,我们需要在映射文件中开启主键自增标签,如果我们使用的是MybatisPlus时,它自带自动回显功能,,不用在映射文件进行开启主键自增标签.
3.5 BUG说明
item_desc表记录全部删除,才可添加新的id数据,否则会报主键重复错误
如果出现主键重复,会报入库异常.则需要提前删除多余测试数据
三 商品管理中商品列表文件上传操作
3.1 官网API
<!-- 图片上传操作
file-list="fileList" 双向数据绑定 控制图片的数量数组结构[],
:on-preview="handlePreview" 点击图片时候调用的函数
:on-remove="handleRemove" 当用户点击删除按钮时,触发函数
multiple 配置多选
drag 是否启用拖拽
action="图片提交的地址信息"
-->
<el-upload
class="upload-demo"
action="https://jsonplaceholder.typicode.com/posts/"
:on-preview="handlePreview"
:on-remove="handleRemove"
:file-list="fileList"
list-type="picture"
multiple
drag>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
3.2 文件上传的业务接口文档
- 请求路径: http://localhost:8091/file/upload
- 请求类型: post
- 请求参数:
参数名称 | 参数说明 | 备注 |
---|---|---|
file | 文件上传的参数名称 | file中携带的是二进制信息 |
- 返回值结果:
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 返回ImageVO对象 |
- ImageVO对象说明
参数名称 | 参数类型 | 参数说明 | 备注 |
---|---|---|---|
virtualPath | String | 图片实际路径 不包含磁盘信息 | 例如: 2021/11/11/a.jpg 不需要写磁盘地址 |
urlPath | String | 图片url访问地址 | http://image.jt.com/2021/11/11/a.jpg 需要指定域名地址 |
fileName | String | 文件上传后的文件名称 | UUID.type |
3.3 后端VO层创建ImageVO对象
package com.jt.vo;
public class ImageVO {
private String virtualPath; //虚拟路径
private String urlPath; //网络地址
private String fileName; //图片名称
}
3.4 文件上传入门案例
@RestController
@CrossOrigin
@RequestMapping("/file")
public class FileController {
/**
* 业务: 文件上传入门案例
* URL: http://localhost:8091/file/upload
* 参数: file=[101001010111]
* 返回值: SysResult对象(ImageVO)
* 知识回顾: 字节流/字符流/缓存流 默认的语法复杂
* 高级API: SpringMVC 专门针对与流,开发了一个高级API
* 文件上传步骤:
* 1.获取文件上传名称
* 2.准备文件上传的目录
* 3.准备全文件的路径 目录/文件名称
* 4.实现上传
*/
@PostMapping("/upload")
public SysResult upload(MultipartFile file) throws IOException {
//1.动态获取文件名称
String fileName = file.getOriginalFilename();
//2.准备文件目录 Linux系统不能识别\
String dirPath = "F:/images";
File dirFile = new File(dirPath);
if(!dirFile.exists()){
//应该创建一个新目录 创建多级目录
dirFile.mkdirs();
}
//3.拼接文件路径
String filePath = "F:/images/" + fileName;
//4.实现文件上传
file.transferTo(new File(filePath));
System.out.println("实现文件上传");
return SysResult.success();
}
}
3.5 正则表达式
正则表达式,又称规则表达式。(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。
许多程序设计语言都支持利用正则表达式进行字符串操作。例如,在Perl中就内建了一个功能强大的正则表达式引擎。正则表达式这个概念最初是由Unix中的工具软件(例如sed和grep)普及开的。正则表达式通常缩写成“regex”,单数有regexp、regex,复数有regexps、regexes、regexen。
语法:
1)匹配确定的次数:
例子: a{5} a出现5次
a{5,} a出现至少5次 >=5
a{5,8} a出现只能 5-8次
2)匹配任意字符:
3)匹配字符区间范围
[xyz] 该字符只能取值 x/y/z中的一个 匹配单个字符
^ xyz 该字符除了xyz之外的其他字符.
[a-z] 该字符必须 a-z的区间中的一个
[0-9] 该字符必须 0-9的区间中的一个
4)分组结构:
(png|jpg|gif) 字符只能匹配png|jpg|gif中的一个 匹配的是字符串
3.6 编辑FileController
package com.jt.controller;
import com.jt.service.FileService;
import com.jt.vo.ImageVO;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@RestController
@CrossOrigin
@RequestMapping("/file")
public class FileController {
@Autowired
private FileService fileService;
/*需求分析:文件上传成功返回的是ImageVO对象
* @param file
* @return
* @throws IOException*/
@PostMapping("/upload")
public SysResult upload(MultipartFile file) throws IOException {
ImageVO imageVO = fileService.upload(file);
//不成功,返回null
if (imageVO == null) {
return SysResult.fail();
}
return SysResult.success(imageVO);
}
/*高级API:SpringMVC提供文件上传的API*/
// @PostMapping("/upload")
// public SysResult upload(MultipartFile file) throws IOException {
// //1.动态获取文件名称
// String fileName = file.getOriginalFilename();
// //2.准备文件目录 ,注意:linux系统不识别反斜杠\,需要改为斜杠
// String dirPath = "D:/我的文档/Pictures/爱好";
//
// File dirFile = new File(dirPath);
// if(! dirFile.exists()){
// //文件目录不存在,应该创建新的目录
// dirFile.mkdirs();
// }
// //3.拼接文件路径
// String filePath = "D:/我的文档/Pictures/爱好" + fileName;
// //4.实现文件上传
// file.transferTo(new File(filePath));//会出现IO异常,上抛或者try-catch
// System.out.println("实现文件上传");
// return SysResult.success();
// }
}
3.6 编辑FileServiceImpl
package com.jt.service;
import com.jt.vo.ImageVO;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
@Service
public class FileServiceImpl implements FileService{
/**
* 规则说明:
* 文件磁盘地址: E:/images/yyyy/MM/dd/uuid.jpg
* 网络访问地址: http://image.jt.com/yyyy/MM/dd/uuid.jpg
*/
private String localDir = "E:/images"; //本地磁盘前缀
private String preURLPath = "http://image.jt.com"; //网络访问域名
/**
* 1.校验文件上传的类型 jpg|png|gif
* 2.应该校验文件是否为恶意程序. 木马.exe.jpg
* 3.为了提高检索效率 应该分目录存储. 1.hash方式 xx/xx/xx/xx 分布不均
* 2.日期格式 yyyy/MM/dd 目录不断增长
* 4.防止文件重名 UUID.jpg
* @param file
* @return
*/
@Override
public ImageVO upload(MultipartFile file) {
//1.图片类型的校验 正则表达式 aaa.jpg
String fileName = file.getOriginalFilename();
//字符大小写 干扰正则的判断 将所有的文件转化为小写字母
fileName = fileName.toLowerCase();
//程序不满足正则, 则用户上传的图片有问题
if(!fileName.matches("^.+\\.(jpg|png|gif)$")){
return null;
}
//2. 校验文件是否为恶意程序 判断依据 属性宽度和高度 aa.exe.jpg
try {
//该对象是用来专门操作图片的API
BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
int height = bufferedImage.getHeight();
int width = bufferedImage.getWidth();
//如果有一项为0 则表示一定不是正经的图片
if(height == 0 || width == 0){
return null;
}
//3.分目录存储文件 /yyyy/MM/dd
//3.1 准备文件根目录
String dateDir = new SimpleDateFormat("/yyyy/MM/dd/").format(new Date());
//拼接文件目录 F:/images/2021/MM/dd/
String dirPath = localDir + dateDir;
File dirFile = new File(dirPath);
//3.2 判断是否需要创建目录
if(!dirFile.exists()){ //不存在目录时,应该创建目录
dirFile.mkdirs();
}
//4.防止文件重名 UUID.后缀
String uuid = UUID.randomUUID().toString().replace("-", "");
//获取.的下标位置
int index = fileName.lastIndexOf(".");
//截取文件类型
String fileType = fileName.substring(index);
//拼接新文件路径
String realFileName = uuid + fileType;
//5.实现文件上传操作
//5.1 准备文件的全路径 文件目录/文件名称
String realFilePath = dirPath + realFileName;
//5.2 实现文件上传
file.transferTo(new File(realFilePath));
//6.封装返回值结果
//封装虚拟路径 /2021/11/11/uuid.jpg
String virtualPath = dateDir + realFileName;
//封装URL地址 协议名称://域名:端口号/图片虚拟地址
String urlPath = preURLPath + virtualPath;
System.out.println("图片网络地址:"+urlPath);
//封装VO对象
ImageVO imageVO = new ImageVO();
imageVO.setVirtualPath(virtualPath);
imageVO.setUrlPath(urlPath);
imageVO.setFileName(realFileName);
return imageVO;
} catch (IOException e) {
e.printStackTrace();
return null; //如果程序执行报错,则返回null
}
}
}
3.7 页面效果
点击添加商品按钮-->添加基本信息-->添加商品图片-->上传文件-->在本地磁盘中选择图片,确定
成功后点击图片名称可以预览图片,但现在由于没有图片地址协议,显示不出来
图片网络地址: http://image.jt.com/2021/08/11/bcde4a39f32a41b287f3f7b485e80d82.jpg
之后切换为磁盘地址:
E:/images/2021/08/11/bcde4a39f32a41b287f3f7b485e80d82.jpg
上传完成后,编辑商品详情:
添加商品完成后,提示成功.我们可以在指定的本地磁盘路径中找到上传的图片,日期类的文件夹使用的长久一些
四 商品列表状态修改
4.1 页面JS--item.vue
<el-table-column prop="status" label="状态" width="80px">
<template slot-scope="scope">
<el-switch v-model="scope.row.status" active-color="#13ce66" inactive-color="#ff4949"
@change="updateStatus(scope.row)"></el-switch>
</template>
</el-table-column>
触发的事件:
async updateStatus(item) {
const {
data: result
} = await this.$http.put("/item/updateItemStatus", {
id: item.id,
status: item.status
})
if (result.status !== 200) return this.$message.error("更新状态失败")
this.$message.success("更新状态成功")
},
4.2 商品列表修改状态的业务接口文档
- 请求路径: /itemCat/status/{id}/{status}
- 请求类型: put
- 请求参数:
参数名称 | 参数说明 | 备注 |
---|---|---|
id | 用户ID值 | 不能为null |
status | 用户的状态信息 | 不能为null |
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
4.3 编辑itemcontroller
/*修改状态*/
@PutMapping("/updateItemStatus")
public SysResult updateItemStatus(@RequestBody Item item){
itemService.updateItemStatus(item);
return SysResult.success(item);
}
4.4 编辑ItemServiceImpl
/*修改状态*/
@Override
public void updateItemStatus(Item item) {
itemMapper.updateById(item);
}
重启服务器,商品列表就可以修改状态,作用是用于商品的商家和下架
五 商品列表的删除
5.1 页面js--item.vue
按钮绑定事:
<el-table-column label="操作">
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" size="mini" @click="updateItemBtn(scope.row)">修改</el-button>
<el-button type="danger" icon="el-icon-delete" size="mini" @click="deleteItemBtn(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
事件触发的方法:
async deleteItemBtn(item) {
//消息确认框
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
//根据id删除数据
const {
data: result
} = await this.$http.delete("/item/deleteItemById", {
params: {
id: item.id
}
})
if (result.status !== 200) return this.$message.error("商品删除失败")
this.$message.success("商品删除成功")
//重新获取商品列表信息
this.getItemList()
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
5.2 商品列表删除操作的业务接口文档
5.3 编辑ItemController
/*商品列表的删除*/
@DeleteMapping("/deleteItemById")
public SysResult deleteItemById(Integer id){
itemService.deleteItemById(id);
return SysResult.success();
}
5.4 编辑ItemServiceImpl
/*删除商品列表的id时,商品详情信息也要删除*/
@Override
public void deleteItemById(Integer id) {
itemMapper.deleteById(id);
itemDescMapper.deleteById(id);
}
重启服务器,刷新页面即可该操作
六 商品列表中上传文件的删除操作
6.1 页面js---additem.vue
触发的方法:
//移除图片的方法
async handleRemove(file) {
//移除数组中的数据
let virtualPath = file.response.data.virtualPath
//通过findIndex函数 获取数组中指定数据的位置
let index = this.addItemForm.images.findIndex(x => x === virtualPath)
//删除数组中指定的数据
this.addItemForm.images.splice(index, 1)
//删除服务中的文件
let {
data: result
} = await this.$http.delete("/file/deleteFile", {
params: {
virtualPath: virtualPath
}
})
if (result.status !== 200) return this.$message.error("删除图片失败")
this.$message.success("删除图片成功")
},
6.2 文件删除的业务接口文档
- 请求路径: http://localhost:8091/file/deleteFile
- 请求类型: delete
- 请求参数:
参数名称 | 参数说明 | 备注 |
---|---|---|
virtualPath | 文件上传的虚拟的路径 | 删除时需要磁盘路径一起删除 |
- 返回值结果:
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
6.3 编辑后台FileController
/*图片删除*/
@DeleteMapping("/deleteFile")
public SysResult deleteFile(String virtualPath){
fileService.deleteFile(virtualPath);
return SysResult.success();
}
6.4 编辑FileServiceImpl
/*删除图片*/
@Override
public void deleteFile(String virtualPath) {
//1.准备文件的全路径
String path = localDir + virtualPath;
//2.将路径封装为对象
File file = new File(path);
//3.实现文件删除
file.delete();
}
会把指定路径下的图片删除