1 商品列表
1.1 准备
2 商品列表展现
2.1 前端请求
2.2 ItemController
/*业务需求:商品列表的展现
请求路径: /item/getItemList?query=&pageNum=1&pageSize=10
请求类型: get
请求参数: 使用pageResult对象接收
返回值:SysResult(pageResult)
* */
//@PathVariable restful动态获取参数
@GetMapping("/getItemList")
public SysResult getItemList(PageResult pageResult){//3个参数
pageResult=itemService.getItemList(pageResult);//3+2
return SysResult.success(pageResult);
}
2.3 ItemServiceImpl
/*
* sql:select * from item limit 起始位置,显示条数
*
* */
@Override
public PageResult getItemList(PageResult pageResult) {
//1.构建模糊查询
boolean flag= StringUtils.hasLength(pageResult.getQuery());//动态sql,为true才拼接条件
QueryWrapper<Item> queryWrapper = new QueryWrapper<>();
queryWrapper.like(flag,"title", pageResult.getQuery());
//2.定义分页对象
//页数 条数
IPage page=new Page(pageResult.getPageNum(), pageResult.getPageSize());
//page的参数由原来的页数/条数 ,经过业务调用添加了 总记录数和分页的结果
page=itemMapper.selectPage(page,queryWrapper);
long total=page.getTotal();//获取总数
List<Item> rows=page.getRecords();//获取分页的结果
return pageResult.setTotal(total).setRows(rows);
}
2.4 编辑分页配置类
@Configuration //标识配置类
public class MybatisPlusConfig {
//将自定义对象交给容器管理 告诉MP 当前使用的数据库是mysql/maridb
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
3 商品新增业务接口
3.1 关于商品信息说明
说明: item的基本信息与ItemDesc的详情信息 可以采用对象的方式进行包裹.
例如: {item: item的数据信息, itemDesc: itemDesc的数据信息}
3.1 修改表字段类型
3.2 业务接口分析
请求路径: 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"....... "
},
itemParam: {
dynamicArray: [
{paramId: 1, paramVals: "亮黑色,釉白色"},
{paramId: 2, paramVals: "8GB+128GB,8GB+256GB"}
],
staticArray: [
{"paramId": 3,"paramVals": "华为Mate 40 Pro"},
{"paramId": 4,"paramVals": "0.575kg"}.....
]
}
}
请求参数: 使用ItemVO对象接收
参数名称 | 参数类型 | 参数说明 | 备注 |
item | Item | 商品基本信息对象封装 | 不能为null |
itemDesc | ItemDesc | 商品详情信息 | 不能为null |
3.3 前端请求
3.4 封装ItemVO对象
把Item和ItemDesc封装到一个ItemVO对象中
@Data
@Accessors(chain = true)
public class ItemVO implements Serializable {
private Item item;
private ItemDesc itemDesc;
}
3.5 ItemController
/*商品新增
请求路径: /item/saveItem
请求类型: post
参数信息: itemVO对象
* */
@PostMapping("/saveItem")
public SysResult saveItem(@RequestBody ItemVO itemVO){//Item+ItemDesc
itemService.saveItem(itemVO);
return SysResult.success();
}
3.6 ItemServiceImpl
/**
* 需求: 完成2部分入库操作
* 步骤1: 完成Item入库操作
* 步骤2: 完成ItemDesc入库操作 item.id=itemDesc.id
* mybatis 知识讲解
* <insert id="xxxx" useGeneratedKeys="true"
* keyColumn="id"
* keyProperty="id">
* 新增sql
* </insert>
* MP知识讲解:
* MP基于对象的方式操作数据,如果实现数据的入库操作,
* 则数据都会与对象绑定,动态回显.
* 难点知识: 如何实现数据回显!!!!!!
* @param itemVO
*/
@Override
@Transactional
public void saveItem(ItemVO itemVO) {
//1.步骤1 实现Item对象入库
Item item = itemVO.getItem().setStatus(true);
//刚开始id为null,入库操作时候,id在数据库中会自动赋值
//赋值之后,对象中的ID依然为null
itemMapper.insert(item);
//2.步骤2 实现ItemDesc对象入库
ItemDesc itemDesc = itemVO.getItemDesc();
itemDesc.setId(item.getId());
itemDescMapper.insert(itemDesc);
}
4 商品删除 和状态更新 操作
4.1 商品删除
4.1.1 ItemController
/*
请求路径: /item/deleteItemById
请求类型: delete
请求参数: id
*/
@DeleteMapping("/deleteItemById")
public SysResult deleteItemById(Integer id){
itemService.deleteItemById(id);
return SysResult.success();
}
4.1.2 ItemService
item和item_desc都要一起删除
@Override
@Transactional
public void deleteItemById(Integer id) {
itemMapper.deleteById(id);
itemDescMapper.deleteById(id);
}
4.2 商品状态更新
4.2.1 ItemController
/*
/item/updateItemStatus
*/
@PutMapping("/updateItemStatus")
public SysResult updateItemStatus(@RequestBody Item item){
itemService.updateItemStatus(item);
return SysResult.success();
}
4.2.2 ItemService
@Override
@Transactional
public void updateItemStatus(Item item) {
itemMapper.updateById(item);
}
4.3 商品修改
4.3.1
/*
回显
* 根据ID查询商品信息:
请求路径: /item/{id}
* 参数: id
请求类型: GET
*/
@GetMapping("/{id}")
public SysResult findItemReturnById(@PathVariable Integer id){
Item item=itemService.findItemReturnById(id);
return SysResult.success(item);
}
/*
请求路径: /user/updateItem
请求类型: PUT
请求参数: 对象结构
返回值: SysResult对象*/
@PutMapping("/updateItem")
public SysResult updateItem(@RequestBody Item item){
itemService.updateItem(item);
return SysResult.success();
}
4.3.2
@Override
public Item findItemReturnById(Integer id) {
return itemMapper.selectItemById(id);
}
@Override
public void updateItem(Item item) {
itemMapper.updateItemById(item);
}
4.3.3
@Update("update item set title=#{title},sell_point=#{sellPoint},price=#{price},num=#{num} where id=#{id}")
void updateItemById(Item item);
@Select("select * from item where id=#{id}")
Item selectItemById(Integer id);
5 文件上传操作
5.1 文件上传业务说明
说明:当用户选择多张图片时,则是一张一张的传输.
5.2 接口信息
请求路径: http://localhost:8091/file/upload
请求类型: post
请求参数:
参数名称 | 参数说明 | 备注 |
file | 文件上传的参数名称 | file中携带的是二进制信息 |
ImageVO对象说明
参数名称 | 参数类型 | 参数说明 | 备注 |
virtualPath | String | virtualPath | 例如: 2021/11/11/a.jpg 不需要写磁盘地址 |
urlPath | String | urlPath | http://image.jt.com/2021/11/11/a.jpg 需要指定域名地址 |
fileName | String | 文件上传后的文件名称 | UUID.type |
5.3 ImageVO对象封装
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ImageVO implements Serializable {
private String virtualPath; //虚拟路径 动态变化的路径
private String urlPath; //图片网络地址
private String fileName; //图片名称
}
5.4 文件上传入门案例
@RestController
@CrossOrigin
@RequestMapping("/file")
public class FileController {
/**
* URL: /file/upload
* 类型: post
* 参数: file=字节信息
* 返回值: SysResult(ImageVO)
* 知识点:
* SpringMVC针对与IO操作开发了MultipartFile
* 底层实现就是常规IO流,简化了用户的操作过程.无需手动关流
* SpringMVC中默认支持最大1M
* 步骤:
* 1.获取文件名称
* 2.准备文件路径
* 3.准备文件上传的全路径
* 4.实现文件上传操作
*/
@PostMapping("/upload")
public SysResult upload(MultipartFile file) throws IOException {
//1.获取文件名称 a.jpg
String fileName = file.getOriginalFilename();
//2.准备文件目录
String dir = "D:/project3/images";
File dirFile = new File(dir);
if(!dirFile.exists()){//判断目录是否存在
dirFile.mkdirs(); //创建多级目录
}
String path = dir + "/" + fileName;
//实现文件上传
file.transferTo(new File(path));
return SysResult.success();
}
}
5.5 文件上传完整代码
5.5.1 编辑FileController
@PostMapping("/upload")
public SysResult fileUpload(MultipartFile file){
ImageVO imageVO=fileService.upload(file);
if (imageVO==null){
return SysResult.fail();
}
return SysResult.success(imageVO);
}
5.5.2 编辑FileService
1.校验文件是否为图片
2.防止文件为恶意程序
3.分目录存储 按照时间维护划分
4.防止文件重名 UUID
5.5.2.1 校验(1,2步)
@Override
public ImageVO upload(MultipartFile file) {
//1.
//获取图片名称全部转小写
String filename = file.getOriginalFilename().toLowerCase();
if (!filename.matches("^.+\\.(jpg|png|gif)$")) {//java中 \\ 代表 \ ;不识别单个\
return null;
}
//2.通过校验宽度和高度判断是否为图片
try {
BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
int width = bufferedImage.getWidth();
int height = bufferedImage.getHeight();
if (width == 0 || height == 0) {
return null;
}
} catch (IOException e) {
//一般条件下,为了不影响代码结构,将检查异常,转化为运行时异常
e.printStackTrace();
throw new RuntimeException(e);//
}
return null;
}
5.5.2.2
@Override
public ImageVO upload(MultipartFile file) {
//1.
//获取图片名称全部转小写
String fileName = file.getOriginalFilename().toLowerCase();
if (!fileName.matches("^.+\\.(jpg|png|gif)$")) {//java中 \\ 代表 \ ;不识别单个\
return null;
}
//2.通过校验宽度和高度判断是否为图片
try {
BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
int width = bufferedImage.getWidth();
int height = bufferedImage.getHeight();
if (width == 0 || height == 0) {
return null;
}
//3 分目录存储
String datePath = new SimpleDateFormat("/yyyy/MM/dd/").format(new Date());
String fileDir = rootDir + datePath;
File dirFile = new File(fileDir);//
if (!dirFile.exists()) {
dirFile.mkdirs();
}
//4.动态生成UUID
String UUID = java.util.UUID.randomUUID().toString();
int index = fileName.lastIndexOf(".");
String fileType = fileName.substring(index);
fileName = UUID + fileType;
//5.实现文件上传
String path = fileDir + "/" + fileName;
file.transferTo(new File(path));
//6.准备imageVO的数据返回
String virtualPath = datePath + fileName;
//String urlPath="https://img14.360buyimg.com/n0/jfs/t1/162878/20/21270/387542/61b8da25Ed63246d6/baffb7e01f3edefb.jpg";
String urlPath = rootURL + virtualPath;
//String urlPath = rootDir + virtualPath;
System.out.println(urlPath);
ImageVO imageVO = new ImageVO(virtualPath, urlPath, fileName);
return imageVO;
} catch (IOException e) {
//一般条件下,为了不影响代码结构,将检查异常,转化为运行时异常
e.printStackTrace();
throw new RuntimeException(e);//
}
}
6 文件删除业务接口
6.2 FileController
/*
请求路径: /file/deleteFile
请求类型: delete
请求参数: virtualPath
返回值: SysResult
*/
@DeleteMapping("/deleteFile")
public SysResult deleteFile(String virtualPath){
fileService.deleteFile(virtualPath);
return SysResult.success();
}
6.3 FileService
/*
删除文件 需要文件全路径
*/
@Override
public void deleteFile(String virtualPath) {
String localPath=rootDir+virtualPath;
File file = new File(localPath);
if (file.exists()) {//如果文件存在则删除
file.delete();
}
}
7 拓展(商品列表修改)
7.1 前端
7.1.1 修改按钮
7.1.2 弹窗回显
<!-- 修改商品数据 -->
<el-dialog title="修改商品" :visible.sync="updateItemDialogVisible" width="50%" @close="closeUpdateDialog">
<!-- 定义用户提交表单数据-->
<el-form :model="updateItemModel" :rules="rules" ref="updateItemRef" label-width="100px">
<!-- <el-form-item label="ID" prop="id">
<el-input v-model="updateItemModel.id" disabled ></el-input>
</el-form-item> -->
<el-form-item label="标题" prop="title">
<el-input v-model="updateItemModel.title" ></el-input>
</el-form-item>
<el-form-item label="买点" prop="sellPoint">
<el-input v-model="updateItemModel.sellPoint" ></el-input>
</el-form-item>
<el-form-item label="价格" prop="price">
<el-input v-model="updateItemModel.price"></el-input>
</el-form-item>
<el-form-item label="数量" prop="num">
<el-input v-model="updateItemModel.num"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="updateItemDialogVisible = false" >取 消</el-button>
<el-button type="primary" @click="updateItem">确 定</el-button>
</span>
</el-dialog>
7.1.3 data
updateItemDialogVisible: false,
//定义修改用户数据封装
updateItemModel: {
}
7.1.4 methods
//商品列表修改
// 数据回显 根据ID获取数据
async updateItemBtn(item){
this.updateItemDialogVisible = true
const {data: result} = await this.$http.get("/item/"+item.id)
if(result.status !== 200) return this.$message.error("商品查询失败")
this.updateItemModel = result.data
},
closeUpdateDialog(){
//重置表格数据
this.$refs.updateItemRef.resetFields()
},
updateItem(){
//1.预校验数据
this.$refs.updateItemRef.validate(async valid => {
if(!valid) return this.$message.error("表单验证没有通过")
//根据接口文档要求封装数据
let item = {}
item.id = this.updateItemModel.id
item.title = this.updateItemModel.title
item.sellPoint = this.updateItemModel.sellPoint
item.price = this.updateItemModel.price
item.num = this.updateItemModel.num
const {data: result} = await this.$http.put(`/item/updateItem`,item)
if(result.status !== 200) return this.$message.error("用户修改失败")
this.$message.success("用户更新成功")
this.updateItemDialogVisible = false
this.getItemList()
})
}
7.2 后端实现
7.2.1 ItemController
/*
回显
* 根据ID查询商品信息:
请求路径: /item/{id}
* 参数: id
请求类型: GET
*/
@GetMapping("/{id}")
public SysResult findItemReturnById(@PathVariable Integer id){
Item item=itemService.findItemReturnById(id);
return SysResult.success(item);
}
/*
请求路径: /user/updateItem
请求类型: PUT
请求参数: 对象结构
返回值: SysResult对象*/
@PutMapping("/updateItem")
public SysResult updateItem(@RequestBody Item item){
itemService.updateItem(item);
return SysResult.success();
}
7.2.2 ItemService
@Override
public Item findItemReturnById(Integer id) {
return itemMapper.selectById(id);
}
@Override
public void updateItem(Item item) {
itemMapper.updateItemById(item);
}
7.2.3 ItemController
public interface ItemMapper extends BaseMapper<Item> {
@Update("update item set title=#{title},sell_point=#{sellPoint},price=#{price},num=#{num} where id=#{id}")
void updateItemById(Item item);
// @Select("select * from item where id=#{id}")
// Item selectItemById(Integer id);
}
7.3 效果
8 反向代理
8.1 业务需求
需求说明: 当用户完成图片上传之后 会根据网络地址访问图片,但是查找的图片一定存在于磁盘中.
URL地址: http://image.jt.com/2021/12/16/f9981c76-e6a7-49fa-88d9-3be0851dbf50.jpg
磁盘地址: D:/project3/images/2021/12/16/f9981c76-e6a7-49fa-88d9-3be0851dbf50.jpg
技术难点:
1.当用户访问URL网络地址时,应该按照磁盘地址进行查找!
如何将网络地址转化为磁盘地址?
8.2 反向代理介绍
反向代理服务器位于用户与目标服务器之间,但是对于用户而言,反向代理服务器就相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源。同时,用户不需要知道目标服务器的地址,也无须在用户端作任何设定。反向代理服务器通常可用来作为Web加速,即使用反向代理作为Web服务器的前置机来降低网络和服务器的负载,提高访问效率。
8.2.1 知识铺垫
如果网站将真实地址直接告知用户.导致整个服务器不安全. 所以需要采用代理的技术规避该问题.
8.2.2 反向代理特点
反向代理服务器介于用户和目标服务器之间
用户从反向代理服务器获取资源, 用户以为反向代理服务器就是目标服务器.
用户不清楚真实的服务器到底是谁, 保护了真实服务器的信息. 也称之为 "服务器端代理"
8.3 正向代理介绍
8.3.1 正向代理介绍
正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。
8.3.2 正向代理特点
正向代理服务器介于客户端和原始服务器之间.
用户访问正向代理服务器,并且指定目标服务器地址.
服务器端不清楚到底是谁访问的,以为是正向代理服务器访问的. 保护了用户信息. 也称之为客户端代理
.
8.4 nginx
8.4.1 Nginx介绍
Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点(俄文:Рамблер)开发的,第一个公开版本0.1.0发布于2004年10月4日。
其将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、简单的配置文件和低系统资源的消耗而闻名。2011年6月1日,nginx 1.0.4发布。
Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。
8.4.2 Nginx特点
nginx是一块开源免费的 轻量级的反向代理服务器/web服务器.
nginx并发能力强 理论值: 5万次/秒 实际值: 3万次/秒
tomcat: 150-220 —>1000个/秒 内存占用200M
nginx占用内存少 不超过2M
8.4.3 Nginx下载
URL地址: http://nginx.org/en/download.html
8.4.4 Nginx 安装和使用
说明:
1.nginx启动会占用80端口!!
2.nginx启动路径不要有中文/空格/特殊字符 底层开发语言:C语言
访问测试:
8.4.5 关于nginx 80端占用问题说明
检查端口号占用
8.4.6 关于nginx 启动项说明
说明: nginx的启动每次都会启动2个进程项.
主进程: 主要提供反向代理服务. 占用内存大的
守护进程: 防止主进程意外关闭. 占用内存小的
任务管理器 进程
8.4.7 nginx 命令(熟练掌握)
说明: nginx的命令需要在根目录中运行 nginx.exe 所在的路径就是根目录
命令:
- 启动nginx start nginx
- 重启nginx nginx -s reload
- 关闭nginx nginx -s stop
8.5 Nginx反向代理机制
http {
#每个反向代理服务,就是一个server
server {
#nginx默认监听端口号 默认都是80
listen 80;
#nginx要拦截的域名
server_name localhost;
#拦截所有的请求
location / {
# root 代表代理的是一个目录
root html;
# 配置默认访问的首页
index index.html index.htm;
}
}
}
8.6 Nginx实现图片代理
URL地址: http://image.jt.com/2021/12/16/f9981c76-e6a7-49fa-88d9-3be0851dbf50.jpg
磁盘地址: D:/project3/images/2021/12/16/f9981c76-e6a7-49fa-88d9-3be0851dbf50.jpg
代理机制:
域名: http://image.jt.com:80
代理为:
D:/project3/images
配置图片代理:
#配置图片代理
server {
listen 80;
server_name image.jt.com;
location / {
root D:/project3/images;
}
}
编辑完成之后,重启nginx
8.7 修改hosts文件
文件位置: C:\Windows\System32\drivers\etc
8.7.1 检查是否只读
如果该文件为只读 应该去除只读选项
8.7.2 检查权限
8.7.3 修改hosts文件
127.0.0.1 localhost
127.0.0.1 image.jt.com #图片
127.0.0.1 manage.jt.com #后端
127.0.0.1 web.jt.com #前端
9 项目发布准备
9.1 项目发布
1.检查所有的sql中的表名是否存在大小写问题. 检查注解 检查Sql
2.根据码云 检查POM.xml文件内容
3.将项目打包 install 命令
4.前端项目打包
检查是否生成dist目录
9.2 前端发布准备
1.修改main.js的路径
2.编辑AddItem.vue文件
3.将前端项目进行编译
如果将上述的操作修改完成,之后需要将程序编译。如图所示
9.3 前端项目发布
9.3.1 业务说明
将编译之后的dist目录 复制到nginx的根目录中。
9.3.2 前端项目发布
需求: 用户通过http://web.jt.com 访问 dist/index.html
#配置前端代理
server {
listen 80;
server_name web.jt.com;
location / {
root dist;
index index.html;
}
}
重启nginx
9.3.3 前端代码测试
9.4 后端项目发布
9.4.1 动态获取端口号
package com.jt.controller;
@RestController
@CrossOrigin
public class PortController {
//动态获取端口号
@Value("${server.port}")
private Integer port;
@GetMapping("/getPort")
public String getPort(){
return "当前端口号:"+port;
}
}
9.4.2 项目发布
命令: java -jar 8091.jar
9.4.3 nginx完成后端发布
#配置后端代理
server {
listen 80;
server_name manage.jt.com;
location / {
#proxy_pass 映射的是请求的地址
proxy_pass http://localhost:8091;
}
}
9.4.4 后端项目测试
通过:http://manage.jt.com/itemCat/findItemCatList/3 测试后端域名是否可用