Bläddra i källkod

Merge branch 'master' into project-qczj-20220713

# Conflicts:
#	4dkankan-common-utils/src/main/java/com/fdkankan/common/constant/SceneConstant.java
dengsixing 3 år sedan
förälder
incheckning
bba000d551
61 ändrade filer med 4893 tillägg och 25 borttagningar
  1. 1 0
      4dkankan-common-utils/src/main/java/com/fdkankan/common/constant/SceneConstant.java
  2. 195 0
      4dkankan-common-utils/src/main/java/com/fdkankan/common/util/CreateHouseJsonUtil.java
  3. 14 0
      4dkankan-common-utils/src/main/java/com/fdkankan/common/util/FileUtil.java
  4. 1 1
      4dkankan-common-utils/src/main/java/com/fdkankan/common/util/JwtUtil.java
  5. 25 24
      4dkankan-common-utils/src/main/java/com/fdkankan/common/util/TestUtil.java
  6. 162 0
      4dkankan-common-web/pom.xml
  7. 54 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/aop/FeignInterceptor.java
  8. 111 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/aop/VisitLogInterceptor.java
  9. 29 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/bean/DownLoadProgressBean.java
  10. 29 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/bean/DownLoadTaskBean.java
  11. 108 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/config/FileRouteConfig.java
  12. 36 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/config/LogPathHostNameProperty.java
  13. 8 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/config/RedisDefaultConfig.java
  14. 44 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/config/ResultStatusDecoder.java
  15. 155 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/config/SentinelFileDataSourceInitFunc.java
  16. 57 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/config/SentinelUrlBlockHandler.java
  17. 110 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/config/ShiroConfig.java
  18. 40 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/config/ShiroModularRealmAuthenticator.java
  19. 23 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/config/SupportAutoConfiguration.java
  20. 50 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/constant/CameraTypeEnum.java
  21. 26 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/constant/FilterConstant.java
  22. 78 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/controller/BaseController.java
  23. 50 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/controller/CustomErrorController.java
  24. 138 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/exception/GlobalExceptionHandler.java
  25. 34 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/exception/JwtAuthenticationException.java
  26. 10 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/factory/LogFactory.java
  27. 113 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/jwt/JwtFilter.java
  28. 33 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/jwt/JwtToken.java
  29. 37 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/model/SSOUser.java
  30. 60 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/realm/AgentJwtRealm.java
  31. 62 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/realm/AppJwtRealm.java
  32. 61 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/realm/ManagerJwtRealm.java
  33. 90 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/realm/UserJwtRealm.java
  34. 54 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/response/BaseResponseAdvice.java
  35. 73 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/user/SSOLoginHelper.java
  36. 97 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/user/SSOLoginStore.java
  37. 38 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/user/SSOUser.java
  38. 21 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/util/SsoUtil.java
  39. 35 0
      4dkankan-common-web/src/main/java/com/fdkankan/web/util/WebUtil.java
  40. 77 0
      4dkankan-utils-pay/pom.xml
  41. 92 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/AlipayDefaultConfig.java
  42. 81 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipayConfig.java
  43. 60 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipayGoodsDetail.java
  44. 161 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipayService.java
  45. 38 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipayUtil.java
  46. 113 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipaymentEx.java
  47. 56 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/PayPalDefaultConfig.java
  48. 43 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PayPalConfig.java
  49. 130 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PayPalmentEx.java
  50. 88 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalOrderAddressEx.java
  51. 59 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalOrderItemEx.java
  52. 5 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalPaymentIntent.java
  53. 5 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalPaymentMethod.java
  54. 268 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalService.java
  55. 66 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/UrlUtils.java
  56. 64 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/WXPayDefaultConfig.java
  57. 673 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/sdk/WXPay.java
  58. 53 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/sdk/WXPayConfig.java
  59. 45 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/sdk/WXPayConstants.java
  60. 307 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/sdk/WXPayUtil.java
  61. 47 0
      pom.xml

+ 1 - 0
4dkankan-common-utils/src/main/java/com/fdkankan/common/constant/SceneConstant.java

@@ -114,4 +114,5 @@ public class SceneConstant {
 
     public static final int FAILURE_CODE_5037 = 5037;
     public static final String FAILURE_MSG_5037 = "场景正在计算中,请待计算完成后再操作。";
+
 }

+ 195 - 0
4dkankan-common-utils/src/main/java/com/fdkankan/common/util/CreateHouseJsonUtil.java

@@ -0,0 +1,195 @@
+package com.fdkankan.common.util;
+
+import java.io.IOException;
+import java.util.Map;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+
+public class CreateHouseJsonUtil {
+
+	/**
+	 * 根据用户上传的户型图json文件生成houseType.json
+	 * @param filePath
+	 * @return
+	 */
+	public static JSONObject createHouseTypeJsonByUser(String filePath) {
+		JSONObject house = init();
+		JSONArray floors = house.getJSONArray("floors");
+		
+		JSONArray floorJson = readFloorJson(filePath);
+		for(int i=0;i<floorJson.size();++i) {
+			JSONObject floor = floorJson.getJSONObject(i);
+			JSONObject floorData = convert(floor,i);
+			floors.add(floorData);
+		}
+		return house;
+	}
+	
+	private static JSONObject init() {
+		JSONObject outContent = new JSONObject();
+		outContent.put("name", "houseType.json");
+		outContent.put("version", "2.1");
+		
+		outContent.put("floors", new JSONArray());
+		outContent.put("newVectorId", null);
+		outContent.put("setting", null);
+		outContent.put("boundingBox", null);
+		return outContent;
+	}
+	
+	private static JSONArray readFloorJson(String filePath) {
+		try {
+			JSONObject floorplan = FileUtil.readJson(filePath);
+			JSONArray floors = floorplan.getJSONArray("floors");
+			return floors;
+		} catch (IOException e) {
+
+			return null;
+		}
+	}
+	
+	private static JSONObject convert(JSONObject floorJson,int floor) {
+		JSONArray rooms = floorJson.getJSONArray("rooms");
+		JSONObject walls = floorJson.getJSONObject("walls");
+		JSONObject points = floorJson.getJSONObject("points");
+		JSONObject symbols = floorJson.getJSONObject("symbols");
+		
+		JSONArray _points = convertPoints(points);
+		JSONArray _walls = convertWalls(walls);
+		JSONArray _symbols = convertSymbols(symbols,floor);
+		JSONArray _rooms = convertRooms(rooms,floor);
+
+
+		JSONObject floorData = new JSONObject();
+		floorData.put("points", _points);
+		floorData.put("walls", _walls);
+		floorData.put("symbols", _symbols);
+		floorData.put("rooms", _rooms);
+		return floorData;
+	}
+	
+	private static JSONArray convertPoints(JSONObject points) {
+		
+	   JSONArray _points = new JSONArray();
+       for (Map.Entry<String, Object> entry: points.entrySet()) {
+    	   JSONObject pointValue = (JSONObject)entry.getValue();
+    	   JSONObject _pointValue = new JSONObject();
+    	   _pointValue.put("x", pointValue.getFloat("x"));
+    	   _pointValue.put("y", pointValue.getFloat("y"));
+    	   _pointValue.put("parent", pointValue.getJSONObject("parent"));
+    	   _pointValue.put("vectorId", pointValue.getString("vectorId"));
+    	   
+    	   _points.add(_pointValue);
+       }
+		
+       return _points;
+	}
+	
+	private static JSONArray convertWalls(JSONObject walls) {
+		
+	   JSONArray _walls = new JSONArray();
+       for (Map.Entry<String, Object> entry: walls.entrySet()) {
+    	   JSONObject wallValue = (JSONObject)entry.getValue();
+    	   JSONObject _wallValue = new JSONObject();
+    	   _wallValue.put("start", wallValue.getString("start"));
+    	   _wallValue.put("end", wallValue.getString("end"));
+    	   _wallValue.put("children", wallValue.getJSONArray("children"));
+    	   _wallValue.put("vectorId", wallValue.getString("vectorId"));
+    	   _wallValue.put("width", 0.2);
+    	   //leftEdgeId
+    	   //rightEdgeId
+    	   _walls.add(_wallValue);
+       }
+		
+       return _walls;
+	}
+	
+	//门/窗
+	private static JSONArray convertSymbols(JSONObject symbols,int floor) {
+	   JSONArray _symbols = new JSONArray();
+       for (Map.Entry<String, Object> entry: symbols.entrySet()) {
+    	   JSONObject symbolValue = (JSONObject)entry.getValue();
+    	   JSONObject _symbolValue = new JSONObject();
+    	   _symbolValue.put("start", symbolValue.getJSONObject("startPoint"));
+    	   _symbolValue.put("end", symbolValue.getJSONObject("endPoint"));
+    	   _symbolValue.put("parent", symbolValue.getString("parent"));
+    	   _symbolValue.put("openSide", symbolValue.getString("openSide"));
+    	   _symbolValue.put("vectorId", symbolValue.getString("vectorId"));
+    	   											_symbolValue.put("points2d", symbolValue.getJSONArray("points2d"));
+    	   _symbolValue.put("geoType", symbolValue.getString("geoType"));
+    	   _symbolValue.put("floor", floor);
+    	   
+    	   //"groundClearance": -0.7,
+		   //"height": 1.3,
+    	   
+    	   _symbols.add(_symbolValue);
+       }
+		
+       return _symbols;
+	}
+	
+	private static JSONArray convertRooms(JSONArray rooms,int floor) {
+		JSONArray _rooms = new JSONArray();
+		
+		for(int i=0;i<rooms.size();++i) {
+			JSONObject room = rooms.getJSONObject(i);
+			String name = room.getString("name");
+			JSONObject center = room.getJSONObject("center");
+			String roomId = room.getString("roomId");
+			JSONArray wallIds = room.getJSONArray("wallIds");
+			JSONArray wallPointIDs = room.getJSONArray("wallPointIDs");
+			String parent = room.getString("parent");
+			
+			JSONObject _room = new JSONObject();
+			_room.put("name", name);
+			_room.put("roomId", roomId);
+			_room.put("center", center);
+			_room.put("wallIds", wallIds);
+			_room.put("wallPointIDs", wallPointIDs);
+			_room.put("parent", parent);
+			
+			_rooms.add(_room);
+		}
+		return _rooms;
+	}
+
+	/*
+	//构件,包括:柱子,烟囱等
+	private JSONObject convertComponents(JSONObject components) {
+	   JSONArray beams = new JSONArray();
+	   JSONArray flues = new JSONArray();
+	   
+	   JSONObject result = new JSONObject();
+	   
+       for (Map.Entry<String, Object> entry: components.entrySet()) {
+    	   JSONObject componentValue = (JSONObject)entry.getValue();
+    	   JSONObject _componentValue = new JSONObject();
+    	   String geoType = componentValue.getString("geoType");
+    	   
+    		   
+    	   											_componentValue.put("center", componentValue.getJSONObject("center"));
+    	   _componentValue.put("angle", componentValue.getIntValue("angle"));  	   
+    	   _componentValue.put("vectorId", componentValue.getString("vectorId"));
+    	   _componentValue.put("points2d", componentValue.getJSONArray("points2d"));
+    	   _componentValue.put("geoType", componentValue.getString("geoType"));
+    	   _componentValue.put("sideWidth", componentValue.getFloatValue("sideWidth"));  	   
+    	   _componentValue.put("sideThickness", componentValue.getFloatValue("sideThickness"));  	   
+    	   if(geoType.endsWith("Beam")) {
+    		   beams.add(_componentValue);
+    	   }
+    	   else if(geoType.endsWith("Flue")) {
+    		   flues.add(_componentValue);
+    	   }
+       }
+		
+       result.put("beams", beams);
+       result.put("flues", flues);
+       return result;
+	}
+	*/
+	
+	//rooms
+	//tags
+}

+ 14 - 0
4dkankan-common-utils/src/main/java/com/fdkankan/common/util/FileUtil.java

@@ -1,6 +1,8 @@
 package com.fdkankan.common.util;
 
 import cn.hutool.core.collection.CollUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.fdkankan.common.constant.ErrorCode;
 import com.fdkankan.common.exception.BusinessException;
 import java.io.*;
@@ -411,6 +413,18 @@ public class FileUtil {
 		}
 	}
 
+	public static JSONObject readJson(String filePath) throws IOException {
+		try {
+			String content = cn.hutool.core.io.FileUtil.readUtf8String(filePath);
+			JSONObject jsonObj = JSON.parseObject(content);
+			return jsonObj;
+
+		} catch (Exception e) {
+			log.error("读取json失败,filePath=" + filePath, e);
+			return null;
+		}
+	}
+
 	public static void main(String[] args) throws Exception {
 
 

+ 1 - 1
4dkankan-common-utils/src/main/java/com/fdkankan/common/util/JwtUtil.java

@@ -138,7 +138,7 @@ public class JwtUtil {
 
     public static void main(String[] args) {
         long start = System.currentTimeMillis();
-        System.out.println(getUsername("eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMzExMjMxMTE3OCIsInVzZXJOYW1lIjoiMTMxMTIzMTExNzgiLCJpYXQiOjE1NjQwNDY0OTgsImp0aSI6IjhhZWJlNzhlLTU2OGUtNDY2Yi1iY2E3LWQ4ZjI0MjgxYzJhZCJ9.1N3UNjkT39mXtymfkJQVQJxOlFM3gfC6bgfur5mVmEU"));
+        System.out.println(getUsername("eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMzUzNjUwMTEyOCIsImxvZ2luVHlwZSI6InVzZXIiLCJ1c2VyTmFtZSI6IjEzNTM2NTAxMTI4IiwiaWF0IjoxNjU4ODI4NjA2LCJqdGkiOiI4NjczYjFiMi0xYjc4LTRmMTEtOGQ5My05OWE0OWRlMGVhMjAifQ.0oYzEwTsV1iLn_cMeiqLc5upJfcYipRbIDSndSxeuK0"));
         System.out.println(System.currentTimeMillis() - start);
     }
 

+ 25 - 24
4dkankan-common-utils/src/main/java/com/fdkankan/common/util/TestUtil.java

@@ -1,5 +1,6 @@
 package com.fdkankan.common.util;
 
+import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.img.ImgUtil;
 import cn.hutool.core.io.IoUtil;
@@ -22,6 +23,7 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.function.Consumer;
+import lombok.Data;
 
 /**
  * <p>
@@ -43,33 +45,32 @@ public class TestUtil {
     }
 
     public static void main(String[] args) throws Exception {
-        String s = FileUtils.readFile("F:\\test\\link-scene.json");
-        JSONObject jsonObject = JSON.parseObject(s);
-        JSONArray tags = jsonObject.getJSONArray("tags");
-        ExecutorService executorService = Executors.newFixedThreadPool(5);
-        List<Future> futureList = new ArrayList<>();
-        for (Object tag : tags) {
-            Callable<Boolean> call = new Callable() {
-                @Override
-                public Boolean call() throws Exception {
-                    JSONObject tag1 = (JSONObject) tag;
-                    String path = tag1.getString("path");
-                    if(StrUtil.isNotEmpty(path)){
-                        tag1.put("path", path.replace("4k", "123"));
-                    }
-                    return true;
-                }
-            };
-            futureList.add(executorService.submit(call));
-        }
-        //这里一定要加阻塞,不然会导致oss文件还没打包好,主程序已经结束返回了
-        for (Future future : futureList) {
-            future.get();
-        }
 
-        System.out.println(JSON.toJSONString(jsonObject));
+        test test = new test();
+        test1 test1 = new test1();
+        test1.setTest("test1");
+        test2 test2 = new test2();
+        test2.setTest("test2");
+        BeanUtil.copyProperties(test1, test);
+        BeanUtil.copyProperties(test2, test);
+        System.out.println(test.getTest());
 
 
     }
 
 }
+
+@Data
+class test{
+    private String test;
+}
+
+@Data
+class test1{
+    private String test;
+}
+
+@Data
+class test2{
+    private String test;
+}

+ 162 - 0
4dkankan-common-web/pom.xml

@@ -0,0 +1,162 @@
+<?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>4dkankan-utils</artifactId>
+    <groupId>com.fdkankan</groupId>
+    <version>2.0.0-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>4dkankan-common-web</artifactId>
+
+  <dependencies>
+
+    <dependency>
+      <groupId>com.fdkankan</groupId>
+      <artifactId>4dkankan-common-utils</artifactId>
+      <version>2.0.0-SNAPSHOT</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.projectlombok</groupId>
+      <artifactId>lombok</artifactId>
+    </dependency>
+
+<!--    <dependency>-->
+<!--      <groupId>cn.hutool</groupId>-->
+<!--      <artifactId>hutool-all</artifactId>-->
+<!--    </dependency>-->
+
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>servlet-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-webflux</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>com.alibaba</groupId>
+      <artifactId>fastjson</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.shiro</groupId>
+      <artifactId>shiro-spring</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>net.coobird</groupId>
+      <artifactId>thumbnailator</artifactId>
+      <version>0.4.8</version>
+    </dependency>
+
+    <dependency>
+      <groupId>com.google.code.gson</groupId>
+      <artifactId>gson</artifactId>
+      <version>2.8.5</version>
+    </dependency>
+
+    <dependency>
+      <groupId>com.fdkankan</groupId>
+      <artifactId>4dkankan-utils-redis</artifactId>
+      <version>2.0.0-SNAPSHOT</version>
+    </dependency>
+
+    <dependency>
+      <groupId>com.alibaba.cloud</groupId>
+      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>com.alibaba.cloud</groupId>
+      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-web</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.poi</groupId>
+      <artifactId>poi</artifactId>
+      <version>3.8</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.poi</groupId>
+      <artifactId>poi-ooxml</artifactId>
+      <version>3.8</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-test</artifactId>
+      <scope>test</scope>
+      <exclusions>
+        <exclusion>
+          <groupId>org.junit.vintage</groupId>
+          <artifactId>junit-vintage-engine</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>junit</groupId>
+          <artifactId>junit</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.springframework.plugin</groupId>
+      <artifactId>spring-plugin-core</artifactId>
+      <version>1.2.0.RELEASE</version>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.plugin</groupId>
+      <artifactId>spring-plugin-metadata</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-aop</artifactId>
+    </dependency>
+    <!--        <dependency>-->
+    <!--            <groupId>com.alibaba.cloud</groupId>-->
+    <!--            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>-->
+    <!--        </dependency>-->
+
+    <dependency>
+      <groupId>com.baomidou</groupId>
+      <artifactId>mybatis-plus-extension</artifactId>
+      <version>3.4.3.4</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-validation</artifactId>
+    </dependency>
+
+  </dependencies>
+
+  <build>
+
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+      </plugin>
+    </plugins>
+
+  </build>
+
+
+</project>

+ 54 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/aop/FeignInterceptor.java

@@ -0,0 +1,54 @@
+//
+//import com.alibaba.fastjson.JSON;
+//import com.fdkankan.common.constant.LogFormatConstant;
+//import java.util.Enumeration;
+//import javax.servlet.http.HttpServletRequest;
+//import lombok.extern.slf4j.Slf4j;
+//import org.aspectj.lang.ProceedingJoinPoint;
+//import org.aspectj.lang.annotation.Around;
+//import org.aspectj.lang.annotation.Aspect;
+//import org.aspectj.lang.annotation.Pointcut;
+//import org.aspectj.lang.reflect.MethodSignature;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.stereotype.Component;
+//import org.springframework.web.context.request.RequestContextHolder;
+//import org.springframework.web.context.request.ServletRequestAttributes;
+//
+//@Component
+//@Aspect
+//@Slf4j
+//public class FeignInterceptor {
+//
+//	// 切入点表达式
+//	@Pointcut("execution(* com.fdkankan.*.feign..*.*(..))")
+//	public void privilege() {
+//	}
+//
+//	@Around("privilege()")
+//	public Object around(ProceedingJoinPoint pjp) throws Throwable {
+//
+//		// 获取类名
+//		String className = pjp.getTarget().getClass().getName();
+//		// 获取执行的方法名称
+//		String methodName = pjp.getSignature().getName();
+//		// 获取参数名称
+//		String[] parameterNamesArgs = ((MethodSignature) pjp.getSignature()).getParameterNames();
+//		// 定义返回参数
+//		Object result = null;
+//		// 获取方法参数
+//		Object[] args = pjp.getArgs();
+//		StringBuilder params = new StringBuilder();
+//		for(int i = 0; i < parameterNamesArgs.length; i++){
+//			params.append(",").append(parameterNamesArgs[i]).append("=").append(args[i]);
+//		}
+//		log.info(LogFormatConstant.FEIGN_LOG_START, className, methodName, params.substring(1));
+//		// 执行目标方法
+//		result = pjp.proceed();
+//
+//		log.info(LogFormatConstant.FEIGN_LOG_END, className, methodName, JSON.toJSONString(result));
+//
+//		return result;
+//	}
+//}
+//

+ 111 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/aop/VisitLogInterceptor.java

@@ -0,0 +1,111 @@
+//
+//import com.fdkankan.common.constant.LogFormatConstant;
+//import com.fdkankan.common.factory.LogFactory;
+//import java.util.Enumeration;
+//import javax.servlet.http.HttpServletRequest;
+//import lombok.extern.slf4j.Slf4j;
+//import org.aspectj.lang.ProceedingJoinPoint;
+//import org.aspectj.lang.annotation.Around;
+//import org.aspectj.lang.annotation.Aspect;
+//import org.aspectj.lang.annotation.Pointcut;
+//import org.aspectj.lang.reflect.MethodSignature;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.stereotype.Component;
+//import org.springframework.web.context.request.RequestContextHolder;
+//import org.springframework.web.context.request.ServletRequestAttributes;
+//
+//@Component
+//@Aspect
+//@Slf4j
+//public class VisitLogInterceptor {
+//
+//	// 切入点表达式
+//	@Pointcut("execution(public * com.fdkankan.*..controller..*.*(..))")
+//	public void privilege() {
+//	}
+//
+//	@Around("privilege()")
+//	public Object around(ProceedingJoinPoint pjp) throws Throwable {
+//
+//		// 获取类名
+//		String className = pjp.getTarget().getClass().getName();// pjp.getTarget().getClass().getSimpleName();
+//		// 获取执行的方法名称
+//		String methodName = pjp.getSignature().getName();
+//		// 获取参数名称
+//		String[] parameterNamesArgs = ((MethodSignature) pjp.getSignature()).getParameterNames();
+//		// 定义返回参数
+//		Object result = null;
+//		// 获取方法参数
+//		Object[] args = pjp.getArgs();
+//		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
+//		// 请求的URL
+//		String requestURL = request.getRequestURL().toString();
+//		String ip = getIpAddr(request);
+//
+//		StringBuffer paramsBuf = new StringBuffer();
+//		// 获取请求参数集合并进行遍历拼接
+//		for (int i = 0; i < args.length; i++) {
+//			if (paramsBuf.length() > 0) {
+//				paramsBuf.append("|");
+//			}
+//			paramsBuf.append(parameterNamesArgs[i]).append(" = ").append(args[i]);
+//		}
+//		StringBuffer headerBuf = new StringBuffer();
+//		Enumeration<String> headerNames = request.getHeaderNames();
+//		while (headerNames.hasMoreElements()) {
+//			String key = (String) headerNames.nextElement();
+//			String value = request.getHeader(key);
+//			if (headerBuf.length() > 0) {
+//				headerBuf.append("|");
+//			}
+//			headerBuf.append(key).append("=").append(value);
+//		}
+//
+//		// 打印请求参数参数
+//		// 记录开始时间
+//		long start = System.currentTimeMillis();
+//
+//		log.info(LogFormatConstant.REQUEST_LOG_START, ip, requestURL, className, methodName, paramsBuf.toString(), headerBuf.toString());
+//
+//		// 执行目标方法
+//		result = pjp.proceed();
+//
+//		// 获取执行完的时间 打印返回报文
+//		log.info(LogFormatConstant.REQUEST_LOG_END, requestURL, className, methodName, result, (System.currentTimeMillis() - start));
+//		return result;
+//	}
+//
+//	/**
+//	 * @Title: getIpAddr
+//	 * @Description: 获取ip
+//	 * @param request
+//	 * @return
+//	 * @return String 返回类型
+//	 */
+//	public String getIpAddr(HttpServletRequest request) {
+//		String ipAddress = null;
+//		ipAddress = request.getHeader("x-forwarded-for");
+//		if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
+//			ipAddress = request.getHeader("Proxy-Client-IP");
+//		}
+//		if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
+//			ipAddress = request.getHeader("WL-Proxy-Client-IP");
+//		}
+//		if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
+//			ipAddress = request.getRemoteAddr();
+//		}
+//
+//		//对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
+//		if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
+//			// = 15
+//			if (ipAddress.indexOf(",") > 0) {
+//				ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
+//			}
+//		}
+//		// 或者这样也行,对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
+//		//return ipAddress!=null&&!"".equals(ipAddress)?ipAddress.split(",")[0]:null;
+//		return ipAddress;
+//	}
+//}
+//

+ 29 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/bean/DownLoadProgressBean.java

@@ -0,0 +1,29 @@
+package com.fdkankan.web.bean;
+
+import java.io.Serializable;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * <p>
+ * 场景下载进度
+ * </p>
+ *
+ * @author dengsixing
+ * @since 2022/2/22
+ **/
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class DownLoadProgressBean implements Serializable {
+
+    private String url;
+
+    private Integer percent;
+
+    private Integer status;
+
+}

+ 29 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/bean/DownLoadTaskBean.java

@@ -0,0 +1,29 @@
+package com.fdkankan.web.bean;
+
+import java.io.Serializable;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * <p>
+ * TODO
+ * </p>
+ *
+ * @author dengsixing
+ * @since 2022/2/22
+ **/
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class DownLoadTaskBean implements Serializable {
+
+    private Long userId;
+
+    private String num;
+
+    private String type;
+
+}

+ 108 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/config/FileRouteConfig.java

@@ -0,0 +1,108 @@
+package com.fdkankan.web.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+
+@Component
+@ConfigurationProperties(prefix = "file.route")
+public class FileRouteConfig {
+    /**
+     * 路径
+     */
+    //保存硬盘地址
+    private String hardDisk;
+	//保存硬盘地址(激光相机)
+	private String hardDiskLaser;
+    //默认图片类型文件夹
+    private String imageFolder;
+    //默认文件类型文件夹
+    private String documentFolder;
+    //默认的视频类型文件夹
+    private String videoFolder;
+    //默认的音频类型文件夹
+    private String musicFolder;
+    //允许上传的ip(上传白名单)
+    private String[] IPs;
+    /**
+     * 类型
+     */
+    //图片类型
+    private String[] imageType;
+    //文件类型
+    private String[] documentType;
+    //视频类型
+    private String[] videoType;
+    //音频类型
+    private String[] musicType;
+	public String getHardDisk() {
+		return hardDisk;
+	}
+	public String getHardDiskLaser() {
+		return hardDiskLaser;
+	}
+	public String getImageFolder() {
+		return imageFolder;
+	}
+	public String getDocumentFolder() {
+		return documentFolder;
+	}
+	public String getVideoFolder() {
+		return videoFolder;
+	}
+	public String getMusicFolder() {
+		return musicFolder;
+	}
+	public String[] getIPs() {
+		return IPs;
+	}
+	public String[] getImageType() {
+		return imageType;
+	}
+	public String[] getDocumentType() {
+		return documentType;
+	}
+	public String[] getVideoType() {
+		return videoType;
+	}
+	public String[] getMusicType() {
+		return musicType;
+	}
+	public void setHardDisk(String hardDisk) {
+
+		this.hardDisk = hardDisk;
+	}
+	public void setHardDiskLaser(String hardDiskLaser) {
+
+		this.hardDiskLaser = hardDiskLaser;
+	}
+	public void setImageFolder(String imageFolder) {
+		this.imageFolder = imageFolder;
+	}
+	public void setDocumentFolder(String documentFolder) {
+		this.documentFolder = documentFolder;
+	}
+	public void setVideoFolder(String videoFolder) {
+		this.videoFolder = videoFolder;
+	}
+	public void setMusicFolder(String musicFolder) {
+		this.musicFolder = musicFolder;
+	}
+	public void setIPs(String[] iPs) {
+		IPs = iPs;
+	}
+	public void setImageType(String[] imageType) {
+		this.imageType = imageType;
+	}
+	public void setDocumentType(String[] documentType) {
+		this.documentType = documentType;
+	}
+	public void setVideoType(String[] videoType) {
+		this.videoType = videoType;
+	}
+	public void setMusicType(String[] musicType) {
+		this.musicType = musicType;
+	}
+    
+    
+}

+ 36 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/config/LogPathHostNameProperty.java

@@ -0,0 +1,36 @@
+package com.fdkankan.web.config;
+
+import ch.qos.logback.core.PropertyDefinerBase;
+import com.fdkankan.common.util.FileUtils;
+import org.springframework.util.ObjectUtils;
+
+//@Component
+public class LogPathHostNameProperty extends PropertyDefinerBase {
+
+//    @Value("${hostName.filePath:/opt/hosts/hosts.txt}")
+//    private String hostNamePath;
+
+    @Override
+    public String getPropertyValue() {
+
+        String hostNamePath = getContext().getProperty("hostName.filePath");
+        if(ObjectUtils.isEmpty(hostNamePath)){
+            hostNamePath = "/opt/hosts/hosts.txt";
+        }
+        String hostName = "null";
+        try {
+            hostName = FileUtils.readFile(hostNamePath);
+            // 去除空格
+            if(!ObjectUtils.isEmpty(hostName)){
+                hostName = hostName.trim().replaceAll("\\s","");
+            }
+        } catch (Exception e) {
+            System.err.println("=========================error======================");
+            System.err.println("从文件中获取服务器名称失败,文件路径:"+hostNamePath);
+            return hostName;
+        }
+        return hostName;
+    }
+
+
+}

+ 8 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/config/RedisDefaultConfig.java

@@ -0,0 +1,8 @@
+package com.fdkankan.web.config;
+
+public class RedisDefaultConfig {
+//    public static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS; //redis默认过期时间单位
+//    public static final int DEFAULT_EXPIRE_TIME = 7200;  // redis默认过期时间,单位/秒, 60*60*2=2H, 两小时
+//    public static final int CAMERA_EXPIRE_TIME = 604800;  // 相机登陆7天有效期
+//    public static final int USER_EXPIRE_TIME = 21600;  // agent用户有效期
+}

+ 44 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/config/ResultStatusDecoder.java

@@ -0,0 +1,44 @@
+//package common.config;
+//
+//import com.alibaba.cloud.commons.io.IOUtils;
+//import com.alibaba.fastjson.JSONObject;
+//import com.fdkankan.common.constant.ServerCode;
+//import com.fdkankan.common.exception.BusinessException;
+//import com.fdkankan.common.response.ResultData;
+//import feign.Response;
+//import feign.codec.Decoder;
+//import lombok.extern.slf4j.Slf4j;
+//
+//import java.io.IOException;
+//import java.lang.reflect.Type;
+//import java.nio.charset.StandardCharsets;
+//import java.util.Objects;
+//
+//@Slf4j
+//public final class ResultStatusDecoder implements Decoder {
+//
+//    public static final String CONTENT_KEY = "content";
+//    final Decoder delegate;
+//
+//    public ResultStatusDecoder(Decoder delegate) {
+//        Objects.requireNonNull(delegate, "Decoder must not be null. ");
+//        this.delegate = delegate;
+//    }
+//
+//    @Override
+//    public Object decode(Response response, Type type) throws IOException {
+//        // 判断是否返回参数是否是异常
+//        String resultStr = IOUtils.toString(response.body().asInputStream(), StandardCharsets.UTF_8);
+//        log.info("feign调用返回,result msg ->{}",resultStr);
+//        try {
+//            ResultData resultData = JSONObject.parseObject(resultStr, ResultData.class);
+//            if(resultData.getCode() != ServerCode.SUCCESS.code()){
+//                throw new BusinessException(resultData.getCode(),resultData.getMessage());
+//            }
+//        }catch (Exception e){
+//            e.printStackTrace();
+//        }
+//        // 回写body
+//        return delegate.decode(response.toBuilder().body(resultStr, StandardCharsets.UTF_8).build(), type);
+//    }
+//}

+ 155 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/config/SentinelFileDataSourceInitFunc.java

@@ -0,0 +1,155 @@
+//
+//import com.alibaba.csp.sentinel.command.handler.ModifyParamFlowRulesCommandHandler;
+//import com.alibaba.csp.sentinel.datasource.*;
+//import com.alibaba.csp.sentinel.init.InitFunc;
+//import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
+//import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
+//import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
+//import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
+//import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+//import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
+//import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
+//import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
+//import com.alibaba.csp.sentinel.slots.system.SystemRule;
+//import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
+//import com.alibaba.csp.sentinel.transport.util.WritableDataSourceRegistry;
+//import com.alibaba.fastjson.JSON;
+//import com.alibaba.fastjson.TypeReference;
+//
+//import java.io.File;
+//import java.io.IOException;
+//import java.util.List;
+//
+///**
+// * <p>
+// * sentinel规则数据持久化到本地文件
+// * </p>
+// *
+// * @author dengsixing
+// * @since 2022/1/26
+// **/
+//public class SentinelFileDataSourceInitFunc implements InitFunc {
+//
+//    @Override
+//    public void init() throws Exception {
+//        //持久化在本地的目录
+//        String ruleDir = System.getProperty("user.home") + "/sentinel/rules";
+//        String flowRulePath = ruleDir + "/flow-rule.json";
+//        String degradeRulePath = ruleDir + "/degrade-rule.json";
+//        String systemRulePath = ruleDir + "/system-rule.json";
+//        String authorityRulePath = ruleDir + "/authority-rule.json";
+//        String paramFlowRulePath = ruleDir + "/param-flow-rule.json";
+//
+//        this.mkdirIfNotExits(ruleDir);
+//        this.createFileIfNotExits(flowRulePath);
+//        this.createFileIfNotExits(degradeRulePath);
+//        this.createFileIfNotExits(systemRulePath);
+//        this.createFileIfNotExits(authorityRulePath);
+//        this.createFileIfNotExits(paramFlowRulePath);
+//
+//        // 流控规则
+//        ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<>(
+//                flowRulePath,
+//                flowRuleListParser
+//        );
+//        // 将可读数据源注册至FlowRuleManager
+//        // 这样当规则文件发生变化时,就会更新规则到内存
+//        FlowRuleManager.register2Property(flowRuleRDS.getProperty());
+//        WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<List<FlowRule>>(
+//                flowRulePath,
+//                this::encodeJson
+//        );
+//        // 将可写数据源注册至transport模块的WritableDataSourceRegistry中
+//        // 这样收到控制台推送的规则时,Sentinel会先更新到内存,然后将规则写入到文件中
+//        WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);
+//
+//        // 降级规则
+//        ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource<>(
+//                degradeRulePath,
+//                degradeRuleListParser
+//        );
+//        DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
+//        WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>(
+//                degradeRulePath,
+//                this::encodeJson
+//        );
+//        WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);
+//
+//        // 系统规则
+//        ReadableDataSource<String, List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource<>(
+//                systemRulePath,
+//                systemRuleListParser
+//        );
+//        SystemRuleManager.register2Property(systemRuleRDS.getProperty());
+//        WritableDataSource<List<SystemRule>> systemRuleWDS = new FileWritableDataSource<>(
+//                systemRulePath,
+//                this::encodeJson
+//        );
+//        WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);
+//
+//        // 授权规则
+//        ReadableDataSource<String, List<AuthorityRule>> authorityRuleRDS = new FileRefreshableDataSource<>(
+//                flowRulePath,
+//                authorityRuleListParser
+//        );
+//        AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
+//        WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new FileWritableDataSource<>(
+//                authorityRulePath,
+//                this::encodeJson
+//        );
+//        WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);
+//
+//        // 热点参数规则
+//        ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource<>(
+//                paramFlowRulePath,
+//                paramFlowRuleListParser
+//        );
+//        ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
+//        WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<>(
+//                paramFlowRulePath,
+//                this::encodeJson
+//        );
+//        ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
+//    }
+//
+//    private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(
+//            source,
+//            new TypeReference<List<FlowRule>>() {}
+//    );
+//    private Converter<String, List<DegradeRule>> degradeRuleListParser = source -> JSON.parseObject(
+//            source,
+//            new TypeReference<List<DegradeRule>>() {}
+//    );
+//    private Converter<String, List<SystemRule>> systemRuleListParser = source -> JSON.parseObject(
+//            source,
+//            new TypeReference<List<SystemRule>>() {}
+//    );
+//
+//    private Converter<String, List<AuthorityRule>> authorityRuleListParser = source -> JSON.parseObject(
+//            source,
+//            new TypeReference<List<AuthorityRule>>() {}
+//    );
+//
+//    private Converter<String, List<ParamFlowRule>> paramFlowRuleListParser = source -> JSON.parseObject(
+//            source,
+//            new TypeReference<List<ParamFlowRule>>() {}
+//    );
+//
+//    private void mkdirIfNotExits(String filePath) throws IOException {
+//        File file = new File(filePath);
+//        if (!file.exists()) {
+//            file.mkdirs();
+//        }
+//    }
+//
+//    private void createFileIfNotExits(String filePath) throws IOException {
+//        File file = new File(filePath);
+//        if (!file.exists()) {
+//            file.createNewFile();
+//        }
+//    }
+//
+//    private <T> String encodeJson(T t) {
+//        return JSON.toJSONString(t);
+//    }
+//}

+ 57 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/config/SentinelUrlBlockHandler.java

@@ -0,0 +1,57 @@
+//package com.fdkankan.web.config;
+//
+//import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
+//import com.alibaba.csp.sentinel.slots.block.BlockException;
+//import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
+//import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
+//import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
+//import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
+//import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
+//import com.fasterxml.jackson.databind.ObjectMapper;
+//import com.fdkankan.common.response.ResultData;
+//import lombok.extern.slf4j.Slf4j;
+//import org.springframework.http.MediaType;
+//import org.springframework.stereotype.Component;
+//
+//import javax.servlet.http.HttpServletRequest;
+//import javax.servlet.http.HttpServletResponse;
+//import java.io.IOException;
+//
+///**
+// * <p>
+// * sentinel统一异常处理
+// * </p>
+// *
+// * @author dengsixing
+// * @since 2022/1/26
+// **/
+//@Slf4j
+//@Component
+//public class SentinelUrlBlockHandler implements BlockExceptionHandler {
+//    @Override
+//    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws IOException {
+//        String msg = null;
+//        if (e instanceof FlowException) {
+//            msg = "限流了";
+//        } else if (e instanceof DegradeException) {
+//            msg = "降级了";
+//        } else if (e instanceof ParamFlowException) {
+//            msg = "热点参数限流";
+//        } else if (e instanceof SystemBlockException) {
+//            msg = "系统规则(负载/...不满足要求)";
+//        } else if (e instanceof AuthorityException) {
+//            msg = "授权规则不通过";
+//        }
+//        // http状态码
+//        response.setStatus(500);
+//        response.setCharacterEncoding("utf-8");
+//        response.setHeader("Content-Type", "application/json;charset=utf-8");
+//        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+//
+//        new ObjectMapper()
+//                .writeValue(
+//                        response.getWriter(),
+//                        ResultData.error(-1, msg)
+//                );
+//    }
+//}

+ 110 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/config/ShiroConfig.java

@@ -0,0 +1,110 @@
+package com.fdkankan.web.config;
+
+import com.fdkankan.common.constant.LoginType;
+import com.fdkankan.web.realm.AppJwtRealm;
+import com.fdkankan.web.realm.ManagerJwtRealm;
+import com.fdkankan.web.realm.UserJwtRealm;
+import com.fdkankan.web.constant.FilterConstant;
+import com.fdkankan.web.jwt.JwtFilter;
+import com.fdkankan.web.realm.AgentJwtRealm;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.Filter;
+import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
+import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
+import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
+import org.apache.shiro.mgt.DefaultSubjectDAO;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
+import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ShiroConfig {
+
+    @Autowired
+    ModularRealmAuthenticator modularRealmAuthenticator;
+    @Autowired
+    private UserJwtRealm userJwtRealm;
+    @Autowired
+    private AgentJwtRealm agentJwtRealm;
+    @Autowired
+    private ManagerJwtRealm managerJwtRealm;
+    @Autowired
+    private AppJwtRealm appJwtRealm;
+
+    @Bean("shiroFilter")
+    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
+         ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
+        shiroFilterFactoryBean.setSecurityManager(securityManager);
+        //拦截器
+        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
+        // 配置不会被拦截的链接 顺序判断
+//        filterChainDefinitionMap.put("/**", "anon");
+
+        // 添加自己的过滤器并且取名为jwt
+        Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
+        filterMap.put("user_jwt", new JwtFilter(LoginType.USER.code()));
+        filterMap.put("manager_jwt", new JwtFilter(LoginType.MANAGER.code()));
+        filterMap.put("agent_jwt", new JwtFilter(LoginType.AGENT.code()));
+        filterMap.put("app_jwt", new JwtFilter(LoginType.APP.code()));
+        shiroFilterFactoryBean.setFilters(filterMap);
+        //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边
+        filterChainDefinitionMap.put(FilterConstant.FILTER_USER_URL + "/**", "user_jwt");
+        filterChainDefinitionMap.put(FilterConstant.FILTER_SCENE_URL + "/**", "user_jwt");
+        filterChainDefinitionMap.put(FilterConstant.FILTER_DEVICE_URL + "/**", "user_jwt");
+        filterChainDefinitionMap.put(FilterConstant.FILTER_PAY_URL + "/**", "user_jwt");
+        filterChainDefinitionMap.put(FilterConstant.FILTER_MANAGER_URL + "/**", "manager_jwt");
+        filterChainDefinitionMap.put(FilterConstant.FILTER_AGENT_URL + "/**", "agent_jwt");
+        filterChainDefinitionMap.put(FilterConstant.FILTER_APP_URL + "/**", "app_jwt");
+        //未授权界面;
+        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
+        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
+
+        return shiroFilterFactoryBean;
+    }
+
+    @Bean("securityManager")
+    public SecurityManager securityManager() {
+        List<Realm> realms = new ArrayList<>();
+        realms.add(userJwtRealm);
+        realms.add(agentJwtRealm);
+        realms.add(managerJwtRealm);
+        realms.add(appJwtRealm);
+
+        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
+        //设置realm.
+        securityManager.setAuthenticator(modularRealmAuthenticator);
+        securityManager.setRealms(realms);
+        /*
+         * 关闭shiro自带的session,详情见文档
+         * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
+         */
+        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
+        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
+        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
+        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
+        securityManager.setSubjectDAO(subjectDAO);
+
+        return securityManager;
+    }
+
+    @Bean
+    public ModularRealmAuthenticator modularRealmAuthenticator(){
+        //自己重写的ShiroModularRealmAuthenticator
+        ShiroModularRealmAuthenticator modularRealmAuthenticator = new ShiroModularRealmAuthenticator();
+        modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
+        return modularRealmAuthenticator;
+    }
+
+
+
+
+
+}

+ 40 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/config/ShiroModularRealmAuthenticator.java

@@ -0,0 +1,40 @@
+package com.fdkankan.web.config;
+
+import com.fdkankan.web.jwt.JwtToken;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
+import org.apache.shiro.realm.Realm;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class ShiroModularRealmAuthenticator extends ModularRealmAuthenticator {
+
+    @Override
+    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
+            throws AuthenticationException {
+        // 判断getRealms()是否返回为空
+        assertRealmsConfigured();
+        // 强制转换回自定义的CustomizedToken
+        JwtToken customizedToken = (JwtToken) authenticationToken;
+        // 登录类型
+        String loginType = customizedToken.getLoginType();
+        // 所有Realm
+        Collection<Realm> realms = getRealms();
+        // 登录类型对应的所有Realm
+        Collection<Realm> typeRealms = new ArrayList<>();
+        for (Realm realm : realms) {
+            if (realm.getName().contains(loginType))
+                typeRealms.add(realm);
+        }
+
+        // 判断是单Realm还是多Realm
+        if (typeRealms.size() == 1)
+            return doSingleRealmAuthentication(typeRealms.iterator().next(), customizedToken);
+        else
+            return doMultiRealmAuthentication(typeRealms, customizedToken);
+    }
+
+}

+ 23 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/config/SupportAutoConfiguration.java

@@ -0,0 +1,23 @@
+//package common.config;
+//
+//
+//import feign.codec.Decoder;
+//import feign.optionals.OptionalDecoder;
+//import org.springframework.beans.factory.ObjectFactory;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
+//import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
+//import org.springframework.cloud.openfeign.support.SpringDecoder;
+//import org.springframework.context.annotation.Bean;
+//import org.springframework.context.annotation.Configuration;
+//
+//@Configuration
+//public class SupportAutoConfiguration {
+//    @Autowired
+//    private ObjectFactory<HttpMessageConverters> messageConverters;
+//
+//    @Bean
+//    public Decoder feignDecoder() {
+//        return new ResultStatusDecoder(new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters))));
+//    }
+//}

+ 50 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/constant/CameraTypeEnum.java

@@ -0,0 +1,50 @@
+package com.fdkankan.web.constant;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum CameraTypeEnum {
+    DOUBLE_EYE(0,"KK-","4DKKLITE_","旧双目相机"),
+    FDKK_PRO(1,"KK-","4DKKPRO_","四维看看pro八目相机"),
+    FDKK_LITE(2,"KK-","4DKKLITE_","四维看看lite"),
+    ZHIHOUSE_REDHOUSE(5,"KK-","4DKKLITE_","指房宝小红屋相机"),
+    DOUBLE_EYE_TURN(9,"KJ-","4DKKLITE_","双目转台"),
+    LASER_TURN(10,"SS-","4DKKLA_","激光转台");
+
+
+    private int type;
+    private String sceneNumPrefix;
+    private String wifiNamePrefix;
+    private String desc;
+
+    public int getType() {
+        return type;
+    }
+
+    public String getSceneNumPrefix() {
+        return sceneNumPrefix;
+    }
+
+    static Map<Integer,CameraTypeEnum> typeMaps = new HashMap<>(5);
+
+    static{
+        for (CameraTypeEnum typeEnum : CameraTypeEnum.values()) {
+            typeMaps.put(typeEnum.getType(),typeEnum);
+        }
+    }
+
+    CameraTypeEnum(Integer type, String sceneNumPrefix, String wifiNamePrefix, String desc) {
+        this.type = type;
+        this.sceneNumPrefix = sceneNumPrefix;
+        this.wifiNamePrefix = wifiNamePrefix;
+        this.desc = desc;
+    }
+
+    public static String getSceneNumPrefixByType(Integer type){
+        if(typeMaps.containsKey(type)){
+            return typeMaps.get(type).getSceneNumPrefix();
+        }
+        return "";
+    }
+
+}

+ 26 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/constant/FilterConstant.java

@@ -0,0 +1,26 @@
+package com.fdkankan.web.constant;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FilterConstant {
+
+    public static final String FILTER_USER_URL = "/service/user";
+    public static final String FILTER_SCENE_URL = "/service/scene/edit";
+    public static final String FILTER_DEVICE_URL = "/service/device";
+    public static final String FILTER_PAY_URL = "/service/order/scanPay";
+    public static final String FILTER_MANAGER_URL = "/service/manager";
+    public static final String FILTER_AGENT_URL = "/service/agent";
+    public static final String FILTER_APP_URL = "/service/app";
+
+    public static List<String> getFilterPattern(){
+        List<String> arr = new ArrayList<>();
+        arr.add(FILTER_USER_URL);
+        arr.add(FILTER_SCENE_URL);
+        arr.add(FILTER_DEVICE_URL);
+        arr.add(FILTER_PAY_URL);
+//        arr.add(FILTER_MANAGER_URL);
+//        arr.add(FILTER_AGENT_URL);
+        return arr;
+    }
+}

+ 78 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/controller/BaseController.java

@@ -0,0 +1,78 @@
+package com.fdkankan.web.controller;
+
+import com.fdkankan.common.util.DateEditor;
+import com.fdkankan.web.user.SSOLoginHelper;
+import com.fdkankan.web.user.SSOUser;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Date;
+import java.util.Objects;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.propertyeditors.StringTrimmerEditor;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
+
+public class BaseController {
+    @Autowired
+    protected HttpServletRequest request;
+
+    @Autowired
+    protected HttpServletResponse response;
+
+    @Autowired
+    SSOLoginHelper ssoLoginHelper;
+
+    @InitBinder
+    protected void initBinder(WebDataBinder webDataBinder) {
+        webDataBinder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
+        webDataBinder.registerCustomEditor(Date.class, new DateEditor(true));
+    }
+
+    protected String getToken(){
+        return request.getHeader("token");
+    }
+
+    protected Long getUserId(){
+        SSOUser ssoUser = ssoLoginHelper.loginCheck(request.getHeader("token"));
+        if(Objects.nonNull(ssoUser)){
+            return ssoUser.getId();
+        }
+        return null;
+    }
+
+    protected SSOUser getSsoUser(){
+        return ssoLoginHelper.loginCheck(request.getHeader("token"));
+    }
+
+    protected com.fdkankan.web.model.SSOUser getSsoUserV3(){
+        return ssoLoginHelper.loginCheckV3(request.getHeader("token"));
+    }
+
+    public static void output(HttpServletResponse resp, File file) {
+        OutputStream os = null;
+        BufferedInputStream bis = null;
+        byte[] buff = new byte[1024];
+        try {
+            os = resp.getOutputStream();
+            bis = new BufferedInputStream(new FileInputStream(file));
+            int i = 0;
+            while ((i = bis.read(buff)) != -1) {
+                os.write(buff, 0, i);
+                os.flush();
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                bis.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+}

+ 50 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/controller/CustomErrorController.java

@@ -0,0 +1,50 @@
+package com.fdkankan.web.controller;
+
+import com.fdkankan.common.response.ResultData;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.boot.web.servlet.error.ErrorController;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+/**
+ * <p>
+ * 404 500 401等错误返回转发接口,统一返回格式
+ * </p>
+ *
+ * @author dengsixing
+ * @since 2022/3/9
+ **/
+@Controller
+public class CustomErrorController implements ErrorController {
+
+    @RequestMapping("/error")
+    @ResponseBody
+    public ResultData handleError(HttpServletRequest request, HttpServletResponse response){
+
+        //获取statusCode:401,404,500
+        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
+        String message = "";
+        HttpStatus httpStatus = HttpStatus.valueOf(statusCode);
+        switch (httpStatus){
+            case INTERNAL_SERVER_ERROR :
+                message = "服务器内部异常!";
+                break;
+            case NOT_FOUND:
+                message = "接口不存在!";
+                break;
+            case FORBIDDEN:
+                message = "禁止访问!";
+                break;
+        }
+        return ResultData.error(statusCode, message);
+    }
+
+
+    @Override
+    public String getErrorPath() {
+        return "/error";
+    }
+}

+ 138 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/exception/GlobalExceptionHandler.java

@@ -0,0 +1,138 @@
+package com.fdkankan.web.exception;
+
+import cn.hutool.core.exceptions.ExceptionUtil;
+import com.fdkankan.common.constant.ErrorCode;
+import com.fdkankan.common.constant.ServerCode;
+import com.fdkankan.common.exception.BusinessException;
+import com.fdkankan.common.response.ResultData;
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.BindException;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.FieldError;
+import org.springframework.validation.ObjectError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.MissingServletRequestParameterException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.multipart.MultipartException;
+
+/**
+ * 全局异常处理器
+ */
+@RestControllerAdvice
+@Slf4j
+public class GlobalExceptionHandler {
+
+    /**
+     * 处理未知异常
+     */
+    @ResponseBody
+    @ExceptionHandler(value = Exception.class)
+    public ResultData exceptionHandler(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Exception e) throws Exception {
+            log.error("服务错误:", e);
+            //增加sentinel异常次数
+//            Tracer.trace(e);
+            return ResultData.error(ServerCode.SYSTEM_ERROR.code(), ExceptionUtil.stacktraceToString(e, 3000));
+    }
+
+    /**
+     * 限流熔断异常处理
+     * 如果在这里捕获的话,就需要在接口中加入@SentinelResour注解
+     */
+//    @ResponseBody
+//    @ExceptionHandler({FlowException.class, DegradeException.class})
+//    public ResultData flowExceptionHandler(HttpServletRequest httpServletRequest, Exception e) {
+//        String requestURI = httpServletRequest.getRequestURI();
+//        String resource = null;
+//        if(e instanceof FlowException){
+//            resource = ((FlowException) e).getRule().getResource();
+//        }
+//        if(e instanceof DegradeException){
+//            resource = ((DegradeException) e).getRule().getResource();
+//        }
+//
+//        log.error("请求限流:requestURI{},resource:{}", requestURI, resource);
+//        return ResultData.error(ServerCode.SYSTEM_ERROR.code(), "系统繁忙,请稍后重试");
+//    }
+
+    /**
+     * <p>
+            form表达提交,缺少参数异常处理
+     * </p>
+     * @author dengsixing
+     * @date 2022/4/14
+     * @param e
+     * @return com.fdkankan.common.response.ResultData
+     **/
+    @ResponseBody
+    @ExceptionHandler(value = MissingServletRequestParameterException.class)
+    public ResultData missingServletRequestParameterException(
+        MissingServletRequestParameterException e){
+        return ResultData.error(
+            ServerCode.PARAM_REQUIRED.code(),
+            ServerCode.PARAM_REQUIRED.formatMessage(e.getParameterName()));
+    }
+
+    /**
+     * <p>
+     form表达提交,文件为空异常处理
+     * </p>
+     * @author dengsixing
+     * @date 2022/4/14
+     * @param e
+     * @return com.fdkankan.common.response.ResultData
+     **/
+    @ResponseBody
+    @ExceptionHandler(value = MultipartException.class)
+    public ResultData multipartException(MultipartException e){
+        return ResultData.error(
+            ServerCode.PARAM_REQUIRED.code(),
+            ServerCode.PARAM_REQUIRED.formatMessage("文件"));
+    }
+
+
+    /**
+     * 参数校验异常拦截处理
+     */
+    @ResponseBody
+    @ExceptionHandler({BindException.class,MethodArgumentNotValidException.class})
+    public ResultData validationBodyException(Exception e) {
+        BindingResult result = null;
+        if(e instanceof MethodArgumentNotValidException){
+            result = ((MethodArgumentNotValidException) e).getBindingResult();
+        }
+        if(e instanceof BindException){
+            result = ((BindException) e).getBindingResult();
+        }
+        String message = "";
+        if (result.hasErrors()) {
+            List<ObjectError> errors = result.getAllErrors();
+            if (errors != null) {
+                errors.forEach(p -> {
+                    FieldError fieldError = (FieldError) p;
+                    log.error("参数校验错误:类:{},字段:{},错误信息:{}",fieldError.getObjectName(), fieldError.getField(), fieldError.getDefaultMessage());
+                });
+                if (errors.size() > 0) {
+                    FieldError fieldError = (FieldError) errors.get(0);
+                    message = fieldError.getDefaultMessage();
+                }
+            }
+        }
+        return ResultData.error(ErrorCode.PARAM_REQUIRED.code(), message);
+    }
+
+
+    /**
+     * 处理业务异常
+     */
+    @ResponseBody
+    @ExceptionHandler(value = BusinessException.class)
+    public ResultData businessExceptionHandler(HttpServletRequest httpServletRequest, BusinessException e) {
+        log.info("业务异常code:{},message:{}", e.getCode(), e.getMessage());
+        return ResultData.error(e.getCode(), e.getMessage());
+    }
+}

+ 34 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/exception/JwtAuthenticationException.java

@@ -0,0 +1,34 @@
+package com.fdkankan.web.exception;
+
+import org.apache.shiro.ShiroException;
+
+public class JwtAuthenticationException extends ShiroException {
+
+    private static final long serialVersionUID = 2899335020273674736L;
+
+    private int code;
+
+    private String msg;
+
+    public JwtAuthenticationException(int code, String msg){
+        super(msg);
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+}

+ 10 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/factory/LogFactory.java

@@ -0,0 +1,10 @@
+package com.fdkankan.web.factory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LogFactory {
+    public static final Logger V_LOG = LoggerFactory.getLogger("visitLog");
+    public static final Logger P_LOG = LoggerFactory.getLogger("programLog");
+
+}

+ 113 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/jwt/JwtFilter.java

@@ -0,0 +1,113 @@
+package com.fdkankan.web.jwt;
+
+import com.alibaba.fastjson.JSON;
+import com.fdkankan.common.exception.BusinessException;
+import com.fdkankan.common.response.ResultData;
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+public class JwtFilter extends BasicHttpAuthenticationFilter {
+    private static Logger log = LoggerFactory.getLogger("programLog");
+
+    private String loginType;
+
+    public JwtFilter(String loginType){
+        this.loginType = loginType;
+    }
+
+    /**
+     * 执行登录认证
+     *
+     * @param request
+     * @param response
+     * @param mappedValue
+     * @return
+     */
+    @Override
+    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
+        try {
+            log.info("Step 1: 接口拦截shiro认证权限");
+            executeLogin(request, response);
+            String url = ((HttpServletRequest) request).getRequestURI();
+//            getSubject(request, response).checkPermission(url);
+            return true;
+        } catch (Exception e) {
+            // 认证出现异常,传递错误信息msg
+            String msg = e.getMessage();
+            // 获取应用异常(该Cause是导致抛出此throwable(异常)的throwable(异常))
+            Throwable throwable = e.getCause();
+            ResultData resultData = ResultData.error(3002, msg);
+            if (throwable instanceof BusinessException) {
+                resultData.setCode(((BusinessException) throwable).getCode());
+                resultData.setMessage(((BusinessException)throwable).getMessage());
+            }
+            // 直接返回Response信息
+            this.response(response, resultData);
+            return false;
+        }
+    }
+
+    /**
+     *
+     */
+    @Override
+    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
+        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+        String token = httpServletRequest.getHeader("token");
+
+        JwtToken jwtToken = new JwtToken(token, loginType);
+        // 提交给realm进行登入,如果错误他会抛出异常并被捕获
+        getSubject(request, response).login(jwtToken);
+        // 如果没有抛出异常则代表登入成功,返回true
+        return true;
+    }
+
+    /**
+     * 对跨域提供支持
+     */
+    @Override
+    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
+        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
+        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
+        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
+        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
+        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
+        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
+            httpServletResponse.setStatus(HttpStatus.OK.value());
+            return false;
+        }
+        return super.preHandle(request, response);
+    }
+
+    /**
+     * 无需转发,直接返回Response信息
+     */
+    private void response(ServletResponse response, ResultData resultData) {
+        response.setCharacterEncoding("UTF-8");
+        response.setContentType("application/json; charset=utf-8");
+        PrintWriter out = null;
+        try {
+            out = response.getWriter();
+            out.append(JSON.toJSONString(resultData));
+            out.flush();
+            out.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            if (out != null){
+                out.close();
+            }
+        }
+    }
+
+}

+ 33 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/jwt/JwtToken.java

@@ -0,0 +1,33 @@
+package com.fdkankan.web.jwt;
+
+import org.apache.shiro.authc.AuthenticationToken;
+
+public class JwtToken implements AuthenticationToken {
+
+    private String token;
+
+    private String loginType;
+
+    public JwtToken(String token, String loginType){
+        this.token = token;
+        this.loginType = loginType;
+    }
+
+    @Override
+    public Object getPrincipal() {
+        return token;
+    }
+
+    @Override
+    public Object getCredentials() {
+        return token;
+    }
+
+    public String getLoginType() {
+        return loginType;
+    }
+
+    public void setLoginType(String loginType) {
+        this.loginType = loginType;
+    }
+}

+ 37 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/model/SSOUser.java

@@ -0,0 +1,37 @@
+package com.fdkankan.web.model;
+
+import java.io.Serializable;
+import java.util.Set;
+import lombok.Data;
+
+/**
+ * sso user
+ *
+ *  2018-04-02 19:59:49
+ */
+@Data
+public class SSOUser implements Serializable {
+
+    private static final long serialVersionUID = -2560069033053679931L;
+
+    private Long id;
+
+    private String userName;
+
+    private String password;
+
+    private String email;
+
+    private Set<String> permissionSet;
+
+    private Set<String> roleSet;
+
+    /**
+     * 是否相机登录,0-否,1-是
+     */
+    private Integer cameraLogin;
+
+    private Long cameraId;
+
+
+}

+ 60 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/realm/AgentJwtRealm.java

@@ -0,0 +1,60 @@
+package com.fdkankan.web.realm;
+
+import cn.hutool.core.util.StrUtil;
+import com.fdkankan.common.util.JwtUtil;
+import com.fdkankan.web.exception.JwtAuthenticationException;
+import com.fdkankan.web.jwt.JwtToken;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AgentJwtRealm extends AuthorizingRealm {
+    private static Logger log = LoggerFactory.getLogger("programLog");
+
+    @Override
+    public boolean supports(AuthenticationToken token) {
+        return token instanceof JwtToken;
+    }
+
+    @Override
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
+        return simpleAuthorizationInfo;
+    }
+
+    @Override
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
+        log.info("Step 2: Agent进行用户名正确与否验证");
+        String token = (String) auth.getCredentials();
+        if (StrUtil.isEmpty(token)){
+            throw new JwtAuthenticationException(3004, "无token,请重新登录");
+        }
+        // 获取 token 中的 userName
+        String userName = JwtUtil.getUsername(token);
+        if (StrUtil.isEmpty(userName)){
+            throw new JwtAuthenticationException(3004, "访问异常!");
+        }
+//        if (!userName.contains(SsoUtil.PREFIX_CACHE_AGENT)){
+//            throw new JwtAuthenticationException(3004, "用户未登录");
+//        }
+        if (!JwtUtil.isVerify(token, userName)) {
+            throw new JwtAuthenticationException(3004, "非法访问!");
+        }
+//        // 防止重复登陆, redisToken是最新登录获取的token
+//        String redisToken = JedisUtil.getStringValue(userName);
+//        if (StrUtil.isEmpty(redisToken) || !redisToken.equals(token)){
+//            throw new JwtAuthenticationException(3004, "用户未登录");
+//        }
+
+        return new SimpleAuthenticationInfo(token, token, "jwt_realm");
+    }
+}

+ 62 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/realm/AppJwtRealm.java

@@ -0,0 +1,62 @@
+package com.fdkankan.web.realm;
+
+import cn.hutool.core.util.StrUtil;
+import com.fdkankan.web.exception.JwtAuthenticationException;
+import com.fdkankan.web.jwt.JwtToken;
+import com.fdkankan.common.util.JwtUtil;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AppJwtRealm extends AuthorizingRealm {
+    private static Logger log = LoggerFactory.getLogger("programLog");
+
+    @Override
+    public boolean supports(AuthenticationToken token) {
+        return token instanceof JwtToken;
+    }
+
+    @Override
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
+        return simpleAuthorizationInfo;
+    }
+
+    @Override
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
+        log.info("Step 2: Agent进行用户名正确与否验证");
+        String token = (String) auth.getCredentials();
+        log.info("doGetAuthenticationInfo - step3 :" + token);
+        if (StrUtil.isEmpty(token)){
+            throw new JwtAuthenticationException(3004, "无token,请重新登录");
+        }
+        // 获取 token 中的 userName
+        String userName = JwtUtil.getUsername(token);
+        if (StrUtil.isEmpty(userName)){
+            throw new JwtAuthenticationException(3004, "访问异常!");
+        }
+        String regex = "^-?\\d+(\\.\\d+)?$";
+        if(userName.matches(regex)){
+            // TODO: 2021/12/21
+//            if (!JedisUtil.exists(SsoUtil.PREFIX_CACHE_CAMERA + userName)){
+//                throw new JwtAuthenticationException(3004, "用户未登录");
+//            }
+//            if (!JedisUtil.getStringValue(SsoUtil.PREFIX_CACHE_CAMERA + userName).contains(token)){
+//                throw new JwtAuthenticationException(3004, "用户未登录");
+//            }
+        }
+// TODO: 2021/12/21
+//        JedisUtil.expire(SsoUtil.PREFIX_CACHE_CAMERA + userName, 21600);
+
+        return new SimpleAuthenticationInfo(token, token, "jwt_realm");
+    }
+}

+ 61 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/realm/ManagerJwtRealm.java

@@ -0,0 +1,61 @@
+package com.fdkankan.web.realm;
+
+import cn.hutool.core.util.StrUtil;
+import com.fdkankan.web.exception.JwtAuthenticationException;
+import com.fdkankan.web.jwt.JwtToken;
+import com.fdkankan.common.util.JwtUtil;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ManagerJwtRealm extends AuthorizingRealm {
+    private static Logger log = LoggerFactory.getLogger("programLog");
+
+    @Override
+    public boolean supports(AuthenticationToken token) {
+        return token instanceof JwtToken;
+    }
+
+    @Override
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
+        return simpleAuthorizationInfo;
+    }
+
+    @Override
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
+        log.info("Step 2: Manager进行用户名正确与否验证");
+        String token = (String) auth.getCredentials();
+        if (StrUtil.isEmpty(token)){
+            throw new JwtAuthenticationException(3004, "无token,请重新登录");
+        }
+        // 获取 token 中的 userName
+        String userName = JwtUtil.getUsername(token);
+        if (StrUtil.isEmpty(userName)){
+            throw new JwtAuthenticationException(3004, "访问异常!");
+        }
+//        if (!userName.contains(SsoUtil.PREFIX_CACHE_MANAGER)){
+//            throw new JwtAuthenticationException(3004, "用户未登录");
+//        }
+        if (!JwtUtil.isVerify(token, userName)) {
+            throw new JwtAuthenticationException(3004, "非法访问!");
+        }
+        // 防止重复登陆, redisToken是最新登录获取的token
+        // TODO: 2021/12/21  
+//        String redisToken = JedisUtil.getStringValue(userName);
+//        if (StrUtil.isEmpty(redisToken) || !redisToken.equals(token)){
+//            throw new JwtAuthenticationException(3004, "用户未登录");
+//        }
+
+        return new SimpleAuthenticationInfo(token, token, "jwt_realm");
+    }
+}

+ 90 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/realm/UserJwtRealm.java

@@ -0,0 +1,90 @@
+package com.fdkankan.web.realm;
+
+import cn.hutool.core.util.StrUtil;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fdkankan.common.constant.ErrorCode;
+import com.fdkankan.common.exception.BusinessException;
+import com.fdkankan.common.util.JwtUtil;
+import com.fdkankan.web.exception.JwtAuthenticationException;
+import com.fdkankan.web.jwt.JwtToken;
+import com.fdkankan.web.user.SSOLoginHelper;
+import com.fdkankan.web.user.SSOUser;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class UserJwtRealm extends AuthorizingRealm {
+    private static Logger log = LoggerFactory.getLogger("programLog");
+
+//    @Autowired
+//    private UserFeignClient userService;
+    @Autowired
+    private ObjectMapper mapper;
+
+    @Autowired
+    private SSOLoginHelper ssoLoginHelper;
+
+    /**
+     * 必须重写此方法,不然Shiro会报错
+     */
+    @Override
+    public boolean supports(AuthenticationToken token) {
+        return token instanceof JwtToken;
+    }
+    /**
+     * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的
+     */
+    @Override
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
+        String username = (String)principals.getPrimaryPrincipal();
+//        List list = principals.asList();
+//        Result result = userService.findByUserName(username);
+//        if (result.getCode() == Result.CODE_FAILURE){
+//            return authorizationInfo;
+//        }
+//        SSOUser dbUser = mapper.convertValue(result.getData(), SSOUser.class);
+//        authorizationInfo.setRoles(dbUser.getRoleSet());
+//        authorizationInfo.setStringPermissions(dbUser.getPermissionSet());
+        return authorizationInfo;
+    }
+    /**
+     * 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。
+     */
+    @Override
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
+        log.info("Step 2: User进行用户名正确与否验证");
+        String token = (String) auth.getCredentials();
+        if (StrUtil.isEmpty(token)){
+            throw new JwtAuthenticationException(3004, "无token,请重新登录");
+        }
+        // 解密获得username,用于和数据库进行对比
+        String username = JwtUtil.getUsername(token);
+        if (username == null) {
+            throw new BusinessException(ErrorCode.TOKEN_ILLEGAL);
+        }
+        if (!JwtUtil.isVerify(token, username)) {
+            throw new BusinessException(ErrorCode.TOKEN_ILLEGAL);
+        }
+        // TODO: 2021/12/21
+        SSOUser ssoUser = this.ssoLoginHelper.loginCheck(token);
+        if (ssoUser == null){
+            throw new BusinessException(ErrorCode.TOKEN_NOT_FOUND);
+        }
+//
+//        // refresh
+        // TODO: 2021/12/21
+//        JedisUtil.expire(token, 21600);
+        return new SimpleAuthenticationInfo(username, token, "jwt_realm");
+    }
+}

+ 54 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/response/BaseResponseAdvice.java

@@ -0,0 +1,54 @@
+package com.fdkankan.web.response;
+
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.fdkankan.common.response.PageInfo;
+import com.fdkankan.common.response.ResultData;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.MethodParameter;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
+
+@RestControllerAdvice
+@Slf4j
+public class BaseResponseAdvice implements ResponseBodyAdvice<Object> {
+    @Override
+    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
+        return true;
+    }
+
+    @Override
+    public Object beforeBodyWrite(Object body, MethodParameter methodParameter,
+        MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass,
+        ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
+//        if(serverHttpRequest.getURI().getPath().contains("feign")){
+//            return body;
+//        }
+//        log.info("响应拦截成功。。。");
+//        List<String> strings = serverHttpRequest.getHeaders().get("Accept-Language");
+//        System.out.println(strings.get(0));
+
+        //admin监控请求,直接返回
+        if(serverHttpRequest.getURI().getPath().contains("actuator")){
+            return body;
+        }
+
+        if(body instanceof String){
+            return JSONObject.toJSONString(ResultData.ok(body));
+        }
+        if(body instanceof Page){
+            Page page = (Page) body;
+            return ResultData.ok(new PageInfo(page.getCurrent(), page.getSize(), page.getTotal(), page.getRecords()));
+        }
+        if (body instanceof ResultData) {
+            return body;
+        } else {
+            return ResultData.ok(body);
+        }
+    }
+}

+ 73 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/user/SSOLoginHelper.java

@@ -0,0 +1,73 @@
+package com.fdkankan.web.user;
+
+import com.fdkankan.common.constant.LoginType;
+import com.fdkankan.common.util.JwtUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SSOLoginHelper {
+
+    @Autowired
+    private SSOLoginStore ssoLoginStore;
+
+    public SSOUser loginCheck(String token) {
+        if (token != null && token.trim().length() > 0) {
+            String username = JwtUtil.getUsername(token);
+            SSOUser ssoUser = ssoLoginStore.get(username);
+            if (ssoUser != null) {
+                return ssoUser;
+            }
+        }
+        return null;
+    }
+
+    public com.fdkankan.web.model.SSOUser loginCheckV3(String token) {
+        if (token != null && token.trim().length() > 0) {
+            com.fdkankan.web.model.SSOUser ssoUser = ssoLoginStore.getV3(token);
+            if (ssoUser != null) {
+                return ssoUser;
+            }
+        }
+        return null;
+    }
+
+    public String login(String loginType,String userName) {
+        return this.login(loginType,userName,null, null);
+    }
+    public String login(String loginType,String userName,Integer time) {
+        return this.login(loginType,userName,time, null);
+    }
+    public String login(SSOUser ssoUser) {
+        String loginType = LoginType.USER.code();
+        String userName = ssoUser.getUserName();
+        return this.login(loginType,userName,null, ssoUser);
+    }
+    public String login(SSOUser ssoUser,Integer time) {
+        String loginType = LoginType.USER.code();
+        String userName = ssoUser.getUserName();
+        return this.login(loginType,userName,time, ssoUser);
+    }
+
+    /**
+     * @param loginType     登录类型
+     * @param userName      登录用户名
+     * @param time          过期时间,时间单位为秒,默认为 6小时 21600
+     * @param ssoUser       sooUser
+     */
+    public String login(String loginType,String userName,Integer time,
+        SSOUser ssoUser) {
+        String token = JwtUtil.createJWT(-1, userName,loginType);
+        ssoLoginStore.put(loginType,userName,token,time, ssoUser);
+        return token;
+    }
+
+    public  void logout(String token) {
+        String username = JwtUtil.getUsername(token);
+        String loginType = JwtUtil.getLoginType(token);
+        this.logout(loginType,username);
+    }
+    public  void logout(String loginType,String userName) {
+        ssoLoginStore.remove(loginType,userName);
+    }
+}

+ 97 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/user/SSOLoginStore.java

@@ -0,0 +1,97 @@
+package com.fdkankan.web.user;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fdkankan.common.constant.LoginType;
+import com.fdkankan.redis.constant.RedisKey;
+import com.fdkankan.redis.util.RedisUtil;
+import java.util.Objects;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+@Slf4j
+@Component
+public class SSOLoginStore {
+
+    @Resource
+    private RedisUtil redisUtil;
+    /**
+     * get
+     * @param userName
+     * @return
+     */
+    public SSOUser get(String userName) {
+        String redisKey = getSsoUserRedisKey(userName);
+        String objectValue =  redisUtil.get(redisKey);
+        if (objectValue != null) {
+            return JSONObject.parseObject(objectValue, SSOUser.class);
+        }
+        return null;
+    }
+
+
+    @Resource
+    @Qualifier("redisTemplate2")
+    private RedisTemplate redisTemplate2;
+
+    public com.fdkankan.web.model.SSOUser getV3(String token) {
+        String redisKey = "token#".concat(token);
+        Object obj = redisTemplate2.opsForValue().get(redisKey);
+        if(Objects.nonNull(obj)){
+            return (com.fdkankan.web.model.SSOUser)obj;
+        }
+        return null;
+    }
+
+    /**
+     * remove
+     * @param loginType     登录类型
+     * @param userName      登录用户名
+     */
+    public  void remove(String loginType,String userName) {
+        String redisKey = getTokenRedisKey(loginType,userName);
+        if(redisUtil.hasKey(redisKey)){
+            redisUtil.del(redisKey);
+        }
+        if(LoginType.USER.code().equals(loginType)  ){
+            String ssoUserRedisKey = getSsoUserRedisKey(userName);
+            if(redisUtil.hasKey(ssoUserRedisKey)){
+                redisUtil.del(ssoUserRedisKey);
+            }
+        }
+    }
+
+    /**
+     *  put
+     * @param loginType     登录类型
+     * @param userName      登录用户名
+     * @param token         token
+     * @param time          过期时间,时间单位为秒,默认为 6小时 21600
+     * @param ssoUser       sooUser
+     */
+    public  void put(String loginType, String userName, String token,Integer time, SSOUser ssoUser) {
+        if(time == null || time <=0 ){
+            time = RedisKey.USER_EXPIRE_TIME;
+        }
+        String redisKey = getTokenRedisKey(loginType,userName);
+        redisUtil.set(redisKey, token, time);
+        if(LoginType.USER.code().equals(loginType) && ssoUser!=null ){
+            redisUtil.set(getSsoUserRedisKey(userName),JSONObject.toJSONString(ssoUser),time);
+        }
+    }
+
+    /**
+     * 获取token key
+     */
+    private static String getTokenRedisKey(String loginType,String userName){
+        return String.format(RedisKey.TOKEN_USER, loginType,userName);
+    }
+    private static String getSsoUserRedisKey(String userName){
+        return String.format(RedisKey.SSO_USER,userName);
+    }
+
+
+}

+ 38 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/user/SSOUser.java

@@ -0,0 +1,38 @@
+package com.fdkankan.web.user;
+
+import lombok.Data;
+import lombok.ToString;
+
+import java.io.Serializable;
+import java.util.Set;
+
+@Data
+@ToString
+public class SSOUser implements Serializable {
+
+    private static final long serialVersionUID = -2560069033053679931L;
+
+    private Long id;
+
+    private String userName;
+
+    private String password;
+
+    private String email;
+
+    private Set<String> permissionSet;
+
+    private Set<String> roleSet;
+
+    /**
+     * 是否相机登录,0-否,1-是
+     */
+    private Integer cameraLogin;
+
+    /**
+     * 相机登录的相机id
+     */
+    private Long cameraId;
+
+
+}

+ 21 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/util/SsoUtil.java

@@ -0,0 +1,21 @@
+package com.fdkankan.web.util;
+
+public class SsoUtil {
+
+    public static final String REDIRECT_URL = "redirect_url";
+    public static final String SSO_USER = "sso_user";
+    public static final String SSO_SERVER = "sso_server";
+    public static final String SSO_LOGIN = "/login";
+    public static final String SSO_LOGOUT = "/logout";
+    public static final String SSO_LOGOUT_PATH = "logoutPath";
+    public static final String PREFIX_SHIRO_CACHE = "shiro:cache:";
+    public static final String PREFIX_MSG_AUTH_CODE = "msg:auth:code:";
+    public static final String PREFIX_CACHE_MANAGER = "manager:";
+    public static final String PREFIX_CACHE_AGENT = "agent:";
+    public static final String PREFIX_CACHE_CAMERA = "camera:";
+    public static final String SSO_SESSIONID = "token";
+    public static final String PREFIX_CACHE_USERINFO = "user_info:";
+    public static final String PREFIX_MSG_NOT_CODE = "msg:not:code:";//短信重发验证
+
+    public static final String USER_TOKEN = "user:token:";
+}

+ 35 - 0
4dkankan-common-web/src/main/java/com/fdkankan/web/util/WebUtil.java

@@ -0,0 +1,35 @@
+package com.fdkankan.web.util;
+
+import cn.hutool.http.ContentType;
+import com.alibaba.fastjson.JSON;
+import java.util.HashMap;
+import javax.servlet.http.HttpServletRequest;
+import org.aspectj.lang.JoinPoint;
+
+/**
+ * <p>
+ * TODO
+ * </p>
+ *
+ * @author dengsixing
+ * @since 2022/6/9
+ **/
+public class WebUtil {
+
+    /**
+     * 获取请求参数中的场景码
+     * @param pjp
+     * @param request
+     * @return
+     */
+    public static String getNum(JoinPoint pjp, HttpServletRequest request){
+        Object[] args = pjp.getArgs();
+        String contentType = request.getContentType();
+        if(contentType.contains(ContentType.JSON.getValue())){
+            HashMap hashMap = JSON.parseObject(JSON.toJSONString(args[0]), HashMap.class);
+            return (String) hashMap.get("num");
+        }
+        return request.getParameter("num");
+    }
+
+}

+ 77 - 0
4dkankan-utils-pay/pom.xml

@@ -0,0 +1,77 @@
+<?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>4dkankan-utils</artifactId>
+    <groupId>com.fdkankan</groupId>
+    <version>2.0.0-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>4dkankan-utils-pay</artifactId>
+
+  <dependencies>
+
+    <dependency>
+      <groupId>org.projectlombok</groupId>
+      <artifactId>lombok</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>com.alipay</groupId>
+      <artifactId>alipay-sdk-java</artifactId>
+      <version>20170324180803</version>
+    </dependency>
+    <dependency>
+      <groupId>com.alipay</groupId>
+      <artifactId>alipay-trade-sdk</artifactId>
+      <version>20161215</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-context</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>com.google.zxing</groupId>
+      <artifactId>core</artifactId>
+      <version>2.1</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.code.gson</groupId>
+      <artifactId>gson</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-configuration</groupId>
+      <artifactId>commons-configuration</artifactId>
+      <version>1.10</version>
+    </dependency>
+
+    <dependency>
+      <groupId>com.paypal.sdk</groupId>
+      <artifactId>rest-api-sdk</artifactId>
+      <version>1.14.0</version>
+    </dependency>
+
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-tx</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.alibaba</groupId>
+      <artifactId>fastjson</artifactId>
+    </dependency>
+  </dependencies>
+
+
+</project>

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 92 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/AlipayDefaultConfig.java


+ 81 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipayConfig.java

@@ -0,0 +1,81 @@
+package com.fdkankan.pay.alipay.sdk;
+
+import com.alipay.demo.trade.service.AlipayTradeService;
+import com.alipay.demo.trade.service.impl.AlipayTradeServiceImpl;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+
+@Component
+public abstract class AlipayConfig {
+
+    /** 签名类型 */
+    private String signType = "RSA2";
+    /** 格式 */
+    private String formate = "json";
+    /** 编码 */
+    private String charset = "UTF-8";
+    /** 最大查询次数 */
+    private int maxQueryRetry = 5;
+    /** 查询间隔(毫秒) */
+    private long queryDuration = 5000;
+    /** 最大撤销次数 */
+    private int maxCancelRetry = 3;
+    /** 撤销间隔(毫秒) */
+    private long cancelDuration = 3000;
+
+    /** 支付宝gatewayUrl */
+    abstract public String getGatewayUrl();
+
+    /** 商户应用id */
+    abstract public String getAppid();
+
+    /** RSA私钥,用于对商户请求报文加签 */
+    abstract public String getAppPrivateKey();
+
+    /** 支付宝RSA公钥,用于验签支付宝应答 */
+    abstract public String getAlipayPublicKey();
+
+    /** 同步地址 */
+    abstract public String getReturnUrl();
+    /** 异步地址 */
+    abstract public String getNotifyUrl();
+
+    public String getSignType(){
+        return signType;
+    }
+
+    public String getFormate(){
+        return formate;
+    }
+
+    public String getCharset(){
+        return charset;
+    }
+
+    public int getMaxQueryRetry() {
+        return maxQueryRetry;
+    }
+
+    public long getQueryDuration() {
+        return queryDuration;
+    }
+
+    public int getMaxCancelRetry() {
+        return maxCancelRetry;
+    }
+
+    public long getCancelDuration() {
+        return cancelDuration;
+    }
+
+    @Bean
+    public AlipayTradeService alipayTradeService() {
+        return new AlipayTradeServiceImpl.ClientBuilder()
+                .setGatewayUrl(getGatewayUrl())
+                .setAppid(getAppid())
+                .setPrivateKey(getAppPrivateKey())
+                .setAlipayPublicKey(getAlipayPublicKey())
+                .setSignType(getSignType())
+                .build();
+    }
+}

+ 60 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipayGoodsDetail.java

@@ -0,0 +1,60 @@
+package com.fdkankan.pay.alipay.sdk;
+
+import java.io.Serializable;
+
+public class AlipayGoodsDetail implements Serializable {
+
+    private static final long serialVersionUID = -8875896136641533825L;
+
+    public AlipayGoodsDetail() {
+    }
+
+    public static AlipayGoodsDetail newInstance(String goodsId, String goodsName, long price, int quantity) {
+        AlipayGoodsDetail info = new AlipayGoodsDetail();
+        info.setGoodsId(goodsId);
+        info.setGoodsName(goodsName);
+        info.setPrice(price);
+        info.setQuantity(quantity);
+        return info;
+    }
+    // 必填,商品的编号,length=32
+    private String goodsId;
+    // 必填,商品的名称,length=256
+    private String goodsName;
+    // 必填,商品数量
+    private int quantity;
+    // 必填,商品单价,单位为元
+    private long price;
+
+    public String getGoodsId() {
+        return goodsId;
+    }
+
+    public void setGoodsId(String goodsId) {
+        this.goodsId = goodsId;
+    }
+
+    public String getGoodsName() {
+        return goodsName;
+    }
+
+    public void setGoodsName(String goodsName) {
+        this.goodsName = goodsName;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public void setQuantity(int quantity) {
+        this.quantity = quantity;
+    }
+
+    public long getPrice() {
+        return price;
+    }
+
+    public void setPrice(long price) {
+        this.price = price;
+    }
+}

+ 161 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipayService.java

@@ -0,0 +1,161 @@
+package com.fdkankan.pay.alipay.sdk;
+
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayResponse;
+import com.alipay.api.domain.TradeFundBill;
+import com.alipay.api.response.AlipayTradePrecreateResponse;
+import com.alipay.api.response.AlipayTradeQueryResponse;
+import com.alipay.demo.trade.model.GoodsDetail;
+import com.alipay.demo.trade.model.builder.AlipayTradePrecreateRequestBuilder;
+import com.alipay.demo.trade.model.builder.AlipayTradeQueryRequestBuilder;
+import com.alipay.demo.trade.model.result.AlipayF2FPrecreateResult;
+import com.alipay.demo.trade.model.result.AlipayF2FQueryResult;
+import com.alipay.demo.trade.service.AlipayTradeService;
+import com.alipay.demo.trade.utils.Utils;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class AlipayService {
+    private static Logger log = LoggerFactory.getLogger("programLog");
+
+    @Autowired
+    private AlipayTradeService alipayTradeService;
+
+    /**
+     * 当面付-扫码付
+     *
+     * 扫码支付,指用户打开支付宝钱包中的“扫一扫”功能,扫描商户针对每个订单实时生成的订单二维码,并在手机端确认支付。
+     *
+     * 发起预下单请求,同步返回订单二维码
+     *
+     * 适用场景:商家获取二维码展示在屏幕上,然后用户去扫描屏幕上的二维码
+     * @return
+     * @throws AlipayApiException
+     */
+    public Map<String, String> tradePrecreate(AlipaymentEx alipaymentEx, String notifyUrl) throws AlipayApiException {
+        if (StringUtils.isEmpty(alipaymentEx.getOutTradeNo())){
+            throw new AlipayApiException("outTradeNo should not be null");
+        }else if (StringUtils.isEmpty(alipaymentEx.getSubject())){
+            throw new AlipayApiException("subject should not be null");
+        }else if (StringUtils.isEmpty(alipaymentEx.getBody())){
+            throw new AlipayApiException("body should not be null");
+        }else if (alipaymentEx.getTotalAmount() == null){
+            throw new AlipayApiException("totalAmout should not be null");
+        }else if (StringUtils.isEmpty(alipaymentEx.getStoreId())){
+            throw new AlipayApiException("storeid should not be null");
+        }else if (alipaymentEx.getGoodsDetailList() == null || alipaymentEx.getGoodsDetailList().size() == 0){
+            throw new AlipayApiException("goodsDetailList should not be null");
+        }else if (StringUtils.isEmpty(notifyUrl)){
+            throw new AlipayApiException("notifyUrl should not be null");
+        }
+
+        List<AlipayGoodsDetail> alipayGoodsDetails = alipaymentEx.getGoodsDetailList();
+        List<GoodsDetail> goodsDetailList = new ArrayList<>();
+        for (AlipayGoodsDetail alipayGoodsDetail : alipayGoodsDetails){
+            GoodsDetail goods1 = GoodsDetail.newInstance(alipayGoodsDetail.getGoodsId(),
+                    alipayGoodsDetail.getGoodsName(), alipayGoodsDetail.getPrice(), alipayGoodsDetail.getQuantity());
+            goodsDetailList.add(goods1);
+        }
+
+        AlipayTradePrecreateRequestBuilder builder = new AlipayTradePrecreateRequestBuilder()
+                .setSubject(alipaymentEx.getSubject())
+                .setTotalAmount(alipaymentEx.getTotalAmount().toString())
+                .setOutTradeNo(alipaymentEx.getOutTradeNo())
+                .setUndiscountableAmount(alipaymentEx.getUndiscountableAmount() == null ? "" : alipaymentEx.getUndiscountableAmount().toString())
+                .setSellerId(alipaymentEx.getSellerId() == null ? "" : alipaymentEx.getSellerId())
+                .setBody(alipaymentEx.getBody())
+                .setOperatorId(alipaymentEx.getOperatorId() == null ? "" : alipaymentEx.getOperatorId())
+                .setStoreId(alipaymentEx.getStoreId())
+                .setTimeoutExpress(alipaymentEx.getTimeoutExpress())
+                //支付宝服务器主动通知商户服务器里指定的页面http路径,根据需要设置
+                .setNotifyUrl(notifyUrl)
+                .setGoodsDetailList(goodsDetailList);
+
+        Map<String, String> resp = new HashMap<>();
+        AlipayF2FPrecreateResult result = alipayTradeService.tradePrecreate(builder);
+        AlipayTradePrecreateResponse res = result.getResponse();
+        switch (result.getTradeStatus()) {
+            case SUCCESS:
+                dumpResponse(res);
+                log.info("支付宝预下单成功。");
+                resp.put("code", "0");
+                resp.put("out_trade_no", res.getOutTradeNo());
+                resp.put("qr_code", res.getQrCode());
+                break;
+
+            case FAILED:
+                log.error("支付宝预下单失败,code:" + res.getCode() + ",msg:" + res.getMsg());
+                resp.put("code", "-1");
+                break;
+
+            case UNKNOWN:
+                log.error("系统异常,预下单状态未知,code:" + res.getCode() + ",msg:" + res.getMsg());
+                resp.put("code", "-2");
+                break;
+
+            default:
+                log.error("不支持的交易状态,交易返回异常,code:" + res.getCode() + ",msg:" + res.getMsg());
+                resp.put("code", "-3");
+                break;
+        }
+        return resp;
+    }
+
+    public String tradeQuery(String outTradeNo) {
+        // (必填) 商户订单号,通过此商户订单号查询当面付的交易状态
+        String Type;
+        // 创建查询请求builder,设置请求参数
+        AlipayTradeQueryRequestBuilder builder = new AlipayTradeQueryRequestBuilder()
+                .setOutTradeNo(outTradeNo);
+        System.out.println("请求参数:" + builder);
+        AlipayF2FQueryResult result = alipayTradeService.queryTradeResult(builder);
+        switch (result.getTradeStatus()) {
+            case SUCCESS:
+                log.info("查询返回该订单支付成功: )");
+                Type = "SUCCESS";
+                AlipayTradeQueryResponse response = result.getResponse();
+                dumpResponse(response);
+                log.info(response.getTradeStatus());
+                if (Utils.isListNotEmpty(response.getFundBillList())) {
+                    for (TradeFundBill bill : response.getFundBillList()) {
+                        log.info(bill.getFundChannel() + ":" + bill.getAmount());
+                    }
+                }
+                break;
+            case FAILED:
+                log.error("查询返回该订单支付失败或被关闭!!!");
+                Type = "FAILED";
+                break;
+            case UNKNOWN:
+                log.error("系统异常,订单支付状态未知!!!");
+                Type = "UNKNOWN";
+                break;
+            default:
+                log.error("不支持的交易状态,交易返回异常!!!");
+                Type = "default";
+                break;
+        }
+        return Type;
+    }
+
+    // 简单打印应答
+    private void dumpResponse(AlipayResponse response) {
+        if (response != null) {
+            log.info(String.format("code:%s, msg:%s", response.getCode(), response.getMsg()));
+            if (StringUtils.isNotEmpty(response.getSubCode())) {
+                log.info(String.format("subCode:%s, subMsg:%s", response.getSubCode(),
+                        response.getSubMsg()));
+            }
+            log.info("body:" + response.getBody());
+        }
+    }
+}

+ 38 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipayUtil.java

@@ -0,0 +1,38 @@
+package com.fdkankan.pay.alipay.sdk;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+
+import java.awt.image.BufferedImage;
+import java.util.Hashtable;
+import java.util.Map;
+
+public class AlipayUtil {
+
+    /**
+     * 根据url生成二位图片对象
+     *
+     * @param codeUrl
+     * @return
+     * @throws WriterException
+     */
+    public static BufferedImage getQRCodeImge(String codeUrl) throws WriterException {
+        Map<EncodeHintType, Object> hints = new Hashtable();
+        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
+        hints.put(EncodeHintType.CHARACTER_SET, "UTF8");
+        int width = 300;
+        BitMatrix bitMatrix = (new MultiFormatWriter()).encode(codeUrl, BarcodeFormat.QR_CODE, width, width, hints);
+        BufferedImage image = new BufferedImage(width, width, 1);
+        for(int x = 0; x < width; ++x) {
+            for(int y = 0; y < width; ++y) {
+                image.setRGB(x, y, bitMatrix.get(x, y) ? -16777216 : -1);
+            }
+        }
+
+        return image;
+    }
+}

+ 113 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipaymentEx.java

@@ -0,0 +1,113 @@
+package com.fdkankan.pay.alipay.sdk;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.List;
+
+public class AlipaymentEx implements Serializable {
+
+    private static final long serialVersionUID = 8082279987959002563L;
+    // (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
+    private String outTradeNo;
+    // (必填) 订单标题,粗略描述用户的支付目的。如“喜士多(浦东店)消费”
+    private String subject;
+    // 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
+    private String body;
+    // (必填) 订单总金额,单位为元,不能超过1亿元
+    // 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
+    private BigDecimal totalAmount;
+    // (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
+    // 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
+    private BigDecimal undiscountableAmount;
+    // 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
+    // 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
+    private String sellerId;
+    // (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
+    private String storeId;
+    // 商户操作员编号,添加此参数可以为商户操作员做销售统计
+    private String operatorId;
+    // 支付超时,线下扫码交易定义为5分钟
+    private String timeoutExpress = "5m";
+    // 商品明细列表,需填写购买商品详细信息
+    private List<AlipayGoodsDetail> goodsDetailList;
+
+    public String getOutTradeNo() {
+        return outTradeNo;
+    }
+
+    public void setOutTradeNo(String outTradeNo) {
+        this.outTradeNo = outTradeNo;
+    }
+
+    public String getSubject() {
+        return subject;
+    }
+
+    public void setSubject(String subject) {
+        this.subject = subject;
+    }
+
+    public String getBody() {
+        return body;
+    }
+
+    public void setBody(String body) {
+        this.body = body;
+    }
+
+    public BigDecimal getTotalAmount() {
+        return totalAmount;
+    }
+
+    public void setTotalAmount(BigDecimal totalAmount) {
+        this.totalAmount = totalAmount;
+    }
+
+    public BigDecimal getUndiscountableAmount() {
+        return undiscountableAmount;
+    }
+
+    public void setUndiscountableAmount(BigDecimal undiscountableAmount) {
+        this.undiscountableAmount = undiscountableAmount;
+    }
+
+    public String getSellerId() {
+        return sellerId;
+    }
+
+    public void setSellerId(String sellerId) {
+        this.sellerId = sellerId;
+    }
+
+    public String getStoreId() {
+        return storeId;
+    }
+
+    public void setStoreId(String storeId) {
+        this.storeId = storeId;
+    }
+
+    public String getOperatorId() {
+        return operatorId;
+    }
+
+    public void setOperatorId(String operatorId) {
+        this.operatorId = operatorId;
+    }
+
+    public String getTimeoutExpress() {
+        return timeoutExpress;
+    }
+
+    public void setTimeoutExpress(String timeoutExpress) {
+        this.timeoutExpress = timeoutExpress;
+    }
+
+    public List<AlipayGoodsDetail> getGoodsDetailList() {
+        return goodsDetailList;
+    }
+
+    public void setGoodsDetailList(List<AlipayGoodsDetail> goodsDetailList) {
+        this.goodsDetailList = goodsDetailList;
+    }
+}

+ 56 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/PayPalDefaultConfig.java

@@ -0,0 +1,56 @@
+package com.fdkankan.pay.paypal;
+
+import com.fdkankan.pay.paypal.sdk.PayPalConfig;
+import org.springframework.stereotype.Component;
+
+@Component
+public class PayPalDefaultConfig extends PayPalConfig {
+
+    @Override
+    public String getClientId() {
+        return "AZJEGWlvvI2q52bR4k_mC1ftW8tEnlaJj30huGQTBsdAjwmKlMDiEiMixVKbfrdw6fB55NSj_BAE8FPP";
+    }
+
+    @Override
+    public String getSecret() {
+        return "EL-RGNmsbFpcKT7QuIlxMxX7MQplp8rCyaGDZ5KOCMQ9BkOhY5OYZyVInAeHT8_4tXoPth8tOEZY_3s_";
+    }
+
+    @Override
+    public String getMode() {
+        return "live";
+    }
+
+    public String getPaySuccessUrl(){
+        return "api/order/pay/paypal/callback";
+    }
+
+    public String getH5PaySuccessUrl(){
+        return "api/order/pay/paypal/callbackH5";
+    }
+
+    public String getCancelUrl(){
+        return "api/order/pay/paypal/cancel";
+    }
+
+    public String getH5CancelUrl(){
+        return "api/order/pay/paypal/cancelH5";
+    }
+
+    public String getSuccessUrl(){
+        return "index.html#/payresult/success/recharge";
+    }
+
+    public String getFailUrl(){
+        return "#/payresult/fail/recharge";
+    }
+
+    public String getH5SuccessUrl(){
+        return "mobile.html#/payresult/success";
+    }
+
+    public String getH5FailUrl(){
+        return "mobile.html#/payresult/fail";
+    }
+
+}

+ 43 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PayPalConfig.java

@@ -0,0 +1,43 @@
+package com.fdkankan.pay.paypal.sdk;
+
+import com.paypal.base.rest.APIContext;
+import com.paypal.base.rest.OAuthTokenCredential;
+import com.paypal.base.rest.PayPalRESTException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Component
+public abstract class PayPalConfig {
+
+    abstract public String getClientId();
+
+    abstract public String getSecret();
+
+    abstract public String getMode();
+
+    @Bean
+    public Map<String, String> paypalSdkConfig(){
+        Map<String, String> sdkConfig = new HashMap<>();
+        sdkConfig.put("mode", getMode());
+        return sdkConfig;
+    }
+
+    @Bean
+    public OAuthTokenCredential authTokenCredential(){
+        return new OAuthTokenCredential(getClientId(), getSecret(), paypalSdkConfig());
+    }
+
+    @Bean
+    public APIContext apiContext() throws PayPalRESTException {
+        String accessToken = authTokenCredential().getAccessToken();
+        log.info("paypal新的accessToken:" + accessToken);
+        APIContext apiContext = new APIContext(accessToken);
+        apiContext.setConfigurationMap(paypalSdkConfig());
+        return apiContext;
+    }
+}

+ 130 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PayPalmentEx.java

@@ -0,0 +1,130 @@
+package com.fdkankan.pay.paypal.sdk;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.List;
+
+public class PayPalmentEx implements Serializable {
+
+    private static final long serialVersionUID = 5027458587257867876L;
+    /**
+     * 订单Id
+     */
+    private String orderSn;
+    /**
+     * 支付的总价
+     */
+    private BigDecimal orderTotal;
+    /**
+     * 商品总价
+     */
+    private BigDecimal subTotal;
+    /**
+     * 税费
+     */
+    private BigDecimal tax;
+    /**
+     * 描述
+     */
+    private String description;
+    /**
+     * 成功回调url
+     */
+    private String successUrl;
+    /**
+     * 支付失败url
+     */
+    private String failUrl;
+    /**
+     * 取消支付url
+     */
+    private String cancelUrl;
+    /**
+     * 收货地址
+     */
+    private PaypalOrderAddressEx paypayOrderAddressEx;
+    /**
+     * 订单商品明细
+     */
+    private List<PaypalOrderItemEx> paypalOrderItemExList;
+
+    public String getOrderSn() {
+        return orderSn;
+    }
+
+    public void setOrderSn(String orderSn) {
+        this.orderSn = orderSn;
+    }
+
+    public BigDecimal getOrderTotal() {
+        return orderTotal;
+    }
+
+    public void setOrderTotal(BigDecimal orderTotal) {
+        this.orderTotal = orderTotal;
+    }
+
+    public BigDecimal getSubTotal() {
+        return subTotal;
+    }
+
+    public void setSubTotal(BigDecimal subTotal) {
+        this.subTotal = subTotal;
+    }
+
+    public BigDecimal getTax() {
+        return tax;
+    }
+
+    public void setTax(BigDecimal tax) {
+        this.tax = tax;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getSuccessUrl() {
+        return successUrl;
+    }
+
+    public void setSuccessUrl(String successUrl) {
+        this.successUrl = successUrl;
+    }
+
+    public String getFailUrl() {
+        return failUrl;
+    }
+
+    public void setFailUrl(String failUrl) {
+        this.failUrl = failUrl;
+    }
+
+    public String getCancelUrl() {
+        return cancelUrl;
+    }
+
+    public void setCancelUrl(String cancelUrl) {
+        this.cancelUrl = cancelUrl;
+    }
+
+    public PaypalOrderAddressEx getPaypayOrderAddressEx() {
+        return paypayOrderAddressEx;
+    }
+
+    public void setPaypayOrderAddressEx(PaypalOrderAddressEx paypayOrderAddressEx) {
+        this.paypayOrderAddressEx = paypayOrderAddressEx;
+    }
+
+    public List<PaypalOrderItemEx> getPaypalOrderItemExList() {
+        return paypalOrderItemExList;
+    }
+
+    public void setPaypalOrderItemExList(List<PaypalOrderItemEx> paypalOrderItemExList) {
+        this.paypalOrderItemExList = paypalOrderItemExList;
+    }
+}

+ 88 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalOrderAddressEx.java

@@ -0,0 +1,88 @@
+package com.fdkankan.pay.paypal.sdk;
+
+import java.io.Serializable;
+
+public class PaypalOrderAddressEx implements Serializable {
+
+    private static final long serialVersionUID = 5667747195118555705L;
+
+    private String firstName;
+
+    private String lastName;
+
+    private String countryCode;
+
+    private String state;
+
+    private String city;
+
+    private String address;
+
+    private String phone;
+
+    private String postalCode;
+
+    public String getFirstName() {
+        return firstName;
+    }
+
+    public void setFirstName(String firstName) {
+        this.firstName = firstName;
+    }
+
+    public String getLastName() {
+        return lastName;
+    }
+
+    public void setLastName(String lastName) {
+        this.lastName = lastName;
+    }
+
+    public String getCountryCode() {
+        return countryCode;
+    }
+
+    public void setCountryCode(String countryCode) {
+        this.countryCode = countryCode;
+    }
+
+    public String getState() {
+        return state;
+    }
+
+    public void setState(String state) {
+        this.state = state;
+    }
+
+    public String getCity() {
+        return city;
+    }
+
+    public void setCity(String city) {
+        this.city = city;
+    }
+
+    public String getAddress() {
+        return address;
+    }
+
+    public void setAddress(String address) {
+        this.address = address;
+    }
+
+    public String getPhone() {
+        return phone;
+    }
+
+    public void setPhone(String phone) {
+        this.phone = phone;
+    }
+
+    public String getPostalCode() {
+        return postalCode;
+    }
+
+    public void setPostalCode(String postalCode) {
+        this.postalCode = postalCode;
+    }
+}

+ 59 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalOrderItemEx.java

@@ -0,0 +1,59 @@
+package com.fdkankan.pay.paypal.sdk;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+public class PaypalOrderItemEx implements Serializable {
+
+    private static final long serialVersionUID = 7764234386178614511L;
+
+    private String sku;
+
+    private String name;
+
+    private BigDecimal price;
+
+    private int quantity;
+
+    private String currency;
+
+    public String getSku() {
+        return sku;
+    }
+
+    public void setSku(String sku) {
+        this.sku = sku;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public BigDecimal getPrice() {
+        return price;
+    }
+
+    public void setPrice(BigDecimal price) {
+        this.price = price;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public void setQuantity(int quantity) {
+        this.quantity = quantity;
+    }
+
+    public String getCurrency() {
+        return currency;
+    }
+
+    public void setCurrency(String currency) {
+        this.currency = currency;
+    }
+}

+ 5 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalPaymentIntent.java

@@ -0,0 +1,5 @@
+package com.fdkankan.pay.paypal.sdk;
+
+public enum PaypalPaymentIntent {
+	sale, authorize, order
+}

+ 5 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalPaymentMethod.java

@@ -0,0 +1,5 @@
+package com.fdkankan.pay.paypal.sdk;
+
+public enum PaypalPaymentMethod {
+	credit_card, paypal
+}

+ 268 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalService.java

@@ -0,0 +1,268 @@
+package com.fdkankan.pay.paypal.sdk;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.fdkankan.pay.paypal.PayPalDefaultConfig;
+import com.paypal.api.payments.*;
+import com.paypal.base.codec.binary.Base64;
+import com.paypal.base.rest.APIContext;
+import com.paypal.base.rest.PayPalRESTException;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+public class PaypalService {
+    private static Logger log = LoggerFactory.getLogger("programLog");
+
+    @Autowired
+    private APIContext apiContext;
+    @Autowired
+    private PayPalDefaultConfig config;
+
+    private static final String CURRENCY = "USD";
+
+    /**
+     * 创建支付
+     *
+     * @param paymentEx
+     * @return
+     * @throws PayPalRESTException
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public Payment createPayment(PayPalmentEx paymentEx, String successUrl, String cancelUrl) throws PayPalRESTException {
+        if (StringUtils.isEmpty(successUrl) || StringUtils.isEmpty(cancelUrl)) {
+            throw new PayPalRESTException("缺少url");
+        }
+        Transaction transaction = new Transaction();
+        transaction.setDescription(paymentEx.getDescription());
+        // 将我们的订单ID保存到支付信息中,用于后面支付回传
+        if (null != paymentEx.getOrderSn()) {
+            transaction.setCustom(paymentEx.getOrderSn());
+        }
+        //订单价格
+        Amount amount = new Amount();
+        amount.setCurrency(CURRENCY);
+        // 支付的总价,paypal会校验 total = subTotal + tax + ...
+        amount.setTotal(paymentEx.getOrderTotal().toString());
+        // 设置各种费用
+        Details details = new Details();
+        // 商品总价
+        if (paymentEx.getSubTotal() != null) {
+            details.setSubtotal(paymentEx.getSubTotal().toString());
+        }
+        // 税费
+        if (paymentEx.getTax() != null) {
+            details.setTax(paymentEx.getTax().toString());
+        }
+
+        amount.setDetails(details);
+
+        transaction.setAmount(amount);
+
+        ItemList itemList = new ItemList();
+        // 收货地址
+        PaypalOrderAddressEx orderAddress = paymentEx.getPaypayOrderAddressEx();
+        if (orderAddress != null) {
+            ShippingAddress shippingAddress = new ShippingAddress();
+            if (StringUtils.isNotEmpty(orderAddress.getFirstName()) && StringUtils.isNotEmpty(orderAddress.getLastName())) {
+                shippingAddress.setRecipientName(orderAddress.getFirstName() + "." + orderAddress.getLastName());
+            }
+            shippingAddress.setCountryCode(orderAddress.getCountryCode());
+            shippingAddress.setState(orderAddress.getState());
+            shippingAddress.setCity(orderAddress.getCity());
+            shippingAddress.setLine1(orderAddress.getAddress());
+            shippingAddress.setPhone(orderAddress.getPhone());
+            shippingAddress.setPostalCode(orderAddress.getPostalCode());
+
+            itemList.setShippingAddress(shippingAddress);
+            itemList.setShippingPhoneNumber(orderAddress.getPhone());
+        }
+
+        // 商品明细
+        List<PaypalOrderItemEx> orderItemList = paymentEx.getPaypalOrderItemExList();
+        if (orderItemList != null && orderItemList.size() > 0) {
+            List<Item> items = new ArrayList<>();
+            for (PaypalOrderItemEx orderItemEx : orderItemList) {
+                Item item = new Item();
+                item.setSku(orderItemEx.getSku());
+                item.setName(orderItemEx.getName());
+                item.setPrice(orderItemEx.getPrice().toString());
+                item.setQuantity(String.valueOf(orderItemEx.getQuantity()));
+                item.setCurrency(CURRENCY);
+                items.add(item);
+            }
+            itemList.setItems(items);
+            transaction.setItemList(itemList);
+        }
+
+        List<Transaction> transactions = new ArrayList<>();
+        transactions.add(transaction);
+
+        // 支付信息
+        Payer payer = new Payer();
+        payer.setPaymentMethod(PaypalPaymentMethod.paypal.toString());
+
+        Payment payment = new Payment();
+        //刷新accessToken时间;
+        apiContext = new APIContext("Bearer " + getAccessToken());
+        apiContext.setConfigurationMap(config.paypalSdkConfig());
+
+        payment.setIntent(PaypalPaymentIntent.sale.toString());
+        payment.setPayer(payer);
+        payment.setTransactions(transactions);
+
+        //回调地址
+        RedirectUrls redirectUrls = new RedirectUrls();
+        redirectUrls.setReturnUrl(successUrl);
+        redirectUrls.setCancelUrl(cancelUrl);
+        payment.setRedirectUrls(redirectUrls);
+
+        return payment.create(apiContext);
+    }
+
+    /**
+     * 执行支付
+     *
+     * @param paymentId
+     * @param payerId
+     * @return
+     * @throws PayPalRESTException
+     */
+    public Payment executePayment(String paymentId, String payerId) throws PayPalRESTException {
+        Payment payment = new Payment();
+        payment.setId(paymentId);
+        PaymentExecution paymentExecute = new PaymentExecution();
+        paymentExecute.setPayerId(payerId);
+        apiContext = new APIContext("Bearer " + getAccessToken());
+        apiContext.setConfigurationMap(config.paypalSdkConfig());
+        return payment.execute(apiContext, paymentExecute);
+    }
+
+    /**
+      * 获取token
+      * 了解更多:https://developer.paypal.com/webapps/developer/docs/integration/mobile/verify-mobile-payment/
+      * @return
+      */
+    private String getAccessToken() {
+        BufferedReader reader = null;
+        try {
+            URL url = new URL(UrlUtils.TOKEN_URL);
+            String authorization = config.getClientId() + ":" + config.getSecret();
+            authorization = Base64.encodeBase64String(authorization.getBytes());
+
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            conn.setRequestMethod("POST");// 提交模式
+            //设置请求头header
+            conn.setRequestProperty("Accept", "application/json");
+            conn.setRequestProperty("Accept-Language", "en_US");
+            conn.setRequestProperty("Authorization", "Basic " + authorization);
+            // conn.setConnectTimeout(10000);//连接超时 单位毫秒
+            // conn.setReadTimeout(2000);//读取超时 单位毫秒
+            conn.setDoOutput(true);// 是否输入参数
+            String params = "grant_type=client_credentials";
+            conn.getOutputStream().write(params.getBytes());// 输入参数
+
+            InputStreamReader inStream = new InputStreamReader(conn.getInputStream());
+            reader = new BufferedReader(inStream);
+            StringBuilder result = new StringBuilder();
+            String lineTxt = null;
+            while ((lineTxt = reader.readLine()) != null) {
+                result.append(lineTxt);
+            }
+            reader.close();
+            String accessTokey = JSONObject.parseObject(result.toString()).getString("access_token");
+            log.info("getAccessToken:" + accessTokey);
+            return accessTokey;
+        } catch (Exception err) {
+            err.printStackTrace();
+        } finally {
+            if (reader != null){
+                try {
+                    reader.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return null;
+    }
+
+    public String getPaymentDetails(String paymentId) {
+        BufferedReader reader = null;
+        try {
+            URL url = new URL(UrlUtils.PAYMENT_DETAIL + paymentId);
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            conn.setRequestMethod("GET");// 提交模式
+            //设置请求头header
+            conn.setRequestProperty("Accept", "application/json");
+            conn.setRequestProperty("Authorization", "Bearer " + getAccessToken());
+            // conn.setConnectTimeout(10000);//连接超时 单位毫秒
+            // conn.setReadTimeout(2000);//读取超时 单位毫秒
+            InputStreamReader inStream = new InputStreamReader(conn.getInputStream());
+            reader = new BufferedReader(inStream);
+            StringBuilder result = new StringBuilder();
+            String lineTxt = null;
+            while ((lineTxt = reader.readLine()) != null) {
+                result.append(lineTxt);
+            }
+            reader.close();
+            return result.toString();
+        } catch (Exception err) {
+            err.printStackTrace();
+        }finally {
+            if (reader != null){
+                try {
+                    reader.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return null;
+    }
+
+    public boolean verifyPayment(String paymentId){
+        //从PayPal获取付款详情
+        String str = getPaymentDetails(paymentId);
+        JSONObject detail = JSONObject.parseObject(str);
+        //校验订单是否完成
+        if("approved".equals(detail.getString("state"))){
+            JSONObject transactions = detail.getJSONArray("transactions").getJSONObject(0);
+            JSONObject amount = transactions.getJSONObject("amount");
+            JSONArray relatedResources = transactions.getJSONArray("related_resources");
+            //从数据库查询总金额与Paypal校验支付总金额
+            double total = 0;
+            if( total != amount.getDouble("total") ){
+                return false;
+            }
+            //校验交易货币类型
+            String currency = "USD";
+            if( !currency.equals(amount.getString("currency")) ){
+                return false;
+            }
+            //校验每个子订单是否完成
+            for (int i = 0,j = relatedResources.size(); i < j; i++) {
+                JSONObject sale = relatedResources.getJSONObject(i).getJSONObject("sale");
+                if( !"completed".equals(sale.getString("state")) ){
+                    System.out.println("子订单未完成,订单状态:"+sale.getString("state"));
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+
+}

+ 66 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/UrlUtils.java

@@ -0,0 +1,66 @@
+package com.fdkankan.pay.paypal.sdk;
+
+import javax.servlet.http.HttpServletRequest;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+public class UrlUtils {
+
+    //沙箱链接
+//    public static final String TOKEN_URL = "https://api.sandbox.paypal.com/v1/oauth2/token";
+    public static final String TOKEN_URL = "https://api.paypal.com/v1/oauth2/token";
+    public static final String PAYMENT_DETAIL = "https://api.sandbox.paypal.com/v1/payments/payment/";
+
+    public static String getBaseURl(HttpServletRequest request) {
+        String scheme = request.getScheme();
+        String serverName = request.getServerName();
+        int serverPort = request.getServerPort();
+        String contextPath = request.getContextPath();
+        StringBuffer url =  new StringBuffer();
+        url.append(scheme).append("://").append(serverName);
+        if ((serverPort != 80) && (serverPort != 443)) {
+            url.append(":").append(serverPort);
+        }
+        url.append(contextPath);
+        if(url.toString().endsWith("/")){
+            url.append("/");
+        }
+        return url.toString();
+    }
+
+    /**
+     * 获取用户实际ip
+     * @param request
+     * @return
+     */
+    public static String getIpAddr(HttpServletRequest request){
+        String ipAddress = request.getHeader("x-forwarded-for");
+        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
+            ipAddress = request.getHeader("Proxy-Client-IP");
+        }
+        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
+            ipAddress = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
+            ipAddress = request.getRemoteAddr();
+            if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){
+                //根据网卡取本机配置的IP
+                InetAddress inet=null;
+                try {
+                    inet = InetAddress.getLocalHost();
+                } catch (UnknownHostException e) {
+                    e.printStackTrace();
+                }
+                ipAddress= inet.getHostAddress();
+            }
+        }
+        //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
+        if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15
+            if(ipAddress.indexOf(",")>0){
+                ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));
+            }
+        }
+        return ipAddress;
+    }
+
+}

+ 64 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/WXPayDefaultConfig.java

@@ -0,0 +1,64 @@
+package com.fdkankan.pay.wx;
+
+import com.fdkankan.pay.wx.sdk.WXPayConfig;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+public class WXPayDefaultConfig implements WXPayConfig {
+
+    private byte[] certData;
+
+    public WXPayDefaultConfig() throws Exception {
+        /*String certPath = CONSTANTFILEPATH.WEIXINCERT;
+        File file = new File(certPath);
+        InputStream certStream = new FileInputStream(file);
+        this.certData = new byte[(int) file.length()];
+        certStream.read(this.certData);
+        certStream.close();*/
+    }
+
+    public String getAppID() {
+        return "wx779dbafb46bab697";
+    }
+
+    public String getMchID() {
+        return "1505605401";
+    }
+
+    public String getKey() {
+        return "4DAGE1684DAGE1684DAGE1684DAGE168";
+    }
+
+    public String getSecret() {
+        return "b578c41b560f37c2c2c1981114830d23";
+    }
+
+    public String getCreateIP() {
+        return "47.104.99.106";
+    }
+
+    public String getH5RedirectURL() {
+        return "mobile.html#/check";
+    }
+
+    public String getNotifyURL() {
+        return "api/order/pay/wechatPay/notify";
+    }
+
+    public String getOrderURL() {
+        return "https://api.mch.weixin.qq.com/pay/unifiedorder";
+    }
+
+    public InputStream getCertStream() {
+        return new ByteArrayInputStream(this.certData);
+    }
+
+    public int getHttpConnectTimeoutMs() {
+        return 8000;
+    }
+
+    public int getHttpReadTimeoutMs() {
+        return 10000;
+    }
+}

+ 673 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/sdk/WXPay.java

@@ -0,0 +1,673 @@
+package com.fdkankan.pay.wx.sdk;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Map;
+
+public class WXPay {
+
+    private WXPayConfig config;
+    private WXPayConstants.SignType signType;
+    private boolean useSandbox;
+
+    public WXPay(final WXPayConfig config) {
+        this(config, WXPayConstants.SignType.MD5, false);
+    }
+
+    public WXPay(final WXPayConfig config, final WXPayConstants.SignType signType) {
+        this(config, signType, false);
+    }
+
+    public WXPay(final WXPayConfig config, final WXPayConstants.SignType signType, final boolean useSandbox) {
+        this.config = config;
+        this.signType = signType;
+        this.useSandbox = useSandbox;
+    }
+
+
+    /**
+     * 向 Map 中添加 appid、mch_id、nonce_str、sign_type、sign <br>
+     * 该函数适用于商户适用于统一下单等接口,不适用于红包、代金券接口
+     *
+     * @param reqData
+     * @return
+     * @throws Exception
+     */
+    public Map<String, String> fillRequestData(Map<String, String> reqData) throws Exception {
+        reqData.put("appid", config.getAppID());
+        reqData.put("mch_id", config.getMchID());
+        reqData.put("nonce_str", WXPayUtil.generateNonceStr());
+        if (WXPayConstants.SignType.MD5.equals(this.signType)) {
+            reqData.put("sign_type", WXPayConstants.MD5);
+        }
+        else if (WXPayConstants.SignType.HMACSHA256.equals(this.signType)) {
+            reqData.put("sign_type", WXPayConstants.HMACSHA256);
+        }
+        reqData.put("sign", WXPayUtil.generateSignature(reqData, config.getKey(), this.signType));
+        return reqData;
+    }
+
+    /**
+     * 判断xml数据的sign是否有效,必须包含sign字段,否则返回false。
+     *
+     * @param reqData 向wxpay post的请求数据
+     * @return 签名是否有效
+     * @throws Exception
+     */
+    public boolean isResponseSignatureValid(Map<String, String> reqData) throws Exception {
+        // 返回数据的签名方式和请求中给定的签名方式是一致的
+        return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), this.signType);
+    }
+
+    /**
+     * 判断支付结果通知中的sign是否有效
+     *
+     * @param reqData 向wxpay post的请求数据
+     * @return 签名是否有效
+     * @throws Exception
+     */
+    public boolean isPayResultNotifySignatureValid(Map<String, String> reqData) throws Exception {
+        String signTypeInData = reqData.get(WXPayConstants.FIELD_SIGN_TYPE);
+        WXPayConstants.SignType signType;
+        if (signTypeInData == null) {
+            signType = WXPayConstants.SignType.MD5;
+        }
+        else {
+            signTypeInData = signTypeInData.trim();
+            if (signTypeInData.length() == 0) {
+                signType = WXPayConstants.SignType.MD5;
+            }
+            else if (WXPayConstants.MD5.equals(signTypeInData)) {
+                signType = WXPayConstants.SignType.MD5;
+            }
+            else if (WXPayConstants.HMACSHA256.equals(signTypeInData)) {
+                signType = WXPayConstants.SignType.HMACSHA256;
+            }
+            else {
+                throw new Exception(String.format("Unsupported sign_type: %s", signTypeInData));
+            }
+        }
+        return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), signType);
+    }
+
+
+    /**
+     * 不需要证书的请求
+     * @param strUrl String
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 超时时间,单位是毫秒
+     * @param readTimeoutMs 超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public String requestWithoutCert(String strUrl, Map<String, String> reqData,
+                                     int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String UTF8 = "UTF-8";
+        String reqBody = WXPayUtil.mapToXml(reqData);
+        URL httpUrl = new URL(strUrl);
+        HttpURLConnection httpURLConnection = (HttpURLConnection) httpUrl.openConnection();
+        httpURLConnection.setDoOutput(true);
+        httpURLConnection.setRequestMethod("POST");
+        httpURLConnection.setConnectTimeout(connectTimeoutMs);
+        httpURLConnection.setReadTimeout(readTimeoutMs);
+        httpURLConnection.connect();
+        OutputStream outputStream = httpURLConnection.getOutputStream();
+        outputStream.write(reqBody.getBytes(UTF8));
+
+        // if (httpURLConnection.getResponseCode()!= 200) {
+        //     throw new Exception(String.format("HTTP response code is %d, not 200", httpURLConnection.getResponseCode()));
+        // }
+
+        //获取内容
+        InputStream inputStream = httpURLConnection.getInputStream();
+        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, UTF8));
+        final StringBuffer stringBuffer = new StringBuffer();
+        String line = null;
+        while ((line = bufferedReader.readLine()) != null) {
+            stringBuffer.append(line).append("\n");
+        }
+        String resp = stringBuffer.toString();
+        if (stringBuffer!=null) {
+            try {
+                bufferedReader.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        if (inputStream!=null) {
+            try {
+                inputStream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        if (outputStream!=null) {
+            try {
+                outputStream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        // if (httpURLConnection!=null) {
+        //     httpURLConnection.disconnect();
+        // }
+
+        return resp;
+    }
+
+
+    /**
+     * 需要证书的请求
+     * @param strUrl String
+     * @param reqData 向wxpay post的请求数据  Map
+     * @param connectTimeoutMs 超时时间,单位是毫秒
+     * @param readTimeoutMs 超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public String requestWithCert(String strUrl, Map<String, String> reqData,
+                                  int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String UTF8 = "UTF-8";
+        String reqBody = WXPayUtil.mapToXml(reqData);
+        URL httpUrl = new URL(strUrl);
+        char[] password = config.getMchID().toCharArray();
+        InputStream certStream = config.getCertStream();
+        KeyStore ks = KeyStore.getInstance("PKCS12");
+        ks.load(certStream, password);
+
+        // 实例化密钥库 & 初始化密钥工厂
+        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+        kmf.init(ks, password);
+
+        // 创建SSLContext
+        SSLContext sslContext = SSLContext.getInstance("TLS");
+        sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
+        HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
+
+        HttpURLConnection httpURLConnection = (HttpURLConnection) httpUrl.openConnection();
+
+        httpURLConnection.setDoOutput(true);
+        httpURLConnection.setRequestMethod("POST");
+        httpURLConnection.setConnectTimeout(connectTimeoutMs);
+        httpURLConnection.setReadTimeout(readTimeoutMs);
+        httpURLConnection.connect();
+        OutputStream outputStream = httpURLConnection.getOutputStream();
+        outputStream.write(reqBody.getBytes(UTF8));
+
+        // if (httpURLConnection.getResponseCode()!= 200) {
+        //     throw new Exception(String.format("HTTP response code is %d, not 200", httpURLConnection.getResponseCode()));
+        // }
+
+        //获取内容
+        InputStream inputStream = httpURLConnection.getInputStream();
+        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, UTF8));
+        final StringBuffer stringBuffer = new StringBuffer();
+        String line = null;
+        while ((line = bufferedReader.readLine()) != null) {
+            stringBuffer.append(line);
+        }
+        String resp = stringBuffer.toString();
+        if (stringBuffer!=null) {
+            try {
+                bufferedReader.close();
+            } catch (IOException e) {
+                // e.printStackTrace();
+            }
+        }
+        if (inputStream!=null) {
+            try {
+                inputStream.close();
+            } catch (IOException e) {
+                // e.printStackTrace();
+            }
+        }
+        if (outputStream!=null) {
+            try {
+                outputStream.close();
+            } catch (IOException e) {
+                // e.printStackTrace();
+            }
+        }
+        if (certStream!=null) {
+            try {
+                certStream.close();
+            } catch (IOException e) {
+                // e.printStackTrace();
+            }
+        }
+        // if (httpURLConnection!=null) {
+        //     httpURLConnection.disconnect();
+        // }
+
+        return resp;
+    }
+
+    /**
+     * 处理 HTTPS API返回数据,转换成Map对象。return_code为SUCCESS时,验证签名。
+     * @param xmlStr API返回的XML格式数据
+     * @return Map类型数据
+     * @throws Exception
+     */
+    public Map<String, String> processResponseXml(String xmlStr) throws Exception {
+        String RETURN_CODE = "return_code";
+        String return_code;
+        Map<String, String> respData = WXPayUtil.xmlToMap(xmlStr);
+        if (respData.containsKey(RETURN_CODE)) {
+            return_code = respData.get(RETURN_CODE);
+        }
+        else {
+            throw new Exception(String.format("No `return_code` in XML: %s", xmlStr));
+        }
+
+        if (return_code.equals(WXPayConstants.FAIL)) {
+            return respData;
+        }
+        else if (return_code.equals(WXPayConstants.SUCCESS)) {
+           if (this.isResponseSignatureValid(respData)) {
+               return respData;
+           }
+           else {
+               throw new Exception(String.format("Invalid sign value in XML: %s", xmlStr));
+           }
+        }
+        else {
+            throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));
+        }
+    }
+
+    /**
+     * 作用:提交刷卡支付<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> microPay(Map<String, String> reqData) throws Exception {
+        return this.microPay(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:提交刷卡支付<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> microPay(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_MICROPAY_URL;
+        }
+        else {
+            url = WXPayConstants.MICROPAY_URL;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:统一下单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> unifiedOrder(Map<String, String> reqData) throws Exception {
+        return this.unifiedOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:统一下单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> unifiedOrder(Map<String, String> reqData,  int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_UNIFIEDORDER_URL;
+        }
+        else {
+            url = WXPayConstants.UNIFIEDORDER_URL;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:查询订单<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> orderQuery(Map<String, String> reqData) throws Exception {
+        return this.orderQuery(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:查询订单<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据 int
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> orderQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_ORDERQUERY_URL;
+        }
+        else {
+            url = WXPayConstants.ORDERQUERY_URL;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:撤销订单<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> reverse(Map<String, String> reqData) throws Exception {
+        return this.reverse(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:撤销订单<br>
+     * 场景:刷卡支付<br>
+     * 其他:需要证书
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> reverse(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REVERSE_URL;
+        }
+        else {
+            url = WXPayConstants.REVERSE_URL;
+        }
+        String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:关闭订单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> closeOrder(Map<String, String> reqData) throws Exception {
+        return this.closeOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:关闭订单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> closeOrder(Map<String, String> reqData,  int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_CLOSEORDER_URL;
+        }
+        else {
+            url = WXPayConstants.CLOSEORDER_URL;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:申请退款<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refund(Map<String, String> reqData) throws Exception {
+        return this.refund(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:申请退款<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付<br>
+     * 其他:需要证书
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refund(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REFUND_URL;
+        }
+        else {
+            url = WXPayConstants.REFUND_URL;
+        }
+        String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:退款查询<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refundQuery(Map<String, String> reqData) throws Exception {
+        return this.refundQuery(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:退款查询<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refundQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REFUNDQUERY_URL;
+        }
+        else {
+            url = WXPayConstants.REFUNDQUERY_URL;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:对账单下载(成功时返回对账单数据,失败时返回XML格式数据)<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> downloadBill(Map<String, String> reqData) throws Exception {
+        return this.downloadBill(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:对账单下载<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付<br>
+     * 其他:无论是否成功都返回Map。若成功,返回的Map中含有return_code、return_msg、data,
+     *      其中return_code为`SUCCESS`,data为对账单数据。
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return 经过封装的API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> downloadBill(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_DOWNLOADBILL_URL;
+        }
+        else {
+            url = WXPayConstants.DOWNLOADBILL_URL;
+        }
+        String respStr = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs).trim();
+        Map<String, String> ret;
+        // 出现错误,返回XML数据
+        if (respStr.indexOf("<") == 0) {
+            ret = WXPayUtil.xmlToMap(respStr);
+        }
+        else {
+            // 正常返回csv数据
+            ret = new HashMap<String, String>();
+            ret.put("return_code", WXPayConstants.SUCCESS);
+            ret.put("return_msg", "sdk");
+            ret.put("data", respStr);
+        }
+        return ret;
+    }
+
+
+    /**
+     * 作用:交易保障<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> report(Map<String, String> reqData) throws Exception {
+        return this.report(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:交易保障<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> report(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REPORT_URL;
+        }
+        else {
+            url = WXPayConstants.REPORT_URL;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return WXPayUtil.xmlToMap(respXml);
+    }
+
+
+    /**
+     * 作用:转换短链接<br>
+     * 场景:刷卡支付、扫码支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> shortUrl(Map<String, String> reqData) throws Exception {
+        return this.shortUrl(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:转换短链接<br>
+     * 场景:刷卡支付、扫码支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> shortUrl(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_SHORTURL_URL;
+        }
+        else {
+            url = WXPayConstants.SHORTURL_URL;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:授权码查询OPENID接口<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> authCodeToOpenid(Map<String, String> reqData) throws Exception {
+        return this.authCodeToOpenid(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:授权码查询OPENID接口<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> authCodeToOpenid(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_AUTHCODETOOPENID_URL;
+        }
+        else {
+            url = WXPayConstants.AUTHCODETOOPENID_URL;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+} // end class

+ 53 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/sdk/WXPayConfig.java

@@ -0,0 +1,53 @@
+package com.fdkankan.pay.wx.sdk;
+
+import java.io.InputStream;
+
+public interface WXPayConfig {
+
+
+    /**
+     * 获取 App ID
+     *
+     * @return App ID
+     */
+    public String getAppID();
+
+
+    /**
+     * 获取 Mch ID
+     *
+     * @return Mch ID
+     */
+    public String getMchID();
+
+
+    /**
+     * 获取 API 密钥
+     *
+     * @return API密钥
+     */
+    public String getKey();
+
+
+    /**
+     * 获取商户证书内容
+     *
+     * @return 商户证书内容
+     */
+    public InputStream getCertStream();
+
+    /**
+     * HTTP(S) 连接超时时间,单位毫秒
+     *
+     * @return
+     */
+    public int getHttpConnectTimeoutMs();
+
+    /**
+     * HTTP(S) 读数据超时时间,单位毫秒
+     *
+     * @return
+     */
+    public int getHttpReadTimeoutMs();
+
+}

+ 45 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/sdk/WXPayConstants.java

@@ -0,0 +1,45 @@
+package com.fdkankan.pay.wx.sdk;
+
+/**
+ * 常量
+ */
+public class WXPayConstants {
+
+    public enum SignType {
+        MD5, HMACSHA256
+    }
+
+    public static final String FAIL     = "FAIL";
+    public static final String SUCCESS  = "SUCCESS";
+    public static final String HMACSHA256 = "HMAC-SHA256";
+    public static final String MD5 = "MD5";
+
+    public static final String FIELD_SIGN = "sign";
+    public static final String FIELD_SIGN_TYPE = "sign_type";
+
+    public static final String MICROPAY_URL     = "https://api.mch.weixin.qq.com/pay/micropay";
+    public static final String UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
+    public static final String ORDERQUERY_URL   = "https://api.mch.weixin.qq.com/pay/orderquery";
+    public static final String REVERSE_URL      = "https://api.mch.weixin.qq.com/secapi/pay/reverse";
+    public static final String CLOSEORDER_URL   = "https://api.mch.weixin.qq.com/pay/closeorder";
+    public static final String REFUND_URL       = "https://api.mch.weixin.qq.com/secapi/pay/refund";
+    public static final String REFUNDQUERY_URL  = "https://api.mch.weixin.qq.com/pay/refundquery";
+    public static final String DOWNLOADBILL_URL = "https://api.mch.weixin.qq.com/pay/downloadbill";
+    public static final String REPORT_URL       = "https://api.mch.weixin.qq.com/payitil/report";
+    public static final String SHORTURL_URL     = "https://api.mch.weixin.qq.com/tools/shorturl";
+    public static final String AUTHCODETOOPENID_URL = "https://api.mch.weixin.qq.com/tools/authcodetoopenid";
+
+    // sandbox
+    public static final String SANDBOX_MICROPAY_URL     = "https://api.mch.weixin.qq.com/sandboxnew/pay/micropay";
+    public static final String SANDBOX_UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/sandboxnew/pay/unifiedorder";
+    public static final String SANDBOX_ORDERQUERY_URL   = "https://api.mch.weixin.qq.com/sandboxnew/pay/orderquery";
+    public static final String SANDBOX_REVERSE_URL      = "https://api.mch.weixin.qq.com/sandboxnew/secapi/pay/reverse";
+    public static final String SANDBOX_CLOSEORDER_URL   = "https://api.mch.weixin.qq.com/sandboxnew/pay/closeorder";
+    public static final String SANDBOX_REFUND_URL       = "https://api.mch.weixin.qq.com/sandboxnew/secapi/pay/refund";
+    public static final String SANDBOX_REFUNDQUERY_URL  = "https://api.mch.weixin.qq.com/sandboxnew/pay/refundquery";
+    public static final String SANDBOX_DOWNLOADBILL_URL = "https://api.mch.weixin.qq.com/sandboxnew/pay/downloadbill";
+    public static final String SANDBOX_REPORT_URL       = "https://api.mch.weixin.qq.com/sandboxnew/payitil/report";
+    public static final String SANDBOX_SHORTURL_URL     = "https://api.mch.weixin.qq.com/sandboxnew/tools/shorturl";
+    public static final String SANDBOX_AUTHCODETOOPENID_URL = "https://api.mch.weixin.qq.com/sandboxnew/tools/authcodetoopenid";
+
+}

+ 307 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/sdk/WXPayUtil.java

@@ -0,0 +1,307 @@
+package com.fdkankan.pay.wx.sdk;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.security.MessageDigest;
+import java.util.*;
+
+
+public class WXPayUtil {
+
+    /**
+     * XML格式字符串转换为Map
+     *
+     * @param strXML XML字符串
+     * @return XML数据转换后的Map
+     * @throws Exception
+     */
+    public static Map<String, String> xmlToMap(String strXML) throws Exception {
+        Map<String, String> data = new HashMap<String, String>();
+        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+
+        // 禁用XML外部实体注入
+        /**
+         * XXE
+         * XML 外部实体注入漏洞(XML External Entity Injection,简称 XXE),
+         * 是一种容易被忽视,但危害巨大的漏洞。它可以利用 XML 外部实体加载注入,
+         * 执行不可预控的代码,可导致读取任意文件、执行系统命令、探测内网端口、攻击内网网站等危害。
+         */
+
+        /**
+         * 原理:
+         * 通常,我们在使用微信支付时,商家会有一个通知的 URL 来接收异步支付结果。
+         * 然而,微信在 JAVA 版本的 SDK 存在一个 XXE 漏洞来处理这个结果。
+         * 由此攻击者可以向通知的 URL 中构建恶意的回调数据,以便根据需要窃取商家服务器上的任意信息。
+         * 一旦攻击者获得商家的关键安全密钥(md5-key 和merchant-Id),
+         * 那么他们可以通过发送伪造的信息来欺骗商家而无需付费购买任意商品。
+         */
+        documentBuilderFactory.setExpandEntityReferences(false);
+        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
+        InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
+        org.w3c.dom.Document doc = documentBuilder.parse(stream);
+        doc.getDocumentElement().normalize();
+        NodeList nodeList = doc.getDocumentElement().getChildNodes();
+        for (int idx=0; idx<nodeList.getLength(); ++idx) {
+            Node node = nodeList.item(idx);
+            if (node.getNodeType() == Node.ELEMENT_NODE) {
+                org.w3c.dom.Element element = (org.w3c.dom.Element) node;
+                data.put(element.getNodeName(), element.getTextContent());
+            }
+        }
+        try {
+            stream.close();
+        }
+        catch (Exception ex) {
+
+        }
+        return data;
+    }
+
+    /**
+     * 将Map转换为XML格式的字符串
+     *
+     * @param data Map类型数据
+     * @return XML格式的字符串
+     * @throws Exception
+     */
+    public static String mapToXml(Map<String, String> data) throws Exception {
+        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+        DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
+        org.w3c.dom.Document document = documentBuilder.newDocument();
+        org.w3c.dom.Element root = document.createElement("xml");
+        document.appendChild(root);
+        for (String key: data.keySet()) {
+            String value = data.get(key);
+            if (value == null) {
+                value = "";
+            }
+            value = value.trim();
+            org.w3c.dom.Element filed = document.createElement(key);
+            filed.appendChild(document.createTextNode(value));
+            root.appendChild(filed);
+        }
+        TransformerFactory tf = TransformerFactory.newInstance();
+        Transformer transformer = tf.newTransformer();
+        DOMSource source = new DOMSource(document);
+        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+        StringWriter writer = new StringWriter();
+        StreamResult result = new StreamResult(writer);
+        transformer.transform(source, result);
+        String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
+        try {
+            writer.close();
+        }
+        catch (Exception ex) {
+        }
+        return output;
+    }
+
+
+    /**
+     * 生成带有 sign 的 XML 格式字符串
+     *
+     * @param data Map类型数据
+     * @param key API密钥
+     * @return 含有sign字段的XML
+     */
+    public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
+        return generateSignedXml(data, key, WXPayConstants.SignType.MD5);
+    }
+
+    /**
+     * 生成带有 sign 的 XML 格式字符串
+     *
+     * @param data Map类型数据
+     * @param key API密钥
+     * @param signType 签名类型
+     * @return 含有sign字段的XML
+     */
+    public static String generateSignedXml(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
+        String sign = generateSignature(data, key, signType);
+        data.put(WXPayConstants.FIELD_SIGN, sign);
+        return mapToXml(data);
+    }
+
+
+    /**
+     * 判断签名是否正确
+     *
+     * @param xmlStr XML格式数据
+     * @param key API密钥
+     * @return 签名是否正确
+     * @throws Exception
+     */
+    public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
+        Map<String, String> data = xmlToMap(xmlStr);
+        if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
+            return false;
+        }
+        String sign = data.get(WXPayConstants.FIELD_SIGN);
+        return generateSignature(data, key).equals(sign);
+    }
+
+    /**
+     * 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
+     *
+     * @param data Map类型数据
+     * @param key API密钥
+     * @return 签名是否正确
+     * @throws Exception
+     */
+    public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
+        return isSignatureValid(data, key, WXPayConstants.SignType.MD5);
+    }
+
+    /**
+     * 判断签名是否正确,必须包含sign字段,否则返回false。
+     *
+     * @param data Map类型数据
+     * @param key API密钥
+     * @param signType 签名方式
+     * @return 签名是否正确
+     * @throws Exception
+     */
+    public static boolean isSignatureValid(Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
+        if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
+            return false;
+        }
+        String sign = data.get(WXPayConstants.FIELD_SIGN);
+        return generateSignature(data, key, signType).equals(sign);
+    }
+
+    /**
+     * 生成签名
+     *
+     * @param data 待签名数据
+     * @param key API密钥
+     * @return 签名
+     */
+    public static String generateSignature(final Map<String, String> data, String key) throws Exception {
+        return generateSignature(data, key, WXPayConstants.SignType.MD5);
+    }
+
+    /**
+     * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
+     *
+     * @param data 待签名数据
+     * @param key API密钥
+     * @param signType 签名方式
+     * @return 签名
+     */
+    public static String generateSignature(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
+        Set<String> keySet = data.keySet();
+        String[] keyArray = keySet.toArray(new String[keySet.size()]);
+        Arrays.sort(keyArray);
+        StringBuilder sb = new StringBuilder();
+        for (String k : keyArray) {
+            if (k.equals(WXPayConstants.FIELD_SIGN)) {
+                continue;
+            }
+            if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
+                sb.append(k).append("=").append(data.get(k).trim()).append("&");
+        }
+        sb.append("key=").append(key);
+        if (WXPayConstants.SignType.MD5.equals(signType)) {
+            return MD5(sb.toString()).toUpperCase();
+        }
+        else if (WXPayConstants.SignType.HMACSHA256.equals(signType)) {
+            return HMACSHA256(sb.toString(), key);
+        }
+        else {
+            throw new Exception(String.format("Invalid sign_type: %s", signType));
+        }
+    }
+
+
+    /**
+     * 获取随机字符串 Nonce Str
+     *
+     * @return String 随机字符串
+     */
+    public static String generateNonceStr() {
+        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
+    }
+
+
+    /**
+     * 生成 MD5
+     *
+     * @param data 待处理数据
+     * @return MD5结果
+     */
+    public static String MD5(String data) throws Exception {
+        MessageDigest md = MessageDigest.getInstance("MD5");
+        byte[] array = md.digest(data.getBytes("UTF-8"));
+        StringBuilder sb = new StringBuilder();
+        for (byte item : array) {
+            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
+        }
+        return sb.toString().toUpperCase();
+    }
+
+    /**
+     * 生成 HMACSHA256
+     * @param data 待处理数据
+     * @param key 密钥
+     * @return 加密结果
+     * @throws Exception
+     */
+    public static String HMACSHA256(String data, String key) throws Exception {
+        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
+        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
+        sha256_HMAC.init(secret_key);
+        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
+        StringBuilder sb = new StringBuilder();
+        for (byte item : array) {
+            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
+        }
+        return sb.toString().toUpperCase();
+    }
+
+    /**
+     * 根据url生成二位图片对象
+     *
+     * @param codeUrl
+     * @return
+     * @throws WriterException
+     */
+    public static BufferedImage getQRCodeImge(String codeUrl) throws WriterException {
+        int width = 300;
+        //根据url生成二维码
+        MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
+        // 设置二维码参数
+        Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
+        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
+        BitMatrix bitMatrix = multiFormatWriter.encode(codeUrl, BarcodeFormat.QR_CODE, width, width, hints);
+        BufferedImage image = new BufferedImage(width, width, 1);
+        for(int x = 0; x < width; ++x) {
+            for(int y = 0; y < width; ++y) {
+                image.setRGB(x, y, bitMatrix.get(x, y) ? -16777216 : -1);
+            }
+        }
+        return image;
+
+    }
+}

+ 47 - 0
pom.xml

@@ -16,6 +16,8 @@
         <module>4dkankan-utils-email</module>
         <module>4dkankan-utils-rabbitmq</module>
         <module>4dkankan-utils-rubber-sheeting</module>
+        <module>4dkankan-common-web</module>
+      <module>4dkankan-utils-pay</module>
     </modules>
 
     <groupId>com.fdkankan</groupId>
@@ -43,6 +45,10 @@
         <java.version>1.8</java.version>
         <fastjson-version>1.2.83</fastjson-version>
         <hutool-version>5.7.17</hutool-version>
+        <spring.cloud-version>Hoxton.SR8</spring.cloud-version>
+        <servlet-api-version>2.4</servlet-api-version>
+        <shiro.version>1.7.1</shiro.version>
+        <spring.plugin.metadata-version>1.2.0.RELEASE</spring.plugin.metadata-version>
     </properties>
 
     <dependencies>
@@ -62,6 +68,29 @@
 
     <dependencyManagement>
         <dependencies>
+
+            <dependency>
+                <groupId>cn.hutool</groupId>
+                <artifactId>hutool-all</artifactId>
+                <version>${hutool-version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.alibaba.cloud</groupId>
+                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
+                <version>2.2.7.RELEASE</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springframework.cloud</groupId>
+                <artifactId>spring-cloud-dependencies</artifactId>
+                <version>${spring.cloud-version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
             <dependency>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-dependencies</artifactId>
@@ -117,6 +146,24 @@
                 <version>2.5.0</version>
             </dependency>
 
+            <dependency>
+                <groupId>javax.servlet</groupId>
+                <artifactId>servlet-api</artifactId>
+                <version>${servlet-api-version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.apache.shiro</groupId>
+                <artifactId>shiro-spring</artifactId>
+                <version>${shiro.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springframework.plugin</groupId>
+                <artifactId>spring-plugin-metadata</artifactId>
+                <version>${spring.plugin.metadata-version}</version>
+            </dependency>
+
         </dependencies>
     </dependencyManagement>