门禁码
diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/oauth.kt b/oauth/src/main/kotlin/com/supwisdom/oauth/oauth.kt
index 8fb70be..79acca5 100644
--- a/oauth/src/main/kotlin/com/supwisdom/oauth/oauth.kt
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/oauth.kt
@@ -62,7 +62,7 @@
     @GetMapping("/login")
     fun loginView() = "login"
 
-    @GetMapping("/index")
+    @GetMapping(value = ["/", "/index"])
     fun indexView() = "index"
 }
 
diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/security.kt b/oauth/src/main/kotlin/com/supwisdom/oauth/security.kt
index 0fdf884..191178b 100644
--- a/oauth/src/main/kotlin/com/supwisdom/oauth/security.kt
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/security.kt
@@ -46,6 +46,7 @@
             }
             user.lastlogin = DateUtil.getNow()
             oAuthUserService.saveUser(user)
+            defaultTargetUrl = "/index"
             super.onAuthenticationSuccess(request, response, authentication)
         } else {
             throw UserLoginFailException("登录错误")
@@ -131,7 +132,7 @@
                 // 设置 Web MVC 应用权限
                 http.authorizeRequests()
                         .antMatchers("/login", "/login/form", "/userinfor").permitAll()
-                        .antMatchers("/static/**").permitAll()
+                        .antMatchers("/css/**").permitAll()
                         .antMatchers("/code/image").permitAll()
                         .antMatchers("/**").hasAnyRole("USER", "ADMIN")
                         .anyRequest().authenticated()
diff --git a/oauth/src/main/resources/templates/index.html b/oauth/src/main/resources/templates/index.html
new file mode 100644
index 0000000..6efd74e
--- /dev/null
+++ b/oauth/src/main/resources/templates/index.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+
+<html xmlns:th="http://www.thymeleaf.org">
+<head>
+    <title>用户中心</title>
+    <meta name="_csrf" th:content="${_csrf.token}"/>
+    <!-- default header name is X-CSRF-TOKEN -->
+    <meta name="_csrf_header" th:content="${_csrf.headerName}"/>
+    <link rel="stylesheet" type="text/css" th:href="@{/css/weui.min.css}"/>
+    <link rel="stylesheet" type="text/css" th:href="@{/css/jquery-weui.css}"/>
+</head>
+<body>
+用户中心
+</body>
+</html>
\ No newline at end of file
diff --git a/oauth/src/main/resources/templates/login.html b/oauth/src/main/resources/templates/login.html
index f3ef3d6..c6660ad 100644
--- a/oauth/src/main/resources/templates/login.html
+++ b/oauth/src/main/resources/templates/login.html
@@ -6,8 +6,8 @@
     <meta name="_csrf" th:content="${_csrf.token}"/>
     <!-- default header name is X-CSRF-TOKEN -->
     <meta name="_csrf_header" th:content="${_csrf.headerName}"/>
-    <link rel="stylesheet" type="text/css" th:href="@{/static/css/weui.min.css}"/>
-    <link rel="stylesheet" type="text/css" th:href="@{/static/css/jquery-weui.css}"/>
+    <link rel="stylesheet" type="text/css" th:href="@{/css/weui.min.css}"/>
+    <link rel="stylesheet" type="text/css" th:href="@{/css/jquery-weui.css}"/>
 </head>
 <body>
 <p style="color:red;padding: 0 0 10px 0;"
@@ -18,13 +18,13 @@
            th:name="${_csrf.parameterName}"
            th:value="${_csrf.token}"/>
     <div class="weui-cells__title">统一身份认证</div>
-    <div class="weui-cells" style="margin-top:30%;">
+    <div class="weui-cells" >
         <div class="weui-cell">
             <div class="weui-cell__hd">
                 <label class="weui-label">账号</label>
             </div>
             <div class="weui-cell__bd">
-                <input class="weui-input" type="text" id="username" name="username" placeholder="学工号、手机号、邮箱">
+                <input class="weui-input" type="text" id="username" name="username" placeholder="学工号\手机号\邮箱">
             </div>
         </div>
         <div class="weui-cell">
@@ -37,7 +37,7 @@
         </div>
 
     </div>
-    <button type="submit" class="weui-btn weui-btn_plain-primary">登 录</button>
+    <button type="submit" class="weui-btn weui-btn_plain-primary" style="margin:30px;">登 录</button>
 </form>
 </body>
 </html>
\ No newline at end of file
diff --git a/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/DoorQRCodeParam.java b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/DoorQRCodeParam.java
new file mode 100644
index 0000000..e047a7a
--- /dev/null
+++ b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/DoorQRCodeParam.java
@@ -0,0 +1,23 @@
+package com.supwisdom.dlpay.api.bean;
+
+
+import com.supwisdom.dlpay.api.APIRequestParam;
+import com.supwisdom.dlpay.api.annotation.Sign;
+import com.supwisdom.dlpay.api.exception.RequestParamCheckException;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotNull;
+
+@Getter
+@Setter
+public class DoorQRCodeParam extends APIRequestParam {
+  @Sign
+  @NotNull(message = "二维码不能为空")
+  private String qrcode;
+
+  @Override
+  public boolean checkParam() throws RequestParamCheckException {
+    return true;
+  }
+}
diff --git a/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/DoorQrcodeResponse.java b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/DoorQrcodeResponse.java
new file mode 100644
index 0000000..dd77797
--- /dev/null
+++ b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/DoorQrcodeResponse.java
@@ -0,0 +1,17 @@
+package com.supwisdom.dlpay.api.bean;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class DoorQrcodeResponse extends ApiResponse {
+  private String userid;
+  private String citycardno;
+  private String bankcardno;
+  private String status;
+}
diff --git a/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/ConsumePropxy.java b/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/ConsumePropxy.java
index b5e1f3b..62584f7 100644
--- a/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/ConsumePropxy.java
+++ b/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/ConsumePropxy.java
@@ -22,4 +22,6 @@
     @PostMapping("/thirdpay/finish")
     ThirdPayResponse thirdpayFinish(@RequestBody ThirdPayfinishParam param);
 
+    @PostMapping("/qrcodequery")
+    DoorQrcodeResponse qrcodequery(@RequestBody DoorQRCodeParam param);
 }
diff --git a/payapi-sdk/src/test/java/com/supwisdom/dlpay/paysdktest/CitizenCardPayProxyTest.java b/payapi-sdk/src/test/java/com/supwisdom/dlpay/paysdktest/CitizenCardPayProxyTest.java
index 36705fe..8229dcf 100644
--- a/payapi-sdk/src/test/java/com/supwisdom/dlpay/paysdktest/CitizenCardPayProxyTest.java
+++ b/payapi-sdk/src/test/java/com/supwisdom/dlpay/paysdktest/CitizenCardPayProxyTest.java
@@ -173,6 +173,17 @@
     assertThat("qrcodeConfirm:" + response.getRetmsg() + response.getException()+",query="+response.isRequireQuery(),
             response.getRetcode(), equalTo(0));
   }
+  @Test
+  public void qrcodeQuery(){
+    ApiLoginHelper helper = new ApiLoginHelper(apiLoginProxy);
+    helper.login(appid, secret);
+    DoorQRCodeParam param = new DoorQRCodeParam();
+    param.setQrcode("6M4I1KGUO_UOVJVYCW52FQ");
+
+    DoorQrcodeResponse response = consumePropxy.qrcodequery(param);
+    assertThat("qrcode query " + response.getRetmsg() + response.getException(),
+            response.getRetcode(), equalTo(0));
+  }
 
   @Test
   public void thirdpay(){
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/framework/util/MD5.java b/payapi/src/main/java/com/supwisdom/dlpay/framework/util/MD5.java
index 91abab4..bcf4445 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/framework/util/MD5.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/framework/util/MD5.java
@@ -1,5 +1,7 @@
 package com.supwisdom.dlpay.framework.util;
 
+import org.apache.commons.codec.binary.Base64;
+
 import java.security.MessageDigest;
 
 public class MD5 {
@@ -83,5 +85,21 @@
         }
         return null;
     }
-
+    /** 对字符串进行MD5加密 */
+    public static String encodeByMD5ToURLSafeBase64(String originString) {
+        if (originString != null) {
+            try {
+                // 创建具有指定算法名称的信息摘要
+                MessageDigest md = MessageDigest.getInstance("MD5");
+                // 使用指定的字节数组对摘要进行最后更新,然后完成摘要计算
+                byte[] results = md.digest(originString.getBytes("utf-8"));
+                // 将得到的字节数组变成字符串返回
+                String resultString = Base64.encodeBase64URLSafeString(results);
+                return resultString.toUpperCase();
+            } catch (Exception ex) {
+                ex.printStackTrace();
+            }
+        }
+        return null;
+    }
 }
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/framework/util/RandomUtils.java b/payapi/src/main/java/com/supwisdom/dlpay/framework/util/RandomUtils.java
index 0afac38..74a0534 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/framework/util/RandomUtils.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/framework/util/RandomUtils.java
@@ -64,8 +64,15 @@
     	String result = s + "_"+savesecret;
 		return result;
     }
-    
-	
+
+	public static String getSecureRandomHex() {
+		SecureRandom a = new SecureRandom();
+		byte[] phonesecret = new byte[10];
+		a.nextBytes(phonesecret);
+
+		String savesecret = Hex.encodeHexString(phonesecret);
+		return savesecret;
+	}
 	/**
 	 * 
 	 * @param length
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/util/AesUtil.java b/payapi/src/main/java/com/supwisdom/dlpay/util/AesUtil.java
new file mode 100644
index 0000000..ddd8f91
--- /dev/null
+++ b/payapi/src/main/java/com/supwisdom/dlpay/util/AesUtil.java
@@ -0,0 +1,162 @@
+package com.supwisdom.dlpay.util;
+
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.lang.StringUtils;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.UnsupportedEncodingException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+public class AesUtil {
+
+	public static String encrypt(String rawdata, String secret) {
+		Cipher cipher;
+		try {
+			cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
+			SecretKeySpec key = new SecretKeySpec(secret.getBytes("UTF-8"), "AES");
+			cipher.init(Cipher.ENCRYPT_MODE, key);
+			byte[] encdata = cipher.doFinal(rawdata.getBytes("UTF-8"));
+			String hexdata = Base64.encodeBase64String(encdata);
+			
+			return hexdata;
+		} catch (NoSuchAlgorithmException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (NoSuchPaddingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (UnsupportedEncodingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (InvalidKeyException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (IllegalBlockSizeException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (BadPaddingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		return null;
+		
+	}
+	public static String encryptCFB(String rawdata, String secret ,String ivstr,String KEY_ALG_MODE) {
+		Cipher cipher;
+		try {
+			//byte[] iv = new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF };
+			byte[] iv = Hex.decodeHex(ivstr.toCharArray());
+			IvParameterSpec ivSpec = new IvParameterSpec(iv);
+			cipher = Cipher.getInstance(KEY_ALG_MODE);
+			SecretKeySpec key = new SecretKeySpec(Base64.decodeBase64(secret), "AES");
+			cipher.init(Cipher.ENCRYPT_MODE, key,ivSpec);
+			byte[] encdata = cipher.doFinal(rawdata.getBytes("UTF-8"));
+			String hexdata = Base64.encodeBase64String(encdata);
+
+			return hexdata;
+		} catch (NoSuchAlgorithmException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (NoSuchPaddingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (UnsupportedEncodingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (InvalidKeyException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (IllegalBlockSizeException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (BadPaddingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (InvalidAlgorithmParameterException e) {
+			e.printStackTrace();
+		} catch (DecoderException e) {
+			e.printStackTrace();
+		}
+		return null;
+
+	}
+	
+	public static String decrypt(String encdata, String secret){
+		Cipher cipher;
+		try {
+			cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
+			SecretKeySpec key = new SecretKeySpec(secret.getBytes("UTF-8"), "AES");
+			cipher.init(Cipher.DECRYPT_MODE, key);
+			byte[] decordedValue = Base64.decodeBase64(encdata);
+			byte[] decdata = cipher.doFinal(decordedValue);
+			String rawdata = new String(decdata);
+			
+			return rawdata;
+		} catch (NoSuchAlgorithmException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (NoSuchPaddingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (UnsupportedEncodingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (InvalidKeyException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (IllegalBlockSizeException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (BadPaddingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		return null;
+	}
+
+	public static String decryptCFB(String encdata, String secret,String ivstr,String KEY_ALG_MODE){
+		Cipher cipher;
+		try {
+//			byte[] iv = new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF };
+			byte[] iv = Hex.decodeHex(ivstr.toCharArray());
+			IvParameterSpec ivSpec = new IvParameterSpec(iv);
+			cipher = Cipher.getInstance(KEY_ALG_MODE);
+			SecretKeySpec key = new SecretKeySpec(Base64.decodeBase64(secret), "AES");
+			cipher.init(Cipher.DECRYPT_MODE, key,ivSpec);
+			byte[] decordedValue = Base64.decodeBase64(encdata);
+			byte[] decdata = cipher.doFinal(decordedValue);
+			String rawdata = new String(decdata);
+
+			return rawdata;
+		} catch (NoSuchAlgorithmException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (NoSuchPaddingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (InvalidKeyException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (IllegalBlockSizeException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (BadPaddingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (InvalidAlgorithmParameterException e) {
+			e.printStackTrace();
+		} catch (DecoderException e) {
+			e.printStackTrace();
+		}
+		return null;
+	}
+}
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/util/QrCodeTotpUtil.java b/payapi/src/main/java/com/supwisdom/dlpay/util/QrCodeTotpUtil.java
new file mode 100644
index 0000000..fa51bb3
--- /dev/null
+++ b/payapi/src/main/java/com/supwisdom/dlpay/util/QrCodeTotpUtil.java
@@ -0,0 +1,59 @@
+package com.supwisdom.dlpay.util;
+
+public class QrCodeTotpUtil {
+    public static String generateTOTP(String seed){
+        long X = 30;
+        long T0 = 0;
+        String steps = "0";
+        long time = System.currentTimeMillis() / 1000;
+        long T = (time - T0) / X;
+        steps = Long.toHexString(T).toUpperCase();
+        while (steps.length() < 16) {
+            steps = "0" + steps;
+        }
+        String totp = TOTP.generateTOTP(seed, steps, "8", "HmacSHA256");
+        return totp;
+    }
+
+    public static String generateTOTP(String seed,String returnDigits){
+        long X = 30;
+        long T0 = 0;
+        String steps = "0";
+        long time = System.currentTimeMillis() / 1000;
+        long T = (time - T0) / X;
+        steps = Long.toHexString(T).toUpperCase();
+        while (steps.length() < 16) {
+            steps = "0" + steps;
+        }
+        String totp = TOTP.generateTOTP(seed, steps, returnDigits, "HmacSHA256");
+        return totp;
+    }
+
+    public static boolean verifyCode(String totp, String secret, int offset) {
+
+        String second = "30";
+        long T0 = 0;
+        String[] keys = new String[offset * 2 + 1];
+        long time = System.currentTimeMillis() / 1000;
+        for (int i = 0; i < keys.length; i++) {
+            String steps = "0";
+            try {
+                long T = (time - T0) / Long.parseLong(second);
+                steps = Long.toHexString(T + (i - offset)).toUpperCase();
+                while (steps.length() < 16) {
+                    steps = "0" + steps;
+                }
+                String key = TOTP.generateTOTP(secret, steps, "8", "HmacSHA256");
+                keys[i] = key;
+            } catch (final Exception e) {
+                System.out.println("Error : " + e);
+            }
+        }
+        for (String key : keys) {
+            if (key.equals(totp)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/util/QrcodeRawData.java b/payapi/src/main/java/com/supwisdom/dlpay/util/QrcodeRawData.java
new file mode 100644
index 0000000..44ac288
--- /dev/null
+++ b/payapi/src/main/java/com/supwisdom/dlpay/util/QrcodeRawData.java
@@ -0,0 +1,22 @@
+package com.supwisdom.dlpay.util;
+
+public class QrcodeRawData {
+  private String totp;
+  private String userid;
+
+  public String getTotp() {
+    return totp;
+  }
+
+  public void setTotp(String totp) {
+    this.totp = totp;
+  }
+
+  public String getUserid() {
+    return userid;
+  }
+
+  public void setUserid(String userid) {
+    this.userid = userid;
+  }
+}
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/util/RSAKeysGenerate.java b/payapi/src/main/java/com/supwisdom/dlpay/util/RSAKeysGenerate.java
new file mode 100644
index 0000000..4af2148
--- /dev/null
+++ b/payapi/src/main/java/com/supwisdom/dlpay/util/RSAKeysGenerate.java
@@ -0,0 +1,179 @@
+package com.supwisdom.dlpay.util;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang.StringUtils;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import javax.crypto.Cipher;
+import java.io.ByteArrayOutputStream;
+import java.security.*;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.HashMap;
+import java.util.Map;
+
+public class RSAKeysGenerate {
+
+  public static final String KEY_ALGORITHM = "RSA";
+  private static final String PUBLIC_KEY = "RSAPublicKey";
+  private static final String PRIVATE_KEY = "RSAPrivateKey";
+  private static final String KEY_ALG_MODE = "RSA/ECB/PKCS1Padding";
+  /** 密钥大小 */
+  private static final int KEY_SIZE = 1024;
+  /** *//**
+   * RSA最大加密明文大小
+   */
+  private static final int MAX_ENCRYPT_BLOCK = 117;
+
+  /** *//**
+   * RSA最大解密密文大小
+   */
+  private static final int MAX_DECRYPT_BLOCK = 128;
+  /** 默认的安全服务提供者 */
+  private static final Provider DEFAULT_PROVIDER = new BouncyCastleProvider();
+  public static void main(String[] args) {
+    Map<String, Object> keyMap;
+    try {
+      keyMap = initKey();
+      String publicKey = getPublicKey(keyMap);
+      System.out.println(publicKey);
+      String privateKey = getPrivateKey(keyMap);
+      System.out.println(privateKey);
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  public static String getPublicKey(Map<String, Object> keyMap) throws Exception {
+    Key key = (Key) keyMap.get(PUBLIC_KEY);
+    byte[] publicKey = key.getEncoded();
+    return encryptBASE64(key.getEncoded());
+  }
+
+  public static String getPrivateKey(Map<String, Object> keyMap) throws Exception {
+    Key key = (Key) keyMap.get(PRIVATE_KEY);
+    byte[] privateKey = key.getEncoded();
+    return encryptBASE64(key.getEncoded());
+  }
+
+
+  public static String encryptBASE64(byte[] key) throws Exception {
+    return Base64.encodeBase64String(key);
+  }
+
+  public static Map<String, Object> initKey() throws Exception {
+    KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
+    keyPairGen.initialize(1024);
+    KeyPair keyPair = keyPairGen.generateKeyPair();
+    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
+    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
+    Map<String, Object> keyMap = new HashMap<String, Object>(2);
+    keyMap.put(PUBLIC_KEY, publicKey);
+    keyMap.put(PRIVATE_KEY, privateKey);
+    return keyMap;
+  }
+
+  public static PrivateKey getPrivateKey(final String key) throws Exception {
+    byte[] keyBytes = null;
+    keyBytes = com.sun.jersey.core.util.Base64.decode(key);
+    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
+    KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
+    PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
+    return privateKey;
+  }
+  public static PublicKey getPublicKey(final String key) throws Exception {
+    byte[] keyBytes;
+    keyBytes = com.sun.jersey.core.util.Base64.decode(key);
+    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
+    KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
+    PublicKey publicKey = keyFactory.generatePublic(keySpec);
+    return publicKey;
+  }
+
+  public static byte[] decrypt(PrivateKey privateKey, byte[] data) throws Exception {
+    Cipher cipher = Cipher.getInstance(KEY_ALG_MODE, DEFAULT_PROVIDER);
+    cipher.init(Cipher.DECRYPT_MODE, privateKey);
+
+    int inputLen = data.length;
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    int offSet = 0;
+    byte[] cache;
+    int i = 0;
+    // 对数据分段解密
+    while (inputLen - offSet > 0) {
+      if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
+        cache = cipher.doFinal(data, offSet, MAX_DECRYPT_BLOCK);
+      } else {
+        cache = cipher.doFinal(data, offSet, inputLen - offSet);
+      }
+      out.write(cache, 0, cache.length);
+      i++;
+      offSet = i * MAX_DECRYPT_BLOCK;
+    }
+    byte[] decryptedData = out.toByteArray();
+    out.close();
+    return decryptedData;
+
+  }
+  public static String decryptbyte(PrivateKey privateKey, String encrypttext) {
+    if (privateKey == null || StringUtils.isBlank(encrypttext)) {
+      return null;
+    }
+    try {
+      byte[] en_data = org.apache.commons.codec.binary.Base64.decodeBase64(encrypttext);
+
+
+      byte[] data = decrypt(privateKey, en_data);
+      return new String(data);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+  /**
+   * 使用指定的公钥加密数据。
+   *
+   * @param publicKey 给定的公钥。
+   * @param data 要加密的数据。
+   * @return 加密后的数据。
+   */
+  public static byte[] encrypt(PublicKey publicKey, byte[] data) throws Exception {
+    Cipher cipher = Cipher.getInstance(KEY_ALG_MODE, DEFAULT_PROVIDER);
+    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+
+    int inputLen = data.length;
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    int offSet = 0;
+    byte[] cache;
+    int i = 0;
+    // 对数据分段加密
+    while (inputLen - offSet > 0) {
+      if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
+        cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
+      } else {
+        cache = cipher.doFinal(data, offSet, inputLen - offSet);
+      }
+      out.write(cache, 0, cache.length);
+      i++;
+      offSet = i * MAX_ENCRYPT_BLOCK;
+    }
+    byte[] encryptedData = out.toByteArray();
+    out.close();
+    return encryptedData;
+  }
+  public static byte[] encryptbyte(PublicKey publicKey, String plaintext) {
+    if (publicKey == null || plaintext == null) {
+      return null;
+    }
+    byte[] data = plaintext.getBytes();
+    try {
+      byte[] en_data = encrypt(publicKey, data);
+      return en_data;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+}
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/util/TOTP.java b/payapi/src/main/java/com/supwisdom/dlpay/util/TOTP.java
new file mode 100644
index 0000000..bcab4d3
--- /dev/null
+++ b/payapi/src/main/java/com/supwisdom/dlpay/util/TOTP.java
@@ -0,0 +1,264 @@
+package com.supwisdom.dlpay.util;
+
+/**
+ Copyright (c) 2011 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, is permitted pursuant to, and subject to the license
+ terms contained in, the Simplified BSD License set forth in Section
+ 4.c of the IETF Trust's Legal Provisions Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+ */
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+
+/**
+ * This is an example implementation of the OATH TOTP algorithm. Visit
+ * www.openauthentication.org for more information.
+ * 
+ * @author Johan Rydell, PortWise, Inc.
+ */
+public class TOTP {
+	private static final int[] DIGITS_POWER
+	// 0 1 2 3 4 5 6 7 8
+	= { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };
+
+	private TOTP() {
+	}
+
+	/**
+	 * This method uses the JCE to provide the crypto algorithm. HMAC computes a
+	 * Hashed Message Authentication Code with the crypto hash algorithm as a
+	 * parameter.
+	 * 
+	 * @param crypto
+	 *            : the crypto algorithm (HmacSHA1, HmacSHA256, HmacSHA512)
+	 * @param keyBytes
+	 *            : the bytes to use for the HMAC key
+	 * @param text
+	 *            : the message or text to be authenticated
+	 */
+	private static byte[] hmac_sha(String crypto, byte[] keyBytes, byte[] text) {
+		try {
+			Mac hmac;
+			hmac = Mac.getInstance(crypto);
+
+			SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
+			hmac.init(macKey);
+
+			return hmac.doFinal(text);
+		} catch (GeneralSecurityException gse) {
+			throw new UndeclaredThrowableException(gse);
+		}
+	}
+
+	/**
+	 * This method converts a HEX string to Byte[]
+	 * 
+	 * @param hex
+	 *            : the HEX string
+	 * 
+	 * @return: a byte array
+	 */
+	private static byte[] hexStr2Bytes(String hex) {
+		// Adding one byte to get the right conversion
+		// Values starting with "0" can be converted
+		byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();
+
+		// Copy all the REAL bytes, not the "first"
+		byte[] ret = new byte[bArray.length - 1];
+
+		for (int i = 0; i < ret.length; i++)
+			ret[i] = bArray[i + 1];
+
+		return ret;
+	}
+
+	/**
+	 * This method generates a TOTP value for the given set of parameters.
+	 * 
+	 * @param key
+	 *            : the shared secret, HEX encoded
+	 * @param time
+	 *            : a value that reflects a time
+	 * @param returnDigits
+	 *            : number of digits to return
+	 * 
+	 * @return: a numeric String in base 10 that includes
+	 *          {@link truncationDigits} digits
+	 */
+	public static String generateTOTP(String key, String time,
+			String returnDigits) {
+		return generateTOTP(key, time, returnDigits, "HmacSHA1");
+	}
+
+	/**
+	 * This method generates a TOTP value for the given set of parameters.
+	 * 
+	 * @param key
+	 *            : the shared secret, HEX encoded
+	 * @param time
+	 *            : a value that reflects a time
+	 * @param returnDigits
+	 *            : number of digits to return
+	 * 
+	 * @return: a numeric String in base 10 that includes
+	 *          {@link truncationDigits} digits
+	 */
+	public static String generateTOTP256(String key, String time,
+			String returnDigits) {
+		return generateTOTP(key, time, returnDigits, "HmacSHA256");
+	}
+
+	/**
+	 * This method generates a TOTP value for the given set of parameters.
+	 * 
+	 * @param key
+	 *            : the shared secret, HEX encoded
+	 * @param time
+	 *            : a value that reflects a time
+	 * @param returnDigits
+	 *            : number of digits to return
+	 * 
+	 * @return: a numeric String in base 10 that includes
+	 *          {@link truncationDigits} digits
+	 */
+	public static String generateTOTP512(String key, String time,
+			String returnDigits) {
+		return generateTOTP(key, time, returnDigits, "HmacSHA512");
+	}
+
+	/**
+	 * This method generates a TOTP value for the given set of parameters.
+	 * 
+	 * @param key
+	 *            : the shared secret, HEX encoded
+	 * @param time
+	 *            : a value that reflects a time
+	 * @param returnDigits
+	 *            : number of digits to return
+	 * @param crypto
+	 *            : the crypto function to use
+	 * 
+	 * @return: a numeric String in base 10 that includes
+	 *          {@link truncationDigits} digits
+	 */
+	public static String generateTOTP(String key, String time,
+			String returnDigits, String crypto) {
+		int codeDigits = Integer.decode(returnDigits).intValue();
+		String result = null;
+
+		// Using the counter
+		// First 8 bytes are for the movingFactor
+		// Compliant with base RFC 4226 (HOTP)
+		while (time.length() < 16)
+			time = "0" + time;
+
+		// Get the HEX in a Byte[]
+		byte[] msg = hexStr2Bytes(time);
+		byte[] k = hexStr2Bytes(key);
+		byte[] hash = hmac_sha(crypto, k, msg);
+
+		// put selected bytes into result int
+		int offset = hash[hash.length - 1] & 0xf;
+
+		int binary = ((hash[offset] & 0x7f) << 24)
+				| ((hash[offset + 1] & 0xff) << 16)
+				| ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
+
+		int otp = binary % DIGITS_POWER[codeDigits];
+
+		result = Integer.toString(otp);
+
+		while (result.length() < codeDigits) {
+			result = "0" + result;
+		}
+
+		return result;
+	}
+
+	public static void main(String[] args) {
+		long X = 30;
+		long T0 = 0;
+		String steps = "0";
+		long time = System.currentTimeMillis() / 1000;
+		System.out.println(time);
+		long T = (time - T0) / X;
+		steps = Long.toHexString(T).toUpperCase();
+		while (steps.length() < 16) {
+			steps = "0" + steps;
+		}
+		String seed = "9e8bb3d2df73741c041aef39e37ee015fc98233720a1350fcd67d5c3027896ac";
+		String totp = TOTP.generateTOTP(seed, steps, "8", "HmacSHA256");
+		System.out.println(totp);
+
+		/*
+
+
+		SecureRandom a = new SecureRandom();
+		byte[] b = a.generateSeed(8);
+		// Seed for HMAC-SHA1 - 20 bytes
+		String seed = "3132333435363738393031323334353637383930";
+
+		// Seed for HMAC-SHA256 - 32 bytes
+		//String seed32 = "3132333435363738393031323334353637383930313233343536373839303132";
+		String seed32 = "e5b72370f61687c8075b8383266f4fbcbb3b55da178eed76329666600c134093";
+
+		// Seed for HMAC-SHA512 - 64 bytes
+		String seed64 = "3132333435363738393031323334353637383930"
+				+ "3132333435363738393031323334353637383930"
+				+ "3132333435363738393031323334353637383930" + "31323334";
+		long T0 = 0;
+		long X = 30;
+		long[] testTime = { 59L, 1111111109L, 1111111111L, 1234567890L,
+				2000000000L, 20000000000L };
+
+		String steps = "0";
+		DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+		df.setTimeZone(TimeZone.getTimeZone("UTC"));
+
+		try {
+			System.out.println("+---------------+-----------------------+"
+					+ "------------------+--------+--------+");
+			System.out.println("|  Time(sec)    |   Time (UTC format)   "
+					+ "| Value of T(Hex)  |  TOTP  | Mode   |");
+			System.out.println("+---------------+-----------------------+"
+					+ "------------------+--------+--------+");
+
+			for (int i = 0; i < testTime.length; i++) {
+				long T = (testTime[i] - T0) / X;
+				steps = Long.toHexString(T).toUpperCase();
+
+				while (steps.length() < 16)
+					steps = "0" + steps;
+
+				String fmtTime = String.format("%1$-11s", testTime[i]);
+				String utcTime = df.format(new Date(testTime[i] * 1000));
+				System.out.print("|  " + fmtTime + "  |  " + utcTime + "  | "
+						+ steps + " |");
+				System.out.println(generateTOTP(seed, steps, "8", "HmacSHA1")
+						+ "| SHA1   |");
+				System.out.print("|  " + fmtTime + "  |  " + utcTime + "  | "
+						+ steps + " |");
+				System.out.println(generateTOTP(seed32, steps, "8",
+						"HmacSHA256") + "| SHA256 |");
+				System.out.print("|  " + fmtTime + "  |  " + utcTime + "  | "
+						+ steps + " |");
+				System.out.println(generateTOTP(seed64, steps, "8",
+						"HmacSHA512") + "| SHA512 |");
+
+				System.out.println("+---------------+-----------------------+"
+						+ "------------------+--------+--------+");
+			}
+		} catch (final Exception e) {
+			System.out.println("Error : " + e);
+		}
+
+		*/
+	}
+}
\ No newline at end of file
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt
index 8b7b796..d993aea 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt
@@ -88,8 +88,8 @@
     fun registerMobileFilter(): FilterRegistrationBean<MobileSecurityFilter> {
         val registrationBean = FilterRegistrationBean<MobileSecurityFilter>()
         registrationBean.filter = mobileSecurityFilter
-        registrationBean.order = 2
         registrationBean.addUrlPatterns("/mobile/*")
+        registrationBean.order = 2
         return registrationBean
     }
 }
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt
index 7c35683..cfdd7ec 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt
@@ -18,6 +18,7 @@
 import org.apache.commons.lang3.StringUtils
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.context.ApplicationContext
+import org.springframework.data.redis.core.RedisTemplate
 import org.springframework.http.ResponseEntity
 import org.springframework.validation.annotation.Validated
 import org.springframework.web.bind.annotation.*
@@ -50,6 +51,12 @@
     @Autowired
     private lateinit var agentServiceProxy: AgentServiceProxy
 
+    @Autowired
+    lateinit var redisTemplate: RedisTemplate<String, String>
+
+    @Autowired
+    private lateinit var qrCodeService: QRCodeService
+
     /**
      * ============================================================================
      * 消费流水结果查询统一接口
@@ -754,4 +761,26 @@
                 }
         }
     }
+
+    /**
+     * ============================================================================
+     *                            门禁码查询接口
+     * ============================================================================
+     * */
+    @PostMapping("/qrcodequery")
+    fun qrcodeQuery(@RequestBody param: DoorQRCodeParam): ResponseEntity<Any> {
+        var token = redisTemplate.opsForValue().get(param.qrcode)
+        if(token.isNullOrEmpty()){
+            return ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .fail(1, "二维码不存在或已失效"))
+        }
+        val ret = qrCodeService.decodeCode(token)
+        return if(ret.retcode==0){
+            ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .success(ret, "成功"))
+        }else{
+            ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .fail(ret.retcode, ret.retmsg))
+        }
+    }
 }
\ No newline at end of file
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/qrcode_srvice_impl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/qrcode_srvice_impl.kt
new file mode 100644
index 0000000..95a1de0
--- /dev/null
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/qrcode_srvice_impl.kt
@@ -0,0 +1,155 @@
+package com.supwisdom.dlpay.api.service.impl
+
+import com.google.gson.Gson
+import com.supwisdom.dlpay.api.bean.ApiResponse
+import com.supwisdom.dlpay.api.bean.DoorQrcodeResponse
+import com.supwisdom.dlpay.api.service.QRCodeService
+import com.supwisdom.dlpay.api.service.UserService
+import com.supwisdom.dlpay.framework.service.SystemUtilService
+import com.supwisdom.dlpay.framework.util.MD5
+import com.supwisdom.dlpay.framework.util.TradeDict
+import com.supwisdom.dlpay.mobile.service.MobileApiService
+import com.supwisdom.dlpay.util.AesUtil
+import com.supwisdom.dlpay.util.QrCodeTotpUtil
+import com.supwisdom.dlpay.util.QrcodeRawData
+import com.supwisdom.dlpay.util.RSAKeysGenerate
+import mu.KotlinLogging
+import org.apache.commons.lang.math.NumberUtils
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.data.redis.core.RedisTemplate
+import org.springframework.stereotype.Service
+import java.time.Duration
+
+@Service
+class QRCodeServiceImpl:QRCodeService{
+    @Autowired
+    private lateinit var systemUtilService: SystemUtilService
+    @Autowired
+    private lateinit var mobileApiService: MobileApiService
+    @Autowired
+    lateinit var userService: UserService
+    @Autowired
+    lateinit var redisTemplate: RedisTemplate<String, String>
+
+    val logger = KotlinLogging.logger { }
+
+    override fun encodeCode(uid:String): ApiResponse {
+        var resp = ApiResponse()
+        val rootkey = systemUtilService.getBusinessValue("aes.cfb.rootkey")
+        val iv = systemUtilService.getBusinessValue("aes.cfb.iv")
+        if (rootkey.isNullOrEmpty()||iv.isNullOrEmpty()) {
+            resp.retcode = 1
+            resp.retmsg = "秘钥未配置"
+            return resp
+        }
+        val muser = mobileApiService.findUserById(uid)
+        if(muser==null||TradeDict.STATUS_NORMAL!=muser.status){
+            resp.retcode = 1
+            resp.retmsg = "用户不存在或状态异常"
+            return resp
+        }
+        if(muser.userid.isNullOrEmpty()){
+            resp.retcode = 1
+            resp.retmsg = "用户未绑定身份"
+            return resp
+        }
+        val totp = QrCodeTotpUtil.generateTOTP(muser.secertkey)
+        val rowdata = QrcodeRawData()
+        rowdata.userid = muser.userid
+        rowdata.setTotp(totp)
+        val orgData = Gson().toJson(rowdata)
+        val publicKey = RSAKeysGenerate.getPublicKey(muser.rsapublic)
+        val encdata = org.apache.commons.codec.binary.Base64.encodeBase64String(RSAKeysGenerate.encryptbyte(publicKey, orgData))
+        val qrcode = AesUtil.encryptCFB("$uid:$encdata", rootkey, iv, "AES/CFB/NoPadding")
+
+        val key = MD5.encodeByMD5ToURLSafeBase64(qrcode)
+        redisTemplate.opsForValue().set(key,qrcode, Duration.ofMinutes(3))
+        resp.retcode = 0
+        resp.retmsg = key
+        return resp
+    }
+
+    override fun decodeCode(qrcode :String): DoorQrcodeResponse {
+
+        //解密
+        var resp = DoorQrcodeResponse()
+        try {
+            val rootkey = systemUtilService.getBusinessValue("aes.cfb.rootkey")
+            val iv = systemUtilService.getBusinessValue("aes.cfb.iv")
+            val totpoffset = systemUtilService.getBusinessValue("aes.cfb.totp.offset")
+            var offset = 20
+            if (NumberUtils.isDigits(totpoffset)) {
+                offset = Integer.valueOf(totpoffset).toInt()
+            }
+            if (rootkey.isNullOrEmpty()||iv.isNullOrEmpty()) {
+                resp.retcode = 1
+                resp.retmsg = "秘钥未配置"
+                return resp
+            }
+
+            println("vtoken=[" + qrcode + "],length=[" + qrcode.length + "]")
+            val encdataBack = AesUtil.decryptCFB(qrcode, rootkey, iv, "AES/CFB/NoPadding")
+            if (encdataBack == null) {
+                resp.retcode = 1
+                resp.retmsg = "二维码解析失败"
+                return resp
+            }
+            val uid = encdataBack.substring(0, encdataBack.indexOf(":"))
+            val muser = mobileApiService.findUserById(uid)
+            if(muser==null||TradeDict.STATUS_NORMAL!=muser.status){
+                resp.retcode = 1
+                resp.retmsg = "用户不存在或状态异常"
+                return resp
+            }
+            val seed32 = muser.secertkey
+            val rawEncdata = encdataBack.substring(encdataBack.indexOf(":") + 1)
+
+            val privateKey = RSAKeysGenerate.getPrivateKey(muser.rsaprivate)
+            val rawdataBack = RSAKeysGenerate.decryptbyte(privateKey, rawEncdata)
+            logger.info("rawdata_back=$rawdataBack")
+
+            val rawData = Gson().fromJson(rawdataBack, QrcodeRawData::class.java)
+            logger.info("userid=" + rawData.userid)
+            if (null == rawData || rawData.userid.isNullOrEmpty()) {
+                resp.retcode = 1
+                resp.retmsg = "用户不存在"
+                return resp
+            }
+            val person = userService.findOnePersonByUserid(rawData.userid)
+
+            /*if(TradeDict.STATUS_NORMAL!=person.status){
+                resp.retcode = 1
+                resp.retmsg = "用户状态异常"
+                return resp
+            }*/
+            val verifyBarcode = QrCodeTotpUtil.verifyCode(rawData.totp, seed32, offset)
+            return if (verifyBarcode) {
+                resp.retcode = 0
+                resp.retmsg = "OK"
+                resp.userid = person.userid
+                resp.status = person.status
+                val bankcard = mobileApiService.findCardByUserid(person.userid)
+                if(bankcard!=null){
+                    resp.bankcardno = bankcard.cardno
+                }
+                val citycard = mobileApiService.findCityCardByUserid(person.userid)
+                if(citycard!=null){
+                    resp.citycardno = citycard.cardno
+                }
+                resp
+            } else {
+                resp.retcode = 1
+                resp.retmsg = "二维码校验失败"
+                resp
+            }
+        } catch (e: Exception) {
+            e.printStackTrace()
+            resp.retcode = 1
+            resp.retmsg = "二维码识别异常"
+            return resp
+        }
+
+    }
+
+}
+
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/qrcode_service.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/qrcode_service.kt
new file mode 100644
index 0000000..029f6a8
--- /dev/null
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/qrcode_service.kt
@@ -0,0 +1,9 @@
+package com.supwisdom.dlpay.api.service
+
+import com.supwisdom.dlpay.api.bean.ApiResponse
+import com.supwisdom.dlpay.api.bean.DoorQrcodeResponse
+
+interface QRCodeService {
+    fun encodeCode(uid: String): ApiResponse
+    fun decodeCode(qrcode: String): DoorQrcodeResponse
+}
\ No newline at end of file
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
index 3e75d86..b929bea 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
@@ -1,9 +1,9 @@
 package com.supwisdom.dlpay.mobile
 
-import com.mascloud.sdkclient.Client
 import com.supwisdom.dlpay.agent.citizencard.YnrccUtil
 import com.supwisdom.dlpay.agent.service.CitizencardPayService
 import com.supwisdom.dlpay.api.bean.JsonResult
+import com.supwisdom.dlpay.api.service.QRCodeService
 import com.supwisdom.dlpay.api.service.UserService
 import com.supwisdom.dlpay.api.util.MobileNumberCheck
 import com.supwisdom.dlpay.framework.core.JwtConfig
@@ -17,9 +17,9 @@
 import com.supwisdom.dlpay.mobile.service.MobileApiService
 import com.supwisdom.dlpay.system.service.DictionaryProxy
 import com.supwisdom.dlpay.util.ConstantUtil
+import com.supwisdom.dlpay.util.RSAKeysGenerate
 import org.apache.commons.lang.StringUtils
 import org.jose4j.jwt.ReservedClaimNames
-import org.jose4j.lang.JoseException
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.data.redis.core.RedisTemplate
 import org.springframework.http.HttpStatus
@@ -95,11 +95,12 @@
         val temp = redisTemplate.opsForValue().get(phone)
         if (temp.isNullOrEmpty()) {
             val code = RandomUtils.randomNumber(6)
+            System.out.println(code)
+            redisTemplate.opsForValue().set(phone, code, Duration.ofMinutes(5))
             val rs = mobileApiService.sendSms(phone, code)
             if ("0" != rs.retcode) {
                 return JsonResult.error(rs.retmsg)
             }
-            redisTemplate.opsForValue().set(phone, code, Duration.ofMinutes(5))
         }
         return JsonResult.ok("验证码已发送")
     }
@@ -196,6 +197,12 @@
         }
         user.lastlogin = DateUtil.getNow()
         user.jti = jwt.jti
+        val keyMap = RSAKeysGenerate.initKey()
+        val publicKey = RSAKeysGenerate.getPublicKey(keyMap)
+        val privateKey = RSAKeysGenerate.getPrivateKey(keyMap)
+        user.rsaprivate = privateKey
+        user.rsapublic = publicKey
+        user.secertkey = RandomUtils.getSecureRandomHex()
         mobileApiService.saveUser(user)
         redisTemplate.delete(user.uid)
         var payseted = false
@@ -234,11 +241,12 @@
     lateinit var dictionaryProxy: DictionaryProxy
     @Autowired
     lateinit var citizencardPayService: CitizencardPayService
-
     @Autowired
     lateinit var apiJwtRepository: ApiJwtRepository
     @Autowired
     lateinit var jwtConfig: JwtConfig
+    @Autowired
+    lateinit var qrcodeService:QRCodeService
 
     @RequestMapping("/idtypes")
     fun idtypes(): JsonResult {
@@ -693,11 +701,13 @@
     @RequestMapping("/qrcode")
     fun qrcode(): JsonResult {
         val p = SecurityContextHolder.getContext().authentication
-        mobileApiService.findUserById(p.name)
+        val user = mobileApiService.findUserById(p.name)
                 ?: return JsonResult.error("用户不存在,请注册")
-        //TODO qrcode
-        return JsonResult.ok("ok").put("qrcode", p.name)!!
+        val resp = qrcodeService.encodeCode(user.uid)
+        return if(resp.retcode==0){
+            JsonResult.ok("ok").put("qrcode", resp.retmsg)!!
+        }else{
+            JsonResult.error(resp.retmsg)
+        }
     }
-
-
 }
\ No newline at end of file
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/domain/TBMobileUser.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/domain/TBMobileUser.kt
index 68b9155..276d8d0 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/domain/TBMobileUser.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/domain/TBMobileUser.kt
@@ -160,6 +160,12 @@
      * */
     @Column(name = "ulogo", length = 100)
     var ulogo: String? = null
+    @Column(name = "rsaprivate", length = 1000)
+    var rsaprivate: String? = null
+    @Column(name = "rsapublic", length = 1000)
+    var rsapublic: String? = null
+    @Column(name = "secertkey", length = 64)
+    var secertkey: String? = null
 
 
     fun checkLoginpwdtime():Int{
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt
index 5e1ad77..0311352 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt
@@ -18,6 +18,8 @@
 
     fun findCardByUserid(userid :String) :TCard?
 
+    fun findCityCardByUserid(userid :String) :TCard?
+
     fun saveCard(card:TCard):TCard
 
     fun sendSms(phone:String,code:String):BaseResp
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt
index 87ef47b..cc1bc47 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt
@@ -60,6 +60,10 @@
         return cardDao.findCardByUseridAndCardtype(userid,ConstantUtil.CARDTYPE_BANKCARD)
     }
 
+    override fun findCityCardByUserid(userid: String): TCard? {
+        return cardDao.findCardByUseridAndCardtype(userid,ConstantUtil.CARDTYPE_CITIZENCARD)
+    }
+
     override fun saveCard(card: TCard): TCard {
         return cardDao.save(card)
     }
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/security.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/security.kt
index 5dc7de1..154dee4 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/security.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/security.kt
@@ -41,6 +41,7 @@
 import org.springframework.web.cors.CorsConfigurationSource
 import org.springframework.web.cors.UrlBasedCorsConfigurationSource
 import org.springframework.web.filter.OncePerRequestFilter
+import org.springframework.web.servlet.config.annotation.CorsRegistry
 import java.security.SecureRandom
 import java.util.*
 import javax.servlet.FilterChain
@@ -131,15 +132,84 @@
 }
 
 @Component
-class MobileSecurityFilter : HttpFilter() {
-    override fun doFilter(req: HttpServletRequest, res: HttpServletResponse, chain: FilterChain) {
-//        val url = req.requestURI
-//        if (url.contains("/userinfor")) {
-//            SecurityContextHolder.getContext().authentication = RedisTokenStore(redisConnectionFactory).readAuthentication(jwt)
-//            chain.doFilter(req, res)
-//            return
-//        }
-        chain.doFilter(req, res)
+class MobileSecurityFilter : OncePerRequestFilter() {
+    @Autowired
+    lateinit var jwtConfig: JwtConfig
+
+    @Autowired
+    lateinit var apiJwtRepository: ApiJwtRepository
+
+    private var jwtUtil: JwtTokenUtil? = null
+
+    private fun getUtil(): JwtTokenUtil {
+        if (jwtUtil == null) {
+            jwtUtil = JwtTokenUtil((jwtConfig))
+        }
+        return jwtUtil as JwtTokenUtil
+    }
+
+    override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
+
+        request.getHeader(jwtConfig.header)?.let { authHeader ->
+            try {
+                val jwt = if (authHeader.startsWith(jwtConfig.tokenHeader)) {
+                    authHeader.substring(jwtConfig.tokenHeader.length)
+                } else {
+                    throw JoseException("JWT Header error")
+                }
+                val claims = getUtil().verifyToken(jwt)
+                apiJwtRepository.findById(claims[ReservedClaimNames.JWT_ID].toString()).let {
+                    if (!it.isPresent) {
+                        throw JoseException("JWT has not been register")
+                    }
+                    // token 已被设为黑名单
+                    if (it.get().status != TradeDict.JWT_STATUS_NORMAL) {
+                        throw JoseException("JWT status error : ${it.get().status}")
+                    }
+                }
+                if (jwtConfig.multiTenant) {
+                    val tenantId = request.getHeader(Constants.HEADER_TETANTID)
+                    if (tenantId == null) {
+                        response.status = HttpStatus.UNAUTHORIZED.value()
+                        return
+                    }
+                    if (claims[Constants.JWT_CLAIM_TENANTID] != tenantId) {
+                        response.status = HttpStatus.UNAUTHORIZED.value()
+                        return
+                    }
+                    TenantContext.setTenantSchema(tenantId)
+                }
+                val auth = UsernamePasswordAuthenticationToken(claims[Constants.JWT_CLAIM_UID], null,
+                        (claims[Constants.JWT_CLAIM_AUTHORITIES] as ArrayList<*>)
+                                .map { SimpleGrantedAuthority(it as String) })
+                SecurityContextHolder.getContext().authentication = auth
+            } catch (e: InvalidJwtException) {
+                SecurityContextHolder.clearContext()
+                if (e.hasExpired()) {
+                    // jwt 过期后返回 401
+                    apiJwtRepository.deleteById(e.jwtContext.jwtClaims.jwtId)
+                }
+                response.status = HttpStatus.UNAUTHORIZED.value()
+                return
+            } catch (e: JoseException) {
+                SecurityContextHolder.clearContext()
+                // jwt 失效后返回 401
+                response.status = HttpStatus.UNAUTHORIZED.value()
+                response.contentType = "application/json;charset=UTF-8"
+                return
+            } catch (e: Exception) {
+                SecurityContextHolder.clearContext()
+                // jwt 失效后返回 401
+                response.status = HttpStatus.UNAUTHORIZED.value()
+                response.contentType = "application/json;charset=UTF-8"
+                return
+            }
+        }
+        response.setHeader("Access-Control-Allow-Origin", "*");
+        response.setHeader("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS");
+        response.setHeader("Access-Control-Allow-Headers", "*");
+        response.setHeader("Access-Control-Allow-Credentials", "true")
+        filterChain.doFilter(request, response)
     }
 }
 
@@ -151,12 +221,15 @@
         @Configuration
         @Order(1)
         class ApiWebSecurityConfigurationAdapter : WebSecurityConfigurerAdapter() {
-
+            @Autowired
+            lateinit var apiJwtAuthenticationFilter: ApiJwtAuthenticationFilter
             override fun configure(http: HttpSecurity) {
                 // 设置 API 访问权限管理
                 http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                         .and()
                         .antMatcher("/api/**")
+                        .addFilterAfter(apiJwtAuthenticationFilter,
+                                UsernamePasswordAuthenticationFilter::class.java)
                         .authorizeRequests()
                         .antMatchers("/api/auth/**").permitAll()
                         .antMatchers("/api/notify/**").permitAll()
@@ -188,9 +261,9 @@
 
             @Autowired
             lateinit var userDetailsService: MobileUserService
-
             @Autowired
-            lateinit var apiFilter: ApiJwtAuthenticationFilter
+            lateinit var mobileSecurityFilter: MobileSecurityFilter
+
 
             override fun configure(auth: AuthenticationManagerBuilder) {
                 auth.authenticationProvider(userProvider())
@@ -220,7 +293,7 @@
                         .cors()
                         .and()
                         .antMatcher("/mobileapi/**")
-                        .addFilterAfter(apiFilter,
+                        .addFilterAfter(mobileSecurityFilter,
                                 UsernamePasswordAuthenticationFilter::class.java)
                         .authorizeRequests().antMatchers("/mobileapi/i/**", "/mobileapi/login")
                         .permitAll().anyRequest().authenticated()
@@ -230,8 +303,6 @@
                         .failureHandler(failureHandler)
                         .successHandler(successHandler)
                         .and().csrf().disable()
-                        .sessionManagement().maximumSessions(1)
-                        .expiredUrl("/mobileapi/sessionexpired")
             }
 
             @Bean
@@ -242,7 +313,7 @@
                 configuration.allowedMethods = listOf("GET", "POST")
                 configuration.allowedHeaders = listOf("*")
                 val source = UrlBasedCorsConfigurationSource()
-                source.registerCorsConfiguration("/mobileapi/**", configuration);
+                source.registerCorsConfiguration("/mobileapi/**", configuration)
                 return source
             }
         }