Browse Source

Merge branch 'dev' of ssh://10.10.100.21/source/saas-platform into dev

guq 7 years ago
parent
commit
4db55ff105
32 changed files with 477 additions and 352 deletions
  1. 3 2
      applications/document/document-server/src/main/java/com/usoftchina/saas/document/service/impl/ProductServiceImpl.java
  2. 2 1
      applications/sale/sale-server/src/main/resources/application.yml
  3. 4 1
      applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/task/SendPurchaseInResTask.java
  4. 4 1
      applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/task/SendPurchaseInTask.java
  5. 4 1
      applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/task/SendPurchaseOutResTask.java
  6. 4 1
      applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/task/SendPurchaseOutTask.java
  7. 1 1
      applications/transfers/transfers-server/src/main/resources/application.yml
  8. 5 4
      applications/transfers/transfers-server/src/test/java/com/usoftchina/saas/transfers/service/RabbitSendServiceTest.java
  9. 2 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/DatacenterApplication.java
  10. 6 1
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/controller/SnapshotController.java
  11. 33 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/mapper/CommonMapper.java
  12. 15 10
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/po/SnapshotUsage.java
  13. 11 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/repository/SnapshotDataRepository.java
  14. 23 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/service/SnapshotUsageService.java
  15. 45 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/service/impl/SnapshotUsageServiceImpl.java
  16. 15 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/snapshot/event/ReadyToRestoreEvent.java
  17. 22 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/snapshot/event/RestoreFailedEvent.java
  18. 15 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/snapshot/event/RestoredEvent.java
  19. 60 1
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/snapshot/event/SnapshotLifecycle.java
  20. 22 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/snapshot/event/SnapshotUseEvent.java
  21. 27 2
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/snapshot/support/MysqlStrategy.java
  22. 25 0
      base-servers/datacenter/datacenter-server/src/main/resources/mapper/CommonMapper.xml
  23. 4 3
      base-servers/datacenter/datacenter-server/src/main/resources/mapper/SchemaMapper.xml
  24. 1 1
      base-servers/datacenter/datacenter-server/src/test/java/com/usoftchina/saas/dc/SnapshotServiceTest.java
  25. 45 0
      base-servers/datacenter/datacenter-server/src/test/java/com/usoftchina/saas/dc/SnapshotUsageServiceTest.java
  26. 1 0
      frontend/saas-web/app/util/FormUtil.js
  27. 3 3
      frontend/saas-web/app/view/core/base/GridPanel.js
  28. 2 1
      frontend/saas-web/app/view/sale/b2b/PurchaseDetailController.js
  29. 2 2
      frontend/saas-web/app/view/sys/account/DataList.js
  30. 2 2
      frontend/saas-web/app/view/sys/maxnumbers/DataList.js
  31. 66 309
      frontend/saas-web/app/view/sys/messagelog/DataList.js
  32. 3 5
      frontend/saas-web/app/view/sys/power/TreePanel.js

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

@@ -234,8 +234,9 @@ public class ProductServiceImpl extends CommonBaseServiceImpl<ProductMapper, Pro
     private void uploadChangeToB2B(Product product) {
         if (ConfigsCache.current().enableB2B()){
             Product oldProduct = getMapper().selectByPrimaryKey(product.getId());
-            if (!oldProduct.getPr_spec().equals(product.getPr_spec()) || !oldProduct.getPr_orispeccode().equals(product.getPr_orispeccode())
-                || !oldProduct.getPr_brand().equals(product.getPr_brand())){
+            if (!ObjectUtils.getStringValue(oldProduct.getPr_spec()).equals(ObjectUtils.getStringValue(product.getPr_spec()))
+                || !ObjectUtils.getStringValue(oldProduct.getPr_orispeccode()).equals(ObjectUtils.getStringValue(product.getPr_orispeccode()))
+                || !ObjectUtils.getStringValue(oldProduct.getPr_brand()).equals(ObjectUtils.getStringValue(product.getPr_brand()))){
                 updateB2BStatus(String.valueOf(product.getId()), "待上传");
             }
         }

+ 2 - 1
applications/sale/sale-server/src/main/resources/application.yml

@@ -79,6 +79,7 @@ ribbon:
   ConnectTimeout: 10000
   eager-load:
     enabled: true
+    clients: inquiry-server
 feign:
   hystrix:
     enabled: true
@@ -90,7 +91,7 @@ hystrix:
                 enabled: true
               isolation:
                     thread:
-                        timeoutInMilliseconds: 5000
+                        timeoutInMilliseconds: 8000
 b2b:
   baseUrl:
     inquiry: https://test-inquiry.uuzcc.cn

+ 4 - 1
applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/task/SendPurchaseInResTask.java

@@ -89,7 +89,10 @@ public class SendPurchaseInResTask extends Executable {
             PurchaseProdInOutDetail b2bPurchaseProdInOutDetail = b2bPurchaseProdInOutDetailList.get(i);
             b2bPurchaseProdInOutDetail.setPd_id(prodIODetailDTO.getId());
             b2bPurchaseProdInOutDetail.setPd_detno(new BigDecimal(prodIODetailDTO.getPd_pdno()).shortValue());
-            b2bPurchaseProdInOutDetail.setPd_orderdetno(new BigDecimal(prodIODetailDTO.getPd_orderdetno()).shortValue());
+            Integer orderDetno = prodIODetailDTO.getPd_orderdetno();
+            if (orderDetno != null) {
+                b2bPurchaseProdInOutDetail.setPd_orderdetno(new BigDecimal(orderDetno).shortValue());
+            }
         }
     }
 

+ 4 - 1
applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/task/SendPurchaseInTask.java

@@ -97,7 +97,10 @@ public class SendPurchaseInTask extends Executable{
             PurchaseProdInOutDetail b2bPurchaseProdInOutDetail = b2bPurchaseProdInOutDetailList.get(i);
             b2bPurchaseProdInOutDetail.setPd_id(prodIODetailDTO.getId());
             b2bPurchaseProdInOutDetail.setPd_detno(new BigDecimal(prodIODetailDTO.getPd_pdno()).shortValue());
-            b2bPurchaseProdInOutDetail.setPd_orderdetno(new BigDecimal(prodIODetailDTO.getPd_orderdetno()).shortValue());
+            Integer orderDetno = prodIODetailDTO.getPd_orderdetno();
+            if (orderDetno != null) {
+                b2bPurchaseProdInOutDetail.setPd_orderdetno(new BigDecimal(orderDetno).shortValue());
+            }
         }
     }
 

+ 4 - 1
applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/task/SendPurchaseOutResTask.java

@@ -89,7 +89,10 @@ public class SendPurchaseOutResTask extends Executable {
             PurchaseProdInOutDetail b2bPurchaseProdInOutDetail = b2bPurchaseProdInOutDetailList.get(i);
             b2bPurchaseProdInOutDetail.setPd_id(prodIODetailDTO.getId());
             b2bPurchaseProdInOutDetail.setPd_detno(new BigDecimal(prodIODetailDTO.getPd_pdno()).shortValue());
-            b2bPurchaseProdInOutDetail.setPd_orderdetno(new BigDecimal(prodIODetailDTO.getPd_orderdetno()).shortValue());
+            Integer orderDetno = prodIODetailDTO.getPd_orderdetno();
+            if (orderDetno != null) {
+                b2bPurchaseProdInOutDetail.setPd_orderdetno(new BigDecimal(orderDetno).shortValue());
+            }
         }
     }
 }

+ 4 - 1
applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/task/SendPurchaseOutTask.java

@@ -93,7 +93,10 @@ public class SendPurchaseOutTask extends Executable{
             PurchaseProdInOutDetail b2bPurchaseProdInOutDetail = b2bPurchaseProdInOutDetailList.get(i);
             b2bPurchaseProdInOutDetail.setPd_id(prodIODetailDTO.getId());
             b2bPurchaseProdInOutDetail.setPd_detno(new BigDecimal(prodIODetailDTO.getPd_pdno()).shortValue());
-            b2bPurchaseProdInOutDetail.setPd_orderdetno(new BigDecimal(prodIODetailDTO.getPd_orderdetno()).shortValue());
+            Integer orderDetno = prodIODetailDTO.getPd_orderdetno();
+            if (orderDetno != null) {
+                b2bPurchaseProdInOutDetail.setPd_orderdetno(new BigDecimal(orderDetno).shortValue());
+            }
         }
     }
 }

+ 1 - 1
applications/transfers/transfers-server/src/main/resources/application.yml

@@ -89,7 +89,7 @@ rabbit:
     queue: saas_trade_dev_queue
     dlqueue: saas_trade_dev_dlqueue
     routingkey: saas_trade_dev_dl.*
-    expired: 30000
+    expired: 60000
 b2b:
   baseUrl:
     inquiry: https://test-inquiry.uuzcc.cn

+ 5 - 4
applications/transfers/transfers-server/src/test/java/com/usoftchina/saas/transfers/service/RabbitSendServiceTest.java

@@ -1,6 +1,8 @@
 package com.usoftchina.saas.transfers.service;
 
+import com.rabbitmq.tools.json.JSONUtil;
 import com.usoftchina.saas.transfers.dto.MessageInfo;
+import com.usoftchina.saas.utils.JsonUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -31,10 +33,9 @@ public class RabbitSendServiceTest {
 
     @Test
     public void TestB_SendDelay() {
-        MessageInfo msg = new MessageInfo();
-        msg.setBizId("测试");
-        msg.setMsgId("测试延迟0001");
-        sendService.sendDelayMessage(msg, "测试");
+        MessageInfo messageInfo = JsonUtils.fromJsonString("{\"msgId\":\"72b04cac-c318-428d-8444-87ca2a2f7ec3\",\"userId\":65,\"appId\":\"trade\",\"bizType\":\"PURCHASE\",\"bizId\":\"1944\",\"companyId\":371,\"timestamp\":1548147987040}", MessageInfo.class);
+        sendService.sendDelayMessage(messageInfo, "测试重新发送");
+        System.out.println("发送延迟队列成功");
     }
 
 

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

@@ -7,6 +7,7 @@ import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
 import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.scheduling.annotation.EnableAsync;
 
 /**
  * @author yingp
@@ -20,6 +21,7 @@ import org.springframework.cloud.openfeign.EnableFeignClients;
 @EnableFeignClients({
         "com.usoftchina.saas.account.api"
 })
+@EnableAsync
 public class DatacenterApplication {
 
     public static void main(String[] args) {

+ 6 - 1
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/controller/SnapshotController.java

@@ -4,6 +4,7 @@ import com.github.pagehelper.PageInfo;
 import com.usoftchina.saas.base.Result;
 import com.usoftchina.saas.dc.po.Snapshot;
 import com.usoftchina.saas.dc.service.SnapshotService;
+import com.usoftchina.saas.dc.service.SnapshotUsageService;
 import com.usoftchina.saas.page.PageDefault;
 import com.usoftchina.saas.page.PageRequest;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -20,6 +21,9 @@ public class SnapshotController {
     @Autowired
     private SnapshotService snapshotService;
 
+    @Autowired
+    private SnapshotUsageService snapshotUsageService;
+
     /**
      * 生成快照
      *
@@ -60,7 +64,8 @@ public class SnapshotController {
      * @return
      */
     @PostMapping("/restore")
-    public Result restore(@RequestParam Long id) {
+    public Result restore(@RequestParam String id) {
+        snapshotUsageService.save(id);
         return Result.success();
     }
 }

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

@@ -4,6 +4,7 @@ import org.apache.ibatis.annotations.Param;
 
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * @author yingp
@@ -21,4 +22,36 @@ public interface CommonMapper {
     List<LinkedHashMap<String, Object>> selectByTableAndColumnAndValue(
             @Param("tableName") String tableName, @Param("columnName") String columnName,
             @Param("columnValue") Object columnValue);
+
+    /**
+     * 按指定表、字段、值删除
+     *
+     * @param tableName
+     * @param columnName
+     * @param columnValue
+     * @return
+     */
+    int deleteByTableAndColumnAndValue(
+            @Param("tableName") String tableName, @Param("columnName") String columnName,
+            @Param("columnValue") Object columnValue);
+
+    /**
+     * 插入map数据
+     *
+     * @param tableName
+     * @param record
+     * @return
+     */
+    int insertMap(@Param("tableName") String tableName, @Param("record") Map<String, Object> record);
+
+    /**
+     * 插入list数据
+     *
+     * @param tableName
+     * @param columns
+     * @param data
+     * @return
+     */
+    int insertListMap(@Param("tableName") String tableName, @Param("columns") List<String> columns,
+                      @Param("data") List<Map<String, Object>> data);
 }

+ 15 - 10
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/po/SnapshotUsage.java

@@ -1,5 +1,6 @@
 package com.usoftchina.saas.dc.po;
 
+import com.usoftchina.saas.context.BaseContextHolder;
 import org.springframework.data.annotation.Id;
 import org.springframework.data.mongodb.core.index.Indexed;
 import org.springframework.data.mongodb.core.mapping.Document;
@@ -39,6 +40,18 @@ public class SnapshotUsage implements Serializable {
     private short status;
     private String message;
 
+    public SnapshotUsage() {
+        this.createTime = System.currentTimeMillis();
+        this.creatorId = BaseContextHolder.getUserId();
+        this.companyId = BaseContextHolder.getCompanyId();
+        this.status = SnapshotUsage.Status.NOT_READY;
+    }
+
+    public SnapshotUsage(String usedSnapshotId) {
+        this();
+        this.usedSnapshotId = usedSnapshotId;
+    }
+
     public String get_id() {
         return _id;
     }
@@ -106,22 +119,14 @@ public class SnapshotUsage implements Serializable {
     public interface Status {
         short NOT_READY = 0;
         /**
-         * 导出当前数据
-         */
-        short DUMPING_ORIGIN = 1;
-        /**
-         * 读取备份文件
-         */
-        short READ_DUMPFILE = 1;
-        /**
-         * 还原中(删除旧数据,)
+         * 还原中(删除当前数据,使用快照数据写入)
          */
         short RESTORE = 1;
         /**
          * 操作失败需要使用originData还原
          */
         short ROLLBACK = -1;
-        short FAILED = 2;
+        short FAILED = -2;
         short SUCCESS = 2;
     }
 }

+ 11 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/repository/SnapshotDataRepository.java

@@ -4,6 +4,8 @@ import com.usoftchina.saas.dc.po.SnapshotData;
 import org.springframework.data.mongodb.repository.MongoRepository;
 import org.springframework.stereotype.Repository;
 
+import java.util.List;
+
 /**
  * @author yingp
  * @date 2019/1/22
@@ -16,4 +18,13 @@ public interface SnapshotDataRepository extends MongoRepository<SnapshotData, St
      * @param snapshotId
      */
     void deleteBySnapshotId(String snapshotId);
+
+    /**
+     * 查找
+     * @param snapshotId
+     * @param dcName
+     * @param dbName
+     * @return
+     */
+    List<SnapshotData> findBySnapshotIdAndDcNameAndDbName(String snapshotId, String dcName, String dbName);
 }

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

@@ -0,0 +1,23 @@
+package com.usoftchina.saas.dc.service;
+
+import com.usoftchina.saas.dc.po.SnapshotUsage;
+
+/**
+ * @author yingp
+ * @date 2019/1/23
+ */
+public interface SnapshotUsageService {
+    /**
+     * 保存
+     * @param snapshotId
+     * @return
+     */
+    SnapshotUsage save(String snapshotId);
+
+    /**
+     * 查找
+     * @param id
+     * @return
+     */
+    SnapshotUsage selectByPrimaryKey(String id);
+}

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

@@ -0,0 +1,45 @@
+package com.usoftchina.saas.dc.service.impl;
+
+import com.usoftchina.saas.context.SpringContextHolder;
+import com.usoftchina.saas.dc.po.Snapshot;
+import com.usoftchina.saas.dc.po.SnapshotUsage;
+import com.usoftchina.saas.dc.repository.SnapshotRepository;
+import com.usoftchina.saas.dc.repository.SnapshotUsageRepository;
+import com.usoftchina.saas.dc.service.SnapshotUsageService;
+import com.usoftchina.saas.dc.snapshot.event.ReadyToCreateEvent;
+import com.usoftchina.saas.dc.snapshot.event.ReadyToRestoreEvent;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+/**
+ * @author yingp
+ * @date 2019/1/23
+ */
+@Service
+public class SnapshotUsageServiceImpl implements SnapshotUsageService{
+
+    @Autowired
+    private SnapshotUsageRepository snapshotUsageRepository;
+
+    @Autowired
+    private SnapshotRepository snapshotRepository;
+
+    @Override
+    public SnapshotUsage save(String snapshotId) {
+        Optional<Snapshot> optional = snapshotRepository.findById(snapshotId);
+        if (optional.isPresent()) {
+            SnapshotUsage usage = snapshotUsageRepository.save(new SnapshotUsage(snapshotId));
+            SpringContextHolder.getContext().publishEvent(new ReadyToRestoreEvent(this,
+                    optional.get(), usage));
+            return usage;
+        }
+        return null;
+    }
+
+    @Override
+    public SnapshotUsage selectByPrimaryKey(String id) {
+        return snapshotUsageRepository.findById(id).orElse(null);
+    }
+}

+ 15 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/snapshot/event/ReadyToRestoreEvent.java

@@ -0,0 +1,15 @@
+package com.usoftchina.saas.dc.snapshot.event;
+
+import com.usoftchina.saas.dc.po.Snapshot;
+import com.usoftchina.saas.dc.po.SnapshotUsage;
+
+/**
+ * @author yingp
+ * @date 2019/1/23
+ */
+public class ReadyToRestoreEvent extends SnapshotUseEvent {
+
+    public ReadyToRestoreEvent(Object source, Snapshot snapshot, SnapshotUsage usage) {
+        super(source, snapshot, usage);
+    }
+}

+ 22 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/snapshot/event/RestoreFailedEvent.java

@@ -0,0 +1,22 @@
+package com.usoftchina.saas.dc.snapshot.event;
+
+import com.usoftchina.saas.dc.po.Snapshot;
+import com.usoftchina.saas.dc.po.SnapshotUsage;
+
+/**
+ * @author yingp
+ * @date 2019/1/22
+ */
+public class RestoreFailedEvent extends SnapshotUseEvent{
+
+    private Exception exception;
+
+    public RestoreFailedEvent(Object source, Snapshot snapshot, SnapshotUsage usage, Exception exception) {
+        super(source, snapshot, usage);
+        this.exception = exception;
+    }
+
+    public Exception getException() {
+        return exception;
+    }
+}

+ 15 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/snapshot/event/RestoredEvent.java

@@ -0,0 +1,15 @@
+package com.usoftchina.saas.dc.snapshot.event;
+
+import com.usoftchina.saas.dc.po.Snapshot;
+import com.usoftchina.saas.dc.po.SnapshotUsage;
+
+/**
+ * @author yingp
+ * @date 2019/1/23
+ */
+public class RestoredEvent extends SnapshotUseEvent {
+
+    public RestoredEvent(Object source, Snapshot snapshot, SnapshotUsage usage) {
+        super(source, snapshot, usage);
+    }
+}

+ 60 - 1
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/snapshot/event/SnapshotLifecycle.java

@@ -5,6 +5,7 @@ import com.usoftchina.saas.account.cache.CompanyLockCache;
 import com.usoftchina.saas.context.SpringContextHolder;
 import com.usoftchina.saas.dc.po.DataSourceInfo;
 import com.usoftchina.saas.dc.po.Snapshot;
+import com.usoftchina.saas.dc.po.SnapshotUsage;
 import com.usoftchina.saas.dc.repository.SnapshotDataRepository;
 import com.usoftchina.saas.dc.repository.SnapshotRepository;
 import com.usoftchina.saas.dc.repository.SnapshotUsageRepository;
@@ -92,7 +93,6 @@ public class SnapshotLifecycle {
     @EventListener(CreateFailedEvent.class)
     public void onCreateFailedEvent(CreateFailedEvent event) {
         Snapshot snapshot = event.getSnapshot();
-        event.getException().printStackTrace();
         logger.error("failed to create snapshot {}", event.getException(), snapshot.get_id());
         // 解锁
         CompanyLockCache.unlock(snapshot.getCompanyId());
@@ -111,4 +111,63 @@ public class SnapshotLifecycle {
         snapshotUsageRepository.deleteByUsedSnapshotId(snapshot.get_id());
         snapshotUsageRepository.deleteByOriginSnapshotId(snapshot.get_id());
     }
+
+    @Async
+    @EventListener(ReadyToRestoreEvent.class)
+    public void onReadyToRestoreEvent(ReadyToRestoreEvent event) {
+        Snapshot snapshot = event.getSnapshot();
+        SnapshotUsage usage = event.getUsage();
+        try {
+            usage.setStatus(SnapshotUsage.Status.RESTORE);
+            snapshotUsageRepository.save(usage);
+            // 为防止导入过程中有人操作,导致部分数据不一致,在导出前锁定该公司不允许操作
+            CompanyLockCache.lock(usage.getCompanyId());
+            restoreSnapshotData(snapshot);
+            SpringContextHolder.getContext().publishEvent(new RestoredEvent(this, snapshot, usage));
+        } catch (Exception e) {
+            SpringContextHolder.getContext().publishEvent(new RestoreFailedEvent(this, snapshot, usage, e));
+        }
+    }
+
+    /**
+     * 连接到不同数据中心,查找数据,生成快照数据,删除,使用快照数据写入
+     *
+     * @param snapshot
+     */
+    private void restoreSnapshotData(Snapshot snapshot) {
+        String dcName = StringUtils.nullIf(CompanyCache.current().getCompany().getDcName(), "default");
+        List<DataSourceInfo> dss = dataSourceInfoService.findByDcNameUseDefault(dcName);
+        if (!CollectionUtils.isEmpty(dss)) {
+            dss.parallelStream().forEach(ds -> {
+                if ("mysql".equals(ds.getDbType())) {
+                    mysqlStrategy.imp(snapshot, ds);
+                }
+            });
+        }
+    }
+
+    @Async
+    @EventListener(RestoredEvent.class)
+    public void onRestoredEvent(RestoredEvent event) {
+        SnapshotUsage usage = event.getUsage();
+        // 解锁
+        CompanyLockCache.unlock(usage.getCompanyId());
+        usage.setStatus(SnapshotUsage.Status.SUCCESS);
+        snapshotUsageRepository.save(usage);
+    }
+
+    @Async
+    @EventListener(RestoreFailedEvent.class)
+    public void onRestoreFailedEvent(RestoreFailedEvent event) {
+        Snapshot snapshot = event.getSnapshot();
+        SnapshotUsage usage = event.getUsage();
+        logger.error("failed to restore by snapshot {}", event.getException(), snapshot.get_id());
+        // 解锁
+        CompanyLockCache.unlock(usage.getCompanyId());
+        // 回滚
+        usage.setStatus(SnapshotUsage.Status.FAILED);
+        usage.setMessage(event.getException().getMessage());
+        snapshotUsageRepository.save(usage);
+        // TODO
+    }
 }

+ 22 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/snapshot/event/SnapshotUseEvent.java

@@ -0,0 +1,22 @@
+package com.usoftchina.saas.dc.snapshot.event;
+
+import com.usoftchina.saas.dc.po.Snapshot;
+import com.usoftchina.saas.dc.po.SnapshotUsage;
+
+/**
+ * @author yingp
+ * @date 2019/1/23
+ */
+public abstract class SnapshotUseEvent extends SnapshotEvent {
+
+    private SnapshotUsage usage;
+
+    public SnapshotUseEvent(Object source, Snapshot snapshot, SnapshotUsage usage) {
+        super(source, snapshot);
+        this.usage = usage;
+    }
+
+    public SnapshotUsage getUsage() {
+        return usage;
+    }
+}

+ 27 - 2
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/snapshot/support/MysqlStrategy.java

@@ -13,8 +13,7 @@ import com.usoftchina.saas.utils.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
-import java.util.LinkedHashMap;
-import java.util.List;
+import java.util.*;
 
 /**
  * @author yingp
@@ -63,4 +62,30 @@ public class MysqlStrategy implements CompanyStrategy {
             DynamicDataSourceContextHolder.clear();
         }
     }
+
+    public void imp(final Snapshot snapshot, final DataSourceInfo ds) {
+        List<SnapshotData> snapshotDataList = snapshotDataRepository.findBySnapshotIdAndDcNameAndDbName(
+                snapshot.get_id(), ds.getDcName(), ds.getDbName());
+        if (!CollectionUtils.isEmpty(snapshotDataList)) {
+            final long companyId = snapshot.getCompanyId();
+            // 如果不存在数据源则自动创建
+            dataSourceRegister.createDataSource(ds);
+            snapshotDataList.parallelStream().forEach(snapshotData -> {
+                try {
+                    DynamicDataSourceContextHolder.set(ds);
+                    // 删除当前数据
+                    commonMapper.deleteByTableAndColumnAndValue(snapshotData.getTableName(),
+                            snapshotData.getColumnName(), companyId);
+                    // 写入快照数据
+                    List<Map<String, Object>> list = (List<Map<String, Object>>) snapshotData.getData();
+                    if (!CollectionUtils.isEmpty(list)) {
+                        commonMapper.insertListMap(snapshotData.getTableName(),
+                                new ArrayList<>(list.get(0).keySet()), list);
+                    }
+                } finally {
+                    DynamicDataSourceContextHolder.clear();
+                }
+            });
+        }
+    }
 }

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

@@ -4,4 +4,29 @@
     <select id="selectByTableAndColumnAndValue" resultType="java.util.LinkedHashMap" statementType="STATEMENT">
         select * from ${tableName} where ${columnName}=${columnValue}
     </select>
+    <delete id="deleteByTableAndColumnAndValue" statementType="STATEMENT">
+        delete from ${tableName} where ${columnName}=${columnValue}
+    </delete>
+    <insert id="insertMap" statementType="STATEMENT">
+        insert into ${tableName}
+        <foreach collection="record.keys" item="key" open="(" close=")" separator="," >
+            ${key}
+        </foreach>
+        values
+        <foreach collection="record.keys" item="key" open="(" close=")" separator=",">
+            #{record[${key}]}
+        </foreach>
+    </insert>
+    <insert id="insertListMap">
+        insert into ${tableName}
+        <foreach collection="columns" item="key" open="(" close=")" separator="," >
+            ${key}
+        </foreach>
+        values
+        <foreach collection="data" item="record" index="index" separator =",">
+            <foreach collection="columns" item="key" index="keyIndex" open="(" close=")" separator=",">
+                #{data[${index}].${key}}
+            </foreach>
+        </foreach>
+    </insert>
 </mapper>

+ 4 - 3
base-servers/datacenter/datacenter-server/src/main/resources/mapper/SchemaMapper.xml

@@ -6,10 +6,11 @@
         <result column="column_name" jdbcType="VARCHAR" property="columnName"/>
     </resultMap>
     <select id="selectSchemaInfoByColumns" resultMap="BaseResultMap">
-        select table_name,column_name from information_schema.`COLUMNS` where table_schema=#{schema,jdbcType=VARCHAR}
-        and column_name in
+        select c.table_name,c.column_name from information_schema.`COLUMNS` c,information_schema.`TABLES` t where
+        t.table_schema=#{schema,jdbcType=VARCHAR} and t.table_type='BASE TABLE' and c.table_schema=t.table_schema
+        and c.table_name=t.table_name and c.column_name in
         <foreach collection="columnNames" index="index" item="columnName" open="(" separator="," close=")">
-          #{columnName}
+            #{columnName}
         </foreach>
     </select>
 </mapper>

+ 1 - 1
base-servers/datacenter/datacenter-server/src/test/java/com/usoftchina/saas/dc/SnapshotServiceTest.java

@@ -35,11 +35,11 @@ public class SnapshotServiceTest {
         while(true) {
             snapshot = snapshotService.selectByPrimaryKey(snapshot.get_id());
             System.out.println("Status: " + snapshot.getStatus());
+            Thread.sleep(1000);
             if (snapshot.getStatus() == Snapshot.Status.SUCCESS ||
                     snapshot.getStatus() == Snapshot.Status.FAILED) {
                 break;
             }
-            Thread.sleep(1000);
         }
     }
 }

+ 45 - 0
base-servers/datacenter/datacenter-server/src/test/java/com/usoftchina/saas/dc/SnapshotUsageServiceTest.java

@@ -0,0 +1,45 @@
+package com.usoftchina.saas.dc;
+
+import com.usoftchina.saas.context.BaseContextHolder;
+import com.usoftchina.saas.dc.po.SnapshotUsage;
+import com.usoftchina.saas.dc.service.SnapshotUsageService;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+/**
+ * @author yingp
+ * @date 2019/1/22
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class SnapshotUsageServiceTest {
+
+    @Autowired
+    private SnapshotUsageService snapshotUsageService;
+
+    @Test
+    public void testA_save() throws Exception {
+        BaseContextHolder.setCompanyId(134);
+        BaseContextHolder.setUserId(41);
+        SnapshotUsage usage = snapshotUsageService.save("5c47b8bcae463e94f090f7a3");
+        printStatus(usage);
+    }
+
+    private void printStatus(SnapshotUsage usage) throws Exception {
+        while(true) {
+            usage = snapshotUsageService.selectByPrimaryKey(usage.get_id());
+            System.out.println("Status: " + usage.getStatus());
+            Thread.sleep(1000);
+            if (usage.getStatus() == SnapshotUsage.Status.SUCCESS ||
+                    usage.getStatus() == SnapshotUsage.Status.FAILED) {
+                break;
+            }
+        }
+    }
+}

+ 1 - 0
frontend/saas-web/app/util/FormUtil.js

@@ -74,6 +74,7 @@ Ext.define('saas.util.FormUtil', {
                     title: '_nogroup'
                 }]);
                 a.group = a.group || '_nogroup';
+                b.group = b.group || '_nogroup';
                 let v1 = gs.findIndex(function(g) {
                     return g.title == a.group;
                 })

+ 3 - 3
frontend/saas-web/app/view/core/base/GridPanel.js

@@ -239,12 +239,12 @@ Ext.define('saas.view.core.base.GridPanel', {
     },
 
     onItemClick: function(form, grid, record,a,index,c) {
-        var classList = c.target.classList.value;
-        if(classList.indexOf('fa-pencil')>-1){
+        var classList = c.target.classList;
+        if(classList.contains('fa-pencil')){
             var config = {};
             config.initId = record.get('id');
             saas.util.BaseUtil.openTab(form._formXtype, '修改'+form._title, form._formXtype+config.initId, config);
-        }else if(classList.indexOf('fa-trash-o')>-1){
+        }else if(classList.contains('fa-trash-o')){
             //删除
             var id = record.get('id');
             if(id){

+ 2 - 1
frontend/saas-web/app/view/sale/b2b/PurchaseDetailController.js

@@ -23,7 +23,8 @@ Ext.define('saas.view.sale.b2b.PurchaseDetailController', {
             } )
         })
         .catch(function(e) {
-            saas.util.BaseUtil.showErrorToast(e.message);
+            view.setLoading(false);
+            saas.util.BaseUtil.showErrorToast('转单失败: ' + e.message);
         });
     }
 });

+ 2 - 2
frontend/saas-web/app/view/sys/account/DataList.js

@@ -272,8 +272,8 @@ Ext.define('saas.view.sys.account.DataList', {
             store.setPageSize(pageSize);
         },
         itemClick: function(view,record,a,index,c) {
-            var classList = c.target.classList.value;
-            if(classList.indexOf('fa-pencil')>-1){
+            var classList = c.target.classList;
+            if(classList.contains('fa-pencil')){
                 this.dialog = this.getController().getView().add({
                     xtype: 'sys-account-editwindow',
                     bind: {

+ 2 - 2
frontend/saas-web/app/view/sys/maxnumbers/DataList.js

@@ -39,8 +39,8 @@ Ext.define('saas.view.sys.maxnumbers.DataList1', {
                     }]
                 }],
                 onItemClick: function(form, grid, record,a,index,c) {
-                    var classList = c.target.classList.value;
-                    if(classList.indexOf('fa-pencil')>-1){
+                    var classList = c.target.classList;
+                    if(classList.contains('fa-pencil')){
                         var form = this.ownerCt;
                         this.dialog = form.getController().getView().add({
                             xtype: 'sys-maxnumbers-window',

+ 66 - 309
frontend/saas-web/app/view/sys/messagelog/DataList.js

@@ -1,317 +1,74 @@
+/**
+ * 操作日志
+ */
 Ext.define('saas.view.sys.messagelog.DataList', {
-    extend: 'Ext.grid.Panel',
+    extend: 'saas.view.core.base.BasePanel',
     xtype: 'sys-messagelog-datalist',
 
-    autoScroll: true,
-    frame:true,
-    layout:'fit',
-    dataUrl:'/api/commons/messagelog/list',
-
-    plugins: [{
-        ptype: 'menuclipboard'
-    }],
-
-    tbar: [{
-        width: 110,
-        name: 'ml_name',
-        xtype: 'textfield',
-        emptyText : '单据类型',
-        enableKeyEvents: true,
-        listeners: {
-            keydown: {
-                fn: function(th, e, eOpts) {
-                    if(e.keyCode == 13) {
-                        var grid = th.up('grid');
-                        grid.condition = grid.getConditions();
-                        grid.store.loadPage(1);
-                    }
-                }
-            }
-        }
-    },{
-        width: 150,
-        name: 'ml_code',
-        xtype: 'textfield',
-        emptyText : '单据编号',
-        enableKeyEvents: true,
-        listeners: {
-            keydown: {
-                fn: function(th, e, eOpts) {
-                    if(e.keyCode == 13) {
-                        var grid = th.up('grid');
-                        grid.condition = grid.getConditions();
-                        grid.store.loadPage(1);
-                    }
-                }
-            }
-        }
-    },{
-        width: 110,
-        name: 'ml_man',
-        xtype: 'textfield',
-        emptyText : '操作人员',
-        enableKeyEvents: true,
-        listeners: {
-            keydown: {
-                fn: function(th, e, eOpts) {
-                    if(e.keyCode == 13) {
-                        var grid = th.up('grid');
-                        grid.condition = grid.getConditions();
-                        grid.store.loadPage(1);
-                    }
-                }
-            }
-        }
-    },{
-        cls:'x-formpanel-btn-blue',
-        xtype:'button',
-        text:'查询',
-        listeners: {
-            click:function(b){
-                var grid = b.ownerCt.ownerCt;
-                grid.condition = grid.getConditions();
-                grid.store.loadPage(1);
-            }
-        }
-    },'->'],
-
-    //字段属性
-    columns : [{
-        text : "id", 
-        width : 0, 
-        dataIndex : "id", 
-        xtype : "numbercolumn",   
-    },{
-        text:'单据类型',
-        dataIndex : "ml_name",
-        width : 110.0, 
-    },{
-        text : "单据编号", 
-        width : 150.0, 
-        dataIndex : "ml_code", 
-    }, 
-    {
-        text : "操作", 
-        dataIndex : "ml_content", 
-        width : 200.0, 
-    }, 
-    {
-        xtype:'datecolumn',
-        format:'Y-m-d H:i:s',
-        text : "操作时间", 
-        dataIndex : "createTime", 
-        width : 150.0, 
-    },
-    {
-        text : "结果", 
-        dataIndex : "ml_result", 
-        width : 150.0, 
-    }, 
-    {
-        text : "操作人员", 
-        dataIndex : "ml_man", 
-        width : 110, 
-    }, {
-        dataIndex: '',
-        flex: 1
-    }],
-
-    condition:'',
-
-    listeners:{
-        boxready: function(grid, width, height, eOpts) {
-            var store = grid.getStore(),
-            gridBodyBox = grid.body.dom.getBoundingClientRect(),
-            gridBodyBoxHeight = gridBodyBox.height;
-
-            var pageSize = Math.floor(gridBodyBoxHeight / 32);
-
-            store.setPageSize(pageSize);
-
-            grid.ownerCt.el.dom.style.left = '8px';
-            grid.el.dom.style.padding = '0px'
-        }
-    },
-
-    initComponent: function() {
+    viewName: 'sys-messagelog-datalist',
+    dataUrl: '/api/commons/messagelog/list',
+    initComponent: function () {
         var me = this;
-        if(me.columns){
-            var fields = me.columns.map(column => column.dataIndex);
-            me.store = Ext.create('Ext.data.Store',{
-                fields:fields,
-                autoLoad: true,
-                pageSize: 11,
-                data: [],
-                proxy: {
-                    timeout:8000,
-                    type: 'ajax',
-                    url: me.dataUrl,
-                    actionMethods: {
-                        read: 'GET'
-                    },
-                    reader: {
-                        type: 'json',
-                        rootProperty: 'data.list',
-                        totalProperty: 'data.total',
-                    }
+        Ext.apply(this, {
+            searchField: [{
+                width: 110,
+                name: 'ml_name',
+                xtype: 'textfield',
+                emptyText : '单据类型',
+            }, {
+                width: 150,
+                name: 'ml_code',
+                xtype: 'textfield',
+                emptyText : '单据编号',
+            }, {
+                width: 110,
+                name: 'ml_man',
+                xtype: 'textfield',
+                emptyText : '操作人员',
+            }],
+
+            gridConfig: {
+                dataUrl: me.dataUrl,
+                actionColumn: [],
+                selModel: {
+                    type: 'cellmodel'
                 },
-                listeners: {
-                    beforeload: function (store, op) {
-                        var condition = me.condition;
-                        if (Ext.isEmpty(condition)) {
-                            condition = "";
-                        }
-                        Ext.apply(store.proxy.extraParams, {
-                            number: op._page,
-                            size: store.pageSize,
-                            condition: JSON.stringify(condition)
-                        });
-                    }
-                }
-            });
-
-            Ext.apply(me, {
-                dockedItems:[{
-                    xtype: 'pagingtoolbar',
-                    dock: 'bottom',
-                    displayInfo: true,
-                    store: me.store
+                hiddenTools: true,
+                data: [],
+                columns: [{
+                    text : "id", 
+                    width : 0, 
+                    dataIndex : "id", 
+                    xtype : "numbercolumn",   
+                }, {
+                    text:'单据类型',
+                    dataIndex : "ml_name",
+                    width : 110.0, 
+                }, {
+                    text : "单据编号", 
+                    width : 150.0, 
+                    dataIndex : "ml_code", 
+                }, {
+                    text : "操作", 
+                    dataIndex : "ml_content", 
+                    width : 200.0, 
+                }, {
+                    xtype:'datecolumn',
+                    format:'Y-m-d H:i:s',
+                    text : "操作时间", 
+                    dataIndex : "createTime", 
+                    width : 150.0, 
+                }, {
+                    text : "结果", 
+                    dataIndex : "ml_result", 
+                    width : 150.0, 
+                }, {
+                    text : "操作人员", 
+                    dataIndex : "ml_man", 
+                    width : 110, 
                 }]
-            });
-        }
-        me.callParent(arguments);
-    },
-
-    /**
-     * 获得过滤条件
-     */
-    getConditions: function() {
-        var me = this,
-        tbar = me.getDockedItems()[0],
-        items = Ext.Array.filter(tbar.items.items, function(item) {
-            return !!item.name;
-        }),
-        conditions = [];
-        for(var i = 0; i < items.length; i++) {
-            var item = items[i];
-            var field = item.name,
-            func = item.getCondition,
-            value = item.value,
-            condition;
-
-            if(value&&value!=''){
-                if(typeof func == 'function') {
-                    condition = {
-                        type: 'condition',
-                        value: func(value)
-                    }
-                }else {
-                    var type = item.fieldType || me.getDefaultFieldType(item),
-                    operation = item.operation || me.getDefaultFieldOperation(item),
-                    conditionValue = me.getConditionValue(item, value);
-        
-                    if(!conditionValue) {
-                        continue;
-                    }
-                    condition = {
-                        type: type,
-                        field: field,
-                        operation: operation,
-                        value: conditionValue
-                    }
-                }
-                conditions.push(condition);
-            }
-        }
-
-        return conditions;
-    },
-
-    /**
-     * 只要arr1和arr2中存在相同项即返回真
-     */
-    isContainsAny: function (arr1, arr2) {
-        for (var i = 0; i < arr2.length; i++) {
-            var a2 = arr2[i];
-            if (!!arr1.find(function (a1) {
-                    return a1 == a2
-                })) {
-                return true;
-            }
-        }
-        return false;
-    },
-
-    getDefaultFieldType: function (field) {
-        var me = this,
-            xtypes = field.getXTypes().split('/'),
-            type;
-
-        if (me.isContainsAny(xtypes, ['numberfield'])) {
-            type = 'number';
-        } else if (me.isContainsAny(xtypes, ['datefield', 'condatefield', 'conmonthfield'])) {
-            type = 'date';
-        } else if (me.isContainsAny(xtypes, ['dbfindtrigger'])) {
-            type = 'enum';
-        } else if (me.isContainsAny(xtypes, ['combobox', 'multicombo', 'combo', 'radiofield', 'radio'])) {
-            type = 'enum';
-        } else {
-            type = 'string';
-        }
-
-        return type;
-    },
-
-    getDefaultFieldOperation: function (field) {
-        var me = this,
-            xtypes = field.getXTypes().split('/'),
-            operation;
-
-        if (me.isContainsAny(xtypes, ['numberfield', 'datefield', 'dbfindtrigger'])) {
-            operation = '=';
-        } else if (me.isContainsAny(xtypes, ['condatefield', 'conmonthfield'])) {
-            operation = 'between';
-        } else if (me.isContainsAny(xtypes, ['multidbfindtrigger', 'combobox', 'multicombo', 'combo'])) {
-            operation = 'in';
-        } else {
-            operation = 'like';
-        }
-
-        return operation;
-    },
-
-    /**
-     * 处理部分字段值
-     */
-    getConditionValue: function (field, value) {
-        var me = this,
-            xtypes = field.getXTypes().split('/'),
-            conditionValue;
-        if (me.isContainsAny(xtypes, ['datefield'])) {
-            conditionValue = Ext.Date.format(new Date(from), 'Y-m-d H:i:s');
-        } else if (me.isContainsAny(xtypes, ['conmonthfield'])) {
-            var from = value.from,
-                to = value.to;
-
-            conditionValue = from + ',' + to;
-        } else if (me.isContainsAny(xtypes, ['condatefield'])) {
-            var from = value.from,
-                to = value.to;
-
-            conditionValue = Ext.Date.format(new Date(from), 'Y-m-d 00:00:00') + ',' + Ext.Date.format(new Date(to), 'Y-m-d 23:59:59');
-        } else if (me.isContainsAny(xtypes, ['dbfindtrigger'])) {
-            conditionValue = value;
-        } else if (me.isContainsAny(xtypes, ['combobox', 'combo'])) {
-            conditionValue = '\'' + value + '\'';
-        } else if (me.isContainsAny(xtypes, ['multicombo'])) {
-            conditionValue = value.map ? value.map(function (v) {
-                return '\'' + v.value + '\'';
-            }).join(',') : '';
-        } else {
-            conditionValue = value;
-        }
-
-        return conditionValue;
+            },
+        });
+        this.callParent(arguments);
     }
-
 });

+ 3 - 5
frontend/saas-web/app/view/sys/power/TreePanel.js

@@ -78,11 +78,9 @@ Ext.define('saas.view.sys.power.TreePanel', {
             }
             var classList = e.target.classList;
             var clickIcon = false;
-            Ext.Array.each(classList, function(item) {
-				if(item.indexOf('x-action-col')>-1){
-                    clickIcon = true;
-                }
-            });
+            if(classList.contains('x-action-col')) {
+                clickIcon = true;
+            }
             if(clickIcon){return;}
             //加载右边的grid
             var id = record.get('id');