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
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}'" )
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)
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)
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
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)
def post(self, request): log.i(f'POST method') self.detail(request) data = request.data return BihuResponse(data=data)
def get(self, request): log.i(f'GET method') self.detail(request) return BihuResponse()
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}')
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')
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}')