文章目录

(一)请求处理过程
(二)品牌新增页面分析
(三)品牌新增后台代码
(四)qs工具
(五)页面校验
(六)新增完成后关闭窗口
(七)文件上传代码逻辑
(八)绕过网关访问图片上传并解决跨域问题
(九)fastDFS的介绍
(十)fastDFS的使用

(一)请求处理过程

品牌查询为例,如下:

(二)品牌新增页面分析

之前完成了品牌的查询,接下来就是新增功能,点击新增品牌按钮,如下:

Brand.vue页面有一个提交按钮,如下:

点击触发addBrand方法,如下:

this.show就是我们要找的弹窗,如下:



分析该自定义组件级联选择的实现,如下:

新增品牌后看报错,可以看到请求路径请求方式,如下:

请求参数如下:

大概的请求信息我们知道了,接下来看回代码,如下:

(三)品牌新增后台代码

这里需要注意的是,品牌和商品分类之间是多对多关系
因此我们有一张中间表,来维护两者间关系,如下:

CREATE TABLE `tb_category_brand` (`category_id` bigint(20) NOT NULL COMMENT '商品类目id',`brand_id` bigint(20) NOT NULL COMMENT '品牌id',PRIMARY KEY (`category_id`,`brand_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品分类和品牌的中间表,两者是多对多关系';

这张表中并没有设置外键约束,似乎与数据库的设计范式不符,为什么这么做?

  • 外键会严重影响数据库读写的效率
  • 数据删除时会比较麻烦

在电商行业,性能是非常重要的,我们宁可在代码中通过逻辑来维护表关系,也不设置外键

代码如下:

    /*** 新增品牌** @param brand* @param cids* @return*/@PostMappingpublic ResponseEntity<Void> saveBrand(@RequestBody Brand brand, @RequestParam("cids") List<Long> cids) {brandService.saveBrand(brand, cids);return ResponseEntity.status(HttpStatus.CREATED).build(); //201,已在服务器上成功创建了一个或多个新资源//如果异常自动抛500}
    /*** 新增品牌** @param brand* @param cids*/@Transactionalpublic void saveBrand(Brand brand, List<Long> cids) {brandMapper.insertSelective(brand);//再新增中检表for (Long cid : cids) {//通用mapper操作范围不能超过一张表//所以操作中间表需要我们自己写SQL语句brandMapper.insertCategoryAndBrand(cid, brand.getId());}}
    @Insert("insert into tb_category_brand(category_id,brand_id) " +"values(#{cid},#{bid})")void insertCategoryAndBrand(@Param("cid") Long cid, @Param("bid") Long bid);

测试结果如下:


原因:基于JSON数据格式进行传输,所有请求参数会被封装成一个JSON对象
所以在controller不能使用两个对象去接收它,如下:

解决:我们让前端不要传JSON了,传普通的字段,4个参数

(四)qs工具

QS是一个第三方库,我们可以用npm install qs --save来安装
不过我们在项目中已经集成了,如下:

这个工具的名字:QS,即Query String,请求参数字符串
什么是请求参数字符串?例如: name=jack&age=21
QS工具可以便捷的实现 JS的Object与QueryString的转换

在我们的项目中,QS被注入到了Vue的原型对象中,我们可以通过this.$qs来获取这个工具:

用法如下:

下面分析qs对象,在控制台中打印,如下:

    created() {console.log(this.$qs);}

发现其中有3个方法:

  • parse():把请求参数格式(QueryString)转成JSON格式(Object)
  • stringify():把JSON格式(Object)转成请求参数格式(QueryString)

测试一下,使用浏览器工具,把qs对象保存为一个临时变量temp1,然后调用stringify()方法:


注意:此时后端就不能使用@RequestBody去接受JSON数据格式了,要去掉

(五)页面校验

在组件后面指定required :rules,就会开启表单校验,如下:

其中v代表我们输入框输入的内容,如下:

(六)新增完成后关闭窗口

我们无论添加成功还是失败,都要关闭窗口,并且同步数据
这需要窗口子组件通知父组件把自己(子组件)关闭掉,代码流程如下:

(七)文件上传代码逻辑

文件的上传并不只是在品牌管理中有需求,以后的其它服务也可能需要
因此我们创建一个独立的微服务,专门处理各种上传


我们需要EurekaClientweb依赖:

    <dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency></dependencies>

覆盖application.yml配置,如下:

server:port: 8082
spring:application:name: upload-serviceservlet:multipart:max-file-size: 5MB #默认是1MB
eureka:client:service-url:defaultZone: http://127.0.0.1:10086/eurekainstance:lease-renewal-interval-in-seconds: 5lease-expiration-duration-in-seconds: 15

编写引导类,如下:

@SpringBootApplication
@EnableDiscoveryClient
public class LeyouUploadApplication {public static void main(String[] args) {SpringApplication.run(LeyouUploadApplication.class, args);}
}

编写controller,如下:

@Controller
@RequestMapping("upload")
public class UploadController {@Autowiredprivate UploadService uploadService;@PostMapping("image")public ResponseEntity<String> uploadImage(@RequestParam("file") MultipartFile file) {String url = uploadService.uploadImage(file);if (StringUtils.isBlank(url)) {return ResponseEntity.badRequest().build();}return ResponseEntity.status(HttpStatus.CREATED).body(url);}
}

编写service,如下:

@Service
public class UploadService {private static final List<String> CONTENT_TYPES = Arrays.asList("image/jpeg", "image/gif");private static final Logger LOGGER = LoggerFactory.getLogger(UploadService.class);public String uploadImage(MultipartFile file) {String originalFilename = file.getOriginalFilename();//校验文件类型String contentType = file.getContentType(); //获取mime类型if (!CONTENT_TYPES.contains(contentType)) {LOGGER.info("文件类型不合法: {}", originalFilename); //{}是占位符,originalFilename会填充占位符return null;}try {//校验文件内容BufferedImage bufferedImage = ImageIO.read(file.getInputStream());if (bufferedImage == null) {LOGGER.info("文件内容不合法: {}", originalFilename);}//保存到文件服务器file.transferTo(new File("G:\\image\\" + originalFilename));//返回url,进行回显return "http://image.leyou.com/" + originalFilename;} catch (IOException e) {LOGGER.info("服务器内部错误: {}", originalFilename);e.printStackTrace();}return null;}
}

配置hosts文件nginx,如下:
127.0.0.1 image.leyou.com

 server {listen       80;server_name  image.leyou.com;proxy_set_header X-Forwarded-Host $host;proxy_set_header X-Forwarded-Server $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;location /{root G:\\image;}}

postman测试结果如下:

(八)绕过网关访问图片上传并解决跨域问题

图片上传是文件的传输,如果也经过Zuul网关的代理,文件就会经过多次网路传输,造成网络负担
在高并发时,可能导致网络阻塞,Zuul网关不可用,这样我们的整个系统就瘫痪了
所以,我们上传文件的请求就不经过网关来处理了

我们修改nginx配置,将以/api/upload开头的请求拦截下来,转交到真实的服务地址:

location /api/upload {proxy_pass http://127.0.0.1:8082;proxy_connect_timeout 600;proxy_read_timeout 600;
}

但是这样写显然是不对的,因为ip和端口虽然对了,但是路径没变,依然是:http://127.0.0.1:8002/api/upload/image,前面多了一个/api
Nginx提供了rewrite指令,用于对地址进行重写,语法规则:rewrite "用来匹配路径的正则" 重写后的路径 [指令];

完整配置如下:

     # 上传路径的映射location /api/upload { proxy_pass http://127.0.0.1:8082;proxy_connect_timeout 600;proxy_read_timeout 600;rewrite "^/api/(.*)$" /$1 break; }location / {proxy_pass http://127.0.0.1:10010;proxy_connect_timeout 600;proxy_read_timeout 600;}
  • 首先,我们映射路径是/api/upload,而下面一个映射路径是 / ,根据最长路径匹配原则,/api/upload优先级更高。也就是说,凡是以/api/upload开头的路径,都会被第一个配置处理

  • proxy_pass:反向代理,这次我们代理到8082端口,也就是upload-service服务

  • rewrite "^/api/(.*)$" /$1 break,路径重写:

    • "^/api/(.*)$":匹配路径的正则表达式,用了分组语法,把/api/以后的所有部分当做1组

    • /$1:重写的目标路径,这里用$1引用前面正则表达式匹配到的分组(组编号从1开始),即/api/后面的所有。这样新的路径就是除去/api/以外的所有,就达到了去除/api前缀的目的

    • break:指令,常用的有2个,分别是:last、break

      • last:重写路径结束后,将得到的路径重新进行一次路径匹配
      • break:重写路径结束后,不再重新匹配路径。

      我们这里不能选择last,否则以新的路径/upload/image来匹配,就不会被正确的匹配到8082端口了

修改完成,输入nginx -s reload命令重新加载配置

测试发现存在跨域问题,如下:

分析:我们之前是在网关使用过滤器统一解决跨域问题,现在不经过网关就存在跨域问题
我们在upload-service中添加一个CorsFilter即可,如下:

测试结果如下:

(九)fastDFS的介绍

先思考一下,现在上传的功能,有没有什么问题?

上传本身没有任何问题,问题出在保存文件的方式,我们是保存在服务器机器,就会有下面的问题:

  • 单机器存储,存储能力有限
  • 无法进行水平扩展,因为多台机器的文件无法共享,会出现访问不到的情况
  • 数据没有备份,有单点故障风险
  • 并发能力差

这个时候,最好使用分布式文件存储来代替本地文件存储

什么是分布式文件系统

分布式文件系统(Distributed File System)是指文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点相连

通俗来讲:

  • 传统文件系统管理的文件就存储在本机
  • 分布式文件系统管理的文件存储在很多机器,这些机器通过网络连接,要被统一管理
    无论是上传或者访问文件,都需要通过管理中心来访问

什么是FastDFS

FastDFS是由淘宝的余庆先生所开发的一个轻量级、高性能的开源分布式文件系统
用纯C语言开发,功能丰富:

  • 文件存储
  • 文件同步
  • 文件访问(上传、下载)
  • 存取负载均衡
  • 在线扩容

适合有大容量存储需求的应用或系统
同类的分布式文件系统有谷歌的GFS、HDFS(Hadoop)、TFS(淘宝)等

FastDFS的架构


FastDFS两个主要的角色:Tracker ServerStorage Server

  • Tracker Server:跟踪服务器,主要负责调度storage节点与client通信
    在访问上起负载均衡的作用,记录storage节点的运行状态,是连接client和storage节点的枢纽
  • Storage Server:存储服务器,保存文件和文件的meta data(元数据),每个storage server会启动一个单独的线程主动向Tracker cluster中每个tracker server报告其状态信息,包括磁盘使用情况,文件同步情况及文件上传下载次数统计等信息
  • Group:文件组,多台Storage Server的集群
    上传一个文件到同组内的一台机器上后,FastDFS会将该文件实时同步到同组内的其它所有机器上,起到备份的作用,不同组的服务器,保存的数据不同,而且相互独立,不进行通信
  • Tracker Cluster:跟踪服务器的集群,有一组Tracker Server(跟踪服务器)组成
  • Storage Cluster:存储集群,由多个Group组成

上传和下载流程

  1. Client通过Tracker server查找可用的Storage server
  2. Tracker server向Client返回一台可用的Storage server的IP地址和端口号
  3. Client直接通过Tracker server返回的IP地址和端口与其中一台Storage server建立连接并进行文件上传
  4. 上传完成,Storage server返回Client一个文件ID,文件上传结束

下载

  1. Client通过Tracker server查找要下载文件所在的的Storage server
  2. Tracker server向Client返回包含指定文件的某个Storage server的IP地址和端口号
  3. Client直接通过Tracker server返回的IP地址和端口与其中一台Storage server建立连接并指定要下载文件
  4. 下载文件成功

(十)fastDFS的使用

其实我们一开始就在父工程做了版本管理,之后只需要在子工程引入依赖即可,如下:

修改application.yml,新增内容如下:

fdfs:so-timeout: 1501 # 超时时间connect-timeout: 601 # 连接超时时间thumb-image: # 缩略图width: 60height: 60tracker-list: # tracker地址:你的虚拟机服务器地址+端口(默认是22122)- 192.168.28.233:22122

修改hosts文件,如下:

新增一个Java配置类,如下:

@Configuration
@Import(FdfsClientConfig.class)
// 解决jmx重复注册bean的问题
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
public class FastClientImporter {}

编写测试用例,如下:

@SpringBootTest
@RunWith(SpringRunner.class)
public class FastDFSTest {@Autowiredprivate FastFileStorageClient storageClient;@Autowiredprivate ThumbImageConfig thumbImageConfig;@Testpublic void testUpload() throws FileNotFoundException {// 要上传的文件File file = new File("G:\\dwrg.jpg");// 上传并保存图片,参数:1-上传的文件流 2-文件的大小 3-文件的后缀 4-可以不管他StorePath storePath = this.storageClient.uploadFile(new FileInputStream(file), file.length(), "jpg", null);// 带分组的路径System.out.println(storePath.getFullPath());// 不带分组的路径System.out.println(storePath.getPath());}@Testpublic void testUploadAndCreateThumb() throws FileNotFoundException {File file = new File("G:\\dwrg.jpg");// 上传并且生成缩略图StorePath storePath = this.storageClient.uploadImageAndCrtThumbImage(new FileInputStream(file), file.length(), "png", null);// 带分组的路径System.out.println(storePath.getFullPath());// 不带分组的路径System.out.println(storePath.getPath());// 获取缩略图路径String path = thumbImageConfig.getThumbImagePath(storePath.getPath());System.out.println(path);}
}

测试结果如下:



最后改造leyou-upload,如下:

乐优商城之品牌新增fastDFS(九)相关推荐

  1. 乐优商城之分类查询品牌查询(八)

    文章目录 (一)编写分类查询 (二)跨域问题 (三)cors跨域原理 (四)解决跨域问题 (五)品牌查询页面分析 (六)品牌查询后台代码 (七)分页查询排序的原理 (八)axios (一)编写分类查询 ...

  2. 【javaWeb微服务架构项目——乐优商城day03】——(搭建后台管理前端,Vuetify框架,使用域名访问本地项目,实现商品分类查询,cors解决跨域,品牌的查询)

    乐优商城day03 0.学习目标 1.搭建后台管理前端 1.1.导入已有资源 1.2.安装依赖 1.3.运行一下看看 1.4.目录结构 1.5.调用关系 2.Vuetify框架 2.1.为什么要学习U ...

  3. 乐优商城源码/数据库及笔记总结

    文章目录 1 源码 2 笔记 2.1 项目概述 2.2 微服务 3 项目优化 4 项目或学习过程中涉及到的设计模式 5 安全问题 6 高内聚低耦合的体现 7 项目中待优化的地方 1 源码 Github ...

  4. 【javaWeb微服务架构项目——乐优商城day05】——商品规格参数管理(增、删、改,查已完成),SPU和SKU数据结构,商品查询

    乐优商城day05 0.学习目标 1.商品规格数据结构 1.1.SPU和SKU 1.2.数据库设计分析 1.2.1.思考并发现问题 1.2.2.分析规格参数 1.2.3.SKU的特有属性 1.2.4. ...

  5. 乐优商城之项目搭建(四)

    文章目录 (一)项目分类 (二)电商行业 (三)专业术语 (四)项目介绍 (五)技术选型 (六)开发环境 (七)搭建后台环境:父工程 (八)搭建后台环境:eureka (九)搭建后台环境:zuul ( ...

  6. 乐优商城(05)--商品管理

    乐优商城(05)–商品管理 一.导入图片资源 现在商品表中虽然有数据,但是所有的图片信息都是无法访问的,因此需要把图片导入到服务器中: 将images.zip文件上传至/leyou/static目录: ...

  7. 乐优商城笔记六:商品详情页

    使用模板引擎 Thymeleaf + nginx 完成商品详情页静态化 完成乐优商城商品详情页 搭建商品详情页微服务 创建子工程 GroupId:com.leyou.service ArtifactI ...

  8. 乐优商城学习笔记五-商品规格管理

    0.学习目标 了解商品规格数据结构设计思路 实现商品规格查询 了解SPU和SKU数据结构设计思路 实现商品查询 了解商品新增的页面实现 独立编写商品新增后台功能 1.商品规格数据结构 乐优商城是一个全 ...

  9. 乐优商城 Day 09(thymeleaf,Rabbitmq,商品详情页,非教程)

    乐优商城学习Day09: 注意:此次代码都是在第八天的基础上 第八天的链接如下: https://blog.csdn.net/zcylxzyh/article/details/100859210 此次 ...

最新文章

  1. spring4 整合 mybatis3 配置文件
  2. Docker 集群 图形化显示 Visualizer
  3. 算法分析与设计-实验三 贪心算法设计
  4. android struts2 图片上传,xhEditor struts2实现图片上传
  5. MYSQL 【汇总数据】 【分组数据】 学习记录
  6. google python代码规范_Python代码这样写才规范优雅! (二)
  7. python的requests快速上手、高级用法和身份认证
  8. 3.1 语音的产生与感知
  9. java list判断是否存在字符串_java怎么判断字符串是否存在于list集合中?
  10. 21天学通C语言-学习笔记(4)
  11. 【图像去噪】基于脉冲神经网络PCNN实现图像去噪附matlab代码
  12. 简单英译汉SQL脚本
  13. 经纬度转GeoHash
  14. vue 项目中 zip 压缩包文件下载
  15. Ubuntu 20.04 Micosoft edg 浏览器安装教程
  16. 微信小程序的基本操作
  17. mac地址储存在计算机的内存,mac地址存储在
  18. Quartz入门教程
  19. MySQL的时间戳2038年问题还有16年,最好在设计上的时候使用datetime就可以了,不要使用时间戳字段了,即使用了也不要用int类型进行映射,使用long类型映射即可
  20. Java SE(7)

热门文章

  1. OSChina 周日乱弹 ——书中自有颜如玉
  2. Designing Specification
  3. 推荐21款最佳 HTML5 网页游戏
  4. 网易互娱耗时最长的活动
  5. TSP问题解析篇之自适应大邻域搜索(ALNS)算法深度通读(附python代码)
  6. Unity3D中删除指定路径的文件+删除文件夹
  7. 产品思维训练 | 你的项目总是不能按期上线,你会如何解决?
  8. 在linux上运行迷宫问题,C语言 迷宫问题(堆栈及其应用)
  9. 自己整理的:学习verilog DHL问题笔记——Quartus常见错误
  10. python读取fnl数据计算200-800km范围内的区域平均、散度、涡度实现grads函数