阅读视图

发现新文章,点击刷新页面。

我对 Vue Router「动态路由」的一点暴论

本文是对我之前在 X (Twitter) 平台发表的 推文 的整合。

每当我接触到一些既有的使用 Vue 开发的业务系统时,总是能看到一种被称为「动态路由」的实现方式。

如果你只知道 React Router 或 Next.js 的「动态路由」,嗯,首先我们说的不是一个东西。Vue Router 这边的「动态路由」指的是:服务端存储一份完整的 Vue Router 所需数据结构,前端在初次加载页面时必须先从服务端获取这个结构,然后遍历结构并执行 router.addRoute() 才能继续工作。根据用户角色不同,返回的路由列表也会有所差异。

对于这种实现方式,我一直难以理解将完整路由数据结构放在服务端维护的必要性,尤其是对于那些操作 JSON 不太方便的后端编程语言来说(说的就是你,Java)。当我向早期参与这些项目开发的人询问原因时,他们往往只能给出这两点理由:

  • 需要根据用户角色展示其可访问的页面
  • 可以根据领导要求临时开关某些功能

然而,我觉得第一点理由似乎站不住脚。前端完全可以通过简单地获取用户角色权限字符串来实现相同功能(甚至他们使用的框架,比如若依,就已经内置了这种功能)。同时,这种方式也无法真正阻止「高级用户」了解系统中所有页面的存在,因为这些业务页面通常需要视情况使用 await import()、Vite 的 import.meta.glob、Webpack (Vue CLI) 的 require.context 或其它打包器的类似 API 进行批量导入,查看源代码进行简单查找就能发现所有页面和它们的文件名。

至于第二点关于能够随时关闭某个功能(页面)的需求,也找不到必须由后端传输整个路由表数据结构的合理理由。在我们从零开发的一套业务系统中,采用的方案是:后端维护一个简化的树形结构,仅记录每个路由的技术名称、所需权限和启用状态,而前端则在第一次加载页面时获取该结构,将其转换为 Map,之后由路由守卫来检查 Map 中的项目,判断页面是否可以访问。

以我浅薄的经历来看,「动态路由」真的是一种毫无优点、但是缺点却一大堆的方案。这种做法往往会导致各种混乱的代码出现,成为开发维护的噩梦(前司一些前端调试「动态路由」就要浪费两天时间)。当然,这些问题或许还有一些技术方面外的考量吧,这里我还是不要过多评价了。

不用 eas-cli 编译 React Native (Expo) 应用的 Android 版本

为什么不用 eas-cli

  • 它需要你注册 Expo 帐号,并且建立一个新的 Project 在上面
  • 它每次运行都要连接 Expo 的云服务
  • 它不让你在 Windows 上跑。气抖冷!!

(当然如果以上方面对你不是问题的话,eas 其实挺适合懒人在自己的电脑/基础设施上跑的,特别是你不在内地的电脑上进行开发时)

需求

  • 配好 JDK 和 Android SDK,并且指定好它们的 PATHJAVA_HOMEANDROID_HOME详见
  • 项目启用 Prebuild

配置 Keystore

为了让编译出来的应用使用我们自己的 Keystore,我们需要修改 android/app/build.gradle 文件,在 signingConfigs 加入自己的 Keystore 信息,并且在 buildTypesrelease 中设置 signingConfig signingConfigs.release

由于我们的项目使用 Expo Managed Workflow,为了确保 Native 部分不会出现不同步的问题,这里通过编写项目级插件的方式实现修改:

// plugins/withAndroidSignature.js

const { withAppBuildGradle } = require("@expo/config-plugins");
const fs = require("fs");
const path = require("path");

module.exports = function withAndroidSignature(config) {
    return withAppBuildGradle(config, config => {
        if (config.modResults.language === "groovy") {
            config.modResults.contents = setAndroidSignature(config.modResults.contents);
        } else {
            throw new Error("如果不是 groovy,则无法在 app/build.gradle 中设置 signingConfigs");
        }
        return config;
    });
};

function setAndroidSignature(appBuildGradle) {
    if (!fs.existsSync(path.resolve(__dirname, "../credentials.json"))) {
        console.warn("警告:没有设置正式版本的 Android Keystore 文件,因为 credentials.json 不存在。");
        return appBuildGradle;
    }
    const info = JSON.parse(fs.readFileSync(path.resolve(__dirname, "../credentials.json"), { encoding: "utf8" }));

    // 使用正则表达式插入签名信息
    let output = appBuildGradle.replace(
        /(signingConfigs\s*\{)/,
        `$1
        release {
            storeFile file(${JSON.stringify(path.resolve(__dirname, "../credentials/android-release.keystore"))})
            storePassword ${JSON.stringify(info.android.keystore.keystorePassword)}
            keyAlias ${JSON.stringify(info.android.keystore.keyAlias)}
            keyPassword ${JSON.stringify(info.android.keystore.keyPassword)}
        }`,
    );

    // 使用正则表达式替换 signingConfig
    output = output.replace(
        /(release\s*\{)[^}]*?signingConfig\s+signingConfigs\.debug/s,
        `$1
            signingConfig signingConfigs.release
`,
    );

    return output;
}

为了方便维护,我们将 Keystore 的本体放在项目根目录下 credentials/android-release.keystore ,而 Keystore 的信息放在 credentials.json 里。

(这里的 credentials.json 文件格式与 Expo 文档中的 Use local credentials 一致)

{  
  "android": {  
    "keystore": {  
      "keystorePath": "credentials/android-release.keystore",  
      "keystorePassword": "your keystore password",  
      "keyAlias": "your key alias",  
      "keyPassword": "your key password"  
    }  
  },  
  "ios": {  
    "provisioningProfilePath": "ios/certs/profile.mobileprovision",  
    "distributionCertificate": {  
      "path": "ios/certs/dist-cert.p12",  
      "password": "password"  
    }  
  }  
}

然后,在 app.json 中指定这个项目级插件:

{  
  "expo": {  
    "plugins": [  
      "./plugins/withAndroidSignature"
    ]
  }  
}

编译

首先,我们需要执行一次 npx expo prebuild,让它生成 android 文件夹。这个文件夹包含了我们项目的 Native 部分。

打包给 Google Play 商店用的 .aab 文件:

cd android
./gradlew bundleRelease

你的编译产物会出现在项目根目录下 android/app/build/outputs/bundle/release/app-release.aab

而如果是给别人直接安装的 .apk 文件:

cd android
./gradlew assembleRelease

你的编译产物会出现在项目根目录下 android/app/build/outputs/apk/release/app-release.apk

如果在 Windows 下编译,需要把 ./gradlew 改成 ./gradlew.bat

还有一些东西……

为了简化上述的操作,我推荐在 package.json 中设置好相应的脚本。

{
  "scripts": {
    "prepare": "pnpm run clean && expo prebuild",
    "build:android": "cd android && ./gradlew assembleRelease",
    "clean": "node -e \"const opt = { recursive: true, force: true }; fs.rmSync('./ios', opt); fs.rmSync('./android', opt)\""
  }
}

另外,不要忘了把这些东西加到你的 .gitignore 里:

/credentials/*.keystore
/credentials.json
  
/ios
/android

这篇博文受到了上面那个「eas-cli 不能在 Windows 跑」的 Issue 中 这篇回复 的启发,我对于其中的操作流程进行了一些改进,让它更适合 Expo Managed Workflow。

【更正版】简单实现类型安全的、能触发 CustomEvent 的 EventTarget

我想写一个 TypeScript 类,这个类提供一系列的事件可供监听。为了实现类型安全,改进开发体验,我自己研究了一下,实现了一个可以以泛型输入所有可能的事件类型的 TypedEventTarget 类。

经过 JackWorks 的指正,代码改成了这样。

typed-event-target.d.ts 文件内容:

export class TypedEventTarget<T> extends EventTarget {
    // 这个类型体操是我从 `lib.dom.d.ts` 抄的我会乱说(
    addEventListener<K extends keyof T>(
        type: K,
        listener: (this: TypedEventTarget<T>, ev: TypedCustomEvent<K, T[K]>) => any,
        options?: boolean | AddEventListenerOptions,
    ): void;
    addEventListener(
        type: string,
        listener: EventListenerOrEventListenerObject,
        options?: boolean | AddEventListenerOptions,
    ): void;
    removeEventListener<K extends keyof T>(
        type: K,
        listener: (this: TypedEventTarget<T>, ev: TypedCustomEvent<K, T[K]>) => any,
        options?: boolean | EventListenerOptions,
    ): void;
    removeEventListener(
        type: string,
        listener: EventListenerOrEventListenerObject,
        options?: boolean | EventListenerOptions,
    ): void;
    dispatchEvent<K extends keyof T>(event: TypedCustomEvent<K, T[K]>): void;
}

export class TypedCustomEvent<S, T> extends CustomEvent<T> {
    constructor(type: S, eventInitDict?: CustomEventInit<T> | undefined);
}

typed-event-target.js 文件内容:

// 这里我们小小的欺骗了一下 tsc。
// 在运行时下,这个 EventTarget 其实就是原来的 EventTarget,
// 而非从 EventTarget 继承出来的类。这样可以避免非必要的性能开销。
export const TypedEventTarget = EventTarget;
export const TypedCustomEvent = CustomEvent;

将这两个文件同时放在一个合适的目录下,就搞定了!

使用方法

我们想要写一个 Person 类,这个类有 nameChangeageChange 这两个自定义事件。那么,我们可以这么写:

import { TypedEventTarget, TypedCustomEvent } from "@/utils/typed-event-target"; // 请根据项目实际情况修改路径

// 创建 Person 类的事件表
export interface PersonEventMap {
    nameChange: string;
    ageChange: number;
}

export default class Person extends TypedEventTarget<PersonEventMap> {
    name: string;
    age: number;

    constructor(name: string, age: number) {
        super();
        this.name = name;
        this.age = age;
    }

    setName(name: string) {
        this.name = name;
        // 在使用 dispatchEvent 时,如果类型不正确,会出现错误
        this.dispatchEvent<"nameChange">(
            new CustomEvent("nameChange", {
                detail: name,
            }),
        );
    }

    setAge(age: number) {
        this.age = age;
        this.dispatchEvent<"ageChange">(
            new CustomEvent("ageChange", {
                detail: age,
            }),
        );
    }
}

调用这个类时,我们绑定事件也会有正确的补全提示和类型检查。

本博文的上一个版本

我想写一个 TypeScript 类,这个类提供一系列的事件可供监听。为了实现类型安全,改进开发体验,我自己研究了一下,实现了一个可以以泛型输入所有可能的事件类型的 TypedEventTarget 类。

废话不多说,直接上代码。

typed-event-target.d.ts 文件内容:

export default class TypedEventTarget<T> extends EventTarget {
    // 这个类型体操是我从 `lib.dom.d.ts` 抄的我会乱说(
    addEventListener<K extends keyof T>(
        type: K,
        listener: (this: TypedEventTarget, ev: T[K]) => any,
        options?: boolean | AddEventListenerOptions,
    ): void;
    addEventListener(
        type: string,
        listener: EventListenerOrEventListenerObject,
        options?: boolean | AddEventListenerOptions,
    ): void;
    removeEventListener<K extends keyof T>(
        type: K,
        listener: (this: TypedEventTarget, ev: T[K]) => any,
        options?: boolean | EventListenerOptions,
    ): void;
    removeEventListener(
        type: string,
        listener: EventListenerOrEventListenerObject,
        options?: boolean | EventListenerOptions,
    ): void;
    dispatchEvent<K extends keyof T>(event: T[K]): void;
}

typed-event-target.js 文件内容:

// 这里我们小小的欺骗了一下 tsc。
// 在运行时下,这个 EventTarget 其实就是原来的 EventTarget,
// 而非从 EventTarget 继承出来的类。这样可以避免非必要的性能开销。
export default EventTarget;

将这两个文件同时放在一个合适的目录下,就搞定了!

使用方法

我们想要写一个 Person 类,这个类有 nameChangeageChange 这两个自定义事件。那么,我们可以这么写:

import TypedEventTarget from "@/utils/typed-event-target"; // 请根据项目实际情况修改路径

// 创建 Person 类的事件表
export interface PersonEventMap {
    nameChange: CustomEvent<string>;
    ageChange: CustomEvent<number>;
}

export default class Person extends TypedEventTarget<PersonEventMap> {
    name: string;
    age: number;
    
    constructor(name: string, age: number) {
        super();
        this.name = name;
        this.age = age;
    }
    
    setName(name: string) {
        this.name = name;
        // 在使用 dispatchEvent 时,如果类型不正确,会出现错误
        this.dispatchEvent<"nameChange">(
            new CustomEvent("nameChange", {
                detail: name,
            }),
        );
    }
        
    setAge(age: number) {
        this.age = age;
        this.dispatchEvent<"ageChange">(
            new CustomEvent("ageChange", {
                detail: age,
            }),
        );
    }
}

调用这个类时,我们绑定事件也会有正确的补全提示和类型检查:

类型提示
在调用 addEventListener 事件时,会提供准确的名称提示。
类型提示
调用事件的详情内容时,推导出来的类型也是准确的。

在 IE11 使用 CSS Grid 实现多列卡片列表布局

我们维护的某网站需要多列布局的、分页的、内容高度不固定的卡片列表,效果如下:

网格
网格布局的效果。它们的内容高度均不一致,但是在网格中必须做到视觉高度一致。卡片本身的代码是从 Adam Wathan 的传教博文 抄的,感激不尽(

实现这种网格列表,其实使用 CSS Grid 是最为科学的方案,因为它灵活、好用、易于理解,同时对于这种内容不定高的多列卡片列表非常友好:

.grid-list {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 20px;
}

但是有一个棘手的问题是:这个网站的浏览器兼容性要求是 Chrome 60+ 和 IE11。没错,都 2022 年了,还有苦逼开发者(比如我)还需要想办法让网站能在 IE11 正常工作。

不过幸运的是,通过对 CSS Grid 兼容性 的查询,我们可以得知:

  • IE10+ 支持旧版的规范,且需要添加 -ms- 的前缀
  • Chrome 57+ 开箱支持

根据前辈的指引,我意识到在 IE11 中用 CSS Grid 实现这种网格列表是完全没问题的,只是需要一些技巧。

我们的项目使用到了 SCSS、PostCSS 和 Autoprefixer,所以,首先在 postcss.config.js 中声明使用针对 IE11 的 CSS Grid 转译:

module.exports = {  
    plugins: {  
        autoprefixer: {  
            grid: "autoplace"
        }  
    }  
};

然后,把这个 mixin 丢进项目中:

@mixin gridList($maxRow, $column, $rowSize: auto, $columnSize: 1fr, $gapX: 0, $gapY: 0) {
    @for $i from 1 through $maxRow {
        &--grid-#{$i} {
            display: grid;
            grid-template-columns: repeat(#{$column}, #{$columnSize});
            grid-template-rows: repeat(#{$i}, #{$rowSize});
            gap: $gapY $gapX;
        }
    }
}

然后在需要的地方使用这个 mixin(假设我们的网格列表一页最多有 9 项,每列有 3 项,即最多会有 3 行):

.grid-list {
    padding: 30px;
    @include gridList(3, 3, $gapX: 30px, $gapY: 30px)
}

嗯,那么这个 mixin 是干什么的呢?

我们前面提到了 IE11 支持旧版的 CSS Grid 规范,而在该版本的规范中,必须显式声明你会用到的行数和列数;而且它并不支持 gap 属性。

那么,对于需要显式声明行数和列数的问题,我们可以换一个思路:借助 SCSS 函数的力量,把所有可能的卡片列数的 CSS 都生成出来;然后在 HTML 部分,由模板渲染器告诉页面一共有多少行。

以下是 mixin 生成的代码:

.demo-list {
  padding: 30px;
}

.demo-list--grid-1 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(1, auto);
  gap: 30px 30px;
}

.demo-list--grid-2 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(2, auto);
  gap: 30px 30px;
}

.demo-list--grid-3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, auto);
  gap: 30px 30px;
}

这样,我们就得到了行数有一行、两行和三行时所对应的 CSS 类。我们只要在生成模板时,使用对应的 CSS 类即可:

<!-- 以下伪代码仅供参考,实际使用时请替换为你使用的模板引擎/界面库的语法 -->
<ul class="demo-list demo-list--grid-{{ Math.ceil(Page.PageList.length / 3) }}">
	{%- for PageItem in Page.PageList -%}  
		<li class="demo-card">
			<!-- 略 -->
		</li>
    {%- endfor -%}
</ul>

IE 使用的旧版规范属性是以 -ms- 开头的,同时我们还需要通过空白的网格来模拟 gap 属性。不过,Autoprefixer 可以帮我们进行转译:

.demo-list {
  padding: 30px;
}

.demo-list--grid-1 {
  display: -ms-grid;
  display: grid;
  -ms-grid-columns: 1fr 30px 1fr 30px 1fr;
  grid-template-columns: repeat(3, 1fr);
  -ms-grid-rows: auto;
  grid-template-rows: repeat(1, auto);
  gap: 30px 30px;
}

.demo-list--grid-1 > *:nth-child(1) {
  -ms-grid-row: 1;
  -ms-grid-column: 1;
}

.demo-list--grid-1 > *:nth-child(2) {
  -ms-grid-row: 1;
  -ms-grid-column: 3;
}

.demo-list--grid-1 > *:nth-child(3) {
  -ms-grid-row: 1;
  -ms-grid-column: 5;
}

.demo-list--grid-2 {
  display: -ms-grid;
  display: grid;
  -ms-grid-columns: 1fr 30px 1fr 30px 1fr;
  grid-template-columns: repeat(3, 1fr);
  -ms-grid-rows: auto 30px auto;
  grid-template-rows: repeat(2, auto);
  gap: 30px 30px;
}

.demo-list--grid-2 > *:nth-child(1) {
  -ms-grid-row: 1;
  -ms-grid-column: 1;
}

.demo-list--grid-2 > *:nth-child(2) {
  -ms-grid-row: 1;
  -ms-grid-column: 3;
}

.demo-list--grid-2 > *:nth-child(3) {
  -ms-grid-row: 1;
  -ms-grid-column: 5;
}

.demo-list--grid-2 > *:nth-child(4) {
  -ms-grid-row: 3;
  -ms-grid-column: 1;
}

.demo-list--grid-2 > *:nth-child(5) {
  -ms-grid-row: 3;
  -ms-grid-column: 3;
}

.demo-list--grid-2 > *:nth-child(6) {
  -ms-grid-row: 3;
  -ms-grid-column: 5;
}

/* 后略... you get the idea */ 

结论

经过一系列的努力,我们终于拥有了在 IE11 正确显示的多列卡片列表。好耶!

尽管业界正积极淘汰 IE11,但现实情况是,还有一些商业应用仍然有着对 IE11 的兼容需求。确实,兼容老旧浏览器是一个让人头疼的问题,然而通过变换思路,我们可以找到一些取巧的解决方案。

当然更好的方法是,提桶跑路,去给历史包袱不重的项目填坑(逃

后记

这篇博文本来是一篇在 2022 年私下写的笔记,因为一开始只是写给我自己看的,加上我想应该需要支持 IE11 的项目越来越少,一直没有发布。

但是现实总是骨感的,就连不久前发布了 4.0.0 BETA 的 jQuery 团队 都说要继续支持 IE11,直到 jQuery 5 再放弃支持。所以这篇博文大概还是能发挥余热吧。

自建云游戏服务的尝试(2024 更新)

为什么?

我最近经常玩《原神》,但是我:

  • 只有一台能带动这游戏的远程工作站,我还很难物理访问它
  • 平时在外面只能使用弱鸡的旧款 MacBook Pro
  • 用 iPhone 12 Pro 玩,感觉体验堪忧

显然,再为这么一款游戏而专门购买游戏本是不划算的,而且游戏本有着笨重、噪音大、外观普遍中二感爆棚、电池续航几乎不存在等缺点。

考虑到近年来 StadiaGeForce Now 等云游戏服务在国外的兴起,我有了新的主意:拿自己的远程工作站搭建云游戏服务。

本博文不是真正意义上的教程,但是记录了我对若干方案的尝试和使用体验。

环境

  • 服务端(工作站)和客户端都在重重 NAT 之下。现在不是问题了,家里和移动网络都有公网 IPv6,可以点对点直连。同样我也不再推荐直接 ECS 当跳板、承载游戏流量的方案了,因为流量费是真的奢侈啊 TAT
  • 有一台阿里云 ECS 做跳板机,安装有:
    • Wireguard(用于和不同的远程设备连接)
    • nginx(通过 stream 模块向公网转发端口)

RDP

在此之前,我经常会使用 RDP 来连接我的远程工作站。启动 RDP 服务不需要复杂的服务端设置,在网关机上也只要简单转发一个端口到公网即可。

其实 RDP 对于一般的应用程序是没有问题的,但是完全不适合打游戏:

Moonlight + Sunshine

我的工作站配备有 GTX 1650 Super,因此可以借助 GeForce Experience 的游戏串流(Streaming)功能和 Moonlight 客户端,轻松搭建自己的云游戏服务。

总体来说,在有合适的网络条件下,Moonlight 可以提供足够好的游戏体验。但是使用官方的 GeForce Experience 时,会有一些蛋疼的问题:

  • 配对时必须在远程主机上输入四位数字确认
  • 一旦连接过 RDP(因为我平时还要工作!),串流服务就会停止工作
    • 所以,上述的配对过程也不能在 RDP 进行,必须使用其它的远程桌面服务(如 Teamviewer);
      同时,每次连接过 RDP,都需要用 Teamviewer 再连接一次。

不过幸运的是,现在有 Sunshine 了。Sunshine 是一个兼容 Moonlight 的游戏串流服务端,支持带有视频硬件编码功能的 AMD、Intel 和 Nvidia 显卡,而且完美解决了上述的配对麻烦(用 Web GUI 就可以配对)、RDP 导致服务端罢工的问题。

对了,如果你在 Xiaomi HyperOS 设备上使用 Moonlight for Android,需要呼出虚拟键盘,得同时用三指按动屏幕,而且每根手指距离要有大约 2cm 以上。别问我是怎么知道的(躺

Parsec

Parsec 是一款与 Moonlight 类似的应用,但是对非技术导向的用户更加友好,而且解决了 Moonlight + Sunshine 的一些痛点:

  • 验证是基于他们自家帐号系统的(比在远程主机确认配对方便多了!)
  • 连完 RDP 以后可以直接连接
  • 灵活的连接方式,可以自动视情况通过局域网、NAT 打洞、Wireguard 等方式连接到服务端
  • 支持 AMD 和 Intel 显卡(虽然我现在用的是 Nvidia 显卡)

不过,Parsec 也有一些缺点:

  • 你只能使用 Parsec 他们家的帐号系统,而且他们的客户端比较黑箱。我不是很在乎就是了……
  • 视频质量并不算很好,就算你在 GUI 把码率开到最大也无济于事。不过,蚊子写过一篇博文,提到手动在配置文件中加上 encoder_min_bitrate = 50 就可以改善
  • Android 客户端不算那么好用,而且不支持手柄振动,也不支持将触摸屏当作笔记本触摸板使用。

让 GPU 一直工作

我在淘宝上随便买了个 HDMI 锁屏宝,解决了这个问题。但是不要买的太随便,因为如果锁屏宝模拟的显示器分辨率和刷新率不够高,那么想要更好的串流分辨率和帧率是没门的。

不过,也可以试试蚊子提到的 虚拟显示器

对比

总之,不同的连接方案有着它们的优缺点,所以还得视场合选择使用哪种方式连接到我的工作站。

服务/特性 RDP Moonlight + Sunshine Parsec
支持显卡 N/A 各种 各种
配置难度 中等
首次连接 输入本机登录信息 在服务端完成配对操作 输入 Parsec 登录信息
流量消耗 较高 中等
图形渲染位置 客户端 服务端 服务端
针对游戏优化
使用 RDP 后再连接 N/A 可直接连接 可直接连接
自动打洞
剪贴板共享 有(仅限文本)
开源软件 客户端/服务端均开源

自建云游戏服务的优越性

就是可以玩各种自己喜欢的游戏啦,而不只局限于云游戏厂商提供的可玩游戏。

不过,如果你没有长期使用 Windows 远程桌面的需求,而且只想玩一些大众游戏,那么类似于 网易云游戏平台腾讯云游戏 等服务都是可以考虑的。

Update: 如果你是原神国服玩家,还可以考虑一下官方的云・原神

流量费

别人花 648 抽优菈,我花 648 让游戏跑起来(笑

不过现在四大运营商都推出了便宜到爆炸、而且每月流量至少有 100G 的 5G 流量卡(不是物联网卡),同时通过与家里进行点对点连接,现在出门在外都可以省掉不少流量费了。

然后我靠着免费的原石抽到了她(

优菈

在 Termux 编译和使用 bwm-ng(需要 root)

bwm-ng 是一个很方便的命令行工具,可以实时监控操作系统的网速和磁盘读写速度。

我想在 Termux 使用它,但是目前 Termux 的软件源还没有,所以就只能自己编译安装了。

步骤

首先,准备好安装了 Termux,且已经 root 的 Android 手机。

然后,我们需要给 Termux 安装 root-repo,再安装 sudo

pkg install root-repo
pkg install sudo

安装 Git 和构建工具集:

pkg install git build-essential

接下来,就可以进行编译安装了,但是跟一般的编译安装有点区别:

git clone https://github.com/vgropp/bwm-ng
cd bwm-ng
sudo ./autogen.sh
sudo make

在这里,./autogen.shmake 命令的前面都增加了 sudo,这是为了让编译脚本能够正确识别到 /proc/ 下的特殊文件(在 Android 下,该目录只能由 root 用户访问)。如果不使用 sudo,会看到这样的报错:

checking for /proc/net/dev... no
checking for /proc/diskstats... no
checking for /proc/partitions... no
checking for sys/dkstat.h... no
checking whether cc and linker accepts -framework IOKit -framework CoreFoundation... no
checking for sg_get_network_io_stats,sg_get_disk_io_stats in -lstatgrab... no
configure: error: "NO INPUT FOUND"

编译完成后,将 src 目录下的 bwm-ng 二进制文件移动到合适的位置(一般在 $PATH 中指定过的路径),使用 sudo bwm-ng 就可以使用了。

  bwm-ng v0.6.3 (probing every 0.500s), press 'h' for help
  input: /proc/net/dev type: rate
  /         iface                   Rx                   Tx                Total
  ==============================================================================
      wifi-aware0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
             ifb0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
             sit0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
             p2p0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
    r_rmnet_data1:           0.00 KB/s            0.00 KB/s            0.00 KB/s
               lo:           0.00 KB/s            0.00 KB/s            0.00 KB/s
      rmnet_data2:           0.00 KB/s            0.00 KB/s            0.00 KB/s
      rmnet_data5:           0.00 KB/s            0.00 KB/s            0.00 KB/s
       rmnet_ipa0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
           dummy0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
      rmnet_data1:           0.00 KB/s            0.00 KB/s            0.00 KB/s
    r_rmnet_data2:           0.00 KB/s            0.00 KB/s            0.00 KB/s
      rmnet_data0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
            wlan1:         229.75 KB/s           10.00 KB/s          239.76 KB/s
      rmnet_data3:           0.00 KB/s            0.00 KB/s            0.00 KB/s
            wlan0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
    r_rmnet_data3:           0.00 KB/s            0.00 KB/s            0.00 KB/s
    r_rmnet_data0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
          ip_vti0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
          ip6tnl0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
            bond0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
             ifb2:           0.00 KB/s            0.00 KB/s            0.00 KB/s
             ifb1:           0.00 KB/s            0.00 KB/s            0.00 KB/s
       rmnet_mhi0:          24.65 KB/s          227.47 KB/s          252.12 KB/s
      rmnet_data4:          18.40 KB/s          231.23 KB/s          249.63 KB/s
         ip6_vti0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
  ------------------------------------------------------------------------------
            total:         272.80 KB/s          468.71 KB/s          741.50 KB/s

如何在已有的 Vue CLI 项目使用 esbuild

EDIT 2: Vue CLI 都凉了,建议更换 Rspack

EDIT: 其实可以试着用相同的方法把 Babel 换成 SWC,因为它最低可以编译到 ES5。不过我现在不需要负责这个兼容 IE11 的项目了哈哈哈哈

背景

我们的门户网站项目(Vue 2 / Vue CLI 5 / TypeScript / Element UI)前端部分编译所需时间太长,因此开始考虑在不对已有项目进行过于伤筋动骨的调整的前提下,提升编译速度的方式。

由于 Vite 使用了 esbuild 进行编译速度的提升,我们想到了一个主意:借助 esbuild-loader,把 Vue CLI 中的 Babel 替换为 esbuild。

分析

在项目根目录执行 npx vue-cli-service inspect,可以看到最终生成的 Webpack 配置中,以下部分涉及到了 babel-loader:

      /* config.module.rule('js') */
      {
        test: /\.m?jsx?$/,
        exclude: [
          function () { /* omitted long function */ }
        ],
        use: [
          /* config.module.rule('js').use('babel-loader') */
          // (略)
        ]
      },
      /* config.module.rule('ts') */
      {
        test: /\.ts$/,
        use: [
          /* config.module.rule('ts').use('babel-loader') */
          // (略)
          /* config.module.rule('ts').use('ts-loader') */
          // (略)
        ]
      },
      /* config.module.rule('tsx') */
      {
        test: /\.tsx$/,
        use: [
          /* config.module.rule('tsx').use('babel-loader') */
          // (略)
          /* config.module.rule('tsx').use('ts-loader') */
          // (略)
        ]
      }

由此可知,我们需要将 jststsx 的默认规则进行清空处理,然后让 jststsx 使用 esbuild-loader

操作

首先,安装 esbuild-loader

npm i -D esbuild-loader

然后,在 vue.config.js 下的 chainWebpack 中加入以下内容:

// 清空已有的使用 `babel-loader` 的规则
config.module.rule("js").uses.clear();
config.module.rule("ts").uses.clear();
config.module.rule("tsx").uses.clear();

// 注入使用 `esbuild-loader` 的新规则
config.module.rule("js")
    .test(/\.m?jsx?$/)
    .use("esbuild-loader")
    .loader("esbuild-loader")
    .options({
        loader: "jsx",
        target: "es2015"
    })
    .end();
config.module.rule("ts")
    .test(/\.ts$/)
    .use("esbuild-loader")
    .loader("esbuild-loader")
    .options({
        loader: "ts",
        target: "es2015"
    })
    .end();
config.module.rule("tsx")
    .test(/\.tsx$/)
    .use("esbuild-loader")
    .loader("esbuild-loader")
    .options({
        loader: "tsx",
        target: "es2015"
    })
    .end();

就可以了。

已知问题

  • 如果项目中有使用到 Web Worker,肯定会炸掉,出现 TypeError: Failed to construct 'URL': Invalid URL 错误。采用 Babel 则正常工作。

遗憾

尽管 esbuild 是好文明,我们依然无法在那个门户网站项目中使用;因为我们项目要求兼容 IE11,而 esbuild 目前最低只能编译到 ES2015,这就导致编译产物无法在 IE11 等不(完全)支持 ES2015 的浏览器中运行;不过,在本地调试时还是很爽的,因为开发环境冷启动和热更新速度确实快了不少。

不过,如果你的项目不需要兼容 IE11 等老浏览器,但恰好还没有升级到 Vue 3 + Vite,完全可以试一试。能快一点是一点啊)

我的 2021

于是,2021 年猝不及防的结束了。虽然我很多时候懒得写这种总结,但是考虑到我还有一个博客在运行,还是写一篇总结好了。

设备添置

今年,我利用自己的收入,给自己添置了很多新的装备。其中两件最重要的是:

同时,今年我把我的 Homelab 建设了起来。我通过购买一些全新/二手设备和配件,完成了我业余机房的基础建设。

功能上,可以分为图形虚拟化服务器、Linux 虚拟化服务器、云游戏服务器、NAS 和软路由;同时,还借助一台阿里云 ECS 实现端口转发、VPN 等功能。当然,这样的业余机房也不是没有代价的;一个是噪音比较大(就这我还没考虑机架式服务器),还有一个是电费要比以前高出不少(好在内蒙古的电价相对还是很低的)。但是,这套 Homelab 极大的改善了我(自认为是 Power User)的 IT 生活体验,包括云游戏也可以让我在任何地方享受家里安装的 PC 游戏。

说到 PC 游戏,其实这一块一直是我最缺乏了解的领域。我主要的游戏经历都是在家里的山寨 FC、后来(打着 MP4 旗号)的模拟器机、智能手机,以及 Nintendo Switch 上;由于以前家里的 PC 配置并不理想,加上父母的态度对 PC 游戏并不友好,我在 PC 上玩过的游戏寥寥无几(不过包括 Minecraft)。其实我开始涉足云游戏,只是希望拥有更好的远程桌面体验;虽然今年显卡价格蹭蹭蹭的上涨,但是因为我去年恰好买了张索泰的 GTX 1650 Super 当亮机卡(迷惑行为),我误打误撞的就开始玩起了《原神》,结果我一发不可收拾,还注册了 Steam 账号,开始研究我还能在 PC 上玩些什么有趣的游戏。因为就算是 1650 Super 这种入门级游戏显卡,它带来的游戏体验也是碾压对我来说的传统游戏设备(至少现今),而且键鼠操作也让我感觉更加灵活、舒适。

不过我也发现很多 PC 游戏都有着烦人的 DRM 和反作弊措施 (aka rootkit)。虽然我不认为我是什么隐私怪,但这让我感觉并不太爽;同时因为我工作站的十代 i7 算不上玩游戏的最佳选择,我从群友那边收了一套 B450 + Ryzen 5 3600 的套装,用手上的一些东西组装了专用的云游戏服务器。结果我的 1650 Super 反而成瓶颈了(

兼职

随着我不断接近毕业,学校的事情也变得越来越少。于是今年年初,在 @qwe7002 的介绍下,我开始利用空闲时间做一些前端开发的远程兼职,并陆续参与了几个项目:

  • 某微信小程序(2 月至 4 月)。这个小程序是经过了几个外包开发者之手,代码逻辑混乱不堪。我经过不懈努力,成功的重构了一小部分浑浊不清的代码,还写了几个新的页面。不过这个项目的经历也让我明白 TypeScript 对于这种多人合作的大型 JS 项目真的很关键,同时对小程序的好感进一步受损,因为这玩意即使是对开发者也很不友好啊。
  • 某 ERP 系统前端(3 月至年底)。这一次,我和另外几位小伙伴,从零开始了一个比较庞大的 Vue 2 项目。我参与了整个项目的选型和脚手架搭建工作,同时意志坚定的选择了 TypeScript(虽然代码里还是有一大坨 any。这个项目还是让我感觉非常愉悦的,因为毫无大型商业项目开发经验的我们居然成功的把项目做了出来,积累了不少经验;而且,还成功让我的小伙伴们掌握了 TypeScript 的基本使用(他们以前都只有 JavaScript 的使用经验);而最重要的是,我拿到了一笔非常可观的报酬。

12 月,我顺利的完成了毕业设计和毕设答辩,进入实习阶段。2022 年起,我将在福州开始我人生中的全新篇章——成为社畜。虽然不知道以后还会发生什么,但是我对我的未来还是充满期待的。

博客

今年我没有对自己博客花很多的时间。不过,我的博客一年来运行基本正常,没出幺蛾子,还是可喜可贺的。

以及,我开始用 Golang 重构我博客的评论系统了。原来写的 Node.js 后端虽然能用,但是算上依赖以后的体积非常臃肿,而且非常吃内存(要知道我的 VPS 也就是 1c1g 的配置);同时,我想找个东西来练练 Golang,所以就拿自己博客的评论系统开刀了。

目前还没有写完,但是到时候大概可以给我可怜的 VPS 节约不少内存吧。

2022……?

2022 年对于我来说会是变化最大的一年,因为我的身份要从学生变成社畜了,同时以后要开始在南方城市常年驻扎。所以未来的事情,只能说走一步看一步吧。

听某些人说,2022 年意味着 2020 年的重复,因为 2022 年拿英文读听起来像 Two thousand twenty too(也是 2020 年)。2021 年的世界依然不太平,所以只能希望 2022 年不要真的是这样,以及第三次世界大战永远不要发生了(至少在我的整个人生中)。

最后,祝大家新年快乐。

npm、镜像源与 package-lock.json

在国内开发涉及 Node.js 的应用都知道,裸连官方的 https://registry.npmjs.org 非常慢,等待时间令人捉急。

解决这种问题,我们自然想到的就是找镜像源(就像 Linux 发行版的包管理器那样)。

国内目前已经有一些 npm 镜像源,大多数情况下,它们其实还是可以用的。但是,package-lock.json 中会记录下各个包的原始 URL:

{
    "node_modules/@babel/compat-data": {
        "version": "7.16.0",
        "resolved": "https://repo.huaweicloud.com/repository/npm/@babel/compat-data/-/compat-data-7.16.0.tgz",
        "integrity": "sha512-DGjt2QZse5SGd9nfOSqO4WLJ8NN/oHkijbXbPrxuoJO3oIPJL3TciZs9FX+cOHNiY9E9l0opL8g7BmLe3T+9ew==",
        "dev": true,
        "license": "MIT",
        "engines": {
            "node": ">=6.9.0"
        } 
    },
    "node_modules/@babel/core": {
        "version": "7.16.0",
        "resolved": "https://repo.huaweicloud.com/repository/npm/@babel/core/-/core-7.16.0.tgz",
        "integrity": "sha512-mYZEvshBRHGsIAiyH5PzCFTCfbWfoYbO/jcSdXQSUQu1/pW0xDZAUP7KEc32heqWTAfAHhV9j1vH8Sav7l+JNQ==",
        "dev": true,
        "license": "MIT"
    }
}

其实如果项目仅仅在国内开发和编译,问题不会很大。但是如果你开发的是开源项目(特别是面向海外的)或者在国外服务器跑 CI,你的项目就有可能会遇到安装依赖时连接不稳定的问题(因为国内这些镜像源通常不会针对国外优化)。

对此,我的建议是:

  • 尽量避免使用镜像源。
    • 进行肉翻。
    • 对于连接缓慢的问题,可以给 npm 设置代理,或者利用国外 VPS 为 npm 自建 SNI Proxy,然后在本地修改 hosts 文件/进行 DNS 劫持。
  • 如果一定要使用镜像源,则需要:
    • 考虑清楚你的项目是否将完全在国内开发,并让团队中所有人使用相同的镜像源。
    • 尽量选择能够通过 sed 简单替换 package-lock.json 中所记载地址的镜像源(例如华为源、中科大源)。

新的桌面工作站:HP Z2 G5 Tower

我的上一代桌面工作站是台戴尔的 Inspiron 660,购于 2013 年初。经过数年来的多次升级,它拥有了三代 i7 处理器、16GB 内存和固态硬盘。

其实这台机器的性能直到今天依然不算逊色,基本上可以满足日常使用。但是,因为一些个人原因,我开始日常需要开启多个虚拟机,于是这台机器的 CPU 就开始有些吃不消了。

考虑到这台机器已经有 8 年历史,于是我便决定让它退居二线,同时将主力工作站进行升级换代。

经过反复的挑选,我最终选中了惠普的 Z2 G5 Tower。它在这些方面非常吸引我:

  • 免工具维护
  • 极高的可扩展性,有着合理的升级路线
  • 没有光污染,但是不失颜值的机箱箱体

在我购买这台机器时,我其实已经听说了 11 代酷睿系列处理器即将发布的消息,但是我这台机器预期服役 5 年以上,感觉差上一代应该问题不大。

以及我买的配置其实听起来就感觉有点尴尬:i7-10700 / 8GB DDR4 2666 x1 / 2TB 机械硬盘,不过我自己已经有一些配件了,可以直接换上去用。

最终,成功在国内商家以 6.8k 拿下。

安装

因为卖家发的是顺丰,所以我下单以后第三天就到了。毕竟在内蒙古这种地方,这个速度已经算是很快了。

包装

机箱的颜值确实不错,不过感觉比想象中的要略小一点。

  • 正面 IO:
    • 2 个 USB 3.1 Gen1
    • 2 个 USB 3.1 Gen2
    • 耳麦接口
  • 背面 IO:
    • 2 个 USB 2.0
    • 2 个 USB 3.1 Gen1
    • 2 个 USB 3.1 Gen2
    • 1 个 RJ-45(I219-LM,千兆以太网)
    • 线路输入和线路输出
    • 2 个全尺寸 DisplayPort
    • 1 个 VGA (Flex IO modules)
机箱前面 机箱后面

打开机箱也非常简单,只要扳动背面右侧的黑色开关即可。

初见机箱内部

后盖上粘贴的贴纸介绍了主板各个部位的用途。

后盖贴纸

中间那个横着的玩意是……显卡风扇。应该是辅助散热的,不过也给我后面的显卡安装带来了一些麻烦(见后文)。

显卡散热器

工作站的主板。这块主板的可扩充性的确非常丰富,主要亮点有:

  • 4 条 DDR4 内存插槽
  • 4 个 SATA 3 接口
  • 两个 M.2 M Key 插槽(只支持 2280 尺寸)和一个 M.2 E Key 插槽(我这台已经预装了 AX201)
  • 4 个 PCI-E 插槽

顺便主板是前后一体贯通的,而且是非标准螺丝孔位,所以是没法更换市售主板的。不过对我来说无所谓啦。

主板

传说中的 Flex IO modules;我这台配的是 VGA 输出。

Flex IO modules

预装的 AX201 网卡,支持 Wi-Fi 6 和蓝牙 5.0。

AX201 网卡

主板的 PCI-E 插槽,配置如下:

  • 1 个 PCI-E x16
  • 2 个 PCI-E x4(x1 信号)
  • 1 个 PCI-E x16(x4 信号)

PCI-E 插槽

其它配件

我的附带了一个笔记本尺寸的 DVD-RW 驱动器,不过还预留了一个空的 5.25 寸扩展槽。

DVD-RW 驱动器

700W 的电源适配器,有着 80 Plus 铂金认证。预留了两条 6+2 的显卡供电。

虽然是非标准的电源,不过使用一些高端显卡应该是绰绰有余。

Edit: 有 Telegram 群友指出这个电源适配器应该是符合 ATX12VO 标准的。电源参数看起来确实如此,但是实际上孔位和尺寸跟正常的 ATX 电源完全对不上,而且电源接口也是非标准的。

电源适配器

三星 8G DDR4 2666 内存。

内存

东芝 DT01ACA200 机械硬盘(2TB / CMR);拆下来以后被我塞进 NAS 里了。

机械硬盘

硬盘托架;这台机器可以安装两块 3.5 寸硬盘。

硬盘托架

其它附带的东西

包装中除了主机本体,还有小册子、品字电源线、键盘和鼠标。

其它物品

附带的键盘和鼠标;朴实无华,但是手感还算说得过去。

鼠标 键盘

安装我自己的配件

随后,我安装了我自己已有的一些配件:

  • 显卡:ZOTAC GTX 1650 Super (4GB GDDR6)
  • NVMe SSD:铠侠 RC10 500GB
  • 内存:英睿达 16GB DDR4 2666

但是安装显卡时,我发现我扣不上机箱的显卡散热器。仔细一看,发现了问题所在:我的显卡的供电口位置很尴尬,恰好和散热器冲突。

显卡散热器问题

所以,我只好先把固定散热器(?)的那个黑色的东西拆下来了,然后才有了足够的空间。拆下来以后看起来问题不大,机箱的显卡散热器还能固定住的样子。

那么把整个散热器都拆掉呢?我试了,结果机器在 POST 就会报错,抱怨风扇出现了问题。

理论上来说,我可以装个假负载,来让主板认为风扇在正常工作;但是这台机器的风扇接口也是非标准的,就有点尴尬。

使用体验

安装完毕以后,顺利开机,没有任何问题。

得益于 24G 的内存、8 核 16 线程的处理器和 Hyper-V,整体的体验是滑溜溜的,开 3 个 Windows 虚拟机都压力不大。

hwinfo 信息

CPU

2021 年 4 月更新:因为发现 500G 的存储空间有点捉襟见肘,再加上 Chia 挖矿潮即将兴起,所以提前把固态硬盘换成了建兴 T10 240GB + 铠侠 RC10 1TB 的组合。很爽。

总结

感觉这台机器买的很值,相比我 8 年前的旧电脑,是一次非常巨大的飞跃。开心!

顺便一提,我在购买这台机器时,有考虑上一块更好的显卡,但是现在显卡实在是贵的太离谱了,就先把之前买的 GTX 1650 Super 装上了。(摊手

我的 2020

好耶,我终于把 2020 年勉强过完了。

成就

我在 2019 年终总结里写道:

待定!计划赶不上变化的。

结果,受到 COVID-19 疫情的影响,今年的大计划其实几乎都泡汤了(包括计划参加的一个比赛、2020 年夏天的面基出游计划等)。

所以,总体上感觉今年是碌碌无为的一年。不过没办法……

买买买

不过今年买到了不少很棒的新硬件:

  • WI-1000XM2
    听感很好,佩戴起来相当舒适,而且 10 个小时的续航还是很香的。
  • 自组装台式机
    我第一次亲自从头开始组装的台式机,虽然有些配件选择上的遗憾,不过点亮后还是很有成就感。
  • 一加 8T
    强大的骁龙 865、65W 快充(虽然是私有协议)、滑溜溜的 120Hz 屏幕、很强的可折腾度……用起来太爽了!
  • AirPods Pro
    出门使用还是很方便的,包括超级简单的多设备切换功能!

今年并没有购买很多专辑,不过还是感觉这些是很不错的:

博客

今年的确没有写很多博客,不过还是做了不少工作,让它「符合潮流」:

  • 将评论系统后端用 TypeScript 进行了重构,修正了很多错误
  • 给自己博客增加了暗色模式,并支持跟随系统切换
  • 给自己写了一套全新的卡片式博客主题,使用了新的设计语言

2021 年计划?

在 Intel 版 MacBook Pro 以 EFI 的形式安装 Windows 10

为什么?

最近我想玩 Minecraft Windows 10 Edition,但是我在学校,我自己的电脑只有一部 MacBook Pro 2017。

那为什么不用 Bootcamp 呢?

因为我也不知道为什么,只有无尽的黑屏与 Windows 10 安装向导初始化的画面。我无法开始安装。

而且 Bootcamp 还有其它的缺点,具体可以看 这里

顺便少数派那个教程有点过时了,我在我的新款 MacBook Pro 安装时撞了些坑,所以决定写个新教程,造福人类。

准备材料

  • Windows 10 ISO
  • UNetbootin
  • 8G 以上的 U 盘(建议使用 USB 3.0 的)
  • USB Hub(建议使用 USB 3.0 的)
  • 支持 Windows 的键鼠套装(在安装完整的 Bootcamp 驱动以前,内置键盘和触摸板用不了的)
  • 机智的你

如果你使用的是带 T2 芯片的新款机型,请先根据 官方说明 允许通过外部 USB 设备启动。

我的安装环境

  • 机型:MacBook Pro (13-inch, 2017, Two Thunderbolt 3 ports)
  • 操作系统:macOS 10.13.5 (17F77)
  • 磁盘:256GB,只有 macOS 分区

分出 Windows 分区

假如你打算留下 macOS,请使用这个命令调整 macOS 分区大小:

diskutil apfs resizeContainer disk1 200GB    # 200GB 指你给 macOS 留的分区大小

这些数值可以酌情调整。但是,请确保有足够的剩余空间。

制作 Windows 10 安装盘

下载好 Windows 10 ISO,插上你的 U 盘,然后先格式化一下:

格式化 U 盘

我们把 格式 设置为 MS-DOS (FAT)方案 设置为 主引导记录

然后我们打开 UNetbootin,选择 Diskimage,打开我们准备好的 Windows 10 ISO。下面的 Type 当然选择 USB Drive,然后将 Drive 设置成你要制作安装盘的 U 盘(如果不确定的话,可以把主机上所有其它 U 盘和读卡器什么的都拔掉,这样就只有一个选项了)。

UNetbootin

然后点击 OK,耐心等待写入完毕就是了。

写入过程

在写入完毕后,我们打开 启动转换助理,选择 操作 => 下载 Windows 支持软件,并将保存位置设为我们的安装盘的根目录下。接下来我们会需要的。

下载即将结束时会向你请求权限,这是正常的,直接输入密码确认就行了。

下载 Windows 支持软件

如果你尝试过 Boot Camp 安装

如果你没有尝试过 Boot Camp 安装,请跳过这一节!

直接照着 这篇帖子 里面的方法做就行。

以及你需要先把 SIP 暂时关掉。

开始安装

把你的 U 盘、键盘和鼠标插上你的 USB Hub,并连接 MacBook。

重启你的 MacBook 并按住 Option 键,你会看到有好几个磁盘的选项。按方向键选择 黄色图标的 EFI Boot,然后回车。

然后先按提示一路走下去,然后到了选择磁盘这一步,你会发现没有磁盘可选。这是正常的,我们还需要加载驱动程序。

我们点击 加载驱动程序,再点击 浏览,找到 C:\WindowsSupport\$WinPEDriver$\AppleSSD64,确定。然后点击下一步。

稍后,我们就可以看到我们的磁盘分区了。我们按照正常的方法创建好分区,然后继续一路向前就是了。

安装驱动

当我们进入安装好的 Windows 10 以后,找到你安装 U 盘下的 WindowsSupport\BootCamp 文件夹,运行里面的 setup.exe,安装驱动程序,然后重新启动就是了。

然后就大功告成了,你的内置键盘、触摸板等一系列硬件都可以使用了。

(在我设备上的)已知问题

  • Windows 睡眠时间过长以后,你需要经过完整的开机过程才能还原。
  • 每次返回 macOS,你恐怕都需要按住 Option 键手工选择 macOS 分区。在设置里改 启动磁盘 不管用。
  • 蓝牙工作异常

为什么大家都在安利这一样东西?

大概无数次我在各大群组里(看见别人)求某种东西的推荐,然后群友们就异口同声的说:「XXX(指同一件东西)不香吗?」

但是这样的安利反而让我感觉不安,因为:

  • 一种东西肯定有它自己的优缺点,但是在那些群友的口中,给人一种 XXX 非常完美的错觉。
    真的,跑分不能说明一切。
  • 安利这种东西的群友中,大概率有相当一部分是它的 fanboy,然后如果你提出不太一样的安利,大概会跟你争个面红耳赤,甚至引起严重的不愉快。
    好吧,我大概是已经屈服于这种「政治正确」了。
  • 有的群友可能只是云用户,在瞎起哄而已。

所以为什么通常情况下,我越来越不想在群聊中为这种事情浪费时间了。我宁可自己问问 Google 娘。

新的家庭服务器:MicroServer Gen10

我的上一代家庭服务器是一台技嘉的 GB-BXBT-2807。其实作为家庭服务器来说,它是个还行的选择,但是内部只能安装一块 2.5 寸 9.5mm 以下的 SATA 硬盘,而且只有一个 USB 3.0 和两个 USB 2.0 接口。

我家的上传速率其实还是可以的,而随着我开始尝到私有云的甜头,我陆续增加了三块硬盘。然而因为它只有一个 USB 3.0 接口,所以最后我的家庭服务器变成了这个样子。

胶水 NAS

看来,我需要一台真正的多盘位 NAS 服务器了。我一度考虑过以下方案:

  • 搞个蜗牛星际。但是在 KK 家摸到真机以后,我对它的做工倍感失望。
  • 买个迎广 MS04,自己从头攒一台 NAS 出来。但是这样的话,总支出大概不会跟买一台 Gen10 差太远,而且保修这个问题会变得比较复杂。

最终,我还是决定购买 HPE ProLiant MicroServer Gen10。

可以说,这款主机对我来说,几乎是完美的:

  • 4 盘位(虽然并不是热插拔)
  • 低功耗(x3216 的 TDP 只有 15w)
  • 主机本身是正常的 x86 PC,可以很方便的安装各种主流 Linux 发行版
  • 做工良好,可靠性强
  • 两个 PCI-E 插槽(分别为 x8 和 x1),未来升级万兆会很方便
  • 接口丰富:四个 USB 3.0、两个 USB 2.0、两个千兆以太网口、两个全尺寸 DP 和一个 VGA
  • 预装 8GB DDR4 ECC 内存
  • ……

就这样,我以 2.8k CNY 的价格在美亚拿下了这玩意。其实我一开始考虑从德亚买的,只要 2.5k(当时),但是并不能直邮到中国。为了省事,我就直接在美亚买了。

安装

近两周以后,它抵达了我家。到手的第一件事,当然是拆开检查一下了。箱子中并没有太多的东西(主机、美标品字电源线和一堆小册子),不过对于它的目标用户来说大概够了吧。

顺便那条品字电源线用的是带接地的美标插头,这使得它并不能在新国标的插线板上使用。所以如果你家没有多余的品字电源线,别忘了单独买一条!

Gen10 正面

Gen10 背面

Gen10 左侧

PSU 是台达的 flex 电源,宽电压,最大输出 200W;输出有 24pin x1、大 4P x1 和小 4P(软驱电源插头)x1。

接下来就要安装硬盘了。这玩意并没有独立的硬盘仓,所以你需要这样安装硬盘:

  1. 从硬盘仓顶部拧下来 4 颗螺丝
  2. 把这 4 颗螺丝拧到硬盘两侧最左面和最右面的孔位上
    硬盘上的螺丝
  3. 把硬盘正面朝右,稍微用力的推进硬盘仓。移除硬盘也是超级容易的(见硬盘仓左下角贴纸):
    硬盘仓(安装后)

然后就开始安装系统 SSD 了。这玩意并没有 M.2 NVMe 插槽,但是在主板上提供了一个额外的 SATA 接口,所以我买了一块普通的 SATA SSD。

然而,Gen10 的 PSU 并没有多余的电源线,你能利用的只有那个小 4P 插头。所以你需要买这样的转接线:

我那个 Gamemax 机箱正好附送了一根这样的转接线,所以我就直接拿来用了。

至于 SSD 的固定……Gen10 上侧的那几个空位是给你固定笔记本光驱用的,所以你的 SSD 大概就只能这样放着。当然你可以再买个笔记本光驱位转硬盘位之类的东西,不过 NAS 这种东西本身也不需要经常挪动,再说 SSD 里面并没有活动的部件,所以我就无所谓了。

系统 SSD(安装后)

系统

我选用的是 Ubuntu 18.04 LTS。因为:

  • Ubuntu 是我熟悉的发行版系列
  • 我拥有充分的控制能力

安装过程没什么坑,一切都在预料之中。随后,我通过 apt 安装了各种我需要的软件(nginx、Aria2、Transmission 等),写好 /etc/fstab 表,就大功告成了。

目前,我的硬盘使用情况如下:

硬盘 安装位置 用途
东芝 240G SSD(TR200) 顶部 系统盘
东芝 2T 监控盘(DT01ABA200V) 硬盘仓 1 一般文件存储
西数 4T 蓝盘(WD40EZRZ) 硬盘仓 2 BT/PT 下载

同时,我还有一块 2T 的东芝移动硬盘用于冷备份;我会定期将它连接到 Gen10 上,运行我的脚本来进行 rsync。

嗯,我觉得目前就足够了。等将来有需求的话,再考虑加硬盘吧。

完工

2024 更新

这台机子还在服役,但是我后来又买了三块 4T 硬盘来组建 RAID-Z1 阵列,同时为了降低运维难度(其实是我懒),所以把系统换成了 TrueNAS Scale。

内存也加到了 16G,因为 ZFS 对内存的需求更多一些。

系统运行

我之前的确考虑过 CPU 的问题,毕竟这玩意是焊在主板上的。但我的需求不高(就是存取文件和下载啥的,顺便开个 web 服务器),即使是看动画的话,解码都在客户端完成,所以也就 Gen10 了。

实测我的 CPU 负载一般可以控制在 1.0 以下;如果我真的需要算力,大概我就直接拿主力机器搞事了。

neofetch


我的 2019

好耶,我终于把 2019 年瞎鸡巴过完了!

感觉今年反而是相当平淡的一年,所以其实并没有太多的要讲。都是无病呻吟

成就

  • 考到了 C1 驾照
  • 获得了 5000 元的奖学金(第一次!)

面基

今年去了广州、福州和深圳,在线下见到了 qwe7002(又一次)、whitebox、寿司同学和 kookxiang。

我们一起玩了马车 8,一起进行技术交流,还在 kookxiang 家参观了多种灵车。然后 tcdw 对蜗牛星际的做工倍感失望,一怒之下买了 HPE Microserver Gen10。

开心!

买买买

今年买到了不少很棒的新硬件:

  • Nintendo Switch
    相当便携;上面的第一方游戏真的太好玩了,还有很多不错的第三方游戏。吹爆!
  • Dell UltraSharp U2518D
    颜色非常舒适的 2K 显示器,还内置 USB Hub!
  • AOC P2491VWHE/BW
    还行的 1080p 显示器,功耗很低(我的实测只有 18w 左右)。最初是买来给宿舍用的,奈何我的桌子太小了,所以也搬回家里用了……Switch 大屏游戏?暂且算了吧 🤣
  • 小米 CC9e
    便宜,日常应用体验很流畅!虽然屏幕分辨率很低,不过对我的使用场景来说够用了(大概)。
  • 树莓派 4
    好耶,是千兆以太网和 USB 3.1 gen1。
  • Intel Core i7-3770(散片)
    我家那台戴尔应该可以再战几年了!
  • HPE Microserver Gen10(在撰写本博文时还在路上)

同时在文娱方面增加了不少支出;今年我购买了大量的音乐(139 张专辑和 EP,包括数字专辑、捡垃圾和海淘)。其中我非常喜欢的有:

以及我看着 Telegram 群里的大佬们买各种 灵车 买的不亦乐乎,结果我看了半天什么都没买……不过也没什么实际意义啊,而且没那么可靠吧(?)

博客

今年的确没有写很多博文。大概我对这种东西的兴趣也开始下降了吧,主要感觉很多事情不是不值得写就是不适合写……

不过放心,我目前还没有关掉这博客的打算呢。

稍后打算把我的域名都转移到 Cloudflare 了;name.com 续费死贵。

以及想写个新的博客主题了,但是没什么头绪……

2020 年计划?

待定!计划赶不上变化的。

在树莓派(Raspbian)上安装最新稳定版 nginx

如果需要在 Raspbian 上安装最新稳定版 nginx,其实官方是 提供了 Debian 的 apt 源 的,但是并没有提供 armhf 的二进制文件。

于是,我们只好自己通过 Debian 的方式编译安装了。

此方法优点

  • 安装好的 nginx,配置文件路径、维护方法等与官方 Debian 版 nginx 一致
  • 如果需要卸载 nginx,只需执行 sudo apt purge nginx

步骤

1. 安装添加官方 apt 源前所需的包

sudo apt install curl gnupg2 ca-certificates lsb-release

2. 添加官方 apt 源

# 添加官方 apt 源。与官方说明不同的是,由于我们需要源代码,这里添加的是 deb-src 而不是 deb
echo "deb-src http://nginx.org/packages/debian `lsb_release -cs` nginx" | sudo tee /etc/apt/sources.list.d/nginx.list

# 添加 PGP 公钥
curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo apt-key add -
sudo apt-key fingerprint ABF5BD827BD9BF62

# 下载最新的软件包列表
sudo apt update

3. 安装 nginx 所需的依赖

sudo apt build-dep nginx

4. 下载 nginx 的源代码

# 建议单独建立文件夹来存放此次 nginx 编译所需文件,因为你的工作目录会多出若干文件
mkdir nginx_build
cd nginx_build

# 下载源代码
apt source nginx

5. 编译安装

此时我们检查目录下有什么文件:

drwxr-xr-x 10 tcdw tcdw    4096 Nov  5 03:20  nginx-1.16.1
-rw-r--r--  1 tcdw tcdw  114248 Aug 13 17:17  nginx_1.16.1-1~buster.debian.tar.xz
-rw-r--r--  1 tcdw tcdw    1510 Aug 13 17:17  nginx_1.16.1-1~buster.dsc
-rw-r--r--  1 tcdw tcdw 1032630 Aug 13 17:17  nginx_1.16.1.orig.tar.gz

由此可见,此次我们需要先进入 nginx-1.16.1 文件夹。实际文件夹名称可能会由于版本更新而与本文不一致,但应该只有那一个文件夹,且所有相关文件 / 文件夹均以 nginx- 开头。

cd nginx-1.16.1

开始编译安装:

dpkg-buildpackage -uc -b

完成后,返回上一层目录,发现我们的目录中出现了 nginx_1.16.1-1~buster_armhf.deb 文件。我们现在可以开始安装了:

sudo dpkg -i nginx_1.16.1-1~buster_armhf.deb

大功告成!

nginx -V 的输出

升级新版本

重复第 4 - 5 步即可。

驾照

今天我考了科目四,一次过关。

我从 2017 年 6 月就在驾校报名了,但是因为我的拖延症和一些客观原因,我的战线延续了整整两年:

  • 科目一:2017 年 8 月
  • 科目二:2019 年 1 月
  • 科目三:2019 年 7 月
  • 科目四:今天(7 月 26 日)

太丢人了,同样是 2017 年参加完高考的学生,我居然在两年后的夏天才拿本(捂脸

不过结果实现了,我也就这样变成了一位有证司机了,还是可喜可贺的。

宿舍用小型 UPS 电源与新台灯

去年 11 月,我购买了一部二手的联想手机,把自己的电信米粉卡塞了进去,用来运行 Telegram SMS 和开热点。这部手机其实一切都好,但是有个问题:电池不耐用。因为我们会定时熄灯,导致手机并不能 24 小时充电,因此:

  • 如果一直开热点,第二天就没电了。
  • 如果按需开热点可以解决,但这部手机开热点的过程很麻烦,需要进入多级菜单。

我其实还考虑过使用充电宝,但是:

  • 我的品胜充电宝,如果重新插入电源,那么充电过程就会中止,必须手工再启动一次。
  • 我的紫米充电宝,尽管可以即插即用,但是如果在断电的情况下手机充满电,那么就会自动停止充电。
  • 我的 Anker 充电宝,容量太小了(只有 5000 毫安)。

虽然我理解这些充电宝的设计是有道理的,但显然不能满足我的需求。几个月以后,忍无可忍的我决定试试传说中的那种适合宿舍用路由器的 UPS。

几天以后,UPS 到了。

UPS 的外包装 正面 背面

我选购的这款 UPS 可同时提供 USB、5V 和 9V / 12V 输出(可以通过开关切换 9V / 12V);最大输出电流为 2A(所有端口总和)。

同时,它使用 DC 输入,非常节约空间。

我把它接上了我的手机,让这部 UPS 为我的手机持续供电。效果还是不错的:我同时开着 Telegram SMS、代理软件和热点,也不用担心手机中途断电啦。


不久以后,我们团队在一个省级比赛获得了一等奖,每个人都得到了这样的奖品:飞利浦台灯。

因为这部台灯使用 12V 输入,功耗很低,我便把它接入了我的 UPS。这样,即使熄灯也可以使用啦。

⇧ 旧的『宿舍神灯』。

⇧ 新的飞利浦台灯。从我的实际感觉看,光线还是比那部『宿舍神灯』要舒服的,而且还有四档调光,非常灵活。

总之,很开心获得这么一套装备升级。我可以变得更懒惰了!

20190612 更新:一加 3T

我的联想电池鼓包了,连盖子都盖不上了。考虑到这部联想的坑爹之处,我买了一部二手一加 3T 作为 Telegram SMS 服务器使用。

目前来看,它的电池并不是很坑爹,所以只需要用普通的 2A 电源定时充电即可。

于是这 UPS 的供电目标就只剩下那个台灯了。不过不知道未来还会发生什么…… 😅

phone spec

phone status

拆解我爸 2010 年装的廉价 PC

2010 年,我爸觉得我家的 P4 台式机太过时了,所以就花 2k 托人组装了一台新 PC。

然而这台 PC 不仅卡顿时常发生,而且非常不稳定,经常死机、蓝屏,期间我还被母上大人认为我是让那台 PC 不稳定的罪魁祸首,因为我安装的那么几款软件,结果我没少被母上大人数落。😒😒😒半年以后,那台机器突然无法开机,甚至不会 POST,然后我爸只好把同一位 JS 叫过来进行维修。然后,JS 把那块烧掉的双敏主板换掉了。

2011 年,我爸给我买了一块 1TB 的希捷移动硬盘,但是在那台机器上带!不!动!,必须连接外置电源才能正常启动。

2013 年,因为母上大人实在不能忍受那台 PC,所以购买了一台戴尔整机(经过我的几次硬件升级,目前还在我家服役中)。而那台廉价 PC 就拿到我爸那边做监控机,结果半年以后硬盘就完蛋了,SMART 状态为不良,0x05 爆表。随后,我舅舅又拿来一块闲置 80 GB 硬盘,在我爸那边做工控设备,直到 2018 年 7 月彻底完蛋,甚至不再 POST 了。然后那台机器就在仓库里又放了半年。

2019 年,我把那台 PC 从我爸仓库搬了回来,决定看看还有没有什么利用价值。

做工廉价的机箱

机箱

这个机箱从外面看还像是那么回事,但是轻飘飘的,而且金属部分甚至会划手。

不过我丢掉了这个机箱才想起来忘了拍照了。

前面板

做工粗糙,而且接线的地方是用热胶固定的……什么鬼……

机箱内部

机箱内部

散发着寒意而且相当脏乱差的内部。

CPU

安装在主板上的 CPU 和 768 MB DDR2 内存。CPU 与风扇之间的硅脂所剩无几的样子,不知道是当年给我爸装机的那 JS 敷衍了事,还是发生了什么黑魔法。

CPU 本体

AMD Athlon 7750。这台机器是 2010 年装的,但是据 @qwe7002 说,这片 CPU 在 2008 年就属于比较慢的了。

擦干净的主板

擦干净的主板,是 ASUS M2N68-AM PLUS。也是低端板子呢。

光驱

PHILIPS SPD2213,据说是市场上最便宜的光驱。

大炸弹

电源参数

嗯,来自惠州高级工厂的产物。这台机器的不稳定,一定程度上就是这个大炸弹搞得鬼了(包括 USB 供电不足的问题)。

顺便这个序列号其实没什么卵用,因为看起来他家所有产品都用这个序列号。

电源内部

电源内部

电源内部

于是我把大炸弹的风扇拆了出来,发现了绿色的奇怪地方。那是做什么用的呢?

电源内部

原来特么是用来遮盖固定两组线缆的部分!看起来这玩意还是从奇怪的电子垃圾上迫真剪下来的风扇,然后这样凑合固定一下,就装到电源里面了。

结论

这台机器,最后除了两根 SATA 线被我拆了出来,被我原封不动的扔到垃圾桶了。

对于我们这种装机小白,看起来还是买品牌整机比较好。虽然性价比不是那么高,但最起码很靠谱啊。

以及便宜没好货。

我的 2018

好耶,我终于把 2018 年瞎鸡巴过完了!让我们看看究竟发生了什么吧。

新增项目

大学以后,自由的时间越来越多啦,于是自己糊了一些奇怪的项目:

以及其它一些奇怪的多人合作和为别人糊的东西(小声

非主流氪金(?

没想到曾经作为网易云音乐、Spotify 等串流服务的忠实用户的我,居然在 qwe 前辈的影响下,开始买专辑听了。其中实体专辑占了相当一部分,而且基本上都是捡垃圾

同时终于意识到,听专辑其实是另外一种完全不同的听音乐的方式。一张专辑里面的曲目安排等等,按顺序听下来其实相当有趣的!而串流服务上的那些歌单,却把这些有趣的地方都掩盖了,就相当尴尬了(

以及

毕竟我还是相信那句话,要是真的喜欢的话,就花钱支持他们吧。(ref)

(我知道我的数字收藏还是比大佬们差远了 QAQ

设备的变迁

2018 年初,我给我家安装了网件的 GS308 千兆交换机,还买了块 2TB 的东芝监控盘和硬盘盒,用一部 NUC 搞起了自家的云存储服务。

然后我意识到我其实并不需要那么多存储……直到 2018 年底,我只使用了不到 200GB。毕竟以前很多垃圾文件盒所谓资源塞满了我的移动硬盘什么的,删除它们其实就没啥了。于是大概很长时间都不用担心存储空间不足的问题了。

蛤,你说那种网盘?我现在都几乎不用了(

以及手机的话,我的主力开始转向一部 iPhone 6S Plus 了(虽然是我爸淘汰的,他换了部高级黑色 oppo)。一个原因嘛,就是我跟 macOS 进行互联互通,不至于那么胶水化。以及 iOS 真的是不折腾党的福音啊!

杭州、上海、厦门

今年暑假,我做了件相当作死的事情:一个人去杭州、上海和厦门瞎几把玩。

(TODO:添加相关图片)

结果当然是不尽如人意的:我在杭州的最后一天中暑了,然后在上海的酒店休息了半天,最后潦草的去了趟外滩,隔着河观赏了东方明珠塔;同时中暑的其它后遗症一直持续到我的厦门行程,那是相当尴尬了……

不过其实游玩景点并不是重点。在这期间还是和许多在网络上认识的、从未谋面的人见了面,感觉相当开心!qwe7002、Librazy、TonyPrince、SJoshua、rsqppp、Jack Works、M0xkLurk3r,以及其它最终因故没有与我会面的朋友们,感谢你们的支持!

结果我关于美丽的景色毫无印象,却对与沙雕网友的交流印象深刻

杂项成就

  • 通过了英语四级考试

已知问题

  • 瞎几把花钱导致自己山穷水尽(比如 Bandcamp 上的音乐和各种蜜汁转接头
  • 我用的 MacBook Pro 没有 Touch Bar(因此也没有另外两个雷电 3 接口,导致我的外设设定是相当的 clunky 啊。。后悔死了,但我当时真的糊涂了
  • 对一些底层的事物了解不深
  • 很多东西只会瞎几把做,然后搞得一塌糊涂

2019 年要做点啥?

  • 把 Pomment 项目写到能够发布的程度
  • 去更多的城市旅行,包括但不限于南京 / 福州 / 广州 / 成都等等
  • 停止瞎氪金,买一些重要的东西:
    • Nintendo Switch
    • Hades Canyon
    • 2019 款 15 寸 MacBook Pro
    • 一个还行的 1080p 或 2k 显示器
  • 通过科目二考试
  • 把三次元的其它事情处理好

啊,差不多就这些了。

2019 年,大家要一起努力哦。

2018 厦门面基记

7 月 20 日,我乘坐动车从上海虹桥到了厦门北。

qwe7002Librazy 在厦门北站接了我,带着我乘坐厦门地铁到了岛内。

当天晚上,我们和 TonyPrince、SJoshua 和 Lafit 进行了愉快的聚餐。

在 qwe7002 的劝说下,终于尝试了一下海鲜,感觉好棒

第二天和第三天,我们上午在鹭江道星巴克谈笑风生,下午瞎玩,晚上在厦大学生公寓附近继续谈笑风生,同时玩基岩版 Minecraft。

我们陆续游览了世茂海峡大厦、厦门大学和鼓浪屿。芙蓉隧道超有趣的!

qwe7002 带着我和 Librazy 瞎转,去了各种未必能去的景点。真是令人难忘的景色!

由于机票时间等原因,我在 22 号晚上与 qwe7002 和 Librazy 告了别,在 23 号抱着恋恋不舍的心情独自回到了呼和浩特。

这次厦门之旅真是太棒了,特别是有了 qwe7002 和 Librazy 的陪伴。

我们后会有期!

好耶,是 qwe 和老赖

我(曾经)设想过的外置智能大脑

因为一些我不知道的原因,我感觉我情商是负的,在社交等场合常常遇到挫折。

我感觉我的大脑缺乏其它人在情感与社交方面的硬件加速。因此,我一直设想一款外置大脑,可以帮助我解决现实生活中的实际问题,令我的情商提升 -200%。

  • 通过机器学习,预测现实生活中我说出不同话语导致的不同结果,从而让矛盾最小化,生活顺利率和声誉最大化
  • 通过大数据技术:
  • 通过 AI,帮助我自动喷人,对抗那些喷子
  • 通过区块链技术记录下那些欺负我的人
  • 通过机器学习,制订出最好的计划

这样,我的大脑就省去了很多负担,我可以安心的写垃圾 JavaScript 代码了!

(未完待续

没用的无懈可击

近年来,我终于意识到为什么我总是不开心,因为我试图让自己无懈可击。

上初中的时候,我所在的班级管理严格,如果出现违反班规的情况,那么就会有惩罚。最常见的就是罚抄课文。

因为我初中的时候就被我同学在暗处捅过很多刀子,向负责罚抄课文的班干部举报我,本来无关紧要的小细节被炒成一件严重的问题(比如忘了关灯、迟到不到一分钟但老师都还没来),作为惩罚,我不得不常常一两点才睡觉。

天真的我以为,只要我做的更好,那么他们就不会惩罚我了。但我并不是计算机那样的东西,我还是偶尔会犯错误,被那些平时笑脸相迎的同学找到把柄,被拿去告状。(其实我们并没有那种告状就有奖励的机制,但也许他们告状的动机是为了看我被惩罚来获得愉悦感?不过我跟他们表面上的关系都不算很好。)

我找老师说他们老告我状,然而我们老师总会说类似『为什么人家不告别人就告你,肯定是你自己有问题』。我很难受但是无语。

于是,我开始试图让自己变得无懈可击,来确保人家根本抓不到我的把柄。有一次我意识到自己要迟到,马路上全是车,还努力往学校跑,差点被一辆疾驰的车撞飞,我恰好闪了过去,侥幸逃过一劫。

但后来我每天很累的努力让自己什么问题都没有,却还是会被同学抓住我的把柄,然后我依然要接受惩罚,抄课文抄到一两点。

最后在母上大人的支持下,我公然拒绝抄课文,最后强行把事件捅到了班主任那边,我们班主任还和母上进行了一番谈话……

就这样,我再也没有抄过课文。

然而我努力让自己无懈可击的心理并没有就这样消除。从各个方面,我害怕别人批评我、害怕我做的有一点不完美、害怕哪一天又有人从很小的细节抓住我的把柄,然后强行上升到某种高度,把我打压下去。于是总是习惯性的把自己搞的很疲惫,直到我有理由感叹『嗯,别人肯定找不到理由抓我把柄』了。

tcdw 与 ECMAScript

其实 tcdw 小时候并没有对计算机特别感兴趣,tcdw 曾经的梦想是当一名建筑设计师。但是,因为在 tcdw 很小的时候家里就有了一台电脑,tcdw 还是玩了很多奇怪的东西,包括 Frontpage 2003。

那时 tcdw 出于好玩的目的,设计了一个又一个版本的个人网站框架,除了内容。 tcdw 还设计了一套自己的『前端样式库』(其实只是在 Frontpage 的 CSS 选项菜单里把所有 HTML 标签都那么定义了一下样式)。

然而 tcdw 发现自己的网站总是缺点什么。好奇的 tcdw 通过右键点击来观察,终于发现了有一种神奇的东西叫 Flash。2006 年, tcdw 的舅舅送给 tcdw 一张 Macromedia MX 全家桶光盘、一张网页素材大全 DVD,还有一本 Flash MX 教材。于是 tcdw 便很开心的开始制作奇怪的 Flash,并嵌入到自己做的网页里。后来 tcdw 又缠着母上大人买了一本《精通 Flash 8》和一张 Flash 8 的盗版 CD。有了滤镜特性,tcdw 做的更来劲了。

2007 年,tcdw 从《小读者》杂志注意到袁日涉拥有自己的环保网站,tcdw 很羡慕,便进去看了看。当 tcdw 看到她在 2006 年的获奖作品《从纸到树的生态研究》里的 Flash 导航菜单,tcdw 很诧异:为什么那个菜单背景可以跟着鼠标移动,而且还会缓动变色?

虽然那本书上已经讲了一些 ActionScript,tcdw 也能照着书上的例子使用 gotoAndPlay 之类的 API 控制播放,但是那两本书讲的非常粗略,因为都是主要讲 Flash 动画制作,而不是 Flash 编程。

后来 tcdw 摸爬滚打的求助度娘,终于发现有一家网站叫闪吧,那边有大量的 Flash 资源。tcdw 如饥似渴的下载了大量的 Flash 源文件,观察它们是如何运作的。这时 tcdw 才发现,原来 ActionScript 可以写的更加复杂!

于是,tcdw 照葫芦画瓢的尝试,凭借小学学的那点英语判断各种关键词的意图,初步建立了编程的概念。而当时 tcdw 也发现了有一款软件叫网页特效制作专家,里面集合了大量的网页特效。因为它们的本质都是 JavaScript,tcdw 也开始靠着复制粘贴奇怪的代码片段,试着拼凑一些不需要 Flash 的网页特效,结果还很成功。不过因为 HTML 4 的表现力太有限了(而且 tcdw 还在用 Frontpage 2003 + IE 6 的坑爹组合),所以 tcdw 的重心依然在 Flash 上。

闪吧投稿的质量也是参差不齐。因为很多投稿人本身对编程也没有太深入的研究,只是用一些意大利面代码完成一些交互性的东西,以至于他们写的代码就有很多逻辑缺陷,而 tcdw 盲目学习他们,对自己的编程思维产生了很多负面影响。

2008 年,tcdw 为 杨静 翻唱的《雪人》做了一个 Flash MV。那是 tcdw 当时做过的最复杂的 Flash 了,不仅用到了很多 Flash 元件,还使用了 tcdw 学到的 ActionScript 实现了雪花特效以及一个很中二的『拖动光盘到标题文字即可开始播放』的功能(用到了 hitTest 这个 API)。做完以后,得到了父母的赞扬,还在小学的多媒体设备上,向全班同学放了一遍。那是 tcdw 最信心满满的时刻之一了。

闪吧投稿

到了 2010 年,tcdw 感到自己的 Flash 制作已经有了套路,而且有些东西看起来比闪吧上已有的一些源文件(包括粗制滥造的、从同一个教程复制粘贴的)质量还要好。那时,tcdw 做过的最复杂的 Flash 作品是一个交互性比较高的音乐播放器,可以调节进度、音量、播放列表,还有 LRC 展示,以及 没有完工的、基于搜搜的在线搜索音乐并添加到当前播放列表的功能(换成 2018 年应该是基于网易云音乐的吧)。不过遗憾的是,这个播放器在 2010 年的一次 U 盘故障中丢失了。

于是,tcdw 大胆的尝试往闪吧投稿。tcdw 做的一个中看不中用的光影特效、俩鼠标特效和一个 Flash 调色板都审核通过了。 tcdw 在自己的作品介绍里都留下了 QQ 号,然后还真的有不少人加 tcdw 好友,而且大概有 30 多人吧。于是 tcdw 还建了一个 QQ 群,叫『闪客交流』。

每当有人问 tcdw 问题,tcdw 一般出于保持脸面的考虑,会凑合回答一下;tcdw 也遇到过一大堆伸手党,这时 tcdw 就很高冷的说:『闪客教学,收费。』然后他们就走了(没人真的来付费希望 tcdw 教他们

其实 tcdw 也不是没有接纳过伸手党的所谓任务。有一个伸手党希望 tcdw 帮忙做一下他的 Flash 作业, tcdw 当时傻乎乎的同意了。但 tcdw 后来发现他的那个作业还很难做,而 tcdw 还没在人家的 deadline 以前搞定。 tcdw 就想:『我凭什么要为他做作业?』

想到这里, tcdw 便告诉他:『你的作业我不做了。』因为 tcdw 当时的态度也不得当,人家便进 tcdw 的 QQ 空间大骂 tcdw 是骗子云云(当然那些评论都被 tcdw 删除了)。后来吸取了教训,就再也没有承担过那种一上来就求做作业的伸手党的任务。

牛逼的 QQ 空间红人

从闪吧过来加 tcdw QQ 的人,有两位开了很多钻,QQ 空间里面全是 Flash,看起来特别炫酷,还标榜是『blahblah 的腾讯博客』、『网络红人』什么的, tcdw 羡慕死了,为什么 tcdw 的同学开了黄钻,却还在用官方那几套模板?如果做一套很炫酷的 QQ 空间模板,一定会受到周围的人羡慕、尊敬啊!

当时 tcdw 也开通了自己的个人网站,用的是 5944 的免费空间,而且访问地址更新事宜已经在 tcdw 的 QQ 空间公告了好几次(虽然大概没人真的看过)。于是, tcdw 把专门为自己 QQ 空间做的 Flash 传到了自己的免费空间,再在 QQ 空间引用自己做好的 Flash。果然,有很多『萌新』开始加 tcdw 的 QQ 了。

其实 tcdw 当时做的 Flash 并没有什么技术含量:把一些非主流的元素加进去,用 tcdw 小时候学会的 Flash 动画制作方法把它们耦合在一起,再用意大利面的 ActionScript 代码让它们做点什么交互性的事情,最后把 Flash 糊上 QQ 空间就是了。

当时还有一种叫 QCC 的东西非常火爆。其中有一家推出的 QCC 模板非常华丽,功能也很齐全(比如可以读取你的日志列表、显示最近访客等等),收费还记得是 60 元一年、100 元永久。但人家的反逆向工程还是可以的,用 Sothink SWF Decomplier 和 ASV2010 打开,程序都会崩溃。不过后来 tcdw 还是想办法把他们的那个音乐播放器元件结构逆向了出来,并填充了 tcdw 自己写的 ActionScript 代码,实现了一致的功能。

至于空间日志展示等功能, tcdw 也解决了。2010 年的某一期《电脑爱好者》杂志,有篇文章安利了 Chrome 浏览器,表示你可以用里面的开发者工具做很多事情,比如通过录制 Network 动态来下载 FLV 视频。于是 tcdw 下载了 Chrome 浏览器,通过观察 Network 的动态,发掘了很多 tcdw 梦寐以求的 QQ 空间私有 API。然后 tcdw 通过 split 大法解析 JSON,实现了各种付费 QCC 才有的高级功能,吸引了好多人的目光(其实最多也就几十人)。同时 tcdw 发现 Chrome 浏览器在他爸花 2k 装的坑爹 Windows XP 台式机运行非常快,于是 tcdw 便从 IE 6 / 8 用户变成了一名 Chrome 用户,直到今天。

原来 Flash 已经开始被人讨厌了呢

然而好景不长,当 tcdw 在制作功能更高级的 QQ 空间 Flash 时,腾讯突然宣布:为了防止有人通过 Flash 盗取 QQ 密码,从 2011 年 8 月起只允许在白名单列表的网站的 Flash 被插入到 QQ 空间首页。

那还玩个屁啊!只见各种 QCC 卖家和 QQ 空间 FD 模块作者哭天喊地,希望 QQ 空间能够解除这种限制,但是并没有什么用。期间虽然有人发现了一些绕过方法,但是后来都被封堵了。再后来 QQ 空间推出 6.0 版,以前的一大堆套路全失效了。tcdw 也是在那时渐渐跟 tcdw 认识的那群闪客疏远,最后在一两年前的一次 QQ 好友清理中被 tcdw 删除了。

没有 Flash 的 QQ 空间是无聊的。再往后,tcdw 觉得 QQ 空间没什么好玩的,就不再续费黄钻。tcdw 的黄钻等级从 7 级一路下滑到 1 级了。

2011 年,tcdw 恰好通过 ZYH 的 NES 模拟器『ZYH Emulator』知道了百度超级马里奥吧,后来又找到了超吧大水库吧,然后认识了 Jixun。Jixun 菊苣是一个很厉害的人,不仅帮助 tcdw 修改过很多意大利面代码,还让 tcdw 知道了 HTML5、GitHub、响应式页面等很多新奇的东西,让 tcdw 大开眼界。

但在接下来几年里,由于学业原因,tcdw 的 JavaScript 学习就停滞了。虽然期间 tcdw 利用 HTML5 API 做过一些类似 HTML5 音乐播放器之类的东西,但是他的编程思维还是小时候的那套。

吐槽大王与主流 JavaScript

2014 年底,tcdw 在百度免费空间吧 QQ 群看到有人发布了 Hostker 的链接。tcdw 进去一看,发现跟自己熟悉的那种虚拟主机官网很不一样,看上去非常亲和。在示例用户那一栏,tcdw 看到了 卜卜口 的博客,点进去一看,发现样式很新奇,页面加载速度也很快。tcdw 试图查看源文件,却发现只有几个看似单薄的 js 文件。后来通过体验他的 妹 blog,tcdw 第一次拥有了关于 SPA 的概念。

后来,tcdw 在一次偶然的机会认识了 佳佳酱,进而认识了一群有趣的人。tcdw 从他们那里知道了 Node.js,便信心满满的开始编写一些会读取文件的、看起来很正经的东西。然后 tcdw 便发现,自己写的代码不符合他的期望:

var fs = require("fs");
var str;
fs.readFile("1.txt", {encoding: "utf8"}, function(err, txt){
    if (err) throw err;
    console.log("文件读取成功!");
    str = txt;
});
console.log(str);

tcdw 想:我是想先输出『文件读取成功』再输出 1.txt 的内容啊,为什么是先输出 undefined 再输出『文件读取成功』?tcdw 很困惑,他上网搜索答案,知道了这是因为 JavaScript 的异步处理方式导致的,但是一大堆文章都没有给出较好的解决方案。后来,tcdw 便改用了 fs.readFileSync 来读取他的文件,实现了他期望的那种顺序执行。

但后来 tcdw 想使用 superagent 库来读取网络的内容来帮他不断在 SMW Central 的某活动中刷帖,但那个库并没有提供同步方法。于是,tcdw 想到了一个馊主意来实现他期望的同步执行,其实就是 Callback Hell

tcdw 还通过 setTimeout 来延时,并递归调用请求函数,试图实现不间断定时刷帖。但 tcdw 的程序运行了没多久,就因为 Maximum call stack size exceeded 错误崩溃了。那时的 tcdw 想不到更好的办法,就只好在他的 VPS 上设置了一个 cron 任务,每半分钟重新调用一次脚本来发帖。

问题是解决了,但 tcdw 渴望知道究竟该如何玩转 JavaScript 的异步编程,于是通过不断努力,终于搞明白了这些奇怪的事情。

好耶,是 ES2015

直到 2017 年,tcdw 终于结束了高考。tcdw 终于开始系统性的学习 ES2015,不仅学到了很多已经存在几年的新特性,还拥有了新的思维。

同时,tcdw 终于开始大胆尝试接触以前他觉得很复杂、难以搞懂的东西,比如 webpack、rollup 之类的打包工具。与此同时,tcdw 深入的学习了一些更加抽象的知识,比如 DOM 和面向对象思维。总之大学以来的一年是 tcdw 进步最大的一年,算是可喜可贺吧。

总结

由此可见,tcdw 走的这条路其实非常不科学,以至于 tcdw 尽管很小就接触编程,但不仅没有取得骄人的成绩,还跟很多同龄菊苣的水平差距巨大。tcdw 还残留了很多古董的编程思想,以至于很多人看到 tcdw 写的代码都感到有些莫名其妙。

不过那些事情都过去了,而且我距离毕业还有几年。于是对于弥补一些事情来说,还是来得及的吧。

评论框

经过两个月的不懈努力,我填了我去年给自己挖的评论框 大坑 的一部分。

目前,我的博客已经开始使用我自己写的评论框了。目前基本完成了后端、访客前端、Telegram 提醒姬和 Disqus 导入工具的开发工作,但是管理面板还没有开工(于是我只有借助 cURL 来管理评论了

在这里要特别感谢一下 qwe7002老宋Jixun 姐姐,他们对 Pomment 的程序设计和实现提出了重要的建议。

总之非常开心用上自己写的评论系统。

如何提出计算机相关问题(更新)

我发现很多计算机相关问题解决速度不够快,或者进度很快夭折的原因,是他们不会合理地提出计算机相关问题,不会合理地尝试解决问题。

有时他们试图在互联网上提问,但因为上述原因,他们往往会被潦草地告知先去阅读《提问的智慧》(或者 RTFM),或者被直接无视。虽然说这里面确实存在着不知感恩的伸手党,但也有很多人只是心急火燎地试图解决问题,因为上述原因走到了无可奈何的地步。

我看到这种场景一次次发生,顿生怜悯之心,决定撰写这篇博文。

先看看软件的官方说明

对于一些常见的错误,软件官方说明一般会以 FAQ(常见问题)的形式给出解决方法,看一看往往很有用。

尝试自行搜索

其实很多计算机问题,你往往不是第一个遇到的(特别是开源软件,一些问题往往可以在它们的 GitHub issue 中看到)。网络上的解决方案,也许比你询问别人得到的更加准确。

不要认为别人知道的跟你知道的一样

「这个 APP 为什么在我手机上闪退啊?」

这个问题看起来很简单,但是对于别人来说很难:你的手机是什么品牌的?配置如何?Android 版本是哪个?系统被厂商魔改程度如何?等等。

因此,提出计算机问题(特别是在网络社区提问),一定要考虑下这个问题,并提供必要的信息(你的系统版本等等)。

相信软件给出的提示

「你的计算机尚未安装 Java,请访问 https://www.java.com 下载 Java。」

嗯,软件已经给出了足够的建议,但是小明直接把这个提示截屏下来,去群里问如何解决这个问题。Geek 们会感觉不可思议:软件都提示你去下载 Java 了啊!小明听说以后,就去下载 Java,问题解决了。

小明为什么会无视软件提示,其实很大的一个原因就是很多用户对软件提示的盲目不信任。有时系统提示「请与系统管理员联系」,但是这是我家的电脑,系统管理员是谁啊?同时,某些国产软件通过恐吓等方式诱使用户去添加启动项等等,而用户反感这种流氓软件,不信任这些软件提示。

其实来自负责任的大厂的软件、「民间」的很多免费软件和开源软件的提示都很负责,照着它们去做其实是解决问题的第一途径。

先了解这是什么

看到一个陌生的格式,你只知道这是图片或者音频,你有转换成熟悉的格式的想法。请控制住你的这种想法,先去了解这个陌生格式到底是什么、它与其它格式有什么不同。

说到这一点,我必须提一下我的一个失败例子:很久以前,我拿到 m4a 格式的音乐,我甚至没有去了解 m4a 是什么,就直接寻求把 m4a 转换成 mp3 的方法。那时哪怕我查一下 m4a 与 mp3 的区别,也许就会改变我的想法,直接去下个好一点的音乐播放器,避免浪费大量转换格式消耗的时间,还毫无意义地降低音质。

合理提供日志

小明想用 MultiMC 启动器启动 Minecraft,结果启动不成功,软件弹出一个窗口,显示了出现的错误详情。于是,小明就把错误窗口截图,在群里提问。但是,由于他的窗口并没有显示足够完整的日志,问题无法继续解决。

也许他看不懂这些日志里到底说明了什么,便觉得屏幕上显示出来的就够了。这种情况下,最好把当时运行时出现的完整错误日志发上去,并说明你想做什么,然后又发生了什么。

顺便提醒一下,大段日志请截图,哪怕是拍照也可以。总之不要直接将文本贴到论坛上,甚至是聊天窗口里!被这样刷屏是很不爽的!

提升古诗词背诵及脸部记忆能力

古诗词背诵与脸部记忆,对 tcdw 来说一直是个老大难的事情。

如果说在初一的时候,tcdw 还能在母上大人的呵斥下勉强背诵《木兰辞》,在高三时 tcdw 已经什么古诗词都背不下来了。

那些古诗词,tcdw 翻来覆去的试图记忆很多遍,但是一合上书,那古诗词瞬间从他的脑海中蒸发,一打开书又神奇的回来了。

tcdw 也记不住别人的脸部。tcdw 所在的专业人数就不算多,每当遇到集体活动(比如升旗),tcdw 就不知所措了,因为他永远没法自己找到他们的位置。于是,在类似活动,tcdw 只好四处随意走动,直到被同学们发现。

其实 tcdw 的记忆力本身大概没问题。地点、网址、事件什么的,他都有很好的记忆力。但是面对古诗词与别人的脸,tcdw 就抓瞎了。

tcdw 好希望通过记忆地点、网址、事件的渠道记住古诗词与别人的脸。比如,当我和最好的朋友在一起聊天,他说了一句古诗词,让我感到甜蜜,产生了深刻的印象;各个地点的建筑物,看起来就像我需要记忆的人脸一样,我便可以产生印象了。

然而现实是残酷的,这应该只是天方夜谭。

tcdw 急迫希望记忆他很难记住的,却很好的记住了其它一些奇怪的东西。于是 tcdw 也常常被批评:『背诵这么简单的事情,你多做不了啊』。好吧,那你为什么历史学的那么好,但是化学学的一塌糊涂呢?

那些对于别人来说很简单的事情,然而 tcdw 很难做到,这种体验也许是别人难以理解的吧。

一袋凉皮引发的『血案』

我们都知道 XZ Premium 是防水溅的,而 tcdw 有时也会直接用水冲洗一下他的 XZ Premium,还在同学面前炫耀一番。

在一个天气还凑合的星期日下午,tcdw 回到了学校,舍友给 tcdw 所在寝室每个人都买了一袋凉皮,每袋凉皮都有附带辣椒油。

这些凉皮是装在塑料袋里的,包括辣椒油和蔬菜包。

tcdw 准备干掉这些凉皮,便把塑料袋打开,把凉皮倒进了饭盒。但是,辣椒油也是在塑料袋里的。

为了把辣椒油倒出来,tcdw 拿出剪刀,把塑料袋剪开了一个口,但没想到口剪得太大了,里面的辣椒油洒到了 tcdw 的桌子上。

辣椒油

tcdw 很慌张,他的手机被洒了,抽屉里的东西也被洒了。他慌里慌张的拿出纸巾、洗洁精和抹布,努力进行清理。

tcdw 的 XZ Premium 上全是辣椒油,为了清理的更干净一些,他把他的手机用洗洁精洗了一遍。

在报销了 14 瓶洗洁精和一大堆纸巾以后,终于把辣椒油清理干净了,味道也闻不到了。


就在前几天,tcdw 跟 qwe7002 聊天,谈到了副卡的事情,于是 tcdw 被安利买了一张米粉卡。

第二天,tcdw 的米粉卡到货了,他兴高采烈地给 XZ Premium 装上了米粉卡,然后上网冲浪。回去以后,他感到手机上全是汗渍,便在水龙头下又冲洗了一遍。

晚上,tcdw 绝望的发现,他的 XZ Premium 出现了奇怪的亮斑。

奇怪的亮斑

tcdw 瑟瑟发抖,赶紧跟 Telegram 群里的大佬们讨论,最后得出结论,大概是防水盖没弄好,或者防水胶出问题了。

于是,tcdw 只好把他的 XZ Premium 寄到了北京,开始返厂维修之路。

在此期间,tcdw 的舍友借给 tcdw 一部 vivo Y29L。这部手机小巧美观,内置了 Google 框架,但是 RAM 只有 1G,连正在播放的音乐播放器都有可能会自杀,而且系统是 Android 4.4 的。然而没办法啊,有舍友愿意借 tcdw 手机已经很不错了。

对 tcdw 来说,真是自作自受。

为了你的生活和谐,请确保在第一步做的事情就很靠谱,这样以后可能的不靠谱的事情就没机会做了(

5 月 13 日更新

经过一个月的漫长等待,tcdw 终于拿到了修好的 XZ Premium。于是,除了底部的磨损,这看起来就是部全新的机器了,因为整个屏幕和外框都被换掉了。

tcdw 终于把上个月买的 XZ Premium 贴膜和保护套用上了。但是这钢化膜居然左右两侧怎么也贴合不住,于是就有了奇怪的两边。。先这样吧 🌚

总之手机最后修好了,还是可喜可贺的。

修好的手机

修好的手机

我写的一言轮播脚本(2018 更新)

看到很多 ACG 博客都用上了一言,但我觉得在同一个页面只显示一条内容未免有些单调,所以想让它定时轮播,而且采用较好的切换效果。

我后来在 WNJXYK 上看到了我想要的轮播效果,但那是基于 jQuery 制作的。

因为对于我这种讨厌为一个小功能放上大型前端库的人来说不能忍,所以我用原生 JS 写了一个。

2018 年,为了获得更佳的性能和稳定性,我把这个脚本进行了重写,从 JavaScript 动画更改为 CSS 渐变,并改掉了一些不科学的地方。

下面是实际效果:

如果你在使用 RSS 阅读器,请访问原文查看效果。

HTML 部分

<span id="hitokoto"></span>

JavaScript 部分

(function() {
    var server = 'https://api.lwl12.com/hitokoto/v1';
    var target = document.getElementById('hitokoto');
    var fadeDur = 1;
    var waitDur = 10;
    var errorMsg = '加载出现了问题!';
    /* -=-=-=-=-=-=-=-=-=-=-=- 你的配置部分结束 -=-=-=-=-=-=-=-=-=-=-=- */
    var first = true;
    var present = '';
    target.style.opacity = 0;
    target.style.transition = 'opacity ' + fadeDur + 's';
    function loadData() {
        var connect = new XMLHttpRequest();
        connect.open('GET', server, true);
        connect.onload = function() {
            if (connect.status >= 200 && connect.status < 400) {
                present = connect.responseText;
                if (first) {
                    first = false;
                    target.textContent = present;
                    fadeIn();
                } else {
                    fadeOut();
                }
            } else {
                  target.style.opacity = 1;
                  target.textContent = errorMsg;
                  setTimeout(loadData, (fadeDur + waitDur) * 1000);
            }
        }
        connect.onerror = function() {
            target.textContent = errorMsg;
            setTimeout(loadData, (fadeDur + waitDur) * 1000);
        }
        connect.send();
    }
    function fadeOut() {
        target.style.opacity = 0;
        setTimeout(function() {
            target.textContent = present;
            fadeIn();
        }, fadeDur * 1000);
    }
    function fadeIn() {
        target.style.opacity = 1;
        setTimeout(loadData, (fadeDur + waitDur) * 1000);
    }
    loadData();
})();

土豆评论

2018 年 5 月更新:它开发完成了。


我想了想,决定捡起这个被我在去年弃掉的坑,并重新设计、编写相关代码,确保它有着更加科学的设计。撸起袖子加油干!

背景

Disqus 在中国大陆已经不能用很久了,好气啊。

使用 Disqus 的那些博客,往往会考虑换成多说、ISSO、原生或者一些小众评论框服务,或者直接拿 Github issues 托管评论,也有人(比如我)想:来我博客的人应该都会翻墙吧。

直到有一天,某人问我:

你博客的评论是怎么回事儿呀?好像自己没有看见回复按钮。

看来必须找个替代服务了,要不然我的一些(并不经常翻墙的)朋友没法往我的博客发布评论啊。

多说

多说就算了吧。

  • 抛开国产服务的通用问题不谈,首先它的 HTTPS 支持实在太不正经(博客一定会出现 Mixed Content),还要自己搭个 HTTPS 反代服务器来实现小绿锁。
  • 它的评论提醒也不正经到爆,除了浮窗以外似乎没有别的方法查看评论动态,有个邮件提醒也是摆设。
  • 只能通过社交网络登录,授权会过期,而且还会很烦人地提醒你去延期。
  • 多说带来的垃圾评论比原生 Wordpress 还多,太烦人。
  • 网站界面水平大概是 Disqus 的零头。

更新:多说下线了。

小众评论框

这也是我的考虑之一,但我后来放弃了。

  • 友言:看上去很久没有更新了,而且 UI 也没比多说好到哪去。更重要的是,它不支持 HTTPS
  • LiveRe:看上去是个很棒的评论服务,而且完整支持 HTTPS,然而免费版功能少到可怜,甚至不支持评论导出。网站界面不是韩文就是别扭的中文,想看看专业版有哪些功能,介绍只有韩文版本。英文界面也比这样强啊!
  • 萌评论:功能比 LiveRe 还少,而且看上去极不可靠。虽然我知道偷揉是个很棒的前端开发者,但至少目前没法考虑。

至于 GitHub issues?首先你要有一个 GitHub 帐号,然后还要知道如何使用 GitHub issues …… 对于我这种所谓的技术宅问题是不大,但是对于圈子以外的朋友,门槛比翻墙用 Disqus 还高。

为了让我看起来是个真正的菊苣,我打算自己写一个(对我来说)最好的评论框服务!

于是 Pomment (Powerful Comment) 的故事就开始了。

Pomment 的计划特点?

  • 易于安装、更新
  • 采用多个 SQLite 数据库存储数据
  • 提供多种管理 API
  • 提供邮件提醒、webhook 支持
  • 前后端分离
  • ……

至于什么时候做好,也许是个谜(

我的 2017

好耶,我的 2017 结束了!

不可思议的

  • 我用着一部没有 root 的 Xperia XZ Premium,居然很开心
  • 我用上了曾经被我黑来黑去的 MacBook Pro,闭嘴了
  • 我家里的 Windows 台式机配置更平衡了,仅仅因为我增加了 RAM 和 SSD,不过比较残念的是主板上四个 SATA 口都是 SATA2 的其实有一个是 SATA3。M.2 SSD?我好穷啊
  • 买了一大堆奇怪的外设,其中一部分是为我 MacBook Pro 的妥协(Type-C),把我自己炸的不要不要的
  • 对三次元音乐大幅恢复兴趣
  • 突然变得通俗、接地气
  • 身份又增加了半吊子设计师
  • 我与我的舍友用着同样的手机、同样的 Type-C 转接头、同样的 U 盘
  • 在正版软件上砸了不少钱,基本实现了软件完全正版化
  • 本博客在 2017 年还活着
  • 解散了所有自己管理的 Telegram 群组,因为我真的不适合做社区管理员(

三次元的

  • 我度过了人生中最后一段高中时光,进入大学
  • 我有了很多三次元小伙伴
  • 第一次与网上的 dalao 面基
  • 做事被迫变得很麻利
  • 我开始主动寻求改变自己内心很奇怪的一面。也许以前我的三次元生活确实很惨,但它们都过去了,就试图从现在开始变得更好

Geeky 的

  • 开始第三次学习 JavaScript,阅读了《你不知道的 JavaScript》等书籍。曾经受某专家毒害停留在 IE6 时代的我终于用上了 ES7,还开始使用 gulp,eslint 这些看起来很高大上的东西。它们真的好好用啊,减少了很多不必要的付出
  • 开始尝试使用 PowerShell,主要是不希望继续拘泥于 Linux 的命令行
  • 第一次提交自己的程序到 NPM,分别是 Typecho Markdown 导出工具OneAnime.js
  • 用上了 qwe7002SilverBlog,当了一回小白鼠,并为他提出了很多建议,同时受到了其思想熏陶(
  • 系统分工更明确:家里的台式机跑着 Windows 10,我的 Macbook Pro 当然跑着 macOS,从闲鱼收的 二手 NUC 跑着 Debian testing,Orange Pi One 吃灰开始

瑟瑟发抖的

Flash Player 要在 2020 年 停止支持 了。虽然是在意料之中,知道它已经过时了,不过还是感觉很惋惜。如果不是小时候被舅舅安利 Flash 6,接触 ActionScript,也许我永远不会跳进码农这个大坑呢。

荣耀的

我被评为 SMW Central 2017 年 12 月的 Member of The Month。虽然这真的只是我的业余爱好,但这样还是很开心的。

总之

希望我的 2018 会更棒!

我与 Linux

貌似很多人都是一上来就学习使用桌面版 GNU/Linux 发行版(下面简称 Linux),但我的过程嘛……

Android

小时候的我也认为,世界上只有 Windows 这种桌面操作系统。

2012 年,我拥有了摩托罗拉 xt910。我想给我的 xt910 刷 MIUI V4,但我的 xt910 是国行的,使用国行底包直接刷进去会出现发热的问题。

相信给摩托罗拉手机刷过机的机油都知道,摩托罗拉有一种东西叫 BL 锁,而 xt910 还锁区,所以国行 xt910 用户正常情况下不能刷欧版或港版 xt910 的底包。有一种不完美的绕过限制的方法就是「清除 18 区」,需要用「终端模拟器」执行几个命令,然后刷 MIUI 和启动工具,用第三方的 2nd-init 启动使用欧版底包的 MIUI V4:

su
dd if=/dev/block/cid of=/sdcard/cid-backup.bin
dd if=/sdcard/cid-go.bin of=/dev/block/cid

那时的我超级爱折腾,反复执行这套命令多次,觉得每次都要输入 /sdcard 很繁琐,就开始反复求助度娘,了解如何切换到指定目录、如何列举目录……后来我发现「终端模拟器」实在太有用处了,进而知道使用 adb shell 可以在电脑访问到一样的终端。Android 的命令行,成了我当时最常使用的「Linux」。

当时我还用批处理调用 adb shell 和 fastboot,搞了个「国行 213 root 工具」,就是用 fastboot 刷入国行 145 固件的内核,再通过 adb root,最后刷入国行 213 固件的内核,把以前需要手工完成的步骤自动化了。我把它以 xt910 吧吧主名义发布到贴吧上,颇有成就感。

然而 Android 虽然基于 Linux 内核,但它毕竟跟真正的桌面版 Linux 发行版没法比啊。恰逢 2013 年我家采购电脑,一家三口都有了自己的电脑,我都有了自己的台式机,原来我家的旧台式机就被淘汰了。我就在我家的旧台式机上安装了 Ubuntu 13.04,开始尝试输入百度等地方搜到的各种命令。新鲜劲过后我绝望的发现,「真正的」Linux 一点也不好用(其实是我把它当成了一个不太好用的 Windows),要啥没啥,连 QQ 都是很久以前的。不久以后,我爸又把旧台式机搬走了。

至于我的 xt910,它已经过时了,再怎么刷机,也无法拯救它的衰老。后来我有了 Galaxy S5,xt910 就被我扔在抽屉里。因为 Galaxy S5 很好用(一直到今年夏天被淘汰),刷机也很方便,不再需要碰那些命令,Linux 什么的也被我抛到脑后。

CentOS

2014 年底,一次很偶然的机会,我认识了 佳佳酱,进而认识了一群有趣的人。我从他们那里知道了有种东西叫 Node.js,还有种博客系统叫 Ghost。当时的 Ghost 深深打动了我,但是传统的 PHP 虚拟主机是没法运行 Ghost 这样的 Node.js 应用程序的,于是我开始了解 VPS 这种神奇的东西,开始折腾圈子里很多人喜欢的 CentOS 6.5、LNMP 一键包之类的东西。我知道了 Linux 的磁盘挂载、权限控制、包管理器的使用等等知识,还有各种折腾 CentOS 的奇怪技巧(比如如何编译安装各种本来 Debian 和 Ubuntu 就有足够新的版本的软件)。不久以后,我把自己的博客真的 迁移到了 Ghost

中间换过几次 VPS,我也渐渐发现 CentOS 的局限性:虽然官方包管理器的软件应该可以保证稳定,但是太老了,安装新软件不是加软件源就是编译安装。又因为它其它一些不好用的地方,后来我彻底放弃了这个发行版,改用 Ubuntu,用 apt 部署所有我需要的服务器软件。

Debian & Gnome

后来我觉得 Windows 10 太闷了,加上当时 RAM 不够,我决定:再次尝试迁移到 Linux!

与第一次安装 Ubuntu 相比,我这次有了迁移的信心:

  • Chrome
  • Telegram
  • Node.js 与 git,它们对 *nix 本身就更友好
  • QQ Web 版
  • LibreOffice
  • ……

我安装的是 Debian jessie + Gnome;我没有删除硬盘上的 Windows 10,因为我担心可能还有 Windows 限定的情形,但是并没有。这经历也为我后来迁移到 macOS 奠定了基础。

我就这样开心的折腾了很久,把 Debian 搞得很符合自己心意。代价就是我在折腾 Linux 上浪费了更多的时间。

2017 年,我把我家台式机的 RAM 增加到 16 GB,安装了 64 位 Windows 10,发现体验太好了,再加上 Windows Subsystem Linux 也可以用了,我后来把 Debian 格掉了。

macOS

虽然 macOS 并不属于 Linux 家族,但我还是要提一下。

macOS 给我的第一印象就是省心。我不再需要向以前那样折腾,我拥有的是一个开箱即用的、美观易用的、UNIX 核心的桌面操作系统。而且,它拥有更大的生态圈。

即使需要我使用命令行干预系统,我也可以用我学会的 Linux 知识轻松搞定,因为 Linux 本来就属于 *nix 家族啊。


这就是我与 Linux 的黑历史了。这篇文章不是说明什么道理,只是跟大家分享一下我的经历。

把 WSL 的发行版换成 Debian 时撞的坑

tl;dr:别自以为是

参考教程:Debian on Windows via WSL

  1. 下载 Git for Windows 这种庞然大物不是必须的,你直接从 GitHub 上 Download ZIP 就行
  2. 安装 Python,一定要记得在第一步勾选 Add Python 3.x to PATH
  3. 一定要乖乖地从 PowerShell 运行,而不是 cmd,否则会出现 WinError 126 错误
  4. 刚安装好的 Debian 实在太精简了,所以别忘了先 apt update 然后装个文本编辑器,否则你编辑点什么都相当痛苦
  5. 这个 Debian 连 man 都没有,所以也需要自己装
  6. 为了修复 locale 问题,你需要
echo "en_US.UTF-8 UTF-8" > /etc/locale.gen  
apt install locales

现在你终于可以 apt install 一通乱装了。

Ubuntu 16.04 能用还是好好用吧,不要学我

为什么公共 block 列表有时是个很糟的主意

很多社区都提供 block 某人的选项,于是就有人说了:为什么不搞个公共的 block 列表呢?这样我们都可以把某人 block 掉,每个人都很开心啦!

首先我们需要搞清楚为什么要在社区里 block 一个人。也许你觉得他的发言火药味冲天,也许你跟他有着恩怨,也许你觉得他的观点让你很不开心,也许你就是看他不顺眼 …… 总之,主观因素往往占很大一部分。

但不是所有人的主观思想都跟你一样。你主观上觉得不喜欢的社区成员,也许别人觉得没啥问题。或者他可能只是互联网的萌新,并不太懂得网络社区礼仪,但经过时间的推移,渐渐的意识到自己以前的行为多愚蠢(比如我)。

不喜欢一个人或一种事物是人的基本权利,但把自己的主观想法施加给别人,别人在知道为什么 block 某人的原因后未必认同你;更重要的是,把一大堆基于不同人的主观因素的 block 列表批量导入设置,你和列表整理者的做法是对别人的严重不尊重。

你不喜欢的东西,别人未必不喜欢。

但有一个重要的例外,就是交易中的骗子。他们都损害了普通消费者的权益,为了保护共同利益,建立一个列表,让别人在交易时少撞坑还是很有必要的。

故事:试图不关闭 SIP 在 macOS Sierra 上使用 proxychains-ng

2022 更新:强行让一个应用走代理的方式现在真的太多了。我目前用的是 Surge,真是太香了(


因为一些原因,我需要通过 proxychains 代理我的 ssh 来连接远程主机。我按部就班地用 homebrew 装好了 proxychains-ng,运行系统上已有的 ssh 客户端(/usr/bin/ssh),期望着就像 Debian Linux 上那样可以轻松代理,然而并!不!能!

你会被卡在这里:

被卡住

我们启用一个国外代理,运行系统上已有的 curl(/usr/bin/curl),你猜怎么着?

tcdw@tcdw-mac.local ~ $ proxychains4 curl ip.cn
[proxychains] config file found: /usr/local/etc/proxychains.conf
[proxychains] preloading /usr/local/Cellar/proxychains-ng/4.12_1/lib/libproxychains4.dylib
当前 IP:blah blah 来自:内蒙古自治区呼和浩特市 电信

看来,proxychains 此时并不能让那些程序走代理。

试图解决

Google 了一下,发现很多博文建议关闭 SIP:

macOS 10.11 后下由于开启了 SIP(System Integrity Protection) 会导致命令行下 proxychains-ng 代理的模式失效,如果你要使用 proxychains-ng 这种简单的方法,就需要先关闭 SIP。

具体的关闭方法如下:blah blah

我不想关闭 SIP,怎么办呢?办法还是有的。

根据苹果的 官方说明,以下路径受到保护:

/System
/usr (不包含 /usr/local)
/bin
/sbin
Apps that are pre-installed with OS X

……

这样就知道如何解决了:把那些二进制复制到不受保护的路径,就可以了!

我在我的 .profile 文件中定义过这个:

export PATH=/Users/tcdw/usr/bin:$PATH

于是我把 ssh 和 curl 复制到我的 bin 路径:

cp $(which ssh) ~/usr/bin/ssh
cp $(which curl) ~/usr/bin/curl

再试试,果然可以代理了!

tcdw@tcdw-mac.local ~ $ proxychains4 curl ip.cn
[proxychains] config file found: /usr/local/etc/proxychains.conf
[proxychains] preloading /usr/local/Cellar/proxychains-ng/4.12_1/lib/libproxychains4.dylib
[proxychains] DLL init: proxychains-ng 4.12
[proxychains] Dynamic chain  ...  127.0.0.1:1080  ...  ip.cn:80  ...  OK
当前 IP:blah blah 来自:日本

总结

  • 把二进制放到 SIG 的保护范围以外即可正常使用 proxychains-ng,而无需关闭 SIG
  • 通过 homebrew 安装的二进制基本在 /usr/local 目录下,因此无需担心此问题。所以,你可以直接通过 homebrew 单独安装 OpenSSH
  • 你也可以考虑下 Proxifier,只要设置好规则即可无痛让各种应用(包括位于被保护路径的命令行工具)使用代理。这是一款商业软件,有 30 天的试用期

Xperia XZ Premium

勘误!我 XZ Premium 附带的耳机型号是 MH750,而非之前所写的 NC750。对于这个错误深表歉意。

然后我真的买了副 NC750

我三年前的 Galaxy S5 逐渐向不可用的方向发展,于是需要换新手机了。

索尼的 Xperia XZ Premium 实在是太帅了,大概是所有骁龙 835 旗舰机中唯一一款第一眼就看中的:全玻璃镜面机身、接近原版 AOSP 的官方固件、3.5mm 耳机孔,以及靠谱一点的系统更新,但更重要的是可以体验索尼大法的力量

于是下单,把国行双卡版 (G8142) 抱回了家。

等机器真的到手时,才发现实物比图片还漂亮,感受跟我三年前拿到 Galaxy S5 时比起来实在是强太多了。不过母上大人说手机拿起来好滑,不过我没有这种感觉,大概是我手汗比较多吧。


开机以后,那系统实在是流畅的让人窒息,比 TouchWiz 强多了。更重要的是,这是官方固件。这才是我心目中的 Android 呢!

不过国行固件并没有 GMS,而且内置了百度全家桶(虽然可以很方便地停用)。我不想解 Bootloader 锁和 root,于是决定刷港版固件。

于是我用 Xperifirm 下载了港版固件,照着 Google 搜到的 Flashtool 教程开始刷机,然后卡在了奇怪的地方——

01/031/2017 13:31:23 - INFO  - (MainSWT.java:154) - Device disconnected
01/031/2017 13:31:56 - INFO  - (MainSWT.java:166) - Device connected with USB debugging off
01/031/2017 13:31:56 - INFO  - (MainSWT.java:167) - For 2011 devices line, be sure you are not in MTP mode

啊,我明明已经进入 Flashmode 了啊!最后麻烦了好几位朋友,折腾了整整一天才发现,新款的索尼手机需要用 Newflasher 来刷,好在操作也是很容易的:

  1. 安装好 Flashmode 驱动
  2. 把 newflasher.exe 放在已经解包的固件的目录(就是有一大堆 sin 和 ta 格式文件的那个)
  3. 运行 newflasher.exe

终于顺利的刷好了港版固件,然后通过 Google Play 装好了所有的必需应用。这手机正式投入使用。

刚刚退役的 Galaxy S5 与 XZ Premium

至于它的拍照和音乐播放。。嗯,画质自然没得说。网上的样张已经很多了,这里就不会浪费诸位时间了。

音乐的话,我这种木耳真的听不出音质与我的 Galaxy S5 有什么显著的提升,包括一般的 m4a 和无损音乐(众:那你还搞业余编曲啊

顺便它附带的 MH750 耳机戴起来好别扭,那耳塞是左耳死活塞不紧,右耳反而塞的很紧,在加上那不一致的左右耳线长,感觉超级不舒服。还是用 miniso 的 earpod 外形耳机好了


这部手机是不打算折腾了,老实跟着官方 ota 走。以前我喜欢整天刷机、root、鼓捣 xposed 框架,但现在看来好没劲啊。

我浑浑噩噩的 Android 手机折腾史算是结束了吧。

在 DOSBox 使用 MIDI (MT-32、GM) 音源

20171031 更新:我发现 TiMidity++ Windows Synthesizer 这玩意貌似有点不靠谱,有时候无法正确启动,所以如果遇到这问题,可以考虑 VirtualMIDISynth

其实我真觉得这种博文不应该多出现,但 Google 了一下,似乎没有靠谱一点的中文教程,所以我写一下我的配置方法,造福萌新。

于 Windows 10 x64 测试通过。

准备材料

General MIDI

VirtualMIDISynth 方案

安装 VirtualMIDISynth,在桌面托盘右键,进入设置页面,添加你喜爱的波表文件。

波表文件配置

(图片是我从官网拿的,因为目前我只有一台 MacBook 可用

TiMidity++ 方案

安装 TiMidity++,然后编辑 C:\timidity\Musix\timidity.cfg(如果你指定了其它安装路径,就是 TiMidity 根目录下的 Musix\timidity.cfg),把剩下的内容都注释掉,然后指定 GeneralUser GS 的位置:

# 文件所在目录,具体视 sf2 文件放置位置决定
dir "C:\"

# 指定 sf2 格式的波表文件,半角引号里面就是文件名
soundfont "GeneralUser GS v1.471.sf2"

然后启动 timw32g,你可以打开一个 MIDI 文件,测试效果。

Munt

安装 Munt,然后打开程序,选择 Options -> ROM Configuration,载入刚才下载的两个 MT-32 ROM。

MT-32 ROM 加载

DOSBox

敲命令 mixer /listmidi,会看到这样的输出:

可用 MIDI 设备

正如你所见,我们现在有 M$ 的音源、MT-32 模拟器、TiMidity++ 可选,左侧是它们对应的 ID。

要使用对应的 Driver,在开始菜单找到 DOSBox Config,打开,搜索这几行:

[midi]
#     mpu401: Type of MPU-401 to emulate.
#             Possible values: intelligent, uart, none.
# mididevice: Device that will receive the MIDI data from MPU-401.
#             Possible values: default, win32, alsa, oss, coreaudio, coremidi, none.
# midiconfig: Special configuration options for the device driver. This is usually the id of the device you want to use.
#               See the README/Manual for more details.

mpu401=intelligent
mididevice=default
midiconfig=2

看到那个 midiconfig=2 了吗?在这个例子里,如果需要使用不同音源,把等号后面的数字改成对应的 ID(如果想要使用 MT-32 音源,就改成 1),保存配置文件,即可使用。

游戏配置

不同游戏有不同的设置方法,但是很多都允许你设置音源。按照游戏提示操作即可。

配置

然后你的 DOS 游戏就有了格外动听的音乐。

局限性

昔日的折腾党

五年以前的我,整天体验各种新的 Android ROM,鼓捣自己的网站,耗费大量时间在折腾的过程中,自己还乐此不疲。

五年以后的我,拥有了更丰富的计算机知识,却越来越不想折腾;我的一大宗旨就是:只要工作就行!

  • 稀奇古怪的优化 ROM 与无 buff 的 LineageOS,选择后者
  • Gentoo 与 Debian,选择后者
  • kate 与 vim,选择前者(如果可以)
  • 宁愿写个小轮子也不愿意折腾复杂的工具

如果我想恢复 efs 怎么办?折腾党的方案:

  1. 下载华丽的 efs 备份工具(可能需要注册论坛账号并回复帖子)
  2. 安装 APK,等待
  3. 恢复 efs

我的方案:

  1. adb shell
  2. su
  3. umount /efs
  4. dd if=/sdcard/efs.img of=/dev/block/mmcblk0p3
  5. reboot

知识越丰富,折腾是完全没必要的,还可以节省大量的时间。

虽然更多的时候会把折腾当成浪费时间的摆脱:看,我花了这么多时间,解决了问题,多辛苦啊!

不过不用担心,我已经不再爱折腾了。。

❌