diff --git a/fir_client/src/components/FirLogin.vue b/fir_client/src/components/FirLogin.vue index 6a01a57..286fba5 100644 --- a/fir_client/src/components/FirLogin.vue +++ b/fir_client/src/components/FirLogin.vue @@ -74,7 +74,29 @@

- 或使用以下账户登录 + + + + +
+ + + +
+ {{ userinfo.first_name }} +
+
+
+
+
+ + 或使用以下账户登录 +
{ + if(data.code === 1000){ + this.set_cookie_and_token(data); + }else if(data.code ===1005) { + this.$message.error(data.msg) + }else { + this.showuserlist=false + this.$message.error(data.msg) + } + },{ + 'methods':'PUT', + 'data':{uid:userinfo.uid,token:userinfo.token,'wid':this.unique_wid} + }) + }, set_cookie_and_token(data) { this.$cookies.remove("auth_token"); this.$cookies.set("token", data['token'], 3600 * 24 * 30); @@ -168,6 +207,12 @@ export default { this.loop_flag = false; } else if (data.code === 1006) { return this.loop_get_wx_info(wx_login_ticket, c_count, unique_key) + } else if (data.code === 2000){ + this.wx_visible = false; + this.loop_flag = false; + this.userinfo_list = data.data; + this.unique_wid = data.wid; + this.showuserlist=true } }, { "methods": "POST", diff --git a/fir_client/vue.config.js b/fir_client/vue.config.js index 1f44f6d..dcc2780 100644 --- a/fir_client/vue.config.js +++ b/fir_client/vue.config.js @@ -82,7 +82,7 @@ if (page) { } } -const version='2.2.5'; +const version='2.2.6'; const pro_base_env = { baseUrl: 'https://flyapps.cn', diff --git a/fir_ser/api/management/commands/create_weixin_menu.py b/fir_ser/api/management/commands/create_weixin_menu.py new file mode 100644 index 0000000..7cdb8a8 --- /dev/null +++ b/fir_ser/api/management/commands/create_weixin_menu.py @@ -0,0 +1,13 @@ +from django.core.management.base import BaseCommand + +from common.libs.mp.wechat import create_menu + + +class Command(BaseCommand): + help = 'create weixin menu' + + def add_arguments(self, parser): + parser.add_argument('menu_json', nargs='?', type=str, default=None) + + def handle(self, *args, **options): + create_menu(options.get('menu_json', None)) diff --git a/fir_ser/api/migrations/0005_auto_20220329_1131.py b/fir_ser/api/migrations/0005_auto_20220329_1131.py new file mode 100644 index 0000000..f4e9b38 --- /dev/null +++ b/fir_ser/api/migrations/0005_auto_20220329_1131.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.3 on 2022-03-29 11:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('api', '0004_auto_20220326_1847'), + ] + + operations = [ + migrations.AlterModelOptions( + name='thirdwechatuserinfo', + options={'verbose_name': '微信相关信息', 'verbose_name_plural': '微信相关信息'}, + ), + migrations.AlterField( + model_name='thirdwechatuserinfo', + name='openid', + field=models.CharField(max_length=64, verbose_name='普通用户的标识,对当前公众号唯一'), + ), + migrations.AlterUniqueTogether( + name='thirdwechatuserinfo', + unique_together={('user_id', 'openid')}, + ), + ] diff --git a/fir_ser/api/models.py b/fir_ser/api/models.py index 5cc63c3..4520f91 100644 --- a/fir_ser/api/models.py +++ b/fir_ser/api/models.py @@ -72,7 +72,7 @@ class UserInfo(AbstractUser): class ThirdWeChatUserInfo(models.Model): user_id = models.ForeignKey(to=UserInfo, verbose_name="用户ID", on_delete=models.CASCADE) - openid = models.CharField(max_length=64, unique=True, verbose_name="普通用户的标识,对当前公众号唯一") + openid = models.CharField(max_length=64, null=False, verbose_name="普通用户的标识,对当前公众号唯一") nickname = models.CharField(max_length=64, verbose_name="昵称", blank=True) sex = models.SmallIntegerField(default=0, verbose_name="性别", help_text="值为1时是男性,值为2时是女性,值为0时是未知") subscribe_time = models.BigIntegerField(verbose_name="订阅时间") @@ -83,6 +83,11 @@ class ThirdWeChatUserInfo(models.Model): enable_notify = models.BooleanField(verbose_name="是否允许推送消息", default=0) created_time = models.DateTimeField(auto_now_add=True, verbose_name="授权时间") + class Meta: + verbose_name = '微信相关信息' + verbose_name_plural = "微信相关信息" + unique_together = (('user_id', 'openid'),) + def __str__(self): return f"{self.user_id}-{self.nickname}-{self.openid}" diff --git a/fir_ser/api/urls.py b/fir_ser/api/urls.py index 62d9e73..409a9ce 100644 --- a/fir_ser/api/urls.py +++ b/fir_ser/api/urls.py @@ -19,6 +19,7 @@ from api.views.advert import UserAdInfoView from api.views.apps import AppsView, AppInfoView, AppReleaseInfoView, AppsQrcodeShowView from api.views.domain import DomainCnameView, DomainInfoView from api.views.download import ShortDownloadView, InstallView, DownloadView +from api.views.getip import GetRemoteIp from api.views.login import LoginView, UserInfoView, RegistView, AuthorizationView, ChangeAuthorizationView, \ UserApiTokenView, CertificationView, ChangeInfoView from api.views.login_wx import WeChatLoginView, WeChatLoginCheckView, WeChatBindView, WeChatWebLoginView @@ -35,6 +36,7 @@ from api.views.uploads import AppAnalyseView, UploadView urlpatterns = [ # path("",include(router.urls)), + re_path("^rip", GetRemoteIp.as_view()), re_path("^login", LoginView.as_view()), re_path("^auth$", AuthorizationView.as_view()), re_path("^authc$", ChangeAuthorizationView.as_view()), diff --git a/fir_ser/api/utils/serializer.py b/fir_ser/api/utils/serializer.py index 9e70ec4..787ebe2 100644 --- a/fir_ser/api/utils/serializer.py +++ b/fir_ser/api/utils/serializer.py @@ -6,7 +6,7 @@ from rest_framework import serializers from api import models from api.utils.apputils import bytes2human from api.utils.modelutils import get_user_domain_name, get_app_domain_name, get_app_download_uri -from common.base.baseutils import get_choices_dict +from common.base.baseutils import get_choices_dict, WeixinLoginUid from common.cache.storage import AdPicShowCache from common.utils.caches import get_user_free_download_times, get_user_cert_auth_status from common.utils.storage import Storage @@ -94,6 +94,22 @@ class UserInfoSerializer(serializers.ModelSerializer): return get_user_domain_name(obj, 0) +class UserInfoWeiXinSerializer(UserInfoSerializer): + class Meta: + model = models.UserInfo + fields = ["uid", "first_name", 'head_img', 'storage_active', 'token'] + + uid = serializers.SerializerMethodField() + token = serializers.SerializerMethodField() + + def get_uid(self, obj): + return WeixinLoginUid().get_encrypt_uid(obj.uid) + + def get_token(self, obj): + access_token = make_token(obj.uid, 600, key=self.context.get('ticket', 'weixin_login'), force_new=True) + return access_token + + class AppsSerializer(serializers.ModelSerializer): class Meta: model = models.Apps diff --git a/fir_ser/api/views/getip.py b/fir_ser/api/views/getip.py new file mode 100644 index 0000000..922d808 --- /dev/null +++ b/fir_ser/api/views/getip.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- +# project: fir_ser +# filename: getip +# author: liuyu +# data: 2022/3/28 + +import logging + +from rest_framework.response import Response +from rest_framework.views import APIView + +from common.base.baseutils import get_real_ip_address + +logger = logging.getLogger(__name__) + + +class GetRemoteIp(APIView): + + def get(self, request): + logger.info(f"real_ip:{get_real_ip_address(request)}") + return Response({'real_ip': get_real_ip_address(request)}) diff --git a/fir_ser/api/views/login_wx.py b/fir_ser/api/views/login_wx.py index 61b97e7..2a601d3 100644 --- a/fir_ser/api/views/login_wx.py +++ b/fir_ser/api/views/login_wx.py @@ -6,16 +6,17 @@ from rest_framework.views import APIView from api.models import UserInfo, ThirdWeChatUserInfo from api.utils.response import BaseResponse -from api.utils.serializer import UserInfoSerializer +from api.utils.serializer import UserInfoSerializer, UserInfoWeiXinSerializer from api.utils.utils import set_user_token -from common.base.baseutils import get_real_ip_address +from common.base.baseutils import get_real_ip_address, WeixinLoginUid from common.cache.storage import WxLoginBindCache from common.core.auth import ExpiringTokenAuthentication from common.core.sysconfig import Config from common.core.throttle import VisitRegister1Throttle, VisitRegister2Throttle -from common.libs.mp.wechat import make_wx_login_qrcode, show_qrcode_url, WxWebLogin +from common.libs.mp.wechat import make_wx_login_qrcode, show_qrcode_url, WxWebLogin, WxTemplateMsg from common.utils.caches import set_wx_ticket_login_info_cache, get_wx_ticket_login_info_cache from common.utils.pending import get_pending_result +from common.utils.token import verify_token logger = logging.getLogger(__name__) @@ -56,6 +57,43 @@ class WeChatLoginView(APIView): return wx_qr_code_response(ret, code, qr_info, get_real_ip_address(request)) return Response(ret.dict) + def put(self, request): + ret = BaseResponse() + data = request.data + token = data.get('token') + uid = data.get('uid') + wid = data.get('wid') + if token and uid and wid: + uid = WeixinLoginUid().get_decrypt_uid(uid) + wid = WeixinLoginUid().get_decrypt_uid(wid) + if verify_token(token, uid, True): + user = UserInfo.objects.filter(uid=uid).first() + + if user and user.is_active: + wx_user_obj = ThirdWeChatUserInfo.objects.filter(openid=wid, enable_login=True, + user_id=user).first() + if wx_user_obj: + key, user_info = set_user_token(user, request) + serializer = UserInfoSerializer(user_info) + data = serializer.data + ret.userinfo = data + ret.token = key + ret.msg = "验证成功!" + WxTemplateMsg(wid, wx_user_obj.nickname).login_success_msg(user.first_name) + else: + ret.msg = "微信状态异常,请重新登录" + ret.code = 1006 + else: + ret.msg = "用户被禁用,请更换其他用户登录" + ret.code = 1005 + else: + ret.msg = "校验失败,请重新登录" + ret.code = 1006 + else: + ret.msg = "参数异常,请重新登录" + ret.code = 1006 + return Response(ret.dict) + class WeChatBindView(APIView): authentication_classes = [ExpiringTokenAuthentication, ] @@ -102,8 +140,18 @@ class WeChatLoginCheckView(APIView): ret.msg = "还未绑定用户,请通过手机或者邮箱登录账户之后进行绑定" ret.code = 1005 else: + ids = wx_ticket_data.get('ids', []) w_type = wx_ticket_data.get('w_type', '') to_user = wx_ticket_data.get('to_user', '') + if ids: + user_queryset = UserInfo.objects.filter(pk__in=ids, is_active=True).all() + if user_queryset: + ret.data = UserInfoWeiXinSerializer(user_queryset, many=True, + context={'ticket': ticket}).data + ret.wid = WeixinLoginUid().get_encrypt_uid(to_user) + ret.code = 2000 + return Response(ret.dict) + user = UserInfo.objects.filter(pk=wx_ticket_data['pk']).first() wx_obj = ThirdWeChatUserInfo.objects.filter(user_id=user, openid=to_user, enable_login=True).filter() diff --git a/fir_ser/api/views/thirdlogin.py b/fir_ser/api/views/thirdlogin.py index c2a4adb..74de50d 100644 --- a/fir_ser/api/views/thirdlogin.py +++ b/fir_ser/api/views/thirdlogin.py @@ -46,23 +46,30 @@ class TextXMLParser(XMLParser): def reply_login_msg(rec_msg, to_user, from_user): content = f'还未绑定用户,请通过手机或者邮箱登录账户之后进行绑定' u_data_id = -1 - wx_user_obj = ThirdWeChatUserInfo.objects.filter(openid=to_user, enable_login=True).first() + ids = [] + wx_user_obj_queryset = ThirdWeChatUserInfo.objects.filter(openid=to_user, enable_login=True).all() wx_ticket_info = get_wx_ticket_login_info_cache(rec_msg.Ticket) - if wx_user_obj: - u_data_id = wx_user_obj.user_id.pk - content = f'用户 {wx_user_obj.user_id.first_name} 登录成功' - WxTemplateMsg(to_user, wx_user_obj.nickname).login_success_msg(wx_user_obj.user_id.first_name) - else: - wx_user_info = update_or_create_wx_userinfo(to_user) - WxTemplateMsg(to_user, wx_user_info.get('nickname', '')).login_failed_msg() - + wx_user_count = wx_user_obj_queryset.count() + if 0 <= wx_user_count <= 1: + wx_user_obj = wx_user_obj_queryset.first() + if wx_user_obj: + u_data_id = wx_user_obj.user_id.pk + content = f'用户 {wx_user_obj.user_id.first_name} 登录成功' + WxTemplateMsg(to_user, wx_user_obj.nickname).login_success_msg(wx_user_obj.user_id.first_name) + else: + wx_user_info = update_or_create_wx_userinfo(to_user) + WxTemplateMsg(to_user, wx_user_info.get('nickname', '')).login_failed_msg() + elif wx_user_count > 1: + ids = [x['user_id__id'] for x in wx_user_obj_queryset.values('user_id__id')] + u_data_id = wx_user_obj_queryset.first().user_id.pk + content = f'您刚才进行了扫码登录,多用户登录中' if wx_ticket_info and wx_ticket_info.get('ip_addr'): ip_addr = wx_ticket_info.get('ip_addr') logger.info(f"{content} ip:{ip_addr}") - set_wx_ticket_login_info_cache(rec_msg.Ticket, {'pk': u_data_id, 'to_user': to_user}) + set_wx_ticket_login_info_cache(rec_msg.Ticket, {'pk': u_data_id, 'to_user': to_user, "ids": ids}) reply_msg = reply.TextMsg(to_user, from_user, content) return reply_msg.send() @@ -93,8 +100,9 @@ def update_or_create_wx_userinfo(to_user, user_obj=None, w_type=''): def wx_bind_utils(rec_msg, to_user, from_user, content): w_type = rec_msg.Eventkey.split('.')[-1] uid = rec_msg.Eventkey.split('.')[-2] - wx_user_obj = ThirdWeChatUserInfo.objects.filter(openid=to_user).first() user_obj = UserInfo.objects.filter(uid=uid).first() + wx_user_obj = ThirdWeChatUserInfo.objects.filter(openid=to_user, user_id__uid=uid).first() + if wx_user_obj: wx_template_msg_obj = WxTemplateMsg(to_user, wx_user_obj.nickname) if user_obj and user_obj.uid == wx_user_obj.user_id.uid: @@ -133,11 +141,33 @@ class ValidWxChatToken(APIView): if ret == 0: content = '欢迎使用fly应用分发平台,感谢您的关注' rec_msg = receive.parse_xml(encrypt_xml) + logger.info(f"code:{ret}, parse_xml result {rec_msg.__dict__ if rec_msg else rec_msg}") if isinstance(rec_msg, receive.Msg): to_user = rec_msg.FromUserName from_user = rec_msg.ToUserName if rec_msg.MsgType == 'text': + msg = rec_msg.Content.decode('utf-8') + if msg and (msg.startswith('解除登录绑定') or msg.startswith('解除消息绑定')): + msg_list = msg.split(' ') + if len(msg_list) == 2: + uid = msg_list[-1] + wx_user_obj = ThirdWeChatUserInfo.objects.filter(openid=to_user, user_id__uid=uid).first() + data = {} + w_msg = [] + if msg.startswith('解除登录绑定'): + data.update({'enable_login': False}) + w_msg.append('扫码登录') + if msg.startswith('解除消息绑定'): + w_msg.append('消息推送') + data.update({'enable_notify': False}) + if data: + ThirdWeChatUserInfo.objects.filter(openid=to_user, user_id__uid=uid).update(**data) + WxTemplateMsg(to_user, wx_user_obj.nickname).unbind_success_msg( + wx_user_obj.user_id.first_name, f"{' '.join(w_msg)} 解绑成功", + "如需重新绑定,请登陆平台,进行绑定。感谢您的关注") + return HttpResponse("success") + content = random.choices([*GOOD_XX, content, rec_msg.Content.decode('utf-8')])[0] reply_msg = reply.TextMsg(to_user, from_user, content) result = reply_msg.send() @@ -165,56 +195,85 @@ class ValidWxChatToken(APIView): result = reply_msg.send() logger.info(f"replay msg: {result}") return HttpResponse(result) - elif rec_msg.Eventkey in ['query_bind', 'unbind']: - wx_user_obj = ThirdWeChatUserInfo.objects.filter(openid=to_user).filter( - Q(enable_login=True) | Q(enable_notify=True)).first() + elif rec_msg.Eventkey in ['query_bind', 'unbind', 'unbind_all']: + wx_user_obj_queryset = ThirdWeChatUserInfo.objects.filter(openid=to_user).filter( + Q(enable_login=True) | Q(enable_notify=True)).all() if rec_msg.Eventkey == 'query_bind': - if wx_user_obj: - user_obj = wx_user_obj.user_id - content = f'绑定用户 {user_obj.first_name} ' - if user_obj.email: - content += f' 登录邮箱:{user_obj.email} ' - if user_obj.mobile: - content += f' 登录手机:{user_obj.mobile}' - user_cert_obj = UserCertificationInfo.objects.filter(user_id=user_obj, - status=1).first() - if user_cert_obj: - name = user_cert_obj.name - else: - name = user_obj.first_name - description = f'绑定了' - if wx_user_obj.enable_login: - description += " 微信登录 " - if wx_user_obj.enable_notify: - description += " 消息通知 " - description += "功能。" - - WxTemplateMsg(to_user, wx_user_obj.nickname).bind_query_success_msg(user_obj.first_name, - name, - user_obj.mobile, - user_obj.email, - description) - else: - content = '暂无登录绑定信息' + for wx_user_obj in wx_user_obj_queryset: + if wx_user_obj: + user_obj = wx_user_obj.user_id + content = f'绑定用户 {user_obj.first_name} ' + if user_obj.email: + content += f' 登录邮箱:{user_obj.email} ' + if user_obj.mobile: + content += f' 登录手机:{user_obj.mobile}' + user_cert_obj = UserCertificationInfo.objects.filter(user_id=user_obj, + status=1).first() + if user_cert_obj: + name = user_cert_obj.name + else: + name = user_obj.first_name + description = f'绑定了' + if wx_user_obj.enable_login: + description += " 微信登录 " + if wx_user_obj.enable_notify: + description += " 消息通知 " + description += "功能。" + + WxTemplateMsg(to_user, wx_user_obj.nickname).bind_query_success_msg( + user_obj.first_name, + name, + user_obj.mobile, + user_obj.email, + description) + if wx_user_obj_queryset.count() == 0: + content = '暂无绑定信息' wx_user_info = update_or_create_wx_userinfo(to_user) WxTemplateMsg(to_user, wx_user_info.get('nickname')).query_bind_info_failed_msg( - "查询登录绑定", content) - - elif rec_msg.Eventkey == 'unbind': - if wx_user_obj: - content = f'解绑用户 {wx_user_obj.user_id.first_name} 成功' - WxTemplateMsg(to_user, wx_user_obj.nickname).unbind_success_msg( - wx_user_obj.user_id.first_name) - ThirdWeChatUserInfo.objects.filter(openid=to_user).delete() - else: - content = f'暂无登录绑定信息' + "查询绑定", content) + + elif rec_msg.Eventkey == 'unbind_all': + for wx_user_obj in wx_user_obj_queryset: + if wx_user_obj: + content = f'解绑用户 {wx_user_obj.user_id.first_name} 成功' + WxTemplateMsg(to_user, wx_user_obj.nickname).unbind_success_msg( + wx_user_obj.user_id.first_name, + "解除绑定成功,您将无法使用微信扫描登录平台和微信消息推送功能", + "如需重新绑定,请登陆平台,在个人资料进行登录绑定。感谢您的关注") + ThirdWeChatUserInfo.objects.filter(openid=to_user).update(enable_login=False, + enable_notify=False) + if wx_user_obj_queryset.count() == 0: + content = f'暂无绑定信息' wx_user_info = update_or_create_wx_userinfo(to_user) WxTemplateMsg(to_user, wx_user_info.get('nickname')).query_bind_info_failed_msg( - "解除登录绑定", content) - + "解除绑定", content) + elif rec_msg.Eventkey == 'unbind': + if wx_user_obj_queryset.count() == 0: + content = f'暂无绑定信息' + else: + content = '' + for wx_user_obj in wx_user_obj_queryset: + user_obj = wx_user_obj.user_id + user_content = f'用户昵称 {user_obj.first_name} ' + if user_obj.email: + user_content += f' 登录邮箱:{user_obj.email} ' + if user_obj.mobile: + user_content += f' 登录手机:{user_obj.mobile}' + + content += f'♥{user_content}♥\n' + if wx_user_obj.enable_login: + login_info = f'解除登录绑定 {user_obj.uid}' + content += f'【解除登录绑定】' + if wx_user_obj.enable_notify: + notify_info = f'解除消息绑定 {user_obj.uid}' + content += f'【解除消息绑定】' + content += '\n\n' + content += '请点击您要解除绑定的信息\n' logger.info(f"to_user:{to_user} from_user:{from_user} reply msg: {content}") reply_msg = reply.TextMsg(to_user, from_user, content) result = reply_msg.send() + if rec_msg.Eventkey == 'unbind': + return HttpResponse(result) elif rec_msg.Event in ['subscribe', 'unsubscribe']: # 订阅 reply_msg = reply.TextMsg(to_user, from_user, content) @@ -235,6 +294,8 @@ class ValidWxChatToken(APIView): else: logger.error('密文解密失败') logger.info(f"replay msg: {result}") + # 直接回复修改为微信模板消息回复 + # return HttpResponse(result) return HttpResponse("success") diff --git a/fir_ser/common/base/baseutils.py b/fir_ser/common/base/baseutils.py index 654f4d7..3116f6c 100644 --- a/fir_ser/common/base/baseutils.py +++ b/fir_ser/common/base/baseutils.py @@ -56,18 +56,26 @@ class AESCipher(object): return data -class AppleDeveloperUid(object): +class AesBaseCrypt(object): def __init__(self): self.cipher = AESCipher(self.__class__.__name__) - def get_encrypt_uid(self, raw): - return self.cipher.encrypt(raw.encode('utf-8')).decode('utf-8') + def get_encrypt_uid(self, key): + return self.cipher.encrypt(key.encode('utf-8')).decode('utf-8') def get_decrypt_uid(self, enc): return self.cipher.decrypt(enc) +class AppleDeveloperUid(AesBaseCrypt): + ... + + +class WeixinLoginUid(AesBaseCrypt): + ... + + def make_from_user_uuid(uid): random_str = uuid.uuid1().__str__().split("-")[0:-1] user_ran_str = uuid.uuid5(uuid.NAMESPACE_DNS, uid).__str__().split("-") diff --git a/fir_ser/common/core/sysconfig.py b/fir_ser/common/core/sysconfig.py index 29642d4..dd666dc 100644 --- a/fir_ser/common/core/sysconfig.py +++ b/fir_ser/common/core/sysconfig.py @@ -199,8 +199,7 @@ class AuthConfCache(WechatConfCache): @property def NOTIFY(self): - # return super().get_value('NOTIFY', AUTHCONF.NOTIFY) - return AUTHCONF.NOTIFY + return super().get_value('NOTIFY', AUTHCONF.NOTIFY) @property def LOGIN(self): diff --git a/fir_ser/common/libs/mp/wechat.py b/fir_ser/common/libs/mp/wechat.py index 53294e4..73ddc8d 100644 --- a/fir_ser/common/libs/mp/wechat.py +++ b/fir_ser/common/libs/mp/wechat.py @@ -56,8 +56,8 @@ def get_wx_access_token_cache(c_count=1, ): return get_wx_access_token_cache(c_count + 1) -def create_menu(): - menu_json = { +def create_menu(menu_json=None): + default_menu_json = { "button": [ { "type": "click", @@ -79,14 +79,19 @@ def create_menu(): }, { "type": "click", - "name": "查询登录绑定", + "name": "查询当前绑定", "key": "query_bind" }, { "type": "click", - "name": "解除登录绑定", - "key": "unbind" + "name": "解除所有绑定", + "key": "unbind_all" }, + { + "type": "click", + "name": "账户解绑管理", + "key": "unbind" + } ] }, { @@ -96,6 +101,17 @@ def create_menu(): } ] } + if menu_json is None: + menu_json = default_menu_json + try: + if isinstance(menu_json, str): + menu_json = json.loads(menu_json) + if not isinstance(menu_json, dict): + logger.error(f"menu json check failed menu_json:{menu_json}") + return + except Exception as e: + logger.error(f"menu json check failed menu_json:{menu_json} Exception:{e}") + return p_url = f"https://api.weixin.qq.com/cgi-bin/menu/create?access_token={get_wx_access_token_cache()}" req = requests.post(url=p_url, data=json.dumps(menu_json, ensure_ascii=False).encode('utf-8')) print(req.json()) @@ -202,8 +218,6 @@ class WxTemplateMsg(object): 登录系统:OA系统 登录时间:2014-11-28 10:06:32 如有疑问,请致电IT服务台400-888-8888 或 关注公众号在线反馈 - :param to_user: - :param wx_nick_name: 微信昵称 :param username: flyapps 用户昵称 :return: """ @@ -302,7 +316,7 @@ class WxTemplateMsg(object): } return self.send_msg(msg_id, content_data) - def unbind_success_msg(self, username): + def unbind_success_msg(self, username, context, description): now_time = get_format_time() msg_id = 'RabYMg8-jPGhonk957asbW17iLHSLp8BfEXnyesRZ60' content_data = { @@ -319,11 +333,11 @@ class WxTemplateMsg(object): "color": "#173177" }, "keyword3": { - "value": "解除绑定成功,您将无法使用微信扫描登录平台", + "value": context, "color": "#173177" }, "remark": { - "value": "如需重新绑定,请登陆平台,在个人资料进行绑定。感谢您的关注", + "value": description, "color": "#173177" }, } diff --git a/fir_ser/common/notify/notify.py b/fir_ser/common/notify/notify.py index 3135eec..2463180 100644 --- a/fir_ser/common/notify/notify.py +++ b/fir_ser/common/notify/notify.py @@ -68,7 +68,7 @@ def sign_unavailable_developer(user_obj, app_obj): """ message_type = 3 now_time = get_format_time().replace('_', ' ') - msg = Config.MSG_NOT_EXIST_DEVELOPER % (user_obj.first_name, now_time, app_obj.name) + msg = Config.MSG_NOT_EXIST_DEVELOPER % (user_obj.first_name, app_obj.name, now_time) for wx_user_obj in get_notify_wx_queryset(user_obj, message_type): res = WxTemplateMsg(wx_user_obj.openid, wx_user_obj.nickname).operate_failed_msg( user_obj.first_name, f'应用 {app_obj.name} 签名失败了', diff --git a/fir_ser/fir_ser/settings.py b/fir_ser/fir_ser/settings.py index 54efffb..b267694 100644 --- a/fir_ser/fir_ser/settings.py +++ b/fir_ser/fir_ser/settings.py @@ -292,7 +292,7 @@ FILE_UPLOAD_TMP_KEY = ".tmp" SYNC_CACHE_TO_DATABASE = { 'download_times': 10, # 下载次数同步时间 - 'best_proxy_ips_times': 60 * 60, # 代理ip 自动获取时间 + 'best_proxy_ips_times': 6 * 60 * 60, # 代理ip 自动获取时间 'wx_get_access_token_times': 60 * 10, # 微信access_token 自动获取时间 'try_login_times': (10, 12 * 60 * 60), # 当天登录失败次数,超过该失败次数,锁定24小时 'auto_clean_tmp_file_times': 60 * 30, # 定时清理上传失误生成的临时文件 @@ -471,13 +471,13 @@ CELERY_BEAT_SCHEDULE = { 'download_times_notify_check_job': { 'task': 'api.tasks.download_times_notify_check_job', # 'schedule': SYNC_CACHE_TO_DATABASE.get("auto_check_ios_developer_active_times"), - 'schedule': crontab(hour=1, minute=10), + 'schedule': crontab(hour='6,12,18', minute=10), 'args': () }, 'apple_developer_devices_check_job': { 'task': 'api.tasks.apple_developer_devices_check_job', # 'schedule': SYNC_CACHE_TO_DATABASE.get("auto_check_ios_developer_active_times"), - 'schedule': crontab(hour=1, minute=20), + 'schedule': crontab(hour='6,12,18', minute=20), 'args': () }, 'apple_developer_cert_notify_check_job': { @@ -491,11 +491,11 @@ CELERY_BEAT_SCHEDULE = { 'schedule': SYNC_CACHE_TO_DATABASE.get("wx_get_access_token_times"), 'args': (), }, - 'get_best_proxy_ips_job': { - 'task': 'xsign.tasks.get_best_proxy_ips_job', - 'schedule': SYNC_CACHE_TO_DATABASE.get("best_proxy_ips_times"), - 'args': (), - }, + # 'get_best_proxy_ips_job': { + # 'task': 'xsign.tasks.get_best_proxy_ips_job', + # 'schedule': SYNC_CACHE_TO_DATABASE.get("best_proxy_ips_times"), + # 'args': (), + # }, 'auto_check_ios_developer_active_job': { 'task': 'xsign.tasks.auto_check_ios_developer_active_job', # 'schedule': SYNC_CACHE_TO_DATABASE.get("auto_check_ios_developer_active_times"), diff --git a/fir_ser/xsign/utils/iproxy.py b/fir_ser/xsign/utils/iproxy.py index 1d73d9f..0521841 100644 --- a/fir_ser/xsign/utils/iproxy.py +++ b/fir_ser/xsign/utils/iproxy.py @@ -17,8 +17,12 @@ logger = logging.getLogger(__name__) def get_best_proxy_ips(url='https://api.appstoreconnect.apple.com/agreement'): - active_proxy_ips = [proxy_info['proxy'] for proxy_info in Config.APPLE_DEVELOPER_API_PROXY_LIST if - proxy_info.get('active')] + try: + active_proxy_ips = [proxy_info['proxy'] for proxy_info in Config.APPLE_DEVELOPER_API_PROXY_LIST if + proxy_info.get('active')] + except Exception as e: + logger.error(f'get_best_proxy_ips failed Exception:{e}') + return access_ip_info = [] if not active_proxy_ips: return @@ -39,7 +43,7 @@ def get_best_proxy_ips(url='https://api.appstoreconnect.apple.com/agreement'): pools.shutdown() best_sorted_ips = sorted(access_ip_info, key=lambda x: x.get('time'))[:8] best_sorted_ips = [ip_proxy['ip'] for ip_proxy in best_sorted_ips] - IpProxyListCache().set_storage_cache(best_sorted_ips, 24 * 60 * 60) + IpProxyListCache().set_storage_cache(best_sorted_ips, 6 * 60 * 60) return best_sorted_ips @@ -73,7 +77,7 @@ def get_proxy_ip_from_cache(change_ip=False): else: proxy_info = Config.APPLE_DEVELOPER_API_PROXY logger.info(f"make ip proxy cache {proxy_info}") - active_proxy_cache.set_storage_cache(proxy_info, 24 * 60 * 60) + active_proxy_cache.set_storage_cache(proxy_info, 60 * 60) return proxy_info diff --git a/fir_ser/xsign/utils/supersignutils.py b/fir_ser/xsign/utils/supersignutils.py index 3b292b1..b84160f 100644 --- a/fir_ser/xsign/utils/supersignutils.py +++ b/fir_ser/xsign/utils/supersignutils.py @@ -617,7 +617,7 @@ class IosUtils(object): return status, device_obj if device_obj and device_obj.status not in ['ENABLED', 'DISABLED']: - if not IosUtils.check_device_status(developer_obj)[0]: + if not IosUtils.check_device_status(developer_obj, org_device_obj=device_obj)[0]: return False, 'UNEXPECTED_ERROR' sync_device_obj, _ = update_or_create_developer_udid_info(device_obj, developer_obj) @@ -683,7 +683,7 @@ class IosUtils(object): @staticmethod @call_function_try_attempts(try_attempts=2) - def check_device_status(developer_obj, device_obj_list=None): + def check_device_status(developer_obj, device_obj_list=None, org_device_obj=None): status = True if device_obj_list is None: status, device_obj_list = get_api_obj(developer_obj).get_device() @@ -693,6 +693,9 @@ class IosUtils(object): developer_obj.status = 5 developer_obj.save(update_fields=['status']) return False, f'issuer_id:{developer_obj.issuer_id} device status unexpected. device_obj:{device_obj}' + else: + if org_device_obj and org_device_obj.id == device_obj.id: + org_device_obj.status = True if device_obj.status == 'ENABLED' else False return True, '' @staticmethod @@ -1187,7 +1190,10 @@ class IosUtils(object): udid_result_list = [device.udid for device in result] udid_same = set(udid_result_list) & set(udid_developer_list) - same_p = (len(udid_same) / len(udid_result_list) + len(udid_same) / len(udid_developer_list)) / 2 + if len(udid_result_list) > 0 and len(udid_developer_list) > 0: + same_p = (len(udid_same) / len(udid_result_list) + len(udid_same) / len(udid_developer_list)) / 2 + else: + same_p = 0 # 获取设备列表的时候,有时候会发生灵异事件,尝试多次获取 【也可能是未知bug导致】【已经存在,获取的数据并不是该用户数据】 if status and ((isinstance(result, list) and len(result) == 0) or same_p < 0.8): time.sleep(2) diff --git a/fir_ser/xsign/views/supersign.py b/fir_ser/xsign/views/supersign.py index 0697533..1055dac 100644 --- a/fir_ser/xsign/views/supersign.py +++ b/fir_ser/xsign/views/supersign.py @@ -265,13 +265,16 @@ class DeveloperView(APIView): } try: logger.error(f"user {request.user} add new developer {data.get('issuer_id', '')} data {data_info}") - developer_obj = AppIOSDeveloperInfo.objects.create(user_id=request.user, **data_info) + developer_obj, created = AppIOSDeveloperInfo.objects.update_or_create(user_id=request.user, + issuer_id=data_info.get('issuer_id'), + defaults=data_info) IosUtils.create_developer_space(developer_obj, request.user) status, result = IosUtils.active_developer(developer_obj, False) if not status: res.code = 1008 res.msg = result.get("return_info", "未知错误") - delete_developer_and_clean(developer_obj, request.user) + if created: + delete_developer_and_clean(developer_obj, request.user) return Response(res.dict) else: IosUtils.get_device_from_developer(developer_obj) @@ -279,7 +282,7 @@ class DeveloperView(APIView): except Exception as e: logger.error(f"user {request.user} create developer {data_info} failed Exception:{e}") res.code = 1005 - res.msg = "添加失败" + res.msg = f"添加失败 {e}" return Response(res.dict) return self.get(request)