rainco il y a 7 ans
Parent
commit
920309556f
100 fichiers modifiés avec 2249 ajouts et 324 suppressions
  1. 3 3
      README.md
  2. 1 0
      applications/commons/commons-dto/src/main/java/com/usoftchina/saas/commons/exception/BizExceptionCode.java
  3. 2 0
      applications/commons/commons-server/src/main/resources/application.yml
  4. 12 0
      applications/commons/commons-server/src/main/resources/config/application-dev.yml
  5. 1 1
      applications/commons/commons-server/src/main/resources/logback-spring.xml
  6. 1 0
      applications/document/document-dto/src/main/java/com.usoftchina.saas.document.dto/ProductDTO.java
  7. 9 0
      applications/document/document-dto/src/main/java/com/usoftchina/saas/document/entities/Product.java
  8. 10 3
      applications/document/document-server/src/main/java/com/usoftchina/saas/document/service/impl/ProductServiceImpl.java
  9. 1 1
      applications/document/document-server/src/main/resources/logback-spring.xml
  10. 12 1
      applications/document/document-server/src/main/resources/mapper/ProductMapper.xml
  11. 10 0
      applications/money/money-server/src/main/java/com/usoftchina/saas/money/po/Paybalance.java
  12. 10 0
      applications/money/money-server/src/main/java/com/usoftchina/saas/money/po/Recbalance.java
  13. 1 1
      applications/money/money-server/src/main/resources/logback-spring.xml
  14. 13 2
      applications/money/money-server/src/main/resources/mapper/PaybalanceMapper.xml
  15. 16 2
      applications/money/money-server/src/main/resources/mapper/RecbalanceMapper.xml
  16. 1 0
      applications/pom.xml
  17. 1 1
      applications/purchase/purchase-server/src/main/resources/logback-spring.xml
  18. 2 0
      applications/sale/sale-server/src/main/resources/application.yml
  19. 12 0
      applications/sale/sale-server/src/main/resources/config/application-dev.yml
  20. 1 1
      applications/sale/sale-server/src/main/resources/logback-spring.xml
  21. 1 1
      applications/storage/storage-server/src/main/resources/logback-spring.xml
  22. 20 0
      applications/transfers/pom.xml
  23. 17 0
      applications/transfers/transfers-api/pom.xml
  24. 62 0
      applications/transfers/transfers-server/pom.xml
  25. 6 0
      applications/transfers/transfers-server/src/main/docker/Dockerfile
  26. 17 0
      applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/TransfersApplication.java
  27. 65 0
      applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/config/RabbitConfig.java
  28. 8 0
      applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/po/BrokerMessagelog.java
  29. 122 0
      applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/po/MessageInfo.java
  30. 261 0
      applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/service/BaseRabbitReceiveService.java
  31. 68 0
      applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/service/RabbitReceiveService.java
  32. 97 0
      applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/service/RabbitSendService.java
  33. 30 0
      applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/task/TaskSchedulerConfig.java
  34. 65 0
      applications/transfers/transfers-server/src/main/resources/application.yml
  35. 13 0
      applications/transfers/transfers-server/src/main/resources/config/application-dev.yml
  36. 23 0
      applications/transfers/transfers-server/src/main/resources/config/application-docker-prod.yml
  37. 12 0
      applications/transfers/transfers-server/src/main/resources/config/application-docker-test.yml
  38. 10 0
      applications/transfers/transfers-server/src/main/resources/config/application-docker.yml
  39. 131 0
      applications/transfers/transfers-server/src/main/resources/logback-spring.xml
  40. 36 0
      applications/transfers/transfers-server/src/test/java/com/usoftchina/saas/transfers/service/RabbitSendServiceTest.java
  41. 8 0
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/mapper/AccountCompanyMapper.java
  42. 6 1
      base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/service/impl/AccountServiceImpl.java
  43. 1 1
      base-servers/account/account-server/src/main/resources/logback-spring.xml
  44. 3 0
      base-servers/account/account-server/src/main/resources/mapper/AccountCompanyMapper.xml
  45. 1 1
      base-servers/admin-server/src/main/resources/logback-spring.xml
  46. 12 0
      base-servers/auth/auth-api/src/main/java/com/usoftchina/saas/auth/api/AuthApi.java
  47. 7 1
      base-servers/auth/auth-client/src/main/java/com/usoftchina/saas/auth/client/interceptor/AuthRestInterceptor.java
  48. 3 0
      base-servers/auth/auth-common/src/main/java/com/usoftchina/saas/auth/common/jwt/JwtInfo.java
  49. 37 2
      base-servers/auth/auth-server/src/main/java/com/usoftchina/saas/auth/controller/AuthController.java
  50. 1 1
      base-servers/auth/auth-server/src/main/resources/logback-spring.xml
  51. 1 1
      base-servers/datacenter/datacenter-server/src/main/resources/logback-spring.xml
  52. 1 1
      base-servers/eureka-server/src/main/resources/logback-spring.xml
  53. 2 2
      base-servers/file/file-server/src/main/resources/application.yml
  54. 1 1
      base-servers/file/file-server/src/main/resources/logback-spring.xml
  55. 9 0
      base-servers/gateway-server/src/main/java/com/usoftchina/saas/gateway/config/AuthConfig.java
  56. 27 1
      base-servers/gateway-server/src/main/java/com/usoftchina/saas/gateway/config/AuthFilter.java
  57. 1 0
      base-servers/gateway-server/src/main/resources/application.yml
  58. 1 1
      base-servers/gateway-server/src/main/resources/logback-spring.xml
  59. 1 1
      base-servers/mail/mail-server/src/main/resources/logback-spring.xml
  60. 1 1
      base-servers/sms/sms-server/src/main/resources/logback-spring.xml
  61. 1 1
      base-servers/socket/socket-server/src/main/resources/logback-spring.xml
  62. 13 0
      base-servers/ui-server/src/main/resources/config/application-dev.yml
  63. 1 1
      base-servers/ui-server/src/main/resources/logback-spring.xml
  64. 1 1
      base-servers/zipkin-server/src/main/resources/application.yml
  65. 1 1
      base-servers/zipkin-server/src/main/resources/logback-spring.xml
  66. 42 0
      framework/core/src/main/java/com/usoftchina/saas/cache/RedisConfig.java
  67. 7 7
      framework/core/src/main/java/com/usoftchina/saas/exception/ExceptionCode.java
  68. 154 0
      framework/core/src/main/java/com/usoftchina/saas/utils/RedisUtil.java
  69. 32 13
      frontend/saas-portal-web/src/components/conenter/addenterprise.vue
  70. 3 0
      frontend/saas-portal-web/src/components/conenter/company.vue
  71. 1 0
      frontend/saas-portal-web/src/components/conenter/failure.vue
  72. 54 25
      frontend/saas-portal-web/src/components/conenter/invitation.vue
  73. 6 7
      frontend/saas-portal-web/src/pages/invitation_mobile_join/invitation_mobile_join.html
  74. 7 0
      frontend/saas-portal-web/static/css/gongsi.css
  75. 22 19
      frontend/saas-portal-web/static/css/invitation_mobile_join.css
  76. 1 1
      frontend/saas-portal-web/static/css/main.css
  77. 3 2
      frontend/saas-portal-web/static/css/toast.style.css
  78. 0 0
      frontend/saas-portal-web/static/img/TriangleCopy.png
  79. BIN
      frontend/saas-portal-web/static/img/caidian2x.png
  80. BIN
      frontend/saas-portal-web/static/img/tijiao.png
  81. BIN
      frontend/saas-portal-web/static/img/tijiao2x.png
  82. 0 0
      frontend/saas-portal-web/static/js/stomp.min.js
  83. 12 0
      frontend/saas-web/app/Application.scss
  84. 6 1
      frontend/saas-web/app/model/document/ProductDetail.js
  85. 6 1
      frontend/saas-web/app/model/document/customeraddress.js
  86. 6 1
      frontend/saas-web/app/model/document/customercontact.js
  87. 6 1
      frontend/saas-web/app/model/document/stocking.js
  88. 6 1
      frontend/saas-web/app/model/document/vendorcontact.js
  89. 6 1
      frontend/saas-web/app/model/money/RecBalance1.js
  90. 6 1
      frontend/saas-web/app/model/money/RecBalance2.js
  91. 5 0
      frontend/saas-web/app/model/sale/SaleInDetail.js
  92. 5 0
      frontend/saas-web/app/model/sale/SaleOutDetail.js
  93. 5 0
      frontend/saas-web/app/model/sale/Saledetail.js
  94. 8 1
      frontend/saas-web/app/model/stock/Makematerial.js
  95. 0 39
      frontend/saas-web/app/model/stock/ProdIODetail.js
  96. 15 15
      frontend/saas-web/app/util/BaseUtil.js
  97. 237 108
      frontend/saas-web/app/util/FormUtil.js
  98. 143 0
      frontend/saas-web/app/util/ViewUtil.js
  99. 14 2
      frontend/saas-web/app/view/core/base/BasePanel.js
  100. 107 41
      frontend/saas-web/app/view/core/base/BasePanelController.js

+ 3 - 3
README.md

@@ -118,9 +118,9 @@
 
 > 日志
 
-* [kibana](http://192.168.0.63:5601)
-* [logstash](tcp://192.168.0.63:5000)
-* [elasticsearch](http://192.168.0.63:9200/_search?pretty)
+* [kibana](http://10.1.81.1:5601)
+* [logstash](tcp://10.1.81.1:5000)
+* [elasticsearch](http://10.1.81.1:9200/_search?pretty)
 
 > 服务
 

+ 1 - 0
applications/commons/commons-dto/src/main/java/com/usoftchina/saas/commons/exception/BizExceptionCode.java

@@ -21,6 +21,7 @@ public enum BizExceptionCode implements BaseExceptionCode {
     NULL_CODERULE(79100, "单据规则不存在,请先确定单据生成规则"),
     CODE_EXISTS(79200, "单据编号已存在"),
     BIZ_CLOSE(79301, "只能关闭已审核的单据"),
+    ORDER_NOT_EXITTS(79300, "单据不存在"),
     BIZ_OPEN(79302, "只能打开已关闭的单据"),
     BIZ_AUDITED(79303,"只能审核未审核的单据"),
     BIZ_UNAUDITED(79304,"只能反审核已审核的单据"),

+ 2 - 0
applications/commons/commons-server/src/main/resources/application.yml

@@ -37,6 +37,8 @@ spring:
   redis:
     host: 10.1.81.62
     port: 6379
+  profiles:
+    active: dev
 management:
   endpoints:
     web:

+ 12 - 0
applications/commons/commons-server/src/main/resources/config/application-dev.yml

@@ -0,0 +1,12 @@
+eureka:
+  instance:
+    leaseRenewalIntervalInSeconds: 10
+    health-check-url-path: /actuator/health
+    status-page-url-path: /actuator/info
+    metadata-map:
+      user.name: ${spring.security.user.name}
+      user.password: ${spring.security.user.password}
+  client:
+    registryFetchIntervalSeconds: 5
+    serviceUrl:
+      defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@127.0.0.1:8500/eureka/

+ 1 - 1
applications/commons/commons-server/src/main/resources/logback-spring.xml

@@ -28,7 +28,7 @@
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
-    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.0.63:5000"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="10.1.81.1:5000"/>
 
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
 

+ 1 - 0
applications/document/document-dto/src/main/java/com.usoftchina.saas.document.dto/ProductDTO.java

@@ -40,4 +40,5 @@ public class ProductDTO implements Serializable {
     private Double po_onhand;
 
     private Double po_avprice;
+    private String pr_remark;
 }

+ 9 - 0
applications/document/document-dto/src/main/java/com/usoftchina/saas/document/entities/Product.java

@@ -32,6 +32,15 @@ public class Product extends CommonBaseEntity implements Serializable {
     private String pr_text2;
     private String pr_text3;
     private String pr_text4;
+    private String pr_remark;
+
+    public String getPr_remark() {
+        return pr_remark;
+    }
+
+    public void setPr_remark(String pr_remark) {
+        this.pr_remark = pr_remark;
+    }
 
     public String getPr_code() {
         return pr_code;

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

@@ -30,7 +30,7 @@ import com.usoftchina.saas.document.service.*;
 import com.usoftchina.saas.exception.BizException;
 import com.usoftchina.saas.page.PageRequest;
 import com.usoftchina.saas.utils.CollectionUtils;
-import com.usoftchina.saas.utils.DateUtils;
+import com.usoftchina.saas.utils.ObjectUtils;
 import com.usoftchina.saas.utils.RegexpUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -142,6 +142,9 @@ public class ProductServiceImpl extends CommonBaseServiceImpl<ProductMapper, Pro
             messageLogService.save(docBaseDTO);
         }else{
             String oldCode = getMapper().getCodeById(product.getId());
+            if (StringUtils.isEmpty(oldCode)){
+                throw new BizException(BizExceptionCode.ORDER_NOT_EXITTS);
+            }
             if (!product.getPr_code().equals(oldCode)){
                 validEnableUpdateCode(product.getId());
             }
@@ -425,7 +428,7 @@ public class ProductServiceImpl extends CommonBaseServiceImpl<ProductMapper, Pro
                 close(docBaseDTO.getId());
             }catch (Exception e){
                 String msg = BizExceptionCode.DEAL_FAILED.getMessage();
-                errorMsg.append(String.format(msg, docBaseDTO.getCode(), e.getMessage()));
+                errorMsg.append(String.format(msg, docBaseDTO.getCode(), e.getMessage()) + "<br />");
             }
         }
         return errorMsg.toString();
@@ -439,7 +442,7 @@ public class ProductServiceImpl extends CommonBaseServiceImpl<ProductMapper, Pro
                 open(docBaseDTO.getId());
             }catch (Exception e){
                 String msg = BizExceptionCode.DEAL_FAILED.getMessage();
-                errorMsg.append(String.format(msg, docBaseDTO.getCode(), e.getMessage()));
+                errorMsg.append(String.format(msg, docBaseDTO.getCode(), e.getMessage()) + "<br />");
             }
         }
         return errorMsg.toString();
@@ -580,6 +583,10 @@ public class ProductServiceImpl extends CommonBaseServiceImpl<ProductMapper, Pro
 
     @Override
     public boolean deleteDetailById(Long id) {
+        Product productTemp = getMapper().selectByPrimaryKey(id);
+        if (ObjectUtils.isEmpty(productTemp)){
+            throw new BizException(BizExceptionCode.ORDER_NOT_EXITTS);
+        }
         Long prodId = productDetailMapper.selectProdidByPrimaryKey(id);
         validProductOperation(prodId, BizExceptionCode.BIZ_RELDELETE_DELETEPROD);
         String prCode = getMapper().selectByPrimaryKey(prodId).getPr_code();

+ 1 - 1
applications/document/document-server/src/main/resources/logback-spring.xml

@@ -28,7 +28,7 @@
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
-    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.0.63:5000"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="10.1.81.1:5000"/>
 
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
 

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

@@ -31,6 +31,7 @@
         <result column="pr_text3" property="pr_text3" jdbcType="VARCHAR"/>
         <result column="pr_text4" property="pr_text4" jdbcType="VARCHAR"/>
         <result column="pr_text5" property="pr_text5" jdbcType="VARCHAR"/>
+        <result column="pr_remark" property="pr_remark" jdbcType="VARCHAR"/>
     </resultMap>
     <resultMap id="ProductDTOResultMapper" type="com.usoftchina.saas.document.dto.ProductDTO">
         <id column="pr_id" property="id" jdbcType="INTEGER"/>
@@ -53,6 +54,7 @@
         <result column="pr_leadtime" property="pr_leadtime" jdbcType="INTEGER"/>
         <result column="pr_status" property="pr_status" jdbcType="VARCHAR"/>
         <result column="pr_statuscode" property="pr_statuscode" jdbcType="VARCHAR"/>
+        <result column="pr_remark" property="pr_remark" jdbcType="VARCHAR"/>
     </resultMap>
 
 
@@ -97,7 +99,7 @@
         pr_whname, pr_zxbzs, pr_leadtime, pr_brand, pr_standardprice, pr_purcprice, pr_saleprice,
         pr_vendid, pr_vendcode, pr_vendname, pr_status,
         pr_statuscode, companyId, updaterId, updateTime, pr_text1, pr_text2, pr_text3,
-        pr_text4, pr_text5
+        pr_text4, pr_text5,pr_remark
     </sql>
     <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Long">
         select
@@ -233,6 +235,9 @@
             <if test="creatorName != null" >
                 creatorName,
             </if>
+            <if test="pr_remark != null" >
+                pr_remark,
+            </if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="pr_code != null">
@@ -328,6 +333,9 @@
             <if test="creatorName != null" >
                 #{creatorName,jdbcType=VARCHAR},
             </if>
+            <if test="pr_remark != null" >
+                #{pr_remark,jdbcType=VARCHAR},
+            </if>
         </trim>
     </insert>
     <update id="updateByPrimaryKeySelective" parameterType="com.usoftchina.saas.document.entities.Product">
@@ -417,6 +425,9 @@
             <if test="pr_text5 != null">
                 pr_text5 = #{pr_text5,jdbcType=VARCHAR},
             </if>
+            <if test="pr_remark != null">
+                pr_remark = #{pr_remark,jdbcType=VARCHAR},
+            </if>
         </set>
         where pr_id = #{id}
     </update>

+ 10 - 0
applications/money/money-server/src/main/java/com/usoftchina/saas/money/po/Paybalance.java

@@ -81,6 +81,16 @@ public class Paybalance extends CommonBaseEntity implements Serializable {
 
     private String pb_auditman;
 
+    private Double pb_amount;
+
+    public Double getPb_amount() {
+        return pb_amount;
+    }
+
+    public void setPb_amount(Double pb_amount) {
+        this.pb_amount = pb_amount;
+    }
+
     public Date getPb_auditdate() {
         return pb_auditdate;
     }

+ 10 - 0
applications/money/money-server/src/main/java/com/usoftchina/saas/money/po/Recbalance.java

@@ -81,6 +81,16 @@ public class Recbalance extends CommonBaseEntity implements Serializable {
 
     private Date rb_auditdate;
 
+    private Double rb_amount;
+
+    public Double getRb_amount() {
+        return rb_amount;
+    }
+
+    public void setRb_amount(Double rb_amount) {
+        this.rb_amount = rb_amount;
+    }
+
     public String getRb_auditman() {
         return rb_auditman;
     }

+ 1 - 1
applications/money/money-server/src/main/resources/logback-spring.xml

@@ -28,7 +28,7 @@
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
-    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.0.63:5000"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="10.1.81.1:5000"/>
 
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
 

+ 13 - 2
applications/money/money-server/src/main/resources/mapper/PaybalanceMapper.xml

@@ -41,6 +41,8 @@
     <result column="createTime" property="createTime" jdbcType="TIMESTAMP" />
     <result column="pb_auditdate" property="pb_auditdate" jdbcType="TIMESTAMP" />
     <result column="pb_auditman" property="pb_auditman" jdbcType="VARCHAR" />
+    <result column="pb_amount" property="pb_amount" jdbcType="DOUBLE" />
+    <result column="pd_paycode" property="pd_paycode" jdbcType="VARCHAR" />
   </resultMap>
   <sql id="Example_Where_Clause">
     <where>
@@ -105,13 +107,13 @@
     pb_pdamount, pb_pbdamount, pb_preamount, pb_discounts, pb_havebalance,
      pb_status, pb_statuscode, pb_remark, paybalance.companyId,
     paybalance.updaterId,paybalance.updatedate, pb_text1, pb_text2, pb_text3, pb_text4, pb_text5, paybalance.creatorName,
-    paybalance.createTime, pb_auditdate, pb_auditman
+    paybalance.createTime, pb_auditdate, pb_auditman,pb_amount
   </sql>
   <sql id="left_Column_List">
     ve_leftamount
   </sql>
   <sql id="detail_Column_List">
-    pd_bankname,pd_amount,pd_paymethod,pd_remark
+    pd_bankname,pd_amount,pd_paymethod,pd_remark,pd_paycode
   </sql>
   <select id="selectList" resultMap="BaseResultMap">
     select
@@ -209,6 +211,9 @@
       <if test="updaterId != null">
         updaterId,
       </if>
+      <if  test="pb_amount != null">
+        pb_amount,
+      </if>
       <if test="pb_text1 != null">
         pb_text1,
       </if>
@@ -304,6 +309,9 @@
       <if test="createTime != null" >
         #{createTime,jdbcType=TIMESTAMP},
       </if>
+      <if test="pb_amount != null" >
+        #{pb_amount,jdbcType=DOUBLE},
+      </if>
     </trim>
     <selectKey resultType="Long" keyProperty="id" order="AFTER">
       SELECT LAST_INSERT_ID()
@@ -384,6 +392,9 @@
       </if>
       <if test="pb_text5 != null">
         pb_text5 = #{pb_text5,jdbcType=VARCHAR},
+      </if>
+      <if test="pb_amount != null">
+        pb_amount = #{pb_amount,jdbcType=DOUBLE},
       </if>
         pb_auditman = #{pb_auditman,jdbcType=VARCHAR},
         pb_auditdate = #{pb_auditdate,jdbcType=TIMESTAMP}

+ 16 - 2
applications/money/money-server/src/main/resources/mapper/RecbalanceMapper.xml

@@ -41,6 +41,8 @@
     <result column="createTime" property="createTime" jdbcType="TIMESTAMP" />
     <result column="rb_auditman" property="rb_auditman" jdbcType="VARCHAR" />
     <result column="rb_auditdate" property="rb_auditdate" jdbcType="TIMESTAMP" />
+    <result column="rb_amount" property="rb_amount" jdbcType="DOUBLE" />
+    <result column="rd_paycode" property="rd_paycode" jdbcType="VARCHAR" />
   </resultMap>
   <sql id="Example_Where_Clause">
     <where>
@@ -105,13 +107,13 @@
     rb_rdamount, rb_rbdamount, rb_preamount, rb_discounts, rb_havebalance, rb_status, rb_statuscode, rb_remark,
     recbalance.companyId,
     recbalance.updaterId,recbalance.updatedate, rb_text1, rb_text2, rb_text3, rb_text4, rb_text5,
-    recbalance.creatorName, recbalance.createTime, rb_auditman, rb_auditdate
+    recbalance.creatorName, recbalance.createTime, rb_auditman, rb_auditdate, rb_amount
   </sql>
   <sql id="left_Column_List">
     cu_leftamount
   </sql>
   <sql id="detail_Column_List">
-      rd_bankname,rd_amount,rd_paymethod,rd_paycode,rd_remark
+      rd_bankname,rd_amount,rd_paymethod,rd_paycode,rd_remark,rd_paycode
   </sql>
 
   <select id="selectByExample" parameterType="com.usoftchina.saas.money.po.RecbalanceExample" resultMap="BaseResultMap">
@@ -273,6 +275,9 @@
       <if test="createTime != null" >
         createTime,
       </if>
+      <if test="rb_amount != null">
+        rb_amount,
+      </if>
     </trim>
     <trim prefix="values (" suffix=")" suffixOverrides=",">
       <if test="rb_code != null">
@@ -347,6 +352,9 @@
       <if test="createTime != null" >
         #{createTime,jdbcType=TIMESTAMP},
       </if>
+      <if test="rb_amount != null">
+        #{rb_amount,jdbcType=DOUBLE},
+      </if>
     </trim>
     <selectKey resultType="Long" keyProperty="id" order="AFTER">
       SELECT LAST_INSERT_ID()
@@ -433,6 +441,9 @@
       <if test="record.rb_text5 != null">
         rb_text5 = #{record.rb_text5,jdbcType=VARCHAR},
       </if>
+      <if test="pb_amount != null">
+        pb_amount = #{pb_amount,jdbcType=DOUBLE},
+      </if>
     </set>
     <if test="_parameter != null">
       <include refid="Update_By_Example_Where_Clause" />
@@ -536,6 +547,9 @@
       </if>
       <if test="rb_text5 != null">
         rb_text5 = #{rb_text5,jdbcType=VARCHAR},
+      </if>
+      <if test="rb_amount != null">
+        rb_amount = #{rb_amount,jdbcType=DOUBLE},
       </if>
         rb_auditman = #{rb_auditman,jdbcType=VARCHAR},
       rb_auditdate = #{rb_auditdate,jdbcType=TIMESTAMP}

+ 1 - 0
applications/pom.xml

@@ -19,6 +19,7 @@
         <module>money</module>
         <module>document</module>
         <module>commons</module>
+        <module>transfers</module>
     </modules>
 
 

+ 1 - 1
applications/purchase/purchase-server/src/main/resources/logback-spring.xml

@@ -28,7 +28,7 @@
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
-    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.0.63:5000"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="10.1.81.1:5000"/>
 
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
 

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

@@ -37,6 +37,8 @@ spring:
   redis:
     host: 10.1.81.62
     port: 6379
+  profiles:
+    active: dev
 eureka:
   instance:
     leaseRenewalIntervalInSeconds: 10

+ 12 - 0
applications/sale/sale-server/src/main/resources/config/application-dev.yml

@@ -0,0 +1,12 @@
+eureka:
+  instance:
+    leaseRenewalIntervalInSeconds: 10
+    health-check-url-path: /actuator/health
+    status-page-url-path: /actuator/info
+    metadata-map:
+      user.name: ${spring.security.user.name}
+      user.password: ${spring.security.user.password}
+  client:
+    registryFetchIntervalSeconds: 5
+    serviceUrl:
+      defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@127.0.0.1:8500/eureka/

+ 1 - 1
applications/sale/sale-server/src/main/resources/logback-spring.xml

@@ -28,7 +28,7 @@
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
-    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.0.63:5000"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="10.1.81.1:5000"/>
 
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
 

+ 1 - 1
applications/storage/storage-server/src/main/resources/logback-spring.xml

@@ -28,7 +28,7 @@
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
-    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.0.63:5000"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="10.1.81.1:5000"/>
 
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
 

+ 20 - 0
applications/transfers/pom.xml

@@ -0,0 +1,20 @@
+<?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>applications</artifactId>
+        <groupId>com.usoftchina.saas</groupId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>transfers</artifactId>
+
+    <packaging>pom</packaging>
+    <modules>
+        <module>transfers-api</module>
+        <module>transfers-server</module>
+    </modules>
+
+</project>

+ 17 - 0
applications/transfers/transfers-api/pom.xml

@@ -0,0 +1,17 @@
+<?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>transfers</artifactId>
+        <groupId>com.usoftchina.saas</groupId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.usoftchina.saas</groupId>
+    <artifactId>transfers-api</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+
+
+</project>

+ 62 - 0
applications/transfers/transfers-server/pom.xml

@@ -0,0 +1,62 @@
+<?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>transfers</artifactId>
+        <groupId>com.usoftchina.saas</groupId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>transfers-server</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.usoftchina.saas</groupId>
+            <artifactId>server-starter</artifactId>
+        </dependency>
+        <!-- sleuth -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-zipkin</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.amqp</groupId>
+            <artifactId>spring-rabbit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>net.logstash.logback</groupId>
+            <artifactId>logstash-logback-encoder</artifactId>
+        </dependency>
+        <!-- feign -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+        <!-- test -->
+        <dependency>
+            <groupId>com.usoftchina.saas</groupId>
+            <artifactId>test-starter</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>com.spotify</groupId>
+                <artifactId>docker-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 6 - 0
applications/transfers/transfers-server/src/main/docker/Dockerfile

@@ -0,0 +1,6 @@
+FROM frolvlad/alpine-oraclejdk8:slim
+VOLUME /tmp
+ADD transfers-server-1.0.0-SNAPSHOT.jar app.jar
+RUN sh -c 'touch /app.jar'
+ENV JAVA_OPTS=""
+ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]

+ 17 - 0
applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/TransfersApplication.java

@@ -0,0 +1,17 @@
+package com.usoftchina.saas.transfers;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+
+@SpringBootApplication
+@EnableEurekaClient
+@EnableFeignClients("com.usoftchina.saas")
+public class TransfersApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(TransfersApplication.class, args);
+    }
+
+
+}

+ 65 - 0
applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/config/RabbitConfig.java

@@ -0,0 +1,65 @@
+package com.usoftchina.saas.transfers.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.io.Serializable;
+
+/**
+ * @Description Rabbit配置类
+ * @Author chenwei
+ * @Date 2018/12/28
+ */
+@ConfigurationProperties("spring.rabbitmq")
+public class RabbitConfig implements Serializable {
+
+//    @Value("${spring.rabbitmq.host}")
+    private String host;
+//    @Value("${spring.rabbitmq.port}")
+    private int port;
+//    @Value("${spring.rabbitmq.username}")
+    private String username;
+//    @Value("${spring.rabbitmq.password}")
+    private String password;
+//    @Value("${spring.rabbitmq.virtual-host}")
+    private String virtualHost;
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getVirtualHost() {
+        return virtualHost;
+    }
+
+    public void setVirtualHost(String virtualHost) {
+        this.virtualHost = virtualHost;
+    }
+}

+ 8 - 0
applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/po/BrokerMessagelog.java

@@ -0,0 +1,8 @@
+package com.usoftchina.saas.transfers.po;
+
+/**
+ * @author: guq
+ * @create: 2018-12-29 09:46
+ **/
+public class BrokerMessagelog {
+}

+ 122 - 0
applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/po/MessageInfo.java

@@ -0,0 +1,122 @@
+package com.usoftchina.saas.transfers.po;
+
+import java.io.Serializable;
+
+/**
+ * @Description 消息内容
+ * @Author chenwei
+ * @Date 2018/12/27
+ */
+public class MessageInfo implements Serializable {
+    /**
+     * 消息ID
+     */
+    private String msgId;
+    /**
+     * 用户ID
+     */
+    private String userId;
+    /**
+     * 应用ID
+     */
+    private String appId;
+    /**
+     * 业务类型
+     */
+    private String bizType;
+
+    /**
+     * 业务单据ID
+     */
+
+    private String bizId;
+    /**
+     * 公司id
+     * @return
+     */
+    private Long companyId;
+
+    /**
+     * 时间戳
+     */
+    private long timestamp;
+
+    public long getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(long timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public int getRetryCount() {
+        return retryCount;
+    }
+
+    public void setRetryCount(int retryCount) {
+        this.retryCount = retryCount;
+    }
+
+    /**
+     * 重试次数
+     */
+    private int retryCount;
+
+    public String getMsgId() {
+        return msgId;
+    }
+
+    public void setMsgId(String msgId) {
+        this.msgId = msgId;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+
+    public String getBizType() {
+        return bizType;
+    }
+
+    public void setBizType(String bizType) {
+        this.bizType = bizType;
+    }
+
+    public String getBizId() {
+        return bizId;
+    }
+
+    public void setBizId(String bizId) {
+        this.bizId = bizId;
+    }
+
+    public MessageInfo(String userId, String appId, String bizType, String bizId) {
+        this.userId = userId;
+        this.appId = appId;
+        this.bizType = bizType;
+        this.bizId = bizId;
+    }
+
+    public MessageInfo() {
+
+    }
+    public Long getCompanyId() {
+        return companyId;
+    }
+
+    public void setCompanyId(Long companyId) {
+        this.companyId = companyId;
+    }
+}

+ 261 - 0
applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/service/BaseRabbitReceiveService.java

@@ -0,0 +1,261 @@
+package com.usoftchina.saas.transfers.service;
+
+import com.rabbitmq.client.AMQP;
+import com.rabbitmq.client.Channel;
+import com.usoftchina.saas.transfers.po.MessageInfo;
+import com.usoftchina.saas.utils.JsonUtils;
+import com.usoftchina.saas.utils.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.amqp.core.AcknowledgeMode;
+import org.springframework.amqp.core.Message;
+import org.springframework.amqp.rabbit.annotation.RabbitHandler;
+import org.springframework.amqp.rabbit.annotation.RabbitListener;
+import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
+import org.springframework.amqp.rabbit.connection.ConnectionFactory;
+import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
+import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
+import org.springframework.amqp.support.AmqpHeaders;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.annotation.AnnotationConfigurationException;
+import org.springframework.messaging.handler.annotation.Headers;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @Description RabbitMq 消息接收处理基类
+ * @Author chenwei
+ * @Date 2018/12/27
+ */
+public abstract class BaseRabbitReceiveService implements ChannelAwareMessageListener {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(BaseRabbitReceiveService.class);
+    private static final String DELAY_QUEUE_NAME_SUFFIX = "_delay";
+
+    protected boolean isDynamicCreate = false;
+    protected String host;
+    protected int port;
+    protected String virtualHost;
+    protected String username;
+    protected String password;
+    protected String queueName;
+    //延时时间,单位:毫秒
+    protected String delayTime = "60000";
+    //心跳时间,单位秒
+    protected int requestedHeartBeat = 30;
+    protected String delayQueueName;
+
+    /**
+     * 初始化连接
+     * @return
+     */
+    private ConnectionFactory initConnectionFactory(){
+        if (StringUtils.isEmpty(host)) {
+            throw new IllegalArgumentException("host为空");
+        }
+        if (port <= 0) {
+            throw new IllegalArgumentException("port小于等于0");
+        }
+        if (StringUtils.isEmpty(virtualHost)) {
+            throw new IllegalArgumentException("virtualHost为空");
+        }
+        if (StringUtils.isEmpty(username)) {
+            throw new IllegalArgumentException("username为空");
+        }
+        if (StringUtils.isEmpty(password)) {
+            throw new IllegalArgumentException("password为空");
+        }
+        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
+        connectionFactory.setHost(host);
+        connectionFactory.setPort(port);
+        connectionFactory.setVirtualHost(virtualHost);
+        connectionFactory.setUsername(username);
+        connectionFactory.setPassword(password);
+        connectionFactory.setRequestedHeartBeat(requestedHeartBeat);
+        return connectionFactory;
+    }
+
+    /**
+     * 动态创建消息监听
+     * @return
+     */
+    @Bean
+    protected SimpleMessageListenerContainer simpleMessageListenerContainer(){
+        if (!isDynamicCreate){
+            return null;
+        }
+        if (this.getClass().isAnnotationPresent(RabbitListener.class)){
+            throw new AnnotationConfigurationException("动态创建时,不能配置@RabbitListener注解");
+        }
+        if (StringUtils.isEmpty(queueName)){
+            throw new IllegalArgumentException("queueName为空");
+        }
+        ConnectionFactory connectionFactory = initConnectionFactory();
+        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
+        container.setQueueNames(queueName);
+        //设置手动应答
+        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
+        //使用ChannelAwareMessageListener接口,必须实现OnMessage方法
+        container.setMessageListener(this);
+        return container;
+    }
+
+    @Override
+    public void onMessage(Message message, Channel channel) {
+        String messageJson = new String(message.getBody());
+        long tag = message.getMessageProperties().getDeliveryTag();
+        String queueName = message.getMessageProperties().getConsumerQueue();
+        processMessage(messageJson, channel, tag, queueName);
+    }
+
+    /**
+     * 接收消息处理
+     * @param messageBytes
+     * @param channel
+     * @param headers
+     */
+    @RabbitHandler
+    public void receiveMessage(byte[] messageBytes, Channel channel, @Headers Map<String, Object> headers) {
+        receiveMessage(new String(messageBytes), channel, headers);
+    }
+
+    /**
+     * 接收消息处理
+     *
+     * @param messageJson
+     * @param channel
+     * @param headers
+     */
+    @RabbitHandler
+    public void receiveMessage(String messageJson, Channel channel, @Headers Map<String, Object> headers) {
+        long tag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
+        String queueName = (String) headers.get(AmqpHeaders.CONSUMER_QUEUE);
+        processMessage(messageJson, channel, tag, queueName);
+    }
+
+    /**
+     * 处理消息
+     * @param messageJson
+     * @param channel
+     * @param tag
+     * @param queueName
+     */
+    private void processMessage(String messageJson, Channel channel, long tag, String queueName) {
+        MessageInfo messageInfo = getMessageInfo(messageJson);
+        //当消息不符合格式MessageInfo格式时,丢弃消息
+        if (messageInfo == null) {
+            LOGGER.warn("接收消息处理.消息格式.异常,messageJson:{},tag:{}", messageJson, tag);
+            basicNack(channel, tag);
+            return;
+        }
+
+        try {
+            LOGGER.info("接收消息处理[BaseRabbitReceiveService.receiveMessage].处理开始,messageJson:{},tag:{}", messageJson, tag);
+            processMessage(messageInfo);
+            LOGGER.info("接收消息处理[BaseRabbitReceiveService.receiveMessage].处理结束,messageJson:{},tag:{}", messageJson, tag);
+        } catch (Exception e) {
+            LOGGER.error("接收消息处理[BaseRabbitReceiveService.receiveMessage].异常,messageJson:{},tag:{}", messageJson, tag, e);
+            //出现异常时,消息转发为延时消息
+            sendDelayMessage(messageInfo, channel, queueName);
+        } finally {
+            basicAck(channel, tag);
+        }
+    }
+
+    /**
+     * 获取消息内容
+     * @param messageJson
+     * @return
+     */
+    private MessageInfo getMessageInfo(String messageJson) {
+        MessageInfo messageInfo = null;
+        try {
+            messageInfo = JsonUtils.fromJsonString(messageJson, MessageInfo.class);
+        } catch (Exception e) {
+            LOGGER.error("获取消息内容实体[BaseRabbitReceiveService.getMessageInfo].异常,messageJson:{}", messageJson, e);
+
+        }
+        return messageInfo;
+    }
+
+    /**
+     * 发送延时消息
+     * @param messageInfo
+     * @param channel
+     * @param queueName
+     */
+    private void sendDelayMessage(MessageInfo messageInfo, Channel channel, String queueName) {
+        //重试次数+1
+        /*messageInfo.setRetryCount(messageInfo.getRetryCount() + 1);
+        String messageJson = JsonUtils.toJsonString(messageInfo);
+        try {
+            String dQueueName = getDelayQueueName(channel, queueName);
+            AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
+            //设置延时时间
+            builder.expiration(delayTime);
+            channel.basicPublish("", dQueueName, builder.build(), messageJson.getBytes());
+            LOGGER.info("发送延时消息[BaseRabbitReceiveService.sendDelayMessage].正常,messageJson:{},queueName:{}", messageJson, queueName);
+        } catch (IOException e) {
+            LOGGER.error("发送延时消息[BaseRabbitReceiveService.sendDelayMessage].异常,messageJson:{},queueName:{}", messageJson, queueName, e);
+        }*/
+    }
+
+    /**
+     * 获取延时队列
+     * @param channel
+     * @param queueName
+     * @return
+     * @throws IOException
+     */
+    private String getDelayQueueName(Channel channel, String queueName) throws IOException {
+        if (!StringUtils.isEmpty(delayQueueName)) {
+            return delayQueueName;
+        }
+        //初始化延时队列
+        String dQueueName = queueName + DELAY_QUEUE_NAME_SUFFIX;
+        Map<String, Object> arguments = new HashMap<String, Object>(2);
+        arguments.put("x-dead-letter-exchange", "");
+        arguments.put("x-dead-letter-routing-key", queueName);
+        channel.queueDeclare(dQueueName, true, false, false, arguments);
+        delayQueueName = dQueueName;
+        return dQueueName;
+    }
+
+    /**
+     * 消息应答No
+     * @param channel
+     * @param tag
+     */
+    private void basicNack(Channel channel, long tag) {
+        try {
+            channel.basicNack(tag, false, false);
+            LOGGER.info("接收消息处理.消息应答No[BaseRabbitReceiveService.receiveMessage.basicNack].正常,tag:{}", tag);
+        } catch (IOException e) {
+            LOGGER.error("接收消息处理.消息应答No[BaseRabbitReceiveService.receiveMessage.basicNack].异常,tag:{}", tag, e);
+        }
+    }
+
+    /**
+     * 消息应答Yes
+     * @param channel
+     * @param tag
+     */
+    private void basicAck(Channel channel, long tag) {
+        try {
+            channel.basicAck(tag, false);
+            LOGGER.info("接收消息处理.消息应答Yes[BaseRabbitReceiveService.receiveMessage.basicAck].正常,tag:{}", tag);
+        } catch (IOException e) {
+            LOGGER.error("接收消息处理.消息应答Yes[BaseRabbitReceiveService.receiveMessage.basicAck].异常,tag:{}", tag, e);
+        }
+    }
+
+    /**
+     * 处理消息
+     * @param messageInfo
+     * @throws Exception
+     */
+    public abstract void processMessage(MessageInfo messageInfo) throws Exception;
+
+}

+ 68 - 0
applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/service/RabbitReceiveService.java

@@ -0,0 +1,68 @@
+package com.usoftchina.saas.transfers.service;
+
+import com.usoftchina.saas.transfers.config.RabbitConfig;
+import com.usoftchina.saas.transfers.po.MessageInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * @Description 消息消费
+ * @Author chenwei
+ * @Date 2018/12/28
+ */
+@Service
+@EnableConfigurationProperties(RabbitConfig.class)
+public class RabbitReceiveService extends BaseRabbitReceiveService {
+
+    @Autowired
+    private RabbitConfig rabbitConfig;
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(RabbitReceiveService.class);
+    private static final String QUEUENAME = "saas_trade";
+
+    @PostConstruct
+    public void init(){
+        isDynamicCreate = true;
+        host = rabbitConfig.getHost();
+        port = rabbitConfig.getPort();
+        username = rabbitConfig.getUsername();
+        password = rabbitConfig.getPassword();
+        virtualHost = rabbitConfig.getVirtualHost();
+        queueName = QUEUENAME;
+    }
+
+    @Override
+    public void processMessage(MessageInfo messageInfo) throws Exception {
+        switch (messageInfo.getAppId()){
+            case "trade":
+                processMessageForReceivable(messageInfo.getBizId(), messageInfo.getBizType());
+            default:
+                throw new Exception("无法处理的消息");
+        }
+    }
+
+    private void processMessageForReceivable(String bizId, String bizType) throws Exception {
+        /*  根据不同的bizType,方案:1.调用不同的方法.    2.处理自己需要变量操作,调用同一个方法  */
+       switch (bizType){
+           case "PURCHASE":
+               /* 执行必要的操作 */
+               execute();
+               break;
+           case "PURCHASECHANGE":
+               /* 执行必要的操作 */
+               break;
+           default:
+               throw new Exception();
+       }
+    }
+
+    private void execute() throws Exception {
+        throw new Exception();
+    }
+
+}

+ 97 - 0
applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/service/RabbitSendService.java

@@ -0,0 +1,97 @@
+package com.usoftchina.saas.transfers.service;
+
+import com.usoftchina.saas.transfers.po.MessageInfo;
+import com.usoftchina.saas.utils.JsonUtils;
+import com.usoftchina.saas.utils.ObjectUtils;
+import com.usoftchina.saas.utils.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.amqp.core.Message;
+import org.springframework.amqp.rabbit.core.RabbitTemplate;
+import org.springframework.amqp.rabbit.support.CorrelationData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import java.util.UUID;
+
+/**
+ * @Description RabbitMQ发送消息
+ * @Author chenwei
+ * @Date 2018/12/27
+ */
+@Service
+public class RabbitSendService implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(RabbitSendService.class);
+
+    @Autowired
+    private RabbitTemplate rabbitTemplate;
+
+    @PostConstruct
+    public void init(){
+        //如果消息没有到exchange,则confirm回调,ack=false
+        //如果消息到达exchange,则confirm回调,ack=true
+        //exchange到queue成功,则不回调return
+        //exchange到queue失败,则回调return(否则不回回调,消息就丢了)
+        rabbitTemplate.setConfirmCallback(this);
+        rabbitTemplate.setReturnCallback(this);
+    }
+
+    /**
+     *
+     * @param queueName 队列名
+     * @param userId    用户ID
+     * @param appId     应用
+     * @param bizType   业务类型
+     * @param bizId     业务ID
+     * @return  消息ID
+     */
+    public String sendMessage(String queueName, String userId, String appId, String bizType, String bizId){
+        MessageInfo messageInfo = new MessageInfo(userId, appId, bizType, bizId);
+        return sendMessage(queueName, messageInfo);
+    }
+
+    /**
+     * 发送消息
+     * @param queueName     队列名
+     * @param messageInfo   消息体
+     * @return
+     */
+    public String sendMessage(String queueName, MessageInfo messageInfo){
+        if (StringUtils.isEmpty(queueName)){
+            throw new IllegalArgumentException("QueueName(队列名)不能为空!");
+        }
+        if (ObjectUtils.isEmpty(messageInfo)){
+            throw new IllegalArgumentException("messageInfo(消息体)不能为空!");
+        }
+        //消息ID
+        messageInfo.setMsgId(UUID.randomUUID().toString());
+        //时间戳
+        messageInfo.setTimestamp(System.currentTimeMillis());
+        CorrelationData correlationData = new CorrelationData(messageInfo.getMsgId());
+        String messageJson = JsonUtils.toJsonString(messageInfo);
+        rabbitTemplate.convertAndSend(queueName, (Object) messageJson, correlationData);
+        LOGGER.info("发送消息[RabbitSendService.sendMessage].正常,queueName:{},messageInfo:{},correlationData:{}",
+                queueName, messageJson, correlationData.getId());
+        return messageInfo.getMsgId();
+    }
+
+    @Override
+    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
+        if (ack){
+            LOGGER.info("发送消息确认[RabbitSendService.confirm].正常,correlationData:{}",
+                    correlationData.getId());
+        }else {
+            LOGGER.info("发送消息确认[RabbitSendService.confirm].正常,correlationData:{}",
+                    correlationData.getId(), cause);
+        }
+    }
+
+    @Override
+    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
+        LOGGER.error("发送消息确认到达队列[RabbitSendService.returnedMessage].异常," +
+                "message:{},replyCode:{},replyText:{},exchange:{},routingKey:{}",
+                message, replyCode, replyText, exchange, routingKey);
+    }
+}

+ 30 - 0
applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/task/TaskSchedulerConfig.java

@@ -0,0 +1,30 @@
+package com.usoftchina.saas.transfers.task;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.SchedulingConfigurer;
+import org.springframework.scheduling.config.ScheduledTaskRegistrar;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * @author: guq
+ * @create: 2018-12-29 09:38
+ **/
+@Configuration
+@EnableScheduling
+public class TaskSchedulerConfig implements SchedulingConfigurer{
+
+    @Override
+    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
+        scheduledTaskRegistrar.setScheduler(taskScheduler());
+    }
+
+    @Bean(destroyMethod = "shutdown")
+    public Executor taskScheduler() {
+        return Executors.newScheduledThreadPool(100);
+    }
+
+}

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

@@ -0,0 +1,65 @@
+spring:
+  application:
+    name: transfers-server
+  security:
+    user:
+      name: admin
+      password: select111***
+  messages:
+    basename: i18n/messages
+    encoding: UTF-8
+  rabbitmq:
+    host: 10.1.81.75
+    port: 5672
+    virtual-host: dev
+    username: saas
+    password: select123***
+  zipkin:
+    sender:
+      type: rabbit
+    locator:
+      discovery:
+        enabled: true
+  sleuth:
+    sampler:
+      probability: 1.0
+  redis:
+    host: 10.1.81.62
+    port: 6379
+  profiles:
+    active: dev
+management:
+  endpoints:
+    web:
+      exposure:
+        include: "*"
+  endpoint:
+    health:
+      show-details: always
+eureka:
+  instance:
+    leaseRenewalIntervalInSeconds: 10
+    health-check-url-path: /actuator/health
+    status-page-url-path: /actuator/info
+    metadata-map:
+      user.name: ${spring.security.user.name}
+      user.password: ${spring.security.user.password}
+  client:
+    registryFetchIntervalSeconds: 5
+    serviceUrl:
+      defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@10.1.81.61:8510/eureka/
+server:
+  port: 8960
+  tomcat:
+    uri-encoding: UTF-8
+info:
+  name: '@project.artifactId@'
+  description: '@project.description@'
+  version: '@project.version@'
+  spring-boot-version: '@spring.boot.version@'
+  spring-cloud-version: '@spring.cloud.version@'
+auth:
+  public-key: auth/pub.key
+ribbon:
+  ReadTimeout: 10000
+  ConnectTimeout: 10000

+ 13 - 0
applications/transfers/transfers-server/src/main/resources/config/application-dev.yml

@@ -0,0 +1,13 @@
+eureka:
+  instance:
+    leaseRenewalIntervalInSeconds: 10
+    health-check-url-path: /actuator/health
+    status-page-url-path: /actuator/info
+    prefer-ip-address: true
+    metadata-map:
+      user.name: ${spring.security.user.name}
+      user.password: ${spring.security.user.password}
+  client:
+    registryFetchIntervalSeconds: 5
+    serviceUrl:
+      defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@127.0.0.1:8500/eureka/

+ 23 - 0
applications/transfers/transfers-server/src/main/resources/config/application-docker-prod.yml

@@ -0,0 +1,23 @@
+eureka:
+  instance:
+    hostname: saas-transfers-server
+    prefer-ip-address: false
+  client:
+    serviceUrl:
+      defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@saas-eureka-server:8500/eureka/
+spring:
+  rabbitmq:
+    host: 10.10.100.103
+    port: 5672
+    virtual-host: docker
+    username: saas
+    password: select123***
+  datasource:
+    url: jdbc:mysql://10.10.100.18:3306/saas_biz?characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
+    username: saas
+    password: select111***
+  redis:
+    host: 10.10.100.173
+    port: 6379
+logging:
+  destination: 10.10.100.160:5000

+ 12 - 0
applications/transfers/transfers-server/src/main/resources/config/application-docker-test.yml

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

+ 10 - 0
applications/transfers/transfers-server/src/main/resources/config/application-docker.yml

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

+ 131 - 0
applications/transfers/transfers-server/src/main/resources/logback-spring.xml

@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <include resource="org/springframework/boot/logging/logback/base.xml" />
+    <jmxConfigurator/>
+
+    <!--
+    %m
+    输出代码中指定的消息
+    %p
+    输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
+    %r
+    输出自应用启动到输出该log信息耗费的毫秒数
+    %c
+    输出所属的类目,通常就是所在类的全名
+    %t
+    输出产生该日志事件的线程名
+    %n
+    输出一个回车换行符,Windows平台为“\r\n”,Unix平台为“\n”
+    %d
+    输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},
+    输出类似:2002年10月18日 22:10:28,921
+    %l
+    输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)
+    -->
+
+    <springProperty scope="context" name="log.path" source="logging.path" defaultValue="/var/log/saas/transfers-server"/>
+    <springProperty scope="context" name="spring.application.name" source="spring.application.name" defaultValue="transfers-server"/>
+    <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
+    <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
+    <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.0.63:5000"/>
+
+    <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
+
+    <appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>${log.level.console}</level>
+        </filter>
+        <encoder>
+            <pattern>${common-pattern}</pattern>
+        </encoder>
+    </appender>
+
+    <appender name="ROOT_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/root.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${log.path}/%d{yyyy-MM}/root-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
+            <maxFileSize>128MB</maxFileSize>
+            <maxHistory>7</maxHistory>
+            <totalSizeCap>20GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${common-pattern}</pattern>
+        </encoder>
+    </appender>
+
+    <!-- Appender to log in a JSON format -->
+    <appender name="JSON_APPENDER" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
+        <destination>${log.destination}</destination>
+        <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
+            <providers>
+                <pattern>
+                    <pattern>
+                        {
+                        "severity": "%level",
+                        "service": "${spring.application.name:-}",
+                        "trace": "%X{X-B3-TraceId:-}",
+                        "span": "%X{X-B3-SpanId:-}",
+                        "parent": "%X{X-B3-ParentSpanId:-}",
+                        "exportable": "%X{X-Span-Export:-}",
+                        "pid": "${PID:-}",
+                        "thread": "%thread",
+                        "class": "%logger{40}",
+                        "rest": "%message"
+                        }
+                    </pattern>
+                </pattern>
+            </providers>
+        </encoder>
+    </appender>
+
+    <logger name="org.springframework" level="INFO"/>
+    <logger name="com.usoftchina.saas" level="INFO"/>
+
+    <springProfile name="dev">
+        <root level="INFO">
+            <appender-ref ref="CONSOLE_APPENDER"/>
+        </root>
+    </springProfile>
+
+    <springProfile name="test">
+        <root level="INFO">
+            <appender-ref ref="CONSOLE_APPENDER"/>
+            <appender-ref ref="ROOT_APPENDER"/>
+        </root>
+    </springProfile>
+
+    <springProfile name="docker">
+        <logger name="org.springframework" level="WARN"/>
+        <logger name="com.usoftchina.saas" level="WARN"/>
+        <root level="WARN">
+            <appender-ref ref="CONSOLE_APPENDER"/>
+            <appender-ref ref="JSON_APPENDER"/>
+        </root>
+    </springProfile>
+
+    <springProfile name="docker-dev">
+        <root level="INFO">
+            <appender-ref ref="CONSOLE_APPENDER"/>
+        </root>
+    </springProfile>
+
+    <springProfile name="docker-test">
+        <logger name="org.springframework" level="WARN"/>
+        <logger name="com.usoftchina.saas" level="WARN"/>
+        <root level="WARN">
+            <appender-ref ref="CONSOLE_APPENDER"/>
+            <appender-ref ref="JSON_APPENDER"/>
+        </root>
+    </springProfile>
+
+    <springProfile name="docker-prod">
+        <logger name="org.springframework" level="WARN"/>
+        <logger name="com.usoftchina.saas" level="WARN"/>
+        <root level="WARN">
+            <appender-ref ref="CONSOLE_APPENDER"/>
+            <appender-ref ref="JSON_APPENDER"/>
+        </root>
+    </springProfile>
+
+</configuration>

+ 36 - 0
applications/transfers/transfers-server/src/test/java/com/usoftchina/saas/transfers/service/RabbitSendServiceTest.java

@@ -0,0 +1,36 @@
+package com.usoftchina.saas.transfers.service;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @Description 消息生成测试
+ * @Author chenwei
+ * @Date 2018/12/28
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringBootTest
+@EnableAutoConfiguration
+public class RabbitSendServiceTest {
+
+    @Autowired
+    private RabbitSendService rabbitSendService;
+
+    private final String QUEUENAME = "saas_trade";
+    private final String USERID = "1";
+    private final String APPID = "trade";
+    private final String BIZTYPE = "PURCHASE";
+    private final String BIZID = "1";
+
+
+    @Test
+    public void sendMessage() throws Exception {
+        String messageId = rabbitSendService.sendMessage(QUEUENAME, USERID, APPID, BIZTYPE, BIZID);
+        System.out.println(messageId);
+    }
+
+}

+ 8 - 0
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/mapper/AccountCompanyMapper.java

@@ -81,4 +81,12 @@ public interface AccountCompanyMapper {
      * @return
      */
     int hasBind(@Param("accountId") Long accountId, @Param("companyId") Long companyId);
+
+    /**
+     * 账户公司以前绑定过,现在已解绑
+     * @param accountId
+     * @param companyId
+     * @return
+     */
+    int hasUnBindData(@Param("accountId") Long accountId, @Param("companyId") Long companyId);
 }

+ 6 - 1
base-servers/account/account-server/src/main/java/com/usoftchina/saas/account/service/impl/AccountServiceImpl.java

@@ -134,7 +134,12 @@ public class AccountServiceImpl implements AccountService {
 
     @Override
     public void bindCompany(Long accountId, Long companyId) {
-        accountCompanyMapper.insert(accountId, companyId);
+        int count = accountCompanyMapper.hasUnBindData(accountId, companyId);
+        if (count > 0) {
+            updateBindCompanyStatus(accountId, companyId, "1");
+        }else {
+            accountCompanyMapper.insert(accountId, companyId);
+        }
     }
 
     @Override

+ 1 - 1
base-servers/account/account-server/src/main/resources/logback-spring.xml

@@ -28,7 +28,7 @@
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
-    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.0.63:5000"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="10.1.81.1:5000"/>
 
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
 

+ 3 - 0
base-servers/account/account-server/src/main/resources/mapper/AccountCompanyMapper.xml

@@ -42,4 +42,7 @@
     <select id="hasBind" resultType="int">
         SELECT COUNT(*) FROM AC_ACCOUNT_COMPANY WHERE ACCOUNT_ID = #{accountId} AND COMPANY_ID = #{companyId} AND STATUS = 1
     </select>
+    <select id="hasUnBindData" resultType="int">
+        SELECT COUNT(*) FROM AC_ACCOUNT_COMPANY WHERE ACCOUNT_ID = #{accountId} AND COMPANY_ID = #{companyId} AND STATUS = 0
+    </select>
 </mapper>

+ 1 - 1
base-servers/admin-server/src/main/resources/logback-spring.xml

@@ -28,7 +28,7 @@
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
-    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.0.63:5000"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="10.1.81.1:5000"/>
 
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
 

+ 12 - 0
base-servers/auth/auth-api/src/main/java/com/usoftchina/saas/auth/api/AuthApi.java

@@ -1,12 +1,14 @@
 package com.usoftchina.saas.auth.api;
 
 import com.usoftchina.saas.auth.common.cookie.CookieInfo;
+import com.usoftchina.saas.auth.common.jwt.JwtInfo;
 import com.usoftchina.saas.auth.dto.AuthDTO;
 import com.usoftchina.saas.auth.dto.TokenDTO;
 import com.usoftchina.saas.base.Result;
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestParam;
 
 /**
@@ -50,4 +52,14 @@ public interface AuthApi {
      */
     @PostMapping(value = "/sso/authorize")
     Result<AuthDTO> ssoAuthorize(CookieInfo info);
+
+    /**
+    * @Description 获取新token
+    * @Param: [info]
+    * @return: com.usoftchina.saas.base.Result<com.usoftchina.saas.auth.dto.TokenDTO>
+    * @Author: guq
+    * @Date: 2018/12/28
+    */
+    @PostMapping("/generateToken")
+    public Result<TokenDTO> generateToken(@RequestBody JwtInfo info);
 }

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

@@ -13,6 +13,7 @@ import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import java.util.Enumeration;
 
 /**
  * @author yingp
@@ -37,7 +38,12 @@ public class AuthRestInterceptor extends HandlerInterceptorAdapter {
             if (annotation != null) {
                 return super.preHandle(request, response, handler);
             }
-            String token = request.getHeader(authConfig.getAuthHeader());
+            String token = null;
+            Enumeration<String> headers = request.getHeaders(authConfig.getAuthHeader());
+            while(headers.hasMoreElements()){
+                //取最后一个元素
+                token = headers.nextElement();
+            }
             if (null != token) {
                 JwtInfo infoFromToken = JwtHelper.getInfoFromToken(token, authConfig.getPublicKey());
                 BaseContextHolder.setAppId(infoFromToken.getAppId());

+ 3 - 0
base-servers/auth/auth-common/src/main/java/com/usoftchina/saas/auth/common/jwt/JwtInfo.java

@@ -21,6 +21,9 @@ public class JwtInfo implements Serializable{
         this.realName = realName;
     }
 
+    public JwtInfo() {
+    }
+
     public JwtInfo(String appId, Long companyId, Long userId, String userName, String realName) {
         this.appId = appId;
         this.companyId = companyId;

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

@@ -19,6 +19,7 @@ import com.usoftchina.saas.auth.po.VirtualAuthorizeLog;
 import com.usoftchina.saas.auth.service.AuthorizeCountService;
 import com.usoftchina.saas.auth.service.AuthorizeLogService;
 import com.usoftchina.saas.base.Result;
+import com.usoftchina.saas.cache.CacheKeyHelper;
 import com.usoftchina.saas.exception.BizException;
 import com.usoftchina.saas.exception.ExceptionCode;
 import com.usoftchina.saas.page.PageDefault;
@@ -98,7 +99,6 @@ public class AuthController {
         if (authorizeCountService.isFrozen(username)) {
             return Result.error(ExceptionCode.AUTH_FROZEN);
         }
-
         Result<AccountDTO> result = accountApi.validByUsernameAndPwd(username, password);
         if (result.isSuccess()) {
             authorizeCountService.clear(username);
@@ -114,6 +114,9 @@ public class AuthController {
             JwtInfo info = new JwtInfo(appId, companyId, accountDTO.getId(), accountDTO.getUsername(), accountDTO.getRealname());
             JwtToken jwtToken = JwtHelper.generateToken(info, privateKeyPath, expire);
             TokenDTO tokenDTO = BeanMapper.map(jwtToken, TokenDTO.class);
+            //登陆成功记入redis
+            String key = CacheKeyHelper.generatePublicKey(tokenDTO.getToken());
+            RedisUtil.set(key, info, expire);
             // 登录日志
             authorizeLogService.save(AuthorizeLog.from(request)
                     .setAccountId(accountDTO.getId())
@@ -153,6 +156,9 @@ public class AuthController {
         JwtInfo info = new JwtInfo(appId, companyId, accountDTO.getId(), accountDTO.getUsername(), accountDTO.getRealname());
         JwtToken jwtToken = JwtHelper.generateToken(info, privateKeyPath, expire);
         TokenDTO tokenDTO = BeanMapper.map(jwtToken, TokenDTO.class);
+        //登陆成功记入redis
+        String key = CacheKeyHelper.generatePublicKey(tokenDTO.getToken());
+        RedisUtil.set(key, info, expire);
         // 登录日志
         authorizeLogService.saveVirtual(VirtualAuthorizeLog.from(request)
                 .setMobile(Long.parseLong(accountDTO.getMobile()))
@@ -207,6 +213,9 @@ public class AuthController {
             if (null != companyDTO){
                 authDTO.setCompanyId(companyDTO.getId());
             }
+            //登陆成功记入redis
+            String key = CacheKeyHelper.generatePublicKey(tokenDTO.getToken());
+            RedisUtil.set(key, jwtInfo, expire);
             return Result.success(authDTO);
         }
         return Result.error(ExceptionCode.COOKIE_ILLEGAL_ARGUMENT);
@@ -262,8 +271,12 @@ public class AuthController {
                 JwtInfo jwtInfo = new JwtInfo(appId, companyId, accountDTO.getId(), accountDTO.getUsername(), accountDTO.getRealname());
                 JwtToken jwtToken = JwtHelper.generateToken(jwtInfo, privateKeyPath, expire);
                 TokenDTO tokenDTO = BeanMapper.map(jwtToken, TokenDTO.class);
+                AuthDTO authDTO = new AuthDTO(tokenDTO, accountDTO);
+                //登陆成功记入redis
+                String key = CacheKeyHelper.generatePublicKey(tokenDTO.getToken());
+                RedisUtil.set(key, jwtInfo, expire);
                 socketMessageApi.sendToClient(new ClientMessage(clientId, "/sso/callback",
-                        JsonUtils.toJsonString(new AuthDTO(tokenDTO, accountDTO))));
+                        JsonUtils.toJsonString(authDTO)));
             }
             ServletUtils.writeJsonPMessage(response, callback, true);
         }
@@ -310,6 +323,9 @@ public class AuthController {
             JwtInfo info = new JwtInfo(infoFromToken.getAppId(), companyId, infoFromToken.getUserId(),
                     infoFromToken.getUserName(), infoFromToken.getRealName());
             JwtToken jwtToken = JwtHelper.generateToken(info, privateKeyPath, expire);
+            //登陆成功记入redis
+            String key = CacheKeyHelper.generatePublicKey(jwtToken.getToken());
+            RedisUtil.set(key, info, expire);
             return Result.success(BeanMapper.map(jwtToken, TokenDTO.class));
         }
         return Result.error(ExceptionCode.COMPANY_NOT_BIND);
@@ -370,4 +386,23 @@ public class AuthController {
     public Result<PageInfo<AuthorizeLogDTO>> getLogs(@PageDefault PageRequest page) {
         return Result.success(authorizeLogService.findByPage(page));
     }
+    /**
+    * @Description 重新生成Token
+    * @Param: [info]
+    * @return: TokenDTO
+    * @Author: guq
+    * @Date: 2018/12/27
+    */
+    @PostMapping("/generateToken")
+    public Result<TokenDTO> generateToken(@RequestBody JwtInfo info) {
+        TokenDTO tokenDTO = null;
+        if (!StringUtils.isEmpty(info)) {
+            JwtToken jwtToken = JwtHelper.generateToken(info, privateKeyPath, expire);
+            tokenDTO = BeanMapper.map(jwtToken, TokenDTO.class);
+            //登陆成功记入redis
+            String key = CacheKeyHelper.generatePublicKey(jwtToken.getToken());
+            RedisUtil.set(key, info, expire);
+        }
+        return Result.success(tokenDTO);
+    }
 }

+ 1 - 1
base-servers/auth/auth-server/src/main/resources/logback-spring.xml

@@ -28,7 +28,7 @@
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
-    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.0.63:5000"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="10.1.81.1:5000"/>
 
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
 

+ 1 - 1
base-servers/datacenter/datacenter-server/src/main/resources/logback-spring.xml

@@ -28,7 +28,7 @@
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
-    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.0.63:5000"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="10.1.81.1:5000"/>
 
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
 

+ 1 - 1
base-servers/eureka-server/src/main/resources/logback-spring.xml

@@ -28,7 +28,7 @@
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
-    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.0.63:5000"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="10.1.81.1:5000"/>
 
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
 

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

@@ -86,6 +86,6 @@ fdfs:
     width: 150
     height: 150
   tracker-list:
-    - 192.168.0.63:22122
+    - 10.1.81.1:22122
 file:
-  base-url: http://192.168.0.63:8888/
+  base-url: http://10.1.81.1:8888/

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

@@ -28,7 +28,7 @@
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
-    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.0.63:5000"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="10.1.81.1:5000"/>
 
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
 

+ 9 - 0
base-servers/gateway-server/src/main/java/com/usoftchina/saas/gateway/config/AuthConfig.java

@@ -13,6 +13,15 @@ public class AuthConfig {
     private String publicKey;
     private String authHeader = "Authorization";
     private List<String> ignores;
+    private int expire;
+
+    public int getExpire() {
+        return expire;
+    }
+
+    public void setExpire(int expire) {
+        this.expire = expire;
+    }
 
     public String getPublicKey() {
         return publicKey;

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

@@ -12,12 +12,15 @@ import com.usoftchina.saas.auth.common.jwt.JwtInfo;
 import com.usoftchina.saas.auth.dto.AuthDTO;
 import com.usoftchina.saas.auth.dto.TokenDTO;
 import com.usoftchina.saas.base.Result;
+import com.usoftchina.saas.cache.CacheKeyHelper;
 import com.usoftchina.saas.context.BaseContextHolder;
 import com.usoftchina.saas.exception.BizException;
 import com.usoftchina.saas.exception.ExceptionCode;
 import com.usoftchina.saas.gateway.error.PermissionException;
 import com.usoftchina.saas.gateway.util.AntPathRequestMatcher;
 import com.usoftchina.saas.utils.CollectionUtils;
+import com.usoftchina.saas.utils.RedisUtil;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.cloud.gateway.filter.GatewayFilterChain;
@@ -60,7 +63,30 @@ public class AuthFilter implements GlobalFilter, Ordered {
             if (!isIgnore(exchange.getRequest())) {
                 // 鉴别身份信息
                 String token = getAuthToken(exchange.getRequest());
-                JwtInfo jwt = JwtHelper.getInfoFromToken(token, authConfig.getPublicKey());
+                String key = CacheKeyHelper.generatePublicKey(token);
+                //刷新时间
+                RedisUtil.expire(key, authConfig.getExpire());
+                JwtInfo jwt = null;
+                try {
+                    jwt = JwtHelper.getInfoFromToken(token, authConfig.getPublicKey());
+                } catch (BizException e) {
+                    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());
+                        }
+                        Result<TokenDTO> result = authApi.generateToken(jwt);
+                        if (result.isSuccess() && null != result.getData()) {
+                            token = result.getData().getToken();
+                            //返回前端处理
+                            exchange.getResponse().getHeaders().add(authConfig.getAuthHeader(), token);
+                            //向headers中放token,记得build
+                            ServerHttpRequest request = exchange.getRequest().mutate().header(authConfig.getAuthHeader(), token).build();
+                            //将现在的request 变成 change对象
+                            exchange = exchange.mutate().request(request).build();
+                        }
+                    }
+                }
                 BaseContextHolder.setAppId(jwt.getAppId());
                 BaseContextHolder.setUserId(jwt.getUserId());
                 BaseContextHolder.setCompanyId(jwt.getCompanyId());

+ 1 - 0
base-servers/gateway-server/src/main/resources/application.yml

@@ -177,6 +177,7 @@ info:
 auth:
   auth-header: Authorization
   public-key: auth/pub.key
+  expire: 18000
   ignores:
     - /api/auth/authorize
     - /api/auth/virtual/authorize/**

+ 1 - 1
base-servers/gateway-server/src/main/resources/logback-spring.xml

@@ -28,7 +28,7 @@
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
-    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.0.63:5000"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="10.1.81.1:5000"/>
 
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
 

+ 1 - 1
base-servers/mail/mail-server/src/main/resources/logback-spring.xml

@@ -28,7 +28,7 @@
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
-    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.0.63:5000"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="10.1.81.1:5000"/>
 
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
 

+ 1 - 1
base-servers/sms/sms-server/src/main/resources/logback-spring.xml

@@ -28,7 +28,7 @@
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
-    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.0.63:5000"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="10.1.81.1:5000"/>
 
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
 

+ 1 - 1
base-servers/socket/socket-server/src/main/resources/logback-spring.xml

@@ -28,7 +28,7 @@
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
-    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.0.63:5000"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="10.1.81.1:5000"/>
 
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
 

+ 13 - 0
base-servers/ui-server/src/main/resources/config/application-dev.yml

@@ -0,0 +1,13 @@
+eureka:
+  instance:
+    leaseRenewalIntervalInSeconds: 10
+    health-check-url-path: /actuator/health
+    status-page-url-path: /actuator/info
+    prefer-ip-address: true
+    metadata-map:
+      user.name: ${spring.security.user.name}
+      user.password: ${spring.security.user.password}
+  client:
+    registryFetchIntervalSeconds: 5
+    serviceUrl:
+      defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@127.0.0.1:8500/eureka/

+ 1 - 1
base-servers/ui-server/src/main/resources/logback-spring.xml

@@ -28,7 +28,7 @@
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
-    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.0.63:5000"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="10.1.81.1:5000"/>
 
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
 

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

@@ -41,7 +41,7 @@ zipkin:
   storage:
     type: elasticsearch
     elasticsearch:
-      hosts: 192.168.0.63:9200
+      hosts: 10.1.81.1:9200
       index: zipkin
       index-shards: 5
       index-replicas: 1

+ 1 - 1
base-servers/zipkin-server/src/main/resources/logback-spring.xml

@@ -28,7 +28,7 @@
     <springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active" defaultValue="dev"/>
     <springProperty scope="context" name="common-pattern" source="logging.common-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}:[%5p] [%t:%r] [%C{1}:%M:%L] --> %m%n"/>
     <springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
-    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="192.168.0.63:5000"/>
+    <springProperty scope="context" name="log.destination" source="logging.destination" defaultValue="10.1.81.1:5000"/>
 
     <contextName>${spring.application.name}-${spring.profiles.active}-logback</contextName>
 

+ 42 - 0
framework/core/src/main/java/com/usoftchina/saas/cache/RedisConfig.java

@@ -0,0 +1,42 @@
+package com.usoftchina.saas.cache;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+/**
+ * @author: guq
+ * @create: 2018-12-27 10:32
+ **/
+@Configuration
+public class RedisConfig {
+
+    @Bean
+    @SuppressWarnings("all")
+    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
+        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
+        template.setConnectionFactory(factory);
+        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
+        ObjectMapper om = new ObjectMapper();
+        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
+        jackson2JsonRedisSerializer.setObjectMapper(om);
+        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
+        // key采用String的序列化方式
+        template.setKeySerializer(stringRedisSerializer);
+        // hash的key也采用String的序列化方式
+        template.setHashKeySerializer(stringRedisSerializer);
+        // value序列化方式采用jackson
+        template.setValueSerializer(jackson2JsonRedisSerializer);
+        // hash的value序列化方式采用jackson
+        template.setHashValueSerializer(jackson2JsonRedisSerializer);
+        template.afterPropertiesSet();
+        return template;
+    }
+}

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

@@ -31,10 +31,10 @@ public enum ExceptionCode implements BaseExceptionCode {
 
     // 账户管理相关
     COMPANY_NAME_EXIST(52000, "公司名称已注册"),
-    COMPANY_NAME_HASREGISTER(52000, "企业已开通saas,请联系管理员 %s 将您添加至企业"),
+    COMPANY_NAME_HASREGISTER(52000, "企业已开通saas服务,请联系管理员 %s 将您添加至企业"),
     COMPANY_CODE_EXIST(52001, "公司商业登记证号已注册"),
     COMPANY_DOMAIN_EXIST(52002, "域名已存在"),
-    COMPANY_NOT_EXIST(52003, "公司不存在"),
+    COMPANY_NOT_EXIST(52003, "企业不存在"),
     COMPANY_NOT_BIND(52004, "未绑定该公司"),
     USER_NAME_EXIST(53000, "用户名已注册"),
     USER_MOBILE_EXIST(53001, "手机号已注册"),
@@ -45,11 +45,11 @@ public enum ExceptionCode implements BaseExceptionCode {
     USER_NOT_ENABLE(53006, "用户禁止使用"),
     ROLE_NOT_EXIST(53020, "角色不存在"),
     MISSING_PERMISSIONS(53030, "权限缺失"),
-    USER_COMPANY_EXIST(53007, "企业已存在账户"),
-    ROLE_HAS_USE(54030, "存在已使用该角色的账户,不允许删除"),
-    APPLY_DOING_EXIST(54031, "已存在待处理的加入申请"),
-    HAS_BIND_COMPANY(54032, "已加入企业"),
-    SMS_VALIDCODE_EXPIRE(54033, "短信验证码已过期"),
+    USER_COMPANY_EXIST(53007, "该账户已在企业服务中"),
+    ROLE_HAS_USE(54030, "该角色已有账户使用,不允许删除"),
+    APPLY_DOING_EXIST(54031, "您已申请加入此企业,管理员处理中,请勿重复申请"),
+    HAS_BIND_COMPANY(54032, "您的账户存在该企业服务中,请勿重复加入"),
+    SMS_VALIDCODE_EXPIRE(54033, "短信验证码已过期,请重新获取"),
     SMS_VALIDCODE_ERROR(54034, "短信验证码错误"),
 
     // 文件相关

+ 154 - 0
framework/core/src/main/java/com/usoftchina/saas/utils/RedisUtil.java

@@ -0,0 +1,154 @@
+package com.usoftchina.saas.utils;
+
+import com.usoftchina.saas.context.SpringContextHolder;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author: guq
+ * @create: 2018-12-27 10:39
+ **/
+public class RedisUtil {
+
+
+    private static RedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean("redisTemplate", RedisTemplate.class);
+
+    /**
+     * 指定缓存失效时间
+     * @param key 键
+     * @param time 时间(秒)
+     * @return
+     */
+    public static boolean expire(String key, long time) {
+        try {
+            if (time > 0) {
+                redisTemplate.expire(key, time, TimeUnit.SECONDS);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 根据key 获取过期时间
+     * @param key 键 不能为null
+     * @return 时间(秒) 返回0代表为永久有效
+     */
+    public static long getExpire(String key) {
+        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
+    }
+
+    /**
+    * @Description 判断key是否存在
+    * @Param: [key]
+    * @return: boolean
+    * @Author: guq
+    * @Date: 2018/12/27
+    */
+    public static boolean hasKey(String key) {
+        try {
+            return redisTemplate.hasKey(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+    * @Description 删除缓存
+    * @Param: [keys]
+    * @return: void
+    * @Author: guq
+    * @Date: 2018/12/27
+    */
+    @SuppressWarnings("unchecked")
+    public static void del(String... keys) {
+        if (null != keys && keys.length > 0) {
+            if (keys.length == 1) {
+                redisTemplate.delete(keys[0]);
+            } else {
+                redisTemplate.delete(CollectionUtils.arrayToList(keys));
+            }
+        }
+    }
+
+    /**
+    * @Description 获取key
+    * @Param: [key]
+    * @return: java.lang.Object
+    * @Author: guq
+    * @Date: 2018/12/27
+    */
+    public static Object get(String key) {
+        return key == null ? null : redisTemplate.opsForValue().get(key);
+    }
+
+    /**
+    * @Description 放入缓存
+    * @Param: [key, obj]
+    * @return: boolean
+    * @Author: guq
+    * @Date: 2018/12/27
+    */
+    public static boolean set(String key, Object obj) {
+        try {
+            redisTemplate.opsForValue().set(key, obj);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    /**
+    * @Description 添加时间
+    * @Param: [key, value, time]
+    * @return: boolean
+    * @Author: guq
+    * @Date: 2018/12/27
+    */
+    public static boolean set(String key, Object value, long time) {
+        try {
+            if (time > 0) {
+                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
+            } else {
+                set(key, value);
+            }
+            return true;
+        }catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+    * @Description 要增加delta
+    * @Param: [key, delta]
+    * @return: long
+    * @Author: guq
+    * @Date: 2018/12/27
+    */
+    public long incr(String key, long delta) {
+        if (delta < 0) {
+            throw new RuntimeException("递增因子必须大于0");
+        }
+        return redisTemplate.opsForValue().increment(key, delta);
+    }
+
+
+    /**
+     * @Description 要减少delta
+     * @Param: [key, delta]
+     * @return: long
+     * @Author: guq
+     * @Date: 2018/12/27
+     */
+    public long decr(String key, long delta) {
+        if (delta < 0) {
+            throw new RuntimeException("递减因子必须大于0");
+        }
+        return redisTemplate.opsForValue().increment(key, -delta);
+    }
+}

+ 32 - 13
frontend/saas-portal-web/src/components/conenter/addenterprise.vue

@@ -75,7 +75,7 @@
                     <ul style="padding:0;position: relative;">
                         <li>
                             <span class="qy-biaoti"><span class="xingxing marght">*</span>查找企业</span>
-                            <input @input="Testingqyname" @focus="Testingqyname" @blur="showwarning" ref="lookupqyname" class="inpind" :class= "{showborder:isgsname}" type="text"  placeholder="输入企业名称或管理员">
+                            <input @click.stop @input="Testingqyname" @focus="Testingqyname" @blur="showwarning" ref="lookupqyname" class="inpind" :class= "{showborder:isgsname}" type="text"  placeholder="输入企业名称或管理员">
                             <span v-show="iswarning" class="warning" ref="warningqyname"></span>
                         </li>
                         <!-- 匹配企业后显示的内容 -->
@@ -154,6 +154,7 @@ import { setTimeout, clearTimeout } from 'timers';
                 isqyaddress:false,//地址
                 isyzusername:false,
                 isheidemail:false,
+                iscompanyname:false,
             }
         },
         components:{
@@ -227,11 +228,11 @@ import { setTimeout, clearTimeout } from 'timers';
                 this.isqyname1 = false;
             },
             showqyname1(){
-                this.isqyname1 = true;
                 let qyname = this.$refs.qyname.value.replace(/\s+/g, "");//公司名字过滤空格
                 if(qyname == ''){
                     this.$refs.qyno.innerHTML = ' <img style="width:14px" src="/static/img/warning.png" alt=""> 企业名称不能为空';
-                }
+                };
+                this.isqyname1 = true;
             },
             //检测企业名称2
             Testingqyname(){
@@ -277,7 +278,15 @@ import { setTimeout, clearTimeout } from 'timers';
             },
             //企业名称2提示语显示
             showwarning(){
-                this.iswarning = true;
+                let qyname = this.$refs.lookupqyname.value.replace(/\s+/g, "");//企业名字
+                if (!this.companyId && qyname!='') {
+                    this.$refs.warningqyname.innerHTML = '<img style="width:14px" src="/static/img/warning.png" alt=""> 请选择要加入的企业';
+                } else {
+                    this.$refs.warningqyname.innerHTML = '<img style="width:14px" src="/static/img/ok.png" alt="">'
+                }
+                setTimeout(()=>{
+                    this.iswarning = true;
+                },100)
             },
             //验证个人姓名1
             yzusername(){
@@ -324,13 +333,22 @@ import { setTimeout, clearTimeout } from 'timers';
             },
             // 选择要加入的企业
             qylist(i){
-                this.companyId = this.arr[i].id,
-                this.admin = this.arr[i].admin,
-                this.newindex = i,
-                this.$refs.lookupqyname.value = this.arr[i].name,
+                let companyName = JSON.parse(window.sessionStorage.getItem("companyname"));
+                this.companyId = this.arr[i].id;
+                this.admin = this.arr[i].admin;
+                this.newindex = i;
+                this.$refs.lookupqyname.value = this.arr[i].name;
                 this.islookup = false;
-                if (this.admin == this.mytoken.account.realname) {
-                    this.$refs.warningqyname.innerHTML = '<img style="width:14px" src="/static/img/warning.png" alt=""> 账号已在企业服务中,不能重复加入';
+                for (let j = 0; j < companyName.length; j++) {
+                    if (this.arr[i].name == companyName[j]) {
+                        this.iscompanyname = true
+                        break
+                    } else {
+                        this.iscompanyname = false
+                    }
+                }
+                if (this.iscompanyname || this.admin == this.mytoken.account.realname) {
+                    this.$refs.warningqyname.innerHTML = '<img style="width:14px" src="/static/img/warning.png" alt=""> 您的账户已在企业服务中,请勿重复加入';
                 } else {
                     this.$refs.warningqyname.innerHTML = '<img style="width:14px" src="/static/img/ok.png" alt="">';
                     // this.$refs.lookupqyname.style.border = '1px solid #1E88F5';
@@ -351,13 +369,14 @@ import { setTimeout, clearTimeout } from 'timers';
                     this.$message.error('企业或管理员不存在');
                 } else if (!this.companyId) {
                     this.$message.error('请选择要加入的企业');
-                } else if (this.admin == this.mytoken.account.realname) {
-                    this.$message.error('账号已在企业服务中,不能重复加入');
+                } else if (this.admin == this.mytoken.account.realname || this.iscompanyname) {
+                    this.$message.error('您的账户已在企业服务中,请勿重复加入');
                 } else if (!this.isTestingname) {
                     this.$message.error('姓名不能为空');
                 } else if (!this.isregname) {
                     this.$message.error('姓名不能包含符号、数字等非法字符');
                 } else {
+                    this.$store.state.isloading = true;
                     this.$ajax({
                         url: this.$url.api+'/api/account/accountCenter/company/join',
                         method:'POST',
@@ -371,6 +390,7 @@ import { setTimeout, clearTimeout } from 'timers';
                         }
                     })
                     .then(res=>{
+                        this.$store.state.isloading = false;
                         if (res.data.success) {
                             this.$message.success('提交成功,待管理员批准加入');
                             // this.$store.state.ishongdian = true;//显示红点
@@ -477,7 +497,6 @@ import { setTimeout, clearTimeout } from 'timers';
                                     this.$router.push({path: '/enterprise'});
                                 },3000)
                             })
-                            this.$message.success('保存成功,待管理员批准');
                         } else {
                             this.$message.error('企业名称不能包含符号等非法字符');
                         }

+ 3 - 0
frontend/saas-portal-web/src/components/conenter/company.vue

@@ -173,7 +173,10 @@
                         this.list = res.data.data.spaces;
                         this.pagingtion(1,this.size);
                         let enterprise = [];
+                        let qyname = [];
                         for (let i = 0; i < this.list.length; i++) {
+                            qyname.push(this.list[i].name);
+                            window.sessionStorage.setItem('companyname',JSON.stringify(qyname))
                             if (this.list[i].saas_) {
                                 let addenterprise = {'id':'', 'logoUrl': '', 'name': '','dcName':''};
                                 addenterprise.id = this.list[i].id;

+ 1 - 0
frontend/saas-portal-web/src/components/conenter/failure.vue

@@ -43,6 +43,7 @@ p {
     color: #344A69;
     letter-spacing: 0;
     top: 60%;
+    line-height: 40px;
 }
 .text2 {
     font-family: PingFangSC-Regular;

+ 54 - 25
frontend/saas-portal-web/src/components/conenter/invitation.vue

@@ -4,8 +4,8 @@
         <div class="Popup" v-if="istanchaung">
         <!-- <div class="Popup"> -->
             <div class="shang">
-                <img src="/static/img/caidian2x.png" alt="">
-                <img src="/static/img/tijiao2x.png" alt="">
+                <!-- <img src="/static/img/caidian2x.png" alt=""> -->
+                <img src="/static/img/tijiao.png" alt="">
                 <img class="xs" @click="hidden" src="/static/img/assets/chahao.png" alt="">
             </div>
             <div class="zhong">
@@ -19,8 +19,10 @@
 
         <img src="/static/img/beijing.png" alt="">
         <div class="centent">
-            <img src="/static/img/assets/logo@2x.png" alt="">
-            <p class="inv-logo">U企云服</p>
+            <div>
+                <img style="width:80px" src="/static/img/assets/logo@2x.png" alt="">
+                <span class="inv-logo">U企云服</span>
+            </div>
             <div class="inv-title">
                 <p>{{enterprise.username}}</p>
                 <p>
@@ -36,7 +38,7 @@
                 <span v-if="!isobtaincode" ref="obtaincode">{{time}}</span>
             </div>
             <input class="padding-left" type="text" ref="name" @blur="name" placeholder="请输入姓名">
-            <select class="role padding-left" ref="role" @blur="roles">
+            <select style="background-color: white;" class="role padding-left" ref="role" @blur="roles">
                 <option value="" disabled selected hidden>岗位角色</option>
                 <option v-for="(d,i) in arr" :key="i" :value=d.id>{{d.name}}</option>
             </select>
@@ -70,10 +72,13 @@ import { setTimeout } from 'timers';
                 times:'',
                 istanchaung:false,
             }
+        },
+        computed: {
+            
         },
         created(){
             this.$store.state.isinvitation = false;
-            this.param();//获取页面参数
+            this.browserRedirect();
         },
         mounted(){
             this.$store.state.isinvitation = false;
@@ -82,6 +87,25 @@ import { setTimeout } from 'timers';
             this.$store.state.isinvitation = true
         },
         methods:{
+            browserRedirect() {
+                let url1 = window.location.href;
+                let param = url1.substring(url1.lastIndexOf('=')+1, url1.length);
+                var sUserAgent = navigator.userAgent.toLowerCase();
+                var bIsIpad = sUserAgent.match(/ipad/i) == "ipad";
+                var bIsIphoneOs = sUserAgent.match(/iphone os/i) == "iphone os";
+                var bIsMidp = sUserAgent.match(/midp/i) == "midp";
+                var bIsUc7 = sUserAgent.match(/rv:1.2.3.4/i) == "rv:1.2.3.4";
+                var bIsUc = sUserAgent.match(/ucweb/i) == "ucweb";
+                var bIsAndroid = sUserAgent.match(/android/i) == "android";
+                var bIsCE = sUserAgent.match(/windows ce/i) == "windows ce";
+                var bIsWM = sUserAgent.match(/windows mobile/i) == "windows mobile";
+                if (bIsIpad || bIsIphoneOs || bIsMidp || bIsUc7 || bIsUc || bIsAndroid || bIsCE || bIsWM) {
+                    // 移动端访问跳转到移动端页面
+                    window.location.href = 'invitation_mobile_join.html?param='+param
+                } else {
+                    this.param();//获取pc端页面参数
+                }
+            },
             //验证手机
             phone(){
                 let phone = this.$refs.phone.value;//手机 
@@ -168,7 +192,9 @@ import { setTimeout } from 'timers';
                 let name = this.$refs.name.value;//姓名
                 let roleid = this.$refs.role.value;//角色
                 if (!this.isphone) {
-                    this.$message.error('手机号码不能为空');
+                    this.$message.error({
+                        message: '手机号码不能为空'
+                        });
                 } else if (!this.isregphone){
                     this.$message.error('请输入正确的手机号码');
                 } else if (!this.isname) {
@@ -255,7 +281,9 @@ import { setTimeout } from 'timers';
                             this.role()
                         },800)
                     } else {
-                        this.$message.error(res.data.message);
+                        this.$message.error({
+                            message:res.data.message,
+                            });
                         this.$router.push({path:'/failure'})
                     }
                 })
@@ -273,7 +301,7 @@ import { setTimeout } from 'timers';
     width: 100%;
 }
 .centent {
-    width: 352px;
+    width: 27rem;
     position: absolute;
     top: 50%;
     left: 50%;
@@ -285,6 +313,9 @@ import { setTimeout } from 'timers';
     font-size: 26px;
     color: #FFFFFF;
     letter-spacing: 3.25px;
+    display: inline-block;
+    position: relative;
+    bottom: -20px;
 }
 .padding-left {
     padding-left: 15px; 
@@ -295,7 +326,7 @@ import { setTimeout } from 'timers';
     opacity: 0.87;
     background: #FFFFFF;
     box-shadow: 0 2px 4px 0 #3C8EFF;
-    font-size: 15px;
+    font-size: 14px;
     border: 0;
     height: 40px;
     border-radius: 4px;
@@ -303,7 +334,7 @@ import { setTimeout } from 'timers';
 .inv-title {
     text-align: left;
     margin-top: 37px;
-    margin-bottom: 40px;
+    margin-bottom: 20px;
 }
 .inv-title > p{
     font-family: PingFangSC-Medium;
@@ -314,7 +345,7 @@ import { setTimeout } from 'timers';
     line-height: 30px;
 }
 .inv-title >p:nth-child(1) {
-    margin-bottom: 16px;
+    /* margin-bottom: 16px; */
 }
 .inv-text {
     font-family: PingFangSC-Regular;
@@ -332,12 +363,18 @@ import { setTimeout } from 'timers';
     width: 100%;
     margin-bottom: 39px;
     opacity: 0.87;
-    background: #FFFFFF;
     box-shadow: 0 2px 4px 0 #3C8EFF;
-    font-size: 15px;
+    font-size: 14px;
     border: 0;
     height: 40px;
     border-radius: 4px;
+    appearance:none;
+   -moz-appearance:none;
+   -webkit-appearance:none;
+   /*设置箭头*/
+   background: url(/static/img/TriangleCopy.png) no-repeat scroll right center transparent;
+   padding-right: 14px;
+   background-position-x: 95%;
 }
 .Verification {
     border: 0;
@@ -352,7 +389,7 @@ import { setTimeout } from 'timers';
 .Verification > input {
     width: 68%;
     border: 0;
-    font-size: 15px;
+    font-size: 14px;
     outline:none;
 }
 .Verification > span {
@@ -393,20 +430,12 @@ import { setTimeout } from 'timers';
     height: 30%;
     position: relative;
 }
-.shang > img {
-    position: absolute;
-}
 .shang > img:nth-child(1){
-    top: 15px;
-    left: 34%;
     width: 124px;
+    margin-top: 15px;
 }
 .shang > img:nth-child(2){
-    top: 18px;
-    left: 38%;
-    width: 101px;
-}
-.shang > img:nth-child(3){
+    position: absolute;
     top: 0px;
     right: 0px;
 }

+ 6 - 7
frontend/saas-portal-web/src/pages/invitation_mobile_join/invitation_mobile_join.html

@@ -174,7 +174,7 @@
                     has_icon:true,
                     has_close_btn:false,
                     fullscreen:false,
-                    timeout:2000,
+                    timeout:233000,
                     position_class: "toast-top-center",
                     sticky:false,
                     has_progress:false,
@@ -184,10 +184,10 @@
         </script> 
     </head>
     <body>
-        <div class="x-join" style="width:100%;height:100%">
+        <div class="x-join" style="width:100%;">
             <div class="x-join-center" hidden>
                 <div class="subtitle">U企云服</div>
-                <div style="margin:30px 0 0 0px">
+                <div style="margin:20px 0 0 0px">
                     <!-- 邀请人姓名 -->
                     <span id="sendman" style="padding:0 0 0 2px;color: #fff;"></span>
                     </br>
@@ -203,10 +203,9 @@
                 <button style="position: absolute;" class="expand_icon" type="button" ></button>
                 <button class="btn-submit" style="border-radius: 30px;width: 80%;      margin-top: 40px;   font-size: 18px; height: 47px;" type="button" id="joinBtn" onclick="join();">提 交</button>
             </div>
-            <div class="x-join-bottom" style="    height: 55px;">
-                <span style="font-size:16px;padding:0 0 0 5px;color: #999;">U企云服 | 电子行业企业管理云端方案</span>
-                </br>
-                <span style="font-size:16px;padding:0 0 0 5px;color: #999;">深圳市优软科技有限公司</span>
+            <div class="x-join-bottom" style=" display:table-cell;font-size:12px;font-height:14px;vertical-align:middle;text-align:center;   height: 50px;">
+                <span class="x-bottom-text">U企云服 | 电子行业企业管理云端方案</span>
+                <span class="x-bottom-text" style="    margin-top: 2px;">深圳市优软科技有限公司</span>
             </div>
             <div class="x-join-error" hidden>
                 </br></br></br></br>

+ 7 - 0
frontend/saas-portal-web/static/css/gongsi.css

@@ -26,6 +26,13 @@ input::-webkit-input-placeholder {
     padding: 0 !important;
     text-align: center !important;
     margin-top: 0px !important;
+    opacity: 0.8 !important;
+}
+/* ele提示弹窗 */
+.el-message {
+    min-width: 160px !important;
+    height: 32px !important;
+    padding-right: 18px !important;
 }
 /* 弹窗 -----------------------------------------------*/
 .tanchuang {

+ 22 - 19
frontend/saas-portal-web/static/css/invitation_mobile_join.css

@@ -4,14 +4,12 @@
 	box-sizing: border-box
 }
 body, html {
-    min-height: 100%;
 }
 body {
     background-repeat: no-repeat;
     background-position: center;
     background-attachment: fixed;
     z-index: -1;
-    height: 100%;
     width: 100%;
     background-size: cover;
     background-image: url(../img/invitation_background.png);
@@ -41,13 +39,12 @@ body {
 }
 .x-join-center {
 	width: 100%;
-    height: 100%;
     /* left: 50%; */
     /* margin-left: -140px; */
     text-align: center;
 }
 .x-join-center .subtitle {
-    margin-top: 30px;
+    margin-top: 20px;
     margin-left: -15px;
 	display: block;
     height: 50px;
@@ -67,6 +64,15 @@ body {
 	margin: 40px auto 0px auto;
 	overflow: hidden;
 }
+.x-bottom-text{
+    margin-top: 5px;
+    display: block;
+    font-family: PingFangSC-Regular;
+    color: #666666;
+    letter-spacing: 1.88px;
+    font-size: 15px;
+    padding: 0 0 0 5px;
+}
 .x-join-center .share-step .share-step-tag {
 	display: block;
 	width: 30px;
@@ -96,50 +102,48 @@ body {
 	margin: 15px 5px 0 30px;
 }
 .input_style{
+    border-radius: 30px;
+    opacity: 0.8;
+    border: 1px solid #bdbdbd;
     -webkit-appearance: none;
-    font-size: 14.5px;
+    font-size: 16.5px;
     margin: 0 auto;
     padding-top: 3px;
     padding-left: 13px;
     width: 80%;
     margin-top: 18px;
     height: 40px;
-	display: block;
+    display: block;
     color: #555;
     background-color: #fff;
     background-image: none;
-    border: 1px solid #ccc;
-    border-radius:30px;
-    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-    -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
-    -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
-    transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+    border: 1px solid #656565;
 }
 .expand_icon{
     border: none;
-    margin-left: 292px;
+    margin-left: 70%;
     position: absolute;
     margin-top: -26px;
-    display: block;
+    display: inline-block;
     height: 16px;
     width: 28px;
     background: url(../img/assets/expand.png)no-repeat right;
 }
 .btn-check{
+    border-radius: 30px;
     padding-top: 4px;
     border-top-right-radius: 30px;
     border-bottom-right-radius: 30px;
     margin-top: -35px;
-    margin-left: 30px;
+    margin-left: 55%;
     line-height: 20px;
     height: 30px;
-    background: #fff;
+    background: unset;
     color: #369ab9;
     /* background-image: linear-gradient(to bottom, #69b5f7 0, #579dda 100%); */
     /* border-color: #428bca; */
     width: 110px;
-    display: inline-block;
+    display: block;
     /* padding: 0px; */
     margin-bottom: 0;
     font-size: 16px;
@@ -153,7 +157,6 @@ body {
     -ms-user-select: none;
     user-select: none;
     border: 1px solid transparent;
-    border-left: 2px solid #a9a9a9;
 }
 
 .btn-submit{

+ 1 - 1
frontend/saas-portal-web/static/css/main.css

@@ -101,7 +101,7 @@ main > section {
     height: 100%;
     width: 100%;
     background: #000;
-    opacity: 0.5;
+    opacity: 0.35;
     z-index: 10000;
 }
 /* 登录弹窗 */

+ 3 - 2
frontend/saas-portal-web/static/css/toast.style.css

@@ -20,7 +20,8 @@ i {
   -moz-osx-font-smoothing: grayscale; }
 
 .toast-icon-error:before {
-  content: "×"; }
+  content: "E";
+  font-weight: 600;}
 
 .toast-icon-info:before {
   content: ""; }
@@ -111,7 +112,7 @@ i {
     position: absolute;
     margin-left: 5px;
     top: 0%;
-    margin-top: 180px;
+    margin-top: 160px;
     left: 50%;
     -ms-transform: translate(-50%,-50%);
     -moz-transform: translate(-50%,-50%);

+ 0 - 0
frontend/saas-portal-web/static/img/Triangle Copy 4.png → frontend/saas-portal-web/static/img/TriangleCopy.png


BIN
frontend/saas-portal-web/static/img/caidian2x.png


BIN
frontend/saas-portal-web/static/img/tijiao.png


BIN
frontend/saas-portal-web/static/img/tijiao2x.png


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
frontend/saas-portal-web/static/js/stomp.min.js


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

@@ -143,6 +143,18 @@ body.launching {
   }
 }
 
+.x-toolbar {
+  .x-btn-over.x-btn-plain-toolbar-small{
+    .x-btn-wrap{
+      .x-btn-button{
+        .x-btn-inner{
+          color: #606060;
+        }
+      }
+    }
+  }
+}
+
 .x-btn-pressed{
   .x-btn-wrap{
     .x-btn-button{

+ 6 - 1
frontend/saas-web/app/model/document/ProductDetail.js

@@ -10,6 +10,11 @@ Ext.define('saas.model.document.ProductDetail', {
         { name: 'pd_whid', type: 'int' },
         { name: 'pd_price', type: 'float' },
         { name: 'pd_amount', type: 'float' },
-        { name: 'pd_num', type: 'float' }
+        { name: 'pd_num', type: 'float' },
+        { name: 'pd_text1', type: 'string' },
+        { name: 'pd_text2', type: 'string' },
+        { name: 'pd_text3', type: 'string' },
+        { name: 'pd_text4', type: 'string' },
+        { name: 'pd_text5', type: 'string' },
     ]
 });

+ 6 - 1
frontend/saas-web/app/model/document/customeraddress.js

@@ -7,6 +7,11 @@ Ext.define('saas.model.document.customeraddress', {
         { name: 'ca_person', type: 'string' },
         { name: 'ca_phone', type: 'string' },
         { name: 'ca_default', type: 'string' },
-        { name: 'ca_cuid', type: 'int' }
+        { name: 'ca_cuid', type: 'int' },
+        { name: 'ca_text1', type: 'string' },
+        { name: 'ca_text2', type: 'string' },
+        { name: 'ca_text3', type: 'string' },
+        { name: 'ca_text4', type: 'string' },
+        { name: 'ca_text5', type: 'string' },
     ]
 });

+ 6 - 1
frontend/saas-web/app/model/document/customercontact.js

@@ -8,6 +8,11 @@ Ext.define('saas.model.document.customercontact', {
         { name: 'cc_qq', type: 'string' },
         { name: 'cc_email', type: 'string' },
         { name: 'cc_default', type: 'string' },
-        { name: 'cc_cuid', type: 'int' }
+        { name: 'cc_cuid', type: 'int' },
+        { name: 'cc_text1', type: 'string' },
+        { name: 'cc_text2', type: 'string' },
+        { name: 'cc_text3', type: 'string' },
+        { name: 'cc_text4', type: 'string' },
+        { name: 'cc_text5', type: 'string' },
     ]
 });

+ 6 - 1
frontend/saas-web/app/model/document/stocking.js

@@ -13,6 +13,11 @@ Ext.define('saas.model.document.stocking', {
         { name: 'st_whname', type: 'string' },
         { name: 'st_batchqty', type: 'float' },
         { name: 'st_actqty', type: 'float' },
-        { name: 'st_num', type: 'float' }
+        { name: 'st_num', type: 'float' },
+        { name: 'st_text1', type: 'string' },
+        { name: 'st_text2', type: 'string' },
+        { name: 'st_text3', type: 'string' },
+        { name: 'st_text4', type: 'string' },
+        { name: 'st_text5', type: 'string' },
     ]
 });

+ 6 - 1
frontend/saas-web/app/model/document/vendorcontact.js

@@ -8,6 +8,11 @@ Ext.define('saas.model.document.vendorcontact', {
         { name: 'vc_qq', type: 'string' },
         { name: 'vc_email', type: 'string' },
         { name: 'vc_default', type: 'string' },
-        { name: 'vc_veid', type: 'int' }
+        { name: 'vc_veid', type: 'int' },
+        { name: 'vc_text1', type: 'string' },
+        { name: 'vc_text2', type: 'string' },
+        { name: 'vc_text3', type: 'string' },
+        { name: 'vc_text4', type: 'string' },
+        { name: 'vc_text5', type: 'string' },
     ]
 });

+ 6 - 1
frontend/saas-web/app/model/money/RecBalance1.js

@@ -10,6 +10,11 @@ Ext.define('saas.model.money.RecBalance1', {
         { name: 'rd_amount', type: 'float' },
         { name: 'rd_paymethod', type: 'string' },
         { name: 'rd_paycode', type: 'string' },
-        { name: 'rd_remark', type: 'string' }
+        { name: 'rd_remark', type: 'string' },
+        { name: 'rd_text1', type: 'string' },
+        { name: 'rd_text2', type: 'string' },
+        { name: 'rd_text3', type: 'string' },
+        { name: 'rd_text4', type: 'string' },
+        { name: 'rd_text5', type: 'string' },
     ]
 });

+ 6 - 1
frontend/saas-web/app/model/money/RecBalance2.js

@@ -10,6 +10,11 @@ Ext.define('saas.model.money.RecBalance2', {
         { name: 'rbd_sldate', type: 'date' },
         { name: 'rbd_amount', type: 'float' },
         { name: 'rbd_nowbalance', type: 'float' },
-        { name: 'rbd_remark', type: 'string' }
+        { name: 'rbd_remark', type: 'string' },
+        { name: 'rbd_text1', type: 'string' },
+        { name: 'rbd_text2', type: 'string' },
+        { name: 'rbd_text3', type: 'string' },
+        { name: 'rbd_text4', type: 'string' },
+        { name: 'rbd_text5', type: 'string' },
     ]
 });

+ 5 - 0
frontend/saas-web/app/model/sale/SaleInDetail.js

@@ -49,6 +49,11 @@ Ext.define('saas.model.sale.SaleInDetail', {
         { name: 'pd_ordercode', type: 'string' }, // 销售单号
         { name: 'pd_orderdetno', type: 'int' }, // 销售序号
         { name: 'pd_remark', type: 'string' }, // 备注
+        { name: 'pd_text1', type: 'string' },
+        { name: 'pd_text2', type: 'string' },
+        { name: 'pd_text3', type: 'string' },
+        { name: 'pd_text4', type: 'string' },
+        { name: 'pd_text5', type: 'string' },
     ],
     //一对一映射
     associations: [{ type: 'hasOne', model: 'saas.model.document.Product', associationKey: 'productDTO'}]

+ 5 - 0
frontend/saas-web/app/model/sale/SaleOutDetail.js

@@ -48,6 +48,11 @@ Ext.define('saas.model.sale.SaleOutDetail', {
         { name: 'pd_ordercode', type: 'string' }, // 销售单号
         { name: 'pd_orderdetno', type: 'string' }, // 销售序号
         { name: 'pd_remark', type: 'string' }, // 备注
+        { name: 'pd_text1', type: 'string' },
+        { name: 'pd_text2', type: 'string' },
+        { name: 'pd_text3', type: 'string' },
+        { name: 'pd_text4', type: 'string' },
+        { name: 'pd_text5', type: 'string' },
     ],
     //一对一映射
     associations: [{ type: 'hasOne', model: 'saas.model.document.Product', associationKey: 'productDTO'}]

+ 5 - 0
frontend/saas-web/app/model/sale/Saledetail.js

@@ -44,6 +44,11 @@ Ext.define('saas.model.sale.Saledetail', {
         },
         { name: 'sd_delivery', type: 'date' }, // 交货日期
         { name: 'sd_remark', type: 'string' }, // 备注
+        { name: 'sd_text1', type: 'string' },
+        { name: 'sd_text2', type: 'string' },
+        { name: 'sd_text3', type: 'string' },
+        { name: 'sd_text4', type: 'string' },
+        { name: 'sd_text5', type: 'string' },
     ],
     //一对一映射
     associations: [{ type: 'hasOne', model: 'saas.model.document.Product', associationKey: 'productDTO'}]

+ 8 - 1
frontend/saas-web/app/model/stock/Makematerial.js

@@ -16,6 +16,13 @@ Ext.define('saas.model.stock.Makematerial', {
         { name: 'mm_qty', type: 'float' },
         { name: 'mm_amount', type: 'float' },
         { name: 'mm_repprodcode', type: 'string' },
-        { name: 'mm_remark', type: 'string' }],
+        { name: 'mm_remark', type: 'string' },
+        { name: 'mm_text1', type: 'string' },
+        { name: 'mm_text2', type: 'string' },
+        { name: 'mm_text3', type: 'string' },
+        { name: 'mm_text4', type: 'string' },
+        { name: 'mm_text5', type: 'string' }
+    ],
+        
     associations: [{ type: 'hasOne', model: 'saas.model.document.ProductDTO', associationKey: 'ProductDTO'}]        
 });

+ 0 - 39
frontend/saas-web/app/model/stock/ProdIODetail.js

@@ -1,39 +0,0 @@
-Ext.define('saas.model.stock.ProdIODetail', {
-    extend: 'saas.model.Base',
-
-    fields: [
-
-        { name: 'id', type: 'int' },
-        { name: 'pd_piid', type: 'int' },
-        { name: 'pd_inoutno', type: 'string' },
-        { name: 'pd_piclass', type: 'string' },
-        { name: 'pd_pdno', type: 'int' },
-        { name: 'pd_ordercode', type: 'string' },
-        { name: 'pd_orderdetno', type: 'int' },
-        { name: 'pd_prodid', type: 'int' },
-        { name: 'pd_prodcode', type: 'string' },
-        { name: 'pd_unit', type: 'string' },
-        { name: 'pd_inqty', type: 'float' },
-        { name: 'pd_outqty', type: 'float' },
-        { name: 'pd_orderprice', type: 'float' },
-        { name: 'pd_sendprice', type: 'float' },
-        { name: 'pd_price', type: 'float' },
-        { name: 'pd_total', type: 'float' },
-        { name: 'pd_taxrate', type: 'float' },
-        { name: 'pd_netprice', type: 'float' },
-        { name: 'pd_nettotal', type: 'float' },
-        { name: 'pd_whid', type: 'int' },
-        { name: 'pd_whcode', type: 'string' },
-        { name: 'pd_whname', type: 'string' },
-        { name: 'pd_inwhid', type: 'int' },
-        { name: 'pd_inwhcode', type: 'string' },
-        { name: 'pd_inwhname', type: 'string' },
-        { name: 'pd_orderid', type: 'int' },
-        { name: 'pd_sdid', type: 'int' },
-        { name: 'pd_status', type: 'string' },
-        { name: 'pd_ym', type: 'int' },
-        { name: 'pd_yqty', type: 'float' },
-        { name: 'pd_remark', type: 'string' },
-        { name: 'pd_ioid', type: 'int' },
-    ]
-});

+ 15 - 15
frontend/saas-web/app/util/BaseUtil.js

@@ -56,26 +56,26 @@ Ext.define('saas.util.BaseUtil', {
          */
         showSuccessToast: function (content, title, configs) {
             Ext.toast(Ext.Object.merge({
-                cls:'x-toast-success',
+                cls: 'x-toast-success',
                 html: content,
                 title: title,
                 closable: false,
                 align: 't',
-                autoCloseDelay:3000,
-                autoClose:true,
+                autoCloseDelay: 3000,
+                autoClose: true,
                 maxWidth: 400
             }, configs));
         },
 
         showErrorToast: function (content, title, configs) {
             Ext.toast(Ext.Object.merge({
-                cls:'x-toast-error',
+                cls: 'x-toast-error',
                 html: content,
                 title: title,
                 closable: false,
                 align: 't',
-                autoCloseDelay:3000,
-                autoClose:true,
+                autoCloseDelay: 3000,
+                autoClose: true,
                 maxWidth: 400
             }, configs));
         },
@@ -143,13 +143,13 @@ Ext.define('saas.util.BaseUtil', {
         request: function (config) {
             var url = config.url,
                 params = config.params,
-                async = Ext.isBoolean(config.async) ? config.async : true,
-                    method = config.method || 'GET',
-                    timeout = config.timeout || 8000,
-                    defaultHeaders = {
-                        'Access-Control-Allow-Origin': '*',
-                        "Content-Type": 'application/json;charset=UTF-8'
-                    };
+                async = (Ext.isBoolean(config.async) ? config.async : true),
+                method = config.method || 'GET',
+                timeout = config.timeout || 8000,
+                defaultHeaders = {
+                    'Access-Control-Allow-Origin': '*',
+                    "Content-Type": 'application/json;charset=UTF-8'
+                };
             return new Ext.Promise(function (resolve, reject) {
                 Ext.Ajax.request({
                     url: url,
@@ -181,11 +181,11 @@ Ext.define('saas.util.BaseUtil', {
          * @param decimalPrecision: 最大小数位数
          * @param thousandSeparator: 显示千分位分隔符
          */
-        numberFormat: function(value, decimalPrecision, thousandSeparator) {
+        numberFormat: function (value, decimalPrecision, thousandSeparator) {
             value = Number(value) || 0;
             decimalPrecision = Ext.isNumber(decimalPrecision) ? decimalPrecision : 2;
             thousandSeparator = !!thousandSeparator;
-            
+
             var f1 = thousandSeparator ? '0,000' : '0';
             var arr = (value + '.').split('.');
             var xr = (new Array(arr[1].length > decimalPrecision ? decimalPrecision : arr[1].length)).fill('0');

+ 237 - 108
frontend/saas-web/app/util/FormUtil.js

@@ -2,35 +2,29 @@ Ext.define('saas.util.FormUtil', {
 
     statics: {
 
-        // 请求页面组件接口模板
-        baseUrl: '/api/ui/co_view/config?name={viewName}',
-        // 保存自定义配置
-        // saveUrl: '/api/ui/co_view/config?name={viewName}',
-        // 模板替换正则
-        urlRe: /(.*){viewName}(.*)/g,
+        // 主表允许自定义的配置名
+        MAIN_ALLOW_CUS_FIELDS: ['fieldLabel', 'hidden', 'index', 'columnWidth', 'group'],
+        // 从表允许自定义的配置名
+        DETAIL_ALLOW_CUS_FIELDS: ['text', 'hidden', 'index', 'width'],
 
         setItems: function(form) {
             let me = this,
-            defaultItems;
-            
             defaultItems = form.defaultItems;
 
-            me.applyCusItemConfig(form)
-            .then(function(res) {
-                if(res.success) {
-                    let config = res.data || true, items = defaultItems || [];
-                    if(config) {
-                        let cusItems = config.items || [];
-                        Ext.Array.each(cusItems, function(cusItem) {
-                            let item = items.find(function(item) {
-                                return item.name == cusItem.name;
-                            });
-                            Ext.apply(item, cusItem);
-                        });
-                        items = me.applyDefaultItems(form, items);
+            saas.util.ViewUtil.getViewConfig(form.viewName)
+            .then(function(cfg) {
+                if(cfg) {
+                    let items = [];
+
+                    items = me.applyItemsGroup(defaultItems || []);
+                    items = me.initItems(items);
+                    items = me.applyCusMainItemConfig(items, cfg);
+                    items = me.applyCusDetailItemConfig(items, cfg);
+
+                    form.configItems = items;
+
+                    items = me.applyDefaultItems(form, items);
 
-                        items = me.applyItemsGroup(items);
-                    }
                     form.removeAll();
                     return form.addItems(items);
                 }else {
@@ -44,24 +38,232 @@ Ext.define('saas.util.FormUtil', {
                 me.loadData(form);
             })
             .catch(function(response) {
-                saas.util.BaseUtil.showErrorToast('加载数据错误:' + response.message);
                 console.error(response);
             });
 
         },
 
-        /**
-         * 获得form的客户自定义字段配置
-         * @param form: form组件
-         * @param url: url
-         */
+        applyItemsGroup: function(items) {
+            let groups = [],
+            groupCount = 0,
+            newItems = [];
+
+            Ext.Array.each(items, function(it, index) {
+                let item = Object.assign({}, it),
+                groupName = item.group;
+                if(!!groupName) {
+                    let idx = groups.findIndex(function(g) {
+                        return g.title == groupName;
+                    }),group;
+
+                    if(idx == -1) {
+                        group = {
+                            title: groupName,
+                            count: 1
+                        };
+                        groups.push(group);
+                    }else {
+                        group = groups[idx];
+                        group.count++;
+                    }
+                }
+                newItems.push(item);
+            });
+
+            Ext.Array.sort(newItems, function(a, b) {
+                let gs = groups.concat([{
+                    title: '_nogroup'
+                }]);
+                a.group = a.group || '_nogroup';
+                let v1 = gs.findIndex(function(g) {
+                    return g.title == a.group;
+                })
+                let v2 = gs.findIndex(function(g) {
+                    return g.title == b.group;
+                })
+                return v1 - v2;
+            });
+
+            Ext.Array.each(groups, function(g) {
+                let idx = newItems.findIndex(function(i) {
+                    return i.group == g.title;
+                });
+                g.index = idx;
+            });
+
+            Ext.Array.each(groups, function(group, index) {
+                let formIndex = group.index;
+                delete group.index;
+                Ext.Array.insert(newItems, formIndex + index, [{
+                    xtype: 'separatefield',
+                    name: 'group' + (++groupCount),
+                    html: group.title,
+                    fieldLabel: group.title || '分组' + groupCount
+                }]);
+            });
+
+            return newItems;
+        },
+
+        initItems: function(items) {
+            let itemCount = detailCount = 1, newItems = [];
+            Ext.Array.each(items, function(it, i) {
+                let item = Object.assign({}, it);
+                if(item.xtype == 'detailGridField') {
+                    let columns = item.columns,
+                    colCount = 1;
+                    Ext.Array.each(columns, function(col, j) {
+                        if((col.hidden || col.width == 0 || !col.dataIndex) && (!col.hasOwnProperty('initHidden') || col.initHidden)) {
+                            Ext.applyIf(col, {
+                                index: -1,
+                                initHidden: true
+                            });
+                        }else {
+                            Ext.applyIf(col, {
+                                text: '',
+                                hidden: false,
+                                index: colCount++,
+                                allowBlank: true,
+                                width: 100,
+                                initHidden: false
+                            });
+                        }
+                    });
+                    if(!columns[columns.length - 1].flex) {
+                        columns.push({
+                            dataIndex: '',
+                            initHidden: true,
+                            flex: 1,
+                            allowBlank: true
+                        });
+                    }
+                    Ext.applyIf(item, {
+                        allowBlank: false,
+                        columnWidth: 1,
+                        gname: 'detail' + detailCount,
+                        fieldLabel: '从表' + (detailCount++),
+                    });
+                }else if(item.xtype == 'hidden') {
+                    Ext.applyIf(item, {
+                        fieldLabel: '',
+                        hidden: true,
+                        initHidden: true,
+                    });
+                }else if(item.xtype == 'separatefield') {
+                    Ext.applyIf(item, {
+                        fieldLabel: item.html,
+                        columnWidth: 1,
+                    });
+                }else {
+                    Ext.applyIf(item, {
+                        fieldLabel: '',
+                        columnWidth: 0.25,
+                    });
+                }
+
+                if(item.hidden) {
+                    if(item.initHidden || !item.hasOwnProperty('initHidden')) {
+                        Ext.applyIf(item, {
+                            index: -1,
+                            initHidden: true
+                        });
+                    }else {
+                        Ext.applyIf(item, {
+                            index: itemCount++,
+                            initHidden: false
+                        }); 
+                    }
+                }else {
+                    Ext.applyIf(item, {
+                        index: itemCount++,
+                        initHidden: false
+                    });
+                }
+
+                Ext.applyIf(item, {
+                    name: 'item' + i,
+                    hidden: false,
+                    allowBlank: true,
+                    group: undefined,
+                });
+
+                newItems.push(item);
+            });
+
+            Ext.Array.sort(newItems, function(a, b) {
+                return a.index - b.index;
+            });
 
-        applyCusItemConfig: function(form) {
+            return newItems;
+        },
+
+        // 将自定义配置应用到默认items
+        applyCusMainItemConfig: function(items, cfg) {
             let me = this,
-            viewName = form.viewName,
-            defaultItems = form.defaultItems,
-            url = me.baseUrl.replace(me.urlRe, '$1' + viewName);
-            return saas.util.BaseUtil.request({url, async: false});
+            cusMainItems = cfg.main || [];
+
+            Ext.Array.each(cusMainItems, function(cusItem) {
+                let item = Ext.Array.findBy(items, function(item) {
+                    return item.name == cusItem.name;
+                });
+                if(!!item) {
+                    let keys = Ext.Object.getAllKeys(cusItem);
+                    keys.map(function(k) {
+                        if(me.MAIN_ALLOW_CUS_FIELDS.indexOf(k) != -1) {
+                            if(k != 'hidden' || item.allowBlank) {
+                                item['_init_' + k] = item.hasOwnProperty('_init_' + k) ? item['_init_' + k] : item[k]; 
+                                item[k] = cusItem[k];
+                            }
+                        }
+                    });
+                    
+                }
+            });
+
+            Ext.Array.sort(items, function(a, b) {
+                return a.index - b.index;
+            });
+
+            return items;
+        },
+
+        // 将自定义配置应用到从表items
+        applyCusDetailItemConfig: function(items, cfg) {
+            let me = this,
+            detailCount = 1;
+
+            Ext.Array.each(items, function(item) {
+                let gname = item.gname;
+
+                if(item.xtype == 'detailGridField' && cfg.hasOwnProperty(gname)) {
+                    let columns = item.columns,
+                    cusColumns = cfg[gname] || [];
+
+                    Ext.Array.each(cusColumns, function(cusCol) {
+                        let col = Ext.Array.findBy(columns, function(col) {
+                            return col.dataIndex == cusCol.dataIndex;
+                        });
+                        if(!!col) {
+                            let keys = Ext.Object.getAllKeys(cusCol);
+                            keys.map(function(k) {
+                                if(me.DETAIL_ALLOW_CUS_FIELDS.indexOf(k) != -1) {
+                                    if(k != 'hidden' || col.allowBlank) {
+                                        col['_init_' + k] = col.hasOwnProperty('_init_' + k) ? col['_init_' + k] : col[k]; 
+                                        col[k] = cusCol[k];
+                                    }
+                                }
+                            });
+                            
+                        }
+                    });
+
+                    Ext.Array.sort(columns, function(a, b) {
+                        return a.index - b.index;
+                    });
+                }
+            });
+
+            return items;
         },
 
         /**
@@ -167,13 +369,6 @@ Ext.define('saas.util.FormUtil', {
                         }
                     });
 
-                    if(columns[columns.length - 1].flex != 1) {
-                        columns.push({
-                            flex: 1,
-                            allowBlank: true
-                        });
-                    }
-
                     cnames.push(item.detnoColumn);
 
                     formModel.set('detail' + index + '.detailBindFields', cnames);
@@ -207,72 +402,6 @@ Ext.define('saas.util.FormUtil', {
             return items;
         },
 
-        applyItemsGroup: function(items) {
-            let groups = [];
-
-            Ext.Array.each(items, function(item, index) {
-                let groupName = item.group;
-                if(!!groupName) {
-                    let idx = groups.findIndex(function(g) {
-                        return g.title == groupName;
-                    }),group;
-
-                    if(idx == -1) {
-                        group = {
-                            title: groupName,
-                            count: 1
-                        };
-                        groups.push(group);
-                    }else {
-                        group = groups[idx];
-                        group.count++;
-                    }
-                }
-            });
-
-            Ext.Array.sort(items, function(a, b) {
-                let gs = groups.concat([{
-                    title: '_nogroup'
-                }]);
-                a.group = a.group || '_nogroup';
-                let v1 = gs.findIndex(function(g) {
-                    return g.title == a.group;
-                })
-                let v2 = gs.findIndex(function(g) {
-                    return g.title == b.group;
-                })
-                return v1 - v2;
-            });
-
-            Ext.Array.each(groups, function(g) {
-                let idx = items.findIndex(function(i) {
-                    return i.group == g.title;
-                });
-                g.index = idx;
-            });
-
-            Ext.Array.each(groups, function(group, index) {
-                let formIndex = group.index;
-                delete group.index;
-                Ext.Array.insert(items, formIndex + index, [{
-                    xtype: 'container',
-                    userCls: 'x-field-separator',
-                    height: 36,
-                    html: group.title,
-                    columnWidth: 1,
-                    ignore: true,
-                    isValid: function() {
-                        return true;
-                    },
-                    isDirty: function() {
-                        return false;
-                    }
-                }]);
-            });
-
-            return items;
-        },
-
         loadData: function(form) {
             let me = this;
             form.setLoading(true);
@@ -364,8 +493,8 @@ Ext.define('saas.util.FormUtil', {
                     }
                 }).catch(function(res) {
                     form.clearDirty();
-                    saas.util.BaseUtil.showErrorToast(res.message);
                     form.setLoading(false);
+                    saas.util.BaseUtil.showErrorToast('请求单据编号错误: ' + res.message);
                 })
             }
         }

+ 143 - 0
frontend/saas-web/app/util/ViewUtil.js

@@ -0,0 +1,143 @@
+Ext.define('saas.util.ViewUtil', {
+    statics: {
+
+        // 请求页面组件接口模板
+        getCfgUrl: '/api/ui/customize/getConfig?name={viewName}',
+        // 保存自定义配置
+        saveCfgUrl: '/api/ui/customize/saveConfig',
+        // 模板替换正则
+        urlRe: /(.*){viewName}(.*)/g,
+        // 从表允许自定义的配置名
+        DETAIL_ALLOW_CUS_FIELDS: ['text', 'hidden', 'index', 'width'],
+
+        getViewConfig: function (viewName) {
+            let me = this,
+                url = me.getCfgUrl.replace(me.urlRe, '$1' + viewName);
+            return saas.util.BaseUtil.request({
+                    url,
+                    async: false
+                })
+                .then(function (res) {
+                    if (res.success) {
+                        let cfg = me.decodeViewConfig(res.data);
+                        return cfg;
+                    } else {
+                        return null
+                    }
+                })
+                .catch(function (response) {
+                    saas.util.BaseUtil.showErrorToast('读取自定义配置失败:' + response.message);
+                    return null;
+                });
+        },
+
+        decodeViewConfig: function (cfgs) {
+            cfgs = cfgs || [];
+            let cfg = {};
+            for (let i = 0; i < cfgs.length; i++) {
+                let c = cfgs[i],
+                    pos = c.position;
+                if (pos) {
+                    cfg[pos] = JSON.parse(c.content);
+                }
+            }
+
+            return cfg;
+        },
+
+        applyColumns: function (viewName, columns) {
+            let me = this;
+
+            return me.getViewConfig(viewName)
+            .then(function (cfg) {
+                let newColumns = [];
+
+                newColumns = me.initColumnItems(columns);
+                if (cfg) {
+                    return me.applyColumnConfig(newColumns, cfg['columns']);
+                } else {
+                    return newColumns;
+                }
+            });
+        },
+
+        /**
+         * 为列字段添加可能会被自定义的初始化配置
+         */
+        initColumnItems: function (columns) {
+            columns = Ext.isArray(columns) ? columns : [];
+            let colCount = 1,
+            newColumns = [];
+
+            Ext.Array.each(columns, function (c, j) {
+                let col = Object.assign({}, c);
+                if (col.hidden || col.width == 0 || !col.dataIndex) {
+                    Ext.applyIf(col, {
+                        index: -1,
+                        initHidden: true
+                    });
+                } else {
+                    Ext.applyIf(col, {
+                        text: '',
+                        hidden: false,
+                        index: colCount++,
+                        allowBlank: true,
+                        width: 100,
+                        initHidden: false
+                    });
+                }
+                newColumns.push(col);
+            });
+
+            Ext.Array.sort(newColumns, function (a, b) {
+                return a.index - b.index;
+            });
+
+            return newColumns;
+        },
+
+        /**
+         * 应用自定义配置到列字段
+         */
+        applyColumnConfig: function (columns, cusColumns) {
+            let me = this;
+
+            Ext.Array.each(cusColumns, function (cusCol) {
+                let col = Ext.Array.findBy(columns, function (col) {
+                    return col.dataIndex == cusCol.dataIndex;
+                });
+                if (!!col) {
+                    let keys = Ext.Object.getAllKeys(cusCol);
+                    keys.map(function (k) {
+                        if (me.DETAIL_ALLOW_CUS_FIELDS.indexOf(k) != -1) {
+                            if (k != 'hidden' || col.allowBlank) {
+                                col['_init_' + k] = col.hasOwnProperty('_init_' + k) ? col['_init_' + k] : col[k];
+                                col[k] = cusCol[k];
+                            }
+                        }
+                    });
+
+                }
+            });
+
+            Ext.Array.sort(columns, function (a, b) {
+                return a.index - b.index;
+            });
+
+            return columns;
+        },
+
+        saveViewConfig: function (name, position, content) {
+            let me = this;
+            return saas.util.BaseUtil.request({
+                url: me.saveCfgUrl,
+                params: JSON.stringify({
+                    name: name,
+                    position: position,
+                    content: JSON.stringify(content)
+                }),
+                method: 'POST',
+            });
+        }
+    }
+});

+ 14 - 2
frontend/saas-web/app/view/core/base/BasePanel.js

@@ -39,7 +39,7 @@ Ext.define('saas.view.core.base.BasePanel', {
         gridIdField = gridConfig.idField,
         gridCodeField = gridConfig.codeField,
         gridStatusCodeField = gridConfig.statusCodeField,
-        gridColumns = gridConfig.columns,
+        gridColumns = Ext.Array.clone(gridConfig.columns),
         deleteMsg = gridConfig.deleteMsg;
 
         Ext.apply(me, {
@@ -66,7 +66,9 @@ Ext.define('saas.view.core.base.BasePanel', {
                 dataUrl: gridDataUrl,
                 idField: gridIdField,
                 codeField: gridCodeField,
-                columns: gridColumns,
+                _columns: gridColumns.map(function(c) {
+                    return Object.assign({}, c);
+                }),
                 statusCodeField : gridStatusCodeField,
                 deleteMsg: deleteMsg
             }]
@@ -93,5 +95,15 @@ Ext.define('saas.view.core.base.BasePanel', {
                 });
             });
         }
+    },
+
+    refresh: function () {
+        this.items.items[0].store.load()
+    },
+
+    refreshViewConfig: function() {
+        var me = this;
+        var grid = me.items.items[0];
+        grid.refreshColumns();
     }
 });

+ 107 - 41
frontend/saas-web/app/view/core/base/BasePanelController.js

@@ -8,14 +8,8 @@ Ext.define('saas.view.core.base.BasePanelController', {
     query: function() {
         var form = this.view;
         var grid = form.down('core-base-gridpanel');
-        grid.condition = '';
-        var fields = form.searchField.map(f => f.name);
-        var items = [];
-        Ext.each(fields, function(f, index){
-            var field = form.dockedItems.items[0].down('[name='+f+']');
-            items.push(field);
-        });
-        grid.condition = this.getCondition(items);
+        
+        grid.condition = this.getConditions();
         grid.store.loadPage(1);
     },
 
@@ -25,11 +19,16 @@ Ext.define('saas.view.core.base.BasePanelController', {
         saas.util.BaseUtil.openTab(form.xtype,'新增' + form._title,id);
     },
 
-    getCondition: function(items) {
-        var me = this,
-        conditions = [];
+    /**
+     * 获得过滤条件
+     */
+    getConditions: function() {
+        var me = this;
+        var form = this.view;
+        var items = form.dockedItems.items[0].items.items;
+        var conditions = [];
 
-        for(var i = 0; i < items.length; i++) {
+        for(let i = 0; i < items.length; i++) {
             var item = items[i];
             var field = item.name,
             func = item.getCondition,
@@ -43,10 +42,9 @@ Ext.define('saas.view.core.base.BasePanelController', {
                         value: func(value)
                     }
                 }else {
-                    var xtype = item.xtype || 'textfield',
-                    type = item.fieldType || me.getDefaultFieldType(xtype),
-                    operation = item.operation || me.getDefaultFieldOperation(xtype),
-                    conditionValue = me.getConditionValue(xtype, value);
+                    var type = item.fieldType || me.getDefaultFieldType(item),
+                    operation = item.operation || me.getDefaultFieldOperation(item),
+                    conditionValue = me.getConditionValue(item, value);
         
                     if(!conditionValue) {
                         continue;
@@ -60,38 +58,58 @@ Ext.define('saas.view.core.base.BasePanelController', {
                 }
                 conditions.push(condition);
             }
-        };
+        }
+
         return conditions;
     },
 
-    getDefaultFieldType: function(xtype) {
-        var type;
+    /**
+     * 只要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(Ext.Array.contains(['numberfield'], xtype)) {
+        if (me.isContainsAny(xtypes, ['numberfield'])) {
             type = 'number';
-        }else if(Ext.Array.contains(['datefield', 'condatefield'], xtype)) {
+        } else if (me.isContainsAny(xtypes, ['datefield', 'condatefield', 'conmonthfield'])) {
             type = 'date';
-        }else if(Ext.Array.contains(['combobox', 'multicombo', 'combo', 'radiofield', 'radio'], xtype)) {
+        } else if (me.isContainsAny(xtypes, ['dbfindtrigger'])) {
+            type = 'enum';
+        } else if (me.isContainsAny(xtypes, ['combobox', 'multicombo', 'combo', 'radiofield', 'radio'])) {
             type = 'enum';
-        }else {
+        } else {
             type = 'string';
         }
 
         return type;
     },
 
-    getDefaultFieldOperation: function(xtype) {
-        var operation;
+    getDefaultFieldOperation: function (field) {
+        var me = this,
+            xtypes = field.getXTypes().split('/'),
+            operation;
 
-        if(Ext.Array.contains(['numberfield'], xtype)) {
-            operation = '=';
-        }else if(Ext.Array.contains(['datefield'], xtype)) {
+        if (me.isContainsAny(xtypes, ['numberfield', 'datefield', 'dbfindtrigger'])) {
             operation = '=';
-        }else if(Ext.Array.contains(['condatefield'], xtype)) {
+        } else if (me.isContainsAny(xtypes, ['condatefield', 'conmonthfield'])) {
             operation = 'between';
-        }else if(Ext.Array.contains(['combobox', 'multicombo', 'combo'], xtype)) {
+        } else if (me.isContainsAny(xtypes, ['multidbfindtrigger', 'combobox', 'multicombo', 'combo'])) {
             operation = 'in';
-        }else {
+        } else {
             operation = 'like';
         }
 
@@ -101,25 +119,73 @@ Ext.define('saas.view.core.base.BasePanelController', {
     /**
      * 处理部分字段值
      */
-    getConditionValue: function(xtype, value) {
-        var conditionValue;
-        if(xtype == 'datefield') {
+    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(xtype == 'condatefield') {
+        } 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;
+                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(xtype == 'combobox' || xtype == 'combo') {
+        } else if (me.isContainsAny(xtypes, ['dbfindtrigger'])) {
+            conditionValue = value;
+        } else if (me.isContainsAny(xtypes, ['combobox', 'combo'])) {
             conditionValue = '\'' + value + '\'';
-        }else if(xtype == 'multicombo') {
-            conditionValue = value.map(function(v) {
+        } else if (me.isContainsAny(xtypes, ['multicombo'])) {
+            conditionValue = value.map ? value.map(function (v) {
                 return '\'' + v.value + '\'';
-            }).join(',');
-        }else {
+            }).join(',') : '';
+        } else {
             conditionValue = value;
         }
 
         return conditionValue;
+    },
+
+    onColSetting: function() {
+        var me = this,
+        panel = me.getView(),
+        viewName = panel.viewName,
+        columns = panel.defaultColumns,
+        items = [];
+
+        for(let i = 0; i < columns.length; i++) {
+            let col = columns[i];
+            if(!col.initHidden) {
+                items.push(Object.assign({}, col));
+            }
+        }
+
+        me.openSettingWindow(viewName, items, 'columns');
+    },
+
+    openSettingWindow: function(viewName, items, settype) {
+        var panel = saas.util.BaseUtil.getCurrentTab(),
+        box = panel.getBox(),
+        refs = panel.getReferences() || {},
+        win = refs.settingwin;
+
+        title = '列设置';
+
+        if(!win) {
+            win = panel.add({
+                title: title,
+                xtype: 'settingwin',
+                width: box.width * 0.8,
+                height: box.height * 0.8,
+                viewName: viewName,
+                fieldItems: Ext.Array.clone(items),
+                settype: settype,
+            });
+        }
+        win.show();
     }
 });

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff