1.增加异常设备和设备黑名单功能

2.增加超级签名高级配置【异常设备注册等待】【异常设备注册等待中继续注册新设备】【开发者异常状态是否等待】
3.优化系统配置文件功能,增加用户个人配置
4.优化代码
dependabot/npm_and_yarn/fir_admin/async-2.6.4
nineven 2 years ago
parent 4cd0dc7586
commit 3bbabdafdb
  1. 4
      fir_client/src/components/FirDownload.vue
  2. 4
      fir_client/src/components/ShortDownload.vue
  3. 404
      fir_client/src/components/user/FirSuperSignBase.vue
  4. 6
      fir_client/src/components/user/FirUserAdvert.vue
  5. 39
      fir_client/src/restful/index.js
  6. 2
      fir_client/vue.config.js
  7. 7
      fir_ser/api/migrations/0002_auto_20220215_2129.py
  8. 19
      fir_ser/api/migrations/0005_auto_20220412_1744.py
  9. 57
      fir_ser/api/models.py
  10. 6
      fir_ser/api/tasks.py
  11. 2
      fir_ser/api/urls.py
  12. 12
      fir_ser/api/utils/serializer.py
  13. 59
      fir_ser/api/views/personalconfig.py
  14. 4
      fir_ser/api/views/thirdlogin.py
  15. 4
      fir_ser/common/cache/storage.py
  16. 255
      fir_ser/common/core/sysconfig.py
  17. 24
      fir_ser/common/libs/apple/appleapiv3.py
  18. 6
      fir_ser/common/libs/pay/ali.py
  19. 4
      fir_ser/common/libs/storage/aliyunApi.py
  20. 6
      fir_ser/common/utils/caches.py
  21. 60
      fir_ser/config.py
  22. 2
      fir_ser/fir_ser/settings.py
  23. 48
      fir_ser/xsign/migrations/0003_deviceabnormaludid_deviceblackudid.py
  24. 51
      fir_ser/xsign/models.py
  25. 26
      fir_ser/xsign/tasks.py
  26. 4
      fir_ser/xsign/urls.py
  27. 13
      fir_ser/xsign/utils/modelutils.py
  28. 17
      fir_ser/xsign/utils/serializer.py
  29. 5
      fir_ser/xsign/utils/signals.py
  30. 179
      fir_ser/xsign/utils/supersignutils.py
  31. 12
      fir_ser/xsign/views/receiveudids.py
  32. 110
      fir_ser/xsign/views/supersign.py

@ -652,6 +652,10 @@ export default {
this.err_password = true
this.currentappinfo.need_password = true
this.password = ''
} else if (data.code === 1007) {
this.msg = data.msg;
this.show_err_msg(data.msg);
this.wrong = true;
} else {
this.show_err_msg(data.msg);
// eslint-disable-next-line no-unused-vars

@ -617,6 +617,10 @@ export default {
this.err_password = true;
this.currentappinfo.need_password = true;
this.password = ''
} else if (data.code === 1007) {
this.msg = data.msg;
this.show_err_msg(data.msg);
this.wrong = true;
} else {
this.show_err_msg(data.msg);
// eslint-disable-next-line no-unused-vars

@ -391,6 +391,80 @@
</span>
</el-dialog>
<el-dialog
:close-on-click-modal="false"
:close-on-press-escape="false"
:visible.sync="addblackDeviceVisible"
center
title="设备黑名单手动添加"
width="666px">
<el-form :model="blackdeviceinfo"
label-width="100px" style="margin:0 auto;">
<el-form-item label="设备UDID">
<el-input v-model="blackdeviceinfo.udid" clearable/>
</el-form-item>
<el-form-item label="是否生效">
<el-switch
v-model="blackdeviceinfo.enable"
active-color="#13ce66"
active-text="生效"
inactive-color="#ff4949"
inactive-text="失效">
</el-switch>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="blackdeviceinfo.description" :autosize="{ minRows: 3, maxRows: 6}"
type="textarea"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="saveblackdevice">保存</el-button>
<el-button @click="addblackDeviceVisible=false">取消</el-button>
</div>
</el-dialog>
<el-dialog
:close-on-click-modal="false"
:close-on-press-escape="false"
:visible.sync="signConfigVisible"
center
title="签名高级配置"
width="666px">
<el-card v-for="info in sign_config_lists" :key="info.key" class="box-card" shadow="hover"
style="margin-bottom: 10px">
<div slot="header" class="clearfix">
<span><el-tag size="medium" type="info">配置KEY</el-tag> <el-tag size="medium">{{ info.key }}</el-tag></span>
<div style="float: right">
<el-switch
v-model="info.value"
active-color="#13ce66"
active-text="启用"
active-value="true"
inactive-color="#ff4949"
inactive-text="关闭"
inactive-value="false"
@change="changeSignConfig(info)">
</el-switch>
</div>
</div>
<el-tag size="medium" type="info">描述信息</el-tag>
{{ info.title }}
</el-card>
<div slot="footer" class="dialog-footer">
<el-button @click="updateSignConfig">恢复默认值</el-button>
<el-button @click="signConfigVisible=false">取消</el-button>
</div>
</el-dialog>
<el-tabs v-model="activeName" tab-position="top" type="border-card" @tab-click="handleClick">
<el-tab-pane label="开发者账户" name="iosdeveloper">
@ -469,6 +543,9 @@
<el-button type="plain" @click="setdeveloperstatusFun">
批量设置账户状态
</el-button>
<el-button type="plain" @click="signConfigFun">
签名高级配置
</el-button>
</div>
</el-col>
</el-row>
@ -1064,8 +1141,8 @@
<el-table-column
align="center"
label="开发者ID"
width="170"
prop="issuer_id">
prop="issuer_id"
width="170">
<template slot-scope="scope">
<el-popover placement="top" trigger="hover">
<p>开发者ID: {{ get_developer_uid(scope.row.issuer_id) }}</p>
@ -1112,9 +1189,9 @@
<div>
<el-tooltip content="仅仅删除数据库数据,不操作苹果开发者设备" placement="top">
<el-button
plain
size="mini"
type="danger"
plain
@click="udidDeleteFun(scope,0)">仅删除
</el-button>
</el-tooltip>
@ -1122,9 +1199,9 @@
<div v-if="!(scope.row.issuer_id && scope.row.issuer_id.indexOf(':')> -1)" style="margin-top: 5px">
<el-tooltip content="删除的同时,会检测并同时禁用苹果开发者设备" placement="bottom">
<el-button
plain
size="mini"
type="danger"
plain
@click="udidDeleteFun(scope,1)">删除并禁用设备
</el-button>
</el-tooltip>
@ -1457,6 +1534,215 @@
</el-tab-pane>
<el-tab-pane label="异常设备" name="abnormaldevice">
<el-input
v-model="udidsearch"
clearable
placeholder="输入UDID"
style="width: 30%;margin-right: 30px;margin-bottom: 10px"/>
<el-input
v-model="appidseach"
clearable
placeholder="输入开发者用户ID"
style="width: 30%;margin-right: 30px;margin-bottom: 10px"/>
<el-button icon="el-icon-search" type="primary" @click="handleCurrentChange(1)">
搜索
</el-button>
<el-table
v-loading="loading"
:data="abnormal_device_lists"
border
stripe
style="width: 100%">
<el-table-column
align="center"
label="设备UDID"
width="200">
<template slot-scope="scope">
<el-popover placement="top" trigger="hover">
<p>设备序列号: {{ scope.row.udid_info.serial }}</p>
<p>设备产品: {{ scope.row.udid_info.product }}</p>
<p>设备状态: {{ scope.row.udid_info.device_status }}</p>
<div slot="reference" class="name-wrapper">
<span>{{ scope.row.udid_info.udid }}</span>
</div>
</el-popover>
</template>
</el-table-column>
<el-table-column
align="center"
label="设备状态"
width="80">
<template slot-scope="scope">
<el-tag v-if="scope.row.udid_info.status === 'ENABLED'">{{ scope.row.udid_info.device_status }}</el-tag>
<el-tag v-else type="danger">{{ scope.row.udid_info.device_status }}</el-tag>
</template>
</el-table-column>
<el-table-column
align="center"
label="添加备注"
prop="description">
</el-table-column>
<el-table-column
:formatter="deviceformatter"
align="center"
label="添加时间"
prop="operate_time"
width="100">
</el-table-column>
<el-table-column
align="center"
label="开发者ID"
prop="developer_id"
width="166">
<template slot-scope="scope">
<el-popover placement="top" trigger="hover">
<p>开发者ID: {{ scope.row.udid_info.developer_id }}</p>
<p>开发者备注: {{ scope.row.udid_info.developer_description }}</p>
<p>开发者状态: {{ scope.row.udid_info.developer_status }}</p>
<div slot="reference" class="name-wrapper">
<span>{{ scope.row.udid_info.developer_id }}</span>
</div>
</el-popover>
</template>
</el-table-column>
<el-table-column
align="center"
label="自动移除"
width="80">
<template slot-scope="scope">
<el-popover placement="top" trigger="hover">
<div v-if="scope.row.auto_remove">
<p>已经开启自动移除当设备重新注册或同步设备信息时会自动判断并进行操作</p>
<p>若想要手动操作可点击
<el-button plain size="small" type="warning" @click="changeAbnormalState(scope.row,0)">关闭
</el-button>
</p>
</div>
<div v-else>
<p>已经关闭自动移除该异常设备只能手动进行操作</p>
<p>若想要设备重新注册或同步设备信息时自动操作可点击
<el-button plain size="small" @click="changeAbnormalState(scope.row,1)">开启</el-button>
</p>
</div>
<div slot="reference" class="name-wrapper">
<el-tag v-if="scope.row.auto_remove">开启</el-tag>
<el-tag v-else type="danger">关闭</el-tag>
</div>
</el-popover>
</template>
</el-table-column>
<el-table-column
align="center"
fixed="right"
label="操作"
width="100">
<template slot-scope="scope">
<el-tooltip content="删除该异常设备信息" placement="top">
<el-button
plain
size="mini"
type="danger"
@click="changeAbnormalState(scope.row,-1)">删除
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="设备黑名单" name="blackdevice">
<el-input
v-model="udidsearch"
clearable
placeholder="输入UDID"
style="width: 30%;margin-right: 30px;margin-bottom: 10px"/>
<el-button icon="el-icon-search" type="primary" @click="handleCurrentChange(1)">
搜索
</el-button>
<div style="float: right">
<el-button plain type="primary" @click="addblackDeviceVisible=true">
添加设备
</el-button>
</div>
<el-table
v-loading="loading"
:data="black_device_lists"
border
stripe
style="width: 100%">
<el-table-column
align="center"
label="设备UDID"
prop="udid"
width="200">
</el-table-column>
<el-table-column
align="center"
label="备注"
prop="description">
</el-table-column>
<el-table-column
:formatter="deviceformatter"
align="center"
label="添加时间"
prop="operate_time"
width="100">
</el-table-column>
<el-table-column
align="center"
label="是否生效"
width="80">
<template slot-scope="scope">
<el-popover placement="top" trigger="hover">
<div v-if="scope.row.enable">
<p>已经生效设备注册安装会进行拦截操作</p>
<p>若想要关闭可点击
<el-button plain size="small" type="warning" @click="changeAbnormalBlackState(scope.row,0)">失效
</el-button>
</p>
</div>
<div v-else>
<p>已经失效设备注册安装不会进行拦截操作</p>
<p>若想要拦截该设备可点击
<el-button plain size="small" @click="changeAbnormalBlackState(scope.row,1)">生效</el-button>
</p>
</div>
<div slot="reference" class="name-wrapper">
<el-tag v-if="scope.row.enable">生效中</el-tag>
<el-tag v-else type="danger">已失效</el-tag>
</div>
</el-popover>
</template>
</el-table-column>
<el-table-column
align="center"
fixed="right"
label="操作"
width="100">
<template slot-scope="scope">
<el-tooltip content="删除该设备信息" placement="top">
<el-button
plain
size="mini"
type="danger"
@click="changeAbnormalBlackState(scope.row,-1)">删除
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<div v-if="activeName!== 'adddeveloper'" style="margin-top: 20px">
<el-pagination
@ -1477,6 +1763,8 @@
<script>
import {
abnormalDeviceUtil,
blackDeviceUtil,
developercert,
DeviceBillInfo,
DeviceRankInfo,
@ -1485,6 +1773,7 @@ import {
iosdevices,
iosdevicesudid,
iosudevices,
personalConfigInfo,
signoperatemessage
} from "@/restful";
import {format_choices, getUserInfoFun, removeAaary} from "@/utils";
@ -1514,6 +1803,9 @@ export default {
app_rank_number: 0,
app_bill_info_lists: [],
app_udid_lists: [],
abnormal_device_lists: [],
black_device_lists: [],
sign_config_lists: [],
activeName: "iosdeveloper",
udidsearch: "",
Bundleidsearch: "",
@ -1526,6 +1818,8 @@ export default {
timerangesearch: [],
dialogaddDeveloperVisible: false,
importcertDeveloperVisible: false,
addblackDeviceVisible: false,
signConfigVisible: false,
target_uid: '',
target_number: 1,
transferVisible: false,
@ -1533,6 +1827,7 @@ export default {
transferInfo: {uid: '', name: '', number: 0},
title: "",
editdeveloperinfo: {auth_type: 0, usable_number: 100, app_limit_number: 100, p8key: ''},
blackdeviceinfo: {udid: '', enable: true, description: ''},
isedit: false,
placeholder: "",
pagination: {"currentPage": 1, "total": 0, "pagesize": 10},
@ -1617,6 +1912,79 @@ export default {
}
},
methods: {
changeSignConfig(info) {
personalConfigInfo(data => {
if (data.code === 1000) {
this.$message.success("操作成功")
this.signConfigFun()
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'PUT', data: {config_key: info.key, config_value: info.value}
})
},
updateSignConfig() {
personalConfigInfo(data => {
if (data.code === 1000) {
this.$message.success("操作成功")
this.signConfigFun()
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'DELETE'
})
},
signConfigFun() {
personalConfigInfo(data => {
if (data.code === 1000) {
this.sign_config_lists = data.data
this.signConfigVisible = true
} else {
this.$message.error("获取数据失败了 " + data.msg)
}
}, {
methods: 'GET'
})
},
saveblackdevice() {
blackDeviceUtil(data => {
if (data.code === 1000) {
this.$message.success("添加成功")
this.addblackDeviceVisible = false
this.get_data_from_tabname(this.activeName);
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {methods: 'POST', data: this.blackdeviceinfo})
},
changeAbnormalBlackState(info, state) {
if (state === -1) {
this.DeviceBase(blackDeviceUtil, {methods: 'DELETE', data: {pk: info.id, state: state}})
} else {
this.DeviceBase(blackDeviceUtil, {methods: 'PUT', data: {pk: info.id, state: state}})
}
},
DeviceBase(func, params) {
func(data => {
if (data.code === 1000) {
this.$message.success("操作成功")
this.get_data_from_tabname(this.activeName);
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, params)
},
changeAbnormalState(info, state) {
if (state === -1) {
this.DeviceBase(abnormalDeviceUtil, {methods: 'DELETE', data: {pk: info.id, state: state}})
} else {
this.DeviceBase(abnormalDeviceUtil, {methods: 'PUT', data: {pk: info.id, state: state}})
}
},
refreshactiveFun() {
this.get_data_from_tabname(this.activeName, {
"size": this.pagination.pagesize,
@ -2115,6 +2483,10 @@ export default {
} else if (tabname === 'operatemsg') {
data.operate_status = this.operatestatus;
this.operateMessageFun({"methods": "GET", "data": data})
} else if (tabname === 'abnormaldevice') {
this.abnormalDeviceFun({"methods": "GET", "data": data})
} else if (tabname === 'blackdevice') {
this.blackDeviceFun({"methods": "GET", "data": data})
}
},
@ -2340,6 +2712,26 @@ export default {
}
}, params)
},
abnormalDeviceFun(params) {
abnormalDeviceUtil(data => {
if (data.code === 1000) {
this.abnormal_device_lists = data.data;
this.pagination.total = data.count;
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, params)
},
blackDeviceFun(params) {
blackDeviceUtil(data => {
if (data.code === 1000) {
this.black_device_lists = data.data;
this.pagination.total = data.count;
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, params)
},
iosudevicesFun(action, data) {
if (action !== 'GET') {
this.loadingfun = this.$loading({
@ -2376,7 +2768,7 @@ export default {
getUserInfoFun(this);
if (this.$route.params.act) {
let activeName = this.$route.params.act;
let activeName_list = ["iosdeveloper", "adddeveloper", "iosudevices", "useddevices", "devicesudid", "devicesbill", "transferbill", "devicesrank", "operatemsg"];
let activeName_list = ["iosdeveloper", "adddeveloper", "iosudevices", "useddevices", "devicesudid", "devicesbill", "transferbill", "devicesrank", "operatemsg", "abnormaldevice", "blackdevice"];
for (let index in activeName_list) {
if (activeName_list[index] === activeName) {
this.activeName = activeName;
@ -2401,7 +2793,7 @@ export default {
<style scoped>
.el-main {
margin: 20px auto 100px;
width: 1188px;
width: 1255px;
position: relative;
padding-bottom: 1px;
color: #9b9b9b;

@ -299,6 +299,12 @@ export default {
}
},
beforeAvatarUpload(file, id, act) {
if (!file) {
this.$message.warning("展示图片不存在")
this.bind_advert_sure = false;
this.handleCurrentChange(1)
return
}
return AvatarUploadUtils(this, file, {
'app_id': this.$store.state.userinfo.uid,
'upload_key': file.name,

@ -667,6 +667,18 @@ export function notifyConfigInfo(callBack, params) {
);
}
/**消息配置 */
export function personalConfigInfo(callBack, params) {
getData(
params.methods,
USERSEVER + '/personal/config',
params.data,
data => {
callBack(data);
}
);
}
export function geetest(self, uid, params, callback) {
return geetestbase(loginFun, self, uid, params, callback, res => {
self.$message.error(res.msg)
@ -812,7 +824,7 @@ export function developercert(callBack, params) {
);
}
/**签名证书 */
/**操作日志 */
export function signoperatemessage(callBack, params) {
getData(
params.methods,
@ -824,6 +836,31 @@ export function signoperatemessage(callBack, params) {
);
}
/**异常设备管理 */
export function abnormalDeviceUtil(callBack, params) {
getData(
params.methods,
SIGNSEVER + '/abnormal/device',
params.data,
data => {
callBack(data);
}
);
}
/**异常设备管理 */
export function blackDeviceUtil(callBack, params) {
getData(
params.methods,
SIGNSEVER + '/black/device',
params.data,
data => {
callBack(data);
}
);
}
/**获取签名任务状态 */
export function gettask(callBack, params) {
getData(

@ -82,7 +82,7 @@ if (page) {
}
}
const version='2.3.2';
const version='2.3.3';
const pro_base_env = {
baseUrl: 'https://flyapps.cn',

@ -1,8 +1,9 @@
# Generated by Django 3.2.3 on 2022-02-15 21:29
from django.conf import settings
from django.db import migrations
from config import DOMAINCONF
price_info_list = [
{
"name": "1k_times",
@ -32,14 +33,14 @@ price_info_list = [
"is_enable": True,
},
]
WEB_DOMAIN = settings.WEB_DOMAIN.split('//')[1]
WEB_DOMAIN = DOMAINCONF.WEB_DOMAIN.split('//')[1]
domain_cname_info_list = [
{
"domain_record": WEB_DOMAIN,
"ip_address": WEB_DOMAIN,
"is_enable": True,
"is_system": True,
"is_https": True if settings.WEB_DOMAIN.startswith('https') else False,
"is_https": True if DOMAINCONF.WEB_DOMAIN.startswith('https') else False,
"description": f"默认下载页域名 {WEB_DOMAIN}",
},
]

@ -1,6 +1,7 @@
# Generated by Django 3.2.3 on 2022-04-12 17:44
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
@ -40,4 +41,22 @@ class Migration(migrations.Migration):
'unique_together': {('app_id', 'token')},
},
),
migrations.CreateModel(
name='UserPersonalConfig',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('value', models.TextField(max_length=10240, verbose_name='配置值')),
('enable', models.BooleanField(default=True, verbose_name='是否启用该配置项')),
('description', models.CharField(blank=True, default='', max_length=256, verbose_name='备注')),
('updated_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('key', models.CharField(max_length=128, 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': '个人配置项',
'unique_together': {('user_id', 'key')},
},
),
]

@ -235,7 +235,7 @@ class AppReleaseInfo(models.Model):
verbose_name_plural = "应用详情"
def __str__(self):
return "%s-%s" % (self.app_id, self.release_id)
return f"{self.app_id}-{self.release_id}"
class AppStorage(models.Model):
@ -279,7 +279,7 @@ class AppStorage(models.Model):
return super(AppStorage, self).save(*args, **kwargs)
def __str__(self):
return "%s %s" % (self.user_id.get_username(), self.name)
return f"{self.user_id.get_username()}-{self.name}"
class Order(models.Model):
@ -303,7 +303,7 @@ class Order(models.Model):
description = models.TextField('备注', blank=True, null=True, default='')
def __str__(self):
return "%s-%s-%s" % (self.user_id, self.order_number, self.actual_amount / 100)
return f"{self.user_id}-{self.order_number}-{self.actual_amount / 100}"
class Price(models.Model):
@ -477,21 +477,6 @@ class AppReportInfo(models.Model):
return "%s-%s" % (self.app_name, self.report_reason)
class SystemConfig(models.Model):
key = models.CharField(max_length=128, unique=True, verbose_name="配置名称")
value = models.TextField(max_length=10240, verbose_name="配置值")
enable = models.BooleanField(default=True, verbose_name="是否启用该配置项")
description = models.CharField(verbose_name="备注", max_length=256, default='', blank=True)
updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
verbose_name = '系统配置项'
verbose_name_plural = "系统配置项"
def __str__(self):
return "%s-%s" % (self.key, self.description)
class NotifyReceiver(models.Model):
receiver_name = models.CharField(max_length=128, verbose_name="姓名")
user_id = models.ForeignKey(to=UserInfo, verbose_name="用户ID", on_delete=models.CASCADE)
@ -549,3 +534,39 @@ class AppDownloadToken(models.Model):
def __str__(self):
return "%s-%s-%s" % (self.app_id, self.token, self.description)
class BaseConfig(models.Model):
value = models.TextField(max_length=10240, verbose_name="配置值")
enable = models.BooleanField(default=True, verbose_name="是否启用该配置项")
description = models.CharField(verbose_name="备注", max_length=256, default='', blank=True)
updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
verbose_name = '基础配置'
verbose_name_plural = "基础配置"
abstract = True
class SystemConfig(BaseConfig):
key = models.CharField(max_length=128, unique=True, verbose_name="配置名称")
class Meta:
verbose_name = '系统配置项'
verbose_name_plural = "系统配置项"
def __str__(self):
return "%s-%s" % (self.key, self.description)
class UserPersonalConfig(BaseConfig):
user_id = models.ForeignKey(to=UserInfo, verbose_name="用户ID", on_delete=models.CASCADE)
key = models.CharField(max_length=128, verbose_name="配置名称")
class Meta:
verbose_name = '个人配置项'
verbose_name_plural = "个人配置项"
unique_together = (('user_id', 'key'),)
def __str__(self):
return "%s-%s" % (self.key, self.description)

@ -11,7 +11,7 @@ from captcha.models import CaptchaStore
from api.utils.ctasks import sync_download_times, auto_clean_upload_tmp_file, auto_clean_remote_client_log, \
notify_check_user_download_times, notify_check_apple_developer_devices, notify_check_apple_developer_cert
from api.views.login import get_login_type
from common.core.sysconfig import Config, invalid_config_cache
from common.core.sysconfig import Config, ConfigCacheBase
from common.libs.geetest.geetest_utils import check_bypass_status
from common.libs.mp.wechat import sync_wx_access_token
from common.utils.storage import get_local_storage
@ -27,11 +27,11 @@ def start_api_sever_do_clean():
logger.info("clean local storage cache")
get_local_storage(clean_cache=True)
check_bypass_status()
invalid_config_cache()
ConfigCacheBase().invalid_config_cache()
def clean_config_cache(key):
invalid_config_cache(key)
ConfigCacheBase().invalid_config_cache(key)
@app.task

@ -27,6 +27,7 @@ from api.views.login_wx import WeChatLoginView, WeChatLoginCheckView, WeChatBind
from api.views.logout import LogoutView
from api.views.notify import NotifyReceiverView, NotifyConfigView, NotifyInfoView
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.thirdlogin import ValidWxChatToken, ThirdWxAccount
@ -62,6 +63,7 @@ urlpatterns = [
re_path("^orders$", OrderView.as_view()),
re_path("^notify/receiver$", NotifyReceiverView.as_view()),
re_path("^notify/config$", NotifyConfigView.as_view()),
re_path("^personal/config$", PersonalConfigView.as_view()),
re_path("^notify/notify$", NotifyInfoView.as_view()),
re_path("^orders.sync$", OrderSyncView.as_view()),
re_path("^certification$", CertificationView.as_view()),

@ -8,6 +8,7 @@ from api.utils.apputils import bytes2human
from api.utils.modelutils import get_user_domain_name, get_app_domain_name, get_app_download_uri
from common.base.baseutils import get_choices_dict, WeixinLoginUid
from common.cache.storage import AdPicShowCache
from common.core.sysconfig import Config
from common.utils.caches import get_user_free_download_times, get_user_cert_auth_status
from common.utils.storage import Storage
from common.utils.token import make_token
@ -507,3 +508,14 @@ class AppDownloadTokenSerializer(serializers.ModelSerializer):
class Meta:
model = models.AppDownloadToken
exclude = ["id", "app_id"]
class PersonalConfigSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserPersonalConfig
exclude = ["user_id", "id"]
title = serializers.SerializerMethodField()
def get_title(self, obj):
return getattr(Config, f'{obj.key}_DES')

@ -0,0 +1,59 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# project: fir_ser
# filename: getip
# author: liuyu
# data: 2022/3/28
import logging
from rest_framework.response import Response
from rest_framework.views import APIView
from api.models import UserPersonalConfig
from api.utils.response import BaseResponse
from api.utils.serializer import PersonalConfigSerializer
from common.core.auth import ExpiringTokenAuthentication, SuperSignPermission
from common.core.sysconfig import UserConfig, Config
logger = logging.getLogger(__name__)
class PersonalConfigView(APIView):
authentication_classes = [ExpiringTokenAuthentication, ]
permission_classes = [SuperSignPermission, ]
def get(self, request):
res = BaseResponse()
config_key = request.query_params.get("config_key", None)
developer_personal_config = Config.DEVELOPER_STATUS_CONFIG
personal_config_queryset = UserPersonalConfig.objects.filter(user_id=request.user,
key__in=developer_personal_config)
if personal_config_queryset.count() != len(developer_personal_config):
for key in developer_personal_config:
UserConfig(request.user).set_default_value(key)
if config_key:
personal_config_queryset = personal_config_queryset.filter(key=config_key)
personal_config_serializer = PersonalConfigSerializer(personal_config_queryset, many=True)
res.data = personal_config_serializer.data
res.count = personal_config_queryset.count()
return Response(res.dict)
def put(self, request):
res = BaseResponse()
config_key = request.data.get("config_key", None)
config_value = request.data.get("config_value", None)
if config_key is not None and config_value is not None and config_key in Config.DEVELOPER_STATUS_CONFIG:
UserPersonalConfig.objects.filter(user_id=request.user, key=config_key).update(value=config_value)
return Response(res.dict)
def delete(self, request):
res = BaseResponse()
developer_personal_config = Config.DEVELOPER_STATUS_CONFIG
for key in developer_personal_config:
UserConfig(request.user).del_value(key)
Config.invalid_config_cache(f'{key}_DES')
return Response(res.dict)

@ -18,10 +18,10 @@ from api.utils.response import BaseResponse
from api.utils.serializer import WeixinInfoSerializer
from api.views.login import get_login_type
from common.core.auth import ExpiringTokenAuthentication
from common.core.sysconfig import Config
from common.libs.mp.chat import reply, receive
from common.libs.mp.wechat import check_signature, WxMsgCrypt, get_userinfo_from_openid, WxTemplateMsg
from common.utils.caches import set_wx_ticket_login_info_cache, get_wx_ticket_login_info_cache
from config import WEB_DOMAIN
logger = logging.getLogger(__name__)
@ -195,7 +195,7 @@ class ValidWxChatToken(APIView):
logger.info(f"replay msg: {result}")
return HttpResponse(result)
elif rec_msg.Eventkey == 'flyapps':
reply_msg = reply.TextMsg(to_user, from_user, WEB_DOMAIN)
reply_msg = reply.TextMsg(to_user, from_user, Config.WEB_DOMAIN)
result = reply_msg.send()
logger.info(f"replay msg: {result}")
return HttpResponse(result)

@ -205,9 +205,9 @@ class SignUdidQueueCache(RedisCacheBase):
super().__init__(self.cache_key)
class SystemConfigCache(RedisCacheBase):
class UserSystemConfigCache(RedisCacheBase):
def __init__(self, prefix_key):
self.cache_key = f"{CACHE_KEY_TEMPLATE.get('sysconfig_key')}_{prefix_key}"
self.cache_key = f"{CACHE_KEY_TEMPLATE.get('config_key')}_{prefix_key}"
super().__init__(self.cache_key)

@ -12,10 +12,10 @@ from django.template import Context, Template
from django.template.base import VariableNode
from rest_framework import serializers
from api.models import SystemConfig
from common.cache.storage import SystemConfigCache
from config import BASECONF, API_DOMAIN, MOBILEPROVISION, WEB_DOMAIN, THIRDLOGINCONF, AUTHCONF, IPACONF, \
DOWNLOADTIMESCONF, PAYCONF, STORAGEKEYCONF, SENDERCONF, APPLEDEVELOPERCONF
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
logger = logging.getLogger(__name__)
@ -37,41 +37,48 @@ def get_render_context(tmp: str, context: dict) -> str:
return template.render(context)
def invalid_config_cache(key='*'):
SystemConfigCache(key).del_many()
def get_render_value(value):
if value:
try:
context_dict = {}
for sys_obj_dict in SystemConfig.objects.filter(enable=True).values().all():
if re.findall('{{.*%s.*}}' % sys_obj_dict['key'], sys_obj_dict['value']):
logger.warning(f"get same render key. so continue")
continue
context_dict[sys_obj_dict['key']] = sys_obj_dict['value']
value = get_render_context(value, context_dict)
except Exception as e:
logger.warning(f"db config - render failed {e}")
return value
class ConfigCacheBase(object):
def __init__(self, px=''):
def __init__(self, px='system', model=SystemConfig, cache=UserSystemConfigCache, serializer=SystemConfigSerializer,
timeout=60 * 60 * 24 * 30):
self.px = px
self.model = model
self.cache = cache
self.timeout = timeout
self.serializer = serializer
def invalid_config_cache(self, key='*'):
UserSystemConfigCache(f'{self.px}_{key}').del_many()
def get_render_value(self, value):
if value:
try:
context_dict = {}
for sys_obj_dict in self.model.objects.filter(enable=True).values().all():
if re.findall('{{.*%s.*}}' % sys_obj_dict['key'], sys_obj_dict['value']):
logger.warning(f"get same render key. so continue")
continue
context_dict[sys_obj_dict['key']] = sys_obj_dict['value']
value = get_render_context(value, context_dict)
except Exception as e:
logger.warning(f"db config - render failed {e}")
return value
def get_value_from_db(self, key):
data = SystemConfigSerializer(SystemConfig.objects.filter(enable=True, key=key).first()).data
data = self.serializer(self.model.objects.filter(enable=True, key=key).first()).data
if re.findall('{{.*%s.*}}' % data['key'], data['value']):
logger.warning(f"get same render key:{key}. so get default value")
data['key'] = ''
return data
def get_default_data(self, key, default_data):
if default_data is None:
default_data = {}
return default_data
def get_value(self, key, data=None):
if data is None:
data = {}
cache = SystemConfigCache(key)
data = self.get_default_data(key, data)
cache = self.cache(f'{self.px}_{key}')
cache_data = cache.get_storage_cache()
if cache_data is not None and cache_data.get('key', '') == key:
return cache_data.get('value')
@ -80,19 +87,37 @@ class ConfigCacheBase(object):
if d_key != key and data is not None:
db_data['value'] = json.dumps(data)
db_data['key'] = key
db_data['value'] = get_render_value(db_data['value'])
db_data['value'] = self.get_render_value(db_data['value'])
try:
db_data['value'] = json.loads(db_data['value'])
except Exception as e:
logger.warning(f"db config - json loads failed {e}")
cache.set_storage_cache(db_data, timeout=60 * 60 * 24 * 30)
cache.set_storage_cache(db_data, timeout=self.timeout)
return db_data.get('value')
def set_value(self, key, value):
def save_db(self, key, value, enable, description, **kwargs):
defaults = {'value': value}
if enable is not None:
defaults['enable'] = enable
if description is not None:
defaults['description'] = description
self.model.objects.update_or_create(key=key, defaults=defaults, **kwargs)
def delete_db(self, key, **kwargs):
self.model.objects.filter(key=key, **kwargs).delete()
def set_value(self, key, value, enable=None, description=None, **kwargs):
if not isinstance(value, str):
value = json.dumps(value)
SystemConfig.objects.update_or_create(key=key, defaults={'value': value})
SystemConfigCache(key).del_storage_cache()
self.save_db(key, value, enable, description, **kwargs)
self.cache(f'{self.px}_{key}').del_storage_cache()
def set_default_value(self, key, **kwargs):
self.set_value(key, self.get_value(key, None), **kwargs)
def del_value(self, key, **kwargs):
self.delete_db(key, **kwargs)
self.cache(f'{self.px}_{key}').del_storage_cache()
def __getattribute__(self, name):
try:
@ -101,17 +126,27 @@ class ConfigCacheBase(object):
logger.error(f"__getattribute__ Error {e} {name}")
return self.get_value(name)
@property
def MOBILEPROVISION(self):
return self.get_value('MOBILEPROVISION', MOBILEPROVISION)
class DomainConfCache(ConfigCacheBase):
def __init__(self, *args, **kwargs):
super(DomainConfCache, self).__init__(*args, **kwargs)
@property
def API_DOMAIN(self):
return self.get_value('API_DOMAIN', API_DOMAIN)
return self.get_value('API_DOMAIN', DOMAINCONF.API_DOMAIN)
@property
def MOBILEPROVISION(self):
return self.get_value('MOBILEPROVISION', DOMAINCONF.MOBILEPROVISION)
@property
def WEB_DOMAIN(self):
return self.get_value('WEB_DOMAIN', WEB_DOMAIN)
return self.get_value('WEB_DOMAIN', DOMAINCONF.WEB_DOMAIN)
class BaseConfCache(ConfigCacheBase):
def __init__(self, *args, **kwargs):
super(BaseConfCache, self).__init__(*args, **kwargs)
@property
def POST_UDID_DOMAIN(self):
@ -127,12 +162,7 @@ class ConfigCacheBase(object):
@property
def WECHAT_WEB_LOGIN_REDIRECT_DOMAIN(self):
return self.get_value('WECHAT_WEB_LOGIN_REDIRECT_DOMAIN')
class BaseConfCache(ConfigCacheBase):
def __init__(self):
super(BaseConfCache, self).__init__()
return self.get_value('WECHAT_WEB_LOGIN_REDIRECT_DOMAIN', self.API_DOMAIN)
@property
def IOS_PMFILE_DOWNLOAD_DOMAIN(self):
@ -142,18 +172,10 @@ class BaseConfCache(ConfigCacheBase):
'is_https': True if self.API_DOMAIN.split("://")[0] == "https" else False,
}
@property
def DEFAULT_THROTTLE_RATES(self):
"""
暂时无用
:return:
"""
return super().get_value('DEFAULT_THROTTLE_RATES', BASECONF.DEFAULT_THROTTLE_RATES)
class IpaConfCache(ConfigCacheBase):
def __init__(self):
super(IpaConfCache, self).__init__()
def __init__(self, *args, **kwargs):
super(IpaConfCache, self).__init__(*args, **kwargs)
@property
def MOBILE_CONFIG_SIGN_SSL(self):
@ -177,21 +199,21 @@ class IpaConfCache(ConfigCacheBase):
访问苹果api超时时间默认2分钟
:return:
"""
return super().get_value('APPLE_DEVELOPER_API_TIMEOUT', 2 * 60)
return super().get_value('APPLE_DEVELOPER_API_TIMEOUT', IPACONF.APPLE_DEVELOPER_API_TIMEOUT)
class WechatConfCache(ConfigCacheBase):
def __init__(self):
super(WechatConfCache, self).__init__()
def __init__(self, *args, **kwargs):
super(WechatConfCache, self).__init__(*args, **kwargs)
@property
def THIRDLOGINCONF(self):
return super().get_value('THIRDLOGINCONF', THIRDLOGINCONF.wx_official)
class AuthConfCache(WechatConfCache):
def __init__(self):
super(AuthConfCache, self).__init__()
class AuthConfCache(ConfigCacheBase):
def __init__(self, *args, **kwargs):
super(AuthConfCache, self).__init__(*args, **kwargs)
@property
def REGISTER(self):
@ -219,8 +241,8 @@ class AuthConfCache(WechatConfCache):
class GeeTestConfCache(ConfigCacheBase):
def __init__(self):
super(GeeTestConfCache, self).__init__()
def __init__(self, *args, **kwargs):
super(GeeTestConfCache, self).__init__(*args, **kwargs)
@property
def GEETEST_ID(self):
@ -248,8 +270,8 @@ class GeeTestConfCache(ConfigCacheBase):
class UserDownloadTimesCache(ConfigCacheBase):
def __init__(self):
super(UserDownloadTimesCache, self).__init__()
def __init__(self, *args, **kwargs):
super(UserDownloadTimesCache, self).__init__(*args, **kwargs)
@property
def USER_FREE_DOWNLOAD_TIMES(self):
@ -269,8 +291,8 @@ class UserDownloadTimesCache(ConfigCacheBase):
class PayConfCache(ConfigCacheBase):
def __init__(self):
super(PayConfCache, self).__init__()
def __init__(self, *args, **kwargs):
super(PayConfCache, self).__init__(*args, **kwargs)
@property
def PAY_SUCCESS_URL(self):
@ -285,14 +307,19 @@ class PayConfCache(ConfigCacheBase):
return super().get_value('PAY_CONFIG_KEY_INFO', PAYCONF.PAY_CONFIG_KEY_INFO)
class ThirdPartConfCache(ConfigCacheBase):
def __init__(self):
super(ThirdPartConfCache, self).__init__()
class ThirdStorageConfCache(ConfigCacheBase):
def __init__(self, *args, **kwargs):
super(ThirdStorageConfCache, self).__init__(*args, **kwargs)
@property
def STORAGE(self):
return super().get_value('STORAGE', STORAGEKEYCONF.STORAGE)
class ThirdPartConfCache(ConfigCacheBase):
def __init__(self, *args, **kwargs):
super(ThirdPartConfCache, self).__init__(*args, **kwargs)
@property
def SENDER(self):
return super().get_value('SENDER', SENDERCONF.SENDER)
@ -307,8 +334,8 @@ class ThirdPartConfCache(ConfigCacheBase):
class AppleDeveloperConfCache(ConfigCacheBase):
def __init__(self):
super(AppleDeveloperConfCache, self).__init__()
def __init__(self, *args, **kwargs):
super(AppleDeveloperConfCache, self).__init__(*args, **kwargs)
@property
def DEVELOPER_USE_STATUS(self):
@ -334,11 +361,87 @@ class AppleDeveloperConfCache(ConfigCacheBase):
def DEVELOPER_UID_KEY(self):
return super().get_value('DEVELOPER_UID_KEY', APPLEDEVELOPERCONF.DEVELOPER_UID_KEY)
@property
def DEVELOPER_WAIT_ABNORMAL_DEVICE(self):
return super().get_value('DEVELOPER_WAIT_ABNORMAL_DEVICE', APPLEDEVELOPERCONF.DEVELOPER_WAIT_ABNORMAL_DEVICE)
@property
def DEVELOPER_ABNORMAL_DEVICE_WRITE(self):
return super().get_value('DEVELOPER_ABNORMAL_DEVICE_WRITE', APPLEDEVELOPERCONF.DEVELOPER_ABNORMAL_DEVICE_WRITE)
@property
def DEVELOPER_WAIT_STATUS(self):
return super().get_value('DEVELOPER_WAIT_STATUS', APPLEDEVELOPERCONF.DEVELOPER_WAIT_STATUS)
@property
def DEVELOPER_WAIT_ABNORMAL_STATE(self):
return super().get_value('DEVELOPER_WAIT_ABNORMAL_STATE', APPLEDEVELOPERCONF.DEVELOPER_WAIT_ABNORMAL_STATE)
class UserPersonalConfKeyCache(ConfigCacheBase):
def __init__(self, *args, **kwargs):
super(UserPersonalConfKeyCache, self).__init__(*args, **kwargs)
@property
def DEVELOPER_STATUS_CONFIG(self):
return super().get_value('DEVELOPER_STATUS_CONFIG', USERPERSONALCONFIGKEY.DEVELOPER_STATUS_CONFIG)
class ConfigDescriptionCache(ConfigCacheBase):
def __init__(self, *args, **kwargs):
super(ConfigDescriptionCache, self).__init__(*args, **kwargs)
@property
def DEVELOPER_WAIT_ABNORMAL_DEVICE_DES(self):
return super().get_value('DEVELOPER_WAIT_ABNORMAL_DEVICE_DES',
CONFIGDESCRIPTION.DEVELOPER_WAIT_ABNORMAL_DEVICE_DES)
@property
def DEVELOPER_ABNORMAL_DEVICE_WRITE_DES(self):
return super().get_value('DEVELOPER_ABNORMAL_DEVICE_WRITE_DES',
CONFIGDESCRIPTION.DEVELOPER_ABNORMAL_DEVICE_WRITE_DES)
@property
def DEVELOPER_WAIT_ABNORMAL_STATE_DES(self):
return super().get_value('DEVELOPER_WAIT_ABNORMAL_STATE_DES',
CONFIGDESCRIPTION.DEVELOPER_WAIT_ABNORMAL_STATE_DES)
class ConfigCache(BaseConfCache, IpaConfCache, AuthConfCache, UserDownloadTimesCache, GeeTestConfCache, PayConfCache,
ThirdPartConfCache, AppleDeveloperConfCache):
def __init__(self):
super(ConfigCache, self).__init__()
class ConfigCache(BaseConfCache, DomainConfCache, IpaConfCache, AuthConfCache, UserDownloadTimesCache, GeeTestConfCache,
PayConfCache, ThirdStorageConfCache, ThirdPartConfCache, AppleDeveloperConfCache,
UserPersonalConfKeyCache, ConfigDescriptionCache):
def __init__(self, *args, **kwargs):
super(ConfigCache, self).__init__(*args, **kwargs)
Config = ConfigCache()
class UserConfigSerializer(serializers.ModelSerializer):
class Meta:
model = UserPersonalConfig
fields = "__all__"
class UserPersonalConfigCache(ConfigCache):
def __init__(self, user_obj):
self.user_obj = user_obj
super().__init__(f'user_{user_obj.uid}', UserPersonalConfig, UserSystemConfigCache, UserConfigSerializer)
def get_default_data(self, key, default_data):
n_data = getattr(Config, key)
if n_data is None:
n_data = {}
return n_data
def delete_db(self, key, **kwargs):
super(UserPersonalConfigCache, self).delete_db(key, user_id=self.user_obj)
def save_db(self, key, value, enable=None, description=None, **kwargs):
super(UserPersonalConfigCache, self).save_db(key, value, enable, description, user_id=self.user_obj)
def set_default_value(self, key, **kwargs):
super(UserPersonalConfigCache, self).set_default_value(key, user_id=self.user_obj)
UserConfig = UserPersonalConfigCache

@ -82,7 +82,7 @@ class DevicesAPI(object):
# https://developer.apple.com/documentation/appstoreconnectapi/devices
def __init__(self, base_uri, jwt_headers):
self.headers = jwt_headers
self.devices_url = '%s/devices' % base_uri
self.devices_url = f'{base_uri}/devices'
def list_devices(self, query_parameters=None):
"""
@ -150,7 +150,7 @@ class DevicesAPI(object):
403 ErrorResponse Forbidden Request not authorized. Content-Type: application/json
404 ErrorResponse Not Found Resource not found. Content-Type: application/json
"""
base_url = '%s/%s' % (self.devices_url, device_id)
base_url = f'{self.devices_url}/{device_id}'
params = {
"fields[devices]": "addedDate, deviceClass, model, name, platform, status, udid",
}
@ -176,7 +176,7 @@ class DevicesAPI(object):
404 ErrorResponse Not Found Resource not found. Content-Type: application/json
409 ErrorResponse Conflict The provided resource data is not valid. Content-Type: application/json
"""
base_url = '%s/%s' % (self.devices_url, device_id)
base_url = f'{self.devices_url}/{device_id}'
json = {
'data': {
'type': 'devices',
@ -196,7 +196,7 @@ class BundleIDsAPI(object):
# https://developer.apple.com/documentation/appstoreconnectapi/bundle_ids
def __init__(self, base_uri, jwt_headers):
self.headers = jwt_headers
self.bundle_ids_url = '%s/bundleIds' % base_uri
self.bundle_ids_url = f'{base_uri}/bundleIds'
def register_bundle_id(self, bundle_id_name, bundle_id_identifier, platform="IOS", seed_id=''):
"""
@ -236,7 +236,7 @@ class BundleIDsAPI(object):
404 ErrorResponse Not Found Resource not found. Content-Type: application/json
409 ErrorResponse Conflict The provided resource data is not valid. Content-Type: application/json
"""
base_url = '%s/%s' % (self.bundle_ids_url, bundle_id)
base_url = f'{self.bundle_ids_url}/{bundle_id}'
json = {}
return request_format_log(self,
requests.delete(base_url, json=json, headers=self.headers, proxies=self.proxies,
@ -280,7 +280,7 @@ class BundleIDsAPI(object):
404 ErrorResponse Not Found Resource not found. Content-Type: application/json
409 ErrorResponse Conflict The provided resource data is not valid. Content-Type: application/json
"""
base_url = '%s/%s' % (self.bundle_ids_url, bundle_id)
base_url = f'{self.bundle_ids_url}/{bundle_id}'
json = {
'data': {
'type': 'bundleIds',
@ -299,7 +299,7 @@ class BundleIDsCapabilityAPI(object):
# https://developer.apple.com/documentation/appstoreconnectapi/bundle_id_capabilities
def __init__(self, base_uri, jwt_headers):
self.headers = jwt_headers
self.bundle_ids_capability_url = '%s/bundleIdCapabilities' % base_uri
self.bundle_ids_capability_url = f'{base_uri}/bundleIdCapabilities'
def disable_capability(self, bundle_id, capability_type):
"""
@ -312,7 +312,7 @@ class BundleIDsCapabilityAPI(object):
404 ErrorResponse Not Found Resource not found. Content-Type: application/json
409 ErrorResponse Conflict The provided resource data is not valid. Content-Type: application/json
"""
base_url = '%s/%s_%s' % (self.bundle_ids_capability_url, bundle_id, capability_type)
base_url = f'{self.bundle_ids_capability_url}/{bundle_id}_{capability_type}'
json = {}
return request_format_log(self,
requests.delete(base_url, json=json, headers=self.headers, proxies=self.proxies,
@ -355,7 +355,7 @@ class ProfilesAPI(object):
# https://developer.apple.com/documentation/appstoreconnectapi/profiles
def __init__(self, base_uri, jwt_headers):
self.headers = jwt_headers
self.profiles_url = '%s/profiles' % base_uri
self.profiles_url = f'{base_uri}/profiles'
def create_profile(self, bundle_id, certificate_id_list, profile_name, device_id_list,
profile_type='IOS_APP_ADHOC'):
@ -415,7 +415,7 @@ class ProfilesAPI(object):
404 ErrorResponse Not Found Resource not found. Content-Type: application/json
409 ErrorResponse Conflict The provided resource data is not valid. Content-Type: application/json
"""
base_url = '%s/%s' % (self.profiles_url, profile_id)
base_url = f'{self.profiles_url}/{profile_id}'
json = {}
return request_format_log(self,
requests.delete(base_url, json=json, headers=self.headers, proxies=self.proxies,
@ -460,7 +460,7 @@ class CertificatesAPI(object):
# https://developer.apple.com/documentation/appstoreconnectapi/certificates
def __init__(self, base_uri, jwt_headers):
self.headers = jwt_headers
self.certificates_url = '%s/certificates' % base_uri
self.certificates_url = f'{base_uri}/certificates'
def create_certificate(self, csr_content, certificate_type='IOS_DISTRIBUTION'):
"""
@ -528,7 +528,7 @@ class CertificatesAPI(object):
404 ErrorResponse Not Found Resource not found. Content-Type: application/json
409 ErrorResponse Conflict The provided resource data is not valid. Content-Type: application/json
"""
base_url = '%s/%s' % (self.certificates_url, certificate_id)
base_url = f'{self.certificates_url}/{certificate_id}'
json = {}
return request_format_log(self,
requests.delete(base_url, json=json, headers=self.headers, proxies=self.proxies,

@ -26,7 +26,7 @@ class Alipay(object):
def __get_ali_pay(self):
return AliPay(
appid=self.ali_config.get("APP_ID"),
app_notify_url="%s/%s" % (self.ali_config.get("APP_NOTIFY_URL"), self.name),
app_notify_url=f"{self.ali_config.get('APP_NOTIFY_URL')}/{self.name}",
app_private_key_string=self.ali_config.get("APP_PRIVATE_KEY"),
alipay_public_key_string=self.ali_config.get("ALI_PUBLIC_KEY"),
sign_type="RSA2", # RSA 或者 RSA2
@ -42,12 +42,12 @@ class Alipay(object):
out_trade_no=out_trade_no,
total_amount=total_amount / 100,
subject=self.ali_config.get("SUBJECT"),
body="充值 %s" % str(total_amount / 100),
body=f"充值 {total_amount / 100}",
time_expire=time_expire,
return_url=self.ali_config.get("RETURN_URL"),
passback_params=json.dumps(passback_params)
)
result = {'type': self.p_type, 'url': "https://openapi.alipay.com/gateway.do?%s" % order_string,
result = {'type': self.p_type, 'url': f"https://openapi.alipay.com/gateway.do?{order_string}",
'out_trade_no': out_trade_no}
logger.info(f"支付宝支付连接生成成功 {result}")
return result

@ -147,7 +147,7 @@ class AliYunOss(object):
{
"Action": p_action,
"Effect": "Allow",
"Resource": ["acs:oss:*:*:%s/%s" % (self.bucket_name, name), ]
"Resource": [f"acs:oss:*:*:{self.bucket_name}/{name}"]
}
],
"Version": "1"
@ -203,7 +203,7 @@ class AliYunOss(object):
if os.path.isfile(local_file_full_path):
filename = os.path.basename(local_file_full_path)
headers = {
'Content-Disposition': 'attachment; filename="%s"' % filename,
'Content-Disposition': f'attachment; filename="{filename}"',
'Cache-Control': ''
}
self.bucket.put_object_from_file(filename, local_file_full_path, headers)

@ -184,7 +184,7 @@ def get_user_free_download_times(user_id, act='get', amount=1, auth_status=False
def consume_user_download_times(user_id, app_id, amount=1, auth_status=False):
with cache.lock("%s_%s" % ('consume_user_download_times', user_id), timeout=10, blocking_timeout=6):
with cache.lock(f"consume_user_download_times_{user_id}", timeout=10, blocking_timeout=6):
if get_user_free_download_times(user_id, 'get', amount, auth_status) - amount >= 0:
get_user_free_download_times(user_id, 'set', amount, auth_status)
else:
@ -210,7 +210,7 @@ def enable_user_download(user_id):
def add_user_download_times(user_id, download_times=0):
with cache.lock("%s_%s" % ('consume_user_download_times', user_id), timeout=10, blocking_timeout=6):
with cache.lock(f"consume_user_download_times_{user_id}", timeout=10, blocking_timeout=6):
try:
UserInfo.objects.filter(pk=user_id).update(download_times=F('download_times') + download_times)
return enable_user_download(user_id)
@ -259,7 +259,7 @@ def user_auth_success(user_id):
def update_order_status(out_trade_no, status):
with cache.lock("%s_%s" % ('user_order_', out_trade_no), timeout=10, blocking_timeout=6):
with cache.lock(f"user_order_{out_trade_no}", timeout=10, blocking_timeout=6):
order_obj = Order.objects.filter(order_number=out_trade_no).first()
if order_obj:
order_obj.status = status

@ -7,12 +7,15 @@ import os
from common.constants import AppleDeveloperStatus
API_DOMAIN = "https://app.hehelucky.cn"
WEB_DOMAIN = "https://app.hehelucky.cn"
MOBILEPROVISION = "https://static.flyapps.top/embedded1.mobileprovision"
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
class DOMAINCONF(object):
API_DOMAIN = "https://app.hehelucky.cn"
WEB_DOMAIN = "https://app.hehelucky.cn"
MOBILEPROVISION = "https://static.flyapps.top/embedded2.mobileprovision"
class BASECONF(object):
VERSION = '1.3.1'
@ -146,10 +149,10 @@ class STORAGEKEYCONF(object):
'name': 'local',
'type': 0,
'auth': {
'domain_name': API_DOMAIN.split("://")[1],
'domain_name': DOMAINCONF.API_DOMAIN.split("://")[1],
# 正式环境需要填写正式的访问域名,如果配置cdn,可以填写cdn的域名,仅支持阿里云 cdn,
# 开启cdn之后,如果该域名和服务器域名不相同,需要设置阿里云cdn 缓存配置,自定义HTTP响应头 添加 Access-Control-Allow-Origin * 才可以
'is_https': True if API_DOMAIN.split("://")[0] == "https" else False,
'is_https': True if DOMAINCONF.API_DOMAIN.split("://")[0] == "https" else False,
'download_auth_type': 1, # 0:不开启token 1:本地token 2:cdn 开启cdn,并且使用本地存储,使用阿里云cdn进行url鉴权,
'cnd_auth_key': '', # 当cdn为阿里云并且 download_auth_type=2 的时候 生效,需要 开启阿里云OSS私有Bucket回源
},
@ -290,12 +293,12 @@ class IPACONF(object):
# 'http': '47.243.172.202:17897',
# 'https': '47.243.172.202:17897'
}
APPLE_DEVELOPER_API_TIMEOUT = 60 # 访问苹果api超时时间,默认3分钟
APPLE_DEVELOPER_API_TIMEOUT = 2 * 60 # 访问苹果api超时时间,默认3分钟
MOBILE_CONFIG_SIGN_SSL = {
# 描述文件是否签名,默认是关闭状态;如果开启,并且ssl_key_path 和 ssl_pem_path 正常,则使用填写的ssl进行签名,否则默认不签名
'open': True,
'ssl_key_path': f'/data/cert/{API_DOMAIN.split("://")[1]}.key',
'ssl_pem_path': f'/data/cert/{API_DOMAIN.split("://")[1]}.pem'
'ssl_key_path': f'/data/cert/{DOMAINCONF.API_DOMAIN.split("://")[1]}.key',
'ssl_pem_path': f'/data/cert/{DOMAINCONF.API_DOMAIN.split("://")[1]}.pem'
}
DEFAULT_MOBILEPROVISION = {
# 默认描述文件路径或者下载路径,用户企业签名或者超级签名 跳转 [设置 - 通用 - 描述文件|设备管理] 页面
@ -403,10 +406,45 @@ class APPLEDEVELOPERCONF(object):
AppleDeveloperStatus.DEVICE_ABNORMAL, AppleDeveloperStatus.ABNORMAL_STATUS]
# 开发者api写操作查询[该状态用于苹果api接口]
DEVELOPER_WRITE_STATUS = [AppleDeveloperStatus.ACTIVATED, AppleDeveloperStatus.MAINTENANCE,
AppleDeveloperStatus.CERTIFICATE_EXPIRED, AppleDeveloperStatus.CERTIFICATE_MISSING,
AppleDeveloperStatus.DEVICE_ABNORMAL]
AppleDeveloperStatus.CERTIFICATE_EXPIRED, AppleDeveloperStatus.DEVICE_ABNORMAL]
# 开发者不可 修改为状态,用户前端控制
DEVELOPER_DISABLED_STATUS = [AppleDeveloperStatus.AGREEMENT_NOT_AGREED, AppleDeveloperStatus.CERTIFICATE_EXPIRED,
AppleDeveloperStatus.CERTIFICATE_MISSING, AppleDeveloperStatus.DEVICE_ABNORMAL]
DEVELOPER_UID_KEY = "T:" # 开发者共享给其他第三方用户, 中间必须包含 : 前端需要根据 : 进行分割
"""
DEVELOPER_WAIT_STATUS 开发者等待状态详情查看 model.AppIOSDeveloperInfo DEVELOPER_WAIT_ABNORMAL_STATE 状态有关
DEVELOPER_WAIT_ABNORMAL_STATE 开发者异常状态是否等待,True 表示等待直到开发者状态恢复正常并且设备注册成功 False 表示忽略
继续下一个新开发者账户
"""
DEVELOPER_WAIT_STATUS = [AppleDeveloperStatus.MAINTENANCE, AppleDeveloperStatus.CERTIFICATE_EXPIRED,
AppleDeveloperStatus.CERTIFICATE_MISSING, AppleDeveloperStatus.ABNORMAL_STATUS]
DEVELOPER_WAIT_ABNORMAL_STATE = True
# 开发者共享给其他第三方用户, 中间必须包含 : 前端需要根据 : 进行分割
DEVELOPER_UID_KEY = "T:"
"""
DEVELOPER_WAIT_ABNORMAL_DEVICE 异常设备注册是否等待,True 表示等待直到注册成功False 表示忽略继续下一个新开发者账户
DEVELOPER_ABNORMAL_DEVICE_WRITE 受DEVELOPER_WAIT_ABNORMAL_DEVICE影响当DEVELOPER_WAIT_ABNORMAL_DEVICE 为True 才生效
异常设备注册等待中是否还继续注册新设备, True 表示设备异常下继续注册新设备
"""
DEVELOPER_WAIT_ABNORMAL_DEVICE = True
DEVELOPER_ABNORMAL_DEVICE_WRITE = False
class CONFIGDESCRIPTION(object):
DEVELOPER_WAIT_ABNORMAL_DEVICE_DES = '【异常设备注册等待】:开启 表示等待直到注册成功,关闭 表示忽略。' \
'ps:异常设备指 设备注册到开发者,但是设备状态为 “不合格”,“处理中” 时,既设备不可用,' \
'开启等待,则等待设备状态恢复正常,最大避免设备数重复消耗'
DEVELOPER_ABNORMAL_DEVICE_WRITE_DES = '【异常设备注册等待中继续注册新设备】:开启 表示新设备还可以注册该 设备异常 开发者中,关闭 表示忽略。 ' \
'ps:该配置仅当开启【异常设备注册等待】时生效'
DEVELOPER_WAIT_ABNORMAL_STATE_DES = '【开发者异常状态是否等待】:开启 表示等待直到开发者状态恢复正常,并且设备注册成功,关闭 表示忽略。 ps:当开发者状态为 ' \
'“维护”,“证书丢失”,“证书过期”,“异常状态时”,开启等待,可以最大的避免设备数重复消耗 '
class USERPERSONALCONFIGKEY(object):
DEVELOPER_STATUS_CONFIG = ['DEVELOPER_WAIT_ABNORMAL_STATE', 'DEVELOPER_WAIT_ABNORMAL_DEVICE',
'DEVELOPER_ABNORMAL_DEVICE_WRITE']

@ -259,7 +259,7 @@ CORS_ALLOW_HEADERS = (
)
CACHE_KEY_TEMPLATE = {
'sysconfig_key': 'sysconfig',
'config_key': 'config',
'task_state_key': 'task_state',
'pending_state_key': 'pending_state',
'wx_login_bind_key': 'wx_login_bind',

@ -0,0 +1,48 @@
# Generated by Django 3.2.3 on 2022-04-15 14:39
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('xsign', '0002_auto_20220405_1452'),
]
operations = [
migrations.CreateModel(
name='DeviceBlackUDID',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('udid', models.CharField(max_length=64, unique=True, verbose_name='手动添加设备udid')),
('enable', models.BooleanField(default=True, verbose_name='是否生效')),
('description', models.CharField(blank=True, default='', max_length=256, verbose_name='备注')),
('created_time', models.DateTimeField(auto_now=True, verbose_name='添加时间')),
('user_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL,
verbose_name='操作用户')),
],
options={
'verbose_name': '设备黑名单',
'verbose_name_plural': '设备黑名单',
},
),
migrations.CreateModel(
name='DeviceAbnormalUDID',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('auto_remove', models.BooleanField(default=True, verbose_name='是否自动移除')),
('description', models.CharField(blank=True, default='', max_length=256, verbose_name='备注')),
('created_time', models.DateTimeField(auto_now_add=True, verbose_name='添加时间')),
('udid', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='xsign.udidsyncdeveloper',
verbose_name='自动添加设备udid')),
('user_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL,
verbose_name='操作用户')),
],
options={
'verbose_name': '异常设备信息',
'verbose_name_plural': '异常设备信息',
},
),
]

@ -57,7 +57,7 @@ class AppIOSDeveloperInfo(models.Model):
return super(AppIOSDeveloperInfo, self).save(*args, **kwargs)
def __str__(self):
return f"{self.user_id}-{self.issuer_id}"
return f"{self.user_id}-{self.issuer_id}-{self.get_status_display()}-{self.description}"
class UDIDsyncDeveloper(models.Model):
@ -166,7 +166,7 @@ class DeveloperAppID(models.Model):
unique_together = ('aid', 'developerid', 'app_id')
def __str__(self):
return "%s-%s-%s" % (self.aid, self.app_id, self.developerid)
return f"{self.aid}-{self.app_id}-{self.developerid}"
class DeveloperDevicesID(models.Model):
@ -184,7 +184,7 @@ class DeveloperDevicesID(models.Model):
unique_together = ('did', 'developerid', 'app_id')
def __str__(self):
return "%s-%s-%s-%s" % (self.id, self.app_id, self.developerid, self.udid)
return f"{self.id}-{self.app_id}-{self.developerid}-{self.udid}"
class IosDeveloperPublicPoolBill(models.Model):
@ -210,7 +210,7 @@ class IosDeveloperPublicPoolBill(models.Model):
verbose_name_plural = "设备消耗账单"
def __str__(self):
return "%s-%s" % (self.user_id, self.description)
return f"{self.user_id}-{self.description}"
class IosDeveloperBill(models.Model):
@ -236,7 +236,7 @@ class IosDeveloperBill(models.Model):
verbose_name_plural = "设备划转账单"
def __str__(self):
return "%s-%s%s" % (self.user_id, self.to_user_id, self.description)
return f"{self.user_id}-{self.to_user_id}{self.description}"
class AppleDeveloperToAppUse(models.Model):
@ -255,7 +255,7 @@ class AppleDeveloperToAppUse(models.Model):
unique_together = ('app_id', 'developerid')
def __str__(self):
return "%s-%s" % (self.app_id.name, self.developerid.issuer_id)
return f"{self.app_id.name}-{self.developerid.issuer_id}"
class OperateMessageBase(models.Model):
@ -282,4 +282,41 @@ class AppleSignMessage(OperateMessageBase):
verbose_name_plural = "应用签名操作记录"
def __str__(self):
return "%s-%s-%s" % (self.app_id.name, self.developerid.issuer_id, self.title)
return f"{self.app_id.name}-{self.developerid.issuer_id}-{self.title}"
class DeviceBlackUDID(models.Model):
"""
设备黑名单
"""
user_id = models.ForeignKey(to=UserInfo, verbose_name="操作用户", on_delete=models.CASCADE)
udid = models.CharField(max_length=64, verbose_name='手动添加设备udid', unique=True)
enable = models.BooleanField(verbose_name="是否生效", default=True)
description = models.CharField(verbose_name="备注", max_length=256, default='', blank=True)
created_time = models.DateTimeField(auto_now=True, verbose_name="添加时间")
class Meta:
verbose_name = '设备黑名单'
verbose_name_plural = "设备黑名单"
def __str__(self):
return f"{self.udid}-{self.description}-{self.created_time}"
class DeviceAbnormalUDID(models.Model):
"""
异常设备
系统根据 异常状态等待 判断是否等待等待的数据将会插入该库并在下次注册或者设备检测 根据是否自动移除进行操作
"""
user_id = models.ForeignKey(to=UserInfo, verbose_name="操作用户", on_delete=models.CASCADE)
udid = models.ForeignKey(to=UDIDsyncDeveloper, verbose_name="自动添加设备udid", on_delete=models.CASCADE)
auto_remove = models.BooleanField(verbose_name="是否自动移除", default=True)
description = models.CharField(verbose_name="备注", max_length=256, default='', blank=True)
created_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")
class Meta:
verbose_name = '异常设备信息'
verbose_name_plural = "异常设备信息"
def __str__(self):
return f"{self.udid}-{self.description}-{self.created_time}"

@ -23,19 +23,21 @@ logger = logging.getLogger(__name__)
@shared_task
def run_sign_task(format_udid_info, short, client_ip):
app_obj = Apps.objects.filter(short=short).first()
if MigrateStorageState(app_obj.user_id.uid).get_state():
user_obj = app_obj.user_id
if MigrateStorageState(user_obj.uid).get_state():
msg = "数据迁移中,无法处理该操作"
return msg
ios_obj = IosUtils(format_udid_info, app_obj.user_id, app_obj)
status, msg = ios_obj.sign_ipa(client_ip)
if ios_obj.developer_obj:
if not status:
add_sign_message(app_obj.user_id, ios_obj.developer_obj, app_obj, '签名失败了', msg, False)
else:
add_sign_message(app_obj.user_id, ios_obj.developer_obj, app_obj, '签名成功', msg, True)
try:
ios_sign_obj = IosUtils(format_udid_info, user_obj, app_obj)
status, msg = ios_sign_obj.sign_ipa(client_ip)
if ios_sign_obj.developer_obj:
if not status:
add_sign_message(user_obj, ios_sign_obj.developer_obj, app_obj, '签名失败了', msg, False)
else:
add_sign_message(user_obj, ios_sign_obj.developer_obj, app_obj, '签名成功', msg, True)
except Exception as e:
logger.error(f"run_sign_task failed. udid_info:{format_udid_info} app_obj:{app_obj} Exception:{e}")
return '系统内部错误,请稍后再试或联系管理员'
if not status:
code = msg.get("code", -1)
if code == 0:
@ -46,6 +48,8 @@ def run_sign_task(format_udid_info, short, client_ip):
msg = "维护中"
elif code == 1003:
msg = "应用余额不足"
elif code == 1007:
msg = "设备注册中,请耐心等待"
elif code in [1004, 1001, 1009]:
msg = msg.get('msg', '未知错误')
else:

@ -20,12 +20,14 @@ from xsign.views.download import XsignDownloadView
from xsign.views.receiveudids import IosUDIDView, TaskView, ShowUdidView
from xsign.views.supersign import DeveloperView, SuperSignUsedView, AppUDIDUsedView, SuperSignCertView, \
DeviceUsedBillView, DeveloperDeviceView, DeviceUsedRankInfoView, AppleDeveloperBindAppsView, DeviceTransferBillView, \
SignOperateMessageView
SignOperateMessageView, AbnormalDeviceInfoView, BlackDeviceInfoView
urlpatterns = [
re_path(r"^developer$", DeveloperView.as_view()),
re_path(r"^devices$", SuperSignUsedView.as_view()),
re_path(r"^message$", SignOperateMessageView.as_view()),
re_path(r"^abnormal/device$", AbnormalDeviceInfoView.as_view()),
re_path(r"^black/device$", BlackDeviceInfoView.as_view()),
re_path(r"^udid$", AppUDIDUsedView.as_view()),
re_path(r"^udevices$", DeveloperDeviceView.as_view()),
re_path(r"^cert$", SuperSignCertView.as_view()),

@ -15,7 +15,7 @@ from common.constants import SignStatus
from common.core.sysconfig import Config
from xsign.models import APPSuperSignUsedInfo, UDIDsyncDeveloper, AppUDID, APPToDeveloper, AppIOSDeveloperInfo, \
IosDeveloperPublicPoolBill, IosDeveloperBill, DeveloperDevicesID, AppleDeveloperToAppUse, DeveloperAppID, \
AppleSignMessage
AppleSignMessage, DeviceAbnormalUDID
logger = logging.getLogger(__name__)
@ -207,3 +207,14 @@ def add_sign_message(user_obj, developer_obj, app_obj, title, message, is_succes
title = f'应用【{app_obj.name}{title}'
AppleSignMessage.objects.create(user_id=user_obj, developerid=developer_obj, app_id=app_obj,
title=title, message=message, operate_status=is_success)
def sync_abnormal_device(developer_obj, udid):
DeviceAbnormalUDID.objects.filter(auto_remove=True, udid__udid=udid, udid__developerid=developer_obj).delete()
def update_or_create_abnormal_device(sync_device_obj, user_obj, app_obj, client_ip):
defaults = {
"description": f"签名应用【{app_obj.name}】 客户端IP地址:【{client_ip}",
}
return DeviceAbnormalUDID.objects.update_or_create(user_id=user_obj, udid=sync_device_obj, defaults=defaults)

@ -372,3 +372,20 @@ class AppleSignMessageSerializer(serializers.ModelSerializer):
if obj.app_id:
return {'bundle_name': obj.app_id.name, 'bundle_id': obj.app_id.bundle_id}
return {}
class AbnormalDeviceSerializer(serializers.ModelSerializer):
class Meta:
model = models.DeviceAbnormalUDID
exclude = ["user_id", "udid"]
udid_info = serializers.SerializerMethodField()
def get_udid_info(self, obj):
return DeveloperDeviceSerializer(obj.udid).data
class BlackDeviceSerializer(serializers.ModelSerializer):
class Meta:
model = models.DeviceBlackUDID
exclude = ["user_id"]

@ -8,7 +8,7 @@ from django.dispatch import receiver
from api.models import AppReleaseInfo
from api.utils.utils import migrating_storage_file_data, get_filename_from_apptype
from common.constants import SignStatus
from common.constants import SignStatus, DeviceStatus
from common.core.signals import run_resign_task_signal, delete_app_signal, xsign_app_download_url_signal, \
xsign_migrate_data_signal, xsign_clean_data_signal, xsign_app_release_obj_signal
from common.core.sysconfig import Config
@ -65,10 +65,11 @@ def xsign_app_download_url_callback(sender, **kwargs):
app_pk = kwargs.get('app_pk')
local_storage = LocalStorage(**Config.IOS_PMFILE_DOWNLOAD_DOMAIN)
appudid_obj = AppUDID.objects.filter(app_id_id=app_pk, udid__udid=udid,
appudid_obj = AppUDID.objects.filter(app_id_id=app_pk, udid__udid=udid, udid__status=DeviceStatus.ENABLED,
sign_status=SignStatus.SIGNATURE_PACKAGE_COMPLETE).last()
if appudid_obj:
super_sign_obj = APPSuperSignUsedInfo.objects.filter(udid__udid__udid=udid,
udid__udid__status=DeviceStatus.ENABLED,
app_id_id=app_pk,
developerid__status__in=Config.DEVELOPER_USE_STATUS).last()
if super_sign_obj and super_sign_obj.user_id.supersign_active:

@ -24,7 +24,7 @@ from common.base.magic import run_function_by_locker, call_function_try_attempts
from common.cache.state import CleanErrorBundleIdSignDataState
from common.cache.storage import RedisCacheBase
from common.constants import DeviceStatus, AppleDeveloperStatus, SignStatus
from common.core.sysconfig import Config
from common.core.sysconfig import Config, UserConfig
from common.notify.notify import sign_failed_notify, sign_unavailable_developer_notify, sign_app_over_limit_notify
from common.utils.caches import del_cache_response_by_short, send_msg_over_limit, check_app_permission, \
consume_user_download_times_by_app_obj, add_udid_cache_queue, get_and_clean_udid_cache_queue
@ -32,10 +32,11 @@ from common.utils.storage import Storage
from fir_ser.settings import SUPER_SIGN_ROOT, MEDIA_ROOT
from xsign.models import APPSuperSignUsedInfo, AppUDID, AppIOSDeveloperInfo, APPToDeveloper, \
UDIDsyncDeveloper, DeveloperAppID, DeveloperDevicesID, IosDeveloperPublicPoolBill, AppleDeveloperToAppUse, \
IosDeveloperBill
IosDeveloperBill, DeviceAbnormalUDID, DeviceBlackUDID
from xsign.utils.iossignapi import ResignApp, AppDeveloperApiV2
from xsign.utils.modelutils import get_ios_developer_public_num, check_ipa_is_latest_sign, \
update_or_create_developer_udid_info, check_uid_has_relevant, get_developer_udided, add_sign_message
update_or_create_developer_udid_info, check_uid_has_relevant, get_developer_udided, add_sign_message, \
update_or_create_abnormal_device
from xsign.utils.serializer import BillAppInfoSerializer, BillDeveloperInfoSerializer
from xsign.utils.utils import delete_app_to_dev_and_file
@ -53,6 +54,42 @@ def check_org_file(user_obj, org_file):
return download_files_form_oss(storage_obj, org_file)
def get_abnormal_queryset(user_obj, udid):
return DeviceAbnormalUDID.objects.filter(user_id=user_obj,
udid__udid=udid,
udid__developerid__status__in=Config.DEVELOPER_USE_STATUS)
def check_user_udid_black(user_obj, udid):
if DeviceBlackUDID.objects.filter(user_id=user_obj, udid=udid, enable=True).count():
return True
def get_developer_queryset_by_udidsync(user_obj, udid, status_list):
return AppIOSDeveloperInfo.objects.filter(user_id=user_obj,
status__in=Config.DEVELOPER_USE_STATUS,
certid__isnull=False,
udidsyncdeveloper__udid=udid,
udidsyncdeveloper__status__in=status_list)
def check_udid_black(user_obj, udid):
if check_user_udid_black(user_obj, udid):
return True
if UserConfig(user_obj).DEVELOPER_WAIT_ABNORMAL_DEVICE:
if get_abnormal_queryset(user_obj, udid).filter(auto_remove=False).count():
return True
if get_developer_queryset_by_udidsync(user_obj, udid, [DeviceStatus.ENABLED, DeviceStatus.DISABLED]).count():
get_abnormal_queryset(user_obj, udid).delete()
return False
for developer_obj in get_developer_queryset_by_udidsync(user_obj, udid,
[DeviceStatus.PROCESSING, DeviceStatus.INELIGIBLE]):
IosUtils.get_device_from_developer(developer_obj)
return get_abnormal_queryset(user_obj, udid).count()
def resign_by_app_id_and_developer(app_id, developer_id, developer_app_id, need_download_profile=True, force=True):
"""
:param app_id:
@ -265,21 +302,9 @@ def get_developer_by_pk_list(developer_pk_list, f_key, app_search_flag=True):
return developer_obj
def get_developer_user_by_app_udid(user_objs, udid, app_obj, private_first=True, read_only=True):
"""
:param read_only:
:param user_objs: [user_obj]
:param udid: udid
:param app_obj: app_obj
:param private_first: 专属应用优先使用专属开发者而不是公共开发者
:return: (developer_obj, is_exist_devices)
"""
if read_only:
status_choice = Config.DEVELOPER_USE_STATUS
else:
status_choice = Config.DEVELOPER_SIGN_STATUS
status_filter = {'developerid__certid__isnull': False, 'developerid__status__in': status_choice}
def get_developer_user_by_app_exist_udid(user_objs, udid, app_obj, abnormal_use_status):
status_filter = {'developerid__certid__isnull': False, 'developerid__status__in': abnormal_use_status,
'status__in': [DeviceStatus.ENABLED, DeviceStatus.DISABLED]}
developer_udid_queryset = UDIDsyncDeveloper.objects.filter(developerid__user_id__in=user_objs,
**status_filter).values('developerid').all().distinct()
@ -307,6 +332,44 @@ def get_developer_user_by_app_udid(user_objs, udid, app_obj, private_first=True,
else:
logger.info(f"udid:{udid} is a new device. app_obj:{app_obj} will find a suitable developer and register it")
return developer_udid_queryset, False
def get_developer_user_by_app_udid(user_objs, udid, app_obj, private_first=True, read_only=True):
"""
:param read_only:
:param user_objs: [user_obj]
:param udid: udid
:param app_obj: app_obj
:param private_first: 专属应用优先使用专属开发者而不是公共开发者
:return: (developer_obj, is_exist_devices)
"""
if read_only:
status_choice = Config.DEVELOPER_USE_STATUS
else:
status_choice = Config.DEVELOPER_SIGN_STATUS
developer_udid_queryset, status = get_developer_user_by_app_exist_udid(user_objs, udid, app_obj, status_choice)
if status:
return developer_udid_queryset, status
else:
logger.warning(
f"udid:{udid} app_obj:{app_obj} find normal developer failed. now find abnormal device developer")
obj, status = get_developer_user_by_app_exist_udid(user_objs, udid, app_obj, [AppleDeveloperStatus.DEVICE_ABNORMAL])
if status:
return obj, status
else:
logger.warning(f"udid:{udid} app_obj:{app_obj} find abnormal developer failed. now find can wait developer")
if UserConfig(app_obj.user_id).DEVELOPER_WAIT_ABNORMAL_STATE:
obj, status = get_developer_user_by_app_exist_udid(user_objs, udid, app_obj, Config.DEVELOPER_WAIT_STATUS)
if status:
return obj, status
else:
logger.warning(f"udid:{udid} app_obj:{app_obj} find abnormal developer failed. now find register developer")
# 新设备查找策略
# 根据app查找开发者账户
exist_app_developer_pk_list = []
@ -318,7 +381,11 @@ def get_developer_user_by_app_udid(user_objs, udid, app_obj, private_first=True,
if get_developer_udided(developer_obj)[2] < developer_obj.usable_number:
exist_app_developer_pk_list.append(developer_obj.pk)
if UserConfig(app_obj.user_id).DEVELOPER_WAIT_ABNORMAL_DEVICE and UserConfig(
app_obj.user_id).DEVELOPER_ABNORMAL_DEVICE_WRITE:
status_choice.append(AppleDeveloperStatus.DEVICE_ABNORMAL)
# 查询状态正常的专属开发者信息,判断是否为专属应用
status_filter = {'developerid__certid__isnull': False, 'developerid__status__in': status_choice}
apple_to_app = AppleDeveloperToAppUse.objects.filter(app_id=app_obj, **status_filter).first()
# 存在专属开发者账户,但是也同时存在已经安装的设备,优先选择已经安装设备的开发者账户?是or否
@ -536,7 +603,7 @@ class IosUtils(object):
return False, {'code': res.code, 'msg': res.msg}
self.get_developer_auth(True)
if not self.developer_obj:
msg = "udid %s app %s not exists apple developer" % (self.udid, self.app_obj)
msg = f"udid {self.udid} app {self.app_obj} not exists apple developer"
d_result['code'] = 1005
d_result['msg'] = msg
logger.error(d_result)
@ -592,7 +659,7 @@ class IosUtils(object):
# 库里面存在,并且设备是可用状态,因此无需api注册
if sync_device_obj:
logger.info(f"app {app_obj} device {sync_device_obj.serial} already in developer {developer_obj}")
if not sync_device_obj.status:
if sync_device_obj.status != DeviceStatus.ENABLED:
# 库里面存在,并且设备是禁用状态,需要调用api启用
status, result = get_api_obj(developer_obj, app_obj).set_device_status("enable", sync_device_obj.serial,
sync_device_obj.product,
@ -600,9 +667,19 @@ class IosUtils(object):
failed_call_prefix,
set_failed_callback)
if not status: # 已经包含异常操作,暂定
return status, result
sync_device_obj = UDIDsyncDeveloper.objects.filter(udid=device_udid,
developerid=developer_obj,
status__in=[DeviceStatus.PROCESSING,
DeviceStatus.INELIGIBLE]).first()
if sync_device_obj:
msg = result
if UserConfig(user_obj).DEVELOPER_WAIT_ABNORMAL_DEVICE:
update_or_create_abnormal_device(sync_device_obj, user_obj, app_obj, client_ip)
msg = 'DEVELOPER_WAIT_ABNORMAL_DEVICE'
return False, msg
sync_device_obj.status = True
sync_device_obj.save(update_fields=['status'])
else:
# 库里面不存在,注册设备,新设备注册默认就是启用状态
status, device_obj = get_api_obj(developer_obj, app_obj).register_device(device_udid, device_name,
@ -611,11 +688,15 @@ class IosUtils(object):
if not status:
return status, device_obj
if device_obj and device_obj.status not in ['ENABLED', 'DISABLED']:
status, sync_device_obj = IosUtils.check_device_status(developer_obj, org_device_obj=device_obj)
if device_obj and device_obj.status not in [DeviceStatus.ENABLED, DeviceStatus.DISABLED]:
status, sync_device_obj = IosUtils.check_device_status(developer_obj, serial=device_obj.id)
if not status:
update_or_create_developer_udid_info(device_obj, developer_obj)
return False, 'UNEXPECTED_ERROR'
sync_device_obj, _ = update_or_create_developer_udid_info(device_obj, developer_obj)
msg = '设备状态异常'
if UserConfig(user_obj).DEVELOPER_WAIT_ABNORMAL_DEVICE:
update_or_create_abnormal_device(sync_device_obj, user_obj, app_obj, client_ip)
msg = 'DEVELOPER_WAIT_ABNORMAL_DEVICE'
return False, msg
else:
sync_device_obj, _ = update_or_create_developer_udid_info(device_obj, developer_obj)
@ -684,7 +765,7 @@ class IosUtils(object):
@staticmethod
@call_function_try_attempts(try_attempts=2)
def check_device_status(developer_obj, device_obj_list=None, org_device_obj=None):
def check_device_status(developer_obj, device_obj_list=None, serial=None):
status = True
if device_obj_list is None:
status, device_obj_list = get_api_obj(developer_obj).get_device()
@ -698,13 +779,13 @@ class IosUtils(object):
err_msg, False)
return False, err_msg
else:
if org_device_obj and org_device_obj.id == device_obj.id:
if serial and serial == device_obj.id:
sync_device_obj, _ = update_or_create_developer_udid_info(device_obj, developer_obj)
return True, sync_device_obj
return True, ''
@staticmethod
@call_function_try_attempts()
@call_function_try_attempts(try_attempts=2)
def make_and_download_profile(app_obj, developer_obj, failed_call_prefix, developer_app_id_obj=None,
new_did_list=None,
failed_callback=None):
@ -716,16 +797,22 @@ class IosUtils(object):
failed_callback.extend([
{
'func_list': [magic_wrapper(IosUtils.clean_super_sign_things_by_app_obj, app_obj, developer_obj)],
'err_match_msg': ["There is no App ID with ID"]
},
{
'func_list': [magic_wrapper(IosUtils.active_developer, developer_obj)],
'err_match_msg': ["There are no current certificates on this team matching the provided certificate"]
'func_list': [magic_wrapper(IosUtils.active_developer, developer_obj, False)],
'err_match_msg': [
"There are no current certificates on this team matching the provided certificate",
"MUST contain type and id members"
]
},
{
'func_list': [magic_wrapper(IosUtils.check_device_status, developer_obj)],
'err_match_msg': ["There are no current ios devices on this team matching the provided device IDs"]
},
{
'func_list': [magic_wrapper(IosUtils.clean_super_sign_things_by_app_obj, app_obj, developer_obj)],
'err_match_msg': [
"There is no App ID with ID",
"There are no current certificates on this team matching the provided certificate"
]
}
])
device_id_list = DeveloperDevicesID.objects.filter(app_id=app_obj,
@ -737,7 +824,7 @@ class IosUtils(object):
if not developer_app_id_obj:
developer_app_id_obj = DeveloperAppID.objects.filter(developerid=developer_obj, app_id=app_obj).first()
if not developer_app_id_obj:
logger.error("bundle id not found. so return and exit")
logger.error(f"app {app_obj} developer {developer_obj} .bundle id not found. so return and exit")
return True, 'continue'
developer_app_id = developer_app_id_obj.aid
profile_id = developer_app_id_obj.profile_id
@ -782,9 +869,19 @@ class IosUtils(object):
if result:
return result
if check_udid_black(self.user_obj, self.udid):
msg = {'code': 1007}
return False, msg
self.get_developer_auth(False)
logger.info("udid %s not exists app_id %s ,need sign" % (self.udid, self.app_obj))
if self.developer_obj:
if UserConfig(
self.user_obj).DEVELOPER_WAIT_ABNORMAL_STATE and self.developer_obj.status in Config.DEVELOPER_WAIT_STATUS:
msg = {'code': 1007}
return False, msg
logger.info(f"udid {self.udid} not exists app_id {self.app_obj}")
if consume_user_download_times_by_app_obj(self.app_obj):
d_result['code'] = 1009
@ -800,7 +897,7 @@ class IosUtils(object):
if count > 3:
break
logger.warning(
"call_loop download_profile appid:%s developer:%s count:%s" % (self.app_obj, self.developer_obj, count))
f"call_loop download_profile appid:{self.app_obj} developer:{self.developer_obj} count:{count}")
if self.developer_obj:
# register_devices_prefix = f"check_or_register_devices_{self.developer_obj.issuer_id}_{self.udid}"
issuer_id = self.developer_obj.issuer_id
@ -821,6 +918,8 @@ class IosUtils(object):
if not status:
if 'UNEXPECTED_ERROR' in str(did_udid_result):
return False, {}
if 'DEVELOPER_WAIT_ABNORMAL_DEVICE' in str(did_udid_result):
return False, {'code': 1007, 'msg': '设备注册中,请耐心等待'}
msg = f"app_id {self.app_obj} register devices failed. {did_udid_result}"
self.sign_failed_fun(d_result, msg)
continue
@ -1274,4 +1373,10 @@ class IosUtils(object):
UDIDsyncDeveloper.objects.filter(udid__in=will_del_udid_list, developerid=developer_obj).delete()
# 清理
udids = [device.udid for device in result if device.status in [DeviceStatus.ENABLED, DeviceStatus.DISABLED]]
DeviceAbnormalUDID.objects.filter(auto_remove=True,
udid__developerid__user_id=developer_obj.user_id,
udid__udid__in=udids).delete()
return status, result

@ -27,7 +27,7 @@ from common.utils.pending import get_pending_result
from common.utils.token import verify_token, make_token
from fir_ser.celery import app
from xsign.tasks import run_sign_task
from xsign.utils.supersignutils import udid_bytes_to_dict, make_sign_udid_mobile_config
from xsign.utils.supersignutils import udid_bytes_to_dict, make_sign_udid_mobile_config, check_user_udid_black
logger = logging.getLogger(__name__)
@ -70,9 +70,9 @@ class IosUDIDView(APIView):
client_ip = get_real_ip_address(request)
logger.info(f"client_ip {client_ip} short {short} app_info {app_obj}")
# from api.utils.app.supersignutils import IosUtils
# ios_obj = IosUtils(format_udid_info, app_obj.user_id, app_obj)
# ios_obj.sign_ipa(client_ip)
# from xsign.utils.supersignutils import IosUtils
# ios_sign_obj = IosUtils(format_udid_info, app_obj.user_id, app_obj)
# ios_sign_obj.sign_ipa(client_ip)
# return Response('ok')
data = {
'format_udid_info': format_udid_info,
@ -184,6 +184,10 @@ class TaskView(APIView):
res.code = 1006
res.msg = '下载授权码失效或有误'
if check_user_udid_black(app_obj.user_id, udid):
res.code = 1007
res.msg = '设备注册中,请耐心等待'
if res.code != 1000:
return Response(res.dict)

@ -24,14 +24,14 @@ from common.core.sysconfig import Config
from common.utils.download import get_app_download_url
from xsign.models import AppIOSDeveloperInfo, APPSuperSignUsedInfo, AppUDID, IosDeveloperPublicPoolBill, \
UDIDsyncDeveloper, AppleDeveloperToAppUse, DeveloperAppID, APPToDeveloper, DeveloperDevicesID, \
IosDeveloperBill, AppleSignMessage
IosDeveloperBill, AppleSignMessage, DeviceAbnormalUDID, DeviceBlackUDID
from xsign.tasks import run_resign_task_do, run_resign_task
from xsign.utils.modelutils import get_user_public_used_sign_num, get_user_public_sign_num, check_uid_has_relevant, \
get_developer_devices
from xsign.utils.serializer import DeveloperSerializer, SuperSignUsedSerializer, DeviceUDIDSerializer, \
BillInfoSerializer, \
DeveloperDeviceSerializer, AppleDeveloperToAppUseSerializer, AppleDeveloperToAppUseAppsSerializer, \
BillTransferSerializer, AppleSignMessageSerializer
BillTransferSerializer, AppleSignMessageSerializer, AbnormalDeviceSerializer, BlackDeviceSerializer
from xsign.utils.supersignutils import IosUtils
logger = logging.getLogger(__name__)
@ -197,14 +197,13 @@ class DeveloperView(APIView):
res.msg = result.get("return_info")
return Response(res.dict)
elif act == "checkcert":
if developer_obj.certid:
status, result = IosUtils.get_developer_cert_info(developer_obj)
if status:
res.data = result
else:
res.code = 1008
res.msg = result.get("return_info")
return Response(res.dict)
status, result = IosUtils.get_developer_cert_info(developer_obj)
if status:
res.data = result
else:
res.code = 1008
res.msg = result.get("return_info")
return Response(res.dict)
elif act in ["renewcert", "cleancert"]:
if developer_obj.certid:
# clean developer somethings. remove profile and revoke cert
@ -958,3 +957,94 @@ class SignOperateMessageView(APIView):
res.status_choices = get_choices_dict(AppleSignMessage.status_choices)
return Response(res.dict)
class AbnormalDeviceInfoView(APIView):
authentication_classes = [ExpiringTokenAuthentication, ]
permission_classes = [SuperSignPermission, ]
def get(self, request):
res = BaseResponse()
udid = request.query_params.get("udid", None)
issuer_id = request.query_params.get("issuer_id", None)
abnormal_udid_queryset = DeviceAbnormalUDID.objects.filter(user_id=request.user)
if issuer_id:
abnormal_udid_queryset = abnormal_udid_queryset.filter(udid__developerid__issuer_id=issuer_id)
if udid:
abnormal_udid_queryset = abnormal_udid_queryset.filter(udid__udid=udid)
page_obj = PageNumber()
page_serializer = page_obj.paginate_queryset(queryset=abnormal_udid_queryset.order_by("-created_time"),
request=request,
view=self)
abnormal_udid_serializer = AbnormalDeviceSerializer(page_serializer, many=True, )
res.data = abnormal_udid_serializer.data
res.count = abnormal_udid_queryset.count()
return Response(res.dict)
def put(self, request):
res = BaseResponse()
pk = request.data.get("pk", None)
state = request.data.get("state", None)
if pk is not None and state is not None and isinstance(state, int):
DeviceAbnormalUDID.objects.filter(user_id=request.user, pk=pk).update(auto_remove=True if state else False)
return Response(res.dict)
def delete(self, request):
res = BaseResponse()
pk = request.query_params.get("pk", None)
if pk is not None:
DeviceAbnormalUDID.objects.filter(user_id=request.user, pk=pk).delete()
return Response(res.dict)
class BlackDeviceInfoView(APIView):
authentication_classes = [ExpiringTokenAuthentication, ]
permission_classes = [SuperSignPermission, ]
def get(self, request):
res = BaseResponse()
udid = request.query_params.get("udid", None)
black_udid_queryset = DeviceBlackUDID.objects.filter(user_id=request.user)
if udid:
black_udid_queryset = black_udid_queryset.filter(udid=udid)
page_obj = PageNumber()
page_serializer = page_obj.paginate_queryset(queryset=black_udid_queryset.order_by("-created_time"),
request=request,
view=self)
black_udid_serializer = BlackDeviceSerializer(page_serializer, many=True, )
res.data = black_udid_serializer.data
res.count = black_udid_queryset.count()
return Response(res.dict)
def put(self, request):
res = BaseResponse()
pk = request.data.get("pk", None)
state = request.data.get("state", None)
if pk is not None and state is not None and isinstance(state, int):
DeviceBlackUDID.objects.filter(user_id=request.user, pk=pk).update(enable=True if state else False)
return Response(res.dict)
def post(self, request):
res = BaseResponse()
udid = request.data.get("udid")
description = request.data.get("description")
enable = request.data.get("enable", True)
if udid:
DeviceBlackUDID.objects.update_or_create(user_id=request.user, udid=udid,
defaults={'description': description, 'enable': enable})
return Response(res.dict)
def delete(self, request):
res = BaseResponse()
pk = request.query_params.get("pk", None)
if pk is not None:
DeviceBlackUDID.objects.filter(user_id=request.user, pk=pk).delete()
return Response(res.dict)

Loading…
Cancel
Save