Compare commits

...

42 Commits

Author SHA1 Message Date
isummer e12779d654 修复容器部署失败问题,重新编写本地部署文档 2 years ago
isummer ac590563a7 修复超级签共享用户下载文件失败问题 2 years ago
isummer 8fc1d353c9 管理后台增加重置密码功能 2 years ago
isummer 35b64d74de 修复flyapps容器构建失败问题,优化部署文档 2 years ago
isummer a064988f56 修复flyapps容器构建失败问题,优化部署文档 2 years ago
isummer adff361c11 修复非苹果设备导致余额不足问题 2 years ago
isummer 419e39eaaa 修复非苹果设备导致余额不足问题 2 years ago
isummer 9d9cb3f2cb Merge remote-tracking branch 'origin/master' 2 years ago
isummer dd802dccbb 优化代码 2 years ago
isummer 1262bff671 优化代码 2 years ago
isummer 289737ed00 修复异常,优化代码 2 years ago
isummer dc7b491534 修复新苹果,新应用无法设置专属签名问题 2 years ago
isummer eed0f5194c 增加苹果设备类型区分,优化苹果设备注册量及展示 2 years ago
isummer e0599585be 修复前端应用超级签名展示异常问题 2 years ago
isummer 42451f2cb0 修复超级签权限问题 2 years ago
isummer 646162593e 增加打包时间 2 years ago
isummer 200494fcf3 增加打包时间 2 years ago
isummer fe5a7605f0 增加打包时间 2 years ago
isummer f70351d8e5 修复容器启动异常问题 2 years ago
isummer 9e2f40383a 优化代码,增加邀请码配置,修改极验弹窗模式 2 years ago
isummer debe95791e 优化容器部署 2 years ago
isummer a3887825c9 更新依赖包,支持python39环境 2 years ago
isummer 8a8f9ef7fe 升级python环境为python39 2 years ago
isummer 8e325e6cde 优化容器启停异常 2 years ago
isummer 92961cfa61 flyapps-cli增加容器访问 2 years ago
isummer 8c25aab63c flyapps-cli增加容器访问 2 years ago
isummer 0cc5084390 修复授权绑定设备添加重复问题 2 years ago
isummer 4d98981485 容器部署支持管理后台访问 2 years ago
isummer a7a1472533 苹果应用授权码支持添加绑定设备 2 years ago
isummer 332d12bddf 容器部署优化 2 years ago
isummer 6439824468 优化docker部署方式 2 years ago
isummer 42478f21d9 1.修复授权码为一次,超级签苹果包下载失败问题 2 years ago
isummer 2c67305f8a 增加通过命令给用户充值次数 2 years ago
isummer 79037d5248 修复应用上传超级签检测问题 2 years ago
isummer 525ec097d7 更新readme 2 years ago
isummer a35ca0b087 更新配置文件信息 2 years ago
isummer d9ac93ccc1 no message 2 years ago
isummer 1ff3da6896 修复管理页面预览展示 2 years ago
isummer cbad06e95d 应用时间更新修复 2 years ago
isummer b093d7133b 页面底部信息配置迁移 2 years ago
isummer 7aa0471ba9 增加应用当天下载次数展示 2 years ago
isummer 2224ce27c7 优化代码 2 years ago
  1. 2
      .gitignore
  2. 163
      README.md
  3. 12
      build.sh
  4. 7
      build_client.sh
  5. 102
      doc/docker.md
  6. BIN
      doc/images/img_1.png
  7. BIN
      doc/images/img_10.png
  8. BIN
      doc/images/img_11.png
  9. BIN
      doc/images/img_2.png
  10. BIN
      doc/images/img_3.png
  11. BIN
      doc/images/img_4.png
  12. BIN
      doc/images/img_5.png
  13. BIN
      doc/images/img_6.png
  14. BIN
      doc/images/img_7.png
  15. BIN
      doc/images/img_8.png
  16. BIN
      doc/images/img_9.png
  17. 166
      doc/local.md
  18. 51
      docker.md
  19. 67
      docker/build/docker-compose.yml
  20. 31
      docker/flyapps/docker-compose.only.yml
  21. 48
      docker/flyapps/docker-compose.yml
  22. 7
      docker/init/clean.sh
  23. 10
      docker/init/download.images
  24. 24
      docker/init/init.sh
  25. 1
      docker/init/timezone
  26. 43
      docker/mariadb/docker-compose.yml
  27. 108
      docker/mariadb/server.cnf
  28. 25
      docker/redis/docker-compose.yml
  29. 6
      docker/scripts/build.sh
  30. 9
      docker/scripts/start_all.sh
  31. 10
      docker/scripts/stop_all.sh
  32. 2
      fir_admin/.env.production
  33. 8
      fir_admin/src/api/user.js
  34. 2
      fir_admin/src/views/appinfos/list.vue
  35. 2
      fir_admin/src/views/login/index.vue
  36. 58
      fir_admin/src/views/userinfos/UserDetail.vue
  37. 1
      fir_client/package.json
  38. 6
      fir_client/public/index.html
  39. 6
      fir_client/public/short.html
  40. BIN
      fir_client/src/assets/github.png
  41. BIN
      fir_client/src/assets/wxchat.jpg
  42. 14
      fir_client/src/components/FirFooter.vue
  43. 5
      fir_client/src/components/FirLogin.vue
  44. 10
      fir_client/src/components/apps/FirAppInfosBase.vue
  45. 82
      fir_client/src/components/apps/FirAppInfossecurity.vue
  46. 5
      fir_client/src/components/apps/FirApps.vue
  47. 26
      fir_client/src/components/base/AppleDeveloperBindApp.vue
  48. 15
      fir_client/src/components/index/FirContact.vue
  49. 4
      fir_client/src/components/index/FirIndexBody.vue
  50. 9
      fir_client/src/components/index/FirIndexTem.vue
  51. 16
      fir_client/src/components/index/FirNews.vue
  52. 38
      fir_client/src/components/user/FirSuperSignBase.vue
  53. 21
      fir_client/src/restful/download.js
  54. 50
      fir_client/src/restful/index.js
  55. 6
      fir_client/src/utils/base/utils.js
  56. 22
      fir_client/src/utils/format.js
  57. 6
      fir_client/src/utils/index.js
  58. 70
      fir_client/vue.config.js
  59. 37
      fir_ser/Dockerfile
  60. 11
      fir_ser/Dockerfile.cli
  61. 5
      fir_ser/admin/utils/serializer.py
  62. 2
      fir_ser/admin/views/celery_flower.py
  63. 14
      fir_ser/admin/views/login.py
  64. 14
      fir_ser/api/management/commands/add_user_download_times.py
  65. 2
      fir_ser/api/management/commands/services/command.py
  66. 15
      fir_ser/api/management/commands/services/services/base.py
  67. 4
      fir_ser/api/utils/apputils.py
  68. 5
      fir_ser/api/utils/modelutils.py
  69. 22
      fir_ser/api/utils/serializer.py
  70. 27
      fir_ser/api/views/apps.py
  71. 2
      fir_ser/api/views/domain.py
  72. 18
      fir_ser/api/views/download.py
  73. 4
      fir_ser/api/views/login.py
  74. 7
      fir_ser/api/views/uploads.py
  75. 12
      fir_ser/cli.py
  76. 180
      fir_ser/common/base/magic.py
  77. 12
      fir_ser/common/constants.py
  78. 146
      fir_ser/common/core/decorators.py
  79. 12
      fir_ser/common/core/signals.py
  80. 4
      fir_ser/common/core/sysconfig.py
  81. 5
      fir_ser/common/libs/mp/wechat.py
  82. 2
      fir_ser/common/libs/pay/wx.py
  83. 6
      fir_ser/common/libs/pay/wxpay/__init__.py
  84. 4
      fir_ser/common/libs/pay/wxpay/core.py
  85. 10
      fir_ser/common/utils/caches.py
  86. 2
      fir_ser/common/utils/download.py
  87. 193
      fir_ser/config.py
  88. 252
      fir_ser/dumpdata.json
  89. 22
      fir_ser/entrypoint.sh
  90. 3
      fir_ser/fir_ser/settings.py
  91. 43
      fir_ser/requirements.txt
  92. 2
      fir_ser/requirements/rpm_requirements.txt
  93. 11
      fir_ser/tests/django_test.py
  94. 20
      fir_ser/xsign/migrations/0004_udidsyncdeveloper_device_class.py
  95. 8
      fir_ser/xsign/models.py
  96. 21
      fir_ser/xsign/utils/modelutils.py
  97. 7
      fir_ser/xsign/utils/serializer.py
  98. 21
      fir_ser/xsign/utils/supersignutils.py
  99. 2
      fir_ser/xsign/views/appinfo.py
  100. 10
      fir_ser/xsign/views/receiveudids.py
  101. Some files were not shown because too many files have changed in this diff Show More

2
.gitignore vendored

@ -12,3 +12,5 @@ fir_client/src/assets/down_right.png
mailhtml/build/ mailhtml/build/
mailhtml/node_modules/ mailhtml/node_modules/
mailhtml/.idea/ mailhtml/.idea/
fir_client/dist_index/
fir_client/dist_short/

@ -1,100 +1,63 @@
##### 从git上面下载源码 ### 用与应用分发,苹果超级签名
```shell #### 部署前准备
cd /data/ - 备案域名【至少需要一个域名,以下可通过子域名部署】
git clone https://github.com/nineaiyu/FlyApps - API域名
``` - 前端web域名
- 下载页域名
- 下载页域名可配置多个
### FlyApp 服务器搭建 [python3 环境] - 存储域名(使用阿里云oss存储)
- ssl证书
##### 搭建python env 环境 - API域名证书
```shell - 存储域名证书(使用阿里云oss存储)
yum install python36 python36-devel redis mariadb-server mariadb-devel -y - 前端web域名证书(可选)
python3 -m venv py3 - Centos8Stream 服务器
source py3/bin/activate - 如果使用oss存储,则带宽为1M,若使用本地存储,则带宽越大越好
``` - 如果使用超级签,最低配置为2cpu 4G内存,若干不使用签名,则1cpu2G就行
- 阿里云短信或极光短信服务【可选一个,主要用与注册,重置密码】
###### 安装pip包 - 阿里云短信
```shell - 极光短信
cd fir_ser/ - 邮箱服务【可选,用与注册,重置密码,通知信息】
pip install -U setuptools pip - 阿里云OSS存储【可选】
pip install -r requirements.txt - [sts授权配置](https://help.aliyun.com/document_detail/100624.html)
``` - 阿里云CDN【可选,用与加速访问】
- 极验验证【可选,滑动验证服务】
###### 配置数据库 - 微信公众号【可选,用与微信扫描登录】
``` - 阿里云支付【可选,用与购买下载次数】
在 config.py 配置redis 和 mysql 数据库 - 微信支付【可选,用与购买下载次数】
```
#### 自用搭建建议
###### 迁移数据库 - 阿里云服务器需要1cpu 2G内存,无需系统盘,如果使用超级签,可以适当增加配置
```shell - 需要阿里云OSS存储和阿里云CDN,并且OSS存储和阿里云服务器部署同一个地区
#如果是mysql,需要做一下配置,如果是sqlite,需要升级sqlite - 可以申请一个极验进行滑动验证,或者开启验证码验证
#记得根据配置创建数据库 - 阿里云备案域名:api和前端可以使用一个域名,下载页单独域名
#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 - web域名和证书
python manage.py migrate - api域名和证书
``` - 下载页域名(可配置证书)
- 存储域名和证书
### FlyApp zsign ipa重签名工具安装 - 本地存储,则该域名和证书可以和api域名证书一致
```shell - 阿里云oss存储
yum install openssl-devel -y - 开启cdn,需要新域名和证书
yum install gcc-c++ gcc -y - 不开启,无需域名和证书
wget https://github.com/nineaiyu/zsign/archive/refs/tags/v1.1.2.tar.gz - Centos8Stream 服务器
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/ ### 1.[Docker 部署](./doc/docker.md) 【推荐】
```
### 2.[本地部署](./doc/local.md)
### FlyApp web端搭建
###### npm编译环境 ### 功能预览
```shell ![img_1.png](./doc/images/img_1.png)
yum install npm ![img_2.png](./doc/images/img_2.png)
npm install -g n ![img_3.png](./doc/images/img_3.png)
n 12.13 # 安装12版本的node ,最新版本会有问题 ![img_4.png](./doc/images/img_4.png)
npm install -g yarn ![img_5.png](./doc/images/img_5.png)
``` ![img_6.png](./doc/images/img_6.png)
![img_7.png](./doc/images/img_7.png)
###### 编译web端和下载页 ![img_8.png](./doc/images/img_8.png)
```shell ![img_9.png](./doc/images/img_9.png)
cd FlyApps/fir_client/ ![img_10.png](./doc/images/img_10.png)
vim vue.config.js #修改api接口地址 ![img_11.png](./doc/images/img_11.png)
# 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,12 +0,0 @@
#!/bin/bash
#
#
# shellcheck disable=SC2006
s_path=`pwd`
#first build fir_client
docker pull node:14.17.3
\cp -a build_client.sh fir_client/
docker run --rm -v "${s_path}"/fir_client:/fir_client -it node:14.17.3 sh /fir_client/build_client.sh
cd "${s_path}"/fir_ser/ && docker build . -t flyapps

@ -1,7 +0,0 @@
#!/bin/bash
#
#
cd /fir_client/ && yarn config set registry https://registry.npm.taobao.org && yarn install && yarn build

@ -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,51 +0,0 @@
##### 从git上面下载源码
```
cd /data/
git clone https://github.com/nineaiyu/FlyApps
```
#### docker环境安装 [centos7]
```
yum install epel-release -y
yum clean all && yum makecache
yum install docker -y
```
#### 配置域名和证书,如果有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,存储等信息
```shell script
vim fir_ser/config.py
API_DOMAIN = "https://app.hehelucky.cn"
WEB_DOMAIN = "https://app.hehelucky.cn"
MOBILEPROVISION = "https://ali-static.jappstore.com/embedded.mobileprovision"
```
### web页面需要修改指定api域名
```vuejs
vim fir_client/vue.config.js
const pro_base_env = {
baseUrl: 'https://flyapps.cn',
index_static: 'https://static.flyapps.cn/index/',
baseShortUrl: 'https://flyapps.top',
short_static: 'https://static.flyapps.top/short/',
};
```
##### 构建静态资源和api服务
```
sh build.sh
```
##### 启动所有服务
```
sh start.sh
```

@ -0,0 +1,67 @@
version: '3.2'
services:
buildflyapps:
image: 'flyapps'
build:
context: ../../fir_ser
dockerfile: Dockerfile
command:
- echo
- success
buildclient:
container_name: buildclient
image: 'node:14.17.3'
working_dir: /fir_client
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../fir_client:/fir_client
- ../../data/web/:/web
command:
- bash
- -c
- yarn config set registry https://registry.npm.taobao.org && yarn install && yarn build index && yarn build short && ([ ! -e /web/fir_client ] && \cp -a dist_index /web/fir_client || \cp -a dist_index/* /web/fir_client/) && ([ ! -e /web/fir_client/short ] && \cp -a dist_short /web/fir_client/short || \cp -a dist_short/* /web/fir_client/short/) && echo build success
buildindex:
container_name: buildindex
image: 'node:14.17.3'
working_dir: /fir_client
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../fir_client:/fir_client
- ../../data/web/:/web
command:
- bash
- -c
- yarn config set registry https://registry.npm.taobao.org && yarn install && yarn build index && ([ ! -e /web/fir_index ] && \cp -a dist_index /web/fir_index || \cp -a dist_index/* /web/fir_index/) && echo build success
buildshort:
container_name: buildshort
image: 'node:14.17.3'
working_dir: /fir_client
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../fir_client:/fir_client
- ../../data/web/:/web
command:
- sh
- -c
- yarn config set registry https://registry.npm.taobao.org && yarn install && yarn build short && ([ ! -e /web/fir_short ] && \cp -a dist_short /web/fir_short || \cp -a dist_short/* /web/fir_short/) && echo build success
buildadmin:
container_name: buildadmin
image: 'node:14.17.3'
working_dir: /fir_admin
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../fir_admin:/fir_admin
- ../../data/web/:/web
command:
- sh
- -c
- yarn config set registry https://registry.npm.taobao.org && yarn install && yarn build:prod && ([ ! -e /web/fir_admin ] && \cp -a dist /web/fir_admin || \cp -a dist/* /web/fir_admin/) && echo build success

@ -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"

@ -0,0 +1,48 @@
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
nginx:
container_name: nginx
restart: always
image: 'nginx:1.21.3'
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../data/web:/data
- ../../nginx.conf.d:/etc/nginx/conf.d
- ../../data/logs/nginx:/var/log/nginx
networks:
flyapps:
ipv4_address: 172.31.31.200
ports:
- 80:80
- 443:443
- 3448:3448
external_links:
- flyapps:flyapps
depends_on:
- flyapps

@ -0,0 +1,7 @@
#!/bin/bash
#
#
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'

@ -0,0 +1,24 @@
#!/bin/bash
#
#
which dockerd
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
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}}
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

@ -0,0 +1 @@
Asia/Shanghai

@ -0,0 +1,43 @@
version: '3.2'
networks:
flyapps:
external: true
name: flyapps
services:
mariadb:
image: bitnami/mariadb:10.7.3
container_name: mariadb
restart: always
environment:
- MARIADB_ROOT_PASSWORD=rootIPD.xx2.19
- MARIADB_DATABASE=flyapps
- MARIADB_USER=flyuser
- MARIADB_PASSWORD=KGzKjZpWBp4R4RSa
#- ALLOW_EMPTY_PASSWORD=yes
- MARIADB_ENABLE_SLOW_QUERY=1
- MARIADB_LONG_QUERY_TIME=3
- MARIADB_SKIP_TEST_DB=yes
- MARIADB_EXTRA_FLAGS=--max-connect-errors=3000 --max_connections=30000
ports:
- 3306:3306
networks:
flyapps:
ipv4_address: 172.31.31.90
volumes:
- ../init/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- ../../data/mariadb:/bitnami/mariadb/data
- ../../data/logs/mariadb:/data/logs/mariadb
- ./server.cnf:/opt/bitnami/mariadb/conf/my_custom.cnf:ro
# adminer:
# image: adminer
# ports:
# - 8080:8080
# networks:
# - fungame

@ -0,0 +1,108 @@
#
# These groups are read by MariaDB server.
# Use it for options that only the server (but not clients) should see
#
# See the examples of server my.cnf files in /usr/share/mysql/
#
# this is only for the mysqld standalone daemon
[mysqld]
binlog_cache_size = 192K
thread_stack = 384K
join_buffer_size = 4096K
query_cache_type = 1
max_heap_table_size = 1024M
default_storage_engine = InnoDB
performance_schema_max_table_instances = 400
table_definition_cache = 400
skip-external-locking
key_buffer_size = 512M
max_allowed_packet = 1G
table_open_cache = 1024
sort_buffer_size = 2048K
net_buffer_length = 4K
read_buffer_size = 2048K
read_rnd_buffer_size = 1024K
myisam_sort_buffer_size = 16M
thread_cache_size = 192
query_cache_size = 256M
tmp_table_size = 1024M
sql-mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
max_connections = 30000
max_connect_errors = 1000
open_files_limit = 65535
expire_logs_days = 10
#log_queries_not_using_indexes=on
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_general_ci
init_connect='SET NAMES utf8mb4'
#character-set-server=utf8
skip_name_resolve
event_scheduler=1
#skip-grant-tables
#innodb_data_home_dir = /var/lib/mysql/
#innodb_data_file_path = ibdata1:10M:autoextend
#innodb_log_group_home_dir = /var/lib/mysql/
#innodb_buffer_pool_size = 1024M
#innodb_log_file_size = 128M
#innodb_log_buffer_size = 32M
#innodb_flush_log_at_trx_commit = 1
#innodb_lock_wait_timeout = 50
#innodb_max_dirty_pages_pct = 90
#innodb_read_io_threads = 4
#innodb_write_io_threads = 4
#
# Allow server to accept connections on all interfaces.
#
#bind-address=0.0.0.0
#
# this is only for embedded server
# This group is only read by MariaDB servers, not by MySQL.
# If you use the same .cnf file for MySQL and MariaDB,
# you can put MariaDB-only options here
[mariadb]
#autoset_open_files_limit
#enable_slow_query_log
#audit
plugin_load_add=server_audit
server_audit_logging=on
server_audit_events=connect,query
server_audit=force_plus_permanent
server_audit_events=QUERY_DDL,QUERY_DML,CONNECT
server_audit_output_type=file
server_audit_file_rotate_now=on
server_audit_file_rotations=9
server_audit_file_rotate_size=1G
server_audit_file_path=/data/logs/mariadb
log_error=/data/logs/mariadb/mariadb.err.log
log_output=FILE
slow_query_log
long_query_time=3
slow_query_log_file=/data/logs/mariadb/mariadb-slow.log
log_queries_not_using_indexes=ON #Logging Queries That Don't Use Indexes
#server_id=2
log-bin=/data/logs/mariadb/mysql-bin
# This group is only read by MariaDB-10.5 servers.
# If you use the same .cnf file for MariaDB of different versions,
# use this group for options that older servers don't understand

@ -0,0 +1,25 @@
version: '3.2'
networks:
flyapps:
external: true
name: flyapps
services:
redis:
image: 'bitnami/redis:6.2.7'
container_name: redis
restart: always
volumes:
- ../init/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- ../../data/redis:/bitnami/redis/data
environment:
#- REDIS_REPLICATION_MODE=master
#- ALLOW_EMPTY_PASSWORD=yes
- REDIS_PASSWORD=nineven
networks:
- flyapps
#ports:
# - 6379:6379

@ -0,0 +1,6 @@
#!/bin/bash
#
#
cd ../build/
docker compose up buildclient buildadmin

@ -0,0 +1,9 @@
#!/bin/bash
#
#
cd ../mariadb/ && docker compose up -d
cd ../redis/ && docker compose up -d
cd ../flyapps/ && docker compose up -d
docker logs -f flyapps

@ -0,0 +1,10 @@
#!/bin/bash
#
#
cd ../flyapps/ && docker compose down
cd ../redis/ && docker compose down
cd ../mariadb/ && docker compose down

@ -2,5 +2,5 @@
ENV = 'production' ENV = 'production'
# base api # base api
VUE_APP_BASE_API = '/prod-api' VUE_APP_BASE_API = '/api/v3/fir/server'

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

@ -212,7 +212,7 @@ export default {
}, },
methods: { methods: {
go_download_url(app_info) { go_download_url(app_info) {
window.open(app_info.preview_url + '/' + app_info.short, '_blank', '') window.open(app_info.preview_url, '_blank', '')
}, },
deleteApp(app_id) { deleteApp(app_id) {
this.$confirm('此操作将永久删除该应用, 是否继续?', '提示', { this.$confirm('此操作将永久删除该应用, 是否继续?', '提示', {

@ -66,7 +66,7 @@ export default {
} }
} }
const validatePassword = (rule, value, callback) => { const validatePassword = (rule, value, callback) => {
if (value.length < 6) { if (value.length < 3) {
callback(new Error('The password can not be less than 6 digits')) callback(new Error('The password can not be less than 6 digits'))
} else { } else {
callback() callback()

@ -28,7 +28,9 @@
<el-row :gutter="12"> <el-row :gutter="12">
<el-col :span="16"> <el-col :span="16">
<el-select v-model="postForm.gender" class="filter-item" placeholder="Please select"> <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-select>
</el-col> </el-col>
</el-row> </el-row>
@ -82,7 +84,9 @@
<el-form-item label="备注"> <el-form-item label="备注">
<el-row :gutter="12"> <el-row :gutter="12">
<el-col :span="16"> <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-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
@ -91,7 +95,9 @@
<el-form-item label="用户头像"> <el-form-item label="用户头像">
<el-row :gutter="12"> <el-row :gutter="12">
<el-col :span="16"> <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-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
@ -177,10 +183,16 @@
<el-form-item label="实名认证"> <el-form-item label="实名认证">
<el-row :gutter="12"> <el-row :gutter="12">
<el-col :span="16"> <el-col :span="16">
<el-select v-model="postForm.certification" class="filter-item" placeholder="Please select" :disabled="postForm.certification === -1"> <el-select v-model="postForm.certification" class="filter-item" placeholder="Please select"
<el-option v-for="item in postForm.certification_status_choices" :key="item.id" :label="item.name" :value="item.id" /> :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> </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 type="primary">
审核认证信息 审核认证信息
</el-button> </el-button>
@ -234,6 +246,9 @@
<el-col :span="8"> <el-col :span="8">
<el-button @click="add_amount_download_times(postForm.id)">下载次数充值</el-button> <el-button @click="add_amount_download_times(postForm.id)">下载次数充值</el-button>
</el-col> </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> <el-button v-if="!is_edit" type="primary" @click="is_edit=true">修改</el-button>
<div v-else> <div v-else>
<el-button type="primary" @click="is_edit=false">取消</el-button> <el-button type="primary" @click="is_edit=false">取消</el-button>
@ -245,7 +260,7 @@
<script> <script>
import { validURL } from '@/utils/validate' import { validURL } from '@/utils/validate'
import { getUserInfos, updateUserInfo } from '@/api/user' import { getUserInfos, resetPassword, updateUserInfo } from '@/api/user'
import { createOrderInfo } from '@/api/order' import { createOrderInfo } from '@/api/order'
import { changeStorageInfo, getStorageList } from '@/api/storage' import { changeStorageInfo, getStorageList } from '@/api/storage'
@ -341,8 +356,7 @@ export default {
storage_selection: [] storage_selection: []
} }
}, },
computed: { computed: {},
},
created() { created() {
if (this.isEdit) { if (this.isEdit) {
const id = this.$route.params && this.$route.params.id const id = this.$route.params && this.$route.params.id
@ -369,9 +383,33 @@ export default {
message: '取消充值' 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) { 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('存储数据迁移并设置刷新成功') this.$message.success('存储数据迁移并设置刷新成功')
}).catch(err => { }).catch(err => {
console.log(err) console.log(err)

@ -36,7 +36,6 @@
"eslint": "^5.16.0", "eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0", "eslint-plugin-vue": "^5.0.0",
"html-webpack-plugin": "^5.3.1", "html-webpack-plugin": "^5.3.1",
"image-webpack-loader": "^7.0.1",
"script-ext-html-webpack-plugin": "^2.1.5", "script-ext-html-webpack-plugin": "^2.1.5",
"uglifyjs-webpack-plugin": "^2.2.0", "uglifyjs-webpack-plugin": "^2.2.0",
"vue-template-compiler": "^2.6.10", "vue-template-compiler": "^2.6.10",

@ -2,9 +2,9 @@
<html lang="zh"> <html lang="zh">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta content="IE=edge" http-equiv="X-UA-Compatible">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta content="width=device-width,initial-scale=1.0" name="viewport">
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link href="<%= BASE_URL %>favicon.ico" rel="icon">
<script src="<%= BASE_URL %>gt.js"></script> <script src="<%= BASE_URL %>gt.js"></script>
<title>FLY分发平台</title> <title>FLY分发平台</title>
</head> </head>

@ -2,9 +2,9 @@
<html lang="zh"> <html lang="zh">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta content="IE=edge" http-equiv="X-UA-Compatible">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta content="width=device-width,initial-scale=1.0" name="viewport">
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link href="<%= BASE_URL %>favicon.ico" rel="icon">
<script src="<%= BASE_URL %>gt.js"></script> <script src="<%= BASE_URL %>gt.js"></script>
<title>应用下载</title> <title>应用下载</title>
</head> </head>

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

@ -28,22 +28,12 @@
</template> </template>
<script> <script>
const footer = process.env.base_env.footer
export default { export default {
name: "FirFooter", name: "FirFooter",
data() { data() {
return { return {
footer: { footer
copyright: 'Copyright © 2020-2022 东城飞阳 版权所有.',
ipcBeiAn: {
url: 'https://beian.miit.gov.cn',
text: '粤ICP备2021097895号',
},
gongAnBeiAn: {
url: 'http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=41142202000062',
text: '豫公网安备41142202000062号',
},
}
} }
} }
} }

@ -125,7 +125,7 @@
</template> </template>
<script> <script>
import {loginFun, set_auth_token, wxLoginFun} from "@/restful"; import {loginFun, wxLoginFun} from "@/restful";
import {geetest} from "@/utils"; import {geetest} from "@/utils";
import {checkEmail, checkphone, getRandomStr} from "@/utils/base/utils"; import {checkEmail, checkphone, getRandomStr} from "@/utils/base/utils";
@ -177,7 +177,6 @@ export default {
this.$cookies.set("username", data.userinfo.username, 3600 * 24 * 30); this.$cookies.set("username", data.userinfo.username, 3600 * 24 * 30);
this.$cookies.set("first_name", data.userinfo.first_name, 3600 * 24 * 30); this.$cookies.set("first_name", data.userinfo.first_name, 3600 * 24 * 30);
this.$store.dispatch("doUserinfo", data.userinfo); this.$store.dispatch("doUserinfo", data.userinfo);
set_auth_token();
this.$router.push({name: 'FirApps'}) this.$router.push({name: 'FirApps'})
}, },
loop_get_wx_info(wx_login_ticket, c_count = 1, unique_key = getRandomStr()) { loop_get_wx_info(wx_login_ticket, c_count = 1, unique_key = getRandomStr()) {
@ -291,7 +290,7 @@ export default {
}); });
return return
} }
if (password.length > 6) { if (password.length >= 6) {
let params = { let params = {
"username": email, "username": email,
"password": password, "password": password,

@ -13,6 +13,7 @@
class="bundleid short" class="bundleid short"
>&nbsp;{{ appinfos.preview_url }}</span> >&nbsp;{{ appinfos.preview_url }}</span>
</el-tooltip> </el-tooltip>
<el-tooltip content="点击查看下载码">
<el-popover <el-popover
placement="right" placement="right"
@ -27,14 +28,17 @@
</vue-qr> </vue-qr>
<el-button size="small" type="primary" @click="save_qr()">保存本地</el-button> <el-button size="small" type="primary" @click="save_qr()">保存本地</el-button>
</div> </div>
<span slot="reference" class="short ng-scope">下载码</span> <span slot="reference" class="short ng-scope">下载码</span>
</el-popover> </el-popover>
</el-tooltip>
<span>{{ master_release.release_type |getapptype }}</span> <span>{{ master_release.release_type |getapptype }}</span>
<el-tooltip content="下载量" placement="top"> <el-tooltip placement="top">
<span><i class="el-icon-cloudy"/><b class="short">{{ appinfos.count_hits }}</b></span> <div slot="content">总下载次数{{ appinfos.count_hits }}<br/>当天下载次数{{ appinfos.today_hits_count }}</div>
<span><i class="el-icon-cloudy"/><b class="short">{{
appinfos.count_hits
}} &nbsp;|&nbsp; {{ appinfos.today_hits_count }} </b></span>
</el-tooltip> </el-tooltip>
<span class="bundleid ng-binding">BundleID<b class="ng-binding"> <span class="bundleid ng-binding">BundleID<b class="ng-binding">
<el-tooltip content="复制到剪切板" placement="top"> <el-tooltip content="复制到剪切板" placement="top">

@ -1,5 +1,6 @@
<template> <template>
<div style="margin-top: 20px;width: 66%;margin-left: 8%"> <div style="margin-top: 20px;width: 66%;margin-left: 8%">
<el-dialog <el-dialog
:close-on-click-modal="false" :close-on-click-modal="false"
:close-on-press-escape="false" :close-on-press-escape="false"
@ -24,6 +25,25 @@
title="下载授权码生成" title="下载授权码生成"
width="500px"> width="500px">
<el-dialog
:visible.sync="addUdidVisible"
append-to-body
center
title="添加设备udid授权"
width="500px">
<el-tag style="margin: 10px 0">可以输入多个udid,以空格或者回车分割</el-tag>
<el-input
v-model="makeTokenInfo.udids"
:rows="17"
placeholder="可以输入多个udid,以空格或者回车分割"
type="textarea">
</el-input>
<span slot="footer">
<el-button @click="addUdidVisible=false,makeTokenInfo.udids=''">取消</el-button>
<el-button @click="addUididFunc">确定</el-button>
</span>
</el-dialog>
<el-form ref="form" :model="makeTokenInfo" label-width="110px"> <el-form ref="form" :model="makeTokenInfo" label-width="110px">
<el-form-item label="指定授权码" style="width: 400px"> <el-form-item label="指定授权码" style="width: 400px">
<el-tag>当存在指定授权码授权码长度和生成数量被禁用</el-tag> <el-tag>当存在指定授权码授权码长度和生成数量被禁用</el-tag>
@ -63,6 +83,12 @@
</el-tooltip> </el-tooltip>
<el-tag style="margin-left: 5px">绑定设备之后二次自动进行设备授权下载应用</el-tag> <el-tag style="margin-left: 5px">绑定设备之后二次自动进行设备授权下载应用</el-tag>
</el-form-item> </el-form-item>
<el-form-item v-if="currentapp.type===1 && currentapp.issupersign && makeTokenInfo.bind_status===true"
label="添加授权设备">
<el-button size="mini" @click="addudid">
添加
</el-button>
</el-form-item>
</el-form> </el-form>
<span slot="footer"> <span slot="footer">
<el-button @click="cancelDownloadToken">取消</el-button> <el-button @click="cancelDownloadToken">取消</el-button>
@ -154,7 +180,7 @@
align="center" align="center"
label="最大可用次数" label="最大可用次数"
prop="max_limit_count" prop="max_limit_count"
width="130"> width="110">
<template slot-scope="scope"> <template slot-scope="scope">
<span v-if="scope.row.max_limit_count === 0">不限次数</span> <span v-if="scope.row.max_limit_count === 0">不限次数</span>
<span v-else>{{ scope.row.max_limit_count }}</span> <span v-else>{{ scope.row.max_limit_count }}</span>
@ -175,16 +201,24 @@
align="center" align="center"
label="设备ID" label="设备ID"
prop="bind_udid"> prop="bind_udid">
<template slot-scope="scope">
<el-tooltip content="点击复制到剪贴板">
<el-link v-clipboard:copy="scope.row.bind_udid"
v-clipboard:success="copy_success"
:underline="false">{{ scope.row.bind_udid }}
</el-link>
</el-tooltip>
</template>
</el-table-column> </el-table-column>
</div> </div>
<el-table-column <el-table-column
align="center" align="center"
fixed="right" fixed="right"
label="操作" label="操作"
width="120"> width="90">
<template slot-scope="scope"> <template slot-scope="scope">
<div> <div>
<el-tooltip content="重置使用次数" placement="top"> <el-tooltip :content="get_reset_title()" placement="top">
<el-button <el-button
size="mini" size="mini"
@click="resetDownloadUsed(scope.row)">重置 @click="resetDownloadUsed(scope.row)">重置
@ -327,16 +361,42 @@ export default {
app_download_token_list: [], app_download_token_list: [],
loading: false, loading: false,
makeTokenVisible: false, makeTokenVisible: false,
addUdidVisible: false,
tokendisable: false, tokendisable: false,
pagination: {"currentPage": 1, "total": 0, "pagesize": 10}, pagination: {"currentPage": 1, "total": 0, "pagesize": 10},
dpwdsearch: '', dpwdsearch: '',
dpwdtitle: '输入下载授权码', dpwdtitle: '输入下载授权码',
makeTokenInfo: {token: '', token_length: 6, token_number: 20, token_max_used_number: 0, bind_status: false}, makeTokenInfo: {
multipleSelection: [] token: '',
token_length: 6,
token_number: 20,
token_max_used_number: 0,
bind_status: false,
udids: ''
},
multipleSelection: [],
} }
}, },
methods: { methods: {
addUididFunc() {
let udids = this.makeTokenInfo.udids
if (udids && udids.length > 10) {
this.makeDownloadToken()
} else {
this.$message.error('设备udid不存在,请检查')
}
},
get_reset_title() {
let title = '重置使用次数'
if (this.currentapp.type === 1 && this.currentapp.issupersign) {
title = '重置使用次数和授权设备ID'
}
return title
},
addudid() {
this.makeTokenInfo.udids = ''
this.addUdidVisible = true
},
format_copy_text(token) { format_copy_text(token) {
return this.currentapp.preview_url + "?password=" + token return this.currentapp.preview_url + "?password=" + token
}, },
@ -369,8 +429,16 @@ export default {
this.multipleSelection = val; this.multipleSelection = val;
}, },
cancelDownloadToken() { cancelDownloadToken() {
this.makeTokenInfo = {token: '', token_length: 6, token_number: 20, token_max_used_number: 0, bind_status: false} this.makeTokenInfo = {
token: '',
token_length: 6,
token_number: 20,
token_max_used_number: 0,
bind_status: false,
udids: ''
}
this.makeTokenVisible = false this.makeTokenVisible = false
this.addUdidVisible = false
}, },
resetDownloadUsed(info) { resetDownloadUsed(info) {
appDownloadToken(data => { appDownloadToken(data => {

@ -491,7 +491,10 @@
<div class=" app-animator"> <div class=" app-animator">
<div class="card app card-ios"> <div class="card app card-ios">
<i class="down-icon el-icon-cloudy"> {{ r.count_hits }}</i> <el-tooltip>
<div slot="content">总下载次数{{ r.count_hits }}<br/>当天下载次数{{ r.today_hits_count }}</div>
<i class="down-icon el-icon-cloudy"> {{ r.count_hits }} &nbsp;|&nbsp; {{ r.today_hits_count }}</i>
</el-tooltip>
<i v-if="r.type === 1" class="type-icon iconfont icon-ios"/> <i v-if="r.type === 1" class="type-icon iconfont icon-ios"/>
<i v-if="r.type === 0" class="type-icon iconfont icon-android2"/> <i v-if="r.type === 0" class="type-icon iconfont icon-android2"/>

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

@ -15,9 +15,13 @@
<el-divider direction="vertical"/> <el-divider direction="vertical"/>
<div class="item"><p style="margin-bottom: 16px">应用举报</p> <div class="item"><p style="margin-bottom: 16px">应用举报</p>
</div> </div>
<p><b>flyapps@126.com</b></p> <div>
<el-tooltip content="拿出手机扫描添加微信" style="margin-top: 33px"> <el-link :href="github" :underline="false"><p><b>FlyApps</b></p></el-link>
<el-image :lazy="true" :src="require('@/assets/wxchat.jpg')" alt="扫描添加微信" style="width: 160px"> </div>
<el-divider/>
<el-tooltip content="拿出手机扫一扫" style="margin-top: 33px">
<el-image :alt="github" :lazy="true" :src="require('@/assets/github.png')"
style="width: 160px">
</el-image> </el-image>
</el-tooltip> </el-tooltip>
@ -30,7 +34,8 @@ export default {
name: "FirContact", name: "FirContact",
data() { data() {
return { return {
bg_img: require("@/assets/imgs/banner_1.jpg") bg_img: require("@/assets/imgs/banner_1.jpg"),
github: process.env.base_env.github
} }
}, },
} }
@ -44,7 +49,7 @@ export default {
.content { .content {
width: 942px; width: 942px;
margin: 80px auto 160px; margin: 10px auto 160px;
text-align: center; text-align: center;
} }

@ -114,12 +114,12 @@ export default {
{ {
logo: require("@/assets/imgs/qq.png"), logo: require("@/assets/imgs/qq.png"),
title: "核心优势3", title: "核心优势3",
content: "<p>利用QQ微信等提供在线解答</p>帮助用户快速准确解决问题和故障" content: "<p>QQ微信等提供在线解答</p>帮助用户快速准确解决问题"
}, },
{ {
logo: require("@/assets/imgs/skill.png"), logo: require("@/assets/imgs/skill.png"),
title: "核心优势4", title: "核心优势4",
content: "<p>由技术支持工程师,负责问题解答</p>需求受理及故障受理" content: "<p>自定义配置,容器化部署</p>升级维护超级便捷"
} }
] ]
} }

@ -21,10 +21,10 @@
<el-link @click="$router.push({name:'FirApps'})">登录</el-link> <el-link @click="$router.push({name:'FirApps'})">登录</el-link>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-link @click="$router.push({name:'FirRegist'})">注册</el-link> <el-link @click="$router.push({name:'FirContact'})">联系我们</el-link>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-link @click="$router.push({name:'FirContact'})">联系我们</el-link> <el-link @click="goGithub">Github</el-link>
</el-col> </el-col>
</el-row> </el-row>
</el-col> </el-col>
@ -64,6 +64,11 @@ export default {
'line-height': '45px', 'line-height': '45px',
background: '#e7eaf1' background: '#e7eaf1'
}, },
github: process.env.base_env.github
}
}, methods: {
goGithub() {
window.open(this.github, '_blank', '');
} }
} }
} }

@ -6,8 +6,8 @@
<p style="color:#b2b2b2">Company News</p> <p style="color:#b2b2b2">Company News</p>
</div> </div>
<div class="nav text-center"> <div class="nav text-center">
<a class="active" href="#">公司新闻</a> <a class="active" href="#">开发历程</a>
<a href="#">行业动态</a> <a href="#">发展足迹</a>
</div> </div>
<ul class="news-container"> <ul class="news-container">
<li v-for="(item,index) in newsList" :key="index"> <li v-for="(item,index) in newsList" :key="index">
@ -40,6 +40,18 @@ export default {
show: false, show: false,
newsList: [ newsList: [
{ {
id: '008',
title: 'ios16系统',
introduce: '超级签名字支持ios16系统签名,只需要手机开启调试模式,即可安装打开',
date: '09-22',
year: '2022'
}, {
id: '007',
title: '私有下载页部署',
introduce: '支持私有下载页部署,方便快捷,稳定访问',
date: '05-18',
year: '2022'
}, {
id: '001', id: '001',
title: '现有版本', title: '现有版本',
introduce: '经过好长时间的编写,平台已经支持超级签名,权限管理,自定义域名,自定义存储', introduce: '经过好长时间的编写,平台已经支持超级签名,权限管理,自定义域名,自定义存储',

@ -521,9 +521,10 @@
{{ developer_used_info.may_sign_number - developer_used_info.can_sign_number }} {{ developer_used_info.may_sign_number - developer_used_info.can_sign_number }}
</el-link> </el-link>
<el-link :underline="false">当前可签名设备数 {{ 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> </div>
<el-link slot="reference" :underline="false"> <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-link>
</el-popover> </el-popover>
<el-progress <el-progress
@ -589,7 +590,10 @@
:underline="false">开发者账户ID: {{ scope.row.issuer_id }} :underline="false">开发者账户ID: {{ scope.row.issuer_id }}
</el-link> </el-link>
</el-tooltip> </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>开发者账户可用设备数: {{ 100 - scope.row.developer_used_number }}</p>
<p>由于您设置可用设备数: {{ scope.row.usable_number }} ,所以现在可用设备数: {{ <p>由于您设置可用设备数: {{ scope.row.usable_number }} ,所以现在可用设备数: {{
scope.row.usable_number - scope.row.developer_used_number > 0 scope.row.usable_number - scope.row.developer_used_number > 0
@ -873,17 +877,22 @@
v-model="udidsearch" v-model="udidsearch"
clearable clearable
placeholder="输入UDID" placeholder="输入UDID"
style="width: 30%;margin-right: 30px;margin-bottom: 10px"/> style="width: 30%;margin-right: 5px;margin-bottom: 10px"/>
<el-input <el-input
v-model="appidseach" v-model="appidseach"
clearable clearable
placeholder="输入开发者用户ID" placeholder="输入开发者用户ID"
style="width: 30%;margin-right: 30px;margin-bottom: 10px"/> style="width: 30%;margin-right: 5px;margin-bottom: 10px"/>
<el-select v-if="status_choices" v-model="devicestatus" clearable multiple <el-select v-model="devicestatus" clearable multiple
placeholder="设备状态" 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-option v-for="item in device_status_choices" :key="item.id" :label="item.name" :value="item.id"/>
</el-select> </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 icon="el-icon-search" type="primary" @click="handleCurrentChange(1)">
搜索 搜索
</el-button> </el-button>
@ -936,7 +945,7 @@
align="center" align="center"
label="设备版本" label="设备版本"
prop="version" prop="version"
width="200"> width="180">
</el-table-column> </el-table-column>
<el-table-column <el-table-column
align="center" align="center"
@ -958,6 +967,12 @@
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column
align="center"
label="设备类型"
prop="device_class"
width="80">
</el-table-column>
<el-table-column <el-table-column
:formatter="deviceformatter" :formatter="deviceformatter"
align="center" align="center"
@ -1808,7 +1823,7 @@ import {
personalConfigInfo, personalConfigInfo,
signoperatemessage signoperatemessage
} from "@/restful"; } from "@/restful";
import {format_choices, getUserInfoFun, removeAaary} from "@/utils"; import {format_choices, getUserInfoFun, removeArray} from "@/utils";
import AppleDeveloperBindApp from "@/components/base/AppleDeveloperBindApp"; import AppleDeveloperBindApp from "@/components/base/AppleDeveloperBindApp";
import {format_time} from "@/utils/base/utils"; import {format_time} from "@/utils/base/utils";
@ -1843,6 +1858,7 @@ export default {
Bundleidsearch: "", Bundleidsearch: "",
appidseach: "", appidseach: "",
devicestatus: "", devicestatus: "",
deviceclass: "",
operatestatus: "", operatestatus: "",
operate_status_choices: [], operate_status_choices: [],
uidsearch: "", uidsearch: "",
@ -1928,6 +1944,7 @@ export default {
appletoapp_title: '', appletoapp_title: '',
status_choices: [], status_choices: [],
device_status_choices: [], device_status_choices: [],
device_class_choices: [],
read_only_mode: 'off', read_only_mode: 'off',
developer_status_choice: [], developer_status_choice: [],
multipleSelection: [], multipleSelection: [],
@ -2353,6 +2370,7 @@ export default {
"issuer_id": this.appidseach, "issuer_id": this.appidseach,
"act": "syncalldevice", "act": "syncalldevice",
"devicestatus": this.devicestatus, "devicestatus": this.devicestatus,
"deviceclass": this.deviceclass,
"udidsearch": this.udidsearch "udidsearch": this.udidsearch
} }
}); });
@ -2524,6 +2542,7 @@ export default {
this.iosdevicerankFun({"methods": "GET", "data": data}) this.iosdevicerankFun({"methods": "GET", "data": data})
} else if (tabname === "iosudevices") { } else if (tabname === "iosudevices") {
data.devicestatus = JSON.stringify(this.devicestatus); data.devicestatus = JSON.stringify(this.devicestatus);
data.deviceclass = JSON.stringify(this.deviceclass);
this.iosudevicesFun("GET", data) this.iosudevicesFun("GET", data)
} else if (tabname === 'operatemsg') { } else if (tabname === 'operatemsg') {
data.operate_status = this.operatestatus; data.operate_status = this.operatestatus;
@ -2730,7 +2749,7 @@ export default {
this.pagination.total = data.count; this.pagination.total = data.count;
} else if (action === "DELETE") { } else if (action === "DELETE") {
if (scope) { if (scope) {
this.app_udid_lists = removeAaary(this.app_udid_lists, scope.row) this.app_udid_lists = removeArray(this.app_udid_lists, scope.row)
} }
} else if (action === 'POST') { } else if (action === 'POST') {
this.$message.success("操作成功"); this.$message.success("操作成功");
@ -2795,6 +2814,7 @@ export default {
this.developer_udevices_lists = data.data; this.developer_udevices_lists = data.data;
this.pagination.total = data.count; this.pagination.total = data.count;
this.device_status_choices = data.status_choices; this.device_status_choices = data.status_choices;
this.device_class_choices = data.device_class_choices;
} else { } else {
this.refreshactiveFun() this.refreshactiveFun()
} }

@ -1,11 +1,18 @@
import Axios from 'axios' import Axios from 'axios'
import {geetestbase} from "@/utils/base/utils"; import {geetestbase} from "@/utils/base/utils";
Axios.defaults.withCredentials = true;
const DOMAIN = process.env.base_env.baseShortUrl; const DOMAIN = process.env.base_env.baseShortUrl;
const APIPATH = '/api/v1/fir/server';
let USERSEVER = DOMAIN + APIPATH; const USERSEVER = 'api/v1/fir/server';
// eslint-disable-next-line no-console
console.log("js build version:" + process.env.base_env.version);
// create an axios instance
const service = Axios.create({
baseURL: DOMAIN, // url = base url + request url
withCredentials: true, // send cookies when cross-domain requests
timeout: 120000 // request timeout
})
function ErrorMsg(error, callBack) { function ErrorMsg(error, callBack) {
if (error && error.response) { if (error && error.response) {
@ -42,7 +49,7 @@ function responseMiddleware(data, callBack) {
function getData(methods = 'GET', url, params = {}, callBack) { function getData(methods = 'GET', url, params = {}, callBack) {
if (methods === "PUT") { if (methods === "PUT") {
Axios service
.put(url, params) .put(url, params)
.then(function (response) { .then(function (response) {
responseMiddleware(response.data, callBack); responseMiddleware(response.data, callBack);
@ -51,7 +58,7 @@ function getData(methods = 'GET', url, params = {}, callBack) {
ErrorMsg(error, callBack); ErrorMsg(error, callBack);
}); });
} else if (methods === 'POST') { } else if (methods === 'POST') {
Axios service
.post(url, params) .post(url, params)
.then(function (response) { .then(function (response) {
responseMiddleware(response.data, callBack); responseMiddleware(response.data, callBack);
@ -60,7 +67,7 @@ function getData(methods = 'GET', url, params = {}, callBack) {
ErrorMsg(error, callBack); ErrorMsg(error, callBack);
}); });
} else } else
Axios service
.get(url, {params: params}) .get(url, {params: params})
.then(function (response) { .then(function (response) {
responseMiddleware(response.data, callBack); responseMiddleware(response.data, callBack);

@ -4,8 +4,8 @@ import router from "@/router";
import {geetestbase} from "@/utils/base/utils"; import {geetestbase} from "@/utils/base/utils";
const Base64 = require('js-base64').Base64; const Base64 = require('js-base64').Base64;
Axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; // Axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
Axios.defaults.withCredentials = true; // Axios.defaults.withCredentials = true;
// Axios.defaults.httpsAgent = new https.Agent({ // Axios.defaults.httpsAgent = new https.Agent({
// keepAlive: true // keepAlive: true
// }); // });
@ -16,11 +16,17 @@ export function requestAbort() {
} }
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log("flyapps js version:" + process.env.base_env.version); console.log("js build version:" + process.env.base_env.version);
const DOMAIN = process.env.base_env.baseUrl; const DOMAIN = process.env.base_env.baseUrl;
const APIPATH = '/api/v1/fir/server'; const USERSEVER = 'api/v1/fir/server';
let USERSEVER = DOMAIN + APIPATH;
// create an axios instance
const service = Axios.create({
baseURL: DOMAIN, // url = base url + request url
withCredentials: true, // send cookies when cross-domain requests
timeout: 120000 // request timeout
})
export function convertRes2Blob(response) { export function convertRes2Blob(response) {
// 提取文件名 // 提取文件名
@ -53,31 +59,25 @@ export function convertRes2Blob(response) {
} }
} }
export function set_auth_token() { service.interceptors.request.use(config => {
Axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
if (VueCookies.get('token')) { if (VueCookies.get('token')) {
// Axios.defaults.headers.common['Authorization'] = localStorage.getItem('access_token'); // Axios.defaults.headers.common['Authorization'] = localStorage.getItem('access_token');
// console.log(config.headers); // console.log(config.headers);
if (VueCookies.get('auth_token')) { if (!VueCookies.get('auth_token')) {
config.headers.Authorization = VueCookies.get('auth_token')
} else {
let token = VueCookies.get('token'); let token = VueCookies.get('token');
let username = VueCookies.get('username'); let username = VueCookies.get('username');
VueCookies.set("auth_token", Base64.encode(token + ':' + username), 3600 * 24 * 30); VueCookies.set("auth_token", Base64.encode(token + ':' + username), 3600 * 24 * 30);
} }
config.headers.Authorization = VueCookies.get('auth_token')
} }
// 更改加载的样式
return config; return config;
}, function (error) { }, function (error) {
// 对请求错误做些什么 // 对请求错误做些什么
// eslint-disable-next-line no-console
console.log(error)
return Promise.reject(error); return Promise.reject(error);
}); });
}
set_auth_token();
function ErrorMsg(error, callBack) { function ErrorMsg(error, callBack) {
if (error && error.response) { if (error && error.response) {
@ -141,7 +141,7 @@ function responseMiddleware(data, callBack) {
function getData(methods, url, params = {}, callBack) { function getData(methods, url, params = {}, callBack) {
if (methods === "DELETE") { if (methods === "DELETE") {
Axios service
.delete(url, {params: params, signal: controller.signal}) .delete(url, {params: params, signal: controller.signal})
.then(function (response) { .then(function (response) {
responseMiddleware(response.data, callBack); responseMiddleware(response.data, callBack);
@ -151,7 +151,7 @@ function getData(methods, url, params = {}, callBack) {
}); });
} else if (methods === "PUT") { } else if (methods === "PUT") {
Axios service
.put(url, params, {signal: controller.signal}) .put(url, params, {signal: controller.signal})
.then(function (response) { .then(function (response) {
responseMiddleware(response.data, callBack); responseMiddleware(response.data, callBack);
@ -161,7 +161,7 @@ function getData(methods, url, params = {}, callBack) {
}); });
} else if (methods === 'POST') { } else if (methods === 'POST') {
Axios service
.post(url, params, {signal: controller.signal}) .post(url, params, {signal: controller.signal})
.then(function (response) { .then(function (response) {
responseMiddleware(response.data, callBack); responseMiddleware(response.data, callBack);
@ -170,7 +170,7 @@ function getData(methods, url, params = {}, callBack) {
ErrorMsg(error, callBack); ErrorMsg(error, callBack);
}); });
} else if (methods === 'FILE') { } else if (methods === 'FILE') {
Axios service
.get(url, {params: params, responseType: 'blob', signal: controller.signal}) .get(url, {params: params, responseType: 'blob', signal: controller.signal})
.then(function (response) { .then(function (response) {
convertRes2Blob(response) convertRes2Blob(response)
@ -179,7 +179,7 @@ function getData(methods, url, params = {}, callBack) {
ErrorMsg(error, callBack); ErrorMsg(error, callBack);
}); });
} else { } else {
Axios service
.get(url, {params: params, signal: controller.signal}) .get(url, {params: params, signal: controller.signal})
.then(function (response) { .then(function (response) {
responseMiddleware(response.data, callBack); responseMiddleware(response.data, callBack);
@ -447,10 +447,10 @@ export function getapppicurl(app_id) {
} }
export function getuploadurl(domain_name = null) { export function getuploadurl(domain_name = null) {
if (domain_name) { if (domain_name && !domain_name.match('http[s?]:///$')) {
return domain_name + APIPATH + '/upload' return domain_name + '/' +USERSEVER + '/upload'
} else { } else {
return USERSEVER + '/upload'; return DOMAIN + USERSEVER + '/upload';
} }
} }
@ -722,7 +722,7 @@ export function geetest(self, uid, params, callback) {
/** 超级签名************************************************相关api */ /** 超级签名************************************************相关api */
let SIGNSEVER = DOMAIN + '/api/v1/fir/xsign'; let SIGNSEVER = '/api/v1/fir/xsign';
/** 超级签名--检测该账户是否可开启超级签名 */ /** 超级签名--检测该账户是否可开启超级签名 */
export function checkCanSign(callBack, params) { export function checkCanSign(callBack, params) {

@ -41,13 +41,13 @@ export function geetestbase(func, self, uid, params, callback, errback, readybac
challenge: data.challenge, challenge: data.challenge,
new_captcha: data.new_captcha, // 用于宕机时表示是新验证码的宕机 new_captcha: data.new_captcha, // 用于宕机时表示是新验证码的宕机
offline: !data.success, // 表示用户后台检测极验服务器是否宕机,一般不需要关注 offline: !data.success, // 表示用户后台检测极验服务器是否宕机,一般不需要关注
product: "float", // 产品形式,包括:float,popup product: "bind", // 产品形式,包括:float,popup
width: "100%" width: "100%"
}, (captchaObj) => { }, (captchaObj) => {
self.$refs.captcha.innerHTML = ''; self.$refs.captcha.innerHTML = '';
captchaObj.appendTo("#captcha"); // captchaObj.appendTo("#captcha");
captchaObj.onReady(() => { captchaObj.onReady(() => {
readyback() readyback(captchaObj)
}).onSuccess(() => { }).onSuccess(() => {
params.geetest = captchaObj.getValidate(); params.geetest = captchaObj.getValidate();
callback(params); callback(params);

@ -0,0 +1,22 @@
module.exports =function dateFormat(format, date=null) {
if(!date){
date = new Date()
}
const args = {
"M+": date.getMonth() + 1,
"d+": date.getDate(),
"h+": date.getHours(),
"m+": date.getMinutes(),
"s+": date.getSeconds(),
"q+": Math.floor((date.getMonth() + 3) / 3),
"S": date.getMilliseconds()
};
if (/(y+)/.test(format))
format = format.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
for (let i in args) {
let n = args[i];
if (new RegExp("(" + i + ")").test(format))
format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? n : ("00" + n).substr(("" + n).length));
}
return format;
}

@ -242,7 +242,7 @@ export function uploadlocalstorage(file, certinfo, app, successcallback, process
} }
export function removeAaary(_arr, _obj) { export function removeArray(_arr, _obj) {
let length = _arr.length; let length = _arr.length;
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
if (_arr[i] == _obj) { if (_arr[i] == _obj) {
@ -356,8 +356,8 @@ export function geetest(self, uid, params, callback, http_func = loginFun) {
message: res.msg, message: res.msg,
type: 'error' type: 'error'
}); });
// eslint-disable-next-line no-unused-vars }, captchaObj => {
}, _ => { captchaObj.verify()
loading.close() loading.close()
}) })
} }

@ -1,15 +1,14 @@
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV); const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV);
const path = require('path'); const path = require('path');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const dateFormat = require("./src/utils/format.js");
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const argv = process.argv;
function resolve(dir) { function resolve(dir) {
return path.join(__dirname, dir); return path.join(__dirname, dir);
} }
const argv = process.argv;
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const compress = new CompressionWebpackPlugin( const compress = new CompressionWebpackPlugin(
{ {
filename: info => { filename: info => {
@ -28,6 +27,7 @@ const compress = new CompressionWebpackPlugin(
); );
const index = { const index = {
outputDir: 'dist_index',
// page 的入口 // page 的入口
entry: 'src/main.js', entry: 'src/main.js',
// 模板来源 // 模板来源
@ -43,6 +43,7 @@ const index = {
}; };
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const mshort = { const mshort = {
outputDir: 'dist_mshort',
// page 的入口 // page 的入口
entry: 'src/main.short.js', entry: 'src/main.short.js',
// 模板来源 // 模板来源
@ -57,6 +58,7 @@ const mshort = {
chunks: ['chunk-vendors', 'chunk-common', 'mshort', 'chunk-elementUI', 'runtime'] chunks: ['chunk-vendors', 'chunk-common', 'mshort', 'chunk-elementUI', 'runtime']
}; };
const short = { const short = {
outputDir: 'dist_short',
// page 的入口 // page 的入口
entry: 'src/short.js', entry: 'src/short.js',
// 模板来源 // 模板来源
@ -71,41 +73,57 @@ const short = {
chunks: ['chunk-vendors', 'chunk-common', 'short', 'chunk-commons', 'runtime'] chunks: ['chunk-vendors', 'chunk-common', 'short', 'chunk-commons', 'runtime']
}; };
let pages = {index, short, mshort}; let pages = {index, short, mshort};
const page = argv[3]; let outputDir = 'dist'
if (page) { let page = argv[3];
if (!page) {
page = 'index'
}
for (const key of Object.keys(pages)) { for (const key of Object.keys(pages)) {
if (key === page) { if (key === page) {
const tmp = pages[key]; const tmp = pages[key];
pages = {}; pages = {};
pages[key] = tmp; pages[key] = tmp;
outputDir = tmp.outputDir
} }
} }
} const version = '2.3.9';
const github = 'https://github.com/nineaiyu/FlyApps';
const version='2.3.8';
const pro_base_env = { const pro_base_env = {
baseUrl: 'https://flyapps.cn', baseUrl: '/', //该选项可以填写web-api的域名,类似 https://api.xxx.com/
index_static: 'https://static.flyapps.cn/index/', index_static: '/', //若配置cdn等加速,可以填写cdn加速域名
baseShortUrl: 'https://flyapps.top', baseShortUrl: '/', //该选项可以填写short-api的域名,也可以和web-api域名一样,类似 https://api.xxx.com/
short_static: 'https://static.flyapps.top/short/', short_static: '/short/', //若配置cdn等加速,可以填写cdn加速域名
version: version, version: version,
}; };
const dev_base_env = { const dev_base_env = {
baseUrl: 'https://app.hehelucky.cn', baseUrl: 'https://app.hehelucky.cn/',
baseShortUrl: 'https://app.hehelucky.cn',
short_static: '/',
index_static: '/', index_static: '/',
baseShortUrl: 'https://app.hehelucky.cn/',
short_static: 'https://app.hehelucky.cn/short/',
version: version, version: version,
}; };
let base_evn = dev_base_env; let base_evn = dev_base_env;
const footer_info = {
copyright: 'Copyright © 2022-2099 isummer 版权所有.',
ipcBeiAn: {
url: 'https://beian.miit.gov.cn',
text: '京ICP备681262896号',
},
gongAnBeiAn: {
url: 'https://www.beian.gov.cn/portal/registerSystemInfo?recordcode=681262896',
text: '京公网安备681262896号',
},
}
if (IS_PROD) { if (IS_PROD) {
base_evn = pro_base_env base_evn = pro_base_env
} else { } else {
base_evn = dev_base_env base_evn = dev_base_env
} }
base_evn.footer_info = footer_info
function get_public_path(pages) { function get_public_path(pages) {
if (!IS_PROD) { if (!IS_PROD) {
@ -118,9 +136,9 @@ function get_public_path(pages) {
} }
let publicPath = get_public_path(pages); let publicPath = get_public_path(pages);
module.exports = { module.exports = {
pages: pages, pages: pages,
outputDir: outputDir,
productionSourceMap: false, //去除生产环境的productionSourceMap productionSourceMap: false, //去除生产环境的productionSourceMap
assetsDir: "static", //静态文件存储位置 assetsDir: "static", //静态文件存储位置
lintOnSave: true, lintOnSave: true,
@ -141,7 +159,13 @@ module.exports = {
config config
.plugin('define') .plugin('define')
.tap(args => { .tap(args => {
args[0]['process.env']['base_env'] = JSON.stringify({baseUrl: base_evn.baseUrl,baseShortUrl:base_evn.baseShortUrl,version:base_evn.version}); args[0]['process.env']['base_env'] = JSON.stringify({
baseUrl: base_evn.baseUrl,
baseShortUrl: base_evn.baseShortUrl,
version: dateFormat("yyyyMMdd.hhmmss") + '.' + base_evn.version,
footer: base_evn.footer_info,
github
});
return args return args
}); });
@ -230,7 +254,11 @@ module.exports = {
priority: 23, // the weight needs to be larger than libs and app or it will be packaged into libs or app priority: 23, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?ali-oss(.*)/ // in order to adapt to cnpm test: /[\\/]node_modules[\\/]_?ali-oss(.*)/ // in order to adapt to cnpm
}, },
appinfo: {
name: 'appinfo', // split elementUI into a single package
priority: 23, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?app-info-parser(.*)/ // in order to adapt to cnpm
},
qrcodejs2: { qrcodejs2: {
name: 'qrcodejs2', // split elementUI into a single package name: 'qrcodejs2', // split elementUI into a single package
priority: 23, // the weight needs to be larger than libs and app or it will be packaged into libs or app priority: 23, // the weight needs to be larger than libs and app or it will be packaged into libs or app
@ -243,7 +271,7 @@ module.exports = {
}, },
common: { common: {
name: 'chunk-commons', name: 'chunk-commons',
test: resolve(resolve('src/components')), // can customize your rules test: resolve('src/components'), // can customize your rules
minChunks: 3, // minimum common number minChunks: 3, // minimum common number
priority: 5, priority: 5,
reuseExistingChunk: true reuseExistingChunk: true

@ -1,41 +1,32 @@
FROM python:3.6.14-slim FROM python:3.9.14-slim
# Fixes some weird terminal issues such as broken clear / CTRL+L # Fixes some weird terminal issues such as broken clear / CTRL+L
ARG PIP_MIRROR=https://mirrors.aliyun.com/pypi/simple
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list \ #RUN sed -i 's/deb.debian.org/mirrors.163.com/g' /etc/apt/sources.list \
&& sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list \ # && sed -i 's/security.debian.org/mirrors.163.com/g' /etc/apt/sources.list \
&& apt update 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/*
RUN apt-get install g++ wget -y
RUN apt-get install libssl-dev openssl libmariadb-dev -y
RUN rm -rf /var/lib/apt/lists/*
#COPY fir_client/dist /data/flyapps/fir_client
#RUN cd /opt/ && wget https://github.com/nineaiyu/zsign/archive/refs/tags/v1.1.2.tar.gz #RUN cd /opt/ && wget https://github.com/nineaiyu/zsign/archive/refs/tags/v1.1.2.tar.gz
COPY zsign-1.1.2.tar.gz /opt/zsign-1.1.2.tar.gz ADD zsign-1.1.2.tar.gz /opt/
RUN cd /opt/ && tar xvf zsign-1.1.2.tar.gz && cd zsign-1.1.2/ && g++ *.cpp common/*.cpp -lcrypto -O3 -std=c++11 -o zsign && cp zsign /usr/bin/ RUN cd /opt/zsign-1.1.2/ && g++ *.cpp common/*.cpp -lcrypto -O3 -std=c++11 -o zsign && cp zsign /usr/bin/ && rm -rf /opt/zsign-1.1.2/
# install pip # install pip
COPY requirements.txt /opt/requirements.txt COPY requirements.txt /opt/requirements.txt
RUN cd /opt/ && pip install -U setuptools pip -i ${PIP_MIRROR} --ignore-installed && pip install --no-cache-dir -r requirements.txt -i ${PIP_MIRROR} && pip install --no-cache-dir uwsgi -i ${PIP_MIRROR} RUN cd /opt/ && pip install -U setuptools pip --ignore-installed && pip install --no-cache-dir -r requirements.txt && pip install --no-cache-dir uwsgi
RUN rm -rf /var/cache/yum/
#COPY Docker/flyapps.conf /etc/supervisor/conf.d/flyapps.conf
#COPY Docker/uwsgi.conf /data/flyapps/fir_ser/uwsgi.conf
#COPY Docker/flyapps-vhost.conf /etc/nginx/conf.d/flyapps-vhost.conf
#COPY Docker/app.hehelucky.cn.pem /data/flyapps/app.hehelucky.cn.pem
#COPY Docker/app.hehelucky.cn.key /data/flyapps/app.hehelucky.cn.key
WORKDIR /data/fir_ser/ WORKDIR /data/fir_ser/
COPY entrypoint.sh entrypoint.sh
RUN addgroup --system --gid 101 nginx \ RUN addgroup --system --gid 101 nginx \
&& adduser --system --disabled-login --ingroup nginx --no-create-home --home /nonexistent --gecos "nginx user" --shell /bin/false --uid 101 nginx && adduser --system --disabled-login --ingroup nginx --no-create-home --home /nonexistent --gecos "nginx user" --shell /bin/false --uid 101 nginx
#EXPOSE 443 #EXPOSE 443
#ENTRYPOINT ["./entrypoint.sh"] #ENTRYPOINT ["./entrypoint.sh"]
#ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf", "-n"] #ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf", "-n"]
CMD ["python", "manage.py", "start", "all","-u","nginx","-usm","1"] #CMD ["python", "manage.py", "start", "all","-u","nginx","-usm","1"]
ENTRYPOINT ["/bin/bash", "entrypoint.sh"]

@ -0,0 +1,11 @@
FROM python:3.9.14-slim
RUN pip install --upgrade pip --no-cache-dir && \
pip install setuptools-rust oss2 requests-toolbelt androguard requests --no-cache-dir
WORKDIR /opt/
COPY cli.py /opt/cli.py
CMD ["python", "cli.py"]

@ -136,11 +136,6 @@ class AdminAppsSerializer(AppsSerializer):
def get_release_count(self, obj): def get_release_count(self, obj):
return models.AppReleaseInfo.objects.filter(app_id=obj).count() return models.AppReleaseInfo.objects.filter(app_id=obj).count()
today_hits_count = serializers.SerializerMethodField()
def get_today_hits_count(self, obj):
return get_app_today_download_times([obj.app_id])
def update(self, instance, validated_data): def update(self, instance, validated_data):
return super(AdminAppsSerializer, self).update(instance, validated_data) return super(AdminAppsSerializer, self).update(instance, validated_data)

@ -2,7 +2,7 @@
# #
from django.conf import settings from django.conf import settings
from django.http import HttpResponse from django.http import HttpResponse
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from proxy.views import proxy_view from proxy.views import proxy_view
from rest_framework.views import APIView from rest_framework.views import APIView

@ -9,6 +9,7 @@ import logging
from django.contrib import auth from django.contrib import auth
from rest_framework.views import APIView from rest_framework.views import APIView
from api.models import UserInfo
from api.utils.auth.util import AuthInfo from api.utils.auth.util import AuthInfo
from api.utils.serializer import UserInfoSerializer from api.utils.serializer import UserInfoSerializer
from api.utils.utils import set_user_token from api.utils.utils import set_user_token
@ -81,3 +82,16 @@ class LoginUserView(APIView):
def get(self, request): def get(self, request):
serializer = UserInfoSerializer(request.user, ) serializer = UserInfoSerializer(request.user, )
return ApiResponse(data=serializer.data) 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='重置密码失败,用户不存在')

@ -0,0 +1,14 @@
from django.core.management.base import BaseCommand
from common.utils.caches import add_user_ds
class Command(BaseCommand):
help = 'add user download times'
def add_arguments(self, parser):
parser.add_argument('uid', type=str, default='')
parser.add_argument('download_times', type=int, default=100)
def handle(self, *args, **options):
add_user_ds(options.get('uid', None), options.get('download_times', 100))

@ -102,7 +102,7 @@ class BaseActionCommand(BaseCommand):
parser.add_argument('-d', '--daemon', nargs="?", const=True) parser.add_argument('-d', '--daemon', nargs="?", const=True)
parser.add_argument('-up', '--uwsgi_processes', type=int, nargs="?", default=get_sys_process_num()) parser.add_argument('-up', '--uwsgi_processes', type=int, nargs="?", default=get_sys_process_num())
parser.add_argument('-ut', '--uwsgi_threads', type=int, nargs="?", default=get_sys_thread_num()) parser.add_argument('-ut', '--uwsgi_threads', type=int, nargs="?", default=get_sys_thread_num())
parser.add_argument('-cn', '--celery_num', type=int, nargs="?", default=20) parser.add_argument('-cn', '--celery_num', type=int, nargs="?", default=8)
parser.add_argument('-usm', '--uwsgi_socket_mode', nargs="?", const=True, parser.add_argument('-usm', '--uwsgi_socket_mode', nargs="?", const=True,
help='run to bind socket mode, default http mode, only uwsgi service') help='run to bind socket mode, default http mode, only uwsgi service')
parser.add_argument('-f', '--force', nargs="?", const=True) parser.add_argument('-f', '--force', nargs="?", const=True)

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

@ -117,13 +117,13 @@ def save_app_infos(app_tmp_filename, app_file_name, user_obj, app_info, bundle_i
app_obj.issupersign = issupersign app_obj.issupersign = issupersign
# appmobj.wxeasytype = False if user_obj.domain_name or appmobj.domain_name else True # appmobj.wxeasytype = False if user_obj.domain_name or appmobj.domain_name else True
app_obj.bundle_id = bundle_id app_obj.bundle_id = bundle_id
app_obj.save(update_fields=["short", "name", "bundle_id", "issupersign"]) app_obj.save(update_fields=["short", "name", "bundle_id", "issupersign", "updated_time"])
except Exception as e: except Exception as e:
logger.error(f"save app info failed,app_obj:{app_obj} Exception:{e}") logger.error(f"save app info failed,app_obj:{app_obj} Exception:{e}")
app_obj.bundle_id = bundle_id app_obj.bundle_id = bundle_id
app_obj.issupersign = issupersign app_obj.issupersign = issupersign
app_obj.name = app_info["labelname"] app_obj.name = app_info["labelname"]
app_obj.save(update_fields=["name", "bundle_id", "issupersign"]) app_obj.save(update_fields=["name", "bundle_id", "issupersign", "updated_time"])
del_cache_response_by_short(app_obj.app_id) del_cache_response_by_short(app_obj.app_id)
MagicCacheData.invalid_cache(app_obj.app_id) MagicCacheData.invalid_cache(app_obj.app_id)
AppReleaseInfo.objects.filter(app_id=app_obj).update(is_master=False) AppReleaseInfo.objects.filter(app_id=app_obj).update(is_master=False)

@ -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() 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): def get_app_storage_used(app_obj):
binary_size_sum = 0 binary_size_sum = 0
for release_obj in AppReleaseInfo.objects.filter(app_id=app_obj).all(): for release_obj in AppReleaseInfo.objects.filter(app_id=app_obj).all():
@ -323,4 +323,7 @@ def get_user_storage_obj(user_obj, default=False):
def get_preview_short_config(user_obj, short): def get_preview_short_config(user_obj, short):
if user_obj:
return f"{'#/' if UserConfig(user_obj).PREVIEW_ROUTE_HASH else ''}{short}" return f"{'#/' if UserConfig(user_obj).PREVIEW_ROUTE_HASH else ''}{short}"
else:
return f"{'#/' if Config.PREVIEW_ROUTE_HASH else ''}{short}"

@ -12,7 +12,7 @@ from api.utils.modelutils import get_user_domain_name, get_app_domain_name, get_
from common.base.baseutils import get_choices_dict, WeixinLoginUid from common.base.baseutils import get_choices_dict, WeixinLoginUid
from common.cache.storage import AdPicShowCache from common.cache.storage import AdPicShowCache
from common.core.sysconfig import Config, UserConfig from common.core.sysconfig import Config, UserConfig
from common.utils.caches import get_user_free_download_times, get_user_cert_auth_status from common.utils.caches import get_user_free_download_times, get_user_cert_auth_status, get_app_today_download_times
from common.utils.storage import Storage from common.utils.storage import Storage
from common.utils.token import make_token from common.utils.token import make_token
@ -166,15 +166,11 @@ class AppsSerializer(serializers.ModelSerializer):
def get_sign_type_choice(self, obj): def get_sign_type_choice(self, obj):
return get_choices_dict(obj.supersign_type_choices) return get_choices_dict(obj.supersign_type_choices)
# supersign_used_number = serializers.IntegerField(default=0) count = serializers.IntegerField(default=0)
# developer_used_count = serializers.IntegerField(default=0)
# def get_supersign_used_number(self, obj): private_developer_number = serializers.IntegerField(default=0)
# return models.APPSuperSignUsedInfo.objects.filter(app_id=obj).all().count() private_developer_used_number = serializers.IntegerField(default=0)
# supersign_used_number = serializers.IntegerField(default=0)
# developer_used_count = serializers.IntegerField(default=0)
#
# def get_developer_used_count(self, obj):
# return models.DeveloperAppID.objects.filter(app_id=obj).all().count()
screenshots = serializers.SerializerMethodField() screenshots = serializers.SerializerMethodField()
@ -216,12 +212,16 @@ class AppsSerializer(serializers.ModelSerializer):
else: else:
return {} return {}
today_hits_count = serializers.SerializerMethodField()
def get_today_hits_count(self, obj):
return get_app_today_download_times([obj.app_id])
class AppsListSerializer(AppsSerializer): class AppsListSerializer(AppsSerializer):
class Meta: class Meta:
model = models.Apps model = models.Apps
fields = ["app_id", "bundle_id", "issupersign", "name", "preview_url", "short", "type", "master_release", fields = ["app_id", "bundle_id", "issupersign", "name", "preview_url", "short", "type", "master_release",
"has_combo", "count_hits"] "has_combo", "count_hits", "today_hits_count"]
def get_master_release(self, obj): def get_master_release(self, obj):
master_release_obj = get_app_master_obj_from_context(self, obj) master_release_obj = get_app_master_obj_from_context(self, obj)

@ -387,11 +387,18 @@ class AppDownloadTokenView(APIView):
token_number = data.get('token_number') token_number = data.get('token_number')
bind_status = data.get('bind_status') bind_status = data.get('bind_status')
token_max_used_number = data.get('token_max_used_number') token_max_used_number = data.get('token_max_used_number')
udid_list_str = data.get('udids')
udid_list = []
if not isinstance(bind_status, bool): if not isinstance(bind_status, bool):
bind_status = False bind_status = False
if bind_status: if bind_status:
token_max_used_number = 1 token_max_used_number = 1
if udid_list_str and len(udid_list_str) > 10:
udid_list = udid_list_str.replace(' ', ' ').replace('\n', ' ').replace(',', ' ').replace('\r\n',
' ').split()
udid_list = set(filter(lambda x: len(x) > 10, udid_list))
token = ''
app_obj = Apps.objects.filter(user_id=request.user, app_id=app_id).first() app_obj = Apps.objects.filter(user_id=request.user, app_id=app_id).first()
if token and isinstance(token, str) and len(token) >= 4: if token and isinstance(token, str) and len(token) >= 4:
@ -408,6 +415,12 @@ class AppDownloadTokenView(APIView):
else: else:
token_number = 20 token_number = 20
if udid_list:
bind_udid_list = AppDownloadToken.objects.filter(app_id=app_obj, bind_udid__isnull=False).values_list(
'bind_udid')
udid_list = list(udid_list - set([x[0] for x in bind_udid_list]))
token_number = len(udid_list)
download_token_queryset = AppDownloadToken.objects.filter(app_id=app_obj).annotate( download_token_queryset = AppDownloadToken.objects.filter(app_id=app_obj).annotate(
token_len=Length('token')).filter(token_len=token_length).values('token').all() token_len=Length('token')).filter(token_len=token_length).values('token').all()
exist_token = [d_token['token'] for d_token in download_token_queryset] exist_token = [d_token['token'] for d_token in download_token_queryset]
@ -415,9 +428,19 @@ class AppDownloadTokenView(APIView):
token_number=token_number + download_token_queryset.count(), token_number=token_number + download_token_queryset.count(),
exist_token=copy.deepcopy(exist_token)) exist_token=copy.deepcopy(exist_token))
bulk_list = [] bulk_list = []
d_index = 0
for d_token in list(set(make_token_list) - set(exist_token))[:token_number]: for d_token in list(set(make_token_list) - set(exist_token))[:token_number]:
bulk_list.append(AppDownloadToken(token=d_token, max_limit_count=token_max_used_number, app_id=app_obj, n_data = {
bind_status=bind_status)) 'token': d_token,
'max_limit_count': token_max_used_number,
'app_id': app_obj,
'bind_status': bind_status,
}
if udid_list:
n_data['bind_udid'] = udid_list[d_index]
n_data['used_count'] = 1
d_index += 1
bulk_list.append(AppDownloadToken(**n_data))
AppDownloadToken.objects.bulk_create(bulk_list) AppDownloadToken.objects.bulk_create(bulk_list)
return Response(res.dict) return Response(res.dict)

@ -41,7 +41,7 @@ def get_domain_filter(request):
def auto_clean_download_cache(user_obj, user_domain_obj, app_obj, delete=False): def auto_clean_download_cache(user_obj, user_domain_obj, app_obj, delete=False):
if user_domain_obj: if user_domain_obj:
base_domain_queryset = UserDomainInfo.objects.filter(user_id=user_obj, is_enable=True).all() 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: if delete:
user_domain_obj.delete() user_domain_obj.delete()
if base_domain_queryset.filter(domain_type__in=[0, 1]).count() == 0: 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.serializer import AppsShortSerializer, AppAdInfoSerializer
from api.utils.signalutils import run_get_xsign_binary_file 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.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.response import mobileprovision_file_response, file_response, ApiResponse
from common.core.sysconfig import Config from common.core.sysconfig import Config
from common.core.throttle import VisitShortThrottle, InstallShortThrottle, InstallThrottle1, InstallThrottle2 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): def get(self, request, short):
res = BaseResponse() res = BaseResponse()
release_id = request.query_params.get("release_id", None) 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 res.ad = AppAdInfoSerializer(ad_obj, context={"key": "ShortDownloadView", "short": short}).data
return Response(res.dict) 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的设置 # key的设置
def calculate_cache_key(self, view_instance, view_method, def calculate_cache_key(self, view_instance, view_method,
request, args, kwargs): request, args, kwargs):

@ -576,9 +576,7 @@ class AuthorizationView(APIView):
ext = request.data.get("ext", None) ext = request.data.get("ext", None)
register_type = get_register_type() register_type = get_register_type()
if ext and register_type.get('code', None) and ext.get('icode', None): if ext and register_type.get('code', None) and ext.get('icode', None):
if ext.get('icode') == '689888666': if ext.get('icode') not in Config.INVITECODE:
pass
else:
res.code = 1008 res.code = 1008
res.msg = "邀请码已失效" res.msg = "邀请码已失效"
return Response(res.dict) return Response(res.dict)

@ -15,6 +15,7 @@ from api.utils.apputils import get_random_short, save_app_infos
from api.utils.modelutils import get_app_download_uri, check_bundle_id_legal, get_user_storage_used, \ from api.utils.modelutils import get_app_download_uri, check_bundle_id_legal, get_user_storage_used, \
get_user_storage_capacity get_user_storage_capacity
from api.utils.response import BaseResponse from api.utils.response import BaseResponse
from api.utils.serializer import AppsSerializer
from api.utils.signalutils import run_signal_resign_utils from api.utils.signalutils import run_signal_resign_utils
from common.base.baseutils import make_app_uuid, make_from_user_uuid from common.base.baseutils import make_app_uuid, make_from_user_uuid
from common.cache.state import MigrateStorageState from common.cache.state import MigrateStorageState
@ -170,9 +171,11 @@ class AppAnalyseView(APIView):
storage.rename_file(app_tmp_filename, app_new_filename) storage.rename_file(app_tmp_filename, app_new_filename)
storage.rename_file(png_tmp_filename, png_new_filename) storage.rename_file(png_tmp_filename, png_new_filename)
app_obj = Apps.objects.filter(bundle_id=data.get("bundleid"), user_id=request.user, type=1).first() app_obj = Apps.objects.filter(bundle_id=data.get("bundleid"), user_id=request.user).first()
if app_obj: if app_obj and app_obj.type == 1:
run_signal_resign_utils(app_obj) run_signal_resign_utils(app_obj)
app_serializer = AppsSerializer(app_obj, context={"storage": Storage(request.user)})
res.data = app_serializer.data
else: else:
storage.delete_file(app_tmp_filename) storage.delete_file(app_tmp_filename)
storage.delete_file(png_tmp_filename) storage.delete_file(png_tmp_filename)

@ -112,8 +112,11 @@ class FLYCliSer(object):
req = requests.put(url, json=data, headers=self._header) req = requests.put(url, json=data, headers=self._header)
if req.status_code == 200: if req.status_code == 200:
if req.json()['code'] == 1000: if req.json()['code'] == 1000:
master_download_url = req.json().get('data', {}).get('preview_url')
print("应用 %s %s 上传更新成功" % (data.get('appname'), data.get('bundleid'))) print("应用 %s %s 上传更新成功" % (data.get('appname'), data.get('bundleid')))
return print("当前应用下载连接:", master_download_url)
print("当前版本下载连接:", master_download_url + '?release_id=' + data.get('upload_key').split('.')[0])
return master_download_url
raise AssertionError(req.text) raise AssertionError(req.text)
def upload_local_storage(self, upload_key, upload_token, app_id, file_path): def upload_local_storage(self, upload_key, upload_token, app_id, file_path):
@ -200,7 +203,7 @@ class FLYCliSer(object):
"udid": appinfo.get('udid', []), "udid": appinfo.get('udid', []),
"type": appinfo['type'], "type": appinfo['type'],
} }
self.analyse({**app_data, **upcretsdata}) return self.analyse({**app_data, **upcretsdata})
class AppInfo(object): class AppInfo(object):
@ -359,6 +362,7 @@ if __name__ == '__main__':
Example: \r\n Example: \r\n
""" """
) )
parser.add_argument('-host', '--host', nargs="?", const=True, type=str, help="服务器地址")
parser.add_argument('-s', '--short', nargs="?", const=True, type=str, help="短连接") parser.add_argument('-s', '--short', nargs="?", const=True, type=str, help="短连接")
parser.add_argument('-cl', '--change_log', nargs="?", const=True, type=str, help="更新日志") parser.add_argument('-cl', '--change_log', nargs="?", const=True, type=str, help="更新日志")
parser.add_argument('-n', '--name', nargs="?", const=True, type=str, help="名称") parser.add_argument('-n', '--name', nargs="?", const=True, type=str, help="名称")
@ -368,6 +372,8 @@ if __name__ == '__main__':
parser.add_argument('-f', '--filepath', nargs="+", type=str, help="filepath") parser.add_argument('-f', '--filepath', nargs="+", type=str, help="filepath")
parser.add_argument('-d', '--dir_path', nargs="+", type=str, help="dir_path") parser.add_argument('-d', '--dir_path', nargs="+", type=str, help="dir_path")
args = parser.parse_args() args = parser.parse_args()
if not args.host:
args.host = domain
if args.token and args.write_token_info: if args.token and args.write_token_info:
write_token(args.token) write_token(args.token)
if args.install_depends: if args.install_depends:
@ -379,7 +385,7 @@ if __name__ == '__main__':
if token: token = token.replace('\r', '').replace('\n', '') if token: token = token.replace('\r', '').replace('\n', '')
if not token: if not token:
raise Exception('token not exist') raise Exception('token not exist')
fly_obj = FLYCliSer(domain, token) fly_obj = FLYCliSer(args.host, token)
file_path_list = [] file_path_list = []
if args.filepath is not None and len(args.filepath) > 0: if args.filepath is not None and len(args.filepath) > 0:
for args_filepath in args.filepath: for args_filepath in args.filepath:

@ -6,10 +6,11 @@
import datetime import datetime
import logging import logging
import time import time
from functools import wraps from functools import wraps, WRAPPER_ASSIGNMENTS
from importlib import import_module from importlib import import_module
from django.core.cache import cache from django.core.cache import cache
from django.http import HttpResponse
logger = logging.getLogger(__name__) 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): class MagicCacheData(object):
@staticmethod @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 invalid_time: 数据缓存提前失效时间单位秒该cache有效时间为 cache_time-invalid_time
:param key: cache唯一标识默认为所装饰函数名称 :param key_func: cache唯一标识默认为所装饰函数名称
:return: :return:
""" """
def decorator(func): def decorator(func):
@wraps(func) @wraps(func)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
cache_key = f'magic_cache_data' cache_key = f'magic_cache_data_{func.__name__}'
if key: if key_func:
cache_key = f'{cache_key}_{key(*args, **kwargs)}' cache_key = f'{cache_key}_{key_func(*args, **kwargs)}'
else:
cache_key = f'{cache_key}_{func.__name__}' cache_time = timeout
if timeout_func:
cache_time = timeout_func(*args, **kwargs)
n_time = time.time() n_time = time.time()
res = cache.get(cache_key) 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: 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'] return res['data']
else: else:
res = {'c_time': n_time, 'data': '', 'status': 'ready'}
cache.set(cache_key, res, cache_time)
try: try:
data = func(*args, **kwargs) res['data'] = func(*args, **kwargs)
res = {'c_time': n_time, 'data': data} res['status'] = 'ok'
cache.set(cache_key, res, cache_time) cache.set(cache_key, res, cache_time)
logger.info( 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: except Exception as e:
logger.error( 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'] return res['data']
@ -217,3 +231,141 @@ class MagicCacheData(object):
cache_key = f'magic_cache_data_{key}' cache_key = f'magic_cache_data_{key}'
res = cache.delete(cache_key) res = cache.delete(cache_key)
logger.warning(f"invalid_cache cache_key:{cache_key} result:{res}") 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 APP_REGISTRATION_COMPLETE = 2
PROFILE_DOWNLOAD_COMPLETE = 3 PROFILE_DOWNLOAD_COMPLETE = 3
SIGNATURE_PACKAGE_COMPLETE = 4 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

@ -11,30 +11,30 @@ logger = logging.getLogger(__name__)
""" """
更新应用的时候是否同时更新超级签名数据 更新应用的时候是否同时更新超级签名数据
""" """
run_resign_task_signal = Signal(providing_args=["app_obj"]) run_resign_task_signal = Signal()
""" """
删除应用时的信号用户清理签名相关数据 删除应用时的信号用户清理签名相关数据
""" """
delete_app_signal = Signal(providing_args=["app_obj"]) delete_app_signal = Signal()
""" """
下载超级签名数据 下载超级签名数据
""" """
xsign_app_download_url_signal = Signal(providing_args=["app_pk", "udid", "download_url_type", "limit"]) xsign_app_download_url_signal = Signal()
""" """
迁移超级签名数据 迁移超级签名数据
""" """
xsign_migrate_data_signal = Signal(providing_args=["app_release_obj", "user_obj", "new_storage_obj", "clean_old_data"]) xsign_migrate_data_signal = Signal()
""" """
清理超级签名数据 清理超级签名数据
""" """
xsign_clean_data_signal = Signal(providing_args=["app_release_obj", "storage_obj"]) xsign_clean_data_signal = Signal()
""" """
根据binary_file获取签名应用数据 根据binary_file获取签名应用数据
""" """
xsign_app_release_obj_signal = Signal(providing_args=["binary_file"]) xsign_app_release_obj_signal = Signal()

@ -273,6 +273,10 @@ class AuthConfCache(ConfigCacheBase):
def REPORT(self): def REPORT(self):
return super().get_value('REPORT', AUTHCONF.REPORT) return super().get_value('REPORT', AUTHCONF.REPORT)
@property
def INVITECODE(self):
return super().get_value('INVITECODE', AUTHCONF.INVITECODE)
class GeeTestConfCache(ConfigCacheBase): class GeeTestConfCache(ConfigCacheBase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

@ -72,11 +72,6 @@ def create_menu(menu_json=None):
"name": "官方地址", "name": "官方地址",
"key": "flyapps" "key": "flyapps"
}, },
{
"type": "view",
"name": "留言反馈",
"url": "https://flyapps.cn/gbook/"
},
{ {
"type": "click", "type": "click",
"name": "查询当前绑定", "name": "查询当前绑定",

@ -24,7 +24,7 @@ class Weixinpay(object):
def __get_wx_pay(self): def __get_wx_pay(self):
return WeChatPay(wechatpay_type=WeChatPayType.NATIVE, return WeChatPay(wechatpay_type=WeChatPayType.NATIVE,
mchid=self.wx_config.get('MCH_ID'), mchid=self.wx_config.get('MCH_ID'),
parivate_key=self.wx_config.get('APP_PRIVATE_KEY'), private_key=self.wx_config.get('APP_PRIVATE_KEY'),
cert_serial_no=self.wx_config.get('SERIAL_NO'), cert_serial_no=self.wx_config.get('SERIAL_NO'),
appid=self.wx_config.get('APP_ID'), appid=self.wx_config.get('APP_ID'),
notify_url=f"{self.wx_config.get('APP_NOTIFY_URL')}/{self.name}", notify_url=f"{self.wx_config.get('APP_NOTIFY_URL')}/{self.name}",

@ -8,7 +8,7 @@ class WeChatPay(object):
def __init__(self, def __init__(self,
wechatpay_type, wechatpay_type,
mchid, mchid,
parivate_key, private_key,
cert_serial_no, cert_serial_no,
appid, appid,
apiv3_key, apiv3_key,
@ -16,7 +16,7 @@ class WeChatPay(object):
""" """
:param wechatpay_type: 微信支付类型示例值WeChatPayType.MINIPROG :param wechatpay_type: 微信支付类型示例值WeChatPayType.MINIPROG
:param mchid: 直连商户号示例值'1230000109' :param mchid: 直连商户号示例值'1230000109'
:param parivate_key: 商户证书私钥示例值'MIIEvwIBADANBgkqhkiG9w0BAQE...' :param private_key: 商户证书私钥示例值'MIIEvwIBADANBgkqhkiG9w0BAQE...'
:param cert_serial_no: 商户证书序列号示例值'444F4864EA9B34415...' :param cert_serial_no: 商户证书序列号示例值'444F4864EA9B34415...'
:param appid: 应用ID示例值'wxd678efh567hg6787' :param appid: 应用ID示例值'wxd678efh567hg6787'
:param apiv3_key: 商户APIv3密钥示例值'a12d3924fd499edac8a5efc...' :param apiv3_key: 商户APIv3密钥示例值'a12d3924fd499edac8a5efc...'
@ -28,7 +28,7 @@ class WeChatPay(object):
self._notify_url = notify_url self._notify_url = notify_url
self._core = Core(mchid=self._mchid, self._core = Core(mchid=self._mchid,
cert_serial_no=cert_serial_no, cert_serial_no=cert_serial_no,
private_key=parivate_key, private_key=private_key,
apiv3_key=apiv3_key) apiv3_key=apiv3_key)
def pay(self, def pay(self,

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# wechatpay v3 python sdk(https://github.com/minibear2021/wechatpayv3)
import json import json
import time import time
import uuid import uuid
@ -175,8 +176,7 @@ class Core():
headers = {} headers = {}
headers.update({'Content-Type': 'application/json'}) headers.update({'Content-Type': 'application/json'})
headers.update({'Accept': 'application/json'}) headers.update({'Accept': 'application/json'})
headers.update( headers.update({'User-Agent': 'flyapps python sdk'})
{'User-Agent': 'wechatpay v3 python sdk(https://github.com/minibear2021/wechatpayv3)'})
authorization = build_authorization( authorization = build_authorization(
path, path,
'GET' if method == RequestType.GET else 'POST', 'GET' if method == RequestType.GET else 'POST',

@ -417,3 +417,13 @@ def add_user_storage_exchange(user_obj, exchange_number, exchange_month, remote_
except Exception as e: except Exception as e:
logger.error(f"{user_obj} download_times less then 0. Exception:{e}") logger.error(f"{user_obj} download_times less then 0. Exception:{e}")
return False return False
def add_user_ds(uid, amount):
obj = UserInfo.objects.filter(uid=uid).first()
try:
amount = int(amount)
except Exception:
amount = 0
if obj and amount:
return admin_change_user_download_times(obj, amount * Config.APP_USE_BASE_DOWNLOAD_TIMES)

@ -125,7 +125,7 @@ def get_app_download_url(request, res, app_id, short, password, release_id, is_d
logger.info(f'app download url :{res.data}') logger.info(f'app download url :{res.data}')
if download_url != "" and "mobileconifg" not in download_url: if download_url != "" and "mobileconifg" not in download_url:
if not check_app_download_token(app_obj.get("need_password"), is_download, app_id, password): if not check_app_download_token(app_obj.get("need_password"), is_download, app_id, password, bool(udid)):
res.code = 1006 res.code = 1006
res.msg = '下载授权码有误' res.msg = '下载授权码有误'
return res return res

@ -11,9 +11,10 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
class DOMAINCONF(object): class DOMAINCONF(object):
API_DOMAIN = "https://app.hehelucky.cn" API_DOMAIN = "https://app.hehelucky.cn" # 用与开启本地存储,上传应用配置
WEB_DOMAIN = "https://app.hehelucky.cn" WEB_DOMAIN = "https://app.hehelucky.cn" # 用于超级签跳转配置,该域名一般为前端页面域名
MOBILEPROVISION = "https://static.flyapps.top/embedded2.mobileprovision" MOBILEPROVISION = "https://static.hehejoy.cn/embedded3.mobileprovision" # 用于苹果包企业签信任企业跳转
DOWNLOAD_DEPLOYMENT_HASH_URL = "https://static.flyapps.top/download.v63.23.tar.gz" DOWNLOAD_DEPLOYMENT_HASH_URL = "https://static.flyapps.top/download.v63.23.tar.gz"
DOWNLOAD_DEPLOYMENT_HISTORY_URL = "https://static.flyapps.top/download.v63.23.tar.gz" DOWNLOAD_DEPLOYMENT_HISTORY_URL = "https://static.flyapps.top/download.v63.23.tar.gz"
DOWNLOAD_DEPLOYMENT_HASH_URL_DES = "hash模式" DOWNLOAD_DEPLOYMENT_HASH_URL_DES = "hash模式"
@ -27,27 +28,26 @@ class BASECONF(object):
SECRET_KEY = 'j!g@^bc(z(a3*i&kp$_@bgb)bug&^#3=amch!3lz&1x&s6ss6t' SECRET_KEY = 'j!g@^bc(z(a3*i&kp$_@bgb)bug&^#3=amch!3lz&1x&s6ss6t'
ALLOWED_HOSTS = ['127.0.0.1', 'synchrotron', '172.16.133.34', 'ali.cdn.flyapp.dvcloud.xin', ALLOWED_HOSTS = ['*']
'api.src.flyapp.dvcloud.xin', 'app.hehelucky.cn']
# When Django start it will bind this host and port # When Django start it will bind this host and port
# ./manage.py runserver 127.0.0.1:8080 # ./manage.py runserver 127.0.0.1:8080
# uwsgi运行时绑定端口 # uwsgi运行时绑定端口
SERVER_BIND_HOST = '127.0.0.1' SERVER_BIND_HOST = '0.0.0.0'
SERVER_LISTEN_PORT = 8898 SERVER_LISTEN_PORT = 8898
# celery flower 任务监控配置 # celery flower 任务监控配置
CELERY_FLOWER_PORT = 5566 CELERY_FLOWER_PORT = 5566
CELERY_FLOWER_HOST = '127.0.0.1' CELERY_FLOWER_HOST = '127.0.0.1'
# geetest 配置信息 # geetest 配置信息, 该配置信息一般用于登录,注册,修改信息的滑动验证
GEETEST_ID = "d3f3cf73200a70bd3a0c32ea22e5003a" GEETEST_ID = "修改为你自己的信息"
GEETEST_KEY = "6f4ab6185c230a4b1f8430f28ebe4804" GEETEST_KEY = "修改为你自己的信息"
GEETEST_CYCLE_TIME = 10 GEETEST_CYCLE_TIME = 10
GEETEST_BYPASS_STATUS_KEY = "gt_server_bypass_status" GEETEST_BYPASS_STATUS_KEY = "gt_server_bypass_status"
GEETEST_BYPASS_URL = "https://bypass.geetest.com/v1/bypass_status.php" GEETEST_BYPASS_URL = "https://bypass.geetest.com/v1/bypass_status.php"
# 访问速率限制 # 访问速率限制,可根据实际情况修改
DEFAULT_THROTTLE_RATES = { DEFAULT_THROTTLE_RATES = {
'ShortAccessUser1': '180/m', 'ShortAccessUser1': '180/m',
'ShortAccessUser2': '2000/h', 'ShortAccessUser2': '2000/h',
@ -64,17 +64,23 @@ class BASECONF(object):
class DBCONF(object): class DBCONF(object):
host = '127.0.0.1' """
mysql数据库信息
"""
host = 'mariadb' # 可以通过主机名进行通信,如果是本地部署,可填写 127.0.0.1
port = '3306' port = '3306'
password = 'KGzKjZpWBp4R4RSa' password = 'KGzKjZpWBp4R4RSa'
user = 'flyuser' user = 'flyuser'
name = 'flyappnew' name = 'flyapps'
class CACHECONF(object): class CACHECONF(object):
host = '127.0.0.1' """
redis 缓存配置
"""
host = 'redis' # 可以通过主机名进行通信,如果是本地部署,可填写 127.0.0.1
port = '6379' port = '6379'
password = '' password = 'nineven'
# 微信公众号登录配置 # 微信公众号登录配置
@ -82,30 +88,32 @@ class THIRDLOGINCONF(object):
wx_official = { wx_official = {
'name': 'wx_official', 'name': 'wx_official',
'auth': { 'auth': {
'app_id': '1wx390e5985fd3699e6', 'app_id': '修改为你自己的信息',
'app_secret': '5b1796722172a902d019f43e4a2fb678', 'app_secret': '修改为你自己的信息',
'token': 'f0ae76c1559cacf72a99db6a41b879b8', 'token': '修改为你自己的信息',
'encoding_aes_key': '7b9U60tCXk3f4yOFideTN5ithh4Y8X3Xl9pRovp83gG', 'encoding_aes_key': '修改为你自己的信息',
}, },
'active': False 'active': False
} }
wx_applet = { wx_applet = {
'auth': { 'auth': {
'app_id': 'wxb65d1894331e9ff3', 'app_id': '修改为你自己的信息',
'app_secret': 'e05b319562b3b123ffef1d9ce64dd665', 'app_secret': '修改为你自己的信息',
}, },
'active': True 'active': False
} }
class AUTHCONF(object): class AUTHCONF(object):
# 邀请码
INVITECODE = ['yugechaoshen666']
# 注册方式,如果启用sms或者email 需要配置 THIRD_PART_CONFIG_KEY_INFO.sender 信息 # 注册方式,如果启用sms或者email 需要配置 THIRD_PART_CONFIG_KEY_INFO.sender 信息
REGISTER = { REGISTER = {
"enable": True, "enable": True,
"captcha": False, # 是否开启注册字母验证码 "captcha": False, # 是否开启注册字母验证码
"geetest": True, # 是否开启geetest验证,如要开启请先配置geetest "geetest": False, # 是否开启geetest验证,如要开启请先配置geetest
"register_type": { "register_type": {
'sms': True, # 短信注册 'sms': False, # 短信注册
'email': True, # 邮件注册 'email': True, # 邮件注册
'code': False, # 邀请码注册,邀请码必填写,需要和短信,邮件一起使用 'code': False, # 邀请码注册,邀请码必填写,需要和短信,邮件一起使用
} }
@ -114,18 +122,18 @@ class AUTHCONF(object):
CHANGER = { CHANGER = {
"enable": True, "enable": True,
"captcha": False, # 是否开启注册字母验证码 "captcha": False, # 是否开启注册字母验证码
"geetest": True, # 是否开启geetest验证,如要开启请先配置geetest "geetest": False, # 是否开启geetest验证,如要开启请先配置geetest
"change_type": { "change_type": {
'sms': True, # 短信注册 'sms': False, # 短信注册
'email': True, # 邮件注册 'email': True, # 邮件注册
'code': False, # 邀请码注册,邀请码必填写,需要和短信,邮件一起使用 'code': False, # 邀请码注册,邀请码必填写,需要和短信,邮件一起使用
} }
} }
LOGIN = { LOGIN = {
"captcha": False, # 是否开启登录字母验证码 "captcha": False, # 是否开启登录字母验证码
"geetest": True, # 是否开启geetest验证 "geetest": False, # 是否开启geetest验证
"login_type": { "login_type": {
'sms': True, # 短信登录 'sms': False, # 短信登录
'email': True, # 邮件登录 'email': True, # 邮件登录
'up': False, # 密码登录 'up': False, # 密码登录
'third': { 'third': {
@ -136,7 +144,7 @@ class AUTHCONF(object):
REPORT = { REPORT = {
"enable": True, "enable": True,
"captcha": False, # 是否开启注册字母验证码 "captcha": False, # 是否开启注册字母验证码
"geetest": True, # 是否开启geetest验证,如要开启请先配置geetest "geetest": False, # 是否开启geetest验证,如要开启请先配置geetest
"report_type": { "report_type": {
'sms': False, # 短信举报 'sms': False, # 短信举报
'email': True, # 邮件举报 'email': True, # 邮件举报
@ -145,7 +153,7 @@ class AUTHCONF(object):
NOTIFY = { NOTIFY = {
"enable": True, "enable": True,
"captcha": False, # 是否开启注册字母验证码 "captcha": False, # 是否开启注册字母验证码
"geetest": True, # 是否开启geetest验证,如要开启请先配置geetest "geetest": False, # 是否开启geetest验证,如要开启请先配置geetest
"notify_type": { "notify_type": {
'sms': False, # 短信通知 'sms': False, # 短信通知
'email': True, # 邮件通知 'email': True, # 邮件通知
@ -173,13 +181,13 @@ class STORAGEKEYCONF(object):
'name': 'aliyun', 'name': 'aliyun',
'type': 2, 'type': 2,
'auth': { 'auth': {
'access_key': 'LTAI4FkbTR', 'access_key': '修改为你自己的信息',
'secret_key': '2iLIxy9', 'secret_key': '修改为你自己的信息',
'bucket_name': 'fge', 'bucket_name': '修改为你自己的信息',
'sts_role_arn': 'ap-sage', 'sts_role_arn': '修改为你自己的信息',
'endpoint': 'oss-cn-beijing-internal.aliyuncs.com', # 服务器和oss在同一个地区,填写内网的endpoint 'endpoint': 'oss-cn-beijing-internal.aliyuncs.com', # 服务器和oss在同一个地区,填写内网的endpoint
'is_https': True, 'is_https': True,
'domain_name': 'aoud.xin', 'domain_name': '修改为你自己的信息',
'download_auth_type': 1, # 1:oss 2:cdn 'download_auth_type': 1, # 1:oss 2:cdn
'cnd_auth_key': '', # 当cdn为阿里云并且 download_auth_type=2 的时候 生效,需要 开启阿里云OSS私有Bucket回源 'cnd_auth_key': '', # 当cdn为阿里云并且 download_auth_type=2 的时候 生效,需要 开启阿里云OSS私有Bucket回源
}, },
@ -189,11 +197,11 @@ class STORAGEKEYCONF(object):
'name': 'qiniuyun', 'name': 'qiniuyun',
'type': 1, 'type': 1,
'auth': { 'auth': {
'access_key': 'mT4fiJ', 'access_key': '修改为你自己的信息',
'secret_key': '0G9fXfhYLynv', 'secret_key': '修改为你自己的信息',
'bucket_name': 'fge', 'bucket_name': '修改为你自己的信息',
'is_https': False, 'is_https': False,
'domain_name': 'foud.xin' 'domain_name': '修改为你自己的信息'
}, },
'active': False 'active': False
} }
@ -214,9 +222,9 @@ class SENDERCONF(object):
'email_port': 465, 'email_port': 465,
'use_tls': False, 'use_tls': False,
'use_ssl': True, 'use_ssl': True,
'username': 'flyapps@126.com', 'username': '修改为你自己的信息',
'password': 'GGHFEUMZBRZIFZGQ', 'password': '修改为你自己的信息',
'form': 'fly分发平台 <flyapps@126.com>', 'form': 'fly分发平台 <修改为你自己的信息>',
'subject': '%(code)s验证', 'subject': '%(code)s验证',
'template_code': { 'template_code': {
'login': 'common.libs.sendmsg.template_content.get_userinfo_login_code_html_content', 'login': 'common.libs.sendmsg.template_content.get_userinfo_login_code_html_content',
@ -232,27 +240,27 @@ class SENDERCONF(object):
'name': 'aliyun', 'name': 'aliyun',
'type': 1, 'type': 1,
'auth': { 'auth': {
'access_key': 'LTAI5tJH2EnjVzJGMmNCYo9U', 'access_key': '修改为你自己的信息',
'secret_key': 'd0LETks5oxkdfbkLGtFihklWGbokab', 'secret_key': '修改为你自己的信息',
'region_id': 'cn-hangzhou', 'region_id': 'cn-hangzhou',
'sing_name': '东城飞阳', 'sing_name': '修改为你自己的信息',
'template_code': { 'template_code': {
'login': 'SMS_216700569', 'login': '修改为你自己的信息',
'change': 'SMS_216700566', 'change': '修改为你自己的信息',
'register': 'SMS_216700567', 'register': '修改为你自己的信息',
'password': 'SMS_222341718', 'password': '修改为你自己的信息',
'common': 'SMS_227259072' 'common': '修改为你自己的信息'
} }
}, },
'active': True 'active': False
}, },
{ {
'name': 'jiguang', 'name': 'jiguang',
'type': 2, 'type': 2,
'auth': { 'auth': {
'app_key': '93e1a9f71db4f044de4db34a', 'app_key': '修改为你自己的信息',
'master_secret': '5f996ee39c7eb52906510cc2', 'master_secret': '修改为你自己的信息',
'sign_id': '18138', 'sign_id': '修改为你自己的信息',
'template_code': { 'template_code': {
'login': '1', 'login': '1',
'change': '1', 'change': '1',
@ -268,40 +276,12 @@ class SENDERCONF(object):
class IPACONF(object): class IPACONF(object):
APPLE_DEVELOPER_API_PROXY_LIST = [ APPLE_DEVELOPER_API_PROXY_LIST = [
{'proxy': '39.101.198.124:17897', 'active': True}, # {'proxy': '39.26.108.94:7897', 'active': True},
{'proxy': '8.142.38.157:17897', 'active': True},
{'proxy': '121.89.194.26:17897', 'active': True},
{'proxy': '39.103.139.122:17897', 'active': True},
{'proxy': '8.142.99.245:17897', 'active': True},
{'proxy': '8.142.174.13:17897', 'active': True},
{'proxy': '39.99.238.93:17897', 'active': True},
{'proxy': '8.142.173.96:17897', 'active': True},
{'proxy': '8.142.163.150:17897', 'active': True},
{'proxy': '39.99.148.6:17897', 'active': True},
{'proxy': '8.142.177.18:17897', 'active': True},
{'proxy': '39.99.238.109:17897', 'active': True},
{'proxy': '8.142.137.58:17897', 'active': True},
{'proxy': '8.142.70.111:17897', 'active': True},
{'proxy': '39.99.250.139:17897', 'active': True},
{'proxy': '39.98.108.35:17897', 'active': True},
{'proxy': '8.142.86.125:17897', 'active': True},
{'proxy': '39.103.134.67:17897', 'active': True},
{'proxy': '121.89.213.83:17897', 'active': True},
{'proxy': '39.103.157.148:17897', 'active': True},
{'proxy': '121.89.211.11:17897', 'active': True},
{'proxy': '39.101.134.27:17897', 'active': True},
{'proxy': '39.103.129.39:17897', 'active': True},
{'proxy': '39.103.170.156:17897', 'active': True},
{'proxy': '8.142.169.88:17897', 'active': True},
{'proxy': '39.103.231.80:17897', 'active': True},
{'proxy': '8.142.43.168:17897', 'active': True},
{'proxy': '8.142.120.245:17897', 'active': True},
{'proxy': '8.142.120.174:17897', 'active': True},
] ]
APPLE_DEVELOPER_API_PROXY = { APPLE_DEVELOPER_API_PROXY = {
# 代理的作用,主要是为了加快苹果api的访问,在国内会出现卡死,访问超时等问题,怀疑是被苹果服务器拦截了 # 代理的作用,主要是为了加快苹果api的访问,在国内会出现卡死,访问超时等问题,怀疑是被苹果服务器拦截了
# 'http': '47.243.172.202:17897', # 'http': '47.243.172.202:7897',
# 'https': '47.243.172.202:17897' # 'https': '47.243.172.202:7897'
} }
APPLE_DEVELOPER_API_TIMEOUT = 2 * 60 # 访问苹果api超时时间,默认3分钟 APPLE_DEVELOPER_API_TIMEOUT = 2 * 60 # 访问苹果api超时时间,默认3分钟
MOBILE_CONFIG_SIGN_SSL = { MOBILE_CONFIG_SIGN_SSL = {
@ -333,14 +313,14 @@ class PAYCONF(object):
{ {
'NAME': 'alipay', 'NAME': 'alipay',
'TYPE': 'ALI', 'TYPE': 'ALI',
'ENABLED': True, 'ENABLED': False,
'AUTH': { 'AUTH': {
'APP_ID': "2021002138691845", 'APP_ID': "修改为你自己的信息",
'APP_PRIVATE_KEY': '''-----BEGIN RSA PRIVATE KEY----- 'APP_PRIVATE_KEY': '''-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAhqf2mwftoxZDNpl4eWsQ6mEfXgMlNPr6jv72ecA4hbKWqChXQmGS1T+0VsRTSoOXRDlu1MMqkTGISzHvmGb7Gmw+Myfs/ojoonD9r8fODvIo1MHolFBhr3GNQu7tqBlVJ76QgiYft+c4kkqCguuyCrd3Te6C5zCIuh6O98r4D3A3LFcm6OdScWGcfEbR+FUv+jSi2oezHeSpkhhpHGBLSsI0L9JOdHetdUE/TwN8V1HABdpnPXtp9SIu6ioIrrligX1ZRlwht2YUt0BPqPp/ApLdRIsqlhD4/ejmtMlaRqqiN6PulEThBew/qaLVSXIr2HCSXtwbki3pFMFOcsjF2wIDAQABAoIBADp4sQL83FnXDvSki8XdkgjUh7RhFUT+PtLdL9YKfADCXd1DNzDiAcqL0RlkQu62WXcMoW3OGavWoGJWmr3I6fy9R/0atzSH6syu19n+nyGqUcShNwdAKErwufB4o8Y8yddqToHVYCyRQOV1aVrEUhmJNUsn6LvPPW/kWRyMjE7XQDFHpL5/Ly7pXe+f9Btm37ZuePTPsm65P88C3GznjZxXhY1LBWFKLPG1470xdReduyeJFZS/TmK0nUxLwkACm9Gfvp7S2KJ3okUXohsGBAgJ68B9YeGiuIJiZhH2DZ1pm3/R9bSpOX3H+6vjaCsacXT5w7LZB+O0Vkthcm9vqeECgYEAvozFkIkFXXCEmCr3QVCJs4Fc6onbXEJU45xxubPhkA1wwwPrSqdubo4RHvNIus45Fn4mLzuQsaPRyJJZajvaKWC00GxhChMYj+nWgkAmABPKGwkMxzjC7wvEJkGyt87fHpK1XMFWQgfJ42VwUtmyemCMuh+A2SOekIJay93xTtkCgYEAtOhmQ4pu2cyqTzT+SD7p/VnS4sNqqM4I8NSvTuLkEo2IHnUj7YG6XoPZjn35dBvYUWWN2dwgfHXGEEzCOIwfy8GPA4eoKCDNEkMvoBVLdrEzMqg5QwG5GsIGvOuFnAzAw+D5YwEym/qmC2oBbat5jsAGT2rMmU5MnaS8a7lvcdMCgYEAiusQQb5TZfrZACMa3cg8i9y9A9R7UzicsM/mbW+B+8aAtfxOdr+4F+uE+d594IrmPcq8ReUUKR34nFRt0bBO7amuSOEqofCoEIt3MsBXs+i5iJpBcaClJSeb2hQ9mhm8uopUpInjPAJ3okva5twFbYikMDE1e5inSk1uqoBlI4kCgYB4rzDJjeg1U9upy2h3OcFPSkTtEgBtbEV6o+fvcF1GIzTTXMIDB7AUrVDNRizL0GeWpXDkDX1+ifL/nLVUk+YCP7XwXOdJHdiwfjGfUZVuMPg+qwrIMLYTq6xjC5uuZrOR+NtluL7SX3u10ZnyV5pYKLIM+OpUu29RGzy3gJVgEQKBgCC9vXS7P9RHTAxYEG4WOzv0tjFUtPOsaHenvNbc7nVe2Kkre0/TO+EtnuhINmJp2y5UEve6cLK2sPnbT8raarjPuomDfN0hwEx3jZd+rPdB/tdRH0LMLBu28TlzHllJYjbINn+NXc0adbqeuA4ziXTZow5yX5J+i9dy55A1bvie 修改为你自己的信息
-----END RSA PRIVATE KEY-----''', -----END RSA PRIVATE KEY-----''',
'ALI_PUBLIC_KEY': '''-----BEGIN CERTIFICATE----- 'ALI_PUBLIC_KEY': '''-----BEGIN CERTIFICATE-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkru1ulQV1v4q+q38nyzgkdd3evf7C1/Ipu6K+ZFb5FiuxJ7mildkBSuKz/8+TRd+tjgk2lfc2ehK5pja3cxDO/nb25sBoWiU09rtxgXLehLsgRRhatbICrlOnYxg5aiB5odAp3NMRqore4lnVYwfIyL9M49I0G/NbQzYjUQvAQJsnHwc6a6Kuqi1CwR1WXI0sDF9w7KXC4vRFFIUTwI4bVq4HQWI7NhbgEajHM/j6D6Bh/OMcTYnJJzCja0WmZRe5flfCsELlPESOCWUMbYoaNfBzpNvvyOpmRgs9jgy2WY9SeaB9hxwkpr8tOd2Sc7j3221JKCyDaFAX+4zPy7/fQIDAQAB 修改为你自己的信息
-----END CERTIFICATE-----''', -----END CERTIFICATE-----''',
'APP_NOTIFY_URL': '{{APP_NOTIFY_URL}}', # 支付支付回调URL 'APP_NOTIFY_URL': '{{APP_NOTIFY_URL}}', # 支付支付回调URL
'RETURN_URL': '{{PAY_SUCCESS_URL}}', # 支付前端页面回调URL 'RETURN_URL': '{{PAY_SUCCESS_URL}}', # 支付前端页面回调URL
@ -350,40 +330,15 @@ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkru1ulQV1v4q+q38nyzgkdd3evf7C1/Ipu6K
{ {
'TYPE': 'WX', 'TYPE': 'WX',
'NAME': 'wxpay', 'NAME': 'wxpay',
'ENABLED': True, 'ENABLED': False,
'AUTH': { 'AUTH': {
'APP_ID': "wx390e5985fd3699e6", 'APP_ID': "修改为你自己的信息",
'MCH_ID': "1608486112", 'MCH_ID': "修改为你自己的信息",
'SERIAL_NO': "27DADA4D2921CDD66B8B20A68276F09B90754922", 'SERIAL_NO': "修改为你自己的信息",
'APP_PRIVATE_KEY': '''-----BEGIN PRIVATE KEY----- 'APP_PRIVATE_KEY': '''-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDbXhNoHljrkS8T 修改为你自己的信息
jXg3+tTkaoOol8FDt0jSGckhzX46gkS16CWYwTthBKurfFtynsJe4uDOphS1ge/r
QEU3+rWNxqa8o6gHSpp2UTYAz/1oYOlXuSa4NA1uD47lmVZJzad2ybWDSsoeRjFj
c+X6F0ZiE3FmdN1iHz8NmbP99foih4smv15X+wX5DrsuLuPVHNB4D0fqvY7P5PO3
wUQXWQNezCYzpPoXX2H/UkyFEFZhWk6/9z3aAkYmqfd6IWPHewOqnVoQRKmo5bXb
yWbB+QIl/HcSfNtq869s5lLGR2Rl2UX8IFXCcXnRPhSAVIeWfXN26Pc9dz9N4VTU
yiZ8Y6AHAgMBAAECggEABdS0U1orJufPBogGIAbMzd1+7mZKPtCKYPtKe1mI92kr
BmLLTQol1+hV39MIYz2RERCaxSNo/YIcrHYi4OALH1+eYvk+qCL1hBuYgeEFbVbW
HPzQ6KiJitljBPtUbdXHk8K8zmaYhMF84pXcEQ+5UTYPF5gXoloORQBG5oM5SN2g
2GTgYw1cpDzzRRwnmpvYd1ZydYNj8m6k7I2L1pwzRS/6/whz1sScpfh+91w1IVM0
WT+pPSdiVtQ7ktmvcTrWj7eNIbcsptZ1QgSV3UHkU0xzLG9N1TqJdHOquunXRS7V
iw/4NgXveXSTSQrmmZVS+Kdyc+z1iDqwOXmE7hjioQKBgQD7js+40NCAPLYXEfer
UFvZ6kem9mJIzAUdeTdK4BjYJrcU+UsXRmcWJIPI9HWSr31f/fSfu2SKyBa6+dFF
SeNHuHPPqQAXrsNuhFG1wcKoNybk7KrsQlXheK7br42565Tegz3UXOKMrnPPlukH
ZZdlYmwBFjEJvIr9jxJvJoW6qQKBgQDfPb697vR8ruLaecPmsq920iC3AQRanYFK
dW6U98JkCN9A/LXA1jGyDTEhtlja+5Ylp0M1EnlcZ079Jnvek5haSNnD0xb1nMy8
P/o0/eWWArTgfjfiJeq1tSdrinGhNz0+Vty74wnbS+5P18H23I5jBlGX5hyNkU4L
axUfJM9jLwKBgCo/1xVkRNB04eRICT/FlFeqKHSbRvCRC37iv+2ca6/J+M/V+s2i
7mdipJuYqzKCtNztayt0rrM8Xczzbjlj6n8+NH05FiHkIUCriomrTEUyVh72vNJH
ZeMjgMK23mfOcEda5YSIQSh9mEfSQbsTTfUiLZ+VGZFYEEP7xo3Se31ZAoGBAJas
LPYytq8EtrYwowktJwJydoQt2otybRYdRmKjCn/MASrypZWeu/Hpt3SCh1xdnAyT
5OeILYMxcv2noMksIxMkwl3KNl/V0dVo9O4ZQ4DJGN3AMuWfI9g6iX2q9mCSUPKn
W9owNbHegN1AyXhdinjJhf6Y4EKohN1uC9Z2WMcfAoGAW90Z2LkqG2fen+R62syP
aaInnu9bitb9rVENCNGXQHdWmIYBMM5zrg8nX8xNJ+yeGQhgxE+YeSq4FOpe0JkA
daWIhg++OHN2MBRutj7oL/AFAxyu467YA5+itEJLHNATbOr/s13S66nePNXox/hr
bIX1aWjPxirQX9mzaL3oEQI=
-----END PRIVATE KEY-----''', -----END PRIVATE KEY-----''',
'API_V3_KEY': '60DbP621a9C3162dDd4AB9c2O15a005L', 'API_V3_KEY': '修改为你自己的信息',
'APP_NOTIFY_URL': '{{APP_NOTIFY_URL}}', # 支付支付回调URL 'APP_NOTIFY_URL': '{{APP_NOTIFY_URL}}', # 支付支付回调URL
'RETURN_URL': '{{PAY_SUCCESS_URL}}', # 支付前端页面回调URL 'RETURN_URL': '{{PAY_SUCCESS_URL}}', # 支付前端页面回调URL
'SUBJECT': '向 FLY分发平台 充值', 'SUBJECT': '向 FLY分发平台 充值',

@ -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,22 @@
#!/bin/bash
function cleanup()
{
local pids=`jobs -p`
if [[ "${pids}" != "" ]]; then
kill ${pids} >/dev/null 2>/dev/null
fi
}
action="${1-start}"
service="${2-all}"
trap cleanup EXIT
rm -f tmp/*.pid
if [[ "$action" == "bash" || "$action" == "sh" ]];then
bash
else
python manage.py "${action}" "${service}" -u nginx -usm 1
fi

@ -195,9 +195,6 @@ STATIC_URL = '/statics/'
# python manage.py collectstatic 收集到的静态文件 # python manage.py collectstatic 收集到的静态文件
STATIC_ROOT = os.path.join(BASE_DIR, "api-static") STATIC_ROOT = os.path.join(BASE_DIR, "api-static")
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "statics")
]
# Media配置 # Media配置
MEDIA_URL = "files/" MEDIA_URL = "files/"

@ -1,25 +1,24 @@
celery==5.1.0 celery==5.2.7
django-celery-results==2.0.1 django-celery-results==2.4.0
django-celery-beat==2.2.0 django-celery-beat==2.3.0
Django==3.2.3 Django==4.0.8
djangorestframework==3.12.4 djangorestframework==3.14.0
djangorestframework-xml==2.0.0 djangorestframework-xml==2.0.0
django-simple-captcha==0.5.14 django-simple-captcha==0.5.17
mysqlclient==2.0.3 mysqlclient==2.1.1
django-redis==4.12.1 django-redis==5.2.0
dnspython==2.1.0 dnspython==2.2.1
oss2==2.14.0 oss2==2.16.0
aliyun-python-sdk-sts==3.0.2 aliyun-python-sdk-sts==3.1.0
qiniu==7.4.1 qiniu==7.9.0
xmltodict==0.12.0 xmltodict==0.13.0
pyOpenSSL==20.0.1 pyOpenSSL==22.1.0
paramiko==2.7.2 paramiko==2.11.0
PyJWT==2.1.0 PyJWT==2.5.0
drf-extensions==0.7.0 python-daemon==2.3.1
python-daemon==2.2.3 psutil==5.9.2
psutil==5.6.6 flower==1.2.0
flower==1.0.0
django-proxy==1.2.1 django-proxy==1.2.1
uWSGI==2.0.20 uWSGI==2.0.20
django-cors-headers==3.10.1 django-cors-headers==3.13.0
django-filter==21.1 django-filter==22.1

@ -1 +1 @@
python36 python36-devel redis mariadb-devel gcc-c++ gcc openssl-devel wget python39 python39-devel redis mariadb-devel gcc-c++ gcc openssl-devel wget

@ -58,9 +58,14 @@ django.setup()
# #
# a = WeChatInfo.objects.filter(openid='11').values('nickname') # a = WeChatInfo.objects.filter(openid='11').values('nickname')
# print(a) # print(a)
from xsign.utils.ctasks import auto_check_ios_developer_active #
from api.models import *
auto_check_ios_developer_active() app_obj = Apps.objects.first()
print(app_obj.updated_time)
app_obj.description='111'
app_obj.save(update_fields=["description","updated_time"])
app_obj = Apps.objects.first()
print(app_obj.updated_time)
# from common.notify.wx import check_apple_developer_devices # from common.notify.wx import check_apple_developer_devices
# #
# user_obj = UserInfo.objects.filter(uid='47ed855a8e6411ec83f100163e1bfc45').first() # user_obj = UserInfo.objects.filter(uid='47ed855a8e6411ec83f100163e1bfc45').first()

@ -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 api.models import Apps, UserInfo
from common.base.daobase import AESCharField 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): class AppIOSDeveloperInfo(models.Model):
@ -75,6 +75,12 @@ class UDIDsyncDeveloper(models.Model):
(DeviceStatus.PROCESSING, '处理中'), (DeviceStatus.INELIGIBLE, '不合格')) (DeviceStatus.PROCESSING, '处理中'), (DeviceStatus.INELIGIBLE, '不合格'))
status = models.CharField(choices=status_choices, verbose_name="设备状态", default=DeviceStatus.DISABLED, max_length=16) 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: class Meta:
verbose_name = 'iOS开发平台同步设备信息' verbose_name = 'iOS开发平台同步设备信息'
verbose_name_plural = "iOS开发平台同步设备信息" verbose_name_plural = "iOS开发平台同步设备信息"

@ -11,7 +11,7 @@ from django.db.models import Count, Sum, Q
from api.models import AppReleaseInfo, UserInfo from api.models import AppReleaseInfo, UserInfo
from common.base.baseutils import is_valid_phone 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 common.core.sysconfig import Config, UserConfig
from xsign.models import APPSuperSignUsedInfo, UDIDsyncDeveloper, AppUDID, APPToDeveloper, AppIOSDeveloperInfo, \ from xsign.models import APPSuperSignUsedInfo, UDIDsyncDeveloper, AppUDID, APPToDeveloper, AppIOSDeveloperInfo, \
IosDeveloperPublicPoolBill, IosDeveloperBill, DeveloperDevicesID, AppleDeveloperToAppUse, DeveloperAppID, \ IosDeveloperPublicPoolBill, IosDeveloperBill, DeveloperDevicesID, AppleDeveloperToAppUse, DeveloperAppID, \
@ -70,8 +70,8 @@ def check_super_sign_permission(user_obj):
return True return True
def check_ipa_is_latest_sign(app_obj, developer_obj=None): def check_ipa_is_latest_sign(app_obj, developer_obj=None, app_udid_obj=None):
if AppUDID.objects.filter(app_id=app_obj, udid__developerid=developer_obj, 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(): sign_status__lt=SignStatus.SIGNATURE_PACKAGE_COMPLETE).first():
return return
release_obj = AppReleaseInfo.objects.filter(app_id=app_obj, is_master=True).first() release_obj = AppReleaseInfo.objects.filter(app_id=app_obj, is_master=True).first()
@ -106,7 +106,8 @@ def update_or_create_developer_udid_info(device_obj, developer_obj):
"product": device_obj.name, "product": device_obj.name,
"udid": device_obj.udid, "udid": device_obj.udid,
"version": device_obj.model, "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) 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): 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 u_count = developer_obj.usable_number
return d_count if d_count > u_count else u_count 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 = [] result_info = []
for dev_obj in developer_obj_lists: for dev_obj in developer_obj_lists:
other_used, flyapp_used, _ = get_developer_udided(dev_obj) 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({ result_info.append({
'other_used': other_used, 'other_used': other_used,
'flyapp_used': flyapp_used, 'flyapp_used': flyapp_used,
'usable_number': usable_number(dev_obj), 'usable_number': usable_number(dev_obj),
'use_number': get_use_number(dev_obj), 'use_number': get_use_number(dev_obj),
'status': dev_obj.status, 'status': dev_obj.status,
'abnormal_register': dev_obj.abnormal_register 'abnormal_register': dev_obj.abnormal_register,
'device_class_count': device_class_count
}) })
use_num = { use_num = {
@ -150,7 +154,8 @@ def get_developer_devices(developer_obj_lists, user_obj):
"may_sign_number": 0, # 可以被用来签名数 "may_sign_number": 0, # 可以被用来签名数
"can_other_used": 0, # 开发者已经使用,但是平台未使用设备数【状态为1】 "can_other_used": 0, # 开发者已经使用,但是平台未使用设备数【状态为1】
"used_number": 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 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['can_other_used'] += info['other_used']
use_num['used_number'] += info['use_number'] use_num['used_number'] += info['use_number']
use_num['may_sign_number'] += (info['usable_number'] - info['flyapp_used'] - info['other_used']) 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 return use_num

@ -4,6 +4,7 @@ from django.db.models import Sum
from rest_framework import serializers from rest_framework import serializers
from common.base.baseutils import AppleDeveloperUid, get_choices_dict from common.base.baseutils import AppleDeveloperUid, get_choices_dict
from common.constants import DeviceClass
from common.core.sysconfig import Config from common.core.sysconfig import Config
from xsign import models from xsign import models
from xsign.utils.modelutils import get_developer_udided, get_use_number from xsign.utils.modelutils import get_developer_udided, get_use_number
@ -18,6 +19,7 @@ class DeveloperSerializer(serializers.ModelSerializer):
exclude = ["id", "user_id", "p8key"] exclude = ["id", "user_id", "p8key"]
developer_used_number = serializers.SerializerMethodField() developer_used_number = serializers.SerializerMethodField()
developer_other_device_used_number = serializers.SerializerMethodField()
developer_used_other_number = serializers.SerializerMethodField() developer_used_other_number = serializers.SerializerMethodField()
use_number = serializers.SerializerMethodField() use_number = serializers.SerializerMethodField()
app_used_count = serializers.SerializerMethodField() app_used_count = serializers.SerializerMethodField()
@ -46,7 +48,10 @@ class DeveloperSerializer(serializers.ModelSerializer):
return 0 return 0
def get_developer_used_number(self, obj): 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): def get_developer_used_other_number(self, obj):
return get_developer_udided(obj)[0] 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.base.magic import run_function_by_locker, call_function_try_attempts, magic_wrapper, MagicCacheData
from common.cache.state import CleanErrorBundleIdSignDataState from common.cache.state import CleanErrorBundleIdSignDataState
from common.cache.storage import RedisCacheBase 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.core.sysconfig import Config, UserConfig
from common.notify.notify import sign_failed_notify, sign_unavailable_developer_notify, sign_app_over_limit_notify 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, \ 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") developer_obj_lists = developer_obj_lists.all().distinct().order_by("created_time")
for developer_obj in developer_obj_lists: 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: if apple_to_app:
apple_to_app_obj = AppleDeveloperToAppUse.objects.filter(app_id=app_obj, apple_to_app_obj = AppleDeveloperToAppUse.objects.filter(app_id=app_obj,
developerid=developer_obj).first() developerid=developer_obj).first()
if apple_to_app_obj: 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: 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) can_used_developer_pk_list.append(developer_obj.pk)
else: 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() 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 and app_udid_obj.sign_status >= SignStatus.PROFILE_DOWNLOAD_COMPLETE:
if app_udid_obj.sign_status == SignStatus.SIGNATURE_PACKAGE_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}' d_result['msg'] = f'udid {udid} exists app_id {app_obj}'
logger.warning(d_result) logger.warning(d_result)
return True, 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): 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) resign_app_obj = IosUtils.get_resign_obj(user_obj, developer_obj)
org_file = os.path.join(MEDIA_ROOT, release_obj.release_id + ".ipa") 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:'签名包检测失败,或许文件下载失败'" msg = f"{user_obj} {developer_obj} {app_obj} sign_ipa failed ERROR:'签名包检测失败,或许文件下载失败'"
logger.error(msg) logger.error(msg)
return False, msg return False, msg
@ -624,6 +626,15 @@ class IosUtils(object):
udid=udid_obj, udid=udid_obj,
sign_status=SignStatus.PROFILE_DOWNLOAD_COMPLETE).update( sign_status=SignStatus.PROFILE_DOWNLOAD_COMPLETE).update(
sign_status=SignStatus.SIGNATURE_PACKAGE_COMPLETE) 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) del_cache_response_by_short(app_obj.app_id)
MagicCacheData.invalid_cache(app_obj.app_id) MagicCacheData.invalid_cache(app_obj.app_id)
return True return True

@ -146,4 +146,6 @@ class AppCanSignView(APIView):
authentication_classes = [ExpiringTokenAuthentication, ] authentication_classes = [ExpiringTokenAuthentication, ]
def get(self, request): def get(self, request):
if check_super_sign_permission(request.user):
return ApiResponse(data={'sign': True}) return ApiResponse(data={'sign': True})
return ApiResponse(data={'sign': False})

@ -15,7 +15,7 @@ from rest_framework.views import APIView
from api.models import Apps, AppDownloadToken from api.models import Apps, AppDownloadToken
from api.utils.modelutils import get_redirect_server_domain, add_remote_info_from_request, \ from api.utils.modelutils import get_redirect_server_domain, add_remote_info_from_request, \
get_app_download_uri get_app_download_uri, get_preview_short_config
from api.utils.response import BaseResponse from api.utils.response import BaseResponse
from common.base.baseutils import get_real_ip_address, make_random_uuid, get_server_domain_from_request, AesBaseCrypt from common.base.baseutils import get_real_ip_address, make_random_uuid, get_server_domain_from_request, AesBaseCrypt
from common.cache.storage import TaskStateCache from common.cache.storage import TaskStateCache
@ -56,12 +56,16 @@ class IosUDIDView(APIView):
stream_f = str(request.body) stream_f = str(request.body)
format_udid_info = udid_bytes_to_dict(stream_f) format_udid_info = udid_bytes_to_dict(stream_f)
logger.info(f"short {short} receive new udid {format_udid_info}") logger.info(f"short {short} receive new udid {format_udid_info}")
org_short = short
short = get_preview_short_config(None, short)
server_domain = get_redirect_server_domain(request) server_domain = get_redirect_server_domain(request)
try: try:
app_obj = Apps.objects.filter(short=short, app_id=app_id).first() app_obj = Apps.objects.filter(short=short, app_id=app_id).first()
if app_obj: 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): if p_token and verify_token(p_token, app_obj.app_id, True):
server_domain = get_app_download_uri(request, app_obj.user_id, app_obj, preview=False)
if app_obj.issupersign and app_obj.user_id.supersign_active: if app_obj.issupersign and app_obj.user_id.supersign_active:
res = check_app_permission(app_obj, BaseResponse()) res = check_app_permission(app_obj, BaseResponse())
if res.code != 1000: if res.code != 1000:
@ -76,7 +80,7 @@ class IosUDIDView(APIView):
# return Response('ok') # return Response('ok')
data = { data = {
'format_udid_info': format_udid_info, 'format_udid_info': format_udid_info,
'short': short, 'short': org_short,
'app_id': app_id, 'app_id': app_id,
'client_ip': client_ip, 'client_ip': client_ip,
'r_token': make_token(app_obj.app_id, time_limit=60, key='receive_udid', force_new=True) 'r_token': make_token(app_obj.app_id, time_limit=60, key='receive_udid', force_new=True)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save