一步两步是魔鬼的步伐

Duan1v's Blog

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() !!}

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

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
# 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

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

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

Ubuntu常用命令

将文件A的内容写入到文件B中

  • cat A > B (追加的话, > 改成 >>)

  • sed -n ‘w B’ A

  • 效率对比

1
2
3
start=$(date +%s%N) && cat /tmp/784-35445384-1692024995-complete.json >> test.log && end=$(date +%s%N) && echo $((($end - $start) / 1000000))

start=$(date +%s%N) && sed -n 'w test.log' /tmp/784-35445384-1692024995-complete.json && end=$(date +%s%N) && echo $((($end - $start) / 1000000))
https://static.duan1v.top/images/20230816115547.png

查找内容

pattern和regex

Pattern
  • pattern指基本正则表达式(Basic Regular Expression,BRE)
  • pattern 是一种简化的正则表达式语法,通常用于基本的模式匹配。
  • pattern 可以包含普通字符、通配符和一些特殊字符,如 .、*、? 等。
  • pattern 不支持高级的正则表达式特性,如分组 ( )、选择符 |、定位符 ^ 和 $ 等。
Regex
  • regex 指扩展正则表达式(Extended Regular Expression,ERE)
  • regex 是正则表达式的完整语法,支持更广泛的模式匹配和高级特性。
  • regex 可以使用普通字符、元字符、量词、字符类、分组、引用等来定义复杂的模式。
  • regex 支持在模式中使用特殊字符和元字符进行更精确的匹配和操作。

vim test

1
2
3
4
5
6
7
8
John Doe 25 8900
Jane Smith 30 6200
David Johnson 23 3500
Mia Nguyen 56 4200
Mohammed Ali 45 5800
Olivia Kim 40 2300
Olivia Ali 34 550
David Kim 36 6000

grep

  • 示例
1
2
grep -i -n "d" test
grep -i -n -E "6$" test
https://static.duan1v.top/images/20230821093233.png
Tips
  • 使用 -E 选项可以启用regex,否则只支持pattern;
  • 使用 -n 选项可以在输出结果中显示匹配行的行号;
  • 使用 -i 选项可以忽略搜索时的大小写区分;
  • 使用 -r 选项可以递归搜索指定目录及其子目录中的文件。

awk

  • 示例
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
awk '{print}' test
awk '{print $2}' test
awk '{sum += $3} END {print sum}' test
awk '$3 > 40 {print}' test
awk '$3 > 40 { if (counter < 1) { print; counter++ } }' test # 只取满足条件的第一行
awk -F'o' '{print $1"-"$2}' test
awk '{if ($3 > 30) print "Large"; else print "Small"}' test
awk '{sum = 0; for (i = 3; i <= NF; i++) sum += $i; print sum}' test
awk '/^O/ {print}' test
awk '{gsub(/O|o/, "H"); print}' test # 将O或o,替换为H
https://static.duan1v.top/images/20230821100530.png
参考

新增SFTP用户

示例

  • 添加用户
1
2
3
4
sudo addgroup sftp_test_group
sudo useradd -g sftp_test_group -m sftp_test_user1
# 键入密码,生成密码:https://www.sexauth.com/
sudo passwd sftp_test_user1
https://static.duan1v.top/images/20230821111222.png
  • 可能用到的根据用户查询组内其他用户
1
2
3
groups sftp_test_user1
getent group sftp_test_group
awk -F":" '{print $1"|"$4}' /etc/passwd | grep -n -E "\|1003$"
https://static.duan1v.top/images/20230821111438.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
sudo vim /etc/ssh/sshd_config
# 追加以下选项
AllowUsers sftp_test_user1
Match User sftp_test_user1
ChrootDirectory /var/sftp/sftp_test_user1
ForceCommand internal-sftp

sudo mkdir /var/sftp/sftp_test_user1
sudo chown -R sftp_test_user1:sftp_test_group /var/sftp/sftp_test_user1

sudo service ssh restart