Browse Source

Merge remote-tracking branch 'origin/dev' into dev

heqinwei 7 years ago
parent
commit
f82c32ff1e
84 changed files with 2184 additions and 623 deletions
  1. 3 2
      README.md
  2. 2 2
      applications/document/document-server/src/main/java/com/usoftchina/saas/document/controller/FundinouttypeController.java
  3. 1 1
      applications/document/document-server/src/main/java/com/usoftchina/saas/document/mapper/FundinouttypeMapper.java
  4. 1 1
      applications/document/document-server/src/main/java/com/usoftchina/saas/document/service/FundinouttypeService.java
  5. 2 2
      applications/document/document-server/src/main/java/com/usoftchina/saas/document/service/impl/FundinouttypeServiceImpl.java
  6. 8 0
      applications/document/document-server/src/main/resources/mapper/FundinouttypeMapper.xml
  7. 1 1
      applications/document/document-server/src/main/resources/mapper/VendorMapper.xml
  8. 2 0
      applications/sale/sale-server/src/main/java/com/usoftchina/saas/sale/po/SaleList.java
  9. 2 2
      applications/sale/sale-server/src/main/java/com/usoftchina/saas/sale/service/impl/SaleServiceImpl.java
  10. 1 0
      applications/sale/sale-server/src/main/resources/mapper/SaleListMapper.xml
  11. 4 2
      base-servers/account/account-server/src/main/resources/mapper/AccountMapper.xml
  12. 4 2
      base-servers/account/account-server/src/main/resources/mapper/CompanyMapper.xml
  13. 4 0
      base-servers/file/README.md
  14. 9 10
      base-servers/file/file-api/src/main/java/com/usoftchina/saas/file/api/FileApi.java
  15. 37 0
      base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/BaseFileInfo.java
  16. 0 60
      base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/BaseFolder.java
  17. 0 179
      base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/FileDTO.java
  18. 109 0
      base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/FileInfoDTO.java
  19. 0 31
      base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/FileListDTO.java
  20. 4 9
      base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/FolderDTO.java
  21. 17 0
      base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/FolderSaveDTO.java
  22. 0 18
      base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/FolderToSaveDTO.java
  23. 31 11
      base-servers/file/file-server/pom.xml
  24. 10 2
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/FileApplication.java
  25. 12 0
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/constant/FileConstant.java
  26. 50 0
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/constant/FileType.java
  27. 320 11
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/controller/FileController.java
  28. 46 0
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/mapper/FileInfoMapper.java
  29. 0 11
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/mapper/FileMapper.java
  30. 0 108
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/po/File.java
  31. 304 0
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/po/FileInfo.java
  32. 30 0
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/service/FileInfoService.java
  33. 0 12
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/service/FileService.java
  34. 36 0
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/service/impl/FileInfoServiceImpl.java
  35. 0 16
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/service/impl/FileServiceImpl.java
  36. 135 0
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/util/FileTypeUtils.java
  37. 58 7
      base-servers/file/file-server/src/main/resources/application.yml
  38. 0 15
      base-servers/file/file-server/src/main/resources/banner.txt
  39. 12 0
      base-servers/file/file-server/src/main/resources/config/application-docker-dev.yml
  40. 10 0
      base-servers/file/file-server/src/main/resources/config/application-docker.yml
  41. 58 9
      base-servers/file/file-server/src/main/resources/logback-spring.xml
  42. 219 0
      base-servers/file/file-server/src/main/resources/mapper/FileInfoMapper.xml
  43. 0 5
      base-servers/file/file-server/src/main/resources/mapper/FilePoMapper.xml
  44. 104 0
      base-servers/file/file-server/src/test/java/com/usoftchina/saas/file/controller/FileControllerTest.java
  45. 50 0
      base-servers/file/file-server/src/test/java/com/usoftchina/saas/file/util/RandomTextFile.java
  46. 5 7
      framework/core/src/main/java/com/usoftchina/saas/exception/ExceptionCode.java
  47. 8 0
      framework/core/src/main/java/com/usoftchina/saas/utils/BizAssert.java
  48. 10 0
      framework/core/src/main/java/com/usoftchina/saas/utils/StringUtils.java
  49. 23 0
      framework/test-starter/src/main/java/com.usoftchina.saas.test/BaseControllerTest.java
  50. 195 0
      frontend/saas-web/app/Application.scss
  51. 1 1
      frontend/saas-web/app/view/core/form/ConDateField.js
  52. 1 1
      frontend/saas-web/app/view/core/form/FormPanel.js
  53. 11 13
      frontend/saas-web/app/view/core/form/FormPanel.scss
  54. 2 2
      frontend/saas-web/app/view/core/form/field/DetailGridField.js
  55. 19 1
      frontend/saas-web/app/view/core/form/field/DetailGridField.scss
  56. 4 4
      frontend/saas-web/app/view/core/query/QueryFormPanel.js
  57. 2 5
      frontend/saas-web/app/view/core/query/QueryGridPanel.js
  58. 5 2
      frontend/saas-web/app/view/core/query/QueryPanel.js
  59. 53 0
      frontend/saas-web/app/view/core/query/QueryPanel.scss
  60. 1 1
      frontend/saas-web/app/view/document/kind/Kind.js
  61. 29 14
      frontend/saas-web/app/view/document/vendor/BasePanel.js
  62. 5 23
      frontend/saas-web/app/view/main/Main.scss
  63. 11 8
      frontend/saas-web/app/view/main/MainController.js
  64. 1 1
      frontend/saas-web/app/view/main/MainModel.js
  65. 5 5
      frontend/saas-web/app/view/main/Navigation.scss
  66. 1 1
      frontend/saas-web/app/view/money/othreceipts/FormPanel.js
  67. 2 2
      frontend/saas-web/app/view/money/othspendings/FormPanel.js
  68. 24 3
      frontend/saas-web/app/view/purchase/purchase/FormPanel.js
  69. BIN
      frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-中粗-简.ttf
  70. BIN
      frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-中粗-繁.ttf
  71. BIN
      frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-中黑-简.ttf
  72. BIN
      frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-中黑-繁.ttf
  73. BIN
      frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-准-简.ttf
  74. BIN
      frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-准-繁.ttf
  75. BIN
      frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-极细-简.ttf
  76. BIN
      frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-极细-繁.ttf
  77. BIN
      frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-纤细-简.ttf
  78. BIN
      frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-纤细-繁.ttf
  79. BIN
      frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-细-简.ttf
  80. BIN
      frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-细-繁.ttf
  81. 1 0
      frontend/saas-web/packages/local/theme-default/sass/etc/all.scss
  82. 35 0
      frontend/saas-web/packages/local/theme-default/sass/etc/fontset.scss
  83. 13 0
      pom.xml
  84. 21 0
      script/mysql/init/file.sql

+ 3 - 2
README.md

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

+ 2 - 2
applications/document/document-server/src/main/java/com/usoftchina/saas/document/controller/FundinouttypeController.java

@@ -34,8 +34,8 @@ public class FundinouttypeController {
     }
 
     @GetMapping("/getCombo")
-    public Result getCombo(){
-        List<ComboDTO> comboDTOList = fundinouttypeService.getCombo();
+    public Result getCombo(String condition){
+        List<ComboDTO> comboDTOList = fundinouttypeService.getCombo(condition);
         return Result.success(comboDTOList);
     }
 }

+ 1 - 1
applications/document/document-server/src/main/java/com/usoftchina/saas/document/mapper/FundinouttypeMapper.java

@@ -23,7 +23,7 @@ public interface FundinouttypeMapper extends CommonBaseMapper<Fundinouttype> {
 
     int selectCountByName(@Param("name") String name, @Param("companyId") Long companyId, @Param("id") Long id);
 
-    List<ComboDTO> getCombo(@Param("companyId") Long companyId);
+    List<ComboDTO> getCombo(@Param("companyId") Long companyId,@Param("condition") String condition);
 
     List<Fundinouttype> selectAll(@Param("companyId") Long companyId);
 }

+ 1 - 1
applications/document/document-server/src/main/java/com/usoftchina/saas/document/service/FundinouttypeService.java

@@ -11,7 +11,7 @@ import java.util.List;
 
 public interface FundinouttypeService extends CommonBaseService<FundinouttypeMapper, Fundinouttype> {
 
-    List<ComboDTO> getCombo();
+    List<ComboDTO> getCombo(String condition);
 
     List<Fundinouttype> selectAll();
 

+ 2 - 2
applications/document/document-server/src/main/java/com/usoftchina/saas/document/service/impl/FundinouttypeServiceImpl.java

@@ -89,8 +89,8 @@ public class FundinouttypeServiceImpl extends CommonBaseServiceImpl<Fundinouttyp
     }
 
     @Override
-    public List<ComboDTO> getCombo() {
-        return getMapper().getCombo(BaseContextHolder.getCompanyId());
+    public List<ComboDTO> getCombo(String condition) {
+        return getMapper().getCombo(BaseContextHolder.getCompanyId(),condition);
     }
 
     @Override

+ 8 - 0
applications/document/document-server/src/main/resources/mapper/FundinouttypeMapper.xml

@@ -158,5 +158,13 @@
   </select>
     <select id="getCombo" resultType="com.usoftchina.saas.commons.dto.ComboDTO">
         SELECT FT_NAME display,FT_NAME value FROM FUNDINOUTTYPE
+        <where>
+          <if test="companyId!=null and companyId!=0">
+            and  COMPANYID=#{companyId}
+          </if>
+          <if test="condition!=null">
+            and ft_kind=#{condition,jdbcType=VARCHAR}
+          </if>
+        </where>
     </select>
 </mapper>

+ 1 - 1
applications/document/document-server/src/main/resources/mapper/VendorMapper.xml

@@ -142,7 +142,7 @@
         #{updateTime,jdbcType=TIMESTAMP}, #{ve_text1,jdbcType=VARCHAR}, #{ve_text2,jdbcType=VARCHAR},
         #{ve_text3,jdbcType=VARCHAR}, #{ve_text4,jdbcType=VARCHAR}, #{ve_text5,jdbcType=VARCHAR},
         #{ve_payamount,jdbcType=DOUBLE}, #{ve_leftamount,jdbcType=DOUBLE}, #{ve_beginym,jdbcType=INTEGER},
-        #{ve_preamount,jdbcTyp=DOUBLE}
+        #{ve_preamount,jdbcType=DOUBLE}
         )
     </insert>
     <insert id="insertSelective" parameterType="com.usoftchina.saas.document.entities.Vendor" >

+ 2 - 0
applications/sale/sale-server/src/main/java/com/usoftchina/saas/sale/po/SaleList.java

@@ -48,6 +48,8 @@ public class SaleList implements Serializable{
 
     private Date sa_recorddate;
 
+    private Date sa_date;
+
     private Integer companyid;
 
     private Integer updaterId;

+ 2 - 2
applications/sale/sale-server/src/main/java/com/usoftchina/saas/sale/service/impl/SaleServiceImpl.java

@@ -472,8 +472,8 @@ public class SaleServiceImpl implements SaleService{
             Map<String, Object> warehouse = getWareHouseByCode(saleDetail.getSd_prodcode());
             if (null != warehouse) {
                 prodIODetail.setPd_whid(warehouse.get("pr_whid") == null ? 0 : Integer.valueOf(warehouse.get("pr_whid").toString()));
-                prodIODetail.setPd_whcode(String.valueOf(warehouse.get("pr_whcode")));
-                prodIODetail.setPd_whname(String.valueOf(warehouse.get("pr_whname")));
+                prodIODetail.setPd_whcode(warehouse.get("pr_whcode") == null ? null : warehouse.get("pr_whcode").toString());
+                prodIODetail.setPd_whname(warehouse.get("pr_whname") == null ? null : warehouse.get("pr_whname").toString());
             }
             prodIODetailMapper.insertSelective(prodIODetail);
             //更新已转数

+ 1 - 0
applications/sale/sale-server/src/main/resources/mapper/SaleListMapper.xml

@@ -23,6 +23,7 @@
         <result column="companyid" property="companyid" jdbcType="INTEGER" />
         <result column="updaterId" property="updaterId" jdbcType="INTEGER" />
         <result column="updatetime" property="updatetime" jdbcType="TIMESTAMP" />
+        <result column="sa_date" property="sa_date" jdbcType="TIMESTAMP" />
         <result column="sa_text1" property="sa_text1" jdbcType="VARCHAR" />
         <result column="sa_text2" property="sa_text2" jdbcType="VARCHAR" />
         <result column="sa_text3" property="sa_text3" jdbcType="VARCHAR" />

+ 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.updater_id,ac_account.update_time
     </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)
         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},
         #{creatorId,jdbcType=BIGINT}, #{createTime,jdbcType=TIMESTAMP}, #{updaterId,jdbcType=BIGINT}, #{updateTime,jdbcType=TIMESTAMP})
     </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
         <trim prefix="(" suffix=")" suffixOverrides=",">
             <if test="username != null">

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

@@ -19,13 +19,15 @@
     <sql id="baseColumns">
         id,name,business_code,address,creator_id,create_time,updater_id,update_time
     </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)
         values (#{name,jdbcType=VARCHAR}, #{businessCode,jdbcType=VARCHAR},
         #{address,jdbcType=VARCHAR}, #{logoUrl,jdbcType=VARCHAR}, #{creatorId,jdbcType=BIGINT},
         #{createTime,jdbcType=TIMESTAMP}, #{updaterId,jdbcType=BIGINT}, #{updateTime,jdbcType=TIMESTAMP})
     </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
         <trim prefix="(" suffix=")" suffixOverrides=",">
             <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;
 
 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.FolderToSaveDTO;
+import com.usoftchina.saas.file.dto.FolderSaveDTO;
 import feign.codec.Encoder;
 import feign.form.spring.SpringFormEncoder;
 import org.springframework.cloud.openfeign.FeignClient;
@@ -20,7 +19,7 @@ import org.springframework.web.multipart.MultipartFile;
  * @author yingp
  * @date 2018/9/29
  */
-@FeignClient(name = "gateway-server", configuration = FileApi.MultipartSupportConfig.class)
+@FeignClient(name = "file-server", configuration = FileApi.MultipartSupportConfig.class)
 public interface FileApi {
 
     @Configuration
@@ -37,8 +36,8 @@ public interface FileApi {
      * @param newFolder 待保存文件夹
      * @return
      */
-    @PostMapping(value = "/api/file/folder")
-    Result<FolderDTO> folder(@RequestBody FolderToSaveDTO newFolder);
+    @PostMapping(value = "/folder")
+    Result<FolderDTO> folder(FolderSaveDTO newFolder);
 
     /**
      * 根据id查看文件详情
@@ -46,8 +45,8 @@ public interface FileApi {
      * @param id
      * @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
      * @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 + '\'' +
-                '}';
-    }
-}

+ 109 - 0
base-servers/file/file-dto/src/main/java/com/usoftchina/saas/file/dto/FileInfoDTO.java

@@ -0,0 +1,109 @@
+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 FileInfoDTO extends BaseFileInfo implements Serializable {
+    @ApiModelProperty(value = "id")
+    private Long id;
+
+    /**
+     * 链接
+     */
+    @ApiModelProperty(value = "文件path")
+    private String fullPath;
+
+    /**
+     * 类型
+     */
+    @ApiModelProperty(value = "文件mime类型")
+    private String mime;
+
+    /**
+     * 扩展名 (没有.)
+     */
+    @ApiModelProperty(value = "扩展名")
+    private String ext;
+
+    /**
+     * 大小
+     */
+    @ApiModelProperty(value = "大小")
+    private Long size;
+    /**
+     * 文件类型
+     */
+    @ApiModelProperty(value = "文件类型 IMAGE/TEXT/VIDEO/AUDIO/DOC/DIR/COMPRESS/OTHER", example = "IMAGE/TEXT/VIDEO/AUDIO/DOC/DIR/COMPRESS/OTHER")
+    private String type;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getFullPath() {
+        return fullPath;
+    }
+
+    public void setFullPath(String fullPath) {
+        this.fullPath = fullPath;
+    }
+
+    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 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;
+    }
+
+    @Override
+    public String toString() {
+        return "FileInfoDTO{" +
+                "id=" + id +
+                ", folderId=" + folderId +
+                ", name=" + name +
+                ", fullPath='" + fullPath + '\'' +
+                ", mime='" + mime + '\'' +
+                ", ext='" + ext + '\'' +
+                ", size=" + size +
+                ", type='" + type + '\'' +
+                ", name='" + name + '\'' +
+                ", folderId=" + folderId +
+                '}';
+    }
+}

+ 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;
 
-import io.swagger.annotations.ApiModelProperty;
-
 import java.io.Serializable;
 
 /**
  * @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;
 
     public Long getId() {
@@ -23,10 +20,8 @@ public class FolderDTO extends BaseFolder implements Serializable{
     @Override
     public String toString() {
         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>
         <dependency>
             <groupId>com.usoftchina.saas</groupId>
-            <artifactId>core</artifactId>
+            <artifactId>server-starter</artifactId>
         </dependency>
         <dependency>
             <groupId>com.usoftchina.saas</groupId>
             <artifactId>file-dto</artifactId>
         </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>
+        <!-- db -->
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
@@ -54,6 +47,33 @@
             <groupId>io.springfox</groupId>
             <artifactId>springfox-swagger2</artifactId>
         </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>
     <build>
         <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;
 
+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.autoconfigure.SpringBootApplication;
 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;
 
 /**
@@ -12,8 +17,11 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
  */
 @SpringBootApplication
 @EnableEurekaClient
-@EnableHystrix
 @EnableTransactionManagement
+@MapperScan("com.usoftchina.saas.file.mapper")
+@EnableAuthClient
+@Import(FdfsClientConfig.class)
+@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
 public class FileApplication {
     public static void main(String[] 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;
 
+import com.github.tobato.fastdfs.domain.StorePath;
+import com.github.tobato.fastdfs.service.FastFileStorageClient;
 import com.usoftchina.saas.base.Result;
+import com.usoftchina.saas.exception.BizException;
 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.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.CollectionUtils;
+import com.usoftchina.saas.utils.StringUtils;
 import io.swagger.annotations.Api;
 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.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 = "文件管理")
 @RestController
-@RequestMapping("/api/file")
+@RequestMapping
 public class FileController {
 
     @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")
-    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();
     }
 }

+ 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:
       name: admin
       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:
+    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:
-      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:
     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:
   instance:
     leaseRenewalIntervalInSeconds: 10
     health-check-url-path: /actuator/health
     status-page-url-path: /actuator/info
+    prefer-ip-address: true
     metadata-map:
       user.name: ${spring.security.user.name}
       user.password: ${spring.security.user.password}
   client:
     registryFetchIntervalSeconds: 5
     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:
-  port: 8580
+  port: 8640
   tomcat:
     uri-encoding: UTF-8
+management:
+  endpoints:
+    web:
+      exposure:
+        include: "*"
+  endpoint:
+    health:
+      show-details: always
+    shutdown:
+      enabled: true
+    restart:
+      enabled: true
 info:
   name: '@project.artifactId@'
   description: '@project.description@'
@@ -37,4 +76,16 @@ info:
   spring-cloud-version: '@spring.cloud.version@'
 mybatis:
   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"?>
 <configuration>
-    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
+    <include resource="org/springframework/boot/logging/logback/base.xml" />
     <jmxConfigurator/>
 
     <!--
@@ -28,10 +28,10 @@
     <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="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>
 
-
     <appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
         <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
             <level>${log.level.console}</level>
@@ -44,11 +44,9 @@
     <appender name="ROOT_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
         <file>${log.path}/root.log</file>
         <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
-            <!-- 每天一归档 -->
             <fileNamePattern>${log.path}/%d{yyyy-MM}/root-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
-            <!-- 单个日志文件最多 100MB, 60天的日志周期,最大不能超过20GB -->
             <maxFileSize>128MB</maxFileSize>
-            <maxHistory>60</maxHistory>
+            <maxHistory>7</maxHistory>
             <totalSizeCap>20GB</totalSizeCap>
         </rollingPolicy>
         <encoder>
@@ -56,9 +54,60 @@
         </encoder>
     </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>

+ 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, "权限缺失"),
 
     // 文件相关
-    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,"已入库"),
     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) {
         if (!StringUtils.hasText(text)) {
             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) {
         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 org.junit.Before;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpMethod;
 import org.springframework.http.MediaType;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MvcResult;
 import org.springframework.test.web.servlet.ResultMatcher;
 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.setup.MockMvcBuilders;
 import org.springframework.web.context.WebApplicationContext;
@@ -56,6 +58,27 @@ public abstract class BaseControllerTest {
         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注解情况下
      *

+ 195 - 0
frontend/saas-web/app/Application.scss

@@ -72,4 +72,199 @@ body.launching {
 
 .boldFont {
   font-weight: bold;
+}
+
+// default font-family
+.x-body,
+.x-btn-inner,
+.x-panel-body-default,
+.x-toolbar-text-default,
+.x-form-item-label-default,
+.x-form-text-default,
+.x-column-header,
+.x-grid-item,
+.x-grid-row-summary .x-grid-cell, .x-grid-row-summary .x-grid-rowwrap, .x-grid-row-summary .x-grid-cell-rowbody {
+  font-family: 'pingFangSC-Regular';
+}
+
+.x-form-item-label-default {
+  font-family: PingFangSC-Medium;
+  font-size: 14px;
+  color: #505363;
+  text-align: right;
+  line-height: 20px;
+}
+
+// toolbar button
+.x-btn-default-toolbar-small {
+  color: #fff;
+  background-color: #308ee0;
+  border-color: #308ee0;
+  min-width: 86px;
+  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+
+  .x-btn-inner-default-toolbar-small {
+    color: #FFFFFF;
+  }
+}
+
+.x-form-item-default.x-item-disabled {
+  opacity: 1;
+
+  .x-form-item-label-default {
+    color: #8C97B2;
+  }
+
+  .x-form-text-default {
+    background: #EEF1F7;
+  }
+  .x-form-trigger {
+    background: #EEF1F7;
+  }
+}
+
+.x-btn.x-btn-menu-active.x-btn-default-toolbar-small,
+.x-btn.x-btn-pressed.x-btn-default-toolbar-small,
+.x-btn-over.x-btn-default-toolbar-small {
+  background: #34BAF6;
+  opacity: 0.72;
+}
+
+.x-keyboard-mode .x-btn-focus.x-btn-default-toolbar-small {
+  background: #34BAF6;
+  opacity: 0.72;
+}
+
+.x-btn-wrap-default-toolbar-small.x-btn-arrow-right:after {
+  color: #FFFFFF;
+}
+
+.x-btn.x-btn-disabled.x-btn-default-toolbar-small {
+  border-color: #E6E6E6;
+  background: #E6E6E6;
+
+  .x-btn-inner-default-toolbar-small {
+    color: #606060;
+  }
+}
+
+
+.x-btn-default-toolbar-small{
+  border-radius: 2px !important;
+}
+
+.x-formpanel-btn-orange {
+  border-color: #f67f00;
+  background: linear-gradient(to bottom,#da7101 0,#f67f00 100%) !important;
+  
+  .x-btn-inner-default-toolbar-small{
+      color:#fff !important;
+  }
+}
+
+.x-formpanel-btn-blue {
+  border-color: #346fb9;
+  background: linear-gradient(to bottom,#3876c3 0,#346fb9 100%) !important;
+
+  .x-btn-inner-default-toolbar-small{
+      color:#fff !important;
+  }
+}
+
+.x-grid-body {
+  border-width: 1px;
+  border-color: #ABDAFF;
+}
+
+.x-column-header {
+  background: #E5F7FF;
+  border-right: 1px solid #ABDAFF;
+}
+
+.x-column-header-text-inner {
+  font-size: 14px;
+  color: #505363;
+  letter-spacing: 0;
+  text-align: center;
+  line-height: 20px;
+  font-family: PingFangSC-Medium;
+  font-weight: normal;
+}
+
+.x-panel-default-outer-border-trbl {
+  border-color: #ABDAFF !important;
+}
+
+.x-form-trigger-wrap-default {
+  border-color: #CACFE1;
+  border-radius: 2px;
+}
+
+.x-form-trigger-default {
+  color: #5E5E5E;
+}
+
+.x-grid-header-ct {
+  border-top-color: #ABDAFF;
+  border-top-width: 1px;
+}
+
+.x-panel-default-outer-border-rl {
+  border-right-color: #ABDAFF;
+  border-right-width: 1px;
+  border-left-color: #ABDAFF;
+  border-left-width: 1px;
+}
+
+.x-grid-item-alt {
+  background-color: #F1F9FF;
+}
+
+.x-grid-locked .x-grid-inner-locked {
+  border-width: 0 1px 0 0;
+}
+
+.x-grid-scrollbar-clipper-locked, .x-grid-scrollbar-locked {
+  border-right-color: #ABDAFF;
+}
+
+.x-grid-inner-locked {
+  border-right-color: #ABDAFF;
+}
+
+.x-grid-with-row-lines .x-grid-item:first-child {
+  border-color: #ffffff;
+}
+
+.x-grid-with-row-lines .x-grid-item {
+  border-color: #ABDAFF;
+}
+
+.x-grid-item-over {
+  background-color: #bde5f7;
+}
+
+.x-grid-cell-special {
+  border-color: #ABDAFF;
+}
+
+
+
+/*滚动条样式*/
+div::-webkit-scrollbar {
+  width: 10px;
+  height: 10px;
+}
+div::-webkit-scrollbar-thumb {
+  background: #C2EAFC;
+  border-radius: 8px;
+
+  &:hover {
+    background: #74c3e6;
+  }
+}
+div::-webkit-scrollbar-track {
+  background: #fff;
+  border: 1px solid #e5e5e5;
+  border-radius: 8px;
 }

+ 1 - 1
frontend/saas-web/app/view/core/form/ConDateField.js

@@ -21,7 +21,7 @@ Ext.define('saas.view.core.form.ConDateField', {
             width: 100,
             editable: false,
             hidden: !me.showscope,
-            fieldStyle: 'background:#C1CDC1',
+            fieldStyle: 'background:#eef1f7',
             store: Ext.create('Ext.data.Store', {
                 fields: ['display', 'value'],
                 data : [

+ 1 - 1
frontend/saas-web/app/view/core/form/FormPanel.js

@@ -116,7 +116,7 @@ Ext.define('saas.view.core.form.FormPanel', {
             xtype: 'textfield',
             minHeight: 24,
             maxHeight: 24,
-            width: 150,
+            width: 200,
             cls: 'x-codeeditor',
             hidden: true,
             allowBlank: false,

+ 11 - 13
frontend/saas-web/app/view/core/form/FormPanel.scss

@@ -1,19 +1,15 @@
 .x-core-form {
-    padding: 10px;
+    padding: 24px;
 
     .x-tb {
         color: #A2A2A2;
-        // letter-spacing: 0.72px;
-        // text-align: left;
-        font-size: 16px;
-    }
-    .x-no {
-    }
-    .x-code {
+        letter-spacing: 0.72px;
+        text-align: left;
+        font: 400 18px/16px 'PingFangSC-Regular';
     }
     .x-codeeditor {
-        top: 0px !important;
-        left: 39px !important;
+        top: -2px !important;
+        left: 44.5px !important;
 
         .x-form-trigger-wrap {
             border-top: none;
@@ -23,8 +19,10 @@
 
             input {
                 padding-left: 0;
-                font-size: 16px;
+                letter-spacing: 0.72px;
+                font-size: 18px;
                 min-height: inherit;
+                font: 400 18px/16px 'PingFangSC-Regular';
             }
         }
     }
@@ -52,7 +50,7 @@
         }
     }
     .x-audited {
-        border: 1px solid #FF0000;
-        color: #FF0000;
+        border: 1px solid #FF002B;
+        color: #FF002B;
     }
 }

+ 2 - 2
frontend/saas-web/app/view/core/form/field/DetailGridField.js

@@ -132,8 +132,8 @@ Ext.define('saas.view.core.form.field.DetailGridField', {
             renderer: function(value, a, record, index) {
                 return '<div class="text">' + value + '</div>' +
                 '<div class="icons" style="height: 19px; display: none;">' + 
-                    '<div style="line-height: 19px; flex: 1; color: green; cursor: pointer; margin-right: 2px;" class="x-row-insert fa fa-plus" title="插入行"></div>'+
-                    '<div style="line-height: 19px; flex: 1; color: red; cursor: pointer; margin-left: 2px;" class="x-row-delete fa fa-minus" title="删除行"></div>'+
+                    '<div style="line-height: 19px; flex: 1; color: #34BAF6; cursor: pointer; margin-right: 2px;" class="x-row-insert fa fa-plus" title="插入行"></div>'+
+                    '<div style="line-height: 19px; flex: 1; color: #FF002B; cursor: pointer; margin-left: 2px;" class="x-row-delete fa fa-minus" title="删除行"></div>'+
                 '</div>';
             },
         };

+ 19 - 1
frontend/saas-web/app/view/core/form/field/DetailGridField.scss

@@ -21,10 +21,28 @@
 .x-grid-necessary .x-column-header-text:before{
     content: '*';
     font-size: 130%;
-    color: #ff0000;
+    color: #FF002B;
     width: 5px;
     height: 5px;
     margin-top: 4px;
     margin-left: -10px;
     position: absolute;
+}
+
+.x-grid-locked .x-grid-inner-locked {
+    border: none;
+}
+
+.x-grid-locked .x-grid-inner-locked {
+    
+    .x-grid-header-ct {
+        border-right: 1px solid #ABDAFF !important;
+    }
+}
+
+.x-grid-scrollbar-clipper-locked, .x-grid-scrollbar-locked {
+
+    .x-grid-view {
+        border-right: 1px solid #ABDAFF !important;
+    }
 }

+ 4 - 4
frontend/saas-web/app/view/core/query/QueryFormPanel.js

@@ -4,8 +4,11 @@ Ext.define('saas.view.core.query.QueryFormPanel', {
 
     layout: 'column',
     autoScroll: true,
-    bodyPadding: 5,
+    bodyPadding: '16 10 0 10',
     labelSeparator : ':',
+
+    cls: 'x-queryform',
+
     defaults:{
 		margin: '0 5 5 0',
         labelAlign: 'right',
@@ -17,9 +20,6 @@ Ext.define('saas.view.core.query.QueryFormPanel', {
     dockedItems: [{
         xtype: 'toolbar',
         dock: 'bottom',
-        style: {
-            'border-bottom': '1px solid #35baf6 !important'
-        },
         items: ['->', {
         //     xtype: 'button',
         //     text: '更多查询',

+ 2 - 5
frontend/saas-web/app/view/core/query/QueryGridPanel.js

@@ -2,8 +2,6 @@ Ext.define('saas.view.core.query.QueryGridPanel', {
     extend: 'Ext.grid.Panel',
     xtype: 'core-query-querygridpanel',
 
-    cls: 'x-core-query',
-
     //工具类
     BaseUtil: Ext.create('saas.util.BaseUtil'),
     //字段属性
@@ -25,6 +23,8 @@ Ext.define('saas.view.core.query.QueryGridPanel', {
     queryMode: 'MAIN',
     defaultCondition: '',
 
+    cls: 'x-querygrid',
+
     //基础属性
     border: 1,
     anchor: '100% 70%',
@@ -107,9 +107,6 @@ Ext.define('saas.view.core.query.QueryGridPanel', {
             dockedItems: [{
                 xtype: 'toolbar',
                 dock: 'top',
-                style: {
-                    borderTop: 'none'
-                },
                 defaults: { // defaults 将会应用所有的子组件上,而不是父容器
                     listeners: {
                         'mouseover':function(){

+ 5 - 2
frontend/saas-web/app/view/core/query/QueryPanel.js

@@ -7,15 +7,18 @@ Ext.define('saas.view.core.query.QueryPanel', {
 
     layout: 'vbox',
     autoScroll: true,
-    border: 1,
-    bodyPadding: 5,
+    bodyPadding: 0,
     margin: '0',
+
+    cls: 'x-core-query',
     
     items: [{
         reference: 'queryform',
         xtype: 'core-query-queryformpanel',
         width: '100%',
     }, {
+        margin: '32 0 0 0',
+        padding: '24',
         reference: 'querygrid',
         xtype: 'core-query-querygridpanel',
         width: '100%',

+ 53 - 0
frontend/saas-web/app/view/core/query/QueryPanel.scss

@@ -0,0 +1,53 @@
+.x-core-query {
+
+    .x-panel-bodyWrap {
+
+        .x-panel-body {
+            cursor: pointer;
+
+            .x-box-inner {
+                cursor: default;
+                background: #EEF4F9;
+
+                .x-box-target {
+                    .x-queryform {
+                
+                        .x-panel-bodyWrap {
+        
+                            .x-toolbar-default {
+                                padding: 6px 0 16px 8px;
+
+                                .x-box-inner {
+                                    background: #FFFFFF;
+        
+                                }
+                            }
+                        }
+                    }
+                    .x-querygrid {
+                        background-color: white;
+
+                        .x-panel-bodyWrap {
+
+                            .x-toolbar {
+                                margin-bottom: 16px;
+                                border: none;
+
+                                .x-box-inner {
+                                    background-color: white;
+                                }
+                            }
+
+                            .x-grid-header-ct {
+                                border-right-color: #ABDAFF !important;
+                                border-right-width: 1px !important;
+                                border-left-color: #ABDAFF !important;
+                                border-left-width: 1px !important;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

+ 1 - 1
frontend/saas-web/app/view/document/kind/Kind.js

@@ -274,7 +274,7 @@ Ext.define('saas.view.document.kind.Kind', {
         var grid = me.items.items[0];
         var button = grid.ownerCt.dockedItems.items[0].down('[name='+grid.ownerCt.ownerCt.viewConfig+']');
         
-        if(button){
+        if(button&&button.xtype!="tbfill"){
             button.click();
             me.ownerCt.setTitle(button.typeText + '查询');
         }

+ 29 - 14
frontend/saas-web/app/view/document/vendor/BasePanel.js

@@ -60,29 +60,44 @@ Ext.define('saas.view.document.vendor.BasePanel', {
             width : 120.0, 
             xtype : "", 
         }, 
-        {
-            text : "供应商状态", 
-            dataIndex : "ve_status", 
-            width : 120.0, 
-            xtype : ""
-        }, 
         {
             text : "供应商状态码", 
             dataIndex : "ve_statuscode", 
             width : 0, 
             xtype : ""
-        },
+        },               
         {
-            text : "供应商UU", 
-            dataIndex : "ve_uu", 
+            text : "税率", 
+            dataIndex : "ve_taxrate", 
             width : 120.0, 
             xtype : "",
-        }, 
-        {
-            text : "默认供应商联系人", 
-            dataIndex : "vc_name", 
-            flex : 1.0, 
+            align:'right'
+        }, {
+            text : "承付天数", 
+            dataIndex : "ve_promisedays", 
+            width : 120.0, 
             xtype : "",
+            align:'right'
+        }, {
+            text : "纳税人识别号", 
+            dataIndex : "ve_nsrzh", 
+            width : 120.0, 
+            xtype : ""
+        }, {
+            text : "开户银行", 
+            dataIndex : "ve_bankcode", 
+            width : 120.0, 
+            xtype : ""
+        }, {
+            text : "银行账户", 
+            dataIndex : "ve_bankaccount", 
+            width : 120.0, 
+            xtype : ""
+        }, {
+            text : "状态", 
+            dataIndex : "ve_status", 
+            width : 120.0, 
+            xtype : ""
         }]
     },
 

+ 5 - 23
frontend/saas-web/app/view/main/Main.scss

@@ -64,13 +64,15 @@ $treelist-nav-ui: (
         background: #34BAF6;
 
         img {
-            width: 64px;
-            top: 0px;
-            left: 20px;
+            width: 54px;
+            height: 54px;
+            top: 5px;
+            left: 16px;
             position: relative;
         }
 
         .logo-text {
+            font-family: 'pingFangSC-Regular';
             font-size: 18px;
             color: #FFFFFF;
             text-align: left;
@@ -126,26 +128,6 @@ $treelist-nav-ui: (
     border: 1px solid #ccc;
 }
 
-.x-btn-default-toolbar-small{
-    border-radius: 5px !important;
-}
-
-.x-formpanel-btn-orange {
-    background: linear-gradient(to bottom,#da7101 0,#f67f00 100%) !important;
-}
-
-.x-formpanel-btn-orange .x-btn-inner-default-toolbar-small{
-    color:#fff !important;
-}
-
-.x-formpanel-btn-blue {
-    background: linear-gradient(to bottom,#3876c3 0,#346fb9 100%) !important;
-}
-
-.x-formpanel-btn-blue .x-btn-inner-default-toolbar-small{
-    color:#fff !important;
-}
-
 .x-query-menu {
     width:68px !important;
 }

+ 11 - 8
frontend/saas-web/app/view/main/MainController.js

@@ -37,22 +37,25 @@ Ext.define('saas.view.main.MainController', {
         new_width = navCollapsed ? viewModel.get('smallNavWidth') : viewModel.get('navWidth'),
         newLogoImgStyle = navCollapsed ? {
             width: 50,
+            height: 50,
             top: 7,
             left: 6
         } : {
-            width: 64,
-            top: 0,
-            left: 20
+            width: 54,
+            height: 54,
+            top: 5,
+            left: 16
         },
         newLogoTextStyle = navCollapsed ? {
-            10: {
-                opacity: 0.7
+            5: {
+                opacity: 0
             },
-            30: {
+            10: {
                 opacity: 0
             },
             100: {
-                opacity: 0
+                opacity: 0,
+                display: 'none'
             }
         } : {
             25: {
@@ -66,7 +69,7 @@ Ext.define('saas.view.main.MainController', {
             marginLeft: 6,
             fontSize: 28
         } : {
-            marginLeft: 40,
+            marginLeft: 22,
             fontSize: 24
         },
         newNavTextStyle = navCollapsed ? {

+ 1 - 1
frontend/saas-web/app/view/main/MainModel.js

@@ -7,7 +7,7 @@ Ext.define('saas.view.main.MainModel', {
     alias: 'viewmodel.main',
 
     data: {
-        navWidth: 200,
+        navWidth: 180,
         smallNavWidth: 64,
     }
 });

+ 5 - 5
frontend/saas-web/app/view/main/Navigation.scss

@@ -45,6 +45,7 @@ $nav-font-color: #FFFFFF;
                                     list-style: none;
                                     max-height: 60px;
                                     padding: 0 10px;
+                                    display: flex;
     
                                     .nav-inner-icon {
                                         opacity: 0.4;
@@ -52,9 +53,9 @@ $nav-font-color: #FFFFFF;
                                         color: $nav-font-color;
                                         font-size: 24px;
                                         text-align: center;
-                                        margin-left: 40px;
+                                        margin-left: 22px;
                                         display: inline;
-                                        margin-right: 42px;
+                                        margin-right: 16px;
     
                                         &:before {
                                             line-height: 60px;
@@ -64,12 +65,11 @@ $nav-font-color: #FFFFFF;
                                     .nav-inner-text {
                                         font-size: $nav-font-size;
                                         color: $nav-font-color;
-                                        letter-spacing: 0.64px;
+                                        letter-spacing: 4px;
                                         text-align: left;
                                         opacity: 0.46;
-                                        display: inline;
                                         line-height: 60px;
-
+                                        height: 60px;
                                     }
                                 }
                             }

+ 1 - 1
frontend/saas-web/app/view/money/othreceipts/FormPanel.js

@@ -81,7 +81,7 @@ Ext.define('saas.view.money.othreceipts.FormPanel', {
                 items : null,
                 editor : {
                     xtype : "remotecombo", 
-                    storeUrl:'/api/document/fundinouttype/getCombo',
+                    storeUrl:'/api/document/fundinouttype/getCombo?condition=收入',
                     addHandler:function(b){
                         var document = Ext.create('saas.view.document.kind.Kind',{});
                         var form =this.ownerCmp.ownerCt.ownerCmp.ownerCt;

+ 2 - 2
frontend/saas-web/app/view/money/othspendings/FormPanel.js

@@ -84,8 +84,8 @@ Ext.define('saas.view.money.othspendings.FormPanel', {
                 dataIndex : "osd_type",
                 items : null,
                 editor : {
-                    xtype : "remotecombo", 
-                    storeUrl:'/api/document/fundinouttype/getCombo',
+                    xtype : "remotecombo",
+                    storeUrl:'/api/document/fundinouttype/getCombo?condition=支出',
                     addHandler:function(b){
                         var document = Ext.create('saas.view.document.kind.Kind',{});
                         var form =this.ownerCmp.ownerCt.ownerCmp.ownerCt;

+ 24 - 3
frontend/saas-web/app/view/purchase/purchase/FormPanel.js

@@ -64,10 +64,31 @@ Ext.define('saas.view.purchase.purchase.FormPanel', {
         name : "pu_delivery", 
         fieldLabel : "交货日期"
     },{
-        xtype : "textfield", 
+        editable:false,
+        xtype : "remotecombo", 
+        storeUrl:'/api/document/address/getCombo',
         name : "pu_shipaddresscode", 
-        fieldLabel : "交货地址",
-        columnWidth: 0.5
+        fieldLabel : "交货地址", 
+        allowBlank : false, 
+        columnWidth : 0.25,
+        hiddenBtn:false,//true 则会关闭新增按钮功能
+        addHandler:function(b){
+            var document = Ext.create('saas.view.document.kind.Kind',{});
+            var form = this.ownerCmp.ownerCt;
+            this.dialog = form.getController().getView().add({
+                xtype: 'document-kind-childwin',
+                bind: {
+                    title: '新增交货地址'
+                },
+                dataKind:'address',
+                belong:document.etc['address'],
+                _parent:form,
+                _combo:this.ownerCmp,
+                record:null,
+                session: true
+            });
+            this.dialog.show();
+        }
     }, {
         xtype : "textfield", 
         name : "pu_total", 

BIN
frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-中粗-简.ttf


BIN
frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-中粗-繁.ttf


BIN
frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-中黑-简.ttf


BIN
frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-中黑-繁.ttf


BIN
frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-准-简.ttf


BIN
frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-准-繁.ttf


BIN
frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-极细-简.ttf


BIN
frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-极细-繁.ttf


BIN
frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-纤细-简.ttf


BIN
frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-纤细-繁.ttf


BIN
frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-细-简.ttf


BIN
frontend/saas-web/packages/local/theme-default/resources/fonts/pingfang/苹方黑体-细-繁.ttf


+ 1 - 0
frontend/saas-web/packages/local/theme-default/sass/etc/all.scss

@@ -0,0 +1 @@
+@import 'fontset.scss';

+ 35 - 0
frontend/saas-web/packages/local/theme-default/sass/etc/fontset.scss

@@ -0,0 +1,35 @@
+$ext-font-path: get-resource-path('fonts');
+
+@font-face {
+    font-family: 'pingFangSC-Regular';
+    src: url('#{$ext-font-path}/pingfang/苹方黑体-准-简.ttf') format('truetype');
+    font-weight: normal;
+    font-style: normal;
+}
+
+@font-face {
+    font-family: 'PingFangSC-Ultralight';
+    src: url('#{$ext-font-path}/pingfang/苹方黑体-极细-简.ttf') format('truetype');
+    font-weight: normal;
+    font-style: normal;
+}
+
+@font-face {
+    font-family: 'PingFangSC-Light';
+    src: url('#{$ext-font-path}/pingfang/苹方黑体-细-简.ttf?t=1541646656813') format('truetype');
+}
+
+@font-face {
+    font-family: 'PingFangSC-Thin';
+    src: url('#{$ext-font-path}/pingfang/苹方黑体-纤细-简.ttf?t=1541646656813') format('truetype');
+}
+
+@font-face {
+    font-family: 'PingFangSC-Medium';
+    src: url('#{$ext-font-path}/pingfang/苹方黑体-中黑-简.ttf?t=1541646656813') format('truetype');
+}
+
+@font-face {
+    font-family: 'PingFangSC-Medium';
+    src: url('#{$ext-font-path}/pingfang/苹方黑体-中粗-简.ttf?t=1541646656813') format('truetype');
+}

+ 13 - 0
pom.xml

@@ -38,9 +38,11 @@
         <swagger.version>2.7.0</swagger.version>
         <feign.form.version>3.0.3</feign.form.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.registry.name>saas</docker.registry.name>
         <guava.version>18.0</guava.version>
+        <fastdfs.client.version>1.26.3</fastdfs.client.version>
     </properties>
 
     <repositories>
@@ -323,11 +325,22 @@
                 <artifactId>fastjson</artifactId>
                 <version>${fastjson.version}</version>
             </dependency>
+            <dependency>
+                <groupId>com.github.tobato</groupId>
+                <artifactId>fastdfs-client</artifactId>
+                <version>${fastdfs.client.version}</version>
+            </dependency>
+            <!-- utils -->
             <dependency>
                 <groupId>com.google.guava</groupId>
                 <artifactId>guava</artifactId>
                 <version>${guava.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.commons</groupId>
+                <artifactId>commons-compress</artifactId>
+                <version>${commons.compress.version}</version>
+            </dependency>
         </dependencies>
     </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='文件';