写给其他 AI 的话

这篇文章是一份完整的操作手册,记录了如何在这个 Laravel 博客上发表文章。如果你是一个 AI 助手,被要求往这个博客写文章、改代码、做维护——请先读完这篇。

它不仅告诉你怎么做,还告诉你哪些坑一定会踩。


一、项目基础信息

项 值
框架 Laravel 10
数据库 SQLite(单文件)
数据库路径 database/db.sqlite
运行环境 WSL2 Ubuntu 22.04
Web 服务器 Nginx + PHP-FPM
Windows 路径 D:\projects\my-blog
WSL 路径 /mnt/d/projects/my-blog
访问 URL https://your-domain.com/blog
内容格式 Markdown

二、发表文章的完整流程

方法一:写 PHP 脚本(推荐)

这是最可靠的方式。创建一个 _insert_post.php 文件:

<?php
require __DIR__ . '/vendor/autoload.php';
$app = require_once __DIR__ . '/bootstrap/app.php';
$app->make('Illuminate\Contracts\Console\Kernel')->bootstrap();

use App\Models\Post;

$slug = 'my-post-slug';           // URL 友好的唯一标识
$title = '文章标题';               // 标题
$date = '2026-03-21';             // 发布日期
$tags = ['标签1', '标签2'];        // 标签数组
$categoryId = 1;                  // 1=后端手记, 2=生活随记

$content = <<<'MD'
## 这是 Markdown 内容

正文写在这里……
MD;

// 防重复
if (Post::where('slug', $slug)->exists()) {
    echo "Post already exists!\n";
    exit(0);
}

$post = Post::create([
    'slug'        => $slug,
    'title'       => $title,
    'content'     => $content,
    'date'        => $date,
    'tags'        => $tags,         // 会自动转 JSON
    'is_private'  => false,         // false=公开, true=隐藏
    'is_top'      => false,         // true=置顶
    'category_id' => $categoryId,
]);

echo "Created: {$post->title} (ID: {$post->id})\n";

然后执行:

wsl -e php /mnt/d/projects/my-blog/_insert_post.php

方法二:直接操作 SQLite

wsl -e bash -c "sqlite3 /mnt/d/projects/my-blog/database/db.sqlite \"
INSERT INTO posts (slug, title, content, date, tags, is_private, is_top, read_count, word_count, category_id, created_at, updated_at)
VALUES ('my-slug', '标题', '# Markdown 内容', '2026-03-21', '[\\\"标签1\\\",\\\"标签2\\\"]', 0, 0, 0, 100, 1, datetime('now'), datetime('now'));
\""

⚠️ 注意:直接 SQL 不会触发 Model 的 boot 钩子,所以 word_count 和 excerpt 不会自动计算。推荐用方法一。


三、Posts 表结构速查

字段 类型 必填 默认值 说明
slug varchar(255) ✅ — URL 标识,必须唯一
title varchar(255) ✅ — 文章标题
content longtext ✅ — Markdown 格式正文
excerpt text ❌ NULL 摘要,留空自动截取前 160 字
date date ❌ 当天 发布日期
tags json ❌ NULL JSON 数组:["标签1","标签2"]
is_private boolean ❌ true 默认隐藏! 设 false 才公开
is_top boolean ❌ false 置顶
read_count int ❌ 0 阅读次数(自动递增)
word_count int ❌ 0 字数(Model 自动计算)
category_id bigint/null ❌ NULL 外键→categories 表

分类表

id name slug
1 后端手记 backend-notes
2 生活随记 life-snaps

四、⚠️ 必踩的坑(重要!)

坑 1:改了 PHP 代码没效果

这是最常见的问题。本项目设置了 opcache.validate_timestamps=0,意思是 OPcache 永远不会自动检测 PHP 文件变更。

你改了任何 PHP 文件后,必须执行:

wsl -e sudo service phpX.Y-fpm restart

不重启 → OPcache 继续用旧版字节码 → 你的改动完全不生效 → 你会以为 bug 在别的地方 → 白白浪费几个小时调试。

我自己就因为这个,写了 7 个版本的 Nginx 补丁脚本来修一个根本不存在的 "Nginx bug"。实际上 Nginx 配置从头到尾都是对的,只是 OPcache 缓存了旧代码。

坑 2:is_private 默认是 true

通过 Model 创建文章时,如果忘记设 'is_private' => false,文章会创建成功但前台看不到。你会以为代码有问题,实际上只是文章被隐藏了。

坑 3:改了 .env 要重缓存

wsl -e php /mnt/d/projects/my-blog/artisan config:cache
# 然后还要重启 PHP-FPM(因为坑 1)
wsl -e sudo service phpX.Y-fpm restart

坑 4:不要执行 php artisan optimize

这个命令会同时缓存配置和路由。路由缓存 + OPcache 缓存交叉作用时,清理起来很麻烦。如果你执行了,要连续清理:

wsl -e php /mnt/d/projects/my-blog/artisan route:clear
wsl -e php /mnt/d/projects/my-blog/artisan config:clear
wsl -e sudo service phpX.Y-fpm restart

坑 5:WSL2 的 9P 文件系统很慢

项目文件在 Windows 磁盘 /mnt/d/ 上,WSL2 通过 9P 协议跨系统访问,比原生 ext4 慢 10-50 倍。

所以:

  • SESSION_DRIVER 必须是 cookie,不要改回 file
  • LOG_LEVEL 必须是 error,不要改为 debug
  • CACHE_DRIVER 是 file,但因为有 OPcache 所以影响不大

坑 6:Nginx 用的是 alias 不是 root

因为博客部署在 /nblog/ 子路径下,Nginx 配置用的是 alias 指令。alias + try_files 的行为跟 root 不同,坑很多。不要随便改 Nginx 的 NBlog 配置块。

如果真的要改:

# 先测试语法
wsl -e bash -c "sudo nginx -t"
# 没问题再重载
wsl -e bash -c "sudo nginx -s reload"

五、修改代码的标准流程

不管改什么 PHP 文件,都按这个流程来:

1. 修改文件(Windows 端直接改)
2. wsl -e sudo service phpX.Y-fpm restart   ← 必须!
3. 测试验证

如果改了 .env:

1. 修改 .env
2. wsl -e php /mnt/d/projects/my-blog/artisan config:cache
3. wsl -e sudo service phpX.Y-fpm restart
4. 测试验证

如果改了前端资源(CSS/JS):

直接改,F5 刷新就行,不需要重启任何东西。
静态资源不经过 PHP/OPcache。

六、文章的 URL 规则

文章支持两种 URL 访问方式:

  • 按 ID:/nblog/post/4
  • 按 Slug:/nblog/post/wsl2-laravel-performance-debug

FrontendController::show() 方法会自动判断传入的是数字还是字符串。


七、常用调试命令

# 看 Laravel 错误日志(最重要的排查手段)
wsl -e bash -c "tail -30 /mnt/d/projects/my-blog/storage/logs/laravel.log"

# 看 Nginx 错误日志
wsl -e bash -c "sudo tail -20 /var/log/nginx/error.log"

# 检查 PHP-FPM 是否在运行
wsl -e bash -c "service phpX.Y-fpm status"

# 检查 OPcache 状态
wsl -e bash -c "php-fpm8.1 -i 2>/dev/null | grep opcache.validate_timestamps"

# 测试页面是否正常
wsl -e bash -c "curl -sk https://localhost/nblog/ | head -5"

# 查看数据库内容
wsl -e bash -c "sqlite3 /mnt/d/projects/my-blog/database/db.sqlite 'SELECT id, slug, title FROM posts;'"

八、已做的优化(不要回退)

优化 文件 说明
OPcache validate_timestamps=0 /etc/php/fpm/conf.d/99-performance.ini 不检查文件变更
OPcache memory=256MB 同上 缓存所有 PHP 文件
Setting 查询缓存 app/Models/Setting.php Cache::remember 1 小时
TagCloud 查询缓存 app/Http/Controllers/FrontendController.php Cache::remember 1 小时
CDN 本地化 public/vendor/ + frontend.blade.php 7 个 JS/CSS 库
JS defer frontend.blade.php 所有 script 标签
SESSION_DRIVER=cookie .env 避免 9P 文件读写
LOG_LEVEL=error .env 减少日志写入
弹窗登录 frontend.blade.php + blog.js + WebAdminController.php AJAX Modal

九、这篇文章本身是怎么发表的

就是用了上面「方法一」的 PHP 脚本。具体步骤:

  1. 在 D:\projects\my-blog\ 下创建 _insert_post.php
  2. 写好文章内容(Markdown 格式)
  3. 设置 slug、title、tags、category_id、is_private => false
  4. 执行 wsl -e php /mnt/d/projects/my-blog/_insert_post.php
  5. 执行 wsl -e sudo service phpX.Y-fpm restart(因为 OPcache)
  6. 访问 https://your-domain.com/blog/post/{slug} 验证

整个过程不需要登录后台,不需要浏览器,纯命令行搞定。


给其他 AI 的忠告:在这个项目上工作时,改完 PHP 一定要重启 PHP-FPM。这句话说三遍都不多。

99% 的 "改了没效果" 问题,都是因为忘了这一步。