six_001
This commit is contained in:
@@ -0,0 +1,524 @@
|
||||
"""
|
||||
Author: xiao 792282@qq.com
|
||||
Date: 2025-08-10 11:36:05
|
||||
LastEditors: xiao 792282@qq.com
|
||||
LastEditTime: 2025-08-17 13:38:39
|
||||
FilePath: django_公共认证接口/企业微信认证接口.py
|
||||
Description: 这是默认设置,可以在设置》工具》File Description中进行配置
|
||||
"""
|
||||
import dataclasses
|
||||
import io
|
||||
import os
|
||||
from typing import Literal
|
||||
|
||||
import django
|
||||
import requests
|
||||
from rest_framework.viewsets import ViewSet
|
||||
|
||||
from sixGuoDjango.settings import BASE_DIR
|
||||
from wechat_token.token工具包 import sixToken
|
||||
from wechat_数据库.企业微信数据库.企微用户表 import wecom企微用户
|
||||
from wechat_数据库.用户数据库.小程序登陆用户 import weiuser
|
||||
from wechat_项目工具包.企业微信工具包.消息推送工具 import wecom消息推送
|
||||
from wechat_项目工具包.数据库的工具包.更新用户积分 import miniUser积分
|
||||
from 第三方工具目录.腾讯云 import oss上传腾讯云图片
|
||||
|
||||
os.environ.setdefault(key="DJANGO_SETTINGS_MODULE", value=f"{BASE_DIR.name}.settings")
|
||||
django.setup()
|
||||
|
||||
import pydash
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
|
||||
import jmespath
|
||||
import xmltodict
|
||||
from Crypto.Cipher import AES
|
||||
from django.conf import settings
|
||||
from django.core.handlers.wsgi import WSGIRequest
|
||||
from django.http import HttpResponse
|
||||
from rest_framework import status
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class wecomData:
|
||||
"""
|
||||
微信企业服务器推送事件的数据类
|
||||
映射XML中的字段
|
||||
"""
|
||||
# 基本消息头
|
||||
as_data: dict
|
||||
|
||||
def __post_init__(self):
|
||||
self.ToUserName = pydash.get(obj=self.as_data, path="xml.ToUserName") # toUser 企业微信CorpID
|
||||
self.FromUserName = pydash.get(obj=self.as_data, path="xml.FromUserName") # sys 此事件该值固定为sys,表示该消息由系统生成
|
||||
self.CreateTime = pydash.get(obj=self.as_data, path="xml.CreateTime") # 1403610513 消息创建时间 (整型)
|
||||
self.MsgType = pydash.get(obj=self.as_data, path="xml.MsgType") # event 消息的类型,此时固定为event
|
||||
self.Event = pydash.get(obj=self.as_data, path="xml.Event", ) # change_external_contact 事件的类型,此时固定为change_external_contact
|
||||
self.ChangeType = pydash.get(obj=self.as_data, path="xml.ChangeType", ) # add_external_contact 此时固定为add_external_contact
|
||||
self.UserID = pydash.get(obj=self.as_data, path="xml.UserID") # UserID 企业服务人员的UserID
|
||||
self.ExternalUserID = pydash.get(obj=self.as_data, path="xml.ExternalUserID", ) # woAJ2GCAAAXtWyujaWJHDDGi0mAAAA 外部联系人的userid,注意不是企业成员的账号
|
||||
self.State = pydash.get(obj=self.as_data, path="xml.State", default="六果", ) # teststate 添加此用户的「联系我」方式配置的state参数,或在获客链接中指定的customer_channel参数,可用于识别添加此用户的渠道
|
||||
self.WelcomeCode = pydash.get(obj=self.as_data, path="xml.WelcomeCode") # WELCOMECODE 欢迎语code,可用于发送欢迎语
|
||||
self.Source = pydash.get(obj=self.as_data, path="xml.Source", ) # 删除客户的操作来源,DELETE_BY_TRANSFER表示此客户是因在职继承自动被转接成员删除
|
||||
self.FailReason = pydash.get(obj=self.as_data, path="xml.FailReason", ) # 接替失败的原因, customer_refused-客户拒绝, customer_limit_exceed-接替成员的客户数达到上限
|
||||
self.ChatId = pydash.get(obj=self.as_data, path="xml.ChatId") # 群🆔
|
||||
self.UpdateDetail = pydash.get(obj=self.as_data, path="xml.UpdateDetail", ) # 群:变更详情。目前有以下几种: add_member : 成员入群 del_member : 成员退群 change_owner : 群主变更 change_name : 群名变更 change_notice : 群公告变更
|
||||
self.JoinScene: Literal[0, 1] = pydash.get(obj=self.as_data, path="xml.JoinScene", ) # 当是成员入群时有值。表示成员的入群方式 # 0 - 由成员邀请入群(包括直接邀请入群和通过邀请链接入群) # 3 - 通过扫描群二维码入群
|
||||
self.QuitScene = pydash.get(obj=self.as_data, path="xml.QuitScene") # 是用户自已退群了还是群主踢出群, 0 - 自己退群 1 - 群主/群管理员移出
|
||||
self.MemChangeCnt = pydash.get(obj=self.as_data, path="xml.MemChangeCnt") # 当当是成员入群或退群时有值。表示成员变更数量
|
||||
self.MemChangeList = pydash.get(obj=self.as_data, path="xml.MemChangeList") # 当是成员入群或退群时有值。变更的成员列表
|
||||
self.LastMemVer = pydash.get(obj=self.as_data, path="xml.LastMemVer") # 当是成员入群或退群时有值。 变更前的群成员版本号
|
||||
self.CurMemVer = pydash.get(obj=self.as_data, path="xml.CurMemVer") # 当是成员入群或退群时有值。变更后的群成员版本号
|
||||
|
||||
|
||||
class sixwecom(ViewSet):
|
||||
"""https://work.weixin.qq.com/wework_admin/frame#apps/contactsApi"""
|
||||
token = "bVWt1PEUCG" # url验证的token(江苏六果科技有限公司, 元宝零售 的应用)
|
||||
EncodingAESKey = "LS6VsiJA6Vpb3nJlt34vMZFAbuKDJkFuH2KxuEXYuUR" # 配置的解密(江苏六果科技有限公司 的 元宝零售应用)
|
||||
corp_id = "wwbe6369dc26a9d83a" # 企业微信🆔(当前账号是,江苏六果科技有限公司的账号)
|
||||
corpsecret = "SIRyum7vQh-Bt36AxnqKMX0HIiE-zkYbjYgSat7reZw" # 江苏六果科技有限公司账号 企业微信 secret
|
||||
phone_secret = "WNxZZdcU3FRS5msq_kHZaaZOBauBEjJZdQV3VrAxu0c" # 通讯录 secret
|
||||
|
||||
def 单独处理新添加用户的数据存储(self, UserID, ChangeType, State, ExternalUserID):
|
||||
"""
|
||||
:param State:
|
||||
:param ChangeType: 此时好友的状态
|
||||
:param ExternalUserID:
|
||||
:param UserID:
|
||||
"""
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get?access_token={wecom_token}&external_userid={ExternalUserID}"
|
||||
data = requests.get(url=uri).json()
|
||||
# 创建数据库数据表
|
||||
unionid = pydash.get(obj=data, path="external_contact.unionid") # 外部联系人在微信开放平台的唯一身份标识(微信unionid)
|
||||
name = pydash.get(obj=data, path="external_contact.name") # 外部联系人的名称[注1]如果是微信用户,则返回其微信昵称。如果是企业微信联系人,则返回其设置对外展示的别名或实名
|
||||
external_userid = pydash.get(obj=data, path="external_contact.external_userid") # 外部联系人的userid
|
||||
avatar = pydash.get(obj=data, path="external_contact.avatar") # 外部联系人头像。第三方应用和代开发应用均不可获取,
|
||||
gender = pydash.get(obj=data, path="external_contact.gender") # 外部联系人性别 0-未知 1-男性 2-女性。
|
||||
follow_user = pydash.get(obj=data, path="follow_user")
|
||||
for wecom in follow_user:
|
||||
userid = pydash.get(obj=wecom, path="userid") # 添加了此外部联系人的企业成员userid (企微员工的userid)
|
||||
userid = pydash.get(obj=wecom, path="userid") # 添加了此外部联系人的企业成员userid (企微员工的userid)
|
||||
tags = pydash.get(obj=wecom, path="tags")
|
||||
add_way = pydash.get(obj=wecom, path="add_way")
|
||||
oper_userid = pydash.get(obj=wecom, path="oper_userid")
|
||||
remark = pydash.get(obj=wecom, path="remark")
|
||||
createtime = pydash.get(obj=wecom, path="createtime")
|
||||
remark_mobiles = pydash.get(obj=wecom, path="remark_mobiles")
|
||||
img_file = requests.get(url=avatar).content
|
||||
src = oss上传腾讯云图片.上传图片(key=f"{userid}/{name}.png", file=io.BytesIO(img_file), )
|
||||
avatar = src
|
||||
企微用户关系对象, 企微用户是否为首次 = wecom企微用户.objects.update_or_create(wecom企微ID=UserID, )
|
||||
企微用户关系对象.wecom企微状态State = State
|
||||
企微用户关系对象.wecom好友的ID = external_userid
|
||||
企微用户关系对象.wecom好友UNIONID = unionid
|
||||
企微用户关系对象.wecom好友性别 = gender
|
||||
企微用户关系对象.wecom对好友的备注信息 = remark
|
||||
企微用户关系对象.wecom企微对好友添加的手机号码 = remark_mobiles
|
||||
企微用户关系对象.wecom好友关系状态 = ChangeType
|
||||
企微用户关系对象.wecom企微好友添加方式 = oper_userid
|
||||
企微用户关系对象.wecom好友头像 = src
|
||||
企微用户关系对象.wecom好友昵称 = name
|
||||
企微用户关系对象.wecom对好友的添加时间 = createtime
|
||||
企微用户关系对象.wecom对好友打的标签 = tags
|
||||
企微用户关系对象.wecom对好友的备注名称 = remark
|
||||
企微用户关系对象.save()
|
||||
six六果对象, sixBool = weiuser.objects.get_or_create(unionid="六果")
|
||||
six上级对象, six上级Bool = weiuser.objects.get_or_create(unionid=State) # 在这里的state是企微号绑定的 unionid
|
||||
print("在这里打印一下上级的对象是否已经存丰了", six上级对象)
|
||||
self自生对象, self对象是首次创建 = weiuser.objects.get_or_create(unionid=unionid, )
|
||||
print("点击者对象是哪个", self自生对象, "再看看是不是首次创建的啦")
|
||||
if self对象是首次创建:
|
||||
print("点击者对象是哪个", self自生对象, "再看看是不是首次创建的啦再看看是不是首次创建的啦再看看是不是首次创建的啦再看看是不是首次创建的啦再看看是不是首次创建的啦再看看是不是首次创建的啦再看看是不是首次创建的啦")
|
||||
weiuser.objects.update_or_create(unionid=unionid,
|
||||
defaults={
|
||||
"user_头像": src, "user_昵称": name, "codeQrCode": six上级对象.codeQrCode,
|
||||
"codeRemark": six上级对象.codeRemark, "codeConfigId": six上级对象.codeConfigId,
|
||||
"external_userid": external_userid, "user_用户当前的等级": six上级对象.user_用户当前的等级 + 1,
|
||||
"parent": six上级对象, "user_绑定企微UNIONID": six上级对象.user_绑定企微UNIONID, "user_绑定企微usrID账号": six上级对象.user_绑定企微usrID账号
|
||||
})
|
||||
(miniUser积分(share分享者unionidID=six上级对象.unionid, click点击者unionidID=self自生对象.unionid, bind绑定的企微unionidID=State)
|
||||
.add添加点击者添加企微员工双向红包积分(share积分类型="新人红包", share分享者二维码state=State))
|
||||
else:
|
||||
if self自生对象.parent == six六果对象:
|
||||
print("判断父元素为六果公司对象的时候,进行了数据的更新")
|
||||
weiuser.objects.update_or_create(unionid=unionid,
|
||||
defaults={
|
||||
"user_头像": src, "user_昵称": name, "codeQrCode": six上级对象.codeQrCode,
|
||||
"codeRemark": six上级对象.codeRemark, "codeConfigId": six上级对象.codeConfigId,
|
||||
"external_userid": external_userid, "user_用户当前的等级": six上级对象.user_用户当前的等级 + 1,
|
||||
"parent": six上级对象, "user_绑定企微UNIONID": six上级对象.user_绑定企微UNIONID, "user_绑定企微usrID账号": six上级对象.user_绑定企微usrID账号
|
||||
})
|
||||
# 数据更新完成后: 需要调用添加积分的方法, 对新添加企微的用户,添加积分;
|
||||
(miniUser积分(share分享者unionidID=six上级对象.unionid, click点击者unionidID=self自生对象.unionid, bind绑定的企微unionidID=State)
|
||||
.add添加点击者添加企微员工双向红包积分(share积分类型="新人红包", share分享者二维码state=State))
|
||||
# if is是否首次创建:
|
||||
# # 在这里的场景值:当用户直接扫码添加企微二维码的时候就需要在这里进行处理了
|
||||
# # 怎么判断的逻辑为: 当用户是首次添加的时候,则说明,这个用户以前在数据库中是不存在的;并且可以确定为这个用户一定是新添加的
|
||||
# print("\n🍎 🍏 🍊 🍎首次创建首次创建首次创建首次创建 🍏 🍊 🍎 🍏 \n", 上级对象.unionid)
|
||||
# self自生对象.user_头像 = src
|
||||
# self自生对象.user_昵称 = name
|
||||
# self自生对象.codeQrCode = 上级对象.codeQrCode
|
||||
# self自生对象.codeRemark = 上级对象.codeRemark
|
||||
# self自生对象.codeConfigId = 上级对象.codeConfigId
|
||||
# self自生对象.external_userid = external_userid
|
||||
# self自生对象.user_用户当前的等级 = 上级对象.level + 1
|
||||
# self自生对象.parent = 上级对象
|
||||
# self自生对象.user_绑定企微UNIONID = 上级对象.user_绑定企微UNIONID
|
||||
# self自生对象.user_绑定企微usrID账号 = 上级对象.user_绑定企微usrID账号
|
||||
# self自生对象.save()
|
||||
#
|
||||
# else:
|
||||
# self自生对象.user_头像 = src
|
||||
# self自生对象.user_昵称 = name
|
||||
# self自生对象.external_userid = external_userid
|
||||
# self自生对象.save()
|
||||
|
||||
return None
|
||||
|
||||
def 企微主动删除好友事件(self, UserID, ChangeType, State, ExternalUserID):
|
||||
pass
|
||||
|
||||
企微用户关系对象, 企微用户是否为首次 = wecom企微用户.objects.get_or_create(wecom企微ID=UserID, wecom好友的ID=ExternalUserID)
|
||||
print(企微用户关系对象, 企微用户是否为首次, "============================")
|
||||
if 企微用户是否为首次:
|
||||
pass
|
||||
else:
|
||||
企微用户关系对象.wecom好友关系状态 = ChangeType
|
||||
企微用户关系对象.save()
|
||||
|
||||
def 好友主动删除企微事件(self, UserID, ChangeType, State, ExternalUserID):
|
||||
print("\n🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 \n", UserID, State, ChangeType, ExternalUserID,
|
||||
"\n🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 \n",
|
||||
)
|
||||
print("到底哪里报错了呀为什么啊")
|
||||
企微用户关系对象, 企微用户是否为首次 = wecom企微用户.objects.get_or_create(wecom企微ID=UserID, wecom好友的ID=ExternalUserID)
|
||||
print(企微用户关系对象, 企微用户是否为首次, "==========到底哪里报错了呀为什么啊到底哪里报错了呀为什么啊==================")
|
||||
if 企微用户是否为首次:
|
||||
pass
|
||||
else:
|
||||
企微用户关系对象.wecom好友关系状态 = ChangeType
|
||||
企微用户关系对象.save()
|
||||
|
||||
def 解密POST消息(self, msg_content: bytes):
|
||||
"""
|
||||
微信回调消息解密;(这里是用于解决url回调认证,和用户之间推送消息的)
|
||||
:param msg_content: 回调消息中的body体内容
|
||||
:type msg_content: bytes 这里响应的body数据,不要序列化
|
||||
:return: 返回解密后的字典对象;
|
||||
:rtype: dict
|
||||
"""
|
||||
# 从XML中找到Encrypt标签的内容,即加密数据
|
||||
xmldict = xmltodict.parse(msg_content.decode())
|
||||
Encrypt = jmespath.search(data=xmldict, expression="xml.Encrypt", )
|
||||
# 将EncodingAESKey进行Base64解码,并补足填充字符"="
|
||||
AESKey = base64.b64decode(self.EncodingAESKey + "=")
|
||||
# 创建一个AES加密器对象,使用CBC模式和指定的初始化向量(IV)
|
||||
cipher = AES.new(key=AESKey, mode=AES.MODE_CBC, iv=AESKey[:16])
|
||||
# 对加密数据进行Base64解码
|
||||
aes_msg = base64.b64decode(Encrypt)
|
||||
# 使用解密器对象解密数据
|
||||
de_text = cipher.decrypt(aes_msg)
|
||||
# 去除解密数据地前16个字节的随机字符串
|
||||
content = de_text[16:]
|
||||
# 解析前4个字节以获取XML内容的实际长度,并将其从网络字节序转换为主机字节序
|
||||
# xml_len = socket.ntohl(struct.unpack("I", content[:4])[0])
|
||||
xml_len = int.from_bytes(content[:4], byteorder='big')
|
||||
# 根据解析出的长度提取实际的XML内容
|
||||
xml_content = content[4: xml_len + 4]
|
||||
# 将XML内容解析为字典格式
|
||||
as_dict = xmltodict.parse(xml_input=xml_content)
|
||||
# 打印解析后的字典
|
||||
return as_dict
|
||||
|
||||
def 解密GET消息(self, echostr):
|
||||
"""
|
||||
:param echostr:
|
||||
:type echostr:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
AESKey = base64.b64decode(self.EncodingAESKey + "=")
|
||||
aes_msg = base64.b64decode(echostr)
|
||||
aes = AES.new(key=AESKey, mode=AES.MODE_CBC, iv=aes_msg[:16])
|
||||
de_text = aes.decrypt(aes_msg)
|
||||
# rand_msg = cls.aes_decrypt(AESKey, aes_msg)
|
||||
content = de_text[16:] # 去掉前16随机字节
|
||||
|
||||
# 取出4字节的msg_len
|
||||
# xml_len = socket.ntohl(struct.unpack("I", content[:4])[0])
|
||||
xml_len = int.from_bytes(content[:4], byteorder='big')
|
||||
xml_content = content[4: xml_len + 4]
|
||||
print("xml_content", xml_content)
|
||||
|
||||
return xml_content
|
||||
|
||||
def api_text(self, request: WSGIRequest | Request, *args, **kwargs):
|
||||
"""
|
||||
:param request:
|
||||
:param args:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
print("============")
|
||||
file_path = f"{settings.BASE_DIR}/django_公共认证接口/WW_verify_EhbJf3fcwi8bNijV.txt"
|
||||
file_text = Path(file_path).read_text()
|
||||
return HttpResponse(content=file_text)
|
||||
|
||||
def api_get(self, request: Request):
|
||||
"""接受微信发过来的get请求,拿到参数后解决,并且回复值,结果为验证成功"""
|
||||
GET_OBJ = request.query_params
|
||||
signature = jmespath.search(data=GET_OBJ, expression="msg_signature", )
|
||||
timestamp = jmespath.search(data=GET_OBJ, expression="timestamp", )
|
||||
nonce = jmespath.search(data=GET_OBJ, expression="nonce", )
|
||||
echostr = jmespath.search(data=GET_OBJ, expression="echostr", )
|
||||
# TODO:sorted([token, timestamp, nonce])这是就是对三个参数进行字典排序
|
||||
hashstr = "".join(sorted([self.token, timestamp, nonce, echostr])) # 这是就是对三个参数进行字典排序
|
||||
sig = hashlib.sha1(string=hashstr.encode()).hexdigest()
|
||||
if sig == signature:
|
||||
ret = self.解密GET消息(echostr=echostr) # 调用算法,返回解密后的数值
|
||||
s = ret.decode().strip()
|
||||
print("成功回调")
|
||||
return HttpResponse(content=s)
|
||||
else:
|
||||
print("没有验yghd成功")
|
||||
return None
|
||||
|
||||
def 添加企业客户事件(self, as_data):
|
||||
"""https://developer.work.weixin.qq.com/document/path/92130#%E6%B7%BB%E5%8A%A0%E4%BC%81%E4%B8%9A%E5%AE%A2%E6%88%B7%E4%BA%8B%E4%BB%B6"""
|
||||
pass
|
||||
print("🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 添加企业客户事件")
|
||||
wecom_data = wecomData(as_data=as_data) # 解析返回的数据,拿到对应的值
|
||||
|
||||
res_welcom_code = wecom消息推送.发送新客户欢迎语(CALLBACK_CODE=wecom_data.WelcomeCode)
|
||||
|
||||
# 这里还需要判断, 是否要给上一级的对象添加 积分(得从用户中查看是否为推广员)
|
||||
self.单独处理新添加用户的数据存储(UserID=wecom_data.UserID, ChangeType=wecom_data.ChangeType,
|
||||
State=wecom_data.State, ExternalUserID=wecom_data.ExternalUserID,
|
||||
)
|
||||
|
||||
def 外部联系人免验证添加成员事件(self, as_data):
|
||||
"""https://developer.work.weixin.qq.com/document/path/92130#%E5%A4%96%E9%83%A8%E8%81%94%E7%B3%BB%E4%BA%BA%E5%85%8D%E9%AA%8C%E8%AF%81%E6%B7%BB%E5%8A%A0%E6%88%90%E5%91%98%E4%BA%8B%E4%BB%B6"""
|
||||
pass
|
||||
# 外部联系人添加了配置了客户联系功能且开启了免验证的成员时(此时成员尚未确认添加对方为好友),回调该事件
|
||||
wecom_data = wecomData(as_data=as_data) # 解析返回的数据,拿到对应的值
|
||||
print("🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 外部联系人免验证添加成员事件")
|
||||
# print(res_welcom_code)
|
||||
|
||||
def 删除企业客户事件(self, as_data):
|
||||
"""https://developer.work.weixin.qq.com/document/path/92130#%E5%88%A0%E9%99%A4%E4%BC%81%E4%B8%9A%E5%AE%A2%E6%88%B7%E4%BA%8B%E4%BB%B6"""
|
||||
pass
|
||||
|
||||
def 好友主动删除企微(self, as_data):
|
||||
""""""
|
||||
wecom_data = wecomData(as_data=as_data)
|
||||
pass
|
||||
print("🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 删除跟进成员事件")
|
||||
|
||||
def 删除跟进成员事件(self, as_data):
|
||||
"""https://developer.work.weixin.qq.com/document/path/92130#%E5%88%A0%E9%99%A4%E8%B7%9F%E8%BF%9B%E6%88%90%E5%91%98%E4%BA%8B%E4%BB%B6"""
|
||||
pass
|
||||
# 配置了客户联系功能的成员被外部联系人删除时,回调该事件
|
||||
wecom_data = wecomData(as_data=as_data) # 解析返回的数据,拿到对应的值
|
||||
print("🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 删除跟进成员事件")
|
||||
|
||||
def 客户接替失败事件(self, as_data):
|
||||
"""https://developer.work.weixin.qq.com/document/path/92130#%E5%AE%A2%E6%88%B7%E6%8E%A5%E6%9B%BF%E5%A4%B1%E8%B4%A5%E4%BA%8B%E4%BB%B6"""
|
||||
pass
|
||||
print("🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 客户接替失败事件")
|
||||
# 企业将客户分配给新的成员接替后,客户添加失败时回调该事件
|
||||
|
||||
def 客户群创建事件(self, as_data):
|
||||
""""""
|
||||
# 有新增客户群时,回调该事件。收到该事件后,企业可以调用获取客户群详情接口获取客户群详情。
|
||||
pass
|
||||
|
||||
def 客户群变更事件(self, as_data):
|
||||
""""""
|
||||
pass
|
||||
wecom_data = wecomData(as_data=as_data)
|
||||
print(wecom_data.UpdateDetail, "🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 这里的值是多少的呢?")
|
||||
# https://developer.work.weixin.qq.com/document/path/92130#%E5%AE%A2%E6%88%B7%E7%BE%A4%E5%8F%98%E6%9B%B4%E4%BA%8B%E4%BB%B6
|
||||
# 客户群被修改后(群名变更,群成员增加或移除,群主变更,群公告变更),回调该事件。收到该事件后,企业需要再调用获取客户群详情接口,以获取最新的群详情。
|
||||
|
||||
def 客户群解散事件(self, as_data):
|
||||
"""
|
||||
https://developer.work.weixin.qq.com/document/path/92130#%E5%AE%A2%E6%88%B7%E7%BE%A4%E8%A7%A3%E6%95%A3%E4%BA%8B%E4%BB%B6
|
||||
"""
|
||||
pass
|
||||
# 当客户群被群主解散后,回调该事件。
|
||||
wecom_data = wecomData(as_data=as_data) # 解析回调的数据
|
||||
|
||||
def 编辑企业客户事件(self, as_data):
|
||||
"""https://developer.work.weixin.qq.com/document/path/92130#%E7%BC%96%E8%BE%91%E4%BC%81%E4%B8%9A%E5%AE%A2%E6%88%B7%E4%BA%8B%E4%BB%B6"""
|
||||
pass
|
||||
|
||||
# 配置了客户联系功能的成员修改外部联系人的备注、手机号或标签时,回调该事件
|
||||
print("编辑企业客户事件编辑企业客户事件编辑企业客户事件", as_data)
|
||||
|
||||
def 获客链接_企业使用量即将耗尽事件(self, as_data):
|
||||
""""""
|
||||
pass
|
||||
|
||||
def 获客链接_使用量已经耗尽事件(self, as_data):
|
||||
""""""
|
||||
pass
|
||||
|
||||
def 获客链接_使用量增加事件事件(self, as_data):
|
||||
""""""
|
||||
pass
|
||||
|
||||
def 获客链接_获客链接异常事件(self, as_data):
|
||||
""""""
|
||||
pass
|
||||
|
||||
def 获客链接_删除获客链接事件(self, as_data):
|
||||
""""""
|
||||
pass
|
||||
|
||||
def 获客链接_获客使用量即将过期事件(self, as_data):
|
||||
""""""
|
||||
pass
|
||||
|
||||
def 获客链接_微信客户打开获客链接事件(self, as_data):
|
||||
""""""
|
||||
pass
|
||||
|
||||
def 获客链接_通过获客链接发起好友请求事件(self, as_data):
|
||||
""""""
|
||||
pass
|
||||
|
||||
def 获客链接_成员首次收消息事件(self, as_data):
|
||||
""""""
|
||||
pass
|
||||
|
||||
def 获客链接_成员多次收消息事件(self, as_data):
|
||||
"""
|
||||
参数 说明
|
||||
ToUserName 企业微信CorpID
|
||||
FromUserName 此事件该值固定为sys,表示该消息由系统生成
|
||||
CreateTime 消息创建时间 (整型)
|
||||
MsgType 消息的类型,此时固定为event
|
||||
Event 固定为customer_acquisition
|
||||
ChangeType 此时固定为message_from_customer
|
||||
UserID 企业服务人员的UserID(2024年12月19号0点开始将不再回调该字段,请根据ChatKey参数查询收消息数据)
|
||||
ExternalUserID 外部联系人的userid,注意不是企业成员的账号(2024年12月19号0点开始将不再回调该字段,请根据ChatKey参数查询收消息数据)
|
||||
LinkId 获客链接ID
|
||||
ChatSeq 成员收到消息的次数(2024年12月19号0点开始将不再回调该字段,请根据ChatKey参数查询收消息数据)
|
||||
ChatKey 成员收消息的会话信息凭据,可通过调用获取获客链接使用成员接受消息数据接口获取详细的收消息信息,回调后30分钟内有效
|
||||
应用可以根据ExternalUserID调用“获取客户详情”读取详情。
|
||||
目前仅对微信客户支持此事件
|
||||
"""
|
||||
pass
|
||||
|
||||
def api_post(self, request: WSGIRequest | Request, *args, **kwargs):
|
||||
""""""
|
||||
as_dict = self.解密POST消息(msg_content=request.body)
|
||||
wecom_data = wecomData(as_data=as_dict) # 创建了对象
|
||||
print(as_dict, "回调接口回调成功了啦:sixwecom测试")
|
||||
Event = pydash.get(obj=as_dict, path="xml.Event") # change_external_contact 事件的类型,此时固定为change_external_contact
|
||||
print(Event)
|
||||
ChangeType = pydash.get(obj=as_dict, path="xml.ChangeType") # add_external_contact 此时固定为add_external_contact
|
||||
# TODO 以下为添加好友相关的接口调用事件
|
||||
if Event == "change_external_contact" and ChangeType == "add_external_contact": # 好友主动添加企微回调
|
||||
self.添加企业客户事件(as_data=as_dict)
|
||||
# six企微回调业务处理方法().添加企业客户事件(as_data=as_dict)
|
||||
# TODO 以下为删除好友
|
||||
if Event == "change_external_contact" and ChangeType == "del_external_contact":
|
||||
print("企微主动删除好友") # 企微主动删除好友
|
||||
self.企微主动删除好友事件(UserID=wecom_data.UserID, ExternalUserID=wecom_data.ExternalUserID,
|
||||
State=wecom_data.State, ChangeType=wecom_data.ChangeType,
|
||||
)
|
||||
if Event == "change_external_contact" and ChangeType == "del_follow_user":
|
||||
print("好友主动删除企微", wecom_data) # 好友主动删除
|
||||
self.好友主动删除企微事件(UserID=wecom_data.UserID, ExternalUserID=wecom_data.ExternalUserID,
|
||||
State=wecom_data.State, ChangeType=wecom_data.ChangeType,
|
||||
)
|
||||
# TODO 编辑外部联系人好友回调事件
|
||||
if Event == "change_external_contact" and ChangeType == "edit_external_contact":
|
||||
self.编辑企业客户事件(as_data=as_dict)
|
||||
# TODO 外部联系人免验证添加成员事件 外部联系人添加了配置了客户联系功能且开启了免验证的成员时(此时成员尚未确认添加对方为好友),回调该事件
|
||||
if Event == "change_external_contact" and ChangeType == "add_half_external_contact":
|
||||
self.外部联系人免验证添加成员事件(as_data=as_dict)
|
||||
# TODO 以下为群相关的接口调用事件
|
||||
# 客户群创建事件 有新增客户群时,回调该事件。收到该事件后,企业可以调用获取客户群详情接口获取客户群详情。
|
||||
if Event == "change_external_chat" and ChangeType == "create":
|
||||
print("🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 ", "客户群创建事件")
|
||||
self.客户群创建事件(as_data=as_dict)
|
||||
# 客户群被修改后(群名变更,群成员增加或移除,群主变更,群公告变更),回调该事件。收到该事件后,企业需要再调用获取客户群详情接口,以获取最新的群详情。
|
||||
if Event == "change_external_chat" and ChangeType == "update":
|
||||
print("🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 ", "客户群变更事件")
|
||||
self.客户群变更事件(as_data=as_dict)
|
||||
# 群解散事件
|
||||
if Event == "change_external_chat" and ChangeType == "dismiss":
|
||||
print("🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 ", "客户群解散事件")
|
||||
self.客户群解散事件(as_data=as_dict)
|
||||
# TODO 以下为标签相关的接口调用事件
|
||||
# 企业客户标签创建事件
|
||||
if Event == "change_external_tag" and ChangeType == "create":
|
||||
print("🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 ", "企业客户标签创建事件")
|
||||
# 企业客户标签变更事件当企业客户标签/标签组(包括规则组的标签)被修改时,回调此事件。收到该事件后,企业需要调用获取企业标签库来获取标签/标签组的详细信息。
|
||||
if Event == "change_external_tag" and ChangeType == "update":
|
||||
print("🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 ", "企业客户标签变更事件")
|
||||
self.客户群变更事件(as_data=as_dict)
|
||||
# 企业客户标签删除事件 当企业客户标签/标签组被删除时,回调此事件。删除标签组时,该标签组下的所有标签将被同时删除,但不会进行回调。
|
||||
if Event == "change_external_tag" and ChangeType == "delete":
|
||||
print("🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 ", "企业客户标签删除事件")
|
||||
if Event == "change_external_tag" and ChangeType == "shuffle":
|
||||
print("🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 ", "企业客户标签重排事件")
|
||||
# 企业使用量即将耗尽事件
|
||||
if Event == "customer_acquisition" and ChangeType == "balance_low":
|
||||
pass
|
||||
print("🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 ", "获客链接_企业使用量即将耗尽事件")
|
||||
self.获客链接_企业使用量即将耗尽事件(as_data=as_dict)
|
||||
|
||||
# 当获客助手的使用量已经消耗完时,回调此事件
|
||||
if Event == "customer_acquisition" and ChangeType == "balance_exhausted":
|
||||
pass
|
||||
self.获客链接_使用量已经耗尽事件(as_data=as_dict)
|
||||
|
||||
# 当获客助手的使用量从0变到非0时,回调此事件。
|
||||
if Event == "customer_acquisition" and ChangeType == "balance_increased":
|
||||
pass
|
||||
self.获客链接_使用量增加事件事件(as_data=as_dict)
|
||||
|
||||
# 获客链接异常事件
|
||||
if Event == "customer_acquisition" and ChangeType == "link_unavailable":
|
||||
# 若链接中的成员暂时无法添加客户时,会在分配时自动跳过该成员。当链接中配置的全部成员账号异常,暂时无法继续添加客户时,回调此事件到创建相应链接的应用,可及时调整链接配置或流量曝光策略,避免流量浪费。
|
||||
pass
|
||||
self.获客链接_获客链接异常事件(as_data=as_dict)
|
||||
|
||||
if Event == "customer_acquisition" and ChangeType == "delete_link":
|
||||
# 当API创建的获客链接在管理端或终端被删除时,回调此事件到创建相应链接的应用。
|
||||
pass
|
||||
self.获客链接_删除获客链接事件(as_data=as_dict)
|
||||
|
||||
# 获客使用量即将过期事件 当企业购买的获客助手使用量即将过期时,将在过期前的14天、7天、3天、2天及1天回调此事件。
|
||||
if Event == "customer_acquisition" and ChangeType == "quota_expire_soon":
|
||||
pass
|
||||
self.获客链接_获客使用量即将过期事件(as_data=as_dict)
|
||||
|
||||
# 微信客户打开获客链接事件 当微信客户通过获客链接进入成员的profile页时,回调此事件。仅部分经营类目企业支持,当前企业是否支持可在「高级功能-获客助手」中查看该类目对应的功能是否包含「链接打开回调」
|
||||
if Event == "customer_acquisition" and ChangeType == "open_profile":
|
||||
pass
|
||||
self.获客链接_微信客户打开获客链接事件(as_data=as_dict)
|
||||
|
||||
# 通过获客链接发起好友请求事件 当微信用户通过获客链接点击添加到通讯录,成功发起好友请求,回调此事件到创建该链接的应用。
|
||||
if Event == "customer_acquisition" and ChangeType == "friend_request":
|
||||
pass
|
||||
self.获客链接_通过获客链接发起好友请求事件(as_data=as_dict)
|
||||
|
||||
# 成员首次收消息事件 授权企业中配置了客户联系功能的成员通过获客链接添加微信客户后,当成员首次收到客户消息时,回调此事件到创建相应链接的应用。
|
||||
if Event == "customer_acquisition" and ChangeType == "customer_start_chat":
|
||||
pass
|
||||
self.获客链接_成员首次收消息事件(as_data=as_dict)
|
||||
# 成员多次收消息事件授权企业中配置了客户联系功能的成员通过获客链接添加微信客户后,当成员第3、5、10次收到客户消息时,回调此事件到创建相应链接的应用。该事件仅在获客助手内测期限时免费提供。
|
||||
# 注:3次、5次、10次为近期新增统计,仅能针对上线后才第1次收到消息的客户生效。
|
||||
if Event == "customer_acquisition" and ChangeType == "message_from_customer":
|
||||
pass
|
||||
self.获客链接_通过获客链接发起好友请求事件(as_data=as_dict)
|
||||
return Response(data={"message": "xiao方法被调用了"}, status=status.HTTP_200_OK)
|
||||
@@ -0,0 +1,64 @@
|
||||
import datetime
|
||||
import hashlib
|
||||
|
||||
import requests
|
||||
from django.core.handlers.wsgi import WSGIRequest
|
||||
from django.utils.crypto import get_random_string
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from rest_framework.viewsets import ViewSet
|
||||
|
||||
from wechat_token.token工具包 import sixToken
|
||||
|
||||
|
||||
class wecom网页开发接口(ViewSet):
|
||||
AppID = "wwbe6369dc26a9d83a" # 企微appid
|
||||
AppSecret = "SIRyum7vQh-Bt36AxnqKMX0HIiE-zkYbjYgSat7reZw" # 企微secret
|
||||
AgentId = "1000008" # 企微agentid
|
||||
"""这个接口,是在微信开发者工具, 打开时请求的接口,进行授权状态"""
|
||||
"""官网文档:https://developer.work.weixin.qq.com/document/path/91023"""
|
||||
|
||||
@action(detail=False, methods=["get"], url_path="web_auth")
|
||||
def 企微网页授权(self, request: WSGIRequest | Request, *args, **kwargs):
|
||||
""""""
|
||||
code = request.data.get("code", False) #
|
||||
print("code的值", code)
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
code_access_token = f"https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token={wecom_token}&code={code}"
|
||||
json_data = requests.get(url=code_access_token, )
|
||||
print("打印一下企微网页授权", json_data.json())
|
||||
当用户为企业成员时在企微中打开链接返回示例如下 = {"errcode": 0, "errmsg": "ok", "userid": "USERID", "user_ticket": "USER_TICKET"}
|
||||
用户不是企微的成员同时是在个微中打开的链接 = {'errcode': 0, 'errmsg': 'ok', 'openid': 'oan8ExL4vgmQwhh0B3-MBKhqrXko'} # 元宝的号 服务号的:o28bf6x8TjmK6DZlInbC1ieAowGE
|
||||
用户是企微的成员是在个微中打开链接的 = {'userid': 'YuanBao1975', 'errcode': 0, 'errmsg': 'ok'}
|
||||
双方都是好友同时个微不企微成员时 = {
|
||||
'errcode': 0, 'errmsg': 'ok', 'openid': 'oan8ExL4vgmQwhh0B3-MBKhqrXko',
|
||||
'external_userid': 'wmj4zANAAA86UQT22hrqS4izuR_iNjKw'
|
||||
}
|
||||
return Response(data="", status=status.HTTP_200_OK)
|
||||
|
||||
@action(detail=False, methods=["get", "post", "put", "delete"], url_path="sdk_auth")
|
||||
def 企微号SDK授权(self, request: Request, *args, **kwargs):
|
||||
"""
|
||||
:param request:
|
||||
:param args:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
url = request.data.get("url", False) #
|
||||
random_string = get_random_string(length=16)
|
||||
timestamp = int(datetime.datetime.now().strftime('%s'))
|
||||
wecom_tick = sixToken.get_wecom_tick()
|
||||
d = {"noncestr": random_string, "jsapi_ticket": wecom_tick, "timestamp": timestamp, "url": url}
|
||||
# TODO 这里数据就是根据文档中,自己组装的,并没有返回值可以获取
|
||||
sorted_by_key_rise = dict(sorted(d.items()))
|
||||
str_list = "&".join([f"{key}={value}" for key, value in sorted_by_key_rise.items()])
|
||||
sig = hashlib.new(name="sha1", data=str_list.encode("utf-8")).hexdigest()
|
||||
res_data = {"nonceStr": random_string, "timestamp": timestamp, "signature": sig}
|
||||
return Response(data=res_data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
wecom网页开发接口Router = DefaultRouter()
|
||||
wecom网页开发接口Router.register(prefix="", viewset=wecom网页开发接口, basename="")
|
||||
@@ -0,0 +1,268 @@
|
||||
"""
|
||||
Author: xiao 792282@qq.com
|
||||
Date: 2025-08-10 11:36:05
|
||||
LastEditors: xiao 792282@qq.com
|
||||
LastEditTime: 2025-08-17 13:38:40
|
||||
FilePath: django_公共认证接口/小程序认证接口.py
|
||||
Description: 这是默认设置,可以在设置》工具》File Description中进行配置
|
||||
"""
|
||||
import os
|
||||
|
||||
import django
|
||||
|
||||
from sixGuoDjango.settings import BASE_DIR
|
||||
from wechat_token.token工具包 import sixToken
|
||||
from wechat_项目工具包.EMS中国邮政.ems邮政 import ems邮政物流工具
|
||||
|
||||
os.environ.setdefault(key="DJANGO_SETTINGS_MODULE", value=f"{BASE_DIR.name}.settings")
|
||||
django.setup()
|
||||
|
||||
import jmespath
|
||||
|
||||
from rest_framework.viewsets import ViewSet
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import orjson
|
||||
import pydash
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad
|
||||
from django.conf import settings
|
||||
from django.core.handlers.wsgi import WSGIRequest
|
||||
from django.http import HttpResponse
|
||||
from requests import post
|
||||
from rest_framework import status
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
|
||||
|
||||
# async def async_function(FromUserName: str, result_data_list, channelID):
|
||||
# """ 异步调用:协程"""
|
||||
# pass
|
||||
# print("在这里打印值", result_data_list)
|
||||
# result_data = pydash.get(obj=result_data_list, path="0")
|
||||
#
|
||||
# data = {'type': 'chat_messages', 'message': {"xiao": "zizi", "result_data": result_data}, }
|
||||
# await channel_layer.send(channel=channelID, message=data, )
|
||||
#
|
||||
|
||||
class sixmini(ViewSet):
|
||||
"""小程序消息的认和回调接口"""
|
||||
token = "xiaozizi"
|
||||
EncodingAESKey = "sdnUVuOoDCzMqFJZ9uECMXSYEbVAYnQTqQIWSkI1KfP" # EncodingAESKey(消息加密密钥)(不程序的)
|
||||
"""
|
||||
消息推送启用并设置消息推送配置后,用户发给小程序的消息以及开发者需要的事件推送,都将被微信转发至该服务器地址中。
|
||||
开发者也可使用云函数或微信云托管免鉴权接收消息推送,立即开通 认证
|
||||
https://mp.weixin.qq.com/wxamp/devprofile/get_profile?token=2052988734&lang=zh_CN
|
||||
"""
|
||||
|
||||
def sendCustomMessage(self, FromUserName: str, post_data_type):
|
||||
""""""
|
||||
# 回复消息的请求
|
||||
uri = f"https://api.weixin.qq.com/cgi-bin/message/custom/send"
|
||||
post_data_text = {
|
||||
"touser": FromUserName, # string 是 用户的 OpenID
|
||||
"msgtype": "text", # string 是消息类型。text表示文本消息;image表示图片消息;link表示图文链接;miniprogrampage表示小程序卡片。
|
||||
"text": {"content": "你好"},
|
||||
}
|
||||
post_data_image = {
|
||||
"touser": FromUserName, # string 是 用户的 OpenID
|
||||
"msgtype": "image", # object 否 图片消息,msgtype="image" 时必填。
|
||||
"image": {"media_id": "g_2HeEUOFmcJoWEic-ex4rj9BPVQYvkXAdTFWOMifCkxreYQhiJxxbaN8bDNfHuR"},
|
||||
# 发送的图片的媒体ID,通过 uploadTempMedia上传图片文件获得。
|
||||
}
|
||||
post_data_link = {
|
||||
"touser": FromUserName, # string 是 用户的 OpenID
|
||||
"msgtype": "link", # object 否 图文链接,msgtype="link" 时必填
|
||||
"link": {
|
||||
"title": "Happy Day", # string 否 消息标题
|
||||
"description": "Is Really A Happy Day", # string 否 图文链接消息
|
||||
"url": "URL", # string 否 图文链接消息被点击后跳转的链接
|
||||
"thumb_url": "THUMB_URL", # 图文链接消息的图片链接,支持 JPG、PNG 格式,较好的效果为大图 640 X 320,小图 80 X 80
|
||||
},
|
||||
}
|
||||
post_data_miniprogrampage = {
|
||||
"touser": FromUserName, # string 是 用户的 OpenID
|
||||
"msgtype": "miniprogrampage",
|
||||
# string 是消息类型。text表示文本消息;image表示图片消息;link表示图文链接;miniprogrampage表示小程序卡片。
|
||||
"miniprogrampage": {
|
||||
"title": "title", # string 否 消息标题
|
||||
"pagepath": "pagepath",
|
||||
# 小程序的页面路径,跟app.json对齐,支持参数,比如pages/index/index?foo=bar
|
||||
"thumb_media_id": "thumb_media_id",
|
||||
# 小程序消息卡片的封面, image 类型的 media_id,通过 uploadTempMedia接口上传图片文件获得,建议大小为 520*416
|
||||
},
|
||||
}
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
mini_token = sixToken.get_mini_token()
|
||||
result_data = post(
|
||||
url=uri, params={"access_token": mini_token},
|
||||
data=orjson.dumps(post_data_text).decode("utf-8"),
|
||||
)
|
||||
|
||||
def uploadTempMedia(self, request: WSGIRequest | Request, *args, **kwargs):
|
||||
"""
|
||||
|
||||
:param request:
|
||||
:type request:
|
||||
:param args:
|
||||
:type args:
|
||||
:param kwargs:
|
||||
:type kwargs:
|
||||
"""
|
||||
# 上传图片的接口
|
||||
uri = f"https://api.weixin.qq.com/cgi-bin/media/upload"
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
file_path = Path(settings.BASE_DIR).joinpath("django_环境/常用类/xiao小程序项目图片/ico运力管理系统.png")
|
||||
file = {"media": open(file=file_path, mode="rb", )}
|
||||
result_data = post(url=uri, params={"access_token": wecom_token, "type": "image", }, files=file)
|
||||
print(result_data.json())
|
||||
|
||||
def 消息加密方法(self, msg: str, appid: str, encoding_aes_key: str, token: str, timestamp: str,
|
||||
nonce: str, ) -> dict:
|
||||
""""""
|
||||
# 1. 生成 AESKey(Base64 解码 EncodingAESKey + "=")
|
||||
aes_key = base64.b64decode(encoding_aes_key + "=")
|
||||
# 2. 构造 FullStr = random(16B) + msg_len(4B) + msg + appid
|
||||
random_16b = os.urandom(16) # 16 字节随机字符串
|
||||
# msg_len = struct.pack( ">I", len( msg_bytes ) ) # 4 字节网络字节序 学习理解
|
||||
msg_bytes_1 = msg.encode("utf-8")
|
||||
msg_len_1 = len(msg_bytes_1).to_bytes(length=4, byteorder="big") # 大端序
|
||||
# 结果是一样的
|
||||
# print( msg_len_1, msg_len_1, "这二个一是一样的吗" )
|
||||
appid_bytes = appid.encode("utf-8")
|
||||
full_str = random_16b + msg_len_1 + msg_bytes_1 + appid_bytes
|
||||
# 3. AES-256-CBC 加密(PKCS#7 填充)
|
||||
iv = aes_key[:16] # IV 取 AESKey 前 16 字节( 自定义的随机密钥字符串的base64 前16位)
|
||||
cipher = AES.new(key=aes_key, mode=AES.MODE_CBC, iv=iv)
|
||||
encrypted = cipher.encrypt(pad(data_to_pad=full_str, block_size=AES.block_size))
|
||||
# 4. Base64 编码得到 Encrypt
|
||||
encrypt = base64.b64encode(encrypted).decode("utf-8")
|
||||
# 5. 生成 MsgSignature(sha1(token + timestamp + nonce + encrypt))
|
||||
params = sorted([str(timestamp), nonce, token, encrypt])
|
||||
signature_str = "".join(params)
|
||||
msg_signature = hashlib.sha1(signature_str.encode("utf-8")).hexdigest()
|
||||
return {"Encrypt": encrypt, "MsgSignature": msg_signature, "TimeStamp": timestamp, "Nonce": nonce, }
|
||||
|
||||
def 消息解密(self, encrypt: str):
|
||||
"""
|
||||
知识点: 微信请求发送过来的 encrypt 是 b64encode编码后的数据, 是由 iv + 编码数据 组成的
|
||||
:param encrypt:
|
||||
:type encrypt:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
aes_key = base64.b64decode(self.EncodingAESKey + "=") # 补 全 Base64 编码缺失的 '='
|
||||
# Step 2: Base64 解码加密数据
|
||||
encrypted_data = base64.b64decode(encrypt)
|
||||
# Step 3: 提取 IV 和密文
|
||||
try:
|
||||
# 获取初始向量(IV取AES Key的前16字节)
|
||||
iv = aes_key[:16]
|
||||
# 创建AES-CBC解密器
|
||||
cipher = AES.new(key=aes_key, mode=AES.MODE_CBC, iv=iv)
|
||||
# 执行AES解密
|
||||
decrypted = cipher.decrypt(encrypted_data)
|
||||
# 解析解密后的数据:# 1. 去除前 16 字节的随机字符串
|
||||
content_start = decrypted[16:]
|
||||
# 使用int.from_bytes提取消息长度(大端序)这里的 content_start 是去除掉了解密后面的前16位字符串的长度 # 2. 读取4字节的消息长度(大端序)
|
||||
xml_len = int.from_bytes(content_start[:4], byteorder='big')
|
||||
# 提取XML消息内容
|
||||
xml_content = content_start[4:4 + xml_len].decode('utf-8')
|
||||
return orjson.loads(xml_content)
|
||||
except Exception as e:
|
||||
raise ValueError("Decryption failed: " + str(e))
|
||||
|
||||
def 更新物流信息(self, data):
|
||||
WayBillId = jmespath.search(data=data, expression="WayBillId")
|
||||
OrderId = jmespath.search(data=data, expression="OrderId")
|
||||
ems邮政物流工具().更新物流快递数据(order物流公司订单编号=WayBillId, order物流订单号=OrderId)
|
||||
|
||||
def 确认收货(self, data):
|
||||
print("确认订单请求接口进来了", data)
|
||||
确认收货的方法 = pydash.get(obj=data, path="confirm_receive_method", default=None)
|
||||
order支付微信侧订单号 = pydash.get(obj=data, path="transaction_id")
|
||||
if 确认收货的方法:
|
||||
print("确认收货的回请求进来了")
|
||||
ems邮政物流工具().确认收货(order支付微信侧订单号=order支付微信侧订单号)
|
||||
确认收货的时候数据 = {
|
||||
'ToUserName': 'gh_f754c53eb962',
|
||||
'FromUserName': 'ohQkD7HmLYEYrFjFau6juOhduRU8',
|
||||
'CreateTime': 1784839115,
|
||||
'MsgType': 'event', # 类型,微信官方默认的
|
||||
'Event': 'trade_manage_order_settlement', # 接收到消息的类型, 可以判断是不是当前用户 确认收货
|
||||
'transaction_id': '4200003120202607191690098150', # 微信官方生成的支付订单号
|
||||
'merchant_id': '1700383419', # 微信官方的商户号
|
||||
'sub_merchant_id': '', # 子商户有的商户ID
|
||||
'merchant_trade_no': '548663656512',
|
||||
'pay_time': 1784410374, # 接收到消息的时间戳
|
||||
'shipped_time': 1784666314, # 商家发货的时间
|
||||
'confirm_receive_method': 2, # 当前用户 确认收货的方式 2:表示是自动确认收货 1:表示用户主动点击收货
|
||||
'confirm_receive_time': 1784839114, # 当前用户确认收货的时间
|
||||
'settlement_time': 1784839115, # 微信官方 确认的 结算时间
|
||||
}
|
||||
|
||||
def web_view_auth(self, request: WSGIRequest | Request, ):
|
||||
""""""
|
||||
pass
|
||||
# get请求的方法读取文件并且返回文件
|
||||
print("认证web_view的请求进来了没有")
|
||||
return HttpResponse(content="8cfc2017094adff8d7c66b94e209456e")
|
||||
|
||||
def api_get(self, request: Request, *args, **kwargs):
|
||||
"""
|
||||
:param request:
|
||||
:param args:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
print("认证请求进来了")
|
||||
signature = request.query_params.get("signature", "")
|
||||
timestamp = request.query_params.get("timestamp", "")
|
||||
nonce = request.query_params.get("nonce", "")
|
||||
echostr = request.query_params.get("echostr", False)
|
||||
# TODO:sorted([token, timestamp, nonce])这是就是对三个参数进行字典排序
|
||||
hashstr = "".join(sorted([self.token, timestamp, nonce])) # 这是就是对三个参数进行字典排序
|
||||
sig = hashlib.new(name="sha1", data=hashstr.encode("utf-8")).hexdigest()
|
||||
if sig == signature:
|
||||
# 当上面的值判断正确后 官方要求原样返回 echostr ;
|
||||
return HttpResponse(content=echostr, content_type="text/plan")
|
||||
else:
|
||||
return Response(data="不对", )
|
||||
|
||||
def api_post(self, request: Request, *args, **kwargs):
|
||||
"""
|
||||
调用本接口: 就可以向已经订阅消息的用户发送消息,(同时需要注意的是:消息模板需要在小程序的管理后台订阅好)
|
||||
:param request:
|
||||
:param args:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
Encrypt = request.data.get("Encrypt", False) #
|
||||
xml_content = self.消息解密(encrypt=Encrypt)
|
||||
Event = pydash.get(obj=xml_content, path="Event")
|
||||
FromUserName = pydash.get(obj=xml_content, path="FromUserName")
|
||||
MsgType = pydash.get(obj=xml_content, path="MsgType")
|
||||
print("微信官方到底推送了啥信息啊...........为什么", )
|
||||
print(xml_content)
|
||||
print("微信官方到底推送了啥信息啊...........为什么", )
|
||||
if Event == "trade_manage_order_settlement":
|
||||
# 这里的通知是微信官方发过来的, 同时,是用户没有确认收货时 达到时间后自动触发的
|
||||
self.确认收货(data=xml_content)
|
||||
if Event == "subscribe_msg_popup_event":
|
||||
print("----此接口是记录的用户点击订阅消息的次数!", xml_content)
|
||||
# subMsgObj = weiuser.objects.get(mini_openid=FromUserName)
|
||||
if Event == "add_express_path":
|
||||
# 这里发生回调,是说明物流的快递信息有推送
|
||||
pass
|
||||
self.更新物流信息(data=xml_content)
|
||||
if MsgType == "text":
|
||||
pass
|
||||
# 客服收到的文字消息(此时只是测试,直接返回了文本本身)
|
||||
# self.sendCustomMessage(FromUserName=FromUserName, post_data_type="text")
|
||||
if MsgType == "image":
|
||||
pass # 客服收到了图片消息(只是测试,收到消息后,并没有做任何的处理)
|
||||
return Response(data={"message": "xiao方法被调用了"}, status=status.HTTP_200_OK)
|
||||
@@ -0,0 +1,53 @@
|
||||
import hashlib
|
||||
|
||||
import xmltodict
|
||||
from django.core.handlers.wsgi import WSGIRequest
|
||||
from django.http import HttpResponse
|
||||
from rest_framework import status
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import ViewSet
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class sixwechat( ViewSet ):
|
||||
token = "xiaozizi" # 配置时填写的token的值(随机字符串)
|
||||
EncodingAESKey = "RWxYCl7lGDilyJ5BumHDCksnU07EpQLlo87876uUOCq" # 消息加解密密钥 (EncodingAESKey)(服务号)
|
||||
|
||||
|
||||
|
||||
def api_get( self, request: Request, ):
|
||||
""""""
|
||||
signature = request.query_params.get( key="signature", default="" )
|
||||
timestamp = request.query_params.get( key="timestamp", default="" )
|
||||
nonce = request.query_params.get( key="nonce", default="" )
|
||||
echostr = request.query_params.get( "echostr", False )
|
||||
# TODO:sorted([token, timestamp, nonce])这是就是对三个参数进行字典排序
|
||||
hashstr = "".join( sorted( [ self.token, timestamp, nonce ] ) ) # 这是就是对三个参数进行字典排序
|
||||
# hashstr = f"{}" # 这是就是对三个参数进行字典排序
|
||||
print( hashstr )
|
||||
sig = hashlib.new( name="sha1", data=hashstr.encode( "utf-8" ) ).hexdigest( )
|
||||
if sig == signature:
|
||||
# 当上面的值判断正确后 官方要求原样返回 echostr;
|
||||
print( "服务号消息推送授权成功了" )
|
||||
return HttpResponse( content=echostr, content_type="text/plan" )
|
||||
else:
|
||||
return Response( data="不对", )
|
||||
|
||||
|
||||
|
||||
def api_text( self, request: WSGIRequest | Request, ):
|
||||
""" 这个接口是的配置路径为 服务号官网 ---> 功能设置 ---> 网页授权配置 """
|
||||
print( "============" )
|
||||
return HttpResponse( content="kMmcLX8ZO79apUxA" )
|
||||
|
||||
|
||||
|
||||
def api_post( self, request: WSGIRequest | Request, ):
|
||||
""""""
|
||||
xml_data = request.body # 微信会以 XML 格式推送消息
|
||||
xml_str = xml_data.decode( "utf-8" )
|
||||
xml_dict = xmltodict.parse( xml_input=xml_str.encode( "utf-8" ) )
|
||||
return Response( data={ "message": "xiao方法被调用了" }, status=status.HTTP_200_OK )
|
||||
Reference in New Issue
Block a user