在mac中通过shell弹窗或发通知

Mac中有个AppleScript,通过它可以实现弹窗或系统通知

弹窗

osascript -e 'display alert "告警!!" as critical'
  • osascript -e 是执行一段apple script
  • display alert是弹窗
  • as critcal是用来控制图标,可以as三个值(informational,warning,critical,默认是informational)

发通知

osascript -e 'display notification "通知内容" with title "标题" subtitle "子标题" sound name "Glass"'
  • display notification 是发通知的命令后面紧跟着通知内容
  • with title 可以修改通知标题(默认是“脚本编辑器”)
  • subtitle 可以添加一个子标题(默认没有)
  • sound name 可以用来选择一种通知的声音,声音存放在/System/Library/Sounds

结合PHP实战

<?php
ini_set('date.timezone','PRC');

$host = 'mysql.example.org';
$port = '3306';
$dbname = 'test';
$username = 'root';
$password = '123456';
$pdo = new PDO("mysql:host={$host};port={$port};dbname={$dbname};charset=UTF8", $username, $password);

$sql = <<<SQL
SELECT u.uid, p.privilege_id, u.updated_at 
FROM user_privilege AS p 
LEFT JOIN user AS u ON u.uid = p.uid
WHERE u.del = 1 and p.del = 0;
SQL;

$statement = $pdo->query($sql);
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
$new = [];

$logDir = __DIR__ . '/log/';
if (!file_exists($logDir)) {
    mkdir($logDir, 0755, true);
}
foreach ($result as $item) {
    $fileName = $logDir . '/' . $item['uid'] . '.json';
    if (!file_exists($fileName)) {
        file_put_contents($fileName, json_encode($item, JSON_PRETTY_PRINT));
        $new[] = $item['uid'];
    }
}
if ($new) {
    $msg = sprintf('%s发现新异常数据%d条!(%s)', date('Y-m-d H:i:s'), count($new), implode(', ', $new));
    $cmd = <<<CMD
osascript -e 'tell application "System Events" to display alert "{$msg}" as critical'
CMD;
    exec($cmd);
}

这个脚本连接一个远程的数据库,检查是否出现用户被删除,但是权限没有被删除的情况,如果发现将它记录到日志并弹窗提示。这里增加了tell application "System Events" to这么个前缀,是指以“System Events”这个应用弹这个窗,这是因为好像有什么机制会阻止cron任务中直接弹窗,需要借用另一个应用的身份。也可以借用”Finder”,发弹窗时Finder会跳动一下,但是弹窗需要点Finder才能出来,而使用System Event可以直接弹出来。

接下来可以在执行crontab -e来增加个计划任务

MAILTO=""
*/1 * * * * php ~/check.php > /dev/null
  • MAILTO=”” 是执行crontab不发执行结果的系统邮件
  • */1是指每1分钟执行一次,剩下的*分别代表:每小时(0~23)/每天(1~31)/每月(1~12)/每周几(0~6)

命名格式转换

在做一些生成代码或生成配置时经常会遇到命名格式不一样的问题,比如有的地方要用驼峰命名(比如代码的类和遍历),有的地方要用下划线命名(比如数据库字段)。因此写了个小工具来快速将一组单词转换成特定格式的命名。

因为我的使用场景中没有太严苛的性能要求,所以里面用到了正则,并且应该也不是最优解,所以不建议在高并发的场景用(也没做过性能测试)。

在查规范的时候学习到了一些有意思的单词,很形象的形容了命名格式。
比如下划线命名是snake,纯大写的下划线命名(比如常量定义AAA_BBB_CCC)是screaming snake(尖叫蛇)。
大驼峰是pascal(这个其实不太理解为什么是帕斯卡)。
用横线命名(比如url、域名、docker服务名:aaa-bbb-ccc)是kebab(烤肉串)。

<?php
/**
 * Created by PhpStorm.
 * User: fyn
 * Date: 2018/10/9
 * Time: 10:40 AM
 */
namespace Fyn\Common;

class StringCase
{


    /**
     * snake_case
     * @param $string
     * @return string
     */
    public static function snakeCase($string) {
        $string = lcfirst($string);
        $string = preg_replace_callback('/[A-Z]+/', function ($text) {
            return '_'.strtolower($text[0]);
        },$string);
        $string = preg_replace('/[^a-zA-Z0-9]+/','_',$string);
        return $string;
    }

    /**
     * SCREAMING_SNAKE_CASE
     * @param $string
     * @return string
     */
    public static function screamingSnakeCase($string) {
        return strtoupper(self::snakeCase($string));
    }

    /**
     * kebab-case
     * @param $string
     * @return string
     */
    public static function kebabCase($string) {
        return str_replace('_','-',self::snakeCase($string));
    }

    /**
     * PascalCase
     * @param $string
     * @return string
     */
    public static function pascalCase($string) {
        $arr = preg_split('/[^a-zA-Z0-9]/', $string);
        $string = implode('',array_map('ucfirst',$arr));
        return $string;
    }

    /**
     * camelCase
     * @param $string
     * @return string
     */
    public static function camelCase($string) {
        return lcfirst(self::pascalCase($string));
    }

    /**
     * htmlcase
     * @param $string
     * @return string
     */
    public static function htmlCase($string) {
        return strtolower(preg_replace('/[^a-zA-Z0-9]+/','', $string));
    }
}

使用方法:

use Fyn\Common\StringCase;

echo StringCase::snakeCase("This_is A-test@text"),"\n";  //this_is_a_test_text

echo StringCase::kebabCase("This_is A-test@text"),"\n";  //this-is-a-test-text

echo StringCase::pascalCase("This_is A-test@text"),"\n"; //ThisIsATestText

echo StringCase::camelCase("This_is A-test@text"),"\n";  //thisIsATestText

php禁用xdebug的彩色html var_dump

这个功能有时会麻烦,直接看请求结果很乱,而且会忽略掉一些大的字段,可以通过这哥参数禁用

ini_set('xdebug.overload_var_dump', 0);
ini_set('html_errors', 0);

用PHP做gRPC的服务端

gRPC的官方文档中虽然没有给出PHP做Server的例子,但实际上grpc的扩展是支持的。

参考扩展中提供的几个类以及扩展内部的一些代码,写了一个简单的示例

这个示例只是证明PHP可以直接写gRPC服务,不代表可以在生产环境中这么用。主要原因有这么几点:

  1. 不借助pcntl或swoole的话是个单进程的服务,并发会阻塞
  2. 没做性能测试以及是否有内存泄漏的测试
  3. 而且直接PHP做Server的争议也比较大

完整示例代码(https://github.com/ssfyn/php-grpc-server-example

Server端的主要逻辑:

$this->server = new \Grpc\Server([]);
$this->server->addHttp2Port('0.0.0.0:50051');
$this->server->start();

while($request = $this->server->requestCall()){
    $method = $request->method;
    $call = $request->call;
    if ($method=='/dev.fyn.HelloWorld/SayHello') {
        //接收请求
        $recv = $call->startBatch([
            \Grpc\OP_RECV_MESSAGE => true
        ]);
        //将请求的数据转换为对象
        $request = new SayHelloRequest();
        $request->mergeFromString($recv->message);
        //调用业务代码
        $impl = new HelloWorldImpl();
        $response = $impl->sayHello($request);
        //处理返回值,即使没有metadata也要设置OP_SEND_INITIAL_METADATA,不然会一直阻塞在这里
        $call->startBatch([
            \Grpc\OP_SEND_INITIAL_METADATA => [],
            \Grpc\OP_SEND_MESSAGE => [
                'message'=>$response->serializeToString()
            ],
            \Grpc\OP_SEND_STATUS_FROM_SERVER => [
                'code'=>\Grpc\STATUS_OK,
                'details'=>'OK'
            ],
        ]);
    } else {
        $call->startBatch([
            \Grpc\OP_SEND_INITIAL_METADATA => [],
            \Grpc\OP_SEND_STATUS_FROM_SERVER => ['code'=>\Grpc\STATUS_NOT_FOUND,'details'=>'Not found'],
        ]);
    }
}

Mac安装PHP+GRPC

安装xcode命令行工具

xcode-select --install

安装brew

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

通过brew安装php5.6(或其它版本)

brew install php@5.6

通过brew安装php切换器

brew install brew-php-switcher

切换php版本

brew-php-switcher 5.6

通过pecl安装PHP的protobuf扩展(要编译,比较慢)

pecl install protobuf

通过pecl安装PHP的grpc扩展(要编译,比较慢)

pecl install grpc

PHP通过runkit覆盖一个方法

首先需要安装runkit扩展

这是原有类

namespace Fyn;

class Hello
{
    public function say(string $name): string {
        return sprintf('Hello %s', $name);
    }
}

通过runkit覆盖(必须先将类load进来才能覆盖)

if (class_exists(\Fyn\Hello:class)) {
    runkit_method_redefine(
        \Fyn\Hello:class,
        'say',
        '$msg',
        'return "Nihao {$msg}";'
    );
}

更多方法:http://php.net/manual/zh/book.runkit.php

不建议在生产环境中使用

Composer如何指定php的版本

这个问题主要出现在本地开发环境的php版本高于生产环境运行的php版本,或构建服务时构建环境的p hp版本高于运行环境的php版本。这时composer会引入一些版本过高的包。可以在composer.json中的config.platform中设置php版本。

{
    "require": {
    }
    "config": {
        "platform" :{
            "php": "5.6",
            "ext-grpc": "1.14",
            "ext-protobuf": "3.5"
        }
    }
}

同时也可以写一下用到的一些扩展,这样在install的时候就不回去检查是否真的装了这些扩展