我们需要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>在上传文件过程中,我们需要对上传的内容进行校验:
校验文件大小校验文件的媒体类型校验文件的内容文件大小在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; } } }注: Zuul的路由功能中,会忽略路由匹配的路径前缀,不过我们的Controller中有 /upload 路径,此时如果通过网关访问,我们的地址应该是http://api.leyou.com/api/upload/upload/image, 这是因为路由匹配的前缀 /upload 在请求转发时会被自动忽略。
这样地址看起来非常臃肿,因此我们可以禁止忽略路由前缀, 在网关中application.yml中配置: 这样,路由前缀也会作为地址一部分转发到微服务,那么我们就可以http://api.leyou.com/api/upload/image 正常访问了
默认情况下,所有的请求都经过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; } }之前,我们把文件保存在服务器机器,就会有下面的问题:
单机器存储,存储能力有限无法进行水平扩展,因为多台机器的文件无法共享,会出现访问不到的情况数据没有备份,有单点故障风险并发能力差这个时候,最好使用分布式文件存储来代替本地文件存储。
分布式文件系统:FastDFS
在ly-upload的pom文件中添加:
<dependency> <groupId>com.github.tobato</groupId> <artifactId>fastdfs-client</artifactId> </dependency>在ly-upload的 application.yml 文件中添加:
只需要把原来保存文件的逻辑去掉,然后上传到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文件中添加:
之前我们已经在数据库导入了数百条品牌及商品数据,不过这些数据中所需要的图片是无法访问的,需要我们把图片放到虚拟机对应的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; } }