查看
@@ -256,38 +437,46 @@
-
+
-
+
-
+
-
+
-
+
+ v-model="editstorageinfo.sts_role_arn" clearable/>
-
+
+
+
+
+
+ style="width: 100%">
@@ -297,64 +486,442 @@
label-width="120px"
style="margin-top: 10px;margin-left: 70px;width: 60%">
+ clearable placeholder="CDN鉴权主KEY"/>
+
+
+
+
+
+
+
+
+ 存储空间最大容量
+ {{ diskSize(editstorageinfo.max_storage_capacity * 1024 * 1024) }}
+
+
+
+
+
-
+
校验并保存
+
+
+
+
+
+
+
+ 搜索
+
+
+ 存储共享
+
+
+
+
+
+
+
+
+
+ 用户UID: {{ scope.row.target_user.uid }}
+
+
+ 用户昵称: {{ scope.row.target_user.name }}
+ 该共享存储已经被使用中
+
+ {{ scope.row.target_user.uid }}
+ {{ scope.row.target_user.uid }}
+
+
+
+
+
+
+
+
+
+
+ - {{ diskSize(scope.row.number) }}
+
+
+
+
+
+
+ {{ scope.row.storage_name }}
+
+
+
+
+ {{ format_time(scope.row.created_time) }}
+
+
+
+
+
+
+
+
+
+
+ 成功
+
+ 已撤回
+ 状态异常
+
+
+
+
+ 生效中
+
+ 失效
+
+
+
+
+
+
+
+
+ 应用版本数设置,当应用历史版本超过该限制,将会自动清理较老的版本
+
+
+
+
+
+
+ 保存修改
+
+
+ {{ diskSize(storage_config.user_max_storage_capacity) }}
+ 已经使用
+ {{ diskSize(storage_config.user_used_storage_capacity) }}
+ 还剩
+
+ {{ diskSize(storage_config.user_max_storage_capacity - storage_config.user_used_storage_capacity) }}
+
+
+
+
+ 清理所有应用数据
+
+
+
+
+
+ 清理应用历史版本数据,只保留最新版本数据
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/fir_client/src/restful/index.js b/fir_client/src/restful/index.js
index 63a5719..b9d05f7 100644
--- a/fir_client/src/restful/index.js
+++ b/fir_client/src/restful/index.js
@@ -390,6 +390,30 @@ export function cleanStorageData(callBack, params) {
);
}
+/**用户存储清理操作 */
+export function shareStorageData(callBack, params) {
+ getData(
+ params.methods,
+ USERSEVER + '/storage/share',
+ params.data,
+ data => {
+ callBack(data);
+ }
+ );
+}
+
+/**用户存储清理操作 */
+export function configStorageData(callBack, params) {
+ getData(
+ params.methods,
+ USERSEVER + '/storage/config',
+ params.data,
+ data => {
+ callBack(data);
+ }
+ );
+}
+
/**用户个人信息 */
export function userinfos(callBack, params) {
getData(
diff --git a/fir_client/src/utils/index.js b/fir_client/src/utils/index.js
index 1158012..da1b12d 100644
--- a/fir_client/src/utils/index.js
+++ b/fir_client/src/utils/index.js
@@ -626,7 +626,7 @@ export function diskSize(num) {
let k = 1024; //设定基础容量大小
let sizeStr = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; //容量单位
let i = 0; //单位下标和次幂
- for (let l = 0; l < 8; l++) { //因为只有8个单位所以循环八次
+ for (let l = 0; l < 9; l++) { //因为只有8个单位所以循环八次
if (num / Math.pow(k, l) < 1) { //判断传入数值 除以 基础大小的次幂 是否小于1,这里小于1 就代表已经当前下标的单位已经不合适了所以跳出循环
break; //小于1跳出循环
}
diff --git a/fir_ser/api/base_views.py b/fir_ser/api/base_views.py
index 72c280b..d6ed57f 100644
--- a/fir_ser/api/base_views.py
+++ b/fir_ser/api/base_views.py
@@ -10,7 +10,8 @@ from api.models import AppReleaseInfo, UserInfo, AppScreenShot, AppStorage
from api.utils.response import BaseResponse
from api.utils.signalutils import run_delete_app_signal
from api.utils.utils import delete_local_files, delete_app_screenshots_files, change_storage_and_change_head_img, \
- migrating_storage_data, clean_storage_data, check_storage_is_new_storage
+ migrating_storage_data, clean_storage_data, check_storage_is_new_storage, migrating_storage_file_data
+from common.base.magic import MagicCacheData
from common.cache.state import MigrateStorageState
from common.utils.caches import del_cache_response_by_short, del_cache_by_delete_app, \
del_cache_storage
@@ -50,6 +51,7 @@ def app_delete(app_obj):
has_combo.has_combo = None
has_combo.save(update_fields=['has_combo'])
+ MagicCacheData.invalid_cache(app_obj.app_id)
app_obj.delete()
return res
@@ -101,12 +103,19 @@ def app_release_delete(app_obj, release_id, storage):
return res
+def check_storage_ok(user_obj, new_storage_obj):
+ if migrating_storage_file_data(user_obj, 'head_img.jpeg', new_storage_obj, False):
+ return True
+
+
def storage_change(use_storage_id, user_obj, force):
if use_storage_id:
if user_obj.storage and use_storage_id == user_obj.storage.id:
return True
try:
if use_storage_id == -1:
+ if not check_storage_ok(user_obj, None):
+ return False
change_storage_and_change_head_img(user_obj, None)
if migrating_storage_data(user_obj, None, False):
if check_storage_is_new_storage(user_obj, None):
@@ -114,6 +123,8 @@ def storage_change(use_storage_id, user_obj, force):
UserInfo.objects.filter(pk=user_obj.pk).update(storage=None)
else:
new_storage_obj = AppStorage.objects.filter(pk=use_storage_id).first()
+ if not check_storage_ok(user_obj, new_storage_obj):
+ return False
change_storage_and_change_head_img(user_obj, new_storage_obj)
if migrating_storage_data(user_obj, new_storage_obj, False):
if check_storage_is_new_storage(user_obj, new_storage_obj):
diff --git a/fir_ser/api/migrations/0006_auto_20220513_0843.py b/fir_ser/api/migrations/0006_auto_20220513_0843.py
new file mode 100644
index 0000000..6d9c1c2
--- /dev/null
+++ b/fir_ser/api/migrations/0006_auto_20220513_0843.py
@@ -0,0 +1,60 @@
+# Generated by Django 3.2.3 on 2022-05-13 08:43
+
+import django.db.models.fields
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('api', '0005_auto_20220412_1744'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='appstorage',
+ name='max_storage_capacity',
+ 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(
+ model_name='userinfo',
+ name='storage',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
+ related_name='app_storage', to='api.appstorage', verbose_name='存储'),
+ ),
+ migrations.AlterUniqueTogether(
+ name='appstorage',
+ unique_together={('user_id', 'bucket_name', 'endpoint'), ('user_id', 'name')},
+ ),
+ migrations.CreateModel(
+ name='StorageShareInfo',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('status',
+ models.SmallIntegerField(choices=[(1, '共享成功'), (2, '取消共享')], default=0, help_text='1 共享成功 2 取消共享',
+ verbose_name='状态')),
+ ('number', models.BigIntegerField(default=0, verbose_name='容量大小,单位byte')),
+ ('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='添加时间')),
+ ('updated_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
+ ('storage_id', models.ForeignKey(on_delete=django.db.models.fields.CharField, to='api.appstorage',
+ verbose_name='存储ID')),
+ ('to_user_id', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE,
+ related_name='storage_to_user_id', to=settings.AUTH_USER_MODEL,
+ verbose_name='用户ID')),
+ ('user_id',
+ models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='storage_org_user_id',
+ to=settings.AUTH_USER_MODEL, verbose_name='用户ID')),
+ ],
+ options={
+ 'verbose_name': '私有存储共享',
+ 'verbose_name_plural': '私有存储共享',
+ },
+ ),
+ ]
diff --git a/fir_ser/api/models.py b/fir_ser/api/models.py
index 7636961..4cd9317 100644
--- a/fir_ser/api/models.py
+++ b/fir_ser/api/models.py
@@ -40,11 +40,12 @@ class UserInfo(AbstractUser):
memo = models.TextField('备注', blank=True, null=True, default=None, )
date_joined = models.DateTimeField(auto_now_add=True, verbose_name="注册时间")
download_times = models.PositiveIntegerField(default=0, verbose_name="可用下载次数,需要用户充值")
+ storage_capacity = models.BigIntegerField(default=0, verbose_name="存储容量,单位byte")
all_download_times = models.BigIntegerField(default=0, verbose_name="总共下载次数")
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)
- storage = models.OneToOneField(to='AppStorage', related_name='app_storage',
- on_delete=models.SET_NULL, verbose_name="存储", null=True, blank=True)
+ storage = models.ForeignKey(to='AppStorage', related_name='app_storage',
+ on_delete=models.SET_NULL, verbose_name="存储", null=True, blank=True)
api_token = models.CharField(max_length=256, verbose_name='api访问密钥', default='')
notify_available_downloads = models.IntegerField(default=0, verbose_name="下载余额不足通知", blank=True, null=True)
notify_available_signs = models.IntegerField(default=0, verbose_name="签名余额不足通知", blank=True, null=True)
@@ -261,6 +262,8 @@ class AppStorage(models.Model):
verbose_name="阿里云下载授权方式")
cnd_auth_key = models.CharField(max_length=128, blank=True, null=True, verbose_name="阿里云cnd_auth_key")
+ max_storage_capacity = models.BigIntegerField(verbose_name="存储最大使用容量,单位btype", default=0)
+
created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
description = models.TextField('备注', blank=True, null=True, default='')
@@ -268,6 +271,7 @@ class AppStorage(models.Model):
class Meta:
verbose_name = '存储配置'
verbose_name_plural = "存储配置"
+ unique_together = (('user_id', 'name'), ('user_id', 'bucket_name', 'endpoint'))
def save(self, *args, **kwargs):
if self.storage_type in (1, 2):
@@ -574,3 +578,29 @@ class UserPersonalConfig(BaseConfig):
def __str__(self):
return "%s-%s" % (self.key, self.description)
+
+
+class StorageShareInfo(models.Model):
+ """
+ 用户共享存储空间,可将该用户私有存储共享给 其他用户使用
+ """
+ user_id = models.ForeignKey(to=UserInfo, verbose_name="用户ID", on_delete=models.CASCADE,
+ related_name='storage_org_user_id')
+ to_user_id = models.ForeignKey(to=UserInfo, verbose_name="用户ID", on_delete=models.CASCADE,
+ related_name='storage_to_user_id', null=True, blank=True)
+ storage_id = models.ForeignKey(to=AppStorage, verbose_name="存储ID", on_delete=models.CharField)
+ status_choices = ((1, '共享成功'), (2, '取消共享'))
+ status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="状态",
+ help_text="1 共享成功 2 取消共享")
+ number = models.BigIntegerField(verbose_name="容量大小,单位byte", default=0)
+ 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="添加时间")
+ updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
+
+ class Meta:
+ verbose_name = '私有存储共享'
+ verbose_name_plural = "私有存储共享"
+
+ def __str__(self):
+ return f"{self.user_id}-{self.to_user_id}—{self.description}"
diff --git a/fir_ser/api/urls.py b/fir_ser/api/urls.py
index 89b9748..29846f6 100644
--- a/fir_ser/api/urls.py
+++ b/fir_ser/api/urls.py
@@ -29,7 +29,7 @@ from api.views.notify import NotifyReceiverView, NotifyConfigView, NotifyInfoVie
from api.views.order import PriceView, OrderView, PaySuccess, OrderSyncView
from api.views.personalconfig import PersonalConfigView
from api.views.report import ReportView
-from api.views.storage import StorageView, CleanStorageView
+from api.views.storage import StorageView, CleanStorageView, ShareStorageView, StorageConfigView
from api.views.thirdlogin import ValidWxChatToken, ThirdWxAccount
from api.views.uploads import AppAnalyseView, UploadView
@@ -48,6 +48,8 @@ urlpatterns = [
re_path("^apps$", AppsView.as_view()),
re_path("^storage$", StorageView.as_view()),
re_path("^storage/clean$", CleanStorageView.as_view()),
+ re_path("^storage/share$", ShareStorageView.as_view()),
+ re_path("^storage/config$", StorageConfigView.as_view()),
re_path(r"^apps/(?P
\w+)", AppInfoView.as_view()),
re_path(r"^download_password/(?P\w+)", AppDownloadTokenView.as_view()),
re_path(r"^appinfos/(?P\w+)/(?P\w+)", AppReleaseInfoView.as_view()),
diff --git a/fir_ser/api/utils/apputils.py b/fir_ser/api/utils/apputils.py
index a465b98..b76b68b 100644
--- a/fir_ser/api/utils/apputils.py
+++ b/fir_ser/api/utils/apputils.py
@@ -9,6 +9,7 @@ import random
from api.models import AppReleaseInfo, Apps
from api.utils.modelutils import get_user_domain_name
from common.base.baseutils import make_app_uuid
+from common.base.magic import MagicCacheData
from common.cache.state import MigrateStorageState
from common.utils.caches import del_cache_response_by_short
from common.utils.storage import Storage
@@ -78,9 +79,10 @@ def clean_history_apps(app_obj, user_obj, history_release_limit=20):
storage_obj.delete_file(release_obj.release_id, app_obj.type)
storage_obj.delete_file(release_obj.icon_url)
release_obj.delete()
+ MagicCacheData.invalid_cache(app_obj.app_id)
-def save_app_infos(app_file_name, user_obj, app_info, bundle_id, app_img, short, size, issupersign):
+def save_app_infos(app_tmp_filename, app_file_name, user_obj, app_info, bundle_id, app_img, short, size, issupersign):
app_uuid = make_app_uuid(user_obj, bundle_id + app_file_name.split(".")[1])
##判断是否存在该app
app_obj = Apps.objects.filter(app_id=app_uuid, user_id=user_obj).first()
@@ -123,9 +125,13 @@ def save_app_infos(app_file_name, user_obj, app_info, bundle_id, app_img, short,
app_obj.name = app_info["labelname"]
app_obj.save(update_fields=["name", "bundle_id", "issupersign"])
del_cache_response_by_short(app_obj.app_id)
-
+ MagicCacheData.invalid_cache(app_obj.app_id)
AppReleaseInfo.objects.filter(app_id=app_obj).update(is_master=False)
+ file_info = storage.get_file_info(app_tmp_filename)
+ logger.info(f'get file {app_tmp_filename} info:{file_info}')
+ size = file_info.get('content_length', size)
+
release_data = {
"app_id": app_obj,
"icon_url": app_img,
diff --git a/fir_ser/api/utils/modelutils.py b/fir_ser/api/utils/modelutils.py
index 1ba542f..79c077b 100644
--- a/fir_ser/api/utils/modelutils.py
+++ b/fir_ser/api/utils/modelutils.py
@@ -13,9 +13,11 @@ from django.db.models import Count
from rest_framework.pagination import PageNumberPagination
from api.models import AppReleaseInfo, UserDomainInfo, DomainCnameInfo, UserAdDisplayInfo, RemoteClientInfo, \
- AppBundleIdBlackList, NotifyReceiver, WeChatInfo, AppDownloadToken
+ AppBundleIdBlackList, NotifyReceiver, WeChatInfo, AppDownloadToken, Apps, StorageShareInfo, UserInfo
from common.base.baseutils import get_server_domain_from_request, get_user_default_domain_name, get_real_ip_address, \
get_origin_domain_name
+from common.base.magic import MagicCacheData
+from common.core.sysconfig import Config
logger = logging.getLogger(__name__)
@@ -248,3 +250,47 @@ def check_app_access_token(app_id, access_token, only_check, udid):
return True
if udid:
return AppDownloadToken.objects.filter(app_id__app_id=app_id, bind_udid=udid).count()
+
+
+@MagicCacheData.make_cache(60 * 60 * 24, key=lambda x: x.app_id)
+def get_app_storage_used(app_obj):
+ binary_size_sum = 0
+ for release_obj in AppReleaseInfo.objects.filter(app_id=app_obj).all():
+ if release_obj.release_type == 0:
+ sign_count = 0
+ else:
+ sign_count = AppReleaseInfo.objects.filter(
+ app_id__apptodeveloper__release_file=release_obj.release_id).values(
+ 'app_id__apptodeveloper__binary_file').distinct().count()
+ binary_size_sum += release_obj.binary_size * (sign_count + 1)
+ return binary_size_sum
+
+
+# @MagicCacheData.make_cache(3600, key=lambda x: x.uid)
+def get_user_storage_used(user_obj):
+ binary_size_sum = 0
+ if isinstance(user_obj, UserInfo):
+ user_obj = [user_obj]
+ for app_obj in Apps.objects.filter(user_id__in=user_obj).all():
+ binary_size_sum += get_app_storage_used(app_obj)
+ return binary_size_sum
+
+
+def get_user_storage_capacity(user_obj):
+ storage_obj = user_obj.storage
+ if not storage_obj:
+ user_storage_capacity = user_obj.storage_capacity
+ storage_capacity = user_storage_capacity if user_storage_capacity else Config.STORAGE_FREE_CAPACITY
+ else:
+ if storage_obj.user_id == user_obj:
+ max_storage_capacity = storage_obj.max_storage_capacity
+ storage_capacity = max_storage_capacity if max_storage_capacity else Config.STORAGE_OSS_CAPACITY
+ else:
+ share_obj = StorageShareInfo.objects.filter(to_user_id=user_obj, status=1, storage_id=storage_obj).first()
+ if share_obj:
+ can_use_number = storage_obj.max_storage_capacity - get_user_storage_used(storage_obj.app_storage.all())
+ storage_capacity = share_obj.number if share_obj.number < can_use_number else can_use_number
+ else:
+ storage_capacity = Config.STORAGE_FREE_CAPACITY
+
+ return storage_capacity
diff --git a/fir_ser/api/utils/serializer.py b/fir_ser/api/utils/serializer.py
index 3a9c39e..f2597e1 100644
--- a/fir_ser/api/utils/serializer.py
+++ b/fir_ser/api/utils/serializer.py
@@ -2,10 +2,11 @@ import logging
import os
from rest_framework import serializers
+from rest_framework.exceptions import ValidationError
from api import models
from api.utils.apputils import bytes2human
-from api.utils.modelutils import get_user_domain_name, get_app_domain_name, get_app_download_uri
+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.cache.storage import AdPicShowCache
from common.core.sysconfig import Config
@@ -62,7 +63,17 @@ class UserInfoSerializer(serializers.ModelSerializer):
model = models.UserInfo
fields = ["username", "uid", "mobile", "job", "email", "domain_name", "role", "first_name",
'head_img', 'storage_active', 'supersign_active', 'free_download_times', 'download_times',
- 'certification', 'qrcode_domain_name']
+ 'certification', 'qrcode_domain_name', 'storage_used', 'storage_capacity', 'storage_used_capacity']
+
+ storage_used_capacity = serializers.SerializerMethodField()
+
+ def get_storage_used_capacity(self, obj):
+ return get_user_storage_used(obj)
+
+ storage_used = serializers.SerializerMethodField()
+
+ def get_storage_used(self, obj):
+ return bytes2human(self.get_storage_used_capacity(obj))
head_img = serializers.SerializerMethodField()
@@ -128,13 +139,13 @@ class AppsSerializer(serializers.ModelSerializer):
def get_preview_url(self, obj):
return get_app_download_uri(None, obj.user_id, obj)
- private_developer_number = serializers.IntegerField(default=0)
- count = serializers.IntegerField(default=0)
+ # private_developer_number = serializers.IntegerField(default=0)
+ # count = serializers.IntegerField(default=0)
#
# def get_private_developer_number(self, obj):
# return models.AppleDeveloperToAppUse.objects.filter(app_id=obj).count()
#
- private_developer_used_number = serializers.IntegerField(default=0)
+ # private_developer_used_number = serializers.IntegerField(default=0)
#
# def get_private_developer_used_number(self, obj):
# return models.DeveloperDevicesID.objects.filter(app_id=obj,
@@ -150,12 +161,12 @@ class AppsSerializer(serializers.ModelSerializer):
def get_sign_type_choice(self, obj):
return get_choices_dict(obj.supersign_type_choices)
- supersign_used_number = serializers.IntegerField(default=0)
+ # supersign_used_number = serializers.IntegerField(default=0)
#
# def get_supersign_used_number(self, obj):
# return models.APPSuperSignUsedInfo.objects.filter(app_id=obj).all().count()
#
- developer_used_count = serializers.IntegerField(default=0)
+ # developer_used_count = serializers.IntegerField(default=0)
#
# def get_developer_used_count(self, obj):
# return models.DeveloperAppID.objects.filter(app_id=obj).all().count()
@@ -354,9 +365,35 @@ class StorageSerializer(serializers.ModelSerializer):
model = models.AppStorage
exclude = ["user_id"]
+ def validate(self, attrs):
+ endpoint = attrs.get('endpoint', '')
+ storage_type = attrs.get('storage_type', '')
+ if storage_type == 2 and endpoint not in Config.STORAGE_ALLOW_ENDPOINT:
+ raise ValidationError(f'endpoint [{endpoint}] not in {Config.STORAGE_ALLOW_ENDPOINT}')
+ max_storage_capacity = attrs.get('max_storage_capacity', -1)
+ if max_storage_capacity != -1:
+ attrs['max_storage_capacity'] = max_storage_capacity * 1024 * 1024
+ elif max_storage_capacity == 0:
+ attrs['max_storage_capacity'] = Config.STORAGE_OSS_CAPACITY
+ else:
+ del attrs['max_storage_capacity']
+ return attrs
+
storage_type_display = serializers.CharField(source="get_storage_type_display", read_only=True)
download_auth_type_choices = serializers.SerializerMethodField()
+ used = serializers.SerializerMethodField()
+ used_number = serializers.SerializerMethodField()
+ shared = serializers.SerializerMethodField()
+
+ def get_used(self, obj):
+ return obj.app_storage.count()
+
+ def get_shared(self, obj):
+ return models.StorageShareInfo.objects.filter(status=1, storage_id=obj).count()
+
+ def get_used_number(self, obj):
+ return get_user_storage_used(obj.app_storage.all())
# secret_key = serializers.SerializerMethodField()
# 加上此选项,会导致update获取不到值
@@ -519,3 +556,27 @@ class PersonalConfigSerializer(serializers.ModelSerializer):
def get_title(self, obj):
return getattr(Config, f'{obj.key}_DES')
+
+
+class StorageShareSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = models.StorageShareInfo
+ exclude = ["id", "remote_addr", "user_id", "to_user_id"]
+
+ target_user = serializers.SerializerMethodField()
+ cancel = serializers.SerializerMethodField()
+ used = serializers.SerializerMethodField()
+ status_display = serializers.CharField(source='get_status_display')
+ storage_name = serializers.CharField(source='storage_id.name')
+
+ def get_target_user(self, obj):
+ user_obj = obj.user_id
+ if self.get_cancel(obj):
+ user_obj = obj.to_user_id
+ return {'uid': user_obj.uid, 'name': user_obj.first_name}
+
+ def get_used(self, obj):
+ return obj.to_user_id.storage_id == obj.storage_id.id and obj.status == 1
+
+ def get_cancel(self, obj):
+ return self.context.get('user_obj').pk == obj.user_id.pk
diff --git a/fir_ser/api/utils/utils.py b/fir_ser/api/utils/utils.py
index 78b7607..3d27f12 100644
--- a/fir_ser/api/utils/utils.py
+++ b/fir_ser/api/utils/utils.py
@@ -120,25 +120,28 @@ def migrating_storage_file_data(user_obj, filename, new_storage_obj, clean_old_d
if old_storage_obj.get_storage_type() == 3:
if new_storage_obj.get_storage_type() == 3:
# 都是本地存储,无需操作
- pass
+ return True
else:
# 本地向云存储上传,并删除本地数据
- new_storage_obj.upload_file(local_file_full_path)
+ res = new_storage_obj.upload_file(local_file_full_path)
if clean_old_data:
- delete_local_files(filename)
+ return delete_local_files(filename)
+ return res
else:
if new_storage_obj.get_storage_type() == 3:
# 云存储下载 本地,并删除云存储
if download_files_form_oss(old_storage_obj, local_file_full_path, True):
if clean_old_data:
- old_storage_obj.delete_file(filename)
+ return old_storage_obj.delete_file(filename)
+ return True
else:
# 云存储互传,先下载本地,然后上传新云存储,删除本地和老云存储
if download_files_form_oss(old_storage_obj, local_file_full_path, True):
- new_storage_obj.upload_file(local_file_full_path)
- delete_local_files(filename)
+ res1 = new_storage_obj.upload_file(local_file_full_path)
+ res2 = delete_local_files(filename)
if clean_old_data:
- old_storage_obj.delete_file(filename)
+ return old_storage_obj.delete_file(filename)
+ return res1 and res2
def migrating_storage_data(user_obj, new_storage_obj, clean_old_data):
diff --git a/fir_ser/api/views/apps.py b/fir_ser/api/views/apps.py
index 3a48145..cd548fe 100644
--- a/fir_ser/api/views/apps.py
+++ b/fir_ser/api/views/apps.py
@@ -14,12 +14,14 @@ from rest_framework.views import APIView
from api.base_views import app_delete
from api.models import Apps, AppReleaseInfo, UserInfo, AppScreenShot, AppDownloadToken
-from api.utils.modelutils import get_user_domain_name, get_app_domain_name
+from api.utils.apputils import bytes2human
+from api.utils.modelutils import get_user_domain_name, get_app_domain_name, get_app_storage_used
from api.utils.response import BaseResponse
from api.utils.serializer import AppsSerializer, AppReleaseSerializer, AppsListSerializer, AppsQrListSerializer, \
AppDownloadTokenSerializer
from api.utils.signalutils import run_delete_app_signal
from api.utils.utils import delete_local_files, delete_app_screenshots_files
+from common.base.magic import MagicCacheData
from common.cache.state import MigrateStorageState
from common.core.auth import ExpiringTokenAuthentication
from common.utils.caches import del_cache_response_by_short, get_app_today_download_times, del_cache_by_delete_app
@@ -63,7 +65,7 @@ class AppsPageNumber(PageNumberPagination):
page_size = 20 # 每页显示多少条
page_size_query_param = 'size' # URL中每页显示条数的参数
page_query_param = 'page' # URL中页码的参数
- max_page_size = None # 最大页码数限制
+ max_page_size = 100 # 最大页码数限制
class AppsView(APIView):
@@ -120,6 +122,7 @@ class AppInfoView(APIView):
if app_obj:
app_serializer = AppsSerializer(app_obj, context={"storage": Storage(request.user)})
res.data = app_serializer.data
+ res.data['storage_used'] = bytes2human(get_app_storage_used(app_obj))
else:
logger.error(f"app_id:{app_id} is not found in user:{request.user}")
res.msg = "未找到该应用"
@@ -268,10 +271,13 @@ class AppReleaseInfoView(APIView):
has_combo.has_combo = None
has_combo.save(update_fields=["has_combo"])
del_cache_response_by_short(has_combo.app_id)
+ MagicCacheData.invalid_cache(app_obj.app_id)
app_obj.delete()
else:
pass
- del_cache_response_by_short(app_obj.app_id)
+ if app_obj:
+ MagicCacheData.invalid_cache(app_obj.app_id)
+ del_cache_response_by_short(app_obj.app_id)
return Response(res.dict)
diff --git a/fir_ser/api/views/storage.py b/fir_ser/api/views/storage.py
index a143576..3243dc8 100644
--- a/fir_ser/api/views/storage.py
+++ b/fir_ser/api/views/storage.py
@@ -6,43 +6,101 @@
import logging
+from django.db.models import Q
from rest_framework.response import Response
from rest_framework.views import APIView
from api.base_views import storage_change, app_delete
-from api.models import AppStorage, Apps
+from api.models import AppStorage, Apps, StorageShareInfo, UserInfo
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.response import BaseResponse
-from api.utils.serializer import StorageSerializer
+from api.utils.serializer import StorageSerializer, StorageShareSerializer
from api.utils.utils import upload_oss_default_head_img
-from common.base.baseutils import get_choices_dict
+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.core.auth import ExpiringTokenAuthentication, StoragePermission
+from common.core.sysconfig import Config
+from common.utils.caches import del_cache_storage
logger = logging.getLogger(__name__)
+def get_storage_group(request, res, storage_type, is_default=True):
+ use_storage_obj = request.user.storage
+ if use_storage_obj:
+ res.storage = use_storage_obj.id
+ else:
+ res.storage = -1 # 默认存储
+ storage_group = []
+ if is_default:
+ storage_group.append({'group_name': '默认存储',
+ 'storages': [{'id': -1, 'name': '默认存储', 'used': True if res.storage == -1 else False}]})
+ for s_type in storage_type:
+ s_group = {'group_name': get_choices_name_from_key(AppStorage.storage_choices, s_type), 'storages': []}
+
+ for obj in AppStorage.objects.filter(storage_type=s_type).filter(
+ Q(user_id=request.user) | Q(storageshareinfo__to_user_id=request.user,
+ storageshareinfo__status=1)).values('id', 'name', 'user_id__first_name',
+ 'user_id__uid').distinct():
+ if obj['user_id__uid'] != request.user.uid:
+ ext = {'username': obj['user_id__first_name']}
+ else:
+ share_count = StorageShareInfo.objects.filter(storage_id=obj['id'], status=1).count()
+ share_used = StorageShareInfo.objects.filter(storage_id=obj['id'], status=1,
+ to_user_id__storage__id=obj['id']).count()
+ ext = {'share_count': share_count, 'share_used': share_used}
+ s_info = {'id': obj['id'], 'name': obj['name'], 'used': False, 'ext': ext}
+ if request.user.storage and request.user.storage.id == obj['id']:
+ s_info['used'] = True
+ if not is_default and obj['user_id__uid'] != request.user.uid:
+ continue
+ else:
+ s_group['storages'].append(s_info)
+ storage_group.append(s_group)
+ return storage_group
+
+
class StorageView(APIView):
authentication_classes = [ExpiringTokenAuthentication, ]
permission_classes = [StoragePermission, ]
+ storage_type = [2]
def get(self, request):
res = BaseResponse()
res.storage_list = []
storage_org_list = list(AppStorage.storage_choices)
for storage_t in storage_org_list:
- if storage_t[0] not in [0, 3]:
+ if storage_t[0] in self.storage_type:
res.storage_list.append({'id': storage_t[0], 'name': storage_t[1]})
+ res.endpoint_list = get_choices_dict([(x, x) for x in Config.STORAGE_ALLOW_ENDPOINT])
act = request.query_params.get("act", None)
+ is_default = request.query_params.get("is_default", 'true')
+ pk = request.query_params.get("pk", None)
+ keysearch = request.query_params.get("keysearch", None)
if act == 'storage_type':
res.download_auth_type_choices = get_choices_dict(AppStorage.download_auth_type_choices)
+ res.default_max_storage_capacity = Config.STORAGE_OSS_CAPACITY
+ return Response(res.dict)
+
+ if act == 'storage_group':
+ res.storage_group_list = get_storage_group(request, res, self.storage_type, is_default == 'true')
return Response(res.dict)
# [1,2] 表示七牛存储和阿里云存储
- storage_obj = AppStorage.objects.filter(user_id=request.user, storage_type__in=[1, 2])
- if storage_obj:
- storage_serializer = StorageSerializer(storage_obj, many=True)
+ storage_queruset = AppStorage.objects.filter(user_id=request.user, storage_type__in=self.storage_type)
+ if pk:
+ storage_queruset = storage_queruset.filter(pk=pk)
+ if keysearch:
+ storage_queruset = storage_queruset.filter(Q(name__contains=keysearch) | Q(bucket_name=keysearch))
+
+ if storage_queruset:
+ page_obj = PageNumber()
+ page_serializer = page_obj.paginate_queryset(queryset=storage_queruset.order_by("-created_time"),
+ request=request,
+ view=self)
+ storage_serializer = StorageSerializer(page_serializer, many=True)
storage_data_lists = storage_serializer.data
storage_lists = {}
for storage_data in storage_data_lists:
@@ -52,6 +110,9 @@ class StorageView(APIView):
storage_lists[storage_type] = []
storage_lists[storage_type].append(storage_data)
res.data = storage_data_lists
+ res.count = storage_queruset.count()
+ else:
+ res.data = []
use_storage_obj = request.user.storage
if use_storage_obj:
@@ -78,11 +139,11 @@ class StorageView(APIView):
res.code = 1005
else:
logger.info(f"user {request.user} add new storage failed")
- res.msg = serializer.errors
+ res.msg = str(serializer.errors)
res.code = 1005
else:
logger.info(f"user {request.user} add new storage failed")
- res.msg = serializer.errors
+ res.msg = str(serializer.errors)
res.code = 1005
return Response(res.dict)
@@ -93,6 +154,20 @@ class StorageView(APIView):
use_storage_id = data.get("use_storage_id", None)
force = data.get("force", None)
if use_storage_id:
+ if use_storage_id != -1:
+ obj = AppStorage.objects.filter(pk=use_storage_id).first()
+ if obj:
+ if obj.user_id != request.user:
+ if not StorageShareInfo.objects.filter(to_user_id=request.user, status=1,
+ storage_id=obj).first():
+ res.code = 1007
+ res.msg = "数据异常,请重试"
+ return Response(res.dict)
+ else:
+ res.code = 1007
+ res.msg = "数据异常,请重试"
+ return Response(res.dict)
+
with MigrateStorageState(request.user.uid) as state:
if state:
if not storage_change(use_storage_id, request.user, force):
@@ -106,10 +181,10 @@ class StorageView(APIView):
storage_id = data.get("id", None)
if storage_id:
- if request.user.storage and storage_id == request.user.storage.id:
- res.msg = '存储正在使用中,无法修改'
- res.code = 1007
- return Response(res.dict)
+ # if request.user.storage and storage_id == request.user.storage.id:
+ # res.msg = '存储正在使用中,无法修改'
+ # res.code = 1007
+ # return Response(res.dict)
storage_obj = AppStorage.objects.filter(id=storage_id, user_id=request.user).first()
storage_obj_bak = AppStorage.objects.filter(id=storage_id, user_id=request.user).first()
serializer = StorageSerializer(instance=storage_obj, data=data, context={'user_obj': request.user},
@@ -120,6 +195,10 @@ class StorageView(APIView):
if upload_oss_default_head_img(request.user, new_storage_obj):
res.msg = serializer.validated_data
logger.info(f"user {request.user} update storage success")
+ del_cache_storage(request.user)
+ for share_obj in StorageShareInfo.objects.filter(user_id=request.user,
+ storage_id=new_storage_obj, status=1).all():
+ del_cache_storage(share_obj.user_id)
else:
storage_obj_bak.save()
logger.error(f"user {request.user} update storage failed")
@@ -127,10 +206,10 @@ class StorageView(APIView):
res.code = 1005
else:
logger.info(f"user {request.user} update storage failed")
- res.msg = serializer.errors
+ res.msg = str(serializer.errors)
res.code = 1005
else:
- res.msg = serializer.errors
+ res.msg = str(serializer.errors)
res.code = 1005
else:
res.msg = '该存储不存在'
@@ -176,3 +255,164 @@ class CleanStorageView(APIView):
res.code = 1007
res.msg = "参数不合法,清理失败"
return Response(res.dict)
+
+
+class ShareStorageView(APIView):
+ authentication_classes = [ExpiringTokenAuthentication, ]
+ permission_classes = [StoragePermission, ]
+
+ def get(self, request):
+ res = BaseResponse()
+ uidsearch = request.query_params.get("uidsearch", None)
+ status = request.query_params.get("operatestatus", '-1')
+ share_list = StorageShareInfo.objects.filter(Q(user_id=request.user) | Q(to_user_id=request.user)).distinct()
+ page_obj = PageNumber()
+ if uidsearch:
+ share_list = share_list.filter(
+ Q(user_id__uid=uidsearch) | Q(to_user_id__uid=uidsearch) | Q(storage_id__name__contains=uidsearch))
+ try:
+ if status != '' and get_choices_name_from_key(StorageShareInfo.status_choices, int(status)):
+ share_list = share_list.filter(status=status)
+ except Exception as e:
+ logger.error(f'status {status} check failed Exception:{e}')
+
+ app_page_serializer = page_obj.paginate_queryset(queryset=share_list.order_by("-created_time"),
+ request=request,
+ view=self)
+ app_serializer = StorageShareSerializer(app_page_serializer, many=True, context={'user_obj': request.user})
+ res.data = app_serializer.data
+ res.count = share_list.count()
+ res.status_choices = get_choices_dict(StorageShareInfo.status_choices)
+ return Response(res.dict)
+
+ def post(self, request):
+ res = BaseResponse()
+ uid = request.data.get('target_uid')
+ sid = request.data.get('target_sid')
+ number = request.data.get('target_number')
+ if uid and number and sid:
+ to_user_obj = UserInfo.objects.filter(uid=uid, is_active=True, supersign_active=True).first()
+ storage_obj = AppStorage.objects.filter(pk=sid, user_id=request.user).first()
+ if to_user_obj and storage_obj:
+ if isinstance(number, int) and 0 < number < 999999999:
+ number = number * 1024 * 1024
+
+ max_storage_capacity = storage_obj.max_storage_capacity
+ storage_capacity = max_storage_capacity if max_storage_capacity else Config.STORAGE_OSS_CAPACITY
+ number = number if number < storage_capacity else storage_capacity
+ user_obj = request.user
+ if user_obj.pk != to_user_obj.pk:
+ try:
+ if True:
+ share_obj = StorageShareInfo.objects.filter(user_id=user_obj, to_user_id=to_user_obj,
+ status=1, storage_id=storage_obj).first()
+ if share_obj:
+ # number += share_obj.number
+ share_obj.number = number if number < storage_capacity else storage_capacity
+ share_obj.remote_addr = get_real_ip_address(request)
+ share_obj.description = f'{user_obj.first_name} 共享给 {to_user_obj.first_name} {share_obj.number} Mb存储空间 '
+ share_obj.save(update_fields=['number', 'remote_addr', 'description'])
+ else:
+ StorageShareInfo.objects.create(user_id=user_obj, to_user_id=to_user_obj,
+ status=1, number=number,
+ storage_id=storage_obj,
+ remote_addr=get_real_ip_address(request),
+ description=f'{user_obj.first_name} 共享给 {to_user_obj.first_name} {number} Mb存储空间')
+ return Response(res.dict)
+ else:
+ res.msg = f'私有存储空间余额不足,当前存储余额最大为 {all_balance}'
+ except Exception as e:
+ res.msg = str(e)
+ else:
+ res.msg = '用户不合法'
+ else:
+ res.msg = '共享数量异常'
+ else:
+ res.msg = '用户信息不存在'
+ else:
+ res.msg = '参数有误'
+ res.code = 1003
+ return Response(res.dict)
+
+ def put(self, request):
+ res = BaseResponse()
+ uid = request.data.get('uid')
+ sid = request.data.get('sid')
+ if uid:
+ user_obj = UserInfo.objects.filter(uid=uid, is_active=True, supersign_active=True).first()
+ if user_obj:
+ share_obj = StorageShareInfo.objects.filter(user_id=request.user, to_user_id__uid=uid, status=1).all()
+ info_list = []
+ number = 0
+ for obj in share_obj:
+ storage_obj = obj.storage_id
+ info_list.append(
+ {
+ 'storage_name': storage_obj.name, 'number': obj.number,
+ 'storage_id': storage_obj.pk, 'storage_access_key': storage_obj.access_key
+ })
+ number += obj.number
+ res.data = {'uid': user_obj.uid, 'name': user_obj.first_name, "info_list": info_list, "number": number}
+ else:
+ res.msg = '用户信息不存在'
+ res.code = 1003
+ else:
+ res.msg = '参数有误'
+ res.code = 1003
+ return Response(res.dict)
+
+ def delete(self, request):
+ res = BaseResponse()
+ uid = request.query_params.get("uid", None)
+ status = request.query_params.get("status", None)
+ number = request.query_params.get("number", None)
+ sid = request.query_params.get("sid", None)
+ if MigrateStorageState(request.user.uid).get_state():
+ res.code = 1008
+ res.msg = "数据迁移中,无法处理该操作"
+ return Response(res.dict)
+ if uid and status and number and sid:
+ share_obj = StorageShareInfo.objects.filter(user_id=request.user, to_user_id__uid=uid, status=status,
+ number=abs(int(number)), storage_id_id=sid).first()
+ if share_obj:
+ target_user_obj = UserInfo.objects.filter(uid=uid).first()
+ if target_user_obj and target_user_obj.storage:
+ if target_user_obj.storage.id == share_obj.storage_id.id:
+ app_obj_lists = Apps.objects.filter(user_id=target_user_obj).all()
+ for app_obj in app_obj_lists:
+ res = app_delete(app_obj)
+ logger.warning(f'clean share storage {target_user_obj} {app_obj} {res}')
+ target_user_obj.storage = None
+ target_user_obj.save(update_fields=['storage'])
+ share_obj.status = 2
+ share_obj.save(update_fields=['status'])
+ else:
+ res.code = 1003
+ res.msg = '共享记录不存在'
+ return Response(res.dict)
+
+
+class StorageConfigView(APIView):
+ authentication_classes = [ExpiringTokenAuthentication, ]
+ permission_classes = [StoragePermission, ]
+
+ def get(self, request):
+ res = BaseResponse()
+ res.data = {
+ 'user_max_storage_capacity': get_user_storage_capacity(request.user),
+ 'user_used_storage_capacity': get_user_storage_used(request.user),
+ 'user_history_limit': UserInfo.objects.filter(pk=request.user.pk).first().history_release_limit,
+ }
+ return Response(res.dict)
+
+ def put(self, request):
+ history_release_limit = request.data.get('user_history_limit')
+ if history_release_limit:
+ try:
+ history_release_limit = int(history_release_limit)
+ except Exception as e:
+ logger.warning(f"update user history_release_limit failed Exception:{e}")
+ history_release_limit = request.user.history_release_limit
+
+ UserInfo.objects.filter(pk=request.user.pk).update(history_release_limit=abs(history_release_limit))
+ return self.get(request)
diff --git a/fir_ser/api/views/uploads.py b/fir_ser/api/views/uploads.py
index 49ea180..b81138a 100644
--- a/fir_ser/api/views/uploads.py
+++ b/fir_ser/api/views/uploads.py
@@ -12,7 +12,8 @@ from rest_framework.views import APIView
from api.models import Apps, AppReleaseInfo, UserInfo, AppScreenShot, CertificationInfo, UserAdDisplayInfo
from api.utils.apputils import get_random_short, save_app_infos
-from api.utils.modelutils import get_app_download_uri, check_bundle_id_legal
+from api.utils.modelutils import get_app_download_uri, check_bundle_id_legal, get_user_storage_used, \
+ get_user_storage_capacity
from api.utils.response import BaseResponse
from api.utils.signalutils import run_signal_resign_utils
from common.base.baseutils import make_app_uuid, make_from_user_uuid
@@ -21,7 +22,7 @@ from common.core.auth import ExpiringTokenAuthentication
from common.core.sysconfig import Config
from common.utils.caches import upload_file_tmp_name, del_cache_response_by_short
from common.utils.storage import Storage
-from common.utils.token import verify_token
+from common.utils.token import verify_token, make_token
from fir_ser import settings
logger = logging.getLogger(__name__)
@@ -40,6 +41,7 @@ class AppAnalyseView(APIView):
# 1.接收 bundelid ,返回随机应用名称和短连接
bundle_id = request.data.get("bundleid", None)
app_type = request.data.get("type", None)
+ filesize = request.data.get("filesize", 0)
if bundle_id:
if check_bundle_id_legal(request.user.uid, bundle_id):
res.code = 1004
@@ -52,6 +54,17 @@ class AppAnalyseView(APIView):
return Response(res.dict)
if bundle_id and app_type:
+ storage_used = get_user_storage_used(request.user)
+ try:
+ filesize = abs(int(filesize))
+ except Exception as e:
+ logger.warning(f"filesize check failed {request.data} Exception:{e}")
+ filesize = 0
+ if filesize + storage_used > get_user_storage_capacity(request.user):
+ res.code = 1008
+ res.msg = "存储空间不足,请升级存储空间或清理无用的历史版本数据来释放空间"
+ return Response(res.dict)
+
ap = 'apk'
if app_type.lower() == 'iOS'.lower():
ap = 'ipa'
@@ -94,7 +107,8 @@ class AppAnalyseView(APIView):
"storage": storage_type,
"is_new": is_new,
"binary_url": binary_url,
- "enable_sign": enable_sign
+ "enable_sign": enable_sign,
+ "access_token": make_token(app_uuid, time_limit=60 * 5, key='update_app_info', force_new=True)
}
if storage_type not in [1, 2]:
res.data['domain_name'] = Config.FILE_UPLOAD_DOMAIN
@@ -135,9 +149,21 @@ class AppAnalyseView(APIView):
png_tmp_filename = data.get("png_key")
png_new_filename = data.get("png_key").strip(settings.FILE_UPLOAD_TMP_KEY)
- logger.info(f"user {request.user} create or update app {data.get('bundleid')} data:{data}")
- if save_app_infos(app_new_filename, request.user, app_info,
- data.get("bundleid"), png_new_filename, data.get("short"), data.get('filesize'),
+
+ bundle_id = data.get("bundleid", "")
+ if not bundle_id:
+ raise KeyError('bundle_id not exist')
+ app_uuid = make_app_uuid(request.user, bundle_id + app_new_filename.split(".")[1])
+ if not verify_token(data.get('access_token', ''), app_uuid, False):
+ res.msg = '授权过期,请重试'
+ res.code = 1004
+ storage.delete_file(app_tmp_filename)
+ storage.delete_file(png_tmp_filename)
+ return Response(res.dict)
+
+ logger.info(f"user {request.user} create or update app {bundle_id} data:{data}")
+ if save_app_infos(app_tmp_filename, app_new_filename, request.user, app_info,
+ bundle_id, png_new_filename, data.get("short"), data.get('filesize'),
data.get('enable_sign')):
# 需要将tmp 文件修改为正式文件
storage.rename_file(app_tmp_filename, app_new_filename)
@@ -274,7 +300,6 @@ class UploadView(APIView):
res.msg = "文件不存在"
for file_obj in files:
try:
-
app_type = file_obj.name.split(".")[-1]
if app_type == "tmp":
app_type = file_obj.name.split(".")[-2]
@@ -287,6 +312,12 @@ class UploadView(APIView):
res.msg = "错误的类型"
return Response(res.dict)
+ storage_used = get_user_storage_used(request.user)
+ if file_obj.size + storage_used > get_user_storage_capacity(request.user):
+ res.code = 1008
+ res.msg = "存储空间不足,请升级存储空间或清理无用的历史版本数据来释放空间"
+ return Response(res.dict)
+
random_file_name = make_from_user_uuid(request.user.uid)
local_file = os.path.join(settings.MEDIA_ROOT, cert_info.get("upload_key", random_file_name))
# 读取传入的文件
diff --git a/fir_ser/common/base/magic.py b/fir_ser/common/base/magic.py
index f87667a..3461e41 100644
--- a/fir_ser/common/base/magic.py
+++ b/fir_ser/common/base/magic.py
@@ -140,9 +140,9 @@ def magic_call_in_times(call_time=24 * 3600, call_limit=6, key=None):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
- cache_key = func.__name__
+ cache_key = f'magic_call_in_times_{func.__name__}'
if key:
- cache_key = f'magic_call_in_times_{cache_key}_{key(*args, **kwargs)}'
+ cache_key = f'{cache_key}_{key(*args, **kwargs)}'
cache_data = cache.get(cache_key)
if cache_data:
if cache_data > call_limit:
@@ -169,3 +169,43 @@ def magic_call_in_times(call_time=24 * 3600, call_limit=6, key=None):
return wrapper
return decorator
+
+
+class MagicCacheData(object):
+ @staticmethod
+ def make_cache(cache_time=60 * 10, key=None):
+ def decorator(func):
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ cache_key = f'magic_cache_data'
+ if key:
+ cache_key = f'{cache_key}_{key(*args, **kwargs)}'
+ else:
+ cache_key = f'{cache_key}_{func.__name__}'
+
+ res = cache.get(cache_key)
+ if res:
+ logger.info(f"exec {func} finished. cache_key:{cache_key} cache data exist result:{res}")
+ return res
+ else:
+ start_time = time.time()
+ try:
+ res = func(*args, **kwargs)
+ cache.set(cache_key, res, cache_time)
+ logger.info(
+ f"exec {func} finished. time:{time.time() - start_time} cache_key:{cache_key} result:{res}")
+ except Exception as e:
+ logger.info(
+ f"exec {func} failed. time:{time.time() - start_time} cache_key:{cache_key} Exception:{e}")
+
+ return res
+
+ return wrapper
+
+ return decorator
+
+ @staticmethod
+ def invalid_cache(key):
+ cache_key = f'magic_cache_data_{key}'
+ res = cache.delete(cache_key)
+ logger.warning(f"invalid_cache cache_key:{cache_key} result:{res}")
diff --git a/fir_ser/common/core/sysconfig.py b/fir_ser/common/core/sysconfig.py
index 6bff803..5631696 100644
--- a/fir_ser/common/core/sysconfig.py
+++ b/fir_ser/common/core/sysconfig.py
@@ -15,7 +15,7 @@ from rest_framework import serializers
from api.models import SystemConfig, UserPersonalConfig
from common.cache.storage import UserSystemConfigCache
from config import BASECONF, THIRDLOGINCONF, AUTHCONF, IPACONF, DOWNLOADTIMESCONF, PAYCONF, STORAGEKEYCONF, SENDERCONF, \
- APPLEDEVELOPERCONF, DOMAINCONF, USERPERSONALCONFIGKEY, CONFIGDESCRIPTION
+ APPLEDEVELOPERCONF, DOMAINCONF, USERPERSONALCONFIGKEY, CONFIGDESCRIPTION, OSSSTORAGECONF
logger = logging.getLogger(__name__)
@@ -293,6 +293,27 @@ class UserDownloadTimesCache(ConfigCacheBase):
def AUTH_USER_GIVE_DOWNLOAD_TIMES(self):
return super().get_value('AUTH_USER_GIVE_DOWNLOAD_TIMES', DOWNLOADTIMESCONF.AUTH_USER_GIVE_DOWNLOAD_TIMES)
+ @property
+ def SIGN_EXTRA_MULTIPLE(self):
+ return super().get_value('SIGN_EXTRA_MULTIPLE', DOWNLOADTIMESCONF.SIGN_EXTRA_MULTIPLE)
+
+
+class OssStorageConfCache(ConfigCacheBase):
+ def __init__(self, *args, **kwargs):
+ super(OssStorageConfCache, self).__init__(*args, **kwargs)
+
+ @property
+ def STORAGE_ALLOW_ENDPOINT(self):
+ return super().get_value('STORAGE_ALLOW_ENDPOINT', OSSSTORAGECONF.STORAGE_ALLOW_ENDPOINT)
+
+ @property
+ def STORAGE_FREE_CAPACITY(self):
+ return super().get_value('STORAGE_FREE_CAPACITY', OSSSTORAGECONF.STORAGE_FREE_CAPACITY)
+
+ @property
+ def STORAGE_OSS_CAPACITY(self):
+ return super().get_value('STORAGE_OSS_CAPACITY', OSSSTORAGECONF.STORAGE_OSS_CAPACITY)
+
class PayConfCache(ConfigCacheBase):
def __init__(self, *args, **kwargs):
@@ -417,7 +438,7 @@ class ConfigDescriptionCache(ConfigCacheBase):
class ConfigCache(BaseConfCache, DomainConfCache, IpaConfCache, AuthConfCache, UserDownloadTimesCache, GeeTestConfCache,
PayConfCache, ThirdStorageConfCache, ThirdPartConfCache, AppleDeveloperConfCache,
- UserPersonalConfKeyCache, ConfigDescriptionCache, WechatConfCache):
+ UserPersonalConfKeyCache, ConfigDescriptionCache, WechatConfCache, OssStorageConfCache):
def __init__(self, *args, **kwargs):
super(ConfigCache, self).__init__(*args, **kwargs)
diff --git a/fir_ser/common/libs/storage/aliyunApi.py b/fir_ser/common/libs/storage/aliyunApi.py
index 9a26de0..e16407c 100644
--- a/fir_ser/common/libs/storage/aliyunApi.py
+++ b/fir_ser/common/libs/storage/aliyunApi.py
@@ -223,6 +223,15 @@ class AliYunOss(object):
os.makedirs(dir_path)
return self.bucket.get_object_to_file(name, local_file_full_path)
+ def get_file_info(self, name):
+ result = self.bucket.head_object(name)
+ base_info = {}
+ if result.content_length:
+ base_info['content_length'] = result.content_length
+ if result.last_modified:
+ base_info['last_modified'] = result.last_modified
+ return base_info
+
def multipart_upload_file(self, local_file_full_path):
if os.path.isfile(local_file_full_path):
total_size = os.path.getsize(local_file_full_path)
diff --git a/fir_ser/common/libs/storage/localApi.py b/fir_ser/common/libs/storage/localApi.py
index caaa542..7a0b4d2 100644
--- a/fir_ser/common/libs/storage/localApi.py
+++ b/fir_ser/common/libs/storage/localApi.py
@@ -72,3 +72,13 @@ class LocalStorage(object):
if os.path.isfile(local_file_full_path):
return True
return False
+
+ @staticmethod
+ def get_file_info(name):
+ file_path = os.path.join(settings.MEDIA_ROOT, name)
+ base_info = {}
+ if os.path.isfile(file_path):
+ stat_info = os.stat(file_path)
+ base_info['content_length'] = stat_info.st_size
+ base_info['last_modified'] = stat_info.st_mtime
+ return base_info
diff --git a/fir_ser/common/libs/storage/qiniuApi.py b/fir_ser/common/libs/storage/qiniuApi.py
index 3264761..d316603 100644
--- a/fir_ser/common/libs/storage/qiniuApi.py
+++ b/fir_ser/common/libs/storage/qiniuApi.py
@@ -85,3 +85,13 @@ class QiNiuOss(object):
except Exception as e:
logger.error(f"check download file and move file {local_file_full_path} failed Exception {e}")
return False
+
+ def get_file_info(self, name):
+ bucket = BucketManager(self.qiniu_obj)
+ result = bucket.stat(self.bucket_name, name)
+ base_info = {}
+ if result.get('fsize', 0):
+ base_info['content_length'] = result.get('fsize', 0)
+ if result.get('putTime', 0):
+ base_info['last_modified'] = result.get('putTime', 0) // 10000000
+ return base_info
diff --git a/fir_ser/common/notify/notify.py b/fir_ser/common/notify/notify.py
index 6322c23..d189d72 100644
--- a/fir_ser/common/notify/notify.py
+++ b/fir_ser/common/notify/notify.py
@@ -42,11 +42,7 @@ def pay_success_notify(user_obj, order_obj):
notify_by_email(user_obj, message_type, get_pay_success_html_content(user_obj, order_obj))
-def get_magic_call_key(*args, **kwargs):
- return args[0].uid
-
-
-@magic_call_in_times(key=get_magic_call_key)
+@magic_call_in_times(key=lambda *x: x[0].uid)
def sign_failed_notify(user_obj, developer_obj, app_obj):
"""
3, '应用签名失败'
@@ -65,7 +61,7 @@ def sign_failed_notify(user_obj, developer_obj, app_obj):
notify_by_email(user_obj, message_type, get_sign_failed_html_content(user_obj, app_obj, developer_obj, now_time))
-@magic_call_in_times(key=get_magic_call_key)
+@magic_call_in_times(key=lambda *x: x[0].uid)
def sign_unavailable_developer_notify(user_obj, app_obj):
"""
3, '应用签名失败'
@@ -82,7 +78,7 @@ def sign_unavailable_developer_notify(user_obj, app_obj):
notify_by_email(user_obj, message_type, get_sign_unavailable_developer_html_content(user_obj, app_obj, now_time))
-@magic_call_in_times(key=get_magic_call_key)
+@magic_call_in_times(key=lambda *x: x[0].uid)
def sign_app_over_limit_notify(user_obj, app_obj, used_num, limit_number):
"""
0, '签名余额不足'
@@ -131,7 +127,7 @@ def check_developer_status_notify(user_obj, developer_obj_list, developer_used_i
yesterday_used_number))
-@magic_call_in_times(key=get_magic_call_key)
+@magic_call_in_times(key=lambda *x: x[0].uid)
def download_times_not_enough(user_obj, msg):
"""
1, '下载次数不足'
diff --git a/fir_ser/common/utils/caches.py b/fir_ser/common/utils/caches.py
index a3d5672..2ad60aa 100644
--- a/fir_ser/common/utils/caches.py
+++ b/fir_ser/common/utils/caches.py
@@ -242,7 +242,7 @@ def consume_user_download_times_by_app_obj(app_obj):
user_id = app_obj.user_id_id
auth_status = get_user_cert_auth_status(user_id)
amount = get_app_d_count_by_app_id(app_obj.app_id)
- if consume_user_download_times(user_id, app_obj.app_id, amount, auth_status):
+ if consume_user_download_times(user_id, app_obj.app_id, int(amount * Config.SIGN_EXTRA_MULTIPLE), auth_status):
return False
return True
diff --git a/fir_ser/common/utils/storage.py b/fir_ser/common/utils/storage.py
index 4dfc6e5..eeeefae 100644
--- a/fir_ser/common/utils/storage.py
+++ b/fir_ser/common/utils/storage.py
@@ -110,6 +110,13 @@ class Storage(object):
except Exception as e:
logger.error(f"rename {old_filename} to {new_filename} failed Exception {e}")
+ def get_file_info(self, name):
+ if self.storage:
+ try:
+ return self.storage.get_file_info(name)
+ except Exception as e:
+ logger.error(f"get file info {name} failed Exception {e}")
+
def upload_file(self, local_file_full_path):
if self.storage:
try:
diff --git a/fir_ser/config.py b/fir_ser/config.py
index aa93196..80f8b96 100644
--- a/fir_ser/config.py
+++ b/fir_ser/config.py
@@ -390,6 +390,7 @@ bIX1aWjPxirQX9mzaL3oEQI=
class DOWNLOADTIMESCONF(object):
+ SIGN_EXTRA_MULTIPLE = 2 # 超级签名消耗额外倍数,超级签名需要占用的服务大量资源
USER_FREE_DOWNLOAD_TIMES = 5
AUTH_USER_FREE_DOWNLOAD_TIMES = 10
NEW_USER_GIVE_DOWNLOAD_TIMES = 100
@@ -459,3 +460,12 @@ class CONFIGDESCRIPTION(object):
class USERPERSONALCONFIGKEY(object):
DEVELOPER_STATUS_CONFIG = ['DEVELOPER_WAIT_ABNORMAL_STATE', 'DEVELOPER_WAIT_ABNORMAL_DEVICE',
'DEVELOPER_ABNORMAL_DEVICE_WRITE']
+
+
+class OSSSTORAGECONF(object):
+ STORAGE_FREE_CAPACITY = 2048 * 1024 * 1024 # 单位byte 2G
+ STORAGE_OSS_CAPACITY = 1024 * 1024 * 1024 * 1024 # 单位byte 1T
+ STORAGE_ALLOW_ENDPOINT = [
+ 'oss-cn-beijing-internal.aliyuncs.com',
+ 'oss-cn-zhangjiakou-internal.aliyuncs.com'
+ ]
diff --git a/fir_ser/xsign/utils/supersignutils.py b/fir_ser/xsign/utils/supersignutils.py
index 117f2b5..bec1b8e 100644
--- a/fir_ser/xsign/utils/supersignutils.py
+++ b/fir_ser/xsign/utils/supersignutils.py
@@ -21,7 +21,7 @@ from api.utils.response import BaseResponse
from api.utils.utils import delete_local_files, download_files_form_oss
from common.base.baseutils import file_format_path, delete_app_profile_file, get_profile_full_path, format_apple_date, \
get_format_time, make_app_uuid, make_from_user_uuid, AesBaseCrypt
-from common.base.magic import run_function_by_locker, call_function_try_attempts, magic_wrapper
+from common.base.magic import run_function_by_locker, call_function_try_attempts, magic_wrapper, MagicCacheData
from common.cache.state import CleanErrorBundleIdSignDataState
from common.cache.storage import RedisCacheBase
from common.constants import DeviceStatus, AppleDeveloperStatus, SignStatus
@@ -615,6 +615,7 @@ class IosUtils(object):
sign_status=SignStatus.PROFILE_DOWNLOAD_COMPLETE).update(
sign_status=SignStatus.SIGNATURE_PACKAGE_COMPLETE)
del_cache_response_by_short(app_obj.app_id)
+ MagicCacheData.invalid_cache(app_obj.app_id)
return True
def check_sign_permission(self):
diff --git a/fir_ser/xsign/views/supersign.py b/fir_ser/xsign/views/supersign.py
index 9f859d0..b1c91c5 100644
--- a/fir_ser/xsign/views/supersign.py
+++ b/fir_ser/xsign/views/supersign.py
@@ -656,12 +656,17 @@ class DeviceTransferBillView(APIView):
def get(self, request):
res = BaseResponse()
uidsearch = request.query_params.get("uidsearch", None)
-
+ status = request.query_params.get("operatestatus", '-1')
user_used_list = IosDeveloperBill.objects.filter(
Q(user_id=request.user) | Q(to_user_id=request.user)).distinct()
page_obj = PageNumber()
if uidsearch:
user_used_list = user_used_list.filter(Q(user_id__uid=uidsearch) | Q(to_user_id__uid=uidsearch))
+ try:
+ if status != '' and get_choices_name_from_key(IosDeveloperBill.status_choices, int(status)):
+ user_used_list = user_used_list.filter(status=status)
+ except Exception as e:
+ logger.error(f'status {status} check failed Exception:{e}')
app_page_serializer = page_obj.paginate_queryset(queryset=user_used_list.order_by("-created_time"),
request=request,
@@ -674,6 +679,7 @@ class DeviceTransferBillView(APIView):
'used_balance': get_user_public_used_sign_num(request.user),
'all_balance': get_user_public_sign_num(request.user)
}
+ res.status_choices = get_choices_dict(IosDeveloperBill.status_choices)
return Response(res.dict)
def post(self, request):
@@ -726,6 +732,24 @@ class DeviceTransferBillView(APIView):
uid = request.data.get("uid", None)
status = request.data.get("status", None)
number = request.data.get("number", None)
+ act = request.data.get("act", '')
+ if act == 'check':
+ if uid:
+ user_obj = UserInfo.objects.filter(uid=uid, is_active=True, supersign_active=True).first()
+ if user_obj:
+ bill_obj = IosDeveloperBill.objects.filter(user_id=request.user, to_user_id__uid=uid,
+ status=2).first()
+ number = 0
+ if bill_obj:
+ number = bill_obj.number
+ res.data = {'uid': user_obj.uid, 'name': user_obj.first_name, "number": number}
+ else:
+ res.msg = '用户信息不存在'
+ res.code = 1003
+ else:
+ res.msg = '参数有误'
+ res.code = 1003
+ return Response(res.dict)
if uid and status and number:
if check_uid_has_relevant(uid, request.user.uid):