(一)梳理思路
搭建项目结构
搭建前端环境,技术准备,git
前后端分离的思想,开发的流程,mybatisplus的入门
商品品牌和分类,以及crud实现、高级查询。分页查询
redis项目实战、页面静态化
图片文件的统一处理
(二)图片的文件处理
单体项目处理图片思路:
用户注册时,上传头像,
将图片上传到本地的盘符,数据库内保存图片的路径
问题1:(资源占用空间)
由于图片是存储在本地的,项目发布之后,图片和war包在同一个服务器,但是图片资源是非常占用资源的,因此占用了服务器的资源。随着图片占用空间的增加,服务器额资源越小。
问题2:(集群环境之下,资源可能访问呢不到)
如果在微服务架构之下,图片存放在一个服务器,因为负载均衡的作用,导致用户访问的是另一个服务器,但是另一个服务器就没有这个资源,用户就获取不到图片资源。
因此需要将图片进行统一处理,
微服务架构下的,分布式模块处理图片上传的思路:
① 撘一个分布式的文件服务器,具有分布式的效果,因为分布式快,因为是集群,就避免了单点故障(有备用服务器)。
② 服务器上面存放很多的图片,通过唯一标识进行区分(比如我们DB中的id)
③ 之后服务器会将标识返回,再将标识存放到数据库中。
④ 这样处理之后,用户在登录时,获取唯一标识,就可以到图片服务器上面去获取图片资源。
⑤ 前台直接就是通过路径(http协议+IP+port+标识)展示用户的图片资源。
-
购买服务器
阿里云、七牛云 -
搭建服务器
使用FastDFS:免费。功能OK
(三)Hello FastDFS
FacstDFs是C写的开源的文件分布式系统
-
FastDFs架构分为:(两大角色)
Tracker server 跟踪或调度服务器(对外)
实现集群,收集Stroage的信息
Storage server 存储服务器(真正实现存储)
分布很多组(group),每个组是不一样的,但是组内的数据时一致的,没有主从概念,都是公平的。 -
首先需要安装FastDFs服务器
需要保证IP地址的分配处理,修改两个配置文件的ip地址
启动服务,和基本的测试 -
通过java客户端,实现文件的上传和下载
① 导入 依赖maven打jar包:
在当前源码包路径下,进入cmd,输入命令:mvn clean package install
(注意需要在源码所在的文件夹内进行打包,否则会找不到文件异常)echo “%MAVEN_HOME%” 查看当前maven的环境变量
引入本地打的jar包
(四)搭建文件上传微服务
关于搭建文件上传系统,我也会在博客中记录,具体搭建思路,后续整理
创建FastDFs的接口以及回调函数(因为其他服务也会有图片文件上传的业务,所以将文件系统服务抽取到公共的接口中,其他服务就可以进行一个调用)
- 创建公共接口,并注册到Eurka注册中心
package cn.lzj.aigou.client;
import cn.lzj.aigou.util.AjaxResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
@FeignClient(value="COMMON-PROVIDER",fallback = FastDfsFallback.class)
public interface FastDfsClient {
//上传
@RequestMapping(value = "/common/upload",method = RequestMethod.POST)
AjaxResult upload(@RequestBody MultipartFile file);
//下载:页面直接使用http://ip/groupname/filename
//删除:直接获取前台传入的路径即可(前台传入的是存放在文件服务系统内的组名+文件唯一标识符)
@RequestMapping(value = "/common/delete",method = RequestMethod.GET)
AjaxResult delete(@RequestParam("filePath") String filePath);
}
- 创建接口的托底函数(当服务器出现问题时,将托底函数的内容返回给用户)
package cn.lzj.aigou.client;
import cn.lzj.aigou.util.AjaxResult;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
@Component
public class FastDfsFallback implements FastDfsClient{
@Override
public AjaxResult upload(MultipartFile file) {
return AjaxResult.me().setSuccess(false).setMsg("抱歉,服务器正忙,请稍后再试~");
}
@Override
public AjaxResult delete(String filePath) {
return AjaxResult.me().setSuccess(false).setMsg("抱歉,服务器正忙,请稍后再试~");
}
}
- 抽取文件上传工具类
package cn.lzj.aigou.common.util;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;
/**
* 微服务中文件上传的工具类
*/
public class FastDfsApiOpr {
/**
* fdfs_client.conf 为配置文件,内部配置的是文件上传系统所部署的位置(IP+port)
*/
public static String CONF_FILENAME = FastDfsApiOpr.class.getResource("/fdfs_client.conf").getFile();
/**
* 上传文件
* @param file
* @param extName
* @return
*/
public static String upload(byte[] file,String extName) {
try {
ClientGlobal.init(CONF_FILENAME); //加载配置文件
TrackerClient tracker = new TrackerClient();
TrackerServer trackerServer = tracker.getConnection();
StorageServer storageServer = null;
StorageClient storageClient = new StorageClient(trackerServer, storageServer);
NameValuePair nvp [] = new NameValuePair[]{
new NameValuePair("age", "18"),
new NameValuePair("sex", "male")
};
String fileIds[] = storageClient.upload_file(file,extName,nvp);
System.out.println(fileIds.length);
System.out.println("组名:" + fileIds[0]);
System.out.println("路径: " + fileIds[1]);
return "/"+fileIds[0]+"/"+fileIds[1];
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 下载文件
* @param groupName
* @param fileName
* @return
*/
public static byte[] download(String groupName,String fileName) {
try {
ClientGlobal.init(CONF_FILENAME);
TrackerClient tracker = new TrackerClient();
TrackerServer trackerServer = tracker.getConnection();
StorageServer storageServer = null;
StorageClient storageClient = new StorageClient(trackerServer, storageServer);
byte[] b = storageClient.download_file(groupName, fileName);
return b;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 删除文件
* @param groupName
* @param fileName
*/
public static int delete(String groupName,String fileName){
try {
ClientGlobal.init(CONF_FILENAME);
TrackerClient tracker = new TrackerClient();
TrackerServer trackerServer = tracker.getConnection();
StorageServer storageServer = null;
StorageClient storageClient = new StorageClient(trackerServer,
storageServer);
int i = storageClient.delete_file(groupName,fileName);
//因为底层源码使用的是0,作为判断数据是否正确删除
System.out.println( i==0 ? "删除成功" : "删除失败:"+i);
return i;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("删除异常,"+e.getMessage());
}
}
}
- 文件上传和下载访问的controller接口
package cn.lzj.aigou.common.controller;
import cn.lzj.aigou.client.FastDfsClient;
import cn.lzj.aigou.common.util.FastDfsApiOpr;
import cn.lzj.aigou.util.AjaxResult;
import org.apache.commons.io.FilenameUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@RestController //加上此注解才表示为一个controller层
@RequestMapping("/common")
public class FastDfsController implements FastDfsClient {
/**
* 文件上传
* @param file 注意,此处有坑,当心~~~~~在微服务中,使用文件的上传,必须使用MultipartFile接受前台所传入的参数
*/
@Override
@RequestMapping(value = "/upload",method = RequestMethod.POST)
public AjaxResult upload(@RequestBody MultipartFile file) {
try {
byte[] bytes = file.getBytes();
//原始名
String originalFilename = file.getOriginalFilename();
//后缀名
String extName = FilenameUtils.getExtension(originalFilename);
// "/"+fileIds[0]+"/"+fileIds[1];
String groupNameAndFileName = FastDfsApiOpr.upload(bytes, extName);
return AjaxResult.me().setSuccess(true).setMsg("亲,文件上传成功!").setObject(groupNameAndFileName);
} catch (IOException e) {
e.printStackTrace();
return AjaxResult.me().setSuccess(false).setMsg("亲,文件上传失败!"+e.getMessage());
}
}
@Override
@RequestMapping(value = "/delete",method = RequestMethod.GET)
public AjaxResult delete(String filePath) {
// filePath: http://ip/groupName/fileName
// /groupName/fileName groupName/fileName
String filePath1 =filePath.substring(1);
String groupName= filePath1.substring(0, filePath1.indexOf("/"));
String fileName=filePath1.substring(filePath1.indexOf("/")+1);
int delete = FastDfsApiOpr.delete(groupName, fileName);
if(delete==0){
return AjaxResult.me().setSuccess(true).setMsg("删除成功!!");
}else{
return AjaxResult.me().setSuccess(false).setMsg("删除失败!!");
}
}
}
此时文件上传的服务就搭建完毕,其他服务有上传文件的业务需求,直接通过fegin进行调用即可。
以下我以产品分类的品牌,上传品牌logo为例,调用文件上传服务进行文件的上传。
直接在产品分类的服务的入口,添加@EnableFeignClients(“cn.lzj.aigou.client”) ,包的路径就是,抽取的公共的文件上传接口的路径。