增加注册登录 geetest 验证,增加免费次数下载,增加速率限制功能

pull/12/head
youngS 4 years ago
parent c2ecb1aa3e
commit 4b2a3c7508
  1. 357
      fir_client/src/assets/gt.js
  2. 2
      fir_client/src/components/FirAppInfostimeline.vue
  3. 56
      fir_client/src/components/FirApps.vue
  4. 12
      fir_client/src/components/FirDownload.vue
  5. 62
      fir_client/src/components/FirLogin.vue
  6. 91
      fir_client/src/components/FirRegist.vue
  7. 19
      fir_client/src/components/ShortDownload.vue
  8. 3
      fir_client/src/restful/download.js
  9. 5
      fir_client/src/restful/index.js
  10. 40
      fir_client/src/utils/index.js
  11. 18
      fir_ser/api/migrations/0024_auto_20210324_1751.py
  12. 2
      fir_ser/api/models.py
  13. 12
      fir_ser/api/utils/crontab/run.py
  14. 149
      fir_ser/api/utils/geetest/geetest_lib.py
  15. 15
      fir_ser/api/utils/geetest/geetest_lib_result.py
  16. 82
      fir_ser/api/utils/geetest/geetest_utils.py
  17. 8
      fir_ser/api/utils/serializer.py
  18. 67
      fir_ser/api/utils/storage/caches.py
  19. 70
      fir_ser/api/utils/throttle.py
  20. 27
      fir_ser/api/views/download.py
  21. 49
      fir_ser/api/views/login.py
  22. 33
      fir_ser/fir_ser/settings.py

File diff suppressed because one or more lines are too long

@ -136,6 +136,8 @@
getdownloadurl(res => { getdownloadurl(res => {
if (res.code === 1000) { if (res.code === 1000) {
window.location.href = res.data.download_url; window.location.href = res.data.download_url;
} else {
this.$message.error(res.msg);
} }
}, { }, {
'data': { 'data': {

@ -201,9 +201,9 @@
</el-col> </el-col>
</el-row> </el-row>
</el-col> </el-col>
<el-col :span="8" class="surplus-card"> <el-col :span="5" class="surplus-card">
<el-row> <el-row>
<el-col :span="8"> <el-col :span="10">
<div> <div>
<span class="name">iOS应用</span> <span class="name">iOS应用</span>
<el-divider direction="vertical"></el-divider> <el-divider direction="vertical"></el-divider>
@ -214,7 +214,7 @@
</div> </div>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="12">
<div> <div>
<span class="name">Android应用</span> <span class="name">Android应用</span>
<el-divider direction="vertical"></el-divider> <el-divider direction="vertical"></el-divider>
@ -224,57 +224,75 @@
<el-divider direction="vertical"></el-divider> <el-divider direction="vertical"></el-divider>
</div> </div>
</el-col> </el-col>
</el-row>
</el-col>
<el-col :span="8"> <el-col :span="5" class="surplus-card">
<el-row>
<el-col :span="12">
<div> <div>
<span class="name">总应用</span> <span class="name">今日苹果下载次数</span>
<el-divider direction="vertical"></el-divider> <el-divider direction="vertical"></el-divider>
</div> </div>
<div> <div>
<span class="value">{{ hdata.ios_count + hdata.android_count}}</span> <span class="value">{{ hdata.ios_today_hits_count }}</span>
<el-divider direction="vertical"></el-divider> <el-divider direction="vertical"></el-divider>
</div> </div>
</el-col> </el-col>
</el-row>
</el-col>
<el-col :span="8" class="surplus-card"> <el-col :span="12">
<el-row>
<el-col :span="8">
<div> <div>
<span class="name">今日苹果下载次数</span> <span class="name">今日安卓下载次数</span>
<el-divider direction="vertical"></el-divider> <el-divider direction="vertical"></el-divider>
</div> </div>
<div> <div>
<span class="value">{{ hdata.ios_today_hits_count }}</span> <span class="value">{{ hdata.android_today_hits_count }}</span>
<el-divider direction="vertical"></el-divider> <el-divider direction="vertical"></el-divider>
</div> </div>
</el-col> </el-col>
<el-col :span="8"> <!-- <el-col :span="8">-->
<!-- <div>-->
<!-- <span class="name">总共下载次数</span>-->
<!-- <el-divider direction="vertical"></el-divider>-->
<!-- </div>-->
<!-- <div>-->
<!-- <span class="value">{{ hdata.all_hits_count}}</span>-->
<!-- <el-divider direction="vertical"></el-divider>-->
<!-- </div>-->
<!-- </el-col>-->
</el-row>
</el-col>
<el-col :span="6" class="surplus-card">
<el-row>
<el-col :span="12">
<div> <div>
<span class="name">今日安卓下载次数</span> <span class="name">今日剩余免费下载次数</span>
<el-divider direction="vertical"></el-divider> <el-divider direction="vertical"></el-divider>
</div> </div>
<div> <div>
<span class="value">{{ hdata.android_today_hits_count }}</span> <span class="value">{{$store.state.userinfo.free_download_times }}</span>
<el-divider direction="vertical"></el-divider> <el-divider direction="vertical"></el-divider>
</div> </div>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="12">
<div> <div>
<span class="name">总共下载次数</span> <span class="name">剩余付费下载次数</span>
<el-divider direction="vertical"></el-divider> <el-divider direction="vertical"></el-divider>
</div> </div>
<div> <div>
<span class="value">{{ hdata.all_hits_count}}</span> <span class="value">{{$store.state.userinfo.download_times }}</span>
<el-divider direction="vertical"></el-divider> <el-divider direction="vertical"></el-divider>
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
</el-col> </el-col>
</el-row> </el-row>
</el-header> </el-header>

@ -62,7 +62,8 @@
</p> </p>
</div> </div>
<div id="actions" class="actions" v-if="agent !==''"> <div id="actions" class="actions" v-if="agent !==''"
style="margin-top: 20%;margin-bottom: 20%">
<el-button type="info" round v-if="agent === 'wxandroid' || agent === 'wxapple'"> <el-button type="info" round v-if="agent === 'wxandroid' || agent === 'wxapple'">
不支持在微信内下载 不支持在微信内下载
@ -383,6 +384,15 @@
} }
window.location.href = this.downloadurl; window.location.href = this.downloadurl;
} else if (res.code === 1009) {
this.isdownload = false;
document.title = res.msg;
this.wrong = true;
this.msg = res.msg;
this.$message({
message: res.msg,
type: 'error',
});
} else { } else {
this.isdownload = false; this.isdownload = false;
this.$message({ this.$message({

@ -57,9 +57,12 @@
</el-row> </el-row>
</el-form-item> </el-form-item>
<el-form-item>
<div id="captcha" ref="captcha"></div>
</el-form-item>
<el-form-item> <el-form-item>
<el-button type="danger" @click="onSubmit">登录</el-button> <el-button type="danger" :disabled="login_disable" @click="onSubmit">登录</el-button>
</el-form-item> </el-form-item>
<el-form-item v-if="register_enable"> <el-form-item v-if="register_enable">
@ -77,7 +80,7 @@
<script> <script>
import {loginFun, set_auth_token} from "../restful"; import {loginFun, set_auth_token} from "../restful";
import {checkEmail, checkphone} from "../utils"; import {checkEmail, checkphone, geetest} from "../utils";
export default { export default {
name: "FirLogin", name: "FirLogin",
@ -94,7 +97,7 @@
rutitle: '', rutitle: '',
rctitle: '', rctitle: '',
register_enable: false, register_enable: false,
login_disable: false,
} }
}, },
methods: { methods: {
@ -150,6 +153,35 @@
if (password.length > 6) { if (password.length > 6) {
let params = {
"username": email,
"password": password,
"authcode": authcode,
"cptch_key": this.cptch.cptch_key,
"login_type": login_type,
};
this.login_disable = true;
if (this.cptch.geetest) {
geetest(this, params, (n_params) => {
this.do_login(n_params);
})
} else {
this.do_login(params)
}
} else {
this.$message({
message: '密码长度过短',
type: 'warning'
});
}
} else {
this.$message({
message: '验证码有误',
type: 'warning'
});
}
},
do_login(params) {
loginFun(data => { loginFun(data => {
if (data.code === 1000) { if (data.code === 1000) {
this.$message({ this.$message({
@ -170,30 +202,11 @@
}); });
this.get_auth_code(); this.get_auth_code();
} }
this.login_disable = false;
}, { }, {
"methods": "POST", "methods": "POST",
"data": { "data": params
"username": email,
"password": password,
"authcode": authcode,
"cptch_key": this.cptch.cptch_key,
"login_type": login_type,
}
});
} else {
this.$message({
message: '密码长度过短',
type: 'warning'
}); });
}
} else {
this.$message({
message: '验证码有误',
type: 'warning'
});
}
}, },
onRegister() { onRegister() {
this.$router.push({name: 'FirRegist'}) this.$router.push({name: 'FirRegist'})
@ -299,5 +312,4 @@
.el-header div:after { .el-header div:after {
margin: 0 -50% 0 0 margin: 0 -50% 0 0
} }
</style> </style>

@ -67,7 +67,11 @@
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="danger" @click="onRegist">注册</el-button> <div id="captcha" ref="captcha"></div>
</el-form-item>
<el-form-item>
<el-button type="danger" @click="onRegist" :disabled="register_disable">注册</el-button>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
@ -84,7 +88,7 @@
<script> <script>
import {getAuthTokenFun, registerFun} from "../restful"; import {getAuthTokenFun, registerFun} from "../restful";
import {checkEmail, checkphone} from "../utils"; import {checkEmail, checkphone, geetest} from "../utils";
export default { export default {
name: "FirRegist", name: "FirRegist",
@ -106,6 +110,7 @@
allow_r: false, allow_r: false,
allow_ways: {}, allow_ways: {},
rutitle: '', rutitle: '',
register_disable: false,
} }
}, },
methods: { methods: {
@ -147,6 +152,23 @@
"data": {} "data": {}
}); });
}, },
do_get_auth_token(params) {
getAuthTokenFun(data => {
if (data.code === 1000) {
this.$notify({
title: '验证码',
message: '您正在进行注册,验证码已经发送您',
type: 'success'
});
this.form.auth_token = data.data.auth_token;
} else {
this.$message({
message: data.msg,
type: 'error'
});
}
}, {"methods": 'POST', 'data': params})
},
getphonecode() { getphonecode() {
let act = 'up'; let act = 'up';
if (!this.docheck()) { if (!this.docheck()) {
@ -169,22 +191,14 @@
"cptch_key": this.cptch.cptch_key, "cptch_key": this.cptch.cptch_key,
"icode": this.form.icode, "icode": this.form.icode,
}; };
getAuthTokenFun(data => { let params = {'act': act, 'target': this.form.email, 'ext': picode};
if (data.code === 1000) { if (this.cptch.geetest) {
this.$notify({ geetest(this, params, (n_params) => {
title: '验证码', this.do_get_auth_token(n_params);
message: '您正在进行注册,验证码已经发送您', })
type: 'success'
});
this.form.auth_token = data.data.auth_token;
} else { } else {
this.$message({ this.do_get_auth_token(params);
message: data.msg,
type: 'error'
});
} }
}, {"methods": 'POST', 'data': {'act': act, 'target': this.form.email, 'ext': picode}})
} else { } else {
this.$message({ this.$message({
message: '图片验证码有误', message: '图片验证码有误',
@ -234,20 +248,7 @@
} }
return 1 return 1
}, },
onRegist() { do_register(params) {
let email = this.form.email;
let password = this.form.password;
let password2 = this.form.password2;
if (!this.docheck()) {
return
}
let authcode = this.form.authcode;
let cptch_flag = authcode.length === this.cptch.length;
if (this.cptch.cptch_key === '' || !this.cptch.cptch_key) {
cptch_flag = true
}
if (cptch_flag) {
if (password === password2 && password.length >= 6) {
registerFun(data => { registerFun(data => {
if (data.code === 1000) { if (data.code === 1000) {
this.$message({ this.$message({
@ -264,14 +265,40 @@
} }
}, { }, {
"methods": "POST", "methods": "POST",
"data": { "data": params
});
this.register_disable = false;
},
onRegist() {
let email = this.form.email;
let password = this.form.password;
let password2 = this.form.password2;
if (!this.docheck()) {
return
}
let authcode = this.form.authcode;
let cptch_flag = authcode.length === this.cptch.length;
if (this.cptch.cptch_key === '' || !this.cptch.cptch_key) {
cptch_flag = true
}
if (cptch_flag) {
if (password === password2 && password.length >= 6) {
let params = {
"username": email, "username": email,
"password": password, "password": password,
"password2": password2, "password2": password2,
"auth_token": this.form.auth_token, "auth_token": this.form.auth_token,
"auth_key": this.form.seicode "auth_key": this.form.seicode
};
this.register_disable = true;
if (this.cptch.geetest) {
geetest(this, params, (n_params) => {
this.do_register(n_params);
})
} else {
this.do_register(params);
} }
});
} else { } else {
this.$message({ this.$message({
message: '密码不一致或者密码长度小于6', message: '密码不一致或者密码长度小于6',

@ -83,7 +83,8 @@
</p> </p>
</div> </div>
<div id="actions" class="actions" v-if="agent !==''"> <div id="actions" class="actions" v-if="agent !==''"
style="margin-top: 20%;margin-bottom: 20%">
<button type="info" round v-if="agent === 'wxandroid' || agent === 'wxapple'"> <button type="info" round v-if="agent === 'wxandroid' || agent === 'wxapple'">
不支持在微信内下载 不支持在微信内下载
@ -233,8 +234,8 @@
<div v-if='iserror' class="main"> <div v-if='iserror' class="main">
<div class="error-container"> <div class="error-container">
<h1>404 - Not Found</h1> <h1>{{ error_msg.head }}</h1>
<header>您访问的 应用 不存在 <header>{{ error_msg.body }}
</header> </header>
</div> </div>
</div> </div>
@ -287,6 +288,7 @@
downloadurl: "", downloadurl: "",
isdownload: false, isdownload: false,
iserror: false, iserror: false,
error_msg: {head: '404 - Not Found', body: '您访问的 应用 不存在'},
udid: "", udid: "",
wxeasytypeflag: false, wxeasytypeflag: false,
timer: '', timer: '',
@ -376,6 +378,11 @@
} }
window.location.href = this.downloadurl; window.location.href = this.downloadurl;
} else if (res.code === 1009) {
this.isdownload = false;
document.title = res.msg;
this.wrong = true;
this.msg = res.msg;
} else { } else {
this.isdownload = false; this.isdownload = false;
alert("密码错误,或者下载链接失效"); alert("密码错误,或者下载链接失效");
@ -534,6 +541,12 @@
} }
} else { } else {
this.iserror = true; this.iserror = true;
if (data.code === 1009) {
this.error_msg.head = '温馨提示';
}
if (data.code === 1004) {
this.error_msg.head = '403 - Forbidden';
}
if (data.msg) { if (data.msg) {
document.title = data.msg; document.title = data.msg;
} }

@ -57,6 +57,9 @@ function getData(url, params = {}, callBack) {
case 408: case 408:
error.message = '请求超时(408)'; error.message = '请求超时(408)';
break; break;
case 429:
error.message = '您的 IP 访问频繁,请稍后再次尝试';
break;
case 500: case 500:
error.message = '服务器错误(500)'; error.message = '服务器错误(500)';
break; break;

@ -63,6 +63,9 @@ function ErrorMsg(error) {
case 408: case 408:
error.message = '请求超时(408)'; error.message = '请求超时(408)';
break; break;
case 429:
error.message = '您的 IP 访问频繁,请稍后再次尝试';
break;
case 500: case 500:
error.message = '服务器错误(500)'; error.message = '服务器错误(500)';
break; break;
@ -82,7 +85,7 @@ function ErrorMsg(error) {
error.message = 'HTTP版本不受支持(505)'; error.message = 'HTTP版本不受支持(505)';
break; break;
default: default:
error.message = `连接出错(${error.response.status})!`; error.message = `连接出错(${error.response.status}, ${JSON.stringify(error.response.data)})!`;
} }
} else { } else {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console

@ -171,7 +171,7 @@ export function uploadaliyunoss(file, certinfo, app, successcallback, processcal
} }
import {uploadstorage} from '../restful' import {loginFun, uploadstorage} from '../restful'
export function uploadlocalstorage(file, certinfo, app, successcallback, processcallback) { export function uploadlocalstorage(file, certinfo, app, successcallback, processcallback) {
uploadstorage(certinfo, file, successcallback, processcallback) uploadstorage(certinfo, file, successcallback, processcallback)
@ -289,3 +289,41 @@ export function ImgToBase64(url, callback) {
return callback ? callback(dataURL) : null; return callback ? callback(dataURL) : null;
}; };
} }
export function geetest(self, params, callback) {
loginFun(res => {
if (res.code === 1000) {
let data = res.data;
// eslint-disable-next-line no-undef
initGeetest({
gt: data.gt,
challenge: data.challenge,
new_captcha: data.new_captcha, // 用于宕机时表示是新验证码的宕机
offline: !data.success, // 表示用户后台检测极验服务器是否宕机,一般不需要关注
product: "float", // 产品形式,包括:float,popup
width: "100%"
}, (captchaObj) => {
self.$refs.captcha.innerHTML = '';
captchaObj.appendTo("#captcha");
captchaObj.onReady(() => {
//your code
}).onSuccess(() => {
params.geetest = captchaObj.getValidate();
callback(params);
}).onError(() => {
captchaObj.destroy();
});
});
} else {
self.$message({
message: res.msg,
type: 'error'
});
}
}, {
"methods": "PUT",
"data": {user_id: self.form.email}
});
}

@ -0,0 +1,18 @@
# Generated by Django 3.0.3 on 2021-03-24 17:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0023_auto_20210316_1602'),
]
operations = [
migrations.AlterField(
model_name='userinfo',
name='download_times',
field=models.PositiveIntegerField(default=0, verbose_name='可用下载次数,需要用户充值'),
),
]

@ -39,7 +39,7 @@ class UserInfo(AbstractUser):
memo = models.TextField('备注', blank=True, null=True, default=None, ) memo = models.TextField('备注', blank=True, null=True, default=None, )
date_joined = models.DateTimeField(auto_now_add=True, verbose_name="注册时间") date_joined = models.DateTimeField(auto_now_add=True, verbose_name="注册时间")
download_times = models.IntegerField(default=100, verbose_name="可用下载次数") download_times = models.PositiveIntegerField(default=0, verbose_name="可用下载次数,需要用户充值")
all_download_times = models.BigIntegerField(default=0, verbose_name="总共下载次数") all_download_times = models.BigIntegerField(default=0, verbose_name="总共下载次数")
domain_name = models.CharField(verbose_name="下载页面域名", blank=True, null=True, max_length=64) domain_name = models.CharField(verbose_name="下载页面域名", blank=True, null=True, max_length=64)
history_release_limit = models.IntegerField(default=10, verbose_name="app 历史记录版本", blank=True, null=True) history_release_limit = models.IntegerField(default=10, verbose_name="app 历史记录版本", blank=True, null=True)

@ -6,11 +6,12 @@
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from django_apscheduler.jobstores import DjangoJobStore, register_events, register_job from django_apscheduler.jobstores import DjangoJobStore, register_events, register_job
from fir_ser.settings import SYNC_CACHE_TO_DATABASE from fir_ser.settings import SYNC_CACHE_TO_DATABASE, GEETEST_CYCLE_TIME
from api.utils.crontab.ctasks import sync_download_times, auto_clean_upload_tmp_file, auto_delete_job_log, \ from api.utils.crontab.ctasks import sync_download_times, auto_clean_upload_tmp_file, auto_delete_job_log, \
auto_delete_tmp_file, auto_check_ios_developer_active auto_delete_tmp_file, auto_check_ios_developer_active
import logging import logging
from api.utils.storage.storage import get_local_storage from api.utils.storage.storage import get_local_storage
from api.utils.geetest.geetest_utils import check_bypass_status
logger = logging.getLogger(__file__) logger = logging.getLogger(__file__)
@ -42,6 +43,11 @@ try:
sync_download_times() sync_download_times()
@register_job(scheduler, "interval", seconds=GEETEST_CYCLE_TIME)
def check_bypass_status_job():
check_bypass_status()
@register_job(scheduler, "interval", seconds=SYNC_CACHE_TO_DATABASE.get("auto_clean_tmp_file_times")) @register_job(scheduler, "interval", seconds=SYNC_CACHE_TO_DATABASE.get("auto_clean_tmp_file_times"))
def auto_clean_upload_tmp_file_job(): def auto_clean_upload_tmp_file_job():
auto_clean_upload_tmp_file() auto_clean_upload_tmp_file()
@ -62,7 +68,9 @@ try:
register_events(scheduler) register_events(scheduler)
scheduler.start() scheduler.start()
get_local_storage(True) # 启动服务的时候,同时执行下面操作
get_local_storage(clean_cache=True)
check_bypass_status()
except Exception as e: except Exception as e:
logger.error("scheduler failed,so shutdown it Exception:%s" % (e)) logger.error("scheduler failed,so shutdown it Exception:%s" % (e))

@ -0,0 +1,149 @@
import random
import json
import requests
import hmac
import hashlib
from .geetest_lib_result import GeetestLibResult
# sdk lib包,核心逻辑。
class GeetestLib:
IS_DEBUG = False # 调试开关,是否输出调试日志
API_URL = "http://api.geetest.com"
REGISTER_URL = "/register.php"
VALIDATE_URL = "/validate.php"
JSON_FORMAT = "1"
NEW_CAPTCHA = True
HTTP_TIMEOUT_DEFAULT = 5 # 单位:秒
VERSION = "python-flask:3.1.1"
GEETEST_CHALLENGE = "geetest_challenge" # 极验二次验证表单传参字段 chllenge
GEETEST_VALIDATE = "geetest_validate" # 极验二次验证表单传参字段 validate
GEETEST_SECCODE = "geetest_seccode" # 极验二次验证表单传参字段 seccode
def __init__(self, geetest_id, geetest_key):
self.geetest_id = geetest_id
self.geetest_key = geetest_key
self.libResult = GeetestLibResult()
def gtlog(self, message):
if self.IS_DEBUG:
print("gtlog: " + message)
# 验证初始化
def register(self, digestmod, param_dict):
self.gtlog("register(): 开始验证初始化, digestmod={0}.".format(digestmod))
origin_challenge = self.request_register(param_dict)
self.build_register_result(origin_challenge, digestmod)
self.gtlog("register(): 验证初始化, lib包返回信息={0}.".format(self.libResult))
return self.libResult
def request_register(self, param_dict):
param_dict.update({"gt": self.geetest_id, "sdk": self.VERSION, "json_format": self.JSON_FORMAT})
register_url = self.API_URL + self.REGISTER_URL
self.gtlog("requestRegister(): 验证初始化, 向极验发送请求, url={0}, params={1}.".format(register_url, param_dict))
try:
res = requests.get(register_url, params=param_dict, timeout=self.HTTP_TIMEOUT_DEFAULT)
res_body = res.text if res.status_code == requests.codes.ok else ""
self.gtlog("requestRegister(): 验证初始化, 与极验网络交互正常, 返回码={0}, 返回body={1}.".format(res.status_code, res_body))
res_dict = json.loads(res_body)
origin_challenge = res_dict["challenge"]
except Exception as e:
self.gtlog("requestRegister(): 验证初始化, 请求异常,后续流程走宕机模式, " + repr(e))
origin_challenge = ""
return origin_challenge
def local_init(self):
self.build_register_result("", "")
self.gtlog("local_init(): bypass当前状态为fail,后续流程将进入宕机模式, " + self.libResult.data)
return self.libResult
# 构建验证初始化返回数据
def build_register_result(self, origin_challenge, digestmod):
# origin_challenge为空或者值为0代表失败
if not origin_challenge or origin_challenge == "0":
# 本地随机生成32位字符串
challenge = "".join(random.sample('abcdefghijklmnopqrstuvwxyz0123456789', 32))
data = json.dumps(
{"success": 0, "gt": self.geetest_id, "challenge": challenge, "new_captcha": self.NEW_CAPTCHA})
self.libResult.set_all(0, data, "bypass当前状态为fail,后续流程走宕机模式")
else:
if digestmod == "md5":
challenge = self.md5_encode(origin_challenge + self.geetest_key)
elif digestmod == "sha256":
challenge = self.sha256_endode(origin_challenge + self.geetest_key)
elif digestmod == "hmac-sha256":
challenge = self.hmac_sha256_endode(origin_challenge, self.geetest_key)
else:
challenge = self.md5_encode(origin_challenge + self.geetest_key)
data = json.dumps(
{"success": 1, "gt": self.geetest_id, "challenge": challenge, "new_captcha": self.NEW_CAPTCHA})
self.libResult.set_all(1, data, "")
# 正常流程下(即验证初始化成功),二次验证
def successValidate(self, challenge, validate, seccode, param_dict={}):
self.gtlog(
"successValidate(): 开始二次验证 正常模式, challenge={0}, validate={1}, seccode={2}.".format(challenge, validate,
seccode))
if not self.check_param(challenge, validate, seccode):
self.libResult.set_all(0, "", "正常模式,本地校验,参数challenge、validate、seccode不可为空")
else:
response_seccode = self.requestValidate(challenge, validate, seccode, param_dict)
if not response_seccode:
self.libResult.set_all(0, "", "请求极验validate接口失败")
elif response_seccode == "false":
self.libResult.set_all(0, "", "极验二次验证不通过")
else:
self.libResult.set_all(1, "", "")
self.gtlog("successValidate(): 二次验证 正常模式, lib包返回信息={0}.".format(self.libResult))
return self.libResult
# 异常流程下(即验证初始化失败,宕机模式),二次验证
# 注意:由于是宕机模式,初衷是保证验证业务不会中断正常业务,所以此处只作简单的参数校验,可自行设计逻辑。
def failValidate(self, challenge, validate, seccode):
self.gtlog(
"failValidate(): 开始二次验证 宕机模式, challenge={0}, validate={1}, seccode={2}.".format(challenge, validate,
seccode))
if not self.check_param(challenge, validate, seccode):
self.libResult.set_all(0, "", "宕机模式,本地校验,参数challenge、validate、seccode不可为空.")
else:
self.libResult.set_all(1, "", "")
self.gtlog("failValidate(): 二次验证 宕机模式, lib包返回信息={0}.".format(self.libResult))
return self.libResult
# 向极验发送二次验证的请求,POST方式
def requestValidate(self, challenge, validate, seccode, param_dict):
param_dict.update(
{"seccode": seccode, "json_format": self.JSON_FORMAT, "challenge": challenge, "sdk": self.VERSION,
"captchaid": self.geetest_id})
validate_url = self.API_URL + self.VALIDATE_URL
self.gtlog("requestValidate(): 二次验证 正常模式, 向极验发送请求, url={0}, params={1}.".format(validate_url, param_dict))
try:
res = requests.post(validate_url, data=param_dict, timeout=self.HTTP_TIMEOUT_DEFAULT)
res_body = res.text if res.status_code == requests.codes.ok else ""
self.gtlog(
"requestValidate(): 二次验证 正常模式, 与极验网络交互正常, 返回码={0}, 返回body={1}.".format(res.status_code, res_body))
res_dict = json.loads(res_body)
seccode = res_dict["seccode"]
except Exception as e:
self.gtlog("requestValidate(): 二次验证 正常模式, 请求异常, " + repr(e))
seccode = ""
return seccode
# 校验二次验证的三个参数,校验通过返回true,校验失败返回false
def check_param(self, challenge, validate, seccode):
return not (
challenge is None or challenge.isspace() or validate is None or validate.isspace() or seccode is None or seccode.isspace())
def md5_encode(self, value):
md5 = hashlib.md5()
md5.update(value.encode("utf-8"))
return md5.hexdigest()
def sha256_endode(self, value):
sha256 = hashlib.sha256()
sha256.update(value.encode("utf-8"))
return sha256.hexdigest()
def hmac_sha256_endode(self, value, key):
return hmac.new(key.encode("utf-8"), value.encode("utf-8"), digestmod=hashlib.sha256).hexdigest()

@ -0,0 +1,15 @@
# sdk lib包的返回结果信息。
class GeetestLibResult:
def __init__(self):
self.status = 0 # 成功失败的标识码,1表示成功,0表示失败
self.data = '' # 返回数据,json格式
self.msg = '' # 备注信息,如异常信息等
def set_all(self, status, data, msg):
self.status = status
self.data = data
self.msg = msg
def __str__(self):
return "GeetestLibResult{{status={0}, data={1}, msg={2}}}".format(self.status, self.data, self.msg)

@ -0,0 +1,82 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# project: 3月
# author: NinEveN
# date: 2021/3/25
import requests
import json
import logging
from django.core.cache import cache as redis_connect
from fir_ser.settings import GEETEST_ID, GEETEST_KEY, GEETEST_BYPASS_URL, \
GEETEST_BYPASS_STATUS_KEY
from api.utils.geetest.geetest_lib import GeetestLib
logger = logging.getLogger(__file__)
# 发送bypass请求,获取bypass状态并进行缓存(如何缓存可根据自身情况合理选择,这里是使用redis进行缓存)
def check_bypass_status():
response = ""
params = {"gt": GEETEST_ID}
try:
response = requests.get(url=GEETEST_BYPASS_URL, params=params)
except Exception as e:
logger.error("check_bypass_status failed Exception:%s" % e)
if response and response.status_code == 200:
logger.debug("check_bypass_status success %s" % response.content)
bypass_status_str = response.content.decode("utf-8")
bypass_status = json.loads(bypass_status_str).get("status")
redis_connect.set(GEETEST_BYPASS_STATUS_KEY, bypass_status)
else:
bypass_status = "fail"
redis_connect.set(GEETEST_BYPASS_STATUS_KEY, bypass_status)
logger.debug("bypass状态已经获取并存入redis,当前状态为 {}".format(bypass_status))
# 从缓存中取出当前缓存的bypass状态(success/fail)
def get_bypass_cache():
bypass_status_cache = redis_connect.get(GEETEST_BYPASS_STATUS_KEY)
if not bypass_status_cache:
bypass_status_cache = 'fail'
return bypass_status_cache
# 验证初始化接口,GET请求
def first_register(user_id, ip_address):
# 必传参数
# digestmod 此版本sdk可支持md5、sha256、hmac-sha256,md5之外的算法需特殊配置的账号,联系极验客服
# 自定义参数,可选择添加
# user_id 客户端用户的唯一标识,确定用户的唯一性;作用于提供进阶数据分析服务,可在register和validate接口传入,不传入也不影响验证服务的使用;若担心用户信息风险,可作预处理(如哈希处理)再提供到极验
# client_type 客户端类型,web:电脑上的浏览器;h5:手机上的浏览器,包括移动应用内完全内置的web_view;native:通过原生sdk植入app应用的方式;unknown:未知
# ip_address 客户端请求sdk服务器的ip地址
bypass_status = get_bypass_cache()
gt_lib = GeetestLib(GEETEST_ID, GEETEST_KEY)
digestmod = "md5"
param_dict = {"digestmod": digestmod, "user_id": user_id, "client_type": "web", "ip_address": ip_address}
if bypass_status == "success":
result = gt_lib.register(digestmod, param_dict)
else:
result = gt_lib.local_init()
# 注意,不要更改返回的结构和值类型
return json.loads(result.data)
# 二次验证接口,POST请求
def second_validate(rdata):
challenge = rdata.get(GeetestLib.GEETEST_CHALLENGE, None)
validate = rdata.get(GeetestLib.GEETEST_VALIDATE, None)
seccode = rdata.get(GeetestLib.GEETEST_SECCODE, None)
bypass_status = get_bypass_cache()
gt_lib = GeetestLib(GEETEST_ID, GEETEST_KEY)
if bypass_status == "success":
result = gt_lib.successValidate(challenge, validate, seccode)
else:
result = gt_lib.failValidate(challenge, validate, seccode)
# 注意,不要更改返回的结构和值类型
if result.status == 1:
response = {"result": "success", "version": GeetestLib.VERSION}
else:
response = {"result": "fail", "version": GeetestLib.VERSION, "msg": result.msg}
logger.info("geetest second_validate info %s" % response)
return response

@ -5,6 +5,7 @@ from api.utils.TokenManager import DownloadToken
from api.utils.app.supersignutils import get_redirect_server_domain from api.utils.app.supersignutils import get_redirect_server_domain
from api.utils.storage.storage import Storage from api.utils.storage.storage import Storage
from api.utils.utils import get_developer_udided from api.utils.utils import get_developer_udided
from api.utils.storage.caches import get_user_free_download_times
import os, json, logging import os, json, logging
logger = logging.getLogger(__file__) logger = logging.getLogger(__file__)
@ -44,7 +45,7 @@ class UserInfoSerializer(serializers.ModelSerializer):
model = models.UserInfo model = models.UserInfo
# fields="__all__" # fields="__all__"
fields = ["username", "uid", "mobile", "job", "email", "domain_name", "last_login", "first_name", fields = ["username", "uid", "mobile", "job", "email", "domain_name", "last_login", "first_name",
'head_img', 'storage_active', 'supersign_active'] 'head_img', 'storage_active', 'supersign_active', 'free_download_times', 'download_times']
head_img = serializers.SerializerMethodField() head_img = serializers.SerializerMethodField()
@ -52,6 +53,11 @@ class UserInfoSerializer(serializers.ModelSerializer):
storage = Storage(obj) storage = Storage(obj)
return storage.get_download_url(obj.head_img) return storage.get_download_url(obj.head_img)
free_download_times = serializers.SerializerMethodField()
def get_free_download_times(self, obj):
return get_user_free_download_times(obj.id)
class AppsSerializer(serializers.ModelSerializer): class AppsSerializer(serializers.ModelSerializer):
class Meta: class Meta:

@ -8,10 +8,12 @@ from django.core.cache import cache
from api.models import Apps, UserInfo, AppReleaseInfo, AppUDID, APPToDeveloper, APPSuperSignUsedInfo from api.models import Apps, UserInfo, AppReleaseInfo, AppUDID, APPToDeveloper, APPSuperSignUsedInfo
import time, os import time, os
from django.utils import timezone from django.utils import timezone
from fir_ser.settings import CACHE_KEY_TEMPLATE, SERVER_DOMAIN, SYNC_CACHE_TO_DATABASE, DEFAULT_MOBILEPROVISION from fir_ser.settings import CACHE_KEY_TEMPLATE, SERVER_DOMAIN, SYNC_CACHE_TO_DATABASE, DEFAULT_MOBILEPROVISION, \
USER_FREE_DOWNLOAD_TIMES
from api.utils.storage.storage import Storage, LocalStorage from api.utils.storage.storage import Storage, LocalStorage
from api.utils.utils import file_format_path from api.utils.utils import file_format_path
import logging import logging
from django.db.models import F
logger = logging.getLogger(__file__) logger = logging.getLogger(__file__)
@ -287,3 +289,66 @@ def set_default_app_wx_easy(user_obj, only_clean_cache=False):
app_obj.wxeasytype = True app_obj.wxeasytype = True
app_obj.save() app_obj.save()
del_cache_response_by_short(app_obj.app_id) del_cache_response_by_short(app_obj.app_id)
def enable_user_download_times_flag(user_id):
set_user_download_times_flag(user_id, 1)
def disable_user_download_times_flag(user_id):
set_user_download_times_flag(user_id, 0)
def check_user_can_download(user_id):
return set_user_download_times_flag(user_id, 2)
def set_user_download_times_flag(user_id, act):
user_can_download_key = "_".join(
[CACHE_KEY_TEMPLATE.get("user_can_download_key"), str(user_id)])
if act == 2:
result = cache.get(user_can_download_key)
if result is None:
return True
return result
return cache.set(user_can_download_key, act, 3600 * 24)
def get_user_free_download_times(user_id, act='get'):
now = timezone.now()
user_free_download_times_key = "_".join(
[CACHE_KEY_TEMPLATE.get("user_free_download_times_key"), str(now.year), str(now.month), str(now.day),
str(user_id)])
user_free_download_times = cache.get(user_free_download_times_key)
if user_free_download_times is not None:
if act == 'set':
return cache.incr(user_free_download_times_key, -1)
else:
return user_free_download_times
else:
cache.set(user_free_download_times_key, USER_FREE_DOWNLOAD_TIMES, 3600 * 24)
if act == 'set':
return cache.incr(user_free_download_times_key, -1)
else:
return USER_FREE_DOWNLOAD_TIMES
def consume_user_download_times(user_id, app_id):
with cache.lock("%s_%s" % ('consume_user_download_times', user_id)):
if get_user_free_download_times(user_id) > 0:
get_user_free_download_times(user_id, 'set')
else:
if not check_user_can_download(user_id):
return False
try:
UserInfo.objects.filter(pk=user_id).update(download_times=F('download_times') - 1)
except Exception as e:
logger.error("%s download_times less then 0. Exception:%s" % (user_id, e))
disable_user_download_times_flag(user_id)
del_cache_response_by_short(app_id)
return False
return True
def check_user_has_all_download_times(user_id):
return get_user_free_download_times(user_id) > 0 or check_user_can_download(user_id)

@ -0,0 +1,70 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# project: 3月
# author: NinEveN
# date: 2021/3/25
from rest_framework.throttling import SimpleRateThrottle
import hashlib
from rest_framework.exceptions import Throttled
class VisitShortThrottle(SimpleRateThrottle):
"""短连接用户访问频率限制1"""
scope = "ShortAccessUser1"
def get_cache_key(self, request, view):
return 'short_access_' + self.get_ident(request) + hashlib.md5(
request.META.get('HTTP_USER_AGENT', '').encode("utf-8")).hexdigest()
class InstallShortThrottle(SimpleRateThrottle):
"""短连接用户访问频率限制2"""
scope = "ShortAccessUser2"
def get_cache_key(self, request, view):
return 'short_access_' + self.get_ident(request)
class LoginUserThrottle(SimpleRateThrottle):
"""登录用户访问频率限制"""
scope = "LoginUser"
def get_cache_key(self, request, view):
if hasattr(request.user, 'uid'):
return request.user.uid
else:
self.get_ident(request)
class VisitRegister1Throttle(SimpleRateThrottle):
"""注册或者登陆限速1"""
scope = "RegisterUser1"
def get_cache_key(self, request, view):
return 'login_register_' + self.get_ident(request)
class VisitRegister2Throttle(SimpleRateThrottle):
"""注册或者登陆限速2"""
scope = "RegisterUser2"
def get_cache_key(self, request, view):
return 'login_register_' + self.get_ident(request)
class GetAuthC1Throttle(SimpleRateThrottle):
"""注册或者登陆限速1"""
scope = "GetAuthC1"
def get_cache_key(self, request, view):
return 'get_auth_' + self.get_ident(request)
class GetAuthC2Throttle(SimpleRateThrottle):
"""注册或者登陆限速2"""
scope = "GetAuthC2"
def get_cache_key(self, request, view):
return 'get_auth_' + self.get_ident(request)

@ -13,7 +13,7 @@ from api.utils.app.apputils import make_resigned
from api.utils.app.supersignutils import make_sign_udid_mobileconfig, get_post_udid_url, get_redirect_server_domain from api.utils.app.supersignutils import make_sign_udid_mobileconfig, get_post_udid_url, get_redirect_server_domain
from api.utils.storage.storage import Storage, get_local_storage from api.utils.storage.storage import Storage, get_local_storage
from api.utils.storage.caches import get_app_instance_by_cache, get_download_url_by_cache, set_app_download_by_cache, \ from api.utils.storage.caches import get_app_instance_by_cache, get_download_url_by_cache, set_app_download_by_cache, \
del_cache_response_by_short del_cache_response_by_short, consume_user_download_times, check_user_has_all_download_times
import os import os
from rest_framework_extensions.cache.decorators import cache_response from rest_framework_extensions.cache.decorators import cache_response
from api.utils.serializer import AppsShortSerializer from api.utils.serializer import AppsShortSerializer
@ -21,6 +21,7 @@ from api.models import Apps, AppReleaseInfo, APPToDeveloper, APPSuperSignUsedInf
from django.http import FileResponse from django.http import FileResponse
import logging import logging
from api.utils.utils import get_profile_full_path from api.utils.utils import get_profile_full_path
from api.utils.throttle import VisitShortThrottle, InstallShortThrottle
logger = logging.getLogger(__file__) logger = logging.getLogger(__file__)
@ -138,6 +139,7 @@ class DownloadView(APIView):
class ShortDownloadView(APIView): class ShortDownloadView(APIView):
throttle_classes = [VisitShortThrottle, InstallShortThrottle]
''' '''
根据下载短链接获取应用信息 根据下载短链接获取应用信息
''' '''
@ -152,13 +154,24 @@ class ShortDownloadView(APIView):
res.code = 1003 res.code = 1003
res.msg = "该应用不存在" res.msg = "该应用不存在"
return Response(res.dict) return Response(res.dict)
if udid:
del_cache_response_by_short(app_obj.app_id, udid=udid) if not check_user_has_all_download_times(app_obj.user_id_id):
res.code = 1009
res.msg = "可用下载额度不足,请联系开发者"
return Response(res.dict)
if not app_obj.isshow: if not app_obj.isshow:
res.code = 1004 res.code = 1004
res.msg = "您没有权限访问该应用" res.msg = "您没有权限访问该应用"
return Response(res.dict) return Response(res.dict)
if udid:
if not app_obj.issupersign:
res.code = 1002
res.msg = "参数有误"
return Response(res.dict)
del_cache_response_by_short(app_obj.app_id, udid=udid)
app_serializer = AppsShortSerializer(app_obj, context={"key": "ShortDownloadView", "release_id": release_id, app_serializer = AppsShortSerializer(app_obj, context={"key": "ShortDownloadView", "release_id": release_id,
"storage": Storage(app_obj.user_id)}) "storage": Storage(app_obj.user_id)})
res.data = app_serializer.data res.data = app_serializer.data
@ -183,6 +196,7 @@ class ShortDownloadView(APIView):
class InstallView(APIView): class InstallView(APIView):
throttle_classes = [VisitShortThrottle, InstallShortThrottle]
''' '''
安装操作前端通过token 认证认证成功之后返回 下载连接并且 让下载次数加一 安装操作前端通过token 认证认证成功之后返回 下载连接并且 让下载次数加一
''' '''
@ -220,12 +234,17 @@ class InstallView(APIView):
res.data = {"download_url": download_url, "extra_url": extra_url} res.data = {"download_url": download_url, "extra_url": extra_url}
if download_url != "" and "mobileconifg" not in download_url: if download_url != "" and "mobileconifg" not in download_url:
set_app_download_by_cache(app_id)
if request.META.get('HTTP_X_FORWARDED_FOR', None): if request.META.get('HTTP_X_FORWARDED_FOR', None):
ip = request.META['HTTP_X_FORWARDED_FOR'] ip = request.META['HTTP_X_FORWARDED_FOR']
else: else:
ip = request.META['REMOTE_ADDR'] ip = request.META['REMOTE_ADDR']
logger.info("remote ip %s short %s download_url %s app_obj %s" % (ip, short, download_url, app_obj)) logger.info("remote ip %s short %s download_url %s app_obj %s" % (ip, short, download_url, app_obj))
if not consume_user_download_times(app_obj.get("user_id"), app_id):
res.code = 1009
res.msg = "可用下载额度不足"
del res.data
return Response(res.dict)
set_app_download_by_cache(app_id)
return Response(res.dict) return Response(res.dict)
else: else:
res.code = 1004 res.code = 1004

@ -1,5 +1,5 @@
from django.contrib import auth from django.contrib import auth
from api.models import Token, UserInfo, Apps from api.models import Token, UserInfo
from rest_framework.response import Response from rest_framework.response import Response
from api.utils.serializer import UserInfoSerializer from api.utils.serializer import UserInfoSerializer
from django.core.cache import cache from django.core.cache import cache
@ -14,6 +14,8 @@ from api.utils.response import BaseResponse
from fir_ser.settings import CACHE_KEY_TEMPLATE, SERVER_DOMAIN, REGISTER, LOGIN from fir_ser.settings import CACHE_KEY_TEMPLATE, SERVER_DOMAIN, REGISTER, LOGIN
from api.utils.storage.caches import login_auth_failed, set_default_app_wx_easy from api.utils.storage.caches import login_auth_failed, set_default_app_wx_easy
import logging import logging
from api.utils.geetest.geetest_utils import first_register, second_validate
from api.utils.throttle import VisitRegister1Throttle, VisitRegister2Throttle, GetAuthC1Throttle, GetAuthC2Throttle
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -169,6 +171,8 @@ def CheckChangeUerinfo(target, act, key, user):
class LoginView(APIView): class LoginView(APIView):
throttle_classes = [VisitRegister1Throttle, VisitRegister2Throttle]
def generate_key(self): def generate_key(self):
return binascii.hexlify(os.urandom(32)).decode() return binascii.hexlify(os.urandom(32)).decode()
@ -181,6 +185,15 @@ class LoginView(APIView):
else: else:
is_valid = True is_valid = True
if is_valid: if is_valid:
if LOGIN.get("geetest"):
geetest = receive.get("geetest", None)
if geetest and second_validate(geetest).get("result", "") == "success":
pass
else:
response.code = 1001
response.msg = "geetest 验证有误"
return Response(response.dict)
login_type = receive.get("login_type", None) login_type = receive.get("login_type", None)
if login_auth_failed("get", username): if login_auth_failed("get", username):
password = receive.get("password") password = receive.get("password")
@ -231,13 +244,25 @@ class LoginView(APIView):
response.data = {} response.data = {}
if LOGIN.get("captcha"): if LOGIN.get("captcha"):
response.data = get_captcha() response.data = get_captcha()
if LOGIN.get("geetest"):
response.data['geetest'] = True
response.data['login_type'] = get_login_type() response.data['login_type'] = get_login_type()
allow_f = REGISTER.get("enable") allow_f = REGISTER.get("enable")
response.data['register_enable'] = allow_f response.data['register_enable'] = allow_f
return Response(response.dict) return Response(response.dict)
def put(self, request):
import hashlib
response = BaseResponse()
user_id = request.data.get('user_id', None)
sha = hashlib.sha1(user_id.encode("utf-8"))
response.data = first_register(sha.hexdigest(), request.META.get('REMOTE_ADDR'))
return Response(response.dict)
class RegistView(APIView): class RegistView(APIView):
throttle_classes = [VisitRegister1Throttle, VisitRegister2Throttle]
def generate_key(self): def generate_key(self):
return binascii.hexlify(os.urandom(32)).decode() return binascii.hexlify(os.urandom(32)).decode()
@ -261,6 +286,15 @@ class RegistView(APIView):
response.msg = "不允许该类型注册" response.msg = "不允许该类型注册"
return Response(response.dict) return Response(response.dict)
if REGISTER.get("geetest"):
geetest = request.data.get("geetest", None)
if geetest and second_validate(geetest).get("result", "") == "success":
pass
else:
response.code = 1018
response.msg = "geetest验证有误"
return Response(response.dict)
is_valid, target = is_valid_sender_code(act, receive.get("auth_token", None), receive.get("auth_key", None)) is_valid, target = is_valid_sender_code(act, receive.get("auth_token", None), receive.get("auth_key", None))
if is_valid and str(target) == str(username): if is_valid and str(target) == str(username):
if login_auth_failed("get", username): if login_auth_failed("get", username):
@ -302,6 +336,8 @@ class RegistView(APIView):
if allow_f: if allow_f:
if REGISTER.get("captcha"): if REGISTER.get("captcha"):
response.data = get_captcha() response.data = get_captcha()
if REGISTER.get("geetest"):
response.data['geetest'] = True
response.data['register_type'] = get_register_type() response.data['register_type'] = get_register_type()
response.data['enable'] = allow_f response.data['enable'] = allow_f
return Response(response.dict) return Response(response.dict)
@ -447,6 +483,7 @@ class UserInfoView(APIView):
class AuthorizationView(APIView): class AuthorizationView(APIView):
throttle_classes = [GetAuthC1Throttle, GetAuthC2Throttle]
def post(self, request): def post(self, request):
res = BaseResponse() res = BaseResponse()
@ -472,6 +509,15 @@ class AuthorizationView(APIView):
res.msg = "图片验证码有误" res.msg = "图片验证码有误"
return Response(res.dict) return Response(res.dict)
if REGISTER.get("geetest"):
geetest = request.data.get("geetest", None)
if geetest and second_validate(geetest).get("result", "") == "success":
pass
else:
res.code = 1018
res.msg = "geetest验证有误"
return Response(res.dict)
res = CheckRegisterUerinfo(target, act, 'register') res = CheckRegisterUerinfo(target, act, 'register')
return Response(res.dict) return Response(res.dict)
@ -479,6 +525,7 @@ class AuthorizationView(APIView):
class ChangeAuthorizationView(APIView): class ChangeAuthorizationView(APIView):
authentication_classes = [ExpiringTokenAuthentication, ] authentication_classes = [ExpiringTokenAuthentication, ]
throttle_classes = [GetAuthC1Throttle, GetAuthC2Throttle]
def post(self, request): def post(self, request):
res = BaseResponse() res = BaseResponse()

@ -116,7 +116,19 @@ AUTH_PASSWORD_VALIDATORS = [
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': ( 'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.JSONRenderer',
) ),
'DEFAULT_THROTTLE_CLASSES': [
'api.utils.throttle.LoginUserThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'ShortAccessUser1': '80/m',
'ShortAccessUser2': '1000/h',
'LoginUser': '200/m',
'RegisterUser1': '30/m',
'RegisterUser2': '300/h',
'GetAuthC1': '8/m',
'GetAuthC2': '30/h',
}
} }
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/ # https://docs.djangoproject.com/en/3.0/topics/i18n/
@ -156,7 +168,8 @@ CACHES = {
"CONNECTION_POOL_KWARGS": {"max_connections": 100}, "CONNECTION_POOL_KWARGS": {"max_connections": 100},
"PASSWORD": "", "PASSWORD": "",
"DECODE_RESPONSES": True "DECODE_RESPONSES": True
} },
"TIMEOUT": 60 * 15
}, },
} }
@ -168,10 +181,18 @@ REST_FRAMEWORK_EXTENSIONS = {
'DEFAULT_USE_CACHE': 'default', 'DEFAULT_USE_CACHE': 'default',
} }
# geetest 配置信息
GEETEST_ID = "d3f3cf73200a70bd3a0c32ea22e5003a"
GEETEST_KEY = "6f4ab6185c230a4b1f8430f28ebe4804"
GEETEST_CYCLE_TIME = 10
GEETEST_BYPASS_STATUS_KEY = "gt_server_bypass_status"
GEETEST_BYPASS_URL = "http://bypass.geetest.com/v1/bypass_status.php"
# 注册方式,如果启用sms或者email 需要配置 THIRD_PART_CONFIG.sender 信息 # 注册方式,如果启用sms或者email 需要配置 THIRD_PART_CONFIG.sender 信息
REGISTER = { REGISTER = {
"enable": True, "enable": True,
"captcha": True, # 是否开启注册字母验证码 "captcha": False, # 是否开启注册字母验证码
"geetest": True, # 是否开启geetest验证,如要开启请先配置geetest
"register_type": { "register_type": {
'sms': False, # 短信注册 'sms': False, # 短信注册
'email': True, # 邮件注册 'email': True, # 邮件注册
@ -182,6 +203,7 @@ REGISTER = {
# 信息修改也会使用该配置 # 信息修改也会使用该配置
LOGIN = { LOGIN = {
"captcha": False, # 是否开启登录字母验证码 "captcha": False, # 是否开启登录字母验证码
"geetest": True, # 是否开启geetest验证
"login_type": { "login_type": {
'sms': True, # 短信登录 'sms': True, # 短信登录
'email': True, # 邮件登录 'email': True, # 邮件登录
@ -200,7 +222,7 @@ THIRD_PART_CONFIG = {
# 正式环境需要填写正式的访问域名,如果配置cdn,可以填写cdn的域名,仅支持阿里云 cdn, # 正式环境需要填写正式的访问域名,如果配置cdn,可以填写cdn的域名,仅支持阿里云 cdn,
# 开启cdn之后,如果该域名和服务器域名不相同,需要设置阿里云cdn 缓存配置,自定义HTTP响应头 添加 Access-Control-Allow-Origin * 才可以 # 开启cdn之后,如果该域名和服务器域名不相同,需要设置阿里云cdn 缓存配置,自定义HTTP响应头 添加 Access-Control-Allow-Origin * 才可以
'is_https': True, 'is_https': True,
'download_auth_type': 0, # 0:不开启token 1:本地token 2:cdn 开启cdn,并且使用本地存储,使用阿里云cdn进行url鉴权, 'download_auth_type': 1, # 0:不开启token 1:本地token 2:cdn 开启cdn,并且使用本地存储,使用阿里云cdn进行url鉴权,
'cnd_auth_key': '', # 当cdn为阿里云并且 download_auth_type=2 的时候 生效,需要 开启阿里云OSS私有Bucket回源 'cnd_auth_key': '', # 当cdn为阿里云并且 download_auth_type=2 的时候 生效,需要 开启阿里云OSS私有Bucket回源
}, },
'active': True 'active': True
@ -289,6 +311,7 @@ THIRD_PART_CONFIG = {
} }
CACHE_KEY_TEMPLATE = { CACHE_KEY_TEMPLATE = {
'user_can_download_key': 'user_can_download',
'download_times_key': 'app_download_times', 'download_times_key': 'app_download_times',
'make_token_key': 'make_token', 'make_token_key': 'make_token',
'download_short_key': 'download_short', 'download_short_key': 'download_short',
@ -300,11 +323,13 @@ CACHE_KEY_TEMPLATE = {
'developer_auth_code_key': 'developer_auth_code', 'developer_auth_code_key': 'developer_auth_code',
'upload_file_tmp_name_key': 'upload_file_tmp_name', 'upload_file_tmp_name_key': 'upload_file_tmp_name',
'login_failed_try_times_key': 'login_failed_try_times', 'login_failed_try_times_key': 'login_failed_try_times',
'user_free_download_times_key': 'user_free_download_times',
'super_sign_failed_send_msg_times_key': 'super_sign_failed_send_msg_times' 'super_sign_failed_send_msg_times_key': 'super_sign_failed_send_msg_times'
} }
DATA_DOWNLOAD_KEY = "d_token" DATA_DOWNLOAD_KEY = "d_token"
FILE_UPLOAD_TMP_KEY = ".tmp" FILE_UPLOAD_TMP_KEY = ".tmp"
USER_FREE_DOWNLOAD_TIMES = 10
SYNC_CACHE_TO_DATABASE = { SYNC_CACHE_TO_DATABASE = {
'download_times': 10, # 下载次数同步时间 'download_times': 10, # 下载次数同步时间

Loading…
Cancel
Save