前情提要
上期分享了如何上线一个钉钉机器人。【python开发钉钉机器人|(1)上线发布、自动回复】http://t.csdnimg.cn/9lrxF
采用花生壳端口映射+python后端的方案,上线了一段时间发现明显的问题:
1、pc需要持续开机,且联网;
期间我经历了出远门关闭路由器、买了新电脑替换了旧电脑(流转给我妈了),这个方案就不切实际了。
2、空请求会导致后端报错。
请求地址填上以后,每天会有大量get请求过来,有的可能是维持连接用的,有的可能是断开连接用的,有的我也不知道是干什么用的。
这些请求如果我一一处理,就会需要写大量的正则,如果我不处理,body格式不正确、拿过来可能就会报错。
是时候改变了,框架不就是干这个用的吗。所以重构了新版本。
准备工作
1.钉钉账号、钉钉开放文档及sdk依赖(同上期)
2.云服务器(新)
3.后端代码(新)
正文
1、钉钉账号、钉钉开放文档及sdk依赖
这部分参照上次的文档即可。http://t.csdnimg.cn/9lrxF
2、云服务器
(1)服务器(本方案必买)
我选用的是腾讯云轻量应用服务器,2核2g系统盘50g带宽4m,新人优惠396元/3年。刚去瞄了一眼,现在贵了,大家酌情选购。嘚瑟一下我买那阵多便宜~~~
我自己这个机器人目前是轻量云够用了,因为文件处理在别的机器上。如果你的机器人要读写数据库,得看好容量选购。
购买完了按照官方提示一路安装,做服务器建议linux,资料经验丰富。我装的ubantu,centos也行,区别不大(我不懂)。
(2)域名(非必买)
域名就是看着美观点,云服都会带公网ip,所以非必须。
买了的话按照管理平台的指引做好解析配置。提示备案就去备案。
(3)配置nginx
下载安装nginx可以参考这个教程。【Linux中安装Nginx,很详细】http://t.csdnimg.cn/RayAf
注意事项有,登录服务器,把压缩包上传到服务器。
安装前切换到root用户,这样可以避免每次执行都要sudo、并输入密码。
如下图,第一行使用su root 切换用户。第二行password没有回显,linux都这样,摸黑输入完回车就可以,正确就会看到第三行,lighthouse@VM-16-9-ubuntu:~$变为了root@VM-16-9-ubuntu:/home/lighthouse#,root@……最后一个#,代表切换成功了。
安装好Nginx以后,找到安装路径,我的在这里/usr/local/nginx/。
运行,这是常用的命令。
# 查看nginx是否在运行
netstat -natp |grep nginx
#检查配置文件是否配置正确,启动前都会查一下
nginx -t
#启动
nginx
#停止
killall -3 nginx
在安装路径下找到/usr/local/nginx/conf/nginx.conf,修改成如下样式。此块代表nginx监听80端口,并将其全部转发到18001端口,运行后我们将用flask监听18001端口。
# 上方省略
server {
listen 80;
server_name xxxx.yyyy.zzzz; # 我看别人写的是自己的域名
charset utf-8;
location / {
proxy_pass http://localhost:18001; # 记住这个端口号,之后配置到flask中
}
# 下方省略
然后检查配置、运行。
确保80端口是开了防火墙的,默认一般是开着的。18001不需要开防火墙。
这样的好处是以后如果用云服做一些别的功能(静态网页什么的)可以在nginx做拓展配置,不需要再增加暴露在公网中的端口。
(4)其他环境依赖
如python3、flask,以及其他业务逻辑需要的三方库比如pandas。
3、后端代码
业务目录结构:
.
├── answers.py # 主要业务逻辑,分支也在此拓展
├── config # 文件服务中校验用的,不用看
│ └── standard_form_header.xlsx
├── download_file.py # 文件服务中下载和校验用的
├── Downloads # 下载目录,不用看
│ ├── 正确的测试20231001224226.xlsx
│ └── 错误的测试20231001224221.xlsx
├── getAccessToken.py # 上期展示过,定时任务
├── login.txt # nohup生成的日志
├── log.txt # nohup生成的日志
├── main.py # 后端的本体
├── __pycache__ # 环境,不用看
├── *******.py # 给其他服务器传递消息用的,手动打码
└── temp
├── 2101********1234.json # 来自用户消息上下文,修改任务状态,每个用户一个文件,手动打码
└── login.txt # 定时任务getAccessToken.py产生的记录
(1)后端的本体 main.py
以下代码主要功能为,接收到消息,解析header中的数据,进行校验,确认是钉钉推送的报文。
这一步也就可以过滤掉空请求。
校验通过的给钉钉回复报文,把body存起来,以便传给处理业务逻辑的模块,向用户发消息。
# -*- coding: utf-8 -*-
from flask import Flask, request
import json, sys
from answers import msgtype_list, tips
# answers是我写的业务逻辑
from typing import List
from alibabacloud_dingtalk.robot_1_0.client import Client as dingtalkrobot_1_0Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_dingtalk.robot_1_0 import models as dingtalkrobot__1__0_models
from alibabacloud_tea_util import models as util_models
from alibabacloud_tea_util.client import Client as UtilClient
app = Flask(__name__)
# 钉钉的报文都是POST方法
@app.route('/', methods = ['POST'])
def robot():
print(request.headers)
token = request.headers.get('token')
if token == 'aaaaaaaa': # 在钉钉的报文header中,黏贴出来
req = request.get_json()
print(req)
# 判断消息类型
try:
msg_type = req.get('msgtype') # 业务逻辑:解析用户发来的消息类型
staff_id = [req.get('senderStaffId')] # 业务逻辑:解析用户id,以便稍后给用户回消息;即便只有1个也要以列表传入
except:
print('传入的json格式有误,请检查输入')
if msg_type:
if msg_type in msgtype_list.keys():
words = msgtype_list[msg_type](req) # 业务逻辑:根据消息内容,给对应用户发送对应的消息
Dingdingbot.send_text(sys.argv[1:], words=words, user_ids=staff_id, token=my_token)
else:
# 业务逻辑:若消息类型未覆盖,则回复固定信息
Dingdingbot.send_text(sys.argv[1:], words=tips, user_ids=staff_id, token=my_token)
response_header = "HTTP/1.1 200 OK\r\n"
response_header += "\r\n"
response_body = "hello"
response = response_header + response_body
return response
# 这部分来自钉钉官方示例代码
class Dingdingbot:
def __init__(self):
pass
@staticmethod
def create_client() -> dingtalkrobot_1_0Client:
"""
使用 Token 初始化账号Client
@return: Client
@throws Exception
"""
config = open_api_models.Config()
config.protocol = 'https'
config.region_id = 'central'
return dingtalkrobot_1_0Client(config)
def send_text(
args: List[str], words, user_ids,token,
) -> None:
client = Dingdingbot.create_client()
batch_send_otoheaders = dingtalkrobot__1__0_models.BatchSendOTOHeaders()
batch_send_otoheaders.x_acs_dingtalk_access_token = token
batch_send_otorequest = dingtalkrobot__1__0_models.BatchSendOTORequest(
msg_param='{ "content": "%s" }' %words,
msg_key='sampleText', # 回复纯文本类型,如果需要更多可以参考官方文档
robot_code='xxxxxxxx', # 每个机器人一个,找到自己的黏贴
user_ids=user_ids # 即便只有1个也要以列表传入
)
try:
client.batch_send_otowith_options(batch_send_otorequest, batch_send_otoheaders,
util_models.RuntimeOptions())
except Exception as err:
if not UtilClient.empty(err.code) and not UtilClient.empty(err.message):
print(err)
pass
with open('./temp/login.txt', 'r') as f:
file = f.read()
dict = json.loads(file)
my_token = dict['token']
app.run(port=18001) # ng转发到18001端口,flask监听
(2)主要业务逻辑answers.py
解析钉钉推送的body(示例)可以得到消息类型、消息内容:
{
"conversationId":"",
"chatbotCorpId":"",
"chatbotUserId":"",
"msgId":"",
"senderNick":"王 语嫣",
"isAdmin":true,
"senderStaffId":"manager****",
"sessionWebhookExpiredTime":1684938346289,
"createAt":1684932944260,
"senderCorpId":"",
"conversationType":"1",
"senderId":"",
"sessionWebhook":"",
"text":{
"content":"这是一条文本消息,内容会在这里,可以读出来"
},
"robotCode":"",
"msgtype":"text"
}
就可以实现不同类型、不同内容的消息回复不同的内容了。
浅浅展示一下。
随机回复的消息:
不能覆盖的类型的消息:
文件类型消息:
定义某些指令:
(3)上下文处理
只靠main.py和answer.py只能实现一问一答的消息处理,如果想要拥有上下文,就需要在处理这个任务分支的时候加入任务记录,我使用的是每个用户一份json文件,通过修改'state'这个状态改变任务的完成状态。
# 修改任务状态,写入文件
message['state'] = 1
js = json.dumps(message, ensure_ascii=False)
with open(file_path, 'w', encoding='utf-8') as json_file:
json_file.write(js)
再次收到这名用户的请求时,读该用户的任务记录,查阅最后一次执行停留在什么状态。
# 读取任务状态
with open(file_path, 'r') as f:
staff_state = f.read()
message = json.loads(staff_state)
if message['state'] == 1:
pass
else:
pass
我这里状态只有简单的三四种,只需要记最近一次执行的记录,不需要把用户历史所有的信息记下来。如果有更复杂的,就需要提升抽象程度、上数据库了,资源占用也会增加。
(4)代码的运行
首先cd 项目目录下。
我主要习惯的有2种运行方式。
python3 main.py
可以在控制台里看打印信息,调试方便。
或者:
nohup python3 main.py > log.txt 2>&1 &
后台运行,输出会记入log.txt中。服务端长期运行的选它,靠谱。
# 查看后台运行的python
ps aux |grep python
# 关指定进程
kill -9 进程号
以上是这次分享的全部内容。从萌生这个念头,到入坑学代码,到今天已经过去了13个月零3天(去年我还连#后面是注释都不懂)。
功能上线了反而感觉心里空落落的,需要整理下学习心得和下一步的目标,再出发吧。