admin 发表于 2024-1-21 12:40:32

mysql数据库里保存了商店信息,用thinkphp5写一个为用户按距离远近推荐附近商店



要实现按距离从近到远推荐附近的商店,你可以使用经纬度坐标来计算距离,并利用数据库中保存的商店信息进行排序和推荐。以下是一个简单的思路,使用ThinkPHP 5框架:
[*]确保你的商店表中包含了商店的经纬度信息。
[*]在你的控制器中,创建一个方法用于处理推荐商店的逻辑。例如:

namespace app\controller;

use think\Controller;
use app\model\Store; // 假设你有一个Store模型用于访问商店数据

class ShopController extends Controller
{
    public function recommendShops($userLatitude, $userLongitude)
    {
      // 获取用户当前位置的经纬度坐标

      // 查询数据库,获取所有商店的经纬度坐标
      $allShops = Store::field('id, name, latitude, longitude')->select();

      // 计算每个商店与用户位置的距离
      foreach ($allShops as $shop) {
            $shop->distance = $this->calculateDistance($userLatitude, $userLongitude, $shop->latitude, $shop->longitude);
      }

      // 根据距离排序商店列表
      $sortedShops = $allShops->sortBy('distance');

      // 返回排序后的商店列表
      return $this->json(['code' => 200, 'data' => $sortedShops]);
    }

    private function calculateDistance($lat1, $lon1, $lat2, $lon2)
    {
      // 实现计算两个经纬度坐标之间距离的函数,可以使用 Haversine 公式等方法
      // 返回距离值
    }
}
请注意,上述代码仅为示例,并未包含实际的距离计算方法。你需要根据你的具体情况选择适当的距离计算算法,例如 Haversine 公式。同时,请根据你的项目结构和数据库表结构调整代码。

问:这种方式会导致分页时距离问题
确实,在实际应用中,如果要进行分页,那么在数据库中每次查询都需要计算距离可能会导致性能问题。为了解决这个问题,可以采用以下优化方式:
[*]使用数据库的地理空间函数: 许多数据库系统都提供了专门用于处理地理空间数据的函数。例如,MySQL 有 ST_DISTANCE 函数,可以用来计算两点之间的距离。你可以使用数据库的地理空间功能,而不是在应用层计算距离。
[*]分页时只查询所需数据: 在分页时,不要一次性查询所有商店的信息。而是通过合理的 SQL 查询语句只获取当前页面所需的商店信息,以减轻数据库负担。
下面是一个使用 MySQL 的 ST_DISTANCE 函数和分页查询的例子:namespace app\controller;

use think\Controller;
use app\model\Store;

class ShopController extends Controller
{
    public function recommendShops($userLatitude, $userLongitude, $page = 1, $pageSize = 10)
    {
      // 使用数据库的地理空间函数计算距离并分页查询
      $result = Store::field('id, name, latitude, longitude, ST_DISTANCE(point(longitude, latitude), point(?, ?)) as distance', [$userLongitude, $userLatitude])
            ->order('distance')
            ->page($page, $pageSize)
            ->select();

      return $this->json(['code' => 200, 'data' => $result]);
    }
}
请注意,上述代码中使用了 MySQL 的 ST_DISTANCE 函数。如果你使用的是其他数据库,可能需要相应的函数或方法。此外,具体的 SQL 查询语句可能需要根据你的数据库结构进行调整。
问:如果不使用ST_DISTANCE函数,还有其他方案吗如果你的数据库不支持或不使用地理空间函数,你仍然可以通过计算经纬度之间的直线距离来实现。以下是另一种方法,使用纯粹的 SQL 计算距离的例子,同时进行分页:
namespace app\controller;

use think\Controller;
use app\model\Store;

class ShopController extends Controller
{
    public function recommendShops($userLatitude, $userLongitude, $page = 1, $pageSize = 10)
    {
      // 计算距离并分页查询
      $result = Store::field('id, name, latitude, longitude,
            ACOS(SIN(RADIANS(latitude)) * SIN(RADIANS(?)) + COS(RADIANS(latitude)) * COS(RADIANS(?)) * COS(RADIANS(longitude - ?))) * 6371 AS distance', [$userLatitude, $userLatitude, $userLongitude])
            ->order('distance')
            ->page($page, $pageSize)
            ->select();

      return $this->json(['code' => 200, 'data' => $result]);
    }
}
这里使用了 Haversine 公式,它通过纬度和经度的差异来计算两点之间的直线距离。请注意,这种方法可能在大规模数据集上性能较差,因此在实际应用中可能需要进一步优化。在实际使用中,还可以考虑使用缓存来存储已计算的距离,以减轻数据库负担。例如,可以使用 Redis 缓存已计算的距离,以避免在每次查询时都重新计算。问:根据你说的可以用Redis缓存已计算的距离,具体怎么实现使用 Redis 缓存已计算的距离可以减轻数据库负担,提高性能。以下是一个简单的示例,展示如何使用 Redis 缓存已计算的距离:
[*]安装 Redis 扩展: 确保你的项目中已经安装了 Redis 扩展,可以使用 Composer 安装:
composer require predis/predis

[*]在配置文件中配置 Redis 连接信息: 打开 config/database.php 文件,配置 Redis 连接信息,确保 Redis 服务器正常运行。
[*]修改控制器方法: 在控制器方法中添加 Redis 缓存逻辑。
namespace app\controller;

use think\Controller;
use think\facade\Cache;
use app\model\Store;

class ShopController extends Controller
{
    public function recommendShops($userLatitude, $userLongitude, $page = 1, $pageSize = 10)
    {
      $cacheKey = "distance_cache_{$userLatitude}_{$userLongitude}";

      // 尝试从缓存中获取已计算的距离
      $result = Cache::get($cacheKey);

      if ($result === false) {
            // 如果缓存中不存在,则进行计算并存入缓存
            $result = Store::field('id, name, latitude, longitude,
                ACOS(SIN(RADIANS(latitude)) * SIN(RADIANS(?)) + COS(RADIANS(latitude)) * COS(RADIANS(?)) * COS(RADIANS(longitude - ?))) * 6371 AS distance', [$userLatitude, $userLatitude, $userLongitude])
                ->order('distance')
                ->page($page, $pageSize)
                ->select();

            // 缓存结果,有效期可以根据实际需求设置
            Cache::set($cacheKey, $result, 3600); // 缓存一小时
      }

      return $this->json(['code' => 200, 'data' => $result]);
    }
}
这个例子中,我使用了 Redis 的 Cache 类来存取缓存。缓存的键名包含用户的经纬度信息,确保每个用户位置都有独立的缓存。请注意,缓存的有效期 (3600 秒) 应根据你的实际需求进行调整。在这个例子中,我将有效期设置为一小时。问:在$result语句种除了使用Store::field方法,可以使用where方法吗是的,你可以使用 where 方法来添加条件,以筛选满足条件的商店数据。在这里,你可以使用 where 方法来添加计算距离的条件。以下是一个示例:
namespace app\controller;

use think\Controller;
use think\facade\Cache;
use app\model\Store;

class ShopController extends Controller
{
    public function recommendShops($userLatitude, $userLongitude, $page = 1, $pageSize = 10)
    {
      $cacheKey = "distance_cache_{$userLatitude}_{$userLongitude}";

      // 尝试从缓存中获取已计算的距离
      $result = Cache::get($cacheKey);

      if ($result === false) {
            // 如果缓存中不存在,则进行计算并存入缓存
            $result = Store::where('1=1') // 添加一个始终成立的条件
                ->field('id, name, latitude, longitude,
                  ACOS(SIN(RADIANS(latitude)) * SIN(RADIANS(?)) + COS(RADIANS(latitude)) * COS(RADIANS(?)) * COS(RADIANS(longitude - ?))) * 6371 AS distance', [$userLatitude, $userLatitude, $userLongitude])
                ->order('distance')
                ->page($page, $pageSize)
                ->select();

            // 缓存结果,有效期可以根据实际需求设置
            Cache::set($cacheKey, $result, 3600); // 缓存一小时
      }

      return $this->json(['code' => 200, 'data' => $result]);
    }
}
在上述例子中,我使用 where('1=1') 添加了一个始终成立的条件,然后在 field 方法中计算距离。你可以根据具体需求使用 where 方法添加其他条件,例如限制距离范围、过滤特定类型的商店等。

页: [1]
查看完整版本: mysql数据库里保存了商店信息,用thinkphp5写一个为用户按距离远近推荐附近商店