分类目录归档:技术

code 1076536 640

拯救老旧 Discuz 的 Flash 上传

Discuz 目前依旧是一个非常重要的建站程序,不少老的站点依旧会使用 Discuz 上运转。
由于 Discuz 是一个多年的程序,所以在系统的设计上,有很多地方充满了对老旧系统的兼容。

比如,Discuz 的上传使用的是 Flash 上传,但在 2021 年,Flash 已经彻底停止使用了,这个时候你就需要使用自己的方式来进行上传。

这里我以 Linux 中国进行的相关改造为例来介绍。

Linux 中国的文章发布系统就是基于 Discuz 实现的,编辑器层面使用的是 Discuz 提供的 TinyMCE,

erf5f

Discuz 提供了一个基于 Flash 的上传组件,但在2021 年,现在这个 Flash 按钮已经完全显示不出来了。

上传组件

这个时候,你就需要自己实现一套上传系统。

由于 Linux 中国的很多底层实现都是基于 Discuz 进行的,因此,此次改造希望继续沿用 Discuz 的系统。

实现路线

在对 Discuz 默认的 Flash 插件进行抓包后,很轻松的就找到了 Discuz 的 Flash 上传组件的接口:/misc.php?mod=swfupload&action=swfupload&operation=album

这个接口接受 Form 表单作为参数,并在表单中接受一个 Hash 作为身份校验,用来判断请求的合法性。

因此,只需要在 TinyMCE 自带的上传功能中加入相应的 Hash 和表单提交的能力,就可以将 Discuz 实现的上传功能改为用 TinyMCE 的原生组件实现。

而 Hash 可以通过分析得出,其算法为 md5(substr(md5($_G['config']['security']['authkey']), 8).$_G['uid'])

因此,需要做的便是,在页面中注入 Hash ,并在 TinyMCE 中调用此值,实现上传功能即可。

样例代码

html/template/default/portal/portalcp_article.htm 文件顶部加入如下代码,从而在文章发布页面注入 hash

<span class="hljs-comment"><!--{eval$swf_hash =  md5(substr(md5($_G['config']['security']['authkey']), 8).$_G['uid']); }--></span><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/javascript"</span>></span><span class="language-javascript"><span class="hljs-keyword">var</span> <span class="hljs-variable constant_">FORMHASH</span> = <span class="hljs-string">"{$swf_hash}"</span></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
Code language: JavaScript (javascript)

html/static/js/editor_tinymce.js 中的 tinyMCE 配置中添加如下配置项目,即可调用 HASH 来上传文件。从而实现文件的上传。

        images_upload_url: "/misc.php?mod=swfupload&action=swfupload&operation=album",
        images_upload_credentials: true,
        images_upload_handler:function(blobInfo, success, failure, progress) {
            var xhr, formData;
            xhr = new XMLHttpRequest();
            xhr.withCredentials = true;
            xhr.open('POST', '/misc.php?mod=swfupload&action=swfupload&operation=album');
            xhr.upload.onprogress = function(e) {
                progress(e.loaded / e.total * 100);
            };
            xhr.onload = function() {
                var json;
                if (xhr.status === 403) {
                    failure('HTTP Error: ' + xhr.status, {
                        remove: true
                    });
                    return;
                }
                if (xhr.status < 200 || xhr.status >= 300) {
                    failure('HTTP Error: ' + xhr.status);
                    return;
                }
                json = JSON.parse(xhr.responseText);
                if (!json || typeof json.bigimg != 'string') {
                    failure('Invalid JSON: ' + xhr.responseText);
                    return;
                }
                success(json.bigimg);
            };
            xhr.onerror = function() {
                failure('Image upload failed due to a XHR Transport error. Code: ' + xhr.status);
            };
            filenameArray = blobInfo.filename().split(".");
            formData = new FormData();
            formData.append('Filedata', blobInfo.blob(), blobInfo.filename());
            formData.append('Filename',blobInfo.filename() );
            formData.append('type','image');
            formData.append('Upload','Submit Query');
            formData.append('uid',discuz_uid);
            formData.append('filetype',"." +filenameArray[filenameArray.length-1]);
            formData.append('hash',FORMHASH);
            xhr.send(formData);
        },
        file_picker_callback: function(cb, value, meta) {
            var input = document.createElement('input');
            input.setAttribute('type', 'file');
            input.setAttribute('accept', 'image/*');
            input.onchange = function() {
                var file = this.files[0];
                var reader = new FileReader();
                reader.onload = function() {
                    var id = 'blobid' + (new Date()).getTime();
                    var blobCache = tinymce.activeEditor.editorUpload.blobCache;
                    var base64 = reader.result.split(',')[1];
                    var blobInfo = blobCache.create(id, file, base64);
                    blobCache.add(blobInfo);
                    cb(blobInfo.blobUri(), {
                        title: file.name
                    });
                };
                reader.readAsDataURL(file);
            };
            input.click();
        },
Code language: JavaScript (javascript)

总结

老旧系统的升级改造不可怕,只要你用心,从业务的底层抽丝剥茧,就能找到要解决的问题。剩下,不过是代码层间的问题罢了。

black and white penguin toy

被 GitHub Action 坑了半天…

我这几天在忙着搞 LCTT 的 Travis-CI 迁移到 Github Action 。整体的 CI 的流程都已经迁移完成了,但是一个特定的检查脚本死活无法正常运行。

在这个过程中我尝试了多种办法,比如修改代码、调整里面的参数、打印其中的参数,似乎都是工作运行正常的。

将项目代码 clone 到本地后,发现代码在本地运转也是正常的。

我一直不知道问题出在哪里,百思不得其解。直到我在跑了几百次 Action 构建后,我发现,Github 默认的 checkout 插件是有问题的。

和标准的 git clone 不同, Github 默认的 Checkout 插件在实际 clone 项目是使用的不是 git clone 命令,而是采用先在本地 init 一个目录,并添加相应的 remote ,并 fetch 代码下来。

github action
GitHub Action 的 Clone

这样的好处是在处理的时候,只会 fetch 到特定的分支到本地,而不会将默认的其他分支一同 clone
到本地。但坏处就是你在执行的时候,只能针对特定的分支进行操作。

而检查脚本则是基于 git 本身的命令进行执行的,因此是需要比如 master 这样的分支的,这就导致在使用了默认的 checkout 插件的时候,检查脚本无法使用。

Travis-CI 的表现
Travis CI 的 Clone

与之对比的,是 Travis CI 在执行 Clone 的时候,采用的是全量 Clone ,再单独 Fetch 某个特定分支。从这个角度上来看,我可以理解 Github Action 为什么会 Clone 的更快一些。不过,这种 Clone 的方式确实给一些 CI 在 Check 时留下隐患。

总结

如果你在 Github 中使用默认的 Checkout 插件获取项目以后,执行 Git 操作出现了问题,很有可能是插件自己的问题,而不是你的问题。你可以选择自己构建 clone 命令,避免这个问题。

black and silver laptop computer

在 macOS 下创建启动 U 盘

因为要重装 Mac mini ,所以研究了一下怎么配置启动 U 盘。

依赖

想要给 U 盘制作一个 macOS 的启动盘,首先,你需要有一个 macOS 的系统,并且有相应的安装软件(Install macOS Catalina 之类的)。此外,还需要有相应的容量 U 盘。

根据 macOS 的系统大小,我比较建议你使用 8G 或 16G 以上的 U 盘。

获取安装软件

由于安装软件比较大,所以一般情况下我们也不会保留这个软件,但当我们需要的时候,就要去安装对应的软件了。

你可以访问 Apple 的官网,找到相应的软件下载地址

系统下载地址:https://support.apple.com/zh-cn/HT211683

如果你已经升级了 Big Sur ,却希望制作 Catalina 的启动盘,那么你需要看看 这篇文章

选择你需要使用的系统

image

会自动打开下载界面

g645c

你只需要点击其中的获取,就可以下载相应的系统镜像。等软件自动下载并安装完成后,就可以进行安装操作了。

查看 U 盘挂载路径

想要制作启动 U 盘,自然要说明对应的路径,这个时候你需要先找到你自己的 U 盘。

你可以在终端中执行 df -h ,在其中找到你自己的 U 盘,比如我这里的是 /Volumes/install

制作启动盘

准备好软件和U盘后,剩下的比较简单,直接执行命令即可。

以 Catalina 为例,只需要执行如下命令

sudo /Applications/Install\ macOS\ Catalina.app/Contents/Resources/createinstallmedia --volume /Volumes/MyVolume

其他版本的系统可以参考 https://support.apple.com/zh-cn/HT201372

black and silver laptop computer

如何从 macOS 系统中启动到恢复模式

macOS 可以在开机的情况下通过按 Control + R 启动到 恢复模式,那是否有不按 Control + R 就能进入到 Recovery 的方式呢?

答案是,有的

你在 macOS 的 Terminal 中输入如下命令,即可进入到恢复模式中。

sudo nvram "recovery-boot-mode=unused"
sudo reboot
Code language: JavaScript (javascript)

在操作完成后,你可以执行如下命令来移除添加的 Flag,重新进入到正常操作的系统中。

nvram -d recovery-boot-mode

来源:https://apple.stackexchange.com/questions/367336/can-i-initiate-a-macos-restart-to-recovery-mode-solely-from-the-command-line

text

对 Discuz 进行手动搬迁

由于服务器托管方的维护,需要对托管在机房的 Linux.cn 服务器进行搬迁,因此,多年不碰 Discuz 的我又要进行一次搬迁。

刚好,记录下来,方便后续查用。

流程图

下方流程图中,绿色为原服务器操作,黄色为新的备份服务器操作

d9uas

具体流程介绍

1. 导出数据库

想要搬迁,首先要处理的是数据库的导出,你可以选择你的站点流量最小的时候,使用 MySQL dump 命令来完成 SQL 文件的导出,导出的命令也非常简单。

mysqldump -uroot -p database >/tmp/db.sql
Code language: JavaScript (javascript)

你可以将上方的 root 调整为合适的用户名;将 database 调整为合适的数据库名,以及将 /tmp/db/sql 调整为合适的文件名

执行命令后,会要求你输入 MySQL 对应用户的密码,输入密码, 稍等片刻,数据库就完成导出,你就可以在 /tmp/db.sql 找到数据库文件。

2. 压缩数据库文件

在进行数据库文件后续的传输时,如果文件太大,可能会导致传输速度较慢,这个时候你可以选择使用 gzip、zip、7zip 之类的进行传递。

我一般习惯用 gzip 进行压缩,并使用 tar 进行打包。

tar -zcvf db.sql.tar.gz /tmp/db.sql

打包后,会获得一个 db.sql.tar.gz 文件,这个文件基于导出的 SQL 进行了一定的压缩,可以确保传输的时候,不需要传输那么大的文件。在实际测试时,可以将 1.1G 的数据库压缩到 188M,效果还是十分明显的。

3. 压缩网站文件

需要传递到新的服务器中的,除了 MySQL 数据库,还需要传递网站的代码文件到新的服务器中,因此,为了方便传输,同样需要进行压缩。

tar -zcvf website.tar.gz /data/website/website.com

命令执行完成后,你就会获得一个 website.tar.gz ,这个文件就可以在后续传递到你的新服务器中。

4. 配置新的网站运行环境

在你备份的同时,你可以在新的服务器上进行环境配置。

一般而言,在搬迁的同时,不会采用新的版本的软件,以避免出现问题。

你可以通过 php -vmysql --version 来查看 PHP 和 MySQL 的版本。

Nginx 的版本倒是不需要太过介怀,他只是一个反向代理,问题不大。

MySQL 的版本则在进行迁移的时候,不建议做版本升级,尽量保持同版本升级;如果跨版本,则需要考虑相应的回滚措施。

5. 进行文件传输

在新的服务器中配置旧服务器的公钥,从而可以直接通过 scp,在两个服务器之间传递文件,简单方便。

scp source root@host:/data/xxx 
Code language: JavaScript (javascript)

执行上面的命令就可以直接在两个服务器之间传输文件,简单方便快捷。

6. 进行文件恢复

完成文件搬迁后,就可以在新的服务器上配置环境,这时可以根据你的配置,将文件迁移至对应的目录中。

涉及到压缩包,可以进行一下解压操作。

7. 配置应用服务器

在我们的系统运行时,会依赖很多应用服务器,比如数据库 MySQL 、反向代理 Nginx 等等。在迁移时,比较稳妥的方案是在当前版本的基础之上进行配置。

这时你需要在新的服务器上配置和旧服务器完全一致的运行环境,从而确保迁移后业务不会出问题。

8. 导入数据库和站点文件

在完成了数据文件的迁移和,就可以进行数据库的导入,并迁移网站文件。

你可以使用 mysql 命令行中的 source 命令,来加载 mysql 的dump 文件。

对于网站文件,只需要根据你的配置进行调整即可。

9. 修复权限

在文件进行迁移的时候,可能会由于迁移前后的用户等问题出现权限问题。因此,如果你发现出现了项目的权限有问题,则需要根据实际情况,调整项目的文件和目录的权限。

10. 修改配置

Discuz 的配置文件会在多个地方重复使用,因此,在实际的使用时,如果你调整了数据库信息,则需要修改以下几个文件中的配置项目。

  • config/config_global.php
  • config/config_ucenter.php
  • uc_server/data/config.inc.php

修改其中的数据库名,从而确保系统中的各模块都可以正常工作。

green and black digital device

如何隐藏 oh-my-zsh 的 Last Login?

oh-my-zsh 是我目前配置新的 Mac 必然会装的。不过,oh-my-zsh 一直有一个我不喜欢的就是它会自动一个 Last Login 的 Hello Message。

jb2ia

这个 Hello Message 倒是不占位置,但是我觉得它让我的命令行不那么极简

因此,我希望将这个提醒删除掉。

删除的方法不复杂,只需要在用户的根目录创建一个空白的~/.hushlogin 文件即可

touch ~/.hushlogin
15bd774f997dc7fae5f8faae791cca7d

几个可以简化 rails 开发命令的函数

在 rails 的 bin 目录下,有一些可执行文件,你在开发过程中使用这些可执行文件来操作,从而使用项目自带的可执行文件

但是默认的 rails 命令使用的是全局的 rails ,如果我希望使用项目中的可执行文件,就需要执行 bin/rails, 略微繁琐,所以有没有一种可以更加简单的方式呢?答案是肯定的。你可以通过在你的命令行中添加一个新的命令来实现这个效果。

具体代码如下:

funciton rx(){
	if test -f "bin/rails"
	then
	  bin/rails $*
	  exit
	else
	  rails $*
	fi
}
funciton yx(){
	if test -f "bin/yarn"
	then
	  bin/yarn $*
	  exit
	else
	  yarn  $*
	fi
}
function bx(){
if test -f "bin/bundle"
	then
	  bin/bundle $*
	  exit
	else
	  bundle  $*
	fi
}
Code language: PHP (php)

你可以将这段代码粘贴在你的 .bashrc.zshrc  文件中,从而使其在命令行启动时可用。

这段代码很简单, 定义了三个新的函数,后续我们在命令行输入 rxyxbx 的时候,会自动调用当前目录或全局的 railsyarnbundle 目录。这样你可以在任何一个目录下使用 rx 命令来操作。

三个函数的结构都是一样的,首先检测当前目录下的子目录是否存在 rails 可执行文件,如果存在,就调用本地的函数,并将参数传递。如果不存在,就调用全局的函数,将参数传递。

总结

我们可以通过定义简单的一些命令,简化项目的开发。而这样的思路,你可以应用在任何一个项目中,而不仅仅是 rails 项目中。

red and white heart illustration

如何实现子域名应用?

多域名应用的心思是从 WordPress.com 的时候起来的,当时一直在想,如何实现这种多域名的应用?

最近看到了一个实现,算是解了心头的疑惑。

如果不涉及 SSL ,子域名应用的实现并不算复杂,可以简单的通过将用户请求进行 rewirte 转发的方式,来实现对请求的转发。

举个例子,比如将 x.example.com 转发到 www.example.com/pages/x 中,这样在应用中就无需单独对于多域名编写代码,只需要从 Path 中提取前缀,并进行数据库查询,将数据结果返回回去就好。

如果涉及到 SSL,子域名应用的实现相对复杂一些,涉及到了 SSL 证书的管理。不过也有实现简单的方式,那就是购买一个泛域名证书,这样 Nginx 会通过泛域名证书提供服务,因此,应用程序在处理上和上面的逻辑一样,只要将请求转发就好了。

如果涉及到子域名绑定,则相对麻烦一些,需要能够编程式的操作 Nginx 的代码文件,不过也还好,你可以在应用的目录下动态的生成 Nginx 的配置文件,并在默认的配置文件中 include 动态生成的目录,从而避免文件生成后的管理成本太高。此外,需要借助 execl ,执行命令对 Nginx 执行检查和重启的操作。

此外,也可以考虑直接使用 default_server ,从而直接将所有未识别的情况转发到某个特定的路径。不过需要在应用中添加请求判断,对于无法绑定的域名,提供一个特定的反馈,引导用户进行绑定。

总结

如果你的应用不涉及到 SSL,也不涉及到域名绑定,则十分简单,直接使用 Nginx 转发请求即可;但如果涉及到了 SSL,则需要考虑泛域名证书,从而降低编程的成本;如果涉及到了域名绑定,则需要为应用程序新增对 Nginx 操作的能力,从而降低应用的管理和研发成本。

继续阅读
pile of assorted-title books

几本超融合的书

这几本是我前段时间在研究超融合时找到的,这里分享给大家。因为这些书都是公开放在网上(厂商提供下载)的,所以整理起来一并发布。

这四本书值得发一个很有意思的点是这四本书都是 For Dummies 出版社为这四家企业定制发行的电子书。官网上没有,但使用的都是 For Dummies 的标示,所以这看起来也是一个出版社营收的事情。

这四本书提供了四个不同的厂商对于超融合的看法,对于在研究超融合的你来说,是一个不错的选择。

black and silver laptop computer

制作一个 macOS 启动盘

我一直以来都是网络安装的 macOS ,但这次我的网络死活没有加载到恢复服务器,我就从相机中拔了一张空白的 SD 卡,来做一个启动盘。

1. 安装 macOS 镜像

安装制作 macOS 启动盘的时候,你需要这样一个 安装 macOS Catalina 的磁盘镜像。

osev1

但正常情况下,我们会把安装 OS 的软件删除掉(毕竟占地 8G),所以,如果你需要制作启动磁盘,第一步就需要安装 macOS 镜像。

你可以访问 App Store 下载:https://itunes.apple.com/cn/app/macos-catalina/id1466841314?ls=1&mt=12

2.制作启动盘

制作启动盘对于磁盘的大小和文件格式有要求,需要你的磁盘

  • 大于 12 GB
  • 磁盘格式为 macOS 拓展文件格式

安装完成 Catalina 后,就可以制作启动盘了,具体的命令如下

sudo /Applications/Install\ macOS\ Catalina.app/Contents/Resources/createinstallmedia --volume /Volumes/install

这里我的磁盘的名称是  install ,如果你的不是,则需要修改为对应的名字。

m8nv3

3. 使用启动盘重启

制作好启动磁盘,就可以试着使用你刚刚制作的启动磁盘重启,并使用其恢复。