店滴开发者手册店滴开发者手册
首页
指南
插件
接口
智能设备
element后台
SDK
首页
指南
插件
接口
智能设备
element后台
SDK
  • 插件

    • 插件开发指南
    • 后台接口
    • 插件API开发指南
    • 后端
    • 目录结构
    • 配置
    • 接口文档
    • 插件模型开发指南
    • 插件搜索功能开发指南
    • 命令行

插件搜索功能开发指南

1. 搜索模型

搜索模型用于处理复杂的搜索条件和数据过滤,通常继承自对应的业务模型。

1.1 搜索模型创建

<?php
namespace addons\diandi_mall\models\search;

use yii\base\Model;
use yii\data\ActiveDataProvider;
use addons\diandi_mall\models\goods\Goods;

/**
 * 商品搜索模型
 */
class GoodsSearch extends Goods
{
    public $keyword; // 关键词搜索
    public $category_id; // 分类ID
    public $min_price; // 最低价格
    public $max_price; // 最高价格
    public $start_date; // 开始日期
    public $end_date; // 结束日期

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['id', 'category_id', 'stock', 'status'], 'integer'],
            [['keyword', 'start_date', 'end_date'], 'safe'],
            [['min_price', 'max_price', 'price'], 'number'],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function scenarios()
    {
        // bypass scenarios() implementation in the parent class
        return Model::scenarios();
    }

    /**
     * Creates data provider instance with search query applied
     *
     * @param array $params
     *
     * @return ActiveDataProvider
     */
    public function search($params)
    {
        $query = Goods::find();

        // add conditions that should always apply here

        $dataProvider = new ActiveDataProvider([
            'query' => $query,
            'pagination' => [
                'pageSize' => 10,
            ],
            'sort' => [
                'defaultOrder' => [
                    'created_at' => SORT_DESC,
                ],
            ],
        ]);

        $this->load($params);

        if (!$this->validate()) {
            // uncomment the following line if you do not want to return any records when validation fails
            // $query->where('0=1');
            return $dataProvider;
        }

        // 关键词搜索
        if (!empty($this->keyword)) {
            $query->andFilterWhere([
                'or',
                ['like', 'name', $this->keyword],
                ['like', 'description', $this->keyword],
                ['like', 'sku', $this->keyword]
            ]);
        }

        // 分类过滤
        if (!empty($this->category_id)) {
            $query->andFilterWhere(['category_id' => $this->category_id]);
        }

        // 价格范围过滤
        if (!empty($this->min_price)) {
            $query->andFilterWhere(['>=', 'price', $this->min_price]);
        }
        if (!empty($this->max_price)) {
            $query->andFilterWhere(['<=', 'price', $this->max_price]);
        }

        // 日期范围过滤
        if (!empty($this->start_date)) {
            $query->andFilterWhere(['>=', 'created_at', strtotime($this->start_date)]);
        }
        if (!empty($this->end_date)) {
            $query->andFilterWhere(['<=', 'created_at', strtotime($this->end_date . ' 23:59:59')]);
        }

        // 状态过滤
        $query->andFilterWhere([
            'status' => $this->status,
        ]);

        return $dataProvider;
    }
}

2. 搜索控制器

搜索控制器用于处理前端的搜索请求,调用搜索模型执行搜索逻辑。

2.1 搜索控制器创建

<?php
namespace addons\diandi_mall\api;

use common\controllers\addons\ApiController;
use addons\diandi_mall\models\search\GoodsSearch;
use Yii;

/**
 * 商品搜索控制器
 */
class SearchController extends ApiController
{
    /**
     * 商品搜索
     * @return array
     */
    public function actionGoods()
    {
        $searchModel = new GoodsSearch();
        $dataProvider = $searchModel->search(Yii::$app->request->queryParams);

        return $this->success([
            'list' => $dataProvider->getModels(),
            'total' => $dataProvider->getTotalCount(),
            'page' => Yii::$app->request->get('page', 1),
            'pageSize' => $dataProvider->pagination->pageSize,
        ]);
    }

    /**
     * 高级搜索
     * @return array
     */
    public function actionAdvanced()
    {
        $params = Yii::$app->request->queryParams;
        
        // 构建搜索条件
        $searchParams = [
            'keyword' => $params['keyword'] ?? '',
            'category_id' => $params['category_id'] ?? null,
            'min_price' => $params['min_price'] ?? null,
            'max_price' => $params['max_price'] ?? null,
            'status' => $params['status'] ?? null,
            'start_date' => $params['start_date'] ?? null,
            'end_date' => $params['end_date'] ?? null,
        ];

        $searchModel = new GoodsSearch();
        $dataProvider = $searchModel->search($searchParams);

        return $this->success([
            'list' => $dataProvider->getModels(),
            'total' => $dataProvider->getTotalCount(),
            'page' => $params['page'] ?? 1,
            'pageSize' => $dataProvider->pagination->pageSize,
            'params' => $searchParams,
        ]);
    }
}

3. 搜索配置

3.1 API路由配置

在 config/api.php 中配置搜索相关的路由:

<?php
return [
    'search' => [
        'class' => 'yii\rest\UrlRule',
        'controller' => ['diandi_mall/search'],
        'pluralize' => false,
        'extraPatterns' => [
            'GET goods' => 'goods',
            'GET advanced' => 'advanced',
        ],
    ],
];

3.2 后台路由配置

在 config/admin.php 中配置后台搜索相关的路由:

<?php
return [
    'search' => [
        'class' => 'yii\rest\UrlRule',
        'controller' => ['diandi_mall/admin-search'],
        'pluralize' => false,
        'extraPatterns' => [
            'GET goods' => 'goods',
            'GET order' => 'order',
        ],
    ],
];

4. 搜索功能实现

4.1 关键词搜索

// 关键词搜索实现
if (!empty($this->keyword)) {
    $query->andFilterWhere([
        'or',
        ['like', 'name', $this->keyword],
        ['like', 'description', $this->keyword],
        ['like', 'sku', $this->keyword],
        ['like', 'barcode', $this->keyword]
    ]);
}

4.2 分类搜索

// 分类搜索实现
if (!empty($this->category_id)) {
    // 单个分类
    if (is_numeric($this->category_id)) {
        $query->andFilterWhere(['category_id' => $this->category_id]);
    }
    // 多个分类
    elseif (is_array($this->category_id)) {
        $query->andFilterWhere(['in', 'category_id', $this->category_id]);
    }
}

4.3 价格范围搜索

// 价格范围搜索实现
if (!empty($this->min_price)) {
    $query->andFilterWhere(['>=', 'price', $this->min_price]);
}
if (!empty($this->max_price)) {
    $query->andFilterWhere(['<=', 'price', $this->max_price]);
}

4.4 日期范围搜索

// 日期范围搜索实现
if (!empty($this->start_date)) {
    $query->andFilterWhere(['>=', 'created_at', strtotime($this->start_date)]);
}
if (!empty($this->end_date)) {
    $query->andFilterWhere(['<=', 'created_at', strtotime($this->end_date . ' 23:59:59')]);
}

4.5 状态搜索

// 状态搜索实现
if ($this->status !== null) {
    $query->andFilterWhere(['status' => $this->status]);
}

5. 高级搜索技巧

5.1 关联查询搜索

// 关联查询搜索
$query->joinWith(['category', 'brand']);

// 搜索分类名称
if (!empty($this->category_name)) {
    $query->andFilterWhere(['like', 'category.name', $this->category_name]);
}

// 搜索品牌名称
if (!empty($this->brand_name)) {
    $query->andFilterWhere(['like', 'brand.name', $this->brand_name]);
}

5.2 自定义排序

// 自定义排序
if (!empty($params['sort'])) {
    $sortField = $params['sort'];
    $sortOrder = !empty($params['order']) ? $params['order'] : SORT_ASC;
    
    switch ($sortField) {
        case 'price':
            $query->orderBy(['price' => $sortOrder]);
            break;
        case 'sales':
            $query->orderBy(['sales' => $sortOrder]);
            break;
        case 'rating':
            $query->orderBy(['rating' => $sortOrder]);
            break;
        default:
            $query->orderBy(['created_at' => SORT_DESC]);
    }
}

5.3 分页和限制

// 自定义分页
$pageSize = !empty($params['pageSize']) ? $params['pageSize'] : 10;
$page = !empty($params['page']) ? $params['page'] : 1;

$dataProvider = new ActiveDataProvider([
    'query' => $query,
    'pagination' => [
        'pageSize' => $pageSize,
        'page' => $page - 1,
    ],
]);

// 限制结果数量
if (!empty($params['limit'])) {
    $query->limit($params['limit']);
}

6. 前端搜索实现

6.1 搜索表单

<form id="search-form">
    <div class="form-group">
        <label for="keyword">关键词</label>
        <input type="text" id="keyword" name="keyword" class="form-control" placeholder="请输入关键词">
    </div>
    <div class="form-group">
        <label for="category_id">分类</label>
        <select id="category_id" name="category_id" class="form-control">
            <option value="">全部分类</option>
            <!-- 分类选项 -->
        </select>
    </div>
    <div class="form-group">
        <label>价格范围</label>
        <div class="row">
            <div class="col-md-6">
                <input type="number" name="min_price" class="form-control" placeholder="最低价格">
            </div>
            <div class="col-md-6">
                <input type="number" name="max_price" class="form-control" placeholder="最高价格">
            </div>
        </div>
    </div>
    <div class="form-group">
        <label for="status">状态</label>
        <select id="status" name="status" class="form-control">
            <option value="">全部状态</option>
            <option value="1">激活</option>
            <option value="0">inactive</option>
        </select>
    </div>
    <button type="button" id="search-btn" class="btn btn-primary">搜索</button>
    <button type="button" id="reset-btn" class="btn btn-default">重置</button>
</form>

6.2 AJAX搜索请求

// 搜索按钮点击事件
$('#search-btn').on('click', function() {
    var params = {
        keyword: $('#keyword').val(),
        category_id: $('#category_id').val(),
        min_price: $('#min_price').val(),
        max_price: $('#max_price').val(),
        status: $('#status').val(),
        page: 1
    };
    
    // 发送搜索请求
    $.ajax({
        url: '/api/addons/diandi_mall/search/goods',
        type: 'GET',
        data: params,
        success: function(response) {
            if (response.code === 200) {
                // 处理搜索结果
                renderResult(response.data);
            } else {
                alert('搜索失败: ' + response.message);
            }
        },
        error: function() {
            alert('网络错误,请重试');
        }
    });
});

// 渲染搜索结果
function renderResult(data) {
    var html = '';
    if (data.list.length > 0) {
        data.list.forEach(function(item) {
            html += '<div class="result-item">';
            html += '<h4>' + item.name + '</h4>';
            html += '<p>价格: ¥' + item.price + '</p>';
            html += '<p>库存: ' + item.stock + '</p>';
            html += '<p>状态: ' + (item.status === 1 ? '激活' : 'inactive') + '</p>';
            html += '</div>';
        });
    } else {
        html = '<div class="no-result">没有找到匹配的结果</div>';
    }
    $('#result-container').html(html);
    
    // 渲染分页
    renderPagination(data.page, Math.ceil(data.total / data.pageSize));
}

// 渲染分页
function renderPagination(currentPage, totalPages) {
    var html = '';
    for (var i = 1; i <= totalPages; i++) {
        html += '<button class="page-btn" data-page="' + i + '">' + i + '</button>';
    }
    $('#pagination').html(html);
    
    // 分页点击事件
    $('.page-btn').on('click', function() {
        var page = $(this).data('page');
        // 重新发送搜索请求
        // ...
    });
}

7. 性能优化

7.1 索引优化

为常用的搜索字段添加数据库索引:

-- 为商品表添加索引
CREATE INDEX idx_goods_name ON diandi_mall_goods(name);
CREATE INDEX idx_goods_price ON diandi_mall_goods(price);
CREATE INDEX idx_goods_status ON diandi_mall_goods(status);
CREATE INDEX idx_goods_category_id ON diandi_mall_goods(category_id);
CREATE INDEX idx_goods_created_at ON diandi_mall_goods(created_at);

7.2 查询优化

  1. **避免使用 SELECT ***:只查询需要的字段
  2. 使用 WHERE 条件:减少返回的数据量
  3. 合理使用 JOIN:避免不必要的关联查询
  4. 使用 LIMIT:限制返回的记录数
  5. 缓存搜索结果:对于频繁的搜索请求使用缓存

7.3 缓存策略

// 使用缓存优化搜索
use yii\caching\FileCache;

public function actionGoods() {
    $params = Yii::$app->request->queryParams;
    $cacheKey = 'search_goods_' . md5(http_build_query($params));
    
    $cache = new FileCache();
    $data = $cache->get($cacheKey);
    
    if ($data === false) {
        $searchModel = new GoodsSearch();
        $dataProvider = $searchModel->search($params);
        
        $data = [
            'list' => $dataProvider->getModels(),
            'total' => $dataProvider->getTotalCount(),
            'page' => $params['page'] ?? 1,
            'pageSize' => $dataProvider->pagination->pageSize,
        ];
        
        // 缓存搜索结果,有效期5分钟
        $cache->set($cacheKey, $data, 300);
    }
    
    return $this->success($data);
}

8. 常见问题

8.1 搜索性能问题

可能原因:

  • 数据库索引缺失
  • 查询条件过于复杂
  • 数据量过大
  • 缓存策略不当

解决方案:

  • 添加适当的数据库索引
  • 优化查询条件,减少不必要的过滤
  • 实现分页和限制,避免一次性返回过多数据
  • 使用缓存,减少数据库查询次数

8.2 搜索结果不准确

可能原因:

  • 搜索条件配置错误
  • 数据模型关联问题
  • 数据一致性问题

解决方案:

  • 检查搜索条件的配置是否正确
  • 确认数据模型的关联关系是否正确
  • 确保数据的一致性和完整性

8.3 搜索功能无响应

可能原因:

  • API 路由配置错误
  • 控制器方法不存在
  • 参数传递错误
  • 服务器错误

解决方案:

  • 检查 API 路由配置是否正确
  • 确认控制器和方法是否存在
  • 验证参数传递是否正确
  • 查看服务器日志,排查错误原因

9. 最佳实践

9.1 搜索功能设计建议

  1. 简洁易用:搜索界面应简洁明了,避免过多的搜索条件
  2. 即时反馈:提供即时的搜索结果反馈,如自动补全、搜索建议等
  3. 错误处理:优雅处理搜索错误,提供友好的错误提示
  4. 性能优先:优化搜索性能,确保搜索速度快
  5. 用户体验:提供清晰的搜索结果展示和分页导航

9.2 代码规范

  1. 命名规范:搜索模型和控制器使用 Search 后缀
  2. 注释规范:为搜索逻辑添加详细的注释
  3. 错误处理:使用 try-catch 捕获和处理异常
  4. 日志记录:记录搜索请求和错误信息

9.3 安全考虑

  1. 参数验证:对所有搜索参数进行验证,防止 SQL 注入
  2. 权限控制:对敏感数据的搜索添加权限控制
  3. 频率限制:防止恶意的搜索请求攻击
  4. 数据过滤:过滤掉敏感信息,保护用户隐私

10. 示例代码

10.1 完整的搜索控制器示例

<?php
namespace addons\diandi_mall\api;

use common\controllers\addons\ApiController;
use addons\diandi_mall\models\search\GoodsSearch;
use addons\diandi_mall\models\search\OrderSearch;
use Yii;
use yii\caching\FileCache;

/**
 * 搜索控制器
 */
class SearchController extends ApiController
{
    /**
     * 商品搜索
     * @return array
     */
    public function actionGoods()
    {
        $params = Yii::$app->request->queryParams;
        $cacheKey = 'search_goods_' . md5(http_build_query($params));
        
        $cache = new FileCache();
        $data = $cache->get($cacheKey);
        
        if ($data === false) {
            $searchModel = new GoodsSearch();
            $dataProvider = $searchModel->search($params);
            
            $data = [
                'list' => $dataProvider->getModels(),
                'total' => $dataProvider->getTotalCount(),
                'page' => $params['page'] ?? 1,
                'pageSize' => $dataProvider->pagination->pageSize,
            ];
            
            // 缓存搜索结果,有效期5分钟
            $cache->set($cacheKey, $data, 300);
        }
        
        return $this->success($data);
    }

    /**
     * 订单搜索
     * @return array
     */
    public function actionOrder()
    {
        $params = Yii::$app->request->queryParams;
        $searchModel = new OrderSearch();
        $dataProvider = $searchModel->search($params);

        return $this->success([
            'list' => $dataProvider->getModels(),
            'total' => $dataProvider->getTotalCount(),
            'page' => $params['page'] ?? 1,
            'pageSize' => $dataProvider->pagination->pageSize,
        ]);
    }

    /**
     * 高级搜索
     * @return array
     */
    public function actionAdvanced()
    {
        $params = Yii::$app->request->queryParams;
        $type = $params['type'] ?? 'goods';
        
        switch ($type) {
            case 'goods':
                $searchModel = new GoodsSearch();
                break;
            case 'order':
                $searchModel = new OrderSearch();
                break;
            default:
                return $this->error('搜索类型错误');
        }
        
        $dataProvider = $searchModel->search($params);

        return $this->success([
            'list' => $dataProvider->getModels(),
            'total' => $dataProvider->getTotalCount(),
            'page' => $params['page'] ?? 1,
            'pageSize' => $dataProvider->pagination->pageSize,
            'type' => $type,
        ]);
    }
}

10.2 完整的搜索模型示例

<?php
namespace addons\diandi_mall\models\search;

use yii\base\Model;
use yii\data\ActiveDataProvider;
use addons\diandi_mall\models\order\Order;

/**
 * 订单搜索模型
 */
class OrderSearch extends Order
{
    public $keyword; // 关键词搜索(订单号、商品名称、用户名称)
    public $user_id; // 用户ID
    public $status; // 订单状态
    public $payment_method; // 支付方式
    public $min_amount; // 最低金额
    public $max_amount; // 最高金额
    public $start_date; // 开始日期
    public $end_date; // 结束日期

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['id', 'user_id', 'status', 'payment_method'], 'integer'],
            [['keyword', 'start_date', 'end_date'], 'safe'],
            [['min_amount', 'max_amount', 'amount'], 'number'],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function scenarios()
    {
        return Model::scenarios();
    }

    /**
     * Creates data provider instance with search query applied
     *
     * @param array $params
     *
     * @return ActiveDataProvider
     */
    public function search($params)
    {
        $query = Order::find();

        // 关联查询
        $query->joinWith(['user', 'items']);

        $dataProvider = new ActiveDataProvider([
            'query' => $query,
            'pagination' => [
                'pageSize' => 10,
            ],
            'sort' => [
                'defaultOrder' => [
                    'created_at' => SORT_DESC,
                ],
            ],
        ]);

        $this->load($params);

        if (!$this->validate()) {
            return $dataProvider;
        }

        // 关键词搜索
        if (!empty($this->keyword)) {
            $query->andFilterWhere([
                'or',
                ['like', 'order.order_sn', $this->keyword],
                ['like', 'user.username', $this->keyword],
                ['like', 'items.name', $this->keyword]
            ]);
        }

        // 用户ID过滤
        if (!empty($this->user_id)) {
            $query->andFilterWhere(['order.user_id' => $this->user_id]);
        }

        // 状态过滤
        if (!empty($this->status)) {
            $query->andFilterWhere(['order.status' => $this->status]);
        }

        // 支付方式过滤
        if (!empty($this->payment_method)) {
            $query->andFilterWhere(['order.payment_method' => $this->payment_method]);
        }

        // 金额范围过滤
        if (!empty($this->min_amount)) {
            $query->andFilterWhere(['>=', 'order.amount', $this->min_amount]);
        }
        if (!empty($this->max_amount)) {
            $query->andFilterWhere(['<=', 'order.amount', $this->max_amount]);
        }

        // 日期范围过滤
        if (!empty($this->start_date)) {
            $query->andFilterWhere(['>=', 'order.created_at', strtotime($this->start_date)]);
        }
        if (!empty($this->end_date)) {
            $query->andFilterWhere(['<=', 'order.created_at', strtotime($this->end_date . ' 23:59:59')]);
        }

        return $dataProvider;
    }
}
Prev
插件模型开发指南
Next
命令行