SAAS-HRM-day4(FastDFS)

1. 圖片統(tǒng)一處理

1.1 問(wèn)題發(fā)現(xiàn)和解決方案

  1. 由于我這是微服務(wù)項(xiàng)目肚菠,所以存儲(chǔ)圖片不能像以前那個(gè)樣子劲装,所以我在這里要統(tǒng)一處理圖片,所以要使用分布式文件系統(tǒng)。
  2. 統(tǒng)一處理圖片的方案有兩種:
    • 1)租用別人的:阿里云對(duì)象存儲(chǔ)或七牛云
    • 2)自己搭建:hdfs寺枉,F(xiàn)astDfs等
  3. 我這里使用自己搭建FastDfs文件系統(tǒng)

1.2 FastDFS

1.2.1 Fastdfs介紹

  1. FastDFS 是用 c語(yǔ)言編寫的一款開(kāi)源的分布式文件系統(tǒng)。FastDFS 為互聯(lián)網(wǎng)量身定制,充分考慮了冗余備份缸托、負(fù)載均衡、線性擴(kuò)容等機(jī)制瘾蛋,并注重高可用俐镐、高性能等指標(biāo),使用 FastDFS很容易搭建一套高性能的文件服務(wù)器集群提供文件上傳哺哼、下載等服務(wù)佩抹。
  2. FastDFS 架構(gòu)包括 Tracker server 和 Storage server∪《客戶端請(qǐng)求 Tracker server 進(jìn)行文件上傳棍苹、下載,通過(guò) Tracker server 調(diào)度最終由 Storage server 完成文件上傳和下載茵汰。
  3. Tracker server 作用是負(fù)載均衡和調(diào)度枢里,通過(guò) Tracker server 在文件上傳時(shí)可以根據(jù)一些策略找到 Storage server 提供文件上傳服務(wù)□逦纾可以將 tracker 稱為追蹤服務(wù)器或調(diào)度服務(wù)器栏豺。
  4. Storage server作用是文件存儲(chǔ),客戶端上傳的文件最終存儲(chǔ)在 Storage服務(wù)器上豆胸,Storageserver沒(méi)有實(shí)現(xiàn)自己的文件系統(tǒng)而是利用操作系統(tǒng)的文件系統(tǒng)來(lái)管理文件奥洼。可以將storage稱為存儲(chǔ)服務(wù)器配乱。
  5. 服務(wù)端的兩個(gè)角色:
    • Tracker:管理集群溉卓,tracker 也可以實(shí)現(xiàn)集群皮迟。每個(gè) tracker 節(jié)點(diǎn)地位平等。收集 Storage 集群的狀態(tài)桑寨。
    • Storage:實(shí)際保存文件 Storage 分為多個(gè)組伏尼,每個(gè)組之間保存的文件是不同的。每個(gè)組內(nèi)部可以有多個(gè)成員尉尾,組成員內(nèi)部保存的內(nèi)容是一樣的爆阶,組成員的地位是一致的,沒(méi)有主從的概念

參考:

1.2.2 Fastdfs上傳下載流程

客戶端上傳文件后存儲(chǔ)服務(wù)器將文件 ID 返回給客戶端沙咏,此文件 ID 用于以后訪問(wèn)該文件的索引信息辨图。文件索引信息包括:組名,虛擬磁盤路徑肢藐,數(shù)據(jù)兩級(jí)目錄故河,文件名。

  1. 組名文件上傳后所在的 storage 組名稱吆豹,在文件上傳成功后有 storage 服務(wù)器返回鱼的,需要客戶端自行保存。
  2. 虛擬磁盤路徑:storage 配置的虛擬路徑痘煤,與磁盤選項(xiàng) store_path*對(duì)應(yīng)凑阶。如果配置了store_path0則是M00,如果配置了 store_path1 則是 M01衷快,以此類推宙橱。
  3. 數(shù)據(jù)兩級(jí)目錄:storage服務(wù)器在每個(gè)虛擬磁盤路徑下創(chuàng)建的兩級(jí)目錄,用于存儲(chǔ)數(shù)據(jù)文件蘸拔。
  4. 文件名:與文件上傳時(shí)不同师郑。是由存儲(chǔ)服務(wù)器根據(jù)特定信息生成,文件名包含:源存儲(chǔ)服務(wù)器IP地址都伪、文件創(chuàng)建時(shí)間戳呕乎、文件大小、隨機(jī)數(shù)和文件拓展名等信息陨晶。
  5. 下載流程圖
    [圖片上傳失敗...(image-ce0149-1569802914948)]

1.2.3 簡(jiǎn)單的FastDFS架構(gòu)

[圖片上傳失敗...(image-ea69bf-1569802914948)]

1.2.4 Fastdfs搭建

運(yùn)維人員已經(jīng)搭建完畢!

簡(jiǎn)單命令

//啟動(dòng)兩個(gè)FastDFS的服務(wù)
service fdfs_trackerd start
service fdfs_storaged start
//
/usr/bin/fdfs_monitor /etc/fdfs/storage.conf
//提供web服務(wù)
/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
//上傳一個(gè)進(jìn)行測(cè)試
fdfs_test /etc/fdfs/client.conf upload /root/install.log
//檢查防火墻狀態(tài)和關(guān)閉防火墻帝璧。如果訪問(wèn)不了上傳的文件先誉,應(yīng)該就是防火墻的問(wèn)題
service iptables stop/status

1.2.5 入門

需求:將本地圖片上傳至圖片服務(wù)器,再控制臺(tái)打印url

1.2.5.1 步驟分析

  1. 創(chuàng)建Maven工程FastDFSTest
  2. 導(dǎo)包(FastDFS所需要的)
  3. 配置文件
  4. 創(chuàng)建java類
  5. 測(cè)試上傳
  6. 測(cè)試瀏覽
  7. 下載和刪除

1.2.5.2 步驟實(shí)現(xiàn)

  1. 創(chuàng)建Maven工程FastDFSTest
  2. 導(dǎo)包(FastDFS所需要的)

由于FastDFS客戶端jar包并沒(méi)有在中央倉(cāng)庫(kù)中的烁,所以需要使用下列命令手動(dòng)安裝jar包到Maven本地倉(cāng)庫(kù)褐耳。

安裝jar包到本地倉(cāng)庫(kù)命令:

mvn install:install-file -DgroupId=組名 -DartifactId=包名 -Dversion=版本 -Dpackaging=jar -Dfile=jar所在磁盤的絕對(duì)路徑
//下面舉例:
mvn install:install-file -DgroupId=org.csource -DartifactId=fastdfs-client -Dversion=1.2 -Dpackaging=jar -Dfile=D:\Java\resource\fastdfs-1.2.jar
        <dependency>
            <groupId>org.csource</groupId>
            <artifactId>fastdfs-client</artifactId>
            <version>1.2</version>
        </dependency>
  1. 配置文件
    在resources下創(chuàng)建個(gè)文件,名字為fdfs_client.conf渴庆,在里面配置對(duì)應(yīng)的服務(wù)器ip和端口铃芦。
tracker_server=172.16.6.63:22122
  1. 創(chuàng)建java類雅镊,用于測(cè)試
package cn.wangningbo;

import org.csource.fastdfs.*;

public class FastdfsTest {
    public static void main(String[] args) throws Exception {
        // 1、加載配置文件刃滓,配置文件中的內(nèi)容就是 tracker 服務(wù)的地址仁烹。
        ClientGlobal.init("D:/maven_work/fastDFS-demo/src/fdfs_client.conf");
        // 2、創(chuàng)建一個(gè) TrackerClient 對(duì)象咧虎。直接 new 一個(gè)卓缰。
        TrackerClient trackerClient = new TrackerClient();
        // 3、使用 TrackerClient 對(duì)象創(chuàng)建連接砰诵,獲得一個(gè) TrackerServer 對(duì)象征唬。
        TrackerServer trackerServer = trackerClient.getConnection();
        // 4、創(chuàng)建一個(gè) StorageServer 的引用茁彭,值為 null
        StorageServer storageServer = null;
        // 5总寒、創(chuàng)建一個(gè) StorageClient 對(duì)象,需要兩個(gè)參數(shù) TrackerServer 對(duì)象理肺、StorageServer 的引用
        StorageClient storageClient = new StorageClient(trackerServer, storageServer);
        // 6偿乖、使用 StorageClient 對(duì)象上傳圖片。
        //擴(kuò)展名不帶“.”
        String[] strings = storageClient.upload_file("D:/pic/benchi.jpg", "jpg",
                null);
        // 7哲嘲、返回?cái)?shù)組贪薪。包含組名和圖片的路徑。
        for (String string : strings) {
            System.out.println(string);
        }
    }
}
  1. 測(cè)試上傳
    在main方法里面指定配置文件路徑和圖片路徑眠副,即可完成上傳

運(yùn)行main方法画切,控制臺(tái)輸出結(jié)果

group1
M00/00/02/rBAGP11vi8SAcE4NAAAv6VpXil8827.png
  1. 測(cè)試上傳結(jié)果瀏覽

瀏覽器訪問(wèn):http://172.16.6.63/group1/M00/00/02/rBAGP11vi8SAcE4NAAAv6VpXil8827.png

顯示圖片,即可算作上傳成功

  1. 下載和刪除

1.3 后臺(tái)上傳服務(wù)

實(shí)現(xiàn)上傳囱怕、下載霍弹、刪除!

1.3.1 模塊結(jié)構(gòu)

在二級(jí)子模塊hrm_basic_parent下創(chuàng)建三級(jí)子模塊hrm_basic_fastdfs_interface和hrm_basic_fastdfs_service娃弓,名字為用來(lái)做圖片統(tǒng)一處理典格。

1.3.2 interface配置

1.3.2.1 步驟分析

  1. 導(dǎo)包
  2. client準(zhǔn)備

1.3.2.2 步驟實(shí)現(xiàn)

  1. 導(dǎo)包
        <dependency>
            <groupId>cn.wangningbo.hrm</groupId>
            <artifactId>hrm_basic_util</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--不能直接依賴starter,有自動(dòng)配置台丛,而消費(fèi)者是不需要額耍缴。-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--客戶端feign支持-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
  1. client客戶端準(zhǔn)備

FastDFSClient

package cn.wangningbo.hrm.client;

import cn.wangningbo.hrm.util.AjaxResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
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;

/**
 * @author wangningbo
 * @since 2019-09-04
 */
@FeignClient(value = "HRM-FASTDFS", configuration = FeignClientsConfiguration.class,
        fallbackFactory = FastDFSClientHystrixFallbackFactory.class)
@RequestMapping("/fastdfs")
public interface FastDFSClient {
    /**
     * 上傳
     *
     * @param file
     * @return
     */
    @RequestMapping(value = "/upload", method = RequestMethod.POST)
    String upload(@RequestBody MultipartFile file);

    /**
     * 刪除
     *
     * @param path
     * @return
     */
    @RequestMapping(value = "/delete", method = RequestMethod.DELETE)
    AjaxResult delete(@RequestParam("path") String path);

    /**
     * 下載
     *
     * @param path
     */
    @RequestMapping(value = "/download", method = RequestMethod.GET)
    void download(@RequestParam("path") String path);//直接把流寫到response

}

FastDFSClientHystrixFallbackFactory

package cn.wangningbo.hrm.client;

import cn.wangningbo.hrm.util.AjaxResult;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

@Component
public class FastDFSClientHystrixFallbackFactory implements FallbackFactory<FastDFSClient> {
    @Override
    public FastDFSClient create(Throwable throwable) {
        return new FastDFSClient() {
            @Override
            public String upload(MultipartFile file) {
                return null;
            }

            @Override
            public AjaxResult delete(String path) {
                return null;
            }

            @Override
            public void download(String path) {

            }
        };
    }
}

1.3.3 service配置

1.3.3.1 步驟分析

  1. 導(dǎo)包
  2. 配置文件
  3. 入口類
  4. 日志
  5. 實(shí)現(xiàn)服務(wù)
    • (1)配置文件和工具類準(zhǔn)備
    • (2)實(shí)現(xiàn)服務(wù)
  6. zuul配置路由
  7. swagger配置
    • (1)本項(xiàng)目配置swagger
    • (2)網(wǎng)關(guān)配置swagger
  8. 測(cè)試swagger
    • (1)swagger本項(xiàng)目測(cè)試
    • (2)swagger網(wǎng)關(guān)測(cè)試
  9. 測(cè)試上傳和刪除(postman)

1.3.3.2 步驟實(shí)現(xiàn)

  1. 導(dǎo)包
        <dependency>
            <groupId>cn.wangningbo.hrm</groupId>
            <artifactId>hrm_basic_fastdfs_interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--web場(chǎng)景-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--測(cè)試場(chǎng)景-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Eureka 客戶端依賴 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </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>
        <!--配置中心支持-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <!--fastdfs包,自己安裝-->
        <dependency>
            <groupId>org.csource</groupId>
            <artifactId>fastdfs-client</artifactId>
            <version>1.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>
  1. 配置文件application.yml
server:
  port: 9003 # 服務(wù)的端口
spring:
  application:
    name: hrm-fastdfs # 應(yīng)用的名字
eureka:
  client: # 這個(gè)服務(wù)是eureka的客戶端
    service-url: # eureka服務(wù)端的url
      defaultZone: http://localhost:7001/eureka # 注冊(cè)到eureka服務(wù)端,服務(wù)端的地址
  instance:
    prefer-ip-address: true # 顯示ip地址
  1. 入口類
package cn.wangningbo.hrm;

import cn.wangningbo.hrm.client.FastDFSClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class FastDFS9003Application {
    public static void main(String[] args) {
        SpringApplication.run(FastDFS9003Application.class, args);
    }
}
  1. 日志集成

在resources下新建一個(gè)logback的配置文件挽霉,名字為logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
scan:當(dāng)此屬性設(shè)置為true時(shí)防嗡,配置文件如果發(fā)生改變,將會(huì)被重新加載侠坎,默認(rèn)值為true蚁趁。
scanPeriod:設(shè)置監(jiān)測(cè)配置文件是否有修改的時(shí)間間隔,如果沒(méi)有給出時(shí)間單位实胸,默認(rèn)單位是毫秒當(dāng)scan為true時(shí)他嫡,此屬性生效番官。默認(rèn)的時(shí)間間隔為1分鐘。
debug:當(dāng)此屬性設(shè)置為true時(shí)钢属,將打印出logback內(nèi)部日志信息徘熔,實(shí)時(shí)查看logback運(yùn)行狀態(tài)。默認(rèn)值為false署咽。
-->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
    <!-- 定義日志的根目錄 -->
    <property name="LOG_HOME" value="/hrm/" />
    <!-- 定義日志文件名稱 -->
    <property name="appName" value="hrm-fastdfs"></property>
    <!-- ch.qos.logback.core.ConsoleAppender 表示控制臺(tái)輸出 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <!--
        日志輸出格式:
            %d表示日期時(shí)間近顷,
            %thread表示線程名,
            %-5level:級(jí)別從左顯示5個(gè)字符寬度
            %logger{50} 表示logger名字最長(zhǎng)50個(gè)字符宁否,否則按照句點(diǎn)分割窒升。 
            %msg:日志消息,
            %n是換行符
        -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </layout>
    </appender>

    <!-- 滾動(dòng)記錄文件慕匠,先將日志記錄到指定文件饱须,當(dāng)符合某個(gè)條件時(shí),將日志記錄到其他文件 -->  
    <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 指定日志文件的名稱
           /hrm/hrm-course/hrm-course.log
        -->
        <file>${LOG_HOME}/${appName}/${appName}.log</file>
        <!--
        當(dāng)發(fā)生滾動(dòng)時(shí)台谊,決定 RollingFileAppender 的行為蓉媳,涉及文件移動(dòng)和重命名
        TimeBasedRollingPolicy: 最常用的滾動(dòng)策略,它根據(jù)時(shí)間來(lái)制定滾動(dòng)策略锅铅,既負(fù)責(zé)滾動(dòng)也負(fù)責(zé)出發(fā)滾動(dòng)酪呻。
        -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--
            滾動(dòng)時(shí)產(chǎn)生的文件的存放位置及文件名稱 %d{yyyy-MM-dd}:按天進(jìn)行日志滾動(dòng) 
            %i:當(dāng)文件大小超過(guò)maxFileSize時(shí),按照i進(jìn)行文件滾動(dòng)
            -->
            <fileNamePattern>${LOG_HOME}/${appName}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
            <!-- 
            可選節(jié)點(diǎn)盐须,控制保留的歸檔文件的最大數(shù)量玩荠,超出數(shù)量就刪除舊文件。假設(shè)設(shè)置每天滾動(dòng)贼邓,
            且maxHistory是365阶冈,則只保存最近365天的文件,刪除之前的舊文件塑径。注意女坑,刪除舊文件是,
            那些為了歸檔而創(chuàng)建的目錄也會(huì)被刪除统舀。
            -->
            <MaxHistory>365</MaxHistory>
            <!-- 
            當(dāng)日志文件超過(guò)maxFileSize指定的大小是匆骗,根據(jù)上面提到的%i進(jìn)行日志文件滾動(dòng) 注意此處配置SizeBasedTriggeringPolicy是無(wú)法實(shí)現(xiàn)按文件大小進(jìn)行滾動(dòng)的,必須配置timeBasedFileNamingAndTriggeringPolicy
            -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 日志輸出格式: -->     
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern>
        </layout>
    </appender>

    <!-- 
        logger主要用于存放日志對(duì)象绑咱,也可以定義日志類型绰筛、級(jí)別
        name:表示匹配的logger類型前綴,也就是包的前半部分
        level:要記錄的日志級(jí)別描融,包括 TRACE < DEBUG < INFO < WARN < ERROR
        additivity:作用在于children-logger是否使用 rootLogger配置的appender進(jìn)行輸出,
        false:表示只用當(dāng)前l(fā)ogger的appender-ref衡蚂,true:
        表示當(dāng)前l(fā)ogger的appender-ref和rootLogger的appender-ref都有效
    -->
    <!-- hibernate logger -->
    <logger name="cn.wangningbo" level="debug" />
    <!-- Spring framework logger -->
    <logger name="org.springframework" level="debug" additivity="false"></logger>



    <!-- 
    root與logger是父子關(guān)系窿克,沒(méi)有特別定義則默認(rèn)為root骏庸,任何一個(gè)類只會(huì)和一個(gè)logger對(duì)應(yīng),
    要么是定義的logger年叮,要么是root具被,判斷的關(guān)鍵在于找到這個(gè)logger,然后判斷這個(gè)logger的appender和level只损。 
    -->
    <root level="info">
        <appender-ref ref="stdout" />
        <appender-ref ref="appLogAppender" />
    </root>
</configuration> 
  1. 實(shí)現(xiàn)服務(wù)
  • (1)配置文件和工具類準(zhǔn)備

配置文件fdfs_client.conf

tracker_server=172.16.6.63:22122

工具類

package cn.wangningbo.hrm.util;

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

public class FastDfsApiOpr {

    //從classpath
    public static String CONF_FILENAME = FastDfsApiOpr.class.getClassLoader()
            .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 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;
        }
    }

    /**
     * 刪除文件
     *
     * @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)實(shí)現(xiàn)服務(wù)
package cn.wangningbo.hrm.controller;

import cn.wangningbo.hrm.util.AjaxResult;
import cn.wangningbo.hrm.util.FastDfsApiOpr;
import com.sun.xml.internal.messaging.saaj.util.ByteInputStream;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import sun.nio.ch.IOUtil;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

@RestController
@RequestMapping("/fastdfs")
public class FastDFSController {
    // 使用slf4j的logger記錄錯(cuò)誤到文件里 在每處報(bào)錯(cuò)的地方使用error方法記錄錯(cuò)誤
    Logger logger = LoggerFactory.getLogger(FastDFSController.class);

    /**
     * 上傳
     *
     * @param file
     * @return
     */
    @PostMapping("/upload")
    public String upload(@RequestParam("file") MultipartFile file) {
        try {
            //拿到上傳的文件名
            String fileName = file.getOriginalFilename();
            //獲取文件的后綴名
            String extName = fileName.substring(fileName.lastIndexOf(".") + 1);
            System.out.println(extName);
            //上傳文件 文件的字節(jié)數(shù)組和后綴名
            return FastDfsApiOpr.upload(file.getBytes(), extName);
        } catch (Exception e) {
            e.printStackTrace();
            //記錄錯(cuò)誤到日志文件
            logger.error("upload_error..." + e.getMessage());
        }
        return null;
    }

    /**
     * 刪除
     *
     * @param path
     * @return
     */
    @DeleteMapping("/delete")
    public AjaxResult delete(@RequestParam("path") String path) {
        //假設(shè)前臺(tái)傳過(guò)來(lái)是/group1/xxx
        try {
            //獲取相對(duì)路徑    // 去掉路徑的第1個(gè)字符 //拿到的就是group1/xxx //
            String pathTemp = path.substring(1);
            // 獲取組名     //以/進(jìn)行分割一姿,拿到分割后的第一塊
            String groupName = pathTemp.substring(0, pathTemp.indexOf("/"));
            //indexOf("/)返回"/"在字符串中第一次出現(xiàn)處的索引
            //substring(數(shù)字n)去掉pathTemp這個(gè)字符串前面的n個(gè)字符
            String remotePath = pathTemp.substring(pathTemp.indexOf("/") + 1);
            System.out.println(groupName);
            System.out.println(remotePath);
            FastDfsApiOpr.delete(groupName, remotePath);
            return AjaxResult.me();
        } catch (Exception e) {
            e.printStackTrace();
            //記錄錯(cuò)誤到日志文件
            logger.error("delete_error..." + e.getMessage());
            return AjaxResult.me().setSuccess(false).setMessage(e.getMessage());
        }
    }

    /**
     * 下載
     *
     * @param path
     * @param response
     */
    @GetMapping("/download")
    public void download(@RequestParam("path") String path, HttpServletResponse response) {
        //獲取相對(duì)路徑    // 去掉路徑的第1個(gè)字符 //拿到的就是group1/xxx //
        String pathTemp = path.substring(1);
        //獲取第一個(gè)"/"之前的字符 //獲取組名
        String groupName = pathTemp.substring(0, pathTemp.indexOf("/"));
        //indexOf("/)返回"/"在字符串中第一次出現(xiàn)處的索引
        //substring(數(shù)字n)去掉pathTemp這個(gè)字符串前面的n個(gè)字符
        String remotePath = pathTemp.substring(pathTemp.indexOf("/") + 1);
        System.out.println(groupName);
        System.out.println(remotePath);
        //注意關(guān)流
        OutputStream os = null;
        InputStream is = null;
        try {
            byte[] datas = FastDfsApiOpr.download(groupName, remotePath);
            os = response.getOutputStream();//直接以流的方式返回
            is = new ByteInputStream(datas, datas.length);
            IOUtils.copy(is, os);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("download_error..." + e.getMessage());
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  1. zuul配置路由
fastdfs.serviceId: hrm-fastdfs # 服務(wù)名
fastdfs.path: /fastdfs/** # 把fastdfs打頭的所有請(qǐng)求都轉(zhuǎn)發(fā)給hrm-fastdfs
server:
  port: 9527
spring:
  application:
    name: zuul-gateway
zuul:
  routes: 
    sysmanage.serviceId: hrm-sysmanage # 服務(wù)名
    sysmanage.path: /sysmanage/** # 把sysmanage打頭的所有請(qǐng)求都轉(zhuǎn)發(fā)給hrm-sysmanage
    course.serviceId: hrm-course # 服務(wù)名
    course.path: /course/** # 把course打頭的所有請(qǐng)求都轉(zhuǎn)發(fā)給hrm-course
    fastdfs.serviceId: hrm-fastdfs # 服務(wù)名
    fastdfs.path: /fastdfs/** # 把fastdfs打頭的所有請(qǐng)求都轉(zhuǎn)發(fā)給hrm-fastdfs
  ignored-services: "*" # 所有服務(wù)都不允許以服務(wù)名來(lái)訪問(wèn)
  prefix: "/services" # 加一個(gè)統(tǒng)一前綴
  retryable: true # 是否重試
ribbon:
  ConnectTimeout: 250 # 連接超時(shí)時(shí)間(ms)
  ReadTimeout: 2000 # 通信超時(shí)時(shí)間(ms)
  OkToRetryOnAllOperations: true # 是否對(duì)所有操作重試
  MaxAutoRetriesNextServer: 2 # 同一服務(wù)不同實(shí)例的重試次數(shù)
  MaxAutoRetries: 1 # 同一實(shí)例的重試次數(shù)
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMillisecond: 3000 # 熔斷超時(shí)時(shí)長(zhǎng):3000ms
  1. swagger配置
  • (1)本項(xiàng)目配置swagger

導(dǎo)包

<!--引入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>

本項(xiàng)目配置

package cn.wangningbo.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;

@Configuration
@EnableSwagger2
public class Swagger2 {
 
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //對(duì)外暴露服務(wù)的包,以controller的方式暴露,所以就是controller的包.
                .apis(RequestHandlerSelectors.basePackage("cn.wangningbo.hrm.controller"))
                .paths(PathSelectors.any())
                .build();
    }


    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("分布式文件系統(tǒng)api")
                .description("分布式文件系統(tǒng)接口文檔說(shuō)明")
                .contact(new Contact("wangningbo", "", "wang_ning_bo163@163.com"))
                .version("1.0")
                .build();
    }
}
  • (2)網(wǎng)關(guān)zuul配置swagger
resources.add(swaggerResource("分布式文件系統(tǒng)", "/services/fastdfs/v2/api-docs", "2.0"));
package cn.wangningbo.hrm.config;

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<>();
        //通過(guò)網(wǎng)關(guān)訪問(wèn)服務(wù)地址
        resources.add(swaggerResource("系統(tǒng)管理", "/services/sysmanage/v2/api-docs", "2.0"));
        resources.add(swaggerResource("課程中心", "/services/course/v2/api-docs", "2.0"));
        resources.add(swaggerResource("分布式文件系統(tǒng)", "/services/fastdfs/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;
    }
}
  1. 測(cè)試swagger,啟動(dòng)項(xiàng)目

  2. 測(cè)試上傳和刪除(postman)

    postman測(cè)試文件上傳:輸入上傳地址-->選擇post方法-->Body-->form-data-->key那里輸入文件的key跃惫,尾部選擇file-->value那里會(huì)有個(gè)按鈕叮叹,點(diǎn)擊以后選擇文件-->send發(fā)送請(qǐng)求

    返回結(jié)果:/group1/M00/00/02/rBAGP11vwsGAPpmLAA3_Ih_wmb8559.png

    瀏覽器訪問(wèn)上傳到的服務(wù)器,172.16.6.63/group1/M00/00/02/rBAGP11vwsGAPpmLAA3_Ih_wmb8559.png

    可以看到圖片

1.3.4 整改前端

略過(guò)

2. 課程管理

2.1 課程管理代碼生成

2.1.1 業(yè)務(wù)說(shuō)明

2.1.2 表設(shè)計(jì)

2.1.3 代碼生成

2.2 課程管理crud

2.2.1 分頁(yè)列表

分頁(yè)+高級(jí)查詢+關(guān)聯(lián)查詢

2.2.1.1 步驟分析

  1. domain改造
  2. controller
  3. IService
  4. ServiceImpl
  5. mapper.java
  6. mapper.xml

2.2.1.2 步驟實(shí)現(xiàn)(Course)

  1. domain改造
    @TableField(exist = false)
    private CourseType courseType; //課程類型

    @TableField(exist = false)
    private CourseDetail detail; //課程詳情
  1. controller
    @RequestMapping(value = "/json", method = RequestMethod.POST)
    public PageList<Course> json(@RequestBody CourseQuery query) {
        return courseService.selectPageList(query);
    }
  1. IService
    //分頁(yè)+高級(jí)查詢+關(guān)聯(lián)查詢
    PageList<Course> selectPageList(CourseQuery query);
  1. ServiceImpl
    @Autowired
    private CourseMapper courseMapper;

    @Override
    public PageList<Course> selectPageList(CourseQuery query) {
        Page page = new Page<>(query.getPage(), query.getRows());
        List<Course> list = courseMapper.loadPageList(page, query);
        return new PageList<>(page.getTotal(), list);
    }
  1. mapper.java
    List<Course> loadPageList(Page page, @Param("query") CourseQuery query);
  1. mapper.xml
    <!--List<Course> loadPageList(Page page, @Param("query") CourseQuery query);-->
    <select id="loadPageList" parameterType="CourseQuery" resultMap="CourseMap">
        SELECT
        c.*, ct.id ct_id,
        ct.`name` ct_name
        FROM
        t_course c
        LEFT JOIN t_course_type ct ON c.course_type_id = ct.id
        <include refid="whereSql"></include>
    </select>
    <sql id="whereSql">
        <where>
            <if test="query.keyword!=null and query.keyword!=''">
                c.name like concat("%",#{query.keyword},"%") or
                c.users like concat('%',#{query.keyword},'%') or
                c.tenantName like concat('%',#{query.keyword},'%') or
                c.userName like concat('%',#{query.keyword},'%')
            </if>
        </where>
    </sql>
    <resultMap id="CourseMap" type="Course">
        <id column="id" property="id" />
        <result column="name" property="name" />
        <result column="users" property="users" />
        <result column="course_type_id" property="courseTypeId" />
        <result column="grade" property="grade" />
        <result column="status" property="status" />
        <result column="tenant_id" property="tenantId" />
        <result column="tenantName" property="tenantName" />
        <result column="user_id" property="userId" />
        <result column="userName" property="userName" />
        <result column="start_time" property="startTime" />
        <result column="end_time" property="endTime" />
        <association property="courseType" javaType="CourseType">
            <id column="ct_id" property="id" />
            <result column="ct_name" property="name" />
        </association>
    </resultMap>

2.2.2 基本信息和詳情增刪改

2.2.3 課程營(yíng)銷信息維護(hù)

2.2.4 課程圖片維護(hù)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末爆存,一起剝皮案震驚了整個(gè)濱河市蛉顽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌先较,老刑警劉巖携冤,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異闲勺,居然都是意外死亡曾棕,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門菜循,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)翘地,“玉大人,你說(shuō)我怎么就攤上這事债朵∽涌簦” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵序芦,是天一觀的道長(zhǎng)臭杰。 經(jīng)常有香客問(wèn)我,道長(zhǎng)谚中,這世上最難降的妖魔是什么渴杆? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮宪塔,結(jié)果婚禮上磁奖,老公的妹妹穿的比我還像新娘。我一直安慰自己某筐,他們只是感情好比搭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著南誊,像睡著了一般身诺。 火紅的嫁衣襯著肌膚如雪蜜托。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,598評(píng)論 1 305
  • 那天霉赡,我揣著相機(jī)與錄音橄务,去河邊找鬼。 笑死穴亏,一個(gè)胖子當(dāng)著我的面吹牛蜂挪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嗓化,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼棠涮,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蟆湖?” 一聲冷哼從身側(cè)響起故爵,我...
    開(kāi)封第一講書(shū)人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎隅津,沒(méi)想到半個(gè)月后诬垂,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡伦仍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年结窘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片充蓝。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡隧枫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谓苟,到底是詐尸還是另有隱情官脓,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布涝焙,位于F島的核電站卑笨,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏仑撞。R本人自食惡果不足惜赤兴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望隧哮。 院中可真熱鬧桶良,春花似錦、人聲如沸沮翔。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至歧譬,卻和暖如春岸浑,著一層夾襖步出監(jiān)牢的瞬間搏存,已是汗流浹背瑰步。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留璧眠,地道東北人缩焦。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓桑李,卻偏偏與公主長(zhǎng)得像陕悬,于是被迫代替她去往敵國(guó)和親衅疙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子壶唤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容