Files
2026-02-06 14:44:06 +08:00

169 lines
5.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}