Browse Source

文件服务

yingp 7 years ago
parent
commit
aa7569c8f7
41 changed files with 1647 additions and 527 deletions
  1. 3 2
      README.md
  2. 4 2
      base-servers/account/account-server/src/main/resources/mapper/AccountMapper.xml
  3. 4 2
      base-servers/account/account-server/src/main/resources/mapper/CompanyMapper.xml
  4. 4 0
      base-servers/file/README.md
  5. 9 10
      base-servers/file/file-api/src/main/java/com/usoftchina/saas/file/api/FileApi.java
  6. 37 0
      base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/BaseFileInfo.java
  7. 0 60
      base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/BaseFolder.java
  8. 0 179
      base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/FileDTO.java
  9. 0 31
      base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/FileListDTO.java
  10. 4 9
      base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/FolderDTO.java
  11. 17 0
      base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/FolderSaveDTO.java
  12. 0 18
      base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/FolderToSaveDTO.java
  13. 31 11
      base-servers/file/file-server/pom.xml
  14. 10 2
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/FileApplication.java
  15. 12 0
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/constant/FileConstant.java
  16. 50 0
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/constant/FileType.java
  17. 320 11
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/controller/FileController.java
  18. 46 0
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/mapper/FileInfoMapper.java
  19. 0 11
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/mapper/FileMapper.java
  20. 0 108
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/po/File.java
  21. 304 0
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/po/FileInfo.java
  22. 30 0
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/service/FileInfoService.java
  23. 0 12
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/service/FileService.java
  24. 36 0
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/service/impl/FileInfoServiceImpl.java
  25. 0 16
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/service/impl/FileServiceImpl.java
  26. 135 0
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/util/FileTypeUtils.java
  27. 58 7
      base-servers/file/file-server/src/main/resources/application.yml
  28. 0 15
      base-servers/file/file-server/src/main/resources/banner.txt
  29. 12 0
      base-servers/file/file-server/src/main/resources/config/application-docker-dev.yml
  30. 10 0
      base-servers/file/file-server/src/main/resources/config/application-docker.yml
  31. 58 9
      base-servers/file/file-server/src/main/resources/logback-spring.xml
  32. 219 0
      base-servers/file/file-server/src/main/resources/mapper/FileInfoMapper.xml
  33. 0 5
      base-servers/file/file-server/src/main/resources/mapper/FilePoMapper.xml
  34. 104 0
      base-servers/file/file-server/src/test/java/com/usoftchina/saas/file/controller/FileControllerTest.java
  35. 50 0
      base-servers/file/file-server/src/test/java/com/usoftchina/saas/file/util/RandomTextFile.java
  36. 5 7
      framework/core/src/main/java/com/usoftchina/saas/exception/ExceptionCode.java
  37. 8 0
      framework/core/src/main/java/com/usoftchina/saas/utils/BizAssert.java
  38. 10 0
      framework/core/src/main/java/com/usoftchina/saas/utils/StringUtils.java
  39. 23 0
      framework/test-starter/src/main/java/com.usoftchina.saas.test/BaseControllerTest.java
  40. 13 0
      pom.xml
  41. 21 0
      script/mysql/init/file.sql

+ 3 - 2
README.md

@@ -65,8 +65,9 @@
 │  |  ├─test-starter--------------------------测试启动辅助工具
 │  |  ├─test-starter--------------------------测试启动辅助工具
 │  │
 │  │
 │  ├─frontend---------------------------------前端
 │  ├─frontend---------------------------------前端
-│  |  ├─web-----------------------------------web前端(vue + element-ui)
-│  |  ├─saas-web------------------------------web前端(extjs)
+│  |  ├─saas-portal-web-----------------------门户前端
+│  |  ├─saas-web------------------------------系统前端(extjs)
+│  |  ├─web-----------------------------------系统前端(vue + element-ui)
 │  │ 
 │  │ 
 │  ├─script-----------------------------------脚本
 │  ├─script-----------------------------------脚本
 │  |  ├─mysql---------------------------------mysql脚本
 │  |  ├─mysql---------------------------------mysql脚本

+ 4 - 2
base-servers/account/account-server/src/main/resources/mapper/AccountMapper.xml

@@ -21,13 +21,15 @@
         ac_account.mobile,ac_account.type,ac_account.enabled,ac_account.creator_id,ac_account.create_time,
         ac_account.mobile,ac_account.type,ac_account.enabled,ac_account.creator_id,ac_account.create_time,
         ac_account.updater_id,ac_account.update_time
         ac_account.updater_id,ac_account.update_time
     </sql>
     </sql>
-    <insert id="insert" parameterType="com.usoftchina.saas.account.po.Account">
+    <insert id="insert" parameterType="com.usoftchina.saas.account.po.Account"
+            useGeneratedKeys="true" keyProperty="id">
         insert into ac_account(username,password,salt,realname,email,mobile,type,enabled,creator_id,create_time,updater_id,update_time)
         insert into ac_account(username,password,salt,realname,email,mobile,type,enabled,creator_id,create_time,updater_id,update_time)
         values (#{username,jdbcType=VARCHAR},#{password,jdbcType=VARCHAR}, #{salt,jdbcType=VARCHAR},
         values (#{username,jdbcType=VARCHAR},#{password,jdbcType=VARCHAR}, #{salt,jdbcType=VARCHAR},
         #{realname,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR}, #{mobile,jdbcType=VARCHAR}, #{type,jdbcType=INTEGER}, #{enabled,jdbcType=BOOLEAN},
         #{realname,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR}, #{mobile,jdbcType=VARCHAR}, #{type,jdbcType=INTEGER}, #{enabled,jdbcType=BOOLEAN},
         #{creatorId,jdbcType=BIGINT}, #{createTime,jdbcType=TIMESTAMP}, #{updaterId,jdbcType=BIGINT}, #{updateTime,jdbcType=TIMESTAMP})
         #{creatorId,jdbcType=BIGINT}, #{createTime,jdbcType=TIMESTAMP}, #{updaterId,jdbcType=BIGINT}, #{updateTime,jdbcType=TIMESTAMP})
     </insert>
     </insert>
-    <insert id="insertSelective" parameterType="com.usoftchina.saas.account.po.Account">
+    <insert id="insertSelective" parameterType="com.usoftchina.saas.account.po.Account"
+            useGeneratedKeys="true" keyProperty="id">
         insert into ac_account
         insert into ac_account
         <trim prefix="(" suffix=")" suffixOverrides=",">
         <trim prefix="(" suffix=")" suffixOverrides=",">
             <if test="username != null">
             <if test="username != null">

+ 4 - 2
base-servers/account/account-server/src/main/resources/mapper/CompanyMapper.xml

@@ -19,13 +19,15 @@
     <sql id="baseColumns">
     <sql id="baseColumns">
         id,name,business_code,address,creator_id,create_time,updater_id,update_time
         id,name,business_code,address,creator_id,create_time,updater_id,update_time
     </sql>
     </sql>
-    <insert id="insert" parameterType="com.usoftchina.saas.account.po.Company">
+    <insert id="insert" parameterType="com.usoftchina.saas.account.po.Company"
+            useGeneratedKeys="true" keyProperty="id">
         insert into ac_company(name, business_code, address, logo_url, creator_id, create_time, updater_id, update_time)
         insert into ac_company(name, business_code, address, logo_url, creator_id, create_time, updater_id, update_time)
         values (#{name,jdbcType=VARCHAR}, #{businessCode,jdbcType=VARCHAR},
         values (#{name,jdbcType=VARCHAR}, #{businessCode,jdbcType=VARCHAR},
         #{address,jdbcType=VARCHAR}, #{logoUrl,jdbcType=VARCHAR}, #{creatorId,jdbcType=BIGINT},
         #{address,jdbcType=VARCHAR}, #{logoUrl,jdbcType=VARCHAR}, #{creatorId,jdbcType=BIGINT},
         #{createTime,jdbcType=TIMESTAMP}, #{updaterId,jdbcType=BIGINT}, #{updateTime,jdbcType=TIMESTAMP})
         #{createTime,jdbcType=TIMESTAMP}, #{updaterId,jdbcType=BIGINT}, #{updateTime,jdbcType=TIMESTAMP})
     </insert>
     </insert>
-    <insert id="insertSelective" parameterType="com.usoftchina.saas.account.po.Company">
+    <insert id="insertSelective" parameterType="com.usoftchina.saas.account.po.Company"
+            useGeneratedKeys="true" keyProperty="id">
         insert into ac_company
         insert into ac_company
         <trim prefix="(" suffix=")" suffixOverrides=",">
         <trim prefix="(" suffix=")" suffixOverrides=",">
             <if test="name != null">
             <if test="name != null">

+ 4 - 0
base-servers/file/README.md

@@ -0,0 +1,4 @@
+## fastdfs
+
+* [环境配置](https://github.com/meiko-zhang/docker-compose-fastdfs-single) 
+* [java client](https://github.com/tobato/FastDFS_Client)

+ 9 - 10
base-servers/file/file-api/src/main/java/com/usoftchina/saas/file/api/FileApi.java

@@ -1,10 +1,9 @@
 package com.usoftchina.saas.file.api;
 package com.usoftchina.saas.file.api;
 
 
 import com.usoftchina.saas.base.Result;
 import com.usoftchina.saas.base.Result;
-import com.usoftchina.saas.file.dto.FileDTO;
-import com.usoftchina.saas.file.dto.FileListDTO;
+import com.usoftchina.saas.file.dto.FileInfoDTO;
 import com.usoftchina.saas.file.dto.FolderDTO;
 import com.usoftchina.saas.file.dto.FolderDTO;
-import com.usoftchina.saas.file.dto.FolderToSaveDTO;
+import com.usoftchina.saas.file.dto.FolderSaveDTO;
 import feign.codec.Encoder;
 import feign.codec.Encoder;
 import feign.form.spring.SpringFormEncoder;
 import feign.form.spring.SpringFormEncoder;
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.cloud.openfeign.FeignClient;
@@ -20,7 +19,7 @@ import org.springframework.web.multipart.MultipartFile;
  * @author yingp
  * @author yingp
  * @date 2018/9/29
  * @date 2018/9/29
  */
  */
-@FeignClient(name = "gateway-server", configuration = FileApi.MultipartSupportConfig.class)
+@FeignClient(name = "file-server", configuration = FileApi.MultipartSupportConfig.class)
 public interface FileApi {
 public interface FileApi {
 
 
     @Configuration
     @Configuration
@@ -37,8 +36,8 @@ public interface FileApi {
      * @param newFolder 待保存文件夹
      * @param newFolder 待保存文件夹
      * @return
      * @return
      */
      */
-    @PostMapping(value = "/api/file/folder")
-    Result<FolderDTO> folder(@RequestBody FolderToSaveDTO newFolder);
+    @PostMapping(value = "/folder")
+    Result<FolderDTO> folder(FolderSaveDTO newFolder);
 
 
     /**
     /**
      * 根据id查看文件详情
      * 根据id查看文件详情
@@ -46,8 +45,8 @@ public interface FileApi {
      * @param id
      * @param id
      * @return
      * @return
      */
      */
-    @GetMapping(value = "/api/file/{id}")
-    Result<FileDTO> get(@PathVariable(value = "id") Long id);
+    @GetMapping(value = "/info/{id}")
+    Result<FileInfoDTO> getFileInfo(@PathVariable(value = "id") Long id);
 
 
     /**
     /**
      * 上传文件到指定文件夹
      * 上传文件到指定文件夹
@@ -57,6 +56,6 @@ public interface FileApi {
      * @return
      * @return
      * @throws Exception
      * @throws Exception
      */
      */
-    @PostMapping(value = "/api/file/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
-    Result<FileListDTO> upload(@RequestParam(value = "folderId") Long folderId, @RequestPart(value = "file") MultipartFile file) throws Exception;
+    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    Result<FileInfoDTO> upload(Long folderId, @RequestPart(value = "file") MultipartFile file) throws Exception;
 }
 }

+ 37 - 0
base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/BaseFileInfo.java

@@ -0,0 +1,37 @@
+package com.usoftchina.saas.file.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * @author yingp
+ * @date 2018/9/29
+ */
+public abstract class BaseFileInfo {
+    /**
+     * 名称
+     */
+    @ApiModelProperty(value = "名称")
+    protected String name;
+
+    /**
+     * 父文件夹
+     */
+    @ApiModelProperty(value = "父文件夹ID")
+    protected Long folderId;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Long getFolderId() {
+        return folderId;
+    }
+
+    public void setFolderId(Long folderId) {
+        this.folderId = folderId;
+    }
+}

+ 0 - 60
base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/BaseFolder.java

@@ -1,60 +0,0 @@
-package com.usoftchina.saas.file.dto;
-
-import io.swagger.annotations.ApiModelProperty;
-
-/**
- * @author yingp
- * @date 2018/9/29
- */
-public abstract class BaseFolder {
-    /**
-     * 名称
-     */
-    @ApiModelProperty(value = "名称")
-    protected String originalFilename;
-
-    /**
-     * 父文件夹
-     */
-    @ApiModelProperty(value = "父文件夹ID")
-    protected Long parentId;
-
-    /**
-     * 排序
-     */
-    @ApiModelProperty(value = "排序")
-    protected Integer orderNum;
-
-    public String getOriginalFilename() {
-        return originalFilename;
-    }
-
-    public void setOriginalFilename(String originalFilename) {
-        this.originalFilename = originalFilename;
-    }
-
-    public Long getParentId() {
-        return parentId;
-    }
-
-    public void setParentId(Long parentId) {
-        this.parentId = parentId;
-    }
-
-    public Integer getOrderNum() {
-        return orderNum;
-    }
-
-    public void setOrderNum(Integer orderNum) {
-        this.orderNum = orderNum;
-    }
-
-    @Override
-    public String toString() {
-        return "BaseFolder{" +
-                "originalFilename='" + originalFilename + '\'' +
-                ", parentId=" + parentId +
-                ", orderNum=" + orderNum +
-                '}';
-    }
-}

+ 0 - 179
base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/FileDTO.java

@@ -1,179 +0,0 @@
-package com.usoftchina.saas.file.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-
-import java.io.Serializable;
-
-/**
- * @author yingp
- * @date 2018/9/29
- */
-@ApiModel(value = "File", description = "文件")
-public class FileDTO implements Serializable {
-    @ApiModelProperty(value = "id")
-    private Long id;
-    /**
-     * 文件夹id
-     */
-    @ApiModelProperty(value = "文件夹id")
-    private Long folderId;
-
-    /**
-     * 文件夹名称
-     */
-    @ApiModelProperty(value = "文件夹名称")
-    private Long folderName;
-
-    /**
-     * 链接
-     */
-    @ApiModelProperty(value = "文件url")
-    private String url;
-
-    /**
-     * 类型
-     */
-    @ApiModelProperty(value = "文件mime类型")
-    private String mime;
-
-    /**
-     * 原始文件名
-     */
-    @ApiModelProperty(value = "原始文件名")
-    private String originalFilename;
-
-    /**
-     * 文件名
-     */
-    @ApiModelProperty(value = "唯一文件名")
-    private String filename;
-
-    /**
-     * 后缀 (没有.)
-     */
-    @ApiModelProperty(value = "后缀")
-    private String ext;
-
-    /**
-     * 大小
-     */
-    @ApiModelProperty(value = "大小")
-    private String size;
-
-    /**
-     * 图标
-     */
-    @ApiModelProperty(value = "图标")
-    private String icon;
-    /**
-     * 文件类型
-     */
-    @ApiModelProperty(value = "文件类型 IMAGE/VIDEO/AUDIO/DOC/OTHER/DIR", example = "IMAGE/VIDEO/AUDIO/DOC/OTHER/DIR")
-    private String dataType;
-
-    public Long getId() {
-        return id;
-    }
-
-    public void setId(Long id) {
-        this.id = id;
-    }
-
-    public Long getFolderId() {
-        return folderId;
-    }
-
-    public void setFolderId(Long folderId) {
-        this.folderId = folderId;
-    }
-
-    public Long getFolderName() {
-        return folderName;
-    }
-
-    public void setFolderName(Long folderName) {
-        this.folderName = folderName;
-    }
-
-    public String getUrl() {
-        return url;
-    }
-
-    public void setUrl(String url) {
-        this.url = url;
-    }
-
-    public String getMime() {
-        return mime;
-    }
-
-    public void setMime(String mime) {
-        this.mime = mime;
-    }
-
-    public String getOriginalFilename() {
-        return originalFilename;
-    }
-
-    public void setOriginalFilename(String originalFilename) {
-        this.originalFilename = originalFilename;
-    }
-
-    public String getFilename() {
-        return filename;
-    }
-
-    public void setFilename(String filename) {
-        this.filename = filename;
-    }
-
-    public String getExt() {
-        return ext;
-    }
-
-    public void setExt(String ext) {
-        this.ext = ext;
-    }
-
-    public String getSize() {
-        return size;
-    }
-
-    public void setSize(String size) {
-        this.size = size;
-    }
-
-    public String getIcon() {
-        return icon;
-    }
-
-    public void setIcon(String icon) {
-        this.icon = icon;
-    }
-
-    public String getDataType() {
-        return dataType;
-    }
-
-    public void setDataType(String dataType) {
-        this.dataType = dataType;
-    }
-
-    @Override
-    public String toString() {
-        return "FileDTO{" +
-                "id=" + id +
-                ", folderId=" + folderId +
-                ", folderName=" + folderName +
-                ", url='" + url + '\'' +
-                ", mime='" + mime + '\'' +
-                ", originalFilename='" + originalFilename + '\'' +
-                ", filename='" + filename + '\'' +
-                ", ext='" + ext + '\'' +
-                ", size='" + size + '\'' +
-                ", icon='" + icon + '\'' +
-                ", dataType='" + dataType + '\'' +
-                '}';
-    }
-}

+ 0 - 31
base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/FileListDTO.java

@@ -1,31 +0,0 @@
-package com.usoftchina.saas.file.dto;
-
-import java.io.Serializable;
-import java.util.List;
-
-/**
- * @author yingp
- * @date 2018/9/29
- */
-public class FileListDTO implements Serializable {
-    private List<FileDTO> files;
-
-    public FileListDTO(List<FileDTO> files) {
-        this.files = files;
-    }
-
-    public List<FileDTO> getFiles() {
-        return files;
-    }
-
-    public void setFiles(List<FileDTO> files) {
-        this.files = files;
-    }
-
-    @Override
-    public String toString() {
-        return "FileListDTO{" +
-                "files=" + files +
-                '}';
-    }
-}

+ 4 - 9
base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/FolderDTO.java

@@ -1,15 +1,12 @@
 package com.usoftchina.saas.file.dto;
 package com.usoftchina.saas.file.dto;
 
 
-import io.swagger.annotations.ApiModelProperty;
-
 import java.io.Serializable;
 import java.io.Serializable;
 
 
 /**
 /**
  * @author yingp
  * @author yingp
- * @date 2018/9/29
+ * @date 2018/11/9
  */
  */
-public class FolderDTO extends BaseFolder implements Serializable{
-    @ApiModelProperty(value = "id", required = true)
+public class FolderDTO extends BaseFileInfo implements Serializable{
     private Long id;
     private Long id;
 
 
     public Long getId() {
     public Long getId() {
@@ -23,10 +20,8 @@ public class FolderDTO extends BaseFolder implements Serializable{
     @Override
     @Override
     public String toString() {
     public String toString() {
         return "FolderDTO{" +
         return "FolderDTO{" +
-                "id=" + id +
-                ", originalFilename='" + originalFilename + '\'' +
-                ", parentId=" + parentId +
-                ", orderNum=" + orderNum +
+                "name='" + name + '\'' +
+                ", folderId=" + folderId +
                 '}';
                 '}';
     }
     }
 }
 }

+ 17 - 0
base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/FolderSaveDTO.java

@@ -0,0 +1,17 @@
+package com.usoftchina.saas.file.dto;
+
+import java.io.Serializable;
+
+/**
+ * @author yingp
+ * @date 2018/9/29
+ */
+public class FolderSaveDTO extends BaseFileInfo implements Serializable{
+    @Override
+    public String toString() {
+        return "FolderSaveDTO{" +
+                "name='" + name + '\'' +
+                ", folderId=" + folderId +
+                '}';
+    }
+}

+ 0 - 18
base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/FolderToSaveDTO.java

@@ -1,18 +0,0 @@
-package com.usoftchina.saas.file.dto;
-
-import java.io.Serializable;
-
-/**
- * @author yingp
- * @date 2018/9/29
- */
-public class FolderToSaveDTO extends BaseFolder implements Serializable{
-    @Override
-    public String toString() {
-        return "FolderToSaveDTO{" +
-                "originalFilename='" + originalFilename + '\'' +
-                ", parentId=" + parentId +
-                ", orderNum=" + orderNum +
-                '}';
-    }
-}

+ 31 - 11
base-servers/file/file-server/pom.xml

@@ -15,24 +15,17 @@
     <dependencies>
     <dependencies>
         <dependency>
         <dependency>
             <groupId>com.usoftchina.saas</groupId>
             <groupId>com.usoftchina.saas</groupId>
-            <artifactId>core</artifactId>
+            <artifactId>server-starter</artifactId>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
             <groupId>com.usoftchina.saas</groupId>
             <groupId>com.usoftchina.saas</groupId>
             <artifactId>file-dto</artifactId>
             <artifactId>file-dto</artifactId>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-actuator</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-security</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.cloud</groupId>
-            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
+            <groupId>com.usoftchina.saas</groupId>
+            <artifactId>auth-client</artifactId>
         </dependency>
         </dependency>
+        <!-- db -->
         <dependency>
         <dependency>
             <groupId>mysql</groupId>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
             <artifactId>mysql-connector-java</artifactId>
@@ -54,6 +47,33 @@
             <groupId>io.springfox</groupId>
             <groupId>io.springfox</groupId>
             <artifactId>springfox-swagger2</artifactId>
             <artifactId>springfox-swagger2</artifactId>
         </dependency>
         </dependency>
+
+        <dependency>
+            <groupId>com.github.tobato</groupId>
+            <artifactId>fastdfs-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-compress</artifactId>
+        </dependency>
+        <!-- sleuth -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-zipkin</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.amqp</groupId>
+            <artifactId>spring-rabbit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>net.logstash.logback</groupId>
+            <artifactId>logstash-logback-encoder</artifactId>
+        </dependency>
+        <!-- test -->
+        <dependency>
+            <groupId>com.usoftchina.saas</groupId>
+            <artifactId>test-starter</artifactId>
+        </dependency>
     </dependencies>
     </dependencies>
     <build>
     <build>
         <plugins>
         <plugins>

+ 10 - 2
base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/FileApplication.java

@@ -1,9 +1,14 @@
 package com.usoftchina.saas.file;
 package com.usoftchina.saas.file;
 
 
+import com.github.tobato.fastdfs.FdfsClientConfig;
+import com.usoftchina.saas.auth.client.EnableAuthClient;
+import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
 import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
-import org.springframework.cloud.netflix.hystrix.EnableHystrix;
+import org.springframework.context.annotation.EnableMBeanExport;
+import org.springframework.context.annotation.Import;
+import org.springframework.jmx.support.RegistrationPolicy;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
 
 
 /**
 /**
@@ -12,8 +17,11 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
  */
  */
 @SpringBootApplication
 @SpringBootApplication
 @EnableEurekaClient
 @EnableEurekaClient
-@EnableHystrix
 @EnableTransactionManagement
 @EnableTransactionManagement
+@MapperScan("com.usoftchina.saas.file.mapper")
+@EnableAuthClient
+@Import(FdfsClientConfig.class)
+@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
 public class FileApplication {
 public class FileApplication {
     public static void main(String[] args) {
     public static void main(String[] args) {
         SpringApplication.run(FileApplication.class, args);
         SpringApplication.run(FileApplication.class, args);

+ 12 - 0
base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/constant/FileConstant.java

@@ -0,0 +1,12 @@
+package com.usoftchina.saas.file.constant;
+
+/**
+ * @author yingp
+ * @date 2018/11/9
+ */
+public class FileConstant {
+    /**
+     * 默认文件夹ID
+     */
+    public static final long DEFAULT_FOLDER_ID = 0;
+}

+ 50 - 0
base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/constant/FileType.java

@@ -0,0 +1,50 @@
+package com.usoftchina.saas.file.constant;
+
+/**
+ * @author yingp
+ * @date 2018/11/9
+ */
+public enum FileType {
+    /**
+     * 文件夹
+     */
+    DIR,
+    /**
+     * 图片
+     */
+    IMAGE,
+    /**
+     * 文本
+     */
+    TEXT,
+    /**
+     * 文档
+     */
+    DOC,
+    /**
+     * 压缩文件
+     */
+    COMPRESS,
+    /**
+     * 视频
+     */
+    VIDEO,
+    /**
+     * 音频
+     */
+    AUDIO,
+    /**
+     * 其他
+     */
+    OTHER;
+
+    public static FileType of(String type) {
+        try {
+            if (null != type) {
+                return FileType.valueOf(type);
+            }
+        } catch (Exception e) {
+        }
+        return OTHER;
+    }
+}

+ 320 - 11
base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/controller/FileController.java

@@ -1,18 +1,36 @@
 package com.usoftchina.saas.file.controller;
 package com.usoftchina.saas.file.controller;
 
 
+import com.github.tobato.fastdfs.domain.StorePath;
+import com.github.tobato.fastdfs.service.FastFileStorageClient;
 import com.usoftchina.saas.base.Result;
 import com.usoftchina.saas.base.Result;
+import com.usoftchina.saas.exception.BizException;
 import com.usoftchina.saas.exception.ExceptionCode;
 import com.usoftchina.saas.exception.ExceptionCode;
+import com.usoftchina.saas.file.constant.FileConstant;
+import com.usoftchina.saas.file.dto.FileInfoDTO;
 import com.usoftchina.saas.file.dto.FolderDTO;
 import com.usoftchina.saas.file.dto.FolderDTO;
-import com.usoftchina.saas.file.dto.FolderToSaveDTO;
-import com.usoftchina.saas.file.service.FileService;
+import com.usoftchina.saas.file.dto.FolderSaveDTO;
+import com.usoftchina.saas.file.po.FileInfo;
+import com.usoftchina.saas.file.service.FileInfoService;
+import com.usoftchina.saas.utils.BeanMapper;
 import com.usoftchina.saas.utils.BizAssert;
 import com.usoftchina.saas.utils.BizAssert;
+import com.usoftchina.saas.utils.CollectionUtils;
+import com.usoftchina.saas.utils.StringUtils;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiOperation;
+import org.apache.commons.compress.archivers.ArchiveOutputStream;
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
+import org.apache.commons.io.IOUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.List;
 
 
 /**
 /**
  * 文件接口
  * 文件接口
@@ -22,16 +40,307 @@ import org.springframework.web.bind.annotation.RestController;
  */
  */
 @Api(value = "FileApi", description = "文件管理")
 @Api(value = "FileApi", description = "文件管理")
 @RestController
 @RestController
-@RequestMapping("/api/file")
+@RequestMapping
 public class FileController {
 public class FileController {
 
 
     @Autowired
     @Autowired
-    private FileService fileService;
+    private FileInfoService fileService;
+
+    @Autowired
+    private FastFileStorageClient storageClient;
+    /**
+     * 文件访问服务baseUrl
+     *
+     * @Deprecated 文件需要权限限制,用nginx不好处理
+     */
+    @Value("${file.base-url}")
+    private String fileBaseUrl;
 
 
-    @ApiOperation(value = "保存文件夹")
+    /**
+     * 检查文件夹ID的有效性
+     *
+     * @param folderId
+     * @return
+     */
+    private FileInfo checkFolder(Long folderId) {
+        if (null == folderId || folderId == FileConstant.DEFAULT_FOLDER_ID) {
+            return FileInfo.newFolder(FileConstant.DEFAULT_FOLDER_ID, null, "");
+        } else if (folderId < 0) {
+            throw new BizException(ExceptionCode.FOLDER_INVALID);
+        } else {
+            FileInfo info = fileService.findByPrimaryKey(folderId);
+            if (null != info) {
+                if (!info.isFolder()) {
+                    throw new BizException(ExceptionCode.FILE_NOT_FOLDER);
+                }
+                return info;
+            } else {
+                throw new BizException(ExceptionCode.FOLDER_NOT_EXISTS);
+            }
+        }
+    }
+
+    @ApiOperation(value = "创建文件夹")
     @PostMapping(value = "/folder")
     @PostMapping(value = "/folder")
-    public Result<FolderDTO> folder(@RequestBody FolderToSaveDTO newFolder) {
-        BizAssert.notNull(newFolder.getOriginalFilename(), ExceptionCode.FOLDER_NAME_EMPTY);
+    public Result<FolderDTO> folder(FolderSaveDTO newFolder) {
+        BizAssert.notNull(newFolder.getName(), ExceptionCode.FOLDER_NAME_EMPTY);
+        // 检查父文件夹
+        FileInfo parent = checkFolder(newFolder.getFolderId());
+        FileInfo info = FileInfo.newFolder(parent.getId(), newFolder.getName());
+        fileService.save(info);
+        return Result.success(BeanMapper.map(info, FolderDTO.class));
+    }
+
+    @ApiOperation(value = "上传文件")
+    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    public Result<FileInfoDTO> upload(Long folderId, @RequestPart(value = "file") MultipartFile file) throws Exception {
+        // 检查父文件夹
+        FileInfo parent = checkFolder(folderId);
+        FileInfo info = FileInfo.newFile(file).folder(parent.getId())
+                .storeBy(storageClient).build();
+        fileService.save(info);
+        return Result.success(BeanMapper.map(info, FileInfoDTO.class));
+    }
+
+    @ApiOperation(value = "下载文件")
+    @GetMapping(value = "/download/{id}")
+    public void download(@PathVariable Long id, HttpServletResponse response) throws Exception {
+        FileInfo info = fileService.findByPrimaryKey(id);
+        if (null == info) {
+            throw new BizException(ExceptionCode.FILE_NOT_EXISTS);
+        }
+        download(info, response);
+    }
+
+    private void download(FileInfo info, HttpServletResponse response) throws Exception{
+        response.setContentType("application/force-download");
+        if (info.isFolder()) {
+            // 压缩后下载
+            try (ArchiveOutputStream out = new ZipArchiveOutputStream(response.getOutputStream());) {
+                cascadeOutput(info, null, out);
+                out.finish();
+            }
+        } else {
+            if (null != info.getFullPath()) {
+                response.setHeader("Content-disposition",
+                        "attachment; filename=" + URLEncoder.encode(info.getName(), "UTF-8"));
+                StorePath path = StorePath.praseFromUrl(info.getFullPath());
+                storageClient.downloadFile(path.getGroup(), path.getPath(),
+                        in -> IOUtils.copy(in, response.getOutputStream()));
+            }
+        }
+    }
+
+    @ApiOperation(value = "下载文件")
+    @GetMapping(value = "/download")
+    public void download(@RequestParam String path, HttpServletResponse response) throws Exception {
+        FileInfo info = fileService.findByFullPath(path);
+        if (null == info) {
+            throw new BizException(ExceptionCode.FILE_NOT_EXISTS);
+        }
+        download(info, response);
+    }
+
+    @ApiOperation(value = "查看文件")
+    @GetMapping(value = "/view/{id}")
+    public void view(@PathVariable Long id, HttpServletResponse response) throws Exception {
+        FileInfo info = fileService.findByPrimaryKey(id);
+        if (null == info) {
+            throw new BizException(ExceptionCode.FILE_NOT_EXISTS);
+        }
+        view(info, response);
+    }
+
+    private void view(FileInfo info, HttpServletResponse response) throws Exception{
+        if (info.isImage() || info.isText()) {
+            if (!StringUtils.isEmpty(info.getMime())) {
+                response.setContentType(info.getMime());
+            }
+            StorePath path = StorePath.praseFromUrl(info.getFullPath());
+            storageClient.downloadFile(path.getGroup(), path.getPath(),
+                    in -> IOUtils.copy(in, response.getOutputStream()));
+        } else {
+            // 无法直接查看的文件,下载到客户端
+            download(info.getId(), response);
+        }
+    }
+
+    @ApiOperation(value = "查看文件")
+    @GetMapping(value = "/view")
+    public void view(@RequestParam String path, HttpServletResponse response) throws Exception {
+        FileInfo info = fileService.findByFullPath(path);
+        if (null == info) {
+            throw new BizException(ExceptionCode.FILE_NOT_EXISTS);
+        }
+        view(info, response);
+    }
+
+    @ApiOperation(value = "查看文件信息")
+    @GetMapping(value = "/info/{id}")
+    public Result<FileInfoDTO> getFileInfo(@PathVariable Long id) {
+        FileInfo info = fileService.findByPrimaryKey(id);
+        if (null != info) {
+            return Result.success(BeanMapper.map(info, FileInfoDTO.class));
+        }
+        return Result.error(ExceptionCode.FILE_NOT_EXISTS);
+    }
+
+    @ApiOperation(value = "查看文件信息")
+    @GetMapping(value = "/info")
+    public Result<FileInfoDTO> getFileInfo(@RequestParam String path) {
+        FileInfo info = fileService.findByFullPath(path);
+        if (null != info) {
+            return Result.success(BeanMapper.map(info, FileInfoDTO.class));
+        }
+        return Result.error(ExceptionCode.FILE_NOT_EXISTS);
+    }
+
+    @ApiOperation(value = "查看子文件")
+    @GetMapping(value = "/list")
+    public Result<List<FileInfoDTO>> listFiles(Long folderId) {
+        List<FileInfo> files = fileService.findByFolderId(checkFolder(folderId).getId());
+        return Result.success(BeanMapper.mapList(files, FileInfoDTO.class));
+    }
+
+    /**
+     * 以压缩文件的方式,级联输出
+     *
+     * @param info
+     * @param directory
+     * @param out
+     * @throws IOException
+     */
+    private void cascadeOutput(FileInfo info, String directory, ArchiveOutputStream out) throws IOException{
+        if (info.isFolder()) {
+            if (null == directory) {
+                directory = "/";
+            } else {
+                directory = directory + info.getName() + "/";
+                out.putArchiveEntry(new ZipArchiveEntry(directory));
+                out.closeArchiveEntry();
+            }
+            List<FileInfo> files = fileService.findByFolderId(info.getId());
+            if (!CollectionUtils.isEmpty(files)) {
+                for (FileInfo f : files) {
+                    cascadeOutput(f, directory,out);
+                }
+            }
+        } else {
+            out.putArchiveEntry(new ZipArchiveEntry(directory + info.getName()));
+            StorePath path = StorePath.praseFromUrl(info.getFullPath());
+            storageClient.downloadFile(path.getGroup(), path.getPath(), in -> IOUtils.copy(in, out));
+            out.closeArchiveEntry();
+        }
+    }
+
+    /**
+     * 级联删除
+     *
+     * @param info
+     */
+    private void cascadeRemove(FileInfo info) {
+        if (info.isFolder()) {
+            List<FileInfo> files = fileService.findByFolderId(info.getId());
+            if (!CollectionUtils.isEmpty(files)) {
+                files.forEach(this::cascadeRemove);
+            }
+            fileService.removeByPrimaryKey(info.getId());
+        } else {
+            fileService.removeByPrimaryKey(info.getId());
+            storageClient.deleteFile(info.getFullPath());
+        }
+    }
+
+    /**
+     * 级联回收
+     *
+     * @param info
+     */
+    private void cascadeRetrieve(FileInfo info) {
+        if (info.isFolder()) {
+            List<FileInfo> files = fileService.findByFolderId(info.getId());
+            if (!CollectionUtils.isEmpty(files)) {
+                files.forEach(this::cascadeRetrieve);
+            }
+        }
+        info.retrieve();
+        fileService.updateByPrimaryKey(info);
+    }
+
+    /**
+     * 级联还原
+     *
+     * @param info
+     */
+    private void cascadeRestore(FileInfo info) {
+        info.restore();
+        fileService.updateByPrimaryKey(info);
+        if (info.isFolder()) {
+            List<FileInfo> files = fileService.findByFolderId(info.getId());
+            if (!CollectionUtils.isEmpty(files)) {
+                files.forEach(this::cascadeRestore);
+            }
+        }
+    }
+
+    @ApiOperation(value = "删除文件")
+    @GetMapping(value = "/delete/{id}")
+    public Result delete(@PathVariable Long id,
+                         @RequestParam(required = false, defaultValue = "false") Boolean purge) {
+        FileInfo info = fileService.findByPrimaryKey(id);
+        if (null != info) {
+            if (purge) {
+                cascadeRemove(info);
+            } else {
+                cascadeRetrieve(info);
+            }
+        }
+        return Result.success();
+    }
+
+    @ApiOperation(value = "删除文件")
+    @GetMapping(value = "/delete")
+    public Result delete(@RequestParam String path,
+                         @RequestParam(required = false, defaultValue = "false") Boolean purge) {
+        FileInfo info = fileService.findByFullPath(path);
+        if (null != info) {
+            if (purge) {
+                cascadeRemove(info);
+            } else {
+                cascadeRetrieve(info);
+            }
+        }
+        return Result.success();
+    }
+
+    @ApiOperation(value = "还原文件")
+    @GetMapping(value = "/restore/{id}")
+    public Result restore(@PathVariable Long id) {
+        FileInfo info = fileService.findByPrimaryKey(id);
+        if (null != info) {
+            cascadeRestore(info);
+        }
+        return Result.success();
+    }
+
+    @ApiOperation(value = "还原文件")
+    @GetMapping(value = "/restore")
+    public Result restore(@RequestParam String path) {
+        FileInfo info = fileService.findByFullPath(path);
+        if (null != info) {
+            cascadeRestore(info);
+        }
+        return Result.success();
+    }
+
+    @ApiOperation(value = "移动文件到文件夹")
+    @GetMapping(value = "/move")
+    public Result move(@RequestParam Long fileId, @RequestParam Long folderId) {
+        FileInfo info = fileService.findByPrimaryKey(fileId);
+        if (null != info) {
+            info.setFolderId(folderId);
+            fileService.updateByPrimaryKey(info);
+        }
         return Result.success();
         return Result.success();
     }
     }
 }
 }

+ 46 - 0
base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/mapper/FileInfoMapper.java

@@ -0,0 +1,46 @@
+package com.usoftchina.saas.file.mapper;
+
+import com.usoftchina.saas.base.mapper.CommonBaseMapper;
+import com.usoftchina.saas.file.po.FileInfo;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2018/9/29
+ */
+public interface FileInfoMapper extends CommonBaseMapper<FileInfo>{
+
+    /**
+     * 按文件夹名查找未删除的
+     *
+     * @param folderId
+     * @return
+     */
+    List<FileInfo> selectVisibleByFolderId(@Param("folderId") Long folderId);
+
+    /**
+     * 按文件夹名查找删除的
+     *
+     * @param folderId
+     * @return
+     */
+    List<FileInfo> selectDeletedByFolderId(@Param("folderId") Long folderId);
+
+    /**
+     * 按文件夹名查找
+     *
+     * @param folderId
+     * @return
+     */
+    List<FileInfo> selectByFolderId(@Param("folderId") Long folderId);
+
+    /**
+     * 按路径查找
+     *
+     * @param fullPath
+     * @return
+     */
+    FileInfo selectByFullPath(@Param("fullPath") String fullPath);
+}

+ 0 - 11
base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/mapper/FileMapper.java

@@ -1,11 +0,0 @@
-package com.usoftchina.saas.file.mapper;
-
-import com.usoftchina.saas.base.mapper.CommonBaseMapper;
-import com.usoftchina.saas.file.po.File;
-
-/**
- * @author yingp
- * @date 2018/9/29
- */
-public interface FileMapper extends CommonBaseMapper<File>{
-}

+ 0 - 108
base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/po/File.java

@@ -1,108 +0,0 @@
-package com.usoftchina.saas.file.po;
-
-import com.usoftchina.saas.base.entity.CommonBaseEntity;
-
-import java.io.Serializable;
-
-/**
- * @author yingp
- * @date 2018/9/29
- */
-public class File extends CommonBaseEntity implements Serializable{
-    /**
-     * 文件夹id
-     */
-    private Long folderId;
-    /**
-     * 数据类型 IMAGE/VIDEO/AUDIO/DOC/OTHER/DIR
-     */
-    private String dataType;
-    /**
-     * 原始文件名
-     */
-    private String originalFilename;
-    /**
-     * 程序生成唯一文件名
-     */
-    private String filename;
-    /**
-     * mime类型
-     */
-    private String mime;
-    /**
-     * 后缀 (没有.)
-     */
-    private String ext;
-    /**
-     * 大小
-     */
-    private String size;
-    /**
-     * 图标
-     */
-    private String icon;
-
-    public Long getFolderId() {
-        return folderId;
-    }
-
-    public void setFolderId(Long folderId) {
-        this.folderId = folderId;
-    }
-
-    public String getDataType() {
-        return dataType;
-    }
-
-    public void setDataType(String dataType) {
-        this.dataType = dataType;
-    }
-
-    public String getOriginalFilename() {
-        return originalFilename;
-    }
-
-    public void setOriginalFilename(String originalFilename) {
-        this.originalFilename = originalFilename;
-    }
-
-    public String getFilename() {
-        return filename;
-    }
-
-    public void setFilename(String filename) {
-        this.filename = filename;
-    }
-
-    public String getMime() {
-        return mime;
-    }
-
-    public void setMime(String mime) {
-        this.mime = mime;
-    }
-
-    public String getExt() {
-        return ext;
-    }
-
-    public void setExt(String ext) {
-        this.ext = ext;
-    }
-
-    public String getSize() {
-        return size;
-    }
-
-    public void setSize(String size) {
-        this.size = size;
-    }
-
-    public String getIcon() {
-        return icon;
-    }
-
-    public void setIcon(String icon) {
-        this.icon = icon;
-    }
-}

+ 304 - 0
base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/po/FileInfo.java

@@ -0,0 +1,304 @@
+package com.usoftchina.saas.file.po;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.github.tobato.fastdfs.domain.StorePath;
+import com.github.tobato.fastdfs.service.FastFileStorageClient;
+import com.usoftchina.saas.base.entity.CommonBaseEntity;
+import com.usoftchina.saas.context.BaseContextHolder;
+import com.usoftchina.saas.file.constant.FileType;
+import com.usoftchina.saas.file.util.FileTypeUtils;
+import com.usoftchina.saas.utils.StringUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author yingp
+ * @date 2018/9/29
+ */
+public class FileInfo extends CommonBaseEntity implements Serializable{
+    /**
+     * 文件夹id
+     */
+    private Long folderId;
+
+    /**
+     * 原始文件名
+     */
+    private String name;
+    /**
+     * 存储路径
+     */
+    private String fullPath;
+    /**
+     * Content-Type
+     */
+    private String mime;
+    /**
+     * 后缀 (没有.)
+     */
+    private String ext;
+    /**
+     * 大小
+     */
+    private long size;
+    /**
+     * 类型
+     * @see com.usoftchina.saas.file.constant.FileType
+     */
+    private String type;
+
+    /**
+     * 是否已删除
+     * 已删除的会保留7天,在回收站接口里面可以查看
+     * 7天后自动清除
+     */
+    private boolean deleted;
+
+    private Long deleterId;
+
+    private Date deleteTime;
+
+    public FileInfo() {
+    }
+
+    public FileInfo(Long id, Long folderId, String name) {
+        this.id = id;
+        this.folderId = folderId;
+        this.name = name;
+    }
+
+    public FileInfo(Long folderId, String name) {
+        this.folderId = folderId;
+        this.name = name;
+    }
+
+    public FileInfo(Long folderId, String name, String fullPath, String mime, String ext, String type, long size) {
+        this.folderId = folderId;
+        this.name = name;
+        this.fullPath = fullPath;
+        this.mime = mime;
+        this.ext = ext;
+        this.type = type;
+        this.size = size;
+    }
+
+    public Long getFolderId() {
+        return folderId;
+    }
+
+    public void setFolderId(Long folderId) {
+        this.folderId = folderId;
+    }
+
+    public String getMime() {
+        return mime;
+    }
+
+    public void setMime(String mime) {
+        this.mime = mime;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getFullPath() {
+        return fullPath;
+    }
+
+    public void setFullPath(String fullPath) {
+        this.fullPath = fullPath;
+    }
+
+    public String getExt() {
+        return ext;
+    }
+
+    public void setExt(String ext) {
+        this.ext = ext;
+    }
+
+    public long getSize() {
+        return size;
+    }
+
+    public void setSize(long size) {
+        this.size = size;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public boolean isDeleted() {
+        return deleted;
+    }
+
+    public void setDeleted(boolean deleted) {
+        this.deleted = deleted;
+    }
+
+    public Long getDeleterId() {
+        return deleterId;
+    }
+
+    public void setDeleterId(Long deleterId) {
+        this.deleterId = deleterId;
+    }
+
+    public Date getDeleteTime() {
+        return deleteTime;
+    }
+
+    public void setDeleteTime(Date deleteTime) {
+        this.deleteTime = deleteTime;
+    }
+
+    /**
+     * 回收
+     */
+    public void retrieve() {
+        this.deleted = true;
+        this.deleterId = BaseContextHolder.getUserId();
+        this.deleteTime = new Date();
+    }
+
+    /**
+     * 从回收站还原
+     */
+    public void restore() {
+        this.deleteTime = null;
+        this.deleterId = null;
+        this.deleted = false;
+    }
+
+    @JsonIgnore
+    public boolean isFolder() {
+        return FileType.of(type) == FileType.DIR;
+    }
+
+    @JsonIgnore
+    public boolean isImage() {
+        return FileType.of(type) == FileType.IMAGE;
+    }
+
+    @JsonIgnore
+    public boolean isText() {
+        return FileType.of(type) == FileType.TEXT;
+    }
+
+    public static FileInfo newFolder(Long folderId, String name) {
+        FileInfo info = new FileInfo(folderId, name);
+        info.setType(FileType.DIR.name());
+        return info;
+    }
+
+    public static FileInfo newFolder(Long id, Long folderId, String name) {
+        FileInfo info = new FileInfo(id, folderId, name);
+        info.setType(FileType.DIR.name());
+        return info;
+    }
+
+    public static Builder newFile(MultipartFile file){
+        return new Builder(file);
+    }
+
+    /**
+     * FileInfo生成器
+     */
+    public static class Builder {
+        private Long folderId;
+        private String name;
+        private String fullPath;
+        private String mime;
+        private String ext;
+        private long size;
+        private String type;
+        private MultipartFile multipartFile;
+        private FastFileStorageClient storageClient;
+        private String baseUrl;
+
+        public Builder(MultipartFile file) {
+            this.name = file.getOriginalFilename();
+            this.ext = FilenameUtils.getExtension(file.getOriginalFilename());
+            this.size = file.getSize();
+            this.mime = file.getContentType();
+            this.type = FileTypeUtils.getFileType(mime, ext).name();
+            this.multipartFile = file;
+        }
+
+        public Builder folder(Long folderId) {
+            this.folderId = folderId;
+            return this;
+        }
+
+        public Builder name(String name) {
+            this.name = name;
+            return this;
+        }
+
+        public Builder fullPath(String fullPath) {
+            this.fullPath = fullPath;
+            return this;
+        }
+
+        public Builder mime(String mime) {
+            this.mime = mime;
+            return this;
+        }
+
+        public Builder ext(String ext) {
+            this.ext = ext;
+            return this;
+        }
+
+        public Builder size(long size) {
+            this.size = size;
+            return this;
+        }
+
+        public Builder type(String type) {
+            this.type = type;
+            return this;
+        }
+
+        public Builder storeBy(FastFileStorageClient storageClient){
+            this.storageClient = storageClient;
+            return this;
+        }
+
+        public Builder baseUrl(String baseUrl) {
+            this.baseUrl = baseUrl;
+            return this;
+        }
+
+        public FileInfo build() throws IOException{
+            if (null != storageClient) {
+                StorePath storePath;
+                if (FileType.of(type) == FileType.IMAGE) {
+                    // 保存图片 + 生成缩略图
+                    storePath = storageClient.uploadImageAndCrtThumbImage(multipartFile.getInputStream(),
+                            size, ext, null);
+                } else {
+                    storePath = storageClient.uploadFile(multipartFile.getInputStream(),
+                            size, ext, null);
+                }
+                this.fullPath = StringUtils.nullIf(baseUrl) + storePath.getFullPath();
+            }
+
+            return new FileInfo(folderId, name, fullPath, mime, ext, type, size);
+        }
+    }
+}

+ 30 - 0
base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/service/FileInfoService.java

@@ -0,0 +1,30 @@
+package com.usoftchina.saas.file.service;
+
+import com.usoftchina.saas.base.service.CommonBaseService;
+import com.usoftchina.saas.file.mapper.FileInfoMapper;
+import com.usoftchina.saas.file.po.FileInfo;
+
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2018/9/30
+ */
+public interface FileInfoService extends CommonBaseService<FileInfoMapper, FileInfo> {
+
+    /**
+     * 按文件夹名查找
+     *
+     * @param folderId
+     * @return
+     */
+    List<FileInfo> findByFolderId(Long folderId);
+
+    /**
+     * 按路径查找
+     *
+     * @param fullPath
+     * @return
+     */
+    FileInfo findByFullPath(String fullPath);
+}

+ 0 - 12
base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/service/FileService.java

@@ -1,12 +0,0 @@
-package com.usoftchina.saas.file.service;
-
-import com.usoftchina.saas.base.service.CommonBaseService;
-import com.usoftchina.saas.file.mapper.FileMapper;
-import com.usoftchina.saas.file.po.File;
-
-/**
- * @author yingp
- * @date 2018/9/30
- */
-public interface FileService extends CommonBaseService<FileMapper, File> {
-}

+ 36 - 0
base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/service/impl/FileInfoServiceImpl.java

@@ -0,0 +1,36 @@
+package com.usoftchina.saas.file.service.impl;
+
+import com.usoftchina.saas.base.service.CommonBaseServiceImpl;
+import com.usoftchina.saas.file.mapper.FileInfoMapper;
+import com.usoftchina.saas.file.po.FileInfo;
+import com.usoftchina.saas.file.service.FileInfoService;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2018/9/30
+ */
+@Service
+public class FileInfoServiceImpl extends CommonBaseServiceImpl<FileInfoMapper, FileInfo> implements FileInfoService {
+
+    @Override
+    public List<FileInfo> findByFolderId(Long folderId) {
+        return getMapper().selectByFolderId(folderId);
+    }
+
+    @Override
+    public FileInfo findByFullPath(String fullPath) {
+        return getMapper().selectByFullPath(fullPath);
+    }
+
+    /**
+     * 清理回收站超过7天的文件
+     */
+    @Scheduled(cron = "0 15 0 * * ?")
+    public void clearRecycleBin() {
+
+    }
+}

+ 0 - 16
base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/service/impl/FileServiceImpl.java

@@ -1,16 +0,0 @@
-package com.usoftchina.saas.file.service.impl;
-
-import com.usoftchina.saas.base.service.CommonBaseServiceImpl;
-import com.usoftchina.saas.file.mapper.FileMapper;
-import com.usoftchina.saas.file.po.File;
-import com.usoftchina.saas.file.service.FileService;
-import org.springframework.stereotype.Service;
-
-/**
- * @author yingp
- * @date 2018/9/30
- */
-@Service
-public class FileServiceImpl extends CommonBaseServiceImpl<FileMapper, File> implements FileService{
-
-}

+ 135 - 0
base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/util/FileTypeUtils.java

@@ -0,0 +1,135 @@
+package com.usoftchina.saas.file.util;
+
+import com.usoftchina.saas.file.constant.FileType;
+import com.usoftchina.saas.utils.StringUtils;
+
+/**
+ * @author yingp
+ * @date 2018/11/8
+ */
+public class FileTypeUtils {
+
+    /**
+     * 判断文件类型
+     *
+     * @param mime
+     * @param ext
+     * @return
+     */
+    public static FileType getFileType(String mime, String ext) {
+        FileType type = getFileTypeByExtension(ext);
+        if (type == FileType.OTHER) {
+            type = getFileTypeByMime(mime);
+        }
+        return type;
+    }
+
+    public static FileType getFileTypeByMime(String mime) {
+        if (!StringUtils.isEmpty(mime)) {
+            if (mime.startsWith("image/")) {
+                return FileType.IMAGE;
+            }
+            if (mime.startsWith("text/")) {
+                return FileType.TEXT;
+            }
+            if (mime.startsWith("application/")) {
+                switch (mime) {
+                    case "application/x-gzip":
+                    case "application/x-bzip2":
+                    case "application/zip":
+                    case "application/x-rar":
+                    case "application/x-tar":
+                    case "application/x-7z-compressed":
+                        return FileType.COMPRESS;
+                    default:
+                        return FileType.DOC;
+                }
+            }
+            if (mime.startsWith("video/")) {
+                return FileType.VIDEO;
+            }
+            if (mime.startsWith("audio/")) {
+                return FileType.AUDIO;
+            }
+        }
+        return FileType.OTHER;
+    }
+
+    public static FileType getFileTypeByExtension(String ext) {
+        if (!StringUtils.isEmpty(ext)) {
+            switch (ext) {
+                case "gif":
+                case "jpeg":
+                case "jpg":
+                case "bmp":
+                case "png":
+                case "tif":
+                case "tiff":
+                case "tga":
+                case "psd":
+                    return FileType.IMAGE;
+                case "txt":
+                case "php":
+                case "html":
+                case "htm":
+                case "js":
+                case "css":
+                case "rtf":
+                case "rtfd":
+                case "py":
+                case "java":
+                case "rb":
+                case "sh":
+                case "pl":
+                case "sql":
+                    return FileType.TEXT;
+                case "ai":
+                case "eps":
+                case "exe":
+                case "doc":
+                case "docx":
+                case "xls":
+                case "xlsx":
+                case "ppt":
+                case "pptx":
+                case "et":
+                case "pps":
+                case "pdf":
+                case "xml":
+                case "odt":
+                case "swf":
+                    return FileType.DOC;
+                case "avi":
+                case "dv":
+                case "mp4":
+                case "mpeg":
+                case "mpg":
+                case "mov":
+                case "wm":
+                case "flv":
+                case "mkv":
+                    return FileType.VIDEO;
+                case "mp3":
+                case "mid":
+                case "ogg":
+                case "mp4a":
+                case "wav":
+                case "wma":
+                    return FileType.AUDIO;
+                case "gz":
+                case "tgz":
+                case "bz":
+                case "bz2":
+                case "tbz":
+                case "zip":
+                case "rar":
+                case "tar":
+                case "7z":
+                    return FileType.COMPRESS;
+                default:
+                    break;
+            }
+        }
+        return FileType.OTHER;
+    }
+}

+ 58 - 7
base-servers/file/file-server/src/main/resources/application.yml

@@ -5,30 +5,69 @@ spring:
     user:
     user:
       name: admin
       name: admin
       password: select111***
       password: select111***
+  rabbitmq:
+    host: 192.168.0.176
+    port: 5672
+    virtual-host: dev
+    username: saas
+    password: select123***
+  zipkin:
+    sender:
+      type: rabbit
+    locator:
+      discovery:
+        enabled: true
+  sleuth:
+    sampler:
+      probability: 1.0
   datasource:
   datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    url: jdbc:mysql://192.168.253.12:3306/saas_file?characterEncoding=utf-8&useSSL=false
+    username: root
+    password: select111***
     hikari:
     hikari:
-      driver-class-name: com.mysql.cj.jdbc.Driver
-      jdbc-url: jdbc:mysql://192.168.253.12:3306/saas_file?characterEncoding=utf-8&useSSL=false
-      username: root
-      password: select111***
+      minimum-idle: 5
+      maximum-pool-size: 50
+      idle-timeout: 30000
+      max-lifetime: 1800000
+      connection-timeout: 30000
   messages:
   messages:
     basename: i18n/messages
     basename: i18n/messages
+  redis:
+    host: 192.168.253.12
+    port: 6379
+  jackson:
+    date-format: yyyy-MM-dd HH:mm:ss
+    time-zone: GMT+8
 eureka:
 eureka:
   instance:
   instance:
     leaseRenewalIntervalInSeconds: 10
     leaseRenewalIntervalInSeconds: 10
     health-check-url-path: /actuator/health
     health-check-url-path: /actuator/health
     status-page-url-path: /actuator/info
     status-page-url-path: /actuator/info
+    prefer-ip-address: true
     metadata-map:
     metadata-map:
       user.name: ${spring.security.user.name}
       user.name: ${spring.security.user.name}
       user.password: ${spring.security.user.password}
       user.password: ${spring.security.user.password}
   client:
   client:
     registryFetchIntervalSeconds: 5
     registryFetchIntervalSeconds: 5
     serviceUrl:
     serviceUrl:
-      defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@127.0.0.1:8500/eureka/
+      defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@192.168.0.181:8510/eureka/
 server:
 server:
-  port: 8580
+  port: 8640
   tomcat:
   tomcat:
     uri-encoding: UTF-8
     uri-encoding: UTF-8
+management:
+  endpoints:
+    web:
+      exposure:
+        include: "*"
+  endpoint:
+    health:
+      show-details: always
+    shutdown:
+      enabled: true
+    restart:
+      enabled: true
 info:
 info:
   name: '@project.artifactId@'
   name: '@project.artifactId@'
   description: '@project.description@'
   description: '@project.description@'
@@ -37,4 +76,16 @@ info:
   spring-cloud-version: '@spring.cloud.version@'
   spring-cloud-version: '@spring.cloud.version@'
 mybatis:
 mybatis:
   type-aliases-package: com.usoftchina.saas.file.po
   type-aliases-package: com.usoftchina.saas.file.po
-  mapper-locations: classpath:mapper/*.xml
+  mapper-locations: classpath:mapper/*.xml
+auth:
+  public-key: auth/pub.key
+fdfs:
+  so-timeout: 1500
+  connect-timeout: 600
+  thumb-image:
+    width: 150
+    height: 150
+  tracker-list:
+    - 192.168.253.3:22122
+file:
+  base-url: http://192.168.253.3:8888/

+ 0 - 15
base-servers/file/file-server/src/main/resources/banner.txt

@@ -1,15 +0,0 @@
-${AnsiColor.BRIGHT_YELLOW}
-
-88        88   ad88888ba     ,ad8888ba,    88888888888  888888888888  ,ad8888ba,   88        88  88  888b      88         db
-88        88  d8"     "8b   d8"'    `"8b   88                88      d8"'    `"8b  88        88  88  8888b     88        d88b
-88        88  Y8,          d8'        `8b  88                88     d8'            88        88  88  88 `8b    88       d8'`8b
-88        88  `Y8aaaaa,    88          88  88aaaaa           88     88             88aaaaaaaa88  88  88  `8b   88      d8'  `8b
-88        88    `"""""8b,  88          88  88"""""           88     88             88""""""""88  88  88   `8b  88     d8YaaaaY8b
-88        88          `8b  Y8,        ,8P  88                88     Y8,            88        88  88  88    `8b 88    d8""""""""8b
-Y8a.    .a8P  Y8a     a8P   Y8a.    .a8P   88                88      Y8a.    .a8P  88        88  88  88     `8888   d8'        `8b
- `"Y8888Y"'    "Y88888P"     `"Y8888Y"'    88                88       `"Y8888Y"'   88        88  88  88      `888  d8'          `8b
-
-
-Application Version: ${application.version}${application.formatted-version}
-Spring Boot Version: ${spring-boot.version}${spring-boot.formatted-version}
-${AnsiColor.DEFAULT}

+ 12 - 0
base-servers/file/file-server/src/main/resources/config/application-docker-dev.yml

@@ -0,0 +1,12 @@
+eureka:
+  instance:
+    hostname: saas-file-server-dev
+    prefer-ip-address: false
+  client:
+    serviceUrl:
+      defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@saas-eureka-server-dev:8510/eureka/
+spring:
+  rabbitmq:
+    virtual-host: dev
+server:
+  port: 8650

+ 10 - 0
base-servers/file/file-server/src/main/resources/config/application-docker.yml

@@ -0,0 +1,10 @@
+eureka:
+  instance:
+    hostname: saas-file-server
+    prefer-ip-address: false
+  client:
+    serviceUrl:
+      defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@saas-eureka-server:8500/eureka/
+spring:
+  rabbitmq:
+    virtual-host: docker

+ 58 - 9
base-servers/file/file-server/src/main/resources/logback-spring.xml

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <?xml version="1.0" encoding="UTF-8"?>
 <configuration>
 <configuration>
-    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
+    <include resource="org/springframework/boot/logging/logback/base.xml" />
     <jmxConfigurator/>
     <jmxConfigurator/>
 
 
     <!--
     <!--
@@ -28,10 +28,10 @@
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.253.3:5000"/>
 
 
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
 
 
-
     <appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
     <appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
         <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
         <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
             <level>${log.level.console}</level>
             <level>${log.level.console}</level>
@@ -44,11 +44,9 @@
     <appender name="ROOT_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
     <appender name="ROOT_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
         <file>${log.path}/root.log</file>
         <file>${log.path}/root.log</file>
         <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
         <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
-            <!-- 每天一归档 -->
             <fileNamePattern>${log.path}/%d{yyyy-MM}/root-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
             <fileNamePattern>${log.path}/%d{yyyy-MM}/root-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
-            <!-- 单个日志文件最多 100MB, 60天的日志周期,最大不能超过20GB -->
             <maxFileSize>128MB</maxFileSize>
             <maxFileSize>128MB</maxFileSize>
-            <maxHistory>60</maxHistory>
+            <maxHistory>7</maxHistory>
             <totalSizeCap>20GB</totalSizeCap>
             <totalSizeCap>20GB</totalSizeCap>
         </rollingPolicy>
         </rollingPolicy>
         <encoder>
         <encoder>
@@ -56,9 +54,60 @@
         </encoder>
         </encoder>
     </appender>
     </appender>
 
 
-    <root level="${log.level.console}">
-        <appender-ref ref="CONSOLE_APPENDER"/>
-        <appender-ref ref="ROOT_APPENDER"/>
-    </root>
+    <!-- Appender to log in a JSON format -->
+    <appender name="JSON_APPENDER" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
+        <destination>${log.destination}</destination>
+        <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
+            <providers>
+                <pattern>
+                    <pattern>
+                        {
+                        "severity": "%level",
+                        "service": "${spring.application.name:-}",
+                        "trace": "%X{X-B3-TraceId:-}",
+                        "span": "%X{X-B3-SpanId:-}",
+                        "parent": "%X{X-B3-ParentSpanId:-}",
+                        "exportable": "%X{X-Span-Export:-}",
+                        "pid": "${PID:-}",
+                        "thread": "%thread",
+                        "class": "%logger{40}",
+                        "rest": "%message"
+                        }
+                    </pattern>
+                </pattern>
+            </providers>
+        </encoder>
+    </appender>
+
+    <logger name="org.springframework" level="INFO"/>
+    <logger name="com.usoftchina.saas" level="INFO"/>
+
+    <springProfile name="dev">
+        <root level="INFO">
+            <appender-ref ref="CONSOLE_APPENDER"/>
+        </root>
+    </springProfile>
+
+    <springProfile name="test">
+        <root level="INFO">
+            <appender-ref ref="CONSOLE_APPENDER"/>
+            <appender-ref ref="ROOT_APPENDER"/>
+        </root>
+    </springProfile>
+
+    <springProfile name="docker">
+        <logger name="org.springframework" level="WARN"/>
+        <logger name="com.usoftchina.saas" level="WARN"/>
+        <root level="WARN">
+            <appender-ref ref="CONSOLE_APPENDER"/>
+            <appender-ref ref="JSON_APPENDER"/>
+        </root>
+    </springProfile>
+
+    <springProfile name="docker-dev">
+        <root level="INFO">
+            <appender-ref ref="CONSOLE_APPENDER"/>
+        </root>
+    </springProfile>
 
 
 </configuration>
 </configuration>

+ 219 - 0
base-servers/file/file-server/src/main/resources/mapper/FileInfoMapper.xml

@@ -0,0 +1,219 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.usoftchina.saas.file.mapper.FileInfoMapper">
+
+    <insert id="insert" parameterType="com.usoftchina.saas.file.po.FileInfo"
+            useGeneratedKeys="true" keyProperty="id">
+        insert into f_fileinfo(folder_id,name,full_path,mime,ext,type,size,company_id,
+        creator_id,create_time,updater_id,update_time,deleted,deleter_id,delete_time)
+        values (#{folderId,jdbcType=BIGINT},#{name,jdbcType=VARCHAR},#{fullPath,jdbcType=VARCHAR},
+        #{mime,jdbcType=VARCHAR},#{ext,jdbcType=VARCHAR},#{type,jdbcType=VARCHAR},#{size,jdbcType=BIGINT},
+        #{companyId,jdbcType=BIGINT},#{creatorId,jdbcType=BIGINT},#{createTime,jdbcType=TIMESTAMP},
+        #{updaterId,jdbcType=BIGINT},#{updateTime,jdbcType=TIMESTAMP},#{deleted,jdbcType=BOOLEAN},
+        #{deleterId,jdbcType=BIGINT},#{deleteTime,jdbcType=TIMESTAMP})
+    </insert>
+    <insert id="insertSelective" parameterType="com.usoftchina.saas.file.po.FileInfo"
+            useGeneratedKeys="true" keyProperty="id">
+        insert into ac_account
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="folderId != null">
+                folder_id,
+            </if>
+            <if test="name != null">
+                name,
+            </if>
+            <if test="fullPath != null">
+                full_path,
+            </if>
+            <if test="mime != null">
+                mime,
+            </if>
+            <if test="ext != null">
+                ext,
+            </if>
+            <if test="type != null">
+                type,
+            </if>
+            <if test="size != null">
+                size,
+            </if>
+            <if test="companyId != null">
+                company_id,
+            </if>
+            <if test="creatorId != null">
+                creator_id,
+            </if>
+            <if test="createTime != null">
+                create_time,
+            </if>
+            <if test="updaterId != null">
+                updater_id,
+            </if>
+            <if test="updateTime != null">
+                update_time,
+            </if>
+            <if test="deleted != null">
+                deleted,
+            </if>
+            <if test="deleterId != null">
+                deleter_id,
+            </if>
+            <if test="deleteTime != null">
+                delete_time,
+            </if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="folderId != null">
+                #{folder_id,jdbcType=BIGINT},
+            </if>
+            <if test="name != null">
+                #{name,jdbcType=VARCHAR},
+            </if>
+            <if test="fullPath != null">
+                #{full_path,jdbcType=VARCHAR},
+            </if>
+            <if test="mime != null">
+                #{mime,jdbcType=VARCHAR},
+            </if>
+            <if test="ext != null">
+                #{ext,jdbcType=VARCHAR},
+            </if>
+            <if test="type != null">
+                #{type,jdbcType=VARCHAR},
+            </if>
+            <if test="size != null">
+                #{size,jdbcType=BIGINT},
+            </if>
+            <if test="companyId != null">
+                #{company_id,jdbcType=BIGINT},
+            </if>
+            <if test="creatorId != null">
+                #{creatorId,jdbcType=BIGINT},
+            </if>
+            <if test="createTime != null">
+                #{createTime,jdbcType=TIMESTAMP},
+            </if>
+            <if test="updaterId != null">
+                #{updaterId,jdbcType=BIGINT},
+            </if>
+            <if test="updateTime != null">
+                #{updateTime,jdbcType=TIMESTAMP},
+            </if>
+            <if test="deleted != null">
+                #{deleted,jdbcType=BOOLEAN},
+            </if>
+            <if test="deleterId != null">
+                #{deleter_id,jdbcType=BIGINT},
+            </if>
+            <if test="deleteTime != null">
+                #{delete_time,jdbcType=TIMESTAMP},
+            </if>
+        </trim>
+    </insert>
+    <update id="updateByPrimaryKey" parameterType="com.usoftchina.saas.file.po.FileInfo">
+        update f_fileinfo set
+        folder_id=#{folderId,jdbcType=BIGINT},name=#{name,jdbcType=VARCHAR},full_path=#{fullPath,jdbcType=VARCHAR},
+        mime=#{mime,jdbcType=VARCHAR},ext=#{ext,jdbcType=VARCHAR},type=#{type,jdbcType=VARCHAR},
+        size=#{size,jdbcType=BIGINT},company_id=#{companyId,jdbcType=BIGINT},
+        creator_id=#{creatorId,jdbcType=BIGINT},create_time=#{createTime,jdbcType=TIMESTAMP},
+        updater_id=#{updaterId,jdbcType=BIGINT},update_time=#{updateTime,jdbcType=TIMESTAMP},
+        deleted=#{deleted,jdbcType=BOOLEAN},deleter_id=#{deleterId,jdbcType=BIGINT},
+        delete_time=#{deleteTime,jdbcType=TIMESTAMP} where id=#{id,jdbcType=BIGINT}
+    </update>
+    <update id="updateByPrimaryKeySelective" parameterType="com.usoftchina.saas.file.po.FileInfo">
+        update f_fileinfo
+        <set>
+            <if test="folderId != null">
+                folder_id=#{folderId,jdbcType=BIGINT},
+            </if>
+            <if test="name != null">
+                name=#{name,jdbcType=VARCHAR},
+            </if>
+            <if test="fullPath != null">
+                full_path=#{fullPath,jdbcType=VARCHAR},
+            </if>
+            <if test="mime != null">
+                mime=#{mime,jdbcType=VARCHAR},
+            </if>
+            <if test="ext != null">
+                ext=#{ext,jdbcType=VARCHAR},
+            </if>
+            <if test="type != null">
+                type=#{type,jdbcType=VARCHAR},
+            </if>
+            <if test="size != null">
+                size=#{size,jdbcType=BIGINT},
+            </if>
+            <if test="companyId != null">
+                company_id=#{companyId,jdbcType=BIGINT},
+            </if>
+            <if test="creatorId != null">
+                creator_id=#{creatorId,jdbcType=BIGINT},
+            </if>
+            <if test="createTime != null">
+                create_time=#{createTime,jdbcType=TIMESTAMP},
+            </if>
+            <if test="updaterId != null">
+                updater_id=#{updaterId,jdbcType=BIGINT},
+            </if>
+            <if test="updateTime != null">
+                update_time=#{updateTime,jdbcType=TIMESTAMP})
+            </if>
+            <if test="deleted != null">
+                deleted=#{deleted,jdbcType=BOOLEAN},
+            </if>
+            <if test="deleterId != null">
+                deleter_id=#{deleterId,jdbcType=BIGINT},
+            </if>
+            <if test="deleteTime != null">
+                delete_time=#{deleteTime,jdbcType=TIMESTAMP})
+            </if>
+        </set>
+        where id=#{id,jdbcType=BIGINT}
+    </update>
+    <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
+        delete from f_fileinfo where id=#{id}
+    </delete>
+
+    <resultMap id="BaseResultMap" type="com.usoftchina.saas.file.po.FileInfo">
+        <id column="id" jdbcType="BIGINT" property="id"/>
+        <result column="folder_id" jdbcType="BIGINT" property="folderId"/>
+        <result column="name" jdbcType="VARCHAR" property="name"/>
+        <result column="full_path" jdbcType="VARCHAR" property="fullPath"/>
+        <result column="mime" jdbcType="VARCHAR" property="mime"/>
+        <result column="ext" jdbcType="VARCHAR" property="ext"/>
+        <result column="type" jdbcType="VARCHAR" property="type"/>
+        <result column="size" jdbcType="BIGINT" property="size"/>
+        <result column="company_id" jdbcType="INTEGER" property="companyId"/>
+        <result column="creator_id" jdbcType="BIGINT" property="creatorId"/>
+        <result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
+        <result column="updater_id" jdbcType="BIGINT" property="updaterId"/>
+        <result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
+        <result column="deleted" jdbcType="BOOLEAN" property="deleted"/>
+        <result column="deleter_id" jdbcType="BIGINT" property="deleterId"/>
+        <result column="delete_time" jdbcType="TIMESTAMP" property="deleteTime"/>
+    </resultMap>
+    <sql id="baseColumns">
+        f_fileinfo.id,f_fileinfo.folder_id,f_fileinfo.name,f_fileinfo.full_path,f_fileinfo.mime,f_fileinfo.ext,
+        f_fileinfo.type,f_fileinfo.size,f_fileinfo.company_id,f_fileinfo.creator_id,f_fileinfo.create_time,
+        f_fileinfo.updater_id,f_fileinfo.update_time,f_fileinfo.deleted,f_fileinfo.deleter_id,f_fileinfo.delete_time
+    </sql>
+
+    <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
+        select <include refid="baseColumns"/> from f_fileinfo where id=#{id}
+    </select>
+    <select id="selectByFullPath" parameterType="java.lang.String" resultMap="BaseResultMap">
+        select <include refid="baseColumns"/> from f_fileinfo where full_path=#{fullPath}
+    </select>
+    <select id="selectByFolderId" parameterType="java.lang.Long" resultMap="BaseResultMap">
+        select <include refid="baseColumns"/> from f_fileinfo where folder_id=#{folderId} order by id
+    </select>
+    <select id="selectVisibleByFolderId" parameterType="java.lang.Long" resultMap="BaseResultMap">
+        select <include refid="baseColumns"/> from f_fileinfo where folder_id=#{folderId}
+        and deleted=false order by id
+    </select>
+    <select id="selectDeletedByFolderId" parameterType="java.lang.Long" resultMap="BaseResultMap">
+        select <include refid="baseColumns"/> from f_fileinfo where folder_id=#{folderId}
+        and deleted=true order by id
+    </select>
+</mapper>

+ 0 - 5
base-servers/file/file-server/src/main/resources/mapper/FilePoMapper.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
-<mapper namespace="com.usoftchina.saas.file.mapper.FileMapper">
-
-</mapper>

+ 104 - 0
base-servers/file/file-server/src/test/java/com/usoftchina/saas/file/controller/FileControllerTest.java

@@ -0,0 +1,104 @@
+package com.usoftchina.saas.file.controller;
+
+import com.usoftchina.saas.base.Result;
+import com.usoftchina.saas.file.dto.FileInfoDTO;
+import com.usoftchina.saas.file.dto.FolderDTO;
+import com.usoftchina.saas.file.util.RandomTextFile;
+import com.usoftchina.saas.test.BaseControllerTest;
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MvcResult;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class FileControllerTest extends BaseControllerTest {
+
+    public static Long folder_1;
+    public static Long folder_1_1;
+    public static Long folder_1_1_1;
+    public static Long file_1_1_1_1;
+    public static Long folder_1_2;
+    public static Long folder_1_2_1;
+
+    private FolderDTO newFolder(Long folderId, String name) throws Exception{
+        MvcResult mvcResult = mockMvc.perform(post("/folder")
+                .param("folderId", String.valueOf(folderId))
+                .param("name", name))
+                .andExpect(isSuccess())
+                .andReturn();
+        Result<FolderDTO> result = result(mvcResult, FolderDTO.class);
+        return result.getData();
+    }
+
+    @Test
+    public void testA_folder() throws Exception {
+        folder_1 = newFolder(0L, "项目").getId();
+
+        folder_1_1 = newFolder(folder_1, "采购").getId();
+        folder_1_1_1 = newFolder(folder_1_1, "报表").getId();
+
+        folder_1_2 = newFolder(folder_1, "销售").getId();
+        folder_1_2_1 = newFolder(folder_1_2, "报表").getId();
+    }
+
+    @Test
+    public void testB_upload() throws Exception {
+        MockMultipartFile file = new MockMultipartFile("file", "test.txt",
+                "text/plain", RandomStringUtils.random(32).getBytes());
+        MvcResult mvcResult = mockMvc.perform(multipart("/upload")
+                .file(file)
+                .param("folderId", String.valueOf(folder_1_1_1)))
+                .andExpect(isSuccess())
+                .andReturn();
+        Result<FileInfoDTO> result = result(mvcResult, FileInfoDTO.class);
+        file_1_1_1_1 = result.getData().getId();
+    }
+
+    @Test
+    public void testE_getFileInfo() throws Exception {
+        mockMvc.perform(get("/info/" + file_1_1_1_1)).andExpect(isSuccess());
+    }
+
+    @Test
+    public void testF_listFiles() throws Exception {
+        mockMvc.perform(get("/list")).andExpect(isSuccess());
+    }
+
+    @Test
+    public void testG_delete() throws Exception {
+        mockMvc.perform(get("/delete/" + folder_1)).andExpect(isSuccess());
+    }
+
+    @Test
+    public void testH_restore() throws Exception {
+        mockMvc.perform(get("/restore/" + folder_1)).andExpect(isSuccess());
+    }
+
+    @Test
+    public void testI_move() throws Exception {
+        mockMvc.perform(get("/move")
+                .param("fileId", String.valueOf(file_1_1_1_1))
+                .param("folderId", String.valueOf(folder_1_2_1)))
+                .andExpect(isSuccess());
+    }
+
+    /**
+     * 最后清除测试数据
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testZ_purge() throws Exception {
+        mockMvc.perform(get("/delete/" + folder_1)
+                .param("purge", "true"))
+                .andExpect(isSuccess());
+    }
+
+}

+ 50 - 0
base-servers/file/file-server/src/test/java/com/usoftchina/saas/file/util/RandomTextFile.java

@@ -0,0 +1,50 @@
+package com.usoftchina.saas.file.util;
+
+import org.apache.commons.lang.RandomStringUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+/**
+ * @author yingp
+ * @date 2018/11/9
+ */
+public class RandomTextFile {
+    private String text;
+
+    private InputStream inputStream;
+
+    private long fileSize;
+
+    private String fileExtName = "text";
+
+    public RandomTextFile() {
+        this(RandomStringUtils.random(30));
+    }
+
+    public RandomTextFile(String text) {
+        this.text = text;
+        this.fileSize = text.getBytes().length;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public InputStream getInputStream() {
+        this.inputStream = new ByteArrayInputStream(text.getBytes());
+        return inputStream;
+    }
+
+    public long getFileSize() {
+        return fileSize;
+    }
+
+    public String getFileExtName() {
+        return fileExtName;
+    }
+
+    public byte[] toByte() {
+        return this.text.getBytes();
+    }
+}

+ 5 - 7
framework/core/src/main/java/com/usoftchina/saas/exception/ExceptionCode.java

@@ -43,13 +43,11 @@ public enum ExceptionCode implements BaseExceptionCode {
     MISSING_PERMISSIONS(53030, "权限缺失"),
     MISSING_PERMISSIONS(53030, "权限缺失"),
 
 
     // 文件相关
     // 文件相关
-    FOLDER_NULL(55000, "文件夹为空"),
-    FOLDER_NAME_EMPTY(55001, "文件夹名称为空"),
-    FOLDER_PARENT_NULL(55002, "父文件夹为空"),
-
-    FILE_NULL(55500, "文件为空"),
-    FILE_NAME_EMPTY(55501, "文件名称为空"),
-    FILE_FOLDER_NULL(55502, "文件夹为空"),
+    FOLDER_NOT_EXISTS(55000, "文件夹不存在"),
+    FOLDER_INVALID(55001, "无效文件夹"),
+    FOLDER_NAME_EMPTY(55002, "文件夹名称为空"),
+    FILE_NOT_EXISTS(55500, "文件不存在"),
+    FILE_NOT_FOLDER(55501, "不是文件夹"),
 
 
     TURNIN_EXIST(60000,"已入库"),
     TURNIN_EXIST(60000,"已入库"),
     TURNINNUM_NOT_EXIST(60001,"无可转数"),
     TURNINNUM_NOT_EXIST(60001,"无可转数"),

+ 8 - 0
framework/core/src/main/java/com/usoftchina/saas/utils/BizAssert.java

@@ -21,6 +21,14 @@ public class BizAssert {
         }
         }
     }
     }
 
 
+    public static void equals(Object object1, Object object2, BaseExceptionCode code) {
+        boolean equals = (null == object1 && null == object2) ||
+                (null != object1 && object1.equals(object2));
+        if (!equals) {
+            throw new BizException(code);
+        }
+    }
+
     public static void hasText(String text, BaseExceptionCode code) {
     public static void hasText(String text, BaseExceptionCode code) {
         if (!StringUtils.hasText(text)) {
         if (!StringUtils.hasText(text)) {
             throw new BizException(code);
             throw new BizException(code);

+ 10 - 0
framework/core/src/main/java/com/usoftchina/saas/utils/StringUtils.java

@@ -15,4 +15,14 @@ public abstract class StringUtils extends org.springframework.util.StringUtils{
     public static String nullIf(String target, String nullValue) {
     public static String nullIf(String target, String nullValue) {
         return (null == target || target.isEmpty()) ? nullValue : target;
         return (null == target || target.isEmpty()) ? nullValue : target;
     }
     }
+
+    /**
+     * 为空取空字符串
+     *
+     * @param target
+     * @return
+     */
+    public static String nullIf(String target) {
+        return nullIf(target, "");
+    }
 }
 }

+ 23 - 0
framework/test-starter/src/main/java/com.usoftchina.saas.test/BaseControllerTest.java

@@ -5,11 +5,13 @@ import com.usoftchina.saas.base.Result;
 import com.usoftchina.saas.utils.JsonUtils;
 import com.usoftchina.saas.utils.JsonUtils;
 import org.junit.Before;
 import org.junit.Before;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpMethod;
 import org.springframework.http.MediaType;
 import org.springframework.http.MediaType;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MvcResult;
 import org.springframework.test.web.servlet.MvcResult;
 import org.springframework.test.web.servlet.ResultMatcher;
 import org.springframework.test.web.servlet.ResultMatcher;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder;
 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
 import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 import org.springframework.web.context.WebApplicationContext;
 import org.springframework.web.context.WebApplicationContext;
@@ -56,6 +58,27 @@ public abstract class BaseControllerTest {
         return MockMvcRequestBuilders.post(urlTemplate, uriVars);
         return MockMvcRequestBuilders.post(urlTemplate, uriVars);
     }
     }
 
 
+    /**
+     * 文件上传
+     *
+     * @param urlTemplate
+     * @param uriVars
+     * @return
+     */
+    public static MockMultipartHttpServletRequestBuilder multipart(String urlTemplate, Object... uriVars) {
+        return MockMvcRequestBuilders.multipart(urlTemplate, uriVars);
+    }
+
+    /**
+     * 请求
+     *
+     * @param urlTemplate
+     * @return
+     */
+    public static MockHttpServletRequestBuilder request(HttpMethod method, String urlTemplate, Object... uriVars) {
+        return MockMvcRequestBuilders.request(method, urlTemplate, uriVars);
+    }
+
     /**
     /**
      * POST Payload方式请求,使用@RequestBody注解情况下
      * POST Payload方式请求,使用@RequestBody注解情况下
      *
      *

+ 13 - 0
pom.xml

@@ -38,9 +38,11 @@
         <swagger.version>2.7.0</swagger.version>
         <swagger.version>2.7.0</swagger.version>
         <feign.form.version>3.0.3</feign.form.version>
         <feign.form.version>3.0.3</feign.form.version>
         <commons.fileupload.version>1.3.3</commons.fileupload.version>
         <commons.fileupload.version>1.3.3</commons.fileupload.version>
+        <commons.compress.version>1.18</commons.compress.version>
         <docker.repository>192.168.253.3:4000</docker.repository>
         <docker.repository>192.168.253.3:4000</docker.repository>
         <docker.registry.name>saas</docker.registry.name>
         <docker.registry.name>saas</docker.registry.name>
         <guava.version>18.0</guava.version>
         <guava.version>18.0</guava.version>
+        <fastdfs.client.version>1.26.3</fastdfs.client.version>
     </properties>
     </properties>
 
 
     <repositories>
     <repositories>
@@ -323,11 +325,22 @@
                 <artifactId>fastjson</artifactId>
                 <artifactId>fastjson</artifactId>
                 <version>${fastjson.version}</version>
                 <version>${fastjson.version}</version>
             </dependency>
             </dependency>
+            <dependency>
+                <groupId>com.github.tobato</groupId>
+                <artifactId>fastdfs-client</artifactId>
+                <version>${fastdfs.client.version}</version>
+            </dependency>
+            <!-- utils -->
             <dependency>
             <dependency>
                 <groupId>com.google.guava</groupId>
                 <groupId>com.google.guava</groupId>
                 <artifactId>guava</artifactId>
                 <artifactId>guava</artifactId>
                 <version>${guava.version}</version>
                 <version>${guava.version}</version>
             </dependency>
             </dependency>
+            <dependency>
+                <groupId>org.apache.commons</groupId>
+                <artifactId>commons-compress</artifactId>
+                <version>${commons.compress.version}</version>
+            </dependency>
         </dependencies>
         </dependencies>
     </dependencyManagement>
     </dependencyManagement>
 
 

+ 21 - 0
script/mysql/init/file.sql

@@ -0,0 +1,21 @@
+CREATE DATABASE `saas_file` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
+use `saas_file`;
+
+create table `f_fileinfo` (
+  id int unsigned primary key not null auto_increment,
+  folder_id int comment '文件夹',
+  name varchar(100) not null comment '名称',
+  full_path varchar(300) comment '保存路径',
+  mime varchar(100) comment 'mime',
+  ext varchar(100) comment '扩展名',
+  type varchar(10) comment '类型',
+  size int comment '大小',
+  company_id int,
+  creator_id int,
+  create_time datetime,
+  updater_id int,
+  update_time datetime,
+  deleted boolean,
+  deleter_id int,
+  delete_time datetime
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文件';