分类目录归档:后端开发技术&PHP

Redisearch使用笔记

通过docker安装:
docker run -p 6380:6379 redislabs/redisearch:latest

127.0.0.1:6379> FT.CREATE myIdx ON HASH PREFIX 1 doc: SCHEMA title TEXT WEIGHT 5.0 body TEXT url TEXT

127.0.0.1:6379>hset doc:1 title “hello world” body “lorem ipsum” url “http://redis.io”
127.0.0.1:6379>hset doc:2 title “hello coupon codes” body “lorem ipsum” url “http://redis.io”
127.0.0.1:6379>hset doc:3 title “hello promotions” body “lorem ipsum” url “http://redis.io”

默认情况下他会处理单复数的问题

命令文档:https://oss.redislabs.com/redisearch/Commands/

搜索词的语法:https://oss.redislabs.com/redisearch/Query_Syntax/

默认情况下多个以空格分隔的词是and判断,如果要是or的关系用|生成

PHP替换Emoji表情

在检测文本语言的时候,emoji表情会影响检测的准确性,从stackoverflow上找了一个函数,作一下记录:

function removeEmoji($text) {
    return trim(preg_replace('/([0-9|#][\x{20E3}])|[\x{00ae}|\x{00a9}|\x{203C}|\x{2047}|\x{2048}|\x{2049}|\x{3030}|\x{303D}|\x{2139}|\x{2122}|\x{3297}|\x{3299}][\x{FE00}-\x{FEFF}]?|[\x{2190}-\x{21FF}][\x{FE00}-\x{FEFF}]?|[\x{2300}-\x{23FF}][\x{FE00}-\x{FEFF}]?|[\x{2460}-\x{24FF}][\x{FE00}-\x{FEFF}]?|[\x{25A0}-\x{25FF}][\x{FE00}-\x{FEFF}]?|[\x{2600}-\x{27BF}][\x{FE00}-\x{FEFF}]?|[\x{2600}-\x{27BF}][\x{1F000}-\x{1FEFF}]?|[\x{2900}-\x{297F}][\x{FE00}-\x{FEFF}]?|[\x{2B00}-\x{2BF0}][\x{FE00}-\x{FEFF}]?|[\x{1F000}-\x{1F9FF}][\x{FE00}-\x{FEFF}]?|[\x{1F000}-\x{1F9FF}][\x{1F000}-\x{1FEFF}]?/u', '', $text));
}

 

PHP里用SoapClient出现”Uncaught SoapFault exception: [WSDL] SOAP-ERROR: Parsing WSDL”处理

在一台很老的服务器上,调用bing的接口时,出现如下错误,以前都是好好的:

PHP Fatal error:  Uncaught SoapFault exception: [WSDL] SOAP-ERROR: Parsing WSDL: Couldn't load from 'https://adinsight.api.bingads.microsoft.com/Api/Advertiser/AdInsight/v13/AdInsightService.svc?singleWsdl' : failed to load external entity "https://adinsight.api.bingads.microsoft.com/Api/Advertiser/AdInsight/v13/AdInsightService.svc?singleWsdl"

后面发现Curl也不行,报:Peer certificate cannot be authenticated with known CA certificates 错误。

wget也不行,也报:ERROR: cannot verify certificate。

现在看来是由于系统的根证书无效了(系统太老应该是很多年前的机器了),所以无法判断microsoft的证书的有效性,按如下操作就可以了:

cd /etc/pki/tls/certs
wget https://curl.haxx.se/ca/cacert.pem
cat cacert.pem >> ca-bundle.crt

思路是去下载一个新的可信证书串(上面是curl的,也可以找一个其它的),把这个串放到系统的可信证书后面去。

如果是在php程序里用curl的话,不用设置操作系统的,可以试试把 https://curl.haxx.se/ca/cacert.pem 下载下来后,再设置php.ini的curl设置

[curl]
curl.cainfo=/path/to/downloaded/cacert.pem

 

通过smtp向google gmail发送邮件报错问题处理

在Laravel里用smtp方式通过gmail发送邮件出现下面的错误:

Failed to authenticate on SMTP server with username "[email protected]" using 3 possible authenticators. Authenticator LOGIN returned Expected response code 235 but got code "535", with message "535-5.7.8 Username and Password not accepted. Learn more at
535 5.7.8 https://support.google.com/mail/?p=BadCredentials a25sm9780898pfo.27 - gsmtp
". Authenticator PLAIN returned Expected response code 235 but got code "535", with message "535-5.7.8 Username and Password not accepted. Learn more at
535 5.7.8 https://support.google.com/mail/?p=BadCredentials a25sm9780898pfo.27 - gsmtp
". Authenticator XOAUTH2 returned Expected response code 250 but got code "535", with message "535-5.7.8 Username and Password not accepted. Learn more at
535 5.7.8 https://support.google.com/mail/?p=BadCredentials a25sm9780898pfo.27 - gsmtp
".

需要按下面的步骤去解决:

1、开启2步验证方式,通过这里:https://www.google.com/landing/2step/

2、为GMAIL设置一个单独的密码,通过这里:https://security.google.com/settings/security/apppasswords

3、把第二步里的密码设置到Laravel的.env文件里。

对于.env邮件的设置也要注意加密方式,下面的我的配置:

MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
[email protected]
MAIL_PASSWORD=独立密码
MAIL_ENCRYPTION=tls
[email protected]
MAIL_FROM_NAME="${APP_NAME}"

 

Laravel操作Redis笔记

Laravel操作Redis好麻烦的,也不封闭完。。。

操作Sortd Set

// 判断是否存在
if (Redis::command("exists", [self::REDIS_KEY_MERCHANT_TITLE])) {
    return;
}
// 增加
Redis::zadd(self::REDIS_KEY_MERCHANT_TITLE, 1, "sdfsdf");
// 加分
Redis::command('zIncrBy', [self::REDIS_KEY_MERCHANT_TITLE, 1, "sdfsdf"]);
// 弹出最小的
Redis::command('zPopMin', [self::REDIS_KEY_PROMOTION_DESC, 1]);
// 弹出最大的
Redis::command('zPopMax', [self::REDIS_KEY_PROMOTION_DESC, 1]);

 

Laravel全局分布式锁的使用

获取锁的代码:vendor/laravel/framework/src/Illuminate/Cache/RedisLock.php

/**
 * Attempt to acquire the lock.
 *
 * @return bool
 */
public function acquire()
{
    if ($this->seconds > 0) {
        return $this->redis->set($this->name, $this->owner, 'EX', $this->seconds, 'NX') == true;
    } else {
        return $this->redis->setnx($this->name, $this->owner) === 1;
    }
}

Redis的set有些特殊:vendor/laravel/framework/src/Illuminate/Redis/Connections/PhpRedisConnection.php

/**
* Set the string value in argument as value of the key.
*
* @param string $key
* @param mixed $value
* @param string|null $expireResolution
* @param int|null $expireTTL
* @param string|null $flag
* @return bool
*/
public function set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
{
    return $this->command('set', [
    $key,
    $value,
    $expireResolution ? [$flag, $expireResolution => $expireTTL] : null, ]);
}

如果在获取锁的时候传了时间,调用redis的set的时候,调用的其实是:
$redis->set($key, $value, [‘NX’, ‘EX’ => $expireTTL])
也就是说不存在的时候才设置该key,过期时间为秒。

Redis的Set操作的参数参考:

  • EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
  • PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
  • NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
  • XX :只在键已经存在时,才对键进行设置操作。

锁的常规使用:

// 设置foo锁的超时时间为5,超过这个时间自动释放
$lock = Cache::lock("foo", 5);
if (!$lock->get()) {
    return false;
}

// 业务处理
$lock->release();

return true;

在指定的时间内获取到锁,如果获取不到会抛出异常:

// 设置foo锁的超时时间为5,超过这个时间自动释放
$lock = Cache::lock("foo", 5);
// 需要在2秒内获取到锁,否则会抛出异常
$lock->block(2);
// 业务处理
$lock->release();

 

Laravel实践笔记

Laravel程序的分层

1、Service层:业务的入口,如UserService

2、Repository层:通常和Service层对应,防止Service文件过大,对一些需要跨Model处理的业务,需要在Repository层来实现。

3、Model层:基础层,一些和数据表相关的简单的功能可以放在这里供调用。

对业务操作的归一化处理

每一个业务项(如用户、文章等,不是每一个表),都需要有一个createXXX、updateXXX、deleteXXX三个方法,他们需要写在Repository类中。

对所有业务层的对象的操作,最终都需要通过这3个方法来实现,这样的目的是做到数据修改的统一处理,方便在这里做日志、缓存等处理。

之所以不通过Laravel检测Model的事件来实现,是因为该种方式是针对单表的,不是针对业务的,会显示太麻烦和不合适。

比如增加一个用户createUser,里面涉及了操作多个表(如用户扩展表),这是一个原子操作,不用对扩展表进行事件的监听处理。

Laravel数据库笔记

在Model里调用self::where等操作时,其实是先到了Model类的 __callStatic 方法,再到了 __call 方法,根据情况,再到了Builder文件,也就是:
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php

大多数的操作都可以去这里面去找。

别的一些常用操作记录

$flight = App\Models\Flight::create(['name' => 'Flight 10']);
$flight->fill(['name' => 'Flight 22']);


Model::find(1, [字段])
ProductModel::where('id', '1')->value('id')
ProductModel::where(['id' => '1'])->value('id')

ProductModel::where(['id' => '1'])->get(['id','name'])->toArray()
ProductModel::where(['id' => '1'])->first(['id','name'])->toarray()

# find in set
whereRaw("FIND_IN_SET(?, accept_type)", [$param['accept_type']])

// 直接取条一条记录
ProductModel::firstWhere("id", 1)
或
ProductModel::firstWhere("id", "=", 1)


// Insert并获取主键
ProductModel::create(['name'=>'sdfsdfds'])->id

ProductSkuPropertyDataModel::updateOrCreate(
    ['sku_id' => $skuId, 'property_id' => $property['id']],
    [
        'product_id' => $productId,
        'sku_id' => $skuId,
        'property_id' => $property['id'],
        'property_value' => $pValue
    ]
);

ProductStyleImageModel::whereIn('origin_hash', [2,3,4])

// insert
$user = User::create([
    'first_name' => 'Taylor',
    'last_name' => 'Otwell',
    'title' => 'Developer',
]);

// var_dump(ProductModel::where(['id' => '1'])->get(['id','name'])->toarray());
// var_dump(ProductModel::create(['name'=>'sdfsdfds'])->id);

// 插入或更新,其实是先select判断再执行
$spiderStatModel = SpiderStatModel::subSite($site['id']);
$spiderStatModel->updateOrCreate($conditions, $data);
// 或者直接:
Model::updateOrCreate($conditions, $array)

//批量更新
App\Models\Flight::where('active', 1)
          ->where('destination', 'San Diego')
          ->update(['delayed' => 1]);

//单条更新
$flight = App\Models\Flight::find(1);
$flight->name = 'New Flight Name';
$flight->save();

$styles = DB::table("t_product_style_0 as product_style")
                    ->where([['product_style.product_id', '=', $product->id]])
                    ->limit(1)
                    ->get();


$model = self::from('huddle as huddle')
        ->leftJoin("store as store", "huddle.store_id", "=", "store.id")
        ->join("store_data as store_data", "huddle.store_id", "=", "store_data.store_id")
        ->join("store_site as store_site", "huddle.store_id", "=", "store_site.store_id")
        ->where([
            'huddle.topic_id' => $actId,
            'huddle.status' => self::STATUS,
            'store_data.status' => self::STATUS,
            'store_site.status' => self::STATUS,
            'store_site.site_id' => $siteId,
            'store_data.lang_id' => $langId,
        ])
        ->where('huddle.status', '>', time())
        ->offset($page * $pageSize)
        ->limit($pageSize);

    $ret = $model->get([
            'huddle.id',
            'huddle.store_id',
            'store_data.name',
            'huddle.rebate',
            'huddle.places',
            'huddle.start_time',
            'huddle.end_time',
            'store.is_upto'
        ])
        ->toArray();


// firstOrNew 需要手动调用 save,才会保存到数据库。适合同时需要修改其他属性的场景。
// firstOrCreate 会自动保存到数据库。适合不需要额外修改其他属性的场景。

use Illuminate\Database\Query\Builder as QueryBuilder;

 

Laravel测试用例编写

测试全部
php artisan test –stop-on-failure

只测试Feature
php artisan test –group=feature –stop-on-failure

只测试某一个文件
php74 vendor/bin/phpunit tests/Feature/ServiceProductTest.php

<?php

namespace Tests\Feature;

use App\Common\ServiceRequestParam;
use App\Services\ProductService;
use App\Models\TagLangModel;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;

class SearchTest extends TestCase
{
    // 警告!!!!!!
    // 不要用这个啊,用了数据会被清空
    // use RefreshDatabase; 

    // 使用下面这个,完成后会回滚数据
    // use DatabaseTransactions;


    public function testSearch()
    {
        // TagLangModel::create(['tag_id'=>1, 'name'=>'sdfssfsdf', 'type'=>'NORMAL', 'lang'=>'en-US']);
        $tagService = resolve('App\Services\SearchService');
        $req = ServiceRequestParam::make([
            'word' => 'Farfetch',
            'limit' => 5
        ]);
        $ret = $tagService->searchProduct($req);
        $this->assertTrue($ret->isSuccess());
    }


}

 

注意事项

  • 不要用use RefreshDatabase,用了数据会被清空。
  • 可以用use DatabaseTransactions,用了数据会回滚。
  • Feature相当于集成测试,一些关联比较多的功能需要写在这里面,不然调用不了

Laravel连接数据库失败的诡异问题

晚上被运营叫起来了,发现一些本该上架的信息没有上架,观察发现是计划任务出现了问题,报数据连接超时:SQLSTATE[HY000] [2002] Connection time out。

找了一半天没有发现问题,config(“database”)看配置也是对的。

最后在框架的Connection类里打出pdo调试信息,发现数据库地址居然是测试环境的,全局搜索测试环境地址:

find ./ -name "*.php" | xargs grep  "1.1.1.1"

发现在一个Console/Command的Test测试文件的全局部分出现了,如下图:

由于Laravel在命令行运行的时候,会全局的加载所有的Command文件,分析出命令字等信息,所以造成了这段代码被执行,所以才引起了这个错误。