Selaa lähdekoodia

基于companyId的数据库备份方法

yingp 6 vuotta sitten
vanhempi
commit
36b4f851ad
51 muutettua tiedostoa jossa 1614 lisäystä ja 20 poistoa
  1. 73 0
      base-servers/account/account-api/src/main/java/com/usoftchina/saas/account/cache/CompanyLockCache.java
  2. 1 1
      base-servers/account/account-server/src/main/resources/mapper/AccountMapper.xml
  3. 0 1
      base-servers/auth/auth-client/src/main/java/com/usoftchina/saas/auth/client/interceptor/AuthRestInterceptor.java
  4. 12 0
      base-servers/auth/auth-common/src/main/java/com/usoftchina/saas/auth/common/constant/AuthConstants.java
  5. 2 1
      base-servers/auth/auth-server/src/main/java/com/usoftchina/saas/auth/controller/AuthController.java
  6. 4 1
      base-servers/datacenter/datacenter-dto/src/main/java/com/usoftchina/saas/dc/dto/DataSourceInfoDTO.java
  7. 17 0
      base-servers/datacenter/datacenter-server/pom.xml
  8. 9 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/DatacenterApplication.java
  9. 27 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/backup/BackupFailedEvent.java
  10. 22 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/backup/BackupReadyEvent.java
  11. 28 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/backup/BackupSuccessEvent.java
  12. 9 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/backup/CompanyStrategy.java
  13. 62 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/backup/DumpContext.java
  14. 54 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/backup/DumpFile.java
  15. 54 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/backup/MysqlStrategy.java
  16. 61 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/controller/BackupController.java
  17. 4 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/controller/DataSourceInfoController.java
  18. 49 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/mapper/BackupMapper.java
  19. 18 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/mapper/CommonMapper.java
  20. 17 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/mapper/DataSourceInfoMapper.java
  21. 21 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/mapper/SchemaMapper.java
  22. 105 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/po/Backup.java
  23. 34 1
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/po/DataSourceInfo.java
  24. 25 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/service/BackupService.java
  25. 17 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/service/DataSourceInfoService.java
  26. 119 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/service/impl/BackupServiceImpl.java
  27. 12 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/service/impl/DataSourceInfoServiceImpl.java
  28. 3 1
      base-servers/datacenter/datacenter-server/src/main/resources/application.yml
  29. 62 0
      base-servers/datacenter/datacenter-server/src/main/resources/mapper/BackupMapper.xml
  30. 7 0
      base-servers/datacenter/datacenter-server/src/main/resources/mapper/CommonMapper.xml
  31. 11 1
      base-servers/datacenter/datacenter-server/src/main/resources/mapper/DataSourceInfoMapper.xml
  32. 8 0
      base-servers/datacenter/datacenter-server/src/main/resources/mapper/SchemaMapper.xml
  33. 90 0
      base-servers/file/file-api/src/main/java/com/usoftchina/saas/file/util/DiskMultipartFile.java
  34. 34 0
      base-servers/file/file-api/src/main/java/com/usoftchina/saas/file/util/FileUploadUtils.java
  35. 1 1
      base-servers/file/file-server/src/main/java/com/usoftchina/saas/file/controller/FileController.java
  36. 8 3
      base-servers/gateway-server/src/main/java/com/usoftchina/saas/gateway/config/AuthFilter.java
  37. 22 0
      framework/commons-compress/pom.xml
  38. 194 0
      framework/commons-compress/src/main/java/com/usoftchina/saas/commons/compress/Zip.java
  39. 29 0
      framework/commons-compress/src/test/java/com/usoftchina/saas/commons/compress/ZipTest.java
  40. 83 0
      framework/core/src/main/java/com/usoftchina/saas/cache/RedisListCache.java
  41. 69 0
      framework/core/src/main/java/com/usoftchina/saas/cache/RedisSetCache.java
  42. 5 0
      framework/core/src/main/java/com/usoftchina/saas/context/SpringContextHolder.java
  43. 1 0
      framework/core/src/main/java/com/usoftchina/saas/exception/ExceptionCode.java
  44. 88 0
      framework/core/src/main/java/com/usoftchina/saas/exception/Try.java
  45. 3 1
      framework/core/src/main/java/com/usoftchina/saas/jdbc/DynamicDataSourceContextHolder.java
  46. 22 5
      framework/core/src/main/java/com/usoftchina/saas/jdbc/JdbcUrl.java
  47. 1 0
      framework/pom.xml
  48. 6 0
      framework/server-starter/pom.xml
  49. 2 1
      framework/server-starter/src/main/java/com/usoftchina/saas/server/ServerAutoConfiguration.java
  50. 4 2
      framework/server-starter/src/main/java/com/usoftchina/saas/server/cache/DefaultRedisConfig.java
  51. 5 0
      pom.xml

+ 73 - 0
base-servers/account/account-api/src/main/java/com/usoftchina/saas/account/cache/CompanyLockCache.java

@@ -0,0 +1,73 @@
+package com.usoftchina.saas.account.cache;
+
+import com.usoftchina.saas.cache.CacheKeyHelper;
+import com.usoftchina.saas.cache.RedisSetCache;
+import com.usoftchina.saas.context.SpringContextHolder;
+import com.usoftchina.saas.exception.BizException;
+import com.usoftchina.saas.exception.ExceptionCode;
+import org.springframework.data.redis.core.RedisTemplate;
+
+/**
+ * 缓存锁定公司
+ *
+ * @author yingp
+ * @date 2019/1/15
+ */
+public class CompanyLockCache extends RedisSetCache<Long> {
+
+    private static final RedisTemplate<String, Long> REDIS_TEMPLATE = SpringContextHolder.getBean("redisTemplate", RedisTemplate.class);
+
+    private CompanyLockCache() {
+        super(() -> REDIS_TEMPLATE, CacheKeyHelper.generatePublicKey("account", "company", "lock"));
+    }
+
+    public static final CompanyLockCache instance() {
+        return CompanyLockCacheHolder.INSTANCE;
+    }
+
+    /**
+     * 是否已锁定
+     *
+     * @param id
+     * @return
+     */
+    public static boolean isLocked(Long id) {
+        return instance().contains(id);
+    }
+
+    /**
+     * 已锁定情况下抛出异常
+     *
+     * @param id
+     */
+    public static void assertNotLocked(Long id) {
+        if (isLocked(id)) {
+            throw new BizException(ExceptionCode.COMPANY_LOCKED);
+        }
+    }
+
+    /**
+     * 锁定
+     *
+     * @param id
+     */
+    public static void lock(Long id) {
+        instance().add(id);
+    }
+
+    /**
+     * 解锁
+     *
+     * @param id
+     */
+    public static void unlock(Long id) {
+        instance().remove(id);
+    }
+
+    /**
+     * 单例
+     */
+    private static class CompanyLockCacheHolder {
+        private static final CompanyLockCache INSTANCE = new CompanyLockCache();
+    }
+}

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

@@ -129,7 +129,7 @@
         creator_id=#{creatorId,jdbcType=BIGINT},
         create_time=#{createTime,jdbcType=TIMESTAMP},
         updater_id=#{updaterId,jdbcType=BIGINT},
-        update_time=#{updateTime,jdbcType=TIMESTAMP})
+        update_time=#{updateTime,jdbcType=TIMESTAMP}
         where id=#{id,jdbcType=BIGINT}
     </update>
     <update id="updateByPrimaryKeySelective" parameterType="com.usoftchina.saas.account.po.Account">

+ 0 - 1
base-servers/auth/auth-client/src/main/java/com/usoftchina/saas/auth/client/interceptor/AuthRestInterceptor.java

@@ -52,7 +52,6 @@ public class AuthRestInterceptor extends HandlerInterceptorAdapter {
                 BaseContextHolder.setUserName(infoFromToken.getRealName());
                 BaseContextHolder.setCompanyId(infoFromToken.getCompanyId());
                 BaseContextHolder.setToken(token);
-                logger.debug("{}: {}", authConfig.getAuthHeader(), BaseContextHolder.getToken());
                 logger.info("request={} CompanyId={} token={} \\r\\n userName={}  ", request.getRequestURI(),
                         infoFromToken.getCompanyId(), token, infoFromToken.getUserName());
             }

+ 12 - 0
base-servers/auth/auth-common/src/main/java/com/usoftchina/saas/auth/common/constant/AuthConstants.java

@@ -0,0 +1,12 @@
+package com.usoftchina.saas.auth.common.constant;
+
+/**
+ * @author yingp
+ * @date 2019/1/15
+ */
+public class AuthConstants {
+    /**
+     * 虚拟账号ID
+     */
+    public static final long VIRTUAL_ACCOUNT_ID = -99999;
+}

+ 2 - 1
base-servers/auth/auth-server/src/main/java/com/usoftchina/saas/auth/controller/AuthController.java

@@ -6,6 +6,7 @@ import com.usoftchina.saas.account.api.CompanyApi;
 import com.usoftchina.saas.account.constant.AccountType;
 import com.usoftchina.saas.account.dto.*;
 import com.usoftchina.saas.auth.client.annotation.IgnoreAuth;
+import com.usoftchina.saas.auth.common.constant.AuthConstants;
 import com.usoftchina.saas.auth.common.cookie.CookieHelper;
 import com.usoftchina.saas.auth.common.cookie.CookieInfo;
 import com.usoftchina.saas.auth.common.jwt.JwtHelper;
@@ -139,7 +140,7 @@ public class AuthController {
         //构造虚拟account,生成token
         String randomNum = DateUtils.format(new Date(), "hhmmss");
         AccountDTO accountDTO = new AccountDTO();
-        accountDTO.setId(-99999L);
+        accountDTO.setId(AuthConstants.VIRTUAL_ACCOUNT_ID);
         accountDTO.setUsername("virtual" + randomNum);
         accountDTO.setRealname("游客" + randomNum);
         accountDTO.setMobile(mobile);

+ 4 - 1
base-servers/datacenter/datacenter-dto/src/main/java/com/usoftchina/saas/dc/dto/DataSourceInfoDTO.java

@@ -1,6 +1,7 @@
 package com.usoftchina.saas.dc.dto;
 
 import com.usoftchina.saas.jdbc.Connectable;
+import com.usoftchina.saas.jdbc.JdbcUrl;
 
 import java.io.Serializable;
 
@@ -16,6 +17,7 @@ public class DataSourceInfoDTO implements Connectable, Serializable{
     private String dbRealName;
     private String dbUserName;
     private String dbPassword;
+    private String dbType;
 
     public String getDcName() {
         return dcName;
@@ -83,6 +85,7 @@ public class DataSourceInfoDTO implements Connectable, Serializable{
                 ", dbRealName='" + dbRealName + '\'' +
                 ", dbUserName='" + dbUserName + '\'' +
                 ", dbPassword='" + dbPassword + '\'' +
+                ", dbType='" + dbType + '\'' +
                 '}';
     }
 
@@ -93,7 +96,7 @@ public class DataSourceInfoDTO implements Connectable, Serializable{
 
     @Override
     public String url() {
-        return String.format("jdbc:mysql://%s:%s/%s?characterEncoding=utf-8&useSSL=false&allowMultiQueries=true",
+        return String.format(JdbcUrl.Type.optional(dbType).orElse(JdbcUrl.Type.MYSQL).getUrlTpl(),
                 dbHost, dbPort, dbRealName);
     }
 

+ 17 - 0
base-servers/datacenter/datacenter-server/pom.xml

@@ -53,6 +53,23 @@
             <groupId>com.usoftchina.saas</groupId>
             <artifactId>datacenter-api</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>com.usoftchina.saas</groupId>
+            <artifactId>auth-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.usoftchina.saas</groupId>
+            <artifactId>account-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.usoftchina.saas</groupId>
+            <artifactId>file-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.usoftchina.saas</groupId>
+            <artifactId>commons-compress</artifactId>
+        </dependency>
     </dependencies>
     <build>
         <plugins>

+ 9 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/DatacenterApplication.java

@@ -1,9 +1,12 @@
 package com.usoftchina.saas.dc;
 
+import com.usoftchina.saas.auth.client.EnableAuthClient;
+import com.usoftchina.saas.server.jdbc.EnableDynamicDataSource;
 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.openfeign.EnableFeignClients;
 
 /**
  * @author yingp
@@ -11,7 +14,13 @@ import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
  */
 @EnableEurekaClient
 @SpringBootApplication
+@EnableAuthClient
 @MapperScan("com.usoftchina.saas.dc.mapper")
+@EnableDynamicDataSource
+@EnableFeignClients({
+        "com.usoftchina.saas.file.api",
+        "com.usoftchina.saas.account.api"
+})
 public class DatacenterApplication {
 
     public static void main(String[] args) {

+ 27 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/backup/BackupFailedEvent.java

@@ -0,0 +1,27 @@
+package com.usoftchina.saas.dc.backup;
+
+import com.usoftchina.saas.dc.po.Backup;
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * @author yingp
+ * @date 2019/1/21
+ */
+public class BackupFailedEvent extends ApplicationEvent {
+    private Backup backup;
+    private Exception exception;
+
+    public BackupFailedEvent(Object source, Backup backup, Exception exception) {
+        super(source);
+        this.backup = backup;
+        this.exception = exception;
+    }
+
+    public Backup getBackup() {
+        return backup;
+    }
+
+    public Exception getException() {
+        return exception;
+    }
+}

+ 22 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/backup/BackupReadyEvent.java

@@ -0,0 +1,22 @@
+package com.usoftchina.saas.dc.backup;
+
+import com.usoftchina.saas.dc.po.Backup;
+import com.usoftchina.saas.file.dto.FileInfoDTO;
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * @author yingp
+ * @date 2019/1/21
+ */
+public class BackupReadyEvent extends ApplicationEvent {
+    private Backup backup;
+
+    public BackupReadyEvent(Object source, Backup backup) {
+        super(source);
+        this.backup = backup;
+    }
+
+    public Backup getBackup() {
+        return backup;
+    }
+}

+ 28 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/backup/BackupSuccessEvent.java

@@ -0,0 +1,28 @@
+package com.usoftchina.saas.dc.backup;
+
+import com.usoftchina.saas.dc.po.Backup;
+import com.usoftchina.saas.file.dto.FileInfoDTO;
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * @author yingp
+ * @date 2019/1/21
+ */
+public class BackupSuccessEvent extends ApplicationEvent {
+    private Backup backup;
+    private FileInfoDTO fileInfo;
+
+    public BackupSuccessEvent(Object source, Backup backup, FileInfoDTO fileInfo) {
+        super(source);
+        this.backup = backup;
+        this.fileInfo = fileInfo;
+    }
+
+    public Backup getBackup() {
+        return backup;
+    }
+
+    public FileInfoDTO getFileInfo() {
+        return fileInfo;
+    }
+}

+ 9 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/backup/CompanyStrategy.java

@@ -0,0 +1,9 @@
+package com.usoftchina.saas.dc.backup;
+
+/**
+ * @author yingp
+ * @date 2019/1/21
+ */
+public interface CompanyStrategy{
+    String COMPANY_ID_COLUMN = "company_id";
+}

+ 62 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/backup/DumpContext.java

@@ -0,0 +1,62 @@
+package com.usoftchina.saas.dc.backup;
+
+import com.usoftchina.saas.commons.compress.Zip;
+import com.usoftchina.saas.dc.po.Backup;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/**
+ * @author yingp
+ * @date 2019/1/21
+ */
+public class DumpContext {
+    private Backup backup;
+    private final Path workspace;
+    private final Path zipPath;
+    private Zip zip;
+
+    public DumpContext(Backup backup) throws IOException{
+        this.backup = backup;
+        this.workspace = Files.createTempDirectory("backup");
+        this.zipPath = Files.createTempFile(backup.getCompanyId() + "-backup-", ".zip");
+        this.zip = Zip.out(zipPath);
+    }
+
+    public Path getWorkspace() {
+        return workspace;
+    }
+
+    public Backup getBackup() {
+        return backup;
+    }
+
+    public synchronized DumpContext append(DumpFile file) throws IOException{
+        if (zip.isClosed()) {
+            throw new IOException("zip file closed");
+        }
+        zip.append(file.getTempFile());
+        return this;
+    }
+
+    public File getZipFile() {
+        return zipPath.toFile();
+    }
+
+    public void close() {
+        try {
+            zip.close();
+        } catch (Exception e) {
+        };
+        try {
+            Files.delete(zipPath);
+        } catch (Exception e) {
+        };
+        try {
+            Files.delete(workspace);
+        } catch (Exception e) {
+        };
+    }
+}

+ 54 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/backup/DumpFile.java

@@ -0,0 +1,54 @@
+package com.usoftchina.saas.dc.backup;
+
+import com.usoftchina.saas.dc.po.DataSourceInfo;
+import org.springframework.util.FileCopyUtils;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author yingp
+ * @date 2019/1/21
+ */
+public class DumpFile {
+    private final DumpContext context;
+    private final String dcName;
+    private final String dbName;
+    private final String tableName;
+    private final String data;
+    private File tempFile;
+
+    public DumpFile(DumpContext context, DataSourceInfo ds, String tableName, String data) {
+        this.context = context;
+        this.dcName = ds.getDcName();
+        this.dbName = ds.getDbName();
+        this.tableName = tableName;
+        this.data = data;
+    }
+
+    public String getDcName() {
+        return dcName;
+    }
+
+    public String getDbName() {
+        return dbName;
+    }
+
+    public String getTableName() {
+        return tableName;
+    }
+
+    public String getData() {
+        return data;
+    }
+
+    public File getTempFile() throws IOException{
+        if (null == tempFile && null != data) {
+            File file = new File(context.getWorkspace().toFile(),
+                    dcName + "-" + dbName + "-" + tableName + ".json");
+            file.createNewFile();
+            FileCopyUtils.copy(data.getBytes(), file);
+        }
+        return tempFile;
+    }
+}

+ 54 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/backup/MysqlStrategy.java

@@ -0,0 +1,54 @@
+package com.usoftchina.saas.dc.backup;
+
+import com.usoftchina.saas.context.BaseContextHolder;
+import com.usoftchina.saas.dc.mapper.CommonMapper;
+import com.usoftchina.saas.dc.mapper.SchemaMapper;
+import com.usoftchina.saas.dc.po.DataSourceInfo;
+import com.usoftchina.saas.jdbc.DynamicDataSourceContextHolder;
+import com.usoftchina.saas.jdbc.DynamicDataSourceRegister;
+import com.usoftchina.saas.utils.CollectionUtils;
+import com.usoftchina.saas.utils.JsonUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2019/1/21
+ */
+@Component
+public class MysqlStrategy implements CompanyStrategy {
+    @Autowired
+    private DynamicDataSourceRegister dataSourceRegister;
+
+    @Autowired
+    private SchemaMapper schemaMapper;
+
+    @Autowired
+    private CommonMapper commonMapper;
+
+    public void exp(final DumpContext context, final DataSourceInfo ds) throws IOException{
+        final long companyId = BaseContextHolder.getCompanyId();
+        // 如果不存在数据源则自动创建
+        dataSourceRegister.createDataSource(ds);
+        DynamicDataSourceContextHolder.set(ds);
+        try {
+            // 查找所有与公司相关表
+            List<String> tables = schemaMapper.selectTableNameBySchema(ds.getDbRealName(), COMPANY_ID_COLUMN);
+            if (!CollectionUtils.isEmpty(tables)) {
+                for (String table : tables) {
+                    // 查找指定表的所有数据
+                    List<LinkedHashMap<String, Object>> data = commonMapper.select(String.format("select * from %s where %s=%s", table, COMPANY_ID_COLUMN, companyId));
+                    if (!CollectionUtils.isEmpty(data)) {
+                        context.append(new DumpFile(context, ds, table, JsonUtils.toJsonString(data)));
+                    }
+                }
+            }
+        } finally {
+            DynamicDataSourceContextHolder.clear();
+        }
+    }
+}

+ 61 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/controller/BackupController.java

@@ -0,0 +1,61 @@
+package com.usoftchina.saas.dc.controller;
+
+import com.usoftchina.saas.base.Result;
+import com.usoftchina.saas.dc.service.BackupService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author yingp
+ * @date 2019/1/15
+ */
+@RestController
+@RequestMapping("/backup")
+public class BackupController {
+
+    @Autowired
+    private BackupService backupService;
+
+    /**
+     * 备份
+     *
+     * @return
+     */
+    @PostMapping
+    public Result backup() {
+        backupService.newBackupTask();
+        return Result.success();
+    }
+
+    /**
+     * 查询列表
+     *
+     * @return
+     */
+    @GetMapping
+    public Result getBackups() {
+        return Result.success();
+    }
+
+    /**
+     * 删除单次备份
+     *
+     * @param id
+     * @return
+     */
+    @DeleteMapping
+    public Result deleteBackup(@RequestParam Long id) {
+        return Result.success();
+    }
+
+    /**
+     * 选择备份还原
+     *
+     * @param id
+     * @return
+     */
+    @PostMapping("/restore")
+    public Result restore(@RequestParam Long id) {
+        return Result.success();
+    }
+}

+ 4 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/controller/DataSourceInfoController.java

@@ -1,5 +1,6 @@
 package com.usoftchina.saas.dc.controller;
 
+import com.usoftchina.saas.auth.client.annotation.IgnoreAuth;
 import com.usoftchina.saas.base.Result;
 import com.usoftchina.saas.dc.cache.DataSourceCache;
 import com.usoftchina.saas.dc.dto.DataSourceInfoDTO;
@@ -22,6 +23,7 @@ public class DataSourceInfoController {
     private DataSourceInfoService dataSourceInfoService;
 
     @GetMapping
+    @IgnoreAuth
     public Result<DataSourceInfoDTO> getDataSourceInfo(@RequestParam("dcName") String dcName,
                                                 @RequestParam("dbName") String dbName) {
         return Result.success(dataSourceInfoService.findDataSourceInfo(dcName, dbName));
@@ -33,6 +35,7 @@ public class DataSourceInfoController {
      * @return
      */
     @GetMapping("/cache/clear")
+    @IgnoreAuth
     public Result clearCache(@RequestParam("dcName") String dcName,
                              @RequestParam("dbName") String dbName) {
         dataSourceInfoService.clearCache(dcName, dbName);
@@ -45,6 +48,7 @@ public class DataSourceInfoController {
      * @return
      */
     @GetMapping("/cache/clearAll")
+    @IgnoreAuth
     public Result clearCacheAll(@RequestParam(value = "dcName", required = false) String dcName,
                                 @RequestParam(value = "dbName", required = false) String dbName) {
         DataSourceCache.of(dcName, dbName).clear();

+ 49 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/mapper/BackupMapper.java

@@ -0,0 +1,49 @@
+package com.usoftchina.saas.dc.mapper;
+
+import com.usoftchina.saas.dc.po.Backup;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2019/1/15
+ */
+public interface BackupMapper {
+
+    /**
+     * 按公司查找
+     *
+     * @param companyId
+     * @return
+     */
+    List<Backup> selectByCompanyId(@Param("companyId") Long companyId);
+
+    /**
+     * 新增
+     * @param backup
+     * @return
+     */
+    Long insert(Backup backup);
+
+    /**
+     * 更新
+     * @param backup
+     * @return
+     */
+    int updateByPrimaryKey(Backup backup);
+
+    /**
+     * 更新
+     * @param backup
+     * @return
+     */
+    int updateByPrimaryKeySelective(Backup backup);
+
+    /**
+     * 删除
+     * @param id
+     * @return
+     */
+    int deleteByPrimaryKey(@Param("id") Long id);
+}

+ 18 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/mapper/CommonMapper.java

@@ -0,0 +1,18 @@
+package com.usoftchina.saas.dc.mapper;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2019/1/15
+ */
+public interface CommonMapper {
+    /**
+     * 传入sql执行查询,取出结果
+     *
+     * @param sql
+     * @return
+     */
+    List<LinkedHashMap<String, Object>> select(String sql);
+}

+ 17 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/mapper/DataSourceInfoMapper.java

@@ -3,6 +3,8 @@ package com.usoftchina.saas.dc.mapper;
 import com.usoftchina.saas.dc.po.DataSourceInfo;
 import org.apache.ibatis.annotations.Param;
 
+import java.util.List;
+
 
 /**
  * @author yingp
@@ -18,4 +20,19 @@ public interface DataSourceInfoMapper {
      * @return
      */
     DataSourceInfo selectByDcNameAndDbName(@Param("dcName") String dcName, @Param("dbName") String dbName);
+
+    /**
+     * 查找全部
+     *
+     * @return
+     */
+    List<DataSourceInfo> selectAll();
+
+    /**
+     * 按数据中心查找
+     *
+     * @param dcName
+     * @return
+     */
+    List<DataSourceInfo> selectByDcNameUseDefault(@Param("dcName") String dcName);
 }

+ 21 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/mapper/SchemaMapper.java

@@ -0,0 +1,21 @@
+package com.usoftchina.saas.dc.mapper;
+
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2019/1/15
+ */
+public interface SchemaMapper {
+    /**
+     * 按数据库、字段名查找表名
+     *
+     * @param schema
+     * @param columnName
+     * @return
+     */
+    List<String> selectTableNameBySchema(@Param("schema") String schema,
+                                         @Param("columnName") String columnName);
+}

+ 105 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/po/Backup.java

@@ -0,0 +1,105 @@
+package com.usoftchina.saas.dc.po;
+
+import com.usoftchina.saas.context.BaseContextHolder;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 数据备份记录
+ *
+ * @author yingp
+ * @date 2019/1/15
+ */
+public class Backup implements Serializable {
+    private Long id;
+    private Long filePath;
+    private Date createTime;
+    private Long creatorId;
+    private Long companyId;
+    /**
+     * 0 - 未开始
+     * 1 - 备份中
+     * -1 - 备份失败
+     * 2 - 完成备份
+     */
+    private short status;
+    private String message;
+
+    public Backup() {
+        this.createTime = new Date();
+        this.creatorId = BaseContextHolder.getUserId();
+        this.companyId = BaseContextHolder.getCompanyId();
+        this.status = Status.NOT_READY;
+    }
+
+    public Backup(long companyId) {
+        this.createTime = new Date();
+        this.creatorId = BaseContextHolder.getUserId();
+        this.companyId = companyId;
+        this.status = Status.NOT_READY;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getFilePath() {
+        return filePath;
+    }
+
+    public void setFilePath(Long filePath) {
+        this.filePath = filePath;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Long getCreatorId() {
+        return creatorId;
+    }
+
+    public void setCreatorId(Long creatorId) {
+        this.creatorId = creatorId;
+    }
+
+    public Long getCompanyId() {
+        return companyId;
+    }
+
+    public void setCompanyId(Long companyId) {
+        this.companyId = companyId;
+    }
+
+    public short getStatus() {
+        return status;
+    }
+
+    public void setStatus(short status) {
+        this.status = status;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public interface Status {
+        short NOT_READY = 0;
+        short DUMPING = 1;
+        short FAILED = -1;
+        short SUCCESS = 2;
+    }
+}

+ 34 - 1
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/po/DataSourceInfo.java

@@ -1,12 +1,15 @@
 package com.usoftchina.saas.dc.po;
 
+import com.usoftchina.saas.jdbc.Connectable;
+import com.usoftchina.saas.jdbc.JdbcUrl;
+
 import java.io.Serializable;
 
 /**
  * @author yingp
  * @date 2018/12/10
  */
-public class DataSourceInfo implements Serializable{
+public class DataSourceInfo  implements Connectable, Serializable{
     private Integer id;
     private String dcName;
     private String dbName;
@@ -15,6 +18,7 @@ public class DataSourceInfo implements Serializable{
     private Integer dbPort;
     private String dbUserName;
     private String dbPassword;
+    private String dbType;
 
     public Integer getId() {
         return id;
@@ -79,4 +83,33 @@ public class DataSourceInfo implements Serializable{
     public void setDbPassword(String dbPassword) {
         this.dbPassword = dbPassword;
     }
+
+    public String getDbType() {
+        return dbType;
+    }
+
+    public void setDbType(String dbType) {
+        this.dbType = dbType;
+    }
+
+    @Override
+    public String qualifier() {
+        return this.dcName + ":" + this.dbName;
+    }
+
+    @Override
+    public String url() {
+        return String.format(JdbcUrl.Type.optional(dbType).orElse(JdbcUrl.Type.MYSQL).getUrlTpl(),
+                dbHost, dbPort, dbRealName);
+    }
+
+    @Override
+    public String username() {
+        return this.dbUserName;
+    }
+
+    @Override
+    public String password() {
+        return this.dbPassword;
+    }
 }

+ 25 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/service/BackupService.java

@@ -0,0 +1,25 @@
+package com.usoftchina.saas.dc.service;
+
+import com.usoftchina.saas.dc.po.Backup;
+
+/**
+ * @author yingp
+ * @date 2019/1/15
+ */
+public interface BackupService {
+
+    /**
+     * 新增
+     *
+     * @return
+     */
+    Backup newBackupTask();
+
+    /**
+     * 删除
+     *
+     * @param id
+     * @return
+     */
+    boolean removeByPrimaryKey(Long id);
+}

+ 17 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/service/DataSourceInfoService.java

@@ -2,6 +2,8 @@ package com.usoftchina.saas.dc.service;
 
 import com.usoftchina.saas.dc.po.DataSourceInfo;
 
+import java.util.List;
+
 /**
  * @author yingp
  * @date 2018/12/10
@@ -18,4 +20,19 @@ public interface DataSourceInfoService {
     DataSourceInfo findDataSourceInfo(String dcName, String dbName);
 
     void clearCache(String dcName, String dbName);
+
+    /**
+     * 查找全部
+     *
+     * @return
+     */
+    List<DataSourceInfo> findAll();
+
+    /**
+     * 查找全部
+     *
+     * @param dcName
+     * @return
+     */
+    List<DataSourceInfo> findByDcNameUseDefault(String dcName);
 }

+ 119 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/service/impl/BackupServiceImpl.java

@@ -0,0 +1,119 @@
+package com.usoftchina.saas.dc.service.impl;
+
+import com.usoftchina.saas.account.cache.CompanyCache;
+import com.usoftchina.saas.account.cache.CompanyLockCache;
+import com.usoftchina.saas.context.SpringContextHolder;
+import com.usoftchina.saas.dc.backup.*;
+import com.usoftchina.saas.dc.mapper.BackupMapper;
+import com.usoftchina.saas.dc.po.Backup;
+import com.usoftchina.saas.dc.po.DataSourceInfo;
+import com.usoftchina.saas.dc.service.BackupService;
+import com.usoftchina.saas.dc.service.DataSourceInfoService;
+import com.usoftchina.saas.exception.Try;
+import com.usoftchina.saas.file.api.FileApi;
+import com.usoftchina.saas.file.dto.FileInfoDTO;
+import com.usoftchina.saas.file.util.FileUploadUtils;
+import com.usoftchina.saas.utils.CollectionUtils;
+import com.usoftchina.saas.utils.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2019/1/15
+ */
+@Service
+public class BackupServiceImpl implements BackupService {
+
+    @Autowired
+    private BackupMapper backupMapper;
+
+    @Autowired
+    private DataSourceInfoService dataSourceInfoService;
+
+    @Autowired
+    private FileApi fileApi;
+
+    @Autowired
+    private MysqlStrategy mysqlStrategy;
+
+    @Override
+    public Backup newBackupTask() {
+        Backup backup = new Backup();
+        backupMapper.insert(backup);
+        SpringContextHolder.getContext().publishEvent(new BackupReadyEvent(this, backup));
+        return backup;
+    }
+
+    public void dump(long companyId) throws Exception {
+        dump(new Backup(companyId));
+    }
+
+    public void dump(Backup backup) throws Exception {
+        String dcName = StringUtils.nullIf(CompanyCache.current().getCompany().getDcName(), "default");
+        List<DataSourceInfo> dss = dataSourceInfoService.findByDcNameUseDefault(dcName);
+        if (!CollectionUtils.isEmpty(dss)) {
+            DumpContext context = new DumpContext(backup);
+            try {
+                dss.parallelStream().forEach(Try.accept(ds -> {
+                    if ("mysql".equals(ds.getDbType())) {
+                        mysqlStrategy.exp(context, ds);
+                    }
+                }));
+                FileInfoDTO fileInfoDTO = FileUploadUtils.upload(context.getZipFile());
+                SpringContextHolder.getContext().publishEvent(
+                        new BackupSuccessEvent(this, backup, fileInfoDTO));
+            } finally {
+                context.close();
+            }
+        }
+    }
+
+    @Override
+    public boolean removeByPrimaryKey(Long id) {
+        return backupMapper.deleteByPrimaryKey(id) > -1;
+    }
+
+    @Async
+    @EventListener(BackupReadyEvent.class)
+    public void onBackupReady(BackupReadyEvent event) {
+        Backup backup = event.getBackup();
+        try {
+            backup.setStatus(Backup.Status.DUMPING);
+            backupMapper.updateByPrimaryKeySelective(backup);
+            // 为防止导出过程中有人操作,导致部分数据不一致,在导出前锁定该公司不允许操作
+            CompanyLockCache.lock(backup.getCompanyId());
+            dump(backup);
+        } catch (Exception e) {
+            SpringContextHolder.getContext().publishEvent(
+                    new BackupFailedEvent(this, backup, e));
+        }
+    }
+
+    @Async
+    @EventListener(BackupFailedEvent.class)
+    public void onBackupFailed(BackupFailedEvent event) {
+        Backup backup = event.getBackup();
+        // 解除锁定
+        CompanyLockCache.unlock(backup.getCompanyId());
+        backup.setStatus(Backup.Status.FAILED);
+        backup.setMessage(event.getException().getMessage());
+        backupMapper.updateByPrimaryKeySelective(backup);
+    }
+
+    @Async
+    @EventListener(BackupSuccessEvent.class)
+    public void onBackupSuccess(BackupSuccessEvent event) {
+        Backup backup = event.getBackup();
+        // 解除锁定
+        CompanyLockCache.unlock(backup.getCompanyId());
+        backup.setStatus(Backup.Status.SUCCESS);
+        backup.setFilePath(event.getFileInfo().getId());
+        backupMapper.updateByPrimaryKeySelective(backup);
+    }
+
+}

+ 12 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/service/impl/DataSourceInfoServiceImpl.java

@@ -7,6 +7,8 @@ import com.usoftchina.saas.dc.service.DataSourceInfoService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.util.List;
+
 /**
  * @author yingp
  * @date 2018/12/10
@@ -26,4 +28,14 @@ public class DataSourceInfoServiceImpl implements DataSourceInfoService{
     public void clearCache(String dcName, String dbName) {
         DataSourceCache.of(dcName, dbName).hdel();
     }
+
+    @Override
+    public List<DataSourceInfo> findAll() {
+        return dataSourceInfoMapper.selectAll();
+    }
+
+    @Override
+    public List<DataSourceInfo> findByDcNameUseDefault(String dcName) {
+        return dataSourceInfoMapper.selectByDcNameUseDefault(dcName);
+    }
 }

+ 3 - 1
base-servers/datacenter/datacenter-server/src/main/resources/application.yml

@@ -78,4 +78,6 @@ info:
   spring-cloud-version: '@spring.cloud.version@'
 mybatis:
   type-aliases-package: com.usoftchina.saas.dc.po
-  mapper-locations: classpath:mapper/*.xml
+  mapper-locations: classpath:mapper/*.xml
+  configuration:
+    call-setters-on-nulls: true

+ 62 - 0
base-servers/datacenter/datacenter-server/src/main/resources/mapper/BackupMapper.xml

@@ -0,0 +1,62 @@
+<?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.dc.mapper.BackupMapper">
+    <resultMap id="BaseResultMap" type="com.usoftchina.saas.dc.po.Backup">
+        <id column="id" jdbcType="BIGINT" property="id"/>
+        <result column="file_path" jdbcType="VARCHAR" property="filePath"/>
+        <result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
+        <result column="creator_id" jdbcType="BIGINT" property="creatorId"/>
+        <result column="company_id" jdbcType="BIGINT" property="companyId"/>
+        <result column="status" jdbcType="INTEGER" property="status"/>
+        <result column="message" jdbcType="VARCHAR" property="message"/>
+    </resultMap>
+    <sql id="baseColumns">
+        id,file_path,create_time,creator_id,company_id,status,message
+    </sql>
+    <select id="selectByCompanyId" resultMap="BaseResultMap">
+        select <include refid="baseColumns"/> from backup where company_id=#{companyId} order by create_time desc
+    </select>
+    <insert id="insert" parameterType="com.usoftchina.saas.dc.po.Backup"
+            useGeneratedKeys="true" keyProperty="id">
+        insert into backup(file_path,create_time,creator_id,company_id,status,message)
+        values (#{filePath,jdbcType=VARCHAR},#{createTime,jdbcType=TIMESTAMP},#{creatorId,jdbcType=BIGINT},
+        #{companyId,jdbcType=BIGINT}, #{status,jdbcType=INTEGER}, #{message,jdbcType=VARCHAR})
+    </insert>
+    <update id="updateByPrimaryKey" parameterType="com.usoftchina.saas.dc.po.Backup">
+        update backup set
+        file_path=#{filePath,jdbcType=VARCHAR},
+        create_time=#{createTime,jdbcType=TIMESTAMP},
+        creator_id=#{creatorId,jdbcType=BIGINT},
+        company_id=#{companyId,jdbcType=BIGINT},
+        status=#{status,jdbcType=INTEGER},
+        message=#{message,jdbcType=VARCHAR}
+        where id=#{id,jdbcType=BIGINT}
+    </update>
+    <update id="updateByPrimaryKeySelective" parameterType="com.usoftchina.saas.dc.po.Backup">
+        update backup
+        <set>
+            <if test="filePath != null">
+                file_path=#{filePath,jdbcType=VARCHAR},
+            </if>
+            <if test="createTime != null">
+                create_time=#{createTime,jdbcType=TIMESTAMP},
+            </if>
+            <if test="creatorId != null">
+                creator_id=#{creatorId,jdbcType=BIGINT},
+            </if>
+            <if test="companyId != null">
+                company_id=#{companyId,jdbcType=BIGINT},
+            </if>
+            <if test="status != null">
+                status=#{status,jdbcType=INTEGER},
+            </if>
+            <if test="message != null">
+                message=#{status,jdbcType=VARCHAR},
+            </if>
+        </set>
+        where id=#{id,jdbcType=BIGINT}
+    </update>
+    <delete id="deleteByPrimaryKey" parameterType="java.lang.Long" >
+        delete from backup where id = #{id,jdbcType=BIGINT}
+    </delete>
+</mapper>

+ 7 - 0
base-servers/datacenter/datacenter-server/src/main/resources/mapper/CommonMapper.xml

@@ -0,0 +1,7 @@
+<?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.dc.mapper.CommonMapper">
+    <select id="select" parameterType="java.lang.String" resultType="java.util.LinkedHashMap">
+        #{sql}
+    </select>
+</mapper>

+ 11 - 1
base-servers/datacenter/datacenter-server/src/main/resources/mapper/DataSourceInfoMapper.xml

@@ -10,11 +10,21 @@
         <result column="db_port" jdbcType="INTEGER" property="dbPort"/>
         <result column="db_username" jdbcType="VARCHAR" property="dbUserName"/>
         <result column="db_password" jdbcType="VARCHAR" property="dbPassword"/>
+        <result column="db_type" jdbcType="VARCHAR" property="dbType"/>
     </resultMap>
     <sql id="baseColumns">
-        id,dc_name,db_name,db_realname,db_host,db_port,db_username,db_password
+        id,dc_name,db_name,db_realname,db_host,db_port,db_username,db_password,db_type
     </sql>
     <select id="selectByDcNameAndDbName" resultMap="BaseResultMap">
         select <include refid="baseColumns"/> from datasource where dc_name=#{dcName} and db_name=#{dbName}
     </select>
+
+    <select id="selectAll" resultMap="BaseResultMap">
+        select <include refid="baseColumns"/> from datasource order by dc_name,db_name
+    </select>
+
+    <select id="selectByDcNameUseDefault" resultMap="BaseResultMap">
+        select <include refid="baseColumns"/> from datasource a where not exists
+        (select 1 from datasource b where b.dc_name=#{dcName} and b.db_name = a.db_name and b.id &lt;&gt; a.id);
+    </select>
 </mapper>

+ 8 - 0
base-servers/datacenter/datacenter-server/src/main/resources/mapper/SchemaMapper.xml

@@ -0,0 +1,8 @@
+<?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.dc.mapper.SchemaMapper">
+    <select id="selectTableNameBySchema">
+        select table_name information_schema.`COLUMNS` where table_schema=#{schema,jdbcType=VARCHAR}
+        and column_name=#{columnName,jdbcType=VARCHAR}
+    </select>
+</mapper>

+ 90 - 0
base-servers/file/file-api/src/main/java/com/usoftchina/saas/file/util/DiskMultipartFile.java

@@ -0,0 +1,90 @@
+package com.usoftchina.saas.file.util;
+
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.FileCopyUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author yingp
+ * @date 2019/1/15
+ */
+public class DiskMultipartFile implements MultipartFile {
+    private final String name;
+
+    private String originalFilename;
+
+    @Nullable
+    private String contentType;
+
+    private final byte[] content;
+
+    public DiskMultipartFile(File file) throws IOException {
+        this(file.getName(), "", null, FileCopyUtils.copyToByteArray(file));
+    }
+
+    public DiskMultipartFile(String name, InputStream contentStream) throws IOException {
+        this(name, "", null, FileCopyUtils.copyToByteArray(contentStream));
+    }
+
+    public DiskMultipartFile(
+            String name, @Nullable String originalFilename, @Nullable String contentType, @Nullable byte[] content) {
+        Assert.hasLength(name, "Name must not be null");
+        this.name = name;
+        this.originalFilename = (originalFilename != null ? originalFilename : "");
+        this.contentType = contentType;
+        this.content = (content != null ? content : new byte[0]);
+    }
+
+    public DiskMultipartFile(
+            String name, @Nullable String originalFilename, @Nullable String contentType, InputStream contentStream)
+            throws IOException {
+        this(name, originalFilename, contentType, FileCopyUtils.copyToByteArray(contentStream));
+    }
+
+    @Override
+    public String getName() {
+        return this.name;
+    }
+
+    @Override
+    public String getOriginalFilename() {
+        return this.originalFilename;
+    }
+
+    @Override
+    @Nullable
+    public String getContentType() {
+        return this.contentType;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return this.content.length == 0;
+    }
+
+    @Override
+    public long getSize() {
+        return this.content.length;
+    }
+
+    @Override
+    public byte[] getBytes() throws IOException {
+        return this.content;
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException {
+        return new ByteArrayInputStream(this.content);
+    }
+
+    @Override
+    public void transferTo(File dest) throws IOException, IllegalStateException {
+        FileCopyUtils.copy(this.content, dest);
+    }
+}

+ 34 - 0
base-servers/file/file-api/src/main/java/com/usoftchina/saas/file/util/FileUploadUtils.java

@@ -0,0 +1,34 @@
+package com.usoftchina.saas.file.util;
+
+import com.usoftchina.saas.base.Result;
+import com.usoftchina.saas.context.SpringContextHolder;
+import com.usoftchina.saas.exception.BizException;
+import com.usoftchina.saas.file.api.FileApi;
+import com.usoftchina.saas.file.dto.FileInfoDTO;
+
+import java.io.File;
+
+/**
+ * @author yingp
+ * @date 2019/1/15
+ */
+public class FileUploadUtils {
+
+    /**
+     * 文件上传
+     *
+     * @param file
+     * @return
+     * @throws Exception
+     */
+    public static FileInfoDTO upload(File file) throws Exception{
+        FileApi api = SpringContextHolder.getBean(FileApi.class);
+        Result<FileInfoDTO> result = api.upload(null, new DiskMultipartFile(file));
+        if (result.isSuccess()) {
+            return result.getData();
+        } else {
+            throw new BizException(result.getCode(), result.getMessage());
+        }
+    }
+
+}

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

@@ -224,7 +224,7 @@ public class FileController {
             List<FileInfo> files = fileService.findByFolderId(info.getId());
             if (!CollectionUtils.isEmpty(files)) {
                 for (FileInfo f : files) {
-                    cascadeOutput(f, directory,out);
+                    cascadeOutput(f, directory, out);
                 }
             }
         } else {

+ 8 - 3
base-servers/gateway-server/src/main/java/com/usoftchina/saas/gateway/config/AuthFilter.java

@@ -1,10 +1,12 @@
 package com.usoftchina.saas.gateway.config;
 
 import com.usoftchina.saas.account.cache.AccountCache;
+import com.usoftchina.saas.account.cache.CompanyLockCache;
 import com.usoftchina.saas.account.cache.ResourceCache;
 import com.usoftchina.saas.account.dto.AccountDTO;
 import com.usoftchina.saas.account.dto.UrlResourceDTO;
 import com.usoftchina.saas.auth.api.AuthApi;
+import com.usoftchina.saas.auth.common.constant.AuthConstants;
 import com.usoftchina.saas.auth.common.cookie.CookieHelper;
 import com.usoftchina.saas.auth.common.cookie.CookieInfo;
 import com.usoftchina.saas.auth.common.jwt.JwtHelper;
@@ -77,7 +79,7 @@ public class AuthFilter implements GlobalFilter, Ordered {
                     if (ExceptionCode.JWT_TOKEN_EXPIRED.getCode() == e.getCode()) {
                         jwt = (JwtInfo)RedisUtil.get(key);
                         if (jwt == null) {
-                            throw new BizException(ExceptionCode.JWT_TOKEN_EXPIRED.getCode(), ExceptionCode.JWT_TOKEN_EXPIRED.getMessage());
+                            throw e;
                         }
                         Result<TokenDTO> result = authApi.generateToken(jwt);
                         if (result.isSuccess() && null != result.getData()) {
@@ -93,12 +95,16 @@ public class AuthFilter implements GlobalFilter, Ordered {
                         RedisUtil.del(key);
                     }
                 }
+                // 公司状态为已锁定
+                CompanyLockCache.assertNotLocked(jwt.getCompanyId());
+
                 BaseContextHolder.setAppId(jwt.getAppId());
                 BaseContextHolder.setUserId(jwt.getUserId());
                 BaseContextHolder.setCompanyId(jwt.getCompanyId());
                 BaseContextHolder.setUserName(jwt.getRealName());
                 BaseContextHolder.setToken(token);
-                if (jwt.getUserId() != -99999) {    //非虚拟用户登录时
+                // 非虚拟用户登录时
+                if (jwt.getUserId() != AuthConstants.VIRTUAL_ACCOUNT_ID) {
                     AccountDTO accountDTO = AccountCache.current().getAccount();
                     if (null == accountDTO) {
                         throw new BizException(ExceptionCode.USER_NOT_EXIST);
@@ -106,7 +112,6 @@ public class AuthFilter implements GlobalFilter, Ordered {
                     // 鉴别角色权限
                     checkPermission(exchange.getRequest(), jwt, accountDTO);
                 }
-
             }
             return chain.filter(exchange);
         } finally {

+ 22 - 0
framework/commons-compress/pom.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>framework</artifactId>
+        <groupId>com.usoftchina.saas</groupId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>commons-compress</artifactId>
+    <description>compress utils</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-compress</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 194 - 0
framework/commons-compress/src/main/java/com/usoftchina/saas/commons/compress/Zip.java

@@ -0,0 +1,194 @@
+package com.usoftchina.saas.commons.compress;
+
+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.compress.utils.IOUtils;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * zip压缩工具
+ *
+ * @author yingp
+ * @date 2019/1/15
+ */
+public class Zip {
+
+    private final ArchiveOutputStream out;
+    private AtomicBoolean closed;
+
+    private Zip(OutputStream out) {
+        this.out = new ZipArchiveOutputStream(out);
+    }
+
+    public static Zip out(Path path) throws IOException{
+        if (!Files.exists(path)) {
+            Files.createFile(path);
+        }
+        return new Zip(new FileOutputStream(path.toFile()));
+    }
+
+    public static Zip out(File file) throws IOException{
+        if (!file.exists()) {
+            file.createNewFile();
+        }
+        return new Zip(new FileOutputStream(file));
+    }
+
+    public static Zip out(OutputStream out) {
+        return new Zip(out);
+    }
+
+    /**
+     * 添加文件夹
+     *
+     * @param folderName
+     * @return
+     * @throws IOException
+     */
+    public ZipFolder appendFolder(String folderName) throws IOException{
+        return appendFolder(null, folderName);
+    }
+
+    private ZipFolder appendFolder(ZipFolder parent, String folderName) throws IOException{
+        ZipFolder folder = new ZipFolder(this, parent, folderName);
+        out.putArchiveEntry(new ZipArchiveEntry(folder.getDirectory()));
+        out.closeArchiveEntry();
+        return folder;
+    }
+
+    private Zip append(ZipFolder parent, InputStream in, String name) throws IOException{
+        out.putArchiveEntry(new ZipArchiveEntry((null == parent ? "" : parent.getDirectory()) + name));
+        IOUtils.copy(in, out);
+        out.closeArchiveEntry();
+        return this;
+    }
+
+    /**
+     * 添加文件
+     *
+     * @param in
+     * @param name
+     * @return
+     * @throws IOException
+     */
+    public Zip append(InputStream in, String name) throws IOException{
+        return append(null, in, name);
+    }
+
+    private Zip append(ZipFolder parent, File file) throws IOException{
+        if (file.isDirectory()) {
+            ZipFolder folder = appendFolder(parent, file.getName());
+            for (File f : file.listFiles()) {
+                append(folder, f);
+            }
+        } else {
+            InputStream in = new BufferedInputStream(new FileInputStream(file));
+            append(parent, in, file.getName());
+            IOUtils.closeQuietly(in);
+        }
+        return this;
+    }
+
+    /**
+     * 批量添加文件(夹)
+     *
+     * @param files
+     * @return
+     * @throws IOException
+     */
+    public Zip append(File... files) throws IOException{
+        for (File file : files) {
+            append(null, file);
+        }
+        return this;
+    }
+
+    /**
+     * 完成写入后关闭
+     */
+    public void close() {
+        IOUtils.closeQuietly(out);
+        closed.set(true);
+    }
+
+    public boolean isClosed() {
+        return closed.get();
+    }
+
+    public class ZipFolder {
+        private final Zip zip;
+        private ZipFolder parent;
+        private final String directory;
+
+        public ZipFolder(Zip zip, ZipFolder parent, String directory) {
+            this.zip = zip;
+            this.parent = parent;
+            this.directory = (null == parent ? "" : parent.getDirectory()) + directory + "/";
+        }
+
+        /**
+         * 往当前文件夹添加文件夹
+         *
+         * @param folderName
+         * @return
+         * @throws IOException
+         */
+        public ZipFolder appendFolder(String folderName) throws IOException{
+            return zip.appendFolder(this, folderName);
+        }
+
+        /**
+         * 往当前文件夹添加文件
+         *
+         * @param in
+         * @param name
+         * @return
+         * @throws IOException
+         */
+        public ZipFolder append(InputStream in, String name) throws IOException{
+            zip.append(this, in, name);
+            return this;
+        }
+
+        /**
+         * 往当前文件夹批量添加文件(夹)
+         *
+         * @param files
+         * @return
+         * @throws IOException
+         */
+        public ZipFolder append(File... files) throws IOException{
+            for (File file : files) {
+                zip.append(this, file);
+            }
+            return this;
+        }
+
+        public String getDirectory() {
+            return directory;
+        }
+
+        /**
+         * 回到上级文件夹,这样可以继续操作上级文件夹
+         *
+         * @return
+         */
+        public ZipFolder parent() {
+            return parent;
+        }
+
+        /**
+         * 回到zip文件,这样可以继续操作根目录
+         *
+         * @return
+         */
+        public Zip root() {
+            return zip;
+        }
+    }
+}

+ 29 - 0
framework/commons-compress/src/test/java/com/usoftchina/saas/commons/compress/ZipTest.java

@@ -0,0 +1,29 @@
+package com.usoftchina.saas.commons.compress;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/**
+ * @author yingp
+ * @date 2019/1/15
+ */
+public class ZipTest {
+    public static void main(String[] args) throws Exception{
+        Path srcPath = Files.createTempDirectory("backup");
+        File file1 = new File(srcPath.toFile(), "1.txt");
+        file1.createNewFile();
+        File file2 = new File(srcPath.toFile(), "2.txt");
+        file2.createNewFile();
+
+        Path zipPath = Files.createTempFile("backup", ".zip");
+        Zip.out(zipPath)
+                .appendFolder("测试").append(file1, file2)
+                .root().close();
+
+        System.out.println(srcPath.toFile().getAbsolutePath());
+        System.out.println(zipPath.toFile().getAbsolutePath());
+//        Files.delete(srcPath);
+//        Files.delete(zipPath);
+    }
+}

+ 83 - 0
framework/core/src/main/java/com/usoftchina/saas/cache/RedisListCache.java

@@ -0,0 +1,83 @@
+package com.usoftchina.saas.cache;
+
+import org.springframework.data.redis.core.BoundListOperations;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+/**
+ * @author yingp
+ * @date 2019/1/15
+ */
+public abstract class RedisListCache<V>{
+
+    private final Supplier<RedisTemplate<String, V>> redisTemplate;
+    private final String key;
+
+    public RedisListCache(Supplier<RedisTemplate<String, V>> redisTemplate, String key) {
+        this.redisTemplate = redisTemplate;
+        this.key = key;
+    }
+
+    protected RedisTemplate<String, V> getRedisTemplate() {
+        return redisTemplate.get();
+    }
+
+    protected BoundListOperations<String, V> getBoundListOperations() {
+        return getRedisTemplate().boundListOps(key);
+    }
+
+    /**
+     * 查找list数据
+     *
+     * @return
+     */
+    public Optional<List<V>> values() {
+        return Optional.ofNullable(this.getBoundListOperations().range(0, -1));
+    }
+
+    public long size() {
+        return this.getBoundListOperations().size();
+    }
+
+    /**
+     * 添加
+     *
+     * @param v
+     */
+    public void put(V v) {
+        this.getBoundListOperations().leftPush(v);
+    }
+
+    /**
+     * 批量添加
+     *
+     * @param values
+     */
+    public void putAll(V... values) {
+        this.getBoundListOperations().leftPushAll(values);
+    }
+
+    /**
+     * 删除
+     *
+     * @param v
+     */
+    public void remove(V v) {
+        this.getBoundListOperations().remove(1, v);
+    }
+
+    /**
+     * 是否存在
+     *
+     * @param v
+     * @return
+     */
+    public boolean contains(V v) {
+        Optional<List<V>> values = values();
+        return values.isPresent() && values.get().contains(v);
+    }
+
+}

+ 69 - 0
framework/core/src/main/java/com/usoftchina/saas/cache/RedisSetCache.java

@@ -0,0 +1,69 @@
+package com.usoftchina.saas.cache;
+
+import org.springframework.data.redis.core.BoundSetOperations;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
+
+/**
+ * @author yingp
+ * @date 2019/1/15
+ */
+public abstract class RedisSetCache<V> {
+    private final Supplier<RedisTemplate<String, V>> redisTemplate;
+    private final String key;
+
+    public RedisSetCache(Supplier<RedisTemplate<String, V>> redisTemplate, String key) {
+        this.redisTemplate = redisTemplate;
+        this.key = key;
+    }
+
+    protected RedisTemplate<String, V> getRedisTemplate() {
+        return redisTemplate.get();
+    }
+
+    protected BoundSetOperations<String, V> getBoundSetOperations() {
+        return getRedisTemplate().boundSetOps(key);
+    }
+
+    /**
+     * 查找全部数据
+     *
+     * @return
+     */
+    public Optional<Set<V>> values() {
+        return Optional.ofNullable(this.getBoundSetOperations().members());
+    }
+
+    public long size() {
+        return this.getBoundSetOperations().size();
+    }
+
+    /**
+     * 添加
+     * @param values
+     */
+    public void add(V... values) {
+        this.getBoundSetOperations().add(values);
+    }
+
+    /**
+     * 删除
+     * @param values
+     */
+    public void remove(V... values) {
+        this.getBoundSetOperations().remove(values);
+    }
+
+    /**
+     * 是否存在
+     *
+     * @param value
+     * @return
+     */
+    public boolean contains(V value) {
+        return this.getBoundSetOperations().isMember(value);
+    }
+}

+ 5 - 0
framework/core/src/main/java/com/usoftchina/saas/context/SpringContextHolder.java

@@ -35,6 +35,11 @@ public class SpringContextHolder {
         return context.getBean(name, clazz);
     }
 
+    public static ApplicationContext getContext() {
+        Assert.notNull(context, "spring context not ready");
+        return context;
+    }
+
     /**
      * 获取当前环境
      *

+ 1 - 0
framework/core/src/main/java/com/usoftchina/saas/exception/ExceptionCode.java

@@ -36,6 +36,7 @@ public enum ExceptionCode implements BaseExceptionCode {
     COMPANY_DOMAIN_EXIST(52002, "域名已存在"),
     COMPANY_NOT_EXIST(52003, "企业不存在"),
     COMPANY_NOT_BIND(52004, "未绑定该公司"),
+    COMPANY_LOCKED(52005, "该公司已锁定"),
     USER_NAME_EXIST(53000, "用户名已注册"),
     USER_MOBILE_EXIST(53001, "手机号已注册"),
     USER_EMAIL_EXIST(53002, "邮箱已注册"),

+ 88 - 0
framework/core/src/main/java/com/usoftchina/saas/exception/Try.java

@@ -0,0 +1,88 @@
+package com.usoftchina.saas.exception;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * @author yingp
+ * @date 2019/1/21
+ */
+public abstract class Try {
+
+    /**
+     * 捕获异常,抛出运行时异常
+     *
+     * @param consumer
+     * @param <T>
+     * @return
+     */
+    public static <T> Consumer<T> accept(UncheckedConsumer<T, Exception> consumer) {
+        Objects.requireNonNull(consumer);
+        return t -> {
+            try {
+                consumer.accept(t);
+            } catch (Exception ex) {
+                throw new RuntimeException(ex);
+            }
+        };
+    }
+
+    /**
+     * 捕获异常,抛出运行时异常
+     *
+     * @param mapper
+     * @param <T>
+     * @return
+     */
+    public static <T, R> Function<T, R> apply(UncheckedFunction<T, R> mapper) {
+        Objects.requireNonNull(mapper);
+        return t -> {
+            try {
+                return mapper.apply(t);
+            } catch (Exception ex) {
+                throw new RuntimeException(ex);
+            }
+        };
+    }
+
+    /**
+     * 捕获异常,返回指定结果
+     *
+     * @param mapper
+     * @param <T>
+     * @return
+     */
+    public static <T, R> Function<T, R> apply(UncheckedFunction<T, R> mapper, R defaultR) {
+        Objects.requireNonNull(mapper);
+        return t -> {
+            try {
+                return mapper.apply(t);
+            } catch (Exception ex) {
+                System.err.println(ex.getMessage());
+                return defaultR;
+            }
+        };
+    }
+
+    @FunctionalInterface
+    public interface UncheckedFunction<T, R> {
+        /**
+         * 支持lambda抛出异常
+         * @param t
+         * @return
+         * @throws Exception
+         */
+        R apply(T t) throws Exception;
+    }
+
+    @FunctionalInterface
+    public interface UncheckedConsumer<T, E extends Exception> {
+        /**
+         * 支持lambda抛出异常
+         * @param t
+         * @throws E
+         */
+        void accept(T t) throws E;
+    }
+}

+ 3 - 1
framework/core/src/main/java/com/usoftchina/saas/jdbc/DynamicDataSourceContextHolder.java

@@ -1,11 +1,13 @@
 package com.usoftchina.saas.jdbc;
 
+import com.alibaba.ttl.TransmittableThreadLocal;
+
 /**
  * Created by Pro1 on 2017/7/27.
  */
 public class DynamicDataSourceContextHolder {
 
-    private static final ThreadLocal<String> contextHolder = new InheritableThreadLocal<>();
+    private static final ThreadLocal<String> contextHolder = new TransmittableThreadLocal<>();
 
     public static void set(String dataSource) {
         contextHolder.set(dataSource);

+ 22 - 5
framework/core/src/main/java/com/usoftchina/saas/jdbc/JdbcUrl.java

@@ -1,6 +1,7 @@
 package com.usoftchina.saas.jdbc;
 
 import java.util.Locale;
+import java.util.Optional;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -23,17 +24,21 @@ public class JdbcUrl {
     }
 
     public enum Type {
-        MYSQL(".+\\/([^\\/]+)\\?.+"),
-        POSTGRESQL(null),
-        ORACLE(null),
-        UNKNOWN(null);
+        MYSQL(".+\\/([^\\/]+)\\?.+",
+                "jdbc:mysql://%s:%s/%s?characterEncoding=utf-8&useSSL=false&allowMultiQueries=true"),
+        POSTGRESQL(null, null),
+        ORACLE(null, null),
+        MONGODB(null, "mongodb://%s:%s/%s"),
+        UNKNOWN(null, null);
 
         private Pattern namePattern;
+        private String urlTpl;
 
-        Type(String nameRegexp) {
+        Type(String nameRegexp, String urlTpl) {
             if (null != nameRegexp) {
                 this.namePattern = Pattern.compile(nameRegexp);
             }
+            this.urlTpl = urlTpl;
         }
 
         protected String getUrlPrefix() {
@@ -56,5 +61,17 @@ public class JdbcUrl {
         public Pattern getNamePattern() {
             return namePattern;
         }
+
+        public static Optional<Type> optional(String type) {
+            try {
+                return Optional.ofNullable(Type.valueOf(type.toUpperCase()));
+            } catch (Exception e) {
+                return Optional.empty();
+            }
+        }
+
+        public String getUrlTpl() {
+            return urlTpl;
+        }
     }
 }

+ 1 - 0
framework/pom.xml

@@ -16,6 +16,7 @@
         <module>core</module>
         <module>server-starter</module>
         <module>test-starter</module>
+        <module>commons-compress</module>
     </modules>
 
 </project>

+ 6 - 0
framework/server-starter/pom.xml

@@ -38,6 +38,12 @@
             <artifactId>mybatis-spring-boot-starter</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+            <scope>provided</scope>
+            <optional>true</optional>
+        </dependency>
         <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>javax.servlet-api</artifactId>

+ 2 - 1
framework/server-starter/src/main/java/com/usoftchina/saas/server/ServerAutoConfiguration.java

@@ -23,7 +23,8 @@ import org.springframework.context.annotation.Configuration;
 @Configuration
 @ComponentScan(basePackages = {
         "com.usoftchina.saas.server.error",
-        "com.usoftchina.saas.server.web"
+        "com.usoftchina.saas.server.web",
+        "com.usoftchina.saas.server.cache"
 })
 public class ServerAutoConfiguration {
 

+ 4 - 2
framework/core/src/main/java/com/usoftchina/saas/cache/RedisConfig.java → framework/server-starter/src/main/java/com/usoftchina/saas/server/cache/DefaultRedisConfig.java

@@ -1,8 +1,9 @@
-package com.usoftchina.saas.cache;
+package com.usoftchina.saas.server.cache;
 
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
 import com.fasterxml.jackson.annotation.PropertyAccessor;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
@@ -15,7 +16,8 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
  * @create: 2018-12-27 10:32
  **/
 @Configuration
-public class RedisConfig {
+@ConditionalOnClass({RedisTemplate.class})
+public class DefaultRedisConfig {
 
     @Bean
     @SuppressWarnings("all")

+ 5 - 0
pom.xml

@@ -217,6 +217,11 @@
                 <artifactId>file-api</artifactId>
                 <version>${project.release.version}</version>
             </dependency>
+            <dependency>
+                <groupId>com.usoftchina.saas</groupId>
+                <artifactId>commons-compress</artifactId>
+                <version>${project.release.version}</version>
+            </dependency>
             <dependency>
                 <groupId>com.usoftchina.saas</groupId>
                 <artifactId>mail-dto</artifactId>