一步两步是魔鬼的步伐

Duan1v's Blog

执着于真相,享受追踪思维漏洞的过程|

PHP 代码技巧

  • 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')}})
  • 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
 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;
        });
    }
}
 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;
1
php artisan migrate --path=database/migrations/new
1
2
3
4
5
6
$bindings = $query->getBindings();

// 替换占位符为实际的值
$fullSql = str_replace('?', '"%s"', $query->toSql());
$fullSql = vsprintf($fullSql, $bindings);
dd($fullSql);
1
2
3
4
5
6
7
8
9
> $qdm=new \App\Http\Middleware\QueryDebugMiddleware();
= App\Http\Middleware\QueryDebugMiddleware {#6075}

> $qdm->handle(request(), fn()=>1);
= 1

> \App\Http\Models\InboundScanModel::with('packages')->where('id',111859)->get();

# sudo tail -f storage/logs/custom-2025-09-04.log
1
2
3
composer require php-mime-mail-parser/php-mime-mail-parser:7.1.0 --no-update

composer update php-mime-mail-parser/php-mime-mail-parser --with-dependencies --ignore-platform-reqs

Ubuntu监控目录

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

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

1
sudo apt-get install inotify-tools nginx -y
  • 将docker项目保存nginx配置的目录挂载到主机,主机目录需要777权限
  • 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
  • 其他事件监听示例
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 包含CLOSE_WRITE字样的事件,表示文件写入已完成,比如ftp上传文件后,完成上传操作,再进行其他操作,可以用到
inotifywait -m /home/vsftpd/dywily/ -e create -e close_write |
while read path action file; do
    if [[ "$action" == "CREATE" ]]; then
        echo "File '$file' was created."
    elif [[ "$action" == *"CLOSE_WRITE"* ]]; then
        echo "File '$file' has been written and closed."
    fi
done

# 所有事件
inotifywait -m /home/vsftpd/dywily/ -e access -e modify -e attrib -e close_write -e close_nowrite -e open -e moved_from -e moved_to -e create -e delete -e delete_self -e move_self |
while read path action file; do
    echo "Event: $action on file: $file in directory: $path"
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
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 命令签名
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主进程中找到保存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
1
/usr/bin/nginx -s reload
https://static.duan1v.top/images/20230816103313.png
1
2
3
netstat -tunlp | grep 端口号
# or
lsof -i:端口号
  • 本地wsl有时候会莫名其妙报这个
  • 先重启nginx,再重启php-fpm
1
sudo du -sh /home/ubuntu/* | sort -rn | head

Ubuntu常用命令

  • 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
  • pattern指基本正则表达式(Basic Regular Expression,BRE)
  • pattern 是一种简化的正则表达式语法,通常用于基本的模式匹配。
  • pattern 可以包含普通字符、通配符和一些特殊字符,如 .、*、? 等。
  • pattern 不支持高级的正则表达式特性,如分组 ( )、选择符 |、定位符 ^ 和 $ 等。
Regex
  • regex 指扩展正则表达式(Extended Regular Expression,ERE)
  • regex 是正则表达式的完整语法,支持更广泛的模式匹配和高级特性。
  • regex 可以使用普通字符、元字符、量词、字符类、分组、引用等来定义复杂的模式。
  • regex 支持在模式中使用特殊字符和元字符进行更精确的匹配和操作。
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
  • 示例
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 选项可以递归搜索指定目录及其子目录中的文件。
  • 示例
 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
参考
  • 添加用户
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

Load data导入csv文件

  • 数据量较大且需要较高的导入效率
  • mysqlimport本质就是对load data语句的封装
  • 本次使用的csv文件是50m,36w行,12列
  • 创建数据表
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
CREATE TABLE `my_user` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  `age` tinyint NOT NULL DEFAULT '0',
  `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  `country` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  `type` tinyint NOT NULL DEFAULT '0',
  `job` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  `brief` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
  • 准备csv文件,可以使用Navicat自动填充数据,再导出成csv,我的csv名为 my_user.csv

  • csv文件中的列排序(注意比数据表中多了一列birthday)

https://static.duan1v.top/images/20230807201234.png
  • mysql的配置 /etc/mysql/mysql.conf.d/mysqld.cnf[mysqld] 选项添加配置,重启mysql
1
 secure-file-priv=/tmp
  • csv文件需要放在上面的目录(/tmp)下

  • sql语句

1
LOAD DATA INFILE '/tmp/my_user.csv' INTO TABLE my_user FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 1 LINES (@dummy,name,age,email,country,type,@dummy,job,@dummy,@dummy,@dummy,@dummy) SET brief='这个人很低调.';
  • 括号中的列需要和csv中的列一致(顺序和数量)
  • 将数据表中需要导入的列对应写在括号内,不需要导入的数据表的列,用 @dummy 占位
  • 使用SET 指定列(brief)的值
https://static.duan1v.top/images/20230807205432.png
  • 使用的是 libreoffice
1
libreoffice --version
  • 使用的命令是
1
cd /tmp && soffice --headless --convert-to csv:"Text - txt - csv (StarCalc)":"44,34,0,1,1/1" my_user.xlsx --outdir ./
  • 可能报错 javaldx failed! ,这主要就是,在正式环境中,没有设置执行用户的home文件夹.记得重启容器!! 假设执行用户为 nobody
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 创建一个目录,例如 /home/nobody
mkdir -p /home/nobody/.config/libreoffice

# 更改目录的所有者和权限
chown -R nobody:nogroup /home/nobody
chmod -R 755 /home/nobody

apk --no-cache add shadow sudo
# error: javaldx failed! 主要就是没有设置执行用户的home文件夹.记得重启容器!!
usermod -d /home/nobody nobody
  • 如果执行转换命令不输出任何东西,可能是/tmp文件夹权限问题
1
sudo chmod 777 /tmp
  • python的Modin+Dask待研究

google api 授权及使用 总结

https://static.duan1v.top/images/20230216223857.pnghttps://static.duan1v.top/images/20230216224808.png
  • 个人账号只能配置为外部用户,需要配置测试用户及发布为正式应用;google suit的用户可以配置为内部用户
  • 第一页配置些应用的基本信息
  • 第二页配置的是授权后,应用内可以调用的接口
  • 个人账号只能配置为外部用户,需要配置测试用户,可以是自己的账号
https://static.duan1v.top/images/20230216225207.png
  • 需要通过下载的凭据生成授权跳转链接,获取code,生成token.json

  • 新建web应用类型的凭据,并配置授权完成后的跳转地址,可以写需要的授权那个页面地址,本地可以配置为localhost

  • 下载凭证

https://static.duan1v.top/images/20230216230330.png https://static.duan1v.top/images/20230217000810.png
  • 本意是通过公私钥匹配,使应用中可以在服务端直接调用接口,无需进行授权交互

  • 创建完成后要继续编辑,添加密钥,及配置全网域(个人账号无法配置)

https://static.duan1v.top/images/20230216231734.png https://static.duan1v.top/images/20230216233017.png
1
composer require google/apiclient:^2.12.1
 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
<?php

require __DIR__ . '/vendor/autoload.php';

use Google\Client;
use Google\Service\Gmail;

function getLabels()
{
    $client = new Client();
    $client->setApplicationName('Gmail API PHP Quickstart');
    $client->setScopes('https://www.googleapis.com/auth/gmail.readonly');
    $client->setAuthConfig("client_secret.json");
    $client->setAccessType('offline');
    $client->setPrompt('select_account consent');
    $tokenPath = "token.json";
    if (file_exists($tokenPath)) {
        $accessToken = json_decode(file_get_contents($tokenPath), true);
        $client->setAccessToken($accessToken);
    } else if ($authCode = $_GET["code"]) {
        // Exchange authorization code for an access token.
        $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
        $client->setAccessToken($accessToken);

        // Check to see if there was an error.
        if (array_key_exists('error', $accessToken)) {
            throw new \Exception(join(', ', $accessToken));
        }
        // Save the token to a file.
        if (!file_exists(dirname($tokenPath))) {
            mkdir(dirname($tokenPath), 0700, true);
        }
        file_put_contents($tokenPath, json_encode($client->getAccessToken()));
    }

    // If there is no previous token or it's expired.
    if ($client->isAccessTokenExpired()) {
        // Refresh the token if possible, else fetch a new one.
        if ($client->getRefreshToken()) {
            $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
        } else {
            // Request authorization from the user.
            $authUrl = $client->createAuthUrl();
            header("Location: " . $authUrl);
        }
    }
    try {
        $service = new Gmail($client);
        // Print the labels in the user's account.
        $user = 'me';
        $results = $service->users_labels->listUsersLabels($user);

        if (count($results->getLabels()) == 0) {
            print "No labels found.\n";
        } else {
            print "Labels:\n";
            foreach ($results->getLabels() as $label) {
                printf("- %s\n", $label->getName());
            }
        }
    } catch (\Exception $e) {
        echo "Message: " . $e->getMessage();
    }
}

getLabels();
  • 页面授权
https://static.duan1v.top/images/20230217001106.png https://static.duan1v.top/images/20230217001518.png
1
2
3
$gmailService = new Google_Service_Gmail($client);

$labels = $gmailService->users_labels->listUsersLabels("me");
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
PHP Fatal error:  Uncaught Google\Service\Exception: {
  "error": {
    "code": 400,
    "message": "Precondition check failed.",
    "errors": [
      {
        "message": "Precondition check failed.",
        "domain": "global",
        "reason": "failedPrecondition"
      }
    ],
    "status": "FAILED_PRECONDITION"
  }
}
https://static.duan1v.top/images/20230217005004.png