增加自定义广告功能

publicsignpoll
youngS 4 years ago
parent fac7eccc05
commit d34073301b
  1. 1
      fir_client/package.json
  2. 34
      fir_client/src/components/FirDownload.vue
  3. 4
      fir_client/src/components/FirHeader.vue
  4. 34
      fir_client/src/components/ShortDownload.vue
  5. 430
      fir_client/src/components/user/FirUserAdvert.vue
  6. 15
      fir_client/src/restful/index.js
  7. 6
      fir_client/src/router/index.js
  8. 1
      fir_ser/api/admin.py
  9. 32
      fir_ser/api/migrations/0012_useraddisplayinfo.py
  10. 22
      fir_ser/api/migrations/0013_auto_20211014_1617.py
  11. 16
      fir_ser/api/migrations/0014_alter_useraddisplayinfo_unique_together.py
  12. 20
      fir_ser/api/models.py
  13. 2
      fir_ser/api/urls.py
  14. 11
      fir_ser/api/utils/auth.py
  15. 17
      fir_ser/api/utils/baseutils.py
  16. 19
      fir_ser/api/utils/serializer.py
  17. 8
      fir_ser/api/utils/utils.py
  18. 2
      fir_ser/api/views/apps.py
  19. 10
      fir_ser/api/views/download.py
  20. 22
      fir_ser/api/views/uploads.py

@ -20,6 +20,7 @@
"vue": "^2.6.10", "vue": "^2.6.10",
"vue-clipboard2": "^0.3.1", "vue-clipboard2": "^0.3.1",
"vue-cookies": "^1.7.0", "vue-cookies": "^1.7.0",
"vue-cropper": "^0.5.7",
"vue-lazyload": "^1.3.3", "vue-lazyload": "^1.3.3",
"vue-qr": "^2.3.0", "vue-qr": "^2.3.0",
"vue-router": "^3.1.3", "vue-router": "^3.1.3",

@ -218,10 +218,18 @@
<el-link type="warning" :underline="false">若安装异常请复制UDID发送给管理员</el-link> <el-link type="warning" :underline="false">若安装异常请复制UDID发送给管理员</el-link>
</div> </div>
<div class="footer" style="margin-top: 30%"> <div class="footer" style="margin-top: 30%;margin-bottom: 8px">
免责声明<br> 免责声明<br>
本网站仅提供下载托管应用为用户自行上传请甄别应用风险后进行下载 本网站仅提供下载托管应用为用户自行上传请甄别应用风险后进行下载
</div> </div>
<div v-if="agent!==''">
<div v-if="ad_info.ad_uri" style="margin-bottom: 80px"/>
<div class="app_bottom_fixed" v-if="ad_info.ad_uri">
<a :href="ad_info.ad_uri" target="_blank">
<img :src="ad_info.ad_pic" alt="welcome" style="object-fit: cover">
</a>
</div>
</div>
</div> </div>
@ -308,6 +316,7 @@
timer: '', timer: '',
gomobile: true, gomobile: true,
mobileprovision: '', mobileprovision: '',
ad_info: {ad_pic: '', ad_uri: ''}
} }
}, },
beforeDestroy() { beforeDestroy() {
@ -475,7 +484,7 @@
}) })
} }
}, },
auto_redircet_url(domain_name) { auto_redirect_url(domain_name) {
if (domain_name) { if (domain_name) {
let nurl = location.href.split("//")[1].split("/"); let nurl = location.href.split("//")[1].split("/");
const user_hostname = domain_name.split("//"); const user_hostname = domain_name.split("//");
@ -503,9 +512,12 @@
} }
getShortAppinfo(data => { getShortAppinfo(data => {
if (data.code === 1000) { if (data.code === 1000) {
if (!this.auto_redircet_url(data.domain_name)) { if (!this.auto_redirect_url(data.domain_name)) {
return; return;
} }
if (data.ad && data.ad.ad_uri) {
this.ad_info = data.ad;
}
this.udid = data.udid; this.udid = data.udid;
if (!data.data.master_release.release_id) { if (!data.data.master_release.release_id) {
this.$message({ this.$message({
@ -713,6 +725,22 @@
</script> </script>
<style scoped> <style scoped>
.app_bottom_fixed {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 3;
font-size: 0;
text-align: center;
background: 0 0;
}
.app_bottom_fixed img {
width: 100%;
max-height: 80px;
}
.mask { .mask {
z-index: 9; z-index: 9;
position: fixed; position: fixed;

@ -50,6 +50,8 @@
<el-dropdown-item command="userinfo">个人资料</el-dropdown-item> <el-dropdown-item command="userinfo">个人资料</el-dropdown-item>
<el-dropdown-item command="apitoken">API token</el-dropdown-item> <el-dropdown-item command="apitoken">API token</el-dropdown-item>
<el-dropdown-item command="setdomian">设置域名</el-dropdown-item> <el-dropdown-item command="setdomian">设置域名</el-dropdown-item>
<el-dropdown-item command="setadvert" v-if="$store.state.userinfo.role>1">宣传广告
</el-dropdown-item>
<el-dropdown-item command="storage" v-if="$store.state.userinfo.storage_active">存储管理 <el-dropdown-item command="storage" v-if="$store.state.userinfo.storage_active">存储管理
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item command="supersign" v-if="$store.state.userinfo.supersign_active">超级签名 <el-dropdown-item command="supersign" v-if="$store.state.userinfo.supersign_active">超级签名
@ -130,6 +132,8 @@
} else if (command === 'setdomian') { } else if (command === 'setdomian') {
this.$router.push({"name": 'FirUserDomain'}) this.$router.push({"name": 'FirUserDomain'})
} else if (command === 'setadvert') {
this.$router.push({"name": 'FirUserAdvert'})
} else if (command === 'myorder') { } else if (command === 'myorder') {
this.$router.push({"name": 'FirUserOrders'}) this.$router.push({"name": 'FirUserOrders'})
} else if (command === 'contact') { } else if (command === 'contact') {

@ -232,10 +232,18 @@
<span style="color: #ebb563">若安装异常请复制UDID发送给管理员</span> <span style="color: #ebb563">若安装异常请复制UDID发送给管理员</span>
</div> </div>
<div class="footer" style="margin-top: 30%"> <div class="footer" style="margin-top: 30%;margin-bottom: 8px">
免责声明<br> 免责声明<br>
本网站仅提供下载托管应用为用户自行上传请甄别应用风险后进行下载 本网站仅提供下载托管应用为用户自行上传请甄别应用风险后进行下载
</div> </div>
<div v-if="agent!==''">
<div v-if="ad_info.ad_uri" style="margin-bottom: 80px"/>
<div class="app_bottom_fixed" v-if="ad_info.ad_uri">
<a :href="ad_info.ad_uri" target="_blank">
<img :src="ad_info.ad_pic" alt="welcome" style="object-fit: cover">
</a>
</div>
</div>
</div> </div>
@ -309,6 +317,7 @@
timer: '', timer: '',
gomobile: true, gomobile: true,
mobileprovision: '', mobileprovision: '',
ad_info: {ad_pic: '', ad_uri: ''}
} }
}, },
beforeDestroy() { beforeDestroy() {
@ -464,7 +473,7 @@
}) })
} }
}, },
auto_redircet_url(domain_name) { auto_redirect_url(domain_name) {
if (domain_name) { if (domain_name) {
let nurl = location.href.split("//")[1].split("/"); let nurl = location.href.split("//")[1].split("/");
const user_hostname = domain_name.split("//"); const user_hostname = domain_name.split("//");
@ -492,9 +501,12 @@
} }
getShortAppinfo(data => { getShortAppinfo(data => {
if (data.code === 1000) { if (data.code === 1000) {
if (!this.auto_redircet_url(data.domain_name)) { if (!this.auto_redirect_url(data.domain_name)) {
return; return;
} }
if (data.ad && data.ad.ad_uri) {
this.ad_info = data.ad;
}
this.udid = data.udid; this.udid = data.udid;
if (!data.data.master_release.release_id) { if (!data.data.master_release.release_id) {
this.iserror = true; this.iserror = true;
@ -718,6 +730,22 @@
white-space: nowrap; white-space: nowrap;
} }
.app_bottom_fixed {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 3;
font-size: 0;
text-align: center;
background: 0 0;
}
.app_bottom_fixed img {
width: 100%;
max-height: 80px;
}
.mask { .mask {
z-index: 9; z-index: 9;
position: fixed; position: fixed;

@ -0,0 +1,430 @@
<template>
<el-main>
<!-- vueCropper 剪裁图片实现-->
<el-dialog title="图片剪裁" :visible.sync="dialogVisible" append-to-body>
<div style="height: 500px">
<vueCropper
ref="cropper"
:img="option.img"
:outputSize="option.size"
:outputType="option.outputType"
:info="true"
:full="option.full"
:canScale="option.canScale"
:canMove="option.canMove"
:canMoveBox="option.canMoveBox"
:autoCropWidth="option.autoCropWidth"
:autoCropHeight="option.autoCropHeight"
:original="option.original"
:autoCrop="option.autoCrop"
:fixed="option.fixed"
:fixedNumber="option.fixedNumber"
:centerBox="option.centerBox"
:infoTrue="option.infoTrue"
:fixedBox="option.fixedBox"
/>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="finish">确认</el-button>
</div>
</el-dialog>
<el-dialog
:title="advert_title"
:close-on-click-modal="false"
:close-on-press-escape="false"
:visible.sync="bind_advert_sure"
center
width="666px">
<el-form :model="editadvertinfo"
label-width="180px" style="margin:0 auto;">
<el-form-item label="广告名称">
<el-input v-model="editadvertinfo.ad_name" clearable/>
</el-form-item>
<el-form-item label="展示图片【高80像素】">
<el-upload
action="#"
drag
:show-file-list="false"
:auto-upload="false"
accept=".png , .jpg , .jpeg"
style="max-height: 200px"
:on-change="onUploadChange">
<img v-if="upload_pic_b" :src="upload_pic_b" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
<el-form-item label="点击跳转连接">
<el-input v-model="editadvertinfo.ad_uri" clearable/>
</el-form-item>
<el-form-item label="展示权重">
<el-input-number v-model="editadvertinfo.weight" :min="0" :max="100"
label="展示权重"/>
</el-form-item>
<el-form-item label="是否发布">
<el-switch
v-model="editadvertinfo.is_enable"
active-color="#13ce66"
inactive-color="#ff4949"
active-text="发布"
inactive-text="未发布">
</el-switch>
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="editadvertinfo.description"
:autosize="{ minRows: 3, maxRows: 6}"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button v-if="!disabled" @click="saveadvert">保存</el-button>
<el-button v-if="!disabled" @click="bind_advert_sure=false,init_advertinfo()">取消</el-button>
</div>
</el-dialog>
<div>
<el-input
style="width: 30%;margin-right: 30px;margin-bottom: 5px"
v-model="search_key"
clearable
placeholder="输入名称搜索"/>
<el-button type="primary" icon="el-icon-search" @click="handleCurrentChange(1)">
搜索
</el-button>
<div style="float: right">
<el-button type="primary" plain @click="bind_advert_sure=true,init_advertinfo()">
添加
</el-button>
</div>
<el-table
:data="advert_info_list"
v-loading="loading"
border
stripe
style="width: 100%">
<el-table-column
fixed
prop="ad_name"
align="center"
label="名称">
</el-table-column>
<el-table-column
fixed
prop="ad_pic"
align="center"
label="展示图片">
<template slot-scope="scope">
<img :src="scope.row.ad_pic" style="max-height: 80px;width: 100%;object-fit: cover"/>
</template>
</el-table-column>
<el-table-column
fixed
prop="ad_uri"
align="center"
label="点击跳转连接">
</el-table-column>
<el-table-column
prop="is_enable"
label="是否发布"
align="center"
width="110">
<template slot-scope="scope">
<el-button v-if="scope.row.is_enable === true" type="success" size="small">已发布
</el-button>
<el-button v-else type="warning" size="small">未发布
</el-button>
</template>
</el-table-column>
<el-table-column
prop="weight"
width="110"
align="center"
label="展示权重">
</el-table-column>
<el-table-column
:formatter="format_create_time"
prop="created_time"
width="170"
align="center"
label="添加时间"
>
</el-table-column>
<el-table-column
fixed="right"
label="操作"
align="center"
width="150">
<template slot-scope="scope">
<el-button
size="mini"
@click="handleEditAdvert(scope.row)">编辑
</el-button>
<el-button
size="mini"
type="danger"
@click="handleDeleteAdvert(scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div style="margin-top: 20px;margin-bottom: 20px">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page.sync="pagination.currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pagination.pagesize"
layout="total,sizes, prev, pager, next"
:total="pagination.total">
</el-pagination>
</div>
</el-main>
</template>
<script>
import {advertinfo} from "@/restful";
import {VueCropper} from 'vue-cropper'
import {getUserInfoFun, AvatarUploadUtils, dataURLtoFile} from '@/utils'
export default {
name: "FirUserAdvert",
components: {
VueCropper,
},
data() {
return {
dialogVisible: false,
option: {
img: '', //
info: true, //
outputSize: 1, //
outputType: 'jpeg', //
canScale: true, //
autoCrop: true, //
autoCropWidth: 200, //
autoCropHeight: 80, //
fixedBox: false, //
fixed: false, //
fixedNumber: [7, 2], //
full: true, //
canMoveBox: true, //
original: false, //
centerBox: true, //
infoTrue: true // true false
},
advert_title: '添加新宣传广告',
bind_advert_sure: false,
advert_info_list: [],
search_key: "",
pagination: {"currentPage": 1, "total": 0, "pagesize": 10},
loading: false,
editadvertinfo: {},
upload_file: undefined,
disabled: false,
upload_pic_b: ''
}
},
methods: {
finish() {
this.$refs.cropper.getCropData((data) => {
this.upload_pic_b = data;
this.upload_file = dataURLtoFile(data, this.upload_file.name);
this.dialogVisible = false;
})
},
init_advertinfo() {
this.editadvertinfo = {
'ad_name': '',
'ad_pic': '',
'ad_uri': '',
'is_enable': true,
'weight': 10
};
this.upload_file = undefined;
this.upload_pic_b = '';
},
saveadvert() {
if (this.editadvertinfo.ad_pic && this.editadvertinfo.ad_pic.indexOf("http") > -1) {
advertinfo(data => {
if (data.code === 1000) {
if (data.data && data.data.id) {
if (this.upload_file) {
this.beforeAvatarUpload(this.upload_file, data.data.id, 'update')
} else {
this.$message.success("更新成功")
}
} else {
this.$message.error("操作失败 " + data.msg)
}
} else {
this.$message.error("更新失败 " + data.msg)
}
this.loading = false;
}, {methods: 'PUT', data: this.editadvertinfo})
} else {
advertinfo(data => {
if (data.code === 1000) {
if (data.data && data.data.id) {
this.beforeAvatarUpload(this.upload_file, data.data.id, 'create')
} else {
this.$message.error("操作失败 " + data.msg)
}
} else {
this.$message.error("添加失败 " + data.msg)
}
this.loading = false;
}, {methods: 'POST', data: this.editadvertinfo})
}
},
beforeAvatarUpload(file, id, act) {
return AvatarUploadUtils(this, file, {
'app_id': this.$store.state.userinfo.uid,
'upload_key': file.name,
'ftype': 'advert',
'ext': {'id': id}
// eslint-disable-next-line no-unused-vars
}, res => {
this.get_data_from_tabname();
this.upload_file = undefined;
let msg = '添加成功';
if (act === 'update') {
msg = '更新成功'
} else {
this.bind_advert_sure = false;
this.upload_pic_b = '';
}
this.$message.success(msg);
});
},
// eslint-disable-next-line no-unused-vars
onUploadChange(file, fileList) {
this.upload_file = file.raw;
const reader = new FileReader();
reader.readAsDataURL(file.raw);
// eslint-disable-next-line no-unused-vars
reader.onload = res => {
this.upload_pic_b = reader.result;
this.option.img = reader.result;
this.dialogVisible = true
};
},
copy_success() {
this.$message.success('复制剪切板成功');
},
handleSizeChange(val) {
this.pagination.pagesize = val;
this.get_data_from_tabname({
"size": this.pagination.pagesize,
"page": 1
})
},
handleCurrentChange(val) {
this.pagination.currentPage = val;
this.get_data_from_tabname({
"size": this.pagination.pagesize,
"page": this.pagination.currentPage
})
},
handleEditAdvert(ad_info) {
this.editadvertinfo = ad_info;
this.upload_pic_b = ad_info.ad_pic;
this.advert_title = "编辑广告信息";
this.bind_advert_sure = true;
},
handleDeleteAdvert(ad_info) {
this.loading = true;
advertinfo(data => {
if (data.code === 1000) {
this.advert_info_list = data.data;
this.pagination.total = data.count;
this.$message.success("操作成功")
} else {
this.$message.error("信息获取失败")
}
this.loading = false;
}, {methods: 'DELETE', data: {'pk': ad_info.id}})
},
get_data_from_tabname(data = {}) {
data.search_key = this.search_key.replace(/^\s+|\s+$/g, "");
this.UseradvertFun(data)
},
UseradvertFun(params) {
this.loading = true;
advertinfo(data => {
if (data.code === 1000) {
this.advert_info_list = data.data;
this.pagination.total = data.count;
} else {
this.$message.error("信息获取失败")
}
this.loading = false;
}, {methods: 'GET', data: params})
},
format_create_time(row) {
return this.format_time(row.created_time)
},
format_time(stime) {
if (stime) {
stime = stime.split(".")[0].split("T");
return stime[0] + " " + stime[1]
} else
return '';
},
}, mounted() {
getUserInfoFun(this);
this.get_data_from_tabname();
},
}
</script>
<style scoped>
.el-main {
margin: 20px auto 100px;
width: 1166px;
position: relative;
padding-bottom: 1px;
color: #9b9b9b;
-webkit-font-smoothing: antialiased;
border-radius: 1%;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
max-height: 80px;
margin-top: 50px;
object-fit: cover;
}
</style>

@ -677,3 +677,18 @@ export function domaininfo(callBack, params, load = true) {
true true
); );
} }
/**自定义广告 */
export function advertinfo(callBack, params, load = true) {
getData(
params.methods,
USERSEVER + '/advert',
params.data,
data => {
callBack(data);
},
load,
true,
true
);
}

@ -158,6 +158,12 @@ const router = new VueRouter({
meta: {label: '绑定域名详情'}, meta: {label: '绑定域名详情'},
component: () => import("@/components/user/FirUserDomain"), component: () => import("@/components/user/FirUserDomain"),
}, },
{
path: 'advert',
name: 'FirUserAdvert',
meta: {label: '自定义广告'},
component: () => import("@/components/user/FirUserAdvert"),
},
{ {
path: 'supersign-help', path: 'supersign-help',
name: 'FirSuperSignHelp', name: 'FirSuperSignHelp',

@ -18,3 +18,4 @@ admin.site.register(Price)
admin.site.register(Order) admin.site.register(Order)
admin.site.register(DomainCnameInfo) admin.site.register(DomainCnameInfo)
admin.site.register(UserDomainInfo) admin.site.register(UserDomainInfo)
admin.site.register(UserAdDisplayInfo)

@ -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': '用户自定义广告',
},
),
]

@ -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'),
),
]

@ -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')},
),
]

@ -499,3 +499,23 @@ class UserDomainInfo(models.Model):
def __str__(self): def __str__(self):
return "%s-%s-%s" % (self.user_id, self.cname_id, self.domain_name) 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)

@ -27,6 +27,7 @@ from api.views.order import PriceView, OrderView, PaySuccess
from api.views.supersign import DeveloperView, SuperSignUsedView, AppUDIDUsedView, SuperSignCertView from api.views.supersign import DeveloperView, SuperSignUsedView, AppUDIDUsedView, SuperSignCertView
from api.views.domain import DomainCnameView, DomainInfoView from api.views.domain import DomainCnameView, DomainInfoView
from api.views.thirdlogin import ValidWxChatToken, ThirdWxAccount from api.views.thirdlogin import ValidWxChatToken, ThirdWxAccount
from api.views.advert import UserAdInfoView
# router=DefaultRouter() # router=DefaultRouter()
# router.register("apps", AppsView) # router.register("apps", AppsView)
@ -50,6 +51,7 @@ urlpatterns = [
re_path(r"^udid/(?P<short>\w+)$", IosUDIDView.as_view()), re_path(r"^udid/(?P<short>\w+)$", IosUDIDView.as_view()),
re_path(r"^task/(?P<short>\w+)$", TaskView.as_view()), re_path(r"^task/(?P<short>\w+)$", TaskView.as_view()),
re_path("^analyse$", AppAnalyseView.as_view()), re_path("^analyse$", AppAnalyseView.as_view()),
re_path("^advert$", UserAdInfoView.as_view()),
re_path("^supersign/developer$", DeveloperView.as_view()), re_path("^supersign/developer$", DeveloperView.as_view()),
re_path("^supersign/devices$", SuperSignUsedView.as_view()), re_path("^supersign/devices$", SuperSignUsedView.as_view()),
re_path("^supersign/udid$", AppUDIDUsedView.as_view()), re_path("^supersign/udid$", AppUDIDUsedView.as_view()),

@ -126,3 +126,14 @@ class DownloadQrPermission(BasePermission):
if domain_type == '0' and results[0].role < 2: if domain_type == '0' and results[0].role < 2:
return False return False
return True 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

@ -8,9 +8,10 @@ import os, re, time
from django.db.models import Count from django.db.models import Count
import datetime import datetime
import random
from django.utils import timezone from django.utils import timezone
from fir_ser.settings import SUPER_SIGN_ROOT 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 api.utils.app.randomstrings import make_app_uuid
from django.core.validators import validate_email from django.core.validators import validate_email
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@ -259,3 +260,17 @@ def get_real_ip_address(request):
def get_origin_domain_name(request): def get_origin_domain_name(request):
meta = request.META meta = request.META
return meta.get('HTTP_ORIGIN', meta.get('HTTP_REFERER', 'http://xxx/xxx')).split('//')[-1].split('/')[0] 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

@ -23,6 +23,8 @@ def get_download_url_from_context(self, obj, key, url, force_new=False):
storage = Storage(obj.user_id) storage = Storage(obj.user_id)
elif isinstance(obj, models.AppReleaseInfo): elif isinstance(obj, models.AppReleaseInfo):
storage = Storage(obj.app_id.user_id) storage = Storage(obj.app_id.user_id)
elif isinstance(obj, models.UserAdDisplayInfo):
storage = Storage(obj.user_id)
elif isinstance(obj, models.UserInfo): elif isinstance(obj, models.UserInfo):
storage = Storage(obj) storage = Storage(obj)
elif isinstance(obj, models.UserCertificationInfo) or isinstance(obj, models.CertificationInfo): elif isinstance(obj, models.UserCertificationInfo) or isinstance(obj, models.CertificationInfo):
@ -652,3 +654,20 @@ class AdminDomainNameSerializer(DomainNameSerializer):
def get_domain_type_choices(self, obj): def get_domain_type_choices(self, obj):
return get_choices_dict(obj.domain_type_choices) 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"]

@ -7,7 +7,7 @@ import os, datetime, random
import binascii import binascii
from fir_ser.settings import SERVER_DOMAIN, CAPTCHA_LENGTH, MEDIA_ROOT, CACHE_KEY_TEMPLATE from fir_ser.settings import SERVER_DOMAIN, CAPTCHA_LENGTH, MEDIA_ROOT, CACHE_KEY_TEMPLATE
from api.models import APPSuperSignUsedInfo, APPToDeveloper, \ 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.caches import get_app_d_count_by_app_id
from api.utils.storage.localApi import LocalStorage from api.utils.storage.localApi import LocalStorage
from api.utils.storage.storage import Storage 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': if user_obj.head_img == 'head_img.jpeg':
clean_old_data = False clean_old_data = False
migrating_storage_file_data(user_obj, user_obj.head_img, new_storage_obj, clean_old_data) 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): def download_files_form_oss(storage_obj, org_file):

@ -239,7 +239,7 @@ class AppInfoView(APIView):
if do_sign_flag == 2: if do_sign_flag == 2:
c_task = run_resign_task.apply_async((app_obj.app_id, False)) c_task = run_resign_task.apply_async((app_obj.app_id, False))
if c_task: 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}") logger.info(f"app {app_obj} run_resign_task msg:{msg}")
if c_task.successful(): if c_task.successful():
c_task.forget() c_task.forget()

@ -3,7 +3,6 @@
# project: 3月 # project: 3月
# author: liuyu # author: liuyu
# date: 2020/3/6 # date: 2020/3/6
from urllib.parse import urlsplit
from rest_framework.views import APIView 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 del_cache_response_by_short, consume_user_download_times, check_app_permission
import os import os
from api.utils.decorators import cache_response # 本来使用的是 drf-extensions==0.7.0 但是还未支持该版本Django 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 api.models import Apps, AppReleaseInfo, APPToDeveloper, APPSuperSignUsedInfo
from django.http import FileResponse from django.http import FileResponse
import logging import logging
from api.utils.baseutils import get_profile_full_path, get_app_domain_name, get_filename_form_file, \ 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 from api.utils.throttle import VisitShortThrottle, InstallShortThrottle, InstallThrottle1, InstallThrottle2
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -157,7 +156,8 @@ class ShortDownloadView(APIView):
if user_obj and user_obj.role and user_obj.role == 3: if user_obj and user_obj.role and user_obj.role == 3:
... ...
else: 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.code = 1004
res.msg = "访问域名不合法" res.msg = "访问域名不合法"
return Response(res.dict) return Response(res.dict)
@ -173,6 +173,8 @@ class ShortDownloadView(APIView):
res.data = app_serializer.data res.data = app_serializer.data
res.udid = udid res.udid = udid
res.domain_name = domain_name 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) return Response(res.dict)
# key的设置 # key的设置

@ -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.baseutils import get_app_domain_name
from api.utils.storage.storage import Storage from api.utils.storage.storage import Storage
from api.utils.storage.caches import upload_file_tmp_name, del_cache_response_by_short 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 api.utils.app.randomstrings import make_app_uuid
from rest_framework.views import APIView from rest_framework.views import APIView
from api.utils.response import BaseResponse from api.utils.response import BaseResponse
@ -120,7 +120,7 @@ class AppAnalyseView(APIView):
if app_obj: if app_obj:
if app_obj.issupersign and app_obj.user_id.supersign_active: if app_obj.issupersign and app_obj.user_id.supersign_active:
c_task = run_resign_task.apply_async((app_obj.app_id, False)) 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}") logger.info(f"app {app_obj} run_resign_task msg:{msg}")
if c_task.successful(): if c_task.successful():
c_task.forget() c_task.forget()
@ -160,7 +160,7 @@ class UploadView(APIView):
if f_type == 'app' or f_type == 'screen': if f_type == 'app' or f_type == 'screen':
app_obj = Apps.objects.filter(app_id=app_id, user_id=request.user).first() 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: if request.user.uid != app_id:
res.code = 1007 res.code = 1007
res.msg = '该用户不存在' res.msg = '该用户不存在'
@ -232,7 +232,7 @@ class UploadView(APIView):
# res.code = 1006 # res.code = 1006
# res.msg = '该应用不存在' # res.msg = '该应用不存在'
# return Response(res.dict) # 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: if request.user.uid != app_id:
res.code = 1007 res.code = 1007
res.msg = '该用户不存在' res.msg = '该用户不存在'
@ -330,7 +330,7 @@ class UploadView(APIView):
del_cache_response_by_short(app_id) del_cache_response_by_short(app_id)
return Response(res.dict) 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: if request.user.uid != app_id:
res.code = 1007 res.code = 1007
res.msg = '该用户不存在' res.msg = '该用户不存在'
@ -360,6 +360,18 @@ class UploadView(APIView):
storage.rename_file(upload_key, new_upload_key) storage.rename_file(upload_key, new_upload_key)
return Response(res.dict) 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) return Response(res.dict)
else: else:
pass pass

Loading…
Cancel
Save