diff --git a/fir_client/package.json b/fir_client/package.json
index f37e808..975839b 100644
--- a/fir_client/package.json
+++ b/fir_client/package.json
@@ -20,6 +20,7 @@
"vue": "^2.6.10",
"vue-clipboard2": "^0.3.1",
"vue-cookies": "^1.7.0",
+ "vue-cropper": "^0.5.7",
"vue-lazyload": "^1.3.3",
"vue-qr": "^2.3.0",
"vue-router": "^3.1.3",
diff --git a/fir_client/src/components/FirDownload.vue b/fir_client/src/components/FirDownload.vue
index ff91dfa..683249e 100644
--- a/fir_client/src/components/FirDownload.vue
+++ b/fir_client/src/components/FirDownload.vue
@@ -218,10 +218,18 @@
若安装异常,请复制UDID,发送给管理员
-
@@ -308,6 +316,7 @@
timer: '',
gomobile: true,
mobileprovision: '',
+ ad_info: {ad_pic: '', ad_uri: ''}
}
},
beforeDestroy() {
@@ -475,7 +484,7 @@
})
}
},
- auto_redircet_url(domain_name) {
+ auto_redirect_url(domain_name) {
if (domain_name) {
let nurl = location.href.split("//")[1].split("/");
const user_hostname = domain_name.split("//");
@@ -503,9 +512,12 @@
}
getShortAppinfo(data => {
if (data.code === 1000) {
- if (!this.auto_redircet_url(data.domain_name)) {
+ if (!this.auto_redirect_url(data.domain_name)) {
return;
}
+ if (data.ad && data.ad.ad_uri) {
+ this.ad_info = data.ad;
+ }
this.udid = data.udid;
if (!data.data.master_release.release_id) {
this.$message({
@@ -713,6 +725,22 @@
diff --git a/fir_client/src/restful/index.js b/fir_client/src/restful/index.js
index 948d3cd..8e6b81e 100644
--- a/fir_client/src/restful/index.js
+++ b/fir_client/src/restful/index.js
@@ -677,3 +677,18 @@ export function domaininfo(callBack, params, load = true) {
true
);
}
+
+/**自定义广告 */
+export function advertinfo(callBack, params, load = true) {
+ getData(
+ params.methods,
+ USERSEVER + '/advert',
+ params.data,
+ data => {
+ callBack(data);
+ },
+ load,
+ true,
+ true
+ );
+}
diff --git a/fir_client/src/router/index.js b/fir_client/src/router/index.js
index b4fbbd5..61dfb89 100644
--- a/fir_client/src/router/index.js
+++ b/fir_client/src/router/index.js
@@ -158,6 +158,12 @@ const router = new VueRouter({
meta: {label: '绑定域名详情'},
component: () => import("@/components/user/FirUserDomain"),
},
+ {
+ path: 'advert',
+ name: 'FirUserAdvert',
+ meta: {label: '自定义广告'},
+ component: () => import("@/components/user/FirUserAdvert"),
+ },
{
path: 'supersign-help',
name: 'FirSuperSignHelp',
diff --git a/fir_ser/api/admin.py b/fir_ser/api/admin.py
index 0a8d083..e9fffef 100644
--- a/fir_ser/api/admin.py
+++ b/fir_ser/api/admin.py
@@ -18,3 +18,4 @@ admin.site.register(Price)
admin.site.register(Order)
admin.site.register(DomainCnameInfo)
admin.site.register(UserDomainInfo)
+admin.site.register(UserAdDisplayInfo)
diff --git a/fir_ser/api/migrations/0012_useraddisplayinfo.py b/fir_ser/api/migrations/0012_useraddisplayinfo.py
new file mode 100644
index 0000000..9a683c1
--- /dev/null
+++ b/fir_ser/api/migrations/0012_useraddisplayinfo.py
@@ -0,0 +1,32 @@
+# Generated by Django 3.2.3 on 2021-10-14 14:35
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('api', '0011_appudid_is_download'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='UserAdDisplayInfo',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('ad_uri', models.CharField(max_length=256, verbose_name='广告跳转地址')),
+ ('ad_pic', models.CharField(help_text='像素最高80px', max_length=256, verbose_name='广告图片地址')),
+ ('weight', models.IntegerField(default=1, verbose_name='广告展示权重')),
+ ('description', models.TextField(blank=True, default='', null=True, verbose_name='描述信息')),
+ ('is_enable', models.BooleanField(default=False, verbose_name='广告开启状态')),
+ ('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': '用户自定义广告',
+ },
+ ),
+ ]
diff --git a/fir_ser/api/migrations/0013_auto_20211014_1617.py b/fir_ser/api/migrations/0013_auto_20211014_1617.py
new file mode 100644
index 0000000..84ceb6d
--- /dev/null
+++ b/fir_ser/api/migrations/0013_auto_20211014_1617.py
@@ -0,0 +1,22 @@
+# Generated by Django 3.2.3 on 2021-10-14 16:17
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('api', '0012_useraddisplayinfo'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='useraddisplayinfo',
+ name='ad_name',
+ field=models.CharField(default='xx', max_length=256, verbose_name='广告名称'),
+ preserve_default=False,
+ ),
+ migrations.AddIndex(
+ model_name='useraddisplayinfo',
+ index=models.Index(fields=['ad_name', 'user_id'], name='api_useradd_ad_name_4ea10e_idx'),
+ ),
+ ]
diff --git a/fir_ser/api/migrations/0014_alter_useraddisplayinfo_unique_together.py b/fir_ser/api/migrations/0014_alter_useraddisplayinfo_unique_together.py
new file mode 100644
index 0000000..bcada00
--- /dev/null
+++ b/fir_ser/api/migrations/0014_alter_useraddisplayinfo_unique_together.py
@@ -0,0 +1,16 @@
+# Generated by Django 3.2.3 on 2021-10-14 16:18
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('api', '0013_auto_20211014_1617'),
+ ]
+
+ operations = [
+ migrations.AlterUniqueTogether(
+ name='useraddisplayinfo',
+ unique_together={('ad_name', 'user_id')},
+ ),
+ ]
diff --git a/fir_ser/api/models.py b/fir_ser/api/models.py
index 9968efb..1ab2c63 100644
--- a/fir_ser/api/models.py
+++ b/fir_ser/api/models.py
@@ -499,3 +499,23 @@ class UserDomainInfo(models.Model):
def __str__(self):
return "%s-%s-%s" % (self.user_id, self.cname_id, self.domain_name)
+
+
+class UserAdDisplayInfo(models.Model):
+ user_id = models.ForeignKey(to="UserInfo", verbose_name="用户ID", on_delete=models.CASCADE)
+ ad_name = models.CharField(verbose_name="广告名称", max_length=256, null=False, blank=False)
+ ad_uri = models.CharField(verbose_name="广告跳转地址", max_length=256, null=False, blank=False)
+ ad_pic = models.CharField(verbose_name="广告图片地址", max_length=256, null=False, blank=False, help_text="像素最高80px", )
+ weight = models.IntegerField(verbose_name="广告展示权重", default=1)
+ description = models.TextField('描述信息', blank=True, null=True, default='')
+ is_enable = models.BooleanField(default=False, verbose_name="广告开启状态")
+ created_time = models.DateTimeField(auto_now_add=True, verbose_name="广告创建时间")
+
+ class Meta:
+ verbose_name = '用户自定义广告'
+ verbose_name_plural = "用户自定义广告"
+ indexes = [models.Index(fields=['ad_name', 'user_id'])]
+ unique_together = ('ad_name', 'user_id',)
+
+ def __str__(self):
+ return "%s-%s-%s" % (self.user_id, self.description, self.is_enable)
diff --git a/fir_ser/api/urls.py b/fir_ser/api/urls.py
index 76040a9..c4863e3 100644
--- a/fir_ser/api/urls.py
+++ b/fir_ser/api/urls.py
@@ -27,6 +27,7 @@ from api.views.order import PriceView, OrderView, PaySuccess
from api.views.supersign import DeveloperView, SuperSignUsedView, AppUDIDUsedView, SuperSignCertView
from api.views.domain import DomainCnameView, DomainInfoView
from api.views.thirdlogin import ValidWxChatToken, ThirdWxAccount
+from api.views.advert import UserAdInfoView
# router=DefaultRouter()
# router.register("apps", AppsView)
@@ -50,6 +51,7 @@ urlpatterns = [
re_path(r"^udid/(?P\w+)$", IosUDIDView.as_view()),
re_path(r"^task/(?P\w+)$", TaskView.as_view()),
re_path("^analyse$", AppAnalyseView.as_view()),
+ re_path("^advert$", UserAdInfoView.as_view()),
re_path("^supersign/developer$", DeveloperView.as_view()),
re_path("^supersign/devices$", SuperSignUsedView.as_view()),
re_path("^supersign/udid$", AppUDIDUsedView.as_view()),
diff --git a/fir_ser/api/utils/auth.py b/fir_ser/api/utils/auth.py
index 7107488..68352d7 100644
--- a/fir_ser/api/utils/auth.py
+++ b/fir_ser/api/utils/auth.py
@@ -126,3 +126,14 @@ class DownloadQrPermission(BasePermission):
if domain_type == '0' and results[0].role < 2:
return False
return True
+
+
+class UserAdInfoPermission(BasePermission):
+ message = "权限不足"
+
+ def has_permission(self, request, view):
+ results = get_user_from_request_auth(request)
+ if results and results[0]:
+ if results[0].role < 2:
+ return False
+ return True
diff --git a/fir_ser/api/utils/baseutils.py b/fir_ser/api/utils/baseutils.py
index 416eb3c..dcf041a 100644
--- a/fir_ser/api/utils/baseutils.py
+++ b/fir_ser/api/utils/baseutils.py
@@ -8,9 +8,10 @@ import os, re, time
from django.db.models import Count
import datetime
+import random
from django.utils import timezone
from fir_ser.settings import SUPER_SIGN_ROOT
-from api.models import AppReleaseInfo, UserDomainInfo, DomainCnameInfo
+from api.models import AppReleaseInfo, UserDomainInfo, DomainCnameInfo, UserAdDisplayInfo
from api.utils.app.randomstrings import make_app_uuid
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
@@ -259,3 +260,17 @@ def get_real_ip_address(request):
def get_origin_domain_name(request):
meta = request.META
return meta.get('HTTP_ORIGIN', meta.get('HTTP_REFERER', 'http://xxx/xxx')).split('//')[-1].split('/')[0]
+
+
+def ad_random_weight(user_obj):
+ ad_info_list = UserAdDisplayInfo.objects.filter(user_id=user_obj, is_enable=True).order_by('-created_time')
+ total = sum([ad_info.weight for ad_info in ad_info_list]) # 权重求和
+ ra = random.uniform(0, total) # 在0与权重和之前获取一个随机数
+ curr_sum = 0
+ ret = ad_info_list.first()
+ for ad_info in ad_info_list:
+ curr_sum += ad_info.weight # 在遍历中,累加当前权重值
+ if ra <= curr_sum: # 当随机数<=当前权重和时,返回权重key
+ ret = ad_info
+ break
+ return ret
diff --git a/fir_ser/api/utils/serializer.py b/fir_ser/api/utils/serializer.py
index 5330a21..6ec54de 100644
--- a/fir_ser/api/utils/serializer.py
+++ b/fir_ser/api/utils/serializer.py
@@ -23,6 +23,8 @@ def get_download_url_from_context(self, obj, key, url, force_new=False):
storage = Storage(obj.user_id)
elif isinstance(obj, models.AppReleaseInfo):
storage = Storage(obj.app_id.user_id)
+ elif isinstance(obj, models.UserAdDisplayInfo):
+ storage = Storage(obj.user_id)
elif isinstance(obj, models.UserInfo):
storage = Storage(obj)
elif isinstance(obj, models.UserCertificationInfo) or isinstance(obj, models.CertificationInfo):
@@ -652,3 +654,20 @@ class AdminDomainNameSerializer(DomainNameSerializer):
def get_domain_type_choices(self, obj):
return get_choices_dict(obj.domain_type_choices)
+
+
+class UserAdInfoSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = models.UserAdDisplayInfo
+ exclude = ["user_id"]
+
+ ad_pic = serializers.SerializerMethodField()
+
+ def get_ad_pic(self, obj):
+ return get_download_url_from_context(self, obj, '', obj.ad_pic)
+
+
+class AppAdInfoSerializer(UserAdInfoSerializer):
+ class Meta:
+ model = models.UserAdDisplayInfo
+ fields = ["ad_uri", "ad_pic"]
diff --git a/fir_ser/api/utils/utils.py b/fir_ser/api/utils/utils.py
index 0a1329e..4c60de9 100644
--- a/fir_ser/api/utils/utils.py
+++ b/fir_ser/api/utils/utils.py
@@ -7,7 +7,7 @@ import os, datetime, random
import binascii
from fir_ser.settings import SERVER_DOMAIN, CAPTCHA_LENGTH, MEDIA_ROOT, CACHE_KEY_TEMPLATE
from api.models import APPSuperSignUsedInfo, APPToDeveloper, \
- UDIDsyncDeveloper, UserInfo, AppReleaseInfo, AppScreenShot, Token, DeveloperDevicesID
+ UDIDsyncDeveloper, UserInfo, AppReleaseInfo, AppScreenShot, Token, DeveloperDevicesID, UserAdDisplayInfo
from api.utils.storage.caches import get_app_d_count_by_app_id
from api.utils.storage.localApi import LocalStorage
from api.utils.storage.storage import Storage
@@ -180,6 +180,12 @@ def change_storage_and_change_head_img(user_obj, new_storage_obj, clean_old_data
if user_obj.head_img == 'head_img.jpeg':
clean_old_data = False
migrating_storage_file_data(user_obj, user_obj.head_img, new_storage_obj, clean_old_data)
+ change_storage_and_change_advert_img(user_obj, new_storage_obj, clean_old_data)
+
+
+def change_storage_and_change_advert_img(user_obj, new_storage_obj, clean_old_data=True):
+ for user_advert_obj in UserAdDisplayInfo.objects.filter(user_id=user_obj):
+ migrating_storage_file_data(user_obj, user_advert_obj.ad_pic, new_storage_obj, clean_old_data)
def download_files_form_oss(storage_obj, org_file):
diff --git a/fir_ser/api/views/apps.py b/fir_ser/api/views/apps.py
index 775f5c0..59c16e6 100644
--- a/fir_ser/api/views/apps.py
+++ b/fir_ser/api/views/apps.py
@@ -239,7 +239,7 @@ class AppInfoView(APIView):
if do_sign_flag == 2:
c_task = run_resign_task.apply_async((app_obj.app_id, False))
if c_task:
- msg = c_task.get(propagate=False)
+ msg = c_task.get(propagate=False, timeout=30)
logger.info(f"app {app_obj} run_resign_task msg:{msg}")
if c_task.successful():
c_task.forget()
diff --git a/fir_ser/api/views/download.py b/fir_ser/api/views/download.py
index 65da265..cfcf6d1 100644
--- a/fir_ser/api/views/download.py
+++ b/fir_ser/api/views/download.py
@@ -3,7 +3,6 @@
# project: 3月
# author: liuyu
# date: 2020/3/6
-from urllib.parse import urlsplit
from rest_framework.views import APIView
@@ -19,12 +18,12 @@ from api.utils.storage.caches import get_app_instance_by_cache, get_download_url
del_cache_response_by_short, consume_user_download_times, check_app_permission
import os
from api.utils.decorators import cache_response # 本来使用的是 drf-extensions==0.7.0 但是还未支持该版本Django
-from api.utils.serializer import AppsShortSerializer
+from api.utils.serializer import AppsShortSerializer, AppAdInfoSerializer
from api.models import Apps, AppReleaseInfo, APPToDeveloper, APPSuperSignUsedInfo
from django.http import FileResponse
import logging
from api.utils.baseutils import get_profile_full_path, get_app_domain_name, get_filename_form_file, \
- check_app_domain_name_access, get_real_ip_address, get_origin_domain_name
+ check_app_domain_name_access, get_real_ip_address, get_origin_domain_name, ad_random_weight
from api.utils.throttle import VisitShortThrottle, InstallShortThrottle, InstallThrottle1, InstallThrottle2
logger = logging.getLogger(__name__)
@@ -157,7 +156,8 @@ class ShortDownloadView(APIView):
if user_obj and user_obj.role and user_obj.role == 3:
...
else:
- if not check_app_domain_name_access(app_obj, get_origin_domain_name(request), user_obj, domain_name.split('//')[-1]):
+ if not check_app_domain_name_access(app_obj, get_origin_domain_name(request), user_obj,
+ domain_name.split('//')[-1]):
res.code = 1004
res.msg = "访问域名不合法"
return Response(res.dict)
@@ -173,6 +173,8 @@ class ShortDownloadView(APIView):
res.data = app_serializer.data
res.udid = udid
res.domain_name = domain_name
+ if user_obj and user_obj.role and user_obj.role > 1:
+ res.ad = AppAdInfoSerializer(ad_random_weight(user_obj)).data
return Response(res.dict)
# key的设置
diff --git a/fir_ser/api/views/uploads.py b/fir_ser/api/views/uploads.py
index f96b2dd..6b5079f 100644
--- a/fir_ser/api/views/uploads.py
+++ b/fir_ser/api/views/uploads.py
@@ -8,7 +8,7 @@ from api.utils.app.apputils import get_random_short, save_app_infos
from api.utils.baseutils import get_app_domain_name
from api.utils.storage.storage import Storage
from api.utils.storage.caches import upload_file_tmp_name, del_cache_response_by_short
-from api.models import Apps, AppReleaseInfo, UserInfo, AppScreenShot, CertificationInfo
+from api.models import Apps, AppReleaseInfo, UserInfo, AppScreenShot, CertificationInfo, UserAdDisplayInfo
from api.utils.app.randomstrings import make_app_uuid
from rest_framework.views import APIView
from api.utils.response import BaseResponse
@@ -120,7 +120,7 @@ class AppAnalyseView(APIView):
if app_obj:
if app_obj.issupersign and app_obj.user_id.supersign_active:
c_task = run_resign_task.apply_async((app_obj.app_id, False))
- msg = c_task.get(propagate=False)
+ msg = c_task.get(propagate=False, timeout=30)
logger.info(f"app {app_obj} run_resign_task msg:{msg}")
if c_task.successful():
c_task.forget()
@@ -160,7 +160,7 @@ class UploadView(APIView):
if f_type == 'app' or f_type == 'screen':
app_obj = Apps.objects.filter(app_id=app_id, user_id=request.user).first()
- elif f_type and f_type in ['head', 'certification']:
+ elif f_type and f_type in ['head', 'certification', 'advert']:
if request.user.uid != app_id:
res.code = 1007
res.msg = '该用户不存在'
@@ -232,7 +232,7 @@ class UploadView(APIView):
# res.code = 1006
# res.msg = '该应用不存在'
# return Response(res.dict)
- elif f_type and f_type in ['head', 'certification']:
+ elif f_type and f_type in ['head', 'certification', 'advert']:
if request.user.uid != app_id:
res.code = 1007
res.msg = '该用户不存在'
@@ -330,7 +330,7 @@ class UploadView(APIView):
del_cache_response_by_short(app_id)
return Response(res.dict)
- elif f_type and app_id and f_type in ['head', 'certification']:
+ elif f_type and app_id and f_type in ['head', 'certification', 'advert']:
if request.user.uid != app_id:
res.code = 1007
res.msg = '该用户不存在'
@@ -360,6 +360,18 @@ class UploadView(APIView):
storage.rename_file(upload_key, new_upload_key)
return Response(res.dict)
+ elif f_type == 'advert':
+ ext = cert_info.get('ext', None)
+ if ext:
+ pk = ext.get('id')
+ if pk:
+ ad_info_obj = UserAdDisplayInfo.objects.filter(user_id=request.user, pk=pk).first()
+ if ad_info_obj:
+ old_file_key = ad_info_obj.ad_pic
+ storage.delete_file(old_file_key)
+ storage.rename_file(upload_key, new_upload_key)
+ ad_info_obj.ad_pic = new_upload_key
+ ad_info_obj.save(update_fields=['ad_pic'])
return Response(res.dict)
else:
pass