栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

基于centeros + docker +fastDFS+SpringBoot的 文件服务器搭建

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

基于centeros + docker +fastDFS+SpringBoot的 文件服务器搭建

centerOS 环境下安装docker
yum install -y docker-io #安装docker
service docker start #启动docker 
docker -v # 查看docker版本

service docker start可能会报The service command supports only basic LSB actions (start, stop, restart, try-restart, reload...... 我们换成systemctl start docker 即可

拉取fastDFS镜像

拉去镜像

docker pull qbanxiaoli/fastdfs

启动fastDFS服务。

其中ip为你服务器ip,WEB_PORT为fast DFS访问的端口;其他的不动

docker run -d --restart=always --privileged=true --net=host --name=fastdfs -e IP=**.160.234.** -e WEB_PORT=8091 -v ${HOME}/fastdfs:/var/local/fdfs qbanxiaoli/fastdfs

测试是否启动成功

如下console输出所示,如果看到URL,则代表启动成功,复制url,浏览器输入url,输出Hello FastDFS 即表示启动成功;需要注意的是:启动FastDFS时,我修改过WEB_PORT的端口,所以url地址需要加上我们设置的WEB_PORT端口,默认是80端口。

[root@install install]# docker exec -it fastdfs /bin/bash
bash-5.0# echo "Hello FastDFS!">index.html
bash-5.0# fdfs_test /etc/fdfs/client.conf upload index.html
This is FastDFS client test program v5.12

Copyright (C) 2008, Happy Fish / YuQing

FastDFS may be copied only under the terms of the GNU General
Public License V3, which may be found in the FastDFS source kit.
Please visit the FastDFS Home Page http://www.csource.org/ 
for more detail.

[2021-10-25 17:16:20] DEBUG - base_path=/var/local/fdfs/storage, connect_timeout=30, network_timeout=60, tracker_server_count=1, anti_steal_token=0, anti_steal_secret_key length=0, use_connection_pool=0, g_connection_pool_max_idle_time=3600s, use_storage_id=0, storage server id count: 0

tracker_query_storage_store_list_without_group: 
	server 1. group_name=, ip_addr=**.160.234.**, port=23000

group_name=group1, ip_addr=**.160.234.**, port=23000
storage_upload_by_filename
group_name=group1, remote_filename=M00/00/00/PaDqDGF2deSADjP6AAAAD30CMqM98.html
source ip address: **.160.234.**
file timestamp=2021-10-25 17:16:20
file size=15
file crc32=2097296035
example file url: http:/group1/M00/00/00/PaDqDGF2deSADjP6AAAAD30CMqM98.html
storage_upload_slave_by_filename
group_name=group1, remote_filename=M00/00/00/PaDqDGF2deSADjP6AAAAD30CMqM98_big.html
source ip address: **.160.234.**
file timestamp=2021-10-25 17:16:20
file size=15
file crc32=2097296035
example file url: http:/group1/M00/00/00/PaDqDGF2deSADjP6AAAAD30CMqM98_big.html
bash-5.0# 

至此,我们在center OS上用docker搭载FastDFS 服务至此已经结束;接下来我们利用springboot搭载后台接口

Springboot 集成FastDFS Client 后台 新建一个springboot项目,引入依赖
 
            org.springframework.boot
            spring-boot-starter
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
            org.springframework.boot
            spring-boot-devtools
            runtime
            true
        


        
        
            com.github.tobato
            fastdfs-client
            1.26.2
        


        
            org.springframework.boot
            spring-boot-starter-web
        

        
        
            cn.hutool
            hutool-all
            5.7.4
        

        
        
            org.projectlombok
            lombok
            true
        

        
        
            junit
            junit
            test
        
Application启动类上添加FastDFSConfig引入注解
@import(FdfsClientConfig.class)
@SpringBootApplication
public class FastDfsServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(FastDfsServerApplication.class, args);
    }

}

新增FastDfsClient类 添加@componet注解,

@component 目的是spring中声明这个类为bean交给IOC容器去管理。创建和依赖引入以及引用都交给IOC容器,

@Slf4j
@Component
public class FastDfsClient {
注入FastFileStorageClient 以及FdfsWebServer

依赖注入有三种方式,注解注入,也就是@autowired 或者@Resource;Set注入;构造器注入,spring推荐使用构造器注入,这可以在一定程度上解决依赖为空的问题。那么为什么那么多人都是用注解注入呢?方便呀!

 
    @Resource
    FastFileStorageClient fastFileStorageClient;

    
    @Resource
    FdfsWebServer fdfsWebServer;
增加上传和删除以及下载相关方法
    
    public String uploadFile(MultipartFile multipartFile) {
        try {
            StorePath storePath = fastFileStorageClient.uploadFile(multipartFile.getInputStream(), multipartFile.getSize(), FilenameUtils.getExtension(multipartFile.getOriginalFilename()), null);
            return storePath.getFullPath();
        } catch (IOException e) {
            log.error(e.getMessage());
            return null;
        }
    }

    
    public String uploadImageAndCrtThumbImage(MultipartFile multipartFile) {
        try {
            StorePath storePath = fastFileStorageClient.uploadImageAndCrtThumbImage(multipartFile.getInputStream(), multipartFile.getSize(), FilenameUtils.getExtension(multipartFile.getOriginalFilename()), null);
            return storePath.getFullPath();
        } catch (Exception e) {
            log.error(e.getMessage());
            return null;
        }
    }

    
    public String uploadFile(File file) {
        try {
            FileInputStream inputStream = new FileInputStream(file);
            StorePath storePath = fastFileStorageClient.uploadFile(inputStream, file.length(), FilenameUtils.getExtension(file.getName()), null);
            return storePath.getFullPath();
        } catch (Exception e) {
            log.error(e.getMessage());
            return null;
        }
    }

    
    public String uploadImageAndCrtThumbImage(File file) {
        try {
            FileInputStream inputStream = new FileInputStream(file);
            StorePath storePath = fastFileStorageClient.uploadImageAndCrtThumbImage(inputStream, file.length(), FilenameUtils.getExtension(file.getName()), null);
            return storePath.getFullPath();
        } catch (Exception e) {
            log.error(e.getMessage());
            return null;
        }
    }

    
    public String uploadFile(byte[] bytes, String fileExtension) {
        ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
        StorePath storePath = fastFileStorageClient.uploadFile(stream, bytes.length, fileExtension, null);
        return storePath.getFullPath();
    }

    
    public boolean downloadFile(String fileUrl, File file) {
        try {
            StorePath storePath = StorePath.parseFromUrl(fileUrl);
            byte[] bytes = fastFileStorageClient.downloadFile(storePath.getGroup(), storePath.getPath(), new DownloadByteArray());
            FileOutputStream stream = new FileOutputStream(file);
            stream.write(bytes);
        } catch (Exception e) {
            log.error(e.getMessage());
            return false;
        }
        return true;
    }

    
    public boolean deleteFile(String fileUrl) {
        if (ObjectUtil.isEmpty(fileUrl)) {
            return false;
        }
        try {
            StorePath storePath = StorePath.parseFromUrl(fileUrl);
            fastFileStorageClient.deleteFile(storePath.getGroup(), storePath.getPath().substring(4, storePath.getPath().length()));
        } catch (Exception e) {
            log.error(e.getMessage());
            return false;
        }
        return true;
    }

    
    public String getResAccessUrl(String path) {
        String url = fdfsWebServer.getWebServerUrl() + path;
        log.info("上传文件地址为:n" + url);
        return url;
    }



增加yml配置类相关配置
spring:
  redis:
    host: localhost
    port: 6379
    database: 0
    connect-timeout: 3000
    jedis:
      pool:
        max-idle: 8
        max-active: 8
        max-wait: 1000
  devtools:
    restart:
      enabled: true
      additional-paths: src/main/java
  main:
    allow-bean-definition-overriding: true
  servlet:
    multipart:
      max-file-size: 5MB
      max-request-size: 10MB

fdfs:
  so-timeout: 1500
  connect-timeout: 600
  pool:
    max-total: 153
    max-wait-millis: 100
  thumb-image:
    height: 150
    width: 150
  tracker-list:
    - **.160.234.**:22122
  web-server-url: http:/
@RestController
@RequestMapping(value = "/fastDfs")
@Slf4j
public class FastDfsController {

    @Resource
    FastDfsClient fastDfsClient;


    @RequestMapping(value = "/uploadImageAndCrtThumbImage", method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.OK)
    public Result uploadImageAndCrtThumbImage(MultipartFile file) {

        log.info("上传的文件:{}", file.getOriginalFilename());

        String url = fastDfsClient.uploadImageAndCrtThumbImage(file);
        log.info("返回的uri为:{}", url);
        String httpUrl = fastDfsClient.getResAccessUrl(url);

        Result result = new Result();
        result.setResult(httpUrl);
        result.success("上传成功");
        return result;
    }
}
附相关依赖

CommonConstant .java

package com.file.server.constant;

public interface CommonConstant {

    
    public static final Integer DEL_FLAG_1 = 1;

    public static final Integer DEL_FLAG_0 = 0;

    public static final Integer SC_INTERNAL_SERVER_ERROR_500 = 500;

    public static final Integer SC_OK_200 = 200;

    
    public static final Integer SC_JEECG_NO_AUTHZ = 510;

    
    public static final int TOKEN_EXPIRE_TIME = 3600; //3600秒即是一小时

    public static final String PREFIX_USER_TOKEN = "PREFIX_USER_TOKEN_";

    
    public static final Integer MENU_TYPE_0 = 0;

    
    public static final Integer MENU_TYPE_1 = 1;

    
    public static final Integer MENU_TYPE_2 = 2;

    
    public static final Integer USER_UNFREEZE = 1;

    public static final Integer USER_FREEZE = 2;

    
    public static String ACCESS_TOKEN = "Access-Token";

    
    public static final String LOGIN_USER_RULES_CACHE = "loginUser_cacheRules";

    
    public static String LOGIN_USER_CACHERULES_ROLE = "loginUser_cacheRules::Roles_";

    
    public static String LOGIN_USER_CACHERULES_PERMISSION = "loginUser_cacheRules::Permissions_";


    
    public static String CURRENT = "current";


    
    public static String SIZE = "size";
}

Result.java

package com.file.server.handler;

import com.file.server.constant.CommonConstant;
import lombok.Data;

import java.io.Serializable;


@Data
public class Result implements Serializable {

    private static final long serialVersionUID = 1L;

    
    private boolean success = true;

    
    private String message = "操作成功!";

    
    private Integer code = 0;

    
    private T result;

    
    private long timestamp = System.currentTimeMillis();

    public Result() {

    }

    public Result error500(String message) {
        this.message = message;
        this.code = CommonConstant.SC_INTERNAL_SERVER_ERROR_500;
        this.success = false;
        return this;
    }

    public Result success(String message) {
        this.message = message;
        this.code = CommonConstant.SC_OK_200;
        this.success = true;
        return this;
    }


    public static Result ok() {
        Result r = new Result();
        r.setSuccess(true);
        r.setCode(CommonConstant.SC_OK_200);
        r.setMessage("成功");
        return r;
    }

    public static Result ok(String msg) {
        Result r = new Result();
        r.setSuccess(true);
        r.setCode(CommonConstant.SC_OK_200);
        r.setMessage(msg);
        return r;
    }

    public static Result ok(Object data) {
        Result r = new Result();
        r.setSuccess(true);
        r.setCode(CommonConstant.SC_OK_200);
        r.setResult(data);
        return r;
    }

    public static Result error(String msg) {
        return error(CommonConstant.SC_INTERNAL_SERVER_ERROR_500, msg);
    }

    public static Result error(int code, String msg) {
        Result r = new Result();
        r.setCode(code);
        r.setMessage(msg);
        r.setSuccess(false);
        return r;
    }

    
    public static Result noauth(String msg) {
        return error(CommonConstant.SC_JEECG_NO_AUTHZ, msg);
    }
}

 
用postman测试一下接口 

测试deleteFile接口时,报错误码:2,错误信息:找不到节点或文件,这里需要更改一下删除接口的代码;

 public boolean deleteFile(String fileUrl) {
        if (ObjectUtil.isEmpty(fileUrl)) {
            return false;
        }
        try {
            StorePath storePath = StorePath.parseFromUrl(fileUrl);
            fastFileStorageClient.deleteFile(storePath.getGroup(), storePath.getPath().substring(4, storePath.getPath().length()));
        } catch (Exception e) {
            log.error(e.getMessage());
            return false;
        }
        return true;
    }

问题又来了,这个对外接口不太安全,没有校验,一旦接口泄露,那岂不是.无法无天了。接下来,我们对接口进行安全校验

添加access_token校验

由于这是一个文件上传服务,所以设计之初就是给其他的系统使用的,既然涉及多个系统,那么用户肯定有一个access_token存在redis中,那么思路来了,

  1. 首先前端上传文件时约定添加access_token
  2. 后端自定义各一个注解,然后利用aop给注解添加增强拦截
  3. 给需要拦截的Controller、Service添加该注解即可实现token验证,
自定义注解
package com.file.server.annotations;

import java.lang.annotation.*;


@Target({ElementType.METHOD,ElementType.TYPE})
@documented
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequire {

}

给自定义注解添加aop增强拦截
package com.file.server.handler;

import cn.hutool.core.map.MapUtil;
import com.file.server.constant.CommonConstant;
import com.file.server.exception.TokenInvalidException;
import com.file.server.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.Objects;


@Aspect
@Component
@Slf4j
public class LoginRequireAop {
    @Resource
    private RedisUtil redisUtil;

    
    @Pointcut("@annotation(com.file.server.annotations.LoginRequire)")
    void loginRequirePointCut() {
    }


    
    @Before("loginRequirePointCut()")
    void checkTokenAvailability(JoinPoint joinPoint) {

        log.info("---------------------befor-------------------");

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String access_token = request.getHeader(CommonConstant.ACCESS_TOKEN);
        //验证token的有效性,如果为null,则抛出NPE异常,便于GlobalException 来捕获到
        if (Objects.isNull(access_token)) {
            throw new NullPointerException();
        }

        //根据token 查询redis中是否有改用户的登陆信息,没有就拒绝请求
        Map userInfo = redisUtil.hmget(access_token);
        if (MapUtil.isEmpty(userInfo)) {
            throw new TokenInvalidException("token过期");
        }
        log.info("ACCESS_TOKEN pass");
    }


}

给需要拦截的Controller、Service添加注解,拦截token

@RestController
@RequestMapping(value = "/fastDfs")
@Slf4j
@LoginRequire
public class FastDfsController {

附GlobalException 异常统一处理以及自定义异常TokenInvalidException

package com.file.server.exception;

import com.file.server.handler.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.io.IOException;


@Slf4j
@RestControllerAdvice
public class GlobalException {
    
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Result handler(MethodArgumentNotValidException e) throws IOException {
        log.error("运行时异常:--------------", e);
        BindingResult bindingResult = e.getBindingResult();
        //这一步是把异常的信息最简化
        ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get();
        return Result.error(HttpStatus.BAD_REQUEST.value(), objectError.getDefaultMessage());
    }


    
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = IllegalArgumentException.class)
    public Result handler(IllegalArgumentException e) throws IOException {
        log.error("Assert异常:-------------->{}", e.getMessage());
        return Result.error(400, e.getMessage());
    }

    
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = TokenInvalidException.class)
    public Result handler(TokenInvalidException e) throws IOException {
        return Result.error(HttpStatus.BAD_REQUEST.value(), e.getMessage());
    }


    
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = NullPointerException.class)
    public Result handler(NullPointerException e) throws Exception {
        return Result.error(HttpStatus.BAD_REQUEST.value(), "参数异常");
    }
}

 
package com.file.server.exception;


public class TokenInvalidException extends RuntimeException {
    public TokenInvalidException(String e) {
        super(e);
    }
}


测试

postman中 的Header中增加access_token参数,因为这个token不存在,所以我们在aop中拦截掉了,返回无效token。

如果是正常存在的token,则会通过验证。

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号