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 }