news 2026/9/11 11:46:19

纯PHP打造轻量级NAS:无数据库实现文件共享与WebDAV挂载

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
纯PHP打造轻量级NAS:无数据库实现文件共享与WebDAV挂载

折腾 NAS 的朋友都清楚,群晖、威联通、飞牛这些商业系统功能确实全面,但如果你只是想把家里或者小团队里的几台电脑文件集中起来,再配合 rclone 这类工具挂载成本地磁盘访问,用一台旧电脑、树莓派,甚至一台常年不关机的云主机,跑一个纯 PHP 写的小服务反而更省心。这篇文章就来聊聊如何不依赖数据库,用 PHP 从零搭一个真正能用的轻量级私有 NAS。

先说清楚这个东西能干什么。整套系统只依赖 PHP 运行环境和文件系统,不需要 MySQL、不需要 SQLite,连 Composer 都可以不用。它支持文件上传下载、目录浏览、分享链接、图片缩略图预览,还能通过 WebDAV 协议被 rclone、Windows 文件资源管理器等客户端直接挂载成本地磁盘。对想在内网快速搭一套文件管理服务、又不想维护复杂环境的人来说,这是一个非常划算的路线。

1. 为什么用纯 PHP 做私有 NAS

1.1 轻量级 NAS 的两条路线

自建 NAS 大致有两条路线。第一条是上商业系统或者开源全家桶,比如群晖 DSM、飞牛 fnOS,或者 Nextcloud、Seafile 这类带数据库和后台任务的项目。这类系统功能完善,搜索、权限、文件版本、移动端 App 都有,但代价是要求硬件资源不低,部署后还要维护数据库、缓存服务、后台队列,对于只是想集中存点资料的人来说确实有点重。

第二条路线就是极简方案。只要系统能跑 PHP,就能把一个目录变成带 Web 界面的网络存储。数据全部平铺在磁盘上,元数据要么从文件系统实时读取,要么用几个 JSON 文件保存,整个过程没有任何常驻进程之外的状态管理。好处很明显:迁移一个目录就能迁移整个系统,备份直接复制文件,几乎不存在“数据库损坏导致全盘服务不可用”的情况。

我最早是在一台树莓派 3B 上跑这套方案的。1GB 内存装完系统后还要跑 PHP-FPM,剩余资源依然很充裕。后来换到一台老笔记本上,把硬盘换成 SSD 之后,局域网内跑满千兆基本没什么压力。对比之前用过的开源文档管理系统,资源占用少了不止一个量级。

1.2 “无数据库”的取舍与适用边界

很多朋友一听到“无数据库”就觉得不靠谱,担心文件索引、分享信息、用户数据没地方放。实际上文件管理类场景里,数据库并不是必需品。目录树本身就是天然的索引结构,文件名和路径就是元数据。查询某个文件是否存在,file_exists()一条函数就够了,根本不需要去数据库里跑一条 SELECT。

那放弃了数据库,代价是什么?首先是没有办法做全盘内容搜索,比如根据文件内容里的某个关键词去找文件。其次,如果以后要支持多用户、细粒度权限、文件版本回溯,纯文件方案会越来越吃力。最后,并发写入同一个 JSON 文件时会有概率丢数据,所以多写并发高的场景不适合这套方案。

适用边界其实很清晰:个人家庭存储、三五人的小团队内部文件共享、开发测试环境里的临时文件交换、以及需要被 WebDAV 客户端挂载的外部存储。只要不是要做一个几十上百人的企业网盘,这套东西完全够用。

2. 整体设计与技术选型

2.1 核心功能清单

在动手写代码前,先列出这套轻量级 NAS 必须具备的功能,避免写着写着就跑偏。

功能模块说明优先级
Web 目录浏览列出目录下的文件与文件夹,显示大小和修改时间必须
文件上传支持普通文件提交,大文件可分块上传必须
文件下载支持原文件下载和目录打包下载必须
文件管理重命名、删除、移动和复制推荐
图片预览自动生成缩略图,Web 端可直接预览可选
视频音频播放前端用 HTML5 标签直接播放,后端透传文件推荐
分享链接生成带随机 Token 的临时下载链接必须
WebDAV 协议供 rclone、Windows 等客户端挂载为本地磁盘推荐
访问控制登录认证、目录隔离、路径穿越防护必须

这些功能里,最难的是 WebDAV 协议支持。很多自建的 PHP NAS 死在 WebDAV 这一关,因为 PROPFIND 要返回正确的 XML 结构,上传、删除、移动等方法和 HTTP 状态码必须严格匹配,否则 rclone 不会正常工作。后面我会专门用一整节来写这块的实现。

2.2 目录结构与数据存储

整个系统的目录设计围绕“程序文件与存储文件分离”的原则,避免把用户数据混在代码目录里,也方便备份。

nas/ ├── index.php # Web 入口与页面路由 ├── api.php # 上传、下载、分享、管理等接口 ├── webdav.php # WebDAV 处理器 ├── functions.php # 公共函数,如路径安全校验 ├── config.php # 配置文件,用户和 Token 都在这里 ├── data/ # 用户存储根目录 │ ├── docs/ │ ├── photos/ │ └── software/ └── shares/ # 分享链接元数据目录 ├── share_abc123.json └── share_def456.json

用户数据全部放在data/下面,shares/目录单独存放分享链接的 JSON 文件,格式很简单,大概是这样的:

{ "token": "abc123", "path": "/docs/工作总结.pdf", "expire_at": "2025-12-31 23:59:59", "created_at": "2025-01-01 10:00:00" }

无数据库不等于无状态,分享链接这类信息还是需要持久化的,只是存储介质从数据库换成了 JSON 文件。每个分享链接对应一个文件,不用关心索引问题,文件名就是天然搜索键。

2.3 安全模型设计

这类系统最容易被人担心的是安全问题。我设计安全模型时重点考虑了三个点。

第一是路径穿越防护。所有从 URL 参数或请求体中拿到的路径,必须经过一个统一的白名单校验函数,使用realpath()解析后确认目标在允许的根目录以内,否则直接拒绝。这一条能挡住../../etc/passwd之类的经典攻击。

第二是访问认证。Web 管理端采用 Token 认证,登录成功后把 Token 放在请求头X-Auth-Token或者 Cookie 里。WebDAV 客户端无法设置自定义头,就采用 HTTP Basic Auth,使用同样的账号密码进行校验,校验通过后组装出对应的内部 Token。

第三是上传文件类型限制。严格模式下一律禁止上传 PHP、phtml、php5 等可执行脚本,哪怕用户就是想让服务器跑脚本,也应该把可执行目录单独隔离开,与数据目录区分开。

3. 核心代码实现与实操细节

3.1 入口与路由设计

整个系统的入口分成两个:index.php负责 Web 页面展示,api.php负责 JSON 接口,webdav.php统一处理所有 WebDAV 请求。用 PHP 内置服务器调试的时候,直接运行:

php -S 0.0.0.0:8080 index.php

如果要用一个入口处理所有路由,需要在.htaccess或者 Nginx 配置里把请求重写到index.php,再在里面做路由分发。但我在实际项目中更喜欢拆分成多个入口,这样不同服务之间互不干扰,WebDAV 请求长连接也不会占用 Web 页面的 PHP-FPM 进程数。

functions.php里有一个核心的安全校验函数:

function safe_path(string $input, string $root): ?string { $root = realpath($root); $full = realpath($root . '/' . ltrim($input, '/')); if ($full === false) { // realpath 返回 false 说明目标不存在,尝试检查父目录 $parent = realpath(dirname($root . '/' . ltrim($input, '/'))); if ($parent === false || strpos($parent, $root) !== 0) { return null; } return $root . '/' . ltrim($input, '/'); } if (strpos($full, $root) !== 0) { return null; } return $full; }

这个函数把用户输入解析成绝对路径后,再检查是否在允许的根目录内。对于不存在的目录或文件,realpath()会返回 false,所以需要退回检查父目录,同时还要额外处理一次..的情况。

3.2 用户认证与 Token 机制

用户和密码直接写在config.php里,密码用password_hash()生成哈希值存储,不保存明文。

// config.php return [ 'username' => 'admin', 'password_hash' => '$2y$10$...', // password_hash('yourpassword', PASSWORD_DEFAULT) 生成 'web_token' => 'a1b2c3d4e5f6a7b8a9b0c1d2e3f4a5b6', 'storage_root' => __DIR__ . '/data', 'share_root' => __DIR__ . '/shares', 'max_upload_size' => 2 * 1024 * 1024 * 1024, // 2GB ];

登录接口的逻辑很直接,从 JSON 请求体里接收用户名和密码,校验通过后返回固定的web_token。这个 Token 不用每次登录重新生成,因为这是单用户或者核心用户群固定的场景,不是公网开放注册的论坛。如果有人觉得这样不够安全,可以在 Token 里拼接时间戳再哈希,不过会牺牲一部分使用便利性。

WebDAV 的 Basic Auth 校验稍微繁琐一点,需要在每次请求时读取Authorization头,解析用户名密码后重新做一次password_verify()校验。如果校验失败,返回401 WWW-Authenticate: Basic realm="NAS"。注意 WebDAV 客户端在遇到 401 后会自动弹出密码框,所以这里不要用 JSON 格式的响应体,必须严格按照 HTTP 规范只输出状态码和响应头。

3.3 文件上传与下载的实现

Web 端的上传接口接收multipart/form-data提交的内容,保存时要注意几点:中文文件名必须做 UTF-8 处理,避免 Windows 上传的中文名在 Linux 下乱码;同名文件要在文件名后追加日期或者序号,不能直接覆盖;目录名不能以.开头,防止误隐藏。

分块上传的实现思路是在前端把大文件切成块,每次上传时把块写入临时文件,全部传完后用file_put_contents($finalPath, $flag = FILE_APPEND)合并。但这套方案在纯 PHP 实现里对内存和磁盘 IO 要求都高,我实际用的是在每个分块里携带总文件 ID 和块序号,后端按序号保存为xxx.part1xxx.part2,全部齐全后再用流式方式合并:

$fin = fopen($finalPath, 'wb'); for ($i = 1; $i <= $totalChunks; $i++) { $part = $tempDir . '/' . $fileId . '.part' . $i; $in = fopen($part, 'rb'); stream_copy_to_stream($in, $fin); fclose($in); unlink($part); } fclose($fin);

下载目录的时候用 ZipArchive 打包,这里有一个很典型的坑:打包时 ZipArchive 会把完整路径写进压缩包,导致用户解压后多了一层嵌套目录。处理办法是在addFile()时设置内部文件名,只保留相对当前目录的名字:

$zip = new ZipArchive(); $zip->open($tempZip, ZipArchive::CREATE | ZipArchive::OVERWRITE); $files = scandir($dir); foreach ($files as $file) { if ($file === '.' || $file === '..') continue; $full = $dir . '/' . $file; if (is_file($full)) { $zip->addFile($full, $file); } elseif (is_dir($full)) { $zip->addDir($full, $file); } } $zip->close();

这里如果文件名里有中文,ZipArchive 在旧版本 PHP 里会乱码,遇到这种情况可以在addFile之前用iconv()或者mb_convert_encoding()强制转成 UTF-8,并且在压缩包内部保留一个 UTF-8 注释标记。新版 PHP 配合 ZipArchive 的setArchiveComment做兼容处理。

3.4 WebDAV 协议支持,让 rclone 能挂载成本地磁盘

这是整套系统最有技术含量的部分,也是网上一堆 PHP NAS 教程里讲得最少的部分。rclone 挂载 WebDAV 时,实际上会向服务器发送大量带Depth头的 PROPFIND 请求,服务器必须按照 RFC 4918 规范返回 207 Multi-Status 响应,并且 XML 里的<D:status>HTTP/1.1 200 OK</D:status>之类的状态码要正确。

我用一个精简版webdav.php支持六个方法:PROPFIND、GET、PUT、MKCOL、DELETE、MOVE。这六个方法覆盖了 rclone 目录浏览和文件操作的大部分场景。核心代码框架大致是:

$method = $_SERVER['REQUEST_METHOD']; $path = parse_path($_SERVER['REQUEST_URI']); switch ($method) { case 'PROPFIND': return handle_propfind($path); case 'GET': return handle_get($path); case 'PUT': return handle_put($path); case 'MKCOL': return handle_mkcol($path); case 'DELETE': return handle_delete($path); case 'MOVE': return handle_move($path); default: http_response_code(405); }

其中 PROPFIND 是重难点。当Depth头为0时返回当前目录本身的信息,为1时需要列出所有子目录和文件的信息。响应体是一个 XML 字符串,要在Content-Type: application/xml头下返回。常用的响应体模板是:

<D:multistatus xmlns:D="DAV:"> <D:response> <D:href>/docs/</D:href> <D:propstat> <D:prop> <D:resourcetype><D:collection/></D:resourcetype> </D:prop> <D:status>HTTP/1.1 200 OK</D:status> </D:propstat> </D:response> </D:multistatus>

rclone 对href里的目录结尾非常敏感,目录必须以/结尾,否则它会认为这是一个文件。此外MOVE方法需要处理Destination请求头,这个头包含了目标路径,必须解析出来做路径校验。PUT 方法则要接收原始请求体并写入沙箱目录,整个过程不要经过$_FILES框架,直接用php://input流读取。

我实际测下来,以下这个最小实现足够让 rclone 正常挂载:

rclone mount nas:/ /mnt/nas --vfs-cache-mode full

这里的nas是在 rclone 里配置的 WebDAV remote,地址填http://your-server:8080/webdav.php,账号密码填写config.php里对应的值。挂载成功后,Linux 下可以直接对/mnt/nas下的文件做读写操作,Windows 下也可以映射网络驱动器,使用体验和本地磁盘基本没有区别。

注意 WebDAV 客户端有的会自动发送OPTIONS请求进行能力探测,服务器要正确返回Allow: PROPFIND, GET, PUT, DELETE, MKCOL, MOVE, COPY, OPTIONS等方法头,否则客户端会认为服务不可用。另外,rclone 的--vfs-cache-mode full会在本地缓存文件,如果 NAS 上文件被其他设备修改,缓存目录可能会出现过期数据,建议改成--vfs-cache-mode off或者按需选模式。

3.5 分享链接与权限控制

分享链接的核心逻辑是随机 Token 生成 + 路径映射。生成 Token 的代码很简单:

$token = bin2hex(random_bytes(16)); $share = [ 'token' => $token, 'path' => $path, 'expire_at' => date('Y-m-d H:i:s', time() + 3600 * 24 * 7), ]; file_put_contents($shareRoot . '/share_' . $token . '.json', json_encode($share, JSON_UNESCAPED_UNICODE));

访问分享链接时表单提交的验证码逻辑类似于反向校验:根据share_XXX.json是否存在判断链接是否有效,然后读取 JSON 里的路径信息。做这个功能的时候要注意每个分享链接生成后立刻写入 JSON,并且加一个定时清理机制,比如在每次创建新分享时顺带扫描一遍shares/目录,删除过期的 JSON 文件,防止分享目录无限膨胀。

另外,分享链接的作用范围要做严格限制。万一有人的 Token 被泄露,不能让对方通过修改路径参数去下载整个存储根目录里的其他文件。我的做法是在生成分享时就把完整物理路径写到 JSON 中,访问时只从 JSON 中读路径,不接受任何来自 URL 的路径参数。这是无状态系统里最不容易出错的做法。

4. 部署步骤与配置要点

4.1 本机快速启动

开发测试阶段用 PHP 内置服务器最省事。在项目根目录执行:

php -S 0.0.0.0:8080 index.php

这里有一个坑:内置服务器默认把请求参数里的.php后缀文件交给 PHP 解析,如果data/目录里有人传了一个shell.php,内置服务器可能会直接执行它。所以开发阶段也要注意存储目录不要放在 Web 根目录下,或者通过路由黑名单拒绝带有.php后缀的访问。

访问http://localhost:8080/,输入配置好的账号密码,应该能看到文件列表。首次使用建议先上传一个测试文件,验证目录可写权限和路径正确性。

4.2 Nginx + PHP-FPM 常规部署

正式环境建议用 Nginx + PHP-FPM。Nginx 配置核心要点有三个:最大的上传体积限制、请求超时时间、以及将对应 URL 重写到入口文件。

server { listen 80; server_name nas.example.com; root /var/www/nas; index index.php; # 上传大小限制,按需调整,此处设置为 4GB client_max_body_size 4096m; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ ^/webdav\.php { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root/webdav.php; fastcgi_pass unix:/run/php/php8.2-fpm.sock; fastcgi_read_timeout 3600s; client_body_timeout 3600s; } }

很多人上传大文件失败,第一反应是改 PHP 的upload_max_filesize,结果忽略了 Nginx 自己的client_max_body_size,Nginx 会在还没到达 PHP 的时候就直接返回 413。这两个参数必须配合调整。

PHP-FPM 的配置也要对应修改:

upload_max_filesize = 4096M post_max_size = 4096M max_execution_time = 3600 max_input_time = 3600 memory_limit = 512M

post_max_size必须不小于upload_max_filesize,建议直接让两者相同,否则大文件上传时 PHP 会直接忽略请求体。

4.3 宝塔面板部署补充

如果你用的是宝塔面板这类图形化管理工具,操作会简单不少,但也要注意几个容易踩坑的地方。

第一,创建网站时 PHP 版本要选对,建议用 8.0 以上版本,因为str_contains这类新函数在旧版本里不存在。第二,上传限制需要在“PHP 版本管理-配置修改”里同时改upload_max_filesizepost_max_sizemax_execution_time。第三,伪静态配置在宝塔后台“网站设置-伪静态”中填入:

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

如果 WebDAV 路径也在这个网站下,记得在上面的location /里排除它,或者单独加一条精确匹配规则。很多人在宝塔里把 WebDAV 请求直接交到index.php入口,最后请求被路由到 Web 页面而不是 WebDAV 处理器,折腾半天才发现是伪静态规则写错了。

5. 常见问题与排查技巧

5.1 rclone 挂载 WebDAV 失败排查

rclone 挂载失败是最常见的问题,我总结出三个高频原因,几乎覆盖了九成以上的故障场景。

第一,PROPFIND 返回的 XML 格式不符合规范。rclone 对href结尾的斜杠要求非常严格,如果目录项的href没有以/结尾,rclone 会认为这是一个文件,导致目录层级错乱。排查方法是先用 curl 手工发请求,观察返回的 XML:

curl -X PROPFIND -u admin:password -H "Depth: 1" http://your-server/webdav.php/

重点检查 XML 里目录项的<D:href>是否以/结尾。

第二,Basic Auth 的用户名密码里包含特殊字符。rclone 配置文件里的pass是经过加密存储的,如果密码里有@:#这类字符,在 URL 中拼接时容易被解析错误。建议用 rclone 交互式配置,在提示输入密码时直接粘贴,而不是手动拼 URL。

第三,服务器返回的Allow头不完整。rclone 在初始化连接时会发送OPTIONS请求探测服务器能力,如果Allow头里没有PROPFINDPUT,rclone 在后续操作时会直接报错。这个问题经常出现在把 WebDAV 处理器和 Web 页面路由混在一起的情况中。

5.2 上传失败与超时排查

上传大文件报 413 的问题,按顺序检查 Nginx 的client_max_body_size、PHP 的post_max_size、PHP 的upload_max_filesize,三个参数缺一不可。如果你用的是 Docker 部署,还要检查 Nginx 容器启动时有没有把client_max_body_size的配置挂载进去,很多镜像默认是 1MB。

上传时间超时的排查则要分两层看。Web 请求层面的超时由 Nginx 的fastcgi_read_timeout控制,PHP 层面的超时由max_execution_time控制。这两个值都要调大,并且每次修改完配置都要重启 PHP-FPM 和 Nginx,只重载部分配置在某些环境下不生效。

实测下来,局域网内传一个 3GB 的视频文件,千兆网络大约需要 30 秒到 1 分钟,如果出现传了大半突然掉线的情况,大概率是fastcgi_read_timeout不够,优先把它调到 3600 秒再试。

5.3 中文文件名与编码问题

中文文件名问题非常折磨人。PHP 在 Linux 下默认对文件名的处理是二进制安全的,也就是字节流。Windows 上传的文件名编码通常是 GBK(或者说本地代码页),直接在 Linux 上保存会变成一堆乱码字符;反过来,Linux 上保存的 UTF-8 文件名在 Windows 的 WebDAV 客户端里也偶尔显示异常。

规范的做法是统一约定文件名为 UTF-8。在functions.php里加一个标准化函数,上传时把接收到的文件名做一次mb_convert_encoding($name, 'UTF-8', 'auto')转换。另外,下载文件时设置Content-Disposition头要注意浏览器对中文名的兼容问题,最好用 RFC 5987 格式的filename*参数:

header('Content-Disposition: attachment; filename="' . rawurlencode($filename) . '"');

如果发现不一致,优先检查 filesystem 实际文件名的字节流和期望字符串的编码是否一致,不要盲目去改系统 locale 设置。

5.4 权限与安全加固

这套系统最怕的是两个问题:目录可写导致上传 PHP 恶意脚本,以及无良爬虫扫路径时引发的路径穿越尝试。

上传校验除了扩展名黑名单,建议再做一个 MIME 类型白名单。黑名单永远防不全,比如.php5.phtml这种可执行扩展名容易被漏掉,而白名单只允许jpg/png/gif/pdf/zip/mp4等已知类型,如果项目场景特殊再单独加。核心数据的存储目录权限设置为700,只允许运行 PHP-FPM 的用户读写,其他用户一律没有访问权限。

storage_root目录不能放在 Web 根目录下,必须放到 Nginx 静态文件直接访问不到的位置。如果实在只能放在 Web 根目录下,用 Nginx 配置显式拒绝访问该目录:

location ~ ^/data/ { deny all; }

路径穿越防护的重点是realpath()校验。我在开发时用一套自动化脚本测试了../../..%2f%2e%2e%2f等编码变体,发现有部分特殊编码确实能绕过简单的字符串过滤,但过不了realpath()这一步。所以宁可多写几行代码,也要把所有路径处理都收敛到safe_path()这一个函数里。

PHP 错误显示也要在正式环境关闭。display_errors设为Offlog_errors设为On。一旦开启display_errors,SQL 报错或文件路径信息可能会直接泄露给攻击者,尤其是在 WebDAV 的 XML 错误响应里,暴露出的绝对路径信息会给后续攻击提供非常大的便利。同时,在php.ini里把track_errors的设置彻底去掉,避免旧版本遗留配置在启动时直接报 fatal error。

还有一个容易被忽略的点是 PHP 的session文件目录权限。如果同一台机器上有多个站点,PHP-FPM 默认的 session 保存目录可能被其他站点干扰,极端情况下会导致登录状态被篡改。建议在config.php里指定 session 保存目录为项目内不可被公网访问的目录。

6. 从一个小项目到长期使用

这套系统搭完之后,我个人的实际体验是“够用且省心”。它不会像商业 NAS 那样频繁提醒你升级系统、更新套件,也不会因为某次数据库迁移失败导致整套服务需要重新配置。数据就静静躺在磁盘上,想备份直接复制目录就行。

最后分享两个我觉得特别实用的小技巧。第一个,把整个storage_root目录挂载进 rclone 后,再配合rclone copy的定时任务,就能把 NAS 数据定期同步到网盘或对象存储做异地备份。第二个,如果以后想加更多用户,不需要引入数据库,只需要在config.php里维护一个用户数组,然后给每个用户分配独立的根目录,登录时根据用户名切换storage_root即可。这套方案的扩展空间,比你想象中要大不少。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 11:46:14

ESP32-S3 N16R8开发板入手指南:环境搭建与工程架构详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 11:44:44

如何3步追踪IP定位与手机号信息:GhostTrack新手实操教程

如何3步追踪IP定位与手机号信息&#xff1a;GhostTrack新手实操教程 【免费下载链接】GhostTrack Useful tool to track location or mobile number 项目地址: https://gitcode.com/GitHub_Trending/gh/GhostTrack 看到陌生IP访问你的服务&#xff0c;或收到未知号码发来…

作者头像 李华
网站建设 2026/9/11 11:44:25

编程入门指南:从Python到项目实战的完整路径

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 11:41:47

代码自动生成框架:原理、实践与优化策略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华