Просмотр исходного кода

提供外部提供第三方服务的方案

ximinghao 1 день назад
Родитель
Сommit
07d719d974
33 измененных файлов с 1265 добавлено и 63 удалено
  1. 2 0
      .idea/encodings.xml
  2. 1 0
      .idea/misc.xml
  3. 269 0
      ExternalService/pom.xml
  4. 20 0
      ExternalService/src/main/java/com/skyversation/xjcy/XjcyExternalApplication.java
  5. 29 0
      ExternalService/src/main/java/com/skyversation/xjcy/controller/OCRController.java
  6. 33 0
      ExternalService/src/main/java/com/skyversation/xjcy/controller/PhoneMessageController.java
  7. 33 0
      ExternalService/src/main/java/com/skyversation/xjcy/controller/WeChatController.java
  8. 1 1
      ExternalService/src/main/java/com/skyversation/xjcy/service/AlyOCRService.java
  9. 66 0
      ExternalService/src/main/java/com/skyversation/xjcy/service/AlyPhoneMessageSendService.java
  10. 15 0
      ExternalService/src/main/java/com/skyversation/xjcy/service/PhoneResult.java
  11. 13 0
      ExternalService/src/main/java/com/skyversation/xjcy/service/Result.java
  12. 6 27
      ExternalService/src/main/java/com/skyversation/xjcy/service/WeChatService.java
  13. 178 0
      ExternalService/src/main/java/com/skyversation/xjcy/util/HttpUtil.java
  14. 32 0
      ExternalService/src/main/resources/application.yml
  15. 44 8
      src/main/java/com/skyversation/xjcy/config/ServiceConfiguration.java
  16. 6 7
      src/main/java/com/skyversation/xjcy/controller/OtherController.java
  17. 5 2
      src/main/java/com/skyversation/xjcy/service/AuthService.java
  18. 4 2
      src/main/java/com/skyversation/xjcy/service/MessageService.java
  19. 1 0
      src/main/java/com/skyversation/xjcy/service/PhoneService.java
  20. 57 0
      src/main/java/com/skyversation/xjcy/service/ocr/AlyOCRService.java
  21. 64 0
      src/main/java/com/skyversation/xjcy/service/ocr/ExternalOCRService.java
  22. 8 0
      src/main/java/com/skyversation/xjcy/service/ocr/OCRService.java
  23. 3 5
      src/main/java/com/skyversation/xjcy/service/phone/AlyPhoneMessageSendService.java
  24. 76 0
      src/main/java/com/skyversation/xjcy/service/phone/ExternalPhoneMessageSendService.java
  25. 1 1
      src/main/java/com/skyversation/xjcy/service/phone/PhoneMessageSendService.java
  26. 86 0
      src/main/java/com/skyversation/xjcy/service/wechat/ExternalWeChatService.java
  27. 12 0
      src/main/java/com/skyversation/xjcy/service/wechat/PhoneResult.java
  28. 13 0
      src/main/java/com/skyversation/xjcy/service/wechat/Result.java
  29. 11 0
      src/main/java/com/skyversation/xjcy/service/wechat/WeChatService.java
  30. 155 0
      src/main/java/com/skyversation/xjcy/service/wechat/WeChatServiceImpl.java
  31. 2 2
      src/main/java/com/skyversation/xjcy/service/wechat/WxServiceMock.java
  32. 1 1
      src/main/java/com/skyversation/xjcy/util/HttpUtil.java
  33. 18 7
      src/main/resources/application.yml

+ 2 - 0
.idea/encodings.xml

@@ -1,6 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="Encoding">
+    <file url="file://$PROJECT_DIR$/ExternalService/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/ExternalService/src/main/resources" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
   </component>

+ 1 - 0
.idea/misc.xml

@@ -5,6 +5,7 @@
     <option name="originalFiles">
       <list>
         <option value="$PROJECT_DIR$/pom.xml" />
+        <option value="$PROJECT_DIR$/ExternalService/pom.xml" />
       </list>
     </option>
   </component>

+ 269 - 0
ExternalService/pom.xml

@@ -0,0 +1,269 @@
+<?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">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.skyversation</groupId>
+    <artifactId>xujing_cyszpt_external</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>war</packaging>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.7.2</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <java.version>1.8</java.version>
+        <geotools.version>26.0</geotools.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-tomcat</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents.client5</groupId>
+            <artifactId>httpclient5</artifactId>
+            <version>5.3.1</version>
+        </dependency>
+        <!--            打包专用,平时注释掉-->
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <version>3.1.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+            <version>4.2.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-tomcat</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.83</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.8.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.htmlparser</groupId>
+            <artifactId>htmlparser</artifactId>
+            <version>2.1</version>
+        </dependency>
+
+        <!--        空间地理信息工具类-->
+        <dependency>
+            <groupId>org.geotools</groupId>
+            <artifactId>gt-geojson</artifactId>
+            <version>${geotools.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.geotools</groupId>
+            <artifactId>gt-main</artifactId>
+            <version>${geotools.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.geotools</groupId>
+            <artifactId>gt-opengis</artifactId>
+            <version>${geotools.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.geotools</groupId>
+            <artifactId>gt-shapefile</artifactId>
+            <version>${geotools.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.geotools</groupId>
+            <artifactId>gt-metadata</artifactId>
+            <version>${geotools.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.locationtech.jts</groupId>
+            <artifactId>jts-core</artifactId>
+            <version>1.18.2</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>31.1-jre</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>dysmsapi20170525</artifactId>
+            <version>4.1.3</version>
+        </dependency>
+        <!--        阿里云地名地址规范化-->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-address-purification</artifactId>
+            <version>1.0.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>ocr_api20210707</artifactId>
+            <version>3.1.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-core</artifactId>
+            <version>4.5.9</version>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/org.osgeo/proj4j -->
+        <dependency>
+            <groupId>org.osgeo</groupId>
+            <artifactId>proj4j</artifactId>
+            <version>0.1.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.13</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>4.5.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>3.14.9</version>
+        </dependency>
+
+        <!--        excel 解析库-->
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+            <version>3.13</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>3.13</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>core</artifactId>
+            <version>3.4.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>javase</artifactId>
+            <version>3.4.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.twelvemonkeys.imageio</groupId>
+            <artifactId>imageio-jpeg</artifactId>
+            <version>3.8.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.twelvemonkeys.imageio</groupId>
+            <artifactId>imageio-tiff</artifactId>
+            <version>3.8.0</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                        </exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>8</source>
+                    <target>8</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <repositories>
+        <repository>
+            <id>mvnrepository.com</id>
+            <url>https://mvnrepository.com/artifact</url>
+        </repository>
+        <repository>
+            <id>osgeo</id>
+            <name>OSGeo Release Repository</name>
+            <url>https://repo.osgeo.org/repository/release/</url>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+            <releases>
+                <enabled>true</enabled>
+                <!--不加如下updatePolicy会报错:resolution will not be reattempted until the update interval of XXX has elapsed or updates are force-->
+                <updatePolicy>always</updatePolicy>
+            </releases>
+        </repository>
+        <repository>
+            <id>osgeo-snapshot</id>
+            <name>OSGeo Snapshot Repository</name>
+            <url>https://repo.osgeo.org/repository/snapshot/</url>
+            <snapshots>
+                <enabled>true</enabled>
+            </snapshots>
+            <releases>
+                <enabled>false</enabled>
+            </releases>
+        </repository>
+        <repository>
+            <id>maven2-repository.dev.java.net</id>
+            <name>Java.net repository</name>
+            <url>http://download.java.net/maven/2</url>
+        </repository>
+        <repository>
+            <snapshots>
+                <enabled>true</enabled>
+            </snapshots>
+            <id>boundless</id>
+            <name>Boundless Maven Repository</name>
+            <url>http://repo.boundlessgeo.com/main</url>
+        </repository>
+    </repositories>
+</project>

+ 20 - 0
ExternalService/src/main/java/com/skyversation/xjcy/XjcyExternalApplication.java

@@ -0,0 +1,20 @@
+package com.skyversation.xjcy;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@SpringBootApplication
+@EnableScheduling
+public class XjcyExternalApplication extends SpringBootServletInitializer {
+    public static void main(String[] args) {
+        SpringApplication.run(XjcyExternalApplication.class, args);
+    }
+
+    @Override
+    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+        return application.sources(XjcyExternalApplication.class);
+    }
+}

+ 29 - 0
ExternalService/src/main/java/com/skyversation/xjcy/controller/OCRController.java

@@ -0,0 +1,29 @@
+package com.skyversation.xjcy.controller;
+
+import com.skyversation.xjcy.service.AlyOCRService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.Map;
+
+@RestController
+@RequestMapping("/ocr")
+public class OCRController {
+
+    @Autowired
+    private AlyOCRService alyOCRService;
+
+    @PostMapping(value = "/business-license", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    public ResponseEntity<Map<String, Object>> recognizeBusinessLicense(@RequestParam("file") MultipartFile file) {
+        try {
+            Map<String, Object> result = alyOCRService.ocrBusinessLicense(file.getInputStream());
+            return ResponseEntity.ok(result);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return ResponseEntity.internalServerError().build();
+        }
+    }
+}

+ 33 - 0
ExternalService/src/main/java/com/skyversation/xjcy/controller/PhoneMessageController.java

@@ -0,0 +1,33 @@
+package com.skyversation.xjcy.controller;
+
+import com.skyversation.xjcy.service.AlyPhoneMessageSendService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/phone")
+public class PhoneMessageController {
+
+    @Autowired
+    private AlyPhoneMessageSendService alyPhoneMessageSendService;
+
+    @PostMapping("/send-code")
+    public ResponseEntity<Map<String, Object>> sendCode(@RequestParam String phone, @RequestParam String code) {
+        Map<String, Object> result = new HashMap<>();
+        boolean success = alyPhoneMessageSendService.sendCode(phone, code);
+        result.put("success", success);
+        return ResponseEntity.ok(result);
+    }
+
+    @PostMapping("/send-notice")
+    public ResponseEntity<Map<String, Object>> sendNotice(@RequestParam String phone, @RequestParam String type) {
+        Map<String, Object> result = new HashMap<>();
+        boolean success = alyPhoneMessageSendService.sendNotice(phone, type);
+        result.put("success", success);
+        return ResponseEntity.ok(result);
+    }
+}

+ 33 - 0
ExternalService/src/main/java/com/skyversation/xjcy/controller/WeChatController.java

@@ -0,0 +1,33 @@
+package com.skyversation.xjcy.controller;
+
+import com.skyversation.xjcy.service.PhoneResult;
+import com.skyversation.xjcy.service.WeChatService;
+import com.skyversation.xjcy.service.Result;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/wechat")
+public class WeChatController {
+
+    @Autowired
+    private WeChatService weChatService;
+
+    @GetMapping("/login")
+    public ResponseEntity<Result> wxLogin(@RequestParam String code) {
+        Result result = weChatService.wxLogin(code);
+        return ResponseEntity.ok(result);
+    }
+
+    @GetMapping("/phone-login")
+    public ResponseEntity<PhoneResult> wxPhoneLogin(@RequestParam("js_code") String jsCode) {
+        PhoneResult result = weChatService.wxPhoneLogin(jsCode);
+        return ResponseEntity.ok(result);
+    }
+
+    @RequestMapping("/acode")
+    public ResponseEntity<byte[]> createWxacode(@RequestParam String path, @RequestParam(required = false) String scene) {
+        return weChatService.createWxacode(path, scene);
+    }
+}

+ 1 - 1
src/main/java/com/skyversation/xjcy/service/AlyOCRService.java → ExternalService/src/main/java/com/skyversation/xjcy/service/AlyOCRService.java

@@ -15,7 +15,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 @Service
-public class AlyOCRService {
+public class AlyOCRService{
 
     @Value("${app.aly.key}")
     private String key;

+ 66 - 0
ExternalService/src/main/java/com/skyversation/xjcy/service/AlyPhoneMessageSendService.java

@@ -0,0 +1,66 @@
+package com.skyversation.xjcy.service;
+
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.dysmsapi20170525.Client;
+import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
+import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
+import com.aliyun.teaopenapi.models.Config;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AlyPhoneMessageSendService{
+
+    @Value("${app.aly.key}")
+    private String key;
+    @Value("${app.aly.secret}")
+    private String secret;
+    @Value("${app.aly.message.endpoint}")
+    private String endpoint;
+
+    @Value("${app.aly.message.code.sign}")
+    private String codeSign;
+    @Value("${app.aly.message.code.template-code}")
+    private String codeTemplate;
+
+    @Value("${app.aly.message.notice.sign}")
+    private String noticeSign;
+    @Value("${app.aly.message.notice.template-code}")
+    private String noticeTemplate;
+
+    public Client createClient() throws Exception {
+        Config config = new Config();
+        config.setAccessKeyId(key);
+        config.setAccessKeySecret(secret);
+        config.setEndpoint(endpoint);
+//        config.setHttpProxy("http://42.121.218.163:1234");
+//        config.setHttpsProxy("http://42.121.218.163:1234");
+        return new Client(config);
+    }
+    public boolean sendCode(String phone, String code) {
+        JSONObject obj = new JSONObject();
+        obj.put("code", code);
+        return send(phone, obj, codeSign, codeTemplate);
+    }
+
+    public boolean sendNotice(String phone, String type) {
+        JSONObject obj = new JSONObject();
+        obj.put("type", type);
+        return send(phone, obj, noticeSign, noticeTemplate);
+    }
+
+    private boolean send(String phone, JSONObject obj, String noticeSign, String noticeTemplate) {
+        SendSmsRequest sendSmsRequest = new SendSmsRequest()
+                .setPhoneNumbers(phone)
+                .setSignName(noticeSign)
+                .setTemplateCode(noticeTemplate)
+                .setTemplateParam(obj.toJSONString());
+        try {
+            SendSmsResponse sendSmsResponse = createClient().sendSms(sendSmsRequest);
+            return sendSmsResponse.getStatusCode() == 200;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+}

+ 15 - 0
ExternalService/src/main/java/com/skyversation/xjcy/service/PhoneResult.java

@@ -0,0 +1,15 @@
+package com.skyversation.xjcy.service;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@AllArgsConstructor
+public class PhoneResult {
+    private final boolean success;
+    private final String error;
+    private final String phone;
+
+
+}

+ 13 - 0
ExternalService/src/main/java/com/skyversation/xjcy/service/Result.java

@@ -0,0 +1,13 @@
+package com.skyversation.xjcy.service;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public class Result {
+    private final boolean success;
+    private final String error;
+    private final String wxOpenId;
+    private final String wxSessionKey;
+}

+ 6 - 27
src/main/java/com/skyversation/xjcy/service/WeChatService.java → ExternalService/src/main/java/com/skyversation/xjcy/service/WeChatService.java

@@ -2,13 +2,8 @@ package com.skyversation.xjcy.service;
 
 import com.alibaba.fastjson.JSONObject;
 import com.skyversation.xjcy.util.HttpUtil;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-import org.apache.http.Header;
-import org.htmlparser.http.HttpHeader;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpMethod;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
 import org.springframework.util.LinkedMultiValueMap;
@@ -21,31 +16,14 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
-@SuppressWarnings("SpellCheckingInspection")
-public class WeChatService {
-
-    @Getter
-    @AllArgsConstructor
-    public static class Result {
-        private final boolean success;
-        private final String error;
-        private final String wxOpenId;
-        private final String wxSessionKey;
-    }
-
-    @Getter
-    @AllArgsConstructor
-    public static class PhoneResult {
-        private final boolean success;
-        private final String error;
-        private final String phone;
-    }
+@Service
+public class WeChatService{
 
     @Value("${app.wechat.secret-key}")
     private String secretKey;
     @Value("${app.wechat.appid}")
     private String appid;
-    @Value("${app.wechat.api-base-url:https://api.weixin.qq.com}")
+    @Value("${app.wechat.api-base-url}")
     private String apiBaseUrl;
 
     private String accessToken;
@@ -81,7 +59,7 @@ public class WeChatService {
     public PhoneResult wxPhoneLogin(String jsCode) {
         String currectUrl = apiBaseUrl + WX_PHONE_PATH + getAccessToken();
 
-        Map<String, String> params = new HashMap<>();
+        Map<String, Object> params = new HashMap<>();
         params.put("code", jsCode);
         Map<String, String> headers = new HashMap<>();
         headers.put("Content-Type", "application/json");
@@ -102,9 +80,10 @@ public class WeChatService {
     }
 
     public ResponseEntity<byte[]> createWxacode(String path, String scene) {
-        Map<String, String> body = new HashMap<>();
+        Map<String, Object> body = new HashMap<>();
         body.put("page", path);
         body.put("scene", scene);
+        body.put("check_path", false);
         ResponseEntity<byte[]> res = HttpUtil.requestPostWithJson(
                 apiBaseUrl + WX_ACODE_PATH + getAccessToken(),
                 body,

+ 178 - 0
ExternalService/src/main/java/com/skyversation/xjcy/util/HttpUtil.java

@@ -0,0 +1,178 @@
+package com.skyversation.xjcy.util;
+
+import com.alibaba.fastjson.JSON;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.TrustStrategy;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.ssl.SSLContextBuilder;
+import org.apache.http.util.EntityUtils;
+import org.springframework.http.*;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.stereotype.Service;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import javax.net.ssl.SSLContext;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Service
+public class HttpUtil {
+
+    public List<String> tempToken = new ArrayList<>();
+    public boolean isRun = false;
+
+
+    public static String requestPost(String url, MultiValueMap<String, Object> params, Map<String, String> headerMap) {
+        RestTemplate client = getRestTemplate();
+
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
+
+        if (headerMap != null) {
+            Set<String> sets = headerMap.keySet();
+            for (String key : sets) {
+                headers.add(key, headerMap.get(key));
+            }
+        }
+
+        HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(params, headers);
+        ResponseEntity<String> response = client.exchange(url, HttpMethod.POST, requestEntity, String.class);
+        return response.getBody();
+    }
+
+    public static String requestGet(String url,
+                                    MultiValueMap<String, String> params,
+                                    Map<String, String> headerMap) {
+
+        ResponseEntity<String> res = requestWithRes(url, params,null, headerMap, String.class, HttpMethod.GET);
+        return res.getBody();
+    }
+
+    public static <T> ResponseEntity<T> requestWithRes(String url,
+                                                       MultiValueMap<String, String> params,
+                                                       MultiValueMap<String, String> body,
+                                                       Map<String, String> headerMap,
+                                                       Class<T> clazz,
+                                                       HttpMethod method) {
+        RestTemplate client = getRestTemplate();
+
+        // 构建带参数的完整 URL
+        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
+        if (params != null) {
+            builder.queryParams(params);
+        }
+
+        // 构建 headers
+        HttpHeaders headers = new HttpHeaders();
+        if (headerMap != null) {
+            headerMap.forEach(headers::add);
+        }
+        HttpEntity<?> entity;
+        if (body != null) {
+            entity = new HttpEntity<>(body, headers);
+        }else {
+            entity = new HttpEntity<>(headers);
+        }
+        URI uri = URI.create(builder.toUriString());
+        return client.exchange(uri, method, entity, clazz);
+    }
+
+    private static RestTemplate getRestTemplate() {
+        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
+        requestFactory.setConnectTimeout(10 * 1000);
+        requestFactory.setReadTimeout(10 * 1000);
+        RestTemplate client = new RestTemplate(requestFactory);
+        client.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
+        return client;
+    }
+
+    public static ResponseEntity<byte[]> requestPostWithJson(String url,
+                                                             Map<String, Object> body,
+                                                             Map<String, String> headerMap){
+        RestTemplate client = getRestTemplate();
+
+        // 构建带参数的完整 URL
+        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
+
+        // 构建 headers
+        HttpHeaders headers = new HttpHeaders();
+        if (headerMap != null) {
+            headerMap.forEach(headers::add);
+        }
+        headers.setContentType(MediaType.APPLICATION_JSON);
+        HttpEntity<?> entity;
+        if (body != null) {
+            String json = JSON.toJSONString(body);
+             entity = new HttpEntity<>(json, headers);
+        }else {
+            entity = new HttpEntity<>(headers);
+        }
+        URI uri = URI.create(builder.toUriString());
+        return client.exchange(uri, HttpMethod.POST, entity, byte[].class);
+    }
+
+    public static ResponseEntity<String> requestGetHttps(String url, MultiValueMap<String, String> params, Map<String, String> headerMap) {
+        try {
+            // 创建忽略 SSL 验证的 HttpClient
+            TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
+            SSLContext sslContext = SSLContextBuilder.create()
+                    .loadTrustMaterial(null, acceptingTrustStrategy)
+                    .build();
+
+            CloseableHttpClient httpClient = HttpClients.custom()
+                    .setSSLContext(sslContext)
+                    .setSSLHostnameVerifier(new NoopHostnameVerifier())
+                    .build();
+
+            // 创建使用自定义 HttpClient 的 RequestFactory
+            HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
+            requestFactory.setHttpClient(httpClient);
+            requestFactory.setConnectTimeout(10 * 1000);
+            requestFactory.setReadTimeout(10 * 1000);
+
+            RestTemplate client = new RestTemplate(requestFactory);
+            client.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
+
+            HttpHeaders headers = new HttpHeaders();
+            // 请勿轻易改变此提交方式,大部分的情况下,提交方式都是表单提交
+            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+            if (headerMap != null) {
+                Set<String> sets = headerMap.keySet();
+                for (String key : sets) {
+                    headers.add(key, headerMap.get(key));
+                }
+            }
+
+            HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers);
+            // 执行 HTTP 请求
+            return client.exchange(url, HttpMethod.GET, requestEntity, String.class);
+        } catch (KeyStoreException | NoSuchAlgorithmException | KeyManagementException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    public static String sendGetRequest(String urlString) throws Exception {
+        HttpClient httpClient = HttpClients.createDefault();
+        HttpGet httpGet = new HttpGet(urlString);
+        HttpResponse response = httpClient.execute(httpGet);
+        HttpEntity entity = (HttpEntity) response.getEntity();
+        return EntityUtils.toString((org.apache.http.HttpEntity) entity);
+    }
+}

+ 32 - 0
ExternalService/src/main/resources/application.yml

@@ -0,0 +1,32 @@
+server:
+  port: 10019
+  servlet:
+    context-path: /xjcy-external/
+spring:
+  application:
+    name: xjcy-external
+  mvc:
+    async:
+      request-timeout: 60000
+  servlet:
+    multipart:
+      max-file-size: 3000MB
+      max-request-size: 3000MB
+app:
+  wechat:
+    appid: ${XJCY_WECHAT_APPID:wx125843453562c86c}
+    secret-key: ${XJCY_WECHAT_SECRET:6028cc345cfdbc76224d750a13519762}
+    api-base-url: ${WECHAT_API_BASE_URL:https://api.weixin.qq.com}
+  aly:
+    key: ${ALIBABA_CLOUD_ACCESS_KEY_ID:LTAI5tGkCWh7nvNzY5vKNWdZ}
+    secret: ${ALIBABA_CLOUD_ACCESS_KEY_SECRET:YivC1800cacnEpcS3OSQKIcr5Itgal}
+    message:
+      endpoint: ${ALIBABA_CLOUD_MESSAGE_ENDPOINT:dysmsapi.aliyuncs.com}
+      code:
+        sign: ${XJCY_ALIBABA_CODE_SIGN:上海元以数智科技}
+        template-code: ${XJCY_ALIBABA_CODE_TEMPLATE:SMS_496690245}
+      notice:
+        sign: ${XJCY_ALIBABA_NOTICE_SIGN:数智科技}
+        template-code: ${XJCY_ALIBABA_NOTICE_TEMPLATE:SMS_500695141}
+    ocr:
+      endpoint: ${ALIBABA_CLOUD_OCR_ENDPOINT:ocr-api.cn-hangzhou.aliyuncs.com}

+ 44 - 8
src/main/java/com/skyversation/xjcy/config/ServiceConfiguration.java

@@ -1,7 +1,15 @@
 package com.skyversation.xjcy.config;
 
-import com.skyversation.xjcy.service.WeChatService;
-import com.skyversation.xjcy.service.WxServiceMock;
+import com.skyversation.xjcy.service.ocr.OCRService;
+import com.skyversation.xjcy.service.ocr.AlyOCRService;
+import com.skyversation.xjcy.service.ocr.ExternalOCRService;
+import com.skyversation.xjcy.service.phone.PhoneMessageSendService;
+import com.skyversation.xjcy.service.phone.AlyPhoneMessageSendService;
+import com.skyversation.xjcy.service.phone.ExternalPhoneMessageSendService;
+import com.skyversation.xjcy.service.wechat.ExternalWeChatService;
+import com.skyversation.xjcy.service.wechat.WeChatService;
+import com.skyversation.xjcy.service.wechat.WeChatServiceImpl;
+import com.skyversation.xjcy.service.wechat.WxServiceMock;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -9,17 +17,45 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 @Configuration
 public class ServiceConfiguration {
 
-    // 当 app.wechat.enabled 为 true 时,注入全功能服务
     @Bean
-    @ConditionalOnProperty(prefix = "app.wechat", name = "enable", havingValue = "true")
+    @ConditionalOnProperty(prefix = "app.wechat", name = "mode", havingValue = "full")
     public WeChatService fullWechatService() {
-        return new WeChatService();
+        return new WeChatServiceImpl();
     }
 
-    // 当 app.wechat.enabled 为 false 或不存在时,注入降级服务 (matchIfMissing = true 确保了默认行为)
     @Bean
-    @ConditionalOnProperty(prefix = "app.wechat", name = "enable", havingValue = "false", matchIfMissing = true)
-    public WeChatService noopWechatService() {
+    @ConditionalOnProperty(prefix = "app.wechat", name = "mode", havingValue = "mock", matchIfMissing = true)
+    public WeChatService mockWechatService() {
         return new WxServiceMock();
     }
+
+    @Bean
+    @ConditionalOnProperty(prefix = "app.wechat", name = "mode", havingValue = "external")
+    public WeChatService externalWechatService() {
+        return new ExternalWeChatService();
+    }
+
+    @Bean
+    @ConditionalOnProperty(prefix = "app.ocr", name = "mode", havingValue = "aliyun")
+    public OCRService aliyunOCRService() {
+        return new AlyOCRService();
+    }
+
+    @Bean
+    @ConditionalOnProperty(prefix = "app.ocr", name = "mode", havingValue = "external", matchIfMissing = true)
+    public OCRService externalOCRService() {
+        return new ExternalOCRService();
+    }
+
+    @Bean
+    @ConditionalOnProperty(prefix = "app.phone.message", name = "mode", havingValue = "aliyun")
+    public PhoneMessageSendService aliyunPhoneMessageSendService() {
+        return new AlyPhoneMessageSendService();
+    }
+
+    @Bean
+    @ConditionalOnProperty(prefix = "app.phone.message", name = "mode", havingValue = "external", matchIfMissing = true)
+    public PhoneMessageSendService externalPhoneMessageSendService() {
+        return new ExternalPhoneMessageSendService();
+    }
 }

+ 6 - 7
src/main/java/com/skyversation/xjcy/controller/OtherController.java

@@ -9,10 +9,10 @@ import com.skyversation.xjcy.bean.User;
 import com.skyversation.xjcy.dms.DMSColumn;
 import com.skyversation.xjcy.dms.DMSColumnConfig;
 import com.skyversation.xjcy.dms.DMSService;
-import com.skyversation.xjcy.service.AlyOCRService;
+import com.skyversation.xjcy.service.ocr.OCRService;
 import com.skyversation.xjcy.service.AuthService;
 import com.skyversation.xjcy.service.MessageService;
-import com.skyversation.xjcy.service.WeChatService;
+import com.skyversation.xjcy.service.wechat.WeChatService;
 import com.skyversation.xjcy.util.MessageManage;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.MediaType;
@@ -24,7 +24,6 @@ import org.springframework.web.multipart.MultipartFile;
 import javax.imageio.ImageIO;
 import javax.servlet.http.HttpServletRequest;
 import java.awt.image.BufferedImage;
-import java.io.File;
 import java.io.InputStream;
 import java.util.*;
 
@@ -39,15 +38,15 @@ public class OtherController {
     private final WeChatService weChatService;
     private final AuthService authService;
     private final DMSService dMSService;
-    private final AlyOCRService alyOCRService;
+    private final OCRService OCRService;
     private final MessageService messageService;
     private final DMSColumnConfig dMSColumnConfig;
 
-    public OtherController(WeChatService weChatService, AuthService authService, DMSService dMSService, AlyOCRService alyOCRService, MessageService messageService, DMSColumnConfig dMSColumnConfig) {
+    public OtherController(WeChatService weChatService, AuthService authService, DMSService dMSService, OCRService OCRService, MessageService messageService, DMSColumnConfig dMSColumnConfig) {
         this.weChatService = weChatService;
         this.authService = authService;
         this.dMSService = dMSService;
-        this.alyOCRService = alyOCRService;
+        this.OCRService = OCRService;
         this.messageService = messageService;
         this.dMSColumnConfig = dMSColumnConfig;
     }
@@ -125,7 +124,7 @@ public class OtherController {
     ){
         try {
             InputStream inputStream = file.getInputStream();
-            Map<String,Object> result =alyOCRService.ocrBusinessLicense(inputStream);
+            Map<String,Object> result = OCRService.ocrBusinessLicense(inputStream);
             if (result.isEmpty()){
                 return MessageManage.error(-1,"解析二维码失败");
             }

+ 5 - 2
src/main/java/com/skyversation/xjcy/service/AuthService.java

@@ -5,6 +5,9 @@ import com.alibaba.fastjson.JSONObject;
 import com.skyversation.xjcy.bean.User;
 import com.skyversation.xjcy.dms.DMSColumn;
 import com.skyversation.xjcy.dms.DMSService;
+import com.skyversation.xjcy.service.wechat.PhoneResult;
+import com.skyversation.xjcy.service.wechat.Result;
+import com.skyversation.xjcy.service.wechat.WeChatService;
 import com.skyversation.xjcy.util.HashBasedLockManager;
 import com.skyversation.xjcy.util.HttpUtil;
 import com.skyversation.xjcy.util.MessageManage;
@@ -326,7 +329,7 @@ public class AuthService {
      * @return 完整的response.body
      */
     public String logOrRegWxAccount(String wxCode) {
-        WeChatService.Result wxResult = weChatService.wxLogin(wxCode);
+        Result wxResult = weChatService.wxLogin(wxCode);
         if (wxResult.isSuccess()) {
             return logOrReg(generateAccount(wxResult.getWxOpenId(), WX_NAME_PREFIX, WX_PASSWORD_PREFIX, WX_SALT));
         } else {
@@ -359,7 +362,7 @@ public class AuthService {
      * @return 完整的response.body
      */
     public String logOrRegWeChatPhoneAccount(String jsCode) {
-        WeChatService.PhoneResult result = weChatService.wxPhoneLogin(jsCode);
+        PhoneResult result = weChatService.wxPhoneLogin(jsCode);
         if (result.isSuccess()) {
             return logOrReg(generateAccount(result.getPhone(), PHONE_NAME_PREFIX, PHONE_PASSWORD_PREFIX, PHONE_SALT,result.getPhone()));
         } else {

+ 4 - 2
src/main/java/com/skyversation/xjcy/service/MessageService.java

@@ -4,6 +4,8 @@ import com.skyversation.xjcy.bean.*;
 import com.skyversation.xjcy.dms.DMSColumn;
 import com.skyversation.xjcy.dms.DMSQuery;
 import com.skyversation.xjcy.dms.DMSService;
+import com.skyversation.xjcy.service.phone.AlyPhoneMessageSendService;
+import com.skyversation.xjcy.service.phone.PhoneMessageSendService;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDate;
@@ -16,10 +18,10 @@ public class MessageService {
     private final AuthService authService;
     private final PhoneMessageSendService phoneMessageSendService;
 
-    public MessageService(DMSService dMSService, AuthService authService, AlyPhoneMessageSendService alyPhoneMessageSendService) {
+    public MessageService(DMSService dMSService, AuthService authService, PhoneMessageSendService phoneMessageSendService) {
         this.dMSService = dMSService;
         this.authService = authService;
-        this.phoneMessageSendService = alyPhoneMessageSendService;
+        this.phoneMessageSendService = phoneMessageSendService;
     }
 
     public void sendMessage(Message message) {

+ 1 - 0
src/main/java/com/skyversation/xjcy/service/PhoneService.java

@@ -1,5 +1,6 @@
 package com.skyversation.xjcy.service;
 
+import com.skyversation.xjcy.service.phone.PhoneMessageSendService;
 import com.skyversation.xjcy.util.cache.ExpirableCache;
 import com.skyversation.xjcy.util.cache.UnifiedExpirationGuavaCacheImpl;
 import lombok.AllArgsConstructor;

+ 57 - 0
src/main/java/com/skyversation/xjcy/service/ocr/AlyOCRService.java

@@ -0,0 +1,57 @@
+package com.skyversation.xjcy.service.ocr;
+
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.ocr_api20210707.Client;
+import com.aliyun.ocr_api20210707.models.RecognizeBusinessLicenseRequest;
+import com.aliyun.ocr_api20210707.models.RecognizeBusinessLicenseResponse;
+import com.aliyun.tea.TeaException;
+import com.aliyun.teaopenapi.models.Config;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class AlyOCRService implements OCRService {
+
+    @Value("${app.aly.key}")
+    private String key;
+    @Value("${app.aly.secret}")
+    private String secret;
+    @Value("${app.aly.ocr.endpoint}")
+    private String endpoint;
+
+    public Client createClient() throws Exception {
+        Config config = new Config();
+        config.setAccessKeyId(key);
+        config.setAccessKeySecret(secret);
+        config.setEndpoint(endpoint);
+        return new Client(config);
+    }
+
+    @Override
+    public Map<String, Object> ocrBusinessLicense(InputStream is) throws Exception {
+
+        Client client = createClient();
+        RecognizeBusinessLicenseRequest request = new RecognizeBusinessLicenseRequest();
+        request.setBody(is);
+
+        com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
+        try {
+            RecognizeBusinessLicenseResponse resp = client.recognizeBusinessLicenseWithOptions(request, runtime);
+            Map<String,Object> output = new HashMap<>();
+            output.put("content", JSONObject.parseObject(resp.getBody().getData()).getJSONObject("data"));
+            return output;
+        } catch (TeaException error) {
+            System.out.println(error.getMessage());
+            System.out.println(error.getData().get("Recommend"));
+        } catch (Exception _error) {
+            TeaException error = new TeaException(_error.getMessage(), _error);
+            System.out.println(error.getMessage());
+            System.out.println(error.getData().get("Recommend"));
+        }
+        return Collections.emptyMap();
+    }
+}

+ 64 - 0
src/main/java/com/skyversation/xjcy/service/ocr/ExternalOCRService.java

@@ -0,0 +1,64 @@
+package com.skyversation.xjcy.service.ocr;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.TypeReference;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.*;
+import org.springframework.stereotype.Service;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.core.io.ByteArrayResource;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.Map;
+
+public class ExternalOCRService implements OCRService {
+    
+    @Value("${app.external.service.url}")
+    private String externalServiceUrl;
+    
+    private final RestTemplate restTemplate = new RestTemplate();
+    
+    @Override
+    public Map<String, Object> ocrBusinessLicense(InputStream is) throws Exception {
+        try {
+            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+            int nRead;
+            byte[] data = new byte[4096];
+            while ((nRead = is.read(data, 0, data.length)) != -1) {
+                buffer.write(data, 0, nRead);
+            }
+            buffer.flush();
+            byte[] fileBytes = buffer.toByteArray();
+            
+            MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
+            body.add("file", new ByteArrayResource(fileBytes) {
+                @Override
+                public String getFilename() {
+                    return "business_license.jpg";
+                }
+            });
+            
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.MULTIPART_FORM_DATA);
+            
+            HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
+            
+            String url = externalServiceUrl + "/ocr/business-license";
+            
+            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
+            
+            if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
+                return JSON.parseObject(response.getBody(), new TypeReference<Map<String, Object>>() {});
+            }
+            
+            return Collections.emptyMap();
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Collections.emptyMap();
+        }
+    }
+}

+ 8 - 0
src/main/java/com/skyversation/xjcy/service/ocr/OCRService.java

@@ -0,0 +1,8 @@
+package com.skyversation.xjcy.service.ocr;
+
+import java.io.InputStream;
+import java.util.Map;
+
+public interface OCRService {
+    Map<String, Object> ocrBusinessLicense(InputStream is) throws Exception;
+}

+ 3 - 5
src/main/java/com/skyversation/xjcy/service/AlyPhoneMessageSendService.java → src/main/java/com/skyversation/xjcy/service/phone/AlyPhoneMessageSendService.java

@@ -1,17 +1,13 @@
-package com.skyversation.xjcy.service;
+package com.skyversation.xjcy.service.phone;
 
 import com.alibaba.fastjson.JSONObject;
 import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
 import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
 import com.aliyun.teaopenapi.models.Config;
 import com.aliyun.dysmsapi20170525.Client;
-import com.aliyun.teautil.models.RuntimeOptions;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
-import javax.annotation.PostConstruct;
-
-@Service
 public class AlyPhoneMessageSendService implements PhoneMessageSendService {
 
     @Value("${app.aly.key}")
@@ -36,6 +32,8 @@ public class AlyPhoneMessageSendService implements PhoneMessageSendService {
         config.setAccessKeyId(key);
         config.setAccessKeySecret(secret);
         config.setEndpoint(endpoint);
+//        config.setHttpProxy("http://42.121.218.163:1234");
+//        config.setHttpsProxy("http://42.121.218.163:1234");
         return new Client(config);
     }
     @Override

+ 76 - 0
src/main/java/com/skyversation/xjcy/service/phone/ExternalPhoneMessageSendService.java

@@ -0,0 +1,76 @@
+package com.skyversation.xjcy.service.phone;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.*;
+import org.springframework.stereotype.Service;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Map;
+
+public class ExternalPhoneMessageSendService implements PhoneMessageSendService {
+    
+    @Value("${app.external.service.url}")
+    private String externalServiceUrl;
+    
+    private final RestTemplate restTemplate = new RestTemplate();
+    
+    @Override
+    public boolean sendCode(String phone, String code) {
+        try {
+            MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
+            params.add("phone", phone);
+            params.add("code", code);
+            
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+            
+            HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers);
+            
+            String url = externalServiceUrl + "/phone/send-code";
+            
+            ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class);
+            
+            if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
+                JSONObject result = JSON.parseObject(response.getBody());
+                return result.getBooleanValue("success");
+            }
+            
+            return false;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    @Override
+    public boolean sendNotice(String phone, String type) {
+        try {
+            MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
+            params.add("phone", phone);
+            params.add("type", type);
+            
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+            
+            HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers);
+            
+            String url = externalServiceUrl + "/phone/send-notice";
+            
+            ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class);
+            
+            if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
+                JSONObject result = JSON.parseObject(response.getBody());
+                return result.getBooleanValue("success");
+            }
+            
+            return false;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+}

+ 1 - 1
src/main/java/com/skyversation/xjcy/service/PhoneMessageSendService.java → src/main/java/com/skyversation/xjcy/service/phone/PhoneMessageSendService.java

@@ -1,4 +1,4 @@
-package com.skyversation.xjcy.service;
+package com.skyversation.xjcy.service.phone;
 
 public interface PhoneMessageSendService {
     boolean sendCode(String phone, String code);

+ 86 - 0
src/main/java/com/skyversation/xjcy/service/wechat/ExternalWeChatService.java

@@ -0,0 +1,86 @@
+package com.skyversation.xjcy.service.wechat;
+
+import com.alibaba.fastjson.JSON;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.*;
+import org.springframework.stereotype.Service;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+public class ExternalWeChatService implements WeChatService {
+    
+    @Value("${app.external.service.url}")
+    private String externalServiceUrl;
+    
+    private final RestTemplate restTemplate = new RestTemplate();
+    
+    @Override
+    public Result wxLogin(String code) {
+        try {
+            String url = UriComponentsBuilder.fromHttpUrl(externalServiceUrl + "/wechat/login")
+                    .queryParam("code", code)
+                    .toUriString();
+            
+            ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
+            
+            if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
+                return JSON.parseObject(response.getBody(), Result.class);
+            }
+            
+            return new Result(false, "调用外部微信服务失败", null, null);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return new Result(false, "调用外部微信服务异常: " + e.getMessage(), null, null);
+        }
+    }
+
+    @Override
+    public PhoneResult wxPhoneLogin(String jsCode) {
+        try {
+            String url = UriComponentsBuilder.fromHttpUrl(externalServiceUrl + "/wechat/phone-login")
+                    .queryParam("js_code", jsCode)
+                    .toUriString();
+            
+            ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
+            
+            if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
+                return JSON.parseObject(response.getBody(), PhoneResult.class);
+            }
+            
+            return new PhoneResult(false, "调用外部微信服务失败", null);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return new PhoneResult(false, "调用外部微信服务异常: " + e.getMessage(), null);
+        }
+    }
+
+    @Override
+    public ResponseEntity<byte[]> createWxacode(String path, String scene) {
+        try {
+            String url = externalServiceUrl + "/wechat/acode";
+            
+            // 构建请求参数
+            MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
+            params.add("path", path);
+            if (scene != null && !scene.isEmpty()) {
+                params.add("scene", scene);
+            }
+            
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+            HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
+            
+            ResponseEntity<byte[]> response = restTemplate.exchange(url, HttpMethod.POST, request, byte[].class);
+            
+            HttpHeaders responseHeaders = new HttpHeaders();
+            responseHeaders.putAll(response.getHeaders());
+            
+            return new ResponseEntity<>(response.getBody(), responseHeaders, response.getStatusCode());
+        } catch (Exception e) {
+            e.printStackTrace();
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
+        }
+    }
+}

+ 12 - 0
src/main/java/com/skyversation/xjcy/service/wechat/PhoneResult.java

@@ -0,0 +1,12 @@
+package com.skyversation.xjcy.service.wechat;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public class PhoneResult {
+    private final boolean success;
+    private final String error;
+    private final String phone;
+}

+ 13 - 0
src/main/java/com/skyversation/xjcy/service/wechat/Result.java

@@ -0,0 +1,13 @@
+package com.skyversation.xjcy.service.wechat;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public class Result {
+    private final boolean success;
+    private final String error;
+    private final String wxOpenId;
+    private final String wxSessionKey;
+}

+ 11 - 0
src/main/java/com/skyversation/xjcy/service/wechat/WeChatService.java

@@ -0,0 +1,11 @@
+package com.skyversation.xjcy.service.wechat;
+
+import org.springframework.http.ResponseEntity;
+
+public interface WeChatService {
+    Result wxLogin(String code);
+
+    PhoneResult wxPhoneLogin(String jsCode);
+
+    ResponseEntity<byte[]> createWxacode(String path, String scene);
+}

+ 155 - 0
src/main/java/com/skyversation/xjcy/service/wechat/WeChatServiceImpl.java

@@ -0,0 +1,155 @@
+package com.skyversation.xjcy.service.wechat;
+
+import com.alibaba.fastjson.JSONObject;
+import com.skyversation.xjcy.util.HttpUtil;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+public class WeChatServiceImpl implements WeChatService {
+
+    @Value("${app.wechat.secret-key}")
+    private String secretKey;
+    @Value("${app.wechat.appid}")
+    private String appid;
+    @Value("${app.wechat.api-base-url}")
+    private String apiBaseUrl;
+
+    private String accessToken;
+    private LocalDateTime accessTokenExpireTime;
+
+    private static final String WX_AUTH_PATH = "/sns/jscode2session";
+    private static final String WX_ACCESS_KEY_PATH = "/cgi-bin/token";
+    private static final String WX_ACODE_PATH = "/wxa/getwxacodeunlimit?access_token=";
+    private static final String WX_PHONE_PATH = "/wxa/business/getuserphonenumber?access_token=";
+
+    @Override
+    public Result wxLogin(String code) {
+        MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
+        params.add("appid", appid);
+        params.add("secret", secretKey);
+        params.add("js_code", code);
+        params.add("grant_type", "authorization_code");
+        String body = HttpUtil.requestGet(apiBaseUrl + WX_AUTH_PATH, params, null);
+        try {
+            JSONObject jsonObject = JSONObject.parseObject(body);
+            String openId = jsonObject.getString("openid");
+            String sessionKey = jsonObject.getString("session_key");
+            Integer errcode = jsonObject.getInteger("errcode");
+            if (errcode == null || errcode == 0) {
+                return new Result(true, null, openId, sessionKey);
+            } else {
+                return new Result(false, errcode == 40029 ? "提供的code无效" : "微信鉴权异常" + errcode, null, null);
+            }
+        } catch (NullPointerException e) {
+            return new Result(false, "无法访问wx鉴权接口", null, null);
+        }
+    }
+
+    @Override
+    public PhoneResult wxPhoneLogin(String jsCode) {
+        String currectUrl = apiBaseUrl + WX_PHONE_PATH + getAccessToken();
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("code", jsCode);
+        Map<String, String> headers = new HashMap<>();
+        headers.put("Content-Type", "application/json");
+        String body = new String(Objects.requireNonNull(HttpUtil.requestPostWithJson(currectUrl, params, headers).getBody()),StandardCharsets.UTF_8);
+        try {
+            JSONObject jsonObject = JSONObject.parseObject(body);
+            Integer errcode = jsonObject.getInteger("errcode");
+            if (errcode == null || errcode == 0) {
+                JSONObject phoneInfo = jsonObject.getJSONObject("phone_info");
+                String phoneNumber = phoneInfo.getString("phoneNumber");
+                return new PhoneResult(true, null, phoneNumber);
+            } else {
+                return new PhoneResult(false, errcode == 40029 ? "提供的code无效" : "微信鉴权异常" + errcode, null);
+            }
+        } catch (NullPointerException e) {
+            return new PhoneResult(false, "无法访问wx鉴权接口", null);
+        }
+    }
+
+    @Override
+    public ResponseEntity<byte[]> createWxacode(String path, String scene) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("page", path);
+        body.put("scene", scene);
+        body.put("check_path", false);
+        ResponseEntity<byte[]> res = HttpUtil.requestPostWithJson(
+                apiBaseUrl + WX_ACODE_PATH + getAccessToken(),
+                body,
+                null
+        );
+
+        if (Objects.requireNonNull(res.getHeaders().get(HttpHeaders.CONTENT_TYPE)).contains("application/json")) {
+            if (res.getBody() != null) {
+                String json = new String(res.getBody(), StandardCharsets.UTF_8);
+                JSONObject jsonObject = JSONObject.parseObject(json);
+                String errcode = jsonObject.getString("errcode");
+                if (errcode != null && errcode.equals("40001")) {
+                    refreshAccessToken();
+                    res = HttpUtil.requestPostWithJson(
+                            apiBaseUrl + WX_ACODE_PATH + getAccessToken(),
+                            body,
+                            null
+                    );
+                }
+            }
+        }
+
+        HttpHeaders headers = new HttpHeaders();
+        copyHeader(headers, res.getHeaders(), HttpHeaders.CONTENT_TYPE);
+        copyHeader(headers, res.getHeaders(), HttpHeaders.CONTENT_LENGTH);
+
+        headers.add(HttpHeaders.CACHE_CONTROL, "public, max-age=3600");
+        return new ResponseEntity<>(res.getBody(), headers, res.getStatusCode());
+    }
+
+    private static void copyHeader(HttpHeaders copyTo, HttpHeaders orange, String header) {
+        List<String> list = orange.get(header);
+        if (list != null) {
+            for (String str : list) {
+                copyTo.add(header, str);
+            }
+        }
+
+    }
+
+    private String getAccessToken() {
+        if (accessTokenExpireTime == null || accessTokenExpireTime.isBefore(LocalDateTime.now())) {
+            refreshAccessToken();
+        }
+        if (accessToken == null || accessToken.isEmpty()) {
+            refreshAccessToken();
+        }
+        return accessToken;
+    }
+
+    private void refreshAccessToken() {
+        MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
+        params.add("appid", appid);
+        params.add("secret", secretKey);
+        params.add("grant_type", "client_credential");
+        String body = HttpUtil.requestGet(apiBaseUrl + WX_ACCESS_KEY_PATH, params, null);
+        try {
+            JSONObject jsonObject = JSONObject.parseObject(body);
+            String accessToken = jsonObject.getString("access_token");
+            int sessionKey = jsonObject.getInteger("expires_in");
+            this.accessToken = accessToken;
+            this.accessTokenExpireTime = LocalDateTime.now().plusSeconds(sessionKey);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+}

+ 2 - 2
src/main/java/com/skyversation/xjcy/service/WxServiceMock.java → src/main/java/com/skyversation/xjcy/service/wechat/WxServiceMock.java

@@ -1,8 +1,8 @@
-package com.skyversation.xjcy.service;
+package com.skyversation.xjcy.service.wechat;
 
 import org.springframework.http.ResponseEntity;
 
-public class WxServiceMock extends WeChatService {
+public class WxServiceMock implements WeChatService {
     @Override
     public Result wxLogin(String code) {
         return new Result(true,null,"abc1234567890defabc1234567890def","");

+ 1 - 1
src/main/java/com/skyversation/xjcy/util/HttpUtil.java

@@ -100,7 +100,7 @@ public class HttpUtil {
     }
 
     public static ResponseEntity<byte[]> requestPostWithJson(String url,
-                                                             Map<String, String> body,
+                                                             Map<String, Object> body,
                                                              Map<String, String> headerMap){
         RestTemplate client = getRestTemplate();
 

+ 18 - 7
src/main/resources/application.yml

@@ -4,7 +4,7 @@ server:
     context-path: /xjcy/
 spring:
   config:
-    import: file:${catalina.base}/conf/apps/xjcy/
+    import: optional:file:${catalina.base}/conf/apps/xjcy/
   application:
     name: xjcy
   mvc:
@@ -40,7 +40,15 @@ app:
     appid: ${XJCY_WECHAT_APPID:wx125843453562c86c}
     secret-key: ${XJCY_WECHAT_SECRET:6028cc345cfdbc76224d750a13519762}
     api-base-url: ${WECHAT_API_BASE_URL:https://api.weixin.qq.com}
-    enable: true
+    mode: ${WECHAT_MODE:external}
+  ocr:
+    mode: ${OCR_MODE:external}
+  phone:
+    message:
+      mode: ${PHONE_MESSAGE_MODE:external}
+  external:
+    service:
+      url: ${EXTERNAL_SERVICE_URL:http://localhost:10019/xjcy-external}
   aly:
     key: ${ALIBABA_CLOUD_ACCESS_KEY_ID:LTAI5tGkCWh7nvNzY5vKNWdZ}
     secret: ${ALIBABA_CLOUD_ACCESS_KEY_SECRET:YivC1800cacnEpcS3OSQKIcr5Itgal}
@@ -63,11 +71,14 @@ app:
   dms:
     path: http://121.43.55.7:10081/dms
   wechat:
-    appid: ungiven
-    secret-key: ungiven
-    api-base-url: ${WECHAT_API_BASE_URL:https://api.weixin.qq.com}
-    enable: false
-
+    api-base-url: http://42.121.218.163:1234/weixin
+    mode: ${WECHAT_MODE:external}
+  ocr:
+    mode: ${OCR_MODE:external}
+  phone:
+    message:
+      mode: ${PHONE_MESSAGE_MODE:external}
+  execute-startup-tasks: false
 #  wechat:
 #    appid: ${XJCY_WECHAT_APPID:wx125843453562c86c}
 #    secret-key: ${XJCY_WECHAT_SECRET:6028cc345cfdbc76224d750a13519762}