Compare commits

...

15 Commits

  1. 141
      README.md
  2. 102
      doc/docker.md
  3. BIN
      doc/images/img_1.png
  4. BIN
      doc/images/img_10.png
  5. BIN
      doc/images/img_11.png
  6. BIN
      doc/images/img_2.png
  7. BIN
      doc/images/img_3.png
  8. BIN
      doc/images/img_4.png
  9. BIN
      doc/images/img_5.png
  10. BIN
      doc/images/img_6.png
  11. BIN
      doc/images/img_7.png
  12. BIN
      doc/images/img_8.png
  13. BIN
      doc/images/img_9.png
  14. 166
      doc/local.md
  15. 81
      docker.md
  16. 31
      docker/flyapps/docker-compose.only.yml
  17. 2
      docker/flyapps/docker-compose.yml
  18. 4
      docker/init/clean.sh
  19. 10
      docker/init/download.images
  20. 18
      docker/init/init.sh
  21. 2
      docker/scripts/build.sh
  22. 6
      docker/scripts/start_all.sh
  23. 6
      docker/scripts/stop_all.sh
  24. 8
      fir_admin/src/api/user.js
  25. 84
      fir_admin/src/views/userinfos/UserDetail.vue
  26. 2
      fir_client/src/components/FirLogin.vue
  27. 26
      fir_client/src/components/base/AppleDeveloperBindApp.vue
  28. 34
      fir_client/src/components/user/FirSuperSignBase.vue
  29. 2
      fir_client/vue.config.js
  30. 8
      fir_ser/Dockerfile
  31. 14
      fir_ser/admin/views/login.py
  32. 15
      fir_ser/api/management/commands/services/services/base.py
  33. 2
      fir_ser/api/utils/modelutils.py
  34. 14
      fir_ser/api/utils/serializer.py
  35. 2
      fir_ser/api/views/domain.py
  36. 18
      fir_ser/api/views/download.py
  37. 10
      fir_ser/cli.py
  38. 180
      fir_ser/common/base/magic.py
  39. 12
      fir_ser/common/constants.py
  40. 146
      fir_ser/common/core/decorators.py
  41. 252
      fir_ser/dumpdata.json
  42. 20
      fir_ser/xsign/migrations/0004_udidsyncdeveloper_device_class.py
  43. 8
      fir_ser/xsign/models.py
  44. 23
      fir_ser/xsign/utils/modelutils.py
  45. 7
      fir_ser/xsign/utils/serializer.py
  46. 21
      fir_ser/xsign/utils/supersignutils.py
  47. 2
      fir_ser/xsign/views/receiveudids.py
  48. 10
      fir_ser/xsign/views/supersign.py

@ -43,126 +43,21 @@
- 不开启,无需域名和证书
- Centos8Stream 服务器
#### 修改配置文件
##### fir_ser配置文件 config.py
```python
class DOMAINCONF(object):
API_DOMAIN = "https://app.hehelucky.cn" # 用与开启本地存储,上传应用配置
WEB_DOMAIN = "https://app.hehelucky.cn" # 用于超级签跳转配置,该域名一般为前端页面域名
MOBILEPROVISION = "https://static.hehejoy.cn/embedded3.mobileprovision" # 用于苹果包企业签信任企业跳转
```
##### fir_client配置文件 vue.confjg.js
```javascript
const pro_base_env = {
baseUrl: '/', //该选项可以填写web-api的域名,类似 https://api.xxx.com/
index_static: '/', //若配置cdn等加速,可以填写cdn加速域名
baseShortUrl: '/', //该选项可以填写short-api的域名,也可以和web-api域名一样,类似 https://api.xxx.com/
short_static: '/short/', //若配置cdn等加速,可以填写cdn加速域名
version: version,
};
```
##### 从git上面下载源码
```shell
cd /data/
git clone https://github.com/nineaiyu/FlyApps
```
### [Docker 部署](./docker.md)
### FlyApp 本地部署 [python3 环境]
##### 搭建python env 环境
```shell
yum install python39 python39-devel redis mariadb-server mariadb-devel -y
python3.9 -m venv py39
source py39/bin/activate
```
###### 安装pip包
```shell
cd fir_ser/
pip install -U setuptools pip
pip install -r requirements.txt
```
###### 配置数据库
```
在 config.py 配置redis 和 mysql 数据库
```
###### 迁移数据库
```shell
#如果是mysql,需要做一下配置,如果是sqlite,需要升级sqlite
#记得根据配置创建数据库
#create database flyapp default character set utf8 COLLATE utf8_general_ci;
#grant all on flyapp.* to flyuser@'127.0.0.1' identified by 'flypwd00oo.1';
python manage.py makemigrations
python manage.py migrate
```
### FlyApp zsign ipa重签名工具安装
```shell
yum install openssl-devel -y
yum install gcc-c++ gcc -y
tar xvf v1.1.2.tar.gz
cd zsign-1.1.2/
g++ *.cpp common/*.cpp -lcrypto -O3 -std=c++11 -o zsign
cp zsign /usr/bin/
```
### FlyApp web端搭建
###### npm编译环境
```shell
yum install npm
npm install -g n
n 12.13 # 安装12版本的node或者14版本 ,最新版本会有问题
npm install -g yarn
```
###### 编译web端和下载页
```shell
cd FlyApps/fir_client/
vim vue.config.js #修改api接口地址
# pro_base_env 正式环境信息
# dev_base_env 开发环境信息
yarn install
yarn build index # web打包
yarn build short # 下载也打包
```
###### web目录操作
```shell
# web目录: /www/wwwroot/fly.dvcloud.xin/
cd /www/wwwroot/fly.dvcloud.xin/
cp -a /data/FlyApps/fir_client/dist/* .
cp -a /data/FlyApps/fir_download/dist/* .
```
##### 更新sqllite
```shell
tar xvf sqlite-autoconf-3310100.tar.gz
cd sqlite-autoconf-3310100
./configure --prefix=/usr/local/sqlite
make -j4
make install
rm -rf /usr/bin/sqlite3
ln -s /usr/local/sqlite/bin/sqlite3 /usr/bin/sqlite3
ll /usr/bin/sqlite3
echo "/usr/local/sqlite/lib" > /etc/ld.so.conf.d/sqlite3.conf
ldconfig
```
## 部署方式
### 1.[Docker 部署](./doc/docker.md) 【推荐】
### 2.[本地部署](./doc/local.md)
### 功能预览
![img_1.png](./doc/images/img_1.png)
![img_2.png](./doc/images/img_2.png)
![img_3.png](./doc/images/img_3.png)
![img_4.png](./doc/images/img_4.png)
![img_5.png](./doc/images/img_5.png)
![img_6.png](./doc/images/img_6.png)
![img_7.png](./doc/images/img_7.png)
![img_8.png](./doc/images/img_8.png)
![img_9.png](./doc/images/img_9.png)
![img_10.png](./doc/images/img_10.png)
![img_11.png](./doc/images/img_11.png)

@ -0,0 +1,102 @@
### Docker最小化部署前准备,如果使用超级签和云存储,需要使用阿里云的服务器
系统|Centos 8 Stream 纯净系统 [2cpu 4G内存]
----|----
域名|```app.hehelucky.cn```
域名SSL证书| nginx 格式证书 [阿里云和腾讯云都可以申请免费ssl证书]
数据路径| ```/data```
### 开始部署
##### 1.从git上面下载源码
```
cd /data/
dnf install git -y
git clone https://github.com/nineaiyu/flyapps
```
## 注意,下面展示的相对路径,都是以```/data/flyapps```为相对目录
#### 2.docker环境安装
```
cd /data/flyapps/docker/init
sh init.sh
```
#### 3.配置域名和证书,如果有cdn或者oss,也要进行配置
a.将域名证书通过```sftp软件```或者```rz命令```复制到```/data/flyapps/nginx.conf.d```目录中
b.将域名证书分别重命名为 ```域名.pem``` 和 ```域名.key``` .本次使用的域名是 ```app.hehelucky.cn``` ,因此,证书名称类似如下
```shell script
nginx.conf.d/app.hehelucky.cn.key
nginx.conf.d/app.hehelucky.cn.pem
```
c.修改NGINX配置文件```nginx.conf.d/flyapps-vhost.conf```,将```server_name```字段修改为域名,将```ssl_certificate```和
```ssl_certificate_key```修改为对应证书路径,修改之后,对应字段如下
```shell
server_name app.hehelucky.cn;
ssl_certificate /etc/nginx/conf.d/app.hehelucky.cn.pem;
ssl_certificate_key /etc/nginx/conf.d/app.hehelucky.cn.key;
```
#### 4.配置api服务需要修改api和web域名,如果有需求,还可以配置 短信,邮箱,geetest,存储等信息
##### fir_ser配置文件 ```fir_ser/config.py```
```python
class DOMAINCONF(object):
API_DOMAIN = "https://app.hehelucky.cn" # 用与开启本地存储,上传应用配置
WEB_DOMAIN = "https://app.hehelucky.cn" # 用于超级签跳转配置,该域名一般为前端页面域名
```
##### fir_client配置文件 ```fir_client/vue.confjg.js```
```javascript
const pro_base_env = {
baseUrl: '/', //该选项可以填写web-api的域名,类似 https://api.xxx.com/
index_static: '/', //若配置cdn等加速,可以填写cdn加速域名
baseShortUrl: '/', //该选项可以填写short-api的域名,也可以和web-api域名一样,类似 https://api.xxx.com/
short_static: '/short/', //若配置cdn等加速,可以填写cdn加速域名
version: version,
};
```
##### 构建静态资源
```
cd /data/flyapps/docker/scripts
sh build.sh
```
##### 启动所有服务
```
cd /data/flyapps/docker/scripts
sh start_all.sh
```
##### 关闭所有服务
```
cd /data/flyapps/docker/scripts
sh stop_all.sh
```
##### 根据提示创建默认管理用户,用与访问管理后台
```shell
docker exec -it flyapps python manage.py createsuperuser
```
##### 测试访问
- 在浏览器输入自己配置的域名 https://app.hehelucky.cn/ 进行访问
- 管理后台访问 https://app.hehelucky.cn:3448/ 进行访问
##### 如果要使用本地的数据库和redis,api服务使用容器,需要修改文件```/data/flyapps/docker/flyapps/docker-compose.yml```
```shell
external_links:
- mariadb:mariadb
- redis:redis
```
修改为
```shell
extra_hosts:
- "mariadb:172.31.31.1"
- "redis:172.31.31.1"
```
并将```nginx:```下面的配置注释掉
同时还需要修改mariadb和redis授权

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

@ -0,0 +1,166 @@
### 本地部署前准备
系统 | Centos 8 Stream [2cpu 4G内存]
----|----
域名|```app.hehelucky.cn```
域名SSL证书| nginx 格式证书 [阿里云和腾讯云都可以申请免费ssl证书]
数据路径| ```/data```
##### 从git上面下载源码
```shell
cd /data/
dnf install git gcc zip unzip -y
git clone https://github.com/nineaiyu/flyapps
```
### flyapps 本地部署 [python3 环境]
##### 搭建python env 环境
```shell
dnf install python39 python39-devel redis mariadb-server mariadb-devel -y
python3.9 -m venv py39
source py39/bin/activate
```
###### 安装pip包
```shell
cd /data/flyapps/fir_ser/
pip install -U setuptools pip -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/
```
#### 配置数据库
##### 启动mariadb数据库
```shell
systemctl restart mariadb
systemctl enable mariadb
```
##### 创建mysql数据库
```shell
mysql
```
执行下面mysql命令
```mariadb
create database flyapps default character set utf8 COLLATE utf8_general_ci;
grant all on flyapps.* to flyuser@'127.0.0.1' identified by 'KGzKjZpWBp4R4RSa';
```
##### 启动redis数据库
```shell
echo 'requirepass nineven' >> /etc/redis.conf # 配置授权密码
systemctl restart redis
systemctl enable redis
```
### flyapps zsign ipa重签名工具安装
```shell
dnf install openssl-devel -y
dnf install gcc-c++ gcc -y
cd /data/flyapps/fir_ser/
tar xvf zsign-1.1.2.tar.gz
cd zsign-1.1.2/
g++ *.cpp common/*.cpp -lcrypto -O3 -std=c++11 -o /usr/bin/zsign
```
#### 配置api服务需要修改api和web域名,如果有需求,还可以配置 短信,邮箱,geetest,存储等信息
##### fir_ser配置文件 ```fir_ser/config.py```
```python
class DOMAINCONF(object):
API_DOMAIN = "https://app.hehelucky.cn" # 用与开启本地存储,上传应用配置
WEB_DOMAIN = "https://app.hehelucky.cn" # 用于超级签跳转配置,该域名一般为前端页面域名
```
##### fir_client配置文件 ```fir_client/vue.confjg.js```
```javascript
const pro_base_env = {
baseUrl: '/', //该选项可以填写web-api的域名,类似 https://api.xxx.com/
index_static: '/', //若配置cdn等加速,可以填写cdn加速域名
baseShortUrl: '/', //该选项可以填写short-api的域名,也可以和web-api域名一样,类似 https://api.xxx.com/
short_static: '/short/', //若配置cdn等加速,可以填写cdn加速域名
version: version,
};
```
### flyapps web端搭建
###### npm编译环境
```shell
dnf install npm -y
npm install -g n
n 12.13 # 安装12版本的node或者14版本 ,最新版本会有问题
npm install -g yarn
```
###### 编译web端和下载页
```shell
cd /data/flyapps/fir_client/
vim vue.config.js #修改api接口地址
# pro_base_env 正式环境信息
# dev_base_env 开发环境信息
yarn install
yarn build index # 前端打包,输出目录 dist_index
yarn build short # 下载页打包,输出目录 dist_short
```
###### 编译web管理后台
```shell
cd /data/flyapps/fir_admin/
yarn install
yarn build:prod # 下载页打包,输出目录 dist
```
### nginx配置
##### 安装nginx
```shell
dnf install nginx -y
```
##### 将域名ssl证书解压到```/etc/nginx/conf.d/``` 目录中,并重命名为```域名.pem```,```域名.key```格式
##### 拷贝虚拟主机配置文件
```shell
cp /data/flyapps/nginx.conf.d/flyapps-vhost.conf /etc/nginx/conf.d/
```
1.创建web目录,并将打包好的web复制该目录
```shell
mkdir -pv /data/{fir_client/short,fir_admin}
cp -a /data/flyapps/fir_client/dist_index/* /data/fir_client/
cp -a /data/flyapps/fir_client/dist_short/* /data/fir_client/short/
cp -a /data/flyapps/fir_admin/dist/* /data/fir_admin/
chown nginx.nginx -R /data/{fir_client,fir_admin}
```
2.启动nginx服务
```shell
nginx -t
systemctl restart nginx
systemctl enable nginx
```
##### 添加redis和mariadb uwsgi本地解析
```shell
echo '127.0.0.1 mariadb redis flyapps' >> /etc/hosts
```
###### 迁移数据库
```shell
cd /data/flyapps/fir_ser/
python manage.py makemigrations
python manage.py migrate
```
#### 启动api服务
```shell
echo 'net.core.somaxconn=1024' >> /etc/sysctl.conf
sysctl -p
cd /data/flyapps/fir_ser/
python manage.py start all -u nginx -usm 1 -d
```
##### 根据提示创建默认管理用户,用与访问管理后台
```shell
python manage.py createsuperuser
```
- 需要输入用户名,邮箱和密码
- 用户名和密码用与登录管理后台
- 邮箱和密码用与登录前端web

@ -1,81 +0,0 @@
##### 从git上面下载源码
```
cd /data/
git clone https://github.com/nineaiyu/FlyApps
```
#### docker环境安装 [centos8]
```
cd /data/FlyApps/docker/init
sh init.sh
```
#### 域名证书准备
- 域名: app.hehelucky.cn
- 证书(nginx)
- app.hehelucky.cn.key
- app.hehelucky.cn.pem
#### 配置域名和证书,如果有cdn或者oss,也要进行配置
```shell script
nginx.conf.d/app.hehelucky.cn.key
nginx.conf.d/app.hehelucky.cn.pem
nginx.conf.d/flyapps-vhost.conf
```
#### api服务需要修改api和web域名,短信,邮箱,geetest,存储等信息
##### fir_ser配置文件 config.py
```python
class DOMAINCONF(object):
API_DOMAIN = "https://app.hehelucky.cn" # 用与开启本地存储,上传应用配置
WEB_DOMAIN = "https://app.hehelucky.cn" # 用于超级签跳转配置,该域名一般为前端页面域名
```
##### fir_client配置文件 vue.confjg.js
```javascript
const pro_base_env = {
baseUrl: '/', //该选项可以填写web-api的域名,类似 https://api.xxx.com/
index_static: '/', //若配置cdn等加速,可以填写cdn加速域名
baseShortUrl: '/', //该选项可以填写short-api的域名,也可以和web-api域名一样,类似 https://api.xxx.com/
short_static: '/short/', //若配置cdn等加速,可以填写cdn加速域名
version: version,
};
```
##### 构建静态资源和api服务
```
cd /data/FlyApps/docker/scripts
sh build.sh
```
##### 构建flyapps-cli
```shell
cd /data/FlyApps/fir_ser
docker build -t flyapps-cli -f Dockerfile.cli .
```
##### 构建镜像的同时,下载依赖镜像
```shell
docker pull 'bitnami/mariadb:10.7.3'
docker pull 'bitnami/redis:6.2.7'
docker pull 'nginx:1.21.3'
```
##### 启动所有服务
```
cd /data/FlyApps/docker/scripts
sh start_all.sh
```
##### 关闭所有服务
```
cd /data/FlyApps/docker/scripts
sh stop_all.sh
```
##### 根据提示创建默认管理用户
```shell
docker exec -it flyapps python manage.py createsuperuser
```
##### 测试访问
- 在浏览器输入自己配置的域名 https://app.hehelucky.cn/ 进行访问
- 管理后台访问 https://app.hehelucky.cn:3448/ 进行访问

@ -0,0 +1,31 @@
version: '3'
networks:
flyapps:
external: true
name: flyapps
services:
flyapps:
container_name: flyapps
restart: always
image: 'nineven/flyapps'
working_dir: /data/fir_ser
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../fir_ser:/data/fir_ser
- ../../data/flyapps/files:/data/fir_ser/files
- ../../data/logs/flyapps/:/data/fir_ser/logs
- ../../data/flyapps/supersign/:/data/fir_ser/supersign
- ../../nginx.conf.d:/data/cert
networks:
flyapps:
ipv4_address: 172.31.31.100
external_links:
- mariadb:mariadb
- redis:redis
sysctls:
- net.core.somaxconn=4096
extra_hosts:
- "mariadb:172.31.31.1"
- "redis:172.31.31.1"

@ -9,7 +9,7 @@ services:
flyapps:
container_name: flyapps
restart: always
image: 'flyapps'
image: 'nineven/flyapps'
working_dir: /data/fir_ser
volumes:
- /etc/localtime:/etc/localtime:ro

@ -2,8 +2,6 @@
#
#
for i in nginx flyapps mariadb redis buildclient buildshort buildadmin build-buildflyapps-1;do echo $i;docker rm -f $i;done
for i in nginx flyapps mariadb redis buildclient buildshort buildadmin ;do echo $i;docker rm -f $i;done
docker network rm flyapps

@ -0,0 +1,10 @@
#!/bin/bash
#
#
docker pull 'bitnami/mariadb:10.7.3'
docker pull 'bitnami/redis:6.2.7'
docker pull 'nginx:1.21.3'
docker pull 'nineven/flyapps:latest'
docker pull 'node:14.17.3'

@ -7,22 +7,18 @@ if [ $? -ne 0 ];then
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
dnf install docker-ce -y
fi
which docker-compose
if [ $? -ne 0 ];then
curl -L https://get.daocloud.io/docker/compose/releases/download/v2.5.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
fi
#which docker-compose
#if [ $? -ne 0 ];then
# curl -L https://get.daocloud.io/docker/compose/releases/download/v2.5.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
# chmod +x /usr/local/bin/docker-compose
#fi
data_path="$(dirname $(dirname `pwd`))/data"
mkdir -pv ${data_path}/{flyapps/files,web,mariadb,redis,logs/{mariadb,nginx,flyapps}}
\cp $(dirname $(dirname `pwd`))/fir_ser/files/head_img.jpeg ${data_path}/flyapps/files/
chown 1001.1001 -R ${data_path}/{flyapps,web,mariadb,redis,logs/{mariadb,nginx,flyapps}}
systemctl start docker && docker network create flyapps --driver bridge --subnet=172.31.31.0/24
chown 101.101 -R ${data_path}/{flyapps,logs/flyapps}
systemctl start docker && docker network create flyapps --driver bridge --subnet=172.31.31.0/24 --gateway=172.31.31.1
systemctl enable docker
systemctl status docker
#docker pull 'bitnami/mariadb:10.7.3'
#docker pull 'bitnami/redis:6.2.7'
#docker pull 'nginx:1.21.3'
#docker pull 'node:14.17.3'

@ -3,4 +3,4 @@
#
cd ../build/
docker-compose up buildclient buildadmin buildflyapps
docker compose up buildclient buildadmin

@ -2,8 +2,8 @@
#
#
cd ../mariadb/ && docker-compose up -d
cd ../redis/ && docker-compose up -d
cd ../mariadb/ && docker compose up -d
cd ../redis/ && docker compose up -d
cd ../flyapps/ && docker-compose up -d
cd ../flyapps/ && docker compose up -d
docker logs -f flyapps

@ -3,8 +3,8 @@
#
cd ../flyapps/ && docker-compose down
cd ../flyapps/ && docker compose down
cd ../redis/ && docker-compose down
cd ../redis/ && docker compose down
cd ../mariadb/ && docker-compose down
cd ../mariadb/ && docker compose down

@ -53,3 +53,11 @@ export function updateCertificationInfo(data) {
data
})
}
export function resetPassword(data) {
return request({
url: '/user/info',
method: 'post',
data
})
}

@ -6,21 +6,21 @@
<el-form-item label="UID">
<el-row :gutter="12">
<el-col :span="16">
<el-input :value="postForm.uid" disabled />
<el-input :value="postForm.uid" disabled/>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="账号" prop="username">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.username" />
<el-input v-model="postForm.username"/>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="昵称">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.first_name" />
<el-input v-model="postForm.first_name"/>
</el-col>
</el-row>
</el-form-item>
@ -28,7 +28,9 @@
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.gender" class="filter-item" placeholder="Please select">
<el-option v-for="item in postForm.gender_choices" :key="item.id" :label="item.name" :value="item.id" />
<el-option v-for="item in postForm.gender_choices" :key="item.id" :label="item.name"
:value="item.id"
/>
</el-select>
</el-col>
</el-row>
@ -37,7 +39,7 @@
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.role" class="filter-item" placeholder="Please select">
<el-option v-for="item in postForm.role_choices" :key="item.id" :label="item.name" :value="item.id" />
<el-option v-for="item in postForm.role_choices" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
</el-col>
</el-row>
@ -46,7 +48,7 @@
<el-form-item label="邮箱" prop="email">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.email" />
<el-input v-model="postForm.email"/>
</el-col>
</el-row>
</el-form-item>
@ -54,35 +56,37 @@
<el-form-item label="手机" prop="mobile">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.mobile" />
<el-input v-model="postForm.mobile"/>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="Q Q" prop="number">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.qq" />
<el-input v-model="postForm.qq"/>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="职位">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.job" />
<el-input v-model="postForm.job"/>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="公司">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.company" />
<el-input v-model="postForm.company"/>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="备注">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.memo" :autosize="{ minRows: 4, maxRows: 6}" type="textarea" placeholder="Please input" />
<el-input v-model="postForm.memo" :autosize="{ minRows: 4, maxRows: 6}" type="textarea"
placeholder="Please input"
/>
</el-col>
</el-row>
</el-form-item>
@ -91,7 +95,9 @@
<el-form-item label="用户头像">
<el-row :gutter="12">
<el-col :span="16">
<el-image :src="postForm.head_img" :preview-src-list="[postForm.head_img]" fit="contain" style="width: 100px; height: 100px" />
<el-image :src="postForm.head_img" :preview-src-list="[postForm.head_img]" fit="contain"
style="width: 100px; height: 100px"
/>
</el-col>
</el-row>
</el-form-item>
@ -177,10 +183,16 @@
<el-form-item label="实名认证">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.certification" class="filter-item" placeholder="Please select" :disabled="postForm.certification === -1">
<el-option v-for="item in postForm.certification_status_choices" :key="item.id" :label="item.name" :value="item.id" />
<el-select v-model="postForm.certification" class="filter-item" placeholder="Please select"
:disabled="postForm.certification === -1"
>
<el-option v-for="item in postForm.certification_status_choices" :key="item.id" :label="item.name"
:value="item.id"
/>
</el-select>
<router-link v-if="postForm.certification_id" :to="{name: 'user_authentication_info_edit',params:{id:postForm.certification_id.id}}">
<router-link v-if="postForm.certification_id"
:to="{name: 'user_authentication_info_edit',params:{id:postForm.certification_id.id}}"
>
<el-button type="primary">
审核认证信息
</el-button>
@ -203,7 +215,7 @@
<el-form-item label="下载次数" prop="download_times">
<el-row :gutter="12">
<el-col :span="8">
<el-input :value="postForm.download_times" disabled />
<el-input :value="postForm.download_times" disabled/>
</el-col>
<el-col :span="8">
<router-link v-if="postForm.id" :to="{name: 'order_info_list',params:{user_id:postForm.id}}">
@ -215,14 +227,14 @@
<el-form-item label="注册时间" prop="timestamp">
<el-row :gutter="20">
<el-col :span="8">
<el-date-picker :value="postForm.date_joined" type="datetime" disabled />
<el-date-picker :value="postForm.date_joined" type="datetime" disabled/>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="应用版本历史记录">
<el-row :gutter="20">
<el-col :span="8">
<el-input v-model="postForm.history_release_limit" />
<el-input v-model="postForm.history_release_limit"/>
</el-col>
</el-row>
</el-form-item>
@ -234,6 +246,9 @@
<el-col :span="8">
<el-button @click="add_amount_download_times(postForm.id)">下载次数充值</el-button>
</el-col>
<el-col :span="8">
<el-button @click="reset_password(postForm.id)">重置密码</el-button>
</el-col>
<el-button v-if="!is_edit" type="primary" @click="is_edit=true">修改</el-button>
<div v-else>
<el-button type="primary" @click="is_edit=false">取消</el-button>
@ -245,7 +260,7 @@
<script>
import { validURL } from '@/utils/validate'
import { getUserInfos, updateUserInfo } from '@/api/user'
import { getUserInfos, resetPassword, updateUserInfo } from '@/api/user'
import { createOrderInfo } from '@/api/order'
import { changeStorageInfo, getStorageList } from '@/api/storage'
@ -277,7 +292,7 @@ const defaultForm = {
export default {
name: 'UserDetail',
components: { }, filters: {
components: {}, filters: {
userStatusFilter(status) {
const statusMap = {
true: '激活,允许登录',
@ -341,8 +356,7 @@ export default {
storage_selection: []
}
},
computed: {
},
computed: {},
created() {
if (this.isEdit) {
const id = this.$route.params && this.$route.params.id
@ -369,9 +383,33 @@ export default {
message: '取消充值'
})
})
}, reset_password(user_id) {
this.$prompt('请输入新密码,密码至少6位', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消'
}).then(({ value }) => {
resetPassword({ user_id: user_id, password: value }).then(res => {
if (res.code === 1000) {
this.$message.success('密码重置成功')
this.fetchData(user_id)
} else {
this.$message.error(res.msg)
}
})
}).catch((e) => {
console.log(e)
this.$message({
type: 'info',
message: '取消充值'
})
})
},
changeStorageData(force) {
changeStorageInfo({ id: this.postForm.id, use_storage_id: this.postForm.storage, force: force }).then(response => {
changeStorageInfo({
id: this.postForm.id,
use_storage_id: this.postForm.storage,
force: force
}).then(response => {
this.$message.success('存储数据迁移并设置刷新成功')
}).catch(err => {
console.log(err)

@ -290,7 +290,7 @@ export default {
});
return
}
if (password.length > 6) {
if (password.length >= 6) {
let params = {
"username": email,
"password": password,

@ -3,8 +3,8 @@
<div>
<div style="text-align: center">
<el-transfer
id="transfer"
v-model="choices_data"
v-loading="loadings"
:button-texts="['', '']"
:data="app_developer_lists"
:format="{
@ -93,6 +93,7 @@
<script>
import {developerBindAppFun} from "@/restful";
import {sort_compare} from "@/utils";
import {Loading} from "element-ui";
export default {
name: 'AppleDeveloperBindApp',
@ -120,8 +121,7 @@ export default {
s_props: {},
s_titles: [],
app_private_used_number: 100,
loadings: false,
app_limit_number: 0
app_limit_number: 0,
};
},
mounted() {
@ -147,14 +147,12 @@ export default {
},
handleChange(value, direction, movedKeys) {
if (this.issuer_id && direction === 'right') {
// if(value.length > this.app_limit_number){
// this.$message.warning(','+this.app_limit_number+'')
for (let i = 0; i < movedKeys.length; i++) {
let app_info = this.get_choice_data_from_key([movedKeys[i]])[0][0]
if (!(app_info && app_info.app_used_number > 0)) {
this.choices_data.splice(this.choices_data.indexOf(movedKeys[i]), 1)
}
}
//
// for (let i = 0; i < movedKeys.length; i++) {
// let app_info = this.get_choice_data_from_key([movedKeys[i]])[0][0]
// if (!(app_info && app_info.app_used_number > 0)) {
// this.choices_data.splice(this.choices_data.indexOf(movedKeys[i]), 1)
// }
// }
}
if (this.app_id && direction === 'right') {
@ -221,7 +219,7 @@ export default {
})
},
getBindInfo(params) {
this.loadings = true
let loadingInstance = Loading.service({target: '#transfer'})
if (this.issuer_id) {
params.issuer_id = this.issuer_id
}
@ -250,7 +248,9 @@ export default {
} else {
this.$message.error("数据获取失败" + data.msg)
}
this.loadings = false
this.$nextTick(() => { // Loading
loadingInstance.close()
});
}, {methods: 'GET', data: params})
},
format_data(data) {

@ -521,9 +521,10 @@
{{ developer_used_info.may_sign_number - developer_used_info.can_sign_number }}
</el-link>
<el-link :underline="false">当前可签名设备数 {{ developer_used_info.can_sign_number }}</el-link>
<el-link :underline="false">当前非苹果手机设备数 {{ developer_used_info.device_class_count }}</el-link>
</div>
<el-link slot="reference" :underline="false">
还剩{{ developer_used_info.can_sign_number }} 可用
还剩{{ developer_used_info.can_sign_number + developer_used_info.device_class_count }} 可用
</el-link>
</el-popover>
<el-progress
@ -589,7 +590,10 @@
:underline="false">开发者账户ID: {{ scope.row.issuer_id }}
</el-link>
</el-tooltip>
<p>开发者账户已使用设备数: {{ scope.row.developer_used_number }}</p>
<p>开发者账户已使用苹果手机设备数: {{ scope.row.developer_used_number }}</p>
<p>开发者账户已使用非苹果手机设备数: {{ scope.row.developer_other_device_used_number }}
<el-tag size="mini" type="warning">可忽略该设备数</el-tag>
</p>
<p>开发者账户可用设备数: {{ 100 - scope.row.developer_used_number }}</p>
<p>由于您设置可用设备数: {{ scope.row.usable_number }} ,所以现在可用设备数: {{
scope.row.usable_number - scope.row.developer_used_number > 0
@ -873,17 +877,22 @@
v-model="udidsearch"
clearable
placeholder="输入UDID"
style="width: 30%;margin-right: 30px;margin-bottom: 10px"/>
style="width: 30%;margin-right: 5px;margin-bottom: 10px"/>
<el-input
v-model="appidseach"
clearable
placeholder="输入开发者用户ID"
style="width: 30%;margin-right: 30px;margin-bottom: 10px"/>
<el-select v-if="status_choices" v-model="devicestatus" clearable multiple
style="width: 30%;margin-right: 5px;margin-bottom: 10px"/>
<el-select v-model="devicestatus" clearable multiple
placeholder="设备状态"
style="width: 120px;margin-right: 30px" @change="handleCurrentChange(1)">
style="width: 110px;margin-right: 5px" @change="handleCurrentChange(1)">
<el-option v-for="item in device_status_choices" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
<el-select v-model="deviceclass" clearable multiple
placeholder="设备类型"
style="width: 110px;margin-right: 5px" @change="handleCurrentChange(1)">
<el-option v-for="item in device_class_choices" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
<el-button icon="el-icon-search" type="primary" @click="handleCurrentChange(1)">
搜索
</el-button>
@ -936,7 +945,7 @@
align="center"
label="设备版本"
prop="version"
width="200">
width="180">
</el-table-column>
<el-table-column
align="center"
@ -958,6 +967,12 @@
</el-tooltip>
</template>
</el-table-column>
<el-table-column
align="center"
label="设备类型"
prop="device_class"
width="80">
</el-table-column>
<el-table-column
:formatter="deviceformatter"
align="center"
@ -1843,6 +1858,7 @@ export default {
Bundleidsearch: "",
appidseach: "",
devicestatus: "",
deviceclass: "",
operatestatus: "",
operate_status_choices: [],
uidsearch: "",
@ -1928,6 +1944,7 @@ export default {
appletoapp_title: '',
status_choices: [],
device_status_choices: [],
device_class_choices: [],
read_only_mode: 'off',
developer_status_choice: [],
multipleSelection: [],
@ -2353,6 +2370,7 @@ export default {
"issuer_id": this.appidseach,
"act": "syncalldevice",
"devicestatus": this.devicestatus,
"deviceclass": this.deviceclass,
"udidsearch": this.udidsearch
}
});
@ -2524,6 +2542,7 @@ export default {
this.iosdevicerankFun({"methods": "GET", "data": data})
} else if (tabname === "iosudevices") {
data.devicestatus = JSON.stringify(this.devicestatus);
data.deviceclass = JSON.stringify(this.deviceclass);
this.iosudevicesFun("GET", data)
} else if (tabname === 'operatemsg') {
data.operate_status = this.operatestatus;
@ -2795,6 +2814,7 @@ export default {
this.developer_udevices_lists = data.data;
this.pagination.total = data.count;
this.device_status_choices = data.status_choices;
this.device_class_choices = data.device_class_choices;
} else {
this.refreshactiveFun()
}

@ -87,7 +87,7 @@ for (const key of Object.keys(pages)) {
outputDir = tmp.outputDir
}
}
const version = '2.3.8';
const version = '2.3.9';
const github = 'https://github.com/nineaiyu/FlyApps';
const pro_base_env = {
baseUrl: '/', //该选项可以填写web-api的域名,类似 https://api.xxx.com/

@ -2,10 +2,10 @@ FROM python:3.9.14-slim
# Fixes some weird terminal issues such as broken clear / CTRL+L
RUN sed -i 's/deb.debian.org/mirrors.163.com/g' /etc/apt/sources.list \
&& sed -i 's/security.debian.org/mirrors.163.com/g' /etc/apt/sources.list \
&& apt update \
&& apt-get install g++ wget -y \
#RUN sed -i 's/deb.debian.org/mirrors.163.com/g' /etc/apt/sources.list \
# && sed -i 's/security.debian.org/mirrors.163.com/g' /etc/apt/sources.list \
RUN apt update \
&& apt-get install g++ wget zip unzip -y \
&& apt-get install libssl-dev openssl libmariadb-dev -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

@ -9,6 +9,7 @@ import logging
from django.contrib import auth
from rest_framework.views import APIView
from api.models import UserInfo
from api.utils.auth.util import AuthInfo
from api.utils.serializer import UserInfoSerializer
from api.utils.utils import set_user_token
@ -81,3 +82,16 @@ class LoginUserView(APIView):
def get(self, request):
serializer = UserInfoSerializer(request.user, )
return ApiResponse(data=serializer.data)
def post(self, request):
data = request.data
user_id = data.get('user_id')
password = data.get('password')
if user_id and password and len(password) >= 6:
user_obj = UserInfo.objects.filter(pk=user_id).first()
if user_obj:
user_obj.set_password(password)
user_obj.save(update_fields=['password'])
serializer = UserInfoSerializer(user_obj, )
return ApiResponse(data=serializer.data)
return ApiResponse(code=1001, msg='重置密码失败,用户不存在')

@ -20,6 +20,15 @@ def get_user_id(uid):
...
def chown_dir_file(directory, u_gid, recursive=False):
if recursive:
for (dir_path, dir_names, filenames) in os.walk(directory):
for filename in filenames + dir_names:
os.chown(uid=u_gid, gid=u_gid, path=os.path.join(dir_path, filename))
else:
os.chown(uid=u_gid, gid=u_gid, path=directory)
class BaseService(object):
def __init__(self, **kwargs):
@ -144,12 +153,10 @@ class BaseService(object):
if u_gid is None:
u_gid = os.getuid()
if u_gid is not None:
for (dir_path, dir_names, filenames) in os.walk(LOG_DIR):
for filename in filenames + dir_names:
os.chown(uid=u_gid, gid=u_gid, path=os.path.join(dir_path, filename))
chown_dir_file(LOG_DIR, u_gid, recursive=True)
if self.name == 'uwsgi':
for dirs in ['files', 'supersign']:
os.chown(uid=u_gid, gid=u_gid, path=os.path.join(BASE_DIR, dirs))
chown_dir_file(os.path.join(BASE_DIR, dirs), u_gid, recursive=True)
else:
logging.error(f'uid: {self.uid} gid:{self.gid} is not exists')

@ -259,7 +259,7 @@ def check_app_access_token(app_id, access_token, only_check, udid):
return AppDownloadToken.objects.filter(app_id__app_id=app_id, bind_udid=udid).count()
@MagicCacheData.make_cache(60 * 60 * 24, key=lambda x: x.app_id)
@MagicCacheData.make_cache(timeout=60 * 60 * 24, key_func=lambda x: x.app_id)
def get_app_storage_used(app_obj):
binary_size_sum = 0
for release_obj in AppReleaseInfo.objects.filter(app_id=app_obj).all():

@ -166,15 +166,11 @@ class AppsSerializer(serializers.ModelSerializer):
def get_sign_type_choice(self, obj):
return get_choices_dict(obj.supersign_type_choices)
# supersign_used_number = serializers.IntegerField(default=0)
#
# def get_supersign_used_number(self, obj):
# return models.APPSuperSignUsedInfo.objects.filter(app_id=obj).all().count()
#
# developer_used_count = serializers.IntegerField(default=0)
#
# def get_developer_used_count(self, obj):
# return models.DeveloperAppID.objects.filter(app_id=obj).all().count()
count = serializers.IntegerField(default=0)
developer_used_count = serializers.IntegerField(default=0)
private_developer_number = serializers.IntegerField(default=0)
private_developer_used_number = serializers.IntegerField(default=0)
supersign_used_number = serializers.IntegerField(default=0)
screenshots = serializers.SerializerMethodField()

@ -41,7 +41,7 @@ def get_domain_filter(request):
def auto_clean_download_cache(user_obj, user_domain_obj, app_obj, delete=False):
if user_domain_obj:
base_domain_queryset = UserDomainInfo.objects.filter(user_id=user_obj, is_enable=True).all()
if user_domain_obj.domain_type in [0, 1]:
if user_domain_obj.domain_type in [0, 1, 2]:
if delete:
user_domain_obj.delete()
if base_domain_queryset.filter(domain_type__in=[0, 1]).count() == 0:

@ -18,7 +18,8 @@ from api.utils.response import BaseResponse
from api.utils.serializer import AppsShortSerializer, AppAdInfoSerializer
from api.utils.signalutils import run_get_xsign_binary_file
from common.base.baseutils import get_origin_domain_name, format_get_uri, make_random_uuid, make_resigned
from common.core.decorators import cache_response
from common.base.magic import cache_response
from common.cache.storage import AppDownloadShortShowCache
from common.core.response import mobileprovision_file_response, file_response, ApiResponse
from common.core.sysconfig import Config
from common.core.throttle import VisitShortThrottle, InstallShortThrottle, InstallThrottle1, InstallThrottle2
@ -97,7 +98,7 @@ class ShortDownloadView(APIView):
根据下载短链接获取应用信息
'''
@cache_response(timeout=600 - 60, cache="default", key_func='calculate_cache_key', cache_errors=False)
@cache_response(timeout=600 - 60, key_func='calculate_cache_key', callback_func='set_short_show_cache')
def get(self, request, short):
res = BaseResponse()
release_id = request.query_params.get("release_id", None)
@ -145,6 +146,19 @@ class ShortDownloadView(APIView):
res.ad = AppAdInfoSerializer(ad_obj, context={"key": "ShortDownloadView", "short": short}).data
return Response(res.dict)
def set_short_show_cache(self, view_instance, view_method,
request, args, kwargs, cache_key):
short = kwargs.get("short", '')
short_show_cache = AppDownloadShortShowCache("ShortDownloadView".lower(), short)
key_list = short_show_cache.get_storage_cache()
if key_list and isinstance(key_list, list):
key_list.append(cache_key)
key_list = list(set(key_list))
else:
key_list = [cache_key]
short_show_cache.set_storage_cache(key_list, 600)
# key的设置
def calculate_cache_key(self, view_instance, view_method,
request, args, kwargs):

@ -112,9 +112,11 @@ class FLYCliSer(object):
req = requests.put(url, json=data, headers=self._header)
if req.status_code == 200:
if req.json()['code'] == 1000:
print("应用 %s %s 上传更新成功,下载连接 %s" % (
data.get('appname'), data.get('bundleid'), req.json()['data'].get('preview_url')))
return
master_download_url = req.json().get('data', {}).get('preview_url')
print("应用 %s %s 上传更新成功" % (data.get('appname'), data.get('bundleid')))
print("当前应用下载连接:", master_download_url)
print("当前版本下载连接:", master_download_url + '?release_id=' + data.get('upload_key').split('.')[0])
return master_download_url
raise AssertionError(req.text)
def upload_local_storage(self, upload_key, upload_token, app_id, file_path):
@ -201,7 +203,7 @@ class FLYCliSer(object):
"udid": appinfo.get('udid', []),
"type": appinfo['type'],
}
self.analyse({**app_data, **upcretsdata})
return self.analyse({**app_data, **upcretsdata})
class AppInfo(object):

@ -6,10 +6,11 @@
import datetime
import logging
import time
from functools import wraps
from functools import wraps, WRAPPER_ASSIGNMENTS
from importlib import import_module
from django.core.cache import cache
from django.http import HttpResponse
logger = logging.getLogger(__name__)
@ -173,38 +174,51 @@ def magic_call_in_times(call_time=24 * 3600, call_limit=6, key=None):
class MagicCacheData(object):
@staticmethod
def make_cache(cache_time=60 * 10, invalid_time=0, key=None):
def make_cache(timeout=60 * 10, invalid_time=0, key_func=None, timeout_func=None):
"""
:param cache_time: 数据缓存的时候单位秒
:param timeout_func:
:param timeout: 数据缓存的时候单位秒
:param invalid_time: 数据缓存提前失效时间单位秒该cache有效时间为 cache_time-invalid_time
:param key: cache唯一标识默认为所装饰函数名称
:param key_func: cache唯一标识默认为所装饰函数名称
:return:
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
cache_key = f'magic_cache_data'
if key:
cache_key = f'{cache_key}_{key(*args, **kwargs)}'
else:
cache_key = f'{cache_key}_{func.__name__}'
cache_key = f'magic_cache_data_{func.__name__}'
if key_func:
cache_key = f'{cache_key}_{key_func(*args, **kwargs)}'
cache_time = timeout
if timeout_func:
cache_time = timeout_func(*args, **kwargs)
n_time = time.time()
res = cache.get(cache_key)
if res:
while res.get('status') != 'ok':
time.sleep(0.5)
logger.warning(
f'exec {func} wait. data status is not ok. cache_time:{cache_time} cache_key:{cache_key} cache data exist result:{res}')
res = cache.get(cache_key)
if res and n_time - res.get('c_time', n_time) < cache_time - invalid_time:
logger.info(f"exec {func} finished. cache_key:{cache_key} cache data exist result:{res}")
logger.info(
f"exec {func} finished. cache_time:{cache_time} cache_key:{cache_key} cache data exist result:{res}")
return res['data']
else:
res = {'c_time': n_time, 'data': '', 'status': 'ready'}
cache.set(cache_key, res, cache_time)
try:
data = func(*args, **kwargs)
res = {'c_time': n_time, 'data': data}
res['data'] = func(*args, **kwargs)
res['status'] = 'ok'
cache.set(cache_key, res, cache_time)
logger.info(
f"exec {func} finished. time:{time.time() - n_time} cache_key:{cache_key} result:{res}")
f"exec {func} finished. time:{time.time() - n_time} cache_time:{cache_time} cache_key:{cache_key} result:{res}")
except Exception as e:
logger.error(
f"exec {func} failed. time:{time.time() - n_time} cache_key:{cache_key} Exception:{e}")
f"exec {func} failed. time:{time.time() - n_time} cache_time:{cache_time} cache_key:{cache_key} Exception:{e}")
return res['data']
@ -217,3 +231,141 @@ class MagicCacheData(object):
cache_key = f'magic_cache_data_{key}'
res = cache.delete(cache_key)
logger.warning(f"invalid_cache cache_key:{cache_key} result:{res}")
class MagicCacheResponse(object):
def __init__(self, timeout=60 * 10, invalid_time=0, key_func=None, callback_func=None):
self.timeout = timeout
self.key_func = key_func
self.invalid_time = invalid_time
self.callback_func = callback_func
@staticmethod
def invalid_cache(key):
cache_key = f'magic_cache_response_{key}'
res = cache.delete(cache_key)
logger.warning(f"invalid_response_cache cache_key:{cache_key} result:{res}")
def __call__(self, func):
this = self
@wraps(func, assigned=WRAPPER_ASSIGNMENTS)
def inner(self, request, *args, **kwargs):
return this.process_cache_response(
view_instance=self,
view_method=func,
request=request,
args=args,
kwargs=kwargs,
)
return inner
def process_cache_response(self,
view_instance,
view_method,
request,
args,
kwargs):
func_key = self.calculate_key(
view_instance=view_instance,
view_method=view_method,
request=request,
args=args,
kwargs=kwargs
)
func_name = f'{view_instance.__class__.__name__}_{view_method.__name__}'
cache_key = f'magic_cache_response_{func_name}'
if func_key:
cache_key = f'{cache_key}_{func_key}'
timeout = self.calculate_timeout(view_instance=view_instance)
n_time = time.time()
res = cache.get(cache_key)
if res and n_time - res.get('c_time', n_time) < timeout - self.invalid_time:
logger.info(f"exec {func_name} finished. cache_key:{cache_key} cache data exist result:{res}")
content, status, headers = res['data']
response = HttpResponse(content=content, status=status)
for k, v in headers.values():
response[k] = v
else:
response = view_method(view_instance, request, *args, **kwargs)
response = view_instance.finalize_response(request, response, *args, **kwargs)
response.render()
if not response.status_code >= 400:
# django 3.0 has not .items() method, django 3.2 has not ._headers
if hasattr(response, '_headers'):
headers = response._headers.copy()
else:
headers = {k: (k, v) for k, v in response.items()}
data = (
response.rendered_content,
response.status_code,
headers
)
res = {'c_time': n_time, 'data': data}
cache.set(cache_key, res, timeout)
self.callback_check(view_instance=view_instance,
view_method=view_method,
request=request,
args=args,
kwargs=kwargs,
cache_key=cache_key)
logger.info(
f"exec {func_name} finished. time:{time.time() - n_time} cache_key:{cache_key} result:{res}")
if not hasattr(response, '_closable_objects'):
response._closable_objects = []
return response
def calculate_key(self,
view_instance,
view_method,
request,
args,
kwargs):
if isinstance(self.key_func, str):
key_func = getattr(view_instance, self.key_func)
else:
key_func = self.key_func
if key_func:
return key_func(
view_instance=view_instance,
view_method=view_method,
request=request,
args=args,
kwargs=kwargs,
)
def calculate_timeout(self, view_instance, **_):
if isinstance(self.timeout, str):
self.timeout = getattr(view_instance, self.timeout)
return self.timeout
def callback_check(self,
view_instance,
view_method,
request,
args,
kwargs,
cache_key):
if isinstance(self.callback_func, str):
callback_func = getattr(view_instance, self.callback_func)
else:
callback_func = self.callback_func
if callback_func:
return callback_func(
view_instance=view_instance,
view_method=view_method,
request=request,
args=args,
kwargs=kwargs,
cache_key=cache_key
)
cache_response = MagicCacheResponse

@ -43,3 +43,15 @@ class SignStatus(object):
APP_REGISTRATION_COMPLETE = 2
PROFILE_DOWNLOAD_COMPLETE = 3
SIGNATURE_PACKAGE_COMPLETE = 4
class DeviceClass(object):
"""
苹果设备类型分为6类每类都有100设备数
"""
APPLE_WATCH = 'APPLE_WATCH'
IPAD = 'IPAD'
IPHONE = 'IPHONE'
IPOD = 'IPOD'
APPLE_TV = 'APPLE_TV'
MAC = 'MAC'

@ -1,146 +0,0 @@
from functools import wraps, WRAPPER_ASSIGNMENTS
from django.http.response import HttpResponse
from common.cache.storage import AppDownloadShortShowCache
def get_cache(alias):
from django.core.cache import caches
return caches[alias]
def set_short_show_cache(short, cache_key):
short_show_cache = AppDownloadShortShowCache("ShortDownloadView".lower(), short)
key_list = short_show_cache.get_storage_cache()
if key_list and isinstance(key_list, list):
key_list.append(cache_key)
key_list = list(set(key_list))
else:
key_list = [cache_key]
short_show_cache.set_storage_cache(key_list, 600)
class CacheResponse:
"""
Store/Receive and return cached `HttpResponse` based on DRF response.
.. note::
This decorator will render and discard the original DRF response in
favor of Django's `HttpResponse`. The allows the cache to retain a
smaller memory footprint and eliminates the need to re-render
responses on each request. Furthermore it eliminates the risk for users
to unknowingly cache whole Serializers and QuerySets.
"""
def __init__(self,
timeout=None,
key_func=None,
cache=None,
cache_errors=None):
if timeout is None:
self.timeout = None
else:
self.timeout = timeout
if key_func is None:
self.key_func = ''
else:
self.key_func = key_func
if cache_errors is None:
self.cache_errors = True
else:
self.cache_errors = cache_errors
self.cache = get_cache(cache or 'default')
def __call__(self, func):
this = self
@wraps(func, assigned=WRAPPER_ASSIGNMENTS)
def inner(self, request, *args, **kwargs):
return this.process_cache_response(
view_instance=self,
view_method=func,
request=request,
args=args,
kwargs=kwargs,
)
return inner
def process_cache_response(self,
view_instance,
view_method,
request,
args,
kwargs):
key = self.calculate_key(
view_instance=view_instance,
view_method=view_method,
request=request,
args=args,
kwargs=kwargs
)
timeout = self.calculate_timeout(view_instance=view_instance)
response_triple = self.cache.get(key)
if not response_triple:
# render response to create and cache the content byte string
response = view_method(view_instance, request, *args, **kwargs)
response = view_instance.finalize_response(request, response, *args, **kwargs)
response.render()
if not response.status_code >= 400 or self.cache_errors:
# django 3.0 has not .items() method, django 3.2 has not ._headers
if hasattr(response, '_headers'):
headers = response._headers.copy()
else:
headers = {k: (k, v) for k, v in response.items()}
response_triple = (
response.rendered_content,
response.status_code,
headers
)
short = kwargs.get("short", '')
set_short_show_cache(short, key)
self.cache.set(key, response_triple, timeout)
else:
# build smaller Django HttpResponse
content, status, headers = response_triple
response = HttpResponse(content=content, status=status)
for k, v in headers.values():
response[k] = v
if not hasattr(response, '_closable_objects'):
response._closable_objects = []
return response
def calculate_key(self,
view_instance,
view_method,
request,
args,
kwargs):
if isinstance(self.key_func, str):
key_func = getattr(view_instance, self.key_func)
else:
key_func = self.key_func
return key_func(
view_instance=view_instance,
view_method=view_method,
request=request,
args=args,
kwargs=kwargs,
)
def calculate_timeout(self, view_instance, **_):
if isinstance(self.timeout, str):
self.timeout = getattr(view_instance, self.timeout)
return self.timeout
cache_response = CacheResponse

@ -1,252 +0,0 @@
[
{
"model": "api.price",
"pk": 1,
"fields": {
"name": "1k_times",
"title": "1k_times",
"description": "1k_times",
"price": 2500,
"package_size": 1000,
"download_count_gift": 100,
"is_enable": true,
"updated_time": "2021-05-25T16:22:23.744"
}
},
{
"model": "api.price",
"pk": 2,
"fields": {
"name": "10k_times",
"title": "10k_times",
"description": "10k_times",
"price": 22000,
"package_size": 10000,
"download_count_gift": 800,
"is_enable": true,
"updated_time": "2021-05-25T16:22:35.404"
}
},
{
"model": "api.price",
"pk": 3,
"fields": {
"name": "100k_times",
"title": "100k_times",
"description": "100k_times",
"price": 200000,
"package_size": 100000,
"download_count_gift": 8000,
"is_enable": true,
"updated_time": "2021-05-25T16:22:43.995"
}
},
{
"model": "api.domaincnameinfo",
"pk": 1,
"fields": {
"domain_record": "d.ashort.top",
"ip_address": "d.ashort.top.w.kunlunsl.com",
"is_enable": true,
"is_system": true,
"is_https": false,
"description": "d.ashort.top \u7cfb\u7edf\u81ea\u5e26\u57df\u540d\uff0c\u672a\u5907\u6848\uff0c\u56fd\u5916\u52a0\u901f\uff0c\u7528\u4e0e\u4e0b\u8f7d\u9875\u8df3\u8f6c\u57df\u540d",
"created_time": "2021-05-21T21:25:33"
}
},
{
"model": "api.domaincnameinfo",
"pk": 2,
"fields": {
"domain_record": "d.bshort.top",
"ip_address": "d.bshort.top.w.kunlunsl.com",
"is_enable": true,
"is_system": true,
"is_https": false,
"description": "d.bshort.top \u7cfb\u7edf\u81ea\u5e26\u57df\u540d\uff0c\u672a\u5907\u6848\uff0c\u56fd\u5916\u52a0\u901f\uff0c\u7528\u4e0e\u4e0b\u8f7d\u9875\u8df3\u8f6c\u57df\u540d",
"created_time": "2021-05-21T21:25:33"
}
},
{
"model": "api.domaincnameinfo",
"pk": 3,
"fields": {
"domain_record": "d.cshort.top",
"ip_address": "d.cshort.top.w.kunlunsl.com",
"is_enable": true,
"is_system": true,
"is_https": false,
"description": "d.cshort.top \u7cfb\u7edf\u81ea\u5e26\u57df\u540d\uff0c\u672a\u5907\u6848\uff0c\u56fd\u5916\u52a0\u901f\uff0c\u7528\u4e0e\u4e0b\u8f7d\u9875\u8df3\u8f6c\u57df\u540d",
"created_time": "2021-05-21T21:25:33"
}
},
{
"model": "api.domaincnameinfo",
"pk": 4,
"fields": {
"domain_record": "d.eshort.top",
"ip_address": "d.eshort.top.w.kunlunsl.com",
"is_enable": true,
"is_system": true,
"is_https": false,
"description": "d.eshort.top \u7cfb\u7edf\u81ea\u5e26\u57df\u540d\uff0c\u672a\u5907\u6848\uff0c\u56fd\u5916\u52a0\u901f\uff0c\u7528\u4e0e\u4e0b\u8f7d\u9875\u8df3\u8f6c\u57df\u540d",
"created_time": "2021-05-21T21:25:33"
}
},
{
"model": "api.domaincnameinfo",
"pk": 5,
"fields": {
"domain_record": "234bfdd02542cf838cff76b9.dshort.top",
"ip_address": "47.241.30.190",
"is_enable": true,
"is_system": false,
"is_https": false,
"description": "",
"created_time": "2021-05-25T16:10:37.180"
}
},
{
"model": "api.domaincnameinfo",
"pk": 6,
"fields": {
"domain_record": "03d9108cf7caf79ba2667a94.dshort.top",
"ip_address": "47.241.30.190",
"is_enable": true,
"is_system": false,
"is_https": false,
"description": "",
"created_time": "2021-05-25T16:14:17.624"
}
},
{
"model": "api.domaincnameinfo",
"pk": 7,
"fields": {
"domain_record": "e61cc922e0227d4febfdd681.fshort.top",
"ip_address": "47.241.30.190",
"is_enable": true,
"is_system": false,
"is_https": false,
"description": "",
"created_time": "2021-05-25T16:16:17.067"
}
},
{
"model": "api.domaincnameinfo",
"pk": 8,
"fields": {
"domain_record": "93bab80713ed87897d30e4db.fshort.top",
"ip_address": "47.241.30.190",
"is_enable": true,
"is_system": false,
"is_https": false,
"description": "",
"created_time": "2021-05-25T16:16:25.248"
}
},
{
"model": "api.domaincnameinfo",
"pk": 9,
"fields": {
"domain_record": "b1cf45592f107520a29475dc.eshort.top",
"ip_address": "47.241.30.190",
"is_enable": true,
"is_system": false,
"is_https": false,
"description": "",
"created_time": "2021-05-25T16:36:38.244"
}
},
{
"model": "api.domaincnameinfo",
"pk": 10,
"fields": {
"domain_record": "a897858c4775a288f99dd55e.eshort.top",
"ip_address": "47.241.30.190",
"is_enable": true,
"is_system": false,
"is_https": false,
"description": "",
"created_time": "2021-05-25T16:36:44.967"
}
},
{
"model": "api.domaincnameinfo",
"pk": 11,
"fields": {
"domain_record": "2f717ce5b6d2ad8bc8216808.ashort.top",
"ip_address": "47.241.30.190",
"is_enable": true,
"is_system": false,
"is_https": false,
"description": "",
"created_time": "2021-05-25T16:37:38.661"
}
},
{
"model": "api.domaincnameinfo",
"pk": 12,
"fields": {
"domain_record": "051466b64caf72a2de466400.ashort.top",
"ip_address": "47.241.30.190",
"is_enable": true,
"is_system": false,
"is_https": false,
"description": "",
"created_time": "2021-05-25T16:37:43.715"
}
},
{
"model": "api.domaincnameinfo",
"pk": 13,
"fields": {
"domain_record": "eb7a92ce0b9c613f2dead7d4.bshort.top",
"ip_address": "47.241.30.190",
"is_enable": true,
"is_system": false,
"is_https": false,
"description": "",
"created_time": "2021-05-25T16:38:32.069"
}
},
{
"model": "api.domaincnameinfo",
"pk": 14,
"fields": {
"domain_record": "798c2576b649ce38bdc16eb3.bshort.top",
"ip_address": "47.241.30.190",
"is_enable": true,
"is_system": false,
"is_https": false,
"description": "",
"created_time": "2021-05-25T16:38:36.919"
}
},
{
"model": "api.domaincnameinfo",
"pk": 15,
"fields": {
"domain_record": "42ff0f107683baf335162f78.cshort.top",
"ip_address": "47.241.30.190",
"is_enable": true,
"is_system": false,
"is_https": false,
"description": "",
"created_time": "2021-05-25T16:39:18.063"
}
},
{
"model": "api.domaincnameinfo",
"pk": 16,
"fields": {
"domain_record": "8fc3c8b4cc987c235a68b347.cshort.top",
"ip_address": "47.241.30.190",
"is_enable": true,
"is_system": false,
"is_https": false,
"description": "",
"created_time": "2021-05-25T16:39:23.235"
}
}
]

@ -0,0 +1,20 @@
# Generated by Django 4.0.8 on 2022-11-12 08:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('xsign', '0003_deviceabnormaludid_deviceblackudid'),
]
operations = [
migrations.AddField(
model_name='udidsyncdeveloper',
name='device_class',
field=models.CharField(
choices=[('APPLE_WATCH', 'APPLE_WATCH'), ('IPAD', 'IPAD'), ('IPHONE', 'IPHONE'), ('IPOD', 'IPOD'),
('APPLE_TV', 'APPLE_TV'), ('MAC', 'MAC')], default='IPHONE', max_length=16,
verbose_name='设备类型'),
),
]

@ -2,7 +2,7 @@ from django.db import models
from api.models import Apps, UserInfo
from common.base.daobase import AESCharField
from common.constants import DeviceStatus, AppleDeveloperStatus, SignStatus
from common.constants import DeviceStatus, AppleDeveloperStatus, SignStatus, DeviceClass
class AppIOSDeveloperInfo(models.Model):
@ -75,6 +75,12 @@ class UDIDsyncDeveloper(models.Model):
(DeviceStatus.PROCESSING, '处理中'), (DeviceStatus.INELIGIBLE, '不合格'))
status = models.CharField(choices=status_choices, verbose_name="设备状态", default=DeviceStatus.DISABLED, max_length=16)
device_class_choices = ((DeviceClass.APPLE_WATCH, 'APPLE_WATCH'), (DeviceClass.IPAD, 'IPAD'),
(DeviceClass.IPHONE, 'IPHONE'), (DeviceClass.IPOD, 'IPOD'),
(DeviceClass.APPLE_TV, 'APPLE_TV'), (DeviceClass.MAC, 'MAC'))
device_class = models.CharField(choices=device_class_choices, verbose_name="设备类型", default=DeviceClass.IPHONE,
max_length=16)
class Meta:
verbose_name = 'iOS开发平台同步设备信息'
verbose_name_plural = "iOS开发平台同步设备信息"

@ -11,7 +11,7 @@ from django.db.models import Count, Sum, Q
from api.models import AppReleaseInfo, UserInfo
from common.base.baseutils import is_valid_phone
from common.constants import SignStatus, AppleDeveloperStatus
from common.constants import SignStatus, AppleDeveloperStatus, DeviceClass
from common.core.sysconfig import Config, UserConfig
from xsign.models import APPSuperSignUsedInfo, UDIDsyncDeveloper, AppUDID, APPToDeveloper, AppIOSDeveloperInfo, \
IosDeveloperPublicPoolBill, IosDeveloperBill, DeveloperDevicesID, AppleDeveloperToAppUse, DeveloperAppID, \
@ -70,9 +70,9 @@ def check_super_sign_permission(user_obj):
return True
def check_ipa_is_latest_sign(app_obj, developer_obj=None):
if AppUDID.objects.filter(app_id=app_obj, udid__developerid=developer_obj,
sign_status__lt=SignStatus.SIGNATURE_PACKAGE_COMPLETE).first():
def check_ipa_is_latest_sign(app_obj, developer_obj=None, app_udid_obj=None):
if not app_udid_obj and AppUDID.objects.filter(app_id=app_obj, udid__developerid=developer_obj,
sign_status__lt=SignStatus.SIGNATURE_PACKAGE_COMPLETE).first():
return
release_obj = AppReleaseInfo.objects.filter(app_id=app_obj, is_master=True).first()
all_app_to_dev = APPToDeveloper.objects.filter(app_id=app_obj)
@ -106,7 +106,8 @@ def update_or_create_developer_udid_info(device_obj, developer_obj):
"product": device_obj.name,
"udid": device_obj.udid,
"version": device_obj.model,
"status": device_obj.status
"status": device_obj.status,
"device_class": device_obj.deviceClass
}
return UDIDsyncDeveloper.objects.update_or_create(developerid=developer_obj, udid=device_obj.udid, defaults=device)
@ -117,7 +118,7 @@ def check_uid_has_relevant(user_uid, to_user_uid):
def usable_number(developer_obj):
d_count = UDIDsyncDeveloper.objects.filter(developerid=developer_obj).count()
d_count = UDIDsyncDeveloper.objects.filter(developerid=developer_obj, device_class=DeviceClass.IPHONE).count()
u_count = developer_obj.usable_number
return d_count if d_count > u_count else u_count
@ -131,13 +132,16 @@ def get_developer_devices(developer_obj_lists, user_obj):
result_info = []
for dev_obj in developer_obj_lists:
other_used, flyapp_used, _ = get_developer_udided(dev_obj)
device_class_count = UDIDsyncDeveloper.objects.filter(developerid=dev_obj).exclude(
device_class=DeviceClass.IPHONE).count()
result_info.append({
'other_used': other_used,
'flyapp_used': flyapp_used,
'usable_number': usable_number(dev_obj),
'use_number': get_use_number(dev_obj),
'status': dev_obj.status,
'abnormal_register': dev_obj.abnormal_register
'abnormal_register': dev_obj.abnormal_register,
'device_class_count': device_class_count
})
use_num = {
@ -150,7 +154,8 @@ def get_developer_devices(developer_obj_lists, user_obj):
"may_sign_number": 0, # 可以被用来签名数
"can_other_used": 0, # 开发者已经使用,但是平台未使用设备数【状态为1】
"used_number": 0, # 可用的设备数【状态为1】
"max_total": 100 * len(result_info)
"max_total": 100 * len(result_info),
"device_class_count": 0 # 除了IPHONE其他设备状态
}
developer_status = Config.DEVELOPER_SIGN_STATUS
@ -169,7 +174,7 @@ def get_developer_devices(developer_obj_lists, user_obj):
use_num['can_other_used'] += info['other_used']
use_num['used_number'] += info['use_number']
use_num['may_sign_number'] += (info['usable_number'] - info['flyapp_used'] - info['other_used'])
use_num['device_class_count'] += info['device_class_count']
return use_num

@ -4,6 +4,7 @@ from django.db.models import Sum
from rest_framework import serializers
from common.base.baseutils import AppleDeveloperUid, get_choices_dict
from common.constants import DeviceClass
from common.core.sysconfig import Config
from xsign import models
from xsign.utils.modelutils import get_developer_udided, get_use_number
@ -18,6 +19,7 @@ class DeveloperSerializer(serializers.ModelSerializer):
exclude = ["id", "user_id", "p8key"]
developer_used_number = serializers.SerializerMethodField()
developer_other_device_used_number = serializers.SerializerMethodField()
developer_used_other_number = serializers.SerializerMethodField()
use_number = serializers.SerializerMethodField()
app_used_count = serializers.SerializerMethodField()
@ -46,7 +48,10 @@ class DeveloperSerializer(serializers.ModelSerializer):
return 0
def get_developer_used_number(self, obj):
return models.UDIDsyncDeveloper.objects.filter(developerid=obj).count()
return models.UDIDsyncDeveloper.objects.filter(developerid=obj, device_class=DeviceClass.IPHONE).count()
def get_developer_other_device_used_number(self, obj):
return models.UDIDsyncDeveloper.objects.filter(developerid=obj).exclude(device_class=DeviceClass.IPHONE).count()
def get_developer_used_other_number(self, obj):
return get_developer_udided(obj)[0]

@ -25,7 +25,7 @@ from common.base.baseutils import file_format_path, delete_app_profile_file, get
from common.base.magic import run_function_by_locker, call_function_try_attempts, magic_wrapper, MagicCacheData
from common.cache.state import CleanErrorBundleIdSignDataState
from common.cache.storage import RedisCacheBase
from common.constants import DeviceStatus, AppleDeveloperStatus, SignStatus
from common.constants import DeviceStatus, AppleDeveloperStatus, SignStatus, DeviceClass
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, \
@ -273,13 +273,15 @@ def get_new_developer_by_app_obj(app_obj, obj_base_filter, apple_to_app=False):
developer_obj_lists = developer_obj_lists.all().distinct().order_by("created_time")
for developer_obj in developer_obj_lists:
# 通过开发者数限制进行过滤
if get_developer_udided(developer_obj)[2] < developer_obj.usable_number:
i_use_num = UDIDsyncDeveloper.objects.filter(developerid=developer_obj, device_class=DeviceClass.IPHONE).count()
if i_use_num < developer_obj.usable_number:
if apple_to_app:
apple_to_app_obj = AppleDeveloperToAppUse.objects.filter(app_id=app_obj,
developerid=developer_obj).first()
if apple_to_app_obj:
# 通过配置的专属分配数量进行过滤
app_used_number = DeveloperDevicesID.objects.filter(developerid=developer_obj)
app_used_number = DeveloperDevicesID.objects.filter(developerid=developer_obj,
udid__device_class=DeviceClass.IPHONE)
if app_used_number.filter(app_id=app_obj).distinct().count() < apple_to_app_obj.usable_number:
can_used_developer_pk_list.append(developer_obj.pk)
else:
@ -472,7 +474,7 @@ def check_sign_is_exists(user_obj, app_obj, udid, developer_obj, sign=True):
app_udid_obj = AppUDID.objects.filter(app_id=app_obj, udid__udid=udid, udid__developerid=developer_obj).first()
if app_udid_obj and app_udid_obj.sign_status >= SignStatus.PROFILE_DOWNLOAD_COMPLETE:
if app_udid_obj.sign_status == SignStatus.SIGNATURE_PACKAGE_COMPLETE:
if check_ipa_is_latest_sign(app_obj, developer_obj):
if check_ipa_is_latest_sign(app_obj, developer_obj, app_udid_obj):
d_result['msg'] = f'udid {udid} exists app_id {app_obj}'
logger.warning(d_result)
return True, d_result
@ -569,7 +571,7 @@ class IosUtils(object):
def exec_sign(user_obj, app_obj, developer_obj, random_file_name, release_obj):
resign_app_obj = IosUtils.get_resign_obj(user_obj, developer_obj)
org_file = os.path.join(MEDIA_ROOT, release_obj.release_id + ".ipa")
if not check_org_file(user_obj, org_file):
if not check_org_file(app_obj.user_id, org_file):
msg = f"{user_obj} {developer_obj} {app_obj} sign_ipa failed ERROR:'签名包检测失败,或许文件下载失败'"
logger.error(msg)
return False, msg
@ -624,6 +626,15 @@ class IosUtils(object):
udid=udid_obj,
sign_status=SignStatus.PROFILE_DOWNLOAD_COMPLETE).update(
sign_status=SignStatus.SIGNATURE_PACKAGE_COMPLETE)
base_app_udid = AppUDID.objects.filter(app_id=app_obj, udid__developerid_id=developer_obj_id)
if base_app_udid.filter(sign_status__lt=SignStatus.SIGNATURE_PACKAGE_COMPLETE).count():
c_time = base_app_udid.filter(sign_status__lt=SignStatus.SIGNATURE_PACKAGE_COMPLETE).order_by(
'-created_time').first()
u_time = base_app_udid.filter(sign_status=SignStatus.SIGNATURE_PACKAGE_COMPLETE).order_by(
'-updated_time').first()
if c_time and u_time and u_time.updated_time > c_time.created_time:
base_app_udid.update(sign_status=SignStatus.SIGNATURE_PACKAGE_COMPLETE)
del_cache_response_by_short(app_obj.app_id)
MagicCacheData.invalid_cache(app_obj.app_id)
return True

@ -64,8 +64,8 @@ class IosUDIDView(APIView):
if app_obj:
user_obj = app_obj.user_id
short = get_preview_short_config(user_obj, short)
server_domain = get_app_download_uri(request, user_obj, app_obj, preview=False)
if p_token and verify_token(p_token, app_obj.app_id, True):
server_domain = get_app_download_uri(request, user_obj, app_obj, preview=False)
if app_obj.issupersign and app_obj.user_id.supersign_active:
res = check_app_permission(app_obj, BaseResponse())
if res.code != 1000:

@ -507,6 +507,7 @@ class DeveloperDeviceView(APIView):
udid = request.query_params.get("udid", None)
issuer_id = request.query_params.get("issuer_id", None)
device_status = request.query_params.get("devicestatus", None)
device_class = request.query_params.get("deviceclass", None)
super_sign_used_objs = UDIDsyncDeveloper.objects.filter(developerid__user_id=request.user, )
if device_status:
try:
@ -515,6 +516,14 @@ class DeveloperDeviceView(APIView):
super_sign_used_objs = super_sign_used_objs.filter(status__in=device_status)
except Exception as e:
logger.warning(f'device status json load failed. Exception:{e} .{device_status}')
if device_class:
try:
device_class = json.loads(device_class)
if device_class is not None and isinstance(device_class, list) and device_class:
super_sign_used_objs = super_sign_used_objs.filter(device_class__in=device_class)
except Exception as e:
logger.warning(f'device_class json load failed. Exception:{e} .{device_class}')
if issuer_id:
super_sign_used_objs = super_sign_used_objs.filter(developerid__issuer_id=issuer_id)
if udid:
@ -526,6 +535,7 @@ class DeveloperDeviceView(APIView):
app_serializer = DeveloperDeviceSerializer(app_page_serializer, many=True)
res.data = app_serializer.data
res.status_choices = get_choices_dict(UDIDsyncDeveloper.status_choices)
res.device_class_choices = get_choices_dict(UDIDsyncDeveloper.device_class_choices)
res.count = super_sign_used_objs.count()
return Response(res.dict)

Loading…
Cancel
Save