def get_update_state(device_code): if isinstance(device_code, Device): return device_code.update_state if isinstance(device_code, basestring): device_update_key = RedisClient.get_device_update_status_key( device_code) update_state = redis_device_client.get(device_update_key) if update_state is not None: return update_state log.info("当前设备游戏更新状态还未缓存: device_code = {}".format(device_code)) device = DeviceService.get_device_by_code(device_code) if device is None: log.error( "当前设备号没有搜索到设备信息: device_code = {}".format(device_code)) return None # 缓存游戏更新设备状态 redis_device_client.setex(device_update_key, DEFAULT_EXPIRED_DEVICE_STATUS, device.update_state) return device.update_state log.error("当前参数既不是字符串类型也不是Device类型: type = {}".format( type(device_code))) return None
def set_update_state(device, update_state, last_update_time=None): # 如果传出的是 设备号 则查找 if isinstance(device, basestring): device = DeviceService.get_device_by_code(device) if device is None: return False update_info = {Device.update_state: update_state} if last_update_time is not None: update_info[Device.last_update_time] = last_update_time rowcount = Device.query.filter_by(id=device.id).update(update_info) if rowcount <= 0: log.error("设备游戏更新状态更新失败: device_id = {} update_state = {}".format( device.id, update_state)) return False log.info("设备更新状态写入数据库完成: rowcount = {} ".format(rowcount)) device_update_key = RedisClient.get_device_update_status_key( device.device_code) # 存储状态到redis中 状态只保存一天,防止数据被删除 缓存一直存在 redis_device_client.setex(device_update_key, DEFAULT_EXPIRED_DEVICE_STATUS, update_state) log.info( "设备更新状态设置成功: device_id = {} device_code = {} update_state = {}". format(device.id, device.device_code, update_state)) return True
def keep_device_heart(device_code): # 先获得心跳的主键 device_heart_key = RedisClient.get_device_heart_key(device_code) redis_device_client.setex(device_heart_key, DEFAULT_EXPIRED_DEVICE_HEART, int(time.time()))
def request_sms(self, mobile): if not settings.SMS_ENABLED: log.info("当前处于调试状态,没有打开短信验证码功能, 不发送短信验证码请求...") return True captcha = str(SmsSenderUtil.get_random()) resp = self.tx_sms_sender.send_with_param("86", mobile, self.sms_text_temp_id, [captcha], "", "", "") try: result = json.loads(resp) if result.get('result') != 0: log.error("发送验证码失败: mobile = {} captcha = {}".format( mobile, captcha)) log.error("返回错误为: resp = {}".format(resp)) return False # 存储验证码到redis中 只保留五分钟有效 key = RedisClient.get_captcha_redis_key(mobile) self.__redis.setex(key, DEFAULT_EXPIRED_CAPTCHA, captcha) log.info("验证码发送成功: mobile = {} captcha = {}".format( mobile, captcha)) return True except Exception as e: log.error("发送验证码失败: mobile = {} captcha = {}".format( mobile, captcha)) log.exception(e) return False
def do_offline_order_by_device_code(device_code): device_code_key = RedisClient.get_device_code_key(device_code) record_key = redis_cache_client.get(device_code_key) if record_key is None: log.error("当前通过device_code = {} 下机失败, 没有在redis中找到对应的上机信息".format( device_code)) return False # 发送下机指令 return WindowsService.do_offline_order(record_key)
def get_online_status(): user_key = RedisClient.get_user_key(g.user_id) record_key = redis_cache_client.get(user_key) if record_key is None: return fail(HTTP_OK, u'当前用户没有上机信息', 0) charging = redis_cache_client.get(record_key) if charging is None: return fail(HTTP_OK, u'当前用户没有上机信息', 0) return success(WindowsService.get_current_time_charging(charging))
def mobile_reach_rate_limit(self, mobile): if not settings.SMS_ENABLED: log.info("调试模式下,可以无限次请求验证码!") return False key = RedisClient.get_mobile_redis_key(mobile) value = self.__redis.get(key) log.info('redis[%s]: %s', key, value) if value is not None: return True self.__redis.setex(key, DEFAULT_EXPIRED_MOBILE, mobile) return False
def delete_device(device_id): device = DeviceService.get_device_by_id(device_id) if device is None: log.warn("当前需要删除的设备不存在: device_id = {}".format(device_id)) return False # 当前设备在线,且设备正在被用户使用,则不能够删除 if DeviceService.get_device_alive_status(device) == Device.ALIVE_ONLINE and \ DeviceService.get_device_status(device) != DeviceStatus.STATUE_FREE: log.warn("当前设备不处于空闲状态,不能删除: device_id = {}".format(device.id)) return False # 删除设备状态缓存信息 device_status_key = RedisClient.get_device_status_key( device.device_code) redis_device_client.delete(device_status_key) # 删除设备更新状态缓存 device_update_status_key = RedisClient.get_device_update_status_key( device.device_code) redis_device_client.delete(device_update_status_key) # 删除设备心跳缓存 device_heart_key = RedisClient.get_device_heart_key(device.device_code) redis_device_client.delete(device_heart_key) # 删除设备上的游戏 DeviceGameService.delete_deploy_device_game(device.id) if not device.delete(): log.warn("设备信息删除失败: {}".format( json.dumps(device.to_dict(), ensure_ascii=False))) return False return True
def online_recharge(user_id, total_fee): # 先获得用户缓存的信息 user_key = RedisClient.get_user_key(user_id) # todo 这里需要加锁, 否则扣费下机时会有影响 lock = DistributeLock(user_key, redis_cache_client) try: lock.acquire() record_key = redis_cache_client.get(user_key) if record_key is None: log.info("当前用户没有在线 record_key = None, 不需要同步在线数据: user_id = {}". format(user_id)) return charge_str = redis_cache_client.get(record_key) if charge_str is None: log.info( "当前用户没有在线 charging = None, 不需要同步在线数据: user_id = {}".format( user_id)) return try: charge_dict = json.loads(charge_str) if charge_dict is None: log.error("解析json数据失败: {}".format(charge_str)) return balance_account = charge_dict.get('balance_account') if not isinstance(balance_account, int): log.error( "balance_account 数据类型不正确: {}".format(balance_account)) return charge_dict['balance_account'] = balance_account + total_fee redis_cache_client.set(record_key, json.dumps(charge_dict)) log.info( "同步修改redis中用户余额信息成功! user_id = {} account = {}".format( user_id, balance_account + total_fee)) except Exception as e: log.error("解析json数据失败: {}".format(charge_str)) log.exception(e) finally: lock.release()
def check_connect(): if not request.is_json: log.warn("参数错误...") return fail(HTTP_OK, u"need application/json!!") device_code = request.json.get('device_code') if device_code is None: return fail(HTTP_OK, u"not have device_code!!!") # 获得设备使用状态 device_status = DeviceService.get_device_status(device_code) if device_status is None: return success({ 'status': -1, 'device_status': device_status, 'msg': "not deploy" }) # 保持心跳 DeviceService.keep_device_heart(device_code) # 从维护状态跳转到空闲状态 if device_status == DeviceStatus.STATUS_MAINTAIN: log.info("当前状态为维护状态,设备已经有心跳需要重新设置空闲状态!") DeviceService.status_transfer(device_code, device_status, DeviceStatus.STATUE_FREE) # 重新获得设备状态 device_status = DeviceService.get_device_status(device_code) device_code_key = RedisClient.get_device_code_key(device_code) record_key = redis_cache_client.get(device_code_key) if record_key is None: return success({ 'status': 0, 'device_status': device_status, 'msg': "not login" }) return success({ "status": 1, "token": record_key, 'device_status': device_status, "msg": "login successed!" })
def get_device_alive_status(device): if isinstance(device, basestring): device_code = device elif isinstance(device, Device): device_code = device.device_code else: log.error("当前设备参数获取存活状态不正确: device = {} type = {}".format( device, type(device))) return Device.ALIVE_OFFLINE # 先获得心跳的主键 device_heart_key = RedisClient.get_device_heart_key(device_code) last_heart_time = redis_device_client.get(device_heart_key) if last_heart_time is None: return Device.ALIVE_OFFLINE return Device.ALIVE_ONLINE
def set_device_status(device, device_status): ''' :param device: Device 类型 :param device_status: :return: ''' if not isinstance(device, Device): log.error("当前设置设备状态传入参数错误: device = {} type = {}".format( device, type(device))) return False if device_status not in Device.STATUS_VALUES: log.error( "当前设置设备状态传入参数错误: device_status = {}".format(device_status)) return False # 先更新数据库,确保数据更新成功 update_info = { Device.state: device_status, Device.state_version: device.state_version + 1 } rowcount = Device.query.filter_by( id=device.id, state_version=device.state_version).update(update_info) if rowcount <= 0: log.error( "更新设备状态失败,版本信息已经被修改: id = {} state_version = {} state = {}". format(device.id, device.state_version, device_status)) return False log.info("设备状态写入数据库完成: rowcount = {} ".format(rowcount)) device_status_key = RedisClient.get_device_status_key( device.device_code) # 存储状态到redis中 状态只保存一天,防止数据被删除 缓存一直存在 redis_device_client.setex(device_status_key, DEFAULT_EXPIRED_DEVICE_STATUS, device_status) log.info( "设备状态设置成功: device_id = {} device_code = {} state = {} state_version = {}" .format(device.id, device.device_code, device_status, device.state_version + 1)) return True
def wechat_offline(): user_key = RedisClient.get_user_key(g.user_id) record_key = redis_cache_client.get(user_key) if record_key is None: return success({ 'status': 0, 'msg': "logout failed! reason: user device is already offline" }) charging = redis_cache_client.get(record_key) if charging is None: return success({ 'status': 0, 'msg': "logout failed! reason: user device is already offline" }) return WindowsService.do_offline(charging)
def get_device_status(device): ''' 获取设备使用状态 :param device: basestring or Device 类型 :return: ''' if isinstance(device, basestring): device_code = device elif isinstance(device, Device): # 如果传入的是设备信息直接返回设备使用状态即可, 缓存和数据库中的设备信息是保持严格一致的,只要写入则同时写入缓存和数据库 return device.state else: log.error("当前参数数据类型不正确: device = {} type = {}".format( device, type(device))) return None # 先判断是否在缓存中 device_status_key = RedisClient.get_device_status_key(device_code) device_status = redis_device_client.get(device_status_key) if device_status is not None: return device_status # 没有从缓存中找到设备状态 则去数据库中找 device = DeviceService.get_device_by_code(device_code) if device is None: log.error("当前设备码没有从缓存中找到,也不存在于数据库中: device_code = {}".format( device_code)) return None # 存储状态到redis中 状态只保存一天,防止数据被删除 缓存一直存在 redis_device_client.setex(device_status_key, DEFAULT_EXPIRED_DEVICE_STATUS, device.state) log.info( "当前设备状态从数据库中加载, 缓存到redis中: device_code = {}".format(device_code)) return device.state
def validate_captcha(self, mobile, captcha): if not settings.SMS_ENABLED: if captcha == settings.SMS_DEBUG_CAPTCHA: return True log.info("调试模式验证码校验失败: 发送过来的验证码 = {} 需要校验的调试验证码 = {}".format( captcha, settings.SMS_DEBUG_CAPTCHA)) return False key = RedisClient.get_captcha_redis_key(mobile) value = self.__redis.get(key) if value is None: log.info("当前手机不存在验证码: {}".format(mobile)) return False if captcha != value: log.info("当前手机验证码错误: phone = {} captcha = {} cache = {}".format( mobile, captcha, value)) return False # 删除已经验证码完成的验证码 self.__redis.delete(key) log.info("删除手机验证码redis key = {}".format(key)) return True
def keep_alive(): if not request.is_json: log.warn("参数错误...") return fail(HTTP_OK, u"need application/json!!") record_key = request.json.get('token') if record_key is None: return fail(HTTP_OK, u"not have token!!!") device_code = request.json.get('device_code') if device_code is None: log.error( "无法保持心跳, 没有传入device_code: record_key = {}".format(record_key)) return fail(HTTP_OK, u"not have device_code!!!") # 保持心跳 DeviceService.keep_device_heart(device_code) charging = redis_cache_client.get(record_key) if charging is None: return success({ "status": 0, "msg": "keepalive failed!reason:token invalid" }) # 获得keep_alive_key 更新最新存活时间 user_online_key = RedisClient.get_user_online_key(record_key) # 设置最新存活时间 最多存在五分钟 redis_cache_client.setex(user_online_key, settings.MAX_LOST_HEART_TIME, int(time.time())) return success({ "status": 1, "msg": "keepalive success", "data": WindowsService.get_current_time_charging(charging) })
def qr_code_online(device_code): # # 当前用户没有登录 # LOGIN_ERROR_BIND = -1 # # 当前用户已经被删除 # LOGIN_ERROR_DELETE = -2 # # 当前用户被禁止使用 # LOGIN_ERROR_FORBID = -3 # # 当前设备不存在 # LOGIN_ERROR_NOT_FIND = -4 # # 用户余额不足 # LOGIN_ERROR_NOT_SUFFICIENT_FUNDS = -5 # # 上机失败 未知错误 # LOGIN_ERROR_UNKNOW = -6 # # 设备已经在使用了 # LOGIN_ERROR_DEVICE_IN_USEING = -7 # # 当前用户已经在使用上机了,但是不是当前设备在使用 # LOGIN_ERROR_USER_IN_USEING = -8 # # 当前设备不处于空闲状态,不能上机 # LOGIN_ERROR_DEVICE_NOT_FREE = -9 scan_from = request.args.get('from') # 登录链接 login_url = url_for("wechat.menu", name="login") # 通过微信二维码扫描则需要判断当前用户是否已经关注公众号 if scan_from != 'playing': # # 初始化用户关注信息 # subscribe, nick_name, head_img_url = 0, '', '' openid = session.get('openid', None) # 如果不是微信二维码扫描 则跳转到登录界面 if openid is None: log.info("当前扫描登录没有openid,需要跳转到登录界面..") return redirect(login_url) # 获得用户的关注状态 以及头像和昵称信息 subscribe, nick_name, head_img_url = get_wechat_user_info(openid) # 如果用户没有关注微信号 直接跳转到关注页面 if subscribe != 1: log.info("当前用户没有关注公众号: subscribe = {} openid = {}".format( subscribe, openid)) return redirect(ATTENTION_URL) # 如果当前用户已经关注 则直接跳转到 祥基指定的链接 2017-10-13 15:26:00 url = '#/playing?code={}'.format(device_code) log.info("当前用户已经关注了公众号,跳转链接: {}".format(url)) return redirect(url) user_id_cookie = session.get('u_id') if user_id_cookie is None: log.warn("当前session中没有u_id 信息,需要登录...") return fail(HTTP_OK, u'当前用户没有登录', LOGIN_ERROR_BIND) user_id = decode_user_id(user_id_cookie) if user_id is None: log.warn( "当前用户信息被篡改,需要重新登录: user_id_cookie = {}".format(user_id_cookie)) return fail(HTTP_OK, u'当前用户登录信息被篡改, 不能登录', LOGIN_ERROR_BIND) # 获得用户信息 user = get_current_user(user_id) if user is None: log.warn("当前user_id还未绑定手机号码: user_id = {}".format(user_id)) return fail(HTTP_OK, u"用户还绑定手机号码登录!", LOGIN_ERROR_BIND) # 如果当前用户 被禁用 则不能上机 if user.deleted: log.warn("当前用户已经被删除了,无法上机: user_id = {}".format(user.id)) return fail(HTTP_OK, u"当前用户已经被删除了,不能上机", LOGIN_ERROR_DELETE) # 判断当前用户是否已经被禁用了 if user.state == 'unused': log.warn("当前用户已经被禁用了,无法上机: user_id = {}".format(user.id)) return fail(HTTP_OK, u"当前用户已经被禁用了,不能上机", LOGIN_ERROR_FORBID) # 获得设备信息 device = DeviceService.get_device_by_code(device_code=device_code) if device is None: log.warn("当前设备号没有对应的设备信息: device_code = {}".format(device_code)) return fail(HTTP_OK, u"设备信息异常,设备不存在", LOGIN_ERROR_NOT_FIND) # 获得最新费率 charge_mode = DeviceService.get_charge_mode(device) log.info("当前费率: charge_mode = {}".format(charge_mode)) # 判断用户是否余额充足 如果小于一分钟不能上机 if user.balance_account < charge_mode: log.info( "用户余额不足,不能上机: user_id = {} device_id = {} account = {}".format( user.id, device.id, user.balance_account)) return fail(HTTP_OK, u"用户余额不足,不能上机!", LOGIN_ERROR_NOT_SUFFICIENT_FUNDS) # 判断是否已经在redis中进行记录 record_key = RedisClient.get_record_key(user.id, device.id) # 获得用户上机key user_key = RedisClient.get_user_key(user.id) # 获得设备上机key device_key = RedisClient.get_device_key(device.id) # 判断是否已经登录了 charging = redis_cache_client.get(record_key) if charging is None: # 判断当前设备是否已经在使用了 if redis_cache_client.get(device_key): log.warn("当前设备{}已经在被使用,但是用户ID = {}又在申请".format(device.id, user.id)) return fail(HTTP_OK, u"当前设备已经在使用上机了,但是不是当前用户在使用!", LOGIN_ERROR_DEVICE_IN_USING) # 判断当前用户是否已经上机了 if redis_cache_client.get(user_key): log.warn("当前用户{}已经在上机,但是又在申请当前设备ID = {}".format( user.id, device.id)) return fail(HTTP_OK, u"当前用户已经在使用上机了,但是不是当前设备在使用!", LOGIN_ERROR_USER_IN_USING) # 判断当前设备是否处于空闲状态 且设备必须处于在线状态 device_status = DeviceService.get_device_status(device) device_alive = DeviceService.get_device_alive_status(device) if device_status != DeviceStatus.STATUE_FREE or device_alive != Device.ALIVE_ONLINE: log.warn("当前设备不处于空闲状态,不能上机: device_id = {} state = {} alive = {}". format(device.id, device_status, device_alive)) return fail(HTTP_OK, u"当前设备不处于空闲状态,或者当前设备不在线,不能上机!", LOGIN_ERROR_DEVICE_NOT_FREE) # 判断当前设备是否正在更新 或者正在自检,这种状态下不能够登录上机 current_update_state = DeviceService.get_update_state(device) if current_update_state == DeviceUpdateStatus.UPDATE_ING or \ current_update_state == DeviceUpdateStatus.UPDATE_CHECK: log.info( "当前设备正在更新或者自检中,不能登录: device_id = {} current_update_state = {}". format(device.id, current_update_state)) return fail(HTTP_OK, u"当前设备处于自检或者更新中,不能上机!", LOGIN_ERROR_DEVICE_NOT_FREE) log.info("用户还未上机可以进行上机: user_id = {} device_id = {}".format( user.id, device.id)) if not WindowsService.do_online(user, device, charge_mode): log.warn("上机记录创建失败,上机失败: user_id = {} device_id = {}".format( user.id, device.id)) return fail(HTTP_OK, u"上机异常!!", LOGIN_ERROR_UNKNOW) log.info("来自微信端游戏仓界面扫描: user_id = {} device_id = {}".format( user.id, device.id)) return success()
def decorator(*args, **kwargs): # 判断重新刷新token是否已经过期,如果过期则需要重新授权登录 openid = session.get('openid', None) # 如果两个关键的token都存在 则正常进入下面的流程 if openid is not None: g.openid = openid return func(*args, **kwargs) log.info("session 中没有openid") code = request.args.get('code', None) if code is None: log.info("url中没有code参数...") # 授权跳转到 url = get_login_oauth_url() if url is not None: return redirect(url) log.info("当前不是get请求访问到这里: {}".format(request.method)) return fail(HTTP_OK, u"微信未授权,请用微信端进行访问!") url = get_token_url(code) if url is None: log.info("获得token链接失败: code = {}".format(code)) return fail(HTTP_OK, u"获得微信token失败!") try: log.info("开始获取openid, 获得token链接为: url = {}".format(url)) resp = requests.get(url, verify=False, timeout=30) if resp.status_code != 200: log.warn("访问token链接失败: status_code = {} url = {}".format( resp.status_code, url)) return fail(HTTP_OK, u"获取access_token失败!") data = json.loads(resp.content) if data is None: log.warn("解析access_token失败: data = {}".format(data)) return fail(HTTP_OK, u"解析access_token失败!") openid = data.get('openid', None) if openid is None: log.warn("解析openid失败: data = {}".format(resp.content)) return fail(HTTP_OK, u"解析openid失败!") session['openid'] = openid # 保存access_token access_token = data.get('access_token', None) if access_token is not None: # session['access_token'] = access_token log.info("用户初次使用得到access_token = {}".format(access_token)) expires_in = data.get("expires_in", None) # 存入redis 中 if access_token is not None and expires_in is not None: # 添加到缓存 redis_cache_client.setex(RedisClient.get_openid_key(openid), expires_in, access_token) g.openid = openid log.info("通过url链接获得openid成功: openid = {}".format(openid)) return func(*args, **kwargs) except Exception as e: log.error("获取用户openid失败:") log.exception(e) return fail(HTTP_OK, u"微信授权失败!")
@time: 2017/10/15 18:51 """ # 获得用户的关注状态 以及头像和昵称信息 import json import sys import requests sys.path.append("..") from exts.common import WECHAT_ACCESS_TOKEN_KEY from exts.redis_api import RedisClient from logger import Logger log = Logger('get_user_info.log').get_logger() tmp_redis_client = RedisClient(db=0) # 获得用户的关注状态 以及头像和昵称信息 def get_wechat_user_info(openid): # 默认设置是未关注状态 subscribe, nick_name, head_img_url = 0, '', '' if openid is None: log.error("openid 为None,未知异常!!!") return subscribe, nick_name, head_img_url access_token = tmp_redis_client.get(WECHAT_ACCESS_TOKEN_KEY) if access_token is None: log.error("access_token 为None,刷新token进程异常!!!") return subscribe, nick_name, head_img_url
import json import threading import time import requests from apscheduler.schedulers.background import BackgroundScheduler import settings from exts.common import WECHAT_ACCESS_TOKEN_KEY, WECHAT_JSAPI_TICKET_KEY, REDIS_PRE_RECORD_KEY, log, cal_cost_time, \ DEFAULT_GAME_UPDATE_TIME from exts.redis_api import RedisClient from service.device.impl import DeviceGameService from service.windows.impl import WindowsService try: cache_client = RedisClient(db=0) except Exception as ex: log.error("启动redis失败..") log.exception(ex) exit(0) # 最后剩余时间阈值 WX_CACHE_LAST_TIME = 300 def update_access_token(): url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={}&secret={}'.format( settings.WECHAT_APP_ID, settings.WECHAT_APP_SECRET) try: resp = requests.get(url, verify=False, timeout=30) if resp.status_code != 200:
def do_charging(record_key_list): if not isinstance(record_key_list, list): log.error("当前传入参数不正确: type = {}".format(type(record_key_list))) return if len(record_key_list) <= 0: log.info("当前没有上线用户,不需要计费...") return # 开始针对用户进行扣费 for record_key in record_key_list: charge_str = cache_client.get(record_key) if charge_str is None: log.info("当前用户已经下线,不需要再计费: record_key = {}".format(record_key)) continue try: charge_dict = json.loads(charge_str) user_id = charge_dict.get('user_id') if user_id is None: log.error("没有关键信息 user_id: charge_str = {}".format(charge_str)) continue device_id = charge_dict.get('device_id') if device_id is None: log.error( "没有关键信息 device_id: charge_str = {}".format(charge_str)) continue record_key = RedisClient.get_record_key(user_id, device_id) # 判断是否已经有5分钟没有收到心跳 user_online_key = RedisClient.get_user_online_key(record_key) last_timestamp = cache_client.get(user_online_key) if last_timestamp is None: log.info( "没有收到任何心跳信息, 强制下机, 当前上线用户没有最后存活时间: user_id = {} device_id = {}" .format(user_id, device_id)) # 执行下机流程 if WindowsService.do_offline_order(record_key): log.info("没有收到任何心跳信息,强制下机完成: record_key = {}".format( record_key)) else: log.error("强制下机失败: record_key = {}".format(record_key)) continue # # 获得当前时间戳 # last_timestamp = int(last_timestamp) # now_timestamp = int(time.time()) # # # 如果当前丢失心跳的时间超过阈值,则默认离线,需要下机 # if now_timestamp - last_timestamp >= settings.MAX_LOST_HEART_TIME: # # 下机 # log.info("当前用户与机器没有收到任何心跳信息,强制下机: record_key = {} last_timestamp = {}".format( # record_key, last_timestamp)) # # 执行下机流程 # do_offline_order(record_key) # log.info("没有收到任何心跳信息,强制下机完成: record_key = {}".format(record_key)) # continue # charge_dict = { # 'id': self.id, # 'user_id': self.user_id, # 'device_id': self.device_id, # # 花费金额数目 # 'cost_money': self.cost_money, # # 上机时间 # 'ctime': self.ctime.strftime('%Y-%m-%d %H:%M:%S'), # # 更新时间,主要用户同步计费 # 'utime': self.utime.strftime('%Y-%m-%d %H:%M:%S'), # # 已经上机时间 # 'cost_time': self.cost_time, # # 计费方式 目前默认 5分钱/分钟 # 'charge_mode': 5, # # 当前用户余额 # 'balance_account': 10000, # # 设备机器码 # 'device_code': 'xx-xx-xx-xx-xx-xx', # } # 如果用户余额不足上机了,则强制下机 ctime = charge_dict.get('ctime') if ctime is None: log.error("没有关键信息 ctime: charge_str = {}".format(charge_str)) continue charge_mode = charge_dict.get('charge_mode') if charge_mode is None: log.error( "没有关键信息 charge_mode: charge_str = {}".format(charge_str)) continue balance_account = charge_dict.get('balance_account') if balance_account is None: log.error("没有关键信息 balance_account: charge_str = {}".format( charge_str)) continue now_timestamp = int(time.time()) start_time = int( time.mktime(time.strptime(ctime, "%Y-%m-%d %H:%M:%S"))) cost_time = cal_cost_time(now_timestamp - start_time) cost_money = cost_time * int(charge_mode) # 如果使用的费用超额半分钟的费用,则强制下机 if cost_money - balance_account >= 0.75 * int(charge_mode): log.info( "当前用户余额不足,强制下机: record_key = {} balance_account = {} " "cost_time = {}分钟 cost_money = {} start_time = {} now_time = {}" .format(record_key, balance_account, cost_time, cost_money, start_time, now_timestamp)) # 执行下机流程 if WindowsService.do_offline_order(record_key): log.info( "当前用户余额不足, 强制下机完成: record_key = {}".format(record_key)) else: log.error("强制下机失败: record_key = {}".format(record_key)) continue except Exception as e: log.error("当前存入的计费数据格式不正确: charge_str = {}".format(charge_str)) log.exception(e) continue
def do_online(user, device, charge_mode): log.info("用户还未上机可以进行上机: user_id = {} device_id = {}".format( user.id, device.id)) record, is_success = UseRecord.create(user.id, device.id, device.address.province, device.address.city, device.address.area, device.address.location) if not is_success: return False # 判断是否已经在redis中进行记录 record_key = RedisClient.get_record_key(user.id, device.id) # 获得用户上线key user_key = RedisClient.get_user_key(user.id) # 获得设备上线key device_key = RedisClient.get_device_key(device.id) # 获得当前设备token device_code_key = RedisClient.get_device_code_key(device.device_code) # 获得keep_alive_key 更新最新存活时间 user_online_key = RedisClient.get_user_online_key(record_key) log.info( "当前上机时间: user_id:{} device_id:{} record_id:{} ctime:{}".format( user.id, device.id, record.id, record.ctime.strftime('%Y-%m-%d %H:%M:%S'))) # 获得计费结构体 charging = record.to_charging() # 得到计费方式 charging['charge_mode'] = charge_mode # 得到当前用户总额 charging['balance_account'] = user.balance_account # 填充设备机器码 charging['device_code'] = device.device_code # 填充用户的openid charging['openid'] = user.openid # charging = { # 'id': self.id, # 'user_id': self.user_id, # 'device_id': self.device_id, # # 花费金额数目 # 'cost_money': self.cost_money, # # 上机时间 # 'ctime': self.ctime.strftime('%Y-%m-%d %H:%M:%S'), # # 更新时间,主要用户同步计费 # 'utime': self.utime.strftime('%Y-%m-%d %H:%M:%S'), # # 已经上机时间 # 'cost_time': self.cost_time, # # 计费方式 目前默认 5分钱/分钟 # 'charge_mode': 5, # # 当前用户余额 # 'balance_account': 10000, # # 设备机器码 # 'device_code': 'xx-xx-xx-xx-xx-xx', # } charge_str = json.dumps(charging) # 操作redis 需要加锁 lock = DistributeLock(user_key, redis_cache_client) try: lock.acquire() # 设置设备当前使用状态 if not DeviceService.set_device_status(device, DeviceStatus.STATUE_BUSY): log.error("设置设备状态失败, 上机异常!!!") return False # 开始上线 把上线信息存储redis redis_cache_client.set(record_key, charge_str) redis_cache_client.set(user_key, record_key) redis_cache_client.set(device_key, record_key) # 根据设备机器码获得记录token redis_cache_client.set(device_code_key, record_key) # 设置最新存活时间 最多存活五分钟 import time redis_cache_client.setex(user_online_key, settings.MAX_LOST_HEART_TIME, int(time.time())) is_success = True except Exception as e: is_success = False log.exception(e) finally: lock.release() # 判断上线是否成功 if is_success: # 发送上线通知 TemplateService.online(user.openid, record.ctime, device.address, user.balance_account, charge_mode) return True
def user_offline(): if not request.is_json: log.warn("参数错误...") return fail(HTTP_OK, u"need application/json!!") user_id = request.json.get('user_id') device_code = request.json.get('device_code') device_id = request.json.get('device_id') log.info("当前强制下机user_id = {}".format(user_id)) log.info("当前强制下机device_code = {}".format(device_code)) log.info("当前强制下机device_id = {}".format(device_id)) if user_id is not None: user_key = RedisClient.get_user_key(user_id) record_key = redis_cache_client.get(user_key) if record_key is None: return success({ 'status': 0, 'msg': "logout failed! reason: user device is already offline" }) charging = redis_cache_client.get(record_key) if charging is None: return success({ 'status': 0, 'msg': "logout failed! reason: user device is already offline" }) log.info("通过user_id下机: user_id = {}".format(user_id)) return WindowsService.do_offline(charging) if device_code is not None: device_code_key = RedisClient.get_device_code_key(device_code) record_key = redis_cache_client.get(device_code_key) if record_key is not None: charging = redis_cache_client.get(record_key) if charging is None: return success({ 'status': 0, 'msg': "logout failed! reason: user device is already offline" }) log.info("通过device_code下机: device_code = {}".format(device_code)) return WindowsService.do_offline(charging) if device_id is not None: device_key = RedisClient.get_device_key(device_id) record_key = redis_cache_client.get(device_key) if record_key is None: return success({ 'status': 0, 'msg': "logout failed! reason: user device is already offline" }) charging = redis_cache_client.get(record_key) if charging is None: return success({ 'status': 0, 'msg': "logout failed! reason: user device is already offline" }) log.info("通过device_id下机: device_id = {}".format(device_id)) return WindowsService.do_offline(charging) return success(u'当前参数没有使任何机器或用户下机')
def do_offline(charging): # offline_lock_key = None lock = None if charging is None: log.error("charging is None 下机异常!!") return fail(HTTP_OK, u"下机异常!") try: charge_dict = json.loads(charging) record_id = charge_dict.get('id') user_id = charge_dict.get('user_id') device_id = charge_dict.get('device_id') charge_mode = charge_dict.get('charge_mode') device_code = charge_dict.get('device_code') openid = charge_dict.get('openid') log.info( "当前下线信息: user_id = {} device_id = {} charge_mode = {} device_code = {}" .format(user_id, device_id, charge_mode, device_code)) # 获得用户上线key user_key = RedisClient.get_user_key(user_id) # 下机需要加锁 lock = DistributeLock(user_key, redis_cache_client) log.info("开始加锁下机: user_key = {}".format(user_key)) # 加锁下机 lock.acquire() # 判断是否已经在redis中进行记录 record_key = RedisClient.get_record_key(user_id, device_id) if redis_cache_client.get(record_key) is None: log.warn("当前用户或者设备已经下机: user_id = {} device_id = {}".format( user_id, device_id)) return success({'status': 1, 'msg': 'logout successed!'}) # 结账下机 result, record, user = WindowsService.cal_offline( user_id=user_id, device_id=device_id, record_id=record_id, charge_mode=charge_mode) if not result: log.error( "下机扣费失败: user_id = {} device_id = {} charge_mode = {}". format(user_id, device_id, charge_mode)) return fail(HTTP_OK, u"下机失败!") # 获得设备上线key device_key = RedisClient.get_device_key(device_id) # 获得当前设备token device_code_key = RedisClient.get_device_code_key(device_code) # 获得keep_alive_key 更新最新存活时间 user_online_key = RedisClient.get_user_online_key(record_key) # 从redis中删除上机记录 redis_cache_client.delete(record_key) redis_cache_client.delete(user_key) redis_cache_client.delete(device_key) redis_cache_client.delete(device_code_key) redis_cache_client.delete(user_online_key) is_success = True except Exception as e: log.error("数据解析失败: {}".format(charging)) log.exception(e) return fail(HTTP_OK, u"数据解析失败!!") finally: # 解锁 if lock is not None: lock.release() log.info("下机完成: lock_key = {}".format(lock.lock_key)) # 如果成功则进行下机提醒 if is_success and openid is not None: TemplateService.offline(openid, record, user.balance_account) log.info("下机成功: user_id = {} device_id = {}".format( user_id, device_id)) return success({'status': 1, 'msg': 'logout successed!'})