destiny

destiny 查看完整档案

北京编辑重庆大学  |  信息技术与应用 编辑北京一一得一科技有限公司  |  后端负责人 编辑 aabvip.com 编辑
编辑

将来的你一定会感谢现在努力的自己!

个人动态

destiny 赞了回答 · 8月27日

h5 audio标签怎么播放流

1、如果这个请求地址不需要额外的身份认证参数,直接把接口地址作为 audio 的 src 即可。
2、如果需要通过其他方式传递一些特殊参数才能工作,那就再 xhr 的请求中把 responseType 设置成 blob ,然后使用 blob 创建本地的 blob URL 进行播放。

关注 5 回答 4

destiny 发布了文章 · 3月29日

「扩展包」Laravel-filesystem-oss 2.0 发布

「扩展包」Laravel-filesystem-oss 2.0 发布

GitHub:https://github.com/iiDestiny/...

最近有时间把之前写的 oss 扩展包给升级了一下,改动如下

  • 新增获取官方 SDK 完整处理能力插件
  • 优化获取直传配置方法,新增自定义回调参数
  • 新增直传回调验签插件,让验签变得简单
  • 修复直传回调功能,让 oss 服务器可正常进入应用服务器回调
  • 新增 bucket 切换能力
  • 新增针对「私有 bucket」访问资源能力
  • 优化 readme 文档,让读者更加容易理解

扩展包要求

  • PHP >= 7.0

安装命令

$ composer require "iidestiny/laravel-filesystem-oss" -vvv

配置

  1. 将服务提供者 Iidestiny\LaravelFilesystemOss\OssStorageServiceProvider::class 注册到 config/app.php 文件:
'providers' => [
    // Other service providers...
    Iidestiny\LaravelFilesystemOss\OssStorageServiceProvider::class,
],
Laravel 5.5+ 会自动注册服务提供者可忽略
  1. config/filesystems.php 配置文件中添加你的新驱动
<?php

return [
   'disks' => [
        //...
        'oss' => [
            'driver' => 'oss',
            'root' => '', // 设置上传时根前缀
            'access_key' => env('OSS_ACCESS_KEY'),
            'secret_key' => env('OSS_SECRET_KEY'),
            'endpoint'   => env('OSS_ENDPOINT'), // 使用 ssl 这里设置如: https://oss-cn-beijing.aliyuncs.com
            'bucket'     => env('OSS_BUCKET'),
            'isCName'    => env('OSS_IS_CNAME', false), // 如果 isCname 为 false,endpoint 应配置 oss 提供的域名如:`oss-cn-beijing.aliyuncs.com`,否则为自定义域名,,cname 或 cdn 请自行到阿里 oss 后台配置并绑定 bucket
            // 如果有更多的 bucket 需要切换,就添加所有bucket,默认的 bucket 填写到上面,不要加到 buckets 中
            'buckets'=>[
                'test'=>[
                    'access_key' => env('OSS_ACCESS_KEY'),
                    'secret_key' => env('OSS_SECRET_KEY'),
                    'bucket'     => env('OSS_TEST_BUCKET'),
                    'endpoint'   => env('OSS_TEST_ENDPOINT'),
                    'isCName'    => env('OSS_TEST_IS_CNAME', false),
                ],
                //...
            ],
        ],
        //...
    ]
];

基本使用

<?php

$disk = Storage::disk('oss');

// 上传
$disk->put('avatars/filename.jpg', $fileContents);

// 检查文件是否存在
$exists = $disk->has('file.jpg');

// 获取文件修改时间
$time = $disk->lastModified('file1.jpg');
$time = $disk->getTimestamp('file1.jpg');

// 拷贝文件
$disk->copy('old/file1.jpg', 'new/file1.jpg');

// 移动文件也可改名
$disk->move('old/file1.jpg', 'new/file1.jpg');

// 获取文件内容
$contents = $disk->read('folder/my_file.txt');

以上方法可在 laravel-filesystem-doc 查阅

进阶使用

// 获取文件访问地址「公共读的 bucket 才生效」
$url = $disk->getUrl('folder/my_file.txt');

// 设置文件访问有效期「$timeout 为多少秒过期」「私有 bucket 才可看见效果」
$url = $disk->signUrl('cat.png', $timeout, ['x-oss-process' => 'image/circle,r_100']);

// 和 signurl 功能一样,区别在于 $expiration 是未来过期时间如:2019-05-05 17:50:32 时链接失效
$url = $disk->getTemporaryUrl('file.md', $expiration);

// 可切换其他 bucket「需要在 config 配置文件中配置 buckets」
$exists = $disk->bucket('test')->has('file.jpg');

获取官方完整 OSS 处理能力

阿里官方 SDK 可能处理了更多的事情,如果你想获取完整的功能可通过此插件获取,
然后你将拥有完整的 oss 处理能力

// 获取完整处理能力
$kernel = $disk->kernel();

// 例如:防盗链功能
$refererConfig = new RefererConfig();
// 设置允许空Referer。
$refererConfig->setAllowEmptyReferer(true);
// 添加Referer白名单。Referer参数支持通配符星号(*)和问号(?)。
$refererConfig->addReferer("www.aliiyun.com");
$refererConfig->addReferer("www.aliiyuncs.com");

$kernel->putBucketReferer($bucket, $refererConfig);
更多功能请查看官方 SDK 手册

前端 web 直传配置

oss 直传有三种方式,当前扩展包使用的是最完整的 服务端签名直传并设置上传回调 方式,扩展包只生成前端页面上传所需的签名参数,前端上传实现可参考 官方文档中的实例 或自行搜索

/**
 * 1. 前缀如:'images/'
 * 2. 回调服务器 url
 * 3. 回调自定义参数,oss 回传应用服务器时会带上
 * 4. 当前直传配置链接有效期
 */
$config = $disk->signatureConfig($prefix = '/', $callBackUrl = '', $customData = [], $expire = 30);

直传回调验签

当设置了直传回调后,可以通过验签插件,验证并获取 oss 传回的数据 文档

注意事项:

  • 如果没有 Authorization 头信息导致验签失败需要先在 apache 或者 nginx 中设置 rewrite
  • 以 apache 为例,修改 httpd.conf 在 DirectoryIndex index.php 这行下面增加「RewriteEngine On」「RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization},last]」
// 验签,就是如此简单
// $verify 验签结果,$data 回调数据
list($verify, $data) = $disk->verify();
// [$verify, $data] = $disk->verify(); // php 7.1 +

if (!$verify) {
    // 验证失败处理,此时 $data 为验签失败提示信息
}

// 注意一定要返回 json 格式的字符串,因为 oss 服务器只接收 json 格式,否则给前端报 CallbackFailed
return response()->json($data);

直传回调验签后返回给前端的数据「包括自定义参数」,例如

{
    "filename": "user/15854050909488182.png",
    "size": "56039",
    "mimeType": "image/png",
    "height": "473",
    "width": "470",
    "custom_name": "zhangsan",
    "custom_age": "24"
}
这其实要看你回调通知方法具体怎么返回,如果直接按照文档给的方法返回是这个样子

前端直传组件分享「vue + element」

<template>
  <div>
    <el-upload
      class="avatar-uploader"
      :action="uploadUrl"
      :on-success="handleSucess"
      :on-change="handleChange"
      :before-upload="handleBeforeUpload"
      :show-file-list="false"
      :data="data"
      :on-error="handleError"
      :file-list="files"
    >
      <img v-if="dialogImageUrl" :data-original="dialogImageUrl" class="avatar">
      <i v-else class="el-icon-plus avatar-uploader-icon" />
    </el-upload>
  </div>
</template>

<script>
import { getOssPolicy } from '@/api/oss' // 这里就是获取直传配置接口

export default {
  name: 'Upload',
  props: {
    url: {
      type: String,
      default: null
    }
  },
  data() {
    return {
      uploadUrl: '', // 上传提交地址
      data: {}, // 上传提交额外数据
      dialogImageUrl: '', // 预览图片
      files: [] // 上传的文件
    }
  },
  computed: {},
  created() {
    this.dialogImageUrl = this.url
  },
  methods: {
    handleChange(file, fileList) {
      console.log(file, fileList)
    },
    // 上传之前处理动作
    async handleBeforeUpload(file) {
      const fileName = this.makeRandomName(file.name)
      try {
        const response = await getOssPolicy()

        this.uploadUrl = response.host

        // 组装自定义参数「如果要自定义回传参数这段代码不能省略」
        if (Object.keys(response['callback-var']).length) {
          for (const [key, value] of Object.entries(response['callback-var'])) {
            this.data[key] = value
          }
        }

        this.data.policy = response.policy
        this.data.OSSAccessKeyId = response.accessid
        this.data.signature = response.signature
        this.data.host = response.host
        this.data.callback = response.callback
        this.data.key = response.dir + fileName
      } catch (error) {
        this.$message.error('获取上传配置失败')
        console.log(error)
      }
    },
    // 文件上传成功处理
    handleSucess(response, file, fileList) {
      const fileUrl = this.uploadUrl + this.data.key
      this.dialogImageUrl = fileUrl
      this.$emit('update:url', fileUrl)
      this.files.push({
        name: this.data.key,
        url: fileUrl
      })
    },
    // 上传失败处理
    handleError() {
      this.$message.error('上传失败')
    },
    // 随机名称
    makeRandomName(name) {
      const randomStr = Math.random().toString().substr(2, 4)
      const suffix = name.substr(name.lastIndexOf('.'))
      return Date.now() + randomStr + suffix
    }
  }

}
</script>

<style>
.avatar-uploader .el-upload {
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
  }
  .avatar-uploader .el-upload:hover {
    border-color: #409EFF;
  }
  .avatar-uploader-icon {
    font-size: 28px;
    color: #8c939d;
    width: 150px;
    height: 150px;
    line-height: 150px;
    text-align: center;
  }
  .avatar {
    width: 150px;
    height: 150px;
    display: block;
  }
</style>
扩展包肯定还有不足之处,最后欢迎各位 PR 以带来更好的功能。😝
查看原文

赞 0 收藏 0 评论 0

destiny 发布了文章 · 3月29日

「开发环境」让 Valet 支持多个本地项目使用不同 PHP 版本

「开发环境」让 Valet 支持多个本地项目使用不同 PHP 版本

Mac 开发环境

在开发过程中,开发环境有非常多如:HomesteadLaradockValetXampp 等,它们在不同场景中都各有优势,这里我说一下我最喜爱的 Valet 它是 Mac 极简主义者的 Laravel 开发环境,它创建一个站点可以说飞快,不用去做其他繁琐的配置一条命令就搞定

Valet 缺陷

之前版本的 Valet 中没有自带命令去切换 PHP 版本,只能我们自己在终端中手动切换 PHP 然后再重启 Valet。
在最新版 Valet 中支持 use 命令去切换 PHP 版本

valet use php@7.2

虽然简化了我们手动切换 PHP 版本的操作,但是 use 命令切换的是所有本地项目的 PHP 版本,意思就是其他所有本地项目也会使用 php@7.2 这个版本,我们本地项目中如果有两个项目需要使用不同 PHP 版本的话,就得来回 use 切换,这样相当耗时对于急性子的同学相当不友好。

技巧

目前官方还没有支持多站点不同 PHP 版本的操作,可能在将来版本中会支持,不过我们可以自己修改。

修改 PHP 监听的 valet.sock 名称

安装完 Valet 之后会自动修改 PHP 配置让其监听 valet.sock,如我本机 php@7.2 配置文件在 /usr/local/etc/php/7.2/php-fpm.d/www.conf,我们将 /Users/luoyan/.config/valet/valet.sock 修改成 /Users/luoyan/.config/valet/valet72.sock

「开发环境」让 Valet 支持多个本地项目使用不同 PHP 版本

然后在重启 Brew 管理的 PHP 服务

sudo brew services restart php@7.2 # 注意需要使用 sudo

这个时候你会发现在 ~/.config/valet 目录中有一个 valet72.sock

「开发环境」让 Valet 支持多个本地项目使用不同 PHP 版本

修改 Nginx 配置

因为 Valet 配置中默认让 nginx 只监听 valet.sock 进程,所以我们需要修改 nginx 配置文件,Valet 有一个命令让本地站点配置成 https 协议,并且生成站点 nginx 配置

# 到本地项目中根目录
valet secure nxdai

在上面命令中注意修改你本地站点域名,这条命令会在 ~/.config/valet/Nginx 目录下生成当前项目站点的 Nginx Https 的配置

「开发环境」让 Valet 支持多个本地项目使用不同 PHP 版本

然后在配置文件中搜索 valet.sock 改成之前在 PHP-FPM www.conf 配置文件中监听的 valet72.sock

「开发环境」让 Valet 支持多个本地项目使用不同 PHP 版本

注意这个配置文件中有两处 fastcgi_pass 要修改

最后重启一下 Mac 本机 Nginx

sudo brew services restart nginx # 主要要用 sudo

到此整个修改流程完毕,其他站点 PHP 版本配置类似

效果图

「开发环境」让 Valet 支持多个本地项目使用不同 PHP 版本

有任何疑问可以到文章下方讨论

查看原文

赞 0 收藏 0 评论 0

destiny 发布了文章 · 3月29日

「实用」微信扫码 - 关注公众号后网站自动登录

「实用」微信扫码关注公众号号后自动登录

序言

常见方式

平常大家见到过最多的扫码登录应该是 开放平台网页登录 大概形式就是:点击微信登录后会出现一个黑页面,页面中有一个二维码,扫码后可以自动获取用户信息然后登录,但是这种方式需要申请开放平台比较麻烦。如图

「实用」微信扫码关注公众号号后自动登录

利于推广方式

另外一种扫码登录方式只需要一个微信服务号就行,大概流程是:点击微信登录,网站自己弹出一个二维码、扫描二维码后弹出公众号的关注界面、只要一关注公众号网站自动登录、第二次扫描登录的时候网站直接登录,大家可以体验一下 「随便找的一个网站」,这种扫码登录的方式个人觉得非常利于推广公众号

前期准备

梳理

其实第二种扫码登录的原理很简单,核心就是依靠 微信带参二维码EasyWeChat 二维码文档

简单的解释一下扫描这个带参二维码有什么不同:

  • 扫描二维码,如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值(自定义值)关注事件推送给开发者。
  • 扫描二维码,如果用户已经关注公众号,在用户扫描后会自动进入会话,微信也会将带场景值(自定义值)扫码事件推送给开发者。

看到这里相信你已经明白了,梳理一下:

  • 生成二维码的时候你自定义一个参数到二维码中,顺便把这个参数传到前端页面中。
  • 前端页面根据这个参数轮询用户登录状态(也可使用 socket)。
  • 用户扫码关注后会推送一个关注事件到服务端,也会把自定义参数带入到事件中。
  • 根据 openid 创建用户后,然后在 Redis 中存储 Key 为场景值(自定义参数) Value 为用户创建后的 id。
  • 前端轮询方法中如果在 Redis 中获取到 Id 后,Auth 登陆,页面再重载一下,流程完毕。

实战

请求登录二维码

前端通过一个点击事件请求微信登录二维码

// 方便清除轮询
let timer = null

$(document).on('click', '.wechat-login', function () {
    // 请求登录二维码
    axios.get('{{ route('wx.pic') }}').then(response => {
      let result = response.data
      if (result.status_code !== 200) {
        return
      }
      // 显示二维码图片
      $('.wechat-url').attr('src', result.data.url)
      // 轮询登录状态
      timer = setInterval(() => {
        // 请求参数是二维码中的场景值
        axios.get('{{ route('home.login.check') }}', {params: {wechat_flag: result.data.weChatFlag}}).then(response => {
          let result = response.data
          if (result.data) {
            window.location.href = '/'
          }
        })
      }, 2000)
    })
  })
  
  // 返回时清除轮询
  $('.wechat-back').click(function () {
  clearInterval(timer)
 })

后端生成带参二维码逻辑,EasyWeChat 配置请自行查阅 文档


    protected $app;

    /**
     * Construct
     *
     * WeChatController constructor.
     */
    public function __construct()
    {
        $this->app = app('wechat.official_account');
    }

  /**
     * 获取二维码图片
     *
     * @param Request $request
     *
     * @return \Illuminate\Http\JsonResponse
     * @throws \Exception
     */
    public function getWxPic(Request $request)
    {
        // 查询 cookie,如果没有就重新生成一次
        if (!$weChatFlag = $request->cookie(WxUser::WECHAT_FLAG)) {
            $weChatFlag = Uuid::uuid4()->getHex();
        }
       
       // 缓存微信带参二维码
        if (!$url = Cache::get(WxUser::QR_URL . $weChatFlag)) {
            // 有效期 1 天的二维码
            $qrCode = $this->app->qrcode;
            $result = $qrCode->temporary($weChatFlag, 3600 * 24);
            $url    = $qrCode->url($result['ticket']);

            Cache::put(WxUser::QR_URL . $weChatFlag, $url, now()->addDay());
        }

        // 自定义参数返回给前端,前端轮询
        return $this->ajaxSuccess(compact('url', 'weChatFlag'))
            ->cookie(WxUser::WECHAT_FLAG, $weChatFlag, 24 * 60);
    }

用户扫描二维码后处理

   /**
     * 微信消息接入(这里拆分函数处理)
     *
     * @return \Symfony\Component\HttpFoundation\Response
     * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
     * @throws \ReflectionException
     */
    public function serve()
    {
        $app = $this->app;

        $app->server->push(function ($message) {
            if ($message) {
                $method = camel_case('handle_' . $message['MsgType']);

                if (method_exists($this, $method)) {
                    $this->openid = $message['FromUserName'];

                    return call_user_func_array([$this, $method], [$message]);
                }

                Log::info('无此处理方法:' . $method);
            }
        });

        return $app->server->serve();
    }
    
    /**
     * 事件引导处理方法(事件有许多,拆分处理)
     *
     * @param $event
     *
     * @return mixed
     */
    protected function handleEvent($event)
    {
        Log::info('事件参数:', [$event]);

        $method = camel_case('event_' . $event['Event']);
        Log::info('处理方法:' . $method);

        if (method_exists($this, $method)) {
            return call_user_func_array([$this, $method], [$event]);
        }

        Log::info('无此事件处理方法:' . $method);
    }
    
    /**
     * 取消订阅
     *
     * @param $event
     */
    protected function eventUnsubscribe($event)
    {
        $wxUser                 = WxUser::whereOpenid($this->openid)->first();
        $wxUser->subscribe      = 0;
        $wxUser->subscribe_time = null;
        $wxUser->save();
    }


    /**
     * 扫描带参二维码事件
     *
     * @param $event
     */
    public function eventSCAN($event)
    {
        if ($wxUser = WxUser::whereOpenid($this->openid)->first()) {
            // 标记前端可登陆
            $this->markTheLogin($event, $wxUser->uid);

            return;
        }
    }

    /**
     * 订阅
     *
     * @param $event
     *
     * @throws \Throwable
     */
    protected function eventSubscribe($event)
    {
        $openId = $this->openid;

        if ($wxUser = WxUser::whereOpenid($openId)->first()) {
            // 标记前端可登陆
            $this->markTheLogin($event, $wxUser->uid);

            return;
        }

        // 微信用户信息
        $wxUser = $this->app->user->get($openId);
        // 注册
        $nickname = $this->filterEmoji($wxUser['nickname']);

        $result = DB::transaction(function () use ($openId, $event, $nickname, $wxUser) {
            $uid  = Uuid::uuid4()->getHex();
            $time = time();

            // 用户
            $user = User::create([
                'uid'        => $uid,
                'created_at' => $time,
            ]);
            // 用户信息
            $user->user_info()->create([
                'email'      => $user->email,
                'nickname'   => $nickname,
                'sex'        => $wxUser['sex'],
                'address'    => $wxUser['country'] . ' ' . $wxUser['province'] . ' ' . $wxUser['city'],
                'avatar'     => $wxUser['headimgurl'],
                'code'       => app(UserRegisterController::class)->inviteCode(10),
                'created_at' => $time,
            ]);
            // 用户账户
            $user->user_account()->create([
                'gold'       => 200,
                'created_at' => $time,
            ]);

            $wxUserModel = $user->wx_user()->create([
                'subscribe'      => $wxUser['subscribe'],
                'subscribe_time' => $wxUser['subscribe_time'],
                'openid'         => $wxUser['openid'],
                'created_at'     => $time,
            ]);

            Log::info('用户注册成功 openid:' . $openId);

            $this->markTheLogin($event, $wxUserModel->uid);
        });

        Log::debug('SQL 错误: ', [$result]);
    }

    /**
     * 标记可登录
     *
     * @param $event
     * @param $uid
     */
    public function markTheLogin($event, $uid)
    {
        if (empty($event['EventKey'])) {
            return;
        }

        $eventKey = $event['EventKey'];

        // 关注事件的场景值会带一个前缀需要去掉
        if ($event['Event'] == 'subscribe') {
            $eventKey = str_after($event['EventKey'], 'qrscene_');
        }

        Log::info('EventKey:' . $eventKey, [$event['EventKey']]);

        // 标记前端可登陆
        Cache::put(WxUser::LOGIN_WECHAT . $eventKey, $uid, now()->addMinute(30));
    }

前端登录检查

    /**
     * 微信用户登录检查
     *
     * @param Request $request
     *
     * @return bool|\Illuminate\Http\JsonResponse
     */
    public function loginCheck(Request $request)
    {
        // 判断请求是否有微信登录标识
        if (!$flag = $request->wechat_flag) {
            return $this->ajaxSuccess(false);
        }

        // 根据微信标识在缓存中获取需要登录用户的 UID
        $uid  = Cache::get(WxUser::LOGIN_WECHAT . $flag);
        $user = User::whereUid($uid)->first();

        if (empty($user)) {
            return $this->ajaxSuccess(false);
        }

        // 登录用户、并清空缓存
        auth('web')->login($user);
        Cache::forget(WxUser::LOGIN_WECHAT . $flag);
        Cache::forget(WxUser::QR_URL . $flag);

        return $this->ajaxSuccess(true);
    }
OK,很实用的一个功能吧,赶快加到你项目中吧!

PS 欣赏一下

「实用」微信扫码关注公众号号后自动登录

查看原文

赞 0 收藏 0 评论 0

destiny 发布了文章 · 3月29日

PHP 依赖注入扩展包 - 让你在任何时候随心所欲使用依赖注入「秀配图 ^_^」

file

Laravel 的依赖注入用得很舒服,然后在网上搜了相关资料写了一个不限于框架的轻量级依赖注入扩展包,使用场景就是你自己定义的方法,调用的时候也可以使用轻松的享受依赖注入「有没有觉得配图非常棒?」

Github 地址

要求

  • PHP >= 7.0

安装

composer require iidestiny/dependency-injection -vvv

使用

使用辅助方法

    // 注册你的自定义类
    di_register(Tools::class)
    
    // 调用你类中的方法
    di_register(Tools::class)->generate($param, $param, $param)
    
    // 类所有方法都可以调用
    di_register(Tools::class)->foo($bar)

实践

例如有时候我们自定义的 Service 服务层可能也需要依赖注入其他工具类,但是我们控制器中已经依赖注入了 Service,调用 Service 中方法的时候就不能轻易的注入其他工具类,使用这个扩展包可以轻易解决这个问题,看下面例子。

<?php

namespace App\Services;


use App\Tools;
use App\User;
use Cache;
use Models\Order;

class OrderService
{
    /**
     * 下单
     *
     * @param User  $user
     * @param       $goods
     * @param       $address
     * @param Cache $cache
     * @param Tools $tools
     */
    public function placeOrder(User $user, $goods, $address, Cache $cache, Tools $tools)
    {
        // 下单逻辑,其中需要依赖注入 Cache 与 Tools
    }

    /**
     * pay
     *
     * @param Order $order
     * @param Cache $cache
     * @param Tools $tools
     */
    public function pay(Order $order, Cache $cache, Tools $tools)
    {
         // 支付逻辑,其中需要依赖注入 Cache 与 Tools
    }

}

我们可以轻易调用并享受 DI

    /**
     * store
     */
    public function store()
    {
        di_register(OrderService::class)->placeOrder($user, $goods, $address);
    }

或者

    /**
     * store
     */
    public function store()
    {
        $orderService = di_register(OrderService::class);
        
        $orderService->placeOrder($user, $goods, $address);
        $orderService->pay($order);
    }

PS

配图是叫一个 Affinity Designer 的软件里面的样板

查看原文

赞 0 收藏 0 评论 0

destiny 发布了文章 · 3月29日

Charles 手机抓包记录

file

Charles 很早之前用过,后来一直不用都忘记怎么使用了。近期公司叫我去测试一个小程序的安全性,第一时间想到了用 Charles 抓包分析,这里记录一下过程方便后续查阅

安装

这里有一个 Charles 4.2 的版本 下载后将 .jar 文件拷贝到 Contents/Java 里面。

简介

Charles 是在 Mac 下常用的网络封包截取工具,在做移动开发时,我们为了调试与服务器端的网络通讯协议,常常需要截取网络封包来分析。Charles 通过将自己设置成系统的网络访问代理服务器,使得所有的网络访问请求都通过它来完成,从而实现了网络封包的截取和分析。
除了在做移动开发中调试端口外,Charles 也可以用于分析第三方应用的通讯协议。配合 Charles 的 SSL 功能,Charles 还可以分析 Https 协议。

Charles 界面介绍

file

不过我个人更喜欢这种模式查看,这样会按照域名分类归档,更方便我看网络请求

file

代理设置

打开 Charles 后将它设置成代理服务器,这样手机上访问 app 的时候就可以监听网络请求。

file

想要解决手机上面的网络抓包还需要一些设置,在 Charles 菜单栏上选择 Proxy -> Proxy Settings,填写代理端口 8888,并且勾选启动代理。

file

手机设置

这里我以 iPhone 为例,首先我们需要获取电脑的 IP 地址,可以在 Charles 帮助栏找到

file

file

我们记住这个地址,然后打开手机找到链接的 WiFi 「注意这里电脑和手机必须链接同一个 WiFi」操作如下:

file

file

配置代理选择手动,然后下面输入 Charles 帮助栏显示的 IP,端口为 8888

file

点击储存的时候,电脑上面会弹出一个 Charles 的消息框,点击 Allow 运行。

SSL 配置

手机访问 HTTPS 网站的时候会显示 unknown ,这是因为 https 是加密的,我们的配置一下证书。

file

电脑端 SSL

先设置电脑上面的证书操作如下:

file

下面这一部如果证书是信任的可以忽略

file

Charles 需要设置要监控的网址,这里我们设置监控所有

file

file

电脑设置完毕

手机端 SSL

手机证书设置如下:

file

点击后如下图所示,用 iPhone Safari 访问这个地址「注意要用 Safari 浏览器」,然后安装证书

file

证书安装完后还需要信任这个证书,在手机「通用 -> 关于本机->证书信任设置」中开启信任

file

Ok 到此为止所有设置完毕

现在你可以打开手机 App,然后 Charles 会抓下所有请求的包,效果图:

file

PS

仔细按照上面的操作进行哟,不然你可能会遇到抓到 https 的网址数据乱码,或者 unknown 的问题。

哈哈。结果我去测试的那个小程序没有 Token 认证,意思是所有接口都对外暴露。。。。然后所有请求的参数都是 base64 编码的「浅加密」,我只要解码改几个参数,再编码请求就得到他们数据了。。。

查看原文

赞 0 收藏 0 评论 0

destiny 发布了文章 · 3月29日

CI 持续集成 - 阿里云云效

file

前两个月给公司搭建了一套持续集成,用的是阿里云的「云效」,「云效」有免费一个月的试用版,有兴趣的朋友可以玩一玩,开阔一下知识范围。开始之前你需要简单了解一下 云效文档哦

概念

持续集成指的是,频繁地(一天多次)将代码集成到主干。它的好处主要有两个。

  1. 快速发现错误。每完成一点更新,就集成到主干,可以快速发现错误,定位错误也比较容易。
  2. 防止分支大幅偏离主干。如果不是经常集成,主干又在不断更新,会导致以后集成的难度变大,甚至难以集成。

持续集成大致流程

提交

流程的第一步,是开发者向代码仓库提交代码。所有后面的步骤都始于本地代码的一次提交(commit)。

构建

所谓构建,指的是将源码转换为可以运行的实际代码,比如安装依赖,配置各种资源(样式表、JS脚本、图片)等等。

测试

全面测试,单元测试和集成测试都要跑,有条件的话,也要做端对端测试。所有测试以自动化为主,少数无法自动化的测试用例,就要人工跑。

部署

通过了第二轮测试,当前代码就是一个可以直接部署的版本(artifact)。将这个版本的所有文件打包( tar filename.tar * )存档,发到生产服务器。

生产服务器将打包文件,解包成本地的一个目录,再将运行路径的符号链接(symlink)指向这个目录,然后重新启动应用。这方面的部署工具有 Ansible,Chef,Puppet 等。

回滚

一旦当前版本发生问题,就要回滚到上一个版本的构建结果。最简单的做法就是修改一下符号链接,指向上一个版本的目录。

更详细的说明:http://www.ruanyifeng.com/blog/2015/09/continuous-integration.html

云效持续集成操作流程

在云效后台中,首先为项目注册应用,并且关联代码库:

file

新建项目之后,选择当前创建的项目:

file

流水线配置

点击左侧流水线栏目,注册一条项目流水线

file

将流水线关联到当前项目的代码库之中,并且选择代码推送后触发流水线的分支

file

点击流水线第一个阶段,一般为名称设为「构建」,选项解释如下:

  1. 分支名称:这里的分支名称是在阿里云虚拟主机中构建时拉取的分支,请根据当前部署环境选择对应的分支
  2. 构建配置:有时我们需要构建产生不同内容的包,用于不同的运行环境(比如集成测试环境和生产环境)云效流水线上的构建任务根据指定 Git 库源代码根目录下的 <应用名称>.release 文件,进行构建打包工作,以便随后流水线上的部署任务进行部署 release 所有配置选项

file

在下面的高级配置之中有个「包」的概念,其意思就是构建的时候根据你配置的包生成多个环境,如配图中会生成「Testing 测试环境」「Staging 预发布环境」

file

包标签中的参数会在阿里云虚拟主机构建的时候生成系统环境变量,然后 Shell 获取
PS:构建成功之后,阿里云虚拟主机会把项目压缩打包成 tgz 格式,然后上传到一个资源空间之中,以便部署阶段下载压缩包

下一个阶段为部署阶段,当然你可以在流水线中新增其他阶段如「测试阶段」,在部署阶段选择对应的包标签配置、应用、环境即可

file

至此流水线配置结束 流水线帮助文档

部署环境配置

点击部署环境

file

在部署配置之中我们需要做如下事情,「部署流程将按照下面排序进行」

  1. 下载路径:设置下载路径,其意是将资源空间之中的压缩包下载到你企业关联的服务器上
  2. Stop:此项设置一个 Shell 或其他操作脚本,一般需要把老项目清理掉
  3. 解压目录:将下载后的项目压缩包解压到指定的目录
  4. Start:此项设置一个 Shell 或其他操作脚本,一般情况是在阿里云服务器上面初始化项目,如「数据迁移」
  5. 执行用户:在阿里云服务器的最大权限用户一般为:root

私密配置项,有时候涉及一些私密信息,不适合放在代码库中如数据库配置,以包标签前缀区分环境配置 私密配置概念

file

file

至此云效后台配置完成

代码库中构建配置脚本

构建的时候阿里云虚拟机,会以 <应用名>.release 文件执行构建方式「这个文件会放到项目根目录」

  1. code.language 是阿里云虚拟机需要的环境配置,配图中为 php 7.0
  2. build.command 为构建时自定义的 Shell 命令,配图中执行了一个 Shell 脚本

file

Build Shell 脚本,在此脚本中将仓库代码构建成一个完整可运行的项目

  1. 在此次可获取「包标签」中自定义的环境变量
  2. 获取包标签环境表里为「PACKAGE_LABEL」
  3. 私密配置项的内容会在构建时生成一个文件其名称固定为「rdc_security_config.properties」放在项目根目录下,可读取文件中的配置来填入 .env 中
  4. 注意 Shell 脚本一定要添加可执行权限
#!/bin/bash

echo "开始构建 ${PACKAGE_LABEL} 环境"

# Git 构建
git pull origin ${ENV_BRANCH}
git submodule init
git submodule update
echo "结束构建 ${ENV_BRANCH}"

# 安装依赖
echo "开始安装依赖"
composer config -g repo.packagist composer https://packagist.phpcomposer.com
composer install
echo "结束安装依赖"

# 设置配置
echo "开始配置项目"

cp .env.example.${PACKAGE_LABEL} .env
S_DB_PASSWORD=`./find.sh ${PACKAGE_LABEL}_DB_PASSWORD`
S_DB_USERNAME=`./find.sh ${PACKAGE_LABEL}_DB_USERNAME`
S_DB_DATABASE=`./find.sh ${PACKAGE_LABEL}_DB_DATABASE`
S_DB_HOST=`./find.sh ${PACKAGE_LABEL}_DB_HOST`
echo "数据库配置读取成功......"
sed -i -e "s/DB_HOST=127.0.0.1/DB_HOST=${S_DB_HOST}/g" .env
sed -i -e "s/DB_DATABASE=homestead/DB_DATABASE=${S_DB_DATABASE}/g" .env
sed -i -e "s/DB_USERNAME=homestead/DB_USERNAME=${S_DB_USERNAME}/g" .env
sed -i -e "s/DB_PASSWORD=secret/DB_PASSWORD=${S_DB_PASSWORD}/g" .env
echo "数据库配置 Done"

# 正式服务器 Redis 配置
if [ "${PACKAGE_LABEL}" == 'production' ];
then
    sed -i -e "s/REDIS_HOST=127.0.0.1/REDIS_HOST=${ENV_REDIS_HOST}/g" .env
    sed -i -e "s/REDIS_PASSWORD=null/REDIS_PASSWORD=${ENV_REDIS_PASSWORD}/g" .env
    echo "Redis 服务配置完成"
fi

# 百度地图配置
sed -i -e "s/Baidu_Map_API_KEY=/Baidu_Map_API_KEY=${ENV_BAIDU_MAP_API_KEY}/g" .env
echo "百度地图配置完成"

sed -i -e "s/WECHAT_PAYMENT_APPID=/WECHAT_PAYMENT_APPID=${ENV_WECHAT_PAYMENT_APPID}/g" .env
sed -i -e "s/WECHAT_PAYMENT_MCH_ID=/WECHAT_PAYMENT_MCH_ID=${ENV_WECHAT_PAYMENT_MCH_ID}/g" .env
sed -i -e "s/WECHAT_PAYMENT_KEY=/WECHAT_PAYMENT_KEY=${ENV_WECHAT_PAYMENT_KEY}/g" .env
echo "微信配置 Done"

sed -i -e "s/WECHAT_OPEN_PLATFORM_APPID=/WECHAT_OPEN_PLATFORM_APPID=${ENV_WECHAT_OPEN_PLATFORM_APPID}/g" .env
sed -i -e "s/WECHAT_OPEN_PLATFORM_SECRET=/WECHAT_OPEN_PLATFORM_SECRET=${ENV_WECHAT_OPEN_PLATFORM_SECRET}/g" .env
sed -i -e "s/WECHAT_OPEN_PLATFORM_TOKEN=/WECHAT_OPEN_PLATFORM_TOKEN=${ENV_WECHAT_OPEN_PLATFORM_TOKEN}/g" .env
sed -i -e "s/WECHAT_OPEN_PLATFORM_AES_KEY=/WECHAT_OPEN_PLATFORM_AES_KEY=${ENV_WECHAT_OPEN_PLATFORM_AES_KEY}/g" .env
echo "微信开放平台配置 Done"

sed -i -e "s/OSS_ID=/OSS_ID=${ENV_OSS_ID}/g" .env
sed -i -e "s/OSS_KEY=/OSS_KEY=${ENV_OSS_KEY}/g" .env
sed -i -e "s/OSS_BUCKET=/OSS_BUCKET=${ENV_OSS_BUCKET}/g" .env
sed -i -e "s/OSS_ENDPOINT=/OSS_ENDPOINT=${ENV_OSS_ENDPOINT}/g" .env
sed -i -e "s/OSS_SSL=/OSS_SSL=${ENV_OSS_SSL}/g" .env
sed -i -e "s/OSS_IS_CNAME=/OSS_IS_CNAME=${ENV_OSS_IS_CNAME}/g" .env
echo "OSS 配置 Done"

sed -i -e "s/APP_ENV=local/APP_ENV=${PACKAGE_LABEL}/g" .env
php artisan key:generate
composer du
chmod -R 777 storage
chmod -R 777 bootstrap

echo "${PACKAGE_LABEL} 环境构建成功!"

这里有个小技巧,有可能不同环境的 .env 的配置项 KEY 不一样,如:测试服务器中用 QINIU 正式服务器中用 OSS,那么可以把 .env.example 修改为对应环境的 .env.example.testing ,之后就可以获取不同环境的 .env 文件

file

cp .env.example.${PACKAGE_LABEL} .env

服务器中部署配置脚本

就是我们在部署环境中指定的脚本

file

项目中这两个脚本分别做了如下操作

clear_old.sh

#!/bin/bash
rm -rf /var/www/CI/yydy-rms
echo 'old project clear !'

init.sh

#!/bin/bash
cd /var/www/CI/yydy-rms
php artisan migrate
composer du
echo 'project is ready!'

当然上面只是一个简单的删除老项目拷贝新项目,你们可以根据自己项目实际需求初始化项目。

PS

现在推送流水线设置的分支会自动触发「流水线工作」,如果流水线某个阶段工作失败可到后台查看失败原因方便排除错误

file

最后附上一张公司实际流水线图

file

云效地址
云效文档

Good Job Done !

查看原文

赞 0 收藏 0 评论 0

destiny 发布了文章 · 3月29日

Sentry 之部署到生产环境

file

Sentry 可以说是炒鸡棒的集中式异常处理服务,但是用 Sentry 官方服务要收费,为了节省开支就选择自己搭建一个 Sentry 服务,在搭建 Sentry 服务的时候遇到一些小问题,在这里分享一下。

搭建

Sentry 的介绍之前我发表过一篇文章 Sentry 自动化异常提醒,Ubuntu 安装 Sentry 在社区也有一篇非常棒的实战安装文章 搭建自己的 sentry 服务,安装成功后是这个样子的

file

安装完后记得设置 根网站地址,并且 根网站地址 必须和搭建的 Sentry 域名一致

file

这里注意:安装成功后一定要设置正确的 根网站地址 否则项目发生了异常 Sentry 服务器接收不到。

邮件提醒

搭建 Sentry 服务之后,我又到项目之中安装好了 Sentry 官方提供的组件,为了测试安装是否正确自己手动制造了一个异常,然后 Sentry 服务也接收到了异常

file

file

但是,并没有发送邮件给我,Sentry 作为自动化异常提醒,没有了邮件提醒,那么 Sentry 的价值就失去了一半,我们也不可能每时每刻都在 Sentry 后端查看仪表盘。所以得想办法解决以下,邮件发送是需要自己配置的,因为是 Docker 安装的 Sentry 所以不知道怎么配置,解决问题的最好思路是先到 GitHub Issue 里面去看看有没有人和我遇到同样的问题。

file

太好了有其他伙伴也遇到了同样的问题,我们来看大神们给出的解决方案

file

docker-compose.yml 里面可以新增 额外的环境变量,在最下方我们可以找到 Mall Server 配置组,然后把对应的 env 里面的 key 放到 docker-compose.yml 里面

file

注意:修改 onpremise 项目下的 config.yml 是无效的

配置成功后重启一下 docker

docker-compose down && docker-compose up -d

OK,SMTP 设置已经有值了,点击测试设置发送一封测试邮件,之后会如你所愿收到一封测试邮件

file

生产环境发生异常后也会邮件提醒你

file

至此完结,好好享受 Sentry 带给我们的便利吧

2019-03-11 更新

Sentry 9.0 以上会给一个配置环境变量的文件 .env 其中设置邮箱的话步骤是:首先注释 docker-compose.yml 文件中的 Email 环境变量

file

然后在 .env 配置文件中加上 Email 环境变量

file

注意:如果使用腾讯企业邮箱的话请使用 587 端口、SENTRY_EMAIL_USERSENTRY_SERVER_EMAIL 请保持一致
查看原文

赞 0 收藏 0 评论 0

destiny 发布了文章 · 3月29日

Live 七牛云直播

最近的直播行业特别火,各种直播,有时候我也会去 熊猫 TV 上面看看 PDD 的 LOL 直播,在看直播的同时又很好奇他这种直播到底如何实现的,那么今天咱门就一起来探究一下吧。

七牛云直播

七牛在视频直播大爆发时代,推出专为直播平台打造的全球化直播流服务和端到端直播场景解决方案,我选择七牛是因为七牛的文档真心让人看着舒服,通俗易懂。在学习七牛云之前你非常有必要先看看 七牛直播云原理 文档,因为这个文档可能比我解释得更加专业。

申请直播云

首先你得在七牛控制台之中开通 直播空间权限

file

一般申请后会在 3 个工作日内通过审核。

file

创建直播空间

在通过审核之后,你需要创建一个直播空间

file

在创建直播空间的时候,直播的域名得要备案过的域名才行,域名建议使用二级域名。

file

选择专门用来存放直播流的存储空间

file

这里需要你配置一下加速点播的域名,配置也非常简单,到菜单栏中 融合 CDN 新建一个加速域名,类型选择 点播平台

file

这里值得注意的是:新建的加速域名需要等一会才会显示 CNAME 的值,CNAME 值显示出来之后,你再去解析下你的域名

file

file

回到创建直播空间这里,选择我们创建好的加速域名

file

创建完成之后来到直播空间主页面,还需要配置一下 直播推流域名直播播放域名 ,这个很简单和前面配置 CDN 加速 一样,把给出的 域名CNAME 值到域名解析管理后台解析一下。正确配置之后如下图所示

file

创建完成直播空间之后,我们需要到 直播流管理 里面创建一个 直播流 ,直播流创建之后我们会得到 推流地址播放地址 就是所谓的直播地址和播放地址

file

推流 PC 端采用 OBS 安装完成之后填上推流地址,就可以进行推流直播了。

file

file

file

推流也可以用手机推流,所谓手机直播,这个请结合官网给出的 SDK 配置,传送门,到此前面的七牛配置与直播推流已经完毕

观看直播

前面的直播推流配置完成之后,我们需要让用户或者观众看到直播,Let`s go

正常情况下用户观看直播的流程是:登录直播网站 -> 点开直播房间 -> 服务器获取播放地址 -> 用户观看直播,当然流程背后可能做了更多事情,我们就简单的模拟一下登录直播网址然后观看直播。那么就意味着我们需要创建一个 Laravel 项目,然后集成 七牛云直播服务端 SDK
你有必要看一下 服务端 SDK 的使用说明和调用方法

项目和组件都准备就绪之后我们创建一个测试直播的控制器

php artisan make:controller LiveController

控制器内容

<?php

namespace App\Http\Controllers;


use Qiniu\Pili\Client;
use function Qiniu\Pili\HDLPlayURL;
use Qiniu\Pili\Mac;

class LiveController extends Controller
{
    protected $client;

    /**
     * LiveController constructor.
     */
    public function __construct()
    {
        $this->client = new Client(new Mac('xxx', 'xxx'));
    }

    public function index()
    {
        // 选择直播空间,正常项目中可用代码获取,这里测试就直接填写
        $this->client->hub('destiny');
        // 获取 HDL 直播地址,hub 指直播空间,streamkey 指流名称
        $liveurl = \Qiniu\Pili\HDLPlayURL('pili-live-hdl.live.aabvip.com', 'destiny', 'live-stream');

        return view('welcome', compact('liveurl'));
    }
}

这里只用了一个获取播放地址的方法,还有更多的方法你可以查看该 SDK 的 readme,我这里获取的是 HDL 直播地址,常见直播地址如下

  • RTMP:底层基于 TCP,在浏览器端依赖 Flash。
  • HTTP-FLV:基于 HTTP 流式 IO 传输 FLV,依赖浏览器支持播放 FLV。
  • WebSocket-FLV:基于 WebSocket 传输 FLV,依赖浏览器支持播放 FLV。WebSocket 建立在 HTTP 之上,建立WebSocket 连接前还要先建立 HTTP 连接。
  • HLS: Http Live Streaming,苹果提出基于 HTTP 的流媒体传输协议。HTML5 可以直接打开播放。
  • RTP:基于UDP,延迟1秒,浏览器不支持。

常见直播协议延迟与性能数据

传输协议播放器延迟内存CPU
RTMPFlash1s430 M11 %
HTTP-FLVVideo1s310 M4.4 %
HLSVideo20s205 M3 %

可以看出在浏览器里做直播,使用 HTTP-FLV 协议是不错的,性能优于 RTMP + Flash,延迟可以做到和 RTMP + Flash 一样甚至更好。下面推荐相应协议的播放插件

  • HLS 协议播放插件推荐 videojs-contrib-hls 这块插件非常多的网站在使用,UI 看着非常漂亮
  • HDL (HTTP-FLV) 协议播放插件推荐 BiliBili-flv.js 这是 B 站开源的 FLV 播放组件,非常不错

引入组件 CDN 之后代码如下

<!doctype html>
<html lang="{{ app()->getLocale() }}">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Laravel</title>
    <!-- 引入 CDN -->
    <script data-original="https://cdn.bootcss.com/flv.js/1.3.2/flv.min.js"></script>
</head>
<body>
<!-- Start -->
<video id="videoElement"></video>
</body>
<script>
    if (flvjs.isSupported()) {
        var videoElement = document.getElementById('videoElement');
        var flvPlayer = flvjs.createPlayer({
            type: 'flv',
            url: '{{ $liveurl }}'
        });
        flvPlayer.attachMediaElement(videoElement);
        flvPlayer.load();
        flvPlayer.play();
    }
</script>
<!-- End -->
</html>

播放效果如下

file

赶快照着教程动手做一下吧,那样你会理解得更深入。

查看原文

赞 0 收藏 0 评论 0

destiny 发布了文章 · 3月29日

Sentry 自动化异常提醒

简介

Sentry 是什么?中文翻译过来是 哨兵 的意思,从字面中可以知道 『站岗、放哨、巡逻、稽查的士兵』,不错,Sentry 是程序的 哨兵 ,它可以监控我们在生产环境中项目的运行状态,一旦某段代码运行报错,或者异常,会第一时间把报错的 路由异常文件请求方式 等一些非常详细的信息以消息或者邮件给我们,让我们第一时间知道:程序出错了,然后我们可以从 Sentry 给我们的详细的错误信息中瞬间找到我们需要处理的代码,在老板不知情的情况下悄悄把 Bug 修复调,你肯定不想等着老板来找你吧。

开始

你如果试用 Sentry 官方提供给你的服务是需要收费的,不过可以免费试用,当然你也可以自己搭建 Sentry文档 自行搭建当然就不收费啦。文档最下方有两种安装方式一个是 docker 一个是 pythonDocker 安装 HerePython 安装 Here

file

这是 Sentry 的官网,我们先不管来体验一把,注册一个账号

file

准备完毕之后先看文档,其实官方给出的文档非常详细

file

选择我们大爱的 PHP Laravel 框架,

拉取

$ composer require sentry/sentry-laravel

添加进服务提供者和门面

'providers' => array(
    // ...
    Sentry\SentryLaravel\SentryLaravelServiceProvider::class,
)

'aliases' => array(
    // ...
    'Sentry' => Sentry\SentryLaravel\SentryFacade::class,
)

然后在 App/Exceptions/Handler.phpreport 修改如下方法

public function report(Exception $exception)
{
    if ($this->shouldReport($exception)) {
        app('sentry')->captureException($exception);
    }
    parent::report($exception);
}

生成配置文件, 配置文件在 config/sentry.php

$ php artisan vendor:publish --provider="Sentry\SentryLaravel\SentryLaravelServiceProvider"

file

在配置文件中 dsn 就在我们看的文档下方,每个用户的 dsn 都是唯一的,在你的项目中配置了 dsn ,Sentry 就能监控你的项目。

file

好了基本配置就这么完事儿,然后现在我们访问当前项目,然后在路由上面乱输入一通。

file

我们会及时收到错误反馈

file

当然也会往我们的邮箱里面发送
file

并且非常详细!!

Sentry 可以和澳大利亚公司旗下的产品 HipChat 互相通信 https://www.atlassian.com/software

file

HipChat 也会受到 Sentry 的错误消息 ^_^

End !

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 1 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

注册于 2016-12-14
个人主页被 271 人浏览