shopxo搜索功能增加规格条码编码

<?php
// +----------------------------------------------------------------------
// | ShopXO 国内领先企业级B2C免费开源电商系统
// +----------------------------------------------------------------------
// | Copyright (c) 2011~2099 http://shopxo.net All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( https://opensource.org/licenses/mit-license.php )
// +----------------------------------------------------------------------
// | Author: Devil
// +----------------------------------------------------------------------
namespace app\service;

use think\facade\Db;
use app\service\SystemService;
use app\service\GoodsService;
use app\service\BrandService;
use app\service\ResourcesService;
use app\service\GoodsCategoryService;

/**
 * 搜索服务层
 * @author   Devil
 * @blog     http://gong.gg/
 * @version  0.0.1
 * @datetime 2016-12-01T21:51:08+0800
 */
class SearchService
{
    /**
     * 排序列表
     * @author  Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2021-11-01
     * @desc    description
     * @param   [array]           $params [输入参数]
     */
    public static function SearchMapOrderByList($params = [])
    {
        // 移除分页和框架的s模块参数
        unset($params['page'], $params['s']);

        // 处理排序参数
        $ov = empty($params['ov']) ? ['default'] : explode('-', $params['ov']);
        $data = MyConst('common_search_order_by_list');
        foreach($data as &$v)
        {
            // 是否选中
            $v['is_active'] = ($ov[0] == $v['type']) ? 1 : 0;

            // url
            $temp_ov = '';
            if($v['type'] == 'default')
            {
                $temp_params = $params;
                unset($temp_params['ov']);
            } else {
                // 类型
                if($ov[0] == $v['type'])
                {
                    $v['value'] = ($ov[1] == 'desc') ? 'asc' : 'desc';
                }

                // 参数值
                $temp_ov = $v['type'].'-'.$v['value'];
                $temp_params = array_merge($params, ['ov'=>$temp_ov]);
            }
            $v['url'] = MyUrl('index/search/index', $temp_params);
        }
        return $data;
    }

    /**
     * 搜素条件处理
     * @author  Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2022-06-30
     * @desc    description
     * @param   [array]          $data   [数据列表]
     * @param   [string]         $pid    [参数字段]
     * @param   [string]         $did    [数据字段]
     * @param   [array]          $params [输入参数]
     * @param   [array]          $ext    [扩展数据]
     */
    public static function SearchMapHandle($data, $pid, $did, $params, $ext = [])
    {
        // 移除分页和框架的s模块参数
        unset($params['page'], $params['s']);

        // ascii字段处理
        $is_ascii = isset($ext['is_ascii']) && $ext['is_ascii'] == true;
        $field = empty($ext['field']) ? 'value' : $ext['field'];
        foreach($data as &$v)
        {
            // 是否转ascii处理主键字段
            if($is_ascii && !empty($field) && isset($v[$field]))
            {
                $v[$did] = StrToAscii($v[$field]);
            }
            $temp_params = $params;
            if(isset($v[$did]))
            {
                if(isset($params[$pid]) && $params[$pid] == $v[$did])
                {
                    unset($temp_params[$pid]);
                } else {
                    $temp_params = array_merge($params, [$pid=>$v[$did]]);
                }
            }
            $v['url'] = MyUrl('index/search/index', $temp_params);
            $v['is_active'] = (isset($params[$pid]) && isset($v[$did]) && $params[$pid] == $v[$did]) ? 1 : 0;
        }
        return $data;
    }

    /**
     * 获取商品列表
     * @author   Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2018-09-07
     * @desc    description
     * @param   [array]          $map    [搜素条件]
     * @param   [array]          $params [输入参数]
     */
    public static function GoodsList($map, $params = [])
    {
        // 返回格式
        $result = [
            'page_start'    => 0,
            'page_size'     => 0,
            'page'          => 1,
            'page_total'    => 0,
            'total'         => 0,
            'data'          => [],
        ];

        // 搜索条件
        $order_by = $map['order_by'];
        $where_base = $map['base'];
        $where_keywords = $map['keywords'];
        $where_screening_price = $map['screening_price'];

        // 分页计算
        $field = 'g.*';
        $result['page'] = max(1, isset($params['page']) ? intval($params['page']) : 1);
        $result['page_size'] = empty($params['page_size']) ? MyC('home_search_limit_number', 20, true) : intval($params['page_size']);
        // 数量不能超过500
        if($result['page_size'] > 500)
        {
            $result['page_size'] = 500;
        }
        $result['page_start'] = intval(($result['page']-1)*$result['page_size']);

        // 搜索商品列表读取前钩子
        $hook_name = 'plugins_service_search_goods_list_begin';
        MyEventTrigger($hook_name, [
            'hook_name'                 => $hook_name,
            'is_backend'                => true,
            'params'                    => &$params,
            'where_base'                => &$where_base,
            'where_keywords'            => &$where_keywords,
            'where_screening_price'     => &$where_screening_price,
            'field'                     => &$field,
            'order_by'                  => &$order_by,
            'page'                      => &$result['page'],
            'page_start'                => &$result['page_start'],
            'page_size'                 => &$result['page_size'],
        ]);

        // 获取商品总数
        $result['total'] = (int) Db::name('Goods')->alias('g')->join('goods_category_join gci', 'g.id=gci.goods_id')->leftJoin('goods_spec_base gsb', 'g.id=gsb.goods_id')->leftJoin('goods_spec_value gsv', 'g.id=gsv.goods_id')->where($where_base)->where(function($query) use($where_keywords) {
            self::SearchKeywordsWhereJoinType($query, $where_keywords);
        })->where(function($query) use($where_screening_price) {
            $query->whereOr($where_screening_price);
        })->count('DISTINCT g.id');

        // 获取商品列表
        if($result['total'] > 0)
        {
            // 查询数据
            $data = Db::name('Goods')->alias('g')->join('goods_category_join gci', 'g.id=gci.goods_id')->leftJoin('goods_spec_base gsb', 'g.id=gsb.goods_id')->leftJoin('goods_spec_value gsv', 'g.id=gsv.goods_id')->field($field)->where($where_base)->where(function($query) use($where_keywords) {
                self::SearchKeywordsWhereJoinType($query, $where_keywords);
            })->where(function($query) use($where_screening_price) {
                $query->whereOr($where_screening_price);
            })->group('g.id')->order($order_by)->limit($result['page_start'], $result['page_size'])->select()->toArray();
            
            // 数据处理
            $params['is_spec'] = 1;
            $params['is_cart'] = 1;
            $goods = GoodsService::GoodsDataHandle($data, $params);

            // 返回数据
            $result['data'] = $goods['data'];
            $result['page_total'] = ceil($result['total']/$result['page_size']);
        } else {
            return DataReturn(MyLang('no_data'), -1, $result);
        }
        return DataReturn(MyLang('handle_success'), 0, $result);
    }

    /**
     * 关键字搜索关系类型
     * @author  Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2021-04-02
     * @desc    description
     * @param   [object]          $query          [查询对象]
     * @param   [array]           $where_keywords [搜索关键字]
     */
    public static function SearchKeywordsWhereJoinType($query, $where_keywords)
    {
        // 搜索关键字默认或的关系
        $join = 'whereOr';

        // 是否开启并且关系
        if(MyC('home_search_is_keywords_where_and') == 1)
        {
            $join = 'where';
        }

        // 条件设置
        $query->$join($where_keywords);
    }

    /**
     * 搜索条件处理
     * @author  Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2021-01-08
     * @desc    description
     * @param   [array]           $params [输入参数]
     */
    public static function SearchWhereHandle($params = [])
    {
        // 搜索商品条件处理钩子
        $hook_name = 'plugins_service_search_goods_list_where';
        MyEventTrigger($hook_name, [
            'hook_name'     => $hook_name,
            'is_backend'    => true,
            'params'        => &$params,
        ]);

        // 基础条件
        $where_base = [
            ['g.is_delete_time', '=', 0],
            ['g.is_shelves', '=', 1]
        ];

        // 关键字
        $where_keywords = [];
        if(!empty($params['wd']))
        {
            // WEB端则处理关键字
            if(APPLICATION_CLIENT_TYPE == 'pc')
            {
                $params['wd'] = AsciiToStr($params['wd']);
            }
            $keywords_fields = 'g.title|g.simple_desc|g.model|gsb.coding|gsb.barcode|gsv.value';//添加规格,编码,条码搜索
            if(MyC('home_search_is_keywords_seo_fields') == 1)
            {
                $keywords_fields .= '|g.seo_title|g.seo_keywords|g.seo_desc';
            }
            $keywords = explode(' ', $params['wd']);
            foreach($keywords as $kv)
            {
                $where_keywords[] = [$keywords_fields, 'like', '%'.$kv.'%'];
            }
        }

        // 品牌
        // 不存在搜索品牌的时候则看是否指定品牌
        if(!empty($params['brand_ids']))
        {
            if(!is_array($params['brand_ids']))
            {
                $params['brand_ids'] = (substr($params['brand_ids'], 0, 1) == '{') ? json_decode(htmlspecialchars_decode($params['brand_ids']), true) : explode(',', $params['brand_ids']);
            }
            if(!empty($params['brand_ids']))
            {
                $where_base[] = ['g.brand_id', 'in', array_unique($params['brand_ids'])];
            }
        }
        // 指定品牌
        if(!empty($params['brand']))
        {
            $where_base[] = ['g.brand_id', 'in', [intval($params['brand'])]];
        }
        // web端
        if(!empty($params['bid']))
        {
            $where_base[] = ['g.brand_id', 'in', [intval($params['bid'])]];
        }

        // 分类id
        // 不存在搜索分类的时候则看是否指定分类
        if(!empty($params['category_ids']))
        {
            if(!is_array($params['category_ids']))
            {
                $params['category_ids'] = (substr($params['category_ids'], 0, 1) == '{') ? json_decode(htmlspecialchars_decode($params['category_ids']), true) : explode(',', $params['category_ids']);
            }
            if(!empty($params['category_ids']))
            {
                $ids = GoodsCategoryService::GoodsCategoryItemsIds($params['category_ids'], 1);
                $where_base[] = ['gci.category_id', 'in', $ids];
            }
        } else {
            if(!empty($params['category_id']))
            {
                $ids = GoodsCategoryService::GoodsCategoryItemsIds([intval($params['category_id'])], 1);
                $where_base[] = ['gci.category_id', 'in', $ids];
            }
        }
        // web端
        if(!empty($params['cid']))
        {
            $ids = GoodsCategoryService::GoodsCategoryItemsIds([intval($params['cid'])], 1);
            $where_base[] = ['gci.category_id', 'in', $ids];
        }

        // 筛选价格
        $map_price = [];
        $where_screening_price = [];
        if(!empty($params['screening_price_values']))
        {
            if(!is_array($params['screening_price_values']))
            {
                $map_price = (substr($params['screening_price_values'], 0, 1) == '{') ? json_decode(htmlspecialchars_decode($params['screening_price_values']), true) : explode(',', $params['screening_price_values']);
            }
        }
        // web端
        if(!empty($params['peid']))
        {
            $temp_price = Db::name('ScreeningPrice')->where(['is_enable'=>1, 'id'=>intval($params['peid'])])->field('min_price,max_price')->find();
            if(!empty($temp_price))
            {
                $map_price[] = implode('-', $temp_price);
            }
        }
        // 价格滑条
        if(!empty($params['price']) && stripos($params['price'], '-') !== false)
        {
            $map_price[] = $params['price'];
        }
        // 处理价格条件
        if(!empty($map_price))
        {
            foreach($map_price as $v)
            {
                $temp = explode('-', $v);
                if(count($temp) == 2)
                {
                    // 最小金额等于0、最大金额大于0
                    if(empty($temp[0]) && !empty($temp[1]))
                    {
                        $where_screening_price[] = [
                            ['min_price', '<=', $temp[1]],
                        ];

                    // 最小金额大于0、最大金额大于0
                    // 最小金额等于0、最大金额等于0
                    } elseif((!empty($temp[0]) && !empty($temp[1])) || (empty($temp[0]) && empty($temp[1])))
                    {
                        $where_screening_price[] = [
                            ['min_price', '>=', $temp[0]],
                            ['min_price', '<=', $temp[1]],
                        ];

                    // 最小金额大于0、最大金额等于0
                    } elseif(!empty($temp[0]) && empty($temp[1]))
                    {
                        $where_screening_price[] = [
                            ['min_price', '>=', $temp[0]],
                        ];
                    }
                }
            }
        }

        // 商品参数、属性
        $map_params = [];
        if(!empty($params['goods_params_values']))
        {
            if(!is_array($params['goods_params_values']))
            {
                $map_params = (substr($params['goods_params_values'], 0, 1) == '{') ? json_decode(htmlspecialchars_decode($params['goods_params_values']), true) : explode(',', $params['goods_params_values']);
            }
        }
        if(!empty($params['psid']))
        {
            $map_params[] = AsciiToStr($params['psid']);
        }
        if(!empty($map_params))
        {
            $ids = Db::name('GoodsParams')->where(['value'=>$map_params, 'type'=>self::SearchParamsWhereTypeValue()])->column('goods_id');
            if(!empty($ids))
            {
                $where_base[] = ['g.id', 'in', $ids];
            }
        }

        // 商品规格
        $map_spec = [];
        if(!empty($params['goods_spec_values']))
        {
            if(!is_array($params['goods_spec_values']))
            {
                $map_spec = (substr($params['goods_spec_values'], 0, 1) == '{') ? json_decode(htmlspecialchars_decode($params['goods_spec_values']), true) : explode(',', $params['goods_spec_values']);
            }
        }
        if(!empty($params['scid']))
        {
            $map_spec[] = AsciiToStr($params['scid']);
        }
        if(!empty($map_spec))
        {
            $ids = Db::name('GoodsSpecValue')->where(['value'=>$map_spec])->column('goods_id');
            if(!empty($ids))
            {
                $where_base[] = ['g.id', 'in', $ids];
            }
        }

        // 排序
        $order_by = 'g.sort_level desc, g.access_count desc, g.sales_count desc, g.id desc';
        if(!empty($params['ov']))
        {
            // 数据库字段映射关系
            $fields = [
                'sales'     => 'g.sales_count',
                'access'    => 'g.access_count',
                'price'     => 'g.min_price',
                'new'       => 'g.id',
            ];

            // 参数判断
            $temp = explode('-', $params['ov']);
            if(count($temp) == 2 && $temp[0] != 'default' && array_key_exists($temp[0], $fields) && in_array($temp[1], ['desc', 'asc']))
            {
                $order_by = $fields[$temp[0]].' '.$temp[1];
            }
        } else {
            if(!empty($params['order_by_type']) && !empty($params['order_by_field']) && $params['order_by_field'] != 'default')
            {
                $order_by = 'g.'.$params['order_by_field'].' '.$params['order_by_type'];
            }
        }

        // 是否存在搜索条件
        $is_map = (count($where_base) > 2 || !empty($where_keywords) || !empty($where_screening_price) || !empty($params['ov'])) ? 1 : 0;
        return [
            'base'              => $where_base,
            'keywords'          => $where_keywords,
            'screening_price'   => $where_screening_price,
            'order_by'          => $order_by,
            'is_map'            => $is_map,
        ];
    }

    /**
     * 参数搜索条件类型
     * @author  Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2021-04-11
     * @desc    description
     */
    public static function SearchParamsWhereTypeValue()
    {
        // 获取配置
        $value = MyC('home_search_params_type');
        if(empty($value))
        {
            $value = [2];
        }

        // 是否为数组
        if(!is_array($value))
        {
            $value = explode(',', $value);
        }

        return $value;
    }

    /**
     * 搜索记录添加
     * @author   Devil
     * @blog     http://gong.gg/
     * @version  1.0.0
     * @datetime 2018-10-21T00:37:44+0800
     * @param   [array]          $params [输入参数]
     */
    public static function SearchAdd($params = [])
    {
        // 是否增加搜索记录
        if(MyC('home_search_history_record', 0) == 1)
        {
            // 排序
            $ov_arr = empty($params['ov']) ? '' : explode('-', $params['ov']);
            if(empty($ov_arr) && !empty($params['order_by_type']) && !empty($params['order_by_field']))
            {
                $ov_arr = [$params['order_by_field'], $params['order_by_type']];
            }

            // 结果仅保留商品id
            if(!empty($params['search_result_data']) && is_array($params['search_result_data']) && !empty($params['search_result_data']['data']))
            {
                $params['search_result_data']['data'] = array_column($params['search_result_data']['data'], 'id');
            }

            // 日志数据
            $data = [
                'user_id'           => isset($params['user_id']) ? intval($params['user_id']) : 0,
                'keywords'          => empty($params['wd']) ? '' : $params['wd'],
                'order_by_field'    => empty($ov_arr) ? '' : $ov_arr[0],
                'order_by_type'     => empty($ov_arr) ? '' : $ov_arr[1],
                'search_result'     => empty($params['search_result_data']) ? '' : json_encode($params['search_result_data'], JSON_UNESCAPED_UNICODE),
                'ymd'               => date('Ymd'),
                'add_time'          => time(),
            ];

            // 参数处理
            $field_arr = [
                'brand_ids'             => ['brand_ids', 'brand', 'bid'],
                'category_ids'          => ['category_ids', 'category_id', 'cid'],
                'screening_price_values'=> ['screening_price_values', 'peid'],
                'goods_params_values'   => ['goods_params_values', 'psid'],
                'goods_spec_values'     => ['goods_spec_values', 'scid'],
            ];
            foreach($field_arr as $k=>$v)
            {
                $item = [];
                foreach($v as $vs)
                {
                    if(!empty($params[$vs]))
                    {
                        // 价格区间
                        if($vs == 'peid')
                        {
                            $temp_price = Db::name('ScreeningPrice')->where(['is_enable'=>1, 'id'=>intval($params[$vs])])->field('min_price,max_price')->find();
                            $params[$vs] = empty($temp_price) ? '' : implode('-', $temp_price);
                        }

                        // Ascii处理
                        if(in_array($vs, ['psid', 'scid']))
                        {
                            $params[$vs] = AsciiToStr($params[$vs]);
                        }

                        // 合并参数
                        $tv = (substr($params[$vs], 0, 1) == '{') ? json_decode(htmlspecialchars_decode($params[$vs]), true) : $params[$vs];
                        if($tv !== '' && $tv !== null)
                        {
                            $item = array_merge($item, is_array($tv) ? $tv : [$tv]);
                        }
                    }
                }
                $data[$k] = empty($item) ? '' : (is_array($item) ? json_encode($item, JSON_UNESCAPED_UNICODE) : $item);
            }
            Db::name('SearchHistory')->insert($data);
        }
    }

    /**
     * 搜索关键字列表
     * @author  Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2021-01-03
     * @desc    description
     * @param   [array]           $params [输入参数]
     */
    public static function SearchKeywordsList($params = [])
    {
        $key = SystemService::CacheKey('shopxo.cache_search_keywords_key');
        $data = MyCache($key);
        if($data === null || MyEnv('app_debug'))
        {
            switch(intval(MyC('home_search_keywords_type', 0)))
            {
                case 1 :
                    $data = Db::name('SearchHistory')->where([['keywords', '<>', '']])->group('keywords')->limit(10)->column('keywords');
                    break;
                case 2 :
                    $keywords = MyC('home_search_keywords', '', true);
                    if(!empty($keywords))
                    {
                        $data = explode(',', $keywords);
                    }
                    break;
            }

            // 没数据则赋空数组值
            if(empty($data))
            {
                $data = [];
            }

            // 存储缓存
            MyCache($key, $data, 180);
        }
        return $data;
    }

    /**
     * 分类下品牌列表
     * @author   Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2018-08-29
     * @desc    description
     * @param   [array]          $map    [搜索条件]
     * @param   [array]          $params [输入参数]
     */
    public static function CategoryBrandList($map, $params = [])
    {
        $data = [];
        if(MyC('home_search_is_brand', 0) == 1)
        {
            // 基础条件
            $brand_where = [
                ['is_enable', '=', 1],
            ];

            // 仅搜索关键字相关的品牌
            if(!empty($map['keywords']))
            {
                $where_keywords = $map['keywords'];
                $ids = Db::name('Goods')->alias('g')->leftJoin('goods_spec_base gsb', 'g.id=gsb.goods_id')->leftJoin('goods_category_join gci', 'g.id=gci.goods_id')->join('goods_spec_value gsv', 'g.id=gsv.goods_id')->where(function($query) use($where_keywords) {
                    self::SearchKeywordsWhereJoinType($query, $where_keywords);
                })->group('g.brand_id')->column('g.brand_id');
                if(!empty($ids))
                {
                    $brand_where[] = ['id', 'in', array_unique($ids)];
                }
            }

            // 仅获取已关联商品的品牌
            $where = [
                ['is_shelves', '=', 1],
                ['is_delete_time', '=', 0],
                ['brand_id', '>', 0],
            ];
            $ids = Db::name('Goods')->where($where)->column('distinct brand_id');
            if(!empty($ids))
            {
                $brand_where[] = ['id', 'in', $ids];
            }

            // 获取品牌列表
            $data_params = [
                'field'     => 'id,name,logo,website_url',
                'where'     => $brand_where,
                'm'         => 0,
                'n'         => 0,
            ];
            $ret = BrandService::BrandList($data_params);
            $data = empty($ret['data']) ? [] : $ret['data'];
        }
        return $data;
    }

    /**
     * 根据分类id获取下级列表
     * @author   Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2018-08-29
     * @desc    description
     * @param   [array]          $params [输入参数]
     */
    public static function GoodsCategoryList($params = [])
    {
        $data = [];
        if(MyC('home_search_is_category', 0) == 1)
        {
            $cid = empty($params['category_id']) ? (empty($params['cid']) ? 0 : intval($params['cid'])) : intval($params['category_id']);
            $where = [
                ['pid', '=', intval($cid)],
            ];
            $data = GoodsCategoryService::GoodsCategoryList(['where'=>$where, 'field'=>'id,name']);
        }
        return $data;
    }

    /**
     * 获取商品价格筛选列表
     * @author   Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2018-09-07
     * @desc    description
     * @param   [array]          $params [输入参数]
     */
    public static function ScreeningPriceList($params = [])
    {
        $data = [];
        if(MyC('home_search_is_price', 0) == 1)
        {
            $data = Db::name('ScreeningPrice')->field('id,name,min_price,max_price')->where(['is_enable'=>1])->order('sort asc')->select()->toArray();
        }
        return $data;
    }

    /**
     * 搜索商品参数列表、去重
     * @author  Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2021-01-08
     * @desc    description
     * @param   [array]          $map    [搜素条件]
     * @param   [array]          $params [输入参数]
     */
    public static function SearchGoodsParamsValueList($map, $params = [])
    {
        $data = [];
        if(MyC('home_search_is_params', 0) == 1)
        {
            // 搜索条件
            $where_base = $map['base'];
            $where_keywords = $map['keywords'];
            $where_screening_price = $map['screening_price'];

            // 仅搜索基础参数
            $where_base[] = ['gp.type', 'in', self::SearchParamsWhereTypeValue()];

            // 一维数组、参数值去重
            $data = Db::name('Goods')->alias('g')->join('goods_category_join gci', 'g.id=gci.goods_id')->join('goods_params gp', 'g.id=gp.goods_id')->leftJoin('goods_spec_base gsb', 'g.id=gsb.goods_id')->leftJoin('goods_spec_value gsv', 'g.id=gsv.goods_id')->where($where_base)->where(function($query) use($where_keywords) {
                self::SearchKeywordsWhereJoinType($query, $where_keywords);
            })->where(function($query) use($where_screening_price) {
                $query->whereOr($where_screening_price);
            })->group('gp.value')->field('gp.value')->select()->toArray();
        }
        return $data;
    }

    /**
     * 搜索商品规格列表、去重
     * @author  Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2021-01-08
     * @desc    description
     * @param   [array]           $map    [搜素条件]
     * @param   [array]           $params [输入参数]
     */
    public static function SearchGoodsSpecValueList($map, $params = [])
    {
        $data = [];
        if(MyC('home_search_is_spec', 0) == 1)
        {
            // 搜索条件
            $where_base = $map['base'];
            $where_keywords = $map['keywords'];
            $where_screening_price = $map['screening_price'];

            // 一维数组、参数值去重
            $data = Db::name('Goods')->alias('g')->join('goods_category_join gci', 'g.id=gci.goods_id')->join('goods_spec_value gsv', 'g.id=gsv.goods_id')->leftJoin('goods_spec_base gsb', 'g.id=gsb.goods_id')->where($where_base)->where(function($query) use($where_keywords) {
                self::SearchKeywordsWhereJoinType($query, $where_keywords);
            })->where(function($query) use($where_screening_price) {
                $query->whereOr($where_screening_price);
            })->group('gsv.value')->field('gsv.value')->select()->toArray();
        }
        return $data;
    }

    /**
     * 搜索条件基础数据
     * @author  Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2021-01-11
     * @desc    description
     * @param   [array]           $params [输入参数]
     */
    public static function SearchMapInfo($params = [])
    {
        // 分类
        $category = null;
        $cid = empty($params['category_id']) ? (empty($params['cid']) ? 0 : intval($params['cid'])) : intval($params['category_id']);
        if(!empty($cid))
        {
            $category = GoodsCategoryService::GoodsCategoryRow(['id'=>$cid, 'field'=>'name,vice_name,describe,seo_title,seo_keywords,seo_desc']);
        }

        // 品牌
        $brand = null;
        $bid = empty($params['brand']) ? (empty($params['bid']) ? 0 : intval($params['bid'])) : intval($params['brand']);
        if(!empty($bid))
        {
            $data_params = [
                'field'     => 'id,name,describe,logo,website_url,seo_title,seo_keywords,seo_desc',
                'where'     => [
                    ['id', '=', $bid]
                ],
                'm'         => 0,
                'n'         => 1,
            ];
            $ret = BrandService::BrandList($data_params);
            if(!empty($ret['data']) && !empty($ret['data'][0]))
            {
                $brand = $ret['data'][0];
            }
        }

        return [
            'category'  => empty($category) ? null : $category,
            'brand'     => empty($brand) ? null : $brand,
        ];
    }

    /**
     * 搜索商品最大金额
     * @author  Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2024-04-21
     * @desc    description
     * @param   [array]           $params [输入参数]
     */
    public static function SearchGoodsMaxPrice($params = [])
    {
        return Db::name('GoodsSpecBase')->max('price');
    }

    /**
     * 搜索页用户Agent验证是否通过
     * @author  Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2024-06-19
     * @desc    description
     * @param   [array]           $params [输入参数]
     */
    public static function SearchProhibitUserAgentCheck($params = [])
    {
        if(!empty($_SERVER) && !empty($_SERVER['HTTP_USER_AGENT']))
        {
            $prohibit = MyC('home_search_prohibit_user_agent', '', true);
            if(!empty($prohibit))
            {
                $user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
                $prohibit = explode(',', strtolower($prohibit));
                foreach($prohibit as $v)
                {
                    if(stripos($user_agent, $v) !== false)
                    {
                        return DataReturn(MyLang('illegal_access_tips'), -1);
                    }
                }
            }
        }
        return DataReturn('success', 0);
    }
}
?>

Nextcloud 21 删除分享页面底部的“获取自己的免费账号”文字链接

使用 Nextcloud 分享文件时,页面底部默认会有一排“获取自己的免费账号”的文字链接,点击就会跳转到 Nextcloud 官网 Simple sign up 页面,如果不需要这个选项,可以在配置文件中添加相关参数去掉这一排文字链接。

  在 /nextcloud/config/config.php 文件中添加以下代码后保存后即可

'simpleSignUpLink.shown' => false,

  如果需要修改这一排文字链接,可以尝试先修改以下两个文件中对应的文字为需要的内容

  文件一:/nextcloud/core/l10n/zh_CN.json 第 311 行,将这行中的“获取自己的免费账号”替换为自己的需要的文字

"Get your own free account" : "获取自己的免费账号",

  文件二:/nextcloud/core/templates/layout.public.php 第 99 行,将这行中的 “https://nextcloud.com/signup/” 替换为自己需要的链接

<a href="https://nextcloud.com/signup/" target="_blank" rel="noreferrer noopener">

  如果需要更换使用其他语言时显示的文字链接,方法也和上面一样。

文章来自于https://www.cnswiz.com/3683.html

Nextcloud 21+ – 调整或关闭分块上传

关闭分块上传能提升处于高上传带宽环境时文件上传的性能表现,可根据自身需求选择是否启用。

  方法为在ssh连接中切换到 Nextcloud 所在目录,执行以下命令( www-data 是 Nextcloud 文件所属用户,根据自己的情况替换)

sudo -u www-data php occ config:app:set files max_chunk_size --value [分块大小的数值]

  命令后面加上自己想要设置的分块上传大小的值(单位为字节),此设置默认为 10485760 字节 (10 MB),如果处于高上传带宽环境时,可以尝试关闭分块上传以提升上传性能,方法为执行以下命令

sudo -u www-data php occ config:app:set files max_chunk_size --value 0

Adjust chunk size on Nextcloud side

For upload performance improvements in environments with high upload bandwidth, the server’s upload chunk size may be adjusted:

sudo -u www-data php occ config:app:set files max_chunk_size –value 20971520

Put in a value in bytes (in this example, 20MB). Set --value 0 for no chunking at all.

Default is 10485760 (10 MiB).

Note

Changing max_chunk_size will not have any performance impact on files uploaded through File Drop shares as unauthenticated file uploads are not chunked.

https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/big_file_upload_configuration.html#adjust-chunk-size-on-nextcloud-side

nextcloud下载超过1G的文件会中断的解决方法

如果你分享的文件超过1G大小,别人通过分享链接下载就会在下载到1G的时候中断。。。

经过仔细研究发现 在/usr/local/nginx/conf/nginx.conf 的http{}下添加

fastcgi_max_temp_file_size 10240m;

并设置允许下载文件的最大大小,就可以下载超过1G的文件。

估计是客户端下载带宽限制导致服务器php进程fread受限,在php最大执行时间内并没有读完完整文件;fastcgi_max_temp_file_size 则会不受客户端带宽限制,php进程能最大限度的将文件fread完并flush予服务器nginx;

记宝塔安装的nextcloud24重新生成缩略图

删除data/appdata_…..下面的preview文件夹

进入根目录cd /www/wwwroot/nextcloud/
sudo -u www /www/server/php/80/bin/php occ files:scan-app-data

重新生成前设置下config.php

‘preview_max_x’ => 1920, //默认4096

‘preview_max_y’ => 1920, //默认4096
相比默认的数值生成的缩略图会小很多

sudo -u www /www/server/php/80/bin/php occ preview:generate-all -vvv

Nextcloud 20-24 缩略图功能优化

本篇用于记录在 Nextcloud 20.0.4 使用过程中对略缩图功能的理解,本想放在上一篇里面作为优化的一部分,但发现描述起来文字很多,就新开一篇吧。

  Nextcloud 的略缩图生成方式和别的程序不同,在默认情况下,Nextcloud 会给一张图片生成多张不同分辨率的略缩图,在用户使用不同设备浏览的时候,根据用户设备屏幕的大小发送匹配屏幕大小的略缩图,详见管理员手册-Previews configuration

  这样带来的好处是用户平时能以更快的速度看到略缩图,因为 Nextcloud 只用把对应用户设备屏幕分辨率的略缩图文件直接传送给用户就可以。但是缺点也很明显,第一次浏览图片时生成略缩图的过程会占用大量的系统资源,如果服务器性能比较低,就会导致 Nextcloud 出现严重卡顿。

  此外,如果图片文件足够大,那么在默认配置下生成的略缩图甚至可能会达到 4K 分辨率,也就是说不仅占用大量资源生成略缩图,而且会出现略缩图占用的空间大小是源图片文件本身的两倍甚至更多的情况出现(因为一张图会对应很多张不同分辨率的略缩图)。

  针对这种情况,个人尝试过三种解决方法

解决方法1:关闭略缩图功能

  在关闭略缩图功能后,网页端浏览图片时就不会显示图片略缩图了,想知道图片内容只能点击图片加载原图,或者结合文件名在脑内回忆,体验很不好。关闭略缩图的方法为在 Nextcloud 目录下的 /config/config.php 中添加以下配置保存即可:

'enable_previews' => false,

解决方法2:设置允许生成的最大略缩图分辨率

  这个方法限制了最大略缩图分辨率,从而提高了略缩图的生成速度,配置方法为在 Nextcloud 目录下的 /config/config.php 中添加以下配置保存即可

'preview_max_x' => 1920,
'preview_max_y' => 1920,

  将 null 改成对应的分辨率数值,第一排是宽,第二排是高(数值根据自己需求设置)。在开启略缩图功能的情况下,在网页双击点开图片全屏查看的也是略缩图(而非原图,不清楚是因为 Nextcloud 在预览时只传送略缩图,还是因为屏幕不够大不足以让 Nextcloud 达到传送原图的条件),所以此处数值不宜设置的太小,设置的太小会导致想在网页端双击想查看大图时出现的却是一个很小略缩图。

解决方法3:使用 Preview Generator 插件

  Preview Generator 插件允许管理员在没有查看图片的情况下提前生成图片的预览图片文件,还可以设置任务每隔一定的时间自动扫描并生成新增图片的略缩图,使用方法如下:

  先在 Nextcloud 的应用中心找到 Preview Generator 插件安装启用,然后打开 ssh,切换到 Nextcloud 目录下,首先设置好 Preview Generator 插件生成的略缩图大小(此设置非必要,按照个人需求决定即可,此命令意思为使 Preview Generator 生成的略缩图宽度为:64、128 和 1024,可自行调整,具体说明见 Preview Generator的GitHub

sudo -u www php occ config:app:set --value="64 128 1024" previewgenerator widthSizes

  然后执行以下命令生成全部略缩图

sudo -u www php occ preview:generate-all -vvv

  如果图片文件较多,建议使用 screen 进行此操作(screen 可以在断开 ssh 的情况下继续执行命令)。如果只需要生成特定用户的略缩图文件,那就在命令后面加上用户名

sudo -u www php occ preview:generate-all -vvv [uesrid]

  生成了现有图片的略缩图后,添加 cron 任务,定时扫描新增的文件并生成略缩图(路径修改为自己的 Nextcloud 所在路径)

crontab -e -u www
*/10 * * * * sudo -u www php /nextcloud/occ preview:generate-all

  以上三种方式各有优势,根据自己的需求设置即可。


最后说下怎么删除略缩图

  所有用户的略缩图文件都位于:网站目录 /data/appdata_*/preview/

  先删除 preview 文件夹,然后到命令行终端切换到 Nextcloud 目录下执行

sudo -u www php occ files:scan-app-data

  此命令用于扫描指定目录的文件,如果不执行这条命令而只是删除 preview 文件夹,那 Nextcloud 就会认为删掉的那些略缩图仍然存在。关于 OCC 命令的详细解释可以查看管理员手册-Using the occ command

shopxo缓存session到redis过期时间只有24分钟

修改config/session.php文件

if(MyFileConfig(‘common_session_is_use_cache’, ”, 0, true) == 1)
{
// redis配置
// 请确保缓存配置文件cache.php中的stores中已经添加了redis缓存配置
$config = [
‘type’ => ‘cache’,
‘store’ => ‘redis’,
‘prefix’ => MyFileConfig(‘common_cache_session_redis_prefix’, ”, ‘shopxo’, true),
‘expire’ => 43200, //添加这个配置修改session为12小时
];
} else {
// 默认配置
$config = [
// session name
‘name’ => ‘PHPSESSID’,
// SESSION_ID的提交变量,解决flash上传跨域
‘var_session_id’ => ”,
// 驱动方式 支持file cache
‘type’ => ‘file’,
// 存储连接标识 当type使用cache的时候有效
‘store’ => null,
// 过期时间
‘expire’ => 43200,
// 前缀
‘prefix’ => ‘shopxo’,

];

}
return $config;
?>

shopxo商城安装教程

西部数码一键预装 >>
宝塔面板一键部署 >>
小皮面板一键部署 >>
URLOS一键部署 >>

1. 服务器系统 不限
2. php版本5.6+         v2.2.0及以上需7.2.5+,v2.2.4及以上需7.4.0
3. mysql版本5.0+     5.6以下版本使用utf8编码,建议使用5.6/5.7+

进入官网 >>
系统下载 >>

attachments-2018-12-F29gqSvt5c26d45a4736b.png

下载最新版,一般最上面是最新版,会有标记,如下紫色位置
建议选择gitee、github、composer下载,网盘不能保证是最新的。

attachments-2019-04-Dcht7D1p5ca38d62a74c8.png

解压后上传到服务器或虚拟主机根目录下

3.1 如果需要部署到主域名下,需要将shopxo下的源码部署到服务器或虚拟主机根目录下
3.2 子目录,则直接上传shopxo目录即可
3.4 系统支持

attachments-2018-12-Zvh8NLYm5c26d6623a5b7.png

解压后的源码目录,右边为源码

attachments-2018-12-S3ZoOrvU5c26d6aa3d08d.png

1. 需要将源码下 runtime 目录设置权限为 755 

2.  系统v2.1.0及以下 php版本需v5.6及以上版本,

3. 系统v2.2.0起 php版本需v7.2.5及以上

如域名为:http://www.xxx.com

3.5 主域名安装 http://www.xxx.com 直接访问即可跳转到安装页面
3.5 子目录安装 http://www.xxx.com/shopxo 直接访问即可跳转到安装页面
3.6 子目录支持访问public进行安装  http://www.xxx.com/shopxo/public 直接访问即可跳转到安装页面

安装建议

建议将域名指向源码public目录下更安全,如果部分虚拟主机不支持的、直接部署源码根目录也可以

点击 同意 即可

attachments-2018-12-JdD0UJHp5c26d8abac4ea.png

环境没问题后点击哦下一步即可

attachments-2018-12-J5EdFhq35c26d92ee0df0.png
attachments-2018-12-j2aoMgJ55c26d9457a7fc.png

 注意数据库版本,最低5.0 、最高5.7 、建议5.6或5.7
5.6以下使用utf8编码,5.6及以上版本建议使用utf8mb4编码

attachments-2019-04-vjVpGaOD5ca3918746b50.png

等待一会儿即可

后台管理默认账号 admin
后台管理默认密码 shopxo
管理后台地址: http://www.xxx.com/index.php?s=/admin/index/index    v1.5版本起:http://www.xxx.com/admin.php
前端地址:http://www.xxx.com

1.5后台管理地址将优化为默认 admin.php 入口,管理后台地址: http://www.xxx.com/admin.php,可自行修改入口地址文件名称,位于系统根目录和public目录下的admin.php文件