存储扩容功能

dependabot/npm_and_yarn/fir_admin/eventsource-1.1.1
nineven 3 years ago
parent 5f967a9b52
commit 3ad5e03504
  1. 8
      fir_client/src/components/apps/FirApps.vue
  2. 163
      fir_client/src/components/user/FirUserStorage.vue
  3. 12
      fir_client/src/restful/index.js
  4. 1
      fir_ser/api/migrations/0001_initial.py
  5. 50
      fir_ser/api/migrations/0002_auto_20220215_2129.py
  6. 23
      fir_ser/api/migrations/0006_auto_20220513_0843.py
  7. 25
      fir_ser/api/models.py
  8. 3
      fir_ser/api/urls.py
  9. 27
      fir_ser/api/utils/modelutils.py
  10. 29
      fir_ser/api/utils/serializer.py
  11. 2
      fir_ser/api/utils/utils.py
  12. 5
      fir_ser/api/views/order.py
  13. 64
      fir_ser/api/views/storage.py
  14. 8
      fir_ser/common/core/sysconfig.py
  15. 44
      fir_ser/common/utils/caches.py
  16. 3
      fir_ser/common/utils/download.py
  17. 5
      fir_ser/config.py

@ -445,8 +445,8 @@
<div> <div>
<el-tooltip placement="top"> <el-tooltip placement="top">
<div slot="content"> <div slot="content">
1.下载应用100M消耗一次下载次数<br> 1.下载应用 {{ times_info.every_size|diskSize }} 消耗 {{ times_info.base_times / 100 }} 下载次数<br>
2.超级签下载下载消耗次数翻倍<br> 2.私有存储模式下载应用 {{ times_info.every_size|diskSize }} 消耗 {{ times_info.private_times / 100 }} 下载次数<br>
</div> </div>
<span class="name">购买次数</span> <span class="name">购买次数</span>
</el-tooltip> </el-tooltip>
@ -1167,6 +1167,10 @@ export default {
getDelappTitle() { getDelappTitle() {
return `删除应用 ${this.delapp.name}` return `删除应用 ${this.delapp.name}`
}, },
times_info() {
let times_info = this.$store.state.userinfo.times_info
return times_info ? times_info : {}
}
}, },
filters: { filters: {
diskSize, diskSize,

@ -191,6 +191,37 @@
<el-button @click="shareVisible=false">取消</el-button> <el-button @click="shareVisible=false">取消</el-button>
</el-form> </el-form>
</el-dialog> </el-dialog>
<el-dialog :close-on-click-modal="false" :destroy-on-close="true" :visible.sync="exchangeVisible"
style="text-align:center" title="存储扩容" width="800px">
<el-tag style="margin-bottom: 20px" type="warning">请勿多次购买多次购买将按照最大的扩容容量进行计算</el-tag>
<el-form label-width="120px">
<el-form-item label="购买时间" style="width: 100%">
<el-radio-group v-model="exchange_prices_key" size="small">
<el-radio v-for="pay in exchange_prices.pay_times" :key="pay.name" :label="pay.name" border>
{{ pay.title }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="扩容存储大小" style="width: 100%;text-align: left">
<el-row :gutter="24">
<el-col :span="10">
<el-input-number v-model="exchange_number" :max="999999999" :min="10" :step="10">
</el-input-number>
</el-col>
<el-col :span="13">
<el-tag style="margin-left: 30px">扩容存储空间大小 {{ exchange_number }} GB</el-tag>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="所消耗下载次数" style="width: 100%;text-align: left">
<el-tag style="margin-left: 30px"> {{ exchangetimes }}</el-tag>
</el-form-item>
<el-button @click="sureExchangeFun(exchange_number,exchange_prices_key)">确定扩容</el-button>
<el-button @click="exchangeVisible=false">取消</el-button>
</el-form>
</el-dialog>
<el-tabs v-model="activeName" tab-position="top" type="border-card" @tab-click="handleClick"> <el-tabs v-model="activeName" tab-position="top" type="border-card" @tab-click="handleClick">
<el-tab-pane label="存储选择" name="change"> <el-tab-pane label="存储选择" name="change">
@ -218,7 +249,9 @@
trigger="hover"> trigger="hover">
<div> <div>
<el-link :underline="false">存储最大容量空间 {{ diskSize(storageinfo.max_storage_capacity) }}</el-link> <el-link :underline="false">存储最大容量空间 {{ diskSize(storageinfo.max_storage_capacity) }}</el-link>
<br>
<el-link :underline="false">已经使用容量 {{ diskSize(storageinfo.used_number) }}</el-link> <el-link :underline="false">已经使用容量 {{ diskSize(storageinfo.used_number) }}</el-link>
<br>
<el-link :underline="false">当前可用容量 <el-link :underline="false">当前可用容量
{{ diskSize(storageinfo.max_storage_capacity - storageinfo.used_number) }} {{ diskSize(storageinfo.max_storage_capacity - storageinfo.used_number) }}
</el-link> </el-link>
@ -265,6 +298,11 @@
<el-form-item label="存储最大容量" label-width="110px" style="text-align: left"> <el-form-item label="存储最大容量" label-width="110px" style="text-align: left">
<el-tag>存储最大容量空间 {{ diskSize(storageinfo.max_storage_capacity) }}</el-tag> <el-tag>存储最大容量空间 {{ diskSize(storageinfo.max_storage_capacity) }}</el-tag>
<span v-if="percentage>=90 && use_storage_id===-1">
<el-tag style="margin: 0 10px" type="danger">容量即将不足请立即扩容</el-tag> <el-button size="small"
@click="exchangeFun">扩容</el-button>
</span>
</el-form-item> </el-form-item>
<el-form-item label="已经使用容量" label-width="110px" style="text-align: left"> <el-form-item label="已经使用容量" label-width="110px" style="text-align: left">
<el-tag>存储已经使用容量空间 {{ diskSize(storageinfo.used_number) }} <el-tag>存储已经使用容量空间 {{ diskSize(storageinfo.used_number) }}
@ -663,6 +701,68 @@
</el-table> </el-table>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="默认存储扩容记录" name="exchange">
<div style="text-align: left;margin-bottom: 15px">
<el-tag>该记录为默认存储扩容记录私有存储无需扩容</el-tag>
</div>
<el-table
v-loading="loading"
:data="storage_exchange_lists"
border
stripe
style="width: 100%">
<el-table-column
align="center"
label="兑换存储大小"
prop="storage_size"
width="140">
<template slot-scope="scope">
{{ diskSize(scope.row.storage_size) }}
</template>
</el-table-column>
<el-table-column
align="center"
label="消耗下载次数"
prop="download_times"
width="140">
<template slot-scope="scope">
{{ parseInt(scope.row.download_times / 100) }}
</template>
</el-table-column>
<el-table-column
align="center"
label="扩容日期"
prop="created_time">
<template slot-scope="scope">
{{ format_time(scope.row.created_time) }}
</template>
</el-table-column>
<el-table-column
align="center"
label="失效日期"
prop="expires_time">
<template slot-scope="scope">
{{ format_time(scope.row.expires_time) }}
</template>
</el-table-column>
<el-table-column
align="center"
label="状态"
prop="expires_time"
width="160">
<template slot-scope="scope">
<el-popover :content="expire_content(scope.row.is_expired)" placement="top-start" trigger="hover">
<div slot="reference">
<el-tag v-if="scope.row.is_expired > 0">生效中</el-tag>
<el-tag v-else type="warning">已失效</el-tag>
</div>
</el-popover>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="存储设置" name="setting" style="text-align: center"> <el-tab-pane label="存储设置" name="setting" style="text-align: center">
<div v-if="migrate_progress && migrate_progress.s_time" style="text-align: left"> <div v-if="migrate_progress && migrate_progress.s_time" style="text-align: left">
<el-card> <el-card>
@ -770,7 +870,7 @@
</template> </template>
<script> <script>
import {cleanStorageData, configStorageData, getStorageinfo, shareStorageData} from "@/restful"; import {cleanStorageData, configStorageData, exchangeStorageData, getStorageinfo, shareStorageData} from "@/restful";
import {deepCopy, diskSize, getFormatDate, getUserInfoFun} from "@/utils"; import {deepCopy, diskSize, getFormatDate, getUserInfoFun} from "@/utils";
import {format_time} from "@/utils/base/utils"; import {format_time} from "@/utils/base/utils";
@ -790,9 +890,11 @@ export default {
target_number: '', target_number: '',
fstorage_lists: [], fstorage_lists: [],
share_bill_lists: [], share_bill_lists: [],
storage_exchange_lists: [],
Sdisabled: true, Sdisabled: true,
canshare: true, canshare: true,
use_storage_id: 0, use_storage_id: 0,
exchange_number: 10,
org_storage_id: 0, org_storage_id: 0,
title: '', title: '',
uidsearch: '', uidsearch: '',
@ -801,6 +903,8 @@ export default {
dialogstorageVisible: false, dialogstorageVisible: false,
editstorageinfo: {}, editstorageinfo: {},
migrate_progress: {}, migrate_progress: {},
exchange_prices: {},
exchange_prices_key: "",
selectlabel: "", selectlabel: "",
storageinfo: {'used_number': 0, 'max_storage_capacity': 1}, storageinfo: {'used_number': 0, 'max_storage_capacity': 1},
storage_list: [], storage_list: [],
@ -808,6 +912,7 @@ export default {
disabled: true, disabled: true,
isaddflag: false, isaddflag: false,
shareVisible: false, shareVisible: false,
exchangeVisible: false,
showshareSync: false, showshareSync: false,
activeName: 'change', activeName: 'change',
storage_info_lists: [], storage_info_lists: [],
@ -819,8 +924,38 @@ export default {
format_time, format_time,
diskSize, diskSize,
getFormatDate, getFormatDate,
cancelStorage() { sureExchangeFun(exchange_number, exchange_prices_key) {
exchangeStorageData(res => {
if (res.code === 1000) {
this.$message.success("扩容成功")
this.exchangeVisible = false
} else {
this.$message.error("操作失败了," + res.msg)
}
}, {"methods": 'POST', "data": {exchange_number, exchange_prices_key}})
},
exchangeFun() {
exchangeStorageData(res => {
if (res.code === 1000) {
this.exchangeVisible = true
this.exchange_prices = res.data
let pay_times = this.exchange_prices.pay_times
if (pay_times && pay_times.length > 0) {
this.exchange_prices_key = pay_times[0].name
}
} else {
this.$message.error("获取数据失败了," + res.msg)
}
}, {"methods": 'PUT', "data": new Date()})
},
expire_content(is_expired) {
if (is_expired > 0) {
return `生效中,还有 ${is_expired} 天到期`
}
return `${-is_expired} 天前已失效`
},
cancelStorage() {
this.$confirm("确定要解除迁移锁么,在未完成迁移状态下解除迁移锁之后,可能会导致数据异常?", '提示', { this.$confirm("确定要解除迁移锁么,在未完成迁移状态下解除迁移锁之后,可能会导致数据异常?", '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
@ -1241,7 +1376,9 @@ export default {
}, {"methods": 'GET', "data": {'act': 'storage_type'}}); }, {"methods": 'GET', "data": {'act': 'storage_type'}});
} else if (tabname === "share") { } else if (tabname === "share") {
data.operatestatus = this.operatestatus data.operatestatus = this.operatestatus
this.loading = true
shareStorageData(res => { shareStorageData(res => {
this.loading = false
if (res.code === 1000) { if (res.code === 1000) {
this.share_bill_lists = res.data this.share_bill_lists = res.data
this.pagination.total = res.count this.pagination.total = res.count
@ -1260,13 +1397,25 @@ export default {
} }
}, {"methods": 'GET', "data": data}) }, {"methods": 'GET', "data": data})
} else if (tabname === 'exchange') {
this.loading = true
exchangeStorageData(res => {
if (res.code === 1000) {
this.storage_exchange_lists = res.data
this.pagination.total = res.count
} else {
this.$message.error("获取数据失败了," + res.msg)
}
this.loading = false
}, {"methods": 'GET', "data": data})
} }
}, },
}, mounted() { }, mounted() {
getUserInfoFun(this); getUserInfoFun(this);
if (this.$route.params.act) { if (this.$route.params.act) {
let activeName = this.$route.params.act; let activeName = this.$route.params.act;
let activeName_list = ["change", "edit", "add", "share", "setting"]; let activeName_list = ["change", "edit", "add", "share", "exchange", "setting"];
for (let index in activeName_list) { for (let index in activeName_list) {
if (activeName_list[index] === activeName) { if (activeName_list[index] === activeName) {
this.activeName = activeName; this.activeName = activeName;
@ -1294,6 +1443,14 @@ export default {
p = 100 p = 100
} }
return p return p
},
exchangetimes() {
let pay_key = this.exchange_prices_key
if (pay_key) {
let m = pay_key.split('_')[1]
return this.exchange_number * parseInt(m) * this.exchange_prices.storage_every_month_price / this.exchange_prices.download_times
}
return 0
} }
} }
} }

@ -414,6 +414,18 @@ export function configStorageData(callBack, params) {
); );
} }
/**用户存储空间兑换操作 */
export function exchangeStorageData(callBack, params) {
getData(
params.methods,
USERSEVER + '/storage/exchange',
params.data,
data => {
callBack(data);
}
);
}
/**用户个人信息 */ /**用户个人信息 */
export function userinfos(callBack, params) { export function userinfos(callBack, params) {
getData( getData(

@ -254,6 +254,7 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=128, unique=True, verbose_name='下载包唯一名称')), ('name', models.CharField(max_length=128, unique=True, verbose_name='下载包唯一名称')),
('title', models.CharField(max_length=128, verbose_name='下载包名称')), ('title', models.CharField(max_length=128, verbose_name='下载包名称')),
('description', models.CharField(max_length=128, verbose_name='下载包描述')), ('description', models.CharField(max_length=128, verbose_name='下载包描述')),
('price_type', models.SmallIntegerField(choices=[(1, '下载次数'), (2, '存储容量')], default=1)),
('price', models.BigIntegerField(verbose_name='下载包价格,单位分')), ('price', models.BigIntegerField(verbose_name='下载包价格,单位分')),
('package_size', models.BigIntegerField(verbose_name='下载包次数')), ('package_size', models.BigIntegerField(verbose_name='下载包次数')),
('download_count_gift', models.IntegerField(default=0, verbose_name='赠送下载次数')), ('download_count_gift', models.IntegerField(default=0, verbose_name='赠送下载次数')),

@ -10,6 +10,7 @@ price_info_list = [
"title": "1k_times", "title": "1k_times",
"description": "1k_times", "description": "1k_times",
"price": 2500, "price": 2500,
"price_type": 1,
"package_size": 1000, "package_size": 1000,
"download_count_gift": 100, "download_count_gift": 100,
"is_enable": True, "is_enable": True,
@ -19,6 +20,7 @@ price_info_list = [
"title": "10k_times", "title": "10k_times",
"description": "10k_times", "description": "10k_times",
"price": 22000, "price": 22000,
"price_type": 1,
"package_size": 10000, "package_size": 10000,
"download_count_gift": 800, "download_count_gift": 800,
"is_enable": True, "is_enable": True,
@ -28,10 +30,58 @@ price_info_list = [
"title": "100k_times", "title": "100k_times",
"description": "100k_times", "description": "100k_times",
"price": 200000, "price": 200000,
"price_type": 1,
"package_size": 100000, "package_size": 100000,
"download_count_gift": 8000, "download_count_gift": 8000,
"is_enable": True, "is_enable": True,
}, },
{
"name": "storage_3_month",
"title": "三个月",
"description": "三个月",
"price": 3,
"price_type": 2,
"package_size": 3,
"download_count_gift": 0,
"is_enable": True,
}, {
"name": "storage_6_month",
"title": "半年",
"description": "半年",
"price": 6,
"price_type": 2,
"package_size": 6,
"download_count_gift": 0,
"is_enable": True,
}, {
"name": "storage_12_month",
"title": "一年",
"description": "一年",
"price": 12,
"price_type": 2,
"package_size": 12,
"download_count_gift": 0,
"is_enable": True,
}, {
"name": "storage_24_month",
"title": "两年",
"description": "两年",
"price": 24,
"price_type": 2,
"package_size": 24,
"download_count_gift": 0,
"is_enable": True,
}, {
"name": "storage_36_month",
"title": "三年",
"description": "三年",
"price": 36,
"price_type": 2,
"package_size": 36,
"download_count_gift": 0,
"is_enable": True,
}
] ]
WEB_DOMAIN = DOMAINCONF.WEB_DOMAIN.split('//')[1] WEB_DOMAIN = DOMAINCONF.WEB_DOMAIN.split('//')[1]
domain_cname_info_list = [ domain_cname_info_list = [

@ -16,11 +16,6 @@ class Migration(migrations.Migration):
name='max_storage_capacity', name='max_storage_capacity',
field=models.BigIntegerField(default=0, verbose_name='存储最大使用容量,单位btype'), field=models.BigIntegerField(default=0, verbose_name='存储最大使用容量,单位btype'),
), ),
migrations.AddField(
model_name='userinfo',
name='storage_capacity',
field=models.BigIntegerField(default=0, verbose_name='存储容量,单位byte'),
),
migrations.AlterField( migrations.AlterField(
model_name='userinfo', model_name='userinfo',
name='download_times', name='download_times',
@ -62,4 +57,22 @@ class Migration(migrations.Migration):
'verbose_name_plural': '私有存储共享', 'verbose_name_plural': '私有存储共享',
}, },
), ),
migrations.CreateModel(
name='StorageExchange',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('download_times', models.BigIntegerField(default=0, verbose_name='消耗下载次数')),
('storage_size', models.BigIntegerField(default=0, verbose_name='兑换空间大小')),
('expires_time', models.DateTimeField(verbose_name='失效时间')),
('description', models.CharField(blank=True, default='', max_length=128, verbose_name='操作描述')),
('remote_addr', models.GenericIPAddressField(verbose_name='远程IP地址')),
('created_time', models.DateTimeField(auto_now_add=True, verbose_name='添加时间')),
('user_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL,
verbose_name='用户ID')),
],
options={
'verbose_name': '存储空间兑换信息',
'verbose_name_plural': '存储空间兑换信息',
},
),
] ]

@ -40,7 +40,6 @@ 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.PositiveBigIntegerField(default=0, verbose_name="可用下载点次数,需要用户充值") download_times = models.PositiveBigIntegerField(default=0, verbose_name="可用下载点次数,需要用户充值")
storage_capacity = models.BigIntegerField(default=0, verbose_name="存储容量,单位byte")
all_download_times = models.BigIntegerField(default=0, verbose_name="总共下载次数") all_download_times = models.BigIntegerField(default=0, verbose_name="总共下载次数")
default_domain_name = models.ForeignKey(to="DomainCnameInfo", verbose_name="默认下载页域名", on_delete=models.CASCADE) default_domain_name = models.ForeignKey(to="DomainCnameInfo", verbose_name="默认下载页域名", on_delete=models.CASCADE)
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)
@ -318,6 +317,8 @@ class Price(models.Model):
name = models.CharField(max_length=128, unique=True, verbose_name="下载包唯一名称") name = models.CharField(max_length=128, unique=True, verbose_name="下载包唯一名称")
title = models.CharField(max_length=128, verbose_name="下载包名称") title = models.CharField(max_length=128, verbose_name="下载包名称")
description = models.CharField(max_length=128, verbose_name="下载包描述") description = models.CharField(max_length=128, verbose_name="下载包描述")
price_type_choices = ((1, '下载次数'), (2, '存储容量'))
price_type = models.SmallIntegerField(choices=price_type_choices, default=1)
price = models.BigIntegerField(null=False, verbose_name="下载包价格,单位分") price = models.BigIntegerField(null=False, verbose_name="下载包价格,单位分")
package_size = models.BigIntegerField(null=False, verbose_name="下载包次数") package_size = models.BigIntegerField(null=False, verbose_name="下载包次数")
download_count_gift = models.IntegerField(default=0, null=False, verbose_name="赠送下载次数") download_count_gift = models.IntegerField(default=0, null=False, verbose_name="赠送下载次数")
@ -330,12 +331,13 @@ class Price(models.Model):
unique_together = ('price', 'package_size') unique_together = ('price', 'package_size')
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if Price.objects.filter(is_enable=True).count() > 3: # 最多3个启用的价格表 if Price.objects.filter(is_enable=True, price_type=1).count() > 3: # 最多3个启用的价格表
raise raise
super(Price, self).save(*args, **kwargs) super(Price, self).save(*args, **kwargs)
def __str__(self): def __str__(self):
return "%s-%s-%s-%s" % (self.name, self.price, self.package_size, self.download_count_gift) return "%s-%s-%s-%s-%s" % (
self.name, self.price, self.package_size, self.download_count_gift, self.get_price_type_display())
class UserCertificationInfo(models.Model): class UserCertificationInfo(models.Model):
@ -604,3 +606,20 @@ class StorageShareInfo(models.Model):
def __str__(self): def __str__(self):
return f"{self.user_id}-{self.to_user_id}{self.description}" return f"{self.user_id}-{self.to_user_id}{self.description}"
class StorageExchange(models.Model):
user_id = models.ForeignKey(to=UserInfo, verbose_name="用户ID", on_delete=models.CASCADE)
download_times = models.BigIntegerField(verbose_name="消耗下载次数", default=0)
storage_size = models.BigIntegerField(verbose_name="兑换空间大小", default=0)
expires_time = models.DateTimeField(verbose_name="失效时间")
description = models.CharField(verbose_name="操作描述", max_length=128, default='', blank=True)
remote_addr = models.GenericIPAddressField(verbose_name="远程IP地址")
created_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")
class Meta:
verbose_name = '存储空间兑换信息'
verbose_name_plural = "存储空间兑换信息"
def __str__(self):
return f"{self.user_id}{self.description}"

@ -29,7 +29,7 @@ from api.views.notify import NotifyReceiverView, NotifyConfigView, NotifyInfoVie
from api.views.order import PriceView, OrderView, PaySuccess, OrderSyncView from api.views.order import PriceView, OrderView, PaySuccess, OrderSyncView
from api.views.personalconfig import PersonalConfigView from api.views.personalconfig import PersonalConfigView
from api.views.report import ReportView from api.views.report import ReportView
from api.views.storage import StorageView, CleanStorageView, ShareStorageView, StorageConfigView from api.views.storage import StorageView, CleanStorageView, ShareStorageView, StorageConfigView, StorageExchangeView
from api.views.thirdlogin import ValidWxChatToken, ThirdWxAccount from api.views.thirdlogin import ValidWxChatToken, ThirdWxAccount
from api.views.uploads import AppAnalyseView, UploadView from api.views.uploads import AppAnalyseView, UploadView
@ -50,6 +50,7 @@ urlpatterns = [
re_path("^storage/clean$", CleanStorageView.as_view()), re_path("^storage/clean$", CleanStorageView.as_view()),
re_path("^storage/share$", ShareStorageView.as_view()), re_path("^storage/share$", ShareStorageView.as_view()),
re_path("^storage/config$", StorageConfigView.as_view()), re_path("^storage/config$", StorageConfigView.as_view()),
re_path("^storage/exchange$", StorageExchangeView.as_view()),
re_path(r"^apps/(?P<app_id>\w+)", AppInfoView.as_view()), re_path(r"^apps/(?P<app_id>\w+)", AppInfoView.as_view()),
re_path(r"^download_password/(?P<app_id>\w+)", AppDownloadTokenView.as_view()), re_path(r"^download_password/(?P<app_id>\w+)", AppDownloadTokenView.as_view()),
re_path(r"^appinfos/(?P<app_id>\w+)/(?P<act>\w+)", AppReleaseInfoView.as_view()), re_path(r"^appinfos/(?P<app_id>\w+)/(?P<act>\w+)", AppReleaseInfoView.as_view()),

@ -3,8 +3,7 @@
# project: 4月 # project: 4月
# author: NinEveN # author: NinEveN
# date: 2021/4/16 # date: 2021/4/16
import datetime
import logging import logging
import random import random
from urllib.parse import urljoin from urllib.parse import urljoin
@ -13,21 +12,24 @@ from django.db.models import Count
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import PageNumberPagination
from api.models import AppReleaseInfo, UserDomainInfo, DomainCnameInfo, UserAdDisplayInfo, RemoteClientInfo, \ from api.models import AppReleaseInfo, UserDomainInfo, DomainCnameInfo, UserAdDisplayInfo, RemoteClientInfo, \
AppBundleIdBlackList, NotifyReceiver, WeChatInfo, AppDownloadToken, Apps, StorageShareInfo, UserInfo, AppStorage AppBundleIdBlackList, NotifyReceiver, WeChatInfo, AppDownloadToken, Apps, StorageShareInfo, UserInfo, AppStorage, \
StorageExchange
from common.base.baseutils import get_server_domain_from_request, get_user_default_domain_name, get_real_ip_address, \ from common.base.baseutils import get_server_domain_from_request, get_user_default_domain_name, get_real_ip_address, \
get_origin_domain_name get_origin_domain_name
from common.base.magic import MagicCacheData from common.base.magic import MagicCacheData
from common.core.sysconfig import Config from common.core.sysconfig import Config, UserConfig
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def get_app_d_count_by_app_id(app_id, is_oss=False): def get_app_d_count_by_app_id(app_id, user_obj):
d_count = 1 d_count = 1
is_oss = user_obj.storage
binary_size = AppReleaseInfo.objects.filter(is_master=True, app_id__app_id=app_id).values('binary_size').first() binary_size = AppReleaseInfo.objects.filter(is_master=True, app_id__app_id=app_id).values('binary_size').first()
if binary_size and binary_size.get('binary_size', 0) > 0: if binary_size and binary_size.get('binary_size', 0) > 0:
d_count += binary_size.get('binary_size') // 1024 // 1024 // 100 d_count += binary_size.get('binary_size') // Config.APP_FILE_CALCULATION_UNIT
return d_count * (Config.APP_USE_BASE_DOWNLOAD_TIMES if not is_oss else Config.PRIVATE_OSS_DOWNLOAD_TIMES) return d_count * (UserConfig(user_obj).APP_USE_BASE_DOWNLOAD_TIMES if not is_oss else UserConfig(
user_obj).PRIVATE_OSS_DOWNLOAD_TIMES)
def get_user_domain_name(obj, domain_type=1): def get_user_domain_name(obj, domain_type=1):
@ -277,11 +279,18 @@ def get_user_storage_used(user_obj):
return binary_size_sum return binary_size_sum
def get_user_exchange_storage_capacity(user_obj):
obj = StorageExchange.objects.filter(user_id=user_obj, expires_time__gte=datetime.datetime.now()).order_by(
'storage_size').last()
if obj:
return obj.storage_size
return 0
def get_user_storage_capacity(user_obj): def get_user_storage_capacity(user_obj):
storage_obj = user_obj.storage storage_obj = user_obj.storage
if not storage_obj: if not storage_obj:
user_storage_capacity = user_obj.storage_capacity storage_capacity = get_user_exchange_storage_capacity(user_obj) + Config.STORAGE_FREE_CAPACITY
storage_capacity = user_storage_capacity if user_storage_capacity else Config.STORAGE_FREE_CAPACITY
else: else:
if storage_obj.user_id == user_obj: if storage_obj.user_id == user_obj:
max_storage_capacity = storage_obj.max_storage_capacity max_storage_capacity = storage_obj.max_storage_capacity

@ -1,3 +1,4 @@
import datetime
import logging import logging
import os import os
@ -9,7 +10,7 @@ from api.utils.apputils import bytes2human
from api.utils.modelutils import get_user_domain_name, get_app_domain_name, get_app_download_uri, get_user_storage_used from api.utils.modelutils import get_user_domain_name, get_app_domain_name, get_app_download_uri, get_user_storage_used
from common.base.baseutils import get_choices_dict, WeixinLoginUid from common.base.baseutils import get_choices_dict, WeixinLoginUid
from common.cache.storage import AdPicShowCache from common.cache.storage import AdPicShowCache
from common.core.sysconfig import Config from common.core.sysconfig import Config, UserConfig
from common.utils.caches import get_user_free_download_times, get_user_cert_auth_status from common.utils.caches import get_user_free_download_times, get_user_cert_auth_status
from common.utils.storage import Storage from common.utils.storage import Storage
from common.utils.token import make_token from common.utils.token import make_token
@ -61,9 +62,18 @@ def get_screenshots_from_self(self, obj, force_new=False):
class UserInfoSerializer(serializers.ModelSerializer): class UserInfoSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = models.UserInfo model = models.UserInfo
fields = ["username", "uid", "mobile", "job", "email", "domain_name", "role", "first_name", fields = ["username", "uid", "mobile", "job", "email", "domain_name", "role", "first_name", "times_info",
'head_img', 'storage_active', 'supersign_active', 'free_download_times', 'download_times', 'head_img', 'storage_active', 'supersign_active', 'free_download_times', 'download_times',
'certification', 'qrcode_domain_name', 'storage_used', 'storage_capacity', 'storage_used_capacity'] 'certification', 'qrcode_domain_name', 'storage_used', 'storage_used_capacity']
times_info = serializers.SerializerMethodField()
def get_times_info(self, obj):
return {
'every_size': UserConfig(obj).APP_FILE_CALCULATION_UNIT,
'private_times': UserConfig(obj).PRIVATE_OSS_DOWNLOAD_TIMES,
'base_times': UserConfig(obj).APP_USE_BASE_DOWNLOAD_TIMES,
}
storage_used_capacity = serializers.SerializerMethodField() storage_used_capacity = serializers.SerializerMethodField()
@ -436,7 +446,7 @@ class StorageSerializer(serializers.ModelSerializer):
class PriceSerializer(serializers.ModelSerializer): class PriceSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = models.Price model = models.Price
exclude = ["id"] exclude = ["id", "price_type", "is_enable"]
class OrdersSerializer(serializers.ModelSerializer): class OrdersSerializer(serializers.ModelSerializer):
@ -590,3 +600,14 @@ class StorageShareSerializer(serializers.ModelSerializer):
def get_cancel(self, obj): def get_cancel(self, obj):
return self.context.get('user_obj').pk == obj.user_id.pk return self.context.get('user_obj').pk == obj.user_id.pk
class StorageExchangeSerializer(serializers.ModelSerializer):
class Meta:
model = models.StorageExchange
exclude = ["id", "remote_addr", "user_id"]
is_expired = serializers.SerializerMethodField()
def get_is_expired(self, obj):
return (obj.expires_time - datetime.datetime.now()).days

@ -198,7 +198,7 @@ def migrating_storage_data(user_obj, new_storage_obj, clean_old_data):
# 消费下载次数 # 消费下载次数
cache_default_data['migrate_num'] += 1 cache_default_data['migrate_num'] += 1
task_cache.set_storage_cache(cache_default_data) task_cache.set_storage_cache(cache_default_data)
amount = get_app_d_count_by_app_id(app_release_obj.app_id.app_id, False) amount = get_app_d_count_by_app_id(app_release_obj.app_id.app_id, user_obj)
consume_user_download_times(user_obj.pk, app_release_obj.app_id, amount, auth_status) consume_user_download_times(user_obj.pk, app_release_obj.app_id, amount, auth_status)
task_cache.del_storage_cache() task_cache.del_storage_cache()
return True return True

@ -61,7 +61,7 @@ class OrderView(APIView):
logger.info(f"{request.user} 下单成功 {res.dict}") logger.info(f"{request.user} 下单成功 {res.dict}")
return Response(res.dict) return Response(res.dict)
price_obj = Price.objects.filter(name=price_id).first() price_obj = Price.objects.filter(name=price_id, price_type=1).first()
if price_obj: if price_obj:
try: try:
order_number = get_order_num() order_number = get_order_num()
@ -154,7 +154,8 @@ class PriceView(APIView):
def get(self, request): def get(self, request):
res = BaseResponse() res = BaseResponse()
price_obj_lists = Price.objects.filter(is_enable=True).all().order_by("updated_time").order_by("price") price_obj_lists = Price.objects.filter(is_enable=True, price_type=1).all().order_by("updated_time").order_by(
"price")
res.data = PriceSerializer(price_obj_lists, many=True).data res.data = PriceSerializer(price_obj_lists, many=True).data
res.pay_choices = get_enable_pay_choices() res.pay_choices = get_enable_pay_choices()
return Response(res.dict) return Response(res.dict)

@ -3,7 +3,6 @@
# project: 3月 # project: 3月
# author: liuyu # author: liuyu
# date: 2020/3/4 # date: 2020/3/4
import logging import logging
from django.db.models import Q from django.db.models import Q
@ -11,19 +10,19 @@ from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from api.base_views import app_delete from api.base_views import app_delete
from api.models import AppStorage, Apps, StorageShareInfo, UserInfo from api.models import AppStorage, Apps, StorageShareInfo, UserInfo, StorageExchange, Price
from api.tasks import migrate_storage_job from api.tasks import migrate_storage_job
from api.utils.apputils import clean_history_apps from api.utils.apputils import clean_history_apps
from api.utils.modelutils import PageNumber, get_user_storage_capacity, get_user_storage_used from api.utils.modelutils import PageNumber, get_user_storage_capacity, get_user_storage_used
from api.utils.response import BaseResponse from api.utils.response import BaseResponse
from api.utils.serializer import StorageSerializer, StorageShareSerializer from api.utils.serializer import StorageSerializer, StorageShareSerializer, StorageExchangeSerializer, PriceSerializer
from api.utils.utils import upload_oss_default_head_img from api.utils.utils import upload_oss_default_head_img
from common.base.baseutils import get_choices_dict, get_choices_name_from_key, get_real_ip_address from common.base.baseutils import get_choices_dict, get_choices_name_from_key, get_real_ip_address
from common.cache.state import MigrateStorageState from common.cache.state import MigrateStorageState
from common.cache.storage import TaskProgressCache from common.cache.storage import TaskProgressCache
from common.core.auth import ExpiringTokenAuthentication, StoragePermission from common.core.auth import ExpiringTokenAuthentication, StoragePermission
from common.core.sysconfig import Config from common.core.sysconfig import Config
from common.utils.caches import del_cache_storage from common.utils.caches import del_cache_storage, add_user_storage_exchange
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -93,10 +92,9 @@ class StorageView(APIView):
return Response(res.dict) return Response(res.dict)
if pk: if pk:
if pk == '-1': if pk == '-1':
storage_capacity = request.user.storage_capacity
default_storage = { default_storage = {
'id': -1, 'id': -1,
'max_storage_capacity': storage_capacity if storage_capacity != 0 else Config.STORAGE_FREE_CAPACITY, 'max_storage_capacity': get_user_storage_capacity(request.user),
'used_number': get_user_storage_used(request.user) 'used_number': get_user_storage_used(request.user)
} }
res.data = [default_storage] res.data = [default_storage]
@ -305,10 +303,10 @@ class ShareStorageView(APIView):
except Exception as e: except Exception as e:
logger.error(f'status {status} check failed Exception:{e}') logger.error(f'status {status} check failed Exception:{e}')
app_page_serializer = page_obj.paginate_queryset(queryset=share_list.order_by("-created_time"), page_serializer = page_obj.paginate_queryset(queryset=share_list.order_by("-created_time"),
request=request, request=request,
view=self) view=self)
app_serializer = StorageShareSerializer(app_page_serializer, many=True, context={'user_obj': request.user}) app_serializer = StorageShareSerializer(page_serializer, many=True, context={'user_obj': request.user})
res.data = app_serializer.data res.data = app_serializer.data
res.count = share_list.count() res.count = share_list.count()
res.status_choices = get_choices_dict(StorageShareInfo.status_choices) res.status_choices = get_choices_dict(StorageShareInfo.status_choices)
@ -461,3 +459,49 @@ class StorageConfigView(APIView):
if migrate_obj.get_state() == request.data.get('storage_status'): if migrate_obj.get_state() == request.data.get('storage_status'):
migrate_obj.del_state() migrate_obj.del_state()
return Response(res.dict) return Response(res.dict)
class StorageExchangeView(APIView):
authentication_classes = [ExpiringTokenAuthentication, ]
permission_classes = [StoragePermission, ]
def get(self, request):
res = BaseResponse()
storage_exchange_queryset = StorageExchange.objects.filter(user_id=request.user)
page_obj = PageNumber()
page_serializer = page_obj.paginate_queryset(queryset=storage_exchange_queryset.order_by("-created_time"),
request=request,
view=self)
app_serializer = StorageExchangeSerializer(page_serializer, many=True)
res.data = app_serializer.data
res.count = storage_exchange_queryset.count()
return Response(res.dict)
def post(self, request):
res = BaseResponse()
exchange_number = request.data.get('exchange_number')
exchange_prices_key = request.data.get('exchange_prices_key')
if exchange_number and exchange_prices_key:
price_obj = Price.objects.filter(is_enable=True, price_type=2, name=exchange_prices_key).first()
if price_obj:
exchange_month = price_obj.price
if not add_user_storage_exchange(request.user, exchange_number, exchange_month,
get_real_ip_address(request)):
res.code = 1002
res.msg = '扩容失败,账号可用下载次数不足'
else:
res.code = 1001
res.msg = '价格异常'
return Response(res.dict)
def put(self, request):
res = BaseResponse()
price_obj_lists = Price.objects.filter(is_enable=True, price_type=2).all().order_by("updated_time").order_by(
"price")
res.data = {
'pay_times': PriceSerializer(price_obj_lists, many=True).data,
'storage_every_month_price': Config.OSS_EXCHANGE_DOWNLOAD_TIMES,
'download_times': Config.APP_USE_BASE_DOWNLOAD_TIMES,
}
return Response(res.dict)

@ -319,6 +319,14 @@ class UserDownloadTimesCache(ConfigCacheBase):
def PRIVATE_OSS_DOWNLOAD_TIMES(self): def PRIVATE_OSS_DOWNLOAD_TIMES(self):
return super().get_value('PRIVATE_OSS_DOWNLOAD_TIMES', DOWNLOADTIMESCONF.PRIVATE_OSS_DOWNLOAD_TIMES) return super().get_value('PRIVATE_OSS_DOWNLOAD_TIMES', DOWNLOADTIMESCONF.PRIVATE_OSS_DOWNLOAD_TIMES)
@property
def APP_FILE_CALCULATION_UNIT(self):
return super().get_value('APP_FILE_CALCULATION_UNIT', DOWNLOADTIMESCONF.APP_FILE_CALCULATION_UNIT)
@property
def OSS_EXCHANGE_DOWNLOAD_TIMES(self):
return super().get_value('OSS_EXCHANGE_DOWNLOAD_TIMES', DOWNLOADTIMESCONF.OSS_EXCHANGE_DOWNLOAD_TIMES)
class OssStorageConfCache(ConfigCacheBase): class OssStorageConfCache(ConfigCacheBase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

@ -3,7 +3,7 @@
# project: 4月 # project: 4月
# author: liuyu # author: liuyu
# date: 2020/4/7 # date: 2020/4/7
import datetime
import logging import logging
import time import time
@ -11,7 +11,7 @@ from django.core.cache import cache
from django.db.models import F from django.db.models import F
from django.utils import timezone from django.utils import timezone
from api.models import Apps, UserInfo, UserCertificationInfo, Order from api.models import Apps, UserInfo, UserCertificationInfo, Order, StorageExchange
from api.utils.modelutils import get_app_d_count_by_app_id, get_app_domain_name, get_user_domain_name from api.utils.modelutils import get_app_d_count_by_app_id, get_app_domain_name, get_user_domain_name
from common.base.baseutils import get_order_num from common.base.baseutils import get_order_num
from common.cache.invalid import invalid_app_cache, invalid_short_cache, invalid_app_download_times_cache, \ from common.cache.invalid import invalid_app_cache, invalid_short_cache, invalid_app_download_times_cache, \
@ -228,14 +228,11 @@ def get_user_cert_auth_status(user_id):
def check_user_has_all_download_times(app_obj, user_obj): def check_user_has_all_download_times(app_obj, user_obj):
if user_obj: if not user_obj:
user_id = user_obj.pk user_obj = app_obj.user_id
storage = user_obj.storage user_id = user_obj.pk
else:
user_id = app_obj.user_id_id
storage = app_obj.user_id.storage
auth_status = get_user_cert_auth_status(user_id) auth_status = get_user_cert_auth_status(user_id)
d_count = get_app_d_count_by_app_id(app_obj.app_id, storage) d_count = get_app_d_count_by_app_id(app_obj.app_id, user_obj)
a = get_user_free_download_times(user_id, auth_status=auth_status) a = get_user_free_download_times(user_id, auth_status=auth_status)
b = d_count b = d_count
c = check_user_can_download(user_id) c = check_user_can_download(user_id)
@ -244,10 +241,10 @@ def check_user_has_all_download_times(app_obj, user_obj):
def consume_user_download_times_by_app_obj(app_obj): def consume_user_download_times_by_app_obj(app_obj):
user_id = app_obj.user_id_id user_obj = app_obj.user_id
auth_status = get_user_cert_auth_status(user_id) auth_status = get_user_cert_auth_status(user_obj.pk)
amount = get_app_d_count_by_app_id(app_obj.app_id, False) amount = get_app_d_count_by_app_id(app_obj.app_id, user_obj)
if consume_user_download_times(user_id, app_obj.app_id, int(amount * (Config.SIGN_EXTRA_MULTIPLE - 1)), if consume_user_download_times(user_obj.pk, app_obj.app_id, int(amount * (Config.SIGN_EXTRA_MULTIPLE - 1)),
auth_status): auth_status):
return False return False
return True return True
@ -398,3 +395,24 @@ def get_and_clean_udid_cache_queue(prefix_key):
data = [] data = []
cache_obj.del_storage_cache() cache_obj.del_storage_cache()
return data return data
def add_user_storage_exchange(user_obj, exchange_number, exchange_month, remote_addr):
with cache.lock("%s_%s" % ('user_storage_order_', user_obj.uid), timeout=10, blocking_timeout=6):
try:
download_times = exchange_number * exchange_month * Config.OSS_EXCHANGE_DOWNLOAD_TIMES
expires_time = datetime.datetime.now() + datetime.timedelta(days=30 * exchange_month)
description = f'扩容成功,消费下载点数{download_times} 兑换了 {exchange_number} GB存储空间'
obj = StorageExchange.objects.create(user_id=user_obj, download_times=download_times,
storage_size=exchange_number * 1024 * 1024 * 1024,
expires_time=expires_time, remote_addr=remote_addr,
description=description)
if add_user_download_times(user_obj.pk, -download_times):
logger.info(f"{user_obj} 存储扩容成功 msg:{description}")
return True
else:
obj.delete()
except Exception as e:
logger.error(f"{user_obj} download_times less then 0. Exception:{e}")
return False

@ -71,7 +71,8 @@ def get_app_instance_by_cache(app_id, limit):
'issupersign', 'user_id__storage', 'issupersign', 'user_id__storage',
'user_id__certification__status').first() 'user_id__certification__status').first()
if app_obj_cache: if app_obj_cache:
app_obj_cache['d_count'] = get_app_d_count_by_app_id(app_id, app_obj_cache.get('user_id__storage', False)) user_obj = UserInfo.objects.filter(pk=app_obj_cache.get('user_id')).first()
app_obj_cache['d_count'] = get_app_d_count_by_app_id(app_id, user_obj)
app_instance_cache.set_storage_cache(app_obj_cache, limit) app_instance_cache.set_storage_cache(app_obj_cache, limit)
if not app_obj_cache: if not app_obj_cache:
return False, '应用不存在' return False, '应用不存在'

@ -392,7 +392,12 @@ bIX1aWjPxirQX9mzaL3oEQI=
class DOWNLOADTIMESCONF(object): class DOWNLOADTIMESCONF(object):
SIGN_EXTRA_MULTIPLE = 2 # 超级签名消耗额外倍数,超级签名需要占用的服务大量资源 SIGN_EXTRA_MULTIPLE = 2 # 超级签名消耗额外倍数,超级签名需要占用的服务大量资源
# 具体计算方式查看该函数 get_app_d_count_by_app_id
APP_USE_BASE_DOWNLOAD_TIMES = 100 # 单个应用下载消费点数 APP_USE_BASE_DOWNLOAD_TIMES = 100 # 单个应用下载消费点数
APP_FILE_CALCULATION_UNIT = 1024 * 1024 * 100 # 应用计算次数,表示应用大小计算点数
OSS_EXCHANGE_DOWNLOAD_TIMES = "{% widthratio 5 1 {{APP_USE_BASE_DOWNLOAD_TIMES}} %}" # 默认 1G 一个月 500下载点数
PRIVATE_OSS_DOWNLOAD_TIMES = 4 # 私有存储单个应用下载消费点数 PRIVATE_OSS_DOWNLOAD_TIMES = 4 # 私有存储单个应用下载消费点数
USER_FREE_DOWNLOAD_TIMES = "{% widthratio 5 1 {{APP_USE_BASE_DOWNLOAD_TIMES}} %}" # 用户每日免费下载点数 USER_FREE_DOWNLOAD_TIMES = "{% widthratio 5 1 {{APP_USE_BASE_DOWNLOAD_TIMES}} %}" # 用户每日免费下载点数

Loading…
Cancel
Save