#!/usr/bin/python # -*- coding: utf-8 -*- # ##@package # # @todo: 激活码 # # @author: Alee # @date 2018-5-4 22:10:51 # @version 1.0 # # @note: 中文必须传utf8,收到的为urlencode内容 .decode('gbk').encode('utf8') # 1.使用礼包卡号 #--------------------------------------------------------------------- from bottle import Bottle, request, static_file import md5 import CouponDB import datetime from lib import ReadConfig import json import urllib2 import urllib from lib import mylog, CommFunc import os.path import zipfile import time import traceback # get: request.query.username request.GET.get('username','') # post: request.forms.get('username') request.POST.get('username') #=============================================================================== # @myapp.route('/cool/kk/:name3/:count#\\d+#') # def maybe(name3, count): # client_ip = request.environ.get('REMOTE_ADDR') # client_ip = request.environ.get('HTTP_X_FORWARDED_FOR') or request.environ.get('REMOTE_ADDR') #=============================================================================== ConfigIO = ReadConfig.ReadConfig(os.getcwd() + "\\..\\Coupon\\config.ini") # 应用端口 CenterPort = ConfigIO.GetValue("Coupon", "CenterPort") #mobiletest.173on.com:55000 Def_ExportUrl = ConfigIO.GetValue("Coupon", "exporturl") + "/Coupon/CouponLoad.php?couponid=%s&channel=%s" # GM端口 PushPort = ConfigIO.GetValue("Coupon", "PushPort") PushKey = ConfigIO.GetValue("Coupon", "PushKey") PushTimeout = ConfigIO.GetInt("Coupon", "PushTimeout", 5) CommonCards = eval(ConfigIO.GetValue("Coupon", "CommonCards")) g_mailJsonDict = {} # {channel:MailJson, ...} # 发放奖励邮件内容json,utf8编码 def GetMailJson(channel): global g_mailJsonDict if channel in g_mailJsonDict: return g_mailJsonDict[channel] jsonPath = os.getcwd() + ("\\..\\Coupon\\CodeMail_%s.json" % channel) if not os.path.isfile(jsonPath): jsonPath = os.getcwd() + "\\..\\Coupon\\CodeMail.json" f = open(jsonPath, "r") MailJson = eval(f.read()) f.close() g_mailJsonDict[channel] = MailJson return MailJson # 礼包使用返回结果码定义 ErrCode_OK = 0 # 成功,非0的码均代表错误 ErrCode_Invalid = 1 # 兑换码无效 ErrCode_Used = 2 # 礼包码已被使用 ErrCode_TypeUsed = 3 # 已经使用过同类型的礼包码 ErrCode_LVLimit = 4 # 等级不足 ErrCode_VIPLVLimit = 5 # VIP等级不足 ErrCode_ParamErr = 97 # 参数错误 ErrCode_FrequentRequest = 98 # 频繁请求 ErrCode_OtherErr = 99 # 其他错误,比如运行错误等 # 礼包码处理结果码信息 {code:["码信息说明", "给玩家的系统提示key"], ...} ErrCodeMsgInfo = { ErrCode_OK:["OK", "CodeRewardSys1"], ErrCode_Invalid:["code invalid", "CodeRewardSys2"], ErrCode_Used:["code has been used", "CodeRewardSys3"], ErrCode_TypeUsed:["You has been used this Giftcode type", "CodeRewardSys4"], ErrCode_LVLimit:["level limit", "LvErr"], ErrCode_VIPLVLimit:["vip level limit", "VipLevel"], ErrCode_ParamErr:["parameter error", ""], ErrCode_FrequentRequest:["frequent request", ""], ErrCode_OtherErr:["other error", ""], } def CouponCodeResponse(errcode, msgEx=""): errmsg = "" if errcode in ErrCodeMsgInfo: errmsg = ErrCodeMsgInfo[errcode][0] if msgEx: errmsg = "%s : %s" % (errmsg, msgEx) resData = {"errcode":errcode, "errmsg":errmsg} if errcode != ErrCode_OK: mylog.error("resError: %s" % str(resData)) else: mylog.debug("resData: %s" % str(resData)) #mylog.info("resData: %s" % str(resData)) return json.dumps(resData, ensure_ascii=False) myapp = Bottle() # 防止短时间内的多次无效访问 accid:time AccID_Cache_Dict = {} def CleanAccID(): global AccID_Cache_Dict if len(AccID_Cache_Dict) < 100: return AccID_Cache_Dict = {} #/api/Coupon/index.php?couponBatchId=%s&agentName=%s" # 下载指定礼包批次 @myapp.route('/Coupon/CouponLoad.php') def LoadCouponBatch(): dataDict = request.GET appid = dataDict.get("channel", "") # 运营提供的APPID,即渠道 if not appid: mylog.debug("no appid") return json.dumps({"error":"param appid"}, ensure_ascii=False) couponid = dataDict.get("couponid", "") if not couponid: return json.dumps({"error":"param couponid"}, ensure_ascii=False) downFile = r".\download\%s.zip" % couponid if os.path.isfile(downFile): return static_file("%s.zip" % couponid, r".\download", download=True) dbController = CouponDB.GetDBEventCon() if not dbController: # 无法获取数据库 mylog.debug("no dbController") return json.dumps({"error":"db"}, ensure_ascii=False) result, data = dbController.find(CouponDB.CouponCodeColName + "_" + appid, {'channel':appid, 'couponid':couponid}) if not data: return json.dumps({"error":"no couponid"}, ensure_ascii=False) fileIO = open(r".\download\%s.txt" % couponid, 'w') for codeInfo in data: fileIO.write(codeInfo["code"] + "\n") fileIO.close() f = zipfile.ZipFile(downFile, 'w', zipfile.ZIP_DEFLATED) f.write(r".\download\%s.txt" % couponid) f.close() return static_file("%s.zip" % couponid, r".\download", download=True) #================客户端请求参数=============================================================== # channel 是 string Appid 如jsgameios # code 是 string 兑换码 # accid 是 string 账号 # spid 是 string 运营商ID,研发自己配置的,如jisu代表极速平台 # sid 是 int 服务器ID 数字 # pushurl 是 string 域名链接s222.xxx.com # roleid 是 string 玩家游戏名 # level 是 int 玩家等级 # viplevel 是 Int 玩家VIP等级 #=============================================================================== #2018.10.10 混服支持导致账号拼接逻辑变更需要spid # 卡类型 开头字母做标识如 g1~999代表此卡只能使用一次 h1~999卡可以重复使用 z开头代表固定码 wx开头代表微信商店 # 玩家使用卡号 参数 平台 区服 账号 区服链接(GM用)卡号 # http://center.xxx.com:53003/Coupon/CouponCode.php @myapp.route('/Coupon/CouponCode.php') def CouponCode(): global AccID_Cache_Dict dataDict = request.GET for key, value in dataDict.items(): mylog.debug("CouponCode key:%s value:%s" % (key, value)) agentName = dataDict.get("channel", "") # 运营提供的APPID,即渠道 if not agentName: return CouponCodeResponse(ErrCode_ParamErr, "no channel appid") codeStr = dataDict.get("code", "") if not codeStr: return CouponCodeResponse(ErrCode_ParamErr, "no code") codeStr = codeStr.strip() #只是用来拼接账号 spID = dataDict.get("spid", "") if not spID: return CouponCodeResponse(ErrCode_ParamErr, "no spid") accid = dataDict.get("accid", "") if not accid: return CouponCodeResponse(ErrCode_ParamErr, "no accid") sid = CommFunc.ToIntDef(dataDict.get("sid", 0), 0) if not sid: return CouponCodeResponse(ErrCode_ParamErr, "no sid") pushurl = dataDict.get("pushurl", "") if not pushurl: return CouponCodeResponse(ErrCode_ParamErr, "no pushurl") # 转化为游戏账号 accid = "%s@%s@s%s" % (accid.lower(), spID, sid) try: CleanAccID() #mylog.debug("AccID_Cache_Dict : %s"%str(AccID_Cache_Dict)) if accid not in AccID_Cache_Dict: AccID_Cache_Dict[accid] = int(time.time()) elif time.time() - AccID_Cache_Dict[accid] < 2: mylog.debug("==========bad: fast click %s" % accid) return CouponCodeResponse(ErrCode_FrequentRequest) AccID_Cache_Dict[accid] = int(time.time()) except: pass dbController = CouponDB.GetDBEventCon() if not dbController: # 无法获取数据库 return CouponCodeResponse(ErrCode_OtherErr, "no dbController") #通过GM接口告知玩家使用情况 gmresult = {'accID':accid, 'sid':sid, 'channel':agentName} #-----------统一固定码处理---------------------- CommonCardsList = CommonCards.get(agentName, []) if codeStr in CommonCardsList: # 不同固定码各自只能领一次 result, commondata = dbController.find_one(CouponDB.CouponCodeColName + "_Common", {"code":codeStr, "accid":accid}) if commondata: # 已使用 return SendGm(ErrCode_Used, gmresult, pushurl)[1] commCheckStatus = CommCheck(dataDict, codeStr) if commCheckStatus != None: return SendGm(commCheckStatus, gmresult, pushurl)[1] gmresult['code'] = codeStr gmresult['coupontype'] = codeStr # 统一格式,服务器不一定有用,固定码根据 codeStr 发奖励 retCode, retMsg = SendGm(ErrCode_OK, gmresult, pushurl) if retCode == ErrCode_OK: dbController.insert(CouponDB.CouponCodeColName + "_Common", {"code":codeStr, "accid":accid}) mylog.debug("common ok") return retMsg #-----------微信商城---------------------- if codeStr.startswith('wx'): operateID = GetOperateID(agentName) if not operateID: return CouponCodeResponse(ErrCode_OtherErr, "wx no appid = %s" % agentName) result, wxdata = dbController.find_one(CouponDB.CouponWXColName, {"code":codeStr, "operateid":operateID}) if not wxdata: return CouponCodeResponse(ErrCode_OtherErr, "not wxdata code(%s) operateID(%s)" % (codeStr, operateID)) if int(wxdata['status']) == 1: mylog.debug("wxcard used!") return SendGm(ErrCode_Used, gmresult, pushurl)[1] wxdata['status'] = 1 wxdata['accid'] = dataDict.get("accid", "") # 此处用原始账号 wxdata['usetime'] = str(datetime.datetime.today()).split(".")[0] wxdata['appid'] = agentName wxdata['serverid'] = 's%s' % sid wxdata['roleid'] = dataDict.get("roleid", "") wxdata['level'] = dataDict.get("level", "") wxdata['viplevel'] = dataDict.get("viplevel", "") if SendWXBill(wxdata, pushurl): # 充值成功 dbController.update(CouponDB.CouponWXColName, {"code":codeStr, "operateid":operateID}, wxdata) # 通知后台记录 SendDataCollectorBillInfo(wxdata, pushurl, operateID) return CouponCodeResponse(ErrCode_OK) return CouponCodeResponse(ErrCode_OtherErr, "SendWXBill error") #-----------批量兑换码处理,同类型只领取一次---------------------- result, data = dbController.find_one(CouponDB.CouponCodeColName + "_" + agentName, {"code":codeStr, "channel":agentName}) if not data: #无此卡 mylog.debug("no card") return SendGm(ErrCode_Invalid, gmresult, pushurl)[1] if int(data['status']) == 1: #已使用,同卡号记录默认可用,避免断线发送失败的情况(未返回结果验证情况下) mylog.debug("card used!") return SendGm(ErrCode_Used, gmresult, pushurl)[1] couponType = data["coupontype"] commCheckStatus = CommCheck(dataDict, data["coupontype"]) if commCheckStatus != None: return SendGm(commCheckStatus, gmresult, pushurl)[1] result, data2 = dbController.find_one(CouponDB.CouponBatchColName, {"couponid":data["couponid"]}) if not data2: #此批次卡已删除 mylog.debug("no couponid") return SendGm(ErrCode_Invalid, gmresult, pushurl)[1] if int(data2['status']) == 1: #暂停使用该批次卡 mylog.debug("couponid frozed") return SendGm(ErrCode_Invalid, gmresult, pushurl)[1] if (data2["minserverno"] != 0 and data2["maxserverno"] != 0): if int(data2["minserverno"]) > sid or int(data2["maxserverno"]) < sid: #不在指定区 mylog.debug("no sid") return SendGm(ErrCode_Invalid, gmresult, pushurl)[1] if data2["expiretime"] != "" and datetime.datetime.today() > GetDateTimeByStr(data2["expiretime"]): #已过期 mylog.debug("time pass") return SendGm(ErrCode_Invalid, gmresult, pushurl)[1] maxUseCount = 0 # -1-无限;0或1-默认1次; >1-限制次数; 没有配置的默认0即默认1次 prefix = "" # 固定前缀 MailJson = GetMailJson(agentName) if couponType in MailJson: mailInfo = MailJson[couponType] prefix = mailInfo.get("Prefix", "") maxUseCount = CommFunc.ToIntDef(mailInfo.get("MaxUseCount", 0)) elif ConfigIO.HasSection(couponType): if ConfigIO.HasOption(couponType, "Prefix"): prefix = ConfigIO.GetValue(couponType, "Prefix") maxUseCount = ConfigIO.GetInt(couponType, "MaxUseCount") # h 开头的卡类型可重复使用 #if not codeStr.startswith(prefix + 'h'): #修改为按配置最大次数判断即可 if maxUseCount in [0, 1]: #再查一次是否用过此批次的其他卡, result, data3 = dbController.find_one(CouponDB.CouponCodeColName + "_" + agentName, \ {"coupontype":data["coupontype"], "accid":accid}) if data3: #用过同类卡,已记录过不再做新记录 gmresult['coupontype'] = data["coupontype"] gmresult['code'] = codeStr mylog.debug("use again") return SendGm(ErrCode_TypeUsed, gmresult, pushurl)[1] elif maxUseCount > 1: result, resultList = dbController.find(CouponDB.CouponCodeColName + "_" + agentName, {"coupontype":data["coupontype"], "accid":accid}, {"code":1}) if not result or len(resultList) >= maxUseCount: gmresult['coupontype'] = data["coupontype"] gmresult['code'] = codeStr mylog.debug("use maxUseCount limit. %s" % maxUseCount) return SendGm(ErrCode_TypeUsed, gmresult, pushurl)[1] data['status'] = 1 data['accid'] = accid data['usetime'] = str(datetime.datetime.today()) gmresult['code'] = codeStr gmresult['coupontype'] = data["coupontype"] retCode, retMsg = SendGm(ErrCode_OK, gmresult, pushurl) if retCode == ErrCode_OK: dbController.update(CouponDB.CouponCodeColName + "_" + agentName, {"code":codeStr, "channel":agentName}, data) mylog.debug("card ok!") return retMsg def CommCheck(dataDict, coupontype): ## 通用常规检查 MailJson = GetMailJson(dataDict.get("channel", "")) if coupontype in MailJson: mailInfo = MailJson[coupontype] lvLimit = mailInfo.get("LV", 0) vipLVLimit = mailInfo.get("VIPLV", 0) else: lvLimit = ConfigIO.GetInt(coupontype, "LV") vipLVLimit = ConfigIO.GetInt(coupontype, "VIPLV") # 使用等级 playerLV = CommFunc.ToIntDef(dataDict.get("level", 0)) if lvLimit > 0 and playerLV < lvLimit: mylog.debug("playerLV(%s) < lvLimit(%s)" % (playerLV, lvLimit)) return ErrCode_LVLimit # vip等级相关 playerVIPLV = CommFunc.ToIntDef(dataDict.get("viplevel", 0)) if vipLVLimit > 0 and playerVIPLV < vipLVLimit: mylog.debug("playerVIPLV(%s) < vipLVLimit(%s)" % (playerVIPLV, vipLVLimit)) return ErrCode_VIPLVLimit return def SendGm(errcode, gmresult, pushurl): try: # GM推送地址 #GMToolPage = http://s1.yhlz.09ge.com:30001/Server/Tool.php # 兼容两种入口,一种客户端只发服务器域名的,一种GM工具直接读取的服务器GM工具地址 if pushurl.endswith("Tool.php"): gmurl = pushurl else: gmurl = "http://%s:%s/Server/Tool.php" % (pushurl, PushPort) gmkey = PushKey if not gmkey or not PushPort: return ErrCode_OtherErr, CouponCodeResponse(ErrCode_OtherErr, "no PushKey or not PushPort") mylog.debug("SendGm:%s" % gmurl) notifyMsg = "" if errcode in ErrCodeMsgInfo: notifyMsg = ErrCodeMsgInfo[errcode][1] if not notifyMsg: notifyMsg = "CodeRewardSys2" if notifyMsg: gmtData = {"queryType":"accID", "playerFind":gmresult["accID"], "notifyMsg":notifyMsg} SendGMTCMD(gmurl, gmtData, gmkey, "GMT_MediaCard") if errcode != ErrCode_OK: return errcode, CouponCodeResponse(errcode) coupontype = gmresult["coupontype"] MailJson = GetMailJson(gmresult["channel"]) if coupontype in MailJson: mailInfo = MailJson[coupontype] mailTitle = mailInfo.get("MailTitle", "MailTitle") mailText = mailInfo.get("MailText", "") mailSender = mailInfo.get("MailSender", "System") mailItems = mailInfo.get("Items", []) else: mailTitle = ConfigIO.GetValue(gmresult["coupontype"], "MailTitle").decode("gbk") mailText = ConfigIO.GetValue(gmresult["coupontype"], "MailText").decode("gbk") mailSender = ConfigIO.GetValue(gmresult["coupontype"], "MailSender").decode("gbk") mailItems = eval(ConfigIO.GetValue(gmresult["coupontype"], "Items")) pack_data = {} pack_data["queryType"] = "accID" pack_data["playerList"] = gmresult["accID"] pack_data["Title"] = mailTitle pack_data["Text"] = mailText pack_data["EndTime"] = str(datetime.datetime.today() + datetime.timedelta(days=15)).split('.')[0] pack_data["Sender"] = mailSender Items = mailItems pack_data["itemNums"] = ','.join([str(i) for i in range(len(Items))]) i = 0 for itemList in Items: pack_data["ItemID%s" % i] = str(itemList[0]) pack_data["ItemCnt%s" % i] = str(itemList[1]) pack_data["IsBind%s" % i] = str(itemList[2] if len(itemList) > 2 else 0) i += 1 pack_data["Detail"] = "cardCode:" + gmresult["code"] ResultType = SendGMTCMD(gmurl, pack_data, gmkey, "GMT_AddPersonalCompensation") if ResultType == 0: return ErrCode_OK, CouponCodeResponse(ErrCode_OK) else: return ErrCode_OtherErr, CouponCodeResponse(ErrCode_OtherErr, "ResultType error is %s" % ResultType) except BaseException: mylog.error("SendGm error %s" % traceback.format_exc()) return ErrCode_OtherErr, CouponCodeResponse(ErrCode_OtherErr, "gm error") def SendGMTCMD(gmurl, gmtData, gmkey, pack_type): ## 发送GM工具命令 pack_data = {} if gmtData and isinstance(gmtData, dict): pack_data.update(gmtData) pack_data["pack_type"] = pack_type pack_data["key"] = gmkey pack_data['coding'] = "utf8" #使用key加密 pack_data_dict = json.dumps(pack_data) sign = md5.md5(pack_data_dict + gmkey).hexdigest() post = {} post['pack'] = pack_data_dict; post['sign'] = sign; result = urllib2.urlopen(gmurl, urllib.urlencode(post), PushTimeout) retContent = result.read() resultDict = json.loads(retContent) result.close() ResultType = resultDict.get("ResultType", 5) # 5未知错误 return ResultType def GetDateTimeByStr(timeStr, timeFomat="%Y-%m-%d"): try: return datetime.datetime.strptime(timeStr, timeFomat) except: mylog.debug("GetDateTimeByStr error %s" % timeStr) return # 通过appid找到运营商 def GetOperateID(appid): appidDict = eval(ConfigIO.GetValue("Coupon", "appid_dict")) for key, value in appidDict.items(): if appid in value: return key return "" # 微信入口充值 def SendWXBill(wxdata, pushurl): try: billurl = "http://%s/api/exchange/index.php" % pushurl post = {} post['AccountID'] = wxdata['accid'] post['RegionName'] = wxdata['serverid'] post['OrderAmount'] = wxdata['money'] post['BillNO'] = wxdata['orderid'] post['OrderInfo'] = wxdata['orderinfo'] post['OperatorID'] = wxdata['appid'] key = ConfigIO.GetValue("Coupon", "key_%s" % post['OperatorID']) # $sign=md5($AccountID.$OrderAmount.$BillNO.$RegionName.$key) mylog.debug("appid = %s key = %s" % (post['OperatorID'], key)) sign = md5.md5(post['AccountID'] + str(post['OrderAmount']) + post['BillNO'] + post['RegionName'] + key).hexdigest() post['Sign'] = sign result = urllib2.urlopen(billurl, urllib.urlencode(post), 3) resultDict = json.loads(result.read()) mylog.debug("resultDict = %s " % (resultDict)) if int(resultDict["errorcode"]) > 0: return True except Exception, e: mylog.info("SendWXBill error %s" % e) return False # 通知后台数据中心记录 def SendDataCollectorBillInfo(wxdata, pushurl, operateID): try: billurl = "http://recharge.game.2460web.com:12000/api/notify/swwx" post = {} post['appid'] = wxdata['appid'] post['regionid'] = wxdata['serverid'] post['orderid'] = wxdata['orderid'] post['passport'] = wxdata['accid'] post['roleid'] = wxdata['roleid'] post['ordertitle'] = wxdata['ordertitle'].encode("UTF8") post['orderinfo'] = wxdata['orderinfo'] post['money'] = wxdata['money'] post['ip'] = wxdata['ip'] post['level'] = wxdata['level'] post['viplevel'] = wxdata['viplevel'] post['time'] = int(time.time()) key = ConfigIO.GetValue("Coupon", "key_%s" % post['appid']) #md5(appid=$appid®ionid=$regionid&passport=$passport&&money=$money&time=$time$app_secret) signStr = "appid=%s®ionid=%s&passport=%s&money=%s&time=%s%s" % (\ post['appid'], post['regionid'], post['passport'], post['money'], post['time'], key) sign = md5.md5(signStr).hexdigest() #mylog.debug("SendDataCollectorBillInfo %s-%s-%s-%s"%(signStr, sign, key, post)) post['sign'] = sign result = urllib2.urlopen(billurl + "?" + urllib.urlencode(post), timeout=3) #mylog.debug("SendDataCollectorBillInfo result %s"%result.read()) except Exception, e: mylog.debug("SendDataCollectorBillInfo error %s" % e) # 查询审核时间 @myapp.route('/auditdate.php') def QueryAuditdate(): return "2021-06-22 11:30:00"