169 lines
5.2 KiB
Go
169 lines
5.2 KiB
Go
package location
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"io/ioutil"
|
||
"log"
|
||
"net/http"
|
||
"net/url"
|
||
"time"
|
||
)
|
||
|
||
// StringOrArray ########################### 核心:兼容字符串/数组的自定义类型 ###########################
|
||
type StringOrArray string
|
||
|
||
// UnmarshalJSON 处理字符串/数组两种格式
|
||
func (s *StringOrArray) UnmarshalJSON(data []byte) error {
|
||
// 尝试解析为纯字符串
|
||
var str string
|
||
if err := json.Unmarshal(data, &str); err == nil {
|
||
*s = StringOrArray(str)
|
||
return nil
|
||
}
|
||
|
||
// 尝试解析为字符串数组
|
||
var strArr []string
|
||
if err := json.Unmarshal(data, &strArr); err != nil {
|
||
return fmt.Errorf("字段解析失败: %v, 原始数据: %s", err, string(data))
|
||
}
|
||
|
||
// 数组取第一个元素(高德返回的数组通常仅1个元素)
|
||
if len(strArr) > 0 {
|
||
*s = StringOrArray(strArr[0])
|
||
} else {
|
||
*s = StringOrArray("")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// String 转为普通字符串,方便使用
|
||
func (s StringOrArray) String() string {
|
||
return string(s)
|
||
}
|
||
|
||
// ########################### 严格对齐高德API的结构体 ###########################
|
||
// AMapEntity 最终返回的实体(对应Java的AMapEntity)
|
||
type AMapEntity struct {
|
||
AddressComponent AddressComponent `json:"addresscomponent"`
|
||
Address StringOrArray `json:"formatted_address"` // 改为兼容类型
|
||
}
|
||
|
||
// StreetNumber 门牌号嵌套对象(高德固定返回对象)
|
||
type StreetNumber struct {
|
||
Number StringOrArray `json:"number"` // 门牌号
|
||
Location StringOrArray `json:"location"` // 经纬度
|
||
Direction StringOrArray `json:"direction"` // 方向
|
||
Distance StringOrArray `json:"distance"` // 距离
|
||
Street StringOrArray `json:"street"` // 所属街道
|
||
}
|
||
|
||
// AddressComponent 地址组件(全量兼容)
|
||
type AddressComponent struct {
|
||
Province StringOrArray `json:"province"` // 省(字符串/数组)
|
||
City StringOrArray `json:"city"` // 市(字符串/数组)
|
||
District StringOrArray `json:"district"` // 区(字符串/数组)
|
||
Township StringOrArray `json:"township"` // 乡镇(纯字符串)
|
||
Street StringOrArray `json:"street"` // 街道(纯字符串)
|
||
StreetNumber StreetNumber `json:"streetnumber"` // 门牌号(对象)
|
||
Adcode StringOrArray `json:"adcode"` // 行政区划编码(纯字符串)
|
||
Country StringOrArray `json:"country"` // 国家(纯字符串)
|
||
CountryCode StringOrArray `json:"countrycode"` // 国家编码(纯字符串)
|
||
}
|
||
|
||
// AMapResponse 高德API顶层响应
|
||
type AMapResponse struct {
|
||
Regeocode Regeocode `json:"regeocode"` // 核心数据
|
||
Status string `json:"status"` // 1=成功,0=失败
|
||
Info string `json:"info"` // 错误信息
|
||
Infocode string `json:"infocode"` // 错误码(精准定位问题)
|
||
}
|
||
|
||
// Regeocode 逆地理编码核心数据
|
||
type Regeocode struct {
|
||
FormattedAddress StringOrArray `json:"formatted_address"` // 改为兼容类型
|
||
AddressComponent AddressComponent `json:"addresscomponent"` // 地址组件
|
||
}
|
||
|
||
// ########################### 配置常量 ###########################
|
||
const (
|
||
appKey = "1b8dd8848bcc062ff1f2cec6db683673" // 替换为你的有效Key
|
||
amapApiUrl = "https://restapi.amap.com/v3/geocode/regeo"
|
||
)
|
||
|
||
// Point2code 经纬度转地址(完整错误处理+兼容)
|
||
func Point2code(longitude, latitude float32) (*AMapEntity, error) {
|
||
// 1. 基础参数校验
|
||
if longitude == 0 || latitude == 0 {
|
||
return nil, errors.New("经纬度不能为0")
|
||
}
|
||
|
||
// 2. 构建请求URL
|
||
baseURL, err := url.Parse(amapApiUrl)
|
||
if err != nil {
|
||
log.Printf("URL解析失败: %v", err)
|
||
return nil, err
|
||
}
|
||
|
||
// 设置查询参数
|
||
params := url.Values{}
|
||
params.Set("key", appKey)
|
||
params.Set("location", fmt.Sprintf("%f,%f", longitude, latitude))
|
||
baseURL.RawQuery = params.Encode()
|
||
|
||
// 3. 全局HTTP客户端(建议单例复用)
|
||
client := &http.Client{Timeout: 10 * time.Second}
|
||
|
||
// 4. 发送GET请求
|
||
req, err := http.NewRequest(http.MethodGet, baseURL.String(), nil)
|
||
if err != nil {
|
||
log.Printf("请求构建失败: %v", err)
|
||
return nil, err
|
||
}
|
||
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
log.Printf("请求发送失败: %v", err)
|
||
return nil, err
|
||
}
|
||
defer resp.Body.Close() // 强制关闭响应体,避免内存泄漏
|
||
|
||
// 5. 校验HTTP状态码
|
||
if resp.StatusCode != http.StatusOK {
|
||
errMsg := fmt.Sprintf("HTTP请求失败,状态码: %d", resp.StatusCode)
|
||
log.Println(errMsg)
|
||
return nil, errors.New(errMsg)
|
||
}
|
||
|
||
// 6. 读取响应体
|
||
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||
if err != nil {
|
||
log.Printf("响应体读取失败: %v", err)
|
||
return nil, err
|
||
}
|
||
|
||
// 7. 解析JSON响应
|
||
var amapResp AMapResponse
|
||
if err := json.Unmarshal(bodyBytes, &amapResp); err != nil {
|
||
log.Printf("JSON解析失败: %v, 响应内容: %s", err, string(bodyBytes))
|
||
return nil, err
|
||
}
|
||
|
||
// 8. 校验高德API业务状态
|
||
if amapResp.Status != "1" {
|
||
errMsg := fmt.Sprintf("高德API返回失败: status=%s, info=%s, infocode=%s",
|
||
amapResp.Status, amapResp.Info, amapResp.Infocode)
|
||
log.Println(errMsg)
|
||
return nil, errors.New(errMsg)
|
||
}
|
||
|
||
// 9. 构造最终返回实体
|
||
result := &AMapEntity{
|
||
AddressComponent: amapResp.Regeocode.AddressComponent,
|
||
Address: amapResp.Regeocode.FormattedAddress,
|
||
}
|
||
|
||
return result, nil
|
||
}
|