Category: 编程、软件、技术

  • 今日冷知识:信用卡号验证

    今日冷知识:信用卡号验证

    几个月前,我为大家普及了冷知识:如何验证一本书的ISBN号。今天我再给大家普及一个冷知识:如何验证信用卡号?这也是来自一个编程做题网站。

    比如我们有这么一个信用卡号:4417 1234 5678 9113(这个当然不是我的,是网上随便搜来的。你只要知道4开头的信用卡是Visa卡)。

    信用卡号的验证很简单:

    1. 从右到左,第2/4/6/8……(偶数位)的数字乘以2。
      1. 用上面的卡号做例子,就是对4/1/1/3/5/7/9/1这几个数字乘以2,得到8/2/2/6/10/14/18/2
    2. 如果第1步中得到的数字大于10,那么就减去9,于是得到:8/2/2/6/1/5/9/2
    3. 将这些数字和原来卡号中奇数位的数字相加:8+4+2+7+2+2+6+4+1+0+6+1+4+8+1+8+1+2+3 = 70
    4. 如果这个和(70)是10的倍数,那么就是一个合法的卡号,否则就不对了。
  • AI在发展,我们怎么办?

    AI在发展,我们怎么办?

    (本文非常长,但结尾处有福利送上。)

    限于条件,从22年底到现在,我其实并没能好好地用上最新的AI工具。不过,我总算也是通过各种方式,在还算“最近”的时间里,接触了不少最新的东西。

    昨天,在现代传媒大厦22楼的星辰仕达举办了一场“AI+智慧医疗”的分享会,我和分享嘉宾之一的、微软昆山工业元宇宙应用中心的Nick聊了一会,有了点想法,就凑一篇文章,总结一下我一年多来,使用各类AI的体会和感想。

    ChatGPT以及同类

    (我没有ChatGPT 4,所以在下面提到的回答和截屏,如果没有特别说明,我都用微软的Copilot。)

    ChatGPT是我们到目前为止最熟悉的AI应用,也有了很多变种。但是它们最基本的应用,都是:接受提问,提供回答。

    仔细分析下来,其实我们在寻求三种不同的回答:

    事实性的

    这是最简单的,也应该是最“准确”的。这里,我给“准确”打了引号,因为对于一个事实性的问答来说,我们不去追究“原始事实”的准确性。换句话说,如果AI搜索到的所有资料都说“莫拉维拉共和国成立于1487年”的话,那么AI经过一定的算法后告诉我们这个共和国成立于1487年没有什么问题,是准确的。但这个信息不一定“正确”。

    这里涉及的问题就非常根本了:

    第一个问题就是:即便AI能给我们数据,我们可以对这些数据有多大的信心?

    AI获取数据的来源、处理数据的“规则”中,有没有所谓的bias(偏见)?

    我们对这种类型的偏见其实一点都不陌生。在人类历史上,曾经有过——甚至现在还有——基于种族、性别、肤色的种种偏见以及种种歧视,并引发了种种悲剧。到了较近的年代,网络时代的“地图炮”也是一种典型的偏见——当然还有种种根植在思想中的固有偏见(“男的没一个好东西”,“女人穿得暴露就是在勾引人”……),我就不一一列举了。

    我们完全可以相信:一个在预设偏见的前提下训练出来的ChatGPT必然也只能给出充满了偏见的回答。所谓的rubbish in, rubbish out,说的也是这个道理。

    第二个问题就是:我们有没有办法去判定一个回答的真实性?如何判定?

    这个问题其实有点悖论的味道:如果我知道一个正确答案,我还去问AI干嘛?我之所以问AI,可不就是因为我不知道答案嘛?

    从我目前使用的情况来看,AI对于纯事实性的回答,还是比较可信的。早期AI问答中出现的那种“一本正经的胡说八道”已经不多了。

    在纯事实问答方面,AI的附加值在于:它博览群书,记忆超群,但它不能替代你的研究。

    所以,我可以想象到的一种最合理的应用场景会是:

    作为在某个领域有一定经验和积累的人,向AI询问针对领域的一些事实问题——这样的回答很容易得到验证。 同时,随着对话的深入,可以向AI询问一些尚未有定论的问题,AI应该可以基于它掌握的数据,给出一些判定。 这样的过程,是有助于此人在这个领域的学习和研究的。

    推理性的

    说老实话,我对AI的推理能力还是有点不放心的。我用了一个最基本的“君子小人岛”的问题来测试。

    我先让Copilot进入角色:

    我:你现在是一个逻辑学家了。我需要你帮助我进行一些逻辑推理。 Copilot:当然,我很乐意帮助你进行逻辑推理。请告诉我你需要帮助的具体问题或情况。😊

    然后再引入前提:

    我:有很多谜题是关于这样一个岛的:这个岛上的居民一些是“君子”,他们总是说实话,另一些是“小人”,他们总是说谎话。这个岛上的居民不是君子就是小人。 Copilot:我明白了,你提到的是一个经典的逻辑谜题类型,通常被称为“君子和小人”的问题。在这类问题中,”君子”总是说实话,而”小人”总是说谎。你的目标通常是通过提问来确定一个或多个居民是君子还是小人。 请告诉我具体的谜题,我会尽力帮助你解决。😊

    (看得出来,Copilot心情不错,很会卖萌。而且,它似乎自信满满,用上了“经典”、“通常”等词。)

    然后我提出了我的问题:

    我遇见三个人A、B、C。每个人不是君子就是小人。A和B做出了如下的陈述: A:我们都是小人。 B:我们中正好有一个君子。 A、B、C各是哪类人?

    出乎我意料的是,Copilot完美地解决了这个问题!而且给出了推理过程:

    我表示:答案对了,但推理过程还很模糊。于是Copilot进一步详细列出了过程:

    对此我是很surprise的!

    我不甘心,给了它一个更难的问题:

    在这个岛上,我遇见两位当地人A和B。他们进行了如下的陈述: A:B是君子而且这个岛是玛雅岛。 B:A是小人而且这个岛是玛雅岛。 这个岛是玛雅岛吗?

    我们先看Copilot的回答:

    很抱歉,Copilot的推理出现了严重的错误。正确的答案是:这个岛不是玛雅岛。但为了防止AI从我的回复中学到推理过程,我将这个推理过程留给各位读者去思考了。

    创造性的

    在昨天的演讲中,Nick分享了两张片子,是他分别用中文和英文“求助”Copilot,如何写当天要用的PPT——当然,我才不信他会用AI的提示来完成昨天的PPT!

    (两个提示词的意思基本相同。回答的质量有很大的差别。另外,我昨天在现场,但我认为Nick根本就没有按照这个提纲,虽然可能用到了一些思路和数据。)

    一个高度创新的东西是没有办法用现有的东西去验证其真伪的,我们只能说对提问者“有没有用”?所以,在这一类型的问答中,AI给出的答案可能有用:能帮你开拓思路,提供新的讨论点,给出基本的数据,做好最基本的框架,完成一个几乎“有着标准答案”的工作汇报……但更多情况下,可能毫无用处:如果你不能appreciate它的回答的话。

    所以,对于Nick这样一位highly innovative的人来说,我相信他在提问前,脑子里已经有了一些想法(所以才能写出那么长的提示词),再根据回答(知道数据正确,发现新的讨论点),最终还是老老实实地回到电脑上写PPT去了(当然也会不断借助Copilot来辅助)。

    衍生话题:更有创造性的”回答“

    2月份的时候,Sora出世,那一段“A stylish woman walks down a Tokyo street…”的视频引爆了眼球。这是在图像、视频上的突破。

    不久前,Suno出世。这次是音乐!

    从文字到图形到视频到音乐,AI在越来越抽象的、也越来越专业的领域快步前进!

    能比音乐更抽象的是什么?我很期待!

    最后,送上我用Suno编写的一首歌曲,歌长约2分钟。你听出来是关于什么的曲子了吗?欢迎留言!

    [video width="512" height="768" mp4="https://blog.rsywx.net/wp-content/uploads/2024/04/Moonlit-Serenity.mp4"][/video]

    最最后,我说说我的判断:我从来不觉得AI是个挑战,或者会威胁“我”的工作,它不过是个会卖萌、偶尔会出bug的工具罢了。

    (大家想想AI最有成就的领域是在哪里,就会同意我的判断了。)

  • 今日冷知识——ISBN号

    今日冷知识——ISBN号

    我挺喜欢编程的,一旦有点闲暇,就会写点小程序,用来磨砺一下脑子。

    我的“任氏有无轩”站点就是如此慢慢慢慢地成型的,从2010年前后开始,我就用PHP以及Symfony框架开始构造,到现在也已经有10几年了。

    最近这一两年,AI很时髦,所以我也试着用AI来帮助我写程序。

    在我看来,写程序至少有两个好处。

    首先是锻炼脑子。脑子是个好东西,而且需要不断地磨炼。

    其次是学习一些新的、也许很冷的知识。

    今天我倒是学到了一个新的冷知识:如何判定一本书的ISBN号是正确的?

    如同人有身份证作为身份的标识外,书籍也有自己的身份标志,也就是ISBN(International Standard Book Number)号。一个ISBN号有13位(2007年后)或者10位(2007年前),这是一个ISO(International Organization for Standardization,国际标准化组织)标准,编号是ISO 2108。

    我们先看如何解读一个13位的ISBN号。就拿我刚买的这本《美国文明的兴起》(上下卷)为例,它的ISBN号是978-7-100-07036-2

    它一共有五个部分:

    1. 978是所谓的“前缀”,目前只有两个:要么是978,要么是979。以后应该会有更多的号码。
    2. 7是所谓的“大分组”。一个国家和地区都有一个代码。无论是汉语的影响力还是出版物数量都是巨大的,所以理所当然获得了一个一位数的代码:7(区域代码)。当然,英语的影响力更大,所以英语出版物的大分组代码有两个:0和1。其他一位数国家、区域和语种代码是:
      1. 法语:2
      2. 德语:3
      3. 日本:4
      4. 前苏联:5
      5. 其他:6/8/9
    3. 100:出版社、出版商代码。以《美国文明的兴起》为例,其出版社是商务印书馆。根据任氏有无轩的收藏记录,中国大陆的出版社中,代码比较小的依次是:
      1. 01:人民出版社
      2. 02:人民文学出版社
      3. 04:高等教育出版社
      4. 100:商务印书馆
      5. 101:中华书局
      6. 108:三联书店
      7. 111:机械工业出版社
      8. 119:外文出版社
      9. 121:电子工业出版社
      10. 208:上海人民出版社
      11. 301:清华大学出版社
      12. 309:复旦大学出版社
      13. 313:上海交通大学出版社
      14. ……
      15. 5327:上海译文出版社
      16. 等等等等
  • 试用ImageFX

    试用ImageFX

    Google新推出了图片生成工具ImageFX。我也第一时间试用了一下。

    根据说明,ImageFX还处于“实验”阶段,采用了一堆Google的技术,还特别强调,用到了SynthID在生成的图像中嵌入数字水印,从而“声明”该图像是AI生成的。

    我先是随便写了一段比较短的、但显然不“真实”的提示:

    ancient stone encarvings showing a man is reading some news on his latest iphone

    首先,我注意到的是,Google会比较友好地给出一些它认为重要的“关键词”,我认为这是它在生成图像时会着重考虑的要素。

    于是我得到了下面这三张图片:

    老实说,我是比较满意的了。几个关键词都得到了体现。newslatest iphone没法体现也很正常,因为要考虑到这是石刻,不可能有这么多细节——当然,如果它再聪明一些,在手机的背后浅浅地刻上一个苹果的标记的话,那就更逆天了。

    在试用过程中,我发现Google还是非常“小心谨慎”,有很多对生成内容的限制。我发现,除了常规的一些限制外,它对国家、宗教相关的提示词非常敏感。

    我又试着做了一张非常“中性”的画:

    和之前我用Copliot画的相比:

    提示词是差不多的。但看得出,Copilot画的橘猫更真实,雪地中孩子们玩耍的场景出来了,(我没有给Copliot以“火炉”的提示),落地玻璃窗和书架也更有气势。相比之下,ImageFX的两只(我只要求一只)橘猫成了两个色团,挂钟的指针非常扭曲。

    最后是一张用ImageFX画的“龙行天下”,这张的提示中我特别加入了“a few landmarks in Suzhou”,所以大概能看到东方之门了——也就是说,东方之门已经被Google认为是苏州的地标建筑了。

    祝各位读者龙年大吉大利,龙年整年都能洋溢着乐观向上的氛围!

  • 一个家里可能用得到的软件

    一个家里可能用得到的软件

    突然发现好久没有写有关“技术”的帖子了,主要原因应该是看书的时间比较多了一些,然后搬家后,需要一点时间来构造家里各种电子设备的应用场景,再决定装些什么软件来帮我偷懒。

    我喜欢“收藏”各种资源:电影、音乐、书籍,当然还有照片。粗粗算了一下:电影大概有几百本, 音乐有几千首,书籍有上千本,照片几万张。

    这些东西当然有一个地方集中保存,也有至少一个备份。

    新家有个大电视,电视的一个HDMI接口连接了一台很老的小电脑,用来上网、播放媒体正好还够用。目前家里简化了的拓扑结构如下:

    电视机(客厅)这个地方是“专心休闲”的时候播放媒体的点。另外,我也还想能在“任何”地方能“播放”这些媒体。于是,就有了需求:我需要一款软件,能够集中管理这些媒体资源,并通过流的方式,在任何地方播放。

    今天我就向大家推荐这款软件:Jellyfin。下载地址:https://jellyfin.org/downloads/server

    因为我的中心媒体库运行在Linux上,所以我在那台机器上安装了Linux的版本。这款软件目前还支持Widows/MacOS,但不支持NAS。

    程序安装好之后,通过大电脑的浏览器访问http://hp:8096,进行一些初步的设置后,进行媒体库的添加。添加媒体库的时候,可以选择类型。虽说类型不会影响资源的播放,但有助于程序对其中的文件进行元数据的搜索(特别是电影和音乐)。

    媒体库添加好了后,有一个漫长的扫描过程。Jellyfish会在所有媒体库中搜索所有支持的媒体类型,并尽量在网上搜索对应的元数据。比如,对于电影来说,就会搜索标题、年份、类型、评分……尽量匹配并更新它的数据库。

    一切就绪后,再次回到主页面并登录,就会看到一个很漂亮的页面:

    Jellyfin支持搜索,可以方便地定位媒体。下面是在大电视上播放的效果:

    而且,在家里的时候,我在手机上也可以用同样的地址访问这个服务器,并进行电影的观赏:

    特别值得一提的是,Jellyfin支持浏览电子书!

    我觉得吧,有了这个软件,我的媒体文件算是有了组织,在三上时间也有了更多的选择。

  • 解决Flutter下两个小问题

    众所周知,我最近在学Flutter(aka 服了它)。

    界面设计先不谈,我拷贝了一个网上的教程,初步得到了如下的界面:

    在编译的时候碰到两个问题,都解决了。

    1. Android build warning Mapping new ns to old ns 这是因为由flutter create创建的应用配置缺省用了比较老的Graddle,修改方法是:
    • 修改\android\build.gradle中的build:graddle7.0.2(再高的也没有实际效果)

      dependencies {
          classpath 'com.android.tools.build:gradle:7.0.2'
          classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
      }
    • 修改\android\gradle\wrapper\gradle-wrapper.properties中的distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip7.0.2-all.zip和上面的文件保持一致。

    重新编译就好了。

    1. 上面界面中“维客”被点击后,会导航到http://rsywx.com,但会出现launchurl err_cleartext_not_permitted,这是因为我要访问http而不是https的外部链接。

    修改方法:修改android\app\src\main\AndroidManifest.xml,加入这一行:

    android:usesCleartextTraffic="true"

    重新编译即可。

  • Slim 4初步研究

    Slim是一个我很喜欢用的轻框架,我用它为我的任氏有无轩站点提供API服务。这几天趁着放假,想重新“折腾”一下我的站点,于是就开了一个虚拟机,装好了必要的软件,准备开发。

    然后我发现,Slim这个框架已经升级到了4,有了重大的变化。

    参照Slim官方说明创建项目后,目录结构如下(请忽略其中的nbproject目录):

    • app目录:它包含了对于整个应用来说最基本的一些文件。具体说明如下。

    • dependencies.php:它主要是创建应用全局的容器。应用安装时,会生成logger这个实例。数据库链接的实例也是在这里生成的:

    return function (ContainerBuilder $containerBuilder) {
        $containerBuilder->addDefinitions([
            ...
            PDO::class => function (ContainerInterface $c) {
                $settings=$c->get('settings')['db'];
                $host=$settings['host'];
                $user=$settings['user'];
                $pass=$settings['pass'];
                $db=$settings['db'];
    
                $dsn="mysql:host=$host;dbname=$db";
                $conn=new PDO($dsn, $user, $pass);
                $conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
                return $conn;
            },
        ]);
    };
    

    注意,我在这里设置了数据库PDO链接的一些属性。以后在应用的任何地方,只要用到PDO这个类的声明,Slim都会在容器中去寻找PDO的实例,从而保证所有的数据库链接都是统一的。

    • middleware.php:中间件我目前在API中还不会用到。按照Slim的说明,中间件可以在应用运行,修改相应的RequestResponse对象。
    • repositories.php:这里注册所有可以成为“仓库”的类。这是Slim 4新增的文件,用来统一管理MVC中的M;但Slim 4抛弃了M,而改用更轻量的仓库。这也是一个容器。如:
    declare(strict_types=1);
    
    use App\Domain\Book\BookRepository;
    use App\Infrastructure\Persistence\Book\DBBookRepository;
    
    use DI\ContainerBuilder;
    
    return function (ContainerBuilder $containerBuilder) {
        $containerBuilder->addDefinitions([
            BookRepository::class => \DI\autowire(DBBookRepository::class),
        ]);
    };

    注:这里用到的一些类会在后续得到说明。不过可以提醒一下,Slim 4用一个接口(BookRepository)定义所有该仓库支持的操作,而用一个实例类(DBBookRepository)对这个接口进行实现。你可以把它们的这种关系类比成C++中的“hpp/cpp”的关系。

    • routes.php:这是常规的路由配置。请注意,Slim4放弃了一个controller中多个action的做法,而是一个action对应一个controller,如下例所示:
    declare(strict_types=1);
    
    use Psr\Http\Message\ResponseInterface as Response;
    use Psr\Http\Message\ServerRequestInterface as Request;
    use Slim\App;
    use Slim\Interfaces\RouteCollectorProxyInterface as Group;
    
    use App\Application\Actions\Index\IndexAction;
    use App\Application\Actions\Book;
    
    return function (App $app) {
        $app->get('/', IndexAction::class);
        $app->group('/summary', function(Group $group) {
            $group->get('', Book\SummaryAction::class);
        });
    };

    于是,/summary这个路由就会调用Book\SummaryAction::class中制定的action函数。

    • settings.php:全局配置文件。数据库的配置也在此处出现:
    return function (ContainerBuilder $containerBuilder) {
        // Global Settings Object
        $containerBuilder->addDefinitions([
            'settings' => [
                'displayErrorDetails' => true, // Should be set to false in production
                'logger' => [
                    'name' => 'slim-app',
                    'path' => isset($_ENV['docker']) ? 'php://stdout' : __DIR__ . '/../logs/app.log',
                    'level' => Logger::DEBUG,
                ],
                //Database connection
                'db' => [
                    'host'  => 'localhost',
                    'user'  => '****',
                    'pass'  => '****',
                    'db'    => '****',
                ],
            ],
        ]);
    };

    总结:app目录中的,都是对应用全局产生影响的文件。

    • public目录:这里是应用的入口文件。一般情况下,对于一个API应用来说,这里没有什么需要修改的。
    • src/Application目录:这里有四个目录。我们重点要关注的是src/Application/Actions目录。

    根据我的理解,针对API要成立的各类实体,开发者可以进行逻辑抽象分类,形成各个action。在具体实现的时候,一般是针对每个实体实现一个抽象的类,然后对每个针对这个实体的操作定义一个action

    比如我的数据库中有“书籍(book)”这个实体,于是我就暂时定义了两个文件:BookAction.phpSummaryAction.php

    //BookAction.php
    
    declare(strict_types=1);
    
    namespace App\Application\Actions\Book;
    
    use App\Application\Actions\Action;
    use App\Domain\Book\BookRepository;
    
    use Psr\Log\LoggerInterface;
    use Psr\Http\Message\ResponseInterface as Response;
    
    abstract class BookAction extends Action
    {
        /**
         * {@inheritdoc}
         */
        protected $repo;
    
        public function __construct(BookRepository $repo, LoggerInterface $logger) {
            parent::__construct($logger);
            $this->repo=$repo;
        }
    }

    注意到,这是一个抽象类,主要目的是为本类(以及后续子类)引入各种dependency。这里我引入了BookRepositoryLoggerInterface

    //SummaryAction.php
    declare(strict_types=1);
    
    namespace App\Application\Actions\Book;
    
    use Psr\Http\Message\ResponseInterface as Response;
    
    class SummaryAction extends BookAction
    {
        /**
         * {@inheritdoc}
         */
        protected function action(): Response
        {
            $res=$this->repo->summary();
            return $this->respondWithData($res);
        }
    }

    SummaryAction继承了BookAction,完成了action方法。而在action方法中,通过调用repo中对应的方法而获取了数据并返回。Slim4很贴心地可以直接返回json数据,不用开发者再转换。调用的例子如下:

    我们再来看src/Domain目录。

    这个目录中包括用来定义各类仓库的文件。比如我们之前看到的BookRepository

    // /src/Domain/Book/BookRepository.php
    declare(strict_types=1);
    
    namespace App\Domain\Book;
    
    interface BookRepository
    {
        public function summary(): array;
        public function detail($id):array;
    }

    如前所述,这是一个接口,只是定义了各类接口,也就是这个仓库能做什么的定义。

    最后来看看src/Infrastructure/Persistence目录。这个目录对上面提到的接口进行实现。

    // /src/Infrastructure/Persistence/Book/DBBookRepository.php
    declare(strict_types=1);
    
    namespace App\Infrastructure\Persistence\Book;
    
    use App\Domain\Book\BookRepository;
    
    class DBBookRepository implements BookRepository
    {
        private $conn;
    
        public function __construct(\PDO $conn) {
            $this->conn=$conn;
        }
    
        public function summary(): array {
            $sql='select count(*) bc, sum(kword) wc, sum(page) pc from book_book';
            $res=$this->conn->query($sql)->fetch();
    
            return $res;
        }
    
        public function detail($id): array {
    
        }
    
    }

    请注意,这个类的构造函数中引入了\PDO,因此上文提到的dependency就起作用了。

    总体感觉,Slim 4的结构还是很清晰的,耦合度也合适,确实是可以一用的轻量级框架。

    本文推送到[go4pro.org]

  • 个人写作工具链引介

    和一位重度文字创作者+声线好听到爆炸的读书会老友聊天时,她提到她过往多年的文字创作和素材之前都放在某国内博客站点上,后来这个站点倒了,她没来得及备份——或者说站点没有提供迁移工具,很多文字和素材都丢失了。

    正好这段时间我也在总结个人写作的工具链,于是我决定加快进度,写一篇文字作为引介。

    我目前的写作,遵循如下的流程:

    [mermaid] graph LR; 本地写作–>版本控制兼简单异地备份; 版本控制兼简单异地备份–>提交到博客平台; 提交到博客平台–>分享到社交平台; 分享到社交平台–>定期备份; [/mermaid]

    本地写作

    对于本地写作,我建议使用MarkDown语法(简称MD)。

    1. 它很容易上手,用上10分钟就能满足大部分书写要求——我这篇文字就是用MD写的。
    2. 由于书写时可以集中在内容,而不是格式,所以可以提高写作效率
    3. MD是纯文本格式+(以文本进行的)特殊标注,所以可以达到最大限度的兼容性。最坏情况下,你的文字内容可以得到保存。
    4. MD可以支持很多排版的内容,比如插入图片、外链、媒体文件等。
    5. 由于MD文件是纯文本文件,可以进行有效的版本控制。版本控制可以让你“回溯”一篇文章的写作过程,有点像Word里的“修订”功能,但轻多了。
    6. 目前,有很多很好的软件支持MD语法写作,很多内容发布平台也同样支持MD语法。

    我目前用的本地写作工具是Obsidian,支持Windows/Mac/Linux全平台。我之前也有过简单的介绍

    版本控制兼简单异地备份

    用纯文本进行写作的好处之一是:可以进行版本控制。简单地说,可以比较不同版本之间的差异。在写作——特别是大量修改——时,这个功能有时非常有用。

    同时,版本控制还可以被当做简单的异地备份来使用,因为你的文件都会通过你的commit/push而保存在离你很远很远的服务器上。

    我目前使用的版本控制平台是gitlab.com,另外一个更出名的是github.com。两者都支持免费注册和私人仓库。

    我使用了SourceTree作为可视化的版本控制软件。

    提交到博客平台并进而分享到社交平台

    我的建议是,最好自己申请一个域名,搭建一个虚拟主机,然后安装一个Wordpress的博客平台。这样,自己所有的文章在本地编辑完成后,只要经过很少的调整(主要是调整图片、媒体文件的上传和位置),就可以在自己的专属平台上发布。肾上鄙人在下我的博客是:blog.rsywx.net

    然后,你可以再次将内容转而分享到社交平台。

    定期备份

    最后,是进行定期备份!你可以备份到移动硬盘上,打包加密后放到网盘上。如果你所有的文章都经过这个流程,那么你只要备份你当地的文稿即可。

    以上是我个人目前写作工具链的引介。供参考。

  • 试用Obsidian

    今天在Twitter上看到了这么个软件的推送:Obsidian,就下载了一份进行测试。

    它的卖点有以下几个:

    1. 完全本地文件存储。

    现在大家都喜欢用云。但云的问题在于:需要网络连接;私有内容放在云端——特别是国内的云——总有泄露内容、莫名被删、服务商跑路的风险。而由于Obsidian是纯粹的本机文件存储,你可以借助别的同步软件完成不同设备间文件的同步。

    1. 完全支持MarkDown语法。

    我个人比较喜欢用MD语法写东西,简单易记的标记被设计用来帮你专注写作,而且现在几乎所有大型网站和工具都会支持MD语法的写作。而纯文本的写作还可以进行有效的版本控制。

    1. 有一个很漂亮的图形关联视图。

    写作的东西多了后,它们之间一定有关联。Obsidian可以基于tag和正文进行“帖子”之间的关联。比如下面这张图就显示了我的Wiki文档间的关联。这样一来,你可以快速地在“关联”或“相似”的帖子之间跳跃,这对进行大型文档的写作是非常有好处的。

    1. 完全免费并支持多个操作系统。

    Windows,macOS,Linux都可以运行。

    我一直认为,写东西是个很认真的事情,也从来不习惯用手机写东西。手机当然很方便:拍照、修图、配文、发布。但它不是一个认真记录、认真回忆的地方。所以,我还是坚持写博客,在自己的电脑上保留一份记录。

  • 新年用编程来消磨时间

    新年好!按照中国人的传统,只有过了农历年,才是进入了一个新的年度。 长假里干嘛呢?我选择看书、编程。

    这次我还是继续之前开始的用Vue.js改写我的“任氏有无轩”站点。

    今天是大年初一,上午出门拜年后,回家就开始写代码。随着我对Vue掌握的深入,也可以开始慢慢加入更多更高级的东西。

    1. 在模板编写方面,使用了更灵活的<template>嵌套,这是为了进行条件渲染。在我现在的站点中,条件渲染主要用在页面加载数据的时候,能显示一个placeholder,提供一些动态效果和内容提示;在数据加载完毕后,就显示实际的内容。我使用的是vue-content-loading这个组件。
    2. 全部改用Axios进行远程API的调用。个人感觉这是一个很大的突破。而且用Axios完成了表单的post工作。
    3. 当然,对所有的后台API(采用PHP Slim框架)也进行了全面的改写。这里还有很大的提高空间,但是目前只要能用就行。
    4. 使用Mobirise来进行页面的设计。这样,我可以稍微跳出现成模板的约束,设计出更简洁、更让自己舒服的界面。

    现在的站点还没有完成,但是已经有了一定的效果。

    [fvplayer id=3\]

    本文推送到[go4pro.org]