接粉报警

This commit is contained in:
AKW 2023-12-19 16:03:02 +08:00
parent 44ebb1a884
commit f9d4bb7a0c
6 changed files with 225 additions and 88 deletions

View File

@ -232,6 +232,9 @@ class JqrNewUserSendGroupMsg(JqrBaseSendMsg):
priority = models.IntegerField(verbose_name='优先级', default=0) priority = models.IntegerField(verbose_name='优先级', default=0)
type = models.IntegerField(verbose_name='类型', choices=JqrNewUserSendGroupMsgTypeChoices.choices, type = models.IntegerField(verbose_name='类型', choices=JqrNewUserSendGroupMsgTypeChoices.choices,
default=JqrNewUserSendGroupMsgTypeChoices.PRIVATE) default=JqrNewUserSendGroupMsgTypeChoices.PRIVATE)
remark = models.CharField(
max_length=128, verbose_name='备注模板', null=True, blank=True)
tags = models.JSONField(verbose_name='标签', null=True, blank=True)
class Meta: class Meta:
db_table = 'jqr_newusersendgroupmsg' db_table = 'jqr_newusersendgroupmsg'
@ -243,20 +246,6 @@ class JqrNewUserSendGroupMsg(JqrBaseSendMsg):
return self.taskname return self.taskname
class JqrNewUserOrderSendGroupMsg(JqrBaseSendMsg):
# 优先级
priority = models.IntegerField(verbose_name='优先级', default=0)
class Meta:
db_table = 'jqr_newuserordersendgroupmsg'
verbose_name = 'JQR New User Order Send Group Message'
verbose_name_plural = 'JQR New User Order Send Group Messages'
ordering = ['-ctime']
def __str__(self):
return self.taskname
class JqrTimePrivateSendGroupMsg(JqrBaseSendMsg): class JqrTimePrivateSendGroupMsg(JqrBaseSendMsg):
sendtype = models.IntegerField(verbose_name='发送类型1=高级群发2=极速群发', sendtype = models.IntegerField(verbose_name='发送类型1=高级群发2=极速群发',
choices=JqrSendGroupMsgSendTypeChoices.choices, choices=JqrSendGroupMsgSendTypeChoices.choices,
@ -280,27 +269,6 @@ class JqrTimePrivateSendGroupMsg(JqrBaseSendMsg):
return self.taskname return self.taskname
class JqrTimeQunSendGroupMsg(JqrBaseSendMsg):
sendtype = models.IntegerField(verbose_name='发送类型1=高级群发2=极速群发',
choices=JqrSendGroupMsgSendTypeChoices.choices,
default=JqrSendGroupMsgSendTypeChoices.ADVANCED)
needsid = models.IntegerField(verbose_name='是否需要sid', choices=JqrSendGroupMsgNeedSidChoices.choices,
default=JqrSendGroupMsgNeedSidChoices.NOT_NEED)
timetype = models.IntegerField(verbose_name='发送时间类型', choices=JqrSendGroupMsgTimeTypeChoices.choices,
default=None)
timejson = models.JSONField(verbose_name='发送时间对象', default=None)
sendtime = models.DateTimeField(verbose_name='下次具体发送时间', null=True, blank=True)
class Meta:
db_table = 'jqr_timequnsendgroupmsg'
verbose_name = 'JQR Time Private Send Group Message'
verbose_name_plural = 'JQR Time Private Send Group Messages'
ordering = ['-ctime']
def __str__(self):
return self.taskname
class JqrKeywordSendGroupMsg(JqrBaseSendMsg): class JqrKeywordSendGroupMsg(JqrBaseSendMsg):
type = models.IntegerField(verbose_name='类型', choices=JqrKeywordSendGroupMsgTypeChoices.choices, type = models.IntegerField(verbose_name='类型', choices=JqrKeywordSendGroupMsgTypeChoices.choices,
default=JqrKeywordSendGroupMsgTypeChoices.PRIVATE) default=JqrKeywordSendGroupMsgTypeChoices.PRIVATE)

View File

@ -10,7 +10,7 @@ from apps.qc.choices import QcWechatbizeventAddcontactIsDeleteChoices, QcQrcodes
from apps.qc.models import QcWechatbizeventAddcontact, QcQrcodes, QcWechatbizuserinfo, QcQrcodesEditLog, QcCorpinfo from apps.qc.models import QcWechatbizeventAddcontact, QcQrcodes, QcWechatbizuserinfo, QcQrcodesEditLog, QcCorpinfo
from apps.jqr.models import JqrExternalFollowUser, JqrExternalUser from apps.jqr.models import JqrExternalFollowUser, JqrExternalUser
from apps.qc.utils import generate_qrcode_by_qrcode from apps.qc.utils import generate_qrcode_by_qrcode
from apps.warning.models import QcWarningRule from apps.warning.models import QcWarningRule, WarningLog
from libs.wechat import WechatWorkerUtil from libs.wechat import WechatWorkerUtil
logger = logging.getLogger('apps') logger = logging.getLogger('apps')
@ -23,8 +23,7 @@ def save_add_contact(data, corpinfo, *args, **kwargs):
if state and state.startswith('mg') and '_' in state: if state and state.startswith('mg') and '_' in state:
[_, _, qrcodeid] = state.split('_') [_, _, qrcodeid] = state.split('_')
data['qrcodeid'] = qrcodeid data['qrcodeid'] = qrcodeid
# TODO 异步执行
check_qrcode(qrcodeid, userid, corpinfo)
externaluserid = data.get('externaluserid') externaluserid = data.get('externaluserid')
corpid = data.get('corpid') corpid = data.get('corpid')
QcWechatbizeventAddcontact.objects.update_or_create( QcWechatbizeventAddcontact.objects.update_or_create(
@ -38,6 +37,11 @@ def save_add_contact(data, corpinfo, *args, **kwargs):
'ctime': datetime.now(), 'ctime': datetime.now(),
} }
) )
qrcodeid = data.get('qrcodeid')
if qrcodeid:
check_qrcode.delay(qrcodeid, userid, corpinfo)
tag_remark_contact.delay(qrcodeid, userid, externaluserid, corpinfo)
alarm_userid_by_qrcodeid.delay(qrcodeid)
edit_add_contact.delay(data, corpinfo, *args, **kwargs) edit_add_contact.delay(data, corpinfo, *args, **kwargs)
@ -103,13 +107,18 @@ def delete_add_contact(data, corpinfo, *args, **kwargs):
def check_qrcode(qrcodeid, userid, corpinfo): def check_qrcode(qrcodeid, userid, corpinfo):
qrcode = QcQrcodes.objects.get(id=qrcodeid) qrcode = QcQrcodes.objects.get(id=qrcodeid)
users = qrcode.userids users = qrcode.userids
if not users:
return
online_userid_map = {user.get('userid'): user for user in users if
user.get('isonline') == QcQrcodesEditLogOperateTypeChoices.OFFLINE}
corpid = qrcode.corpid corpid = qrcode.corpid
userinfo = QcWechatbizuserinfo.objects.get(corpid=corpid, userid=userid) userinfo = QcWechatbizuserinfo.objects.get(corpid=corpid, userid=userid)
today = date.today() today = date.today()
# 账号当前活码的接粉情况 # 账号当前活码的接粉情况
count_info = QcWechatbizeventAddcontact.objects.filter(corpid=corpinfo.get('corpid'), userid=userid).values( user_count_info = QcWechatbizeventAddcontact.objects.filter(corpid=corpid,
userid__in=list(online_userid_map.keys())).values(
'corpid', 'corpid',
'userid').aggregate( 'userid').annotate(
total_count=Count('*'), total_count=Count('*'),
today_count=Sum( today_count=Sum(
Case( Case(
@ -129,11 +138,13 @@ def check_qrcode(qrcodeid, userid, corpinfo):
default=Value(0), default=Value(0),
) )
) )
) ).values('userid', 'total_count', 'today_count', 'qrcode_count', 'qrcode_today_count')
user_count_info_map = {user_count.get('userid'): user_count for user_count in user_count_info}
count_info = user_count_info_map.get(userid)
# 该账号总共加了多少人 # 该账号总共加了多少人
total_count = count_info.get('total_count', 0) total_count = count_info.get('total_count')
# 该账号今天加了多少人 # 该账号今天加了多少人
today_count = count_info.get('today_count', 0) today_count = count_info.get('today_count')
# 该账号当前活码加了多少人 # 该账号当前活码加了多少人
qrcode_count = count_info.get('qrcode_count') qrcode_count = count_info.get('qrcode_count')
# 该账号今天当前活码加了多少人 # 该账号今天当前活码加了多少人
@ -143,27 +154,31 @@ def check_qrcode(qrcodeid, userid, corpinfo):
# 该用户总加粉总上限 # 该用户总加粉总上限
totallimitaddusercount = userinfo.totallimitaddusercount totallimitaddusercount = userinfo.totallimitaddusercount
# 该用户当前活码上限 # 该用户当前活码上限
user_qrcode_limitcount = None user_qrcode_limitcount = online_userid_map.get(userid, {}).get('limitcount')
for user in users:
if user.get('userid') == userid:
user_qrcode_limitcount = user.get('limitcount')
break
user_count = len(users)
offline_user_count = len(
[user for user in users if user.get('isonline') == QcQrcodesEditLogOperateTypeChoices.OFFLINE])
# 该账号是否需要下线 # 该账号是否需要下线
need_offline = False need_offline = False
warning_text_list = []
# 当前接粉号
current_user = online_userid_map.get(userid, {})
# 该账号今天当前活码加的人数 大于 该用户当前活码上限 # 该账号今天当前活码加的人数 大于 该用户当前活码上限
if user_qrcode_limitcount is not None and qrcode_today_count > user_qrcode_limitcount > 0: if user_qrcode_limitcount is not None and qrcode_today_count >= user_qrcode_limitcount > 0:
warning_text_list.append(
f'账号 {current_user.get("username")}-{current_user.get("alias")}-{userid} 今天 {qrcode.name}活码加的人数: {qrcode_today_count} 大于 该用户当前活码上限: {user_qrcode_limitcount}')
need_offline = True need_offline = True
if total_count is not None and total_count > totallimitaddusercount > 0: if total_count is not None and total_count >= totallimitaddusercount > 0:
warning_text_list.append(
f'账号 {current_user.get("username")}-{current_user.get("alias")}-{userid} 总共加了 {total_count} 人 大于 该用户总共加粉上限: {totallimitaddusercount}')
need_offline = True need_offline = True
if today_count is not None and today_count > limitaddusercount > 0: if today_count is not None and today_count >= limitaddusercount > 0:
warning_text_list.append(
f'账号 {current_user.get("username")}-{current_user.get("alias")}-{userid} 今天加了 {today_count} 人 大于 该用户今天加粉上限: {limitaddusercount}')
need_offline = True need_offline = True
if need_offline: if need_offline:
if user_count - offline_user_count > 1: warning_setting_id = qrcode.warning_setting_id
# 最后一个接粉号不下线
if len(online_userid_map) > 1:
for user in users: for user in users:
if user.get('userid') == userid: if user.get('userid') == userid:
user['isonline'] = QcQrcodesEditLogOperateTypeChoices.OFFLINE user['isonline'] = QcQrcodesEditLogOperateTypeChoices.OFFLINE
@ -171,26 +186,132 @@ def check_qrcode(qrcodeid, userid, corpinfo):
qrcode.save() qrcode.save()
# 重新生成活码, 请求企微api更新活码 # 重新生成活码, 请求企微api更新活码
generate_qrcode_by_qrcode(qrcode, corpinfo, is_update=True) generate_qrcode_by_qrcode(qrcode, corpinfo, is_update=True)
# 生成活码修改记录 # 生成活码修改记录
QcQrcodesEditLog.objects.create( QcQrcodesEditLog.objects.create(
userid=userid, userid=userid,
uid=qrcode.uid,
corpid=qrcode.corpid,
agentid=qrcode.agentid,
qrcodeid=qrcode.pk,
detail={
'totallimit': totallimitaddusercount,
'daliylimit': limitaddusercount,
'qrcodelimit': user_qrcode_limitcount,
'addcount': total_count,
'qrcodeaddcount': qrcode_count,
'qrcode_today_count': qrcode_today_count,
},
type=QcQrcodesEditLogTypeChoices.AUTO,
operatetype=QcQrcodesEditLogOperateTypeChoices.OFFLINE,
)
WarningLog.objects.create(
text=','.join(warning_text_list),
uid=qrcode.uid, uid=qrcode.uid,
corpid=qrcode.corpid, log_info={
agentid=qrcode.agentid, 'corpid': corpid,
qrcodeid=qrcode.pk, 'userid': userid,
detail={
'totallimit': totallimitaddusercount,
'daliylimit': limitaddusercount,
'qrcodelimit': user_qrcode_limitcount,
'addcount': total_count,
'qrcodeaddcount': qrcode_count,
'qrcode_today_count': qrcode_today_count,
}, },
type=QcQrcodesEditLogTypeChoices.AUTO, warning_setting_id=warning_setting_id,
operatetype=QcQrcodesEditLogOperateTypeChoices.OFFLINE,
) )
@shared_task(name='alarm_userid_by_qrcodeid', queue='qrcode')
def alarm_userid_by_qrcodeid(qrcodeid):
qrcode = QcQrcodes.objects.get(id=qrcodeid)
users = qrcode.userids
if not users:
return
# 活码成员报警检测
checkminutes = qrcode.checkminutes
now = datetime.now()
checkminutes_ago = now - timedelta(minutes=checkminutes)
online_userid_map = {user.get('userid'): user for user in users if
user.get('isonline') == QcQrcodesEditLogOperateTypeChoices.OFFLINE}
need_check_userid_map = {}
for userid, user in online_userid_map.items():
starttime = user.get('starttime')
if starttime is None:
continue
try:
starttime = datetime.fromisoformat(starttime)
if starttime < checkminutes_ago:
need_check_userid_map[userid] = user
except Exception as e:
logger.error(e)
continue
if len(need_check_userid_map.keys()) == 0:
return
corpid = qrcode.corpid
# 当前活码在线接粉号的接粉情况
user_count_info = QcWechatbizeventAddcontact.objects.filter(corpid=corpid,
userid__in=list(need_check_userid_map.keys()),
qrcodeid=qrcodeid,
createtime__gt=checkminutes_ago.timestamp()).values(
'corpid',
'userid').annotate(
qrcode_count=Count('*'),
).values('qrcode_count', 'userid')
need_check_user_count = len(need_check_userid_map.keys())
total_qrcode_count = sum([user_count.get('qrcode_count') for user_count in user_count_info])
average_qrcode_count = total_qrcode_count / need_check_user_count
offlinechecknum = qrcode.offlinechecknum
warning_logs = []
for user_count in user_count_info:
qrcode_count = user_count.get('qrcode_count', 0)
if (qrcode_count - average_qrcode_count) < offlinechecknum:
user = need_check_userid_map.get(user_count.get('userid'))
text = f"账号 {user.get('userid')} {user.get('username')} {user.get('alias')} 在渠道活码 {qrcode.name} 接粉异常"
log_info = {
'corpid': corpid,
'userid': user_count.get('userid'),
}
warning_log = WarningLog(
uid=qrcode.uid,
warning_setting_id=qrcode.warning_setting_id,
log_info=log_info,
text=text,
)
warning_logs.append(warning_log)
WarningLog.objects.bulk_create(warning_logs)
qrcode.lastchecktime = now
qrcode.save()
@shared_task(name='tag_remark_contact', queue='tag_remark_contact')
def tag_remark_contact(qrcodeid, userid, externaluserid, corpinfo):
corpid = corpinfo.get('corpid')
appsecret = corpinfo.get('appsecret')
qrcode = QcQrcodes.objects.get(id=qrcodeid)
remark = qrcode.remark
wechat_worker = WechatWorkerUtil(corpid, appsecret)
if remark:
# '{添加日期} -{昵称}'
if '{添加日期}' in remark:
now = datetime.now().strftime('%Y%m%d')
remark = remark.replace('{添加日期}', now)
if '{昵称}' in remark or '{性别}' in remark:
success, data = wechat_worker.get_external_contact(externaluserid)
if success:
(external_contact, _, _) = data
name = external_contact.get('name')
remark = remark.replace('{昵称}', name)
gender = external_contact.get('gender')
# gender 外部联系人性别 0-未知 1-男性 2-女性
if '{性别}' in remark:
if gender == 1:
remark = remark.replace('{性别}', '')
elif gender == 2:
remark = remark.replace('{性别}', '')
else:
remark = remark.replace('{性别}', '未知')
wechat_worker.externalcontact_remark(userid, externaluserid, remark)
tags = qrcode.tags
if tags:
tag_ids = [tag.get('id') for tag in tags]
wechat_worker.externalcontact_mark_tag(userid, externaluserid, tag_ids)
# 接粉号报警检测任务 # 接粉号报警检测任务
@shared_task(name='check_follow_user', queue='check_follow_user') @shared_task(name='check_follow_user', queue='check_follow_user')
def check_follow_user(): def check_follow_user():
@ -199,11 +320,8 @@ def check_follow_user():
count = warning_rules.count() count = warning_rules.count()
for i in range(0, count, batch_size): for i in range(0, count, batch_size):
for warning_rule in warning_rules[i:i + batch_size]: for warning_rule in warning_rules[i:i + batch_size]:
warning_setting_id = warning_rule.warning_setting_id
users = warning_rule.userids users = warning_rule.userids
users = [{"alias": "饿了么小霸王155", "corpid": "ww4450b72962300373", "userid": "mg12976",
"username": "[155]5G-8-1-汪琦"},
{"alias": "饿了么小霸王157", "corpid": "ww4450b72962300373", "userid": "mg04598",
"username": "[157]5G-3-2-汪潇"}]
if users and len(users) > 0: if users and len(users) > 0:
corpid = users[0]["corpid"] corpid = users[0]["corpid"]
userid_map = {user.get('userid'): user for user in users} userid_map = {user.get('userid'): user for user in users}
@ -221,6 +339,7 @@ def check_follow_user():
) )
), ),
).values('userid', 'warning_interval_count') ).values('userid', 'warning_interval_count')
warning_logs = []
for user_count in user_count_info: for user_count in user_count_info:
if user_count.get('warning_interval_count') < 1: if user_count.get('warning_interval_count') < 1:
user = userid_map.get(user_count.get('userid')) user = userid_map.get(user_count.get('userid'))
@ -229,13 +348,20 @@ def check_follow_user():
'corpid': corpid, 'corpid': corpid,
'userid': user_count.get('userid'), 'userid': user_count.get('userid'),
} }
warning_log = WarningLog(
print(warning_rule.warning_setting) uid=warning_rule.uid,
warning_setting_id=warning_setting_id,
log_info=log_info,
text=text,
)
warning_logs.append(warning_log)
WarningLog.objects.bulk_create(warning_logs)
# 活码上线 # 活码上线
@shared_task(name='online_qrcode', queue='qrcode') @shared_task(name='online_qrcode', queue='qrcode')
def online_qrcode(): def online_qrcode():
logger.info('开始活码上线---')
batch_size = 1000 batch_size = 1000
qrcodes = QcQrcodes.objects.all() qrcodes = QcQrcodes.objects.all()
for i in range(0, qrcodes.count(), batch_size): for i in range(0, qrcodes.count(), batch_size):

View File

@ -1,6 +1,7 @@
import uuid import uuid
from .choices import * from .choices import *
from ..warning.models import WarningSetting
class QcCorpinfo(models.Model): class QcCorpinfo(models.Model):
@ -84,6 +85,8 @@ class QcQrcodes(models.Model):
ctime = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) ctime = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
utime = models.DateTimeField(verbose_name='更新时间', auto_now=True) utime = models.DateTimeField(verbose_name='更新时间', auto_now=True)
offlinechecknum = models.IntegerField(verbose_name='同组进粉差()人时报警') offlinechecknum = models.IntegerField(verbose_name='同组进粉差()人时报警')
warning_setting = models.ForeignKey(WarningSetting, on_delete=models.DO_NOTHING, db_constraint=False,
verbose_name='报警配置', related_name='qrcodes', null=True, blank=True)
class Meta: class Meta:
db_table = 'qc_qrcodes' db_table = 'qc_qrcodes'

View File

@ -309,3 +309,36 @@ class WechatWorkerUtil(object):
if res.get('errcode') == 0: if res.get('errcode') == 0:
return True, (res.get('send_list'), res.get('next_cursor')) return True, (res.get('send_list'), res.get('next_cursor'))
return False, (res.get('errmsg'), None) return False, (res.get('errmsg'), None)
def externalcontact_remark(self, userid, external_userid, remark, description=None, remark_company=None,
remark_mobiles=None,
remark_pic_mediaid=None):
access_token = self.get_access_token()
url = f'{self.base_url}/externalcontact/remark?access_token={access_token}'
data = {
'userid': userid,
'external_userid': external_userid,
'remark': remark,
'description': description,
'remark_company': remark_company,
'remark_mobiles': remark_mobiles,
'remark_pic_mediaid': remark_pic_mediaid,
}
res = requests.post(url, json=data).json()
if res.get('errcode') == 0:
return True, res.get('result')
return False, res.get('errmsg')
def externalcontact_mark_tag(self, userid, external_userid, add_tag=None, remove_tag=None):
access_token = self.get_access_token()
url = f'{self.base_url}/externalcontact/mark_tag?access_token={access_token}'
data = {
'userid': userid,
'external_userid': external_userid,
'add_tag': add_tag,
'remove_tag': remove_tag,
}
res = requests.post(url, json=data).json()
if res.get('errcode') == 0:
return True, res.get('result')
return False, res.get('errmsg')

View File

@ -5,7 +5,7 @@ set -o errexit
set -o nounset set -o nounset
#watchmedo auto-restart -d yzk_wechat_event/ -p "*.py" -- celery multi start 6 -A yzk_wechat_event worker -l info --logfile=logs/celery.log --concurrency=4 -Q celery,contact,qrcode & #watchmedo auto-restart -d yzk_wechat_event/ -p "*.py" -- celery multi start 6 -A yzk_wechat_event worker -l info --logfile=logs/celery.log --concurrency=4 -Q celery,contact,qrcode,check_follow_user, tag_remark_contact &
watchmedo auto-restart -d yzk_wechat_event/ -p "*.py" -- celery -A yzk_wechat_event worker -l info --logfile=logs/celery.log --concurrency=4 -Q celery,contact,qrcode,check_follow_user & watchmedo auto-restart -d yzk_wechat_event/ -p "*.py" -- celery -A yzk_wechat_event worker -l info --logfile=logs/celery.log --concurrency=4 -Q celery,contact,qrcode,check_follow_user, tag_remark_contact &
celery -A yzk_wechat_event beat -l info --logfile=logs/celery_beat.log & celery -A yzk_wechat_event beat -l info --logfile=logs/celery_beat.log &
tail -f logs/*.log tail -f logs/*.log

View File

@ -3,6 +3,7 @@ import os
from datetime import timedelta from datetime import timedelta
from celery import Celery from celery import Celery
from celery.schedules import crontab
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'yzk_wechat_event.settings.base') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'yzk_wechat_event.settings.base')
@ -14,19 +15,25 @@ app.autodiscover_tasks()
# 定时任务的调度列表,用于注册定时任务 # 定时任务的调度列表,用于注册定时任务
app.conf.beat_schedule = { app.conf.beat_schedule = {
# 'warning_log': { 'online_qrcode': {
# 'task': 'warning_log', 'task': 'online_qrcode',
# 'schedule': timedelta(minutes=1), 'schedule': crontab(minute="0", hour="0"),
# }, },
'check_follow_user': {
'task': 'check_follow_user',
'schedule': timedelta(minutes=1),
},
} }
task_routes = { task_routes = {
'save_add_contact': 'contact', 'save_add_contact': {'queue': 'contact'},
'edit_add_contact': 'contact', 'edit_add_contact': {'queue': 'contact'},
'delete_add_contact': 'contact', 'delete_add_contact': {'queue': 'contact'},
'check_qrcode': 'qrcode', 'check_qrcode': {'queue': 'contact'},
'check_follow_user': 'check_follow_user', 'check_follow_user': {'queue': 'check_follow_user'},
'online_qrcode': 'qrcode', 'online_qrcode': {'queue': 'qrcode'},
'alarm_userid_by_qrcodeid': {'queue': 'qrcode'},
'tag_remark_contact': {'queue': 'tag_remark_contact'},
} }
# #
app.conf.task_routes = task_routes app.conf.task_routes = task_routes