乐优商城--服务(四) : 上传微服务(LyUploadApplication)


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

1. 搭建项目

1.1 引入依赖

我们需要EurekaClient和web依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>leyou</artifactId>
        <groupId>com.leyou.parent</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.leyou.page.service</groupId>
    <artifactId>ly-upload</artifactId>

    <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>
        <!--引入ly-common后便会用到-->
        <dependency>  
            <groupId>com.leyou.common</groupId>
            <artifactId>ly-common</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.github.tobato</groupId>
            <artifactId>fastdfs-client</artifactId>
        </dependency>
    </dependencies>
</project>

1.2 配置

server:
  port: 8082
spring:
  application:
    name: upload-service
  servlet:
    multipart:
      max-file-size: 5MB # 限制文件上传的大小
# Eureka
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
    instance:
      ip-address: 127.0.0.1
      prefer-ip-address: true
fdfs:
  so-timeout: 1501
  connect-timeout: 601
  thumb-image: # 缩略图
    width: 60
    height: 60
  tracker-list: # tracker地址
    - 192.168.184.130:22122
ly:
  upload:
    baseUrl: http://image.leyou.com/
    allowTypes:
      - image/png
      - image/bmp
      - image/jpeg

1.3 启动类

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

2. 业务

2.1 web

2.1.1 页面分析

  • 请求方式:上传肯定是POST
  • 请求路径:/upload/image
  • 请求参数:文件,参数名是file,SpringMVC会封装为一个接口:MultipleFile
  • 返回结果:上传成功后得到的文件的url路径

2.1.2 实现业务

@RestController
@RequestMapping("upload")
public class UploadController {
    @Autowired
    private UploadService uploadService;
	// 图片上传
    @PostMapping("image")
    public ResponseEntity<String> uploadImage(@RequestParam("file")MultipartFile file){
        return ResponseEntity.ok(uploadService.uploadImage(file));
    }
}

2.2 service

在上传文件过程中,我们需要对上传的内容进行校验:

  1. 校验文件大小
  2. 校验文件的媒体类型
  3. 校验文件的内容

文件大小在Spring的配置文件中设置,因此已经会被校验,我们不用管。

@Service
public class UploadService {

    private static final Logger logger = LoggerFactory.getLogger(UploadController.class);

    // 支持的文件类型
    private static final List<String> suffixes = Arrays.asList("image/png", "image/jpeg");

    public String upload(MultipartFile file) {
        try {
            // 1、图片信息校验
            // 1)校验文件类型
            String type = file.getContentType();
            if (!suffixes.contains(type)) {
                logger.info("上传失败,文件类型不匹配:{}", type);
                return null;
            }
            // 2)校验图片内容
            BufferedImage image = ImageIO.read(file.getInputStream());
            if (image == null) {
                logger.info("上传失败,文件内容不符合要求");
                return null;
            }
            // 2、保存图片
            // 2.1、生成保存目录
            File dir = new File("D:\\heima\\upload");
            if (!dir.exists()) {
                dir.mkdirs();
            }
            // 2.2、保存图片
            file.transferTo(new File(dir, file.getOriginalFilename()));

            // 2.3、拼接图片地址
            String url = "http://image.leyou.com/upload/" + file.getOriginalFilename();

            return url;
        } catch (Exception e) {
            return null;
        }
    }
}

2.3 忽略路由前缀

在这里插入图片描述
注:
Zuul的路由功能中,会忽略路由匹配的路径前缀,不过我们的Controller中有 /upload 路径,此时如果通过网关访问,我们的地址应该是http://api.leyou.com/api/upload/upload/image, 这是因为路由匹配的前缀 /upload 在请求转发时会被自动忽略。

这样地址看起来非常臃肿,因此我们可以禁止忽略路由前缀,
在网关中application.yml中配置:
在这里插入图片描述
这样,路由前缀也会作为地址一部分转发到微服务,那么我们就可以http://api.leyou.com/api/upload/image 正常访问了

2.4 绕过网关缓存

默认情况下,所有的请求都经过Zuul网关的代理.

Zuul底层就是一个servlet,通常情况下zuul会将请求交给Spring Dispatch去处理,SpringMVC去控制路由。这种情况下,zuul就会去缓存这个请求。如果有直接通过zuul但是不需要缓存:比如大文件的上传服务,这时候就应该跳过SpringDispatch,在地址前加一个 /zuul.

普通请求并不会有什么影响,但是对于图片上传(文件传输),如果也经过Zuul网关的代理,文件就会经过多次网路传输,造成不必要的网络负担。在高并发时,可能导致网络阻塞,Zuul网关不可用,这样我们的整个系统就瘫痪了。

所以,我们上传文件的请求需要绕过请求的缓存,直接通过路由到达目标微服务。

页面请求路径:
在这里插入图片描述
由于不能修改页面请求地址,我们需要修改到以 /zuul为前缀(在 /api前加),可以通过Nginx的rewrite指令(用于对地址进行重写)实现这一需求。

server {
    listen       80;
    server_name  api.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;
	proxy_set_header Host $host;	   

	// 配置
	location /api/upload {			
		rewrite "^/(.*)$" /zuul/$1; 
	}
	
    location / {
		proxy_pass http://192.168.1.109:10010;
		proxy_connect_timeout 600;
		proxy_read_timeout 600;
    }
}

2.5 之前上传的缺陷

之前,我们把文件保存在服务器机器,就会有下面的问题:

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

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

3. FastDFS

3.1 FastDFS介绍

分布式文件系统:FastDFS

3.2 java客户端

3.1.1 引入依赖

在ly-upload的pom文件中添加:

<dependency>
    <groupId>com.github.tobato</groupId>
    <artifactId>fastdfs-client</artifactId>
</dependency>

3.1.2 引入配置类

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

3.1.3 编写FastDFS属性

在ly-upload的 application.yml 文件中添加:
在这里插入图片描述

3.3 改造上传逻辑

只需要把原来保存文件的逻辑去掉,然后上传到FastDFS即可。

@Service
@Slf4j
@EnableConfigurationProperties(UploadProperties.class)
public class UploadService {

    @Autowired
    private FastFileStorageClient storageClient;

    @Autowired
    private UploadProperties prop;
    
    public String uploadImage(MultipartFile file) {
        try {
            //校验文件类型
            String contentType = file.getContentType();
            if(!prop.getAllowTypes().contains(contentType)){
                throw new LyException(ExceptionEnum.INVALID_FILE_TYPE);
            }
            //校验文件内容
            BufferedImage image = ImageIO.read(file.getInputStream());
            if(image==null)
                throw new LyException(ExceptionEnum.INVALID_FILE_TYPE);
            //上传到FastDFS
            String extension = StringUtils.substringAfterLast(file.getOriginalFilename(),".");
            StorePath storePath = storageClient.uploadFile(file.getInputStream(), file.getSize(), extension, null);
            //返回路径
            return prop.getBaseUrl() + storePath.getFullPath();
        } catch (IOException e) {
            log.error("[文件上传] 上传文件失败",e);
            throw new LyException(ExceptionEnum.UPLOAD_FILE_ERROR);
        }
    }

}

在上述中,我们对文件的返回路径进行了抽取,如下:
在ly-upload的 application.yml 文件中添加:
同时,应当在ly-upload的 application.yml文件中添加:
在这里插入图片描述

3.4 导入图片到虚拟机

之前我们已经在数据库导入了数百条品牌及商品数据,不过这些数据中所需要的图片是无法访问的,需要我们把图片放到虚拟机对应的nginx服务器。(上传到Linux的 /leyou/static目录
然后修改linux中的nginx配置:)

vim /opt/nginx/config/nginx.conf

将以image开头的地址代理到本地路径:

  server {
        listen       80;
        server_name  image.taotao.com;
    
        # 监听域名中带有group的,交给FastDFS模块处理
        location ~/group([0-9])/ {
            ngx_fastdfs_module;
        }
    	# 其它图片都走本地目录
        location /images/ {
            root  /leyou/static;
        }
    
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值