魔王卷子

魔王卷子 查看完整档案

北京编辑烟台南山学院  |  软件技术 编辑北京早耕田国际广告有限公司  |  PHP工程师 编辑 www.baoguoxiao.com 编辑
编辑

一个默默无闻的PHP程序员,专注于后端!

个人动态

魔王卷子 回答了问题 · 2020-12-10

laravel 使用不同的邮箱发送邮件

首先定义配置:

// file: config/mail.php

'mailers' => [
    'demo' => [
        'transport' => 'smtp',
        'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
        'port' => env('MAIL_PORT', 587),
        'encryption' => env('MAIL_ENCRYPTION', 'tls'),
        'username' => env('MAIL_USERNAME'),
        'password' => env('MAIL_PASSWORD'),
        'timeout' => null,
        'auth_mode' => null,
    ],
],

这里的demo是我们设置的第二个邮件。那么调用的方法可以是:

IlluminateSupportFacadesMail::mailer("demo");

后面跟的就是你要操作的方法即可。

关注 3 回答 1

魔王卷子 关注了标签 · 2020-11-30

关注 969

魔王卷子 关注了专栏 · 2020-11-05

advanced-php

advanced php,托大了,托大了

关注 1447

魔王卷子 赞了文章 · 2020-10-08

Laravel 支持markdown编辑器解决方案

原文连接:https://www.wjcms.net/archive...

laravel-markdown-editor--markdown编辑器

说明

此扩展包兼容laravel5.8以上版本

准备工作

安装扩展包

composer require wjcms/laravel-markdown-editor

配置providers

//cconfig/app.php
'providers' => [
    //添加如下一行
    wjcms\laravelmd\LaravelmdServiceProvider::class,
]

拷贝相关文件到项目文件夹中

php artisan vendor:publish --provider="wjcms\laravelmd\LaravelmdServiceProvider"

使用

1.在blade模版引入

@include('layouts.md.md')

2.父模版中需要添加上

#注意在scripts上边需要引入jquery
@stack('styles')

@stack('scripts')

3.修改md.blade.php文件的 imageUploadURL修改为接口路径

4.创建service服务uploadservice.php,实现如下方法。

public function upload(UploadedFile $file)
    {
        $path = '/uploads/'.$file->store(date('y/m'), 'uploads');
        return $this->save($file, $path);
    }

//注意这里还需要创建Attachment模型和数据库(包含path,extension,name三个字段)
    protected function save(UploadedFile $file, $path)
    {
        return Attachment::create([
            'path'=>$path,
            'extension'=>$file->extension(),
            'name'=>$file->getClientOriginalName()
        ]);
    }

5.admin控制器创建方法

/**
     * 图片上传方法
     */
    public function uploadPic(Request $request, UploadService $uploadService)
    {
        $res = $uploadService->upload($request->file('editormd-image-file'));
        return response()->json([
            'success'=>1,
            'message'=>'图片上传成功',
            'url'=> $res->path
        ]);
    }

6.routes/web.php文件添加路由

use App\Http\Controllers\Admin;
//注意这里是laravel8的写法,之前版本自行修改
Route::prefix('admin')->name('admin.')->group(function () {
    Route::post('upload', [Admin\AdminController::class,'uploadPic'])->name('upload');
}

就可以发现markdown编辑器可以使用了。

查看原文

赞 1 收藏 0 评论 0

魔王卷子 回答了问题 · 2020-09-27

求救,php无限极分类不用递归怎么处理成层级结构?

/**
 * 把返回的数据集转换成Tree
 * @param array $list 要转换的数据集
 * @param string $pid parent标记字段
 * @param string $level level标记字段
 * @return array
 * @author 麦当苗儿 <zuojiazi@vip.qq.com>
 */
function list_to_tree($list, $pk='id', $pid = 'pid', $child = '_child', $root = 0) {
    // 创建Tree
    $tree = array();
    if(is_array($list)) {
        // 创建基于主键的数组引用
        $refer = array();
        foreach ($list as $key => $data) {
            $refer[$data[$pk]] =& $list[$key];
        }
        foreach ($list as $key => $data) {
            // 判断是否存在parent
            $parentId =  $data[$pid];
            if ($root == $parentId) {
                $tree[] =& $list[$key];
            }else{
                if (isset($refer[$parentId])) {
                    $parent =& $refer[$parentId];
                    $parent[$child][] =& $list[$key];
                }
            }
        }
    }
    return $tree;
}

来源:https://gitee.com/liu21st/one...

关注 3 回答 2

魔王卷子 收藏了文章 · 2020-07-21

出现大量TIME_WAIT连接的排查与解决

Last-Modified: 2019年7月10日21:58:43

项目生产环境出现大量TIME_WAIT(数千个), 需要一一排查

先上总结:

  • nginx 未开启 keep-alive 导致大量主动断开的tcp连接
  • nginx 与 fastcgi(php-fpm) 的连接默认是短连接, 此时必然出现 TIME_WAIT

状态确认

统计TIME_WAIT 连接的本地地址

netstat -an | grep TIME_WAIT | awk '{print $4}' | sort | uniq -c | sort -n -k1

#    ... 前面很少的略过
#    2 127.0.0.1:56420
#    442 192.168.1.213:8080
#    453 127.0.0.1:9000

分析:

  • 8080端口是nginx对外端口
  • 9000端口是php-fpm的端口

8080 对外web端口

经过确认, nginx 的配置文件中存在一行

# 不启用 keep-alive
keepalive_timeout 0;

尝试抓取 tcp 包

tcpdump tcp -i any -nn port 8080 | grep "我的ip"

# 其中某一次连接的输出如下
# 20:52:54.647907 IP 客户端.6470 > 服务端.8080: Flags [S], seq 2369523978, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
# 20:52:54.647912 IP 服务端.8080 > 客户端.6470: Flags [S.], seq 1109598671, ack 2369523979, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
# 20:52:54.670302 IP 客户端.6470 > 服务端.8080: Flags [.], ack 1, win 256, length 0
# 20:52:54.680784 IP 客户端.6470 > 服务端.8080: Flags [P.], seq 1:301, ack 1, win 256, length 300
# 20:52:54.680789 IP 服务端.8080 > 客户端.6470: Flags [.], ack 301, win 123, length 0
# 20:52:54.702935 IP 服务端.8080 > 客户端.6470: Flags [P.], seq 1:544, ack 301, win 123, length 543
# 20:52:54.702941 IP 服务端.8080 > 客户端.6470: Flags [F.], seq 544, ack 301, win 123, length 0
# 20:52:54.726494 IP 客户端.6470 > 服务端.8080: Flags [.], ack 545, win 254, length 0
# 20:52:54.726499 IP 客户端.6470 > 服务端.8080: Flags [F.], seq 301, ack 545, win 254, length 0
# 20:52:54.726501 IP 服务端.8080 > 客户端.6470: Flags [.], ack 302, win 123, length 0
上述具体的ip已经被我批量替换了, 不方便暴露服务器ip

分析:

  • 可以看到4次挥手的开始是由服务端主动发起的(记住TIME_WAIT只会出现在主动断开连接的一方)
  • 个人理解是, nginx 在配置"不启用keep-alive"时, 会在http请求结束时主动断开连接.
  • 尝试开启http的keep-alive

修改 nginx 配置

keepalive_timeout 65;

reload nginx

nginx -s reload

再次抓包

tcpdump tcp -i any -nn port 8080 | grep "我的ip"

# 21:09:10.044918 IP 客户端.8217 > 服务端.8080: Flags [S], seq 1499308169, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
# 21:09:10.044927 IP 服务端.8080 > 客户端.8217: Flags [S.], seq 2960381462, ack 1499308170, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
# 21:09:10.070694 IP 客户端.8217 > 服务端.8080: Flags [.], ack 1, win 256, length 0
# 21:09:10.077437 IP 客户端.8217 > 服务端.8080: Flags [P.], seq 1:302, ack 1, win 256, length 301
# 21:09:10.077443 IP 服务端.8080 > 客户端.8217: Flags [.], ack 302, win 123, length 0
# 21:09:10.198117 IP 服务端.8080 > 客户端.8217: Flags [P.], seq 1:671, ack 302, win 123, length 670
# 21:09:10.222957 IP 客户端.8217 > 服务端.8080: Flags [F.], seq 302, ack 671, win 254, length 0
# 21:09:10.222980 IP 服务端.8080 > 客户端.8217: Flags [F.], seq 671, ack 303, win 123, length 0
# 21:09:10.247678 IP 客户端.8217 > 服务端.8080: Flags [.], ack 672, win 254, length 0

注意看上面很有意思的地方:

  • tcp 的挥手只有3次, 而非正常的4次. 个人理解是, 服务端在收到 FIN 时, 已经确认自己不会再发送数据, 因此就将 FIN 与 ACK 一同合并发送
  • 此时是客户端主动断开tcp连接, 因此服务端不会出现 TIME_WAIT

再次查看连接状态

netstat -an | grep TIME_WAIT | awk '{print $4}' | sort | uniq -c | sort -n -k1
#      ...忽略上面
#      1 127.0.0.1:60602
#      1 127.0.0.1:60604
#    344 127.0.0.1:9000

此时发现已经没有处于 TIME_WAIT 的连接了.

9000 fast-cgi 端口

经过网上查找资料, 整理:

  • nginx 与 fast-cgi 的默认连接是短连接, 每次连接都需要经过一次完整的tcp连接与断开

当前 nginx 配置

upstream phpserver{
    server 127.0.0.1:9000 weight=1;
}

修改nginx配置使其与fastcgi的连接使用长连接

upstream phpserver{
    server 127.0.0.1:9000 weight=1;
    keepalive 100
}

fastcgi_keep_conn on;

说明:

  • upstream 中的 keepalive 指定nginx每个worker与fastcgi的最大长连接数, 当长连接不够用时, 此时新建立的连接会在请求结束后断开(由于此时指定了 HTTP1.1, fastcgi不会主动断开连接, 因此nginx这边会出现大量 TIME_WAIT, 需谨慎(未验证)
  • 由于php-fpm设置了最大进程数为100, 因此此处的 keepalive 数量指定 100 (未测试)

此处题外话, 如果 nginx 是作为反向代理, 则需增加如下配置:

# 将http版本由1.0修改为1.1
proxy_http_version 1.1;
# 清除"Connection"头部
proxy_set_header Connection "";    
  • 配置 proxy_pass 将请求转发给后端
  • 这里, 理解一下 proxy_passfastcgi_pass 区别

    客户端 --http-->  前端负载均衡Nginx --proxy_pass--> 业务服务器Nginx --fastcgi_pass--> 业务服务器 php-fpm

再次确认 tcp 连接情况

netstat -antp  | grep :9000 | awk '{print $(NF-1)}' | sort | uniq -c
#      6 ESTABLISHED
#      1 LISTEN

ok, 问题解决.

另一种解决方法:

若 nginx 与 fast-cgi 在同一台服务器上, 则使用 unix域 会更为高效, 同时避免了 TIME_WAIT 的问题.

题外

经过上面优化后, TIME_WAIT数量从上千个大幅下降到几十个, 此时发现TIME_WAIT中的存在大量的 127.0.0.1:6379, 6379是redis服务的默认端口....

赶紧改业务代码去, 将 $redis->connect(...) 改成 $redis->pconnect(...)

说明:

  • pconnect 表示 php-fpm 与 redis 建立 tcp 连接后, 在本次http请求结束后仍维持该连接, 下次新的请求进来时可以复用该连接, 从而复用了tcp连接.
查看原文

魔王卷子 赞了文章 · 2020-07-21

出现大量TIME_WAIT连接的排查与解决

Last-Modified: 2019年7月10日21:58:43

项目生产环境出现大量TIME_WAIT(数千个), 需要一一排查

先上总结:

  • nginx 未开启 keep-alive 导致大量主动断开的tcp连接
  • nginx 与 fastcgi(php-fpm) 的连接默认是短连接, 此时必然出现 TIME_WAIT

状态确认

统计TIME_WAIT 连接的本地地址

netstat -an | grep TIME_WAIT | awk '{print $4}' | sort | uniq -c | sort -n -k1

#    ... 前面很少的略过
#    2 127.0.0.1:56420
#    442 192.168.1.213:8080
#    453 127.0.0.1:9000

分析:

  • 8080端口是nginx对外端口
  • 9000端口是php-fpm的端口

8080 对外web端口

经过确认, nginx 的配置文件中存在一行

# 不启用 keep-alive
keepalive_timeout 0;

尝试抓取 tcp 包

tcpdump tcp -i any -nn port 8080 | grep "我的ip"

# 其中某一次连接的输出如下
# 20:52:54.647907 IP 客户端.6470 > 服务端.8080: Flags [S], seq 2369523978, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
# 20:52:54.647912 IP 服务端.8080 > 客户端.6470: Flags [S.], seq 1109598671, ack 2369523979, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
# 20:52:54.670302 IP 客户端.6470 > 服务端.8080: Flags [.], ack 1, win 256, length 0
# 20:52:54.680784 IP 客户端.6470 > 服务端.8080: Flags [P.], seq 1:301, ack 1, win 256, length 300
# 20:52:54.680789 IP 服务端.8080 > 客户端.6470: Flags [.], ack 301, win 123, length 0
# 20:52:54.702935 IP 服务端.8080 > 客户端.6470: Flags [P.], seq 1:544, ack 301, win 123, length 543
# 20:52:54.702941 IP 服务端.8080 > 客户端.6470: Flags [F.], seq 544, ack 301, win 123, length 0
# 20:52:54.726494 IP 客户端.6470 > 服务端.8080: Flags [.], ack 545, win 254, length 0
# 20:52:54.726499 IP 客户端.6470 > 服务端.8080: Flags [F.], seq 301, ack 545, win 254, length 0
# 20:52:54.726501 IP 服务端.8080 > 客户端.6470: Flags [.], ack 302, win 123, length 0
上述具体的ip已经被我批量替换了, 不方便暴露服务器ip

分析:

  • 可以看到4次挥手的开始是由服务端主动发起的(记住TIME_WAIT只会出现在主动断开连接的一方)
  • 个人理解是, nginx 在配置"不启用keep-alive"时, 会在http请求结束时主动断开连接.
  • 尝试开启http的keep-alive

修改 nginx 配置

keepalive_timeout 65;

reload nginx

nginx -s reload

再次抓包

tcpdump tcp -i any -nn port 8080 | grep "我的ip"

# 21:09:10.044918 IP 客户端.8217 > 服务端.8080: Flags [S], seq 1499308169, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
# 21:09:10.044927 IP 服务端.8080 > 客户端.8217: Flags [S.], seq 2960381462, ack 1499308170, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
# 21:09:10.070694 IP 客户端.8217 > 服务端.8080: Flags [.], ack 1, win 256, length 0
# 21:09:10.077437 IP 客户端.8217 > 服务端.8080: Flags [P.], seq 1:302, ack 1, win 256, length 301
# 21:09:10.077443 IP 服务端.8080 > 客户端.8217: Flags [.], ack 302, win 123, length 0
# 21:09:10.198117 IP 服务端.8080 > 客户端.8217: Flags [P.], seq 1:671, ack 302, win 123, length 670
# 21:09:10.222957 IP 客户端.8217 > 服务端.8080: Flags [F.], seq 302, ack 671, win 254, length 0
# 21:09:10.222980 IP 服务端.8080 > 客户端.8217: Flags [F.], seq 671, ack 303, win 123, length 0
# 21:09:10.247678 IP 客户端.8217 > 服务端.8080: Flags [.], ack 672, win 254, length 0

注意看上面很有意思的地方:

  • tcp 的挥手只有3次, 而非正常的4次. 个人理解是, 服务端在收到 FIN 时, 已经确认自己不会再发送数据, 因此就将 FIN 与 ACK 一同合并发送
  • 此时是客户端主动断开tcp连接, 因此服务端不会出现 TIME_WAIT

再次查看连接状态

netstat -an | grep TIME_WAIT | awk '{print $4}' | sort | uniq -c | sort -n -k1
#      ...忽略上面
#      1 127.0.0.1:60602
#      1 127.0.0.1:60604
#    344 127.0.0.1:9000

此时发现已经没有处于 TIME_WAIT 的连接了.

9000 fast-cgi 端口

经过网上查找资料, 整理:

  • nginx 与 fast-cgi 的默认连接是短连接, 每次连接都需要经过一次完整的tcp连接与断开

当前 nginx 配置

upstream phpserver{
    server 127.0.0.1:9000 weight=1;
}

修改nginx配置使其与fastcgi的连接使用长连接

upstream phpserver{
    server 127.0.0.1:9000 weight=1;
    keepalive 100
}

fastcgi_keep_conn on;

说明:

  • upstream 中的 keepalive 指定nginx每个worker与fastcgi的最大长连接数, 当长连接不够用时, 此时新建立的连接会在请求结束后断开(由于此时指定了 HTTP1.1, fastcgi不会主动断开连接, 因此nginx这边会出现大量 TIME_WAIT, 需谨慎(未验证)
  • 由于php-fpm设置了最大进程数为100, 因此此处的 keepalive 数量指定 100 (未测试)

此处题外话, 如果 nginx 是作为反向代理, 则需增加如下配置:

# 将http版本由1.0修改为1.1
proxy_http_version 1.1;
# 清除"Connection"头部
proxy_set_header Connection "";    
  • 配置 proxy_pass 将请求转发给后端
  • 这里, 理解一下 proxy_passfastcgi_pass 区别

    客户端 --http-->  前端负载均衡Nginx --proxy_pass--> 业务服务器Nginx --fastcgi_pass--> 业务服务器 php-fpm

再次确认 tcp 连接情况

netstat -antp  | grep :9000 | awk '{print $(NF-1)}' | sort | uniq -c
#      6 ESTABLISHED
#      1 LISTEN

ok, 问题解决.

另一种解决方法:

若 nginx 与 fast-cgi 在同一台服务器上, 则使用 unix域 会更为高效, 同时避免了 TIME_WAIT 的问题.

题外

经过上面优化后, TIME_WAIT数量从上千个大幅下降到几十个, 此时发现TIME_WAIT中的存在大量的 127.0.0.1:6379, 6379是redis服务的默认端口....

赶紧改业务代码去, 将 $redis->connect(...) 改成 $redis->pconnect(...)

说明:

  • pconnect 表示 php-fpm 与 redis 建立 tcp 连接后, 在本次http请求结束后仍维持该连接, 下次新的请求进来时可以复用该连接, 从而复用了tcp连接.
查看原文

赞 3 收藏 2 评论 3

魔王卷子 回答了问题 · 2020-05-25

解决php框架的ORM 是否能屏蔽MySQL数据库高低版本之间的语法问题?

请问你公司研发了mysql9了吗?赶紧拉出来赚钱啊。国产数据库就靠你们了

关注 5 回答 4

魔王卷子 赞了文章 · 2020-05-21

微信域名拦截检测API源码 检测域名是否能在微信正常打开

可一键检测域名是否被微信屏蔽(网站是否可以在微信客户端中打开),适合做防红防拦截功能,便于及时更换域名。

<?php
/** ----------------------------------
* wx域名检测
* Time:2020-5-15
* www.likeyunba.com
-------------------------------------- **/

header('Content-type: text/json;charset=utf-8'); 
if(empty($_REQUEST['url'])){
    exit('{"code":0,"msg":"参数不正确"}');
}
$api = get_headers('http://mp.weixinbridge.com/mp/wapredirect?url='.$_REQUEST['url']);

if($api[1] !== 'Location: '.$_REQUEST['url'].''){
        $value = array('code'=>202,'msg'=>'域名被封');
}else{
        $value = array('code'=>200,'msg'=>'域名正常');
}
echo json_encode($value,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE);
?>

测试:

http://www.likeyunba.com/api/wx_domain_check.php?url=https://www.qq.com/
http://www.likeyunba.com/api/wx_domain_check.php?url=https://www.taobao.com/

学习交流WeChat:face6009
个人网站:https://www.likeyunba.com/

查看原文

赞 6 收藏 4 评论 1

魔王卷子 关注了用户 · 2020-05-09

谢顶道人 @laolixiuxiu

排头兵@滴滴出行
公众号:高性能API社区

关注 647

魔王卷子 回答了问题 · 2020-04-25

ubuntu20.04如何安装php的Yaf扩展?

你在php.ini里面添加extension=yaf.so了吗?

关注 2 回答 1

魔王卷子 回答了问题 · 2020-04-25

php多进程间通信的问题

建议参考 workerman。

关注 4 回答 3

魔王卷子 回答了问题 · 2020-04-25

解决laravel5.4 post 重置了X-CSRF-TOKEN

你问的问题可能不是laravel的问题,应该是你的PHP环境写入session不成功,导致session被重置。

建议你检查下你的session存储目录的权限问题。是否可写

关注 2 回答 1

魔王卷子 回答了问题 · 2020-02-14

前端用的vue,后端用的php,vue如何将数据传给后台

一般来说就是使用ajax

vue 搭配的话,我建议是 axios

关注 3 回答 3

魔王卷子 关注了标签 · 2020-02-13

laravel

请输入图片描述

RESTful 路由
通过简单的闭包就能响应HTTP请求。帮你快速开始构建非凡的应用。

强大的数据操纵能力
Laravel自带了强大的Eloquent ORM 和迁移工具。能够完美的与MySQL、Postgres、SQL Server 和SQLite协同工作。

优雅的模版引擎
PHP代码或轻量级的Blade模版引擎都可无缝融合。Blade模版可以继承,并且拥有极快的解析速度。相信你会喜欢它的。

为明天做准备
构建大型的企业级应用或者只是提供简单的JSON API;书写强大的控制器或轻巧的RESTful路由,Laravel适应所有级别的开发工作。

可靠的基石
Laravel 的基石是数个Symfony组件,这些经过千锤百炼、可靠的组件为你的应用提供坚实的基础。

基于Composer管理器
Composer 是一套帮你管理第三方扩展包的工具。能够让你迅速在 Packagist 中找到需要的扩展包。

强大的社区支持
无论你是一个PHP新手还是经验丰富的架构师,都能在社区中找到需要的知识。你可以在IRC中讨论Idea,或者在论坛中发布问题。

测试、重构
Laravel 从开始就将测试作为重点功能。我们提供了灵活的IoC容器,集成了PHPUnit 测试工具。不用担心,这些都很容易上手。

关注 4640

魔王卷子 赞了文章 · 2020-01-13

gorm数据库映射工具

gormt

一款 mysql 数据库转 struct 工具

交互界面模式

ui_cn.gif

./gormt -g=true

命令行模式

out.gif

./gormt -g=false

1. 通过当前目录config.toml文件配置默认配置项

out_dir : "."  # 输出目录
singular_table : false  # 表名复数,是否大驼峰构建 参考:gorm.SingularTable
simple : false #简单输出
is_out_sql : false # 是否输出 sql 原信息
is_out_func : true # 是否输出 快捷函数
is_json_tag : false #是否打json标记
is_foreign_key : true #是否导出外键关联
mysql_info :
    host : "127.0.0.1"
    port : 3306
    username : "root"
    password : "qwer"
    database : "oauth_db"

2. 可以使用命令行工具更新配置项

./gormt -H=127.0.0.1 -d=oauth_db -p=qwer -u=root --port=3306

3. 查看帮助

./gormt -h

-------------------------------------------------------
base on gorm tools for mysql database to golang struct

Usage:
  main [flags]

Flags:
  -d, --database string   数据库名
  -h, --help              help for main
  -H, --host string       数据库地址.(注意-H为大写)
  -o, --outdir string     输出目录
  -p, --password string   密码.
      --port int          端口号 (default 3306)
  -s, --singular          是否禁用表名复数
  -u, --user string       用户名.
  

4. 支持gorm 相关属性

  • 数据库表,列字段注释支持
  • singular_table 表名复数(大驼峰)
  • json tag json标签输出
  • gorm.Model 基本模型 支持gorm.Model模式导出>>>
  • PRIMARY_KEY 将列指定为主键
  • UNIQUE 将列指定为唯一
  • NOT NULL 将列指定为非 NULL
  • INDEX 创建具有或不带名称的索引, 如果多个索引同名则创建复合索引
  • UNIQUE_INDEX 和 INDEX 类似,只不过创建的是唯一索引
  • 支持外键相关属性 简单带外键模式导出>>>
  • 支持函数导出(包括:外键,关联体,索引关...)简单函数导出示例>>>

5. 示例展示

--->导出结果示例
  • 参数:singular_table = false simple = false isJsonTag = true
//    用户信息
type UserAccountTbl struct {
    ID          int       `gorm:"primary_key;column:id;type:int(11);not null" json:"-"`                                                   //
    Account     string    `gorm:"unique;column:account;type:varchar(64);not null" json:"account"`                                         //
    Password    string    `gorm:"column:password;type:varchar(64);not null" json:"password"`                                              //
    AccountType int       `gorm:"column:account_type;type:int(11);not null" json:"account_type"`                                          //    帐号类型:0手机号,1邮件
    AppKey      string    `json:"app_key" gorm:"unique_index:UNIQ_5696AD037D3656A4;column:app_key;type:varchar(255);not null"`            //    authbucket_oauth2_client表的id
    UserInfoID  int       `gorm:"unique_index:UNIQ_5696AD037D3656A4;index;column:user_info_id;type:int(11);not null" json:"user_info_id"` //
    RegTime     time.Time `gorm:"column:reg_time;type:datetime" json:"reg_time"`                                                          //
    RegIP       string    `gorm:"column:reg_ip;type:varchar(15)" json:"reg_ip"`                                                           //
    BundleID    string    `json:"bundle_id" gorm:"column:bundle_id;type:varchar(255)"`                                                    //
    Describ     string    `gorm:"column:describ;type:varchar(255)" json:"describ"`                                                        //
}
  • 参数:singular_table = false simple = true isJsonTag = false
--->导出结果
//    用户信息
type UserAccountTbl struct {
    ID          int       `gorm:"primary_key"` //
    Account     string    `gorm:"unique"`      //
    Password    string    //
    AccountType int       //    帐号类型:0手机号,1邮件
    AppKey      string    `gorm:"unique_index:UNIQ_5696AD037D3656A4"`       //    authbucket_oauth2_client表的id
    UserInfoID  int       `gorm:"unique_index:UNIQ_5696AD037D3656A4;index"` //
    RegTime     time.Time //
    RegIP       string    //
    BundleID    string    //
    Describ     string    //
}

更多>>>

6. 支持函数导出(导出函数只是 gorm 的辅助类函数,完全兼容 gorm 相关函数集)

// FetchByPrimaryKey primay or index 获取唯一内容
func (obj *_UserAccountTblMgr) FetchByPrimaryKey(ID int) (result UserAccountTbl, err error) {
    err = obj.DB.Table(obj.GetTableName()).Where("id = ?", ID).Find(&result).Error
    if err == nil && obj.isRelated {
        {
            var info UserInfoTbl // 用户信息
            err = obj.DB.Table("user_info_tbl").Where("id = ?", result.UserInfoTblID).Find(&info).Error
            if err != nil {
                return
            }
            result.UserInfoTbl = info
        }
    }

    return
}

更多>>>

函数调用示例>>>

7. 构建

make windows
make linux
make mac

or

go generate

8. 下一步计划

  • 更新,删除功能函数添加
  • 优化

9. 提供一个windows 可视化工具

1.png
2.jpg
3.jpg
4.jpg

下载地址

查看原文

赞 1 收藏 0 评论 0

魔王卷子 赞了文章 · 2019-12-17

“国货之光” 完美日记的微服务实践和优化思路

如果你是一位程序媛,你一定知道完美日记。
如果你是一位程序员,你的那个她一定知道完美日记。

今年双11,完美日记仅用28分钟就超过了2018年双11全天的销售额,成为第一个登上天猫双11彩妆榜首的国货品牌。在这个遍地都是漂亮小姐姐、号称男人(特指程序员)天堂的公司里,拥有着一支什么样的基础架构技术团队,他们是如何在 4 个月内筹建、上线电商平台的呢?本文将为您分享他们在实践微服务过程遇到的难点和优化思路。

完美日记基础架构技术团队欢迎您的加入,移步文末,了解详情。

起步

自建商城在设计之初,业务部门就提出了两个要求:不崩 & 快速上线。

在立项之后,团队还没有完全配备好,一边从其他团队里调取人手,一边大力招聘,与此同时,我们的架构师也在搭建一套分布式商城开发框架,编写 Demo,让新加入的同学能快速上手。

暴露问题

问题一:分布式事务

为什么会使用分布式事务?

这个暂且可以归因于快速上线,因为生成订单会调用到商品服务扣减库存,使用了分布式事务解决了因为跨服务调用引起库存超卖的问题,带来的问题就是性能上的消耗。

问题二:数据库压力

在大促活动期间,有个实时统计是直接从业务库上直接查询统计的,运营部门的小姐姐在不断地刷新,导致该接口上的压力山大,而且没有使用缓存,连 SQL 查询条件的时间都是动态的,导致 DB 层的缓存也使用不上,每次请求都打到 DB 上。

开发和测试环境是使用自建的 MySQL,生产环境使用的是 PolarDB,从阿里云官网上看到:

  • 集群架构,计算与存储分离
  • 读写分离

我们主观地认为,只要我们使用了集群连接地址就会自动进行读写分离,但是实际上并没有,后来发现在方法上显式的指定只读事务就有请求走到只读节点上了。
@Transactional(readOnly = true)

# 优化思路:

1)从 SQL 洞察和慢 SQL 里找调用响应时间最长和频度最高的 SQL;
2)结合代码,能用缓存代替的直接处理掉,不用能缓存的优化查询,结合阿里云提供的优化分析工具,调整索引;
3)活动高峰时段,禁止分析统计类的查询执行,临时改代码已经来不及了,幸亏 AHAS(阿里云的一款限流降级产品) 的接口限流和 SQL 限流功能;
4)TP 和 AP 分离,避免分析类直接查询到业务库(这是一个比较漫长的过程)。

问题三:缓存压力

除了前面所提到的分布式事务之后,发现还有同事写了使用 Keys 模糊查询 Redis,直接导致 Redis 的 CPU 飙升严重,通过阿里云提供的 Redis 管理工具可以很方便地查看到有哪些慢查询。

另外一个低级错误,我们相信应该不是第一个,也不会是最后一个,本来要设置一个 Key 的过期时间,结果少写了个 Unit 参数,第三个就变更偏移量了。

redisTemplate.opsForValue().set(key, value, offset)

# 为什么我们花了10分钟左右才解决?

1)惯性思维,review 代码没发现出来;
2)在错误日志里发现 Redisson 锁失败时,怀疑是 Redis 写满了;
3)使用阿里云的工具去查大 Key 时发现了 Key 很大,但是直接在网页查看值的时候只看到保存了一个字符,问题就出在这里,因为 RDS 管控台里获取到的值看起来是正确的,大概又过了2分钟左右,我觉得不太对劲,然后登录上去用 redis-cli 查看,傻眼了,里面塞满了 0x00。

问题四:

商城上线当月有一个促销活动,因为瞬间进来的流量过大,小程序前端埋点事件上报的接口连接数爆了,商城实时数据统计调用了流量统计服务的接口,然而服务调用超时时间设置的是60s,导致过多请求积压,CPU 突然飙升得很厉害。

# 优化思路:

1)充分利用 Nginx 的并发处理能力,Lua 脚本提供了强大的处理能力,将 Java 处理请求改为使用 OpenResty 接收;
2)接收到请求之后做好基本的校验之后,使用 lua-resty-kafka 模块异步发送到 Kafka;
3)Kafka 落盘到 HDFS 后,由 Spark 离线计算日志数据;
4)后端接口独立部署,实时数据统计调用接口设置更短的超时时间;

经过以上改造之后,前端日志上报服务单机处理能力由原来的 1K 提升 40K,那种如丝般顺滑的体验实在是太好了。

迭代

从当时的情形来看,针对双11的活动做大动作调整代码优化基本上是来不及了,离活动还有不到两个星期的时间,即便改了,风险也很高。

1、压测

作为一个新上线的项目,数据量还比较小,使用云服务来搭建一套1比1的压测环境还是比较容易的,在这个时间节点上,我们需要模拟真实的场景摸清楚目前的系统能承受多大的压力,需要多少机器。

阿里云上有个 PTS 的压测工具,可以直接导入 Jmeter 脚本,使用起来很方便,接下来说说我们的使用步骤:

1)先是按过往一个月的用户行为日志里,找出用户的路径和每个行为的思考时间,做了一个大概的模型;
2)按照双十一活动的运营节奏,定义了两到三个场景;
3)使用 ECS 搭建 Jmeter 集群,内网对接口进行施压,目的是减少网络开销,让请求都能打到后端服务器上;
4)观察服务器的压力,调节应用内存分配,再通过 PolarDB 性能分析,找出有性能瓶颈的 SQL 尽可能地优化掉;
5)将 Jmeter 脚本导入到 PTS,关联上数据库和 ECS 机器的云监控,设置好思考时间等相关的参数后施压,可以动态秒级调整压力,生成的压测报告就是我们想要的结果,需要拿这个结果来进行下一步的限流控制。

2、限流

1)在接入 AHAS 过程中,由于微商城项目当前版本接入的是spring-cloud-alibaba-dependencies-0.9.0.RELEASE版本来使用阿里云的 OSS 与 SMS,在接入 AHAS 后,需要对依赖 Alibaba 版本的升级,涉及包括 Nacos 配置中心与服务发现的升级和包路径的命名变更修改;
2)在接入 AHAS 的 gateway 网关路由限流,采用的是 SDK 接入方式,AHAS 采用了符合 springboot-starter 特性的 SDK 开发,这样在我们微商城接入 gateway 时只需要在项目 POM 中加入 spring-cloud-gateway-starter-ahas-sentinel,在接入 gateway 的时候发现,网关路由限流采集上传的 API 出现了没有兼容 Restfull 风格 API 的问题,导致 URL 上出现参数时多个url没有合并一起的情况,阿里云 AHAS 支持团队立即发布 Fix 版本,提供新的 SentinelWebInterceptor 拦截器进行清洗 Restful 风格 API 处理;
3)在接入 AHAS 的应用模块限流,采用的也是 SDK 接入方式,在按官网文档进行接入的时候,发现我们微商城采用的是最新版本的 Mybatis Plus 版本,在接入 SQL 限流分析功能时发现出现ahas报错,在将此反馈到ahas钉钉团队支援群后,当时已经差不多凌晨一点了,ahas团队的及时响应以及第二天早上就发布了兼容 Mybatis Plus 版本的SQL 限流分析版本给到我们微商城,在我们接入新版本后,SQL 分析和限流功能也能正常使用了;
4)在使用 AHAS 接入的时候,发现 AHAS 除了接口的 API 限流功能外,还提供了CPU/Load 的限流,对服务器性能情况的监控和保护做了很好的护航,在微商城服务器压力过高时能够很好的保护服务器不被高并发压垮,保证了服务的高可用,同时在服务器压力大的时候,做到了实时 QPS 日志上传的隔离,避免上传抢占服务器资源,保证了服务器在接入 AHAS 后也能保持良好的性能。

未来

未来计划要做的事情:

1)按服务拆分 Redis;
2)数据库读写分离、分库分表、TP/AP 分离;
3)业务中台化:建立业务中台,打通商品中心、库存中心、用户中心和交易中心;

为了更好的应对源源不断的挑战,以下岗位持续招聘中:

  • Java开发工程师
  • 前端开发工程师
  • 测试工程师

有意请发送简历至邮箱:Lynn.Guo@yatsenglobal.com

作者信息:
庄工:逸仙电商架构师&技术委员会负责人,负责完美日记商城基础架构和微服务体系建设。
关工:逸仙电商后端技术专家,现主要参与微商城后端框架集成方案、以及性能调优和微商城技术规范管理。
唐工:逸仙电商技术经理,曾先后就职于中国航信和唯品会,现主要负责前后端技术统筹等打杂工作。


本文作者:庄工、关工、唐工

阅读原文

本文为阿里云内容,未经允许不得转载。

查看原文

赞 2 收藏 1 评论 0

魔王卷子 赞了文章 · 2019-12-05

PHP中pack、unpack的详细用法

PHP中有两个函数pack和unpack,很多PHPer在实际项目中从来没有使用过,甚至也不知道这两个方法是用来干嘛的。这篇文章来为大家介绍一下它俩到底是用来干啥的。

pack

string pack ( string $format [, mixed $args [, mixed $... ]] )

该函数用来将对应的参数($args)打包成二进制字符串。

其中第一个参数$format,有如下选项(可选参数很多,后面会选几个常用的讲解):

CodeDescription
a以NUL字节填充字符串空白
A以SPACE(空格)填充字符串
h十六进制字符串,低位在前
H十六进制字符串,高位在前
c有符号字符
C无符号字符
s有符号短整型(16位,主机字节序)
S无符号短整型(16位,主机字节序)
n无符号短整型(16位,大端字节序)
v无符号短整型(16位,小端字节序)
i有符号整型(机器相关大小字节序)
I无符号整型(机器相关大小字节序)
l有符号长整型(32位,主机字节序)
L无符号长整型(32位,主机字节序)
N无符号长整型(32位,大端字节序)
V无符号长整型(32位,小端字节序)
q有符号长长整型(64位,主机字节序)
Q无符号长长整型(64位,主机字节序)
J无符号长长整型(64位,大端字节序)
P无符号长长整型(64位,小端字节序)
f单精度浮点型(机器相关大小)
d双精度浮点型(机器相关大小)
xNUL字节
X回退一字节
Z以NUL字节填充字符串空白(new in PHP 5.5)
@NUL填充到绝对位置

这么多参数看下来,我第一次是真心懵逼了,大部分说明都很好理解,但是其中的主机、大端、小端等字节序是什么鬼呢?接下里的内容比较枯燥,但必须理解才行,坚持吧。

字节序是什么?

就是字节的顺序,说白了就是多字节数据的存放顺序(一个字节显然不需要顺序)。
比如AB分别对应的二进制表示为0100 00010100 0010。对于储存字符串AB,我们可以0100 0001 0100 0010也可以0100 0010 0100 0001,这个顺序就是所谓的字节序。

高/低位字节

比如字符串AB,左高右低(我们正常的阅读顺序),A为高字节,B为低字节

高/低地址

假设0x123456是按从高位到底位的顺序储存,内存中是这样存放的:

高地址 -> 低地址
12 -> 34 -> 56

大端字节序(网络字节序)

大端就是将高位字节放到内存的低地址端,低位字节放到高地址端。网络传输中(比如TCP/IP)低地址端(高位字节)放在流的开始,对于2个字节的字符串(AB),传输顺序为:A(0-7bit)、B(8-15bit)。
那么小端字节序自然和大端相反。

主机字节序

表示当年机器的字节序(也就是网络字节序是确定的,而主机字节序是依机器确定的),一般为小端字节序。

a和A(打包字符串,用NUL或者空格填充)

$string = pack('a6', 'china');
var_dump($string); //输出结果: string(6) "china",最后一个字节是不可见的NUL
echo ord($string[5]); //输出结果: 0(ASCII码中0对应的就是nul)

//A同理
$string = pack('A6', 'china');
var_dump($string); //输出结果: string(6) "china ",最后一个字节是空格
echo ord($string[5]); //输出结果: 32(ASCII码中32对应的就是空格)

ASCII

附赠ASCII表一张(linux/unix下可以使用man ascii查看)

h和H

$string = pack('H3', 281);
var_dump($string); //输出结果: string(2) "("

for($i=0;$i<strlen($string);$i++) {
echo ord($string[$i]) . PHP_EOL;
}
//输出结果: 40 16

h和H需要特殊说明一下,它们是将对应的参数看做十六进制字符然后打包。什么意思呢?比如上面的281,打包前会将281转换为0x281,因为十六进制的一位对应二进制的四位,上面的0x281只有1.5个字节,后面会默认补0变成0x2810,0x28对应的十进制为40((),0x10对应的十进制为16(dle不可见字符),懂了吧?不懂可以给我留言。。

c和C

$string = pack('c3', 67, 68, -1);
var_dump($string); //输出:string(3) "CD�"

for($i=0;$i<strlen($string);$i++) {
echo ord($string[$i]) . PHP_EOL;
}
//输出: 67 68 225

最后输出本能应该觉得是67 68 -1
ord获取的是字符的ASCII码(范围0-255),这时-1(0000 0001)对应的字符将以补码的形式输出也就是255(1111 1110 + 0000 0001 = 1111 1111)

整型相关

所有的整型类型使用方法完全一样,主要注意它们的位和字节序就可以了,下面以L作为例子展示

$string = pack('L', 123456789);
var_dump($string); //输出:string(4) "�["

for($i=0;$i<strlen($string);$i++) {
echo ord($string[$i]) . PHP_EOL;
}
//输出: 21 205 91 7

f和d

$string = pack('f', 12345.123);
var_dump($string);
//输出:string(4) "~�@F"
var_dump(unpack('f', $string)); //这里提前用到了unpack,后面会讲解
//输出:float(12345.123046875)

f和d是针对浮点数打包,至于为什么打包前是12345.123解包后是12345.123046875,这个和浮点数的储存有关系,后面可以单开一个文章讲解一下IEEE标准

x、X、Z、@

$string = pack('x'); //打包一个nul字符串
echo ord($string); //输出: 0

关于X(大写X),试了N次,没搞明白怎么用,有清楚的童鞋可以给我留言,多谢。

$string = pack('Z2', 'abc5'); //其实就是将从Z后面的数字位置开始,全部设置为nul
var_dump($string); //输出:string(2) "a"

for($i=0;$i<strlen($string);$i++) {
echo ord($string[$i]) . PHP_EOL;
}
//输出: 97 0
$string = pack('@4'); //我理解为填充N个nul
var_dump($string); //输出: string(4) ""

for($i=0;$i<strlen($string);$i++) {
echo ord($string[$i]) . PHP_EOL;
}
//输出: 0 0 0 0

unpack

array unpack ( string $format , string $data )

unpack的使用相当简单,就是讲pack打包的数据解包,打包的时候用的什么参数,就用什么参数解包,具体使用懒得说了,列几个小例子

$string = pack('L4', 1, 2, 3, 4);
var_dump(unpack('L4', $string));
//输出:
array(4) {
[1]=>
int(1)
[2]=>
int(2)
[3]=>
int(3)
[4]=>
int(4)
}

$string = pack('L4', 1, 2, 3, 4);
var_dump(unpack('Ll1/Ll2/Ll3/Ll4', $string)); //可以指定key,用/分割
//输出:
array(4) {
["l1"]=>
int(1)
["l2"]=>
int(2)
["l3"]=>
int(3)
["l4"]=>
int(4)
}

这两个函数到底有啥用途

  • 数据通信(通过二进制格式与其它语言通信)
  • 数据加密(如果不告诉第三方你的打包方式,对方解包的难度就相对很大)
  • 节省空间(比如比较大的数字按字符串储存会浪费很多空间,打包成二进制格式才需要4位<32位数字>)
  • 自己去想吧

关注公众号,一起学习成长~

xysy.jpg

查看原文

赞 80 收藏 103 评论 9

魔王卷子 赞了文章 · 2019-12-01

我们悄悄优化了页面载入速度

随着访问量的增大,我们发现在高峰时期 SegmentFault 的载入速度越来越慢,而且还时不时会出现 502 的问题。而优化载入速度是一个很庞大的系统工程,它不仅涉及到系统设计,前端改造还包括业务逻辑的优化等等。但是,有些问题是可以立马解决的,而且可以带来比较明显的提升效果,因此我们这次的优化就集中在这些问题上了。

加机器

这是增加系统承载量最简单粗暴的方法,当然这次也不能免俗。当然作为一个创业公司,我们对增加服务器一直是比较谨慎的,对现有服务器的性能也是一直压榨压榨再压榨(手动微笑)。当然考虑到现有服务器在高峰期的负载已经逼近峰值,这加机器也是必不可少的了。

于是我们首先升级了 PHP 的版本(这可以带来少量的性能提升),并且增加了服务器的数量。负载立马下降,效果立竿见影。但是要加快载入,还需要一些其它的手段综合使用。

剥离其它业务的访问量

之前为了配置方便,我们的一些附属服务的流量也是从同一个流量入口进入。这就导致了在高峰期的网络拥挤。特别是广告服务,因为一个页面往往有好几个广告位置,这导致请求量被成倍放大。

在这次优化中,这些服务被挪到单独的入口,并进行针对性的网络优化。以前吃紧的带宽,一下子富余了很多。负载均衡服务器的压力也大大下降了

前端页面的小优化

在用debug工具查看页面的网络请求时,我们发现广告业务的请求是同步加载的,这就导致页面的渲染会一顿一顿地进行,用户体验比较糟糕。于是我们修改了广告载入的逻辑,让它与页面加载异步进行,这提高了感官上的页面载入速度。

总结

载入速度优化是一个持续不断的过程,我们也会把这项工作继续进行下去。这些立竿见影的改进完成后,就是业务逻辑的优化,这些硬仗可能会持续很长时间。目前我们已经在对某些页面做针对性的改进,比如大家能看到的新版文章页。后续还有一大波新的页面正在赶来的路上。

查看原文

赞 27 收藏 2 评论 11

魔王卷子 赞了回答 · 2019-10-31

解决phpstorm 如何使用类的时候 如何自动在头部 use 命名空间

Perferences->Editor->General-> Auto Import 选择开启自动引入命名空间。

clipboard.png

关注 3 回答 1