From 39953cca84cdb372e9fb1d48370f44c8ed5bc8c3 Mon Sep 17 00:00:00 2001
From: guotao <499836921@qq.com>
Date: Mon, 12 Jan 2026 18:50:22 +0800
Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BB=A5=E4=B8=8B=E7=AC=AC?=
=?UTF-8?q?=E4=B8=89=E6=96=B9=E6=8E=A5=E5=8F=A3=E5=AF=B9=E6=8E=A5:=201.?=
=?UTF-8?q?=E5=9C=B0=E5=9B=BE=E5=8A=9F=E8=83=BD-=E5=9C=B0=E7=90=86/?=
=?UTF-8?q?=E9=80=86=E5=9C=B0=E7=90=86=E7=BC=96=E7=A0=81=202.=E5=8F=91?=
=?UTF-8?q?=E9=80=81=E7=9F=AD=E4=BF=A1=E9=AA=8C=E8=AF=81=E7=A0=81=203.COS?=
=?UTF-8?q?=E4=BA=91=E5=AF=B9=E8=B1=A1=E5=AD=98=E5=82=A8=E5=AF=B9=E6=8E=A5?=
=?UTF-8?q?=20=E5=85=B6=E4=BB=96:=20=E5=AE=8C=E6=88=90=E6=96=87=E4=BB=B6?=
=?UTF-8?q?=E4=B8=8A=E4=BC=A0/=E9=A2=84=E8=A7=88=E5=85=AC=E5=85=B1?=
=?UTF-8?q?=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pom.xml | 34 +++
.../common/openapi/TencentCOSService.java | 75 +++++++
.../openapi/TencentCloudSMSService.java | 57 +++++
.../common/openapi/amap/AmapFeignClient.java | 35 +++
.../common/openapi/amap/AmapService.java | 39 ++++
.../amap/response/AmapGeocodeResponse.java | 95 ++++++++
.../amap/response/AmapReGeocodeResponse.java | 207 ++++++++++++++++++
.../openapi/dto/CosPutObjectResult.java | 13 ++
.../findmemerchant/config/FeignConfig.java | 13 ++
.../config/ThirdOpenApiConfig.java | 36 +++
src/main/resources/application.yml | 19 +-
11 files changed, 622 insertions(+), 1 deletion(-)
create mode 100644 src/main/java/com/xjhs/findmemerchant/common/openapi/TencentCOSService.java
create mode 100644 src/main/java/com/xjhs/findmemerchant/common/openapi/TencentCloudSMSService.java
create mode 100644 src/main/java/com/xjhs/findmemerchant/common/openapi/amap/AmapFeignClient.java
create mode 100644 src/main/java/com/xjhs/findmemerchant/common/openapi/amap/AmapService.java
create mode 100644 src/main/java/com/xjhs/findmemerchant/common/openapi/amap/response/AmapGeocodeResponse.java
create mode 100644 src/main/java/com/xjhs/findmemerchant/common/openapi/amap/response/AmapReGeocodeResponse.java
create mode 100644 src/main/java/com/xjhs/findmemerchant/common/openapi/dto/CosPutObjectResult.java
create mode 100644 src/main/java/com/xjhs/findmemerchant/config/FeignConfig.java
create mode 100644 src/main/java/com/xjhs/findmemerchant/config/ThirdOpenApiConfig.java
diff --git a/pom.xml b/pom.xml
index 28f0542..ab1ae6b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,25 @@
25
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ 2025.0.1
+ pom
+ import
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
org.springframework.boot
spring-boot-starter-web
@@ -76,6 +94,22 @@
commons-lang3
3.18.0
+
+
+ com.tencentcloudapi
+ tencentcloud-sdk-java
+ 3.1.1396
+
+
+ com.qcloud
+ cos_api
+ 5.6.246
+
+
+ org.apache.tika
+ tika-core
+ 3.2.3
+
diff --git a/src/main/java/com/xjhs/findmemerchant/common/openapi/TencentCOSService.java b/src/main/java/com/xjhs/findmemerchant/common/openapi/TencentCOSService.java
new file mode 100644
index 0000000..63ae89c
--- /dev/null
+++ b/src/main/java/com/xjhs/findmemerchant/common/openapi/TencentCOSService.java
@@ -0,0 +1,75 @@
+package com.xjhs.findmemerchant.common.openapi;
+
+import com.qcloud.cos.COSClient;
+import com.qcloud.cos.exception.CosClientException;
+import com.qcloud.cos.model.GetObjectRequest;
+import com.qcloud.cos.model.PutObjectRequest;
+import com.xjhs.findmemerchant.common.openapi.dto.CosPutObjectResult;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.UUID;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class TencentCOSService {
+ @Value("${appconfig.tencentCos.bucketName}")
+ private String bucketName;
+ private final COSClient tencentCosClient;
+
+ /**
+ * 文件上传到Cos存储桶
+ *
+ * @param file 文件
+ * @return 存储对象信息
+ */
+ public CosPutObjectResult putObject(File file) throws Exception {
+ try {
+ var key = UUID.randomUUID().toString();
+ var req = new PutObjectRequest(this.bucketName, key, file);
+ var result = tencentCosClient.putObject(req);
+ return new CosPutObjectResult(key, result.getETag());
+ } catch (CosClientException e) {
+ log.error("文件上传到对象存储失败", e);
+ throw new Exception("文件上传到对象存储失败");
+ }
+ }
+
+ /**
+ * 获取文件输入流(下载文件)
+ * @param key 对象key
+ * @return 输入流
+ * @throws Exception 下载失败
+ */
+ public InputStream getObject(String key) throws Exception{
+ try {
+ var req = new GetObjectRequest(this.bucketName, key);
+ var cosObject = this.tencentCosClient.getObject(req);
+ return cosObject.getObjectContent();
+ } catch (CosClientException e) {
+ log.error("从对象存储下载文件失败",e);
+ throw new Exception("从对象存储下载文件失败");
+ }
+ }
+
+
+ /**
+ * 删除存储对象
+ *
+ * @param key 对象key
+ * @throws Exception 删除失败
+ */
+ public void deleteObject(String key) throws Exception {
+ try {
+ this.tencentCosClient.deleteObject(this.bucketName, key);
+ } catch (CosClientException e) {
+ log.error("文件上传到对象存储失败", e);
+ throw new Exception("文件上传到对象存储失败");
+ }
+ }
+}
diff --git a/src/main/java/com/xjhs/findmemerchant/common/openapi/TencentCloudSMSService.java b/src/main/java/com/xjhs/findmemerchant/common/openapi/TencentCloudSMSService.java
new file mode 100644
index 0000000..8c4c583
--- /dev/null
+++ b/src/main/java/com/xjhs/findmemerchant/common/openapi/TencentCloudSMSService.java
@@ -0,0 +1,57 @@
+package com.xjhs.findmemerchant.common.openapi;
+
+
+import com.tencentcloudapi.common.exception.TencentCloudSDKException;
+import com.tencentcloudapi.sms.v20190711.models.SendSmsRequest;
+import com.tencentcloudapi.sms.v20190711.SmsClient;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor()
+public class TencentCloudSMSService {
+ @Value("${appconfig.tencentSms.sdkAppId}")
+ private String sdkAppid;
+ @Value("${appconfig.tencentSms.templateId}")
+ private String templateId;
+ @Value("${appconfig.tencentSms.signName}")
+ private String signName;
+ private final SmsClient tencentSmsClient;
+
+
+ /**
+ * 发送短信验证码
+ *
+ * @param phone 11位国内手机号码
+ * @param code 验证码
+ * @return 验证码值
+ * @throws Exception 发送失败
+ */
+ public String sendVerifyCode(String phone, String code) throws Exception {
+ try {
+ if (!phone.startsWith("+86")) {
+ phone = "+86" + phone;
+ }
+ var req = new SendSmsRequest();
+ req.setSmsSdkAppid(this.sdkAppid);
+ req.setTemplateID(this.templateId);
+ req.setSign(this.signName);
+ req.setPhoneNumberSet(new String[]{phone});
+ req.setTemplateParamSet(new String[]{code});
+ var resp = this.tencentSmsClient.SendSms(req);
+ if (resp.getSendStatusSet().length == 0){
+ throw new Exception("取回短信发送结果失败");
+ }
+ if (!"ok".equalsIgnoreCase(resp.getSendStatusSet()[0].getCode())){
+ throw new Exception(resp.getSendStatusSet()[0].getMessage());
+ }
+ return code;
+ } catch (TencentCloudSDKException e) {
+ log.error("验证码发送失败", e);
+ throw new Exception("系统错误,验证码发送失败");
+ }
+ }
+}
diff --git a/src/main/java/com/xjhs/findmemerchant/common/openapi/amap/AmapFeignClient.java b/src/main/java/com/xjhs/findmemerchant/common/openapi/amap/AmapFeignClient.java
new file mode 100644
index 0000000..a98d2bf
--- /dev/null
+++ b/src/main/java/com/xjhs/findmemerchant/common/openapi/amap/AmapFeignClient.java
@@ -0,0 +1,35 @@
+package com.xjhs.findmemerchant.common.openapi.amap;
+
+
+import com.xjhs.findmemerchant.common.openapi.amap.response.AmapGeocodeResponse;
+import com.xjhs.findmemerchant.common.openapi.amap.response.AmapReGeocodeResponse;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+/**
+ * 高德地图服务接口
+ */
+@FeignClient(name = "amapFeignClient", url = "https://restapi.amap.com")
+public interface AmapFeignClient {
+
+ /**
+ * 地理编码 API 服务
+ * @param key 高德Key
+ * @param address 结构化地址信息,规则遵循:国家、省份、城市、区县、城镇、乡村、街道、门牌号码、屋邨、大厦,如:北京市朝阳区阜通东大街6号。
+ * @return 响应信息
+ */
+ @GetMapping("/v3/geocode/geo")
+ AmapGeocodeResponse getGeo(@RequestParam(name = "key") String key,
+ @RequestParam(name = "address") String address);
+
+
+ /**
+ * 逆地理编码 API 服务地址
+ * @param key 高德Key
+ * @param location 经纬度坐标 传入内容规则:经度在前,纬度在后,经纬度间以“,”分割,经纬度小数点后不要超过 6 位。
+ */
+ @GetMapping("/v3/geocode/regeo")
+ AmapReGeocodeResponse getReGeo(@RequestParam(name = "key") String key,
+ @RequestParam(name = "location") String location);
+}
diff --git a/src/main/java/com/xjhs/findmemerchant/common/openapi/amap/AmapService.java b/src/main/java/com/xjhs/findmemerchant/common/openapi/amap/AmapService.java
new file mode 100644
index 0000000..a9583fb
--- /dev/null
+++ b/src/main/java/com/xjhs/findmemerchant/common/openapi/amap/AmapService.java
@@ -0,0 +1,39 @@
+package com.xjhs.findmemerchant.common.openapi.amap;
+
+import com.xjhs.findmemerchant.common.openapi.amap.response.AmapGeocodeResponse;
+import com.xjhs.findmemerchant.common.openapi.amap.response.AmapReGeocodeResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class AmapService {
+
+ @Value("${appconfig.amapKey}")
+ private String amapKey = "c618de6e686c43095a8593db836c7de2";
+ private final AmapFeignClient amapFeignClient;
+
+ /**
+ * 地址转经纬度
+ *
+ * @param address 地址信息
+ * @return 经纬度信息结果
+ */
+ public AmapGeocodeResponse getGeo(String address) {
+ return this.amapFeignClient.getGeo(this.amapKey, address);
+ }
+
+ /**
+ * 经纬度转地理位置信息
+ *
+ * @param lng 经度
+ * @param lat 纬度
+ * @return 位置信息
+ */
+ public AmapReGeocodeResponse getRegeo(double lng, double lat) {
+ return this.amapFeignClient.getReGeo(this.amapKey, lng + "," + lat);
+ }
+}
diff --git a/src/main/java/com/xjhs/findmemerchant/common/openapi/amap/response/AmapGeocodeResponse.java b/src/main/java/com/xjhs/findmemerchant/common/openapi/amap/response/AmapGeocodeResponse.java
new file mode 100644
index 0000000..0969541
--- /dev/null
+++ b/src/main/java/com/xjhs/findmemerchant/common/openapi/amap/response/AmapGeocodeResponse.java
@@ -0,0 +1,95 @@
+package com.xjhs.findmemerchant.common.openapi.amap.response;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 地理编码 API 服务响应内容
+ */
+@Data
+public class AmapGeocodeResponse {
+ /**
+ * 请求状态值,0 表示请求失败,1 表示请求成功
+ */
+ private int status;
+
+ /**
+ * 返回结果的数量
+ */
+ private int count;
+
+ /**
+ * 返回结果的详细说明,当 status 为 0 时,info 会包含错误原因;否则返回 "OK"
+ */
+ private String info;
+
+ /**
+ * 地理编码信息列表,包含多个地址组件
+ */
+ private List geocodes = new ArrayList<>();
+
+ /**
+ * 地理编码信息的详细字段
+ */
+ @Data
+ public static class GeocodeDetails {
+
+ @JsonProperty("formatted_address")
+ private String address;
+
+ /**
+ * 国家,默认返回中国
+ */
+ private String country;
+
+ /**
+ * 省份名称,例如:北京市
+ */
+ private String province;
+
+ /**
+ * 城市名称,例如:北京市
+ */
+ private String city;
+
+ /**
+ * 城市编码,例如:010
+ */
+ private String citycode;
+
+ /**
+ * 区域名称,例如:朝阳区
+ */
+ private String district;
+
+ /**
+ * 街道名称,例如:单通东大街
+ */
+ private String street;
+
+ /**
+ * 门牌号,例如:6号
+ */
+ private String number;
+
+ /**
+ * 区域编码,例如:110101
+ */
+ private String adcode;
+
+ /**
+ * 经纬度坐标,逗号分隔
+ */
+ private String location;
+
+ /**
+ * 匹配级别,用于地理匹配的精确度
+ */
+ private String level;
+ }
+
+
+}
diff --git a/src/main/java/com/xjhs/findmemerchant/common/openapi/amap/response/AmapReGeocodeResponse.java b/src/main/java/com/xjhs/findmemerchant/common/openapi/amap/response/AmapReGeocodeResponse.java
new file mode 100644
index 0000000..11d01dd
--- /dev/null
+++ b/src/main/java/com/xjhs/findmemerchant/common/openapi/amap/response/AmapReGeocodeResponse.java
@@ -0,0 +1,207 @@
+package com.xjhs.findmemerchant.common.openapi.amap.response;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 逆地理位置服务响应内容
+ */
+@Data
+public class AmapReGeocodeResponse {
+
+ /**
+ * 请求状态,1 表示请求成功
+ */
+ private String status;
+
+ /**
+ * 地址组件信息
+ */
+ private Regeocode regeocode;
+
+ /**
+ * 返回的状态信息
+ */
+ private String info;
+
+ /**
+ * 状态码,10000 表示成功
+ */
+ private String infocode;
+
+ /**
+ * 地址组件类,包含具体的地理信息
+ */
+ @Data
+ public static class Regeocode {
+
+ /**
+ * 地址组件,包含城市、区、街道等信息
+ */
+ private AddressComponent addressComponent;
+
+ /**
+ * 格式化后的地址,整合了详细信息
+ */
+ @JsonProperty("formatted_address")
+ private String address;
+ }
+
+ /**
+ * 地址组件,包含详细的地理信息
+ */
+ @Data
+ public static class AddressComponent {
+
+ /**
+ * 城市名称,可能为空
+ */
+ private List city = new ArrayList<>();
+
+ /**
+ * 省份名称
+ */
+ private String province;
+
+ /**
+ * 区域编码
+ */
+ private String adcode;
+
+ /**
+ * 区域名称
+ */
+ private String district;
+
+ /**
+ * 城镇编码
+ */
+ private String towncode;
+
+ /**
+ * 街道信息
+ */
+ private StreetNumber streetNumber;
+
+ /**
+ * 国家名称
+ */
+ private String country;
+
+ /**
+ * 街道所属乡镇名称
+ */
+ private String township;
+
+ /**
+ * 商圈信息,包含多个商圈
+ */
+ private List businessAreas;
+
+ /**
+ * 建筑信息
+ */
+ private Building building;
+
+ /**
+ * 邻里信息
+ */
+ private Neighborhood neighborhood;
+
+ /**
+ * 城市编码
+ */
+ private String citycode;
+ }
+
+ /**
+ * 街道信息,包括街道名称、位置、方向等
+ */
+ @Data
+ public static class StreetNumber {
+
+ /**
+ * 门牌号
+ */
+ private String number;
+
+ /**
+ * 经纬度位置
+ */
+ private String location;
+
+ /**
+ * 方向
+ */
+ private String direction;
+
+ /**
+ * 距离
+ */
+ private String distance;
+
+ /**
+ * 街道名称
+ */
+ private String street;
+ }
+
+ /**
+ * 商圈信息
+ */
+ @Data
+ public static class BusinessArea {
+
+ /**
+ * 商圈位置(经纬度)
+ */
+ private String location;
+
+ /**
+ * 商圈名称
+ */
+ private String name;
+
+ /**
+ * 商圈ID
+ */
+ private String id;
+ }
+
+ /**
+ * 建筑信息,包括建筑名称和类型
+ */
+ @Data
+ public static class Building {
+
+ /**
+ * 建筑名称
+ */
+ private String name;
+
+ /**
+ * 建筑类型
+ */
+ private String type;
+ }
+
+ /**
+ * 邻里信息
+ */
+ @Data
+ public static class Neighborhood {
+
+ /**
+ * 邻里名称
+ */
+ private String name;
+
+ /**
+ * 邻里类型
+ */
+ private String type;
+ }
+}
diff --git a/src/main/java/com/xjhs/findmemerchant/common/openapi/dto/CosPutObjectResult.java b/src/main/java/com/xjhs/findmemerchant/common/openapi/dto/CosPutObjectResult.java
new file mode 100644
index 0000000..4ff570a
--- /dev/null
+++ b/src/main/java/com/xjhs/findmemerchant/common/openapi/dto/CosPutObjectResult.java
@@ -0,0 +1,13 @@
+package com.xjhs.findmemerchant.common.openapi.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class CosPutObjectResult {
+ private String key;
+ private String eTag;
+}
diff --git a/src/main/java/com/xjhs/findmemerchant/config/FeignConfig.java b/src/main/java/com/xjhs/findmemerchant/config/FeignConfig.java
new file mode 100644
index 0000000..d3790a1
--- /dev/null
+++ b/src/main/java/com/xjhs/findmemerchant/config/FeignConfig.java
@@ -0,0 +1,13 @@
+package com.xjhs.findmemerchant.config;
+
+import com.xjhs.findmemerchant.common.openapi.amap.AmapFeignClient;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableFeignClients(basePackageClasses = {
+ AmapFeignClient.class,
+})
+public class FeignConfig {
+
+}
diff --git a/src/main/java/com/xjhs/findmemerchant/config/ThirdOpenApiConfig.java b/src/main/java/com/xjhs/findmemerchant/config/ThirdOpenApiConfig.java
new file mode 100644
index 0000000..6c66601
--- /dev/null
+++ b/src/main/java/com/xjhs/findmemerchant/config/ThirdOpenApiConfig.java
@@ -0,0 +1,36 @@
+package com.xjhs.findmemerchant.config;
+
+import com.qcloud.cos.COSClient;
+import com.qcloud.cos.ClientConfig;
+import com.qcloud.cos.auth.BasicCOSCredentials;
+import com.tencentcloudapi.common.Credential;
+import com.tencentcloudapi.common.profile.ClientProfile;
+import com.tencentcloudapi.common.profile.HttpProfile;
+import com.tencentcloudapi.common.profile.Region;
+import com.tencentcloudapi.sms.v20190711.SmsClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ThirdOpenApiConfig {
+
+ @Bean
+ public SmsClient tencentSmsClient(@Value("${appconfig.tencentSms.secretId}") String secretId,
+ @Value("${appconfig.tencentSms.secretKey}") String secretKey) {
+ var cred = new Credential(secretId, secretKey);
+ var httpProfile = new HttpProfile();
+ httpProfile.setEndpoint("sms.tencentcloudapi.com");
+ var clientProfile = new ClientProfile();
+ clientProfile.setHttpProfile(httpProfile);
+ return new SmsClient(cred, "ap-guangzhou", clientProfile);
+ }
+
+ @Bean
+ public COSClient tencentCosClient(@Value("${appconfig.tencentCos.secretId}") String secretId,
+ @Value("${appconfig.tencentCos.secretKey}") String secretKey){
+ var cred = new BasicCOSCredentials(secretId,secretKey);
+ ClientConfig clientConfig = new ClientConfig(new com.qcloud.cos.region.Region("ap-guangzhou"));
+ return new COSClient(cred, clientConfig);
+ }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 8c6d574..6242563 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -30,4 +30,21 @@ spring:
port: ${REDIS_PORT:6379}
password: ${REDIS_PASS:123456}
database: 0
- timeout: 3s
\ No newline at end of file
+ timeout: 3s
+
+appconfig:
+ saveFileRoot: ${SAVE_FILE_ROOT:./file-data}
+ amapKey: ${AMAP_KEY:c618de6e686c43095a8593db836c7de2}
+ tencentSms:
+ sdkAppId: ${SMS_SDKAPP_ID:1401031336}
+ templateId: ${SMS_TEMPLATE_ID:2512787}
+ signName: ${SMS_SIGN_NAME:新疆火烁智能科技}
+ secretId: ${SMS_SECRET_ID:AKIDWyGMFwQinhPFzXt54rTuAD5kEYheOyOd}
+ secretKey: ${SMS_SECRET_KEY:62mLjvwmQs9GAsQ5f6LQ7umgWCgy31sX}
+ tencentCos:
+ secretId: ${COS_SECRET_ID:AKIDWyGMFwQinhPFzXt54rTuAD5kEYheOyOd}
+ secretKey: ${COS_SECRET_KEY:62mLjvwmQs9GAsQ5f6LQ7umgWCgy31sX}
+ appId: ${COS_APP_ID:1375214531}
+ bucketName: ${COS_APP_BUCKET:merchant-1375214531}
+
+