TypeScript + React.FC + Hook

Vue.js的使用更接近传统的Web开发,入门门槛比较低。同时双向数据绑定等特性也让Vue.js更为平易近人。在我看来Vue.js为易用性做的妥协在成就了Vue.js的同时,也制约了Vue.js,让他无法变得“伟大”。

在Node.js、React、Vue.js出现后,整个前端的表现能力越来越强,同时也变的越来越复杂。传统依靠jQuery的开发模式已无法支持现在大型SPA应用的开发。相比Vue.js,React这种高度组件化开发框架才更能代表今后前端的发展方向。

之前也看过一些React的相关教程。我一方面认同React的组件开发理念,另一方面又被React繁琐的开发体验劝退(Ant Design Pro早期版本里的登录实现十分劝退)。

近期有机会实际使用了React一段时间。相比初次接触React,现在的TypeScript + React.FC + Hook似乎才是React的完全形态。

React的高度组件化,让代码结构很自然的变的清晰(当然,过细的拆分也让人头痛)。TypeScript让很多潜在错误可以在编译阶段被发现,而且编辑器也开始变的智能很多。Hook的引入,彻底释放了React.FC的能力。相比Class Components使用Function Components的代码实现要简洁很多。

企业信息化建设随想

信息化在一个企业中的地位,要不就应当很高,要不就可有可无,不存在中间状态。

企业信息化实际上是以信息化为手段,将企业的管理思想进行落地。企业信息化的过程应当伴随这企业管理流程的梳理和优化,企业的信息化部门在一定程度上是一个管理决策部门。如果做不这一点,信息化部门就完全沦为了后台职能部门,做些简单的系统运维工作。

一个优秀的IT经理应当熟悉公司的业务流程、熟悉当前最新的技术动态、还要有能力争取到足够的资源来推动信息化建设。

Carrot Box流程管理平台

django-lb-workflow 我开发的一个Django流程引擎APP。设计之初是以使用便捷性为目标,自带了完整的模板,希望可以方便的集成到已有系统。尽管已经将django-lb-workflow做到尽量的易用,但距离真正的开箱即用还有一段距离。

Carrot Box是一个完整的Django易用,带了权限管理、部门、角色等必要模块,真正的做到开箱即用。通过对Carrot Box的定制可以方便的改造为OA、工单系统、CRM等业务系统。

Carrot Box的主要特点:

  • 是一个完整的应用,可以直接跑起来,开箱即用。
  • 自带HR模块,支持部门、角色的定义。支持按照部门、角色设置权限。
  • 带了几个范例流程,方便熟悉系统。
  • 包含一个代码生成器的使用范例,用于熟悉如果快速的创建一个自定义流程。
  • simplewf模块使用范例,以纯配置的方式添加新流程。

Carrot Box范例站点

之前的django-lb-workflow范例站点已经切换到 Carrot Box

地址: http://wf.haoluobo.com/

管理员账号:admin 密码:password

切换为其他用户: http://wf.haoluobo.com/impersonate/search

退回管理员账号: http://wf.haoluobo.com/impersonate/stop

将Carrot Box跑起来:

make init-pyenv
make init
make run

JetBrains Quest 解谜过程

JetBrains的推广活动,解谜后可以获取三个月的免费订阅。由于是推广活动,所以解密过程不是非常难。真正让人头痛的是那糟糕的网速,不管挂不挂代理页面的打开都非常的慢。

题目一

48 61 76 65 20 79 6f 75 20 73 65 65 6e 20 74 68 65 20 73 6f 75 72 63 65 20 63 6f 64 65 20 6f 66 20 74 68 65 20 4a 65 74 42 72 61 69 6e 73 20 77 65 62 73 69 74 65 3f

解题

很明显字符串的ASCII码,使用python很容易进行解码

>>> s = "48 61 76 65 20 79 6f 75 20 73 65 65 6e 20 74 68 65 20 73 6f 75 72 63 65 20 63 6f 64 65 20 6f 66 20 74 68 65 20 4a 65 74 42 72 61 69 6e 73 20 77 65 62 73 69 74 65 3f"
>>> ''.join(chr(int(e, 16)) for e in s.split(' '))
'Have you seen the source code of the JetBrains website?'

题目二

查看首页源代码找到解谜线索

JetBrains has a lot of products, but there is one that looks like a joke on our Products page, you should start there... (hint: use Chrome Incognito mode)
It’s dangerous to go alone take this key: Good luck! == Jrrg#oxfn$

根据提示,到产品页面。其中名为“JK”的产品介绍是“dare to lean more”,点击该产品继续进行挑战。

注:之前都不知道JetBrains居然已经有这多的产品了。

题目三

补完 https://jb.gg/### 后面确实的三个数字。数字为500到5000的质数个数。

解题

到网上找了个求质数的函数,跑了一下,很快得到结果574

import math
def isprime(n):
    if n < 2:
        return False
    for i in range(2, int(math.sqrt(n)) + 1):
        if n % i == 0:
            return False
    return True
count = 0
for i in range(500, 5000):
    if isprime(i):
        count += 1
print(count)

题目四

打开上面得到的链接,其中有张图片。图片中的字符为“MPS-31816”

解题

这个题目最坑的在于这个图片实在是太大了,约有15M,死活打不开。

  1. 查看图片属性,用编辑器直接打开图片,都没有获取到有效的信息。
  2. 直接访问 https://www.jetbrains.com/MPS-31816 显示没有这个网页。
  3. 于是直接启用Google搜索 MPS-31816 在JetBrains 网站找到对应页面。注:后续会知道图片上的图标是JebBrains网站的问题区的Logo。

题目五

“The key is to think back to the beginning.” – The JetBrains Quest team
Qlfh$#Li#|rx#duh#uhdglqj#wklv#|rx#pxvw#kdyh#zrunhg#rxw#krz#wr#ghfu|sw#lw1#Wklv#lv#rxu#lvvxh#wudfnhu#ghvljqhg#iru#djloh#whdpv1#Lw#lv#iuhh#iru#xs#wr#6#xvhuv#lq#Forxg#dqg#iru#43#xvhuv#lq#Vwdqgdorqh/#vr#li#|rx#zdqw#wr#jlyh#lw#d#jr#lq#|rxu#whdp#wkhq#zh#wrwdoo|#uhfrpphqg#lw1#|rx#kdyh#ilqlvkhg#wkh#iluvw#Txhvw/#qrz#lw“v#wlph#wr#uhghhp#|rxu#iluvw#sul}h1#Wkh#frgh#iru#wkh#iluvw#txhvw#lv#‟WkhGulyhWrGhyhors†1#Jr#wr#wkh#Txhvw#Sdjh#dqg#xvh#wkh#frgh#wr#fodlp#|rxu#sul}h1#kwwsv=22zzz1mhweudlqv1frp2surpr2txhvw2

解题

很明显要用到之前的 take this key: Good luck! == Jrrg#oxfn$ 。我一开始将问题想的太复杂了,以为第一题里的内容是密钥,用xor进行解密。在网上找了个xor解密的函数,解出来一塌糊涂。

由回头仔细看了一下这所谓的密码,其实就是一个简单的字映射。

>>> s = "Qlfh$#Li#|rx#duh#uhdglqj#wklv#|rx#pxvw#kdyh#zrunhg#rxw#krz#wr#ghfu|sw#lw1#Wklv#lv#rxu#lvvxh#wudfnhu#ghvljqhg#iru#djloh#whdpv1#Lw#lv#iuhh#iru#xs#wr#6#xvhuv#lq#Forxg#dq
g#iru#43#xvhuv#lq#Vwdqgdorqh/#vr#li#|rx#zdqw#wr#jlyh#lw#d#jr#lq#|rxu#whdp#wkhq#zh#wrwdoo|#uhfrpphqg#lw1#|rx#kdyh#ilqlvkhg#wkh#iluvw#Txhvw/#qrz#lw“v#wlph#wr#uhghhp#|rxu#iluvw#s
ul}h1#Wkh#frgh#iru#wkh#iluvw#txhvw#lv#‟WkhGulyhWrGhyhors†1#Jr#wr#wkh#Txhvw#Sdjh#dqg#xvh#wkh#frgh#wr#fodlp#|rxu#sul}h1#kwwsv=22zzz1mhweudlqv1frp2surpr2txhvw2"
>>>
>>> c = ord('J') - ord('G')
>>> ''.join(chr(ord(e) - c) for e in s)
'Nice! If you are reading this you must have worked out how to decrypt it. This is our issue tracker designed for agile teams. It is free for up to 3 users in Cloud and for 10
 users in Standalone, so if you want to give it a go in your team then we totally recommend it. you have finished the first Quest, now it’s time to redeem your first prize. Th
e code for the first quest is “TheDriveToDevelop”. Go to the Quest Page and use the code to claim your prize. https://www.jetbrains.com/promo/quest/'

Go语言版 [Telegram Shell Bot]及Go的初体验

项目地址(Go): https://github.com/vicalloy/telegram-shell-bot-go

由于对Go不熟悉,因此之前版本的 Telegram Shell Bot 采用Python实现。在Python版发布后就开始尝试使用Go重新实现,毕竟之所以要做这东西也是想借个小程序体验一下Go语言。

我对Go语言的一些看法

随着微服务的兴起,相较以往服务的粒度可以拆分的更细。系统复杂度更多的转变为微服务构架设计的复杂度。服务拆分后每个具体的服务的复杂度将降低,系统对程序语言本身的构架能力的依赖会减少很多。同时在服务拆分后,各个服务组件可以灵活的采用适合的技术来完成工作。

Go语言本身极度精简(简陋),如果要象Java一样搭建一个单体的巨型项目会存在一定困难。Go的高性能,异步,加之足够简单,使Go在一些对性能要求较高,但业务复杂度相对较低的场合会非常适合。

Go可能永远不能变的同Java一样流行,但Go已经足够成功,值得去体验。

Go开发的相关资源

  • Tour of Go 官方的简明教程,可以快速浏览一遍了解Go的主要语言特性。代码编写过程中也可以作为参考手册。
  • Go标准类库 Go的标准类库。
  • Vim Go Vim的Go语言插件,支持语法检查、自动格式化、自动补全等特性。注:挺想试试GoLand,不过GoLand只有30天的试用期。
  • 在国内Go的官网访问不是特别方便,可以使用国内翻译的中文教程。Go标准类库(中文版) Tour of Go(中文版)

开发过程中的实际体验

Telegram Shell Bot Go 是我第一次使用Go写东西,由于只是一个小项目体验还不算特别深。Go语言中最精华的并发相关内容(goroutine / channel / select)并没有用到。

总的来说Go有着还不错的开发体验,但另一方便由于习惯了Python,Go又有不少让我用的不是特别舒服的地方。有人说Go是better C,我个人还是比较认可这个观点,相比其他语言Go很像C语言。

优点

  • Python使用缩进语法保证代码的整洁。Go则直接内置代码格式化工具来强制保证代码格式的一致性。
  • 之前听说Go的包管理很差劲。这次使用已经有go mod的支持,用下来体验还不错。
  • GC、性能好、编译速度快等。

不太习惯的地方(不一定是缺点)

  • 基础类型同Python相比差太多。Slice和Python里的List相对应,但功能方面差非常多。其中一个简单的删除对象的功能都需要自己实现。不清楚这是否是出于性能的考虑,需要让用户明确清楚相关操作的时间复杂度。但使用起来真的非常不方便。用了其他开发语言后,更让人怀念Python的列表推导。
  • Go在语法层面比较简单,相对较新的概念也就异步相关的内容(这部分Go设计的不错,实际上也并不难理解),真正难的还是适应Go的编程风格。Go的Telegram类库API风格同Python版有较大差别。我想API的风格差异更多的还是同语言本身风格差异相关的。要完全适应Go的编程风格可能需要先找几个代码写的比较好的项目先学习一下。

[Telegram Shell Bot]远程执行 Shell 命令的 Telegram 机器人

项目地址: https://github.com/vicalloy/telegram-shell-bot

很早之前就打算做这么一个东西,后来因为要调用的脚本是 Python 写的,于是直接在 Bot 里调用 Python 代码。 近期把这个想法重新实现了。

网上也有类似的项目,其中一些比较简单,甚至都没有对用户做校验,这样只要有人可以连上你的机器人就可以控制你的机器。类似的机器人里功能最强的是shell-bot。”shell-bot”模拟了一个 tty,实现较复杂,没有仔细研究。

Telegram Shell Bot目前提供的功能有:

  1. 鉴权,只有在许可列表里的用户才能对机器人发号指令。
  2. 支持命令的黑白名单。注:为避免使用 ; 跳过命令检查,类似的字符也应当加到字符串黑名单中。
  3. Shell 命令的执行,如 ls、cat、ps 等。
  4. 长时间执行命令的管理。如执行 wget 操作,只返回最开始几条输出(防止一直刷屏)。之后可以通过/tasks命令查看有哪些命令还在执行中,并可以通过 /kill pid 的方式强制结束命令。
  5. 支持 sudo。注:sudo 通过 echo password | su -S 的方式实现,需要自行评估风险。
  6. 自定义脚本放在 ./scripts 目录,通过 /script 命令可以快速访问这些脚本并执行。

django-lb-workflow 近期更新

前几天又看了一下Django的Class-based views,想着django-lb-workflow的一些设计似乎还需要优化一下,于是又去折腾了一下django-lb-workflow。

Class-based views提高了代码的复用性,但过多的Mixin和继承层次让代码变的不那么容易理解。而且在面对一些“特殊”需求时会变的有些别扭。

一个列表界面搭配一个查询表单是一个很常见的操作,但想要在Django默认的ListView里添加这个查询Form并不太容易。在ListView里通过重载get_queryset方法修改查询内容。通过重载get_context_data修改context内容。按照我的理解合理的方式应当是在get_queryset函数中创建form,在form校验成功后使用form的参数作为查询条件返回查询后的queryset。但get_queryset无法直接同get_context_data函数打交道,必须先将 self.form = form 在到get_context_data中通过self.form来获取form信息。这个过程变的非常不直观和奇怪。最终我还是选择不直接继承ListView,在自定义的ListView中重载get函数,在get函数中处理form和查询。

主要更新

  • 增加“添加会签人”的功能。只有在被加签的人处理完成后,流程才可以流转到下一节点。
  • 去掉 django-el-pagination,使用Django自带的分页功能。
  • 为简化系统使用,增加 simplewf 模块。对于只有一个事项名称和内容的流程,可以不写任何代码,只需要在系统中配置流程节点。

待解决的一些问题

做了上面一些更新后,对这个项目又开始有些倦怠了。下面的这些问题可能要等到下次再对这个项目提起兴趣的时候了。

  • 流程的查看、编辑等权限通过在settings里配置校验函数实现。事实上并不太直观,操作性上也不是特别好。更合理的方式还是将权限控制这部分也放到 Class-based views 中,可以通过Minxin灵活配置。注:Django REST framework权限部分的设计比较完善,相关代码可以直接移植过来。
  • 目前还不支持Django 3.0。Django 3.0移除了部分兼容性代码,导致系统跑不起来。
  • 文档…

换回自己的VIM配置文件

用过一段时间网上的通用VIM配置文件。使用通用配置文件本来是为了省心,实际用下来却并不省心。通用配置加载的东西是在太多,出了问题很难排查。默认配置里有部分不符合自己习惯的东西也很难改,修改之后更是衍生出一堆奇怪的问题。

最近新装了一台主机,趁着设置电脑的时候将VIM的配置重新整理了一遍,改用自己的VIM配置文件。

当前VIM下的插件管理工具已经很完善,插件的添加和更新都很方便。将自己常用的插件整理了一下,并使用vim-plug对插件进行管理。在加上少量自定义设置后,VIM的配置文件就好了。在换回自己的VIM配置后,VIM终于又重新开始变的好用了。

我的VIM设置可以在 这里 找到。

Telegram机器人

最近重新开始玩Ingress。好多年没玩,主要玩家已由QQ转战Telegram了。还有玩家专门为Telegram做了个Bot用来做新人接待、面基统计等相关工作。稍微研究了一下Telegram的Bot实现,发现Telegram API功能非常强大,而且使用起来也很简单,可以轻易的做出自己的机器人。

如果你想更多的了解Telegram Bot可以做什么,怎么创建一个自己的Bot建议阅读Telegram的官方文档 Bots: An introduction for developers。如果你和我一样使用Python进行开发,可以使用python-telegram-bot进行开发。

让Telegram Bot主动推送消息

一般情况下Bot都是在接收到用户的命令后被动的回复信息,如果希望机器人主动推送消息可以先手动查询chat id,然后Bot发送消息时指定为该chat id。获取chat id的方法如下:

  1. 和机器人对话。如果希望获取group的id,这需要先将机器人加到group,再@bot /xxx给机器人发消息。
  2. 访问 https://api.telegram.org/bot<YourBOTToken>/getUpdates获取消息。
  3. 访问getUpdates接口后将得到一组JSON数据,里面哪个是chat id还是比较容易识别出来的。

一个简单的机器人实例

发送命令51job,这个机器人会调用jobmonitor检查51job的岗位更新情况。

import logging
from telegram.ext import Updater, CommandHandler
from telegram.ext.dispatcher import run_async
from job import qcwy as job_qcwy
TOKEN = 'XXX'
# Enable logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                    level=logging.INFO)
logger = logging.getLogger(__name__)
@run_async
def start(bot, update):
    """Send a message when the command /help is issued."""
    bot.send_message(
        chat_id=update.message.chat_id,
        text="Hi! I'm vicalloy's Bot. \r\n"
        "/51job update 51job. n"
    )
@run_async
def qcwy(bot, update):
    # 为防止其他人恶意向机器人发送信息触发该命令,这里还需要对发送人做个判断,只响应特定用户的请求。
    job_qcwy()
def error(bot, update):
    """Log Errors caused by Updates."""
    logger.warning('Update "%s" caused error "%s"', bot, update.error)
def main():
    updater = Updater(TOKEN)
    dp = updater.dispatcher
    dp.add_handler(CommandHandler("start", start))
    dp.add_handler(CommandHandler("help", start))
    dp.add_handler(CommandHandler("51job", qcwy))
    dp.add_error_handler(error)
    updater.start_polling()
    updater.idle()
if __name__ == '__main__':
    main()

招聘网站岗位信息更新监控工具

项目地址: https://github.com/vicalloy/jobmonitor/

image codecov.io

一个监控招聘网站工作岗位更新情况并发送通知的小工具。很早之前写的一个小脚本,近期重构了一下,让这个脚本可以更方便的扩展。

目前只做了前程无忧以及V2EX的支持,欢迎添加其他网站的支持。

目的

  • 招聘网站的问题
    • 招聘网站每天都会显示大量的岗位更新,但大多岗位都是常年发布,要从这些岗位里过滤出真正更新的岗位并不容易。
    • 招聘网站的搜索功能还不够完善,做不了高度个性化的定制化搜索条件。
    • 专业论坛的招聘版块,几乎没有搜索功能。
  • 这个工具可以做什么
    • 支持定制招聘网站搜索条件,并对网站提供的标准搜索功能进行少量增强。
    • 对检索到的工作岗位进行过滤,如果该岗位之前已发布过,自动忽略。
    • 可部署在服务器上,设置定时任务方式定时推送岗位更新,支持多种消息推送方式。
    • 新工作岗位通知方式支持:显示到控制台、保存到文件、发送到 Slack (强烈推荐 Slack )。注:如果想支持微信、邮件的通知,需要自行扩展。
    • 内置了 51JOB 和 V2EX 的支持。注:如需要支持其他招聘网站,需要自行进行扩展。

使用范例

  • 初始化项目
$ mkdir jobs
$ cd jobs
$ pip install pipenv --upgrade
$ pipenv --python 3.6
$ pipenv shell
$ pipenv install lbjobmonitor
  • 创建jobs.py。使用python jobs.py执行查询。
  • 可在服务器上使用 crontab 设置定时任务,定期检查
# jobs.py
import os
from lbjobmonitor.message import CLIMessageBackend
from lbjobmonitor.message import FileMessageBackend
from lbjobmonitor.monitor import QCWYJobMonitor
from lbjobmonitor.storage import JobMonitorJsonStorage
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = BASE_DIR
def qcwy():
    params = {  # 51job 的查询参数。51job 设置好查询条件后发起查询,通过 chrome 的调试功能查看请求的具体参数。
        'saltype': '',  # 薪资范围
        'keyword': 'python',  # 关键词
        'postchannel': '0000',
        'keywordtype': '2',
        'jobarea': '080200',  # 地区编码
        'pagesize': '5',  # 每页记录数
        '': ''
    }
    storage = JobMonitorJsonStorage(base_path=DATA_DIR)  # 使用 JSON 方式将工作列表保存到当前目录
    message_backend_list = [  # 显示的推送方式
        CLIMessageBackend(),  # 显示到控制台
        FileMessageBackend(fn=os.path.join(DATA_DIR, 'jobs.txt'))  # 保存到文件
    ]
    monitor = QCWYJobMonitor(
        storage=storage, message_backend_list=message_backend_list)
		# monitor.max_page_idx = 1  # 最多查询页数,设置成 1 方便调试
    skip_words = ['AI']
    monitor.monitor_jobs(params=params, skip_words=skip_words)  # 执行查询
if __name__ == "__main__":
    qcwy()

代码导航

  • monitor.py
    • JobMonitor 工作岗位监控基础类
    • QCWYJobMonitor 51JOB岗位监控实现
    • V2exJobMonitor V2EX岗位监控实现
  • storage.py
    • JobMonitorStorage 存储区基础类
    • JobMonitorJsonStorage 将信息以json方式保存到文件的存储区实现
  • message.py
    • BaseMessageBackend 消息发送处理后端基础类
    • IMMessageBackend IM类消息的后端基础类
    • CLIMessageBackend 将消息发送到控制台
    • FileMessageBackend 将消息保存到文件
    • SlackMessageBackend 将消息发送到Slack
    • TelegramMessageBackend 将消息发送到Telegram
  • models.py
    • Job 岗位信息基础数据类
    • QCWYJob 51JOB的岗位信息解析类
    • V2exJob V2EX的岗位信息解析类

注: 还为这个工具做了一个 web 前端界面 https://github.com/vicalloy/jobmonitorweb/ 可通过 web 端查看推送信息。不过个人觉得用 slack 或 telegram 接收和查看推送信息更方便。这个项目里使用了Django Channels来做Web端的实时消息推送,如果感兴趣可以参考一下。

中心化服务问题

最初也考虑过将这个功能做成服务,用户可以通过WEB界面配置自己的订阅规则和消息的接收方式。不过一般网站都会有反爬虫的处理,如果服务器对一个网站访问过于频繁很可能会被该网站给ban掉,这个方案不可行。

当然,如果真想把这个功能做成服务还是有办法的。可以将主要功能用JS实现,用户将数据抓取的规则配置和历史记录保存在服务器。用户打开浏览器手动刷新岗位信息,数据抓取通过用户的浏览器完成。