给Flutter包私有仓库pub_server增加企业微信机器人消息
本文相关代码地址:github。
效果:
功能说明
默认的私有仓库pub_server 服务程序在package上传成功后只是在命令行中输出了一行成功日志,缺少必要的消息通知,包发布成功了开发人员也不知道。 因为工作中企业微信使用较多,而且其中的群机器人可以方便的在工作群中推送消息,因此想着将上传成功的消息通过群机器人推送到群中。
企业微信群机器人的接入方法参看:群机器人配置说明。
代码分析
相关类
修改代码前还是先完整看了一下pub_server 的实现代码。
shelf_pubserver.dart 文件ShelfPubServer 类,该类负责服务端各接口的具体处理逻辑。在requestHandler 方法中就可以得到每个接口的请求和反馈报文。其中package包上传功能涉及到/api/packages/versions/newUpload 和/api/packages/versions/newUploadFinish 两个接口,第一个接口的职责说具体的上传逻辑,第二个接口只是完成上传操作。第一个接口处理成功后会返回一个302请求,让客户端直接请求第二个接口。
cow_repository.dart 文件CopyAndWriteRepository 类,该类是整个服务的核心,ShelfPubServer 类所有的处理操作最终都是交给本类处理。其中,该类持有了file_repository.dart 文件的FileRepository 类负责实际的上传操作。
理论上,在ShelfPubServer 类、CopyAndWriteRepository 类、FileRepository 类这三个类的相关代码中我们都可以监控到package上传成功的消息,可以在相关的代码位置向企业微信的群机器人发送消息请求。
初始方案
我原本是计划在ShelfPubServer 类的_finishUploadSimple 方法中,/api/packages/versions/newUploadFinish 接口返回Successfully uploaded package. 信息时直接发送机器人消息。 但是后面发现两个问题:第一,在/api/packages/versions/newUploadFinish 接口的请求参数中并未携带上传的package包的信息,所以没办法发送相关的通知文本;第二,在ShelfPubServer 类的代码中塞入给群机器人发送消息的代码就污染了原本逻辑代码,造成了不必要的代码耦合,如果我们后面需要增加邮件通知、钉钉通知,那是不是还要新增代码?
其中第一个问题可以通过修改/api/packages/versions/newUpload 接口返回报文的方法实现,不过算是对原逻辑代码的改动,暂时不采用;思考第二个问题时,准备自己实现一个请求处理拦截器进行代码解耦,然后发现了一段代码:
// 启动一个http服务
return shelf_io.serve(
const Pipeline()
.addMiddleware(logRequests()) // 日志中间件
.addHandler(server.requestHandler), // 请求处理器
host,
port);
这是基于shelf 框架启动http服务的代码,其中addMiddleware(logRequests()) 是给接口请求和反馈增加日志输出的中间件。 我没有写过后端接口,看了下Pipeline 的代码后觉得,正好可以使用中间件的方式来实现这个功能。
实现
添加一个通用的拦截器中间件
新增拦截器中间件interceptor_middleware.dart :
Middleware interceptorMiddleware({Function beforeHandler, Function successHandler, Function errorHandler})
该中间件支持业务代码在每个请求处理前,处理成功后,处理失败后分别执行自己的逻辑。
添加企业微信群机器人中间件
新增机器人中间件qywx_robot_middleware.dart :
Middleware qywxRobotMiddleware(String qywxkey, {MsgBuilder msgBuilder})
该中间件封装了[interceptorMiddleware],其中qywxkey 是企业微信开放平台的key,msgBuilder 是群机器人发送消息的消息体构造器,具体参看群机器人配置说明的消息类型及数据格式。
中间件的实现代码中拦截了/api/packages/versions/newUpload 接口请求,当接口处理成功,且statusCode 为302时,则调用相关API发送群机器人消息。
本中间件提供了一个默认的消息体构造器:
Map defaultMsgBuilder(PackageVersion packageVersion) {
return {
'msgtype': 'text',
'text': {
'content': 'OMG~我的天呐!Flutter Package ${packageVersion.packageName}的新品v${packageVersion.versionString}也太好看了吧!用它!用它!用它!',
'mentioned_list': ['@all'],
}
};
}
可选择添加企业微信群机器人中间件
var pipeline = Pipeline();
if (qywxkey!=null && qywxkey.isNotEmpty) {
pipeline = pipeline.addMiddleware(qywxRobotMiddleware(qywxkey)); // 企业微信机器人中间件
}
pipeline = pipeline.addMiddleware(logRequests()); // 日志中间件
// 启动一个http服务
return shelf_io.serve(
pipeline.addHandler(server.requestHandler), // 请求处理器
host,
port);
在启动HTTP服务时,当qywxkey 存在时,则加载机器人中间件,否则不加载。 其中qywxkey 通过命令行参数的形式传入:
ArgParser argsParser() {
var parser = ArgParser();
// 给参数解析器设置可支持的参数以及默认值
parser.addOption('directory',
abbr: 'd', defaultsTo: 'pub_server-repository-data');
parser.addOption('host', abbr: 'h', defaultsTo: 'localhost');
parser.addOption('port', abbr: 'p', defaultsTo: '8080');
parser.addOption('qywxkey', abbr: 'q', defaultsTo: '');
parser.addFlag('standalone', abbr: 's', defaultsTo: false);
return parser;
}
完整代码地址
github
使用方法
cd ~/pub_server
dart example/example.dart -d ~/package-db -h 192.168.1.2 -p 8090 -q xxxx-xxxx-qywxkey
在启动服务时,在传入IP地址和接口外,额外传入qywxkey 即可。
最终效果:
待优化细节
qywxRobotMiddleware 中间件代码中拦截的是/api/packages/versions/newUpload 接口,该接口只是上传操作,并没有上传成功,所以理论上还是要拦截/api/packages/versions/newUploadFinish 接口;/api/packages/versions/newUpload 接口只返回了package的名称和版本号,没有该版本的修改内容;但是Package的官方指南中,更新说明是存在CHANGELOG.md 文件中,所以想要读取版本更新说明,后面还需要解析MarkDown文件;
|