作者归档:白宦成

关于白宦成

独立开发者, 自由职业者, 写作者

black and white penguin toy

@action/checkout 如何抓取所有的历史记录

GitHub 的 Action Template 中默认带了一个 checkout 插件,这个插件可以实现将你的项目 Clone 到 CI 的运行环境中,从而执行各项操作。

为了提升速度,Github 在实际上实现的时候,默认会限制 depth=1,这就导致在 clone 的时候,仅 clone 一个 commit ,如果你需要依赖 git 进行操作,则需要更多的 commit 。

在具体的实现过程中,你需要做的仅仅是在配置 github action 中的 fetch-depth 选项,设置为你需要的 commit 数量。如果你需要的是所有的 commit, 则将该选项设置为 0。具体配置如下

- uses: actions/checkout@v2
        with:
          fetch-depth: 0

这样你就可以在 CI 中访问到所有的数据。

不过,这样你并不能直接执行诸如 git merge-base 这样的命令,因为你虽然将所有的数据抓取到了本地,但并没有建立相应的分支,则需要你先建立相应的分支,才能进行处理。

这个时候你可以在 action 当中新增一行,用来切换分支。

- run: git checkout x

这里的 x 是你需要操作的分支,不过你需要注意,切换过来以后,还需要重新切换回去,不然你的代码就版本不对了。

code 1076536 640

低代码之殇

2021 01 19 142921

我在前一段时间就发过朋友圈吐槽过 Low Code 和 No Code 工具。今天想更加系统的阐述我对于这个问题的看法。

以下内容以问答的形式进行。

1. Low Code/No Code 有价值么?

当然有价值。在传统行业中,大量的行业效率是非常低下的,借助于 Low Code/No Code 工具,可以以更低的成本,很好的解决传统行业遇见的问题。

此外,很多传统行业面临的问题其实是一致的,如果将这些问题的底层逻辑拆分并打包成独立的模块,就可以很好的在 Low Code/No Code 中应用

2. 你为什么发朋友圈吐槽 Low Code/ No Code?

Low Code 和 No Code 近来有些过热了。几乎所有人都在想着要去做一个 Low Code/No Code 平台,这是有问题的。

Low Code 和 No Code 平台的用户是有限的,而不是像 To C 应用,理论上是无限的。在这种情况下,过热会让这个领域轰轰烈烈的生,轰轰烈烈的死。反而可能不会让 Low Code 和 No Code 落地。

目前很多 Low Code 和 No Code 平台的落地逻辑都是基于人人可用,但在实际落地的时候,你会发现,并非人人都是理性且有逻辑的。Low Code 和 No Code 对于逻辑的要求是比普通的 To C 应用更高的,因此,用户群体规模会被进一步缩小。

现有的 Low Code / No Code 工具在处理复杂的业务路逻辑的时候,并不会说完全不需要代码,大部分会在某个关键节点上需要不到 100 行代码,但也正是这100行代码, 限制了 Low Code、No Code应用的用户规模。

3. 你看好国内做 Low Code/No Code 么?

我总体来说,看好产品形态,但不太看好做这个产品的公司。原因是国内的企业大多没有开放的基因,比较喜欢玩「私域流量」,这会导致在跨企业的合作上变得十分困难。

但 Low Code / No Code 产品不可能由一家公司完全做好所有的模块(如果真的做了,那他的投入和长期的收益回报是非常可观的),这就导致在国内做 Low Code/No Code 是一个脏活累活,而不是一个可以轻易 Scale 的产品。

我其实用过很多海外的 Low Code/No Code 产品,其大多是构建一个开放平台,各家接入,这些国内的风气还没有起来,还有待观察。

4. 你会去使用 Low Code/No Code 平台么?

我一直在使用,比如 Notion、AirTable、IFTTT、Zaiper、Microsoft Flow 都是我常用的工具,我会根据实际的使用场景决定使用什么样的工具。

797c396d4608cc56c4f96bbcca699422

工具创造者的自我修养

独立开发者中有一大批人是通过做工具来获取收入的。做工具也算是独立开发者圈子中经久不衰的话题了。

但到了具体的工具开发之时,其中又有不少可以拿来讨论的内容。

而这里,我最想讨论的是工具的理念。

992zk

对于工具类的软件开发而言,最容易出现的就是「别人做了一个什么东西,我觉得不够新/便宜/不爽,我自己也要开发一个」。对于开发者来说,遇见问题, 并自己解决问题是非常常见的。 但是,工程师常常的问题也在于此,因为复制一个产品的成本越来越低,所以最先选择的是复制,而不是思考这个产品的长期发展道路。这会让产品陷入简单的 Copy and Paste 的模式下,长期来看,并不利于产品发展。

在我看来,如果工程师想要做好一款工具,那么一定要为自己的工具准备一个最基本的方法论。这个方法论一定是基于你自己对于问题的看法做出的拆解,而不能是依赖于其他第三方软件的。

这个方法论将指导你的产品朝着最终的目标,要解决的问题,奋力前行。你后续所遇见的问题,都会成为你前行的助力,为拓宽前进的道路。

fcu1l

而一个没有方法论的产品,则会被途中遇见的各种问题引导偏离最初的目的地。我们目前的市场中有太多的工具了,每一款工具都有其优秀之处,倘若没有自己核心的理念,那只会被众多的 Feature 搞得「乱花渐欲迷人眼」,最终失去了自己对于工具的定位,抄成了一个四不像。

i2wjj

总结

如果你想要以一个小团队打造一款好用、生命力持久的工具,那么先想清楚你的目标和理念,再动手开发,是一个好的路子。需求有很多,但并不是每一个都适合你。工具有很多,也不是每一个都适合你,预期去抄别人的理念,做别人要做的工具,不如发现自己的需求,做自己的工具。

6ee6df690137fd06bc6166adb63caca1

物联网设备如何链接到你的小程序?

物联网项目在涉及到传统行业的数字化改造时,是一个非常常见的选择。通过对于传统的物联网设备进行改造,就可以和云计算设备链接起来,并辅以大数据设施,完成对于产业的优化和迭代。

而到了具体物联网设备和小程序开发时,主要有以下几种链接方式:

直接链接 · 蓝牙

蓝牙链接物联网设备算是最为常见的方式。小程序提供了蓝牙的接口,你只需要调用蓝牙接口,链接到物联网设备上,并读取数据,就可以实现和物联网设备的链接。

比如你可以使用小程序来链接小米手环(如果你知道他的蓝牙配置)。

直接链接 · NFC

小程序提供了 NFC 接口,有了 NFC 接口,你就可以调用机身自带的 NFC Reader 和 NFC Writer 对 NFC 卡片进行读取和写入,来完成和外部设备的交互。

对于 Android 用户来说,使用 NFC 交互可以快速完成近场通讯的数据同步问题。

直接链接 · WiFi

小程序提供了 WiFi 接口,如果你的物联网设备可以对外提供 WiFi 服务,则可以通过调用 WiFi 接口,连接到指定 WiFi,并通过标准的 HTTP 接口来获取数据。

间接链接 · WiFi

WiFi 除了可以用作直接链接,还可以用作间接链接,你可以让设备通过 WiFi 连接到互联网上,并通过 HTTP 协议与服务器进行沟通,从而与服务器之间进行交互,交互完成后,你就可以在小程序端直接读取数据了。

间接链接 · NBIoT

NBIoT 你可以简单理解为手机卡链接,只不过使用的是物联网卡和2G/3G模组。如果你的设备上链接了 NBIoT 设备,就可以直接通过 NBIoT 设备访问到你自己的服务器,并提交信息。

总结

物联网的链接方式随着基础设施的迭代,会有不断的更新。如果你知道有别的方法, 欢迎留言告诉我。

red and white nescafe ceramic mug

如何将 HTTP 请求中的 UA 转化为可读的 UA 信息

什么是 UA (User Agent)

UA 是 HTTP 在发送请求的时候,带上的请求方的各项基本信息。就我自己为例,我的 UA 是 Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Edg/88.0.705.50

这个 UA 当中包含了大量的我的基本信息,包括我的电脑信息、我的操作系统、浏览器的版本等。

在具体的 UA 信息中,具体的结构为

User-Agent: <product> / <product-version> <comment>
Code language: HTML, XML (xml)

如果有多组 UA 信息,则语法如下

User-Agent: Mozilla/5.0 (<system-information>) <platform> (<platform-details>) <extensions>
Code language: HTML, XML (xml)

UA 能够帮助我们理解什么?

UA 可以提供设备的基本信息,比如浏览器、比如设备的版本号等,有了这些信息,我们就可以分析用户的使用习惯、使用场景等信息。

比如,当 UA 中有大量的访问是来自微信浏览器,那么你就可以考虑针对微信的浏览器提供一些优化,比如使用微信自带的第三方 SDK 等信息。

如何将 UA 转换为可读的 UA 信息?

各种语言都有自己的 SDK 来实现从 UA 的 String 提取出操作系统的信息,一般来说,都是通过正则表达式来识别的,你可以在 Github 上找到你所使用的语言提供的 SDK。

不过,除了使用开源的 SDK 以外,你还可以考虑使用一些服务来进行解析,比如我今天发现的 User Stack

kyibb

UserStack 是一个 UA 识别服务,如果你接入该服务,就无需自己维护 User Agent 的识别规则,由服务帮你维护,你只需要调用该 API 即可实现对设备的识别。

总结

UA 转换为人类可读的信息需要通过第三方 API 或开源的 SDK 来实现,你可以根据自己的需要来选择不同的方案。SDK 免费可用,但可能更新不够频繁,无法很好的兼容一些重要的设备。

5e54199359bbafe0ef692365a9bcffb6

如何修复 Beego 运行测试时的报错 undefined: web.Trace

Beego 近来已经更新了 2.X,因此,你现在可以使用更新版本的 Beego 来完成你的快速开发。

不过,beego 2.x 在使用 bee 工具初始化一个新的项目时,默认的项目中有一个 bug,当你在项目根目录下执行 go test ./... 时,会提示 tests 目录中有一个错误。而你并没有修改任何的源文件。

如果单独执行 tests 目录,就会报错如下内容

tests/default_test.go:28:2: undefined: web.Trace
Code language: JavaScript (javascript)

你会发现这段代码指向了 default_test.go 中的这行代码。

beego.Trace("testing", "TestBeego", "Code[%d]\n%s", w.Code, w.Body.String())
Code language: CSS (css)

这段代码中的 Trace 找不到导致无法正常使用。

我通过一些搜索,找到了答案。在 GitHub 中,beego 的 repo 下有一个 issue 和一个 pull request 与这个 bug 相关。结论是这个方法已经被移动到 log 文件夹,因此需要调整一下输出的来源。

在代码中引入 "github.com/beego/beego/v2/core/logs" 并将 beego.Trace 替换为 logs.Trace ,即可解决这个问题。

修改后的代码如下:

package test
import (
    "net/http"
    "net/http/httptest"
    "testing"
    "runtime"
    "path/filepath"
    _ "backend-server/routers"
    "github.com/beego/beego/v2/core/logs"
    beego "github.com/beego/beego/v2/server/web"
    . "github.com/smartystreets/goconvey/convey"
)
func init() {
    _, file, _, _ := runtime.Caller(0)
    apppath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, ".." + string(filepath.Separator))))
    beego.TestBeegoInit(apppath)
}
// TestBeego is a sample to run an endpoint test
func TestBeego(t *testing.T) {
    r, _ := http.NewRequest("GET", "/", nil)
    w := httptest.NewRecorder()
    beego.BeeApp.Handlers.ServeHTTP(w, r)
    logs.Trace("testing", "TestBeego", "Code[%d]\n%s", w.Code, w.Body.String())
    Convey("Subject: Test Station Endpoint\n", t, func() {
            Convey("Status Code Should Be 200", func() {
                    So(w.Code, ShouldEqual, 200)
            })
            Convey("The Result Should Not Be Empty", func() {
                    So(w.Body.Len(), ShouldBeGreaterThan, 0)
            })
    })
}
Code language: JavaScript (javascript)

参考阅读

black and white penguin toy

如何使用 GitHub Action 自动部署 MKDocs

MKDocs 是一个基于 Python 写成的文档生成工具,在实际的使用过程中,我们可以借助 GitHub Action 来实现自动部署到 GitHub Pages 。

操作流程

首先,你需要先在本地生成一个 MKDocs 项目

qyeuk

生成后, 你可以在项目的根目录创建 GitHub Action 所需目录。

mkdir -p .github/workflows/

并创建一个配置文件 publish_docs.yml

name: Publish docs via GitHub Pages
on:
  push:
    branches:
      - main
jobs:
  build:
    name: Deploy docs
    runs-on: ubuntu-latest
    steps:
      - name: Checkout main
        uses: actions/checkout@v2
      - name: Deploy docs
        uses: mhausenblas/mkdocs-deploy-gh-pages@master
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          CONFIG_FILE: mkdocs.yml
          EXTRA_PACKAGES: build-base

创建完成后,将配置文件添加到版本控制中,并提交到 GitHub 上,就可以在你提交代码的时候,自动推送到 GitHub Pages 中。

需要注意的是,我使用的是 MKDocs 的 Material Design 主题,如果你使用的是其他主题(非自带的主题),则需要修改 EXTRA_PACKAGES 的配置。

代码解读

这个配置文件中主要是 on 和 steps 比较有学习的价值。

on:
  push:
    branches:
      - main

这段代码限制了,只有在 main 分支上,出现了 commit 的时候,才会触发这个 action 的执行,如果是其他的分支,则不会触发 action 的构建。

    steps:
      - name: Checkout main
        uses: actions/checkout@v2
      - name: Deploy docs
        uses: mhausenblas/mkdocs-deploy-gh-pages@master
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          CONFIG_FILE: mkdocs.yml
          EXTRA_PACKAGES: build-base

这里的两个 step, 第一个是 clone 出所有的代码, 第二个 step 就是我使用一个已经做好的 action mhausenblas/mkdocs-deploy-gh-pages@master,这个 action 的作用就是将 mkdocs 的文档生成并部署到 GitHub Pages 中。

这个 action 自带了 material design 的主题,执行后,会构建并推送到 GitHub Pages。如果你使用的也是这个主题就可以直接按照我的样式进行配置。如果你使用的不是这个主题,则需要将你的主题配置在 EXTRA_PACKAGES,从而让 action 在执行的时候安装相应的 package。

总结

GitHub Action 的 Marketplace 中有大量的现成 Action 可以供你使用,借助现成的 Action ,你可以非常简单的完成自己的 CI/CD 流程,非常方便。

yellow Volkswagen van on road

失望的横店之旅

很久没有出来玩,就飞了一趟杭州,见了老朋友妙正灰,就匆匆来到了横店,毕竟作为中国影视城的头把交椅,横店影视城还是让我比较期待的。

不过,实际来到横店后,却没有那么美好。

  1. 交通不便:横店全称应该是浙江省义乌市东阳市横店镇,没错。名满全国的横店只是一个镇。而且是一个离义务比较远的镇。我从义乌站出发,到横店我住的酒店大概花了一个小时多一点。着实不算近。除非你是自己开车来玩,不然确实体验一般。
  2. 基础设施一般:横店影视城固然声名远扬,但毕竟横店只是一个镇,在生活的各方面基础上还是不如我们所熟悉的大城市。吃的方面完全不如我们熟悉的大城市。如果你指望来横店吃点好的,可能是没有戏了。
  3. 影视城粗制滥造:横店影视城拍了很多电影,所以我以为会非常不错,但实际去了明清宫苑和香港街·广州街之后,我觉得他们在影视城的实际建设上可能并没有花费的太多心思,我只能感叹说,可能现在的影视剧集的滤镜给的太足了,我在电视中完全看不到这么粗制滥造的基础设施。

不过,横店还是有一些不错的:

  1. 艺人确实多:吃晚餐和吃早餐的时候,我各自碰到一组艺人,相比于你在其他城市(除北京外),可能确实会更容易碰到艺人。
  2. 酒店还不错:我这次来住的是双十一买的横店酒店 +门票套餐,住的是百老汇大厦,这个酒店虽然离市区稍微远了一些,但也更加安静。服务什么的也不错。作为一个度假酒店应该还是比较舒服的。

总结

总的来说,横店如果你有更好的选择,不值得去。不过这个地方蛮适合带小孩子来,一下子带小孩子们拍拍照,也不错。

black and white penguin toy

LCTT 从Travis CI迁移到 GitHub Action 实践

LCTT 的 CI 已经在 Travis CI 上运转了多年,一致保持着良好的使用体验。自 2019 年 Github 推出了自家的 CI 工具 Github Action 后,我们就在考虑将 CI 从 Travis-CI 迁移到 Github,以降低维护和沟通的成本,并借助于 GitHub Action Marketplace 实现更强的功能。

项目首页
项目首页

最近,因为 TravisCI 屡屡部署出错,而我们的账户因为使用的较多,已经超出了免费使用的限制,以此为契机,将 CI 从 Travis CI 迁移到 GitHub Action。

Travis CI 的提醒
Travis CI 的提醒

项目介绍

Translate Project 是 LCTT 翻译组的主要协作项目,几百位译者通过 GitHub 进行围绕开源、Linux、软件工程等领域的文章翻译,从 2013 年来,累计了大量的 Commit,致使项目下有非常多的文件。

Transalte Project 借助于 CI 帮助译者进行基本的文章格式进行检查;并定时执行命令,以进行所有的文章检查,对于超时未完成翻译的工作进行回收;对于文章的状态进行标记,生成相应的 Badge。

生成 badge
生成 badge

迁移思路

Travis CI 和 Github Action 在使用方面,其实总体差异不会太大,都是基于 YAML 文件格式来编写配置文件。不过,和 Travis CI 不同的是,Github Action 支持多个不同的配置文件,因此,你可以根据不同的场景,设定不同的配置文件,降低单个配置的文件的复杂度。

此外,由于我们的脚本中依赖了一些 Travis CI 的环境变量,也需要将其替换为 Github Action 中的相应环境变量,从而确保脚本可以运转。

改造实践

1. 分析之前的 CI 流程

我们在 TravisCI 上的 CI 配置文件如图

配置文件
配置文件

总体可以分为三块:

  1. 命令区:说明了安装阶段和执行阶段的操作有哪些
  2. 条件区:指定了这个配置文件在哪些条件下会生效
  3. 部署区:写明了构建产物如何进行部署

在命令区中,有预置的安装过程和后续的执行过程。在安装过程中,安装了一些依赖,并将当前的 pages 资源克隆到本地,以继承上一次构建生成的资料。

在条件区则指明了仅作用于 master 分支

在部署区便是将前面命令区的执行结果进行部署。

基本流程
基本流程

在实际的执行过程中,还会根据环境变量不同,决定是否要执行特定的命令,这部分在后续的改造过程中,就可以调整部署,拆分到不同的文件中。

构建流程
构建流程

2. 直译配置文件

在完成了基本的分析后,就可以建立新的 Action 配置文件了。由于基本的语法很类似,对于其中的不少内容可以进行直译。

比如,我们的配置文件在直译后,结果如下

直译后的结果
直译后的结果

直译的文件已经可以直接运行,不过,这里有很多不满足需要的地方,所以需要做一些修改。

3. 恢复 Travis CI 的环境变量

由于我们使用的 Badge 等生成脚本并非我所编写,所以在这一次的迁移中,并不打算对齐进行调整,以避免出现故障。而脚本中依赖了一些变量,需要将其重新设置出来。

Github Action 提供了一些方法,可以让你手动设置环境变量。你可以在你的构建的 step 中,加入如下代码,从而在构建环境中设定 TRAVIS_BRANCH 和 TRAVIS_EVENT_TYPE 环境变量,确保你可以使用这个环境变量。

 - <strong>name</strong>: Set ENV variables
        run: |
          echo "::set-env name=TRAVIS_BRANCH::${TRAVIS_BRANCH:-$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }')}"
          echo "::set-env name=TRAVIS_EVENT_TYPE::$(if [ "schedule" == "$" ]; then echo "cron"; else echo "$"; fi)"

Code language: JavaScript (javascript)

此外,由于 set-env 这个方法相对比较危险,你还需要在环境变量中,开启危险函数的执行。

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      ACTIONS_ALLOW_UNSECURE_COMMANDS: true

Code language: JavaScript (javascript)

4. 拆分配置文件

Github Action 和 TravisCI 不同的一点是你可以将你的 CI文件拆分成多个文件,从而降低每一个单独的配置文件的复杂度。

根据前面对于流程的分析,可以将我们的 CI 流程拆分成三个部分:

  1. 生成 badge 文件,应当跟随每一次 commit 进行
  2. 生成 status 文件,执行时间较长,可以定期执行
  3. 根据 Pull Request 内容进行整理,做核验

则将我们的配置文件拆分成三个不同的文件

也得益于拆分开,则在 checker 中就可以免于安装一些必要的依赖,从而精简 CI 流程,提升 CI 的执行时间。

5. 测试 CI 的运行情况

在完成了配置文件的编写和拆分后,就可以进行本地的执行测试。Github Action 写完了,难免要执行一下,确保整个流程是正常的。

这个时候你可以安装工具(https://github.com/nektos/act),来在本地执行 action ,从而确认你的代码执行是正确的。

bkpl8

如果你是 macOS ,只需要执行 brew install act 就可以安装 act 工具,来完成 act 的安装。

安装完成 act ,就可以通过执行 act 命令来在本地执行 action ,比如,执行 act pull_request来触发 GitHub 的 Pull Request 事件

mt4ca

通过本地测试后,再将你的配置文件推送到 GitHub 上,进行生产环境的测试即可。

6. 移除 Travis-CI

通过上述的一些步骤,我们完成了从 Travis CI 到 GitHub Action 的迁移,此时,就可以移除项目根目录中的 .travis.yml 文件,彻底关闭 travis-ci。

7. 修改 GitHub 的 Branch 保护条件

为了确保修改符合标准,我们限制了新的 Pull Request 必须通过 CI 检查,才能合并进入 master,因此,也需要修改相应的分支保护规则,确保设定相应的保护。

b5cwa

一些注意事项

1. 环境变量不同

如果你依赖了环境变量,则需要进行相应的修改。或者可以像我一样,先通过 set-env 来让本地拥有临时的环境变量,后续再逐步进行迁移。

2. Action 运行依赖要注意安全

Action 执行过程中会有两个部分。action 本身流程依赖于 master,但执行过程中调用的脚本是根据 source 决定的,因此,从安全角度来看,你应当尽可能将所有的流程放在 Action 中,而不是放在你的源码目录中。

总结

通过对 TravisCI 的流程整理、代码修改等流程,我们将之前的 Travis-CI 迁移至速度更快、性能更好的 GitHub Action ,一方面可以优化我们的工作流,另一方面,也让我们的代码更加简洁明了。

对于还在使用 Travis CI 的项目来说,也可以考虑迁移到 GitHub Action 中,来优化自己的工作流。

参考阅读

  • https://mauricius.dev/run-and-debug-github-actions-locally/
  • https://jeffrafter.com/working-with-github-actions/
  • https://developer.okta.com/blog/2020/05/18/travis-ci-to-github-actions
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)

总结

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