普通视图

Umami3.0版本升级教程:从 v2 平滑迁移

2025年11月8日 16:58

前言

这是一份关于如何将已部署的 Umami2.0 统计系统更新到 3.0 版本的详细教程。如果你是按照我之前的 [post cid="381" /]部署的,那么这篇教程将帮助你平滑升级到最新版本。

更新前的准备

1. 备份数据库

在进行任何更新操作之前,强烈建议先备份数据库数据,以防万一出现问题可以快速回滚。

# 进入到 Umami 目录
cd /www/wwwroot/visitors.ybyq.wang/umami

# 备份数据库数据
docker-compose exec db pg_dump -U umami umami > umami_backup_$(date +%Y%m%d_%H%M%S).sql

备份文件会保存在当前目录下,文件名包含时间戳,方便识别。

2. 记录当前版本

查看当前运行的 Umami 版本,以便后续确认更新是否成功:

docker-compose exec umami cat /app/package.json | grep version

更新步骤

1. 进入项目目录

cd /www/wwwroot/visitors.ybyq.wang/umami

2. 停止当前运行的容器

docker-compose down

这个命令会停止并删除容器,但不会删除数据卷,你的统计数据是安全的。

3. 拉取最新代码

我已经同步了官方最新版本到 Gitee仓库,现在直接拉取更新:

git pull origin master

如果提示有本地修改冲突,可以先备份 docker-compose.yml 文件:

cp docker-compose.yml docker-compose.yml.backup

然后强制拉取:

git fetch --all
git reset --hard origin/master

之后再恢复你的配置文件:

cp docker-compose.yml.backup docker-compose.yml

4. 检查配置文件

Umami3.0 可能会有一些配置变化,检查 docker-compose.yml 文件是否需要更新。

主要关注以下配置:

[collapse status="false" title="展开查看 docker-compose.yml 配置示例"]

version: '3'
services:
  umami:
    image: ghcr.io/umami-software/umami:postgresql-3.0.0
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://umami:你的密码@db:5432/umami
      DATABASE_TYPE: postgresql
      APP_SECRET: 你的随机字符串
    depends_on:
      db:
        condition: service_healthy
    restart: always
    healthcheck:
      test: ["CMD-SHELL", "curl http://localhost:3000/api/heartbeat"]
      interval: 5s
      timeout: 5s
      retries: 5

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: umami
      POSTGRES_USER: umami
      POSTGRES_PASSWORD: 你的密码
    volumes:
      - umami-db-data:/var/lib/postgresql/data
    restart: always
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  umami-db-data:

[/collapse]

确保你的数据库密码等配置信息保持一致。

5. 拉取最新镜像

docker-compose pull

这个命令会从 Docker Hub 拉取最新的 Umami 镜像,可能需要很长时间,请耐心等待。
2025-11-08T08:31:09.png

6. 重新启动服务

docker-compose up -d

容器会自动完成数据库迁移和服务启动。

7. 查看容器状态

docker-compose ps

确保所有容器都处于 Up 状态。

8. 查看日志

如果容器启动失败,可以查看日志排查问题:

# 查看 Umami 容器日志
docker-compose logs -f umami

# 查看数据库容器日志
docker-compose logs -f db

Ctrl + C 退出日志查看。

验证更新

1. 检查版本号

访问你的 Umami 网站,登录后台,在设置页面可以看到当前版本号。

或者使用命令查看:

docker-compose exec umami cat /app/package.json | grep version

Umami 3.0 新特性

Umami3.0 带来了许多改进:

  • 全新的 UI 设计:更现代化的界面,更好的用户体验
    2025-11-08T08:54:33.png
  • 性能优化:查询速度更快,页面加载更流畅
  • 新增功能

    • 改进的事件追踪
    • 更强大的过滤器
    • 自定义报告功能
    • API 增强
  • 安全性提升:修复了已知的安全漏洞

详细更新日志可以查看 Umami 官方发布说明

常见问题

1. 更新后无法启动

症状:执行 docker-compose up -d 后容器自动停止

解决方法

# 查看详细日志
docker-compose logs umami

# 如果是数据库连接问题,检查 DATABASE_URL 配置
# 如果是数据库迁移失败,可以尝试手动执行迁移
docker-compose exec umami npm run db-migrate

2. 数据丢失

症状:更新后统计数据消失

解决方法

# 检查数据卷是否存在
docker volume ls | grep umami

# 如果数据卷存在,检查数据库连接
docker-compose exec db psql -U umami -d umami -c "SELECT COUNT(*) FROM website;"

# 如果需要恢复备份
docker-compose exec -T db psql -U umami -d umami < umami_backup_你的备份文件.sql

3. 端口冲突

症状:提示 3000 端口已被占用

解决方法
修改 docker-compose.yml 中的端口映射:

ports:
  - "3001:3000"  # 将宿主机端口改为 3001

然后更新宝塔反向代理配置,将目标地址改为 http://127.0.0.1:3001

4. 镜像拉取失败

症状docker-compose pull 时下载很慢或失败

解决方法

  • 首先确认镜像标签是否正确。Umami 官方不提供 latest,常用为按数据库区分的标签:

    • Postgres:ghcr.io/umami-software/umami:postgresql-latest
    • MySQL:ghcr.io/umami-software/umami:mysql-latest
    • 常见拼写错误:postgresgl-latest(少了 q)会导致 manifest unknown
  • 可配置 Docker 镜像加速,或固定到明确版本:

    image: ghcr.io/umami-software/umami:postgresql-v3.0.0  # 指定具体版本

5. Docker 网络错误(iptables/DOCKER 链)

症状failed to create network ... Error response from daemon: Failed to Setup IP tables: Unable to enable SKIP DNAT rule: ... iptables: No chain/target/match by that name.

解决方法(依次尝试)

# 1) 重建 Docker 的 iptables 链并清理网络
docker-compose down
sudo systemctl restart docker
docker network prune -f
docker-compose up -d

# 2) 若仍失败,切到 iptables-legacy(按发行版选择其中一组命令)
# Debian/Ubuntu
sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
# CentOS/Alibaba Linux
# sudo alternatives --set iptables /usr/sbin/iptables-legacy
# sudo alternatives --set ip6tables /usr/sbin/ip6tables-legacy
sudo systemctl restart docker
docker-compose up -d

# 3) 仍不行,确保内核模块已加载
sudo modprobe ip_tables iptable_nat
sudo systemctl restart docker
docker-compose up -d

回滚操作

如果更新后出现严重问题,可以回滚到之前的版本:

1. 停止容器

docker-compose down

2. 恢复代码

# 查看之前的提交记录
git log --oneline

# 回退到之前的版本(替换 commit_id)
git reset --hard <之前的commit_id>

3. 恢复数据库(如果需要)

# 启动数据库容器
docker-compose up -d db

# 等待数据库启动
sleep 10

# 恢复备份
docker-compose exec -T db psql -U umami -d umami < umami_backup_你的备份文件.sql

4. 重新启动服务

docker-compose up -d

总结

Umami3.0 带来了许多改进和新特性,更新过程相对简单。只要做好备份,按照步骤操作,一般不会遇到大问题。如果遇到问题,可以参考常见问题部分,或在评论区留言交流。

参考链接

  •  

记录网站第一次被DDOS攻击和鳞状云夕阳

2025年11月2日 00:35

今天下午照例待在图书馆。午饭吃得少,五点就饿了,便喊朋友一起去吃饭。刚走出图书馆,就遇到漂亮的鳞状云和夕阳,顺手拍了几张。

Motion Photo

Motion Photo

[collapse status="false" title="展开查看更多照片和麻辣香锅晚饭"]
Motion Photo

Motion Photo

[/collapse]

回归主线话题

回到座位后看了眼阿里云发来的短信,登录控制台查找,发现出现了黑洞与清洗记录:

点进去一看

问了群友才知道“黑洞”这回事。

这才意识到是遭遇了 DDOS 攻击,而且还是直接打到源站 IP。

然后提交了客服工单,回复大概是:不买防护套餐就只能等待黑洞自动解封。

又去看了下阿里 DDOS 高防产品,最低档套餐一个月一万多。嗯,还是慢慢等吧……

这次攻击强度不小,对方显然知道了我的源站 IP;要是从博客站点发起,估计影响不会这么大。

我大致猜到攻击者是如何拿到我的源站 IP 的了。

这次算是见识了 DDOS 的威力。好在未造成实质损失,顶多少了几个访客。

至于 25 Gbps 是什么概念,我问了下 DeepSeek:
为了更有体感,来看一下 25 Gbps 的流量能做什么:

对比项承载能力假设/说明
高清视频直播约 12,500 个并发流每个直播 2 Mbps
4K 超清视频约 5,200 个并发用户每个视频流 5 Mbps
大型网页加载每秒 50 万+ 次页面请求每页 50 KB
文件下载每秒约 3.125 GB约等于每秒下载一部高清电影

核心概念:25 Gbps 的带宽足以轻松支撑一个大型视频网站或数百万日活 App 的正常访问。攻击者用如此巨大的流量只攻击一个 IP,目的就是确保“一击必杀”,彻底压垮目标。

  •  

对自己的博客做了几下 Apache Bench 测试,出乎意料

2025年10月26日 16:02

昨晚想着给自己的网站做些安全与压力方面的测试,今天就动手实践了一下。

首先下载并准备 Apache Bench(ab 压力测试工具),我用的是 win11 版。
2025-10-26T07:27:28.png

在解压后的 Apache 的 bin 目录中打开 cmd。

简单看了下常用参数后开始测试,一共跑了三组:

abs.exe -n 100 -c 5 https://blog.ybyq.wang/
abs.exe -n 10000 -c 50 https://blog.ybyq.wang/
abs.exe -n 20000 -c 50 https://blog.ybyq.wang/

2025-10-26T07:43:46.png
参数说明(简要)

  • -n: 总请求数(Total requests)。
  • -c: 并发数(Concurrency),同一时刻发起的并行请求数。并发设置过大容易触发 CDN 限速/封顶或放大带宽消耗。

最后一轮测试时,网站突然无法访问。
2025-10-26T07:44:16.png
打开 CDN 的资源监控一看,果然触发了我设置的带宽封顶限制。手动解除后,访问就恢复了。
2025-10-26T07:47:52.png

2025-10-26T07:45:23.png

至于雷池 WAF,一条都没拦截;部署雷池的云服务器负载也基本没有波动。

这次测试大概消耗了 CDN 约 2 GB 流量。下次我先关掉 CDN,再猛猛测几轮看看源站的真实表现。

  •  

Typecho 使用 CDN 后台无法登录且跳转错误的解决

2025年10月26日 13:32

前言

博客站出现后台登录原地打转以及进行后台某些页面或操作展示登录的问题挺久了。
之前根本没想到是CDN的问题,以为是雷池WAF的拦截,但是设置了白名单后仍有问题,就归咎于我是自己把博客代码或配置搞坏了...直到前两天把CDN关掉了,这些问题突然消失了,我才知道这是CDN没配置好的问题。
之前也在typecho社区求助过,可能是问法有问题,只得到了一个英文回答,我按此排查后也未果..
现在博客的加载速度也是优化的比以前快了很多,控制台和网络请求也没有任何错误。
2025-10-26T05:00:26.png

问题描述

没有控制台截图,不想再复刻问题了。

1. 登录成功后仍返回登录页(循环)

  • 登录后台后页面刷新,又回到 /admin/ 登录界面。
  • 网络面板可见登录 POST /index.php/action/login 返回 302 跳转到 /admin/
  • 浏览器未能持久化登录所需的 Set-Cookie,后续访问仍是未登录态。

2. 已登录,访问后台 plugins.php 却是登录页模板

  • 访问 GET /admin/plugins.php 得到 200,但页面内容是登录模板。
  • 响应头出现 x-cache: HITx-swift-cachetime: 10800age: 2700,说明该页在边缘被缓存并被直接命中返回。

3. 启用/禁用插件时被重定向回登录页


原因分析

  1. 回源 301/302 跟随开启

    • CDN 在边缘节点“自行跟随”源站的 302 跳转,导致该响应中的 Set-Cookie 未返回到浏览器。
    • 浏览器拿不到登录 Cookie,自然会回到登录页,形成“登录 → 刷新 → 仍未登录”的循环。
  2. 后台页面被 CDN 缓存

    • 未登录访问后台页返回的是登录模板;一旦被边缘缓存,后续即使已登录也可能命中该缓存,继续看到登录页。

正确的 CDN 配置(以阿里云 CDN 为例)

原则:后台与动态动作完全不缓存;只缓存静态资源与首页等可缓存页面;任何携带登录 Cookie 的请求都不要缓存。

1. 关闭回源 301/302 跟随

  • 全局关闭“回源 301/302 跟随”。
    2025-10-26T04:23:10.png

2. 缓存规则清单(按优先级从高到低)

阿里云 CDN 为“权重数值越大优先级越高”,例如:99 > 90 > 10。
  • [权重 99] php → 0 秒(不缓存)
  • [权重 99] /admin/ → 0 秒(不缓存)
  • [权重 99] /index.php/action/ → 0 秒(不缓存)
  • [权重 99] /admin/plugins.php → 0 秒(不缓存)
  • [权重 10] 主题/插件静态资源目录(示例)

    • /usr/themes/handsome/static/ → 1 年
    • /usr/themes/handsome/assets/ → 1 年
    • /usr/plugins/**/static/ → 1 年
    • /usr/uploads/ → 1 年
  • [权重 90] 首页 / → 5–15 分钟

    确保首页 / 的权重低于后台不缓存规则,避免被覆盖。

可选:如需对 archives 等列表/文章页做短期缓存(会影响评论的实时性),为相关目录单独设置几分钟的 TTL。
我设置的部分示例

3. Cookie 与缓存键

  • 关闭“携带 Cookie 缓存”或开启“带 Cookie 的请求不缓存”。
  • 保持“忽略源站不缓存标头”为关闭,尊重源站的 Cache-Control/Set-Cookie。

4. 刷新与测试

  • 刷新以下路径缓存:/admin/*/index.php/action/*/、以及被误缓存的具体页面(如 /admin/plugins.php)。
  • 验证要点:

    • 登录 POST 的 302 响应能携带并落下登录相关 Set-Cookie
    • 后台页面响应应为 x-cache: MISS 或无 age
    • 首页命中 x-cache: HITage 逐步增大,符合设定的过期时间。
      2025-10-26T04:48:30.png

参考链接


结语

本问题的本质是“302 跟随吞掉 Set-Cookie”与“未登录模板被缓存”。按上面的两步到位:关闭回源 301/302 跟随 + 后台/动作不缓存,再配合首页的有限时缓存,即可既保证登录态正确,又兼顾前台加速效果。

  •  

Handsome主题添加新年倒计时

2025年10月18日 23:21

发现在bing中能搜索到,但没展示完整,可能是第一次发的字太少了。现在用ai重写了,首页也暂时去掉了倒计时,然后等60天的时候我再回来。
给handsome主题的首页加一个精美的倒计时组件,支持多种节日倒计时,包括春节、元旦、情人节等。具有现代化的设计风格,完美适配响应式布局和夜间模式。

功能特性

  • 现代化设计:采用卡片式设计,支持圆角、阴影和渐变效果
  • 响应式布局:完美适配手机、平板和桌面设备
  • 夜间模式支持:自动适配主题的暗色模式
  • 实时更新:每秒自动刷新倒计时
  • 高度可定制:支持自定义日期、标题、颜色和样式
  • 动画效果:秒数显示带有脉冲动画效果
  • 多节日支持:可轻松切换为不同节日的倒计时

效果预览

已支持响应式和夜间模式。效果如下:
2025-10-20T15:11:52.png

快速使用

方法一:通过后台设置(推荐)

  1. 登录您的Handsome主题后台
  2. 进入 开发者设置 页面
  3. 找到 首页列表最前方广告位 选项
  4. 将下方完整代码复制粘贴到该位置
  5. 保存设置即可

方法二:直接编辑主题文件

如果熟悉代码编辑,也可以直接编辑主题文件:

  • 文件位置:usr/themes/handsome/
  • 在首页模板文件中添加代码

代码如下:
[hide]

<style>
    /* 倒计时容器样式 */
    .gn_box {
        padding: 20px 14px;
        margin: 10px;
        margin-bottom: 20px;
        text-align: center;
        background-color: #fff;
        border: 1px solid #e3e8f7;
        border-radius: 15px;
        box-shadow: 0 8px 16px -4px rgba(44, 45, 48, 0.047);
        transition: all 0.3s ease;
    }

    /* 标题样式 */
    .gn_box h1 {
        margin: 10px 0 20px;
        font-size: 28px;
        font-weight: bold;
        color: #465CEB;
    }

    /* 倒计时容器 */
    .HotDate {
        display: flex;
        justify-content: center;
        align-items: center;
        gap: 15px;
        flex-wrap: wrap;
    }

    /* 倒计时数字通用样式 */
    .HotDate span {
        font-size: 20px;
        font-weight: 600;
        padding: 10px 15px;
        border-radius: 8px;
        background-color: rgba(70, 92, 235, 0.08);
        border: 1px solid rgba(70, 92, 235, 0.15);
        transition: transform 0.3s ease, background-color 0.3s ease;
    }

    .HotDate span:hover {
        transform: scale(1.05);
        background-color: rgba(70, 92, 235, 0.12);
    }

    #t_d {
        color: #982585;
    }

    #t_h {
        color: #8f79c1;
    }

    #t_m {
        color: #65b4b5;
    }

    #t_s {
        color: #83caa3;
        animation: pulse 1s infinite;
    }

    /* 秒数脉冲动画 */
    @keyframes pulse {
        0%, 100% {
            opacity: 1;
        }
        50% {
            opacity: 0.6;
        }
    }

    /* 响应式设计 */
    @media (max-width: 600px) {
        .gn_box h1 {
            font-size: 22px;
        }
        
        .HotDate span {
            font-size: 16px;
            padding: 8px 12px;
        }
    }

    /* 暗色模式适配 - 倒计时容器 */
    [data-night="night"] .gn_box,
    .dark-mode .gn_box,
    body.dark .gn_box,
    body.night .gn_box,
    .night .gn_box,
    .night-mode .gn_box,
    html.night .gn_box,
    .theme-dark .gn_box {
        background-color: #2c2c2e;
        border-color: #4a4a4a;
        box-shadow: 0 8px 16px -4px rgba(0, 0, 0, 0.15);
    }

    /* 暗色模式适配 - 标题 */
    [data-night="night"] .gn_box h1,
    .dark-mode .gn_box h1,
    body.dark .gn_box h1,
    body.night .gn_box h1,
    .night .gn_box h1,
    .night-mode .gn_box h1,
    html.night .gn_box h1,
    .theme-dark .gn_box h1 {
        color: #7c89f1;
    }

    /* 暗色模式适配 - 倒计时数字 */
    [data-night="night"] .HotDate span,
    .dark-mode .HotDate span,
    body.dark .HotDate span,
    body.night .HotDate span,
    .night .HotDate span,
    .night-mode .HotDate span,
    html.night .HotDate span,
    .theme-dark .HotDate span {
        background-color: rgba(124, 137, 241, 0.15);
        border-color: rgba(124, 137, 241, 0.25);
    }

    [data-night="night"] .HotDate span:hover,
    .dark-mode .HotDate span:hover,
    body.dark .HotDate span:hover,
    body.night .HotDate span:hover,
    .night .HotDate span:hover,
    .night-mode .HotDate span:hover,
    html.night .HotDate span:hover,
    .theme-dark .HotDate span:hover {
        background-color: rgba(124, 137, 241, 0.2);
    }

    /* 暗色模式适配 - 各时间单位颜色 */
    [data-night="night"] #t_d,
    .dark-mode #t_d,
    body.dark #t_d,
    body.night #t_d,
    .night #t_d,
    .night-mode #t_d,
    html.night #t_d,
    .theme-dark #t_d {
        color: #e94ca6;
    }

    [data-night="night"] #t_h,
    .dark-mode #t_h,
    body.dark #t_h,
    body.night #t_h,
    .night #t_h,
    .night-mode #t_h,
    html.night #t_h,
    .theme-dark #t_h {
        color: #b89ce2;
    }

    [data-night="night"] #t_m,
    .dark-mode #t_m,
    body.dark #t_m,
    body.night #t_m,
    .night #t_m,
    .night-mode #t_m,
    html.night #t_m,
    .theme-dark #t_m {
        color: #87d4d5;
    }

    [data-night="night"] #t_s,
    .dark-mode #t_s,
    body.dark #t_s,
    body.night #t_s,
    .night #t_s,
    .night-mode #t_s,
    html.night #t_s,
    .theme-dark #t_s {
        color: #a8e9c3;
    }
</style>

<div class="gn_box">
    <h1>2026年 - 春节倒计时</h1>
    <div id="CountMsg" class="HotDate">
        <span id="t_d">-- 天</span>
        <span id="t_h">-- 时</span>
        <span id="t_m">-- 分</span>
        <span id="t_s">-- 秒</span>
    </div>
    <script type="text/javascript">
        function getRTime() {
            var EndTime = new Date('2026/02/17 00:00:00');
            var NowTime = new Date();
            var t = EndTime.getTime() - NowTime.getTime();
            
            if (t <= 0) {
                document.getElementById('t_d').innerHTML = '0 天';
                document.getElementById('t_h').innerHTML = '0 时';
                document.getElementById('t_m').innerHTML = '0 分';
                document.getElementById('t_s').innerHTML = '0 秒';
                return;
            }
            
            var d = Math.floor(t / 1000 / 60 / 60 / 24);
            var h = Math.floor(t / 1000 / 60 / 60 % 24);
            var m = Math.floor(t / 1000 / 60 % 60);
            var s = Math.floor(t / 1000 % 60);
            
            document.getElementById('t_d').innerHTML = d + ' 天';
            document.getElementById('t_h').innerHTML = h + ' 时';
            document.getElementById('t_m').innerHTML = m + ' 分';
            document.getElementById('t_s').innerHTML = s + ' 秒';
        }
        
        getRTime();
        setInterval(getRTime, 1000);
    </script>
</div>

[/hide]

  •  

PluginMonitor - Typecho 插件监控工具

2025年10月9日 23:57

为什么需要插件监控?

作为 Typecho 博客的站长,你是否遇到过以下困扰:

  • 插件过多导致性能下降:安装了太多插件,不知道哪些在拖慢网站速度
  • 数据库臃肿:插件创建的数据表越来越多,数据库变得臃肿不堪
  • 内存占用不明:不知道哪些插件占用了大量内存资源
  • 插件管理混乱:忘记哪些插件已启用,哪些插件长期闲置

PluginMonitor 插件正是为解决这些问题而设计的。

核心功能

系统信息监控

  • PHP 版本检测:实时显示服务器 PHP 版本信息
  • 内存使用监控:当前内存使用量、峰值内存、内存限制
  • 数据库大小统计:分别显示插件数据库占用和总数据库大小
  • 插件数量统计:已启用/已安装插件数量对比
    2025-10-09T15:54:26.png

插件状态监控

  • 启用状态:清晰显示每个插件的启用/禁用状态
  • 版本信息:显示插件版本号和作者信息
  • 文件统计:统计每个插件的文件数量
  • 空间占用:精确计算每个插件占用的磁盘空间
  • 描述信息:显示插件的功能描述
    2025-10-09T15:54:42.png

智能数据库分析

  • 表类型识别:自动区分系统表和插件表
  • 存储引擎:显示每个表的存储引擎类型
  • 数据行数:统计表中的数据行数
  • 表大小:精确计算每个表占用的存储空间
    2025-10-09T15:54:55.png

安装与使用

下载地址

[hide]
https://github.com/BXCQ/PluginMonitor
[/hide]

启用插件

  1. 登录 Typecho 后台
  2. 进入「控制台」->「插件」
  3. 找到「插件监控工具」并启用
  4. 启用后在后台管理菜单中会出现「插件监控」选项

使用环境

  • Typecho 版本:1.2.1
  • PHP 版本:8.0 及以上
  • 数据库:MySQL

配置选项

2025-10-09T15:52:42.png

自动刷新设置

  • 不自动刷新:手动刷新,节省服务器资源
  • 5秒/10秒/30秒/60秒:根据需求设置自动刷新间隔

显示选项

  • 显示未启用的插件:选择是否在列表中显示未启用的插件
  • 数据库表显示范围

    • 仅显示插件创建的表(默认)
    • 显示所有表(系统+主题+插件)

实际使用效果

系统概览

使用插件后,可以在一个页面看到:

  • PHP 版本:8.0.15
  • 内存使用:45.2MB / 512MB
  • 峰值内存:67.8MB
  • 插件数据库占用:2.3MB
  • 数据库总大小:15.7MB
  • 已启用/已安装数量:12 / 25

插件列表

每个插件都会显示详细信息:

  • 插件名称:ModernAdmin - 一个美化 Typecho 后台的插件
  • 版本:1.5.1
  • 作者:璇
  • 状态:已启用
  • 文件数:47
  • 占用空间:2.1MB

数据库表分析

  • 插件表:显示插件创建的数据表
  • 系统表:Typecho 核心表(comments、contents、metas 等)
  • 表大小:精确到字节的存储空间统计

界面设计特色

响应式设计

  • 桌面端:完整的表格显示,信息一目了然
  • 移动端:自适应布局,关键信息优先显示
  • 深色模式:支持系统深色模式,护眼更舒适

用户体验优化

  • 固定列宽:防止内容过长影响布局
  • 状态标识:用颜色区分插件启用状态
  • 悬停提示:鼠标悬停显示详细信息
  • 一键刷新:手动刷新按钮,实时更新数据

总结

PluginMonitor 插件为 Typecho 博客管理带来了全新的体验:

全面监控:系统、插件、数据库三位一体监控
直观显示:清晰的数据展示和状态标识
智能分析:自动识别插件表和系统表
性能优化:帮助发现和解决性能问题
开源免费:MIT 协议,完全开源

如果你是 Typecho 博客的站长,强烈推荐安装这个插件。它不仅能让用户更好地了解博客的运行状态,还能帮助优化性能,提升用户体验。


本文由 PluginMonitor 插件作者撰写,如有问题欢迎在评论区反馈。

  •  

中秋夜游记 + 深夜小烧烤

2025年10月7日 19:26

国庆假期也临近尾声了,昨天中秋节,晚上我和朋友三人去了趟园博苑。过去两年我们去岛内的景点都是骑共享单车的,但这次骑的共享电车。回来后又在学校对面吃了顿烧烤。上传昨晚的照片时相册提醒我去年今天我们仨在湄洲岛玩,有点怀念。

园博苑游记

从园林博览苑的南门进入,就是主展岛。到的时候已经是晚上九点,已经有很多人在往外出。第一感觉就是人好多。

这个是进门后的超大狮子门将
IMG20251006211551.jpg

继续往里面走,是三只财神猫
IMG20251006212022.jpg

再往里面也拍了一些,我放进下面的折叠框了:
[collapse status="false" title="展开查看更多图片"]
IMG20251006213812.jpg

IMG20251006213859.jpg

IMG20251006214023.jpg

IMG20251006212438.jpg

IMG20251006212943.jpg

IMG20251006212651.jpg

IMG20251006212909.jpg

IMG20251006212227.jpg

IMG20251006212855.jpg

IMG20251006215650.jpg

IMG20251006215914.jpg

IMG20251006215728.jpg

IMG20251006220017.jpg

[/collapse]

在嘉年华区域有喷雾的和彩色射线,然后就拍了一些实况图。趁此机会,用了一个插件实现了文章动图显示的功能。

Motion Photo

[collapse status="false" title="展开查看更多实况图"]

Motion Photo

Motion Photo

Motion Photo

Motion Photo

[/collapse]

晚上10点,园区关门,我们也就出来了。这是走在蕴珍桥上拍的,感觉楼房在水中的倒影还不错。
IMG20251006221230.jpg

地铁时光

在园博苑西一门出来后就是园博苑站,晚上人挺多,前面几站人都坐满了,后面才找到座位。
坐在椅子上突然想到了之前刷到的一个抖音视频:对着地铁地板拍照,把曝光拉最低,就能得到一张像满天星的照片了。
于是我就试了一下:
IMG_20251007_212319.jpg

然后发给女友看,我说这是满天星星,她说好清楚啊。哈哈哈哈。
[collapse status="false" title="展开查看"]
Screenshot_2025-10-07-21-59-10-33_e39d2c7de19156b.jpg
[/collapse]

烧烤时光

回到学校大门口的时候突然想吃烧烤了,其实是想小聚一下,就在学校对面找了家烧烤店,开整!
他俩点串我点啤酒,喝啥无所谓,就整最便宜的!先来9瓶冰崂山
IMG20251006231324.jpg

由于上的很慢,所以只拍了一点食物:
[collapse status="false" title="花生米、小串和大生蚝"]

mmexport1759847241386.jpg

IMG20251006233001.jpg

mmexport1759845342815.jpg
[/collapse]

朋友说在抖音上看到啤酒喝中国劲酒掺一起喝酒劲更大,然后我去隔壁烧烤店买了一瓶回来试试。
一瓶劲酒平分在三个杯子中,然后用啤酒倒满。掺之后的味道还行,保留了劲酒的药味也减弱了啤酒的味道。
[collapse status="false" title="红色劲酒和掺一起的"]
mmexport1759845943075.jpg

IMG20251007001748.jpg
[/collapse]

后面又加了点串,加了三瓶啤酒。时间也不早了,意满离!
IMG20251007010440.jpg

最后是有一位朋友被我俩架着回宿舍了,哈哈哈哈...

  •  

第二笔项目劳务费到手

2025年10月1日 18:23

第一个项目

在2025年4月,做了一个健身房会员管理系统,用的是uniapp+Express+Mysql,从开发到部署将近两周时间。甲方要求的是一个微信小程序,但由于一些原因做成了一个h5网页版的。不过最后还是拿到了1000元劳务费。

第二个项目

以前对于老师们这种询问都是不予理睬的,但这次我参与了。
2025-10-01T08:52:28.png

这个项目是一个AI+智慧教学系统: 用的是Vue+Django+Mysql前后端分离。由于课堂管理工具课堂派开始收费,老师要做出一个替代它的,然后在基础功能上加一个学生上课状态的实时识别和一个思政课程智能推荐。

说来也挺巧的,第一批功能开发中的两个队友都是熟人,一个是学号同桌,一个是邻班的老乡。

第一批功能开发中我负责的是成绩管理模块,有出勤、答题、作业、小测成绩四部分,这个模块是最麻烦的一个,但好在都是增删改查,把数据库表设计清楚就好弄多了。
2025-10-01T09:23:34.png
带我们做的是老师的一个研究生学生,这上面是一个大致的要求,实际上还有一些功能,比如签到功能。

第二批功能开发在暑假,接了一个作业模块模块。
2025-10-01T09:28:12.png

劳务费按照贡献度来分发,总共6.5我占了2.8。
在刚接手项目的时候我还以为最后能拿个200块,没想到拿到了1600,一个月生活费了。

2025-10-01T09:19:58.png

正好要换手机,那这笔钱加上女朋友资助一些,换一个oppo FindX8手机。
左边是新的x8,右边是正在用的x6。
2025-10-01T10:41:18.png

  •  

基于Spring Boot + Vue.js的GPT AI聊天应用:从开发到部署的完整实践

2025年9月23日 21:57

前言

一个人一个下午做成的,为了应对周四的作业答辩。

🚀 项目概述

本文将详细介绍一个基于Spring Boot后端和Vue.js前端的GPT AI聊天应用的完整开发流程。该项目集成了OpenAI、阿里云通义千问、DeepSeek、豆包、Kimi等多个主流AI平台,支持文本对话和图像生成功能,并实现了用户认证、会话管理、管理员面板、用户统计等完整的业务功能。
体验地址:https://gpt.xuan.cyou/

技术栈

  • 后端: Spring Boot 3.5.5, Java 17, SQLite, JPA/Hibernate
  • 前端: Vue.js 3, Vite, Pinia, JavaScript ES6+
  • AI集成: OpenAI GPT-4, 阿里云通义千问, DeepSeek, 豆包, Kimi
  • 部署: 宝塔面板, Nginx, SSL证书

📁 项目架构

GPT-AI-Project/
├── gpt-ai-java/              # Spring Boot 后端
│   ├── src/main/java/
│   │   ├── controller/       # REST API控制器 (用户, AI, 管理员等)
│   │   ├── service/          # 业务逻辑层 (多AI平台服务, 用户统计等)
│   │   ├── entity/           # 数据实体 (用户, 对话历史, 统计数据等)
│   │   ├── config/           # 配置类
│   │   └── adopter/          # AI平台适配器 (OpenAI, Ali, DeepSeek, Kimi等)
│   ├── src/main/resources/
│   │   └── application.yaml  # 应用配置
│   └── pom.xml              # Maven依赖配置
└── frontend/                 # Vue.js 前端
    ├── src/
    │   ├── components/       # Vue组件
    │   ├── views/           # 页面视图 (聊天, 图像, 管理面板, 用户后台等)
    │   ├── api/             # API调用封装
    │   └── store/           # 状态管理 (Pinia)
    ├── public/              # 静态资源
    └── package.json         # NPM依赖配置

🔧 核心功能实现

1. 后端核心架构

1.1 多AI平台集成

项目支持OpenAI和阿里云通义千问两个AI平台,通过适配器模式实现:

// AI控制器统一入口
@RestController
@RequestMapping("/api/ai")
public class AiController {
    
    @GetMapping("/chat/text/stream")
    public SseEmitter streamChat(
        @RequestParam String platform,
        @RequestParam String model,
        @RequestParam String message
    ) {
        // 根据平台选择对应的AI服务 (示例)
        if ("openai".equals(platform)) {
            return openAiController.sendTextStream(model, message);
        } else if ("ali".equals(platform)) {
            return aliAiController.sendTextStream(model, message);
        }
        // ... 此处添加对 deepseek, doubao, kimi 等平台的路由逻辑
        return null; // or throw an exception
    }
}

1.2 流式响应实现

使用Server-Sent Events (SSE) 实现实时流式对话:

@PostMapping("/chat/text/stream")
public SseEmitter sendTextStream(String model, String message) {
    SseEmitter emitter = new SseEmitter(0L);
    
    CompletableFuture.runAsync(() -> {
        try {
            // 流式读取AI响应并实时推送
            openAIService.sendText(model, message, true, delta -> {
                emitter.send(SseEmitter.event().name("delta").data(delta));
            });
            emitter.send(SseEmitter.event().name("done").data("[DONE]"));
            emitter.complete();
        } catch (Exception ex) {
            emitter.completeWithError(ex);
        }
    });
    
    return emitter;
}

1.3 会话管理系统

实现了完整的对话历史记录功能:

@Entity
public class ConversationHistory {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String userId;
    private String sessionId;
    private String userMessage;
    private String assistantReply;
    private LocalDateTime timestamp;
    // ... getters and setters
}

1.4 跨域配置

为支持前后端分离和域名部署,配置了完整的CORS支持:

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns(
                    "http://localhost:*",
                    "https://gpt.xuan.cyou"
                )
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowCredentials(true);
    }
}

2. 前端核心实现

2.1 API客户端封装

统一封装所有后端API调用:

// 流式聊天
export function streamChat({ platform, model, message, onDelta, onDone }) {
    const url = `/api/ai/chat/text/stream?platform=${platform}&model=${model}&message=${encodeURIComponent(message)}`;
    const eventSource = new EventSource(url);
    
    eventSource.addEventListener('delta', (event) => {
        const data = JSON.parse(event.data);
        onDelta?.(data.delta);
    });
    
    eventSource.addEventListener('done', () => {
        onDone?.();
        eventSource.close();
    });
}

// 图像生成
export async function createImage(params) {
    const response = await fetch('/api/ai/create/image', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(params)
    });
    
    return await response.json();
}

2.2 实时对话界面

Vue.js实现的流式对话体验:

<template>
  <div class="chat-container">
    <div v-for="msg in messages" :key="msg.id" class="message">
      <div class="message-content" v-html="formatMessage(msg.content)"></div>
    </div>
    <div v-if="isStreaming" class="streaming-indicator">AI正在回复...</div>
  </div>
</template>

<script>
import { streamChat } from '@/api/client.js'

export default {
  data() {
    return {
      messages: [],
      isStreaming: false,
      currentStreamingMessage: ''
    }
  },
  methods: {
    async sendMessage(content) {
      this.isStreaming = true;
      this.currentStreamingMessage = '';
      
      streamChat({
        platform: this.platform,
        model: this.model,
        message: content,
        onDelta: (delta) => {
          this.currentStreamingMessage += delta;
          // 实时更新界面
        },
        onDone: () => {
          this.isStreaming = false;
          this.messages.push({
            id: Date.now(),
            content: this.currentStreamingMessage,
            type: 'assistant'
          });
        }
      });
    }
  }
}
</script>

2.3 状态管理 (Pinia)

项目使用Pinia进行全局状态管理,取代了Vuex,提供了更简洁的API和完整的TypeScript支持。

// src/store/index.js
import { defineStore } from 'pinia'

export const useMainStore = defineStore('main', {
  state: () => ({
    user: null,
    currentSession: null,
    settings: {
      platform: 'openai',
      textModel: 'gpt-4o-mini',
      imageModel: 'dall-e-3',
      backendUrl: 'http://localhost:8083'
    }
  }),
  actions: {
    setUser(user) {
      this.user = user;
    },
    updateSettings(settings) {
      this.settings = { ...this.settings, ...settings };
    }
  }
})

🛠️ 开发环境搭建

1. 后端开发环境

# 1. 克隆项目
git clone <repository-url>
cd gpt-ai-java

# 2. 配置application.yaml
server:
  port: 8083

spring:
  application:
    name: gpt-ai
  datasource:
    url: jdbc:sqlite:identifier.sqlite
    driver-class-name: org.sqlite.JDBC

# 3. 运行项目
mvn spring-boot:run

2. 前端开发环境

# 1. 安装依赖
cd frontend
npm install

# 2. 配置开发代理
# vite.config.js
const target = process.env.BACKEND_URL || 'http://localhost:8083'

export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target,
        changeOrigin: true
      }
    }
  }
})

# 3. 启动开发服务器
npm run dev

📦 生产环境部署

1. 后端打包部署

# Maven打包
mvn clean package -DskipTests

# 部署运行
java -jar target/gpt-ai-0.0.1-SNAPSHOT.jar

# 或使用systemd守护进程
sudo systemctl start gpt-ai

2. 前端构建部署

# 构建生产版本
npm run build

# 部署到服务器
# 将dist/目录下所有文件上传到 /www/wwwroot/gpt.xuan.cyou/dist/

3. Nginx配置

针对宝塔面板的完整Nginx配置:

server {
    listen 80;
    listen 443 ssl http2;
    server_name gpt.xuan.cyou;
    root /www/wwwroot/gpt.xuan.cyou/dist;
    
    # SSL配置
    ssl_certificate /www/server/panel/vhost/cert/gpt.xuan.cyou/fullchain.pem;
    ssl_certificate_key /www/server/panel/vhost/cert/gpt.xuan.cyou/privkey.pem;
    
    # API代理到后端服务
    location /api/ {
        proxy_pass http://127.0.0.1:8083;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # 支持SSE长连接
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
    
    # Vue.js SPA路由支持
    location / {
        try_files $uri $uri/ @fallback;
    }
    
    location @fallback {
        rewrite ^.*$ /index.html last;
    }
    
    # 静态资源缓存优化
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

🎯 核心功能特性

1. 多AI平台支持

  • OpenAI集成: 支持GPT-4系列模型,DALL-E图像生成
  • 阿里云集成: 支持通义千问文本对话,通义万相图像生成
  • 新增平台: 已集成DeepSeek, 豆包, Kimi等更多主流AI模型
  • 统一接口: 通过platform参数无缝切换不同AI服务

2. 实时流式对话

  • SSE技术: Server-Sent Events实现实时数据推送
  • 流式体验: 模拟真实对话,逐字显示AI回复
  • 异常处理: 完善的连接错误和重连机制

3. 会话管理系统

  • 历史记录: 完整保存用户对话历史
  • 会话分组: 支持多会话并行管理
  • 数据持久化: SQLite数据库存储

4. 图像生成功能

  • 多平台支持: OpenAI DALL-E和阿里云通义万相
  • 参数配置: 支持不同尺寸和风格设置
  • 结果展示: 优雅的图像展示和下载功能

5. 用户认证系统

  • 登录注册: 基本的用户认证功能
  • 权限控制: 用户数据隔离和访问控制
  • 设置管理: 个性化配置保存

6. 管理员面板

  • 用户管理: 查看、管理所有注册用户
  • 系统监控: 实时查看系统使用统计数据
  • 数据分析: 提供用户行为和AI使用情况的图表分析

7. 用户统计

  • 个人仪表板: 用户可以查看自己的AI使用历史和统计数据
  • 用量跟踪: 详细记录每日和累计的token使用量
  • 可视化图表: 直观展示个人使用趋势

🔍 技术亮点

1. 前后端分离架构

  • RESTful API: 标准的REST接口设计
  • 跨域处理: 完善的CORS配置支持
  • 代理配置: 开发环境和生产环境的统一代理方案

2. 响应式设计

  • 移动适配: 支持手机、平板等多种设备
  • 现代UI: 简洁美观的用户界面
  • 交互体验: 流畅的动画和反馈效果

3. 性能优化

  • 静态资源缓存: 合理的缓存策略
  • Gzip压缩: 减少传输数据量
  • 懒加载: 按需加载提升首屏速度

4. 部署友好

  • 容器化支持: 可轻松容器化部署
  • 宝塔面板: 兼容主流服务器管理面板
  • SSL支持: HTTPS安全访问

🚨 常见问题解决

1. API代理404错误

问题: 前端API请求返回404错误
解决: 确保Nginx配置了正确的API代理规则

2. SSE连接中断

问题: 流式对话中途断开
解决: 配置Nginx的proxy_read_timeout参数

3. 跨域问题

问题: 浏览器CORS错误
解决: 检查后端CORS配置和Nginx代理头设置

4. 静态资源404

问题: Vue路由刷新后404
解决: 配置Nginx的try_files规则

📈 未来扩展方向

1. 功能增强

  • 语音对话: 集成语音识别和合成
  • 文件上传: 支持文档分析功能
  • 插件系统: 可扩展的功能插件架构

2. 技术优化

  • 缓存机制: Redis缓存提升性能
  • 消息队列: 异步处理大量请求
  • 微服务架构: 拆分为多个独立服务

3. 运维改进

  • 监控告警: 完善的系统监控
  • 日志分析: ELK日志分析栈
  • 自动部署: CI/CD持续集成部署

💡 总结

本项目展示了一个完整的现代Web应用开发流程,从技术选型、架构设计到开发实现和生产部署,涵盖了全栈开发的各个环节。通过Spring Boot和Vue.js的组合,实现了高效、可维护的前后端分离架构。

项目的核心价值在于:

  • 技术栈现代化: 采用最新的技术栈和最佳实践
  • 功能完整性: 涵盖了AI对话应用的核心功能
  • 部署友好性: 支持多种部署方式和环境
  • 扩展性良好: 架构设计便于后续功能扩展

希望本文能为想要开发类似AI应用的开发者提供有价值的参考和指导。
Gitee仓库:
前端 - https://gitee.com/wx6765/chatgpt_frontend
后端 - https://gitee.com/wx6765/chatgpt_backend


本文基于实际项目开发经验总结,代码示例均来自真实项目实现。

  •  

2025/9/22 台风前的晚霞 - 桦加沙

2025年9月23日 00:11

今天下午6点钟的夕阳和晚霞。下午的时候就看云挺怪的,根据以往的经验推测出傍晚有超级晚霞,果不其然,从图书馆走出去便看到了。现在发的时候外面已经是呼风唤雨之势了。

IMG20250922181012.jpg

IMG20250922181100.jpg

IMG20250922181155.jpg

IMG20250922181256.jpg

IMG20250922181351.jpg

IMG20250922181417.jpg

IMG20250922181436.jpg

IMG20250922181447.jpg

IMG20250922181517.jpg

IMG20250922181646.jpg

IMG20250922181714.jpg

IMG20250922181751.jpg

IMG20250922181843.jpg

IMG20250922182041.jpg

  •  
❌