python开发钉钉机器人|(2)nginx+flask+上下文处理

前情提要

上期分享了如何上线一个钉钉机器人。【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天(去年我还连#后面是注释都不懂)。

功能上线了反而感觉心里空落落的,需要整理下学习心得和下一步的目标,再出发吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值