Example #1
0
def create_new_platform(user_id):
    # 成为平台属主
    user = User.get(user_id=user_id)
    assert user
    account = Account.get(user_id=user.user_id,
                          platform_id=user.bind_platform_id)
    assert account
    platform = Platform.get(owner_user_id=user.user_id)
    if not platform:
        # SELECT `AUTO_INCREMENT` FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'trade' AND table_name = 'platform';
        # ALTER TABLE `platform` AUTO_INCREMENT = 3;
        platform = Platform.create(owner_user_id=user.user_id)
    platform.platform_id = platform.id
    qrcode_info = WeClient.create_qrcode(scene_str=str(platform.platform_id),
                                         is_permanent=True)
    log.d(f'qrcode_info: {qrcode_info}')
    qrcode_content = qrcode_info['url']
    log.i(
        f'create qrcode, platform_id: {platform.platform_id}, qrcode_content: {qrcode_content}'
    )
    platform.update(qrcode_content=qrcode_content,
                    platform_id=platform.id,
                    ssid=f'WIFI-{platform.platform_id}')
    user.update(bind_platform_id=platform.platform_id)
    account.update(role=Account.Role.PLATFORM_OWNER.value,
                   platform_id=user.bind_platform_id)
    return platform
Example #2
0
    def handle_order_unpaid(cls, order):
        # 查询订单API文档: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2
        # 关闭订单API文档: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_3
        # 下载对账单API文档: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_6
        out_trade_no = order.out_trade_no  # 商户订单号
        attach = order.attach
        total_fee = order.total_fee

        # 1. 查询订单API, 获取查询结果
        ret_json = WePay.query_order(out_trade_no)
        # OrderedDict([
        #  ('return_code', 'SUCCESS'), ('return_msg', 'OK'), ('appid', 'wx54d296959ee50c0b'), ('mch_id', '1517154171'),
        #  ('nonce_str', 'LQ36VyPkbS7tK7Nk'), ('sign', '5182234EB26EBFB718D5FDD1189E6056'), ('result_code', 'SUCCESS'),
        #  ('out_trade_no', '1540519817110XiX6jCKAmXb348V3e'), ('trade_state', 'NOTPAY'),
        #  ('trade_state_desc', '订单未支付')
        # ])
        log.d(f'order query from weixin: {ret_json}')
        #
        if ret_json['return_code'] != 'SUCCESS':
            log.e('order query not success')
            return

        # 2. 检查签名
        if not WePay.is_right_sign(ret_json):
            log.e('sign illegal, sign:{}', ret_json['sign'])
            return

        if ret_json['result_code'] != 'SUCCESS':
            log.e('check signature not success')
            return

        # 3. 交易状态分支处理
        trade_state = ret_json['trade_state']

        if trade_state == 'SUCCESS':

            wx_total_fee = int(ret_json['total_fee'])
            transaction_id = ret_json['transaction_id']

            # 支付成功, 先检查total_fee是否一致, 然后把charge记录状态从0改为1, transaction_id更新为微信返回值
            if wx_total_fee != total_fee:
                log.e(
                    f'total_fee: {wx_total_fee} != order.total_fee:{total_fee}'
                )
                return

            # 增加用户免费资源
            increase_user_resource(total_fee, out_trade_no, transaction_id,
                                   attach)

        elif trade_state in ['NOTPAY', 'CLOSED', 'PAYERROR']:

            # 超时还未支付或订单已经关闭, 需把charge记录状态从0改为-1
            status = Order.Status.EXPIRED.value
            Order.objects.filter(out_trade_no=out_trade_no).update(
                status=status)
            log.i(
                f"UPDATE orders SET status = '{status}' WHERE out_trade_no = '{out_trade_no}'"
            )
Example #3
0
 def get(self, request):
     auth = Authentication(request)
     #
     log.i(f'user_id: {auth.user_id}')
     user = User.get(user_id=auth.user_id)
     account = Account.get(user_id=user.user_id, platform_id=user.bind_platform_id)
     data = account.to_dict()
     return BihuResponse(data=data)
Example #4
0
 def doing(cls, start_time, end_time):
     # 明天到期的用户
     log.d(
         f'select expire account where start_time > {start_time} and end_time <= {end_time}'
     )
     #
     accounts = Account.objects.filter(
         expired_at__gt=start_time,
         expired_at__lte=end_time,
     )
     for account in accounts:
         user = User.get(user_id=account.user_id)
         log.i(
             f'send wechat template message, openid: {user.openid}, expired_at: {account.expired_at}'
         )
         WePush.notify_account_expire(openid=user.openid,
                                      username=account.username,
                                      expired_at=account.expired_at)
Example #5
0
 def get_worker_id(self, blocking=True) -> int:
     if not self.worker_id:
         _id = self.acquire_lock()
         if _id == 0:
             if blocking:
                 if SigTerm.is_term:
                     raise SystemExit()
                 log.w(f'not acquire lock, sleep and reacquire')
                 time.sleep(self.expire_time)
                 return self.get_worker_id()
             else:
                 error = f'worker id is full! max_worker_id: {self.max_worker_id}'
                 sentry_sdk.capture_message(error)
                 raise Exception(error)
         self.worker_id = _id
     log.i(f'Success create worker id: {self.worker_id}')
     assert self.worker_id >= 1
     assert self.worker_id <= self.max_worker_id
     return self.worker_id
Example #6
0
def increase_user_resource(total_fee: int, out_trade_no: str,
                           transaction_id: str, attach: str):
    # 根据out_trade_no检查数据库订单
    order = Order.get(out_trade_no=out_trade_no)
    assert order
    assert not order.is_paid()
    account = Account.get(user_id=order.user_id, platform_id=order.platform_id)
    assert account
    # 计算时长叠加
    tariff = Tariff.attach_to_tariff(attach)
    before = account.expired_at
    after = tariff.increase_duration(before)
    with transaction.atomic():
        # 变更免费资源
        account.update(expired_at=after)
        # 变更订单状态 和 微信订单号
        order.update(status=Order.Status.PAID.value,
                     transaction_id=transaction_id)
        # 插入免费资源历史变更表
        ResourceChange.create(user_id=account.user_id,
                              out_trade_no=order.out_trade_no,
                              before=before,
                              after=after)
    log.i(
        f"UPDATE orders SET status = '{order.status}', transaction_id = '{transaction_id}' WHERE out_trade_no = '{out_trade_no}'"
    )
    # 公众号消息通知owner
    platform = Platform.get(platform_id=account.platform_id)
    user = User.get(user_id=account.user_id)
    owner = User.get(user_id=platform.owner_user_id)
    WePush.notify_owner_order_paid(platform_id=platform.platform_id,
                                   openid=owner.openid,
                                   total_fee=order.total_fee,
                                   nickname=user.nickname,
                                   paid_at=order.updated_at,
                                   trade_no=out_trade_no)
Example #7
0
 def post(self, request):
     log.i(f'POST method')
     self.detail(request)
     data = request.data
     return BihuResponse(data=data)
Example #8
0
 def get(self, request):
     log.i(f'GET method')
     self.detail(request)
     return BihuResponse()
Example #9
0
 def detail(self, request):
     log.i(f'request.META: {request.META}')
     log.i(f'request.content_type: {request.content_type}')
     log.i(f'request.encoding: {request.encoding}')
     log.i(f'request.body: {request.body}')
     log.i(f'request.GET: {request.GET}')
     log.i(f'request.POST: {request.POST}')
     log.i(f'request.data: {request.data}')
Example #10
0
    def post(self, request):
        """
        公众号平台事件通知. (PS: 使用平台自带的自定义菜单时, 平台不会下发消息)
        加密模式下 request.GET:
            {
                'signature': '3b6995754f8910fb784c1a060b17750f67b440a9',
                'timestamp': '1612607821',
                'nonce': '1404486768',
                'openid': 'o0FSR0Zh3rotbOog_b2lytxzKrYo',
                'encrypt_type': 'aes',
                'msg_signature': 'e36e86d1c88014e4dc1ced8323bff7ba006e0b0d'
            }
        明文模式下 request.GET:
            {
                'signature': '8d2e34ffd739d7ae39532f91ce41a48674ee323f',
                'timestamp': '1612608294',
                'nonce': '836218769',
                'openid': 'o0FSR0Zh3rotbOog_b2lytxzKrYo'
            }
        """
        xml = request.body
        msg_signature = request.GET.get('msg_signature', None)
        timestamp = request.GET.get('timestamp')
        nonce = request.GET.get('nonce')
        msg = WeCrypto.decrypt_and_parse_message(xml=xml,
                                                 msg_signature=msg_signature,
                                                 timestamp=timestamp,
                                                 nonce=nonce)
        log.i(f'wechat event: {msg}, url params: {request.GET}')
        appid = msg.target  # 例如: gh_9225266caeb1
        from_user_openid = msg.source

        def get_reply_msg():
            # 被动回复 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html
            if isinstance(msg, SubscribeScanEvent) or isinstance(
                    msg, ScanEvent):
                # 未关注用户扫描带参数二维码事件 - 订阅关注
                # 已关注用户扫描带参数二维码事件
                platform_id = int(msg.scene_id)
                platform = Platform.get(platform_id=platform_id)
                assert platform
                user = User.get(openid=from_user_openid)
                if not user:
                    # 创建 user
                    user = User.create(openid=from_user_openid,
                                       bind_platform_id=platform.platform_id)
                else:
                    # user 表记录, 存在
                    if user.bind_platform_id != platform.platform_id:
                        log.w(
                            f'platform_id change: {user.bind_platform_id} -> {platform.platform_id}, openid: {user.openid}'
                        )
                        user.update(bind_platform_id=platform.platform_id)
                account = Account.get(user_id=user.user_id,
                                      platform_id=user.bind_platform_id)
                if not account:
                    # 创建 account
                    username = MyRandom.random_digit(length=8)
                    expired_at = Datetime.localtime() + datetime.timedelta(
                        days=1)  # 新账户一天内免费
                    account = Account.create(
                        user_id=user.user_id,
                        platform_id=user.bind_platform_id,
                        username=username,
                        password=username,
                        radius_password=username,
                        role=Account.Role.PAY_USER.value,
                        expired_at=expired_at,
                    )
                sentry_sdk.capture_message(
                    f'有用户扫描带参数二维码, platform_id: {platform.platform_id}, openid: {from_user_openid}'
                )
                # 判断是否允许房东注册
                if platform.platform_id == settings.ADMIN_PLATFORM_ID:
                    redis = get_redis()
                    if redis.get('enable_platform_register'):
                        # 新创建平台
                        new_platform = create_new_platform(
                            user_id=user.user_id)
                        platform_url = f'{settings.API_SERVER_URL}/platform/{new_platform.platform_id}'
                        sentry_sdk.capture_message(
                            f'房东平台已建立, platform_url: {platform_url}')
                        redis.delete('enable_platform_register')
                # 应答
                return TextReply(
                    source=appid,
                    target=from_user_openid,
                    content=
                    f'账号: {account.username}\n密码: {account.password}\n状态: {account.status}'
                )

            if isinstance(msg, ClickEvent):
                # 点击按钮 - 账号中心
                if msg.key == WeClient.ACCOUNT_VIEW_BTN_EVENT:
                    user = User.get(openid=from_user_openid)
                    if not user or user.bind_platform_id is None:
                        # 用户未经扫码, 进入公众号
                        return TextReply(source=appid,
                                         target=from_user_openid,
                                         content=f'请先扫描房东的WIFI二维码')
                    else:
                        platform = Platform.get(
                            platform_id=user.bind_platform_id)
                        if platform.is_platform_owner(
                                user_id=user.user_id
                        ) and not settings.is_admin(openid=from_user_openid):
                            # 房东不能打开充值页面, 但 admin 可以
                            return TextReply(source=appid,
                                             target=from_user_openid,
                                             content=f'房东不允许打开充值页面')
                        r = ArticlesReply(source=appid,
                                          target=from_user_openid)
                        r.add_article({
                            'title': f'点击进入',
                            'description': '查询WIFI密码 / WIFI续费',
                            'image':
                            'http://zlxpic.lynatgz.cn/zhuzaiyuan_mini.jpg',
                            'url': WeClient.ACCOUNT_VIEW_URI,
                        })
                        return r
                elif msg.key == WeClient.CUSTOMER_SERVICE_BTN_EVENT:
                    return TextReply(source=appid,
                                     target=from_user_openid,
                                     content=settings.MP_DEFAULT_REPLY)

            elif isinstance(msg, SubscribeEvent):  # 关注公众号事件
                pass

            elif isinstance(msg, TextMessage):  # 文本消息
                if msg.content in ['help', '帮助', '命令']:
                    command = [
                        'id',
                        '搜索 $name',
                        'free',
                        '放通mac',
                        '房东注册',
                    ]
                    message = '命令:\n  ' + '\n  '.join(command)
                    return TextReply(source=appid,
                                     target=from_user_openid,
                                     content=message)

                elif msg.content == 'id':
                    # 查看用户ID
                    user = User.get(openid=from_user_openid)
                    messages = [
                        f'你的信息:',
                        f'openid: {user.openid}',
                        f'user_id: {user.user_id}',
                    ]
                    return TextReply(source=appid,
                                     target=from_user_openid,
                                     content='\n'.join(messages))

                # 以下命令需要 admin 权限
                elif msg.content.startswith('搜索') and settings.is_admin(
                        openid=from_user_openid):
                    # 搜索用户信息
                    name = msg.content.split('搜索')[1].strip()
                    return TextReply(
                        source=appid,
                        target=from_user_openid,
                        content=
                        f'{settings.API_SERVER_URL}/search/user?name={name}')

                elif msg.content.startswith('放通mac') and settings.is_admin(
                        openid=from_user_openid):
                    redis = get_redis()
                    ex = 60 * 5
                    redis.set('enable_mac_authentication',
                              str(datetime.datetime.now()),
                              ex=ex)
                    return TextReply(source=appid,
                                     target=from_user_openid,
                                     content=f'有效时间: {ex}秒')

                elif msg.content.startswith('房东注册') and settings.is_admin(
                        openid=from_user_openid):
                    redis = get_redis()
                    ex = 60 * 5
                    redis.set('enable_platform_register',
                              str(datetime.datetime.now()),
                              ex=ex)
                    return TextReply(source=appid,
                                     target=from_user_openid,
                                     content=f'有效时间: {ex}秒')

                elif msg.content.startswith('free') and settings.is_admin(
                        openid=from_user_openid):
                    expired_at = Datetime.localtime() + datetime.timedelta(
                        minutes=30)
                    account = Account.get(user_id=0, platform_id=0)
                    if not account:
                        account = Account.create(
                            user_id=0,
                            platform_id=0,
                            username='******',
                            password='******',
                            radius_password='******',
                            role=Account.Role.FREE_USER.value,
                            expired_at=expired_at,
                        )
                    else:
                        account.update(expired_at=expired_at)
                    content = f'用户名: {account.username}, 密码: {account.password}, 失效时间: {Datetime.to_str(expired_at, fmt="%Y-%m-%d %H:%M:%S")}'
                    return TextReply(source=appid,
                                     target=from_user_openid,
                                     content=content)

                else:
                    return TextReply(source=appid,
                                     target=from_user_openid,
                                     content=settings.MP_DEFAULT_REPLY)
            return None

        #
        reply = get_reply_msg()
        if reply:
            xml = reply.render()
            xml = WeCrypto.encrypt_message(xml=xml,
                                           msg_signature=msg_signature,
                                           timestamp=timestamp,
                                           nonce=nonce)
            return HttpResponse(content=xml, content_type='text/xml')
        else:
            return Response('success')
Example #11
0
    def doing(cls, start_time, end_time):
        # 所有public的AP
        public_ap = set()
        owner_ap = dict()
        sql = f"""
        SELECT * FROM ap_owner;
        """
        with connection.cursor() as cursor:
            cursor.execute(sql)
            for row in dict_fetchall(cursor):
                username = row['username']
                ap_mac = row['ap_mac']
                is_public = row['is_public']
                if is_public:
                    public_ap.add(ap_mac)
                else:
                    if username not in owner_ap:
                        owner_ap[username] = set()
                    owner_ap[username].add(ap_mac)

        # 按username统计连接最多的AP, 作为用户绑定的常用AP. 需排除is_public的AP
        username_ap = dict()
        # TODO 加上时间筛选, 30天内
        sql = f"""
        SELECT username, ap_mac, count(*) AS accept_count FROM stat_user GROUP BY username, ap_mac ORDER BY accept_count DESC;
        """
        log.i(f'sql: {sql}')
        with connection.cursor() as cursor:
            cursor.execute(sql)
            for row in dict_fetchall(cursor):
                username = row['username']
                ap_mac = row['ap_mac']
                accept_count = row['accept_count']
                if ap_mac in public_ap:
                    continue
                if username in owner_ap:
                    # 跳过已绑定用户的AP
                    continue
                if username in username_ap:
                    # 绑定关系已处理
                    continue
                else:
                    username_ap[username] = f'{ap_mac}:{accept_count}'

        # 按 username, user_mac 统计, 告警: 不等于该ap_owner的username
        username_usermac_ap = dict()
        sql = f"""
        SELECT username, user_mac, ap_mac, count(*) AS accept_count FROM stat_user GROUP BY username, user_mac, ap_mac ORDER BY accept_count DESC;
        """
        log.i(f'sql: {sql}')
        with connection.cursor() as cursor:
            cursor.execute(sql)
            for row in dict_fetchall(cursor):
                username = row['username']
                user_mac = row['user_mac']
                ap_mac = row['ap_mac']
                accept_count = row['accept_count']
                #
                if username in owner_ap:
                    # 跳过已绑定用户的AP
                    continue
                if ap_mac in public_ap:
                    # 公用AP跳过
                    continue
                if f'{username}:{user_mac}' in username_usermac_ap:
                    # 绑定关系已处理
                    continue
                else:
                    username_usermac_ap[f'{username}:{user_mac}'] = ap_mac

        log.i(f'public_ap: {public_ap}')
        log.i(f'owner_ap: {owner_ap}')
        log.i(f'username_ap: {username_ap}')
        log.i(f'username_usermac_ap: {username_usermac_ap}')
        for key, value in username_usermac_ap.items():
            username, user_mac = key.split(':')
            ap_mac = value
            correct_ap_mac, correct_accept_count = username_ap[username].split(':')
            if ap_mac == correct_ap_mac:
                continue
            log.e(f'username: {username}, user_mac: {user_mac} 应绑定AP: {correct_ap_mac}, 次数: {correct_accept_count}. 但现连接: {ap_mac}')