一步两步是魔鬼的步伐

Duan1v's Blog

Ubuntu 配置Nginx

生成SSL证书

生成命令

1
sudo certbot certonly --nginx --nginx-server-root /www/server/nginx/conf -w /www/wwwroot/example.com -d example.com -d www.example.com
  • 其中 --nginx-server-root 需要指向实际的nginx的配置文件

  • 如果报证书文件夹已存在,可以再次执行一遍

重新生成命令

1
sudo certbot renew --dry-run --nginx-server-root /www/server/nginx/conf

由于会自动过期,所以将重新生成命令添加到定时脚本中

1
2
3
4
sudo crontab -e

# 添加下面任务
15 2 * */2 * certbot renew --dry-run --nginx-server-root /www/server/nginx/conf --pre-hook "service nginx stop" --post-hook "service nginx start"

将nginx加入到随服务器启动

添加nginx服务

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
sudo vim /lib/systemd/system/nginx.service

# 添加下面配置
[Unit]
Description=A high performance web server and a reverse proxy server
After=network.target

[Service]
Type=forking
PIDFile=/www/server/nginx/logs/nginx.pid
ExecStart=/www/server/nginx/sbin/nginx -c /www/server/nginx/conf/nginx.conf
ExecReload=/www/server/nginx/sbin/nginx -s reload
ExecStop=/www/server/nginx/sbin/nginx -s quiet
TimeoutStopSec=5
KillMode=mixed

[Install]
WantedBy=multi-user.target

允许自启动

1
sudo systemctl enable nginx.service

查看启动状态

1
sudo systemctl status nginx.service

nginx 配置参考

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
server
{
    listen 80;
    listen 443 ssl;
    server_name example.com www.example.com;
    index index.php index.html index.htm default.php default.htm default.html;
    root /www/wwwroot/example.com/public;

    #error_page 404/404.html;
    if ($server_port !~ 443){
        rewrite ^(/.*)$ https://$host$1 permanent;
    }

    # 改成上面生成的证书路径
    ssl_certificate    /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key    /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    error_page 497  https://$host$request_uri;

    error_page 404 /404.html;
    error_page 502 /502.html;

    location ~ [^/]\.php(/|$)
    {
        try_files $uri =404;
        fastcgi_pass  127.0.0.1:9003;
        # fastcgi_pass unix:/run/php/php7.4-fpm.sock;
        # fastcgi_split_path_info  ^((?U).+\.php)(/?.+)$;
        fastcgi_index index.php;
        # include fastcgi.conf;
        include fastcgi_params;
        set $real_script_name $fastcgi_script_name;
        if ($fastcgi_script_name ~ "^(.+?\.php)(/.+)$") {
            set $real_script_name $1;
            set $path_info $2;
         }
        fastcgi_param SCRIPT_FILENAME $document_root$real_script_name;
        fastcgi_param SCRIPT_NAME $real_script_name;
        fastcgi_param PATH_INFO $path_info;
        fastcgi_buffering off;
    }

    location / {
        try_files $uri $uri/ /index.php$is_args$query_string;
    }

    location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md)
    {
        return 404;
    }

    location ~ \.well-known{
        allow all;
    }

    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
    {
        expires      30d;
        error_log off;
        access_log off;
    }

    location ~ .*\.(js|css)?$
    {
        expires      12h;
        error_log off;
        access_log off;
    }
    access_log  /www/wwwlogs/example.com.log;
    error_log  /www/wwwlogs/example.com.error.log;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
server {
    listen 80;
    server_name my_vue3_backend.com www.my_vue3_backend.com;
    index index.php index.html index.htm default.php default.htm default.html;
    root /mnt/h/workspace/php/vue3-backend/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_buffering off;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
    access_log  /mnt/h/wsl/nginx/logs/my_vue3_backend.com.log;
    error_log  /mnt/h/wsl/nginx/logs/my_vue3_backend.com.error.log;
}

php 重启

  • 通过 php-fpm.conf查找pid文件,比如 /var/php/74/var/run/php-fpm.pid
1
kill -SIGUSR2 `cat /var/php/74/var/run/php-fpm.pid`

参考

XML相关

前端展示

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
<link href="/path/codemirror/css/codemirror.css" rel="stylesheet">
<link href="/path/codemirror/css/foldgutter.css" rel="stylesheet">
<link href="/path/codemirror/css/isotope.css" rel="stylesheet">
<style>
    h4 {
        color: #abb2bf;
    }

    .CodeMirror {
        height: auto;
        font-family: "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
    }

    .CodeMirror-vscrollbar {
        overflow-y: auto !important;
    }

    .cm-tag {
        cursor: pointer;
    }

    .CodeMirror-cursors {
        display: none;
    }

    .CodeMirror-scroll {
        background-color: #282c34 !important;
    }

    .CodeMirror-scroll::before {
        content: "";
        display: block;
        width: 100%;
        height: 30px;
        background-color: #384548;
        border-top-left-radius: 5px;
        border-top-right-radius: 5px;
    }

    .CodeMirror-scroll::after {
        content: "";
        position: absolute;
        top: 0;
        left: 35px;
        width: 60px;
        height: 30px;
        background: url({{asset('falcon/assets/img/sd.png')}});
        background-size: 40px;
        background-repeat: no-repeat;
        background-position: 15px 10px;
        transform: rotate(-3deg);
    }

    .cm-s-isotope span.cm-tag {
        color: #e06c75 !important;
    }

    .cm-s-isotope span.cm-bracket {
        color: #ffffff !important;
    }

    .cm-s-isotope span.cm-attribute {
        color: #d19a66 !important;
    }

    .cm-s-isotope span.cm-string {
        color: #98c379 !important;
    }

    .cm-s-isotope span.cm-meta {
        color: #61aeee !important;
    }
</style>
<div>
    <textarea id="code-xml" name="code" class="d-none">xml文本</textarea>
</div>

<script src="/path/codemirror/js/codemirror.js"></script>
<script src="/path/codemirror/js/foldcode.js"></script>
<script src="/path/codemirror/js/foldgutter.js"></script>
<script src="/path/codemirror/js/xml-fold.js"></script>
<script src="/path/codemirror/js/xml.js"></script>
<script type="module">
function initCode() {
    return CodeMirror.fromTextArea(document.getElementById("code-xml"), {
        mode: "text/xml",
        lineNumbers: true,
        readOnly: true,
        cursorBlinkRate: 0,
        theme: "isotope",
        // extraKeys: {
        //     "Ctrl-Q": function (cm) {
        //         cm.foldCode(cm.getCursor());
        //     }
        // },
        foldGutter: true,
        gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
    });
}
var editor = initCode();
</script>

php格式化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function formatXML($xmlString): bool|string
{
    $dom = new DOMDocument();
    $dom->preserveWhiteSpace = false;
    $dom->formatOutput = true;

    $dom->loadXML($xmlString);

    return $dom->saveXML();
}

php获取xpath

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
function getAllXpath($xmlString, $noSplit = false, $line = 0)
{
    $doc = new DOMDocument();
    $doc->loadXML($xmlString);

    function traverseElement($node, $path, &$result, $noSplit)
    {
        if ($node->nodeType === XML_ELEMENT_NODE) {
            $path[] = $node->nodeName;
            if ($noSplit) {
                $result[] = $path;
            } else {
                $result[] = implode("/", $path);
            }
        }

        foreach ($node->childNodes as $childNode) {
            if ($childNode->nodeType === XML_ELEMENT_NODE) {
                traverseElement($childNode, $path, $result, $noSplit);
            }
        }
        if ($node->childNodes->length > 1) {
            if ($noSplit) {
                $result[] = $path;
            } else {
                $result[] = implode("/", $path);
            }
        }
    }

    $result = [];
    traverseElement($doc->documentElement, [], $result, $noSplit);

    $jsonResult = [];
    foreach ($result as $index => $xpath) {
        $lineNumber = $index + 1;
        $jsonResult[$lineNumber] = $xpath;
    }

    return $line ? data_get($jsonResult, $line) : $jsonResult;
}

PDF相关

前端预览

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<div id="pdf-content"></div>

<script src="/path/pdf.min.js"></script>
<script src="/path/pdf.worker.min.js"></script>
<script type="module">
function loadPdf(url, workerSrc, cid, scale = 0) {
    var pdfJsLib = window['pdfjs-dist/build/pdf'];

    pdfJsLib.GlobalWorkerOptions.workerSrc = workerSrc;

    // Asynchronous download of PDF
    var loadingTask = pdfJsLib.getDocument(url);
    loadingTask.promise.then(function (pdf) {
        var pagesCount = pdf.numPages;
        var container = document.getElementById(cid);
        container.innerHTML = '';
        for (var i = 1; i <= pagesCount; i++) {
            pdf.getPage(i).then(function (page) {
                var desiredWidth = container.offsetWidth;
                // console.log(desiredWidth)
                var viewport = page.getViewport({scale: 1,});
                if (scale === 0) {
                    scale = desiredWidth / viewport.width;
                }
                viewport = page.getViewport({scale: scale,});

                var canvas = document.createElement('canvas');
                var context = canvas.getContext('2d');
                canvas.height = viewport.height;
                canvas.width = viewport.width;

                var renderContext = {
                    canvasContext: context,
                    viewport: viewport
                };

                page.render(renderContext);
                var pageContainer = document.createElement('div');
                pageContainer.className = 'pdfPage';
                pageContainer.appendChild(canvas);
                container.appendChild(pageContainer);
            });
        }
    }, function (reason) {
        console.error(reason);
    });
}
loadPdf("/pdf/download/url", "/path/pdf.worker.min.js", "pdf-content");
</script>

合并

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from PyPDF2 import PdfMerger
import sys

def merge_pdfs(pdf_files,out_path):
    merger = PdfMerger()

    for file in pdf_files:
        merger.append(file)

    merger.write(out_path)
    merger.close()


# "python3 main.py test.pdf,test1.pdf output.pdf"
pdf_files = sys.argv[1] if len(sys.argv) > 1 else ''
out_path = sys.argv[2] if len(sys.argv) > 2 else ''
merge_pdfs(pdf_files.split(","),out_path)

高亮显示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import fitz
import sys

def read_clip(page,x0,y0,x1,y1):
    return page.get_text(clip=fitz.Rect(x0,y0,x1,y1))

def highlight(pdf_path,output_path,texts):
    # READ IN PDF
    doc = fitz.open(pdf_path)
    mupdf_page = doc.load_page(0)
    page_width = mupdf_page.rect.width
    hightOffset=15
    for page in doc:
        sites=[]
        for text in texts:
            sites.extend(page.search_for(text))
        for inst in sites:
            x0,y0,x1,y1=inst
            ys=y0
            next_text=str(read_clip(page,x0,y0+hightOffset,x1,y1+hightOffset)).strip()
            while next_text=="":
                next_row=str(read_clip(page,0,y0+hightOffset,page_width,y1+hightOffset)).strip()
                if next_row=="":
                    break
                y0=y0+hightOffset
                y1=y1+hightOffset
                next_text=str(read_clip(page,x0,y0+hightOffset,x1,y1+hightOffset)).strip()
            highlight=page.add_highlight_annot((-5,ys-2,page_width+5, y1))
            highlight.set_colors(stroke=[1, 0.94 ,0.4])
            highlight.update()

    # OUTPUT
    doc.save(output_path, garbage=4, deflate=True, clean=True)

# "python3 main.py in.pdf output.pdf needles_text"
in_pdf = sys.argv[1] if len(sys.argv) > 1 else ''
out_path = sys.argv[2] if len(sys.argv) > 2 else ''
needles_text = sys.argv[3] if len(sys.argv) > 3 else ''
highlight(in_pdf,out_path,needles_text.split(","))

PHP 代码技巧

blade模版

json_encode数组后的字符串可以在模版中直接用作数组

  • helps.php 代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
if (!function_exists('getPreviousMonth')) {
    function getPreviousMonth(): string
    {
        $currentDate = \Illuminate\Support\Carbon::now();
        $firstDayOfPreviousMonth = $currentDate->copy()->subMonth()->startOfMonth();
        $lastDayOfPreviousMonth = $currentDate->copy()->subMonth()->endOfMonth();

        return json_encode([$firstDayOfPreviousMonth->toDateString(), $lastDayOfPreviousMonth->toDateString()]);
    }
}
  • blade模版中可直接获取数组
1
{!! getPreviousMonth() !!}

新前端模版替换样式等资源的链接规则:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
href="((?!.*(?:https|http|html))[^{>"#]*)"
href="{{asset('theme/$1')}}"

src="((?!.*(?:https|http|html))[^{>"#]*)"
src="{{asset('theme/$1')}}"

52.073679870128096, 4.31522233905295

background
url\('?((?!.*(?:https|http|html))[^{>'"#]*)'?\)
url({{asset('UpConstruction-1.0.0/$1')}})

Carbon

时区

  • php内置函数获取所有时区
1
timezone_identifiers_list();
  • parse转换时区
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use Carbon\Carbon;
// 定义上海时区
date_default_timezone_set('Asia/Shanghai');
// 将字符串直接转为Carbon
Carbon::parse('2023-08-21 05:37:05')->format('Y-m-d H:i:s');
// 输出UTC时间
Carbon::parse('2023-08-21 05:37:05')->timezone('UTC')->format('Y-m-d H:i:s');
// 将字符串根据时区转为Carbon,等效于把一个Carbon转换,比如now()
Carbon::parse('2023-08-21 05:37:05 Europe/Amsterdam')->format('Y-m-d H:i:s');
// 输出UTC时间
Carbon::parse('2023-08-21 05:37:05 Europe/Amsterdam')->timezone('UTC')->format('Y-m-d H:i:s');
// 下方的时间字符串格式也可以获取UTC时间
Carbon::parse('2023-08-25T17:06:00Z')->format("Y-m-d H:i:s");
// 对比
Carbon::parse('2023-08-25T17:06:00Z')->tz('UTC')->format("Y-m-d H:i:s");
Carbon::parse('2023-08-25 17:06:00')->tz('UTC')->format("Y-m-d H:i:s");
https://static.duan1v.top/images/20230821153457.png https://static.duan1v.top/images/20230910003909.png
  • php保存时间到mysql
1
2
3
4
// 先根据系统的时区,或者前端传过来的时间+时区,转成UTC时区,再保存到mysql
Carbon::parse('2023-08-21 05:37:05')->timezone('UTC')->format('Y-m-d H:i:s');
// 等同于
Carbon::parse('2023-08-21 05:37:05')->utc()->format('Y-m-d H:i:s');
  • php从mysql读取时间
1
2
// 根据UTC时间转换字段,转成系统的时区,或者指定的其他时区
Carbon::parse('2023-08-21 05:37:05 UTC')->timezone('Asia/Shanghai')->format('Y-m-d H:i:s');
  • 关于mysql选用的字段,推荐datetime
Mysql的datetime和timestamp的区别
  • 范围:datetime 数据类型的范围是从 ‘1000-01-01 00:00:00’ 到 ‘9999-12-31 23:59:59’,而 timestamp 数据类型的范围是从 ‘1970-01-01 00:00:01’ UTC 到 ‘2038-01-19 03:14:07’ UTC。因此,datetime 支持更广泛的日期范围,而 timestamp 受限于 1970 年至 2038 年之间的范围。
  • 存储空间:datetime 数据类型占用固定的 8 字节存储空间,而 timestamp 数据类型占用 4 字节存储空间。
  • 自动更新功能:当插入或更新记录时,datetime 列不会自动更新,它将保留插入或更新时的值。而 timestamp 列具有自动更新功能,当插入或更新记录时,会自动更新为当前时间戳。
  • 时区处理:datetime 列不会自动转换时区,它存储的是直接输入的日期和时间值。而 timestamp 列会自动将日期和时间值从当前会话时区转换为 UTC 进行存储,并在检索时再将其转换回会话时区。
  • 获取格式化时区
1
$timezone = 'GMT' . now()->format("P");
  • 获取 2023-08-21T05:37:05+00:00 格式的时间
1
Carbon::parse('2023-08-21 05:37:05 UTC')->toW3cString();

时间差

  • 主要是注意第二个参数
1
2
3
4
5
use Carbon\Carbon;
//为正负数
Carbon::parse('2023-08-21')->diffInDays('2023-08-11', false); 
//为正负数的绝对值
Carbon::parse('2023-08-21')->diffInDays('2023-08-11', true);

注意

  • 由于时令的存在,会导致进入时令的那天的时间不是24小时,即两天的时间戳(strtotime()函数获取)之差/3600不是24

自定义的Debugbar

自定义logger

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind('customer_logger', function ($app) {
            $logger = new Logger('custom');
            $handler=new RotatingFileHandler(storage_path('logs/custom.log'),5, Logger::INFO);
            $formatter = new LineFormatter(
                '[%datetime%] %channel%.%level_name%: %message% %context% %extra%' . PHP_EOL,
                'Y-m-d H:i:s.u'
            );
            $handler->setFormatter($formatter);
            $logger->pushHandler($handler);
            return $logger;
        });
    }
}

新建Middleware

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?php

namespace App\Http\Middleware;

use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;

/**
 * Class QueryDebugMiddleware
 * @package App\Http\Middleware
 *
 */
class QueryDebugMiddleware
{
    public function handle($request, \Closure $next)
    {
        $logger = app('customer_logger');
        DB::listen(function ($query) use ($logger) {
            $caller = $this->getCallerInfo();
            $file = $caller['file'];
            $line = $caller['line'];

            // 在这里进行自定义的记录逻辑,例如使用日志记录
            $logger->info('SQL Query', [
                'fullSql' => vsprintf(str_replace('?', "'%s'", $query->sql), $query->bindings),
                'time'    => $query->time,
                'file'    => $file,
                'line'    => $line,
            ]);
        });

        return $next($request);
    }

    protected function getCallerInfo()
    {
        $queryFiles = [
            'Illuminate/Database/',
        ];
        $traces = collect(debug_backtrace())->pluck('line', 'file');
        $start = false;
        $end = true;
        foreach ($traces as $file => $line) {
            if (!$start) {
                $start = collect($queryFiles)->contains(function ($value) use ($file) {
                    return strpos($file, $value) !== false;
                });
            } else {
                $end = collect($queryFiles)->contains(function ($value) use ($file) {
                    return strpos($file, $value) !== false;
                });
            }
            if (!$end && $file && $line) {
                return [
                    'file' => $file,
                    'line' => $line,
                ];
            }
        }
        return [
            'file' => 'Unknown',
            'line' => 'Unknown',
        ];
    }
}

在线上需要观察的方法中添加

1
2
3
4
    public function __construct()
    {
        $this->middleware(QueryDebugMiddleware::class)->only('functionName');
    }

文件

文件上传限制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
代码里设置这个
ini_set('memory_limit',-1);
ini_set("max_execution_time", "500");
set_time_limit(500);

php配置里
file_uploads = On

; Temporary directory for HTTP uploaded files (will use system default if not
; specified).
; https://php.net/upload-tmp-dir
;upload_tmp_dir =

; Maximum allowed size for uploaded files.
; https://php.net/upload-max-filesize
upload_max_filesize = 50M

; Maximum number of files that can be uploaded via a single request
max_file_uploads = 20

post_max_size = 50M

nginx里设置这个
proxy_connect_timeout  500s;
proxy_send_timeout  500s;
proxy_read_timeout  500s;
fastcgi_connect_timeout 500s;
fastcgi_send_timeout 500s;
fastcgi_read_timeout 500s;


# client_max_body_size 用来修改允许客户端上传文件的大小。默认为1m,如果设置为0,表示上传文件大小不受限制。
# 可以在以下模块设置: http, server, location
client_max_body_size 10m;

迁移文件

对于laravel较旧的版本(5之前吧),无法直接指定迁移文件,但老的迁移文件又有问题,无法维护,可以新建文件夹,指定新建的文件夹进行迁移

1
php artisan migrate --path=database/migrations/new

Ubuntu监控目录

注意

  • 业务是监控目录,生成nginx配置

  • 主机需要安装 inotify-tools ,nginx

1
sudo apt-get install inotify-tools nginx -y
  • 将docker项目保存nginx配置的目录挂载到主机,主机目录需要777权限

相关文件,根目录为 root

  • build-web.sh
1
2
3
4
5
6
7
8
9
#!/bin/bash

dir=$1

dir2=$2

sleep 1
cp "$dir/$dir2/default.conf" "/etc/nginx/sites-enabled/$dir2-my-laravel-project.com"
service nginx restart
  • web-dir-monitor.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/bin/sh

# 监视的文件或目录
filename=$1

# 监视发现有增、删、改时执行的脚本
script=$2

inotifywait -mq --format '%f' -e create  $filename | while read line
  do
      bash $script $filename $line
  done
  • nginx配置示例
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
server {
  listen 10001;
  # server_name  ~^(?<subdomain>.+)\.test_domain\.com$;
  # root   "/$subdomain";
  root   "/home/laravel-project/storage/app/websites/1";
  index index.html index.htm;
  location / {
    try_files $uri $uri/ =404;
  }
}
  • 执行命令
1
bash web-dir-monitor.sh /home/laravel-project/storage/app/websites build-web.sh
  • 当然也可以和supervisor一起食用

PHP 错误解决经验

查看日志

查看可疑的僵尸进程

1
ps aux|grep php

laravel中

打印sql

1
2
$fullSql = vsprintf(str_replace('?', '%s', $query->toSql()), $query->getBindings());
dd($fullSql);

输出日志到指定文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
\Illuminate\Support\Facades\DB::enableQueryLog();

# 代码块

$queries = \Illuminate\Support\Facades\DB::getQueryLog();
foreach ($queries as $query) {
    $sql = $query['query'];
    $bindings = $query['bindings'];

    // 将 SQL 查询参数拼接为完整的 SQL 查询语句
    $fullSql = vsprintf(str_replace('?', "'%s'", $sql), $bindings);
    // 将完整的 SQL 查询语句写入日志文件
    $logger = new Logger('custom');
    $logger->pushHandler(new StreamHandler(storage_path('logs/custom.log'), Logger::INFO));

    $logger->info($fullSql);
}

重启队列,清除缓存

1
2
3
4
5
6
7
8
9
# php artisan schedule:finish {id}
php artisan config:clear
php artisan view:clear
php artisan cache:clear
composer dump-autoload
# laravel9中才有
# php artisan schedule:clear-cache

php artisan queue:restart

查看队列积存

  • 古老版queue:work常驻内存的方法:forever,现在一般都是supervisor
1
forever start -c php artisan queue:work --tries=1 --queue=default,high
  • 查看队列积存,先进入redis-cli;默认队列,可以在 config/queue.php 中查看
1
2
3
4
# default队列的长度
llen queues:default
# default队列前10条job
lrange queues:default 0 10
  • 清空redis中的指定job的剩余队列:
1
redis-cli LRANGE "queues:QueueName" 0 -1 | grep "JobName" | while IFS= read -r line; do printf "%q" "$line" | xargs -I {} redis-cli LREM "queues:QueueName" 0 '{}'; done

手动执行定时脚本中的命令

1
php artisan 命令签名

重启php,nginx,cron,supervisor等服务

关于supervisor服务,参考Supervisor的使用

关于cron,查看定时脚本的用户

1
2
3
4
5
sudo crontab -l -u 用户名

sudo service cron restart

* * * * * php7.4 /mnt/g/Workplace/explore/php/blog/artisan schedule:run >> /dev/null 2>&1

关于重启fpm进程

  • 现在fpm主进程中找到保存pid的文件
https://static.duan1v.top/images/20230816102552.png
  • 然后使用SIGUSR2重启
1
2
3
4
kill -SIGUSR2 `cat /www/server/php/72/var/run/php-fpm.pid`

# 也可以启动时指定pid的文件
sudo /usr/sbin/php-fpm7.4 -c /etc/php/7.4/fpm/php-fpm.conf -y /run/php/php7.4-fpm.pid

关于重新加载nginx配置

1
/usr/bin/nginx -s reload

查看进程相关信息

https://static.duan1v.top/images/20230816103313.png

查看端口相关信息

1
2
3
netstat -tunlp | grep 端口号
# or
lsof -i:端口号

502 bad gateway

  • 本地wsl有时候会莫名其妙报这个
  • 先重启nginx,再重启php-fpm

清理服务器磁盘

1
sudo du -sh /home/ubuntu/* | sort -rn | head