普通视图

Received yesterday — 2025年6月1日技术

值得入手的Steam模拟类神作:缺氧(Oxygen Not Included)

作者ysicing
2025年5月31日 23:07

各位小伙伴!今天给大家安利一款超硬核、超上头的模拟经营类游戏——缺氧(Oxygen Not Included)! 如果你喜欢挑战脑力、沉浸式体验和无限可能的建造乐趣,这款游戏绝对不容错过!

缺氧是由 Klei Entertainment(饥荒开发商)打造的一款生存模拟类游戏。玩家需要在一个地下星球中管理一群可爱的小人(复制人),通过建造基地、分配资源、应对环境危机,让他们在缺氧、缺食、缺电的恶劣环境中存活下来!

特点

  • 超高自由度的建造体验
    在游戏中,你可以自由规划地下基地,设计复杂的管道、电力和氧气系统。想建一个自给自足的生态圈?还是一个高科技的自动化基地?全看你的脑洞!从简单的茅厕到复杂的核反应堆,建造过程既烧脑又满足
  • 硬核的资源管理
    氧气、食物、水、能源、温度……每一项资源都需要你精打细算。稍有不慎,小人可能会因为缺氧窒息、食物中毒,甚至被高温烫伤
  • 随机生成的挑战
    每次开局的地图和资源分布都不相同,随机事件(如陨石雨、火山喷发)让每一局都充满新鲜感。你需要根据环境灵活调整策略,永远不会觉得无聊
  • 丰富的 MOD 支持
    社区 MOD 丰富多样,从增加新建筑到优化游戏体验,应有尽有!无论你是想降低难度还是挑战极限,都能找到适合自己的玩法

我的玩法开局看水和铜多不多,不多重开

适合人群

  • 喜欢模拟经营
  • 热衷挑战的硬核玩家:喜欢烧脑、追求极限生存
  • 创意玩家:想打造独一无二的地下乌托邦

当然耐心很重要, 同时要有自我控制能力,因为很容易肝上头

入手性价比

推荐直接购买缺氧完整包, 我上个月入手大概不到 90CNY。

缺氧完整包

推荐购买正版,请勿相信廉价购买,谨防虚假入库,导致游戏和钱两失。

新手建议

  • 前期多关注氧气和食物,善用“暂停”功能规划基地
  • 游戏上手稍有难度,但官方有中文支持,社区教程也超多,入门后根本停不下来
  • 多在 B 站搜搜相关教程
  • 早期多重开几次就会玩了。

官方介绍节选

  • 建立广阔的基地以及探索生存所需的资源:
    从挖掘、资源分配到管道、电力系统,太空殖民地的一切都在你的掌控之下。然而,资源会从你第一次呼吸开始被消耗 ,所以如果你想生存下来的话,就一定要确保你探索得够快。
  • 伴随着压力模拟的精神状态问题
    给你的殖民地提供娱乐休闲活动、优越的住宿条件和更好的食物,来保证生存不会受到精神状态的影响。每个复制人之间都有差异,会对压力做出不同的潜在破坏性反应。 所以不管付出什么代价,一定要确保他们拥有愉悦的心情
  • 用复杂的气体和液体模拟系统来提高效率
    建立一个连锁的管道系统,可以迅速的将燃料和液体输送到基地的关键区域。优质的规划以及获得的加成可以让你的殖民地转变成一个运转良好的不朽机器
  • 通过电网模拟系统来获得电力:
    你可以通过众多不同的能源来获得电力,包括煤,氢,天然气或者仅仅是朴实老旧的油脂。修复电力流失,电路过载和崩溃问题以保持你殖民地的顺利运行

欢迎关注,可以看看我郑再打工每天都在折腾什么。

Received before yesterday技术

Lucky-canvas 抽奖插件折腾记

作者林木木
2025年5月31日 20:02

基于 「Lucky-canvas」 抽奖插件,借助 字节跳动 trae 做了个大转盘,全屏显示、替换背景、概率设置、一键导入、边框修改等能想到的和需要的功能都已实现,效果远超预期。

体验地址:https://immmmm.com/dzp/

使用手册 📚

1.打开网址, 右下角“全屏按钮”,右上角是“设置入口”。点击中间开始转动,结束时会弹出中奖内容,并伴有撒花。

2.右上角点击进入“设置”。首次打开,点击安装字体“Aa 年度最可爱”。

3.设置分层两列,左侧显示的奖项名称、中奖概率,可以直接修改或删除。说明:某一奖项的数值是这一项中奖率的“分子”,所有数值总和是“分母”。如“棒棒糖”的中奖率设置的 15,则中奖率是

15/(10+10+……+15+10+10+5)

4.左侧奖项列表下方是 4 个按钮。顾名思义,第 1、2、4 项试试就知。说明一下第 3 项“上传配置”,采用了“上传 txt 文件,一行一条内容”的形式,一键导入。如:本地新建了以下内容「姓氏+空格+中奖数值」

 1
 1
 1
 1
 1
 1

点击“上传配置”,选择 txt 文件,导入即可。所以,如果要换电脑快速导入,只要编辑保存好自己的“奖项.txt”到别的电脑,导入即可。

5.配置右侧,点击可更改转盘配色、指针、内外层转盘背景。

随心搭配,换着玩儿~

致谢

抽奖组件 lucky-canvas 素材大全

🎉

轻松管理K3s集群服务:System Upgrade Controller 的超实用指南

作者ysicing
2025年5月30日 21:20

K3s 作为轻量级 Kubernetes 发行版,以其高效、简洁的特性深受开发者与运维人员喜爱。但手动升级 K3s 集群可能是个繁琐的任务,幸好有 System Upgrade Controller!这个工具能让你的 K3s 集群实现自动化、无宕机升级,省时又省心。本文将带你了解 System Upgrade Controller 的魅力,并提供简洁的部署步骤,让你的集群管理更轻松!

主要用于升级 k3s 集群节点上的服务,不仅仅局限于 k3s 服务本身。

简介

System Upgrade Controller 是 Rancher 开发的一个自动化升级工具。它通过 Kubernetes 原生资源(如 Plan)管理节点和 K3s 版本的升级,核心优势包括:

  • 自动化:一键配置,自动完成 K3s 版本升级
  • 零宕机:逐节点升级,确保服务不中断
  • 灵活性:支持自定义升级策略,适配各种集群规模
  • 轻量高效:与 K3s 的低资源占用理念完美契合

如果你想让 K3s 集群保持最新或者减少运维负担,绝对值得一试!

项目地址:

在 K3s 上部署

以下是快速部署 System Upgrade Controller 的步骤,简单易上手

kubectl apply -f https://raw.githubusercontent.com/rancher/system-upgrade-controller/master/manifests/system-upgrade-controller.yaml

或者

kubectl apply -k github.com/rancher/system-upgrade-controller

服务控制器默认会部署到 system-upgrade 命名空间下

kubectl get deploy -n system-upgrade
NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
system-upgrade-controller   1/1     1            1           335d

使用场景

常见使用如下,由于权限极高,操作时需要确保重复执行没影响。

  • 升级 k3s 本身
  • 升级 k3s 集群节点服务

升级 k3s 服务

由于我现在的环境特殊,只有一个 master 节点,每次跨版本升级 master 节点都是先手动升级到最新版本,然后在使用下面的命令升级计算节点。(保障至少 1 个控制节点版本是最新的)

---
apiVersion: v1
kind: Secret
metadata:
  name: k3s1306
  namespace: system-upgrade
type: Opaque
stringData:
  upgrade.sh: |
    #!/bin/bash

    set -x

    binfile=$(command -v k3s)

    $binfile -v | grep "v1.30.6" && (
      echo "done"
      exit 0
    ) || (
      wget https://c.ysicing.net/oss/tiga/linux/amd64/k3s
      chmod +x k3s
      mv k3s $binfile && systemctl restart k3s
    )
---
apiVersion: upgrade.cattle.io/v1
kind: Plan
metadata:
  name: k3s1306
  namespace: system-upgrade
spec:
  concurrency: 3
  nodeSelector:
    matchExpressions:
      - {key: kubernetes.io/os, operator: Exists}
  tolerations:
  - {operator: Exists}
  serviceAccountName: system-upgrade
  secrets:
    - name: k3s1306
      path: /host/run/system-upgrade/secrets/k3s1306
  cordon: false
  version: latest
  upgrade:
    image: hub.ysicing.net/ysicing/debian-upgrade:20230909
    command: ["chroot", "/host"]
    args: ["sh", "/run/system-upgrade/secrets/k3s1306/upgrade.sh"]

想了解更多官方的姿势,可以参考

升级集群服务

  • 升级 tailscale 服务
---
apiVersion: v1
kind: Secret
metadata:
  name: ts-script
  namespace: system-upgrade
type: Opaque
stringData:
  upgrade.sh: |
    #!/bin/bash

    set -x
    if tailscale version 2>/dev/null | grep -q "1.82.5"; then
        echo "Tailscale 1.82.5 already installed"
        exit 0
    fi
    export DEBIAN_FRONTEND=noninteractive
    apt-get update -qq
    apt-get install -y --no-install-recommends tailscale
---
apiVersion: upgrade.cattle.io/v1
kind: Plan
metadata:
  name: ts1825
  namespace: system-upgrade
spec:
  concurrency: 1
  nodeSelector:
    matchExpressions:
      - {key: kubernetes.io/os, operator: Exists}
  tolerations:
  - {operator: Exists}
  serviceAccountName: system-upgrade
  secrets:
    - name: ts-script
      path: /host/run/system-upgrade/secrets/ts-script
  cordon: false
  version: latest
  upgrade:
    image: hub.ysicing.net/ysicing/debian-upgrade:20230909
    command: ["chroot", "/host"]
    args: ["sh", "/run/system-upgrade/secrets/ts-script/upgrade.sh"]
  • 升级 easytier
---
apiVersion: v1
kind: Secret
metadata:
  name: debian
  namespace: system-upgrade
type: Opaque
stringData:
  upgrade.sh: |
    #!/bin/sh
    set -e
    if easytier-core -V 2>/dev/null | grep -q "2.2.4"; then
        echo "easytier 2.2.4 already installed"
        exit 0
    fi
    apt-get --assume-yes update
    DEBIAN_FRONTEND=noninteractive apt-get dist-upgrade --assume-yes
    curl https://c.ysicing.net/oss/scripts/easytier.sh | bash
---
apiVersion: upgrade.cattle.io/v1
kind: Plan
metadata:
  name: debian-25021514
  namespace: system-upgrade
spec:
  concurrency: 3
  nodeSelector:
    matchExpressions:
      - {key: kubernetes.io/os, operator: Exists}
  tolerations:
  - {operator: Exists}
  serviceAccountName: system-upgrade
  secrets:
    - name: debian
      path: /host/run/system-upgrade/secrets/debian
  cordon: false
  version: latest
  upgrade:
    image: ysicing/debian
    command: ["chroot", "/host"]
    args: ["sh", "/run/system-upgrade/secrets/debian/upgrade.sh"]

通过上面 3 个例子,其实就是帮你去每个节点执行相关脚本,如果你有大量类似的重复的工作,System Upgrade Controller 就是个绝佳的运维工具,它让版本管理变得简单、高效、无忧,显著提升你的运维体验。


欢迎关注,可以看看我郑再打工每天都在折腾什么。

枇杷熟了的五月月报

2025年5月30日 20:32

今年五月挺精彩,中美关税战暂时告一段落了,伯克希尔开年会了,比特币又上新高了,Google IO又来了。为了赶进度,五月真的挺忙的,这些东西也都没有怎么关注。月底也到了,博客还是要来更新一下。

杂谈

Google IO

今年的Google IO,已经过去了几天才想起来看看网上的新闻和官方的视频,因为懒和忙,也没有及时的写篇文章,想要关注信息看官网,或者中文世界已经都有很多文章了,因此我这里简单说说吧。由于Android新版本的发布节奏改变,Android方面的更新很早之前其实就已经发布了。在这次发布会上,新版本的介绍主要大的可能就是系统UI的更新,这个可以说跟小米的HyperOS更像了,而新发布的Live Notification也与国产厂商已经上线一年的新的实时通知很像,爸爸抄儿子,也是倒反天罡了。

除此之外Android上面的介绍,首先还是Compse,Compose功能和性能都有极大的提高,同时对于KMP的支持也更好,使用Kotlin做全平台开发指日可待。

整个IO,AI仍然是全场的热点,Google的模型更棒了,集成的工具更多了,google的智能眼睛又重启了。但问题是,身在中国的我们还是很难用到,各种墙以及谷歌的限制,只有尊贵的美国人才能体验到完整功能。

关于Google IO的详细可以去官网查看。

假药

因为之前在B站看到一个UP主的推荐,于是想要去买一个口腔清新喷剂,这个喷剂看许可属于一类医疗器械,因此我这里标题的是假药。这个喷剂在淘宝上是又官方旗舰店售卖的,但是本着货比三家的原则,在京东也搜了搜,结果京东的不少非自营商家的价格还挺便宜的,于是就选择了一家购买了一瓶。

无奖竞猜,上图中哪个是假货。

收到货后,发现包装的印刷质量比较差,怀疑是假货,于是问商家,告诉我请放心使用。随后又去淘宝旗舰店看了看,发现两个包装是有一些差异的,淘宝买家分享的评价里面的图明显比我这个印刷质量好多了。我购买的这个上面有个正品标识的二维码,扫码查询之后告诉我是正品,然而这个网址是一个不知名的网址,并且下面还有个给商家注册的入口。同时我又去问了淘宝店的客服,确认了他们没有这个正品验证的网址,他们的包装开口方向也与我购买的不同,因此坐实了这是假货。

而商家仍然不认同这是假货,给了我两个回复,一,商品名称和正品是一样的,二,他这个是新包装。只好选择京东投诉,上传了淘宝正品图和假货图,然后经过跟京东反复掰扯了两个星期,京东仍然回复卖家不承认是假货,只答应京东方面赔偿我商品价格,看起来是对于商家无任何处理。

同时我也在12315提交了投诉和举报,因为选择了绿色通道,结果是截止目前为止,没有任何回应。而我也没有精力和时间去找检测机构进行检测,这件事情也只能到此作罢。

在这里建议大家,京东平台而第三方商家购物需要特别慎重。特别是我这种,商家是药店,还销售假货,简直是害人。

月初是五一,回了趟老家,家里大旱,还在种地的很多人在抽水浇地。正值杨树飘絮,漫天飞舞的杨絮,配上干燥的天气让人相当不舒服。

以上为村子里颓败的房屋。

老家坐落在皖北平原,没山没水,也没啥历史名胜,为了发展旅游。县城搞了个遗址公园,种了许多的樱花树,清明节期间樱花盛放,此时已经全部凋谢了。因为小长假的原因,遗址公园还是围起来收费,搞了些表演,也算是吸引一些周边的人民。

附近还搞了一个县博物馆,主要展示了一些本地的历史文化和文物,虽然说不是什么珍贵文物,但是我觉得对于当地的中小学生来说还是挺好的,想我大学之前没出过本县,没见过啥博物馆,现在的小孩有这些东西可以看看。只可惜农村的父母可能也没有这个意识带小孩看这些。

本月的徒步,先是去了一次无锡惠山,早上早起出发,路上不堵,10点多就开到了惠山脚下。跟着两步路的路线走了个爱心线,这条路线比较简单小朋友跟着她的同学一起在前面走的很快,下午三点半就走完了全程。

就在上周去了苏州大阳山,出发前几天都在下雨,本以为山上会很泥泞,但是实际上没有。因为前几天下雨的原因,这天过来徒步登山的人并不多。我们开车过来,把车停到了苏州乐园的停车场,这里每小时4元,20元封顶,然而往前走的时候发现路边停车只要7块钱每次,因为懒还是没有挪车。之后遇到第二个问题,因为是按照别人路线的反穿走的,在一个门那里被保安拦住不让我们进去,说要去正门买票进去。我们只好往回走,从正门附近的一个小豁口钻进去,并且爬了一段比较难走的路才拐到我们原本要走的路线上。而实际上,大多数人走的金蛇线入口,也就是我们终点的地方这里,上山也是不要门票的。大阳山比惠山难度稍高,但是因为我们先走了2公里的公路,总里程10公里,大家走下来感觉也都不算太累。

五月份还是枇杷成熟的季节,家里人也喜欢吃,相比于苏州东山80一斤的白玉枇杷,公园里免费的枇杷虽然酸,但是也不错了。于是我们选择在周末开着车到广富林郊野公园露营摘枇杷,枇杷树有不少棵,也有不少人带着工具过来摘,我们还是收获满满。

这个月主要看了两本书,首先是《巴菲特之道》这本书,想到要看这本是因为伯克希尔开了年会,巴菲特宣布退休,这本书介绍了巴菲特的部分经历,他的投资原则,我也专门写了文章,感兴趣可以看看。

另外还看了一本《寻路中国》,微信读书推荐的,作者是之前在中国工作过十几年的一位美国记者何伟,书中讲述他开车在中国旅行和与人交流的故事,时间大概是在2002年到2007年之间的事情,他见到了中国的工业化进程,农村的变化等等,书的内容比较吸引人,只花了不到两个星期就看完了。

电视剧也看了两部,首先是美剧《最后生还者》第二季上线了,也就花时间先把第一季看了看,目前在看第二季了,个人感觉第二季没有第一季好看,第一季男女主一起找实验室相当于是主线,每集又有独立的剧情。而到了第二季,感觉就是你找我复仇,我找你复仇,然后有出来一大堆新的组织,越往后越有点看不动。 另外还看了去年大火的剧集《我的阿勒泰》,整部剧只有八集,第一季作家告诉女主要“去生活,去爱,去受伤”,之后就是女主在草原生活的故事,最后结尾也算是happy end,不过我感觉可能留一点遗憾或许会更好。另外就是这部剧的画面很好看,草原加雪山真的很美,想要去新疆看看,可惜是很难请个长假。

尾声

五月工作上挺忙的,但是并没有啥产出,并且被谷歌爸爸卡着公司的产品也没法发布,希望六月能够顺利一点。至于我上面的胡言乱语,你就当耳旁风😄。

看完评论一下吧

优化Typecho的思路

作者老孙
2025年5月30日 19:08
AI摘要:文章介绍了优化Typecho博客性能的多种方法:1)服务器端使用Nginx开启gzip压缩和浏览器缓存;2)图片优化采用原生JS实现懒加载;3)通过PHP压缩HTML输出;4)设置浏览器缓存和页面缓存策略,包括简单的PHP缓存实现。这些技术可显著提升网站加载速度和性能。

服务器端

使用Nginx作为web服务端可以使用以下开启

# 启用 gzip 压缩
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_types
  application/javascript
  application/json
  application/xml
  text/css
  text/plain
  text/xml;

# 浏览器缓存控制
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
    expires 30d;
    add_header Cache-Control "public, no-transform";
}

图片优化

使用原生js实现懒加载

document.addEventListener('DOMContentLoaded', function() {
    // 获取所有图片(如果主题默认输出 src,可以动态替换为 data-src)
    const images = document.querySelectorAll('img[src]:not([data-src])');
    
    // 防止重复处理
    images.forEach(img => {
        if (!img.getAttribute('data-src')) {
            img.setAttribute('data-src', img.src); // 把 src 存到 data-src
            img.removeAttribute('src'); // 移除 src,避免立即加载
        }
    });

    // 懒加载逻辑
    const lazyLoad = (targets) => {
        if ('IntersectionObserver' in window) {
            const observer = new IntersectionObserver((entries) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        const img = entry.target;
                        img.src = img.dataset.src;
                        observer.unobserve(img); // 加载后停止观察
                    }
                });
            });
            targets.forEach(img => observer.observe(img));
        } else {
            // 兼容旧浏览器(滚动监听)
            const checkImages = () => {
                targets.forEach(img => {
                    const rect = img.getBoundingClientRect();
                    if (rect.top < window.innerHeight + 100) { // 提前 100px 加载
                        img.src = img.dataset.src;
                    }
                });
            };
            window.addEventListener('scroll', checkImages);
            checkImages(); // 初始检查
        }
    };

    // 对所有 data-src 图片应用懒加载
    lazyLoad(document.querySelectorAll('img[data-src]'));
});

资源合并与压缩

压缩HTML输出

// 在主题的 functions.php 中
function compress_html($html) {
    $placeholders = [];
    $i = 0;

    // 匹配 <pre> 和 <code> 区块,替换为唯一占位符
    $html = preg_replace_callback(
        '/<(pre|code)[^>]*>.*?<\/\1>/is',
        function ($matches) use (&$placeholders, &$i) {
            $key = "###HTML_COMPRESS_IGNORE_" . $i . "###";
            $placeholders[$key] = $matches[0];
            $i++;
            return $key;
        },
        $html
    );

    // 正常压缩
    $search = array(
        '/\>[^\S ]+/s',
        '/[^\S ]+\</s',
        '/(\s)+/s'
    );
    $replace = array(
        '>',
        '<',
        '\\1'
    );
    $html = preg_replace($search, $replace, $html);

    // 恢复占位符
    if (!empty($placeholders)) {
        $html = str_replace(array_keys($placeholders), array_values($placeholders), $html);
    }

    return $html;
}
//在head.php中开启
<?php ob_start("compress_html"); ?>
//在footer.php中结束
<?php ob_end_flush(); ?>

缓存策略

浏览器缓存

<meta http-equiv="Cache-Control" content="max-age=86400" />

header.php中加入

页面缓存

在主题的 functions.php 中添加简单的页面缓存

function page_cache() {
    // 不缓存后台、登录页和提交操作
    if (defined('__TYPECHO_ADMIN__') || $_SERVER['REQUEST_METHOD'] != 'GET') {
        return;
    }
    
    $cache_dir = __TYPECHO_ROOT_DIR__ . '/cache';
    if (!is_dir($cache_dir)) {
        mkdir($cache_dir, 0755, true);
    }
    
    $url_hash = md5($_SERVER['REQUEST_URI']);
    $cache_file = $cache_dir . '/' . $url_hash . '.html';
    
    // 缓存过期时间(秒)
    $cache_time = 3600; // 1小时
    
    // 如果缓存文件存在且未过期,则直接输出
    if (file_exists($cache_file) && (time() - filemtime($cache_file) < $cache_time)) {
        echo file_get_contents($cache_file);
        exit;
    }
    
    // 否则开始输出缓冲
    ob_start();
}

function save_cache() {
    // 同样跳过后台等页面
    if (defined('__TYPECHO_ADMIN__') || $_SERVER['REQUEST_METHOD'] != 'GET') {
        return;
    }
    
    $cache_dir = __TYPECHO_ROOT_DIR__ . '/cache';
    $url_hash = md5($_SERVER['REQUEST_URI']);
    $cache_file = $cache_dir . '/' . $url_hash . '.html';
    
    // 保存缓冲内容到文件
    $content = ob_get_contents();
    file_put_contents($cache_file, $content);
}
// 在页面开始处调用
page_cache();
// 在页面结束前调用
register_shutdown_function('save_cache');

端午插柳

2025年5月30日 13:31

这么多年了每到端午还记得小时候满村子的爬树去折柳枝,小小的身子拖着比身子大一倍的柳枝往家里拖,之后就会插在家里的大大小小的门上。想想最近一次也是折柳枝大概也是在11年前了,自从离开家就再也没有在节假日回过家,回家也是只有在每年过年的时候。

今年本来是不想插柳枝的,早上出门看到了别人家门口的艾草。就想到了家里,想着是不是也可以挂一点,但是挂什么又不知道了。因为在南方是“清明插柳,端午挂艾”。
这一南一北的习俗真实有意思,但是查了一下好像就只有甘肃某些地方是有这样的习俗的。没想到我们也有“少数民族”特色。
总体来说有两个版本:
1.插柳习俗可能与寒食节纪念介子推的传说有关(寒食节插柳)。由于甘肃历史上多民族交融,寒食与端午的习俗可能有所混合。
2.清代左宗棠在西北戍边时曾命军队广植柳树(“左公柳”),改善了当地环境。后人可能将柳树视为“守护之树”,并融入节日习俗。

入乡随俗,今天回去看看那可不可以采到艾草,柳枝学校有。这样就可以两种都有啦!

夸克网盘免第三方工具下载提速方案

作者刘郎
2025年5月30日 10:36

之前被夸克网盘的限速困扰了许久(非会员白嫖党),和目前大多数主流的网盘一样,非会员用户下载体验极差。以前用油猴脚本还要逐个文件下载,操作太繁琐。

前段时间偶然发现了一个不使用第三方工具就能提速方法,实测平均速度4-5M/s,虽没能达到满速的效果,但对于日常下载也够用了。现在我就把这个亲测有效的方法分享给大家,也给自己留个备份,有需要的博友赶紧去试试吧!

效果展示

刘郎阁

未提速时的效果

刘郎阁

提速后的效果,快了10倍左右

具体操作

1.在PC桌面端,点击快传,点击发送网盘文件:

刘郎阁

2.然后选中要下载的文件或者文件夹,确认添加:

刘郎阁

3.最后点击下载到本地

刘郎阁

这时候你会发现下载速度比之前的百十k,已上升到了4~5M/s左右

该方法从2024年11月15日使用到现在,任然有效,且行且珍惜,收藏起来,快去试试吧!

HomeAssistant如何自定义侧边栏标题,修改标题文本教程

作者张洪Heo
2025年5月29日 15:35
这篇文章介绍了如何在HomeAssistant中自定义侧边栏标题。作者指出该操作虽简单但缺乏系统教程,主要通过HACS社区商店安装Custom Sidebar集成,搜索并安装相关模块(如RainXML),编辑配置文件并添加指定模块路径,随后在/config/www/目录创建sidebar-config.yaml文件设置标题参数。完成配置后重启HomeAssistant服务即可生效。文中还提供了详细的配置参数示例,帮助用户顺利操作。

五岳独尊——泰安&amp;泰山二日游

2025年5月28日 10:10

紧接着之前的北京行,从北京坐车到泰安,准备第二天爬泰山。但是北京走的确实很累,于是打算多休整一天,隔天再去爬泰山,因此是个二日游。

下了火车,就能看到泰安新建的地标建筑——财源门,也是这次计划住的地方。本来想住在如家,但是一犹豫如家一日涨了50,只能忍痛了。

修整体力,体验泰安

因为修整的缘故,上午基本没干啥,等到中午的时候才想着出门去。因为计划直接爬泰山,没怎么做攻略。查了一下到处都是炒鸡,于是想着尝一尝特色吧。中午在点评上找了一家不错的店,79.9要了四个菜:泰山干炒鸡、小酥肉、泰山熏豆干、泰山小豆腐,泰山干炒鸡就是油炸辣子鸡,小酥肉就是炸鸡柳,熏豆干就是拌豆干,小豆腐就是小白菜鸡蛋豆腐汤qaq。菜品都很一般,但是老板人很好,店面也不错,尤其是好评送的山楂汁真的好喝。

吃完了午饭就去逛了逛菜市场,听说泰山是山东煎饼的发扬地,但是菜市场的煎饼六块钱一斤,太贵了。然后就又回去休息了。本打算下午去岱庙,想了想不如第二天下山之后直接去岱庙,然后看看老县衙大院,正好都是顺路。休息到了晚上,准备出门逛逛小吃街买买吃的,也准备一下第二天爬泰山的补给。因此出发去了北新夜市。

到的时候天还没黑,小吃车的摊位都还没出来全,就和我妈在路口坐了一会。夜市很热闹,不过大部分都是小吃。也有不少套圈、打气球等游戏。记得气枪是10块钱50发,要是体验的话很划算,但是想了想算了哈哈哈哈哈。买了不少小吃,鸭货啊,炸场啊,土豆泥拌饭啊好多哈哈哈。

逛完小吃街之后回去才想起来忘记买明天的午饭了qaq,好在有外卖,买了一些汉堡,足够了ω

爬上泰山,五岳独尊

第二天一大早,就坐公交到了天外村,坐大巴进山也很顺利,很快就到了中天门。来到泰山,一定要爬从中天门到南天门这段路,才会有会当凌绝顶,一览众山小的感觉。但是真的应了那句话,十八盘会教训每一个嘴硬的人,看似近在咫尺,却爬了一段路以为登顶了,结果上去一个拐角又是很长的楼梯。即使最后终于到了南天门了,那也是泰山的大门,距离山顶还有好远呢。

不过既然到了南天门,登上玉皇顶那就是板上钉钉的事了,但是这泰山上的人也太多了qaq,尤其是五岳独尊石,想拍一张真的全是人。

本来计划坐缆车下到中天门,然后下山走到红门,再去岱庙和老县衙大院的,但是我妈说她腿疼,也只好做大巴又从天外村出来,回到旅店休息了。其实时间也不早了,要是走下山估计也是天黑了,这样也好。岱庙想想也就是个庙,应该大差不差了。晚上自己出来,吃一些好吃的,比如这个驴杂汤+两个驴肉火烧的15元套餐,还有这个24.5元30串羊肉串外加茶水不限量的套餐。真的是太美了。吃两个驴肉火烧吃饱之后,来到烧烤摊,坐在室外的折叠桌旁,杯中倒了一杯茶水,吹着夏季的暖风,舒缓了一天的疲惫。恰到好处的火候让肉串香气四溢,身边的音响放着抒情的音乐,结束了在泰安的最后一个晚上。

ps后续:吃完烤串之后,想着前一晚夜市买的卤鸭翅还没吃,于是回去路上买了蜜雪冰城,回去躺在床上开着大屏幕边看电影边啃鸭翅哈哈哈哈哈

Typecho文章置顶(非插件)

作者老孙
2025年5月26日 10:12
AI摘要:文章介绍了Typecho博客系统实现文章置顶功能的完整方案,包括在index.php中插入PHP代码处理置顶逻辑,在functions.php添加主题设置项,以及在文章列表模板中添加置顶标识显示代码。该方法解决了传统置顶方式导致文章总数减少的问题,通过单独查询置顶文章并调整分页逻辑来实现正确显示。

置顶

最近才发现原来用的文章置顶还是有些问题的,置顶N篇文章,翻页时文章列表中文章还是维持原有的翻页逻辑,那么列表中文章的总数中就会减少N篇文章.

实现

正常逻辑应该是从文章列表中查找需要置顶的文章展示在首页列表,把原有的文章列表向后压.翻页时会筛选已经置顶的文章.不再重复显示.

步骤

  1. index.php中插入代码

    <?php  
    $sticky = $this->options->sticky;
    $db = Typecho_Db::get();
    $pageSize = $this->options->pageSize;
    if ($sticky && !empty(trim($sticky))) {
     $sticky_cids = array_filter(explode('|', $sticky));
     if (!empty($sticky_cids)) {
         $sticky_html = " <span class='sticky--post'><svg xmlns='http://www.w3.org/2000/svg' width='16px' height='16px' fill='none' viewBox='0 0 24 24' class='bk'>
    <path fill='#242424' fill-rule='evenodd' d='M12.333 16.993a7.4 7.4 0 0 1-1.686-.12 7.25 7.25 0 1 1 8.047-4.334v.001a7.2 7.2 0 0 1-.632 1.188 7.26 7.26 0 0 1-4.708 3.146l-.07.013q-.466.083-.951.105m.356.979a8.4 8.4 0 0 1-1.377 0l-2.075 5.7a.375.375 0 0 1-.625.13l-2.465-2.604-3.563.41a.375.375 0 0 1-.395-.501l2.645-7.267a8.25 8.25 0 1 1 14.333 0l2.645 7.267a.375.375 0 0 1-.396.5l-3.562-.41-2.465 2.604a.375.375 0 0 1-.625-.13zm5.786-3.109a8.25 8.25 0 0 1-4.775 2.962l1.658 4.554 1.77-1.87.344-.362.496.057 2.558.294zm-12.95 0L3.476 20.5l2.557-.295.497-.057.344.363 1.77 1.87 1.658-4.555a8.25 8.25 0 0 1-4.775-2.961' clip-rule='evenodd'></path></svg></span> ";
         
         // 保存原始对象状态
         $originalRows = $this->row;
         $originalStack = $this->stack;
         $originalLength = $this->length;
         $totalOriginal = $this->getTotal();
         
         // 重置当前对象状态
         $this->row = [];
         $this->stack = [];
         $this->length = 0;
         
         if (isset($this->currentPage) && $this->currentPage == 1) {
             // 查询置顶文章
             $selectSticky = $this->select()->where('type = ?', 'post');
             foreach ($sticky_cids as $i => $cid) {
                 if ($i == 0) 
                     $selectSticky->where('cid = ?', $cid);
                 else 
                     $selectSticky->orWhere('cid = ?', $cid);
             }
             $stickyPosts = $db->fetchAll($selectSticky);
             
             // 添加置顶文章到结果集
             foreach ($stickyPosts as &$stickyPost) {
                 $stickyPost['isSticky'] = true;
                 $stickyPost['stickyHtml'] = $sticky_html;
                 $this->push($stickyPost);
             }
             
             // 计算当前页应显示的普通文章数量
             $standardPageSize = $pageSize - count($stickyPosts);
             
             // 确保第一页不会显示太多文章
             if ($standardPageSize <= 0) {
                 $standardPageSize = 0; // 如果置顶文章已经填满或超过一页,则不显示普通文章
             }
         } else {
             // 非第一页显示正常数量的文章
             $standardPageSize = $pageSize;
         }
         
         // 查询普通文章
         if ($this->currentPage == 1) {
             // 第一页需要排除置顶文章并限制数量
             $selectNormal = $this->select()
                 ->where('type = ?', 'post')
                 ->where('status = ?', 'publish')
                 ->where('created < ?', time());
                 
             // 排除所有置顶文章
             foreach ($sticky_cids as $cid) {
                 $selectNormal->where('table.contents.cid != ?', $cid);
             }
             
             $selectNormal->order('created', Typecho_Db::SORT_DESC)
                 ->limit($standardPageSize)
                 ->offset(0);
         } else {
             // 非第一页的查询
             // 计算正确的偏移量:(当前页码-1) * 每页数量 - 置顶文章数
             // 这样可以确保不会漏掉文章
             $offset = ($this->currentPage - 1) * $pageSize - count($sticky_cids);
             $offset = max($offset, 0); // 确保偏移量不为负
             
             $selectNormal = $this->select()
                 ->where('type = ?', 'post')
                 ->where('status = ?', 'publish')
                 ->where('created < ?', time());
                 
             // 排除所有置顶文章
             foreach ($sticky_cids as $cid) {
                 $selectNormal->where('table.contents.cid != ?', $cid);
             }
             
             $selectNormal->order('created', Typecho_Db::SORT_DESC)
                 ->limit($pageSize)
                 ->offset($offset);
         }
     } else {
         // 没有有效的置顶文章ID,正常查询
         $selectNormal = $this->select()
             ->where('type = ?', 'post')
             ->where('status = ?', 'publish')
             ->where('created < ?', time())
             ->order('created', Typecho_Db::SORT_DESC)
             ->page(isset($this->currentPage) ? $this->currentPage : 1, $pageSize);
     }
    } else {
     // 没有设置置顶文章,正常查询
     $selectNormal = $this->select()
         ->where('type = ?', 'post')
         ->where('status = ?', 'publish')
         ->where('created < ?', time())
         ->order('created', Typecho_Db::SORT_DESC)
         ->page(isset($this->currentPage) ? $this->currentPage : 1, $pageSize);
    }
    
    // 添加私有文章查询条件
    if ($this->user->hasLogin()) {
     $uid = $this->user->uid;
     if ($uid) {
         $selectNormal->orWhere('authorId = ? AND status = ?', $uid, 'private');
     }
    }
    
    // 获取普通文章
    $normalPosts = $db->fetchAll($selectNormal);
    
    // 如果没有置顶文章或在前面的代码中没有重置对象状态,则在这里重置
    if (empty($sticky) || empty(trim($sticky)) || empty($sticky_cids)) {
     $this->row = [];
     $this->stack = [];
     $this->length = 0;
    }
    
    // 将普通文章添加到结果集
    foreach ($normalPosts as $normalPost) {
     $this->push($normalPost);
    }
    ?>
  2. 在主题的functions.php文件中查找

    function themeConfig($form) {

    之后插入代码,会增加一个主题设置项

     $sticky = new Typecho_Widget_Helper_Form_Element_Text('sticky', NULL, NULL, _t('置顶文章cid'), _t('多篇文章以`|`符号隔开'), _t('展示需要置顶的文章。'));
     $form->addInput($sticky);
  3. 在文章列表中合适的地方插入

    <?php if (isset($this->isSticky) && $this->isSticky): ?>
    <?php echo $this->stickyHtml; ?>
    <?php endif; ?>

红墙深几许,一梦到明清——北京&amp;故宫一日游

2025年5月26日 10:15

早上九点的火车到达北京丰台站,晚上十点半的火车驶离北京站,计划了一个白天的北京入夏一日游。

到达北京丰台站,因为没吃早饭,正好在这附近吃一吃。感谢地道的老北京“小羊”前辈,为我这次北京行省了不少攻略的时间,还吃到了许多当地物美价廉的特色,真的超级超级好。这次下火车吃的《尹记门钉肉饼》就是推荐的连锁店。不过当时问了一下门钉肉饼只有在十点半之后才开始开卖,没办法因为时间紧迫吃不到了。不过也没关系,要了一份12元的羊杂汤+小饼,也要到了传说中的豆汁,3元一大碗,可惜就喝了五口。羊杂汤很咸,豆汁很酸,就是字面意思上的酸臭,喝起来也还行吧,确实不怎么好喝。要了一个牛肉大懒龙,就剩最后一个了哈哈哈哈哈。

吃过早饭,计划去看一眼天安门之后进故宫博物院,因此先去北京站存个背包。老板人很好,一眼就看出来我是大连的hhhhh,也给我优惠了几块钱。远远的能看到北京站的去看天安门,因为没有预约,被告知好像没法看到天安门???想不到看个天安门还预约了,可能是人真的多吧。折腾了半天去到了侧门进故宫了。

故宫确实大气,人也是真多,但是现在故宫正在修缮,连宫殿的大门都不能靠近,远处的门口挤了一堆人在那拍照。走着走着就很累,因为都忘记去看清明上河图了。网上看的攻略什么盖章集邮全都直接错过了,匆匆忙忙的就逛完御花园从北门上了景山公园。

景山公园的自动贩卖机很方便也很便宜,里面还有怡人的鲜花,安静的小路,而且还有能俯视故宫的美景,真是个好地方。

因为逛完很累了而且已经下午了,想着去北海公园逛逛但是貌似走不动了,于是直接下一个计划——去鼓楼的《姚记炒肝店》吃炒肝和卤煮!

卤煮确实好吃,炒肝也好,还要了炸灌肠,炸灌肠沾上蒜水还真的是灌肠的香味,好吃!

然后去哪呢?“小羊”前辈说去看看后海吧。我看地图附近就是烟袋斜街和南锣鼓巷,烟袋斜街确实不错,就是开始下起了雨。走过银锭桥发现越走越远,就计划回逛南锣鼓巷。可悲的是到了南锣鼓巷雨下大了,而且里面的东西是真的贵,也不好逛,只能草草结束了行程。

天色渐暗,雨还很大,不知道去哪里好了。想起之前看到过有个不错的小景点,在王府井商场的地下有个《和平菓局》,有许多不错的场景可以打卡还在室内,于是就最后选择去那边逛逛结束这一天的行程了。王府井大街很宽,因为并没有做攻略,就知道有个古老的天主教堂,冶可以去打打卡。

休息片刻,之后取回了包,去北京站候车厅休息休息慢慢等车了。

zblog列表页面标题及文章数量

2025年5月25日 22:31

主要用户各种列表页面显示 xx分类/标签下有xx篇文章

{if $type == 'index'}
<h2>最新文章</h2>
{else}
<h2>
{if $type == 'category'}
找到<span>{$category.Count}</span>篇与
{elseif  $type == 'tag'}
找到<span>{$tag.Count}</span>篇与
{elseif  $type == 'author'}
找到<span>{$author.Articles}</span>篇与
{/if}
<span>{$title}</span> 相关的结果</h2>
{/if}

如有更好的方式欢迎评论区补充哈!

zblog判断用户是否登录

2025年5月25日 20:56

Z-blogPHP1.7.4判断用户是否登录

{if $user.ID > 0}
    <!-- 判断用户ID是否大于0 -->
    欢迎你:<a href="{$host}zb_system/cmd.php?act=login" target="_blank">{$zbp->user->StaticName}</a>
    <!-- 用户登录后显示的内容 -->
{else}
    <a href="{$host}zb_system/cmd.php?act=login" target="_blank">登录</a>
    <!-- 用户未登录显示的内容 -->
{/if}

给文章引入联邦宇宙嘟文互动记录

2025年5月24日 17:42

Photo by Pankaj Patel / Unsplash

去年还是前年就在思考如何在 Ghost 中集成 Activitypub 的互动,还找了一个国内博客大佬的实现 ActivityPub 协议的简单实现 - Lawrence Li ,不过这位大佬的方案是完全自己实现协议部分,对我来说有点太复杂了。

后来Ghost官方也发布了一个Ghost和Activitypub的继承方案,不过我看了下项目的Docker-compose文件,感觉太臃肿了(毕竟是商业化产品,性能各方面都要考虑到)。

而且开发一年多了,现在也只在官方付费服务里Beta,所以现在兴致缺缺。

因为最近用Cloudflare Worker实现了很多有意思的玩意,

所已,今天忽然灵光一闪,想到了一个非常有意思的点子,通过和AI的几轮互动,感觉应该能完整实现大佬博客里的那种效果。

其实原理无非就是利用Cloudflare Worker和KV功能,对文章ID和嘟文ID进行储存,在页面展示时再去请求数据进行展示,整个逻辑大概如下:

文章和嘟文同步

  1. Worker定时请求Ghost博客中最新一篇的数据(我这边是用Ghost的唯一文章ID做Key,你的博客系统没有API的可以请求RSS,但是ID必须是唯一的,可以自己截取slug出来应该也是可行的)。
  2. 拿到Key后在KV中进行查找,如果录入过就跳过。
  3. 没录入就拿文章的数据根据长毛象或GTS的API要求组装嘟文进行发布。
  4. 获取到嘟文唯一ID后和文章ID一起存入KV。

嘟文数据获取

  1. 博客文章详情页面加载完后通过文章ID请求Worker。
  2. Worker拿到ID去KV中查找嘟文ID。
  3. 找到话通过嘟文ID去长毛象或者GTS获取嘟文互动数据。
  4. 进行展示。
💡
经蜗牛大佬提醒,根据嘟文的原始可访问性做了控制。
1. 只显示public评论。
2. 不显示敏感(sensitive )内容。
3. 不显示locked用户点赞。

实践

进入Cloudflare Worker直接新建一个Worker,模板选Hello World,然后下面代码覆盖原有代码,我这边Ghost获取文章的部分你们用AI改成获取RSS,并截取文章slug作为文章ID。

// 配置常量
const GTS_INSTANCE = "https://social.gts.com";
const GTS_TOKEN = "ZTU5YTZLZMQTNWRJFSAFAXG3NDQ3MWQZOWRK";
const CACHE_TTL = 600; // 互动数据缓存时间(秒)
const BLOG_URL = "https://blog.com"; // Ghost博客地址
const BLOG_API_KEY = "78eb22fbf6260dcc3a1de7cf82"; // Ghost Admin API Key

// 在 Worker 代码开头添加 CORS 处理函数
const handleCORS = (response, origin) => {
  const headers = new Headers(response.headers);
  headers.set('Access-Control-Allow-Origin', origin || '*');
  headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  return new Response(response.body, {
    status: response.status,
    headers
  });
};

export default {
  async fetch(request, env) {
    // 处理预检请求 (OPTIONS)
    if (request.method === 'OPTIONS') {
      return handleCORS(new Response(null), request.headers.get('Origin'));
    }

    const url = new URL(request.url);
    const path = url.pathname;

    // 处理定时触发的自动发布
    if (path === '/api/sync') {
      return handleAutoPublish(env);
    }

    // 提供互动数据API
    if (path === '/api/interactions' && request.method === 'GET') {
      return getInteractions(url.searchParams, env);
    }

    return new Response('Not Found', { status: 404 });
  },

  // 添加定时触发器配置
  async scheduled(event, env, ctx) {
    ctx.waitUntil(handleAutoPublish(env));
  }
};

// 自动发布最新文章
async function handleAutoPublish(env) {
  try {
    // 从Ghost获取最新文章
    const postsResp = await fetch(`${BLOG_URL}/ghost/api/content/posts/?limit=1&order=published_at%20desc&key=${BLOG_API_KEY}`, {
      headers: {
        'Accept-Version': 'v5.0',
        'Content-Type': 'application/json'
      }
    });

    if (!postsResp.ok) {
      throw new Error('Failed to fetch posts from Ghost');
    }

    const postsData = await postsResp.json();
    const latestPost = postsData.posts[0];

    if (!latestPost) {
      return new Response('No posts found', { status: 200 });
    }

    // 检查是否已经发布过
    const existingMapping = await env.BLOG_TOOT_MAPPING.get(`post:${latestPost.id}`);
    if (existingMapping) {
      return new Response('Post already published', { status: 200 });
    }

    // 发布到GoToSocial
    const tootContent = `${latestPost.title}\n${BLOG_URL+'/'+latestPost.slug}\n\nfrom 1900's Blog.(auto sync)\n\n#博客`;
    
    const tootResp = await fetch(`${GTS_INSTANCE}/api/v1/statuses`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${GTS_TOKEN}`,
        'Content-Type': 'application/json',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
      },
      body: JSON.stringify({
        status: tootContent,
        visibility: "public"
      })
    });

    const tootData = await tootResp.json();

    // 存储映射关系到KV
    await env.BLOG_TOOT_MAPPING.put(
      `post:${latestPost.id}`,
      JSON.stringify({
        toot_id: tootData.id,
        toot_uri: tootData.uri,
        created_at: Date.now()
      })
    );

    return new Response('Auto publish success', { status: 200 });
  } catch (err) {
    return new Response(err.message, { status: 500 });
  }
}

async function getInteractions(params, env) {
  const postId = params.get('post_id');
  if (!postId) return new Response('Missing post_id', { status: 400 });

  // 从KV获取Toot信息
  const tootData = await env.BLOG_TOOT_MAPPING.get(`post:${postId}`);
  if (!tootData) return new Response('Mapping not found', { status: 404 });

  const { toot_id } = JSON.parse(tootData);

  // 并发获取回复和点赞数据
  const [contextResp, favouritesResp] = await Promise.all([
    fetch(`${GTS_INSTANCE}/api/v1/statuses/${toot_id}/context`, {
      headers: { 
        'Authorization': `Bearer ${GTS_TOKEN}`,
        'CF-Cache-Tag': `context_${toot_id}`,
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
      },
      cf: { cacheTtl: CACHE_TTL }
    }),
    fetch(`${GTS_INSTANCE}/api/v1/statuses/${toot_id}/favourited_by`, {
      headers: { 
        'Authorization': `Bearer ${GTS_TOKEN}`,
        'CF-Cache-Tag': `favs_${toot_id}`,
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
      },
      cf: { cacheTtl: CACHE_TTL }
    })
  ]);

  if (!contextResp.ok || !favouritesResp.ok) {
    return new Response('Failed to fetch interactions', { status: 502 });
  }

  // 处理数据
  const [contextData, favouritesData] = await Promise.all([
    contextResp.json(),
    favouritesResp.json()
  ]);

  // 格式化响应
  const formatted = {
    post_id: postId,
    toot_id: toot_id,
    replies: contextData.descendants.map(item => ({
      id: item.id,
      author: {
        name: item.account.display_name,
        avatar: item.account.avatar
      },
      content: item.content,
      created_at: item.created_at
    })),
    favourites: favouritesData.map(user => ({
      id: user.id,
      name: user.display_name,
      avatar: user.avatar,
      username: user.acct
    })),
    stats: {
      replies_count: contextData.descendants.length,
      favourites_count: favouritesData.length
    }
  };

  return new Response(JSON.stringify(formatted), {
    headers: { 
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*' 
    }
  });
}

Worker定时执行

进入Worker的设置页面,绑定KV命名空间和设置Cron执行间隔。

这里KV空间需要提前建好,路径为 储存和数据库 > KV > 创建 > 录入名称 BLOG_TOOT_MAPPING ,然后再去设置页面绑定。

前端渲染

有了API提供数据,前端只需要在页面加载时获取数据进行渲染即可,我这里做了简单的展示,带红心的头像是点赞用户,没带红心的是用户评论,鼠标悬浮在头像上即可展示。

目前暂时还没想好如何更好的实现,之后有想法了再进行完善。

我这边相关代码剥离到了一个单独的js文件里,原理是一样的,你也可以直接写在页面上。具体代码可以用AI帮你生成一个就行。

import tippy from 'tippy.js';
import 'tippy.js/dist/tippy.css';
import 'tippy.js/themes/light.css';

// 配置常量
const API_ENDPOINT = 'https://your.workers.dev/api/interactions';

// 主入口函数
export default async function initActivityPubInteractions() {
    try {
        const container = document.querySelector('#activitypub');
        if (!container) {
            console.error('未找到#activitypub元素');
            return;
        }

        const postId = container.dataset.postid;
        if (!postId) {
            console.error('缺少data-postid属性');
            return;
        }

        const data = await fetchInteractions(postId);
        renderAllInteractions(data, container);

        // 如果有互动数据则显示容器
        if (data.stats.replies_count > 0 || data.stats.favourites_count > 0) {
            container.style.display = 'block';
        }
    } catch (error) {
        console.error('加载互动数据失败:', error);
    }
}

// 获取互动数据
async function fetchInteractions(postId) {
    const response = await fetch(`${API_ENDPOINT}?post_id=${postId}`);
    if (!response.ok) throw new Error('API请求失败');
    return await response.json();
}
// 渲染所有互动(混合点赞和评论)
function renderAllInteractions(data, container) {
    const avatarList = container.querySelector('.discussion-avatar-list');
    if (!avatarList) return;

    avatarList.innerHTML = '';

    // 合并点赞和评论数据
    const allInteractions = [...data.favourites.map((user) => ({ ...user, type: 'like' })), ...data.replies.map((user) => ({ ...user, type: 'reply' }))];

    // 按时间排序(最新的在前)
    allInteractions.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));

    allInteractions.forEach((user) => {
        const li = document.createElement('li');
        li.innerHTML = `
      <div class="comment-user-avatar ${user.type}">
        <img src="${user.avatar || user.author.avatar}" 
             alt="${user.name || user.username}" 
             class="avatar avatar-60 photo" 
             loading="lazy"
             data-user-id="${user.id}"
             data-type="${user.type}">
      </div>
    `;
        avatarList.appendChild(li);

        // 直接在这里初始化 Tippy
        const img = li.querySelector('img');
        if (user.type === 'reply') {
            // 评论工具提示
            tippy(img, {
                theme: 'light',
                allowHTML: true,
                interactive: true,
                maxWidth: 350,
                delay: [100, 0],
                content: '加载中...',
                onShow(instance) {
                    instance.setContent(user.content);
                }
            });
        } else {
            // 点赞工具提示
            tippy(img, {
                content: '💖',
                delay: [100, 0]
            });
        }
    });

    // 更新统计信息
}

HTML 代码部分

<!--- 其他代码 --->
<div class="social-interactions">
    <ol class="discussion-avatar-list"></ol>
</div>

<script>
    // 引入上面的函数文件
    import loadInteractions from '../utils/acitivitypub';
    // 适配Astro的PWA加载
    document.addEventListener('astro:page-load', () => {
        loadInteractions();
    });
</script>
<!--- 其他代码 --->

zblogPHP开启伪静态图文教程

2025年5月24日 12:52

在zblog后台插件管理里面找到【静态管理中心】插件,注意,这个插件是安装程序后自带的,默认是没有开启的,我们启动一下,然后点击扳手图标进入插件设置。

Snipaste_2025-05-24_12-47-07.webp

然后点击伪静态(高手也可以修改下默认伪静态下的网址规则,新手直接略过即可),然后滚动页面到最下面点击保存

Snipaste_2025-05-24_12-49-14.webp

此时页面会跳转伪静态规则页面

Snipaste_2025-05-24_12-49-28.webp

不同服务器环境规则写法不同,根据自己环境选择下,然后复制伪静态规则,填到自己网站控制面板伪静态规则里面保存即可

工作周年与AI焦虑:成长与挑战并存

作者Innei
2025年5月23日 23:06
该渲染由 Shiro API 生成,可能存在排版问题,最佳体验请前往:https://innei.in/notes/192

Folo 与我的一年

转眼间,这份工作又快到一年了。说起来,我毕业以来,经历了两次换工作,第一次是因为工作内容和压力的问题,第二次是因为裁员。不幸的是,这两次实际都没有满一年。

又是这里节骨眼上,恰好是一年,又开始有点焦虑了。焦虑的是,做的事是否达到预期,是否有成长,产品时候有前途等等。一年的时间说长不长说短也不短。

回顾过去的一年,我自认为还是能达到预期的吧。从我刚入职,从一个毛坯的产品开始做,重新设计组件 UI,重构底层,赋予一些有意思的交互设计以及功能,到 Web 端开放公测。不过只是三个月的时间。又是一个月完成了 Web 对 Mobile 的响应式改造。现如今 Mobile App 也已经上线很久。而这些在一年的时间中全部完成了。对于一个只有 4 个开发成员的项目来说,感觉已经很不错了。

希望越来越好吧。

AI 焦虑

回看今年,各路神仙打架。AI 发展越来越快。该说是好事还是坏事呢。如今写代码变得越来越不动脑子了,只是无脑敲着 Tab,出了问题也不知道。非常害怕这样依赖 AI 之后连很简单的逻辑都不会写了。AI 在进步而我在退步。

前段日子,也是借助 AI 完成了大部分的代码。虽然 AI 现在对 Swift 还是不太熟悉。但是循序渐进的引导最终还是能达到一个预期的结果。现在非常流行 Vibe Coding,即便是完全不会编程,只需要一个好点子,以及良好的文字表达,就能引导 AI 一步步做出你想要的结果。慢慢的代码越来越不值钱,最值钱的是好的点子。像我这种只会切图的低级程序员真的那一天就突然被淘汰了。现在的 AI 可比我会写多了。这种焦虑感越来越强。

确实会有一种很矛盾的感觉,一方面对自己所能开发的领域、边界有了更多信心,可以开发前端、iOS 甚至是各种之前并不了解的技术栈;而另一方面,对于自己离开 AI 后独立写代码的信心在显著下降,连带着自己独立思考的能力。 -- 周报 #95 - All AI 与 No AI

看完了?说点什么呢

Raphael AI图片生成器:释放创意,零门槛打造惊艳视觉盛宴

作者ysicing
2025年5月23日 19:53

在 AI 技术席卷全球的今天,图像生成工具正成为内容创作者、设计师和营销人员的得力助手。Raphael AI 作为全球首款完全免费无限制无需注册登录的 AI 图像生成器,以其强大的功能和便捷的操作,迅速在创意圈掀起热潮。

今天偶然看到的,有免费额度的 AI 图片生成器。适合生成配图或者插图,用来生成封面感觉还差点意思,和即梦效果差不多。

Raphael AI 是什么?

Raphael AI 是一款基于先进的 FLUX.1-Dev 模型打造的 AI 图像生成工具,用户只需输入文字描述(提示词),即可在几秒钟内生成高质量、多风格的图像。它的核心优势在于:

  • 完全免费, 零成本创作:无使用次数限制,无需注册或登录,打开网页即可创作
  • 多样化风格:支持写实摄影、动漫、油画、数字艺术等多种艺术风格
  • 快速生成:优化的推理管道确保几秒内出图,效率惊人
  • 隐私保护:零数据保留政策,提示词和生成图像不存储于服务器,保障用户隐私
  • 商业用途:生成图像无水印,可自由用于个人或商业项目
  • 一定质量保障:由 FLUX.1-Dev 模型驱动,提供具有卓越细节和艺术风格控制的照片级逼真图像;高级文本理解

对比国内大模型,我觉得是优势功能的都标注了。为啥说一定质量保障,因为高质量需要订阅才行 😄

测试

英文提示词

Humorous and funny style, preferably with a purple-haired Japanese anime girl holding a sign that says, "I need 66 more followers to reach 1000, please subscribe."

选择一个,其他都略微有些瑕疵

中文提示词

幽默搞笑风, 最好有个紫色头发日漫妹子举牌子,牌子上写着「我还有 66 个粉丝才满 1000,请求订阅」》

总结

官方建议: 请用英文输入提示词以获得最佳效果

经过实操,确实英文提示词要比中文好多了,可能我问的方式仅在 ChatGPT 上实验良好。

官方也提供了一些灵感示例提示词,点击相关图片就可以查看其他人的提示词

再次实操一下,发现生成 SB 比缘生更简单哈哈哈

生成: Cute chubby Pikachu on the grass, surrounded by small white flowers, with the two Chinese characters "SB" written on Pikachu's belly, soft pastel anime style

官网


Mac如何将喜欢的视频作为屏幕保护程序?自定义Mac视频屏保,最新系统教程

作者张洪Heo
2025年5月23日 14:26
这篇文章介绍了如何通过SaveHollywood工具在Mac系统上轻松设置自定义视频屏幕保护。文章提示用户,许多人可能没有意识到可以使用视频作为屏幕保护。通过简单的步骤,你可以从静态图片切换到动态视频,提升桌面的视觉效果。具体来说,读者需要安装SaveHollywood,将高清视频文件拖入工具目录中,并根据提示启用播放功能。文章还提示,操作必须严格遵循步骤,以免遇到“视频库为空”的错误。此外,工具的兼容性较高,且操作界面友好,适合大多数用户。文章鼓励读者按照指导操作,体验一次视觉效果的升级。

iPhone如何查看自己电话卡的上网优先级QCI?SIM卡的QCI查看教程

作者张洪Heo
2025年5月23日 12:32
这篇文章介绍了QCI( QUALITY CLASS IDENTIFIER)在移动网络通信中的作用及等级划分标准,明确了不同QCI等级对网络延迟、传输优先级和业务场景的具体影响。文章还详细讲解了如何通过iOS设备的反馈日志获取SIM卡的QCI等级信息,并强调了对QCI等级的理解对优化网络使用体验的重要性。此外,文章指出QCI等级越高,网络资源的优先级越高,但同时也要求预留更多的传输时延以确保高质量的应用体验。具体而言,QCI等级越高通常适用于对网络延迟较为敏感的业务,如实时视频通话和在线游戏,而QCI等级较低的网络资源可能更适合非实时性要求较高的任务。文中还通过个人使用体验进一步阐述了QCI等级在实际应用中的表现,并总结了不同QCI等级在移动互联网中的典型应用场景。

微软开源的更贴近Windows习惯的Linux命令行文本编辑器:edit

2025年5月23日 11:15

Photo by Christopher Gower / Unsplash

不知道是不是微软知道天下小白苦 Linux 的命令行编辑器久矣,最近开源了一款 windows 操作习惯的 shell 文本编辑器 edit。

GitHub - microsoft/edit: We all edit.
We all edit. Contribute to microsoft/edit development by creating an account on GitHub.
GitHubmicrosoft

支持多行选择、复制粘贴、鼠标控制等操作,操作习惯更偏向 widnows。

所以第一时间安装体验,感觉确实不错,写一篇安装教程。

下载使用你自己平台的二进制文件,Releases · microsoft/edit

curl -LO https://github.com/microsoft/edit/releases/download/v1.0.0/edit-1.0.0-x86_64-linux-gnu.xz

解压,重命名

unxz edit-1.0.0-x86_64-linux-gnu.xz
mv edit-1.0.0-x86_64-linux-gnu edit

给执行权限,并移动到 bin 目录

chmod +x edit
sudo mv edit /usr/local/bin/

测试是否安装成功

edit --version

常用快捷键

  • New File:Ctrl+N
  • Open File:Ctrl+0
  • Save:Ctrl+s
  • Close Editor:Ctrl+W
  • Exit:Ctrl+Q
  • Undo:Ctrl+Z
  • Redo:Ctrl+Y
  • Cut:Ctrl+x
  • Copy:Ctrl+c
  • Paste:Ctrl+V
  • Find:Ctrl+F
  • Replace:Ctrl+R

Google新AI产品无法使用解决指南

作者ysicing
2025年5月22日 18:21

今天看到这个消息推送,之前也遇到过这种情况,这个简单写下如何解决,当个精神美国佬

背景

受限于某些特殊环境或者边缘因素影响,国内是没法正常使用 Google AI 相关的产品的。(默认你已经可以访问 Google AI 等产品了)

接下来给的几种方式,都可以尝试

修改浏览器默认语言

这个跟之前 Github 默认禁止中文用户访问类似,检查你的请求头 accept-language 信息

示例修改 Edge 默认首选语言为英语即可,生效后随便访问一个网站开发者工具查看请求头或者支持切换语言的网站打开是否为英文

如果你的账号没问题了,通常打开那些网站就可以正常使用了。如果还不行,可能就是账号问题了

当然也会带来一些问题,你打开所有支持多语言的网站都会默认使用英文,影响也不大

申请谷歌账号地区修改

有可能,你很早之前就注册了谷歌账号,地区选的中国

查看账号绑定的地区 https://policies.google.com/terms

换区操作

找对入口,其实也很简单,通常我们都选择美区,精神美国佬,社会大学生, 部分 AI 功能暂时只对美区开发。

换区入口 https://policies.google.com/country-association-form

理由选择其他原因,听劝

好了,其他就是基本要求了,自行准备就行。

对了,这两个我港区的小号也是可以正常访问的。

PS: 大佬们都买了 Google One Ultra 没?


用Docker部署Navidrome音乐服务器

作者刘郎
2025年5月22日 14:25

Navidrome是一款基于Web的开源音乐服务器应用程序,可在Linux、Windows、macOS等多种操作系统上运行。它界面简洁直观,音乐管理功能强大,能自动扫描音乐库,获取歌曲信息和专辑封面,支持按艺术家、专辑、流派等分类浏览,还支持MP3、FLAC、AAC等多种音频格式。

它具有流媒体播放功能,可在不同设备上流畅播放音乐,支持Chromecast投屏,也有用户管理功能,可创建多个账户,便于多用户共享音乐库。其安装方式多样,可通过官网下载安装包、使用包管理工具或从源代码编译安装,配置简单,指定音乐库目录等参数后即可使用。

Navidrome适用于家庭音乐服务器、个人Nas音乐收藏管理以及小型办公环境等场景。

效果展示

PC端效果:

刘郎阁

移动端效果:

刘郎阁

刘郎阁

注意:移动端需搭配音流App或其他支持Navidrome的播放器使用。

搭建该项目的初衷

搭建Navidrome个人音乐服务器,是因为市面上的音乐服务器价格高昂,且需要持续开通会员才能享受高质量服务,一旦会员到期,音乐服务便无法继续使用。更令人无奈的是,即便开通会员,部分喜爱的音乐仍需额外付费购买,且这些音乐还存在平台使用限制,换平台播放又得重新掏钱。

与之相比,自行搭建Navidrome个人音乐服务器,一次搭建就能终身免费使用,音乐资源完全由自己掌控,无论是音乐源还是音乐质量都能随心决定。无需开通繁多的音乐会员,也不必额外花钱购买音乐,只要能连接上自建的音乐服务器,无论身处何地都能尽情享受专属的音乐盛宴。

准备工作

搭建Navidrome个人音乐服务器,我们需要3个关键组件:

1.Navidrome:用于管理自定义的音乐资源

2.Music-Tag-Web:用于刮削音乐资源

何为刮削?就是根据音乐资源的名称自动获取该音乐资源的一切信息:歌曲名称、作者、专辑、歌词、相关图片、发行时间、歌曲类型、歌曲大小……

3.音流App:音乐客户端

注意:只要是支持Navidrome服务的客户端都可以使用,不限于音流App

具体操作

搭建Navidrome

1.复制以下命令,粘贴到服务器终端直接运行即可:

docker run -d \
   --name navidrome \
   --restart=unless-stopped \
   --user $(id -u):$(id -g) \
   -v /自定义music文件存储路径/music:/music \
   -v /自定义data文件存储路径/data:/data \
   -p 4533:4533 \
   -e ND_LOGLEVEL=info \
   deluan/navidrome:latest

事项说明:

① "自定义music文件存储路径"和"自定义data文件存储路径"需授权Root权限

② music文件用于存放显示的音乐文件

③ data文件用于存放Navidrome音乐服务器上的数据

④ 在执行该命令前,最好先去服务器后台的"防火墙"开放4533端口

⑤ 在执行该命令前,最好先去服务器的服务商后台开放"防火墙"的4533端口

2.等待上面的命令执行完成后,用以下方式进行第一次登录访问:

服务器ip地址:4533

首次登录,需设置用户名、密码。设置好之后,就可以登陆Navidrome音乐服务器了:

刘郎阁

3.将语言设置为中文

登录Navidrome音乐服务器后,点击右上角头像,然后点击"Personal个性化设置"中的将语言,设置为中文即可。

4.反向代理

如果觉得使用 "服务器ip+端口" 的方式登陆Navidrome音乐服务器不方便,可以进行反向代理设置,将登陆方式改为域名登录。至于怎么设置,这里我就不多说了,可以参考之前的文章,或自行百度。

搭建Music-Tag-Web

1.复制以下命令,粘贴到服务器终端直接运行即可:

docker run -d -p 8001:8001 -v /自定义music文件存储路径/music:/app/media -v /自定义data文件存储路径:/app/data --restart=always xhongc/music_tag_web:latest

事项说明:

① "自定义music文件存储路径"和"自定义data文件存储路径"需授权Root权限

② music文件用于临时存放需要刮削音乐资源的文件

③ data文件用于存放Music-Tag-Web服务器上的数据

③ 在执行该命令前,最好先去服务器后台的"防火墙"开放8001端口

④ 在执行该命令前,最好先去服务器的服务商后台开放"防火墙"的8001端口

2.等待上面的命令执行完成后,用以下方式进行第一次登录访问:

服务器ip地址:8001

首次登录,默认的账号和密码都是admin:

刘郎阁

刘郎阁

该图左边为音乐资源选择区域,右面为选中的音乐资源刮削之后展示的效果区域。

3.Music-Tag-Web的使用

如何使用Music-Tag-Web音乐资源刮削工具,我这里就以BEYOND的"真的爱你"为例,来作为演示:

① 将歌曲源文件上传至Music-Tag-Web服务器的"/music"目录下,即前面我们自定义的路径:"自定义music文件存储路径"

② 在浏览器中登录"服务器ip地址:8001",我们点击音乐资源选择区域中的刷新按钮,即可看到我们上传的音乐名称:

刘郎阁

③ 在左边的音乐资源选择区域选中我们需要刮削的音乐资源,点击中间的第二个或者第三个按钮:手动刮削和自动刮削

④ 此时会弹出来一个"自动修改标签",根据我们自己的需求进行适当的调整,然后点击"保存"系统即可开始进行刮削处理

⑤ 等待一会儿,就可以看到刮削后的效果了,如果不满意刮削后的效果,我们还可以接着继续进行刮削,或者手动刮削处理也行:

刘郎阁

4.反向代理

如果觉得使用 "服务器ip+端口" 的方式登陆Music-Tag-Web音乐资源刮削工具不方便,可以进行反向代理设置,将登陆方式改为域名登录。

资源合并

如何将Music-Tag-Web音乐资源刮削处理后的音乐资源上传到Navidrome音乐服务器上呢?答案很简单,直接复制粘贴即可:

直接将Music-Tag-Web中music目录下的音乐文件,直接剪切或复制粘贴至Navidrome中的music目录下即可。

到这里,我们的Navidrome个人音乐服务器就算弄好了。但现在有个问题,使用网页端来听歌,总感觉有点别扭,而且还不太方便,那咋办?不慌,音流App就能解决这个问题。

音流

音流App是一款免费音乐源连接播放器,可兼容Navidrome、Subsonic等多种音乐服务,能连接自建服务器访问个人音乐库。它资源丰富,以高性能音频引擎实现高品质播放,自动同步歌词。支持离线下载,具备智能搜索、播放列表管理等功能,支持多格式音频,界面简约且有丰富主题 ,还能基于算法进行智能推荐。

1.音流App下载:iOS其他平台

2.登录Navidrome

刘郎阁

主机地址填写你部署Navidrome的地址即可。

Navidrome个人音乐服务器到这里就算搭好啦!这下可以随时随地任性的听歌了!快打开播放器,找一首喜欢的歌,闭上眼睛,舒舒服服地“躺平”享受吧。

无损音乐资源下载站点推荐

1.MyFreeMP3
2.歌曲宝
3.Hifini
4.5song
5.天天无损音乐

微信公众号搜索 [刘郎阁] 关注并回复"1623"获取

260、小满

2025年5月21日 13:14

null

当夜卧稍迟,早起迎露,以应阳气渐满之势。晨起可循田埂而行,观麦浪轻摇,听蛙声初沸,导引气血,使志得舒。饮食宜增苦减辛,多食苦瓜、莴笋、新蚕豆以清心火,佐以薏仁、赤豆利湿健脾。

此时阳气将盛,湿气始滋,当调息静心,戒满戒溢,常怀虚谷之志。衣宜选亚麻、棉纱,疏风透汗,尤需护住脘腹关元。常饮三花饮(金银花、菊花、茉莉),清热解郁,兼防湿暑。运动当选八段锦、易筋经等和缓功法,如云舒卷,似穗低垂,使气机流畅而不壅滞。午后小憩半刻,敛阳养阴,此谓「持满之道」。

0元开88vip??

2025年5月22日 11:53

Photo by Ryan Born / Unsplash

因为最近淘宝买了不少东西,有些商家不送退货险,退货运费都挺贵的。

而且今天想买个好价鞋,但是需要88vip的券,@jun 倒是说帮我抢,但是有些券需要等,还要冲购物金,弄来弄去感觉也挺麻烦的。

所以想来想去还是自己开一个。

之前在咸鱼上几毛钱买过一个夸克云的88vip,估摸着中间应该是有什么门道,所以去这些地方做了一下搜索,了解了一下中间的套路。果然不出所料,感觉中间的利润还挺高的,现在这些大公司的羊毛被这些羊毛党可谓是薅到极致。

羊毛党的思路大概是这样:

  1. 去咸鱼、小红书等地方发帖指导别人开88会员,并说可以回收附带的视频、音乐权益,一般两个包年服务的回收价格在80元左右。
  2. 因为咸鱼、小红书平台的封控问题,引导客户至微信沟通。
  3. 顾客咸鱼上架宝贝,他们拍下,然后让客户开通88vip。
  4. 然后他们通过内部共享渠道拿到其他需要充值这两个服务的用户的手机号
  5. 我们配合进行充值
  6. 他们咸鱼付款。

我核查了一下已经了解到的收益点

  1. 推广金,走他们的邀请链接开通好像会有14-16元左右的推广奖励。
  2. 包年会员转手卖掉赚差价,网易云、优酷或芒果的包年服务价格咸鱼在100元左右,利润20元。
推手收益

可能还会有其他我没了解的收益点,但是上述两项至少会有差不多40元的收益,这可以说是无本买卖,如果能把中间的流程优化好,应该还有提升空间。

唯一的问题就是可不可以持续发展,这个完全看淘宝这边的政策管的严不严了。

特stse

2025年5月21日 16:01

书接上文。

小舅子他们在特种兵式的几天游玩之后表示最后一天想在家里休息一下,陪着他们吃了一天清淡的食物后,按了个脚,第二天一号他们便各自返回工作地去了。

而我们两口新的征程才刚要开始——下一站勒多曼因。

其实勒多曼因这个行程是五一之前就开始关注了的,因为之前徒步峨眉山、笔架山后加了很多户外群,五一前的偶然一天看到有人发了一个雪山冰湖皮筏艇的照片,并说准备开团出发,景点是我喜欢的那种风格,强度也还能接受,所以进群了解了一下,总共三天行程,:

  • 第一天成都集合,乘坐包大巴车出发去康定
  • 下午大概四五点到康定,吃晚饭,吃完后坐中转车去老榆林
  • 老榆林早起坐转运车出发去山脚下的格因草原开始徒步。
  • 徒步20公里,爬升到4300米左右,在山上营地住下。
  • 第三天早起冲顶,然后下山。
  • 各回各家。
  • 我们只需要带两天的路餐,其他的他们包。
  • 总费用650元1人,40人成团出发。

当时想着650这价格是真心不贵了,而且恰好能和小舅子他们来的时间错开,所以毫不犹豫的报了两个名。

但是在后面几天接待下舅子的过程中,群里消息不断,组织力度似乎不太够了,总共个加群的人有50多个,接龙说要去的也满了40个人,但是最后交钱的时候却不够包车的40人。

因为人数不够没办法拼大车,群主就开始降级说租20人小车,但是小车价格和大车只便宜了四五百块钱,最后摊下来每个人要多交100元,然后重新组织了一波接龙,不懂贵这100块会产生了多大的化学反应,这次直接20人都没凑齐...。

眼看着要散团了,但是我的瘾又被钓上来了,不想眼看着散团,索性就说我出个车,看有没有其他愿意出车的一起,凑个十几二十个人也还不错,终于在散团之际组织了三个车,最终有17人一起参与了这趟行程。

我们这个7坐车除开我和S,最终分两拨坐了5人。

先是3位泸州的朋友,两男一女,从泸州过来自贡上车,分别是快60的二叔,很会照顾人的刘姐,以及帅气的空少三笑。之后开车去成都接上2泰拳老师和一个川大的大学生。

后面就正式开始发图了,所以这里先吐槽一下这个团。

这个穿山甲不能算是正式的商业团,只能说是有一个大家信的过的群主去联系的车、住宿、餐食,规划好行程后在群里组织的,不过那点组织力几乎是没有,我们因为自驾所以最后减了100的团费,最终收的550元,但是当时承诺的东西一个都没达成,这里罗列已下,下次大家参加这种群性质的团一定要提前问清楚,留好文字存档。

  1. 说好的只用带路餐,但是最后第一天的晚餐需要自费,第二天的早餐也让我们自费,之后在山上说有鸡汤喝也没有准备,山上的营地的晚餐也很差,更别说早餐馒头还是馊的。
  2. 说好会花一千多请一个solo过贡嘎的领导,上山后发现人家是带了商团,几乎没管过我们团,另外一个年轻的小伙子说是被叫来管我们,但是就下山看到了他们一下,拍照服务之类的就更别提了。
  3. 转运车期初以为是什么大巴之类的,上去才知道想多了,全是那种小货卡,上面拉一块篷布就行了,后来听其他上山的说有人坐的车是拉牛羊的,全是屎...。

好了,亏已经吃了,只能说下次注意了。

行程分享正式开始。


我们5月3号从成都出发康定完美错开了车流高峰,全程几乎没有堵车,一路狂奔,车上听二叔说天全服务站有「此生必驾」318的牌子可以打卡,大家商量一致就在天全休息一会儿。

因为是自驾,所以时间比较充裕,路上又没有堵车,我们四点左右就已经到了康定,所以先去刘姐一个朋友开的餐馆吃了一顿菌菇牦牛火锅,老板做的蘸料一绝。

菌汤牦牛肉蒸气火锅

吃完饭才不到5点,所以大家准备一起去逛逛溜溜城。

我们本来是开车进城的,但是穿了一圈都没找到车位,最后在城区里堵了一个小时重新回到康定进城口的边上的一个集中停车场,20元一天,建议大家来这里玩不要想在城区找车位了。

卖青稞大饼,挺香的,下面的店铺15一个,走了一截发现全是从这个店拿的,人家只卖12元,这几步路就有3元的差价。

走到一半发现下雨了,还好穿的冲锋衣。并且凑巧这边有个广场在搞商业活动,似乎是请了什么藏族歌星在唱歌,虽然是藏文歌,但是唱的还挺好听的,不过摇一摇没识别出来。

大家累了坐在路边休息,因为雨大,二叔又没穿冲锋衣,所以暂时和他们分开,我们两口子单独逛去了。

康定旅游局局长同款抖音樱花大街。

溜溜城打卡,为什么叫溜溜城?

“康定溜溜城”这个充满诗意的别称,主要源于康定与《康定情歌》(又名《跑马溜溜的山上》)的深厚渊源。这首传唱世界的民歌让康定以“溜溜”之名广为人知,而“溜溜”在当地方言中更是承载了独特的文化意蕴,在康定及川西方言中,“溜溜”常用来形容事物“美好”“漂亮”“流畅”。

1. 溜溜城打卡 2. 小巷一景

藏族同胞们的广场舞是锅庄,那「锅庄」又是什么呢?。

锅庄(藏语称“果卓”或“歌庄”)是藏族民间广泛流传的一种传统集体舞蹈,尤其在四川、西藏、云南、青海等藏族聚居区盛行。它不仅是重要的娱乐活动,更是藏族文化、宗教和社会交往的载体。

康定的夜景还是非常漂亮的。

天色渐暗后和大家汇合,去往老榆林的民宿休息。

因为对这边的卫生环境不报什么期待,所以提前买了隔脏睡袋,用塑料袋+垃圾桶装了点热水泡脚,早早便躺下休息了。

1. 早起民宿外的风景 2.房间环境 3. 早起窗外的雪山 4. 隔脏睡袋

没有领队,更没有领队说让我们拍出发照,所以我索性叫上大家一起随便拍了一张。

17个人就这几个拍了照...。

坐上蓬卡就出发了,一路上看不到外面,但感觉早餐都要被颠出来了,估计外面到处都是导弹坑。

车上手机拍的远处雪山

1. 格因草原 2. 马帮的马

今天天气不错,依旧没看到领队...。

过木桥,水很凉,都是山上流下来的雪水。低山区

路过一个红石滩

低山区还有很多松树。

在徒步过程中海拔逐渐上升,慢慢树木就变成灌木和草甸,一路上会还经过各种路况:石头路,涉水路,草甸,沼泽等等。

一处绝佳的和雪山合影的机位

我也来一张,在这里不小心把登山杖掉落滚到峡谷下面去了。😅

一爬一个不吱声

有点累,生命力照片一张。

沿途的树木变成了灌木和草甸。

二叔年纪打了,自重也太重,大家都劝他坐马算了,他也听劝,800块直接座马去了营地。后来到营地听他说座马也听危险的,他上去的时候马没站稳前跪了,把他甩到沙地里了,我们在说幸好是沙地...。

终于走了快一半了,两岔河营地吃个午餐

一桌子的方便面碗和围了一桌子崩溃的驴友们。

继续出发

在一个绝望坡前碰到了其他队友,我不拍照大家是真不想拍合照....,碰上我这种队友是真挺好的。

中间那个是本次帮忙的「领队」

最不想走的就是这种河谷路段,全是碎石头。还碰上大风,风里还夹着小粒的冰雹。

最后一个绝望坡,进入雪线了,天上也飘起了小雪。

绝望坡后面的美景

终于到营地了!

营地景色全览

太累了,完全不想拍照用其他人发的照片把。

休息的棚子。营地的饭菜很不好吃,大家草草吃完便都上床准备休息。

因为营地在海拔4300米左右,加上棚子里氧气不太流通,所以帐篷里十几号人晚上过夜的时候都有不同程度的高反,症状基本上都是头疼。我后半夜睡了会,基本上是醒半小时睡半小时左右,我旁边的一个大学生几乎整夜没睡觉,两三点的时候还爬起来说想下山...,一整晚的噪音把他旁边的一个能睡着的老驴折腾到一晚上没怎么睡,感觉老驴也很难受哈哈。

S就是说觉得冷,因为棚子四处漏风,漏风最严重的就是我们躺下后头顶的地方,风大的时候我问不得不起来给她过上急救毯才好些。

后半夜风停了,我帮她把睡袋裹紧,把头罩住就留个鼻子嘴巴出来,才算是踏踏实实睡了好几个小时,比我有出息多了。

第二天四五点大家便陆续起床了,还能看到星空,但是我手机和充电宝都被冻没电了。

早餐的包子是馊的,我就喝了点稀饭,吃了自己带的面包。

因为出发的时候黑黢黢的,手机也没电了,就没拍什么照片,同行的刘姐借了他的充电宝给我用,让我得以续命。

天终于渐渐亮起来,发现我们被大雾追赶着,手机也有一些电了。

跟着前面的队伍的步伐,发现我们没有走常规的泥巴路线,而是条沿着河谷上行的碎石路段,然后踩着雪沿着山脊线的去往山顶,体验顶满,后来听前面的队友说起才知道,他们跟着一个商团的线路走的。

体验非常棒。

开始爬山脊线。几乎六七十度的大坡,踩着雪前进,非常爽!

就冲这一段爬坡这一趟也值了。

这里的雪一脚下去都能没过膝盖,躺雪上面来一张把。

旁边那条是常规上来的路,马上到了,看上面云层上去的时候应该还能看见雪山。

登顶。

说实话因为湖还没完全化开又被白雪覆盖,也不能去湖面拍照,导致山顶的风景和可玩性很一般,这块白色不知道的人还以为只是一片平坦的雪地。

别的不说,先打个卡把。

看好了,这一剑,会很帅!(换我来拍的时候后面忽然就起了大雾,没拍上😭。)

因为风雪,准备下山了。

后面下山的风景就不发了,下山的时候倒是碰上领队了,因为要做扫尾工作,我们又是垫底的,所以一直被催着下山,搞的我们下山速度飞快,后半段几乎都是跑着下山的。

回到起点。

下雨了,安排第一波人坐皮卡,这波人回到民宿衣服基本上都湿了,我依旧做的蓬卡下山,不过因为没休息好,有点晕车的感觉,差点吐了。

之后便是返程,回去的时候只有泸州的三位和我们一起,另外两个坐其他车了。

也好,我们这几个人更聊的来,我们边开边聊,倒也不觉得累,还拉个群准备下次有什么活动再一起出行。

直到晚上11点30分才安全到达自贡,他们另外拼车叫了个野猪儿回泸州。

这次行程结束我缓了三天才缓过来,每天下班回家几乎粘上枕头就睡,每天的睡眠时长破天荒的突破了8个小时,临近9小时!

下次出行真的不想再开车了。

  • 12  
  • 测试  

CommentNotifier

2025年5月22日 10:11

CommentNotifier

Typecho博客评论邮件提醒,支持异步回调(异步回调优点就是不影响博客评论提交速度),支持编辑邮件模板,支持第三方开发邮件模板,发信方式支持SMTP与阿里云邮箱推送,支持表情回调(需要主题支持)

安装教程

下载后将压缩包解压到 /usr/plugins 目录
文件夹名改为CommentNotifier
登录管理后台,激活插件
配置插件 填写SMTP参数/阿里云邮箱推送参数
支持显示大部分主题的评论表情

软件架构

typecho版本为1.2.0及以上
php: >=7.2.0
如果启用SMTP加密模式PHP需要打开openssl扩展
邮件服务基于PHPMailer

下载插件

开源地址:https://github.com/jrotty/CommentNotifier

Farallon - 又一款Typecho Theme [置顶]

作者老孙
2024年5月20日 18:43
AI摘要:Farallon是一款移植自`hugo-theme-farallon`的Typecho主题,支持多种自定义功能,如观影页面、友情链接、说说页面等。主题通过Markdown语法实现好物页面的数据获取,并优化了复制链接方式。更新内容包括增加图片灯箱、删除QQ评论通知、显示评论者IP信息等。主题适用于多种插件和API,如豆瓣、Neodb、Memos和Mastodon。

说明

这是我移植的第一款主题

来自hugo-theme-farallon

预览

预览

更新

2025.3.26 0.7.1

两种翻页加载方式

主题设置 travel mid 展示 https://www.imsun.org/category/life/ 样式
设置 memos mid 展示 https://www.imsun.org/category/bb/ 样式

首页列表 说说和 图文 根据后台设置的分类mid 混合展示

删除了添加第三方评论的功能

增加了编辑页面的自定义字段的说明

2025.3.18

更改版本号为0.7.0

修改了好物页面的获取方式,使用Markdown语法表格数据获取

以下为示例

| 图片链接 | 商品名称 | 价格 | 商品链接 | 推荐理由 |
|---------|---------|------|----------|----------|
| https://example.com/ | 商品A | ¥99 | https://example.com/product1 | 这是一个很好的产品 |
| https://example.com/ | 商品B | ¥199 | https://example.com/product2 | 非常推荐购买 |

表格的内容不可缺少

优化了复制链接的方式

说说页面统一使用lightbox2

2025.3.15
增加图片灯箱
删除QQ评论通知
删除显示评论归属地

2024.12.25

使用API查询来获取IP归属地
根据等级设置名字颜色,鼠标悬停在昵称时显示等级,在头像显示评论数

2024.12.12

给评论区加上身份等级
显示留言者的IP信息

删除归档中的字数统计

把豆瓣的设置移到了自定义页面,使用自定义字段设置

使用指南

观影页面说明

by 豆瓣

参照Docker 自动同步豆瓣书影音记录部署

使用自定义字段设置douban 默认为https://db.imsun.org

https://www.imsun.org/movies

by Neodb 0.6.3新增

参照[article id="1688"]

获得获取neodb API
使用自定义字段设置neodb 默认为https://neodb.imsun.org

https://www.imsun.org/neodb

友情链接说明

使用此模板功能必须使用links 插件
https://www.imsun.org/links

可使用 寒泥 大佬制作的版本或者其他版本

说说页面说明

by memos

Memos请自行部署
https://www.imsun.org/memos/
利用memos实现动态获取说说,仅支持memos v0.20.0以下版本
使用自定义字段设置memos
在自定义字段中填入memos值为memos地址,不带/
在自定义字段中填入memosID默认值为1, 当您的ID 不为1时 需要设置
在自定义字段中填入memosnum默认值为20,默认获取20条最近的memo

by mastodon

https://www.imsun.org/talks/
支持mastodon gts pleroma
根据
[article id="1664"]
或者
[article id="1643"]
获得API地址
在自定义字段中填入tooot值为Mastodon API 地址 例如 https://www.imsun.org/toot.json

标签页面

https://www.imsun.org/tags/

分类页面

分类图片按照mid.jpg的格式存放在对应的目录中
然后填入相对的url
https://www.imsun.org/category/

归档页面

https://www.imsun.org/archives/

统计页面 0.6.2 移除

https://www.imsun.org/site/

首页摘要

优先获取自定义字段summary,其次显示默认字数摘要

好物页面

基于memos
https://www.imsun.org/goods
在自定义字段中填入memos值为memos地址
在自定义字段中填入memosID默认值为1,不为1时才需要设置
在自定义字段中填入memostag默认值为好物,不为好物时才需要设置

在0.7.0版本移除了以上方式,改用markdown语法实现.

下载地址

https://github.com/jkjoy/typecho-theme-farallon/releases

Drawnix:无限画布上的开源白板工具

作者ysicing
2025年5月21日 20:40

这个项目很早之前就关注过, 那时还没提供 Dockerfile😁

简介

Drawnix 是一款基于 Plait 框架打造的免费开源白板工具,提供思维导图、流程图和自由绘画等功能。依托插件化架构,用户能够随需扩展多种交互组件。每一次操作都能实时保存至浏览器缓存,确保内容不丢失

核心特性

  • 多样化绘图模式
    • 支持思维导图、流程图与自由画笔,满足不同场景需求
    • 可插入图片,通过简单拖拽丰富画面
  • 高效编辑体验
    • 撤销、重做、复制与粘贴操作流畅
    • 自动保存功能避免意外丢失(浏览器缓存)
  • 无限画布与导出能力
    • 通过缩放与滚动打造无边界创作空间
    • 可将画布导出为 PNG 或 JSON(.drawnix) 文件
  • 插件机制与兼容性
    • 插件架构支持 Angular、React 等多种 UI 框架
    • 自带 Slate 富文本扩展,后续可接入更多文本编辑插件
    • 未来可通过社区插件实现更多应用场景

项目

名称灵感

Drawnix ,源于绘画( Draw )与凤凰( Phoenix )的灵感交织。
凤凰象征着生生不息的创造力,而 Draw 代表着人类最原始的表达方式。在这里,每一次创作都是一次艺术的涅槃,每一笔绘画都是灵感的重生。
创意如同凤凰,浴火方能重生,而 Drawnix 要做技术与创意之火的守护者。
Draw Beyond, Rise Above.

与 Plait 框架的深度结合

Drawnix 底层依托作者公司开源的 Plait 画图框架,这是知识库产品的重要技术沉淀。两者紧密结合让开发者享受“一键开箱即用”的便捷,同时还能在业务分层中灵活装载自定义插件。

这款工具不仅是面向个人创作者,同样适用于团队协作与在线教学。无限画布中,每一次涂抹都让构思变得更生动、更立体,技术与想象力在此汇合,带来前所未有的白板体验。

私有化部署

作者已经提供了 Dockerfile,基于此 Dockerfile,我构建了一个镜像, 默认监听 80 端口,除此外无特殊配置

  • ccr.ccs.tencentyun.com/k7scn/drawnix 基于此97ab1d4构建完成

compose 部署

  • docker-compose.yaml
services:
  drawnix:
    image: ccr.ccs.tencentyun.com/k7scn/drawnix
    container_name: drawnix
    ports:
      - '100.90.80.15:8088:80'
    restart: always

然后配置 caddy 代理即可。

k3s 部署

我主要使用 k3s 部署,对外访问使用 cft

  • drawnix.yaml
---
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
metadata:
  labels:
    app: drawnix
  name: drawnix
  namespace: kc-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: drawnix
  updateStrategy:
    type: InPlaceIfPossible
  template:
    metadata:
      labels:
        app: drawnix
    spec:
      tolerations:
      - operator: Exists
      nodeSelector:
        node-role.kubernetes.io/kc: "true"
      containers:
      - image: tcr.china.12306.work/github/drawnix
        imagePullPolicy: Always
        name: drawnix
        ports:
        - containerPort: 80
          protocol: TCP
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
      restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: drawnix
  name: drawnix
  namespace: kc-system
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: drawnix
  type: ClusterIP

apply 之后,在 cfd tunnel 管理页添加公共主机名

演示站

欢迎自建,不保证 SLA,暂时不知道开源版与官方演示有什么区别。


欢迎订阅我的微信公众号,同步更新 😁

说说:又开始出游计划了

2025年5月20日 22:35

前几天说要出去玩,结果突然就拍板定下了,这次计划去爬泰山。途中经过北京顺便也去一趟故宫参观一下!具体的日程可不像之前去西安玩做的那么充足,自带主打一个随性!故宫和泰山其实虽然看起来是核心内容,但是我的心思还是放在吃上了哈哈哈哈哈!

读《巴菲特之道》摘抄

2025年5月20日 09:50

刚刚的伯克希尔年会,巴菲特宣布了即将退休,这将又是一个时代终结。于是本月决定看看跟巴菲特相关的书,《巴菲特之道》这本书介绍了巴菲特的投资理念,内容也不长花了几天就看完了。

巴菲特投资哲学的成长

巴菲特从小就接触投资,做生意积累本金,通过股市赚钱。他人生有几个重要的人,格雷厄姆是他的导师,巴菲特从他这里学会了安全边际,也就是要买价格低于价值的股票。巴菲特在学校是格雷厄姆的学生,毕业后也到格雷厄姆的公司工作了几年,从他这里巴菲特还学会了独立思考。

巴菲特读了费雪的《普通股和不普通的利润》之后,在他的投资理念上更加转向费雪。费雪更加关注公司的成长潜力,以及公司是否有好的管理层,这与格雷厄姆的是两种筛选公司的理念。而巴菲特将两种理念融合,发展出自己的投资准则。

查理芒格,跟巴菲特一样是格雷厄姆的学生,他们是一生的事业伙伴,两人建立了密切的共生关系。

巴菲特的投资准则

巴菲特的投资准则有十二条,分为企业、管理、财务、市场共四个分类,他的很多投资都践行了这些准则的部分或者全部,具体书中单独一章进行了讲解。

具体的准则如下:

企业准则

企业应该简单易懂;

企业应该有持续稳定的运营历史;

企业应该有良好的长期远景;

管理准则:

管理层是否理性?

管理层对股东是否坦诚?

管理层能否抗拒惯性驱使?

财务准则:

  1. 重视资产净收益,而不是每股盈利

  2. 计算真正的”股东盈余“

  3. 寻找高利润率的企业

  4. 企业每留存一美元,至少产生一美元的市值

市场准则

  1. 公司是否有价值? 巴菲特通过现金流和合适的折现率确定企业价值,他使用美国政府长期国债利率作为折现率。价值是未来现金流折现后的现值;成长是确定价值的一个因素。

  2. 当前是否是买入的好时机,价格是否好? 合适的价格+公司表现符合预期才能保证成功,也就是安全边际。

心理学和数学在巴菲特投资中的体现

书中关于持股数量的数学分析,虽然说分散投资可以降低风险,但同时也会降低利润。而巴菲特正是集中投资的范本。书中这段话说的很好,“当世界给予你机会的时候,聪明的投资者会出重手。当他们具有极大赢面时,他们会下大注。其余的时间里,他们做的仅仅是等待。”

巴菲特还是典型的长期主义投资者,通过他的准则可以看到他在选择购买的股票时,也就已经相信这家公司在未来的十年能够创造相当的利润。

系统1与系统2

在丹尼尔·卡尼曼的《思考,快与慢》中首次了解了系统1与系统2,在这本书中再次被提及。系统1是我们的直接思维,一般不花时间,会快速做出判断。而系统2的思维方式是我们认知过程的反思,需要我们投入努力。无论是投资还是做决定,我们都有必要训练系统2,去认真思考,进行推敲。同时在作者看来,具有系统2思维方式的人更加有耐心。

总结

限于个人能力,内容写的比较乱。总结一下巴菲特的成功,理性和耐心是他成功的关键。对于普通人,如果不能够做到这些,并且不愿意花费时间去研究公司,那么巴菲特推荐我们去购买指数基金。

最后用书中的一段话作为结尾。一个人在一生中很难做出数以百计的正确决策,只要做出为数不多的智慧决策就已经足够了。

摘抄

理性的基石就是回望过去、总结现在,分析若干可能情况,最终做出抉择的能力。

投资是经过深入分析,可以承诺本金安全并提供满意回报的行为。不能满足这些要求的就是投机。

格雷厄姆的两项投资原则: 一是不要亏损;二是不要忘记第一条。

任何投资的价值都是公司未来现金流的折现。

巴菲特从格雷厄姆那里学到的最为重要的一课就是:成功的投资来源于,购买那些价格大大低于价值的股票。

从格雷厄姆那里,巴菲特学会了独立思考。如果你是在脚踏实地的基础上得出合乎逻辑的结论,就不要因为别人的反对而耽于行动。

从费雪那里,巴菲特学到了沟通的价值

他定义特许经营权企业的产品或服务:①被需要或渴望;②无可替代;③没有管制

巴菲特说:“市场就像上帝一样,帮助那些自助的人;但和上帝不同之处在于,市场不会原谅那些不知道自己在干什么的人。

在你占据优势的时候要加大筹码。

巴菲特说:“我们所要做的全部就是,将盈利概率乘上可能盈利的数量,减去亏损的概率乘上可能亏损的数量。

当世界给予你机会的时候,聪明的投资者会出重手。当他们具有极大赢面时,他们会下大注。其余的时间里,他们做的仅仅是等待。

巴菲特的风险观:风险与股价之波动无关,与那些个股未来产生利润的确定性有关。

短期而言,股市是台投票机;而长期而言,股市是台称重机。

”首先是将股票视为企业一样,“这将给你一个完全不同于股市中大多数人的视角”。其次是安全边际概念,“这将赋予你竞争优势”。再次是对待股市具有一个真正投资者的态度。

为何懂得人们的冲动是如此有价值:①你能从中学会如何避免多数人的错误;②你可以识别他人的错误,并从中捕捉到机会。

单单有智力不足以取得投资成功,与大脑的容量相比,将理性从情绪中分离出来的能力更为重要。

理性的基石就是回望过去、总结现在,分析若干可能情况,最终做出抉择的能力。

看完评论一下吧

初涉 ML Workflow 系统:Kubeflow Pipelines、Flyte 和 Metaflow

作者四火
2025年5月19日 07:18

入职 Coupang 两个月了,第一个月主要上手和开发 BOS(Business Operating System)系统,第二个月开始调研选型 ML Workflow 平台。前者目前来说相对比较简单,后者对我来说是一个新坑,也比较有意思,随便写写技术上的体会。

先扯点题外话,其实这次求职有几个比较符合我预期的机会,可在思考之后,我基本上毫不犹豫就选择了 Coupang 这一家。最主要的原因,并非因为雇主,而是因为要做的事情。一个相当规模的团队,在大干一场的早期阶段,要在搭建起属于自己相当规模的 AI infra 来。

我觉得软件行业的巨大的变革,新世纪以来就三次,第一次是互联网应用的崛起,我太小没能做啥;一次是十几年前的 cloud,看着它从爆发式增长到如同水和电一样进入我们的生活,可我算是错过了它比较早期的阶段,即便相当长的时间内我在 Amazon,但是我却并不在 AWS;而这一次,当 AI 的浪潮再来的时候,我就很想行动起来,真正投身其中。程序员的一生能有几个赶这样大潮的机会呢,我不想再错过了。虽说我没有 AI 的技术背景,但我知道 ML infra 到 AI infra 却是个我可以切入的角度——从我最初接触软件开始,尤其是学习全栈技术的时期开始,我就认定,技术是相通的,这十几年来我一直在如此实践。因此在调查和思考之后,我觉得这是一个我不想错过,并且更重要的是自认为能够抓住的机会。

当然,就此打住,我目前只是这个领域的初学者,因此理解并不深入。

Why ML Workflow?

接着说正题,在这一个月之前,虽然我经历过不少关于 workflow 的团队,虽然我参与过从零写完整的 workflow 引擎,但这些都是针对于通用 workflow 而言的,我对于机器学习的工作流,也就是 ML workflow 可以说一无所知。于是在问题和需求调查的过程中,第一个关于它的问题就自然而然出现了,我们是否真的需要 ML workflow,而不是通用的 workflow 系统?

其实,这主要还是由于 ML 的生态所决定的。通用 workflow 可以完成很多的事情,但是在机器学习到 AI 的领域内,这个过程中最主要的目的就是把 raw data 给转换成经过训练和验证的 model,其中有很多部分都是有固定模式,因而自成体系的。举例来说:

  • ML workflow 关注数据处理和 ML 或者 AI model 的生命周期,但是通用的 workflow 往往关注将业务流程自动化;
  • ML workflow 需要将 artifact 管理、model registry、model insights 和 experiment tracking 等工具集成起来,但是通用的 workflow 往往是业务 application 层面的集成;
  • ML workflow 执行的 task 往往需要高 GPU 使用和高内存,这和通常我们讨论的 workflow 的 task 对于 CPU 的使用完全不同。

总之,ML workflow 更像是一个 workflow 中的重要分支,它的特异性显著,因而从架构上它有很多在我们谈论通常 workflow 的时候不太涉及的特点,并且它们具有明显的共性。

ML Workflow 的固定套路

Workflow 这样的系统,和很多 infra 系统不同的地方在于,它具有全栈的特性,需要从端到端从用户完整的 use case 去思考。回想起通用的 workflow,我们会想,用户会去怎样定义一个 Workflow,怎样运行和测试它,并且怎样部署到线上跑起来。这其中的前半部分就是 development experience,而后半部分则是 deployment experience。

首先,对于 development experience 这个角度,ML workflow 有它独特的地方,其中最主要的就是 Python SDK。

通用 workflow 我们讲定义一个新的 workflow 的时候,我们通常都需要写一个 DSL,里面定义了一大堆 task 和依赖关系,而对于做得比较好的 workflow 系统来说,可能还需要一个可视化的 drag-and-drop 界面来方便地创建 workflow。

但是对于 ML workflow 来说,它最特殊之处是对于 Python code 的无缝集成。因为 Python 之于 ML 的地位就像是 Java 之于企业架构的地位,任何一个 ML workflow 客户端首先要考虑支持的编程语言就是 Python,用户通过往大了说是 SDK,而往小了说则是简单的 Python decorators,就可以定义 task 和 workflow。比方说,一个简单的 Flyte 的 hello world:

from flytekit import task, workflow

@task
def say_hello(name: str) -> str:
    return f"Hello, {name}!"


@workflow
def hello_workflow(name: str = "World") -> str:
    return say_hello(name=name)

在 ML workflow 的世界中,这是除了 DSL 和视图化之外的第三种定义 workflow 和 task 的方式,也是必须具备的方式。

第二个,对于 deployment experience 的角度,大致上是基于 Kubernetes 从 control plane 到 data plane 固定的交互机制。

我不知道这是不是一种关于 ML workflow 的约定俗成,但是通过调研 Kubeflow Pipelines、Flyte 和 Metaflow,我发现这三种对于 control plane 到 data plane 的交互模式是出乎意料地一致。

  • KubeFlow Pipelines: client [KFP SDK] -> control plane [API Server -> K8s APIs (CRD changes) -> Workflow Controller / K8s Operator] -> data plane [K8s API -> creating Task Pods -> blob storage]
  • Flyte: client [Flyte SDK] -> control plane [Flyte Admin -> K8s APIs (CRD changes) -> Flyte Propeller / K8s Operator] -> data plane [K8s API -> creating Task Pods -> blob storage]
  • Metaflow: client [Metaflow SDK] -> control plane [Metaflow Service -> K8s APIs (CRD changes) -> Metaflow Scheduler / K8s Operator] -> data plane [K8s API -> creating Task Pods -> blob storage]

注:也有把 Operator 那一层归为 data plane 的,我觉得都说得过去。

其中 Metaflow 说的是使用 Kubernetes 集成的情况,因为它并不是非得依赖于 Kubernetes。

但大多数使用都是基于 Kubernetes 的,而且基本上都是这个套路,control plane 的 service 收到请求以后,通过创建 K8s CRD objects 的方式告知 workflow controller(scheduler)来执行 workflow,对于 task 的执行通过调用 data plane 的 K8s API 来创建 task pods 执行。

对于特殊的 task,需要交由特殊的 K8s operator 来执行,那么这个 “交由” 的过程,也是通过 K8s 这一层的 CRD change 来实现——Propeller 负责创建 CRD,而对应的 operator 负责 monitor 相应的 CRD 改变并相应地执行任务。Propeller 和 operator 二者互相并不知道对方的存在。这种方式对于保证 operator 的重用性和跨 workflow 系统的统一性简直是太棒的设计了,我们在 try out 的时候,就让 Kubeflow Pipelines 系统中的 operator,去执行 Flyte 给创建的 PTJob 和 TFJob。

关于架构,我觉得 Flyte 的这张架构图对于 components 层次的划分说得非常清楚,下面的 control plane 和 data plane 是可以有属于自己的 cluster 的,不过值得说明的是,真正最终执行的 task pods,也就是图中的最下面的 K8s Pod,也是可以放在另外的 cluster 上,由远程的 K8s API 调用触发的,这样就可以带来更多一层的灵活性:

[Update on 5/31] 后来看到这篇非常好的分享 《Flyte School: Flyte Architecture Deep Dive》,对于初步了解的工程师来说,推荐观看。下图也来自于它。

ML Workflow 的特性比较

再来比较这三个 workflow 的优劣,我并不打算列全,而是简单说说自己印象最深的几点:

  • Kubeflow Pipelines 基本上有着最大的社区,因此它相对比较成熟,有自带的基于 CRD 的 K8s-native 的集成,因此可以直接跑 TensorFlow job 和 PyTorch job 之类的;UI 功能也比较强大,可以通过 drag-and-drop 来定制 workflow,也支持 yaml 文件创建 workflow。
  • Flyte 最吸引人的是它的 Strong Typing,很多错误能够在编译期本地就能够发现(Kubeflow pipelines 和 Metaflow 都只是 hints);开发过程中,本地直接就能跑,而不需要什么 container;对于 multi-tenancy 支持得最好(比如 RBAC 和 tenant 的 Quota 机制)。
  • Metaflow 的 setup 特别简单,而且本地可以直接调试;它对于 AWS 的一些 service 直接可以集成使用,特别方便(比如 Step Functions);Kubernetes 并不是一个依赖,也可以跑在 VM 上等等。

在我把这三者全部在 EKS 上搭了一遍并使用了一圈,也仔仔细细对别了特种特性和优劣之后,我对于 Flyte 的特性比较感兴趣,我觉得它们对我们团队也比较有用。

具体来说,很多区别但最重要的是两个:一个是 strong typing,其它两个都只支持 Python 类型的 hints,就这一点上,和一些 ML engineer 也讨论过,把问题发现在本地,是非常吸引人的;再一个是 multi-tenancy,对其 Flyte 有很多原生的特性支持,在平台完成之后,我们希望把平台上 ML 的能力开放出去,因此这是很重要的一个特性。此外,我也在考虑对于一个 control plane + 多个 data plane 这种 use case 的情况,这部分的需求还比较模糊,但是 Flyte 依然是这方面支持特性相对比较多的一个。

无论最后的结论为何,我希望我们能够比较灵活地部署选中的这个 ML workflow system,比方说,在 CLI 上,我们考虑在更高维度建立出一层,用户使用同样的命令,无论下面执行的 workflow 系统是什么,都不需要改变,这样一来,等到未来如果我们需要支持第二个,应该能够比较容易地整合进去。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》

解锁高效开发利器——Gitness,助力你的DevOps之旅

作者ysicing
2025年5月18日 22:19

在快节奏的软件开发世界中,高效的代码托管和自动化部署是每个开发团队的追求。你是否曾为繁琐的 CI/CD 流程而头疼?是否希望有一个简单易用、功能强大的平台来管理你的代码和流水线?今天,我们为你介绍一款开源神器——Gitness,它将彻底改变你的开发体验。

作为 Drone 和 Gitness 深度定制用户,我还值得很推荐的。

什么是 Gitness

Gitness 是一个集代码托管与自动化 DevOps 流水线于一体的开源开发平台。作为 Drone CI 的下一代产品,Gitness 不仅保留了强大的持续集成(CI)功能,还新增了源代码托管能力,支持本地部署和 Docker 容器化运行。无论是个人开发者还是小型团队,Gitness 都能提供灵活、高效的解决方案。

核心亮点

  • 一体化平台:代码托管 + CI/CD 流水线 + 制品库
  • 用户友好:提供直观的 UI 界面、REST API,满足不同开发者的使用习惯
  • 灵活部署:支持私有化本地部署
  • 开源免费:完全开源,社区驱动,适合预算有限的团队(非常适合个人用户)

对比 Drone、Woodpecker

  • 目前支持 Runner 有限,仅支持 docker
  • 新增的制品库支持的种类相对比较少,常见的 Docker、Helm 是没问题的
  • 目前数据库仅支持 PG、Sqlite3
  • 流水线
    • 日志相比较 v2 仅支持存储到数据库,暂不支持存储到对象存储
    • 不支持重复执行(如定时、失败重试)
    • 流水线语法变更,相比较 Drone、Woodpecker 等 v2 版本的语法,功能缺少较多,但是满足基本使用
  • 代码仓库功能完善,日常使用是没问题

部署

环境变量配置

目前官方文档未提供,需要参考官方文档 harness/harness#config.go

部署

镜像:

  • harness/harness:unstable
  • 国内镜像 ccr.ccs.tencentyun.com/k7scn/harness:unstable
  • docker-compose.yaml
services:

  gitness:
    image: harness/harness:unstable
    container_name: gitness
    restart: always
    ports:
      - "3000:3000"
      - "3022:3022"
    volumes:
      - /data/gitness:/data
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - GITNESS_DEBUG=true
      - GITNESS_TRACE=true
      - GITNESS_GIT_TRACE=true
      - GITNESS_GIT_DEFAULTBRANCH=master
      - GITNESS_WEBHOOK_ALLOW_LOOPBACK=true
      - GITNESS_WEBHOOK_ALLOW_PRIVATE_NETWORK=true
      - GITNESS_METRIC_ENABLED=false
      - GITNESS_PRINCIPAL_ADMIN_UID=ysicing
      - GITNESS_PRINCIPAL_ADMIN_DISPLAY_NAME=ysicing
      - GITNESS_PRINCIPAL_ADMIN_EMAIL=ysicing@12306.work
      - GITNESS_PRINCIPAL_ADMIN_PASSWORD=ysicing
#     - GITNESS_SMTP_HOST=
#     - GITNESS_SMTP_PORT
#     - GITNESS_SMTP_USERNAME
#     - GITNESS_SMTP_PASSWORD
#     - GITNESS_SMTP_FROM_MAIL
#     - GITNESS_SMTP_INSECURE
      - GITNESS_GITSPACE_DEFAULT_BASE_IMAGE=ccr.ccs.tencentyun.com/k7scn/base:dev-ubuntu-24.04
      - GITNESS_UI_SHOW_PLUGIN=true
      - GITNESS_URL_GIT=http://192.168.23.16:3000/git
      - GITNESS_URL_UI=http://192.168.23.16:3000
      - GITNESS_URL_API=http://192.168.23.16:3000/api
      - GITNESS_URL_GIT_SSH=ssh://192.168.23.16
      - GITNESS_SSH_ENABLE=true
#      - GITFOX_SSH_PORT=3022
#      - GITNESS_CI_PARALLEL_WORKERS=2
#      - GITNESS_GIT_ROOT
      - GITNESS_CI_PLUGINS_ZIP_URL=https://c.ysicing.net/oss/offline/master.zip

部署完成,使用 IP:3000 访问. 账号密码需要使用设置的 GITNESS_PRINCIPAL_ADMIN_UIDGITNESS_PRINCIPAL_ADMIN_PASSWORD

目前配置参数过多,稍微配置不对还可能有 bug。针对这个

  • 我司会开源 GitFox,基于 Gitness 定制的 fork 版本,一些额外特性功能的补充(企业、与禅道的联动)
  • 我个人也维护了一套 Gitness 的魔改版本 Gitless,在上游的版本增加了个人开发者常用的特性(仅从我个人使用)

使用说明

这里简单过一些,后面会专门写一些文章介绍一下。

创建组织

对应其他 Git 服务的组织(ORG/GROUP)

创建仓库

流水线

执行流水线,目前支持部分 drone 插件,基本可以拿来就用如果熟悉 drone 和 gitness 的流水线语法的话

制品仓库

目前支持如下,感觉是借鉴了 Nexus,基本已经覆盖了日常使用。

WebIDE

总结

Gitness 不仅是一款工具,更是开发者高效协作的伙伴。无论你是追求极致效率的个人开发者,还是需要稳定流水线的小型企业团队,Gitness 都能满足你的需求。不过有一说一,目前 Gitness 还是处于快速发展阶段,有 Bug 是正常不过的事,但是基本功能还是没啥问题的。其次他们开源版本支持力度不是那么紧急,更多的还是服务他们的 SAAS 产品。


用户外活动填满五一:勒多曼因篇

2025年5月18日 15:45

null

书接上文。

小舅子他们在特种兵式的几天游玩之后表示最后一天想在家里休息一下,陪着他们吃了一天清淡的食物后,按了个脚,第二天一号他们便各自返回工作地去了。

而我们两口新的征程才刚要开始——下一站勒多曼因。

其实勒多曼因这个行程是五一之前就开始关注了的,因为之前徒步峨眉山、笔架山后加了很多户外群,五一前的偶然一天看到有人发了一个雪山冰湖皮筏艇的照片,并说准备开团出发,景点是我喜欢的那种风格,强度也还能接受,所以进群了解了一下,总共三天行程,:

  • 第一天成都集合,乘坐包大巴车出发去康定
  • 下午大概四五点到康定,吃晚饭,吃完后坐中转车去老榆林
  • 老榆林早起坐转运车出发去山脚下的格因草原开始徒步。
  • 徒步20公里,爬升到4300米左右,在山上营地住下。
  • 第三天早起冲顶,然后下山。
  • 各回各家。
  • 我们只需要带两天的路餐,其他的他们包。
  • 总费用650元1人,40人成团出发。

当时想着650这价格是真心不贵了,而且恰好能和小舅子他们来的时间错开,所以毫不犹豫的报了两个名。

但是在后面几天接待下舅子的过程中,群里消息不断,组织力度似乎不太够了,总共个加群的人有50多个,接龙说要去的也满了40个人,但是最后交钱的时候却不够包车的40人。

因为人数不够没办法拼大车,群主就开始降级说租20人小车,但是小车价格和大车只便宜了四五百块钱,最后摊下来每个人要多交100元,然后重新组织了一波接龙,不懂贵这100块会产生了多大的化学反应,这次直接20人都没凑齐...。

眼看着要散团了,但是我的瘾又被钓上来了,不想眼看着散团,索性就说我出个车,看有没有其他愿意出车的一起,凑个十几二十个人也还不错,终于在散团之际组织了三个车,最终有17人一起参与了这趟行程。

我们这个7坐车除开我和S,最终分两拨坐了5人。

先是3位泸州的朋友,两男一女,从泸州过来自贡上车,分别是快60的二叔,很会照顾人的刘姐,以及帅气的空少三笑。之后开车去成都接上2泰拳老师和一个川大的大学生。

后面就正式开始发图了,所以这里先吐槽一下这个团。

这个穿山甲不能算是正式的商业团,只能说是有一个大家信的过的群主去联系的车、住宿、餐食,规划好行程后在群里组织的,不过那点组织力几乎是没有,我们因为自驾所以最后减了100的团费,最终收的550元,但是当时承诺的东西一个都没达成,这里罗列已下,下次大家参加这种群性质的团一定要提前问清楚,留好文字存档。

  1. 说好的只用带路餐,但是最后第一天的晚餐需要自费,第二天的早餐也让我们自费,之后在山上说有鸡汤喝也没有准备,山上的营地的晚餐也很差,更别说早餐馒头还是馊的。
  2. 说好会花一千多请一个solo过贡嘎的领导,上山后发现人家是带了商团,几乎没管过我们团,另外一个年轻的小伙子说是被叫来管我们,但是就下山看到了他们一下,拍照服务之类的就更别提了。
  3. 转运车期初以为是什么大巴之类的,上去才知道想多了,全是那种小货卡,上面拉一块篷布就行了,后来听其他上山的说有人坐的车是拉牛羊的,全是屎...。

好了,亏已经吃了,只能说下次注意了。

行程分享正式开始。


我们5月3号从成都出发康定完美错开了车流高峰,全程几乎没有堵车,一路狂奔,车上听二叔说天全服务站有「此生必驾」318的牌子可以打卡,大家商量一致就在天全休息一会儿。

因为是自驾,所以时间比较充裕,路上又没有堵车,我们四点左右就已经到了康定,所以先去刘姐一个朋友开的餐馆吃了一顿菌菇牦牛火锅,老板做的蘸料一绝。

菌汤牦牛肉蒸气火锅

吃完饭才不到5点,所以大家准备一起去逛逛溜溜城。

我们本来是开车进城的,但是穿了一圈都没找到车位,最后在城区里堵了一个小时重新回到康定进城口的边上的一个集中停车场,20元一天,建议大家来这里玩不要想在城区找车位了。

卖青稞大饼,挺香的,下面的店铺15一个,走了一截发现全是从这个店拿的,人家只卖12元,这几步路就有3元的差价。

走到一半发现下雨了,还好穿的冲锋衣。并且凑巧这边有个广场在搞商业活动,似乎是请了什么藏族歌星在唱歌,虽然是藏文歌,但是唱的还挺好听的,不过摇一摇没识别出来。

大家累了坐在路边休息,因为雨大,二叔又没穿冲锋衣,所以暂时和他们分开,我们两口子单独逛去了。

康定旅游局局长同款抖音樱花大街。

溜溜城打卡,为什么叫溜溜城?

“康定溜溜城”这个充满诗意的别称,主要源于康定与《康定情歌》(又名《跑马溜溜的山上》)的深厚渊源。这首传唱世界的民歌让康定以“溜溜”之名广为人知,而“溜溜”在当地方言中更是承载了独特的文化意蕴,在康定及川西方言中,“溜溜”常用来形容事物“美好”“漂亮”“流畅”。

1. 溜溜城打卡 2. 小巷一景

藏族同胞们的广场舞是锅庄,那「锅庄」又是什么呢?。

锅庄(藏语称“果卓”或“歌庄”)是藏族民间广泛流传的一种传统集体舞蹈,尤其在四川、西藏、云南、青海等藏族聚居区盛行。它不仅是重要的娱乐活动,更是藏族文化、宗教和社会交往的载体。

康定的夜景还是非常漂亮的。

天色渐暗后和大家汇合,去往老榆林的民宿休息。

因为对这边的卫生环境不报什么期待,所以提前买了隔脏睡袋,用塑料袋+垃圾桶装了点热水泡脚,早早便躺下休息了。

1. 早起民宿外的风景 2.房间环境 3. 早起窗外的雪山 4. 隔脏睡袋

没有领队,更没有领队说让我们拍出发照,所以我索性叫上大家一起随便拍了一张。

17个人就这几个拍了照...。

坐上蓬卡就出发了,一路上看不到外面,但感觉早餐都要被颠出来了,估计外面到处都是导弹坑。

车上手机拍的远处雪山

1. 格因草原 2. 马帮的马

今天天气不错,依旧没看到领队...。

过木桥,水很凉,都是山上流下来的雪水。低山区

路过一个红石滩

低山区还有很多松树。

在徒步过程中海拔逐渐上升,慢慢树木就变成灌木和草甸,一路上会还经过各种路况:石头路,涉水路,草甸,沼泽等等。

一处绝佳的和雪山合影的机位

我也来一张,在这里不小心把登山杖掉落滚到峡谷下面去了。😅

一爬一个不吱声

有点累,生命力照片一张。

沿途的树木变成了灌木和草甸。

二叔年纪打了,自重也太重,大家都劝他坐马算了,他也听劝,800块直接座马去了营地。后来到营地听他说座马也听危险的,他上去的时候马没站稳前跪了,把他甩到沙地里了,我们在说幸好是沙地...。

终于走了快一半了,两岔河营地吃个午餐

一桌子的方便面碗和围了一桌子崩溃的驴友们。

继续出发

在一个绝望坡前碰到了其他队友,我不拍照大家是真不想拍合照....,碰上我这种队友是真挺好的。

中间那个是本次帮忙的「领队」

最不想走的就是这种河谷路段,全是碎石头。还碰上大风,风里还夹着小粒的冰雹。

最后一个绝望坡,进入雪线了,天上也飘起了小雪。

绝望坡后面的美景

终于到营地了!

营地景色全览

太累了,完全不想拍照用其他人发的照片把。

休息的棚子。营地的饭菜很不好吃,大家草草吃完便都上床准备休息。

因为营地在海拔4300米左右,加上棚子里氧气不太流通,所以帐篷里十几号人晚上过夜的时候都有不同程度的高反,症状基本上都是头疼。我后半夜睡了会,基本上是醒半小时睡半小时左右,我旁边的一个大学生几乎整夜没睡觉,两三点的时候还爬起来说想下山...,一整晚的噪音把他旁边的一个能睡着的老驴折腾到一晚上没怎么睡,感觉老驴也很难受哈哈。

S就是说觉得冷,因为棚子四处漏风,漏风最严重的就是我们躺下后头顶的地方,风大的时候我问不得不起来给她过上急救毯才好些。

后半夜风停了,我帮她把睡袋裹紧,把头罩住就留个鼻子嘴巴出来,才算是踏踏实实睡了好几个小时,比我有出息多了。

第二天四五点大家便陆续起床了,还能看到星空,但是我手机和充电宝都被冻没电了。

早餐的包子是馊的,我就喝了点稀饭,吃了自己带的面包。

因为出发的时候黑黢黢的,手机也没电了,就没拍什么照片,同行的刘姐借了他的充电宝给我用,让我得以续命。

天终于渐渐亮起来,发现我们被大雾追赶着,手机也有一些电了。

跟着前面的队伍的步伐,发现我们没有走常规的泥巴路线,而是条沿着河谷上行的碎石路段,然后踩着雪沿着山脊线的去往山顶,体验顶满,后来听前面的队友说起才知道,他们跟着一个商团的线路走的。

体验非常棒。

开始爬山脊线。几乎六七十度的大坡,踩着雪前进,非常爽!

就冲这一段爬坡这一趟也值了。

这里的雪一脚下去都能没过膝盖,躺雪上面来一张把。

旁边那条是常规上来的路,马上到了,看上面云层上去的时候应该还能看见雪山。

登顶。

说实话因为湖还没完全化开又被白雪覆盖,也不能去湖面拍照,导致山顶的风景和可玩性很一般,这块白色不知道的人还以为只是一片平坦的雪地。

别的不说,先打个卡把。

看好了,这一剑,会很帅!(换我来拍的时候后面忽然就起了大雾,没拍上😭。)

因为风雪,准备下山了。

后面下山的风景就不发了,下山的时候倒是碰上领队了,因为要做扫尾工作,我们又是垫底的,所以一直被催着下山,搞的我们下山速度飞快,后半段几乎都是跑着下山的。

回到起点。

下雨了,安排第一波人坐皮卡,这波人回到民宿衣服基本上都湿了,我依旧做的蓬卡下山,不过因为没休息好,有点晕车的感觉,差点吐了。

之后便是返程,回去的时候只有泸州的三位和我们一起,另外两个坐其他车了。

也好,我们这几个人更聊的来,我们边开边聊,倒也不觉得累,还拉个群准备下次有什么活动再一起出行。

直到晚上11点30分才安全到达自贡,他们另外拼车叫了个野猪儿回泸州。

这次行程结束我缓了三天才缓过来,每天下班回家几乎粘上枕头就睡,每天的睡眠时长破天荒的突破了8个小时,临近9小时!

下次出行真的不想再开车了。

Passport

2025年5月18日 10:33

Passport

本插件为 Typecho 博客系统提供密码找回功能。用户可以通过注册邮箱接收密码重置链接,从而重新设置账户密码。
原始仓库地址: typecho-fans/plugins/Passport
此版本在原版基础上进行了功能增强和 UI 优化。

下载

开源地址:https://github.com/little-gt/PLUGION-Passport

功能

  • 通过邮件发送密码重置链接。
  • 支持 SMTP 服务器配置,保证邮件发送的可靠性。
  • 可自定义密码重置邮件模板。
  • 支持多种验证码服务:

    • Google reCAPTCHA v2 (最新版)
    • hCaptcha
    • 可选择不使用验证码
  • 验证码配置项根据所选类型动态显示,界面更简洁。
  • 找回密码和重置密码页面的验证码 UI 左对齐,更美观。
  • 使用最新的 PHPMailer 库特性。
  • 安全可靠的 Token 生成与验证机制。

私有化部署Nexus3镜像源

作者ysicing
2025年5月17日 09:19

昨天升级 Nexus3 翻车了,准备基于 PG 重新搭建 Nexus3 源,并移除一些不用的软件源, 新版软件源支持如下

环境要求

  • 机器配置至少 2C4G
  • 网络要好,推荐境外机器部署
  • 大硬盘可选(不代理 py 等还好)

部署

nexus 服务

使用 compose 或者 k8s,原理差不多,我的环境使用 k3s。

  • docker-compose.yaml
services:
  nexus3:
    image: sonatype/nexus3:latest
    container_name: nexus3
    restart: always
    ports:
      - "8081:8081"
    environment:
      - INSTALL4J_ADD_VM_PARAMS=-Xms2703m -Xmx4G -XX:MaxDirectMemorySize=2703m -Djava.util.prefs.userRoot=/nexus-data/javaprefs
      - NEXUS_DATASTORE_ENABLED=true
      - NEXUS_DATASTORE_NEXUS_JDBCURL=jdbc:postgresql://postgres:5432/nexus3
      - NEXUS_DATASTORE_NEXUS_USERNAME=oup44Fai4ta
      - NEXUS_DATASTORE_NEXUS_PASSWORD=ienah9eiquah7GeiMaengeitie5aeq66
    volumes:
      - /data/nexus/nexusdata:/nexus-data
    depends_on:
      - postgres

  postgres:
    image: bitnami/postgresql:17
    container_name: postgres
    environment:
      - POSTGRESQL_DATABASE=nexus3
      - POSTGRESQL_USERNAME=oup44Fai4ta
      - POSTGRESQL_PASSWORD=ienah9eiquah7GeiMaengeitie5aeq66
    volumes:
      - /data/nexus/postgres:/bitnami/postgresql
    restart: always

在启动前,先创建好目录,并给予权限

mkdir -p /data/nexus/postgres /data/nexus/nexusdata
chmod 777 /data/nexus -R

然后启动, 访问 ip:8081 即可

docker compose up -d

配置 caddy

mirrors.china.12306.work {
	import LOG "/var/log/caddy/mirrors.log"
	@rootOrIndex {
		path /
		path /index.html
		path /mirror.css
		path /.help*
	}

	handle @rootOrIndex {
		file_server {
			hide .git
			root /etc/caddy/pages/mirrors
		}
	}
	reverse_proxy http://100.90.80.3:8081
}

样式我从网易镜像源借鉴来的

目前支持的软件

本文档由 ysicing 收集整理, 希望能对国内开源软件用户有所帮助.

仅列出部分核心软件包:

  • tailscale
  • caddy
  • docker-ce
  • postgresql
  • trivy

为什么没有我用的软件包?因为我暂时用不上, 有好的想法可以联系我.


从赵心童世锦赛夺冠聊聊我的斯诺克情缘

2025年5月17日 07:42
如要阅读全文,点击标题跳转。2025 年 5 月 6 日凌晨 4 点,中国斯诺克运动员赵心童在世锦赛决赛中以 18:12 的比分击败七五三杰之一的老将马克.威廉姆斯,历经将近一个月的赛程,一举夺得世锦赛冠军,成为亚洲首位夺得此殊荣的运动员。仿佛还在梦中一样,难以想象这已经是尘埃落定了的一件事儿。决赛前忐忑的内心感受,至今仍旧在胸中回荡。所幸,历史选择了他,他也不负众望,拿下这一桂冠。在斯诺克领域,最负盛名的三个赛事分别是:斯诺克世界锦标赛、斯诺克英国锦标赛和斯诺克大师赛。如果你一个斯诺克运动员,在职业生涯中拿满这三个大赛的冠军,则会被称作大满贯选手。赵心童在 2021 到 2022 赛季夺得了英锦赛冠军,现在又夺得世锦赛冠军,因此会被称作听牌,只等拿下大师赛冠军,就完成了大满贯。而现在以他的表现,又及正值壮年,因此完成大满贯也只是时间问题。关于比赛本身,已经有太多做视频写文章的人介绍过,我就不再赘述了。单独讲讲自己关于赵心童了解的一些信息。这两天有人翻出来十年前央视的一个纪录片《成为丁俊晖》,我大概在几年前完整看过,其中记录讲解了一群孩子在斯诺克学院,练球追梦的故事。片中讲了几个较有潜质的少年,给我印象比较深的有两个,一个是颜丙涛,一个是赵心童。再过来他们逐渐长大,也慢慢进入到斯诺克赛场,成为职业选手,并开始取得一些亮眼的成绩。这大概就是 2021 年左右,那时候关于赵心童的新闻,褒贬不一,有人被他的球风吸引,有人吐槽他莽夫一个。这些对我而言,都不算是一个严重的问题。当时在某个地方,看到一个言论,使我对赵心童的印象,一直处于不够正面的维度,大概就是说中国的几个年轻小匠,略微有了一点成绩,就不知道自己是谁了,在英国也开始买房之类的。后来也渐渐了解到,赵心童家庭出身本就富足,于是斗志不足似乎也在情理之中,再加上接下来的禁赛,让我对这个年轻人有较大的负面认知。而今重出江湖的赵心童,在世锦赛中的表现,用轻松写意的打法,犀利惊艳的准度,清晰明确的围球,向所有人证明了,在被禁赛的时期,他没有放弃,也没有荒废,相反,不仅准度依旧在线,而且球风更加沉稳,安全球也处理更细腻了。经此一役,让我对他的印象,为之一变。

Nexus3 容器部署3.70.x升级实践-非成功案例

作者ysicing
2025年5月16日 21:09

docker 从 3.70.x 及以下版本升级 3.71.x 及以上版本出现旧数据库不支持错误

官方也很细心给了一份升级文档 upgrading-to-nexus-repository-3-71-0-and-beyond

由于我使用的也是官方镜像,社区也有人给出了解决方案 sonatype/nexus-public#51

友情提示,操作前需要备份持久化数据,避免升级失败,建议先阅读文尾后再参考我的升级

创建 nexus 备份文件

  • 设置-系统-任务-创建任务

  • 搜索 Backup,选择它

  • 创建一下手动触发的任务,备份路径 /nexus-data/backup 就行

  • 创建完任务后,点击 backup 任务运行

  • 触发备份

  • 等待备份完成

不放心,也可以进容器内,看一下:

19:56 ➜  ~ kubectl exec -it pods/nexus3-859bb76886-mc29s -n nat -- bash
bash-4.4$ ls
nexus  sonatype-work  start-nexus-repository-manager.sh
bash-4.4$ cd /nexus-data/
bash-4.4$ ls
backup	blobs  cache  db  elasticsearch  etc  generated-bundles  instances  javaprefs  kar  karaf.pid  keystores  lock	log  orient  port  restore-from-backup	tmp
bash-4.4$ cd backup/
bash-4.4$ ls
analytics-2025-05-16-11-56-22-3.70.1-02.bak  component-2025-05-16-11-56-22-3.70.1-02.bak  config-2025-05-16-11-56-22-3.70.1-02.bak  security-2025-05-16-11-56-22-3.70.1-02.bak
bash-4.4$ exit

示例是 k8s 方式,docker 部署也是类似。

数据迁移

查询对应 nexus-db-migrator 迁移工具

需要下载和你 Nexus 版本匹配的 nexus-db-migrator 工具, 最新版本下载 nexus-db-migrator, 目前最新版本是 3.70.4-02

大概格式如下:

https://download.sonatype.com/nexus/nxrm3-migrator/nexus-db-migrator-<version版本号>.jar

由于我的版本是 3.70.1-02, 选择下载

https://sonatype-download.global.ssl.fastly.net/repository/downloads-prod-group/nxrm3-migrator/nexus-db-migrator-3.70.1-03.jar

可能你对应的版本不存在,可以尝试改 patch 版本,实在不行你就先升级到 3.70.4 最新版本

再次提醒版本一定要匹配上

操作

进入容器内部操作,方便起见, 确保在 /nexus-data/backup 目录下

bash-4.4$ pwd
/nexus-data/backup

下载迁移工具,或者同步到容器内也行(网络不好的情况下)

curl -s -L -O https://sonatype-download.global.ssl.fastly.net/repository/downloads-prod-group/nxrm3-migrator/nexus-db-migrator-3.70.1-03.jar

停 nexus 服务

/opt/sonatype/nexus/bin/nexus stop

执行迁移操作,将数据迁移到 H2 数据库,根据数据量和配置灵活调整 -Xmx4G -Xms4G 值,这里条件有限默认 4G, 执行完成如下图

java -Xmx4G -Xms4G -XX:+UseG1GC -XX:MaxDirectMemorySize=28672M -jar nexus-db-migrator-3.70.1-03.jar --migration_type=h2

再次停 nexus 服务

bash-4.4$ /opt/sonatype/nexus/bin/nexus stop
Shutting down nexus
nexus is not running.

查看迁移的数据

bash-4.4$ ls -ahl nexus.mv.db
-rw-r--r-- 1 nexus nexus 320K May 16 12:19 nexus.mv.db

将迁移的数据文件复制到 db 文件夹中

cp nexus.mv.db /nexus-data/db

然后退出容器

销毁容器

  • compose 部署
docker compose down nexus3
  • k8s 部署副本改成 0
kubectl scale --replicas 0 deploy/nexus3 -n nat

编辑配置文件

为啥不在容器里编辑,啥工具都没有不方便,而且这个配置持久化了

  • 默认配置如下 old
root@nat3:/data/k8s/local/nexus3/etc# cat nexus.properties
# Jetty section
# application-port=8081
# application-host=0.0.0.0
# nexus-args=${jetty.etc}/jetty.xml,${jetty.etc}/jetty-http.xml,${jetty.etc}/jetty-requestlog.xml
# nexus-context-path=/${NEXUS_CONTEXT}

# Nexus section
# nexus-edition=nexus-pro-edition
# nexus-features=\
#  nexus-pro-feature

# nexus.hazelcast.discovery.isEnabled=true
  • 新增 nexus.datastore.enabled=true, 新配置如下
root@nat3:/data/k8s/local/nexus3/etc# cat nexus.properties
# Jetty section
# application-port=8081
# application-host=0.0.0.0
# nexus-args=${jetty.etc}/jetty.xml,${jetty.etc}/jetty-http.xml,${jetty.etc}/jetty-requestlog.xml
# nexus-context-path=/${NEXUS_CONTEXT}

# Nexus section
# nexus-edition=nexus-pro-edition
# nexus-features=\
#  nexus-pro-feature

# nexus.hazelcast.discovery.isEnabled=true
nexus.datastore.enabled=true

启动老服务,验证数据库切换成功

  • compose 部署
docker compose up -d
  • k8s 部署副本改成 0
kubectl scale --replicas 1 deploy/nexus3 -n nat

观察日志,搜索 H2

2025-05-16 12:33:15,378+0000 INFO  [FelixStartLevel] *SYSTEM org.sonatype.nexus.datastore.DataStoreConfigurationDefaultSource - Loaded 'nexus' data store configuration defaults (Embedded H2)
2025-05-16 12:33:15,710+0000 INFO  [FelixStartLevel] *SYSTEM com.zaxxer.hikari.HikariDataSource - nexus - Starting...
2025-05-16 12:33:16,018+0000 INFO  [FelixStartLevel] *SYSTEM com.zaxxer.hikari.HikariDataSource - nexus - Start completed.
2025-05-16 12:33:16,020+0000 INFO  [FelixStartLevel] *SYSTEM org.sonatype.nexus.datastore.mybatis.MyBatisDataStore - nexus - Loading MyBatis configuration from /opt/sonatype/nexus/etc/fabric/mybatis.xml
2025-05-16 12:33:16,147+0000 INFO  [FelixStartLevel] *SYSTEM org.sonatype.nexus.datastore.mybatis.MyBatisDataStore - nexus - MyBatis databaseId: H2
2025-05-16 12:33:16,346+0000 INFO  [FelixStartLevel] *SYSTEM org.sonatype.nexus.datastore.mybatis.MyBatisDataStore - nexus - Creating schema for UpgradeTaskDAO

升级版本失败

直接替换镜像升级就完成了,然后我的数据就没了 😂, 严格按照官方文档来的哇 😂

后续

  • 云缘生镜像站暂停营业几天
  • 虽然有备份数据,但是不想回滚了,重新部署,数据存储使用 PG 吧

zblog输出热门标签

2025年5月16日 17:41

zblog输出20个热门标签

代码

{php}$tags = $zbp->GetTagList('','',array('tag_Count'=>'DESC'),array(20),'');{/php}
{foreach $tags as $tag}
<a href="{$tag.Url}" rel="tag" title="{$tag.Count} 篇文章">#{$tag.Name}</a>
{/foreach}

其中数量可以自行修改代码中的20,改成你想要的数量

参数说明

标签网址:{$tag.Url}
标签名字:{$tag.Name}
标签下文章数量:{$tag.Count}

AplayerFixed背景音乐插件

2025年5月16日 17:28

AplayerFixed

Aplayer to Typecho背景音乐插件,支持使用InstantClick常规pjax的主题。

功能

  • 支持InstantClick / 常规Pjax两种模式
  • 支持设置播放器位置:左下角或右下角
  • 支持设置播放顺序:顺序播放或随机
  • 支持调用网易云/QQ音乐歌单(内置meting音乐解析api)

下载

开源地址

https://github.com/jrotty/AplayerFixed

Caddy拦截响应处理小记

作者ysicing
2025年5月15日 20:34

简单小记,分享一下,主要还是针对 MinIO 异常的处理,其他服务应该也类似

之前配置 MinIO:

xxxx {
        import ERR
        import LOG "/var/log/caddy/minio.log"
        @rootPath {
                path /
        }
        handle @rootPath {
                respond "EdgeONE 403 Forbidden" 403
        }
        reverse_proxy 127.0.0.1:9000
}

仅禁止访问/,且有问题使用 import ERR 兜底

(ERR) {
	handle_errors {
		redir https://dxgw-{err.status_code}.external.ysicing.net
	}
}

之前没太注意,之前这种情况下,如果 MinIO 放回 400 的话是没法拦截的。这时候需要响应匹配器来干活了

域名 {
		import LOG "/var/log/caddy/minio.log"
	    import ERR
        @rootPath {
                path /
        }
        handle @rootPath {
                respond "EdgeONE 403 Forbidden" 403
        }
        reverse_proxy 127.0.0.1::9000 {
	     		@error status 4xx
	     		handle_response @error {
 						respond "EdgeONE 451 Forbidden" 451
				}
		}
}

handle_response 块内,可以使用任何其他指令,功能还挺强大的


因为一个小玩意又写了另一个小玩意

2025年5月15日 17:29

liusheng

blogscn.fun 一直在改版,最近遇到好多人的rss用php抓取不了,就用py写了一个api,然后就有了上面的产物。

全前端没有后端,用了两个api,一个是rss 抓取的api,一个是头像的gravatar。本来到这里就结束了,订阅的信息全部存储在浏览器缓存。

然后,有个东西加载不出来,清理了一下缓存,然后添加的订阅就,没了!
这就有了下面的折腾,要存就需要登录,需要后端怎么能,不用就存呢?于是乎发现了BaaS,即后端即服务,数据库,验证,存储等。
这不就完美了么!
所以,这个站点只有一个文件 index.html

2025-05-15T09:28:50.webp

地址:https://liusheng.maozi.io/

哎,又一电子垃圾!

天啊!我喝了四天啤酒,居然还发现了人生的意义!

作者Jdeal
2025年5月15日 13:00

真的很久没写东西了,工作忙得我晕头转向,每天都在各种打转。

不过五一假期来啦,我终于能喘口气,和朋友们、发小们聚在一起,连续喝了四天,太爽啦!

说到啤酒,我觉得白啤和精酿啤酒都还不错,比那些普通的工业啤酒有意思多了,每一口都超有惊喜。

我最近特别爱喝青岛白啤和福佳白啤。

青岛白啤就像个调皮的小家伙,看起来有点朦胧,倒进杯子里,泡沫超多,而且能坚持好久。喝一口,麦芽的甜味和淡淡的香味混在一起,就像刚切开的香蕉,还带点微微的苦味,味道层次超丰富。

福佳白啤就更特别啦,里面加了橘皮和芫荽籽,喝起来特别好喝,还有点特别的味道,特别清新。喝一口,整个人都轻松了,感觉超棒!

我还喝了好多精酿啤酒呢。双合盛的小麦精酿,麦芽的香气特别纯净,喝起来顺滑又清爽,特别棒。还有小象的德式小麦精酿,麦香特别浓郁,口感醇厚,喝起来特别过瘾。

总之,啤酒就像朋友一样,总能找到适合自己的那一款。

如果你也喜欢啤酒,快给我推荐推荐吧!

说不定我也能找到属于你的那一杯快乐呢!🍺

*Ps 这两天领导不在,划划水,上博客看看有没有什么可以折腾折腾的,哈哈!

看到X上的一段话描写中年打工人的状态,太真实了!
“身边很多朋友,几乎都在步入社会几年后,明显感觉自己的心气被剥削了,疲意,麻木,无欲无求。
上班就是为了过周末,没心情搞兴趣爱好,打开书读不进去,躺着玩手机刷短视频。
说是爱钱,但也没有狠下心赚钱,浑浑噩噩,失去所有探索欲和分享欲。
学生时代大家经常一起谈论文学、电影、旅行、音乐、爱情,后来聊天主题只剩下吃饭、加班、赚钱、家庭,活得像个空心人。
贾樟柯写,“生命到这个地方就不再有奇迹出现,剩下的就是和时间做斗争的一种庸常人生。”
作为大多数的我们,有太多现实需要理清,飞速的时间,普通的自己。开始成为社会的螺丝时代的燃料,理解并成为小时候排斥的无趣的人。
不再想什么意义不意义什么高级不高级了,吃饭就吃饭,打牌就打牌,喝酒就喝酒,我做很多事,只是为了驱散生活的倦怠。”

Nook简约主题

2025年5月14日 20:41
Nook简约typecho单栏主题.webp

Nook

在喧嚣的网络世界,Nook为你打造一处专注表达的宁静之地。极简的设计、温柔的色调、流畅的阅读体验,让每一篇文字都如角落里的微光,安静却充满力量。

支持自定义字体 支持邮箱提醒 拥有文章归档 友链 后台文章类型展现在归档上

  • 归档使用 Archives.html
  • 友链使用 Links.html
  • 关于使用 About.html
  • 留言使用 Lving.html

演示

{button href="https://demo.typecho.work/?theme=Nook" type="blue"}在线预览{/button}

下载主题

开源地址:https://github.com/JaneLens/Nook

THYUU/星度主题 25.5.0 更新内容

作者
2025年5月13日 20:46

全局

  • 【优化】全局动态面板和页脚区域的背景虚化,减少其饱和度
  • 【优化】由于 Chrome 和 Edge 开始内置思源黑体,考虑资源消耗,全局默认字体不再引入在线字体
  • 【优化】适当增加全局动态面板按钮的点击区域,防止没有点击中心位置而导致的问题
  • 【修复】当点击评论表单时,整体评论区块在 CSS 滚动驱动动画变小的问题

顶栏

  • 【新增】整体动态展现,通过新设计的动效在下滑时隐藏,上滑时显示
  • 【新增】标识下滑常驻显示并伴随渐变高斯模糊效果,更好突出品牌
  • 【新增】标识中主标题(默认站点名称)现支持 HTML 结构,可插入 SVG 增强风格
  • 【优化】自定义标识高度受到 CSS 默认值的影响方式,现在修改 SVG 中的 height 属性值可自定义高度
  • 【优化】文章目录和全局动态面板的弹出式窗口逻辑
  • 【修复】刷新后顶栏状态重置的问题
  • 【预告】未来顶栏将会升级风格更现代的智栏,将集成全局消息中心、页面进度动效、文章标题动态、增强交互工具、AI 交互等,敬请期待

首页

  • 【优化】精选分类的文章卡片调整样式
  • 【优化】焦点板块的自动滑动居中支持小屏设备
  • 【优化】限定主题图片过暗时影响顶栏的观感
  • 【修复】收缩布局下限定主题封面图不全屏的问题

文章页

  • 【新增】通用文章模板首屏文章内容动效
  • 【优化】重新调整 H2-H6 文本区块字体大小,使其区分更加明显
  • 【优化】略微调整封面图高度
  • 【优化】在封面图标题处,现在背景虚化跟随标题内容适应
  • 【修复】当封面图过浅时,导致在暗黑风格下看不清的问题
  • 【修复】收缩布局下封面图不全屏的问题

分类页

  • 【新增】文章描述部分,让访客快速了解文章概要
  • 【优化】标题字体大小,追随 H6 字体大小
  • 【优化】调整文章分类和标签位置
  • 【优化】略微调整封面图高度

THYUU 区块

  • 【新增】现在支持 Live Photo,让生活的每一个瞬间都值得纪念

如何正确拉取超大体积的git项目——开发小技巧

2025年5月14日 15:48

最近遇到一个体积相当大的仓库(40GB+),结果在安利给群内小伙伴时,很多人遇到了直接使用 git clone 克隆仓库到本地时,对本地网络的稳定性有巨大挑战,一旦网络波动导致传输中断,就只能从头下载。
很多人拉项目时习惯git clone一把梭哈,这并不是一个很好的习惯,众所周知国内访问github属于玄学,而且有些项目非常大,搞不好需要好几个小时才能拉取完,长时间+不稳定的网络,很容易出意外。本文就是介绍两种应对超大体积项目的拉取的办法「Git LFS」和「Git fetch」。


Git 的 git clone 命令本身是不直接支持断点续传的,git clone 本质上是创建一个新仓库,并一次性拉取远程所有数据(包括提交历史、分支、标签等)。如果中途中断本地目录会处于不完整状态,直接重试 git clone 会失败,如果你退出终端本地目录也会被清空。

使用 Git Large File Storage(Git LFS)

简单介绍一下 Git LFS

Git Large File Storage(Git LFS)是一个用于管理大型文件的Git扩展工具,旨在解决Git原生对大文件支持不足的问题。

Git LFS 优点

  • Git LFS用轻量级文本指针(如oid sha256:…)替换实际大文件,避免仓库臃肿。
  • 大文件内容存储在专用服务器(如GitHub LFS、块储存、自建储存、CDN),仅按需下载。
  • 支持单个文件GB级别存储,突破Git对文件大小的限制。(比如 github 是单文件小于100MB,单文件大于50MB会出现警告)
  • 因为大文件实际是分离储存的,可以优化拉取速度。
  • 安装Git LFS 并配置好后,日常 git 操作时,几乎是无感的,Git LFS 会自动处理大文件。

Git LFS 缺点

  • 需要项目创建者配合从一开始就使用 Git LFS,如果项目没有采用,对于想要克隆拉取项目的人是没有办法去使用 Git LFS 的。
  • 额外的大文件储存和下载都是需要开发者额外掏钱的,像是GitHub就只有10 GiB的免费空间和流量可用,超过的部分需要额外收费。(如果你是pth之类的AI模型文件,huggingface之类的平台给了免费的 LFS 空间)

不要 git clone 一把梭哈,改用 git fetch 分步拉取

简单介绍一下 git fetch 分步拉取

Git LFS 更多面对的是项目中部分文件体积很大的情况,比如Unity项目中的.asset文件、PSD源文件、CSV数据集。但还有一种情况,项目中每个文件体积都不大,可能只有几兆到十几兆,但是项目中文件数量极多,几百上千个,加到一起后整个项目体积也到了 GB 规模。这个时候更适合分步拉取的方式。

和 git clone 相比 git fetch 的优势

  • git fetch 基于 Git 对象模型,天然支持断点续传。
  • 即使多次中断,只要本地 .git 目录未损坏,均可通过反复执行 git fetch 逐步补全数据。
  • 如果中断期间远程仓库新增了提交,git fetch 会同时下载新增内容和未完成的旧内容,最终保持本地与远程一致。

和 git clone 相比 git fetch 的缺点

除了操作稍微多了几个步骤,没任何额外的缺点。

git fetch 分布拉取的方法

  1. 先手动创建空仓库并构建.git文件
mkdir your-repo && cd your-repo
git init
git remote add origin https://github.com/user/repo.git
  1. 分步拉取数据
git fetch origin --progress  # 如果中间传输中断,就在目录下再执行一次这个命令即可。
  1. 等待拉取完毕(以下仅为举例,请根据实现终端显示为准),这时候项目所有内容都会被打包在.git/objects/pack目录下的.pack文件内。
Resolving deltas: 100% (9999/9999), done.
From https://github.com/user/repo.git
 * [new branch]      master     -> origin/master
 * [new tag]         v1.0       -> v1.0
 * [new tag]         v1.1       -> v1.1
 * [new tag]         v1.2       -> v1.2
 * [new tag]         v2.0       -> v2.0
 * [new tag]         v2.1       -> v2.1
 * [new tag]         v2.2       -> v2.2
 * [new tag]         v2.3       -> v2.3
 * [new tag]         v2.4       -> v2.4
 * [new tag]         v3.0       -> v3.0
  1. 将需要的分支检出,比如我们检出 master 主分支。
git checkout master
  1. 其他优化建议

使用浅克隆减少首次下载量,比如只拉取最新分支

git clone --depth 1 https://github.com/user/repo.git

之后再补全历史(或者就干脆不补全,节约点空间)

git fetch --unshallow

总结

总的来说 Git LFS 主要是应对项目中出现几个巨大体积文件的情况,git fetch 主要是应对一次开机无法完成下载的超大型仓库或不稳定网络环境,比直接 git clone 一把梭哈更加灵活。

PS:顺带提一嘴,如果多次中断和重新拉取,可能会在.git/objects/pack目录下出现很多个.pack文件。如果需要清理的的话,在项目目录下执行git gc --auto
再PS:不用在意.pack文件的巨大体积,因为工作目录下你看到的具体文件,是通过硬链接、符号链接构建的,工作目录内的文件和 .pack 中的对象共享同一份数据,并不会真实占用双倍物理空间。实际占用大概是1.2~1.3倍空间,因为.pack文件还是有一定的压缩的。

The post 如何正确拉取超大体积的git项目——开发小技巧 appeared first on 秋风于渭水.

CodeBuddy腾讯云代码助手初体验

作者ysicing
2025年5月13日 19:52

本文算个安利吧,目前比 trae 实在,至少不是老是排队,暂时免费使用。

CodeBuddy 是什么

腾讯云出品的代码助手插件, 支持 VSCodeJetBrains微信开发者工具

对比 Cursor

对比我用的 cursor,目前有两大优势

  • 暂时免费(Craft 对话限制 50 次/月,很容易用完的)
  • Craft 支持从用户需求转到研发需求,最后拆分成迭代执行(很早之前,就跟同事讨论过这个问题,这个应该是未来发展的趋势)

安装

插件安装教程, 安装完成后,扫码登录

使用

简单演示,空仓库如何写一个 caddy 插件

mkdir go/src/github.com/ysicing/caddy2-admin-ui
cd go/src/github.com/ysicing/caddy2-admin-ui
git init
go mod init

配置助手,默认全启用

  • 用户需求 - 研发需求

  • 研发需求 - 迭代执行

  • 迭代 1 - 迭代 n

可能问题

除写了"垃圾代码"外,偶尔不会重构代码外,Agent 执行命令提示没法获取结果,需要 ~/.zshrc 新增如下配置

[[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --locate-shell-integration-path zsh)"

总结

用的不多,偶尔用用还行,更多的还得关注他们后续的付费计划。


针对邮箱检测是否有gravatar头像

2025年5月13日 16:56

最近一直在完善笔墨迹,博友们提交的邮箱好多没有 Gravatar 头像,然后就写了一个功能来检测有没有头像。

想的朋友也可以提交一下,以前提交过的朋友也可以,再提交一下。

2025-05-13T08:54:02.webp

然后又将功能写了一个Gravatar头像检测工具

2025-05-13T08:55:01.webp

github:https://github.com/xiangmingya/Avatar_Check
演示:https://avatar-check.maozi.io/

说说:#1747109233

2025年5月13日 12:07

我又想出去玩了,之前去的西安,这玩一下上瘾了,想着去更远的地方旅行。

离我比较远的选择,一个是新疆,一个是西藏,一个是广东,感觉广东是要必去的,不是有一句话,食在广东。

另外出国的话,一个是日本一个是东南亚。太远的暂时也不去想了,出国先去近一些的。

日本的话,以前真的想去,但是现在不知怎么的感觉脱敏了。现在更倾向去东南亚看看。东南亚比较乱,感觉马来西亚会很不错,到时候计划一下嘿嘿😁

store站点被人扒了,虽然事件解决了但是还是让人恶心

2025年5月13日 11:22

{cat_tips_warning color=""}事件已解决,记录个人心情,全程打码,请勿对号入座,评论区切勿人身攻击{/cat_tips_warning}

store站点被人扒了,不是借鉴不是抄袭,而是直接复制粘贴。虽然事情解决了但是过了两天想起来还是非常让人恶心,我必须把这个事情写出来心情才能好受一些。就当作记录一下心路历程水一篇文章,因为事件终归是解决了所以本文出现的内容全都打码了,请勿对号入座。


起因是在一个明媚的午后,我像往常一样打开TSBC群去群里水表情。看到一个新进群的,昵称是“某某typecho主题工作室”,想必也是卖主题的,心里想去看看有没有有趣的主题找找灵感。结果一进去,预加载的文字感到似曾相识,结果一进去,果不其然,我的网站被扒了。于是心里很气愤,但是想着这store站我也不用来卖,不过如果是他自己用的话,说明我的网站也是被大家所认可的,也是挺好的,想着就算了,想着让他既然扒的我的,就让他在网站底部写上模板出处就完事了,比较都改完了。于是我就去群里艾特他,问他怎么回事。本以为对方会说一声抱歉,夸夸我的主题好看于是就拿来用了,我也不会说些什么,但是却一直没有回复。并不是没看群消息不知道我艾特,而且还迎面发b站推广链接,这下就让我从平缓的心态一下子就生气了。

但是更让我意想不到的来了,点进他发的b站推广链接,标题是“Typecho主题之XXX主题展示”,在评论区我赫然看到了以下内容:

woc,用我的模板然后说是自己的?这能忍?于是我又去群里问他咋回事,这时他回我了:

他的意思是他加了一个标签页,就不是扒了,复制粘贴不叫扒叫借鉴了,可是你知不知道你这复制粘贴连抄袭都算不上啊。然后这人又不理我了,我压制住火气加了他好友说,来友好的解决一下吧。

下面我贴上和那个人的私聊记录。

私聊记录

实话没想那么多,当初只是看见设计挺有思路。 所以就写了!

一开始为了缓和气氛,我还特地发了一个委屈的表情,对面回答也很友好,说设计很有思路就拿去用了,但是看他的语气并没有道歉的意思,而是看得上你的主题我拿来用是你的荣幸。

然后我就说,你说的是借鉴思路,而你做的不是借鉴思路而是复制粘贴,结果不知怎么的让对面有些语无伦次,而且说了让人感到很莫名奇妙的言论,他发了这一大段,属实是把我气笑了:

我说了不是特意去想做什么坏事,如果真要我也可以去改CSS名和JS名

你说你不去做坏事,那你拿我的主题官上自己的名字,然后别人问你还说没有关系,是你自己开发的?你没改css还不是因为让我发现了?

我之前设计的也一样被人仿过,我群也有很多用户。我也没瞎说什么

你的意思是别人抄了你的,你就可以抄别人的了?我指正出来是我在瞎说?是我无理取闹?

所以现在你老说的我好像要怎么样似的

你扒了我的主题,一句道歉也没有,还觉得自己很无辜?你不知道自己应该做些什么么?

我就纳闷了 说实话我也还知道有人转了你的 改成wp了
但大家的出发点并不是你说的那样

用“大家”这个词,来主张自己的正义性,【因为“大家”都是这么说的,你来挑刺一是说我无理取闹,无事生非;二是为自己开脱,毕竟“大家”都这么做,我这么做即使不对也是情有可原的?】

然后在我没缓过气的时候补上了一句

懂我意思吗?

他首先表明了自己是正义的一方,然后开始为自己辩解找理由:

你老觉得我在别人那说错了?
我的写法我也不确定跟你一样吗
我后端确实也是typecho但设置全是json解析
所以这个东西我在B站回复 我站的角度是后端 不是前端

不是哥哥,后端谁能看得到啊,而且一直在说的是前端啊,就算你说你的意思是后端,你自己的视频展示的不就是前端么?你展示一丁点后端的内容了么?

然后又来了一句反问,让你自己思考人生:

你自己也清楚 并非你说的我好像在特意说什么

最后他可怜我,回答道:

如果你觉得不行
我到时候抽空 全写过
好吧,这样可以了吧
确实一开始没想那么复杂 就是想做个站 真是有那些坏心思

因为我在挑刺,所以他为了照顾我,所以他说他抽空重写前端,这样我就不会再找他茬了


后面的我也不说什么了,图片里都有。后续就是我让他加上我的跳转链接,他也加上了,然后第二天他就重写了前端,链接也下了,至此至终一句道歉也没有,一句友好的对话也没有,我还得没事卖个萌发个表情缓和气氛。

昨晚看了一眼TSBC群,不知怎的狐狸让他当管理了,也许人家互相认识或者,希望他今后能对得起自己网站的标语:“创意无界,设计有魂”,而不是再扒别人的,日后能对Typecho的发展有些许贡献吧...

259、立夏

2025年5月13日 10:38

勒多曼因湖边

立夏时节,熏风南来,天地始交,万物并秀。当夜卧早起,迎朝阳以养心气。晨起可临水而立,观荷听蝉,导引阳气,使志无怒。饮食宜增酸减苦,多食樱桃、青梅、新麦以养心脾,佐以莲子、百合清心宁神。

此时心火渐旺,肾水初衰,当静心寡欲,戒骄戒躁,常持虚静。衣宜选用苎麻丝帛,透气生凉,尤需护住后颈命门。常饮绿豆薄荷饮,解暑生津,兼防疰夏。运动当选五禽戏、太极等舒徐功法,如树展枝,似水潺湲,使气血流通而不沸涌。午后小憩一刻,胜服灵丹,此谓「养长之道」。

立夏这天刚好在山上没网,不过在山上的时候还想着要发节气文章,下来后光顾着P照片去了,补发一章。

如何让老旧打印机支持隔空打印,在Mac上搭建Airprint服务,让USB打印机支持iPhone打印

作者张洪Heo
2025年5月12日 16:26
这篇文章介绍了如何通过AirPrint服务解决打印机无法进行隔空打印的问题。用户面临打印机不支持隔空打印的情况,因此需要手动搭建AirPrint服务以实现打印机共享。文章分步指导了安装打印机驱动、开启共享打印机功能、安装并运行AirPrint脚本服务,以及在测试和故障排除过程中需要注意的事项。文章还包括了GitHub项目的链接,以供用户参考。内容简洁明了,操作流程清晰,适合macOS用户的实际操作。

新版Mac系统安装旧版惠普打印机驱动教程,macos15安装惠普打印机驱动

作者张洪Heo
2025年5月12日 15:23
这篇文章介绍了解决惠普打印机驱动安装问题的步骤。当用户尝试从惠普官网下载驱动文件时,由于兼容性问题无法成功安装。通过以下步骤可以实现驱动的正常安装:1. 下载并安装Pacifist驱动程序(可通过指定链接获取);2. 打开Pacifist并解压下载的打印机驱动包;3. 使用Pacifist引导安装程序(需操作系统提供完整权限);4. 输入相关系统信息,完成安装过程。完成后,打印机驱动将暂时不可见;通过系统设置可重新找到并配置该驱动。建议在安装完成后及时检查系统设备列表以确认驱动是否成功识别。

Zerotier自建planet,Mac和istoreos自组网教程,实现远程访问家庭内网服务

作者张洪Heo
2025年5月12日 14:15
这篇文章介绍了在FRP中转架构下,因子设备无法实现直连导致的访问问题。通过部署ZeroTier自组网,作者为家庭NAS实现了直连访问。针对FRP子设备无法进行直连的痛点,作者基于自建ZeroTier Planet服务器,实现了跨网络设备直连。文章详细描述了部署ZeroTier的步骤,包括Docker容器的配置、防火墙的端口开放、网络的创建和IP地址的分配等关键步骤,并附有MacOS及istoreOS客户端配置方法。通过这种方式,非IPv6环境下只需将设备连接到已经配置好的HDD或NAS即可实时访问。文章除了描述部署ZeroTier的完整操作流程外,还提到了如果家里和外网均存在IPv6,则无需ZeroTier服务,用户可以在公司Mac使用零时序服务(零时序)实现mac与NAS的直连。

ImageFlow一款更适合个人使用的图床项目

作者ysicing
2025年5月11日 22:45

ImageFlow 是一个为现代网站和应用程序设计的高效图像服务系统。它能根据设备类型自动提供最合适的图像,并支持 WebP 和 AVIF 等现代图像格式,显著提升网站性能和用户体验。

主要特性

  • API 密钥认证:安全的 API 密钥验证机制,保护您的图片上传功能
  • 自适应图像服务:根据设备类型(桌面端/移动端)自动提供横向或纵向图片
  • 现代格式支持:自动检测浏览器兼容性并提供 WebP 或 AVIF 格式图片
  • 图片过期功能:支持设置图片过期时间,过期后自动删除(支持本地和 S3 存储)
  • 简单的 API:通过简单的 API 调用获取随机图片,支持标签过滤
  • 用户友好的上传界面:支持拖拽上传,具有暗黑模式、实时预览和标签管理功能
  • 图片管理功能:通过直观的管理界面查看、筛选和删除图片
  • 自动图像处理:上传后自动检测图像方向并转换为多种格式
  • 异步处理:图像转换在后台进行,不影响主服务
  • 高性能:优化的网络性能以减少加载时间
  • 易于部署:简单的配置和部署流程
  • 多存储支持:支持本地存储和 S3 兼容存储(如 R2, 不支持MinIO
  • Redis 支持:可选的 Redis 集成,用于元数据和标签存储,提高性能

项目地址

快速部署

使用 docker compose 快速操作

  • 镜像: soyorins/imageflow
  • 国内镜像: ccr.ccs.tencentyun.com/k7scn/imageflow
git clone https://github.com/Yuri-NagaSaki/ImageFlow && cd ImageFlow

更新配置文件

cp .env.example .env
nano .env
# 主要修改API_KEY和存储方式,想快速体验建议填写local

示例.env

API_KEY=ohji8lob1Sagoh4shizooNe9oxif9pai
STORAGE_TYPE=local
LOCAL_STORAGE_PATH=static/images
CUSTOM_DOMAIN=
MAX_UPLOAD_COUNT=20
IMAGE_QUALITY=75
WORKER_THREADS=4
SPEED=5
WORKER_POOL_SIZE=4
# Debug Mode
DEBUG_MODE=false

启动

docker compose up -d
  • 目前版本存在 panic 问题,等后续版本修复, 可以使用 soyorins/imageflow:2.1.1 版本

常见参数设置

  • API_KEY, 用于保护上传和管理接口, 推荐使用 pwgen 32 1 生成
  • IMAGE_QUALITY, WebP 转换的质量设置, 数值范围 1-100, 越高表示质量越好,文件越大
  • SPEED, 范围:0-8 0-8,0=最慢/最高质量,8=最快/最低质量

如何使用

  • 打开 IP:8686 端口 输入你在 env 设置的 API_Key

  • 上传图片, 支持选择图片的过期时间,添加标签对图片进行分类,图片会自动转换为 WebP 和 AVIF 格式

  • 当删除图片时,所有相关格式(原始、WebP、AVIF)将同时被移除

用户外活动填满五一:瓦屋山篇

2025年5月11日 10:18

瓦屋山

今年的五一感觉是有生以来活动安排的最满的一次。

先是五一假日前老婆把小舅子和大姨妈摇来了自贡,28号落地,带他们吃喝玩乐,30号再去了一次瓦屋山徒步。他们2号走后,3号又马不停蹄的去了康定勒多曼因爬山,来回42公里,最高海拔4600米,5号晚上十一点半才重新回到自贡,我到现在都还有点没缓过劲来。

大姨妈不是第一次来了,小舅子倒是首次过来玩,所以老婆先是安排他们去了上次我们两个人都好评的「电子厂餐馆」——一个本地人都不一定找的到地方的苍蝇馆子,要从主路拐进一条小路,进去走个一两公里才能看到。

小路开了一两公里左右,在这个尽头右手边小巷子里

小店需要提前预订,不接直接过去的客人,菜都是非常新鲜的。招牌菜是腰花和牛蹄筋,整体口味都是偏重油重辣的,小舅子和大姨妈吃完后两个人在自贡待了几天就打了几天「标枪」。

腰花非常嫩,里面配菜用了一些类似油渣的东西吃起来非常香(不知道是不是勾芡掉落焦化的)。牛蹄筋则是他们的招牌,炖的非常软烂又略带胶原蛋白的口感,强烈所有外地来的朋友过来品鉴。

1. 牛蹄筋 2. 腰花

老婆之后请了假带他们去看了自贡灯会和恐龙博物馆,这部分我要上班就没参与,也就没给他们拍照流念了。

因为瓦屋山风景绝美,这段时间又免门票,所以30号当天我们准备开车带他们去感受一下四川的山川河流和我们最近喜欢上的户外徒步。

出发!
瓦屋山位于四川省眉山市洪雅县境内,海拔2830米,是中国最大的平顶桌状山,被誉为“云霭之上的诺亚方舟”。这里拥有原始森林、高山瀑布、杜鹃花海等自然奇观,是世界第二大、亚洲第一大的桌山,与南非开普敦的桌山齐名。山顶平台面积约11平方公里,植被覆盖率超过90%,是珍稀动植物的天堂,如珙桐、大熊猫等。四季景色各异:春赏杜鹃、夏避酷暑、秋观彩林、冬览冰雪,尤其冬季冰瀑堪称一绝。道教文化底蕴深厚,传说为太上老君升仙地,清代曾是川内著名道教圣地。现为国家AAAA级旅游景区,兼具生态与人文价值。

瓦屋山是目前我去过的四川普通爬山类型景点中除九寨沟外资源最全面,最好的一处景点了。

下了高速后还要行使大概20公里左右的盘山公路,最后5公里左右进入景区范围,整个山区被密集且高大的原始雪松森林覆盖,小路在森林中盘旋前进,可以说非常有欧洲或者日本那边原始森林的感觉了。(一路上都是盘山公路,晕车的小伙伴要注意提前吃晕车药)

不多时便能穿山而出看到一个湖,这便是山下一处名为雅女湖的景点,环湖有一条公路,马路边上都是民宿和餐馆。

到达景区大门口时不要停。

我们当时停了,发现到里面游客中心还要一公里左右,又重新上车往里开,最后停在游客中心下的停车场里,20元一天。

准备爬山。

出发

山上的负氧离子非常高,溪流沿着峡谷而下,闻着好闻的空气感觉身体都轻飘飘的。

在山上看到了佛光,后来在朋友圈发现四川这几天到处都能看到这个景象。

佛光,像是被人注视。

路边的溪流和「生命力」照片。

1. 生命力照片 2. 溪流

自拍一个

自拍

1. 一只胖胖的熊蜂把花枝都压的下坠了 2. 步道

在溪边堆了个玛尼堆祈福

1. 清澈的湖水 2、3. 瀑布留影

自拍

山上瀑布资源丰富,而且落差都非常大。

1. 山上的瀑布2 2. 生命力照片 3、4. 自拍

小舅子和大姨妈平时都是不怎么锻炼的,一路上又流连于美景拍照,所以走走停停五六个小时才到金花坪,状态也几近掏空的状态。虽然我们状态还好的很,刚刚热身的感觉,但在询问了工作人员后发现下午4点以后就不准继续往上了,而且最后一班下山的缆车也会在4点还是5点后停运,所以只能坐缆车下山。

其实我们两口子是想快速冲顶的,因为非工作日前100名登顶的客人会送一块奖牌,不过今天肯定是拿不到了,只有下次再来了。

瓦屋山的缆车非常长,几乎坐了十来分钟才到站,想想上次峨眉山七八十的票价几分钟就到了。这么远的距离冬天不敢想象这一路会有多美,而且每站只要50元。

下山
自拍
分两篇写把,下一篇 勒多曼因篇。

大学时光回忆录

作者Innei
2025年5月11日 01:57
该渲染由 Shiro API 生成,可能存在排版问题,最佳体验请前往:https://innei.in/notes/190

有时间整理了下 iCloud 相册,删除了许多没有留恋价值的图片。回顾老照片,感受到当时的感动和喜悦,现在看来也是一种珍贵的回忆。

这段回忆从大学开始,再之前的也找不到了。

2018 年,大一。

小米笔记本 + 黑果,踏上开发之路。

2019 年,大二。开始学习前端,刚开始,学的是 Vue2 + Epxress。

2019 年底,找到了一个 Remote 的实习机会。学习 React。然后在这条路上越走越远。

2020 年,经过了半年疫情和实习,在下半年返校季,凭自己的努力换上了 Macbook Pro,黑果转正。也买了人生第一台游戏机,Nintendo Switch。

2020 年的十月,原神上线了。而我在沉迷塞尔达传说旷野之息。

2020 年底,我开始写 Mix Space,一写就是 5 年。

我把这个小窝,布置的很好,这也是我最快乐的快乐的一段时间。

现在想起来前司给我过生日还是挺感动的。2021.4.1

我真的很早就有在写 Swift UI 了,虽然那时候和 Lakr 还没有和见过面。于 2021 年 4 月在学校图书馆。

2021 年劳动节,第一次来杭州,见到了 Lakr。准备前往蚂蚁实习的预备。

2021.7.7,第一次租房,在杭州,三墩,单间,2750。现在回看真是被割惨了。

2021.8 底,离开了,这两个月过得非常煎熬,一点都不快乐。

后面就是秋招了。

https://innei.in/notes/104

在后面我就毕业了。

大学时光匆匆。

看完了?说点什么呢

你要学会合群,否则就会被世界抛弃

作者
2025年5月10日 23:41

引言

“你要学会合群,否则就会被世界抛弃”,这样的告诫像紧箍咒般萦绕在每个人的成长过程中。当我们站在成年人世界的门槛前,这个问题却显露出更为深刻的悖论——合群意味着自我消融于集体狂欢中,而刻意不合群又如同在茫茫人海中举起一面孤绝的旗帜。但真正的智慧在于超越这种非此即彼的二元对立,寻找第三条道路:在保持个体完整性的前提下,与世界建立恰如其分的连接。

现代社会的群体焦虑催生了畸形的合群文化。在路口的人潮如同精准运转的齿轮,每个人都面无表情却保持统一节奏。深夜的办公室里,键盘敲击声此起彼伏,格子间里的年轻人与代码共同闪烁着相似的光芒。这样的画面构成了当代社会的经典隐喻——人们害怕被抛弃的恐惧,已经异化成了对整齐划一的病态追求。

心理学研究显示,过度合群会导致”社会认同偏差”,使个体逐渐丧失独立判断能力。《乌合之众》中描述的群体无意识在社交媒体时代被无限放大,朋友圈里的点赞文化、职场中的服从性测试都在强化这种异化过程。我们追求的合群往往不是精神共鸣,而是对主流价值的屈从性认同。

合群

合群的优势

  • ​​资源与机会的拓展​​:合群者通过广泛社交积累“弱关系”,这些关系可能成为职业发展的跳板。
  • ​​归属感与幸福感​​:良好的人际关系是幸福感的决定性因素。
  • ​​降低决策成本​​:随大流的选择能减少独立决策的压力。

合群的弊端

  • ​​自我消解​​:过度合群可能导致个性湮没,例如为迎合群体而压抑真实想法,最终成为“没有棱角”的人。
  • ​​资源浪费​​:合群者可能因攀比陷入无效社交,如为维持人脉被迫参加无意义的聚会,消耗时间与金钱。

不合群

不合群的​优势

  • ​​独立思考与创新突破​​:不合群者往往能跳出群体思维桎梏。
  • ​​专注自我成长​​:独处者可将精力投入热爱之事。
  • ​​避免情绪内耗​​:不合群者无需为迎合他人隐藏真实情绪,从而减少心理压力。

不合群的弊端

  • ​​社会脱节​​:过度不合群可能导致信息闭塞、人脉薄弱。
  • ​​孤独感​​:长期缺乏群体互动可能引发心理问题,甚至诱发抑郁。

结语

树木在森林中既保持根系的独立性,又通过菌丝网络共享养分。这种自然界的智慧启示我们:理想的生存状态应当是”和而不同”。当我们既能保持专业领域的内核稳定,又能在需要时融入多元网络,便实现了对传统身份范式的超越。这不是非此即彼的选择,而是通过持续的身份协商,在流动中共建更开放的价值生态系统。真正的自由,或许就存在于这种进退有度的动态平衡之中。

那些既能享受独处时的深度思考,又能融入群体创造价值的生命状态,才是对抗异化与孤独的最佳姿态。我们不必做燃烧自己的蜡烛,而可以成为既照亮自己也能温暖他人的星火,在浩瀚夜空中书写属于自己的轨迹。

合群与否,不过表象。自洽与否,方为本质。内心丰盈者,独行也如众。

FileCodeBox:告别网盘烦恼,安全高效的文件分享神器

作者ysicing
2025年5月9日 22:13

在日常工作生活中,处理敏感项目文件时,担心文件被第三方泄露; 使用第三方网盘时还要下载客户端(客户端有时还给你偷跑流量,美名加速),有时下载还得开会员。如果有这些问题的话,我强烈安利一款开源利器——FileCodeBox!它开源、好用、安全,完美解决文件分享的各种难题,让你轻松搞定工作和生活中的文件传输。

开源地址

  • 代码仓库 vastsa/FileCodeBox
  • 镜像地址: lanol/filecodebox
  • 国内镜像: ccr.ccs.tencentyun.com/k7scn/filecodebox

三大优势,解决分享痛点

FileCodeBox 专为文件分享的痛点设计,简单几步就能让你的分享体验焕然一新

超快传输,省时省心

速度取决于你的网络带宽

  • 无大小限制:无论是高清设计稿还是海量项目文件,统统支持,轻松上传
  • 拖拽即传:无需压缩打包,直接拖文件到页面,秒速完成上传
  • 批量分享:支持多文件同时上传和分享,项目交付、团队协作 so easy!

安全可靠,隐私无忧

  • 加密保护:分享链接全程加密,只有指定接收者才能访问,杜绝泄露风险
  • 阅后即焚:支持设置文件过期时间,自动删除,防止资料被长期留存
  • 下载控制:可限制链接使用次数或者有效期,避免文件被恶意传播,敏感资料更安心

极致便捷,随时随地

  • 无需注册:凭码取件
  • 清爽体验:界面简洁

部署简单

要求

以下任选一个就行,我推荐使用腾讯云锐驰 200M,存储使用赠送的对象存储 😄

  • 有公网 IP 的服务器
  • 内网穿透走 cloudflare tunnels

PS: FileCodeBox 也支持对象存储,这里我就不推荐 MinIO 了直接使用本地存储就行,因为这个分享服务我定位是短期临时分享或者按次永久分享。

部署

  • docker-compose.yaml
version: "3"
services:
  filecodebox:
    image: lanol/filecodebox:latest
    # image: ccr.ccs.tencentyun.com/k7scn/filecodebox
    container_name: filecodebox
    volumes:
      - /data/filecodebox:/app/data
    ports:
      - "12345:12345"
    restart: always
  • caddy
kd.012321.best {
        import LOG "/var/log/caddy/kd.log"
        reverse_proxy 10.25.123.1:12345
}

分享文件

  • 打开网页,点击"分享文件"
  • 选择或拖拽文件
  • 设置过期时间和次数
  • 获取提取码

获取文件

  • 打开网页,输入提取码
  • 点击获取
  • 下载文件或查看文本

是不是用 FileCodeBox 分享文件,简单到不可思议。整个过程无需复杂操作,接收方也不需要注册,真正“即传即得”!

管理面板

访问 /#/admin
输入管理员密码 FileCodeBox2023, 登录后请立即修改

管理文件和配置

如果需要发送私密信息,建议自建请勿使用第三方服务,避免不必要的问题

最后想说

在数据安全越来越重要的今天,FileCodeBox 不仅是一款工具,更是一种自由、安全的文件分享方式。它让分享变得高效、可控,完美适配各种场景。如果你也厌倦了网盘的限速和收费,或为敏感文件的隐私问题担忧,FileCodeBox 绝对值得一试!
快部署你的 FileCodeBox,体验前所未有的文件分享自由吧!


记录二五年五一之短暂回归家庭

2025年5月9日 18:10
如要阅读全文,点击标题跳转。打工在外,每年能够回家的机会也就五一,十一和过年这三次了,于是,积累在假期账户里的年假,育儿假基本都会在这些假期中,拼接使用,今年五一,我再次多请了几天,从而得以有一个不短的时间在家里陪老婆孩子。本文拾取一些片段,来记录这一小段生活。

使用 React Native Screens 构建一个 Simple Navigation

作者Innei
2025年5月9日 01:01
该渲染由 Shiro API 生成,可能存在排版问题,最佳体验请前往:https://innei.in/posts/tech/build-simple-navigation-with-react-native-screens

上回说到,我们已经大概了解了 React Native Screen 内部是如何工作的。这篇文章将综合前面的内容,实现一个简单的 React Native Navigation。

构想

一个 Navigation 最基础的就是要实现一个 navigate 方法。

navigate 方法需要实现前进后退的基本行为。

模拟在 iOS Native 中,我们可以使用 pushController 和 presentController 两个方法去实现前进的行为。

那么我们可以命名为:

  • pushControllerView 推入 Stack Navigation
  • presentControllerView 推入 Modal

然后是后退的行为,命名为:

  • back 回退 Stack Navigation
  • dismiss 关闭 Modal

然后我们需要集中式管理 push 和 present 的 navigation 的数据,然后在 UI 中呈现和反馈。

实现

Navigation 框架

顺着上面的构想,我们首先需要一个 Navigation 的类。

class Navigation {}

我们需要把 navigation 的数据保存在这个类中,所以我还需要定义数据的类型。

export interface Route {
  id: string

  Component?: NavigationControllerView<any>
  element?: React.ReactElement

  type: NavigationControllerViewType
  props?: unknown
  screenOptions?: NavigationControllerViewExtraProps
}

export type NavigationControllerViewExtraProps = {
  /**
   * Unique identifier for the view.
   */
  id?: string

  /**
   * Title for the view.
   */
  title?: string

  /**
   * Whether the view is transparent.
   */
  transparent?: boolean
} & Pick<
  ScreenProps,
  | 'sheetAllowedDetents'
  | 'sheetCornerRadius'
  | 'sheetExpandsWhenScrolledToEdge'
  | 'sheetElevation'
  | 'sheetGrabberVisible'
  | 'sheetInitialDetentIndex'
  | 'sheetLargestUndimmedDetentIndex'
>

export type NavigationControllerView<P = {}> = FC<P> &
  NavigationControllerViewExtraProps

上面我们定义 NavigationControllerView 的类型,和 Route 的类型。NavigationControllerView 用于定义 NavigationView 的组件类型,Route 用于在 Navigation 类中保存 navigation 的数据。

为了实现在 UI 中的响应式,我们使用 Jotai 去管理这个数据。

export type ChainNavigationContextType = {
  routesAtom: PrimitiveAtom<Route[]>
}

在 Navigation 类中初始化数据:

export class Navigation {
  private ctxValue: ChainNavigationContextType
  constructor(ctxValue: ChainNavigationContextType) {
    this.ctxValue = ctxValue
  }

  static readonly rootNavigation: Navigation = new Navigation({
    routesAtom: atom<Route[]>([]),
  })
}

Navigation 数据管理

上面已经定义了 Navigation 的类型,然后我们通过对数据的控制来实现 push/back 的操作。

class Navigation {
  private viewIdCounter = 0
  private __push(route: Route) {
    const routes = jotaiStore.get(this.ctxValue.routesAtom)
    const hasRoute = routes.some((r) => r.id === route.id)
    if (hasRoute && routes.at(-1)?.id === route.id) {
      console.warn(`Top of stack is already ${route.id}`)
      return
    } else if (hasRoute) {
      route.id = `${route.id}-${this.viewIdCounter++}`
    }
    jotaiStore.set(this.ctxValue.routesAtom, [...routes, route])
  }

  private resolveScreenOptions<T>(
    view: NavigationControllerView<T>,
  ): Required<NavigationControllerViewExtraProps> {
    return {
      transparent: view.transparent ?? false,
      id: view.id ?? view.name ?? `view-${this.viewIdCounter++}`,
      title: view.title ?? '',
      // Form Sheet
      sheetAllowedDetents: view.sheetAllowedDetents ?? 'fitToContents',
      sheetCornerRadius: view.sheetCornerRadius ?? 16,
      sheetExpandsWhenScrolledToEdge:
        view.sheetExpandsWhenScrolledToEdge ?? true,
      sheetElevation: view.sheetElevation ?? 24,
      sheetGrabberVisible: view.sheetGrabberVisible ?? true,
      sheetInitialDetentIndex: view.sheetInitialDetentIndex ?? 0,
      sheetLargestUndimmedDetentIndex:
        view.sheetLargestUndimmedDetentIndex ?? 'medium',
    }
  }

  pushControllerView<T>(view: NavigationControllerView<T>, props?: T) {
    const screenOptions = this.resolveScreenOptions(view)
    this.__push({
      id: screenOptions.id,
      type: 'push',
      Component: view,
      props,
      screenOptions,
    })
  }

  presentControllerView<T>(
    view: NavigationControllerView<T>,
    props?: T,
    type: Exclude<NavigationControllerViewType, 'push'> = 'modal',
  ) {
    const screenOptions = this.resolveScreenOptions(view)
    this.__push({
      id: screenOptions.id,
      type,
      Component: view,
      props,
      screenOptions,
    })
  }
}

之后,back 的操作也非常简单。

class Navigation {
  private __pop() {
    const routes = jotaiStore.get(this.ctxValue.routesAtom)
    const lastRoute = routes.at(-1)
    if (!lastRoute) {
      return
    }
    jotaiStore.set(this.ctxValue.routesAtom, routes.slice(0, -1))
  }

  /**
   * Dismiss the current modal.
   */
  dismiss() {
    const routes = jotaiStore.get(this.ctxValue.routesAtom)
    const lastModalIndex = routes.findLastIndex((r) => r.type !== 'push')
    if (lastModalIndex === -1) {
      return
    }
    jotaiStore.set(this.ctxValue.routesAtom, routes.slice(0, lastModalIndex))
  }

  back() {
    return this.__pop()
  }
}

从上面的代码不难看出,其实我们只是通过对数据的操作实现 Navigation 的逻辑。而真正要在 UI 中呈现 Navigation 的效果还是需要通过 React Native Screens 来实现。

Navigation UI 框架

在上面的文章中,我们已经知道了我们只需要通过传入不同 React Children 到 React Native Screens 的 <ScreenStack /> 中就能实现原生的 navigate 的效果。

那我们现在只需要透过 Navigation 类中管理的数据,通过一些转换就能实现了。

首先我们在 React 中定义一个 Navigation 上下文对象,确保得到正确的 Navigation 实例(如有多个)。

export const NavigationInstanceContext = createContext<Navigation>(null!)

然后,编写一个 RootStackNavigation 组件。

import { SafeAreaProvider } from 'react-native-safe-area-context'
import type { ScreenStackHeaderConfigProps } from 'react-native-screens'
import { ScreenStack } from 'react-native-screens'

interface RootStackNavigationProps {
  children: React.ReactNode

  headerConfig?: ScreenStackHeaderConfigProps
}

export const RootStackNavigation = ({
  children,
  headerConfig,
}: RootStackNavigationProps) => {
  return (
    <SafeAreaProvider>
      <NavigationInstanceContext value={Navigation.rootNavigation}>
        <ScreenStack style={StyleSheet.absoluteFill}>
          <ScreenStackItem headerConfig={headerConfig} screenId="root">
            {children}
          </ScreenStackItem>
        </ScreenStack>
      </NavigationInstanceContext>
    </SafeAreaProvider>
  )
}

在 App 的入口文件中,我们使用 RootStackNavigation 组件包裹整个应用。

export default function App() {
  return (
    <RootStackNavigation>
       <HomeScreen>
    </RootStackNavigation>
  )
}

const HomeScreen = () => {
  return (
    <View>
      <Text>Home</Text>
    </View>
  )
}

RootStackNavigation 组件的 Children 为首屏,也是 Navigation 的根组件,不参与整体的 navigate 行为,即不能被 pop。

Navigation 数据在 UI 中呈现

接下来我们需要把这些数据转换到 React 元素传入到 React Native Screens 的 <ScreenStackItem /> 中。

const ScreenItemsMapper = () => {
  const chainCtxValue = use(ChainNavigationContext)
  const routes = useAtomValue(chainCtxValue.routesAtom)

  const routeGroups = useMemo(() => {
    const groups: Route[][] = []
    let currentGroup: Route[] = []

    routes.forEach((route, index) => {
      // Start a new group if this is the first route or if it's a modal (non-push)
      if (index === 0 || route.type !== 'push') {
        // Save the previous group if it's not empty
        if (currentGroup.length > 0) {
          groups.push(currentGroup)
        }
        // Start a new group with this route
        currentGroup = [route]
      } else {
        // Add to the current group if it's a push route
        currentGroup.push(route)
      }
    })

    // Add the last group if it's not empty
    if (currentGroup.length > 0) {
      groups.push(currentGroup)
    }

    return groups
  }, [routes])

  return (
    <GroupedNavigationRouteContext value={routeGroups}>
      {routeGroups.map((group) => {
        const isPushGroup = group.at(0)?.type === 'push'
        if (!isPushGroup) {
          return <ModalScreenStackItems key={group.at(0)?.id} routes={group} />
        }
        return <MapScreenStackItems key={group.at(0)?.id} routes={group} />
      })}
    </GroupedNavigationRouteContext>
  )
}

const MapScreenStackItems: FC<{
  routes: Route[]
}> = memo(({ routes }) => {
  return routes.map((route) => {
    return (
      <ScreenStackItem
        stackPresentation={'push'}
        key={route.id}
        screenId={route.id}
        screenOptions={route.screenOptions}
      >
        <ResolveView
          comp={route.Component}
          element={route.element}
          props={route.props}
        />
      </ScreenStackItem>
    )
  })
})

const ModalScreenStackItems: FC<{
  routes: Route[]
}> = memo(({ routes }) => {
  const rootModalRoute = routes.at(0)
  const modalScreenOptionsCtxValue = useMemo<
    PrimitiveAtom<ScreenOptionsContextType>
  >(() => atom({}), [])

  const modalScreenOptions = useAtomValue(modalScreenOptionsCtxValue)

  if (!rootModalRoute) {
    return null
  }
  const isFormSheet = rootModalRoute.type === 'formSheet'
  const isStackModal = !isFormSheet

  // Modal screens are always full screen on Android
  const isFullScreen =
    isAndroid ||
    (rootModalRoute.type !== 'modal' && rootModalRoute.type !== 'formSheet')

  if (isStackModal) {
    return (
      <ModalScreenItemOptionsContext value={modalScreenOptionsCtxValue}>
        <WrappedScreenItem
          stackPresentation={rootModalRoute?.type}
          key={rootModalRoute.id}
          screenId={rootModalRoute.id}
          screenOptions={rootModalRoute.screenOptions}
          {...modalScreenOptions}
        >
          <ModalSafeAreaInsetsContext hasTopInset={isFullScreen}>
            <ScreenStack style={StyleSheet.absoluteFill}>
              <WrappedScreenItem
                screenId={rootModalRoute.id}
                screenOptions={rootModalRoute.screenOptions}
              >
                <ResolveView
                  comp={rootModalRoute.Component}
                  element={rootModalRoute.element}
                  props={rootModalRoute.props}
                />
              </WrappedScreenItem>
              {routes.slice(1).map((route) => {
                return (
                  <WrappedScreenItem
                    stackPresentation={'push'}
                    key={route.id}
                    screenId={route.id}
                    screenOptions={route.screenOptions}
                  >
                    <ResolveView
                      comp={route.Component}
                      element={route.element}
                      props={route.props}
                    />
                  </WrappedScreenItem>
                )
              })}
            </ScreenStack>
          </ModalSafeAreaInsetsContext>
        </WrappedScreenItem>
      </ModalScreenItemOptionsContext>
    )
  }

  return routes.map((route) => {
    return (
      <ModalScreenItemOptionsContext
        value={modalScreenOptionsCtxValue}
        key={route.id}
      >
        <ModalSafeAreaInsetsContext hasTopInset={!isFormSheet}>
          <WrappedScreenItem
            screenId={route.id}
            stackPresentation={route.type}
            screenOptions={route.screenOptions}
          >
            <ResolveView
              comp={route.Component}
              element={route.element}
              props={route.props}
            />
          </WrappedScreenItem>
        </ModalSafeAreaInsetsContext>
      </ModalScreenItemOptionsContext>
    )
  })
})

const ResolveView: FC<{
  comp?: NavigationControllerView<any>
  element?: React.ReactElement
  props?: unknown
}> = ({ comp: Component, element, props }) => {
  if (Component && typeof Component === 'function') {
    return <Component {...(props as any)} />
  }
  if (element) {
    return element
  }
  throw new Error('No component or element provided')
}

const ModalSafeAreaInsetsContext: FC<{
  children: React.ReactNode
  hasTopInset?: boolean
}> = ({ children, hasTopInset = true }) => {
  const rootInsets = useSafeAreaInsets()
  const rootFrame = useSafeAreaFrame()

  return (
    <SafeAreaFrameContext value={rootFrame}>
      <SafeAreaInsetsContext
        value={useMemo(
          () => ({
            ...rootInsets,
            top: hasTopInset ? rootInsets.top : 0,
          }),
          [hasTopInset, rootInsets],
        )}
      >
        {children}
      </SafeAreaInsetsContext>
    </SafeAreaFrameContext>
  )
}

这里需要判断的逻辑可能会有点复杂,需要区分 Stack 和 Modal 的类型,在 ModalStack 中又需要区分 formSheet 等等。同时每个 Modal 中有需要再包裹一层 StackScreen 等等。

从简单来说,就是需要根据 Navigation 的数据,生成对应的 <ScreenStackItem />,然后传入到 <ScreenStack /> 中。

这里的详细的代码均可在下面的链接中查看:

https://github.com/RSSNext/Follow/blob/efc2e9713bcd54f82f9377de35ef5532008d6004/apps/mobile/src/lib/navigation/StackNavigation.tsx

然后我们还需要处理 native navigation 的状态同步,主要在 native 触发 pop 和 dismiss 的时机发送的事件。在前面的文章中讲过,可以通过 ScreenStackItemonDismissed 监听。

这里我们直接对 ScreenStackItem 再次封装。

export const WrappedScreenItem: FC<
  {
    screenId: string
    children: React.ReactNode
    stackPresentation?: StackPresentationTypes

    screenOptions?: NavigationControllerViewExtraProps
    style?: StyleProp<ViewStyle>
  } & ScreenOptionsContextType
> = memo(
  ({
    screenId,
    children,
    stackPresentation,

    screenOptions: screenOptionsProp,
    style,
    ...rest
  }) => {
    const navigation = useNavigation()

    const screenOptionsCtxValue = useMemo<
      PrimitiveAtom<ScreenOptionsContextType>
    >(() => atom({}), [])

    const screenOptionsFromCtx = useAtomValue(screenOptionsCtxValue)

    // Priority: Ctx > Define on Component

    const mergedScreenOptions = useMemo(
      () => ({
        ...screenOptionsProp,
        ...resolveScreenOptions(screenOptionsFromCtx),
      }),
      [screenOptionsFromCtx, screenOptionsProp],
    )

    const handleDismiss = useCallback(
      (
        e: NativeSyntheticEvent<{
          dismissCount: number
        }>,
      ) => {
        if (e.nativeEvent.dismissCount > 0) {
          for (let i = 0; i < e.nativeEvent.dismissCount; i++) {
            navigation.__internal_dismiss(screenId)
          }
        }
      },
      [navigation, screenId],
    )

    const ref = useRef<View>(null)

    return (
      <ScreenItemContext value={ctxValue}>
        <ScreenOptionsContext value={screenOptionsCtxValue}>
          <ScreenStackItem
            key={screenId}
            screenId={screenId}
            ref={ref}
            stackPresentation={stackPresentation}
            style={[StyleSheet.absoluteFill, style]}
            {...rest}
            {...mergedScreenOptions}
            onDismissed={handleDismiss}
            onNativeDismissCancelled={handleDismiss}
          >
            {children}
          </ScreenStackItem>
        </ScreenOptionsContext>
      </ScreenItemContext>
    )
  },
)

定义 NavigationControllerView

export const PlayerScreen: NavigationControllerView = () => {
  return <SheetScreen onClose={() => navigation.dismiss()}></SheetScreen>
}

PlayerScreen.transparent = true

使用 Navigation

那么现在我们就可以在 React 中使用 Navigation 了。

const navigation = useNavigation()

navigation.pushControllerView(PlayerScreen)

那么,一个简单的 Navigation 就完成了。

当然如果你有兴趣的话,也可以查看 Folo 这部分的完整实现,包括如何和 Bottom Tab 结合和页面 ScrollView 的联动。

https://github.com/RSSNext/Follow/blob/6694a346a0bd9f2cea19c71e87484acc56ed3705/apps/mobile/src/lib/navigation

看完了?说点什么呢

安利一下我最近写的两个caddy插件

作者ysicing
2025年5月7日 22:07

我个人 Caddy 粉哈,习惯 Caddy 一梭子,从我历史博客中就可以看出来。最近写了两个 Caddy 的插件,geocngfw.

源码及镜像

源码 ysicing/dockerfiles#caddy

以下是我构建好的镜像,可以根据自己的环境拉取

  • ysicing/caddy2
  • ghcr.io/ysicing/caddy2
  • registry.cn-beijing.aliyuncs.com/k7scn/caddy2
  • ccr.ccs.tencentyun.com/k7scn/caddy2

源码构建

需要 go 环境了

go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy build \
    --with github.com/caddyserver/jsonc-adapter \
    --with github.com/caddy-dns/cloudflare \
    --with github.com/caddy-dns/tencentcloud \
    --with github.com/caddy-dns/alidns \
    --with github.com/ysicing/caddy2-geocn \
    --with github.com/ysicing/caddy2-gfw \
    --with github.com/mholt/caddy-dynamicdns \
    --with github.com/mholt/caddy-events-exec \
    --with github.com/WeidiDeng/caddy-cloudflare-ip \
    --with github.com/xcaddyplugins/caddy-trusted-cloudfront \
    --with github.com/mholt/caddy-l4 \
    --with github.com/mholt/caddy-webdav \
    --with github.com/mholt/caddy-ratelimit

插件 geocn

  • 源码:https://github.com/ysicing/caddy2-geocn
  • 用途:识别来源 ip 是否为中国 ip,我的大部分服务都开启了这个,只针对大陆放行,甚至部分服务只针对部分省市(误判比较大,后续有需要也可以开源 😄)
@china {
		geocn 
	}
	file_server @china {
		root ./docker/example/deny
	}

上面是默认参考,正常情况下不需要调整,GeoIP 数据源来自 Hackl0us/GeoIP2-CN,支持自定义

geocn {
 georemote 你的自定义地址
}

插件 gfw

{
    order gfw before respond
}

:80 {
    gfw {
        # 基本规则配置
        block_rule ip:1.2.3.4
        block_rule url:/admin
        block_rule ua:curl
        block_rule_file /path/to/rules.txt
        ttl 24h

        # 额外安全检测(默认关闭)
        enable_extra true
    }
}

目前是所有实例共享黑名单的,命中就 1 天黑名单直接返回 403,之前想的是命中后触发 hook 执行 iptables 封禁 ip,但是容器跑的好像不太方便。

最后

大家对 Caddy 插件有什么的需求或者想法么?


2025年国内外免费AI绘图大比拼:腾讯元宝、通义万相、豆包等10款平台「德州扒鸡创意胶囊」实测

作者斌仔
2025年5月7日 16:47

前言:AI绘图工具为何成为设计师新宠?

随着AI绘画技术的飞速突破,像Stable Diffusion、Midjourney等先进工具已经全面渗透到设计领域,成为众多设计师的得力助手。在当今数字化设计的浪潮中,免费的AI画图平台如雨后春笋般涌现。本文将对腾讯元宝、通义万相、豆包等10款热门免费AI画图平台进行详细实测,通过极具创意的「德州扒鸡创意胶囊」案例,为您揭秘哪款工具最能精准理解中文提示词,为设计师们在选择合适的AI绘图工具时提供有价值的参考。

核心测试案例解析

1.1 创意需求说明

  • 设计目标:精心制作16:9比例的胶囊造型微缩场景,以满足特定的视觉展示和设计需求。

  • 核心元素

    • 地域特色:山东德州,这座历史悠久的城市拥有独特的文化和地域风情,为设计增添了丰富的内涵。
    • 产品植入:传统扒鸡,作为德州的标志性特产,承载着当地的美食文化和历史记忆。
    • 风格要求:3D渲染光泽质感,这种风格能够使画面更加逼真、生动,展现出高品质的视觉效果。

提示词

参考下面的提示词帮我生成胶囊16:9图片,先基于地区和特产更改示例提示词中的内容然后再生成。
地区:德州
特产:扒鸡
示例提示词为:
一个胶囊形状的创意微缩场景,胶囊横放着。胶囊一半为活力的樱花粉色或现代的银灰色,印有白色的字“東京”和英文“TOKYO”。另一半透明,展示东京真实的特色建筑缩影——一座现代的东京塔或晴空塔微缩模型,完全包含在胶囊里面,不超出边界。背景为繁华的涩谷十字路口或浅草寺的微缩景象,整体风格3D渲染,具有梦幻感,使用C4D制作,材质有光泽感。

腾讯元宝

腾讯元宝:侧边栏 - AI画图

腾讯元宝是腾讯公司推出的一款AI助手工具,集搜索、AI 问答、文生图等功能于一体。其文生图功能允许用户通过文字描述生成图像,对于创意设计、内容创作等领域具有重要价值。用户只需输入一段文字,腾讯元宝即可根据文字内容生成相应的图像。此外,2025 年 2 月 21 日,腾讯元宝上线文生图功能,用户上传图片后,可通过 DeepSeek 模型解析内容并生成图文结合的创意结果(如分析图片场景、生成配文);结合混元T1模型,可识别图片中的文字和场景,辅助生成更精准的绘图描述(例如上传风景图后,AI自动生成绘画关键词)。它还支持AI修图、风格、比例的切换,为用户提供了多样化的创作选择。

在本次测试中,腾讯元宝绘制出了示例提示词的图片,但未绘制修改后的图片

腾讯元宝 - AI画图
腾讯元宝 - AI画图

通义千问

通义千问:图像生成

通义千问是由阿里云研发的一款先进的人工智能语言模型,基于Transformer架构,通过创新的训练方法(如动态NTK感知插值、LogN - Scaling、窗口注意力机制)扩展上下文长度,其千亿级参数规模(Qwen2.5 - Max版本)结合混合专家模型(MoE)架构,在自然语言处理、多模态理解等任务中表现出色。它适用于多种自然语言处理任务,包括文本生成、问答系统、机器翻译、文本分类等,在各个领域都能提供出色的表现。同时,它整合图文生成(通义万相)、音视频理解(通义星尘),支持PDF、Excel等多格式文件分析,还具备企业级服务闭环,实现了阿里云生态整合,无缝对接电商、物流、金融等行业解决方案(如天猫精灵智能客服),并且支持私有化部署,满足金融、政务等敏感场景需求。其开源影响力也较大,Qwen系列模型下载量突破1.8亿,衍生模型数达9万,超越Meta的Llama系列。

在图像生成方面,通义千问绘制出了修改后的图片,但只画出了扒鸡,没有绘制出德州等信息。

通义千问 - 图像生成
通义千问 - 图像生成

豆包

豆包:侧边栏 - 图像生成

豆包是字节跳动开发的通用大模型,融合了自然语言处理、计算机视觉和语音识别等技术。它提供聊天机器人、写作助手以及英语学习助手等功能,可以回答各种问题并进行对话,支持网页、客户端、APP、插件等形式。基于豆包大模型,字节跳动打造了AI对话助手“豆包”、AI应用开发平台“扣子”、互动娱乐应用“猫箱”,以及星绘、即梦等AI创作工具,并把大模型接入抖音、番茄小说、飞书、巨量引擎等50余个业务,用以提升效率和优化产品体验。

  • 豆包不仅在文本处理上表现出色,还具备强大的多模态交互能力,并且支持多风格、多比例的一致性多镜头生成,可应用在电商营销、动画教育、城市文旅、微剧本等领域。通过字节跳动内部 50+ 业务场景实践验证,每日千亿级 tokens 大使用量,使得豆包在推理效率和成本控制上具有明显优势。在图片生成方面,它一次性可生成多达 20 张 3D 风格的高质量图片,极大满足了设计、创意和娱乐等多样化需求。

在本次测试中,豆包完整绘制出扒鸡、德州等信息。

豆包 - 图像生成
豆包 - 图像生成

Gemini

Gemini

Google于2023年推出Gemini系列模型,作为其多模态大模型的里程碑,旨在结合文本、图像、音频等多模态能力,同时提升代码生成、对话理解等核心功能。其核心目标包括多模态统一(处理文本、图像、音频等多种输入输出)、长上下文理解(支持超长上下文,如Gemini Pro支持16万token)以及高效推理(在轻量化版本如Gemini Ace中平衡性能与计算资源)。

Gemini系列有多个版本,如基础版本Gemini 1支持多模态任务,适用于通用场景(如问答、摘要生成);高性能版本Gemini Pro面向复杂任务,具备超长上下文处理能力(支持16万token的上下文输入,适合长文档分析或复杂对话),多模态能力增强,可生成或理解高质量图像描述、音频内容,还支持代码生成;轻量化版本Gemini Ace优化成本与速度,具有低延迟推理特点,适合实时交互(如聊天机器人),适用于移动端或资源受限环境;2024年更新的Gemini 2新增视频理解能力,增强了推理和代码生成能力。

  • 在文生图方面,Gemini在自然语言的修改指令理解、材质质感复现、局部细节微调方面,达到了部分生产创作环节完全可用的水准。例如,它能完成简单形体的材质变换、连续微调形态细节、大幅度改变视角,还能将手绘稿转设计渲染图并拍出产品宣传图,一次性生成多套不同风格的设计,以及进行抠图、换背景、打光影等操作。不过,它也存在一些局限性,如多模态生成能力方面图像生成质量可能不如专用模型(如DALL·E),实时视频处理能力复杂视频分析仍需优化,高性能版本(如Gemini Pro)部署成本较高。

在本次测试中,Gemini绘制出了毫无相关的事物,但却有Dezhou字样。

Gemini
Gemini

即梦AI

即梦AI:图像生成

即梦(Jimeng)是字节跳动旗下的一个融合了前沿AI技术的多模态内容创作平台。它不仅仅能生成文本,更能理解和创造图像、音频乃至视频内容。其核心基于自然语言处理(NLP)、计算机视觉(CV)和先进的生成模型(如GANs、Diffusion Models等),采用“模型联邦”策略,整合了针对文本、图像、音频等不同任务优化的专用模型,并通过智能路由(Intelligent Routing)机制,根据用户需求动态调用最合适的模型组合,实现更专业、更高效的生成效果。同时,它在处理长篇内容或系列创作时,展现出良好的上下文理解和一致性保持能力,原生支持文本到图像(Text - to - Image)、图像到文本(Image - to - Text)、文本到音频(Text - to - Speech)等多种跨模态转换。

  • 2025年4月3日,即梦3.0正式启动灰度测试,并于4月7日全量上线。此次更新以中文文本生成能力和影视级画质为核心突破,支持2K分辨率(2560×1440像素)的直出图像,新增的“影视质感”效果可生成更具真实感和细腻度的图像,适用于广告、海报等商业场景。在中文文本生成能力方面,优化了小字稳定性,解决了此前版本中小字模糊、排版混乱的问题,支持更具设计感的字体生成,对中文指令的识别更精准。此外,它还具备智能化操作与效率提升功能,如精准控制功能,用户可通过简单指令调整图像中元素的细节;消除笔工具,针对生成图像中可能出现的冗余元素,提供一键消除功能。在语义理解上进一步优化,能更准确地解析复杂Prompt,支持多种应用场景的定制化生成,如电商广告、影视概念设计、教育内容等。

在本次测试中,即梦AI绘制出了示例提示词的图片,但未绘制修改后的图片。

即梦AI - 图像生成
即梦AI - 图像生成

哩布哩布AI

LiblibAI - 哩布哩布AI:在线生成

开通会员

哩布哩布AI是由北京奇点星宇科技有限公司运营的人工智能平台,是一个基于人工智能技术的创作平台,主要以AI图像生成功能为核心,在2023年5月创立,在短短时间内发展迅速,已经成为国内AI图像赛道的重要平台之一。

  • 它具有多样化的创作模型,涵盖动漫、游戏、摄影、写实、科幻、插画、平面设计、建筑、工业设计等多个领域,平台拥有10W +的模型可供选择,用户可以一键将所需模型入库,方便快捷地获取各类创作资源,节省寻找素材的时间,提高创作效率。其创作流程便捷,智能图像生成功能可让用户通过输入描述性的文本,将这些文本转化为图像;支持一键上传图片,可用于做配图、插图等且质量非常高,还支持高清修复和图生图功能;用户还可以利用其云端计算资源训练自己的AI模型。在用户体验方面,支持筛选和选择不同的创作模型,支持3D立体、扁平抽象等多种设计风格,提供会员专属权益,具有强大的用户社区,方便用户交流和分享创作经验。此外,它操作便捷,无需复杂配置,用户可以直接打开Liblib AI网页端即可使用云端SD – WEBUI,不用部署,不用下载模型;界面友好直观,即使是新手用户也能快速熟悉操作流程。

在本次测试中,哩布哩布AI绘制出了示例提示词的图片,但未绘制修改后的图片。

哩布哩布AI - 在线生成 - 星流Star - 3
哩布哩布AI - 在线生成 - 星流Star - 3

通义万相

通义万相:文字做图

通义万相是阿里云推出的AI多模态内容生成平台,基于阿里通义大模型,能够自动生成高质量的图片、艺术设计、广告素材、数字人形象等,广泛应用于电商、影视、设计、社交媒体等领域。它整合了文生图、图生图、风格迁移等功能,还具备高清修复、个性化定制等特色功能。其技术架构依托阿里巴巴通义大模型,结合扩散模型(Diffusion Model)和Transformer架构进行高质量图像生成。

  • 在文生图方面,它通过文本描述生成高清图像,支持水彩、油画、中国画、扁平插画、二次元、素描、3D卡通等8种风格,并且风格之间的差别、特色都十分显著,生成速度快,复杂的图像生成在 45s 以下,简单图像在30s以下。相似图像生成功能可让用户上传不超过10M的 jpg、jpeg、png、bmp 图片,点击生成按钮,右侧生成4张相似图片可供下载,生成的相似图与原图贴合程度较高。图像风格迁移功能支持输入两张图片,一张为原图,一张为指定风格图,生成的图像会保留原图的内容和风格图的风格。

在本次测试中,通义万相完整绘制出扒鸡、德州等信息(就是不太美观)。

通义万相 - 文字做图
通义万相 - 文字做图

可灵

可灵:图片生成

可灵AI是快手科技旗下的平台,2025年4月15日,可灵AI宣布基座模型再次升级,面向全球正式发布可灵2.0视频生成模型及可图2.0图像生成模型。作为全球首个用户可用的DiT视频生成模型,可灵AI自去年6月上线至今的10个月时间里,月活用户数量增长25倍,全球用户规模已突破2,200万。3月27日,全球知名AI基准测试机构Artificial Analysis发布了最新的全球视频生成大模型榜单,快手可灵1.6 Pro(高品质模式)以1,000分的Arena ELO基准测试评分登陆图生视频(Image to Video)赛道榜首。

  • 可灵2.0模型在动态质量、语义响应、画面美学等维度保持全球领先;可图2.0模型在指令遵循、电影质感及艺术风格表现等方面显著提升。可灵2.0大师版全面升级视频及图像创作可控生成与编辑能力,上线全新的多模态视频编辑功能,能灵活理解用户意图,支持在一段视频的基础之上,通过输入图片或文字,对生成的视频内容实现元素的增加、删减、替换;可图2.0也上线了实用的图像可控编辑功能——局部重绘和扩图,支持图片的增加、修改和修复,还上线了全新的风格转绘功能,只需要上传一张图片加上风格描述,就能一键切换图片的艺术风格,同时精准保留原图的语义内容。

在本次测试中(使用的是可图1.5),可灵绘制出了示例提示词的图片,但未绘制修改后的图片(速度慢,且最新模型可图2.0需要充值VIP)。

可灵 - 图片生成
可灵 - 图片生成

ChatGPT 4o Image

ChatGPT - 4o 图像生成

GPT - 4o 是 OpenAI 在 2025 年 3 月开始迭代的图像生成功能,其独特之处在于它能够在对话中理解上下文,生成更符合用户意图的图像。这一功能自推出以来,因其便捷性和生成图像的高质量,迅速成为 ChatGPT Plus/Pro 等版本的用户喜爱的功能。不过,使用 GPT - 4o 生成图像存在频率限制,一般情况下,ChatGPT Plus 用户每三小时可以使用大约几十次图像生成功能,在系统高峰期,这一限制可能会进一步减少,且该功能与文本生成功能共享 ChatGPT Plus 会员的权益次数。

  • 在图像生成方面,它具有理解提示词准确、一致性强等特点,擅长精确按照提示要求生成内容、多元素组合场景以及文字呈现准确性高的场景,适用于电商产品展示、企业宣传材料、需要准确呈现特定元素的场景等商业应用场景。

在本次测试中,ChatGPT 4o Image完整绘制出扒鸡、德州等信息(符合预期)。

ChatGPT 4o Image
ChatGPT 4o Image

测试效果

模型名称 效果说明 是否有理解修改能力 是否绘制完整
腾讯元宝 绘制出了示例提示词的图片,但未绘制修改后的图片
通义千问 绘制出了修改后的图片,只画出了扒鸡,但是没有绘制出德州等信息 ✔️
豆包 完整绘制出扒鸡、德州等信息 ✔️ ✔️
Gemini 绘制出了毫无相关的事物,但却有Dezhou字样
即梦AI 绘制出了示例提示词的图片,但未绘制修改后的图片
哩布哩布AI 绘制出了示例提示词的图片,但未绘制修改后的图片
通义万相 完整绘制出扒鸡、德州等信息(就是不太美观) ✔️ ✔️
可灵 绘制出了示例提示词的图片,但未绘制修改后的图片(速度慢,且最新模型需要充值VIP)
ChatGPT 4o Image 完整绘制出扒鸡、德州等信息(符合预期) ✔️ ✔️

总结

AI生成图首选:ChatGPT 4o Image > 豆包 > 通义万相

五一劳动节——高山采茶!

2025年5月7日 15:01

五一驾车5小时回家,一路上有惊无险安全到家。回到家第一时间就是大口深呼吸山里的空气,真的太舒服了,一瞬间人就不困了。

本来打算五一在家里好好休息的,没有想到,五一真正劳动了2天,1号去采了自己家的茶园,一个北方人第一次采茶,老婆说你的方法不对。我说看视频中就是这么采的呀!😂

6个人一天采了17斤鲜叶,真正体会到了茶农的不易。因为每一片叶子都是茶农亲手揪下来的,当然也有机器砍的但是那样的茶就会有很多树枝,烂叶。不像我们都是亲手一下一下摘下来的!

2025-05-07T03:21:01.webp

这几年的茶叶也开始禁止打农药。还设立了举报电话。

2025-05-07T03:24:15.webp

第二天,岳父又带着去采了野茶,一路上东揪一点,西采一点,也是采了一点,回家交给姑父给做了绿茶。

2025-05-07T06:56:03.webp

不说别的高山野茶的香味还就是比家门口的茶叶要香一点!岳父讲海拔不同环境不同,各种因素造就了野茶的味道不同。
但是两个茶叶放了一天时间后香味就都差不多了!奇怪!

当然!有需要茶叶的朋友也可以联系我哦!无农药残留,无重金属超标,无添加剂。

Mac新版微信4.0版本以上如何备份聊天记录到移动硬盘

作者张洪Heo
2025年5月7日 11:14
这篇文章介绍了微信备份迁移的相关知识,重点针对mac用户在微信备份文件不兼容问题的解决方法。用户因微信版本更新引发的整个备份布局变化感到困惑,文章详细指导了如何迁移旧版备份文件、创建新备份文件、完成备份操作,并解决了备份文件丢失及无法截图等问题。文章还提供了具体的操作步骤:复制备份文件至移动硬盘、创建软链接、使用codesign命令重新签名微信应用、授予系统访问权限等。同时,文章提醒用户注意备份前的检查,确保硬盘连接正常,以避免操作失误。整体内容结构清晰,语言简洁明了,非常适合mac用户快速掌握微信备份迁移的技巧。

上瘾性行为,正在毁掉我们这代人

作者
2025年5月6日 23:30
本文转载自公众号邓发发,由于十分赞同收藏于杂记中。另外,切勿断错标题。

哈喽,我是发发。因为我想发财,所以叫发发。前两天刷朋友圈,看到一个前同事凌晨两点发了条动态:“又刷短视频到天亮,明天还要上班,我是不是废了?”配图是一张手机屏幕使用时间截图——日均 8 小时。我回复她:“试试把手机调成黑白模式?”她回我:“试过,没用,刷着刷着又调回来了。”

你看,这就是现代人的困境——我们不是在上瘾,就是在寻找下一个上瘾的路上。

我们到底在沉迷什么?

上瘾的本质,是用短期快感填补长期空虚。

  • 短视频:15 秒一个刺激,刷到停不下来,但关上手机,大脑一片空白。
  • 游戏:赢了想再赢,输了想翻盘,一局接一局,时间像被黑洞吸走。
  • 暴食:明明不饿,却停不住往嘴里塞东西,吃完又后悔,第二天继续循环。
  • 囤积信息:收藏一堆课程、文章,告诉自己“以后看”,结果永远没看。

这些行为的共同点是什么?它们都能让你立刻爽,但长期来看,毫无意义。

为什么我们戒不掉?

因为大脑被劫持了。

科学家发现,上瘾行为会劫持多巴胺系统——这是一种让你感觉“快乐”的神经递质。正常的多巴胺分泌是“努力—奖励”模式,比如:你认真工作,完成项目,得到成就感。你坚持运动,身材变好,感到自信。但上瘾行为直接绕过“努力”,给你“即时奖励”。刷短视频,不用思考,直接爽。打游戏赢一局,立刻有成就感。

久而久之,你的大脑会认为:“既然躺着就能爽,为什么还要努力?”于是,你越来越难专注,越来越没耐心,甚至对现实生活失去兴趣。

上瘾的真正代价

很多人觉得,上瘾只是“浪费时间”,没什么大不了的。但它的真正危害是:让你失去对人生的掌控权。

  • 时间黑洞:每天刷 3 小时短视频,一年就是 1095 小时,相当于 45 天。
  • 注意力碎片化:你再也无法专注读完一本书,甚至看不完一篇长文章。
  • 现实感丧失:虚拟世界的快感越强,现实越显得无聊,于是你更想逃避。
  • 自我厌恶:明知道不该这样,却控制不住,最后陷入“放纵—愧疚—更放纵”的恶性循环。

最可怕的是,上瘾会让人失去“延迟满足”的能力。你不再相信“长期坚持会有回报”,只想立刻得到反馈。于是,你放弃健身、放弃学习、放弃任何需要时间积累的事情,变得越来越浮躁。

如何真正戒瘾?

先认清一个事实:戒瘾不是靠意志力

真正的解决方法,是用新习惯替代旧习惯。比如:

  • 想刷短视频时,立刻站起来做 10 个深蹲。
  • 想打游戏时,打开一本轻松的小说读 10 分钟。
  • 想吃零食时,先喝一大杯水,等 5 分钟再决定。

调整环境,减少诱惑

把手机调成灰度模式(黑白屏),降低视觉刺激。卸载最常刷的 APP,或者设置每天使用限额。睡前把手机放在客厅,不带进卧室。

找到真正的“替代满足”

上瘾的本质是逃避现实,所以关键是:找到比上瘾更有意义的事。比如:

  • 培养一个能带来成就感的爱好(剪辑、绘画、写作、手工)。
  • 加入一个正向社群(运动小组、徒步群)。
  • 设定一个小目标(比如每天写 100 字,跑步 30 分钟)。

记住:戒瘾不是剥夺快乐,而是找回真正的快乐。

最后想说

我曾经也是个很容易“上头”的人,熬夜追剧、刷社交媒体到凌晨。后来我发现,真正的自由,不是想做什么就做什么,而是不想做什么就能不做什么。如果你也深陷上瘾性的行为,别急着否定自己。上瘾不是你的错,只是你还没找到更好的生活方式。从今天开始,试着做一个小改变:每天减少 30 分钟刷手机的时间,换成散步、运动或阅读。给自己 1-3 个月,慢慢调整,别追求立刻戒断。

熬过最初的戒断反应,你会发现——原来生活本身,就足够有趣。

原文链接:https://mp.weixin.qq.com/s/J0ll4R9IX08VoFk_ZBIOWQ

一拖再拖的四月月报

2025年5月6日 21:53

本该写在月底的月报,因为提前回老家被拖了,在老家因为懒也一直拖着没写,回到工作岗位,进入工作状态,这才姗姗动笔。

四月份的工作很忙,有时候晚上甚至搞到很晚,因此做自己事情的时间就少了很多。

折腾

因为想要把用了好多年的HHBK拿出来用的时候,发现之前的连接线找不到了,而这个连接线还是很老的mini B接口,真的很难找到,于是又想着去改造成无线了。之前了解到一个YDKB的改装方案,但是价格要400多,就放弃了。这次在淘宝上一搜索,居然有一个100多块钱的方案,并且支持无线/蓝牙/USB三种模式,usb口也改成了type-c接口,这个价格同时还包含了锂电池,这么实惠的价格,立马就下单了。回来替换也很简单,就是把原来的主控板,换成这个新的主控板就好了,原先的usbhub口位置则是变成了开关按钮和模式切换的按键开口,usb-c在原来min b的位置也勉强能插上线,唯一的缺点就是键位模式设置没有设计在原来键位设置的地方,如果需要修改需要拆开键盘。总体,瑕不掩瑜,推荐尝试改造。

另外,上个月提到我使用Flutter做了一个memos的客户端,经过这个月的修改,和google play的封闭测试,目前已经正式上架google play。感兴趣可前往下载,链接:https://play.google.com/store/apps/details?id=me.isming.fymemos, 同时我也开源了代码,感兴趣可以去github查看,也欢迎贡献代码,链接: GitHub - sangmingming/fymemos: A memos client write in Flutter

清明节去了趟台州,徒步到仙居公盂村,还去了临海台州府城,天台国清寺庙,感兴趣可以看我之前的文章

除了公盂这次徒步,还在月中去了一次苏州西山缥缈峰徒步,缥缈峰难度很低,感觉四五岁的小朋友也可以拿下。因为我们出发比较早,一趟畅通,爬完山之后居然还挺早,又在岛上去看了看东村古村和最佳夕阳观赏点(天气不好,看不到夕阳😂)。

接近月底的周末,因为五一要补一天班,周末只有一天,因此选择在上海找个地方露营。前往广富林郊野公园,发现公园装了收费杆,只有七号门可以进,并且排了很长的队,最终只好在附近找了个农田露营。

这个月主要在看《芯片战争》,看完了余盛的版本,同时这个名字的书还有一个美国的版本,在微信读书看了一小部分,发现其中对于中国的部分有删改,于是又找来了台湾版的译本《晶片战争》,看了一部分。篇幅上来说,余盛版本的更长,其中关于中国的部分篇幅比较长,其中关于中国的介绍是比较乐观的,比如中芯国际,长鑫存储,长江存储等的介绍。而台湾版本的,对于中国的部分不多,评价更加中性,但是因为内容是繁体字,其中很多名词和大陆的说法都不同,看的很慢。对于两本书一起辩证看的,对于我们了解芯片的发展和战争会有更加全面的认识。芯片的设计和生产,涉及到的配套和供应链也很多,而被封锁的中国想要突围,仍然任重道远。

电视剧这个月居然看了两部,首先是王宝强主演的《棋士》,感觉还挺不错的,可以看看。

另外老婆在看一个叫《无忧渡》的电视剧,也跟着一起看完了,这部剧典型的俊男靓女片,男女主都有主角光环,同类型的片子还是《唐朝诡事录》更好看一点。

而看了两部剧的代价就是本来会有点时间练字和看书,被看剧给挤占了,因此以后还是要少看剧。

杂项

看到有网友在玩Slowly这样一个笔友软件,于是也去注册完了完。这个软件基于邮票和根据距离限制邮件送达的时间的设计很有意思。在上面写了公开信,也与几个人有了邮件往来。而每次写信,都要借助翻译软件来优化英语内容,或许也能间接学学英语。立个Flag,在上面找到一两个长期笔友,借此提高一下英语表达的水平。

总结

生活一直在继续,工作繁忙也要抽空多出去多走走。虽然拖了几天但还是把终结补上了,哈哈。也感谢你看到了这里。

看完评论一下吧

轻松部署 Alist + MinIO,打造你的专属私人网盘

作者ysicing
2025年5月6日 20:49

还在为网盘限速、空间不足而焦虑?想要一个安全、快速、完全掌控的私人网盘?今天带你一步步用 Alist 结合 MinIO,快速搭建一个高性能的私人云存储,文件管理从此自由无忧!

部署非常简单,也很适合内网私有化部署。另外这也是一个开源项目,社区灵活度特别高,对接的存储类型非常丰富,但是本文还是着重写写对接 minio。今天的音频调了几版,目前这版相关好点

什么是 Alist 和 MinIO?

  • Alist:一款开源免费的目录列表程序,支持挂载多种存储(如本地存储、云盘、对象存储等),提供简洁美观的界面,支持文件预览、下载、分享等功能。简单来说,它是你文件管理的“超级中枢”。
  • MinIO:一个高性能、分布式的对象存储服务,兼容 S3 协议,适合搭建私有云存储。相比第三方网盘,MinIO 让你完全掌控数据,安全又高效。

通过 Alist + MinIO 的组合,你可以轻松打造一个私有网盘,享受无限存储空间和极速访问体验!

Alist + MinIO 的优势

  • 多存储支持:Alist 支持 MinIO、本地存储、OneDrive、阿里云盘等多种存储方式,灵活扩展。
  • 简洁易用:Alist 界面美观直观,操作简单。
  • 高性能:MinIO 提供企业级的对象存储性能,适合大文件存储和高速访问。
  • 安全可靠:数据存储在你自己的服务器上,隐私有保障。
  • 开源免费:Alist 和 MinIO 均为开源项目,自由使用,社区活跃。

部署步骤:Alist + MinIO 一键搞定

以下以 Docker 部署为例,带你快速搭建 Alist 和 MinIO 的组合。这里就跳过 MinIO 部署相关了,之前也讲过,可以查看我之前写的文章:

准备工作

  • 准备好 MinIO 的账号即可,有存储视频资源最好不过

镜像

根据实际情况来,默认 aio 镜像已经包含本地存储缩略图 ffmpeg 和离线下载 aria2, 后面需要用的上

  • xhofe/alist:main-aio
  • 国内镜像 ccr.ccs.tencentyun.com/k7scn/alist:main-aio

创建 docker compose 文件

  • docker-compose.yml
services:
  alist:
    image: xhofe/alist:main-aio
    # image: ccr.ccs.tencentyun.com/k7scn/alist:main-aio
    container_name: alist
    ports:
      - "5244:5244"
    volumes:
      - /data/alist:/opt/alist/data # 应用程序持久化数据
      - /data/share:/opt/share # 本地存储,可选
    environment:
      - TZ=Asia/Shanghai
      - ALIST_ADMIN_PASSWORD=goxee7dieXeihu9uochoo6iquaighail
    restart: always

ALIST_ADMIN_PASSWORD 支持自定义密码,很早之前我提交的 PR😂,估计也就我一个人这么用。

启动容器

docker compose up -d

配置 caddy

caddy 配置比较简单

alist.ysicing.eu.org {
  reverse_proxy 100.90.80.2:5244
}

访问 alist

访问 Alist:在浏览器输入 http://你的服务器IP:5244 或者 caddy域名,进入 Alist 界面。

默认用户名是 admin, 密码是你配置的 ALIST_ADMIN_PASSWORD 值信息

挂载 MinIO 存储

登录 Alist,点击 管理 > 存储 > 添加

选择存储类型为对象存储

填写以下信息:

  • 挂载路径:自定义,例如 /minio。
  • Endpoint:http://minio 域名地址:9000。
  • Bucket:填写你在 MinIO 创建的存储桶名称,例如 ja。
  • Access Key 和 Secret Key:填入 MinIO 控制台生成的密钥。
  • 强制路径样式:默认勾选
  • 地区:默认留空

保存配置后,返回 Alist 主页,即可看到挂载的 MinIO 存储

可以上面的操作后就可以通过 Alist 浏览、分享 MinIO 中的文件,支持在线预览、下载等功能。

其他

官方文档

总结

通过 Alist 和 MinIO 的组合,你可以轻松搭建一个功能强大、安全可靠的私人网盘,告别存储焦虑!无论是个人文件管理还是团队协作,这个方案都能满足你的需求。快动手试试吧!


手搓点焊机

作者
2025年5月5日 21:47

需求

对于偏爱折腾电子产品的中年男人,拆装电子设备的工具必须配置齐全的。不过,由于使用率偏低,而且受成本因素影响,我至今未增设点焊机。

近日,常使用的短途出行工具GY6小型摩托车频繁出现打火不顺畅的故障。经过排查是电瓶故障引起的,因使用率偏低(一周大概骑行2天,总路程大概10KM),电瓶由于长时间无法及时进行充电,长期亏电损耗导致的容量下降,一般解决方案是修复或者更换新电瓶。观察电瓶生产日期,使用仅大半年,不至于会出现电瓶内部隔板损坏的情况,倘若直接更换有点浪费,因此尝试使用电瓶脉冲修复工具对其进行修复。经过数个小时的修复工作,电瓶容量虽恢复到能用水平,但实测撑不过超一周的停车自然损耗。每次出现亏电无法打火情况便需要拆卸电瓶回家进行数小时修复充电,显然这不是科学解决方案,眼下购置应急启动电源成为一个优选方案。

GY6电瓶
GY6电瓶

优选法拉电容方案

常见的应急启用电源方案:

方案一:锂电池电芯

这个方案和日常用的移动充电宝类似,使用锂电池作为电源,提供较大电流输出实现启动车辆的效果。还有一个优点,日常不用的情况下,还能当充电宝使用。缺点也比较明显,和锂电一样需要定期对其进行充放电维护,且日历寿命较短。

方案二:法拉电容

这个方案使用多个大容量法拉电容进行串并链接组合使用,提供短时间大电流输出实现启动车辆的效果。优点是寿命长,日常无需维护,因此无法当充电宝使用。缺点也比较明显,需要先进行充电后才能使用,充电后能使用次数较少。

应急充电宝技术原理是提供短时间大电流输出,这和点焊机是一致的,因此两个需求的方案可以综合一起进行筛选。因为日常车辆维护会对电瓶健康情况进行检查,这些年需要小车进行应急启动的次数真的屈指可数,而且目前汽车保险公司均免费提供汽车搭电服务,因此个人自备应急启动电源作为汽车应急使用算是伪需求。

方案容量充电时间寿命单次可用价格
锂电芯≥8000mha5V2A,≥4小时3-5年充电一次500焊¥150
法拉电容摩托车50F,小车200F电源16V5A:50F,3分钟;200F,12分钟;10年单次焊接回电大概1-2秒50F,¥60,只能启动摩托车;300F,¥200,大部分汽车

综合上表,最终我选择50F的超级电容方案,实测能启动3次摩托车,点焊基本能秒回电。如果本身有应急启动电源,直接买点焊板会更划算。

组装和物料费用

物料参数价格渠道备注
法拉电容2.7V,6串,带均衡板60海鲜市场
充电板16V5A5.3首富家
外壳200x120x1139.7首富家
端子EC5x1,XT60Ex1,18首富家、PDD
电压表DC4-100V4.6首富家
点焊板K838PDD
搭电钳EC5,50CM15PDD
192

电容预充电
电容预充电

点焊板
点焊板

组装前调试
组装前调试

焊接2个21700试手
焊接2个21700试手

组装了个充电宝
组装了个充电宝

建议和提醒

DIY电子产品有风险,并不适合大部分人使用,综合成本并没有太大价格优势,请谨慎选择。如无法评估自身动手能力,请选择合格的厂家生产的成品。

私有化部署无名杀卡牌游戏

作者ysicing
2025年5月5日 19:52

部署非常简单,非常适合收藏,内网私有化部署。另外这是一个开源项目,灵活度比较高。

项目地址

https://github.com/libnoname/noname

镜像

可以根据自己的网络情况选择对应的镜像下载,镜像比较大, 大概 3.5G 左右。

  • hsiaoshun/noname
  • ccr.ccs.tencentyun.com/k7scn/noname

部署 compose

services:
  noname:
    image: hsiaoshun/noname
    # image: ccr.ccs.tencentyun.com/k7scn/noname
    container_name: noname
    ports:
      - '6080:80'
    restart: always

端口配置

  • 80 游戏本体网页版入口
  • 8080 WS 协议,联机大厅服务(客户端使用)

caddy 代理

示例,不建议公网跑,对带宽有点要求

sgs.ysicing.eu.org {
reverse_proxy 100.90.80.2:6080
}

联机大厅配置说明

目前只支持 windows 和安卓

注意: 结尾的/不能省略, 如果没有证书就是 ws,有证书就是 wss

其他

如果有更多兴趣的话,可以看看无名杀懒人包。


Debian常用初始化流程

作者ysicing
2025年5月5日 14:50


在搭建 k3s 轻量级 Kubernetes 集群时,Debian 系统因其稳定性和灵活性成为首选。然而,Debian 默认配置可能无法满足 k3s 的需求,需要通过初始化优化系统设置。本文将分享一套针对 k3s 环境的 Debian 初始化方案,涵盖基础包安装、系统配置和防火墙规则,仅供参考。

安装基础包

以下命令安装 k3s 集群所需的基础工具,保持系统轻量:

export DEBIAN_FRONTEND=noninteractive
apt update -qq
apt remove -y -qq ufw lxd lxd-client lxcfs lxc-common
apt install --no-install-recommends --no-install-suggests -y -qq nfs-common iptables conntrack jq socat bash-completion open-iscsi rsync ipset ipvsadm htop net-tools wget psmisc git curl nload ebtables ethtool procps

配置系统

配置 ssh

修改 ssh 端口,设置密钥登录,禁用密码登录。这些比较常见,这里就不细说了。

更新内核

之前好像也写过,通常我都是使用最新内核,仅供参考.(通常也会踩坑比较多)

curl https://c.ysicing.net/oss/scripts/debian-upcore.sh | bash
# 或者
apt install -t bookworm-backports linux-image-amd64 -y

配置 system 相关

调整 Systemd 的资源限制和日志设置

mkdir -pv /etc/systemd/system.conf.d
cat > /etc/systemd/system.conf.d/30-k8s-ulimits.conf <<EOF
[Manager]
DefaultLimitCORE=infinity
DefaultLimitNOFILE=100000
DefaultLimitNPROC=100000
EOF

mkdir -pv /etc/systemd/journald.conf.d /var/log/journal

cat > /etc/systemd/journald.conf.d/95-k3s-journald.conf <<EOF
[Journal]
# 持久化保存到磁盘
Storage=persistent
# 最大占用空间 2G
SystemMaxUse=2G
# 单日志文件最大 100M
SystemMaxFileSize=100M
# 日志保存时间 1 周
MaxRetentionSec=1week
# 禁止转发
ForwardToSyslog=no
ForwardToWall=no
EOF

systemctl daemon-reload
systemctl restart systemd-journald

cat > /etc/modules-load.d/10-k3s-modules.conf <<EOF
br_netfilter
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
nf_conntrack
EOF

systemctl daemon-reload
systemctl restart systemd-modules-load

配置防火墙规则

提示:8.8.8.8 为示例白名单 IP,请替换为实际 IP,搭配rc.local

  • /data/scripts/iprule.sh
#!/bin/bash
iptables -I INPUT -s 8.8.8.8 -j ACCEPT
iptables -I INPUT -p udp -j ACCEPT
iptables -I INPUT -i lo -j ACCEPT
iptables -I INPUT -i tailscale0 -j ACCEPT
iptables -I INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -I INPUT -s 10.0.0.0/8 -j ACCEPT
iptables -I INPUT -s 172.16.0.0/12 -j ACCEPT
iptables -I INPUT -s 192.168.0.0/16 -j ACCEPT
iptables -I INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
iptables -A INPUT -p icmp -j DROP
iptables -A OUTPUT -j ACCEPT
iptables -A INPUT -j DROP

防火墙规则没考虑使用 iptables-save 等保存恢复,而是每次开启时重新配置。

总结

通过以上步骤,我们完成了一套针对 k3s 环境的 Debian 系统初始化,优化了网络、资源限制和安全性。你可以直接使用以下脚本一键初始化

curl https://c.ysicing.net/oss/scripts/init.sh

Typecho根据slug添加icon

作者老孙
2025年5月5日 10:50
AI摘要:文章展示了在Typecho中通过slug匹配添加自定义icon的方法,分别针对分类和页面提供了switch-case代码示例,根据不同的slug值显示对应的图标。

使用穷举的方式来匹配自定义icon

根据分类的slug来匹配

                            <?php 
                            switch($categories->slug) {
                                case 'images': echo '<i class="bi bi-images me-1"></i>';
                            break;
                                case 'share': echo '<i class="bi bi-share-fill me-1"></i>';
                            break;
                                case 'NULL': echo '<i class="bi bi-speaker-fill me-1"></i>';
                            break;
                                case 'memos': echo '<i class="bi bi-chat me-1"></i>';
                            break;
                                case 'codes': echo '<i class="bi bi-code me-1"></i>';
                            break;
                                case 'logs': echo '<i class="bi bi-person-fill me-1"></i>';
                            break;
                                case 'test': echo '<i class="bi bi-calendar-fill me-1"></i>';
                            break;
                                case 'tools': echo '<i class="bi bi-tools me-1"></i>';
                            break;
                                case 'music': echo '<i class="bi bi-music-note me-1"></i>';
                            break;
                                case 'links': echo '<i class="bi bi-link me-1"></i>';
                            break;
                                case 'video': echo '<i class="bi bi-camera-video me-1"></i>';
                            break;
                                case 'life': echo '<i class="bi bi-heart-fill me-1"></i>';
                            break;
                                case 'study': echo '<i class="bi bi-book-fill me-1"></i>';
                            break;
                                case 'news': echo '<i class="bi bi-newspaper me-1"></i>';
                            break;
                                case 'themes': echo '<i class="bi bi-palette me-1"></i>';
                            break;
                                case 'plugins': echo '<i class="bi bi-gear-fill me-1"></i>';
                            break;
                                case 'photo': echo '<i class="bi bi-images me-1"></i>';
                            break;
                                default: echo '<i class="bi bi-folder-fill me-1"></i>';
                            } ?>

同样也可以根据自定义页面的slug匹配

<?php $pages = Typecho_Widget::widget('Widget_Contents_Page_List'); ?>
<?php while($pages->next()): ?>
<li>
    <a href="<?php $pages->permalink(); ?>">
    <?php 
    switch($pages->slug) {
        case 'about': echo '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-user" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><circle cx="12" cy="7" r="4" /><path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" /></svg> '; // 关于页面
        break;
        case 'links': echo '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5" /><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5" /></svg>'; // 链接页面
        break;
        case 'archives': echo '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-archive" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><rect x="3" y="4" width="18" height="4" rx="2" /><path d="M5 8v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-10" /><line x1="10" y1="12" x2="14" y2="12" /></svg>'; // 归档页面
        break;
        case 'gbook': echo '<svg  xmlns="http://www.w3.org/2000/svg"  class="icon icon-tabler icon-tabler-article" width="24"  height="24"  viewBox="0 0 24 24"  fill="none"  stroke="currentColor"  stroke-width="2"  stroke-linecap="round"  stroke-linejoin="round"  class="icon icon-tabler icons-tabler-outline icon-tabler-article"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M7 8h10" /><path d="M7 12h10" /><path d="M7 16h10" /></svg>'; // 博客页面
        break;
        case 'messages': echo '<svg  xmlns="http://www.w3.org/2000/svg"  class="icon icon-tabler icon-tabler-messages" width="24"  height="24"  viewBox="0 0 24 24"  fill="none"  stroke="currentColor"  stroke-width="2"  stroke-linecap="round"  stroke-linejoin="round"  class="icon icon-tabler icons-tabler-outline icon-tabler-messages"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M21 14l-3 -3h-7a1 1 0 0 1 -1 -1v-6a1 1 0 0 1 1 -1h9a1 1 0 0 1 1 1v10" /><path d="M14 15v2a1 1 0 0 1 -1 1h-7l-3 3v-10a1 1 0 0 1 1 -1h2" /></svg>'; // 留言页面
        break;
        default: echo '<svg  xmlns="http://www.w3.org/2000/svg"  class="icon icon-tabler icon-tabler-file" width="24"  height="24"  viewBox="0 0 24 24"  fill="none"  stroke="currentColor"  stroke-width="2"  stroke-linecap="round"  stroke-linejoin="round"  class="icon icon-tabler icons-tabler-outline icon-tabler-file"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M14 3v4a1 1 0 0 0 1 1h4" /><path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z" /></svg>'; // 默认图标
    } ?>
    <span><?php $pages->title(); ?></span>
    </a>
</li>
<?php endwhile; ?>

诗和远方

作者Liyun
2025年5月5日 02:18

最近头痛时不时复发,只能强制自己远离工作的紧张节奏,专注于自我的平和。

某种意义上,我其实很适合高强度的工作。压力越大,越是冷静。这可能是以前工作训练出来的,也可能是飞行执照训练出来的。当周围的人都进入狂暴模式的时候,我却一如既往的淡定,稳如泰山。我可以一个个劝住,在大家都手足无措的时候指明方向。工作是长跑,是漫长的登山,越是困难越让我冷静。

某种意义上,我其实是很适合硅谷的丛林法则的。这也可能是这么多年来我在工作上并没有遇到太多天花板,一直能找到突破的方向的缘故。不给自己设限,也便有了无数的可能。

但是,退一万步讲,这真的是我想要的生活吗?功名利禄,好像并没有我真正在乎的。钱变成了数字,让我感觉越来越陌生。我的生活质量并没有因为钱而变得更高,反而失去的时间和自由让我会不时感到窒息。最快乐的日子并不是多了多少标签,而是沉浸在自我的世界中专注。能感受到自己还活着,并非行尸走肉。

年龄越大,越觉得自己跟同龄人的割裂,一种宛如代际的割裂感。他们的高谈阔论,我的诗与远方。我试图融入,可是越融入越痛苦。索性不再假装,索性回归本心。 人生苦短,何必过多地向现实妥协。或许我太早地被经济学的功利主义洗脑,却也侥幸早早得以看清功利主义的局限。

愿我可得一世自由。

Debian 双栈网络时开启 IPv4 优先(音频版)

作者ysicing
2025年5月3日 23:32

PS: 用 AI 生成的图老是不合法微信封面的比例,放到文尾。本文也提供音频版, 欢迎订阅我的微信公众号。

在如今的网络世界,IPv6 正在逐渐普及,但 IPv4 依然是许多场景的“老大哥”。如果你用的是 Debian 系统,并且身处 IPv4 和 IPv6 共存的双栈网络环境,可能会发现系统默认优先使用 IPv6——这在某些情况下并不理想,比如某些服务只支持 IPv4,或者 IPv6 连接不稳定。今天,我们就来聊聊如何在 Debian 上实现 IPv4 优先,甚至在需要时完全禁用 IPv6。跟着这篇教程,轻松搞定网络配置!

为什么需要调整网络优先级?

先来点背景知识:双栈网络指的是设备同时支持 IPv4 和 IPv6 协议栈。现代操作系统(如 Debian)和浏览器通常默认优先使用 IPv6,只有当 IPv6 连接失败时才会“退而求其次”用 IPv4。这听起来很智能,但在实际场景中可能会遇到问题:

  • 服务兼容性:某些老旧服务或内网应用只支持 IPv4,IPv6 优先可能导致连接失败
  • 网络性能:部分网络环境下,IPv6 的延迟或稳定性不如 IPv4
  • 特殊需求:比如开发测试时,你可能希望强制使用某一种协议

所以学会调整 IPv4 和 IPv6 的优先级,或者在极端情况下禁用 IPv6,是每个 Debian 用户的“进阶技能”。下面,我们一步步教你搞定!

让 IPv4 优先:修改 gai.conf 文件

Debian 系统中,/etc/gai.conf 文件控制了 getaddrinfo 函数的行为,这个函数决定了系统如何选择 IPv4 或 IPv6 地址。默认情况下,IPv6 优先,但我们可以通过简单修改让 IPv4 站到“C 位”。

修改步骤

打开终端,输入以下命令编辑 /etc/gai.conf

#precedence ::ffff:0:0/96  100

去掉 # 号,修改为:

precedence ::ffff:0:0/96  100

保存并退出。

懒人福利:如果你不想手动编辑,可以直接用这条命令一键搞定:
bash

sed -i 's/#precedence ::ffff:0:0\/96  100/precedence ::ffff:0:0\/96  100/' /etc/gai.conf

测试效果

配置完成后,用 curl 命令测试一下:

curl ip.sb

也可以使用

# 查询本机外网IPv4地址
curl 4.ipw.cn

# 查询本机外网IPv6地址
curl 6.ipw.cn

# 测试网络是IPv4还是IPv6访问优先(访问IPv4/IPv6双栈站点,如果返回IPv6地址,则IPv6访问优先)
curl test.ipw.cn

如果返回的是类似 6.6.6.6 的 IPv4 地址,恭喜你,IPv4 优先已生效!如果返回的是类似 2001:db8::2 的 IPv6 地址,检查是否正确保存了配置。

原理

::ffff:0:0/96 是 IPv4 地址在 IPv6 协议中的映射范围,设置其优先级为 100(高于默认 IPv6 的优先级),系统就会优先选择 IPv4 地址。

特殊场景:强制 IPv6 优先

有些朋友可能有“奇特”需求,比如测试 IPv6 环境或某些服务明确要求 IPv6 优先。别担心,我们也可以反向操作!

同样编辑 /etc/gai.conf, 在文件末尾添加以下两行:

label 2002::/16    1
label 2001:0::/32   1

保存退出,或者用命令一键添加:
bash

echo -e "label 2002::/16    1\nlabel 2001:0::/32   1" | sudo tee -a /etc/gai.conf

原理

2002::/162001:0::/32 是常见的 IPv6 地址段,设置它们的 label 优先级为 1,确保系统优先选择这些 IPv6 地址。IANA 目前分配的公网 IPv6 地址还未覆盖到 3000:0000::/4,所以这招基本万无一失

(这个未测试过,仅供参考)

极端情况:完全禁用 IPv6

如果你的网络环境压根不需要 IPv6,或者 IPv6 总给你添乱,可以直接禁用它。以下是禁用 IPv6 的方法,适合“断舍离”爱好者。
编辑 /etc/sysctl.conf 文件:

net.ipv6.conf.all.disable_ipv6 = 1
# 禁用eth0的ipv6
net.ipv6.conf.eth0.disable_ipv6 = 1

结语

通过简单的配置文件调整,你就可以在 Debian 双栈网络中自由掌控 IPv4 和 IPv6 的优先级,甚至彻底禁用 IPv6。无论是提升网络兼容性、优化性能,还是满足特定需求,这些技巧都能让你事半功倍!


又是一段短暂而美好的中世纪旅程

2025年5月3日 14:07

想不到,新作来的太快,而且剧情还是接着上一部,就很开心。

总体玩下来感觉有些拖沓,没有第一部时的惊艳。

整体基调和第一部基本一致,但是玩下来就感觉,不够创新。都是一些零散的小事件,然后各种拼凑。说是体验中世纪生活,但是事件关联并不细致,感觉不如上古卷轴5那种关联。但是剧本部分也是很认真的,比如真假隐士任务,在开局的时间线上,隐士还没死,但是已经奄奄一息了,随着任务推进时间流逝,玩家到达隐居所时隐士就已经死了。

游戏里同性恋选择挺难绷的,而且左右恋爱剧情都像是硬插进去的,整体显得很割裂,反而感觉还不如不加感情线好了。另外结局也挺难绷的,还玩道德绑架,不愧是欧美游戏,这种套路就像日系游戏的反派洗白套路一样,都是必备套路,玩着人家的游戏习惯一下就好了,谁让咱自己不出这类的游戏呢。之前玩的游戏看的动画夹带私货还骂,现在想想都是一个性质。

{cat_gallery}









{/cat_gallery}

❌