标签归档:开发经验

在 Dokuwiki 中配置安全规则,来保护配置和数据文件

由于 dokuwiki 的文件路径默认会放在 data/conf/bin 等几个目录当中,如果你不对相应的文件进行保护,如果某个人是了解 dokuwiki 的情况下,它可以越过你的 dokuwiki ,直接读取 wiki 的内容。

而你没有进行保护的时候,Dokuwiki也会在配置当中展示你的配置不安全。

想要开启 Nginx 的保护,你需要在 Nginx 的配置文件当中,添加如下代码。

 location ~ /(data|conf|bin|inc|vendor)/ {
      deny all;
 }

添加完成后,执行如下命令来重启 Nginx,使命令生效。

nginx -t 
nginx -s reload

生效成功后,再次刷新,就可以看到提示已经消失了。同时如果你直接访问 data 目录下的数据文件,也会直接报错。

Dokuwiki 配置 Rewrite 优化路径显示

Dokuwiki 在生成 URL 的时候,支持生成三种不同的 URL:

  • Rewrite 版: /wiki:welcome?do=admin&page=config
  • dokuwiki 控制版: /doku.php/start?do=admin&page=config
  • 默认版:/doku.php?id=start&do=admin&page=config

配置 Rewrite 可以让你的 wiki 的路径更加简单的和明确,屏蔽语言信息,因此,一般而言,都建议大家配置上对应的 rewrite 规则。

在 Nginx 你的 Host 配置下加入如下规则,来实现 Rewrite 的转发


    location / { try_files $uri $uri/ @dokuwiki; }
 
    location @dokuwiki {
        rewrite ^/_media/(.*) /lib/exe/fetch.php?media=$1 last;
        rewrite ^/_detail/(.*) /lib/exe/detail.php?media=$1 last;
        rewrite ^/_export/([^/]+)/(.*) /doku.php?do=export_$1&id=$2 last;
        rewrite ^/(.*) /doku.php?id=$1&$args last;
    }

添加完成后可以执行如下命令来重启 Nginx

nginx -t 
nginx -s reload

再回到 Dokuwiki 后台的配置管理器,找到 userewrite 这一项配置,将其配置为使用.htaccess,并保存,即可将 dokuwiki 默认生成的 URL 变成一个更加干净的 URL。

Dokuwiki 忘记密码了怎么办?

我的个人 Wiki 系统是 Dokuwiki 。相比于别的 Wiki ,Dokuwiki 轻量的同时,功能齐全,帮助我在 TiddlyWiki 和 MediaWiki 之间找到了一个平衡。

我在使用 Tiddly Wiki 的时候,会遇到忘记密码的情况,这种情况下,就需要对 Dokuwiki 进行密码重置的操作。

如果你和我一样,关闭了 Dokuwiki 的任意人可上传,则需要通过修改具体的 auth 文件来完成。

Dokuwiki 的用户授权信息放置在 conf/users.auth.php 文件当中,你需要在服务器上打开这个文件,并在其中添加如下信息

deleteme:$1$4fd0ad31$.cId7p1uxI4a.RcrH81On0:-:-:admin,user 

添加完成后,你将会获得一个名为 deleteme,密码为admin 的用户,接下来你只需要使用这个用户登录到你的 Dokuwiki 当中,并在管理当中的「用户管理器」中修改之前的用户的账号密码。

在用户管理器当中可以修改对应的用户的密码。

修改完成后,使用之前的用户登录,并删除之前的用户即可。

white printing paper with numbers

使用 Sheetjs 将 JSON Array 转化为 Excel

使用 node-excel-stream 来按行处理 Excel 数据 中,我提到,如果你希望简单的完成 Excel 的读取和处理,那么 node-excel-stream 是个不错的选择。而反过来,如果你希望将 JSON Array 导出为 Excel,那么 Sheetjs 是个不错的选择。

注意

Sheetjs 和 exceljs 不同,区分了商业版和社区版。我们这里使用的是社区版 Sheetjs CE

用法

使用 Sheetjs 对数据进行导出时,你只需要调用 XLSX 方法当中的 json_to_sheet ,就可以将你的 JSON Array 变为一个 worksheet,接下来只需要将其放入一个新的 workbook 当中,并导出为文件,就可以完成 JS 数据导出为 Excel。

const XLSX = require("xlsx");

const data = [
  {
    ...
  },
  ...
]

const worksheet = XLSX.utils.json_to_sheet(data);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "sheetNameIsFirst");
XLSX.writeFile(workbook, "output.xlsx");
white printing paper with numbers

使用 node-excel-stream 来按行处理 Excel 数据

数据分析是一个非常常见的需求,而在实际的落地场景当中, Python 是使用最多的。不过我因为写了很久的前端,其实对于Python已经生疏了。当我开始启动项目时,就会选择执行 npm init 来初始化一个项目。既然如此,就试着使用 Node.js 来做数据分析。

在 Node.js 当中操作 Excel ,最好的便是 Exceljs。不过 ExcelJs 封装了大量的函数,对于绝大多数的数据分析场景来说,可能并不适用(也不一定,只是我比较喜欢用代码来描述逻辑,Excel 更多是一个导入导出)。

当明确了我只是需要一个简单的导入导出后,那么 node-excel-stream 就进入了我的视野。

读取 Excel 内容

和 Exceljs 不同,node-excel-stream 的封装相对简单,就是一个 Reader 和 Writer ,提供的方法也十分简单:读取文件、定义格式,按行处理内容;

需要注意的是,node-excel-stream 只支持 xlsx ,而不支持 xls,所以如果你用的是旧版,则需要重新保存成 xlsx 来进行处理。

let dataStream = fs.createReadStream('data.xlsx');
let reader = new ExcelReader(dataStream, {
    sheets: [{
        name: 'Users',
        rows: {
            headerRow: 1,
            allowedHeaders: [{
                name: 'User Name',
                key: 'userName'
            }, {
                name: 'Value',
                key: 'value',
                type: Number
            }]
        }
    }]
})
console.log('starting parse');
reader.eachRow((rowData, rowNum, sheetSchema) => {
    console.log(rowData);
})
.then(() => {
    console.log('done parsing');
});

写入 Excel 内容

写入时和读取时相比,稍微复杂一点,需要将所有的输入使用 Promise.all包裹起来

let writer = new ExcelWriter({
    sheets: [{
        name: 'Test Sheet',
        key: 'tests',
        headers: [{
            name: 'Test Name',
            key: 'name'
        }, {
            name: 'Test Coverage',
            key: 'testValue',
            default: 0
        }]
    }]
});
let dataPromises = inputs.map((input) => {
    // 'tests' is the key of the sheet. That is used
    // to add data to only the Test Sheet
    writer.addData('tests', input);
});
Promise.all(dataPromises)
.then(() => {
    return writer.save();
})
.then((stream) => {
    stream.pipe(fs.createWriteStream('data.xlsx'));
});

总结

如果你需要将 Excel 导入到 Js 当中进行处理,那么 node-excel-stream 是一个不错的选择。

black and yellow printed paper

warehouse — 一个简单易用的 JSON 数据库

在 Hexo 的 Github 组织下,有一个不明显,但却很有用的仓库 —— warehouse。

warehouse 是一个 JSON 数据库,基于 JSON 实现了各种类似于 SQL 的查询,可以帮助我们基于一个 JSON 文件来进行查询。在 Hexo 的静态生成过程中,warehouse 帮了大忙。

用官方的话来说,warehouse 就是 A JSON database with Models, Schemas, and a flexible querying interface.

在实际使用上,warehouse 确实如他所说的那边方便(虽然某些方法没有,但依然不影响他的使用很方便)。

Example

比如,如下代码就定义了 一个 Post 模型和对应的表。并实现了在这个表中插入一个新的数据。

var Database = require('warehouse');
var db = new Database();

var Post = db.model('posts', {
  title: String,
  created: {type: Date, default: Date.now}
});

Post.insert({
  title: 'Hello world'
}).then(function(post){
  console.log(post);
});

如果你需要将这些数据保存为一个单独的文件,只需要修改初始化的参数,并执行 save 方法,就可以将 JSON 导出到指定的文件中

var db = new Database({
  path: "./test.json", // 将数据存储在 test.json 当中
});
db.save();

类似的,如果数据已经构建好了,也只需要执行 load 方法,就可以加载数据。

var db = new Database({
  path: "./test.json", // 将数据存储在 test.json 当中
});
db.load();

场景

如果你希望在内存当中对于 JSON 有一个更好的操作方式,那么 warehouse 是个不错的选择,不需要另外单独安装数据库,就可以实现类似于数据库的查询方式,体验还是非常好的。

如果你想了解更多,可以查看

red yellow and green paper

如何判断一个颜色是什么颜色?

当你看到这个题目的时候,你可能会有点迷惑:“一个颜色是什么颜色”,这个问题好无厘头。但如果我换个用法 ,你可能就能明白 — 人类是如何辨别出一个颜色是红色而不是绿色?

回想小时候,大家应该都听说过 —— 三原色。不同的人可能记得不同,有的人记得是红绿蓝(色光三原色),也有人记得是红青黄(美术三原色)。结果不一样,但并不影响。两种三原色都告诉你了 —— 一个颜色是可以由另外三种颜色组合而成。

这意味着,每一个颜色都对应着三个坐标。他们是 RGB 也好,还是其他也好,都是通过三个颜色的色码来确认是哪个具体的颜色的。我们的显示器也是如此制造的。

但三原色有个问题 —— 变量太多。假设我们在每个维度分 3 个不同的结果,三原色可以拼出 27 种不同的组合。如果我们在每个维度分成 10 阶,这个结果就是 1000 个不同的维度。按照 RGB的 0 ~ 255 ,一共 256 阶,则一共可以形成 16,777,216 个组合。如果你想要构建出一组用于判断的规则,就十分的困难。

因此,换一个坐标系会是更好的办法 —— 试试 HSL 描述方法。

HSL 是将 RGB 三维坐标转换为色相、饱和度、亮度(英语:Hue, Saturation, Lightness)组成的新维度坐标。

当我们变为新的坐标系后,我们定义颜色的坐标就从过去的三个维度,变为了只需要色相(Hue)这一个坐标就可以定位的坐标。毕竟,如果一个蓝色不饱和饱和,都不影响他是个蓝色;同样的,一个蓝色的亮度不高,但依然是蓝色。

通过从 RGB 坐标系转变为 HSL 坐标系,我们就将过去的 16,777,216 个组合简化为 360 阶。从过去的三维坐标系,变为了全新的一维坐标系。

这样我们就可以十分简单的来细分我们的不同的颜色,更好的来编写规则。比如Hue 在 15 ~ 45 时为橙色;在45 ~ 75 时为黄色;在 75 ~105 时为绿色等等。

在我们写代码的时候也会更加简单,我们只需要计算出具体的 Hue 的值,就可以得出对应的颜色的名字。

RGB to HSL 算法

如下算法来自 Wikipedia

https://zh.wikipedia.org/wiki/HSL%E5%92%8CHSV%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4

设 (rgb)分别是一个颜色的红、绿和蓝坐标,它们的值是在0到1之间的实数。设max等价于rgb中的最大者。设min等于这些值中的最小者。要找到在HSL空间中的 (hsl)值,这里的h ∈ [0, 360)是角度的色相角,而sl ∈ [0,1]是饱和度和亮度,计算为:h={\begin{cases}0^{\circ }&{\mbox{if }}max=min\\60^{\circ }\times {\frac  {g-b}{max-min}}+0^{\circ },&{\mbox{if }}max=r{\mbox{ and }}g\geq b\\60^{\circ }\times {\frac  {g-b}{max-min}}+360^{\circ },&{\mbox{if }}max=r{\mbox{ and }}g<b\\60^{\circ }\times {\frac  {b-r}{max-min}}+120^{\circ },&{\mbox{if }}max=g\\60^{\circ }\times {\frac  {r-g}{max-min}}+240^{\circ },&{\mbox{if }}max=b\end{cases}}

s={\begin{cases}0&{\mbox{if }}l=0{\mbox{ or }}max=min\\{\frac  {max-min}{max+min}}={\frac  {max-min}{2l}},&{\mbox{if }}0<l\leq {\frac  {1}{2}}\\{\frac  {max-min}{2-(max+min)}}={\frac  {max-min}{2-2l}},&{\mbox{if }}l>{\frac  {1}{2}}\end{cases}}”></p>



<p><img decoding=

h的值通常规范化到位于0到360°之间。而h = 0用于max = min的(定义为灰色)时候而不是留下h未定义。

RGB to HSL Sample Code

function rgbToHsl(r, g, b) {
  r /= 255, g /= 255, b /= 255;

  var max = Math.max(r, g, b),
    min = Math.min(r, g, b);
  var h, s, l = (max + min) / 2;

  if (max == min) {
    h = s = 0; // achromatic
  } else {
    var d = max - min;
    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);

    switch (max) {
      case r:
        h = (g - b) / d + (g < b ? 6 : 0);
        break;
      case g:
        h = (b - r) / d + 2;
        break;
      case b:
        h = (r - g) / d + 4;
        break;
    }

    h /= 6;
  }

  return [h, s, l];
}

延展阅读

https://www.december.com/html/spec/colorterms.html

使用小程序的 Canvas 2D 提取特定点的颜色

在小程序当中,我们可以通过在 Canvas 画布上绑定 Tap 事件,来获取到用户点击行为,当我们获取到点击行为对应的坐标时,就可以读取到对应位置的颜色。并根据我们的需求,将颜色转换成我们想要的 RBG 色值。

这就是我们常见的各种取色软件的最常见的实现方式。当然,落实到实际开发过程中,大家或多或少会有所不同(涉及到调色。我们拍照的颜色和我们所看到的颜色并不完全一样。不同的相机调色会有所不同。类似的,我们需要加入相应的逆向算法排除对应的相机自身调教的影响)。

在小程序的场景中具体实现方式可参考如下代码:

页面布局

<view>
  <view><button bindtap="chooseImage">1. 选择图片</button></view>
  <view><canvas id="myCanvas" bindtap="onCanvasTap" type="2d" style="background-color:gray;width: 100%;margin-top: 100rpx;"></canvas></view>
</view>

对应的 JS

和在 在小程序中使用的 Canvas 2D API 绘制本地图片 一样,你需要提取到 Canvas 的 Ctx ,用于执行操作。当你通过 bindTap 拿到你的点击点相对于 Canvas 的 左上角的坐标后,就可以使用 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/getImageData">getImageData</a> 来提取具体范围的数据。因为我的目的是提取某个点的颜色,因此我的第三、第四参数都是 1 (取一个像素点的宽度和高度)。

提取出图片数据后,你可以在 data 当中提取一个 4 个元素的数组,按照顺序,分别是 RGBA 的四个值,你可以根据需要使用这个数据(比如放大展示在界面上)。

Page({
  data: {
    color: ''
  },
  onCanvasTap(e) {
    const query = wx.createSelectorQuery()
    query.select('#myCanvas')
      .fields({
        node: true,
        size: true
      })
      .exec((res) => {
        const canvas = res[0].node
        const ctx = canvas.getContext('2d')
        const data = ctx.getImageData(e.detail.x, e.detail.y, 1, 1).data;
        const [red,gree,blue,alpha] = data;
      })
  },
});

WordPress 出现 RedisException: OOM command not allowed when used memory > ‘maxmemory’ 的报错怎么处理?

早晨起来,想登录博客,记录下自己的灵感,突然发现死活登录不上 WordPress 后台。

登录到 VPS 后台,发现没有出现我之前常出的问题 — 硬盘满了。于是再次回到网页端登录,仔细研究后发现,我的登录应该是成功的,但登录完成后,又重新跳转回来,根据这个情况,我猜测可能是登录态出现了问题。

于是尝试切换到 Safari 、Chrome 的无痕模式登录,依然没有解决问题。因此可以排除掉客户端的问题导致的。

找到问题

接下来就是查看服务端问题。登录到服务器上,找到 WordPress 的日志,查看最近的几条日志,突然在众多 Notice 当中,看到了一个 Exception:

RedisException: OOM command not allowed when used memory > 'maxmemory'

看到这个报错,突然明白了问题在哪了。

我的 WordPress 使用 Redis 作为缓存,而我过去一直配置的缓存空间是 128M,看报错,显然是因为 Redis 使用的内存空间大于 128M,而我过去没有配置逐出机制(默认是 noeviction),导致直接 OOM 爆掉了。

而我的登录态也使用了 Redis,没办法在缓存当中塞入新的 Key,自然登录也就失败了。

解决问题

找到问题之后,下一步便是解决问题。

解决问题并不复杂,为 Redis 调整内存空间大小,并配置逐出机制,就可以解决这个问题。

maxmemory 221000000
maxmemory-policy allkeys-lru

将 Redis 逐出机制设置为 allkeys-lru ,并将内存设置为 200M 后,重启 Redis ,果然我的 WordPress 可以正常登录了。

参考

allkeys-lru 表示对移除最近使用最少的 (least recently used)Key。

更多算法可以参考 Key Evicution

black smartphone

使用 Proxy.py 自建 HTTP Proxy

如果你需要对一个集群进行开发,且开发过程中需要访问内网,一个比较常见的方式是建立虚拟专有网。但倘如你不愿建立虚拟专有网,那么一个比较简单的方式是建立一层 HTTP Proxy 来访问内网当中的数据,你只需要在某一个节点上设定 HTTP Proxy,并在你自己的电脑上配置 HTTP Proxy ,就可以完成使用对应的节点来访问具体的网页,解决 HTTP 访问的问题。

原理

HTTP Proxy Servr 的原理并不复杂,就是一个标准的正向代理。你只要能搭建这样的一个服务即可。

实际操作逻辑

以下文章以 Debian GNU/Linux 10 为例

1. 在你的节点上安装 Python3 & pip 3

Python3 是运行 Proxy.py 的运行环境,而 pip3 则是安装 Proxy.py 的工具。执行如下命令来安装。

apt install python3 python3-pip

2. 安装 Proxy.py

这里我们不使用 Virtual Env 来安装,主要是方便后续加入 Systemd 来进行控制。如果你习惯使用 Supervisord 来控制的话,就可以使用 Virtual Env 来安装 Proxy.py。执行如下命令

pip3 install proxy.py

3. 启动 Proxy 进行测试

在你的节点上执行如下命令来启动一个测试服务器。

proxy --hostname 0.0.0.0 

如果你需要监听某一个特定的 IP, 则将 0.0.0.0 修改为你具体的 IP。

配置后,你可以看到如下的输出,则说明已经成功启动了 Proxy 服务器。

启动完成后,你就可以在本地测试访问如下命令来进行测试。

curl -x [服务器IP]:8899  http://httpbin.org/get

当你看到如下输出时,确认返回值当中的 origin 是否是你的节点 IP,如果是你的节点 IP 则说明你的 HTTP Proxy 已经配置成功了。后续你就可以通过这个 HTTP Proxy 来进行访问了。

测试完成后,你可以执行 Ctrl + C 来关闭当前的 Proxy Server。

4. 配置 Basic Auth Authentication

上面配置好了 HTTP Proxy Server,但一个问题是这个 Server 没有安全验证,随时会被别人所利用。所以我们需要配置一些基本的安全配置,来解决这个问题。

在你的 Proxy.py 当中加入 –basic-auth 选项,则可以实现加入基本的鉴权。其中,用户密码用英文冒号隔开。比如我需要创建一个用户名是 admin ,密码也是 admin 的proxy ,就可以执行如下命令。

proxy --hostname 0.0.0.0 --basic-auth admin:admin

5. 配置开机自启动

现在 Auth 也有了,但是我们不能总是开着 Terminal 来运行我们的服务,所以一个好的办法是为其加入 Systemd ,这样我们就可以使用系统自带的能力来完成 Proxy 的自启动了。

执行 systemctl edit --force --full proxy.service 来创建一个新的 Systemd 服务,并在其中加入如下代码并保存。

[Unit]
Description=Proxy Service
Wants=network.target
After=network.target

[Service]
ExecStartPre=/bin/sleep 10
ExecStart=proxy --hostname 0.0.0.0 --basic-auth admin:admin
Restart=always

[Install]
WantedBy=multi-user.target

保存完成后,执行 systemctl enable proxy.service 配置该服务器的开机自启动 & systemctl start proxy.service ,即可实现 Proxy 服务的开机自启动,并在当前启动该服务。

总结

有了 HTTP Proxy ,你能以一个更加简单的方式来访问内网当中的服务器。你只需要控制 Proxy 节点的可访问权限,就能很好的处理内网数据的安全问题。