作者: ianzhi

  • Dnote网站深度诊断报告:发现的问题与优化建议

    Dnote网站深度诊断报告:发现的问题与优化建议

    前言

    最近对个人博客网站 dnote.cn 进行了一次全面的结构分析和诊断,发现了一些值得关注的问题。本文将详细记录发现的问题并提供相应的优化建议。

    一、网站基础数据

    • 文章总数:100篇
    • 分类数:3个(编程95篇、投资18篇、未分类3篇)
    • 标签数:22个
    • 页面数:3个(关于、小玩意儿、搜索)
    • 媒体文件:50个

    二、发现的问题

    1. 内容分类严重失衡

    在100篇文章中,编程类占据了95篇,比例高达95%,而投资类仅有18篇。这反映出内容定位过于单一,建议:

    • 将部分技术文章重新归类,细化分类标签
    • 考虑增加新的分类维度,如”产品”、”创业”、”生活”
    • 将”未分类”的3篇文章尽快归类

    2. SEO基础优化不足

    通过分析发现以下SEO问题:

    • 缺少结构化数据:未发现JSON-LD富媒体标记,错失搜索展示机会
    • 内链锚文本单一:多为”查看更多→”,应使用关键词锚文本
    • 文章摘要较短:部分文章描述信息不足,影响搜索结果吸引力

    3. 性能优化空间

    • 未使用CDN:静态资源未通过CDN分发
    • 缺少Gzip压缩:建议开启以减少传输体积
    • 缺少浏览器缓存配置:应设置合理的缓存策略

    4. 标签使用混乱

    22个标签中,部分标签存在重复或过细的问题,如同时存在”SQL”和”MySQL”、”Docker”和”Linux”等,建议进行标签整合。

    5. 文章日期异常

    所有文章显示发布日期为2026年3月13日(部分甚至显示2025年),这可能是:

    • 批量导入时的伪日期
    • 系统时间配置问题
    • WordPress时区设置问题

    建议核查并修正,避免搜索引擎误判内容为过期内容。

    三、优化建议汇总

    优先级 问题 建议方案
    分类失衡 重新划分分类,增加3-5个新分类
    日期异常 检查WordPress时区设置,修正文章日期
    SEO元标签 添加JSON-LD结构化数据
    内链优化 使用关键词作为锚文本
    性能优化 接入CDN,配置Gzip和缓存

    四、结语

    整体而言,网站技术架构合理,WordPress运行稳定,内容质量较高。主要问题集中在内容组织结构和SEO基础优化上。解决这些问题后,网站的搜索可见性和用户体验都会有明显提升。

    后续我会逐步进行优化并记录改进效果,敬请期待。

  • 国内值得充值的VIP

    国内值得充值的VIP

    淘宝88VIP

    88元/年,性价比高(网易云+夸克+优酷)。

    网易云还好一点,夸克作为娱乐盘还行,作为工作负载,缺少的功能实在是太多了。

    优酷相对爱奇艺/腾讯视频,视频库也相对差了一些。

    但是我不是很敢深度使用,不知道为什么我总觉得阿里家用户态的东西给人缺乏持续性的感觉,总有可能被kill的风险。

    当然这句话可能有失偏颇,可能并不能说是单纯阿里的问题,其他公司可能也是存在的。

    百度网盘

    大部分时间处于175~188元/年之间。

    国内网盘很难评价,矮子里拔高个,百度网盘确实算还不错了。

    只要不打开客户端,同步和备份体验还不错,打开就一股恶心感,打算用Nas+夸克换掉,视频资源用夸克,同步用Nas。

    微信读书

    大部分时间处于158~168元/年之间。

    强烈好评。

  • 在electron中基于容器和服务提供者扩展应用核心能力

    在electron中基于容器和服务提供者扩展应用核心能力

    应用自身可能提供多种不同的能力,结合服务提供者概念和容器,我们可以实现类似插件的扩展机制,并且通过容器来统一管理服务对象,方便后续扩展。

    一、容器

    容器提供注册同步/异步工厂的方法、同步/异步获取指定服务对象的方法,移除某服务的方法和清空全部服务对象的方法。

    export class Container {
      private instances: Map<string, any> = new Map()
      private factories: Map<string, () => any> = new Map()
      private asyncFactories: Map<string, () => Promise<any>> = new Map()
      private resolvingPromises: Map<string, Promise<any>> = new Map()
      private singleton: Set<string> = new Set()
    
      // 同步注册工厂(默认为单例)
      register<T>(token: string, factory: () => T, options: { singleton?: boolean } = { singleton: true }) {
        this.factories.set(token, factory)
        if (options.singleton) {
          this.singleton.add(token)
        } else {
          this.singleton.delete(token)
        }
      }
    
      // 异步注册工厂(默认为单例)
      registerAsync<T>(token: string, factory: () => Promise<T>, options: { singleton?: boolean } = { singleton: true }) {
        this.asyncFactories.set(token, factory)
        if (options.singleton) {
          this.singleton.add(token)
        } else {
          this.singleton.delete(token)
        }
      }
    
      // 同步解析
      resolve<T>(token: string): T {
        // 如果是单例且已存在实例,直接返回
        if (this.singleton.has(token) && this.instances.has(token)) {
          return this.instances.get(token)
        }
    
        // 查找工厂函数
        const factory = this.factories.get(token)
        if (!factory) {
          // 如果没有工厂但有实例,可能是异步实例已完成
          if (this.instances.has(token)) {
            return this.instances.get(token)
          }
          throw new Error(`Service not found: ${token}`)
        }
    
        // 创建实例
        const instance = factory()
    
        // 如果是单例,保存实例
        if (this.singleton.has(token)) {
          this.instances.set(token, instance)
        }
    
        return instance
      }
    
      // 异步解析
      async resolveAsync<T>(token: string): Promise<T> {
        // 如果是单例且已存在实例,直接返回
        if (this.singleton.has(token) && this.instances.has(token)) {
          return this.instances.get(token)
        }
    
        // 检查是否已经有正在解析的Promise
        let resolvingPromise = this.resolvingPromises.get(token)
        if (resolvingPromise) {
          // 如果有,直接返回这个Promise的结果
          return resolvingPromise
        }
    
        // 先检查是否有异步工厂
        const asyncFactory = this.asyncFactories.get(token)
        if (asyncFactory) {
          // 创建一个新的解析Promise并存储
          resolvingPromise = (async () => {
            try {
              const resolved = await asyncFactory()
              // 如果是单例,保存实例
              if (this.singleton.has(token)) {
                this.instances.set(token, resolved)
              }
              return resolved
            } finally {
              // 解析完成后从resolvingPromises中移除
              this.resolvingPromises.delete(token)
            }
          })()
    
          // 如果是单例,存储Promise以避免重复创建
          if (this.singleton.has(token)) {
            this.resolvingPromises.set(token, resolvingPromise)
          }
          return resolvingPromise
        }
    
        // 再检查是否有同步工厂
        const factory = this.factories.get(token)
        if (factory) {
          try {
            const resolved = factory()
            // 如果是单例,保存实例
            if (this.singleton.has(token)) {
              this.instances.set(token, resolved)
            }
            return resolved
          } catch (error) {
            if (error instanceof Error) {
                throw new Error(`Failed to resolve service '${token}': ${error.message}`)
            } else {
                throw error
            }
          }
        }
    
        // 最后检查是否已有实例(可能是之前异步解析完成的)
        if (this.instances.has(token)) {
          return this.instances.get(token)
        }
    
        throw new Error(`Service not found: ${token}`)
      }
    
      // 检查是否存在指定token的服务
      has(token: string): boolean {
        return this.instances.has(token) ||
               this.factories.has(token) ||
               this.asyncFactories.has(token)
      }
    
      // 清除指定服务的实例(不移除工厂)
      clearInstance(token: string): void {
        this.instances.delete(token)
        this.resolvingPromises.delete(token)
      }
    
      // 移除服务(包括工厂和实例)
      remove(token: string): void {
        this.instances.delete(token)
        this.factories.delete(token)
        this.asyncFactories.delete(token)
        this.resolvingPromises.delete(token)
        this.singleton.delete(token)
      }
    }
    

    二、服务提供者

    服务提供者接口比较简单,区分register和boot主要是为了确保boot中可以调用任何其他提供者。

    import type { Container } from "../container"
    
    export default interface ServiceProvider {
        /**
         * 应用启动后立即调用
         * 建议仅用于注册服务,不要执行其他操作,其他操作可以放在boot方法中
         *
         * @param container
         */
        register?(container: Container): void
    
        /**
         * boot函数在所有服务注册完成后调用
         * 可以执行一些初始化操作
         *
         * @param container
         */
        boot?(container: Container): void | Promise<void>
    }
    

    三、应用对象

    import { Container } from './container'
    
    export default class APP {
    
        /**
         * 应用实例,用来保持单例
         */
        private static _ins: APP
    
        /**
         * 容器对象
         */
        private container: Container
    
        /**
         * 服务提供者列表
         */
        private providers: ServiceProvider[] = []
    
        /**
         * 运行应用
         */
        static run() {
            if (APP._ins == null) {
                APP._ins = new APP()
            }
    
            return APP._ins
        }
    
        constructor() {
            // 初始化容器并注册服务提供者
            this.container = new Container()
    
            // 注册服务
            this.registerProviders()
    
            // 启动服务
            this.bootProviders()
        }
    
        registerProviders() {
            for (const provider of this.providers) {
                provider.register?.(this.container)
            }
        }
    
        bootProviders() {
            return Promise.all(
                this.providers.map(provider => provider.boot?.(this.container))
            )
        }
    }
    
  • Caddy配置sts(Strict-Transport-Security)

    Caddy配置sts(Strict-Transport-Security)

    example.com {
        # 启用 HSTS,有效期 1 年(31536000 秒)
        header {
            Strict-Transport-Security max-age=31536000
        }
    
        # 其他配置...
        reverse_proxy localhost:8080
    }
    
  • 创建strapi插件

    创建strapi插件

    需要开发一个插件来实现一些自定义功能,官方文档写的实在是难以理解了。

    1. 创建基础的目录结构

    # 作为项目根目录
    mkdir xxx
    
    # 进入项目目录
    cd xxx
    
    # 创建strapi 项目
    npx create-strapi@latest strapi
    
    # 创建插件
    npx @strapi/sdk-plugin init strapi-plugin
    

    2. 配置工作区

     # xxx目录中添加workspace配置
    {
         "workspaces": [
             "strapi",
             "strapi-plugin"
         ],
         "dependencies": [
             "strapi-plugin": "workspace:*"
         ]
     }
    

    3. strapi目录引入依赖

     {
         ...
         "dependencies": {
             ...
             "strapi-plugin": "0.0.0"
         }
     }
    

    4. 插件目录监听文件修改

    # 根目录执行
    npm run watch --workspace=strapi-plugin
    

    5. 运行strapi

    npm run develop --workspace=strapi
    
  • 未来20年最具颠覆性的科技变革

    未来20年最具颠覆性的科技变革

    1. 通用量子计算机
    2. 人工智能加量子计算
    3. 受控核聚变
    4. 材料突破,为能源技术,信息技术和生物医学提供坚实基础

    本文不构成投资建议,投资有风险,入市需谨慎。

  • 李想谈大佬的共性

    李想谈大佬的共性

    1. 选的长,做长期的事,做难的事,做时间的朋友
    2. 选的准,选择比努力重要
    3. 在很长,很成功的赛道里,高速的迭代

    本文不构成投资建议,投资有风险,入市需谨慎。

  • Parse Platform任务调度

    Parse Platform任务调度

    说起来也挺奇怪的,Parse Platform本身基于js实现,Node生态里的任务调度实现起来并不麻烦,Parse却选择了不集成,使用第三方提供的任务调度。这里使用node-cron来实现Parse Server中的Job调度。

    一、Cloud Code定义Job

    Parse.Cloud.job("test", async (request) =>  {
        // params: passed in the job call
        // headers: from the request that triggered the job
        // log: the ParseServer logger passed in the request
        // message: a function to update the status message of the job object
        const { message } = request
        message('done')
    })
    

    二、定义一个调用Job的函数,方便后面使用

    async function callParseJob(parseServerUrl: string, name: string, data?: any) {
      console.log(`node-cron: callParseJob ${name}`)
      const options: RequestInit = {
        method: 'POST',
        headers: {
          'X-Parse-Application-Id': process.env.APP_ID ?? '',
          'X-Parse-Master-Key': process.env.MASTER_KEY ?? ''
        }
      }
      if (data != null) {
        options.body = JSON.stringify(data)
      }
      const res = await fetch(`${parseServerUrl}/jobs/${name}`, options)
      console.log(`node-cron: job ${name} finished, ${res.statusText}`)
      return await res.json()
    }
    

    三、使用node-cron调度Job

    这里选择通过单独的进程来运行调度程序

    if (cluster.isPrimary) {
      cron.schedule('* * * * *', async () => await callParseJob(process.env.SERVER_URL ?? '', 'test'))
      cluster.fork() // 根据cpu个数fork子进程
    
      cluster.on('exit', function(worker, code, signal) {
        console.log('worker ' + worker.process.pid + ' exited')
        cluster.fork() //新建一个worker
      })
    } else {
      const app = express()
        //...启动Server相关的代码,这里省略
    }
    
  • 为什么mongoDB在国内发展的没redis好?

    为什么mongoDB在国内发展的没redis好?

    mongo用户应该集中在前端转全栈,通过Node操作mongodb,可以保持之前对于JSON的理解,不用接触传统数据库的知识。

    国内用Node作为后端的用户就不多,Serverless发展也一般,搞Node全栈的集中在独立开发者或者大型公司的某个团队,这个体量确实也不是很大。

    如果是纯后端,我很难理解引入Mongo的意义,完全可以把Postgres的表搞成id+json的形式来模拟Mongo无架构,甚至微软搞了一个DocumentDB,兼容mongo协议,就是基于PostgreSQL的。从扩展性,功能,性能任何方面看,这样用pg都是优于采用MongoDB的。

    DocumentDB:开源公告 – Microsoft 开源博客

    Redis不一样,

    1. 大部分应用考虑扩展和性能的情况下,都需要一个缓存中间件,这个首先mongo就不适合
    2. 简单消息队列
    3. 发布订阅
    4. 不提供内置有序集合的语言,有序集合也省的自己写了
    5. 高频读写的小数据也可以开持久化作为主数据库

     

    https://www.zhihu.com/question/478485839/answer/1965357311661965488

  • 阿里云OSS与Cloudflare R2价格对比

    阿里云OSS与Cloudflare R2价格对比

    场景:存储位置美西硅谷,存储量200GB,时间一年,写操作3万/月,读操作30万/月,每月传输200GB流量。

    1. 阿里云

    1. 存储费用:100GB资源包+100GB按量 = 162 + 0.136 元/GB/月 * 100 * 12 = 162 + 163.2 = 325.2(每月免费5GB,单价合¥0.136 元/GB/月
    2. 写操作:0(地域500万次/月免费,超出0.1/万次)
    3. 读操作:0(地域2000万次/月免费,超出0.1/万次)
    4. 流量费用:200 * 0.5 * 12 = 1200
    5. 合计:1525.2

    2. Cloudflare

    1. 存储费用:(200 – 10) * 0.015 * 12 = $34.2(每月免费10GB,合¥0.109/GB/月)
    2. 写操作:0(地域100万次/月免费,超出$4.5/百万次,合0.33/万次)
    3. 读操作:0(地域1000万次/月免费,超出0.36/百万次, 合0.026/万次)
    4. 流量费用:0
    5. 合计折合人民币7.13 * 34.2 = 243.846

    3. 腾讯云

    1. 存储费用:212.16(0.089 元/GB/月
    2. 读写操作:0.01/月/万次 * (30 + 3)万次 * 12 = 3.96
    3. 流量费用:200GB/月资源包 = 1029
    4. 合计: 1245.12

    境外走Cloudflare的CDN的话,三方都没有流量费,伴随使用量增加,估计最终Cloudflare整体的价格相对会更低一些,但是需要增加到一定的用量以后。

  • 优化WordPress后台加载速度

    优化WordPress后台加载速度

    1. PHP预加载脚本

    <?php
    
    /**
    *使用OPcache优化WordPress的预加载脚本。
    *将此文件放在WordPress安装的根目录中。
     */
    
    // Define the base path for WordPress
    define('WP_ROOT_DIR', __DIR__);
    
    // Define ABSPATH (required by WordPress core files)
    if ( ! defined( 'ABSPATH' ) ) {
        define( 'ABSPATH', WP_ROOT_DIR . '/' );
    }
    
    // Define WPINC (required by wp-includes/functions.php)
    if ( ! defined( 'WPINC' ) ) {
        define( 'WPINC', 'wp-includes' );
    }
    
    // Define WP_DEBUG (required by wp-includes/functions.php)
    if ( ! defined( 'WP_DEBUG' ) ) {
        define( 'WP_DEBUG', false );
    }
    
    // 仅预加载最基本的核心文件
    require WP_ROOT_DIR . '/wp-includes/default-constants.php';
    require WP_ROOT_DIR . '/wp-includes/rewrite.php';
    require WP_ROOT_DIR . '/wp-includes/theme.php';
    require WP_ROOT_DIR . '/wp-includes/post.php';
    require WP_ROOT_DIR . '/wp-includes/meta.php';
    require WP_ROOT_DIR . '/wp-includes/user.php';
    require WP_ROOT_DIR . '/wp-includes/cache.php';
    require WP_ROOT_DIR . '/wp-includes/capabilities.php';
    require WP_ROOT_DIR . '/wp-includes/shortcodes.php';
    require WP_ROOT_DIR . '/wp-includes/class-wp-query.php';
    require WP_ROOT_DIR . '/wp-includes/class-wp-widget.php';
    require WP_ROOT_DIR . '/wp-includes/class-wp-roles.php';
    require WP_ROOT_DIR . '/wp-includes/class-wp-user.php';
    require WP_ROOT_DIR . '/wp-includes/class-wp-post.php';
    
    // 后台管理模块的类
    require WP_ROOT_DIR . '/wp-admin/includes/class-wp-list-table.php';
    require WP_ROOT_DIR . '/wp-admin/includes/class-wp-media-list-table.php';
    require WP_ROOT_DIR . '/wp-admin/includes/class-wp-users-list-table.php';
    require WP_ROOT_DIR . '/wp-admin/includes/class-wp-themes-list-table.php';
    
    // Preload database-related files
    require WP_ROOT_DIR . '/wp-includes/wp-db.php';
    // require WP_ROOT_DIR . '/wp-includes/class-wpdb.php'; // 如果不需要,可以注释
    
  • Go语言获取指定年份生肖

    Go语言获取指定年份生肖

    根据给定年份,返回生肖字符串,公元前使用负值即可。(比如2022年,调用使用GetShengXiao(2022),公元前21年,调用使用GetShengXiao(-21))。
    
    
    // 获取生肖索引
    func GetShengXiaoIndex(year int) int {
        // 不存在0年
        if year == 0 {
            panic("err: invalid year")
        }
    
        // 公元前补1
        if year < 0 {
            year += 1
        }
    
        idx := (year - 4) % 12
    
        if idx < 0 {
            idx += 12
        }
    
        return idx
    }
    
    // 根据给定年份获取生肖
    func GetShengXiao(idx int) string {
        return [12]string{"鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪"}[idx]
    }
    
  • WordPress添加关键词和描述标签

    WordPress添加关键词和描述标签

    关键词和描述标签作为SEO的基础配置,在wordpress中没有默认添加,这里记录一下在Wordpress中自动添加关键词和描述标签的方法。

    一、实现代码

    实现思路是使用标签来作为关键词,使用文章摘要作为页面描述,找到使用主题的functions.php文件,添加以下代码即可实现。

    /**
     * 添加SEO相关的Keywords和Description标签
     */
    function add_seo_meta_tags() {
        if (is_home()) {
            $tags = get_tags([
                'number' => 15,
                'orderby' => 'count',
                'order' => 'DESC'
            ]);
            $tags = array_map(function ($item) {
                return $item->name;
            }, $tags);
            if (count($tags) > 0) {
                ?>
                <meta name="keywords" content="<?php echo get_bloginfo('name'); ?>,<?php echo implode(',', $tags); ?>">
                <?php
            }
            ?>
                <meta name="description" content="<?php echo get_bloginfo('description'); ?>">
            <?php
        } else if (is_category() || is_tag()) {
            ?>
                <meta name="keywords" content="<?php echo single_cat_title(); ?>">
                <meta name="description" content="<?php echo strip_tags(category_description()); ?>">
            <?php
        } else if (is_singular()) {
            ?>
                <meta name="description" content="<?php echo strip_tags(get_the_excerpt()); ?>">
            <?php
    
            $tags = array_map(function ($item) {
                return $item->name;
            }, get_the_tags() ?: []);
            if (count($tags) > 0) {
                ?>
                    <meta name="keywords" content="<?php echo implode(',', $tags); ?>">
                <?php
            }
        }
    }
    add_action( 'wp_head', 'add_seo_meta_tags' );
    

    二、使用的前置要求

    请注意,需要保证主题的header.php调用过wp_head()函数,类似下面:

    <!DOCTYPE html>
    <html <?php language_attributes(); ?> class="no-js">
    <head>
    <meta charset="<?php bloginfo( 'charset' ); ?>">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    
    <?php wp_head(); ?>
    </head>
    

    OK,这样我们就给所有页面都添加上关键词和描述标签了~

    相关链接:

    1. wordpress关于meta标签的说明
  • 桥水创始人达利欧给中国投资者的超实用投资建议

    桥水创始人达利欧给中国投资者的超实用投资建议

    近些年美股有两次大的崩盘,绝大部分策略都陷入绝境:

    88b73dd093a998b024ad66d82b29fd4a.png

    但总有一个男人,他的净值曲线走得异常平稳。

    2d814e7f49ffe9407019a22b65f2db3f.png

    他就是全球最大对冲基金——桥水基金创始人、以其代表作“全天候策略”闻名于世的达利欧

    1b34191b61aaeb29a4a0ff78c38d7135.png

    前不久,达利欧在一次分享中,首次专门为中国投资者提出了多个特别好的投资建议。

    选了三个我觉得很有价值的,翻译成白话,给大家分享下:

    01

    如何在低利率下有跑赢通胀的不错回报?

    fc20d305bfd28b84fe9e03fb95f3e065.png

    为什么这么说?因为存在通胀。

    过去30年,我国年平均通货膨胀率约2.5%,也就是同样的钱能购买到的东西平均每年会减少2.5%。

    1978667d1a25b1bd17e5e0f2e6011967.png

    同时通胀并非均匀分布,有的商品会涨价更多。

    像食品、房屋租金等上涨得更明显,如果你的支出集中在吃和租房上,那就会付出更高的成本。

    而不论我们是把现金放在家里,还是存在银行里,都是无法跑赢通胀的。

    0ae37d2af61e374ff844a9970a66d670.png

    所以按达利欧的观点,在如此低利率的环境下,把钱存着是不够的,我们必须构建一个风险可控,能持续跑赢通胀的投资组合

    e0b0809d8edc72a1c694f1fd6ba7abff.png

    02

    什么投资组合能实现上面的目标****

    fc284641a46a23c25cfec37f96c57078.png

    多样化资产,又分成两个维度:资产类别多样,资产地域多样。

    ① 资产类别要多元

    达利欧提到,要拥有10个或更多的没有关联性的资产,即这些资产间的收益归因没有直接关联。

    d3347481ed1a2660e128b3cae9265358.png

    他举了个例子,如果我们有1个资产,然后引入了2个没有相关性但预期回报相似的资产,那投资风险可降低1/3。

    如果能有10-15个资产,那风险就可降低60%-80%,即我们可以获得相同的预期回报,但承担的风险仅为原来的20%。

    8fd740e4d55aaabb0ae77b697d31d1cf.png

    所以回到中国投资人特别关心的黄金,即使黄金今年已经涨了50%,从资产配置的角度,达利欧依然建议我们配置合理的比例。

    c743bf554e24e653f496a43e6fca55c7.png

    因为黄金作为“硬通货”,不依靠国家信用发行,且储量有限,无法像货币一样被无限印刷。在通胀严重,钱不值钱的时候,往往表现突出。这是其他资产无法比拟的优势。

    所以达利欧认为,配置黄金不是为了博取高收益,而是将其作为组合的稳定器。

    b851a881cfdd6977816c69eb0dbc90ef.png

    ② 地域要分散

    45f434972981e362d510ef2aaa4ec372.png

    意思是既要配置国内资产,也要配置海外资产,避免将未来押注在单一国家的经济命运上。

    11374346f2f02a4b3964643af018d6cb.png

    因为不同国家所处的经济环境常常不同。

    2022年美股因为美联储激进加息而大跌,如果当时我们只持有美股,可能会经历较大波动;但如果我们同时配置了日股,就能起到很好的对冲效果。

    632f5cf9cc1cff76c496c86cdbb000c1.png

    综上,不论是资产还是地域都要分散,即在达利欧的观点中,多资产的策略才能在风险可控的情况下,让你不操心地长期跑赢通胀。

    03****

    买入****和持有也大有学问

    只靠多资产策略也并非万事大吉,还有两个重要的操作问题。

    一是什么时候买,择时是个令人纠结的事,涨了怕追高,跌了怕还要跌。

    495a33fdbbceba22da7f45c54ea19a97.png

    零和游戏,简单理解就是所有参与者利益完全对立,大家总收益和总损失完全相抵。

    好比一桌麻将,即便是4位股神对战,最终所有赢的钱加起来也还是0。

    35039467fc0ff844afb91f6e6b498c54.png

    择时是短期博弈,就像大家一起分一个蛋糕,你大我就小。

    而长期投资,赚的是和企业共同成长的钱,蛋糕越来越大,每个人能分得就更多,是正和游戏。

    所以达利欧认为,更明智的做法是承认无法预测市场,放弃则时。

    9886a381d9d90a0c18b82e661a8d1769.png

    第二个是怎么科学持有,这就引出一个概念:定期再平衡。

    简单理解就是,当你组合中资产比例偏离到一定程度后,要卖掉占比增长的资产,买入占比下降的资产,恢复到目标比例。这本质是强制性的“高卖低买”。

    948273e176a1bd38d18aa3d3015b66ba.png

    我们都知道,某种资产涨得越多,风险也就越高。如果不做再平衡,组合可能会随强势资产上涨而风险集中,一旦该资产回调,将面临巨大回撤。

    1a7940bee017d32f4c8a42a36bf83390.png

    这条原则看着非常简单,却极度考验投资者的纪律性。

    假设你现在有个组合。以今年的行情,A股和黄金都涨得不错,导致组合比例发生明显偏离。可能大部分人还会继续持有,赌它们还会涨。但其实此时需要逆人性操作,适当减持并买债,将比例再调回去。

    304e43093115fd1a1de33e1455cf8014.png

    以上就是达利欧给咱们中国投资者的建议了。

    事实上,雪球三分法工具与达利欧的建议极为契合,不仅通过资产、市场来实现投资的标的的分散,还能通过定投,有效降低短期事件的冲击,获得更好的投资体验。

    此外,我们还会提供每年12次的月报诊断服务,帮您更好管理,还有专属的社群服务,陪您一起做好适合自己的配置。

    53206b20f0d9793b571f88bff5fed6df.png

    最后总结下,想在低利率环境下,不操心地跑赢通胀,那就要买多资产策略;别择时,但是需要定期进行再平衡。

    本文不构成投资建议,投资有风险,入市需谨慎。

  • 使用SSH转发服务器端口到本地

    使用SSH转发服务器端口到本地

    注意远程地址、远程端口号、本地地址、本地端口号需要按照实际情况修改。

    function forwardPort() {
        PROC_NAME="ssh -fR $1"
        ProcNumber=`ps -ef |grep -w "$PROC_NAME"|grep -v grep|wc -l`
        if [ $ProcNumber -le 0 ];then
            echo "$1 is not forward.."
            ssh -fCNR $1:localhost:$1 root@0.0.0.0 -p 1234 -o ServerAliveInterval=30
        else
            echo "$1 has forwarded.."
        fi
    }
    for port in 1234 4567
    do
        forwardPort $port
    done
    

    注意,如果需要外网访问转发的端口,需要在远程服务器的配置文件(/etc/ssh/sshd_config)中添加:

    AllowTcpForwarding yes
    
  • 使用Joplin作为博客后端-使用marked转换joplin笔记中资源文件的地址

    使用Joplin作为博客后端-使用marked转换joplin笔记中资源文件的地址

    joplin笔记中资源文件默认使用的是id,但是如果渲染到网页,需要转换为网络地址,记录一下处理过程。

    export async function transformMarkdown(markdown?: string) {
        if (markdown == null || markdown === '') {
            return ''
        }
    
        const renderer = new marked.Renderer()
    
        marked.use({
            async: true,
            async walkTokens(token) {
                if (token.type === "image") {
                    const id = token.href.replace(':/', '')
                    const resource = await joplinResource(id)
                    token.href = `${process.env.SITE_URL}/resources/${id}.${resource.file_extension}`
                    token.title = resource.title
                }
            },
            renderer: {
                image({ href, text, title }) {
                    return `<img src="${href}" alt="${text}" title="${title}" loading="lazy" />`
                }
            }
        })
    
        return await marked.parse(markdown, {
            renderer,
            gfm: true,
            breaks: false,
            pedantic: false
        })
    }
    
  • 使用Joplin作为博客后端-基于Joplin Terminal部署服务端REST API

    使用Joplin作为博客后端-基于Joplin Terminal部署服务端REST API

    Joplin Server本身虽然有接口,但是并不能直接获取笔记数据,了解后发现Joplin Terminal是支持Data API的,这样的话,可以通过在服务器部署一套Joplin Terminal程序来从服务器提供笔记数据,唯一的问题可能就是会导致服务器存储两份笔记数据,不过也不是很大的问题,这里以通过Docker容器部署为例。

    一、安装Joplin Terminal

    # 创建数据存储文件夹
    mkdir -p joplin/cli
    
    cd joplin/cli
    
    npm i joplin
    
    # 同步配置参考官方文档 https://joplinapp.org/help/apps/terminal/
    
    # 同步数据
    npx joplin sync
    

    二、启动Joplin的Data API Server

    修改项目的package.json,增加一个启动脚本。

    {
      "type": "module",
      "scripts": {
        "joplin": "joplin server start",
        ...
      },
      "dependencies": {
        "joplin": "^3.4.1"
      }
    }
    
    npm run joplin
    

    三、docker compose配置

    services:
        ...
        joplin-api:
            image: node:22.12.0
            restart: unless-stopped
            # 这个应该根据实际的uid和gid设置,与挂载目录的/home/test有关
            user: 1000:1000
            volumes:
                # 挂载这个文件主要是为了保持权限一致,避免本地修改容器不能运行
                - /etc/passwd:/etc/passwd:ro
                # 挂载项目代码
                - ./joplin/cli:/app
                # joplin数据保存位置
                - ./joplin/data:/home/test/.config/joplin
                - ./joplin/.npm:/home/test/.npm/
            # 这个start命令参考下面
            command: ["sh", "-c", "chdir /app && npm start"]
    

    四、转发请求,配置定时任务

    joplin terminal启动的data api默认是写死的绑定127.0.0.1,需要本地启动一个反向代理服务器来做一下请求转发,同时也配置一下定时任务。
    我这里服务器用的h3,用什么其实都可以,感觉JS的Web框架语法都差不太多,这里选h3主要是为了资源占用少。

    import { H3 } from "h3"
    import { createServer } from 'node:http'
    import { toNodeHandler } from "h3/node"
    import cron from 'node-cron'
    import { exec } from "node:child_process"
    
    /**
     * 运行node-cron定时任务
     * /30 * * * /path/to/joplin sync
     */
    cron.schedule('*/15 * * * *', joplinSync)
    
    /**
     * 同步数据
     */
    export async function joplinSync() {
      exec('/app/node_modules/.bin/joplin sync', (_, stdout) => {
        console.log('joplin sync: ', stdout)
      })
    }
    
    export const app = new H3()
    
    /**
     * 反向代理外界请求到本机的41184端口
     * joplin Data API Server默认监听41184,因为Docker环境,也不太可能出现冲突的问题
     */
    app.use(
      async (event) => {
        const { url } = event.req
    
        const proxyUrl = new URL(url)
        proxyUrl.protocol = 'http'
        proxyUrl.host = 'localhost:41184'
        const proxy = await fetch(proxyUrl.toString())
        return proxy.body
      }
    )
    
    createServer(toNodeHandler(app)).listen(3000, '0.0.0.0')
    

    五、完善启动脚本

    {
      "type": "module",
      "scripts": {
        "proxy": "node --experimental-strip-types index.ts",
        "joplin": "joplin server start",
        "start": "npm run proxy & npm run joplin"
      },
      "dependencies": {
        "h3": "^2.0.1-rc.2",
        "joplin": "^3.4.1",
        "node-cron": "^4.2.1"
      },
      "devDependencies": {
        "@types/node": "^24.7.1"
      }
    }
    

    六、后续

    后面其实就简单了,感觉直接开放到公共网络不是很安全,最好还是通过容器间通信使用,真正的权限鉴定放在h3这一层。

  • Shell遍历文件夹下所有文件,并将文件内容写入一个文件中

    Shell遍历文件夹下所有文件,并将文件内容写入一个文件中

    软件著作权要求提供代码文档,这里提供使用Shell遍历文件夹下所有文件,并将文件内容写入一个文件中的方法。

    #!/bin/bash
    dir="."
    target="./target.txt"
    # 过滤指定文件或文件夹
    filter=(node_modules out dist $target)
    
    listfile() {
        filelist=`ls $1`
        for file in $filelist
        do
            if [[ "${filter[@]}" =~ "$file" ]];then
                continue
            fi
    
            if [ -d $1/$file ];then
                listfile $1/$file
            else
                cat $1/$file >> $target
            fi
        done
    }
    
    listfile $dir
    
  • 波特五力模型

    波特五力模型

    mindmap
        id(五力)
            id(供应商的讨价还价能力)
            id(潜在竞争者进入的能力)
            id(行业内竞争者现在的竞争能力)
            id(替代品的替代能力)
            id(购买者的讨价还价能力)
    

    本文不构成投资建议,投资有风险,入市需谨慎。

  • MySQL常用SQL语句

    MySQL常用SQL语句

    记录一些常用的MySQL语句,方便查找翻阅。

    1. 查看MySQL数据库磁盘占用大小

    select 
    TABLE_SCHEMA as '数据库', 
    concat(truncate(sum(data_length)/1024/1024,2),'MB') as '数据容量(MB)',
    concat(truncate(sum(index_length)/1024/1024,2),'MB') as '索引容量(MB)'
    from information_schema.tables
    group by TABLE_SCHEMA
    ORDER BY data_size desc
    

    2. 查看MySQL数据库中表的磁盘占用

    select  
    table_schema as '数据库',  
    table_name as '表名',  
    table_rows as '记录数',  
    truncate(data_length/1024/1024, 2) as '数据容量(MB)',  
    truncate(index_length/1024/1024, 2) as '索引容量(MB)'  
    from information_schema.tables  
    where table_schema='bwc_plsedu_com'
    order by data_length desc, index_length desc;
    

    3. MySQL创建、删除用户,授权、撤销授权

    create user user@host identified by 'password';
    # 授权
    grant all privileges on db.table to user@host;
    # 取消授权
    revoke all privileges on test.* from 'user'@'host';
    #删除用户
    drop user 'test'@'127.0.0.1';
    # 刷新权限使授权生效
    flush privileges;
    

    4. 性能检查

    -- 查看当前连接
    SHOW PROCESSLIST;