人力资源后端项目_04-FastDFS分布式文件存储系统

一、租户入驻

1、前端

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EOBLlXxs-1587208096935)(C:\Users\solargen\AppData\Roaming\Typora\typora-user-images\image-20200331162838478.png)]

(1)租户入驻页面的跳转
  • 更改路由配置文件routes.js
  • 更改main.js修改前端的认证,放行/register
(2)表单
(3)套餐和租户类型的选择框

在这里插入图片描述

(4)表单验证

自定义验证规则

data() {
    var validateTel = (rule, value, callback) => {
        let reg = /^1[3,4,5,6,7,8,9][0-9]{9}$/;
        if (!reg.test(value)) {
            callback(new Error('请输入正确的手机号码'));
        } else {
            callback();
        }
    };
    var validateConfirmPassword = (rule, value, callback) => {
        if (value !== this.tenant.password) {
            callback(new Error('两次输入密码不一致!'));
        } else {
            callback();
        }
    };
    return {
        tenantRules:{
            companyName:[
                { required: true, message: '公司名称不能为空', trigger: 'blur' }
            ],
            companyNum:[
                { required: true, message: '公司电话不能为空', trigger: 'blur' },
                { validator: validateTel, trigger: 'blur' }
            ],
            username:[
                { required: true, message: '账号不能为空', trigger: 'blur' }
            ],
            password:[
                { required: true, message: '密码不能为空', trigger: 'blur' }
            ],
            confirmPassword:[
                { required: true, message: '请再次输入密码', trigger: 'blur' },
                { validator: validateConfirmPassword, trigger: 'blur' }

            ]
        }
    };
}
(5)发送请求

2、后端

(1)使用Vo封装请求参数
@Data
public class TenantVo {

    private String companyName;
    private String companyNum;
    private Long tenantType;
    private String address;
    private String logo;
    private String username;
    private String password;
    /**
     * 套餐
     */
    private Long meal;

}
(2)租户入驻的业务处理
 /**
     * 公司入驻
     * 思考:公司入驻的业务流程是什么样子的?要操作哪些表?先后顺序?
     *
     * t_tenant 租户表
     *
     * t_employee 存储租户的管理员账号信息
     *
     * t_tenant_meal 租户套餐表
     *
     * mybatis操作表不能级联操作,只能一张表一张表的操作
     *
     * @param tenantVo
     */
@Override
@Transient //同一个事物,要么全部成功,要么全部失败
public void register(TenantVo tenantVo) {

    //向租户表中插入数据  返回租户id
    Tenant tenant = new Tenant();
    BeanUtils.copyProperties(tenantVo,tenant);
    //初始化租户数据
    tenant.setRegisterTime(System.currentTimeMillis());
    tenant.setState(0);
    baseMapper.insert(tenant);//mybatisplus默认会返回新增数据的主键

    //向员工表中插入数据 - 租户管理员的用户和密码 - 返回员工id
    Employee employee = new Employee();
    BeanUtils.copyProperties(tenantVo,employee);
    //设置这个员工的租户id
    employee.setTenantId(tenant.getId());
    employeeMapper.insert(employee);


    //设置租户表中管理员的ID
    //为了防止修改的时候数据丢失,我们先查询出来,在更改
    tenant = baseMapper.selectById(tenant.getId());
    tenant.setAdminId(employee.getId());
    baseMapper.updateById(tenant);



    //最后修改租户-套餐中间表
    TenantMeal tenantMeal = new TenantMeal();
    tenantMeal.setTenantId(tenant.getId());
    tenantMeal.setMealId(tenantVo.getMeal());
    //计算一年之后
    long time = DateUtils.addYears(new Date(), 1).getTime();
    tenantMeal.setExpireDate(time);//失效日期
    tenantMealMapper.insert(tenantMeal);


}
(3)测试 - mybatisplus忽略字段
package io.niker.hrm.domain;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.Date;

/**
 * <p>
 * 
 * </p>
 *
 * @author lidong
 * @since 2020-03-30
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("t_tenant")
public class Tenant implements Serializable {

    private static final long serialVersionUID=1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    private Long tenantType;

    @TableField("companyName")
    private String companyName;

    @TableField("companyNum")
    private String companyNum;

    @TableField("registerTime")
    private Date registerTime;

    private Integer state;

    private String address;

    private String logo;

    private Long adminId;

    /**
     * 忽略的字段,生成增删改sql语句的时候,不生成这两个数据的sql
     * mybatis的多对一
     */
    @TableField(exist = false)
    private Employee admin;     // 租户管理员
    @TableField(exist = false)
    private TenantType type;    // 机构类型
}

三、分布式文件系统-FastDFS

1、为什么要使用分布式文件系统

(1)不使用分布式文件系统

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qRu8zjsw-1587208096944)(./fastdfs-传统的文件管理的弊端.png)]

(2)使用分布式文件系统

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dSpB9RmR-1587208096946)(./fastdfs-分布式文件系统.png)]

(3)小结

集群环境中必然要使用到分布式文件系统!!!

2、方案选择

(1)分布式文件系统的原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BEMs09q0-1587208096948)(分布式文件系统存储原理.png)]

(2)方案选择
  • 别人搭建好的(花钱)

    阿里云、七牛云

  • 自己搭建

    hdfs、fastdfs【选择】

3、fastdfs的架构原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lGcgkniV-1587208096953)(C:\Users\solargen\AppData\Roaming\Typora\typora-user-images\image-20200331164219518.png)]

Tracker:调度和负载均衡

Storager:存储数据的

(1)用户请求Tracker,Tracker调度Storage,将Storage的ip和端口返回给client

(2)clinet发送数据到这个Storage中

文件上传

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VOXAJBXw-1587208096955)(C:\Users\solargen\AppData\Roaming\Typora\typora-user-images\image-20200331164403177.png)]

文件下载

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kjRJpmfr-1587208096957)(C:\Users\solargen\AppData\Roaming\Typora\typora-user-images\image-20200331164411345.png)]

四、使用FastDFS

1、需求分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QfjB4HJy-1587208096959)(01_文件上传的流程分析.png)]在这里插入图片描述

2、后端服务搭建

在这里插入图片描述

(1)hrm-dfs-service

pom.xml

<dependencies>

    <dependency>
        <groupId>io.niker</groupId>
        <artifactId>hrm-dfs-common</artifactId>
        <version>${project.version}</version>
    </dependency>

    <!--web 的场景启动器-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--erueka的客户端-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    <!--配置中心客户端-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>

    <!--fastdfs的java客户端-->
    <dependency>
        <groupId>cn.bestwu</groupId>
        <artifactId>fastdfs-client-java</artifactId>
        <version>1.27</version>
    </dependency>

    <!--引入swagger支持-->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.9.2</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.9.2</version>
    </dependency>

</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>${spring-boot.version}</version>
            <configuration>
                <mainClass>io.niker.hrm.DFSApplication</mainClass>
            </configuration>
        </plugin>
    </plugins>
</build>

bootstrap.yml

#配置中心
spring:
  cloud:
    config:
      discovery:
        enabled: true
        service-id: CONFIG-SERVER
      name: application-dfs
      profile: dev

#eureka客户端配置
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

配置中心 application-dfs-dev.yml

server:
  port: 8001

#配置中心
spring:
  application:
    name: DFS-SERVICE

#eureka客户端配置
eureka:
  instance:
    instance-id: dfs-service:8001
    prefer-ip-address: false

swagger的集成

package io.niker.hrm.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * swagger的配置
 *  swagger的目的:(1)通过html的方式展示项目中所有的接口(2)提供接口测试
 */
@Configuration
@EnableSwagger2
public class Swagger2 {
 
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //对外暴露服务的包,以controller的方式暴露,所以就是controller的包.
                .apis(RequestHandlerSelectors.basePackage("io.niker.hrm.controller"))
                .paths(PathSelectors.any())
                .build();
    }


    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("文件服务api")
                .description("文件服务接口文档说明")
                .contact(new Contact("liodng", "", "lidong_java@io.niker"))
                .version("1.0")
                .build();
    }

}

启动类

(2)网关

网关的配置文件添加路由

server:
  port: 1299

spring:
  application:
    name: ZUUL-SERVICE


#测试一波

eureka:
  instance:
    instance-id: zuul-service:1299 #服务实例的标识
    prefer-ip-address: true #以ip注册

zuul:
  ignored-services: "*"
  prefix: /services
#路由配置
  routes:
    system:
      path: /system/**
      serviceId: HRM-SYSTEM
    file:
      path: /file/**
      serviceId: DFS-SERVICE

网关的swagger配置

DocumentationConfig.java

package io.niker.hrm.util;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;

import java.util.ArrayList;
import java.util.List;

@Component
@Primary
public class DocumentationConfig implements SwaggerResourcesProvider {
    @Override
    public List<SwaggerResource> get() {
        List resources = new ArrayList<>();
        resources.add(swaggerResource("资源管理", "/services/system/v2/api-docs", "2.0"));
        resources.add(swaggerResource("文件管理", "/services/file/v2/api-docs", "2.0"));
        resources.add(swaggerResource("课程管理", "/services/course/v2/api-docs", "2.0"));
        resources.add(swaggerResource("页面静态化管理", "/services/page/v2/api-docs", "2.0"));
        resources.add(swaggerResource("Es文档搜索管理", "/services/es/v2/api-docs", "2.0"));
        resources.add(swaggerResource("缓存管理", "/services/cache/v2/api-docs", "2.0"));
        resources.add(swaggerResource("用户管理", "/services/user/v2/api-docs", "2.0"));
        resources.add(swaggerResource("短信管理", "/services/sms/v2/api-docs", "2.0"));
        resources.add(swaggerResource("系统管理", "/services/system/v2/api-docs", "2.0"));
        return resources;
    }

    private SwaggerResource swaggerResource(String name, String location, String version) {
        SwaggerResource swaggerResource = new SwaggerResource();
        swaggerResource.setName(name);
        swaggerResource.setLocation(location);
        swaggerResource.setSwaggerVersion(version);
        return swaggerResource;
    }
}

3、上传与删除的接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gZ064aKd-1587208096967)(C:\Users\solargen\AppData\Roaming\Typora\typora-user-images\image-20200331165114791.png)]

(1)添加工具类
package io.niker.hrm.util;

import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;

public class FastDfsApiOpr {
     
    public static String CONF_FILENAME  = FastDfsApiOpr.class.getClassLoader()
            .getResource("fast_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 extName
     * @return
     */
    public static  String upload(String path,String extName) {
 
        try { 
            ClientGlobal.init(CONF_FILENAME);
 
            TrackerClient tracker = new TrackerClient(); 
            TrackerServer trackerServer = tracker.getConnection(); 
            StorageServer storageServer = null;
            StorageClient storageClient = new StorageClient(trackerServer, storageServer);
            String fileIds[] = storageClient.upload_file(path, extName,null);
             
            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;
        } 
    }
     
//    @Test
//    public void testGetFileInfo(){
//        try {
//            ClientGlobal.init(conf_filename);
//
//            TrackerClient tracker = new TrackerClient();
//            TrackerServer trackerServer = tracker.getConnection();
//            StorageServer storageServer = null;
//
//            StorageClient storageClient = new StorageClient(trackerServer, storageServer);
//            FileInfo fi = storageClient.get_file_info("group1", "M00/00/00/wKgRcFV_08OAK_KCAAAA5fm_sy874.conf");
//            System.out.println(fi.getSourceIpAddr());
//            System.out.println(fi.getFileSize());
//            System.out.println(fi.getCreateTimestamp());
//            System.out.println(fi.getCrc32());
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//    }
     
//    @Test
//    public void testGetFileMate(){
//        try {
//            ClientGlobal.init(conf_filename);
//
//            TrackerClient tracker = new TrackerClient();
//            TrackerServer trackerServer = tracker.getConnection();
//            StorageServer storageServer = null;
//
//            StorageClient storageClient = new StorageClient(trackerServer,
//                    storageServer);
//            NameValuePair nvps [] = storageClient.get_metadata("group1", "M00/00/00/wKgRcFV_08OAK_KCAAAA5fm_sy874.conf");
//            for(NameValuePair nvp : nvps){
//                System.out.println(nvp.getName() + ":" + nvp.getValue());
//            }
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//    }

    /**
     * 删除文件
     * @param groupName
     * @param fileName
     */
    public static void 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);
            System.out.println( i==0 ? "删除成功" : "删除失败:"+i);
        } catch (Exception e) {
            e.printStackTrace();
            throw  new RuntimeException("删除异常,"+e.getMessage());
        } 
    }
}
(2)添加fastdfs的配置文件

fast_client.conf

tracker_server=FastDFS分布式文件系统的 ip地址:端口号
(3)接口
package io.niker.hrm.controller;

import io.niker.basic.util.AjaxResult;
import io.niker.hrm.util.FastDfsApiOpr;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@RestController
public class FileController {

    /**
     * 文件上传
     * @param file
     * @return
     */
    @PostMapping("/upload")
    public AjaxResult upload(MultipartFile file){

        try {
            byte[] bytes = file.getBytes();//上传的文件内容

            //美女.png    extName="png"
            //文件名
            String filename = file.getOriginalFilename();
            //获取扩展名
            int index = filename.lastIndexOf(".");
            String extName = filename.substring(index+1);
            String fileId = FastDfsApiOpr.upload(bytes, extName);
            //要将fastdfs返回的file标识响应给前端
            return AjaxResult.me().setSuccess(true).setMessage("上传成功!").setResultObj(fileId);
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.me().setSuccess(false).setMessage("上传失败!");
        }

    }

    /**
     * 文件删除
     * 参数: /group1/M00/00/00/rBEAC16C9IKAQ6E3AAI8-cLFXhc547.png
     */
    @GetMapping("/delete")
    public AjaxResult delete(String fileId){

        try {
            String groupName = null;//    group1
            String fileName = null;//     M00/00/00/rBEAC16C9IKAQ6E3AAI8-cLFXhc547.png

            fileId = fileId.substring(1);// fileId = group1/M00/00/00/rBEAC16C9IKAQ6E3AAI8-cLFXhc547.png
            int index = fileId.indexOf("/");
            groupName = fileId.substring(0,index);
            fileName = fileId.substring(index+1);

            FastDfsApiOpr.delete(groupName,fileName);
            return AjaxResult.me().setSuccess(true).setMessage("删除成功!");
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.me().setSuccess(false).setMessage("删除失败!"+e.getMessage());
        }
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值