From afac67a1018875dfd7c0fc301c6d190549121141 Mon Sep 17 00:00:00 2001 From: wikiboss Date: Mon, 26 Oct 2026 19:14:16 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E5=9C=B0=E5=9D=80):=20=E9=80=89=E7=82=B9?= =?UTF-8?q?=E6=94=B9=E8=B5=B0=E6=9C=8D=E5=8A=A1=E7=AB=AF=E5=A4=A9=E5=9C=B0?= =?UTF-8?q?=E5=9B=BE=EF=BC=8C=E7=9B=B4=E8=BE=96=E5=8E=BF=E6=8C=89=E6=89=80?= =?UTF-8?q?=E5=B1=9E=E7=9C=81=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 正式版不能直接请求地图域名时,由后端转发逆地理。保存区划时带上省的 id,避免同名直辖县挂错省份。 Co-authored-by: Cursor --- addons/shopro/controller/Address.php | 79 ++++++++ addons/shopro/model/UserAddress.php | 122 ++++++++++++ application/common/library/Tianditu.php | 251 ++++++++++++++++++++++++ application/extra/tianditu.php | 6 + 4 files changed, 458 insertions(+) create mode 100644 addons/shopro/controller/Address.php create mode 100644 addons/shopro/model/UserAddress.php create mode 100644 application/common/library/Tianditu.php create mode 100644 application/extra/tianditu.php diff --git a/addons/shopro/controller/Address.php b/addons/shopro/controller/Address.php new file mode 100644 index 0000000..f7d68f0 --- /dev/null +++ b/addons/shopro/controller/Address.php @@ -0,0 +1,79 @@ +success('收货地址', UserAddress::getUserAddress()); + } + + public function defaults() + { + $this->success('默认收货地址', UserAddress::getUserDefaultAddress()); + } + + public function area() + { + $data['provinceData'] = Area::where('level', 1)->order('id asc')->field('id as value, name as label, pid, level')->select(); + foreach ($data['provinceData'] as $k => $p) { + $data['cityData'][$k] = Area::where(['level' => 2, 'pid' => $p->value])->order('id asc')->field('id as value, name as label, pid, level')->select(); + foreach ($data['cityData'][$k] as $i => $c) { + $data['areaData'][$k][$i] = Area::where(['level' => 3, 'pid' => $c->value])->order('id asc')->field('id as value, name as label, pid, level')->select(); + } + } + + $this->success('省市区', $data); + + } + + public function edit() + { + $params = $this->request->post(); + + // 表单验证 + $this->shoproValidate($params, get_class(), 'edit'); + + $this->success('编辑地址', UserAddress::edit($params)); + } + + public function info() + { + $params = $this->request->get(); + $this->success('地址详情', UserAddress::info($params)); + } + + public function del() + { + $params = $this->request->post(); + $this->success('地址详情', UserAddress::del($params)); + + } + + + + public function regeo() + { + $latitude = $this->request->post('latitude'); + $longitude = $this->request->post('longitude'); + if (!is_numeric($latitude) || !is_numeric($longitude) || abs($latitude) < 0.000001 || abs($longitude) < 0.000001) { + $this->error('请先选择位置'); + } + try { + $data = \app\common\library\Tianditu::reverse((float)$longitude, (float)$latitude); + } catch (\Exception $e) { + $this->error($e->getMessage() ?: '地址解析失败'); + } + $this->success('解析成功', $data); + } + +} \ No newline at end of file diff --git a/addons/shopro/model/UserAddress.php b/addons/shopro/model/UserAddress.php new file mode 100644 index 0000000..77d37eb --- /dev/null +++ b/addons/shopro/model/UserAddress.php @@ -0,0 +1,122 @@ + $user->id, + 'deletetime' => null + ]); + } + + public static function getUserDefaultAddress() + { + $user = User::info(); + $default = self::get([ + 'user_id' => $user->id, + 'deletetime' => null, + 'is_default' => '1' + ]); + + return $default; + } + + public static function edit($params) + { + $user = User::info(); + extract($params); + $areaNameArray = explode("-", $area_text); + + try { + $province = Area::get(['name' => $areaNameArray[0], 'level' => 1]); + $city = $province ? Area::get(['name' => $areaNameArray[1], 'pid' => $province->id, 'level' => 2]) : null; + $area = $city ? Area::get(['name' => $areaNameArray[2], 'pid' => $city->id, 'level' => 3]) : null; + + if (!$province || !$city || !$area) { + new Exception('暂不支持,请手动选择行政区'); + } + } catch (\Exception $e) { + new Exception('暂不支持,请手动选择行政区'); + } + $is_default = (isset($is_default) && $is_default) ? '1' : '0'; + if ($is_default) { + self::where(['user_id' => $user->id, 'is_default' => '1'])->update(['is_default' => '0']); + } + + if (!isset($id) || $id == 0) { + $edit = self::create([ + 'consignee' => $consignee, + 'phone' => $phone, + 'province_id' => $province->id, + 'province_name' => $province->name, + 'city_id' => $city->id, + 'city_name' => $city->name, + 'area_id' => $area->id, + 'area_name' => $area->name, + 'is_default' => $is_default, + 'latitude' => (isset($latitude) && $latitude) ? $latitude : null, + 'longitude' => (isset($longitude) && $longitude) ? $longitude : null, + 'user_id' => $user->id, + 'address' => $address + ]); + } else { + $edit = self::update([ + 'consignee' => $consignee, + 'phone' => $phone, + 'province_id' => $province->id, + 'province_name' => $province->name, + 'city_id' => $city->id, + 'city_name' => $city->name, + 'area_id' => $area->id, + 'area_name' => $area->name, + 'is_default' => $is_default, + 'latitude' => (isset($latitude) && $latitude) ? $latitude : null, + 'longitude' => (isset($longitude) && $longitude) ? $longitude : null, + 'user_id' => $user->id, + 'address' => $address, + 'id' => $id + ]); + } + return $edit; + } + + public static function info($params) + { + $user = User::info(); + extract($params); + return self::get(['id' => $id, 'user_id' => $user->id]); + } + + public static function del($params) + { + $user = User::info(); + extract($params); + return self::get(['id' => $id, 'user_id' => $user->id])->delete(); + } +} diff --git a/application/common/library/Tianditu.php b/application/common/library/Tianditu.php new file mode 100644 index 0000000..4a66f04 --- /dev/null +++ b/application/common/library/Tianditu.php @@ -0,0 +1,251 @@ + $area['province'] . '-' . $area['city'] . '-' . $area['district'], + 'address' => $address, + 'formatted_address' => $province . $city . $area['district'] . $address, + ]; + } + + protected static function key() + { + $key = function_exists('config') ? config('tianditu.key') : ''; + if (!$key) { + $file = dirname(__DIR__, 2) . '/extra/tianditu.php'; + $config = is_file($file) ? include $file : []; + $key = is_array($config) && !empty($config['key']) ? $config['key'] : ''; + } + return trim((string)$key); + } + + protected static function request($longitude, $latitude, $key) + { + $postStr = json_encode([ + 'lon' => round($longitude, 6), + 'lat' => round($latitude, 6), + 'ver' => 1, + ], JSON_UNESCAPED_UNICODE); + $url = 'https://api.tianditu.gov.cn/geocoder?postStr=' . rawurlencode($postStr) . '&type=geocode&tk=' . rawurlencode($key); + + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); + curl_setopt($ch, CURLOPT_TIMEOUT, 8); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_USERAGENT, 'chufang-server/1.0'); + $body = curl_exec($ch); + $errno = curl_errno($ch); + curl_close($ch); + + if ($errno || $body === false || $body === '') { + throw new \Exception('地址解析失败,请稍后重试'); + } + + $json = json_decode($body, true); + if (!is_array($json) || !isset($json['status']) || (string)$json['status'] !== '0' || empty($json['result']) || !is_array($json['result'])) { + $message = is_array($json) && !empty($json['msg']) ? $json['msg'] : '地址解析失败'; + throw new \Exception($message); + } + + return $json['result']; + } + + protected static function matchArea(array $component) + { + $district = self::findDistrict($component); + if (!$district) { + throw new \Exception('未能匹配区县,请手动选择所在地区'); + } + + $city = Area::where('id', $district['pid'])->find(); + $province = $city ? Area::where('id', $city['pid'])->find() : null; + if (!$city || !$province || (int)$city['level'] !== 2 || (int)$province['level'] !== 1) { + throw new \Exception('未能匹配省市区,请手动选择所在地区'); + } + + return [ + 'province' => $province['name'], + 'city' => $city['name'], + 'district' => $district['name'], + ]; + } + + protected static function findDistrict(array $component) + { + $countyId = self::adminCode(isset($component['county_code']) ? $component['county_code'] : ''); + if ($countyId) { + $district = Area::where('id', $countyId)->where('level', 3)->find(); + if ($district) { + return $district; + } + } + + $county = trim(isset($component['county']) ? $component['county'] : ''); + if ($county === '') { + return null; + } + + $cityId = self::adminCode(isset($component['city_code']) ? $component['city_code'] : ''); + $query = Area::where('name', $county)->where('level', 3); + if ($cityId) { + $query->where('pid', $cityId); + } + return $query->find(); + } + + protected static function detailAddress(array $result, array $component) + { + $segments = []; + $adminNames = []; + foreach (['province', 'city', 'county'] as $field) { + $name = trim(isset($component[$field]) ? (string)$component[$field] : ''); + if ($name !== '') { + $adminNames[] = $name; + } + } + foreach (['town', 'road', 'address', 'poi'] as $field) { + $segment = trim(isset($component[$field]) ? (string)$component[$field] : ''); + $segment = self::stripAdmin($segment, $adminNames); + if ($segment === '' || self::isRelative($segment)) { + continue; + } + $replaced = false; + foreach ($segments as $index => $exist) { + if (mb_strpos($segment, $exist) !== false) { + $segments[$index] = $segment; + $replaced = true; + break; + } + if (mb_strpos($exist, $segment) !== false) { + $replaced = true; + break; + } + } + if (!$replaced) { + $segments[] = $segment; + } + } + if ($segments) { + return implode('', $segments); + } + + $detail = isset($result['formatted_address']) ? $result['formatted_address'] : ''; + foreach (['province', 'city', 'county'] as $field) { + $part = trim(isset($component[$field]) ? (string)$component[$field] : ''); + if ($part !== '') { + $detail = preg_replace('/' . preg_quote($part, '/') . '/u', '', $detail, 1); + } + } + return trim($detail); + } + + protected static function stripAdmin($segment, array $adminNames) + { + foreach ($adminNames as $name) { + if ($name !== '' && mb_strpos($segment, $name) !== false) { + $segment = str_replace($name, '', $segment); + } + $short = preg_replace('/(省|市|自治区|特别行政区)$/u', '', $name); + if ($short === '' || $short === $name || mb_strlen($short) < 2) { + continue; + } + if ($segment === $short) { + return ''; + } + $suffixLength = mb_strlen($short); + if (mb_strlen($segment) > $suffixLength && mb_substr($segment, -$suffixLength) === $short) { + $segment = mb_substr($segment, 0, mb_strlen($segment) - $suffixLength); + } + } + return trim($segment); + } + + protected static function isRelative($text) + { + return (bool)preg_match('/[东西南北].{0,4}约?\d+米/u', $text); + } + + protected static function adminCode($code) + { + $digits = preg_replace('/\D/', '', (string)$code); + if (strlen($digits) < 6) { + return 0; + } + return (int)substr($digits, -6); + } + + protected static function gcj02ToWgs84($longitude, $latitude) + { + if (self::outOfChina($longitude, $latitude)) { + return [$longitude, $latitude]; + } + + $offset = self::offset($longitude, $latitude); + return [$longitude * 2 - $offset[0], $latitude * 2 - $offset[1]]; + } + + protected static function offset($longitude, $latitude) + { + $a = 6378245.0; + $ee = 0.00669342162296594323; + $dlng = self::transformLng($longitude - 105.0, $latitude - 35.0); + $dlat = self::transformLat($longitude - 105.0, $latitude - 35.0); + $radLat = $latitude / 180.0 * M_PI; + $magic = sin($radLat); + $magic = 1 - $ee * $magic * $magic; + $sqrtMagic = sqrt($magic); + $dlat = ($dlat * 180.0) / (($a * (1 - $ee)) / ($magic * $sqrtMagic) * M_PI); + $dlng = ($dlng * 180.0) / ($a / $sqrtMagic * cos($radLat) * M_PI); + return [$longitude + $dlng, $latitude + $dlat]; + } + + protected static function outOfChina($longitude, $latitude) + { + return $longitude < 72.004 || $longitude > 137.8347 || $latitude < 0.8293 || $latitude > 55.8271; + } + + protected static function transformLat($lng, $lat) + { + $ret = -100.0 + 2.0 * $lng + 3.0 * $lat + 0.2 * $lat * $lat + 0.1 * $lng * $lat + 0.2 * sqrt(abs($lng)); + $ret += (20.0 * sin(6.0 * $lng * M_PI) + 20.0 * sin(2.0 * $lng * M_PI)) * 2.0 / 3.0; + $ret += (20.0 * sin($lat * M_PI) + 40.0 * sin($lat / 3.0 * M_PI)) * 2.0 / 3.0; + $ret += (160.0 * sin($lat / 12.0 * M_PI) + 320 * sin($lat * M_PI / 30.0)) * 2.0 / 3.0; + return $ret; + } + + protected static function transformLng($lng, $lat) + { + $ret = 300.0 + $lng + 2.0 * $lat + 0.1 * $lng * $lng + 0.1 * $lng * $lat + 0.1 * sqrt(abs($lng)); + $ret += (20.0 * sin(6.0 * $lng * M_PI) + 20.0 * sin(2.0 * $lng * M_PI)) * 2.0 / 3.0; + $ret += (20.0 * sin($lng * M_PI) + 40.0 * sin($lng / 3.0 * M_PI)) * 2.0 / 3.0; + $ret += (150.0 * sin($lng / 12.0 * M_PI) + 300.0 * sin($lng / 30.0 * M_PI)) * 2.0 / 3.0; + return $ret; + } +} diff --git a/application/extra/tianditu.php b/application/extra/tianditu.php new file mode 100644 index 0000000..369336f --- /dev/null +++ b/application/extra/tianditu.php @@ -0,0 +1,6 @@ + '55aee666be14cd0100874d6faf3c963c', +];