Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot] 8aa8653df2
Bump async from 2.6.3 to 2.6.4 in /fir_admin 3 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. 13
      fir_admin/src/views/appinfos/list.vue
  35. 2
      fir_admin/src/views/login/index.vue
  36. 84
      fir_admin/src/views/userinfos/UserDetail.vue
  37. 5
      fir_admin/src/views/userinfos/list.vue
  38. 54
      fir_admin/src/views/wxbind/Detail.vue
  39. 17
      fir_admin/src/views/wxbind/list.vue
  40. 6
      fir_admin/yarn.lock
  41. 47
      fir_client/README.md
  42. 1
      fir_client/package.json
  43. 6
      fir_client/public/index.html
  44. 6
      fir_client/public/short.html
  45. BIN
      fir_client/src/assets/github.png
  46. BIN
      fir_client/src/assets/wxchat.jpg
  47. 14
      fir_client/src/components/FirFooter.vue
  48. 3
      fir_client/src/components/FirHeader.vue
  49. 5
      fir_client/src/components/FirLogin.vue
  50. 55
      fir_client/src/components/apps/FirAppInfosBase.vue
  51. 5
      fir_client/src/components/apps/FirAppInfosbaseinfo.vue
  52. 110
      fir_client/src/components/apps/FirAppInfossecurity.vue
  53. 11
      fir_client/src/components/apps/FirAppInfostimeline.vue
  54. 23
      fir_client/src/components/apps/FirApps.vue
  55. 26
      fir_client/src/components/base/AppleDeveloperBindApp.vue
  56. 47
      fir_client/src/components/base/BindDomain.vue
  57. 15
      fir_client/src/components/index/FirContact.vue
  58. 4
      fir_client/src/components/index/FirIndexBody.vue
  59. 9
      fir_client/src/components/index/FirIndexTem.vue
  60. 16
      fir_client/src/components/index/FirNews.vue
  61. 78
      fir_client/src/components/user/FirSuperSignBase.vue
  62. 90
      fir_client/src/components/user/FirUserDomain.vue
  63. 470
      fir_client/src/components/user/FirUserDownload.vue
  64. 11
      fir_client/src/components/user/FirUserQrcode.vue
  65. 1206
      fir_client/src/components/user/FirUserStorage.vue
  66. 21
      fir_client/src/restful/download.js
  67. 125
      fir_client/src/restful/index.js
  68. 5
      fir_client/src/router/index.js
  69. 6
      fir_client/src/utils/base/utils.js
  70. 22
      fir_client/src/utils/format.js
  71. 16
      fir_client/src/utils/index.js
  72. 84
      fir_client/vue.config.js
  73. 37
      fir_ser/Dockerfile
  74. 11
      fir_ser/Dockerfile.cli
  75. 18
      fir_ser/admin/utils/serializer.py
  76. 2
      fir_ser/admin/views/celery_flower.py
  77. 14
      fir_ser/admin/views/login.py
  78. 3
      fir_ser/admin/views/order.py
  79. 11
      fir_ser/admin/views/storage.py
  80. 47
      fir_ser/api/base_views.py
  81. 14
      fir_ser/api/management/commands/add_user_download_times.py
  82. 2
      fir_ser/api/management/commands/services/command.py
  83. 15
      fir_ser/api/management/commands/services/services/base.py
  84. 5
      fir_ser/api/migrations/0001_initial.py
  85. 50
      fir_ser/api/migrations/0002_auto_20220215_2129.py
  86. 78
      fir_ser/api/migrations/0006_auto_20220513_0843.py
  87. 32
      fir_ser/api/migrations/0007_auto_20220725_1612.py
  88. 73
      fir_ser/api/models.py
  89. 39
      fir_ser/api/tasks.py
  90. 8
      fir_ser/api/urls.py
  91. 14
      fir_ser/api/utils/apputils.py
  92. 15
      fir_ser/api/utils/ctasks.py
  93. 102
      fir_ser/api/utils/modelutils.py
  94. 162
      fir_ser/api/utils/serializer.py
  95. 81
      fir_ser/api/utils/utils.py
  96. 39
      fir_ser/api/views/apps.py
  97. 124
      fir_ser/api/views/domain.py
  98. 23
      fir_ser/api/views/download.py
  99. 2
      fir_ser/api/views/getip.py
  100. 14
      fir_ser/api/views/login.py
  101. Some files were not shown because too many files have changed in this diff Show More

2
.gitignore vendored

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

@ -1,63 +1,100 @@
### 用与应用分发,苹果超级签名
#### 部署前准备
- 备案域名【至少需要一个域名,以下可通过子域名部署】
- API域名
- 前端web域名
- 下载页域名
- 下载页域名可配置多个
- 存储域名(使用阿里云oss存储)
- ssl证书
- API域名证书
- 存储域名证书(使用阿里云oss存储)
- 前端web域名证书(可选)
- Centos8Stream 服务器
- 如果使用oss存储,则带宽为1M,若使用本地存储,则带宽越大越好
- 如果使用超级签,最低配置为2cpu 4G内存,若干不使用签名,则1cpu2G就行
- 阿里云短信或极光短信服务【可选一个,主要用与注册,重置密码】
- 阿里云短信
- 极光短信
- 邮箱服务【可选,用与注册,重置密码,通知信息】
- 阿里云OSS存储【可选】
- [sts授权配置](https://help.aliyun.com/document_detail/100624.html)
- 阿里云CDN【可选,用与加速访问】
- 极验验证【可选,滑动验证服务】
- 微信公众号【可选,用与微信扫描登录】
- 阿里云支付【可选,用与购买下载次数】
- 微信支付【可选,用与购买下载次数】
#### 自用搭建建议
- 阿里云服务器需要1cpu 2G内存,无需系统盘,如果使用超级签,可以适当增加配置
- 需要阿里云OSS存储和阿里云CDN,并且OSS存储和阿里云服务器部署同一个地区
- 可以申请一个极验进行滑动验证,或者开启验证码验证
- 阿里云备案域名:api和前端可以使用一个域名,下载页单独域名
#### 部署必备资料
- 域名证书
- web域名和证书
- api域名和证书
- 下载页域名(可配置证书)
- 存储域名和证书
- 本地存储,则该域名和证书可以和api域名证书一致
- 阿里云oss存储
- 开启cdn,需要新域名和证书
- 不开启,无需域名和证书
- Centos8Stream 服务器
## 部署方式
### 1.[Docker 部署](./doc/docker.md) 【推荐】
### 2.[本地部署](./doc/local.md)
### 功能预览
![img_1.png](./doc/images/img_1.png)
![img_2.png](./doc/images/img_2.png)
![img_3.png](./doc/images/img_3.png)
![img_4.png](./doc/images/img_4.png)
![img_5.png](./doc/images/img_5.png)
![img_6.png](./doc/images/img_6.png)
![img_7.png](./doc/images/img_7.png)
![img_8.png](./doc/images/img_8.png)
![img_9.png](./doc/images/img_9.png)
![img_10.png](./doc/images/img_10.png)
![img_11.png](./doc/images/img_11.png)
##### 从git上面下载源码
```shell
cd /data/
git clone https://github.com/nineaiyu/FlyApps
```
### FlyApp 服务器搭建 [python3 环境]
##### 搭建python env 环境
```shell
yum install python36 python36-devel redis mariadb-server mariadb-devel -y
python3 -m venv py3
source py3/bin/activate
```
###### 安装pip包
```shell
cd fir_ser/
pip install -U setuptools pip
pip install -r requirements.txt
```
###### 配置数据库
```
在 config.py 配置redis 和 mysql 数据库
```
###### 迁移数据库
```shell
#如果是mysql,需要做一下配置,如果是sqlite,需要升级sqlite
#记得根据配置创建数据库
#create database flyapp default character set utf8 COLLATE utf8_general_ci;
#grant all on flyapp.* to flyuser@'127.0.0.1' identified by 'flypwd00oo.1';
python manage.py makemigrations
python manage.py migrate
```
### FlyApp zsign ipa重签名工具安装
```shell
yum install openssl-devel -y
yum install gcc-c++ gcc -y
wget https://github.com/nineaiyu/zsign/archive/refs/tags/v1.1.2.tar.gz
tar xvf v1.1.2.tar.gz
cd zsign-1.1.2/
g++ *.cpp common/*.cpp -lcrypto -O3 -std=c++11 -o zsign
cp zsign /usr/bin/
```
### FlyApp web端搭建
###### npm编译环境
```shell
yum install npm
npm install -g n
n 12.13 # 安装12版本的node ,最新版本会有问题
npm install -g yarn
```
###### 编译web端和下载页
```shell
cd FlyApps/fir_client/
vim vue.config.js #修改api接口地址
# pro_base_env 正式环境信息
# dev_base_env 开发环境信息
yarn install
yarn build index # web打包
yarn build short # 下载也打包
```
###### web目录操作
```shell
# web目录: /www/wwwroot/fly.dvcloud.xin/
cd /www/wwwroot/fly.dvcloud.xin/
cp -a /data/FlyApps/fir_client/dist/* .
cp -a /data/FlyApps/fir_download/dist/* .
```
##### 更新sqllite
```shell
tar xvf sqlite-autoconf-3310100.tar.gz
cd sqlite-autoconf-3310100
./configure --prefix=/usr/local/sqlite
make -j4
make install
rm -rf /usr/bin/sqlite3
ln -s /usr/local/sqlite/bin/sqlite3 /usr/bin/sqlite3
ll /usr/bin/sqlite3
echo "/usr/local/sqlite/lib" > /etc/ld.so.conf.d/sqlite3.conf
ldconfig
```

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

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

@ -1,102 +0,0 @@
### 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.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

@ -1,166 +0,0 @@
### 本地部署前准备
系统 | 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

@ -0,0 +1,51 @@
##### 从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
```

@ -1,67 +0,0 @@
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

@ -1,31 +0,0 @@
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"

@ -1,48 +0,0 @@
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

@ -1,7 +0,0 @@
#!/bin/bash
#
#
for i in nginx flyapps mariadb redis buildclient buildshort buildadmin ;do echo $i;docker rm -f $i;done
docker network rm flyapps

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

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

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

@ -1,43 +0,0 @@
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

@ -1,108 +0,0 @@
#
# 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

@ -1,25 +0,0 @@
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

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

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

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

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

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

@ -4,7 +4,7 @@
<el-input v-model="listQuery.bundle_id" placeholder="Bundle_Id" style="width: 250px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.name" placeholder="应用名称" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.short" placeholder="短连接" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<!-- <el-input v-model="listQuery.domain_name" placeholder="应用专属访问域名" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />-->
<!-- <el-input v-model="listQuery.domain_name" placeholder="应用专属访问域名" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />-->
<el-select v-if="type_choices" v-model="listQuery.type" placeholder="应用类型" clearable class="filter-item" style="width: 120px" @change="handleFilter">
<el-option v-for="item in type_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
@ -75,16 +75,11 @@
{{ scope.row.short }}
</template>
</el-table-column>
<el-table-column label="下载次数" width="80" align="center">
<el-table-column label="下载次数" width="100" align="center">
<template slot-scope="scope">
{{ scope.row.count_hits }}
</template>
</el-table-column>
<el-table-column label="当天下载次数" width="80" align="center">
<template slot-scope="scope">
{{ scope.row.today_hits_count }}
</template>
</el-table-column>
<el-table-column label="应用大小" width="80" align="center">
<template slot-scope="scope">
{{ scope.row.master_release.binary_size }}
@ -212,7 +207,7 @@ export default {
},
methods: {
go_download_url(app_info) {
window.open(app_info.preview_url, '_blank', '')
window.open(app_info.preview_url + '/' + app_info.short, '_blank', '')
},
deleteApp(app_id) {
this.$confirm('此操作将永久删除该应用, 是否继续?', '提示', {
@ -221,7 +216,7 @@ export default {
type: 'warning'
}).then(() => {
this.listLoading = true
deleteApp(app_id).then(() => {
deleteApp(app_id).then(response => {
this.$message.success('删除成功')
this.fetchData()
this.listLoading = false

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

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

@ -60,11 +60,6 @@
{{ scope.row.download_times }}
</template>
</el-table-column>
<el-table-column label="当天苹果|安卓下载次数" width="100" align="center">
<template slot-scope="scope">
{{ scope.row.today_hits_count.ios_today_hits_count }} | {{ scope.row.today_hits_count.android_today_hits_count }}
</template>
</el-table-column>
<el-table-column label="应用数量" width="100" align="center">
<template slot-scope="scope">
<router-link :to="{name: 'app_info_list',params:{user_id:scope.row.id}}">

@ -25,54 +25,45 @@
</el-col>
</el-row>
</el-form-item>
<el-form-item label="创建时间" prop="timestamp">
<el-row :gutter="20">
<el-col :span="8">
<el-date-picker :value="postForm.created_time" disabled type="datetime" />
<el-form-item label="地址">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.address" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="订阅时间" prop="timestamp">
<el-form-item label="创建时间" prop="timestamp">
<el-row :gutter="20">
<el-col :span="8">
<el-date-picker :value="postForm.subscribe_time*1000" disabled type="datetime" />
<el-date-picker :value="postForm.created_time" type="datetime" disabled />
</el-col>
</el-row>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="用户头像" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-image
:preview-src-list="[postForm.head_img_url]"
:src="postForm.head_img_url"
fit="contain"
style="width: 100px; height: 100px"
/>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="扫码登录" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.enable_login" disabled />
<el-image :src="postForm.head_img_url" :preview-src-list="[postForm.head_img_url]" fit="contain" style="width: 100px; height: 100px" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="消息通知" label-width="160px">
<el-form-item label="订阅状态" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.enable_notify" disabled />
<el-select v-model="postForm.subscribe" class="filter-item">
<el-option v-for="item in wxbind_state_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="订阅状态" label-width="160px">
<el-form-item label="性别" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.subscribe" class="filter-item" disabled>
<el-option v-for="item in wxbind_state_choices" :key="item.id" :label="item.name" :value="item.id" />
<el-select v-model="postForm.sex" class="filter-item">
<el-option v-for="item in wxbind_sex_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
@ -93,12 +84,17 @@
<script>
import { getWxBindInfos, updateWxBindInfo } from '@/api/wxbind'
const wxbind_state_choices = [
{ id: false, name: '未订阅' },
{ id: true, name: '已订阅' }
]
const wxbind_sex_choices = [
{ id: 0, name: '未知' },
{ id: 1, name: '男' },
{ id: 2, name: '女' }
]
const defaultForm = {
user_id: undefined,
address: undefined,
@ -107,10 +103,9 @@ const defaultForm = {
id: undefined,
nickname: undefined,
openid: undefined,
sex: undefined,
subscribe: undefined,
subscribe_time: undefined,
enable_login: undefined,
enable_notify: undefined
subscribe_time: undefined
}
export default {
@ -122,6 +117,7 @@ export default {
loading: false,
is_edit: false,
wxbind_state_choices,
wxbind_sex_choices,
id: ''
}
},

@ -50,14 +50,14 @@
{{ scope.row.openid }}
</template>
</el-table-column>
<el-table-column label="允许扫码登录" min-width="100px" align="center">
<el-table-column label="地址" min-width="200px" align="center">
<template slot-scope="scope">
{{ scope.row.enable_login }}
{{ scope.row.address }}
</template>
</el-table-column>
<el-table-column label="允许消息通知" min-width="100px" align="center">
<el-table-column label="性别" min-width="60px" align="center">
<template slot-scope="scope">
{{ scope.row.enable_notify }}
{{ scope.row |sexLableFilter }}
</template>
</el-table-column>
<el-table-column class-name="status-col" label="订阅状态" width="95" align="center">
@ -107,6 +107,12 @@ const wxbind_state_choices = [
{ id: true, name: '已订阅' }
]
const wxbind_sex_choices = [
{ id: 0, name: '未知' },
{ id: 1, name: '男' },
{ id: 2, name: '女' }
]
export default {
name: 'WxBindInfo',
components: { Pagination },
@ -133,6 +139,9 @@ export default {
statusLableFilter(row) {
return baseFilter(row.subscribe, wxbind_state_choices)
},
sexLableFilter(row) {
return baseFilter(row.sex, wxbind_sex_choices)
},
statusFilter(status) {
const statusMap = {
true: 'success',

@ -1976,9 +1976,9 @@ async-validator@~1.8.1:
babel-runtime "6.x"
async@^2.6.2:
version "2.6.3"
resolved "https://registry.npm.taobao.org/async/download/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
integrity sha1-1yYl4jRKNlbjo61Pp0n6gymdgv8=
version "2.6.4"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==
dependencies:
lodash "^4.17.14"

@ -39,48 +39,5 @@ yarn lint
```
## deploy nginx
#### short deploy nginx
```shell
location / {
try_files $uri $uri/ /short.html;
}
```
#### index.html
```html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="refresh" content="3;URL=https://flyapps.cn">
<title>FLY分发平台</title>
<style>
.container {
width: 60%;
margin: 10% auto 0;
background-color: #f0f0f0;
padding: 2% 5%;
border-radius: 10px
}
ul {
padding-left: 20px;
}
ul li {
line-height: 2.3
}
a {
color: #20a53a
}
</style>
</head>
<body>
<div class="container">
<h3>这是分发平台下载默认页,正在跳转 <a href="https://flyapps.cn">首页</a></h3>
</div>
</body>
</html>
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

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

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

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

@ -28,12 +28,22 @@
</template>
<script>
const footer = process.env.base_env.footer
export default {
name: "FirFooter",
data() {
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号',
},
}
}
}
}

@ -51,7 +51,6 @@
<el-dropdown-item command="userinfo">个人资料</el-dropdown-item>
<el-dropdown-item command="apitoken">API token</el-dropdown-item>
<el-dropdown-item command="setdomian">设置域名</el-dropdown-item>
<el-dropdown-item command="download">下载配置</el-dropdown-item>
<el-dropdown-item command="setnotify">消息中心</el-dropdown-item>
<el-dropdown-item v-if="$store.state.userinfo.role>1" command="setadvert">宣传广告
</el-dropdown-item>
@ -136,8 +135,6 @@ export default {
} else if (command === 'setdomian') {
this.$router.push({"name": 'FirUserDomain'})
} else if (command === 'download') {
this.$router.push({"name": 'FirUserDownload'})
} else if (command === 'setadvert') {
this.$router.push({"name": 'FirUserAdvert'})
} else if (command === 'myorder') {

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

@ -8,37 +8,33 @@
</div>
<div class="badges">
<el-tooltip content="复制到剪切板" placement="top">
<span v-clipboard:copy="appinfos.preview_url"
<span v-clipboard:copy="short_full_url"
v-clipboard:success="copy_success"
class="bundleid short"
>&nbsp;{{ appinfos.preview_url }}</span>
</el-tooltip>
<el-tooltip content="点击查看下载码">
<el-popover
placement="right"
width="288">
<div style="text-align: center; margin: 0">
<vue-qr ref="qr"
:correctLevel="qrinfo.correctLevel" :logoCornerRadius="qrinfo.logoCornerRadius"
:logoScale="qrinfo.logoScale"
:logoSrc="icon_url"
:margin="qrinfo.margin" :size="266"
:text="appinfos.preview_url">
</vue-qr>
<el-button size="small" type="primary" @click="save_qr()">保存本地</el-button>
</div>
<span slot="reference" class="short ng-scope">下载码</span>
</el-popover>
>&nbsp;{{ short_full_url }}</span>
</el-tooltip>
<el-popover
placement="right"
width="288">
<div style="text-align: center; margin: 0">
<vue-qr ref="qr"
:correctLevel="qrinfo.correctLevel" :logoCornerRadius="qrinfo.logoCornerRadius"
:logoScale="qrinfo.logoScale"
:logoSrc="icon_url"
:margin="qrinfo.margin" :size="266"
:text="short_url(appinfos)">
</vue-qr>
<el-button size="small" type="primary" @click="save_qr()">保存本地</el-button>
</div>
<span slot="reference" class="short ng-scope">下载码</span>
</el-popover>
<span>{{ master_release.release_type |getapptype }}</span>
<el-tooltip placement="top">
<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 content="下载量" placement="top">
<span><i class="el-icon-cloudy"/><b class="short">{{ appinfos.count_hits }}</b></span>
</el-tooltip>
<span class="bundleid ng-binding">BundleID<b class="ng-binding">
<el-tooltip content="复制到剪切板" placement="top">
@ -127,12 +123,13 @@ export default {
margin: 20
},
icon_url: "",
appinfos: {status: 1, preview_url: ''},
appinfos: {status: 1},
master_release: {},
allapp: [],
activity: {
editing: false
},
short_full_url: '',
}
},
methods: {
@ -152,6 +149,9 @@ export default {
//
a.dispatchEvent(new MouseEvent('click'))
},
short_url(appinfo) {
return appinfo.preview_url + '/' + appinfo.short;
},
setfunactive(item, index) {
for (let key in this.$refs) {
if (key === "qr") continue;
@ -168,7 +168,7 @@ export default {
}
},
appDownload(appinfo) {
window.open(appinfo.preview_url, '_blank', '');
window.open(this.short_url(appinfo), '_blank', '');
},
defaulttimeline() {
this.setfunactive('timeline', 5);
@ -219,6 +219,7 @@ export default {
this.master_release = data.data.master_release;
this.appinfos["icon_url"] = this.master_release.icon_url;
this.$store.dispatch('doucurrentapp', this.appinfos);
this.short_full_url = this.appinfos.preview_url + "/" + this.appinfos.short;
} else if (data.code === 1003) {
this.$router.push({name: 'FirApps'});
} else {

@ -12,10 +12,9 @@
</el-button>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="使用空间" style="width: 56%">
<el-input v-model="currentapp.storage_used" :disabled="true" :value="currentapp.storage_used"/>
</el-form-item>
<el-form-item label="应用名称" style="width: 56%">
<el-input v-model="currentapp.name"/>
</el-form-item>

@ -1,6 +1,5 @@
<template>
<div style="margin-top: 20px;width: 66%;margin-left: 8%">
<el-dialog
:close-on-click-modal="false"
:close-on-press-escape="false"
@ -25,25 +24,6 @@
title="下载授权码生成"
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-item label="指定授权码" style="width: 400px">
<el-tag>当存在指定授权码授权码长度和生成数量被禁用</el-tag>
@ -83,12 +63,6 @@
</el-tooltip>
<el-tag style="margin-left: 5px">绑定设备之后二次自动进行设备授权下载应用</el-tag>
</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>
<span slot="footer">
<el-button @click="cancelDownloadToken">取消</el-button>
@ -135,26 +109,12 @@
prop="token"
>
<template slot-scope="scope">
<el-popover
placement="top-start"
trigger="hover"
width="200">
<p>
<el-tag v-clipboard:copy="scope.row.token" v-clipboard:success="copy_success" type="success">
<el-link :underline="false">点击复制授权码</el-link>
</el-tag>
</p>
<p>
<el-tag v-clipboard:copy="format_copy_text(scope.row.token)" v-clipboard:success="copy_success">
<el-link :underline="false">点击复制授权码下载链接</el-link>
</el-tag>
</p>
<el-link slot="reference" :underline="false">{{ scope.row.token }}</el-link>
</el-popover>
<el-tooltip content="点击复制">
<el-link v-clipboard:copy="format_copy_text(scope.row.token)" v-clipboard:success="copy_success"
:underline="false">
{{ scope.row.token }}
</el-link>
</el-tooltip>
</template>
</el-table-column>
@ -180,7 +140,7 @@
align="center"
label="最大可用次数"
prop="max_limit_count"
width="110">
width="130">
<template slot-scope="scope">
<span v-if="scope.row.max_limit_count === 0">不限次数</span>
<span v-else>{{ scope.row.max_limit_count }}</span>
@ -201,24 +161,16 @@
align="center"
label="设备ID"
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>
</div>
<el-table-column
align="center"
fixed="right"
label="操作"
width="90">
width="120">
<template slot-scope="scope">
<div>
<el-tooltip :content="get_reset_title()" placement="top">
<el-tooltip content="重置使用次数" placement="top">
<el-button
size="mini"
@click="resetDownloadUsed(scope.row)">重置
@ -361,44 +313,18 @@ export default {
app_download_token_list: [],
loading: false,
makeTokenVisible: false,
addUdidVisible: false,
tokendisable: false,
pagination: {"currentPage": 1, "total": 0, "pagesize": 10},
dpwdsearch: '',
dpwdtitle: '输入下载授权码',
makeTokenInfo: {
token: '',
token_length: 6,
token_number: 20,
token_max_used_number: 0,
bind_status: false,
udids: ''
},
multipleSelection: [],
makeTokenInfo: {token: '', token_length: 6, token_number: 20, token_max_used_number: 0, bind_status: false},
multipleSelection: []
}
},
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) {
return this.currentapp.preview_url + "?password=" + token
return this.currentapp.preview_url + "/" + this.currentapp.short + "?password=" + token
},
copy_success() {
this.$message.success('复制剪切板成功');
@ -429,16 +355,8 @@ export default {
this.multipleSelection = val;
},
cancelDownloadToken() {
this.makeTokenInfo = {
token: '',
token_length: 6,
token_number: 20,
token_max_used_number: 0,
bind_status: false,
udids: ''
}
this.makeTokenInfo = {token: '', token_length: 6, token_number: 20, token_max_used_number: 0, bind_status: false}
this.makeTokenVisible = false
this.addUdidVisible = false
},
resetDownloadUsed(info) {
appDownloadToken(data => {

@ -4,7 +4,7 @@
:color="app.master_color"
:timestamp="app.created_time|formatdatatimeline"
placement="top">
<el-card :body-style="{border: app.is_master ? '#2abb9d 1px solid' : ''}">
<el-card>
<div class="directive-view-release">
<i v-if="! app.is_master" class="el-icon-cloudy"/>
@ -80,7 +80,7 @@
<el-button v-if="!currentapp.issupersign" class="tooltip-top" @click="previewRelease(app)"><i
class="el-icon-view"/> <span
class="ng-binding">预览该版本</span>
class="ng-binding">预览</span>
</el-button>
<el-button v-if="! app.is_master" class="tooltip-top" @click="make_master_release(app)">
@ -142,7 +142,12 @@ export default {
);
},
previewRelease(app) {
window.open(this.currentapp.preview_url + '?release_id=' + app.release_id, '_blank', '');
let routeData = this.$router.resolve({
name: 'FirDownload',
params: {short: this.currentapp.short},
query: {release_id: app.release_id}
});
window.open(this.currentapp.preview_url + routeData.href, 'target', '');
},
getAppTimelineFun(act = '') {
const loading = this.$loading({

@ -222,7 +222,7 @@
<el-row :gutter="20" style="margin-top: 10px;">
<el-col :span="18">
<el-input v-model="short">
<template slot="prepend">{{ analyseappinfo.short_domain_name }}</template>
<template slot="prepend">{{ analyseappinfo.short_domain_name }}/</template>
</el-input>
</el-col>
</el-row>
@ -445,8 +445,8 @@
<div>
<el-tooltip placement="top">
<div slot="content">
1.下载应用 {{ times_info.every_size|diskSize }} 消耗 {{ times_info.base_times / 100 }} 下载次数<br>
2.私有存储模式下载应用 {{ times_info.every_size|diskSize }} 消耗 {{ times_info.private_times / 100 }} 下载次数<br>
1.下载应用100M消耗一次下载次数<br>
2.超级签下载下载消耗次数翻倍<br>
</div>
<span class="name">购买次数</span>
</el-tooltip>
@ -491,10 +491,7 @@
<div class=" app-animator">
<div class="card app card-ios">
<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 class="down-icon el-icon-cloudy"> {{ r.count_hits }}</i>
<i v-if="r.type === 1" class="type-icon iconfont icon-ios"/>
<i v-if="r.type === 0" class="type-icon iconfont icon-android2"/>
@ -749,11 +746,7 @@ export default {
loading.close();
}, {
'methods': 'POST',
'data': {
"bundleid": analyseappinfo.bundleid,
"type": analyseappinfo.type,
"filesize": analyseappinfo.filesize
}
'data': {"bundleid": analyseappinfo.bundleid, "type": analyseappinfo.type}
});
} else {
@ -1164,16 +1157,12 @@ export default {
this.$router.push({name: 'FirAppInfostimeline', params: {id: app.app_id}})
},
appDownload(app) {
window.open(app.preview_url, '_blank', '');
window.open(app.preview_url + '/' + app.short, '_blank', '');
}
}, computed: {
getDelappTitle() {
return `删除应用 ${this.delapp.name}`
},
times_info() {
let times_info = this.$store.state.userinfo.times_info
return times_info ? times_info : {}
}
},
filters: {
diskSize,

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

@ -21,7 +21,6 @@
</div>
请联系域名管理员前往 <strong>{{ domain_name }}</strong> 域名 DNS 管理后台添加如下 CNAME 记录
<el-table
v-loading="is_loading"
:data="domain_tData"
border
stripe
@ -52,11 +51,6 @@
prop="dns"
width="300">
<template slot-scope="scope">
<el-tooltip content="该记录值为私有下载服务器">
<el-tag v-if="scope.row.is_private" style="margin-right: 5px" type="small">Private</el-tag>
</el-tooltip>
<el-tooltip content="点击复制到剪贴板">
<el-link v-if="scope.row.dns" v-clipboard:copy="scope.row.dns"
v-clipboard:success="copy_success"
@ -86,7 +80,7 @@
<el-col :span="6">
<el-button plain size="small" style="margin-top: 8px" type="danger"
@click="remove_domain">
解除绑定并删除记录
解除绑定
</el-button>
</el-col>
</el-row>
@ -104,14 +98,13 @@
<el-col :span="6">
<el-button plain size="small" style="margin-top: 8px" type="danger"
@click="remove_domain">
解除绑定并删除记录
解除绑定
</el-button>
</el-col>
</el-row>
</div>
<el-table
v-loading="is_loading"
:data="domain_tData"
border
stripe
@ -133,23 +126,10 @@
label="记录值"
prop="dns"
width="300">
<template slot-scope="scope">
<el-tooltip content="该记录值为私有下载服务器">
<el-tag v-if="scope.row.is_private" style="margin-right: 5px" type="small">Private</el-tag>
</el-tooltip>
<el-tooltip content="点击复制到剪贴板">
<el-link v-if="scope.row.dns" v-clipboard:copy="scope.row.dns"
v-clipboard:success="copy_success"
:underline="false">{{ scope.row.dns }}
</el-link>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<div v-if="!bind_status" style="text-align: center;margin: 30px 0">
<el-button :disabled="is_loading" plain type="success" @click="check_cname">已经修改配置再次检查绑定</el-button>
<el-button plain type="success" @click="check_cname">已经修改配置再次检查绑定</el-button>
</div>
</div>
@ -159,8 +139,8 @@
<el-button :disabled="bind_status|| active===1 "
@click="last">上一步
</el-button>
<el-button v-if="force_bind" :disabled="is_loading" @click="next(1)">强制绑定</el-button>
<el-button v-else :disabled="is_loading" @click="next(0)">下一步</el-button>
<el-button v-if="force_bind" @click="next(1)">强制绑定</el-button>
<el-button v-else @click="next(0)">下一步</el-button>
</div>
@ -193,18 +173,17 @@ export default {
c_domain_name: {
type: String,
default: ''
}
},
},
data() {
return {
active: 1,
bind_status: false,
bind_domain_sure: true,
domain_tData: [{'type': 'CNAME', 'host': 'xxx', 'dns': 'demo.xxx.cn', 'is_private': false}],
domain_tData: [{'type': 'CNAME', 'host': 'xxx', 'dns': 'demo.xxx.cn'}],
domain_name: '',
force_bind: false,
b_t_msg: '您的账户',
is_loading: false
}
},
mounted() {
@ -224,13 +203,10 @@ export default {
this.$message.success('复制剪切板成功');
},
check_cname() {
this.is_loading = true
domainFun(data => {
this.is_loading = false
if (data.code === 1000) {
if (this.active++ > 2) this.active = 3;
this.bind_status = true;
this.$message.success("绑定成功")
if (!this.app_id) {
if (this.domain_type === 1) {
this.$store.dispatch("dodomainshow", false);
@ -282,7 +258,7 @@ export default {
this.domain_name = data.data.domain_name;
}
if (data.data.domain_record) {
this.format_domain_tData(data.data);
this.format_domain_tData(data.data.domain_record);
if (this.active++ > 2) this.active = 3;
}
if (data.data.is_enable) {
@ -305,15 +281,14 @@ export default {
domain_name_list.splice(domain_name_list.length - 2, 2);
this.domain_tData[0].host = domain_name_list.join(".")
}
this.domain_tData[0].dns = cname_domain.domain_record;
this.domain_tData[0].is_private = cname_domain.is_private;
this.domain_tData[0].dns = cname_domain;
},
next(force_bind) {
if (this.active === 1) {
domainFun(data => {
if (data.code === 1000) {
if (data.data && data.data.domain_record) {
this.format_domain_tData(data.data);
if (data.data && data.data.cname_domain) {
this.format_domain_tData(data.data.cname_domain);
if (this.active++ > 2) this.active = 3;
}
} else {

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

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

@ -21,10 +21,10 @@
<el-link @click="$router.push({name:'FirApps'})">登录</el-link>
</el-col>
<el-col :span="8">
<el-link @click="$router.push({name:'FirContact'})">联系我们</el-link>
<el-link @click="$router.push({name:'FirRegist'})">注册</el-link>
</el-col>
<el-col :span="8">
<el-link @click="goGithub">Github</el-link>
<el-link @click="$router.push({name:'FirContact'})">联系我们</el-link>
</el-col>
</el-row>
</el-col>
@ -64,11 +64,6 @@ export default {
'line-height': '45px',
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>
</div>
<div class="nav text-center">
<a class="active" href="#">开发历程</a>
<a href="#">发展足迹</a>
<a class="active" href="#">公司新闻</a>
<a href="#">行业动态</a>
</div>
<ul class="news-container">
<li v-for="(item,index) in newsList" :key="index">
@ -40,18 +40,6 @@ export default {
show: false,
newsList: [
{
id: '008',
title: 'ios16系统',
introduce: '超级签名字支持ios16系统签名,只需要手机开启调试模式,即可安装打开',
date: '09-22',
year: '2022'
}, {
id: '007',
title: '私有下载页部署',
introduce: '支持私有下载页部署,方便快捷,稳定访问',
date: '05-18',
year: '2022'
}, {
id: '001',
title: '现有版本',
introduce: '经过好长时间的编写,平台已经支持超级签名,权限管理,自定义域名,自定义存储',

@ -521,10 +521,9 @@
{{ developer_used_info.may_sign_number - developer_used_info.can_sign_number }}
</el-link>
<el-link :underline="false">当前可签名设备数 {{ developer_used_info.can_sign_number }}</el-link>
<el-link :underline="false">当前非苹果手机设备数 {{ developer_used_info.device_class_count }}</el-link>
</div>
<el-link slot="reference" :underline="false">
还剩{{ developer_used_info.can_sign_number + developer_used_info.device_class_count }} 可用
还剩{{ developer_used_info.can_sign_number }} 可用
</el-link>
</el-popover>
<el-progress
@ -590,10 +589,7 @@
:underline="false">开发者账户ID: {{ scope.row.issuer_id }}
</el-link>
</el-tooltip>
<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>开发者账户已使用设备数: {{ scope.row.developer_used_number }}</p>
<p>开发者账户可用设备数: {{ 100 - scope.row.developer_used_number }}</p>
<p>由于您设置可用设备数: {{ scope.row.usable_number }} ,所以现在可用设备数: {{
scope.row.usable_number - scope.row.developer_used_number > 0
@ -877,22 +873,17 @@
v-model="udidsearch"
clearable
placeholder="输入UDID"
style="width: 30%;margin-right: 5px;margin-bottom: 10px"/>
style="width: 30%;margin-right: 30px;margin-bottom: 10px"/>
<el-input
v-model="appidseach"
clearable
placeholder="输入开发者用户ID"
style="width: 30%;margin-right: 5px;margin-bottom: 10px"/>
<el-select v-model="devicestatus" clearable multiple
style="width: 30%;margin-right: 30px;margin-bottom: 10px"/>
<el-select v-if="status_choices" v-model="devicestatus" clearable multiple
placeholder="设备状态"
style="width: 110px;margin-right: 5px" @change="handleCurrentChange(1)">
style="width: 120px;margin-right: 30px" @change="handleCurrentChange(1)">
<el-option v-for="item in device_status_choices" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
<el-select v-model="deviceclass" clearable multiple
placeholder="设备类型"
style="width: 110px;margin-right: 5px" @change="handleCurrentChange(1)">
<el-option v-for="item in device_class_choices" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
<el-button icon="el-icon-search" type="primary" @click="handleCurrentChange(1)">
搜索
</el-button>
@ -945,7 +936,7 @@
align="center"
label="设备版本"
prop="version"
width="180">
width="200">
</el-table-column>
<el-table-column
align="center"
@ -967,12 +958,6 @@
</el-tooltip>
</template>
</el-table-column>
<el-table-column
align="center"
label="设备类型"
prop="device_class"
width="80">
</el-table-column>
<el-table-column
:formatter="deviceformatter"
align="center"
@ -1325,18 +1310,15 @@
v-model="uidsearch"
clearable
placeholder="输入用户UID"
style="width: 30%;margin-right: 20px;margin-bottom: 10px"/>
<el-select v-model="operatestatus" clearable placeholder="操作状态"
style="width: 120px;margin-right: 20px" @change="handleCurrentChange(1)">
<el-option v-for="item in operate_status_choices" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
style="width: 30%;margin-right: 30px;margin-bottom: 10px"/>
<el-button icon="el-icon-search" type="primary" @click="handleCurrentChange(1)">
搜索
</el-button>
<el-button icon="el-icon-s-unfold" plain @click="transferVisible=true">
设备共享
</el-button>
<div style="width: 35%;margin-right: 10px;float:right">
<div style="width: 40%;margin-right: 30px;float:right">
<el-link :underline="false">共享签名池设备数量{{ balance_info.all_balance }} 已经使用 {{
balance_info.used_balance
}}
@ -1388,13 +1370,7 @@
label="设备数量"
prop="number"
width="100">
<template slot-scope="scope">
<el-popover content="点击修改共享设备数量" trigger="hover">
<el-link slot="reference" :underline="false" @click="editShareDevice(scope.row)">
<span v-if="scope.row.cancel">-</span> {{ scope.row.number }}
</el-link>
</el-popover>
</template>
</el-table-column>
<el-table-column
:formatter="deviceformatter"
@ -1445,6 +1421,7 @@
<el-date-picker
v-model="timerangesearch"
:picker-options="pickerOptions"
align="right"
end-placeholder="结束日期"
range-separator="至"
start-placeholder="开始日期"
@ -1823,7 +1800,7 @@ import {
personalConfigInfo,
signoperatemessage
} from "@/restful";
import {format_choices, getUserInfoFun, removeArray} from "@/utils";
import {format_choices, getUserInfoFun, removeAaary} from "@/utils";
import AppleDeveloperBindApp from "@/components/base/AppleDeveloperBindApp";
import {format_time} from "@/utils/base/utils";
@ -1858,7 +1835,6 @@ export default {
Bundleidsearch: "",
appidseach: "",
devicestatus: "",
deviceclass: "",
operatestatus: "",
operate_status_choices: [],
uidsearch: "",
@ -1944,7 +1920,6 @@ export default {
appletoapp_title: '',
status_choices: [],
device_status_choices: [],
device_class_choices: [],
read_only_mode: 'off',
developer_status_choice: [],
multipleSelection: [],
@ -1961,14 +1936,6 @@ export default {
}
},
methods: {
editShareDevice(row) {
// if (row.status !== 2) return
if (!row.cancel) return
this.target_number = row.number;
this.target_uid = row.target_user.uid
this.checkuid(this.target_uid)
this.transferVisible = true
},
changeSignConfig(info) {
personalConfigInfo(data => {
if (data.code === 1000) {
@ -1979,7 +1946,7 @@ export default {
}
}, {
methods: 'PUT', data: {config_key: info.key, config_value: info.value}
}, 'super_sign')
})
},
updateSignConfig() {
personalConfigInfo(data => {
@ -1991,7 +1958,7 @@ export default {
}
}, {
methods: 'DELETE'
}, 'super_sign')
})
},
signConfigFun() {
personalConfigInfo(data => {
@ -2003,7 +1970,7 @@ export default {
}
}, {
methods: 'GET'
}, 'super_sign')
})
},
saveblackdevice() {
blackDeviceUtil(data => {
@ -2101,16 +2068,12 @@ export default {
DeviceTransferBillInfo(data => {
if (data.code === 1000) {
this.transferInfo = data.data
if (this.transferInfo.number) {
this.target_number = this.transferInfo.number
}
this.cantransfer = false
} else {
this.cantransfer = true
this.transferInfo = {}
this.$message.error("查询失败 " + data.msg)
}
}, {'methods': 'PUT', data: {uid: uid, 'act': 'check'}})
}, {'methods': 'PUT', data: {uid: uid}})
},
get_developer_uid(uid) {
if (uid && uid.indexOf(':') > -1) {
@ -2370,7 +2333,6 @@ export default {
"issuer_id": this.appidseach,
"act": "syncalldevice",
"devicestatus": this.devicestatus,
"deviceclass": this.deviceclass,
"udidsearch": this.udidsearch
}
});
@ -2531,7 +2493,6 @@ export default {
this.iosdevicebillFun({"methods": "GET", "data": data})
} else if (tabname === "transferbill") {
data.uidsearch = this.uidsearch.replace(/^\s+|\s+$/g, "");
data.operatestatus = this.operatestatus;
this.deviceTransferFun({"methods": "GET", "data": data})
} else if (tabname === "devicesrank") {
if (this.timerangesearch && this.timerangesearch.length === 2) {
@ -2542,7 +2503,6 @@ export default {
this.iosdevicerankFun({"methods": "GET", "data": data})
} else if (tabname === "iosudevices") {
data.devicestatus = JSON.stringify(this.devicestatus);
data.deviceclass = JSON.stringify(this.deviceclass);
this.iosudevicesFun("GET", data)
} else if (tabname === 'operatemsg') {
data.operate_status = this.operatestatus;
@ -2676,7 +2636,6 @@ export default {
DeviceTransferBillInfo(data => {
if (data.code === 1000) {
this.transfer_bill_lists = data.data;
this.operate_status_choices = data.status_choices
this.pagination.total = data.count;
if (data.balance_info) {
this.balance_info = data.balance_info;
@ -2749,7 +2708,7 @@ export default {
this.pagination.total = data.count;
} else if (action === "DELETE") {
if (scope) {
this.app_udid_lists = removeArray(this.app_udid_lists, scope.row)
this.app_udid_lists = removeAaary(this.app_udid_lists, scope.row)
}
} else if (action === 'POST') {
this.$message.success("操作成功");
@ -2814,7 +2773,6 @@ export default {
this.developer_udevices_lists = data.data;
this.pagination.total = data.count;
this.device_status_choices = data.status_choices;
this.device_class_choices = data.device_class_choices;
} else {
this.refreshactiveFun()
}

@ -48,9 +48,6 @@
label="绑定域名"
prop="domain_name">
<template slot-scope="scope">
<el-tooltip content="该域名解析到了私有下载服务器上面">
<el-tag v-if="scope.row.is_private" style="margin-right: 5px" type="small">Private</el-tag>
</el-tooltip>
<el-tooltip content="点击复制到剪贴板">
<el-link v-if="scope.row.domain_name" v-clipboard:copy="scope.row.domain_name"
v-clipboard:success="copy_success"
@ -80,28 +77,6 @@
</template>
</el-table-column>
<el-table-column
align="center"
label="开启https"
prop="is_https"
width="110">
<template slot-scope="scope">
<el-tooltip content="点击关闭" placement="top">
<div slot="content">
<span v-if="scope.row.is_https">
点击关闭 HTTPS 访问
</span>
<span v-else>
点击开启支持 HTTPS 访问
</span>
</div>
<el-button v-if="scope.row.is_https" size="small" type="success"
@click="changeHttpsFun(scope.row, false)">已开启
</el-button>
<el-button v-else size="small" type="info" @click="changeHttpsFun(scope.row, true)">未开启</el-button>
</el-tooltip>
</template>
</el-table-column>
<el-table-column
@ -126,6 +101,7 @@
</template>
</el-table-column>
<el-table-column
align="center"
label="跳转权重"
@ -178,7 +154,7 @@
<script>
import {domaininfo, personalConfigInfo} from "@/restful";
import {domaininfo} from "@/restful";
import BindDomain from "@/components/base/BindDomain";
import {format_choices, getUserInfoFun} from '@/utils'
import {format_time} from "@/utils/base/utils";
@ -196,71 +172,9 @@ export default {
domain_type_choices: [],
current_domain_info: {'domain_type': 1, 'app_id': null},
loading: false,
configVisible: false,
config_lists: [],
short_download_uri: ''
}
},
methods: {
changeHttpsFun(domain_info, is_https) {
domain_info['is_https'] = is_https
domaininfo(data => {
if (data.code === 1000) {
this.$message.success("https支持修改成功")
} else {
this.$message.error("修改失败 " + data.msg)
}
}, {methods: 'PUT', data: domain_info})
},
configFun() {
personalConfigInfo(data => {
if (data.code === 1000) {
if (data.data.length === 1) {
this.short_download_uri = data.data[0].value
}
} else {
this.$message.error("获取数据失败了 " + data.msg)
}
}, {
methods: 'GET'
}, 'short_download_uri')
personalConfigInfo(data => {
if (data.code === 1000) {
this.config_lists = data.data
this.configVisible = true
} else {
this.$message.error("获取数据失败了 " + data.msg)
}
}, {
methods: 'GET'
}, 'preview_route')
},
changeConfig(info) {
personalConfigInfo(data => {
if (data.code === 1000) {
this.$message.success("操作成功")
this.configFun()
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'PUT', data: {config_key: info.key, config_value: info.value}
}, 'preview_route')
},
updateConfig() {
personalConfigInfo(data => {
if (data.code === 1000) {
this.$message.success("操作成功")
this.configFun()
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'DELETE'
}, 'preview_route')
},
saveWeight(domain_info) {
domaininfo(data => {
if (data.code === 1000) {

@ -1,470 +0,0 @@
<template>
<el-main>
<el-dialog
:close-on-click-modal="false"
:close-on-press-escape="false"
:visible.sync="configVisible"
center
title="下载页部署配置"
width="666px">
<el-card v-for="info in config_lists" :key="info.key" class="box-card" shadow="hover"
style="margin-bottom: 20px">
<div slot="header" class="clearfix">
<span><el-tag size="medium" type="info">配置KEY</el-tag> <el-tag size="medium">{{ info.key }}</el-tag></span>
<div style="float: right">
<el-switch
v-model="info.value"
active-color="#13ce66"
active-text="启用"
active-value="true"
inactive-color="#ff4949"
inactive-text="关闭"
inactive-value="false"
@change="changeConfig(info)">
</el-switch>
</div>
</div>
<el-tag size="medium" type="info">描述信息</el-tag>
{{ info.title }}
</el-card>
<el-divider></el-divider>
<el-card style="margin-top: 20px">
<div slot="header" class="clearfix">
<el-tag size="medium" type="success">下载页源码及操作文档</el-tag>
</div>
<div v-if="short_download_list.length>0">
<div style="margin: 10px" v-for="short_download_uri in short_download_list" :key="short_download_uri.key">
<el-tag size="medium" type="info">{{short_download_uri.title}}</el-tag>
<el-link style="margin-left: 10px" :href="short_download_uri.value" target="_blank">点击下载</el-link>
</div>
</div>
</el-card>
<div slot="footer" class="dialog-footer">
<el-button @click="updateConfig">恢复默认值</el-button>
<el-button @click="configVisible=false">取消</el-button>
</div>
</el-dialog>
<el-dialog
:close-on-click-modal="false"
:close-on-press-escape="false"
:visible.sync="addCnameVisible"
center
title="添加下载页服务器域名"
width="800px">
<el-steps :active="p_active" finish-status="success" :align-center="true">
<el-step title="添加服务器域名"></el-step>
<el-step title="验证服务器域名所有权"></el-step>
<el-step title="添加成功"></el-step>
</el-steps>
<el-container>
<div v-if="p_active===0" style="margin-top: 20px">
<el-tag style="margin: 10px 5px">1.如果是cdn等含有CNAME直接添加cdn的 CNAME </el-tag>
<el-tag style="margin: 10px 5px">2.如果是服务器需要将域名解析到服务器然后添加该解析的域名</el-tag>
<div style="width: 600px;text-align: center;margin: auto">
<el-form ref="form" :model="addSerInfo" label-width="120px">
<el-form-item label="下载服务器域名">
<el-input v-model="addSerInfo.domain_record" placeholder="cdn的cname或者服务器的域名,必填"></el-input>
</el-form-item>
<el-form-item label="下载服务器地址">
<el-input v-model="addSerInfo.ip_address" placeholder="如果有服务器地址,添加服务器ip,否则填写服务器域名"></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="addSerInfo.description"></el-input>
</el-form-item>
</el-form>
<div style="margin-top: 20px;">
<el-button type="primary" @click="addCnameSer">下一步</el-button>
</div>
</div>
</div>
<div v-else-if="p_active===1" style="margin-top: 30px;text-align: center">
请联系域名管理员前往 <strong>{{ cnameInfo.domain_record }}</strong> 域名 DNS 管理后台添加如下 {{ cnameInfo.r_type }} 记录
<el-table
:data="domain_tData"
border
stripe
v-loading="loading"
style="width: 100%;margin-top: 20px">
<el-table-column
align="center"
label="记录类型"
prop="r_type"
width="100">
</el-table-column>
<el-table-column
align="center"
label="主机记录"
prop="host_r"
>
<template slot-scope="scope">
<el-tooltip content="点击复制到剪贴板">
<el-link v-if="scope.row.host_r" v-clipboard:copy="scope.row.host_r"
v-clipboard:success="copy_success"
:underline="false">{{ scope.row.host_r }}
</el-link>
</el-tooltip>
</template>
</el-table-column>
<el-table-column
align="center"
label="记录值"
prop="cname_r"
width="300">
<template slot-scope="scope">
<el-tooltip content="点击复制到剪贴板">
<el-link v-if="scope.row.cname_r" v-clipboard:copy="scope.row.cname_r"
v-clipboard:success="copy_success"
:underline="false">{{ scope.row.cname_r }}
</el-link>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<el-alert :closable="false"
show-icon
style="margin-top: 30px"
title="请在域名DNS配置成功后,点击“下一步”按钮"
type="warning"/>
<div style="margin-top: 20px;text-align: center;">
<el-button type="primary" @click="p_active=0">上一步</el-button>
<el-button type="primary" @click="checkCnameSer" :disabled="loading">下一步</el-button>
</div>
</div>
</el-container>
</el-dialog>
<div>
<el-input
v-model="search_key"
clearable
placeholder="输入下载服务器域名"
style="width: 30%;margin-right: 30px;margin-bottom: 5px"/>
<el-button icon="el-icon-search" type="primary" @click="handleCurrentChange(1)">
搜索
</el-button>
<div style="float: right">
<el-tooltip content="下载页配置,可以定制部署私有下载页">
<el-button plain type="primary" @click="configFun">
下载页配置
</el-button>
</el-tooltip>
<el-button plain type="primary" @click="addDownloadSer">
添加下载页服务器
</el-button>
</div>
<el-table
v-loading="loading"
:data="cname_info_list"
border
stripe
style="width: 100%">
<el-table-column
align="center"
fixed
label="下载服务器域名"
prop="domain_name">
<template slot-scope="scope">
<el-tooltip content="点击复制到剪贴板">
<el-link v-if="scope.row.domain_record" v-clipboard:copy="scope.row.domain_record"
v-clipboard:success="copy_success"
:underline="false">{{ scope.row.domain_record }}
</el-link>
</el-tooltip>
</template>
</el-table-column>
<el-table-column
align="center"
label="下载服务器地址"
prop="ip_address">
</el-table-column>
<el-table-column
align="center"
label="状态"
prop="is_enable"
width="110">
<template slot-scope="scope">
<el-tooltip v-if="scope.row.is_enable === true" content="已经绑定成功" placement="left-start">
<el-button size="small" type="success">成功
</el-button>
</el-tooltip>
<el-tooltip v-else content="点击激活服务器域名绑定信息" placement="left-start">
<el-button size="small" type="warning" @click="continueBind(scope.row)">继续绑定
</el-button>
</el-tooltip>
</template>
</el-table-column>
<el-table-column
:formatter="format_create_time"
align="center"
label="域名绑定时间"
prop="created_time"
width="170"
>
</el-table-column>
<el-table-column
align="center"
label="备注"
prop="description">
</el-table-column>
<el-table-column
align="center"
label="操作"
prop="is_enable"
width="100">
<template slot-scope="scope">
<el-tooltip content="删除该下载页服务器" placement="left-start">
<el-button size="small" type="danger" @click="delCnameSer(scope.row)">删除
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</div>
<div style="margin-top: 20px;margin-bottom: 20px">
<el-pagination
:current-page.sync="pagination.currentPage"
:page-size="pagination.pagesize"
:page-sizes="[10, 20, 50, 100]"
:total="pagination.total"
layout="total,sizes, prev, pager, next"
@size-change="handleSizeChange"
@current-change="handleCurrentChange">
</el-pagination>
</div>
</el-main>
</template>
<script>
import {dCnameInfoFun, personalConfigInfo} from "@/restful";
import {getUserInfoFun} from '@/utils'
import {format_time} from "@/utils/base/utils";
export default {
name: "FirUserDomain",
data() {
return {
addSerInfo: {},
cnameInfo: {},
domain_tData: [],
p_active: 0,
cname_info_list: [],
search_key: "",
pagination: {"currentPage": 1, "total": 0, "pagesize": 10},
loading: false,
configVisible: false,
addCnameVisible: false,
config_lists: [],
short_download_list: []
}
},
methods: {
continueBind(cname_info) {
this.addSerInfo = cname_info
this.addCnameVisible = true
this.addCnameSer()
},
delCnameSer(cname_info) {
this.$confirm('若下载域名绑定到该解析,同时也会删除绑定的下载域名,是否继续删除?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
dCnameInfoFun(data => {
if (data.code === 1000) {
this.$message.success("删除成功")
this.get_data_from_tabname();
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'DELETE',
data: {'domain_record': cname_info.domain_record}
})
})
},
checkCnameSer() {
this.loading=true
dCnameInfoFun(data => {
this.loading=false
if (data.code === 1000) {
this.$message.success(this.addSerInfo.domain_record + " 添加成功")
this.get_data_from_tabname();
this.addDownloadSer()
this.addCnameVisible = false
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'PUT',
data: {'act': 'check', 'domain_record': this.addSerInfo.domain_record}
})
},
addCnameSer() {
let domain_record = this.addSerInfo.domain_record
let ip_address = this.addSerInfo.ip_address
if (domain_record && domain_record.length > 6 && ip_address && ip_address.length > 6) {
dCnameInfoFun(data => {
if (data.code === 1000) {
this.p_active = 1
this.cnameInfo = data.data
this.domain_tData = [this.cnameInfo]
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'POST',
data: this.addSerInfo
})
} else {
this.$message.error("参数有误")
}
},
addDownloadSer() {
this.addSerInfo = {}
this.cnameInfo = {}
this.p_active = 0
this.addCnameVisible = true
},
configFun() {
personalConfigInfo(data => {
if (data.code === 1000) {
this.short_download_list = data.data
} else {
this.$message.error("获取数据失败了 " + data.msg)
}
}, {
methods: 'GET'
}, 'short_download_uri')
personalConfigInfo(data => {
if (data.code === 1000) {
this.config_lists = data.data
this.configVisible = true
} else {
this.$message.error("获取数据失败了 " + data.msg)
}
}, {
methods: 'GET'
}, 'preview_route')
},
changeConfig(info) {
personalConfigInfo(data => {
if (data.code === 1000) {
this.$message.success("操作成功")
this.configFun()
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'PUT', data: {config_key: info.key, config_value: info.value}
}, 'preview_route')
},
updateConfig() {
personalConfigInfo(data => {
if (data.code === 1000) {
this.$message.success("操作成功")
this.configFun()
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'DELETE'
}, 'preview_route')
},
copy_success() {
this.$message.success('复制剪切板成功');
},
handleSizeChange(val) {
this.pagination.pagesize = val;
this.get_data_from_tabname({
"size": this.pagination.pagesize,
"page": 1
})
},
handleCurrentChange(val) {
this.pagination.currentPage = val;
this.get_data_from_tabname({
"size": this.pagination.pagesize,
"page": this.pagination.currentPage
})
},
get_data_from_tabname(data = {}) {
data.search_key = this.search_key.replace(/^\s+|\s+$/g, "");
this.UserCnameInfoFun(data)
},
UserCnameInfoFun(params) {
this.loading = true;
dCnameInfoFun(data => {
if (data.code === 1000) {
this.cname_info_list = data.data;
this.pagination.total = data.count;
} else {
this.$message.error("域名绑定信息获取失败")
}
this.loading = false;
}, {methods: 'GET', data: params})
},
format_create_time(row) {
return format_time(row.created_time)
},
}, mounted() {
getUserInfoFun(this);
this.get_data_from_tabname();
}
}
</script>
<style scoped>
.el-main {
margin: 20px auto 100px;
width: 1166px;
position: relative;
padding-bottom: 1px;
color: #9b9b9b;
-webkit-font-smoothing: antialiased;
border-radius: 1%;
}
</style>

@ -56,7 +56,7 @@
:logoSrc="appinfo.master_release.icon_url"
:margin="qrinfo.margin" :qid="appinfo.app_id"
:size="200"
:text="appinfo.preview_url">
:text="short_url(appinfo)">
</vue-qr>
<div style="margin: 5px 0 5px">
<i v-if="appinfo.type === 1" class=" type-icon iconfont icon-ios"/>
@ -65,9 +65,9 @@
<span>{{ appinfo.name }}</span>
<div class="bottom clearfix">
<el-popover placement="top" trigger="hover">
<span v-clipboard:copy="appinfo.preview_url"
<span v-clipboard:copy="short_url(appinfo)"
v-clipboard:success="copy_success"
>{{ appinfo.preview_url }}</span>
>{{ short_url(appinfo) }}</span>
<div slot="reference" class="name-wrapper">
<el-button plain size="small" type="primary" @click="go_download(appinfo)">预览</el-button>
<el-button plain size="small" type="primary" @click="save_qr(appinfo)">保存本地</el-button>
@ -213,12 +213,15 @@ export default {
this.isIndeterminate = checkedCount > 0 && checkedCount < this.allQrcodeAppid.length;
},
go_download(appinfo) {
window.open(appinfo.preview_url, '_blank', '');
window.open(this.short_url(appinfo), '_blank', '');
},
qrback(dataUrl, id) {
this.qrcode_img_info[id] = dataUrl;
this.allQrcodeAppid.push(id);
},
short_url(appinfo) {
return appinfo.preview_url + '/' + appinfo.short;
},
save_qr(appinfo) {
let dtype = "I";
if (appinfo.master_release.release_type === 0) {

File diff suppressed because it is too large Load Diff

@ -1,18 +1,11 @@
import Axios from 'axios'
import {geetestbase} from "@/utils/base/utils";
const DOMAIN = process.env.base_env.baseShortUrl;
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
})
Axios.defaults.withCredentials = true;
const DOMAIN = process.env.base_env.baseShortUrl;
const APIPATH = '/api/v1/fir/server';
let USERSEVER = DOMAIN + APIPATH;
function ErrorMsg(error, callBack) {
if (error && error.response) {
@ -49,7 +42,7 @@ function responseMiddleware(data, callBack) {
function getData(methods = 'GET', url, params = {}, callBack) {
if (methods === "PUT") {
service
Axios
.put(url, params)
.then(function (response) {
responseMiddleware(response.data, callBack);
@ -58,7 +51,7 @@ function getData(methods = 'GET', url, params = {}, callBack) {
ErrorMsg(error, callBack);
});
} else if (methods === 'POST') {
service
Axios
.post(url, params)
.then(function (response) {
responseMiddleware(response.data, callBack);
@ -67,7 +60,7 @@ function getData(methods = 'GET', url, params = {}, callBack) {
ErrorMsg(error, callBack);
});
} else
service
Axios
.get(url, {params: params})
.then(function (response) {
responseMiddleware(response.data, callBack);

@ -4,8 +4,8 @@ import router from "@/router";
import {geetestbase} from "@/utils/base/utils";
const Base64 = require('js-base64').Base64;
// Axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
// Axios.defaults.withCredentials = true;
Axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
Axios.defaults.withCredentials = true;
// Axios.defaults.httpsAgent = new https.Agent({
// keepAlive: true
// });
@ -16,17 +16,11 @@ export function requestAbort() {
}
// eslint-disable-next-line no-console
console.log("js build version:" + process.env.base_env.version);
console.log("flyapps js version:" + process.env.base_env.version);
const DOMAIN = process.env.base_env.baseUrl;
const USERSEVER = 'api/v1/fir/server';
// 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
})
const APIPATH = '/api/v1/fir/server';
let USERSEVER = DOMAIN + APIPATH;
export function convertRes2Blob(response) {
// 提取文件名
@ -59,25 +53,31 @@ export function convertRes2Blob(response) {
}
}
service.interceptors.request.use(config => {
if (VueCookies.get('token')) {
// Axios.defaults.headers.common['Authorization'] = localStorage.getItem('access_token');
// console.log(config.headers);
if (!VueCookies.get('auth_token')) {
let token = VueCookies.get('token');
let username = VueCookies.get('username');
VueCookies.set("auth_token", Base64.encode(token + ':' + username), 3600 * 24 * 30);
export function set_auth_token() {
Axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
if (VueCookies.get('token')) {
// Axios.defaults.headers.common['Authorization'] = localStorage.getItem('access_token');
// console.log(config.headers);
if (VueCookies.get('auth_token')) {
config.headers.Authorization = VueCookies.get('auth_token')
} else {
let token = VueCookies.get('token');
let username = VueCookies.get('username');
VueCookies.set("auth_token", Base64.encode(token + ':' + username), 3600 * 24 * 30);
}
}
config.headers.Authorization = VueCookies.get('auth_token')
}
return config;
}, function (error) {
// 对请求错误做些什么
// eslint-disable-next-line no-console
console.log(error)
return Promise.reject(error);
});
// 更改加载的样式
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
}
set_auth_token();
function ErrorMsg(error, callBack) {
if (error && error.response) {
@ -141,7 +141,7 @@ function responseMiddleware(data, callBack) {
function getData(methods, url, params = {}, callBack) {
if (methods === "DELETE") {
service
Axios
.delete(url, {params: params, signal: controller.signal})
.then(function (response) {
responseMiddleware(response.data, callBack);
@ -151,7 +151,7 @@ function getData(methods, url, params = {}, callBack) {
});
} else if (methods === "PUT") {
service
Axios
.put(url, params, {signal: controller.signal})
.then(function (response) {
responseMiddleware(response.data, callBack);
@ -161,7 +161,7 @@ function getData(methods, url, params = {}, callBack) {
});
} else if (methods === 'POST') {
service
Axios
.post(url, params, {signal: controller.signal})
.then(function (response) {
responseMiddleware(response.data, callBack);
@ -170,7 +170,7 @@ function getData(methods, url, params = {}, callBack) {
ErrorMsg(error, callBack);
});
} else if (methods === 'FILE') {
service
Axios
.get(url, {params: params, responseType: 'blob', signal: controller.signal})
.then(function (response) {
convertRes2Blob(response)
@ -179,7 +179,7 @@ function getData(methods, url, params = {}, callBack) {
ErrorMsg(error, callBack);
});
} else {
service
Axios
.get(url, {params: params, signal: controller.signal})
.then(function (response) {
responseMiddleware(response.data, callBack);
@ -390,42 +390,6 @@ export function cleanStorageData(callBack, params) {
);
}
/**用户存储清理操作 */
export function shareStorageData(callBack, params) {
getData(
params.methods,
USERSEVER + '/storage/share',
params.data,
data => {
callBack(data);
}
);
}
/**用户存储清理操作 */
export function configStorageData(callBack, params) {
getData(
params.methods,
USERSEVER + '/storage/config',
params.data,
data => {
callBack(data);
}
);
}
/**用户存储空间兑换操作 */
export function exchangeStorageData(callBack, params) {
getData(
params.methods,
USERSEVER + '/storage/exchange',
params.data,
data => {
callBack(data);
}
);
}
/**用户个人信息 */
export function userinfos(callBack, params) {
getData(
@ -447,10 +411,10 @@ export function getapppicurl(app_id) {
}
export function getuploadurl(domain_name = null) {
if (domain_name && !domain_name.match('http[s?]:///$')) {
return domain_name + '/' +USERSEVER + '/upload'
if (domain_name) {
return domain_name + APIPATH + '/upload'
} else {
return DOMAIN + USERSEVER + '/upload';
return USERSEVER + '/upload';
}
}
@ -605,17 +569,6 @@ export function domainFun(callBack, params) {
);
}
/**用户添加下载页服务器*/
export function dCnameInfoFun(callBack, params) {
getData(
params.methods,
USERSEVER + '/cname_info',
params.data,
data => {
callBack(data);
}
);
}
/**微信用户绑定 */
export function wxutils(callBack, params) {
@ -703,10 +656,10 @@ export function notifyConfigInfo(callBack, params) {
}
/**消息配置 */
export function personalConfigInfo(callBack, params, super_sign) {
export function personalConfigInfo(callBack, params) {
getData(
params.methods,
USERSEVER + '/personal/config?config_type=' + super_sign,
USERSEVER + '/personal/config',
params.data,
data => {
callBack(data);
@ -722,7 +675,7 @@ export function geetest(self, uid, params, callback) {
/** 超级签名************************************************相关api */
let SIGNSEVER = '/api/v1/fir/xsign';
let SIGNSEVER = DOMAIN + '/api/v1/fir/xsign';
/** 超级签名--检测该账户是否可开启超级签名 */
export function checkCanSign(callBack, params) {

@ -168,11 +168,6 @@ const router = new VueRouter({
name: 'FirUserDomain',
meta: {label: '绑定域名详情'},
component: () => import("@/components/user/FirUserDomain"),
}, {
path: 'download',
name: 'FirUserDownload',
meta: {label: '下载页设置'},
component: () => import("@/components/user/FirUserDownload"),
},
{
path: 'advert',

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

@ -1,22 +0,0 @@
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 removeArray(_arr, _obj) {
export function removeAaary(_arr, _obj) {
let length = _arr.length;
for (let i = 0; i < length; i++) {
if (_arr[i] == _obj) {
@ -356,8 +356,8 @@ export function geetest(self, uid, params, callback, http_func = loginFun) {
message: res.msg,
type: 'error'
});
}, captchaObj => {
captchaObj.verify()
// eslint-disable-next-line no-unused-vars
}, _ => {
loading.close()
})
}
@ -626,7 +626,7 @@ export function diskSize(num) {
let k = 1024; //设定基础容量大小
let sizeStr = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; //容量单位
let i = 0; //单位下标和次幂
for (let l = 0; l < 9; l++) { //因为只有8个单位所以循环八次
for (let l = 0; l < 8; l++) { //因为只有8个单位所以循环八次
if (num / Math.pow(k, l) < 1) { //判断传入数值 除以 基础大小的次幂 是否小于1,这里小于1 就代表已经当前下标的单位已经不合适了所以跳出循环
break; //小于1跳出循环
}
@ -662,11 +662,3 @@ export function format_choices(key, obj) {
}
return "未知"
}
export function getFormatDate(timestamp) {
let now = new Date(timestamp * 1000)
let y = now.getFullYear()
let m = now.getMonth() + 1
let d = now.getDate()
return y + "-" + (m < 10 ? "0" + m : m) + "-" + (d < 10 ? "0" + d : d) + " " + now.toTimeString().substr(0, 8);
}

@ -1,14 +1,15 @@
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV);
const path = require('path');
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) {
return path.join(__dirname, dir);
}
const argv = process.argv;
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const compress = new CompressionWebpackPlugin(
{
filename: info => {
@ -18,7 +19,7 @@ const compress = new CompressionWebpackPlugin(
threshold: 10240,
test: new RegExp(
'\\.(' +
['js', 'css'].join('|') +
['js','css'].join('|') +
')$'
),
minRatio: 0.8,
@ -27,7 +28,6 @@ const compress = new CompressionWebpackPlugin(
);
const index = {
outputDir: 'dist_index',
// page 的入口
entry: 'src/main.js',
// 模板来源
@ -39,11 +39,10 @@ const index = {
title: 'Fly分发平台',
// 在这个页面中包含的块,默认情况下会包含
// 提取出来的通用 chunk 和 vendor chunk。
chunks: ['chunk-vendors', 'chunk-common', 'index', 'elementui', 'runtime', 'alioss', 'qiniujs', 'qrcodejs2']
chunks: ['chunk-vendors', 'chunk-common', 'index', 'elementui','runtime', 'alioss', 'qiniujs', 'qrcodejs2']
};
// eslint-disable-next-line no-unused-vars
const mshort = {
outputDir: 'dist_mshort',
// page 的入口
entry: 'src/main.short.js',
// 模板来源
@ -58,7 +57,6 @@ const mshort = {
chunks: ['chunk-vendors', 'chunk-common', 'mshort', 'chunk-elementUI', 'runtime']
};
const short = {
outputDir: 'dist_short',
// page 的入口
entry: 'src/short.js',
// 模板来源
@ -73,57 +71,41 @@ const short = {
chunks: ['chunk-vendors', 'chunk-common', 'short', 'chunk-commons', 'runtime']
};
let pages = {index, short, mshort};
let outputDir = 'dist'
let page = argv[3];
if (!page) {
page = 'index'
}
for (const key of Object.keys(pages)) {
if (key === page) {
const tmp = pages[key];
pages = {};
pages[key] = tmp;
outputDir = tmp.outputDir
const page = argv[3];
if (page) {
for (const key of Object.keys(pages)) {
if (key === page) {
const tmp = pages[key];
pages = {};
pages[key] = tmp;
}
}
}
const version = '2.3.9';
const github = 'https://github.com/nineaiyu/FlyApps';
const version='2.3.6';
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加速域名
baseUrl: 'https://flyapps.cn',
index_static: 'https://static.flyapps.cn/index/',
baseShortUrl: 'https://flyapps.top',
short_static: 'https://static.flyapps.top/short/',
version: version,
};
const dev_base_env = {
baseUrl: 'https://app.hehelucky.cn/',
baseUrl: 'https://app.hehelucky.cn',
baseShortUrl: 'https://app.hehelucky.cn',
short_static: '/',
index_static: '/',
baseShortUrl: 'https://app.hehelucky.cn/',
short_static: 'https://app.hehelucky.cn/short/',
version: version,
};
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) {
base_evn = pro_base_env
} else {
base_evn = dev_base_env
}
base_evn.footer_info = footer_info
function get_public_path(pages) {
if (!IS_PROD) {
@ -136,9 +118,9 @@ function get_public_path(pages) {
}
let publicPath = get_public_path(pages);
module.exports = {
pages: pages,
outputDir: outputDir,
productionSourceMap: false, //去除生产环境的productionSourceMap
assetsDir: "static", //静态文件存储位置
lintOnSave: true,
@ -159,13 +141,7 @@ module.exports = {
config
.plugin('define')
.tap(args => {
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
});
args[0]['process.env']['base_env'] = JSON.stringify({baseUrl: base_evn.baseUrl,baseShortUrl:base_evn.baseShortUrl,version:base_evn.version});
return args
});
@ -254,11 +230,7 @@ module.exports = {
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
},
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: {
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
@ -271,7 +243,7 @@ module.exports = {
},
common: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
test: resolve(resolve('src/components')), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true

@ -1,32 +1,41 @@
FROM python:3.9.14-slim
FROM python:3.6.14-slim
# 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.163.com/g' /etc/apt/sources.list \
# && sed -i 's/security.debian.org/mirrors.163.com/g' /etc/apt/sources.list \
RUN apt update \
&& apt-get install g++ wget zip unzip -y \
&& apt-get install libssl-dev openssl libmariadb-dev -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list \
&& sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list \
&& apt update
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
ADD zsign-1.1.2.tar.gz /opt/
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/
COPY zsign-1.1.2.tar.gz /opt/zsign-1.1.2.tar.gz
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/
# install pip
COPY requirements.txt /opt/requirements.txt
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 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 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/
COPY entrypoint.sh entrypoint.sh
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
#EXPOSE 443
#ENTRYPOINT ["./entrypoint.sh"]
#ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf", "-n"]
#CMD ["python", "manage.py", "start", "all","-u","nginx","-usm","1"]
ENTRYPOINT ["/bin/bash", "entrypoint.sh"]
CMD ["python", "manage.py", "start", "all","-u","nginx","-usm","1"]

@ -1,11 +0,0 @@
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"]

@ -4,11 +4,9 @@ import os
from rest_framework import serializers
from api import models
from api.models import Apps
from api.utils.serializer import AppReleaseSerializer, UserInfoSerializer, AppsSerializer, StorageSerializer, \
ThirdWxSerializer, DomainNameSerializer, AppReportSerializer, OrdersSerializer
ThirdWxSerializer, DomainNameSerializer, AppReportSerializer
from common.base.baseutils import get_choices_dict, get_choices_name_from_key
from common.utils.caches import get_app_today_download_times
from common.utils.storage import Storage
logger = logging.getLogger(__name__)
@ -94,18 +92,6 @@ class AdminUserInfoSerializer(UserInfoSerializer):
def get_app_count(self, obj):
return models.Apps.objects.filter(user_id=obj).count()
today_hits_count = serializers.SerializerMethodField()
def get_today_hits_count(self, obj):
android_app_ids = Apps.objects.filter(**{"user_id": obj, "type": 0}).values('app_id')
android_app_ids = [app_dict.get('app_id') for app_dict in android_app_ids]
ios_app_ids = Apps.objects.filter(**{"user_id": obj, "type": 1}).values('app_id')
ios_app_ids = [app_dict.get('app_id') for app_dict in ios_app_ids]
return {
"android_today_hits_count": get_app_today_download_times(android_app_ids),
"ios_today_hits_count": get_app_today_download_times(ios_app_ids)
}
def update(self, instance, validated_data):
return super(AdminUserInfoSerializer, self).update(instance, validated_data)
@ -176,7 +162,7 @@ class AdminStorageSerializer(StorageSerializer):
used_id = serializers.IntegerField(source="user_id.pk")
class AdminOrdersSerializer(OrdersSerializer):
class AdminOrdersSerializer(serializers.ModelSerializer):
class Meta:
model = models.Order
fields = "__all__"

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

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

@ -15,7 +15,6 @@ from admin.utils.utils import BaseModelSet, AppsPageNumber
from api.models import UserInfo, Order
from common.core.auth import AdminTokenAuthentication
from common.core.response import ApiResponse
from common.core.sysconfig import Config
from common.utils.caches import update_order_info, admin_change_user_download_times
logger = logging.getLogger(__name__)
@ -57,7 +56,7 @@ class OrderInfoView(BaseModelSet):
obj = UserInfo.objects.filter(pk=pk).first()
if obj:
if amount > 0:
if admin_change_user_download_times(obj, amount * Config.APP_USE_BASE_DOWNLOAD_TIMES):
if admin_change_user_download_times(obj, amount):
return ApiResponse()
else:
return ApiResponse(code=1005, msg="订单创建失败")

@ -14,8 +14,8 @@ from rest_framework.views import APIView
from admin.utils.serializer import AdminStorageSerializer
from admin.utils.utils import AppsPageNumber, BaseModelSet
from api.base_views import storage_change
from api.models import UserInfo, AppStorage
from api.tasks import migrate_storage_job
from common.base.baseutils import format_storage_selection
from common.core.auth import AdminTokenAuthentication
from common.core.response import ApiResponse
@ -64,13 +64,8 @@ class StorageChangeView(APIView):
use_storage_id = data.get("use_storage_id", None)
force = data.get("force", None)
if use_storage_id:
c_task = migrate_storage_job.apply_async((use_storage_id, obj.pk, force))
msg = c_task.get(propagate=False)
logger.info(f"run migrate storage task {obj} msg:{msg}")
if c_task.successful():
c_task.forget()
else:
ApiResponse(code=1006, msg=msg)
if not storage_change(use_storage_id, obj, force):
ApiResponse(code=1006, msg="修改失败")
return ApiResponse()
return ApiResponse(code=1004, msg="数据校验失败")

@ -10,8 +10,7 @@ from api.models import AppReleaseInfo, UserInfo, AppScreenShot, AppStorage
from api.utils.response import BaseResponse
from api.utils.signalutils import run_delete_app_signal
from api.utils.utils import delete_local_files, delete_app_screenshots_files, change_storage_and_change_head_img, \
migrating_storage_data, clean_storage_data, check_storage_is_new_storage, migrating_storage_file_data
from common.base.magic import MagicCacheData
migrating_storage_data, clean_storage_data, check_storage_is_new_storage
from common.cache.state import MigrateStorageState
from common.utils.caches import del_cache_response_by_short, del_cache_by_delete_app, \
del_cache_storage
@ -51,7 +50,6 @@ def app_delete(app_obj):
has_combo.has_combo = None
has_combo.save(update_fields=['has_combo'])
MagicCacheData.invalid_cache(app_obj.app_id)
app_obj.delete()
return res
@ -65,9 +63,42 @@ def app_screen_delete(screen_id, apps_obj, storage):
del_cache_response_by_short(apps_obj.app_id)
def check_storage_ok(user_obj, new_storage_obj):
if migrating_storage_file_data(user_obj, 'head_img.jpeg', new_storage_obj, False):
return True
def app_release_delete(app_obj, release_id, storage):
res = BaseResponse()
user_obj = app_obj.user_id
if app_obj:
apprelease_count = AppReleaseInfo.objects.filter(app_id=app_obj).values("release_id").count()
appreleaseobj = AppReleaseInfo.objects.filter(app_id=app_obj, release_id=release_id).first()
if not appreleaseobj.is_master:
logger.info(f"delete app release {appreleaseobj}")
storage.delete_file(appreleaseobj.release_id, appreleaseobj.release_type)
delete_local_files(appreleaseobj.release_id, appreleaseobj.release_type)
storage.delete_file(appreleaseobj.icon_url)
appreleaseobj.delete()
elif appreleaseobj.is_master and apprelease_count < 2:
logger.info(f"delete app master release {appreleaseobj} and clean app {app_obj} ")
count = APPToDeveloper.objects.filter(app_id=app_obj).count()
if app_obj.issupersign or count > 0:
logger.info(f"app_id:{app_obj.app_id} is supersign ,delete this app need clean IOS developer")
IosUtils.clean_app_by_user_obj(app_obj)
storage.delete_file(appreleaseobj.release_id, appreleaseobj.release_type)
delete_local_files(appreleaseobj.release_id, appreleaseobj.release_type)
storage.delete_file(appreleaseobj.icon_url)
del_cache_by_delete_app(app_obj.app_id)
appreleaseobj.delete()
delete_app_screenshots_files(storage, app_obj)
has_combo = app_obj.has_combo
if has_combo:
app_obj.has_combo.has_combo = None
app_obj.delete()
else:
pass
del_cache_response_by_short(app_obj.app_id)
return res
def storage_change(use_storage_id, user_obj, force):
@ -76,8 +107,6 @@ def storage_change(use_storage_id, user_obj, force):
return True
try:
if use_storage_id == -1:
if not check_storage_ok(user_obj, None):
return False
change_storage_and_change_head_img(user_obj, None)
if migrating_storage_data(user_obj, None, False):
if check_storage_is_new_storage(user_obj, None):
@ -85,8 +114,6 @@ def storage_change(use_storage_id, user_obj, force):
UserInfo.objects.filter(pk=user_obj.pk).update(storage=None)
else:
new_storage_obj = AppStorage.objects.filter(pk=use_storage_id).first()
if not check_storage_ok(user_obj, new_storage_obj):
return False
change_storage_and_change_head_img(user_obj, new_storage_obj)
if migrating_storage_data(user_obj, new_storage_obj, False):
if check_storage_is_new_storage(user_obj, new_storage_obj):

@ -1,14 +0,0 @@
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('-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('-cn', '--celery_num', type=int, nargs="?", default=8)
parser.add_argument('-cn', '--celery_num', type=int, nargs="?", default=20)
parser.add_argument('-usm', '--uwsgi_socket_mode', nargs="?", const=True,
help='run to bind socket mode, default http mode, only uwsgi service')
parser.add_argument('-f', '--force', nargs="?", const=True)

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

@ -36,8 +36,8 @@ class Migration(migrations.Migration):
('uid', models.CharField(db_index=True, max_length=64, unique=True)),
('mobile', models.BigIntegerField(db_index=True, help_text='用于手机验证码登录', null=True, verbose_name='手机')),
('is_active', models.BooleanField(default=True, verbose_name='账户状态,默认启用')),
('storage_active', models.BooleanField(default=True, verbose_name='配置存储,默认启用')),
('supersign_active', models.BooleanField(default=True, verbose_name='配置超级签,默认启用')),
('storage_active', models.BooleanField(default=False, verbose_name='配置存储,默认关闭')),
('supersign_active', models.BooleanField(default=True, verbose_name='配置超级签,默认关闭')),
('job', models.TextField(blank=True, max_length=128, null=True, verbose_name='职位')),
('company', models.CharField(blank=True, max_length=128, null=True, verbose_name='公司')),
('gender',
@ -254,7 +254,6 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=128, unique=True, verbose_name='下载包唯一名称')),
('title', models.CharField(max_length=128, verbose_name='下载包名称')),
('description', models.CharField(max_length=128, verbose_name='下载包描述')),
('price_type', models.SmallIntegerField(choices=[(1, '下载次数'), (2, '存储容量')], default=1)),
('price', models.BigIntegerField(verbose_name='下载包价格,单位分')),
('package_size', models.BigIntegerField(verbose_name='下载包次数')),
('download_count_gift', models.IntegerField(default=0, verbose_name='赠送下载次数')),

@ -10,7 +10,6 @@ price_info_list = [
"title": "1k_times",
"description": "1k_times",
"price": 2500,
"price_type": 1,
"package_size": 1000,
"download_count_gift": 100,
"is_enable": True,
@ -20,7 +19,6 @@ price_info_list = [
"title": "10k_times",
"description": "10k_times",
"price": 22000,
"price_type": 1,
"package_size": 10000,
"download_count_gift": 800,
"is_enable": True,
@ -30,58 +28,10 @@ price_info_list = [
"title": "100k_times",
"description": "100k_times",
"price": 200000,
"price_type": 1,
"package_size": 100000,
"download_count_gift": 8000,
"is_enable": True,
},
{
"name": "storage_3_month",
"title": "三个月",
"description": "三个月",
"price": 3,
"price_type": 2,
"package_size": 3,
"download_count_gift": 0,
"is_enable": True,
}, {
"name": "storage_6_month",
"title": "半年",
"description": "半年",
"price": 6,
"price_type": 2,
"package_size": 6,
"download_count_gift": 0,
"is_enable": True,
}, {
"name": "storage_12_month",
"title": "一年",
"description": "一年",
"price": 12,
"price_type": 2,
"package_size": 12,
"download_count_gift": 0,
"is_enable": True,
}, {
"name": "storage_24_month",
"title": "两年",
"description": "两年",
"price": 24,
"price_type": 2,
"package_size": 24,
"download_count_gift": 0,
"is_enable": True,
}, {
"name": "storage_36_month",
"title": "三年",
"description": "三年",
"price": 36,
"price_type": 2,
"package_size": 36,
"download_count_gift": 0,
"is_enable": True,
}
]
WEB_DOMAIN = DOMAINCONF.WEB_DOMAIN.split('//')[1]
domain_cname_info_list = [

@ -1,78 +0,0 @@
# Generated by Django 3.2.3 on 2022-05-13 08:43
import django.db.models.fields
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0005_auto_20220412_1744'),
]
operations = [
migrations.AddField(
model_name='appstorage',
name='max_storage_capacity',
field=models.BigIntegerField(default=0, verbose_name='存储最大使用容量,单位btype'),
),
migrations.AlterField(
model_name='userinfo',
name='download_times',
field=models.PositiveBigIntegerField(default=0, verbose_name='可用下载点次数,需要用户充值'),
),
migrations.AlterField(
model_name='userinfo',
name='storage',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
related_name='app_storage', to='api.appstorage', verbose_name='存储'),
),
migrations.AlterUniqueTogether(
name='appstorage',
unique_together={('user_id', 'bucket_name', 'endpoint'), ('user_id', 'name')},
),
migrations.CreateModel(
name='StorageShareInfo',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status',
models.SmallIntegerField(choices=[(1, '共享成功'), (2, '取消共享')], default=0, help_text='1 共享成功 2 取消共享',
verbose_name='状态')),
('number', models.BigIntegerField(default=0, verbose_name='容量大小,单位byte')),
('description', models.CharField(blank=True, default='', max_length=128, verbose_name='操作描述')),
('remote_addr', models.GenericIPAddressField(verbose_name='远程IP地址')),
('created_time', models.DateTimeField(auto_now_add=True, verbose_name='添加时间')),
('updated_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('storage_id', models.ForeignKey(on_delete=django.db.models.fields.CharField, to='api.appstorage',
verbose_name='存储ID')),
('to_user_id', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE,
related_name='storage_to_user_id', to=settings.AUTH_USER_MODEL,
verbose_name='用户ID')),
('user_id',
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='storage_org_user_id',
to=settings.AUTH_USER_MODEL, verbose_name='用户ID')),
],
options={
'verbose_name': '私有存储共享',
'verbose_name_plural': '私有存储共享',
},
),
migrations.CreateModel(
name='StorageExchange',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('download_times', models.BigIntegerField(default=0, verbose_name='消耗下载次数')),
('storage_size', models.BigIntegerField(default=0, verbose_name='兑换空间大小')),
('expires_time', models.DateTimeField(verbose_name='失效时间')),
('description', models.CharField(blank=True, default='', max_length=128, verbose_name='操作描述')),
('remote_addr', models.GenericIPAddressField(verbose_name='远程IP地址')),
('created_time', models.DateTimeField(auto_now_add=True, verbose_name='添加时间')),
('user_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL,
verbose_name='用户ID')),
],
options={
'verbose_name': '存储空间兑换信息',
'verbose_name_plural': '存储空间兑换信息',
},
),
]

@ -1,32 +0,0 @@
# Generated by Django 3.2.3 on 2022-07-25 16:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0006_auto_20220513_0843'),
]
operations = [
migrations.AlterField(
model_name='domaincnameinfo',
name='is_enable',
field=models.BooleanField(default=False, verbose_name='是否启用该解析'),
),
migrations.AlterField(
model_name='domaincnameinfo',
name='domain_record',
field=models.CharField(max_length=128, verbose_name='记录值'),
),
migrations.AddField(
model_name='domaincnameinfo',
name='user_ipk',
field=models.IntegerField(default=0, verbose_name='用户ipk'),
),
migrations.AlterUniqueTogether(
name='domaincnameinfo',
unique_together={('user_ipk', 'domain_record')},
),
]

@ -24,8 +24,8 @@ class UserInfo(AbstractUser):
uid = models.CharField(max_length=64, unique=True, db_index=True) # user_id,唯一标识
mobile = models.BigIntegerField(verbose_name="手机", db_index=True, help_text="用于手机验证码登录", null=True)
is_active = models.BooleanField(default=True, verbose_name="账户状态,默认启用")
storage_active = models.BooleanField(default=True, verbose_name="配置存储,默认启用")
supersign_active = models.BooleanField(default=True, verbose_name="配置超级签,默认启用")
storage_active = models.BooleanField(default=False, verbose_name="配置存储,默认关闭")
supersign_active = models.BooleanField(default=True, verbose_name="配置超级签,默认关闭")
job = models.TextField("职位", max_length=128, blank=True, null=True)
company = models.CharField("公司", max_length=128, blank=True, null=True)
@ -39,12 +39,12 @@ class UserInfo(AbstractUser):
memo = models.TextField('备注', blank=True, null=True, default=None, )
date_joined = models.DateTimeField(auto_now_add=True, verbose_name="注册时间")
download_times = models.PositiveBigIntegerField(default=0, verbose_name="可用下载次数,需要用户充值")
download_times = models.PositiveIntegerField(default=0, verbose_name="可用下载次数,需要用户充值")
all_download_times = models.BigIntegerField(default=0, verbose_name="总共下载次数")
default_domain_name = models.ForeignKey(to="DomainCnameInfo", verbose_name="默认下载页域名", on_delete=models.CASCADE)
history_release_limit = models.IntegerField(default=10, verbose_name="app 历史记录版本", blank=True, null=True)
storage = models.ForeignKey(to='AppStorage', related_name='app_storage',
on_delete=models.SET_NULL, verbose_name="存储", null=True, blank=True)
storage = models.OneToOneField(to='AppStorage', related_name='app_storage',
on_delete=models.SET_NULL, verbose_name="存储", null=True, blank=True)
api_token = models.CharField(max_length=256, verbose_name='api访问密钥', default='')
notify_available_downloads = models.IntegerField(default=0, verbose_name="下载余额不足通知", blank=True, null=True)
notify_available_signs = models.IntegerField(default=0, verbose_name="签名余额不足通知", blank=True, null=True)
@ -63,7 +63,7 @@ class UserInfo(AbstractUser):
self.api_token = self.uid + generate_alphanumeric_token_of_length(64)
if not self.default_domain_name_id:
default_domain_obj = min(
DomainCnameInfo.objects.annotate(Count('userinfo')).filter(is_enable=True, is_system=True, user_ipk=0),
DomainCnameInfo.objects.annotate(Count('userinfo')).filter(is_enable=True, is_system=True),
key=lambda x: x.userinfo__count)
if default_domain_obj:
self.default_domain_name_id = default_domain_obj.pk
@ -261,8 +261,6 @@ class AppStorage(models.Model):
verbose_name="阿里云下载授权方式")
cnd_auth_key = models.CharField(max_length=128, blank=True, null=True, verbose_name="阿里云cnd_auth_key")
max_storage_capacity = models.BigIntegerField(verbose_name="存储最大使用容量,单位btype", default=0)
created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
description = models.TextField('备注', blank=True, null=True, default='')
@ -270,7 +268,6 @@ class AppStorage(models.Model):
class Meta:
verbose_name = '存储配置'
verbose_name_plural = "存储配置"
unique_together = (('user_id', 'name'), ('user_id', 'bucket_name', 'endpoint'))
def save(self, *args, **kwargs):
if self.storage_type in (1, 2):
@ -317,8 +314,6 @@ class Price(models.Model):
name = models.CharField(max_length=128, unique=True, verbose_name="下载包唯一名称")
title = models.CharField(max_length=128, verbose_name="下载包名称")
description = models.CharField(max_length=128, verbose_name="下载包描述")
price_type_choices = ((1, '下载次数'), (2, '存储容量'))
price_type = models.SmallIntegerField(choices=price_type_choices, default=1)
price = models.BigIntegerField(null=False, verbose_name="下载包价格,单位分")
package_size = models.BigIntegerField(null=False, verbose_name="下载包次数")
download_count_gift = models.IntegerField(default=0, null=False, verbose_name="赠送下载次数")
@ -331,13 +326,12 @@ class Price(models.Model):
unique_together = ('price', 'package_size')
def save(self, *args, **kwargs):
if Price.objects.filter(is_enable=True, price_type=1).count() > 3: # 最多3个启用的价格表
if Price.objects.filter(is_enable=True).count() > 3: # 最多3个启用的价格表
raise
super(Price, self).save(*args, **kwargs)
def __str__(self):
return "%s-%s-%s-%s-%s" % (
self.name, self.price, self.package_size, self.download_count_gift, self.get_price_type_display())
return "%s-%s-%s-%s" % (self.name, self.price, self.package_size, self.download_count_gift)
class UserCertificationInfo(models.Model):
@ -378,10 +372,9 @@ class CertificationInfo(models.Model):
class DomainCnameInfo(models.Model):
user_ipk = models.IntegerField(verbose_name="用户ipk", default=0)
domain_record = models.CharField(max_length=128, verbose_name="记录值")
domain_record = models.CharField(max_length=128, unique=True, verbose_name="记录值")
ip_address = models.CharField(max_length=128, verbose_name="域名解析地址", null=False)
is_enable = models.BooleanField(default=False, verbose_name="是否启用该解析")
is_enable = models.BooleanField(default=True, verbose_name="是否启用该解析")
is_system = models.BooleanField(default=False, verbose_name="是否是系统自带解析")
is_https = models.BooleanField(default=False, verbose_name="是否支持HTTPS")
description = models.TextField(verbose_name='备注', blank=True, null=True, default='')
@ -390,10 +383,9 @@ class DomainCnameInfo(models.Model):
class Meta:
verbose_name = '系统分发域名配置'
verbose_name_plural = "系统分发域名配置"
unique_together = (('user_ipk', 'domain_record'),)
def save(self, *args, **kwargs):
if self.user_ipk == 0 and (not self.domain_record or (self.domain_record and len(self.domain_record) < 26)):
if not self.domain_record or (self.domain_record and len(self.domain_record) < 26): # 最多3个启用的价格表
self.domain_record = '%s.%s' % (generate_numeric_token_of_length(24, 'abcdef'), self.domain_record)
super(DomainCnameInfo, self).save(*args, **kwargs)
@ -582,46 +574,3 @@ class UserPersonalConfig(BaseConfig):
def __str__(self):
return "%s-%s" % (self.key, self.description)
class StorageShareInfo(models.Model):
"""
用户共享存储空间可将该用户私有存储共享给 其他用户使用
"""
user_id = models.ForeignKey(to=UserInfo, verbose_name="用户ID", on_delete=models.CASCADE,
related_name='storage_org_user_id')
to_user_id = models.ForeignKey(to=UserInfo, verbose_name="用户ID", on_delete=models.CASCADE,
related_name='storage_to_user_id', null=True, blank=True)
storage_id = models.ForeignKey(to=AppStorage, verbose_name="存储ID", on_delete=models.CharField)
status_choices = ((1, '共享成功'), (2, '取消共享'))
status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="状态",
help_text="1 共享成功 2 取消共享")
number = models.BigIntegerField(verbose_name="容量大小,单位byte", default=0)
description = models.CharField(verbose_name="操作描述", max_length=128, default='', blank=True)
remote_addr = models.GenericIPAddressField(verbose_name="远程IP地址")
created_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")
updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
verbose_name = '私有存储共享'
verbose_name_plural = "私有存储共享"
def __str__(self):
return f"{self.user_id}-{self.to_user_id}{self.description}"
class StorageExchange(models.Model):
user_id = models.ForeignKey(to=UserInfo, verbose_name="用户ID", on_delete=models.CASCADE)
download_times = models.BigIntegerField(verbose_name="消耗下载次数", default=0)
storage_size = models.BigIntegerField(verbose_name="兑换空间大小", default=0)
expires_time = models.DateTimeField(verbose_name="失效时间")
description = models.CharField(verbose_name="操作描述", max_length=128, default='', blank=True)
remote_addr = models.GenericIPAddressField(verbose_name="远程IP地址")
created_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")
class Meta:
verbose_name = '存储空间兑换信息'
verbose_name_plural = "存储空间兑换信息"
def __str__(self):
return f"{self.user_id}{self.description}"

@ -9,8 +9,7 @@ import logging
from captcha.models import CaptchaStore
from api.utils.ctasks import sync_download_times, auto_clean_upload_tmp_file, auto_clean_remote_client_log, \
notify_check_user_download_times, notify_check_apple_developer_devices, notify_check_apple_developer_cert, \
migrate_user_oss_storage
notify_check_user_download_times, notify_check_apple_developer_devices, notify_check_apple_developer_cert
from api.views.login import get_login_type
from common.core.sysconfig import Config, ConfigCacheBase
from common.libs.geetest.geetest_utils import check_bypass_status
@ -22,73 +21,67 @@ from xsign.utils.ctasks import auto_delete_ios_mobile_tmp_file
logger = logging.getLogger(__name__)
def clean_config_cache(key):
ConfigCacheBase().invalid_config_cache(key)
ConfigCacheBase(px='user').invalid_config_cache(key)
@app.task
def start_api_sever_do_clean():
# 启动服务的时候,同时执行下面操作,主要是修改配置存储的时候,需要执行清理,否则会出问题,如果不修改,则无需执行
logger.info("clean local storage cache")
get_local_storage(clean_cache=True)
check_bypass_status()
clean_config_cache('*')
ConfigCacheBase().invalid_config_cache()
def clean_config_cache(key):
ConfigCacheBase().invalid_config_cache(key)
@app.task
def sync_download_times_job():
return sync_download_times()
sync_download_times()
@app.task
def check_bypass_status_job():
if Config.LOGIN.get("geetest") or Config.CHANGER.get('geetest') or Config.REGISTER.get(
'geetest') or Config.REPORT.get('geetest'):
return check_bypass_status()
check_bypass_status()
@app.task
def auto_clean_upload_tmp_file_job():
return auto_clean_upload_tmp_file()
auto_clean_upload_tmp_file()
@app.task
def auto_clean_captcha_store_job():
return CaptchaStore.remove_expired()
CaptchaStore.remove_expired()
@app.task
def auto_delete_tmp_file_job():
return auto_delete_ios_mobile_tmp_file()
auto_delete_ios_mobile_tmp_file()
@app.task
def sync_wx_access_token_job():
if get_login_type().get('third', '').get('wxp'):
return sync_wx_access_token()
sync_wx_access_token()
@app.task
def auto_clean_remote_client_job():
return auto_clean_remote_client_log()
auto_clean_remote_client_log()
@app.task
def download_times_notify_check_job():
return notify_check_user_download_times()
notify_check_user_download_times()
@app.task
def apple_developer_devices_check_job():
return notify_check_apple_developer_devices()
notify_check_apple_developer_devices()
@app.task
def apple_developer_cert_notify_check_job():
return notify_check_apple_developer_cert()
@app.task
def migrate_storage_job(use_storage_id, user_pk, force):
return migrate_user_oss_storage(use_storage_id, user_pk, force)
notify_check_apple_developer_cert()

@ -17,7 +17,7 @@ from django.urls import re_path
from api.views.advert import UserAdInfoView
from api.views.apps import AppsView, AppInfoView, AppReleaseInfoView, AppsQrcodeShowView, AppDownloadTokenView
from api.views.domain import DomainCnameView, DomainInfoView, DomainCnameInfoView
from api.views.domain import DomainCnameView, DomainInfoView
from api.views.download import ShortDownloadView, InstallView, DownloadView
from api.views.getip import GetRemoteIp
from api.views.login import LoginView, UserInfoView, RegistView, AuthorizationView, ChangeAuthorizationView, \
@ -29,7 +29,7 @@ from api.views.notify import NotifyReceiverView, NotifyConfigView, NotifyInfoVie
from api.views.order import PriceView, OrderView, PaySuccess, OrderSyncView
from api.views.personalconfig import PersonalConfigView
from api.views.report import ReportView
from api.views.storage import StorageView, CleanStorageView, ShareStorageView, StorageConfigView, StorageExchangeView
from api.views.storage import StorageView, CleanStorageView
from api.views.thirdlogin import ValidWxChatToken, ThirdWxAccount
from api.views.uploads import AppAnalyseView, UploadView
@ -48,9 +48,6 @@ urlpatterns = [
re_path("^apps$", AppsView.as_view()),
re_path("^storage$", StorageView.as_view()),
re_path("^storage/clean$", CleanStorageView.as_view()),
re_path("^storage/share$", ShareStorageView.as_view()),
re_path("^storage/config$", StorageConfigView.as_view()),
re_path("^storage/exchange$", StorageExchangeView.as_view()),
re_path(r"^apps/(?P<app_id>\w+)", AppInfoView.as_view()),
re_path(r"^download_password/(?P<app_id>\w+)", AppDownloadTokenView.as_view()),
re_path(r"^appinfos/(?P<app_id>\w+)/(?P<act>\w+)", AppReleaseInfoView.as_view()),
@ -73,7 +70,6 @@ urlpatterns = [
re_path(r"^pay_success/(?P<name>\w+)$", PaySuccess.as_view()),
re_path("^cname_domain$", DomainCnameView.as_view()),
re_path("^domain_info$", DomainInfoView.as_view()),
re_path("^cname_info$", DomainCnameInfoView.as_view()),
re_path("^mp.weixin$", ValidWxChatToken.as_view()),
re_path("^mp.applet$", WeChatAppletView.as_view()),
re_path("^mp.web.login$", WeChatWebLoginView.as_view(), name="mp.web.login"),

@ -9,7 +9,6 @@ import random
from api.models import AppReleaseInfo, Apps
from api.utils.modelutils import get_user_domain_name
from common.base.baseutils import make_app_uuid
from common.base.magic import MagicCacheData
from common.cache.state import MigrateStorageState
from common.utils.caches import del_cache_response_by_short
from common.utils.storage import Storage
@ -79,10 +78,9 @@ def clean_history_apps(app_obj, user_obj, history_release_limit=20):
storage_obj.delete_file(release_obj.release_id, app_obj.type)
storage_obj.delete_file(release_obj.icon_url)
release_obj.delete()
MagicCacheData.invalid_cache(app_obj.app_id)
def save_app_infos(app_tmp_filename, app_file_name, user_obj, app_info, bundle_id, app_img, short, size, issupersign):
def save_app_infos(app_file_name, user_obj, app_info, bundle_id, app_img, short, size, issupersign):
app_uuid = make_app_uuid(user_obj, bundle_id + app_file_name.split(".")[1])
##判断是否存在该app
app_obj = Apps.objects.filter(app_id=app_uuid, user_id=user_obj).first()
@ -117,20 +115,16 @@ def save_app_infos(app_tmp_filename, app_file_name, user_obj, app_info, bundle_i
app_obj.issupersign = issupersign
# appmobj.wxeasytype = False if user_obj.domain_name or appmobj.domain_name else True
app_obj.bundle_id = bundle_id
app_obj.save(update_fields=["short", "name", "bundle_id", "issupersign", "updated_time"])
app_obj.save(update_fields=["short", "name", "bundle_id", "issupersign"])
except Exception as e:
logger.error(f"save app info failed,app_obj:{app_obj} Exception:{e}")
app_obj.bundle_id = bundle_id
app_obj.issupersign = issupersign
app_obj.name = app_info["labelname"]
app_obj.save(update_fields=["name", "bundle_id", "issupersign", "updated_time"])
app_obj.save(update_fields=["name", "bundle_id", "issupersign"])
del_cache_response_by_short(app_obj.app_id)
MagicCacheData.invalid_cache(app_obj.app_id)
AppReleaseInfo.objects.filter(app_id=app_obj).update(is_master=False)
file_info = storage.get_file_info(app_tmp_filename)
logger.info(f'get file {app_tmp_filename} info:{file_info}')
size = file_info.get('content_length', size)
AppReleaseInfo.objects.filter(app_id=app_obj).update(is_master=False)
release_data = {
"app_id": app_obj,

@ -9,9 +9,7 @@ import time
from django.core.cache import cache
from api.base_views import storage_change
from api.models import Apps, UserInfo, RemoteClientInfo
from common.cache.state import MigrateStorageState
from common.notify.ntasks import check_user_download_times, check_apple_developer_devices, check_apple_developer_cert
from common.utils.storage import Storage
from fir_ser.settings import CACHE_KEY_TEMPLATE
@ -68,16 +66,3 @@ def notify_check_apple_developer_devices():
def notify_check_apple_developer_cert():
for user_obj in UserInfo.objects.filter(is_active=True, supersign_active=True).all():
check_apple_developer_cert(user_obj, expire_day=7, days=[0, 1, 3, 7])
def migrate_user_oss_storage(use_storage_id, user_pk, force):
msg = 'success'
user_obj = UserInfo.objects.filter(pk=user_pk).first()
if user_obj:
with MigrateStorageState(user_obj.uid) as state:
if state:
if not storage_change(use_storage_id, user_obj, force):
msg = '数据迁移失败'
else:
msg = "数据迁移中,请耐心等待"
return msg

@ -3,7 +3,8 @@
# project: 4月
# author: NinEveN
# date: 2021/4/16
import datetime
import logging
import random
from urllib.parse import urljoin
@ -12,24 +13,19 @@ from django.db.models import Count
from rest_framework.pagination import PageNumberPagination
from api.models import AppReleaseInfo, UserDomainInfo, DomainCnameInfo, UserAdDisplayInfo, RemoteClientInfo, \
AppBundleIdBlackList, NotifyReceiver, WeChatInfo, AppDownloadToken, Apps, StorageShareInfo, UserInfo, AppStorage, \
StorageExchange
AppBundleIdBlackList, NotifyReceiver, WeChatInfo, AppDownloadToken
from common.base.baseutils import get_server_domain_from_request, get_user_default_domain_name, get_real_ip_address, \
get_origin_domain_name
from common.base.magic import MagicCacheData
from common.core.sysconfig import Config, UserConfig
logger = logging.getLogger(__name__)
def get_app_d_count_by_app_id(app_id, user_obj):
def get_app_d_count_by_app_id(app_id):
d_count = 1
is_oss = user_obj.storage
binary_size = AppReleaseInfo.objects.filter(is_master=True, app_id__app_id=app_id).values('binary_size').first()
if binary_size and binary_size.get('binary_size', 0) > 0:
d_count += binary_size.get('binary_size') // Config.APP_FILE_CALCULATION_UNIT
return d_count * (UserConfig(user_obj).APP_USE_BASE_DOWNLOAD_TIMES if not is_oss else UserConfig(
user_obj).PRIVATE_OSS_DOWNLOAD_TIMES)
d_count += binary_size.get('binary_size') // 1024 // 1024 // 100
return d_count
def get_user_domain_name(obj, domain_type=1):
@ -90,16 +86,11 @@ def get_app_download_uri(request, user_obj, app_obj=None, preview=True):
return get_server_domain_from_request(request, server_domain)
def get_min_default_domain_cname_obj(is_system=True, user_ipk=0):
if is_system:
c_n = 'userinfo'
else:
c_n = 'userdomaininfo'
domain_queryset = DomainCnameInfo.objects.annotate(Count(c_n)).filter(is_enable=True, is_system=is_system,
user_ipk=user_ipk)
def get_min_default_domain_cname_obj(is_system=True):
domain_queryset = DomainCnameInfo.objects.annotate(Count('userinfo')).filter(is_enable=True, is_system=is_system)
if not domain_queryset:
return DomainCnameInfo.objects.filter(is_enable=True, is_system=True, user_ipk=user_ipk).first()
return min(domain_queryset, key=lambda x: getattr(x, f'{c_n}__count'))
return DomainCnameInfo.objects.filter(is_enable=True, is_system=True).first()
return min(domain_queryset, key=lambda x: x.userinfo__count)
def get_filename_form_file(filename):
@ -132,8 +123,7 @@ def base_random_weight(obj, key):
def ad_random_weight(user_obj):
ad_info_list = UserAdDisplayInfo.objects.filter(user_id=user_obj, is_enable=True, ad_pic__regex='.+').order_by(
'-created_time')
ad_info_list = UserAdDisplayInfo.objects.filter(user_id=user_obj, is_enable=True).order_by('-created_time')
return base_random_weight(ad_info_list, 'weight')
@ -257,73 +247,3 @@ def check_app_access_token(app_id, access_token, only_check, udid):
return True
if udid:
return AppDownloadToken.objects.filter(app_id__app_id=app_id, bind_udid=udid).count()
@MagicCacheData.make_cache(timeout=60 * 60 * 24, key_func=lambda x: x.app_id)
def get_app_storage_used(app_obj):
binary_size_sum = 0
for release_obj in AppReleaseInfo.objects.filter(app_id=app_obj).all():
if release_obj.release_type == 0:
sign_count = 0
else:
sign_count = AppReleaseInfo.objects.filter(
app_id__apptodeveloper__release_file=release_obj.release_id).values(
'app_id__apptodeveloper__binary_file').distinct().count()
binary_size_sum += release_obj.binary_size * (sign_count + 1)
return binary_size_sum
# @MagicCacheData.make_cache(3600, key=lambda x: x.uid)
def get_user_storage_used(user_obj):
binary_size_sum = 0
if isinstance(user_obj, UserInfo):
user_obj = [user_obj]
for app_obj in Apps.objects.filter(user_id__in=user_obj).all():
binary_size_sum += get_app_storage_used(app_obj)
return binary_size_sum
def get_user_exchange_storage_capacity(user_obj):
obj = StorageExchange.objects.filter(user_id=user_obj, expires_time__gte=datetime.datetime.now()).order_by(
'storage_size').last()
if obj:
return obj.storage_size
return 0
def get_user_storage_capacity(user_obj):
storage_obj = user_obj.storage
if not storage_obj:
storage_capacity = get_user_exchange_storage_capacity(user_obj) + Config.STORAGE_FREE_CAPACITY
else:
if storage_obj.user_id == user_obj:
max_storage_capacity = storage_obj.max_storage_capacity
storage_capacity = max_storage_capacity if max_storage_capacity else Config.STORAGE_OSS_CAPACITY
else:
share_obj = StorageShareInfo.objects.filter(to_user_id=user_obj, status=1, storage_id=storage_obj).first()
if share_obj:
can_use_number = storage_obj.max_storage_capacity - get_user_storage_used(storage_obj.app_storage.all())
storage_capacity = share_obj.number if share_obj.number < can_use_number else can_use_number
else:
storage_capacity = Config.STORAGE_FREE_CAPACITY
return storage_capacity
def get_user_storage_obj(user_obj, default=False):
storage = user_obj.storage
if storage and not default:
return storage
for storage in Config.STORAGE:
if storage.get("active", None):
storage_type = storage.get('type', None)
auth = storage.get('auth', {})
storage = AppStorage(user_id=user_obj, storage_type=storage_type, **auth)
return storage
def get_preview_short_config(user_obj, short):
if user_obj:
return f"{'#/' if UserConfig(user_obj).PREVIEW_ROUTE_HASH else ''}{short}"
else:
return f"{'#/' if Config.PREVIEW_ROUTE_HASH else ''}{short}"

@ -1,18 +1,15 @@
import datetime
import logging
import os
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from api import models
from api.utils.apputils import bytes2human
from api.utils.modelutils import get_user_domain_name, get_app_domain_name, get_app_download_uri, get_user_storage_used, \
get_preview_short_config
from api.utils.modelutils import get_user_domain_name, get_app_domain_name, get_app_download_uri
from common.base.baseutils import get_choices_dict, WeixinLoginUid
from common.cache.storage import AdPicShowCache
from common.core.sysconfig import Config, UserConfig
from common.utils.caches import get_user_free_download_times, get_user_cert_auth_status, get_app_today_download_times
from common.core.sysconfig import Config
from common.utils.caches import get_user_free_download_times, get_user_cert_auth_status
from common.utils.storage import Storage
from common.utils.token import make_token
@ -63,33 +60,9 @@ def get_screenshots_from_self(self, obj, force_new=False):
class UserInfoSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = ["username", "uid", "mobile", "job", "email", "domain_name", "role", "first_name", "times_info",
fields = ["username", "uid", "mobile", "job", "email", "domain_name", "role", "first_name",
'head_img', 'storage_active', 'supersign_active', 'free_download_times', 'download_times',
'certification', 'qrcode_domain_name', 'storage_used', 'storage_used_capacity']
times_info = serializers.SerializerMethodField()
def get_times_info(self, obj):
return {
'every_size': UserConfig(obj).APP_FILE_CALCULATION_UNIT,
'private_times': UserConfig(obj).PRIVATE_OSS_DOWNLOAD_TIMES,
'base_times': UserConfig(obj).APP_USE_BASE_DOWNLOAD_TIMES,
}
storage_used_capacity = serializers.SerializerMethodField()
def get_storage_used_capacity(self, obj):
return get_user_storage_used(obj)
download_times = serializers.SerializerMethodField()
def get_download_times(self, obj):
return obj.download_times // Config.APP_USE_BASE_DOWNLOAD_TIMES
storage_used = serializers.SerializerMethodField()
def get_storage_used(self, obj):
return bytes2human(self.get_storage_used_capacity(obj))
'certification', 'qrcode_domain_name']
head_img = serializers.SerializerMethodField()
@ -99,8 +72,7 @@ class UserInfoSerializer(serializers.ModelSerializer):
free_download_times = serializers.SerializerMethodField()
def get_free_download_times(self, obj):
return get_user_free_download_times(obj.id, auth_status=get_user_cert_auth_status(
obj.id)) // Config.APP_USE_BASE_DOWNLOAD_TIMES
return get_user_free_download_times(obj.id, auth_status=get_user_cert_auth_status(obj.id))
certification = serializers.SerializerMethodField()
@ -154,7 +126,19 @@ class AppsSerializer(serializers.ModelSerializer):
preview_url = serializers.SerializerMethodField()
def get_preview_url(self, obj):
return f"{get_app_download_uri(None, obj.user_id, obj)}/{get_preview_short_config(obj.user_id, obj.short)}"
return get_app_download_uri(None, obj.user_id, obj)
private_developer_number = serializers.IntegerField(default=0)
count = serializers.IntegerField(default=0)
#
# def get_private_developer_number(self, obj):
# return models.AppleDeveloperToAppUse.objects.filter(app_id=obj).count()
#
private_developer_used_number = serializers.IntegerField(default=0)
#
# def get_private_developer_used_number(self, obj):
# return models.DeveloperDevicesID.objects.filter(app_id=obj,
# developerid__appledevelopertoappuse__app_id=obj).distinct().count()
domain_name = serializers.SerializerMethodField()
@ -166,11 +150,15 @@ class AppsSerializer(serializers.ModelSerializer):
def get_sign_type_choice(self, obj):
return get_choices_dict(obj.supersign_type_choices)
count = serializers.IntegerField(default=0)
developer_used_count = serializers.IntegerField(default=0)
private_developer_number = serializers.IntegerField(default=0)
private_developer_used_number = serializers.IntegerField(default=0)
supersign_used_number = serializers.IntegerField(default=0)
#
# def get_supersign_used_number(self, obj):
# return models.APPSuperSignUsedInfo.objects.filter(app_id=obj).all().count()
#
developer_used_count = serializers.IntegerField(default=0)
#
# def get_developer_used_count(self, obj):
# return models.DeveloperAppID.objects.filter(app_id=obj).all().count()
screenshots = serializers.SerializerMethodField()
@ -212,16 +200,12 @@ class AppsSerializer(serializers.ModelSerializer):
else:
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 Meta:
model = models.Apps
fields = ["app_id", "bundle_id", "issupersign", "name", "preview_url", "short", "type", "master_release",
"has_combo", "count_hits", "today_hits_count"]
"has_combo", "count_hits"]
def get_master_release(self, obj):
master_release_obj = get_app_master_obj_from_context(self, obj)
@ -370,39 +354,9 @@ class StorageSerializer(serializers.ModelSerializer):
model = models.AppStorage
exclude = ["user_id"]
def validate(self, attrs):
endpoint = attrs.get('endpoint', '')
storage_type = attrs.get('storage_type', '')
if storage_type == 2 and endpoint not in Config.STORAGE_ALLOW_ENDPOINT:
raise ValidationError(f'endpoint [{endpoint}] not in {Config.STORAGE_ALLOW_ENDPOINT}')
max_storage_capacity = attrs.get('storage_capacity', -1)
if max_storage_capacity != -1:
if max_storage_capacity == 0:
attrs['max_storage_capacity'] = Config.STORAGE_OSS_CAPACITY
else:
attrs['max_storage_capacity'] = max_storage_capacity * 1024 * 1024
del attrs['storage_capacity']
return attrs
storage_type_display = serializers.CharField(source="get_storage_type_display", read_only=True)
storage_capacity = serializers.IntegerField(write_only=True)
download_auth_type_choices = serializers.SerializerMethodField()
used = serializers.SerializerMethodField()
used_number = serializers.SerializerMethodField()
shared = serializers.SerializerMethodField()
max_storage_capacity = serializers.SerializerMethodField()
def get_used(self, obj):
return obj.app_storage.count()
def get_shared(self, obj):
return models.StorageShareInfo.objects.filter(status=1, storage_id=obj).count()
def get_used_number(self, obj):
return get_user_storage_used(obj.app_storage.all())
def get_max_storage_capacity(self, obj):
return obj.max_storage_capacity if obj.max_storage_capacity != 0 else Config.STORAGE_OSS_CAPACITY
download_auth_type_choices = serializers.SerializerMethodField()
# secret_key = serializers.SerializerMethodField()
# 加上此选项,会导致update获取不到值
@ -435,7 +389,7 @@ class StorageSerializer(serializers.ModelSerializer):
class PriceSerializer(serializers.ModelSerializer):
class Meta:
model = models.Price
exclude = ["id", "price_type", "is_enable"]
exclude = ["id"]
class OrdersSerializer(serializers.ModelSerializer):
@ -443,16 +397,6 @@ class OrdersSerializer(serializers.ModelSerializer):
model = models.Order
exclude = ["id"]
actual_download_times = serializers.SerializerMethodField()
def get_actual_download_times(self, obj):
return obj.actual_download_times // Config.APP_USE_BASE_DOWNLOAD_TIMES
actual_download_gift_times = serializers.SerializerMethodField()
def get_actual_download_gift_times(self, obj):
return obj.actual_download_gift_times // Config.APP_USE_BASE_DOWNLOAD_TIMES
class CertificationSerializer(serializers.ModelSerializer):
class Meta:
@ -508,17 +452,6 @@ class DomainNameSerializer(serializers.ModelSerializer):
return app_info
return {}
is_private = serializers.SerializerMethodField()
def get_is_private(self, obj):
return bool(obj.cname_id.user_ipk)
class DomainCnameInfoSerializer(serializers.ModelSerializer):
class Meta:
model = models.DomainCnameInfo
exclude = ["id", "user_ipk", "is_https", "is_system"]
class UserAdInfoSerializer(serializers.ModelSerializer):
class Meta:
@ -586,38 +519,3 @@ class PersonalConfigSerializer(serializers.ModelSerializer):
def get_title(self, obj):
return getattr(Config, f'{obj.key}_DES')
class StorageShareSerializer(serializers.ModelSerializer):
class Meta:
model = models.StorageShareInfo
exclude = ["id", "remote_addr", "user_id", "to_user_id"]
target_user = serializers.SerializerMethodField()
cancel = serializers.SerializerMethodField()
used = serializers.SerializerMethodField()
status_display = serializers.CharField(source='get_status_display')
storage_name = serializers.CharField(source='storage_id.name')
def get_target_user(self, obj):
user_obj = obj.user_id
if self.get_cancel(obj):
user_obj = obj.to_user_id
return {'uid': user_obj.uid, 'name': user_obj.first_name}
def get_used(self, obj):
return obj.to_user_id.storage_id == obj.storage_id.id and obj.status == 1
def get_cancel(self, obj):
return self.context.get('user_obj').pk == obj.user_id.pk
class StorageExchangeSerializer(serializers.ModelSerializer):
class Meta:
model = models.StorageExchange
exclude = ["id", "remote_addr", "user_id"]
is_expired = serializers.SerializerMethodField()
def get_is_expired(self, obj):
return (obj.expires_time - datetime.datetime.now()).days

@ -7,15 +7,14 @@ import binascii
import datetime
import logging
import os
import time
from django.core.cache import cache
from api.models import UserInfo, AppReleaseInfo, AppScreenShot, Token, UserAdDisplayInfo
from api.utils.modelutils import get_app_d_count_by_app_id, get_user_storage_obj
from api.utils.modelutils import get_app_d_count_by_app_id
from api.utils.signalutils import run_xsign_migrate_data, run_xsign_clean_data
from common.base.baseutils import get_real_ip_address
from common.cache.storage import UserTokenCache, TaskProgressCache
from common.cache.storage import UserTokenCache
from common.libs.storage.localApi import LocalStorage
from common.utils.caches import consume_user_download_times
from common.utils.storage import Storage
@ -78,9 +77,8 @@ def change_storage_and_change_head_img(user_obj, new_storage_obj, clean_old_data
def change_storage_and_change_advert_img(user_obj, new_storage_obj, clean_old_data=True):
for user_advert_obj in UserAdDisplayInfo.objects.filter(user_id=user_obj, ad_pic__regex='.+'):
if len(user_advert_obj.ad_pic) > 5:
migrating_storage_file_data(user_obj, user_advert_obj.ad_pic, new_storage_obj, clean_old_data)
for user_advert_obj in UserAdDisplayInfo.objects.filter(user_id=user_obj):
migrating_storage_file_data(user_obj, user_advert_obj.ad_pic, new_storage_obj, clean_old_data)
def download_files_form_oss(storage_obj, org_file, force=False):
@ -109,80 +107,48 @@ def check_storage_is_new_storage(user_obj, new_storage_obj):
def migrating_storage_file_data(user_obj, filename, new_storage_obj, clean_old_data=True):
local_file_full_path = os.path.join(MEDIA_ROOT, filename)
if filename == 'head_img.jpeg':
return True
storage_obj = get_user_storage_obj(user_obj)
if storage_obj.storage_type == 2:
storage_obj.download_auth_type = 2
old_oss_storage_obj = Storage(user_obj, storage_obj, prefix='migrate_storage')
else:
old_oss_storage_obj = Storage(user_obj, prefix='migrate_storage')
old_storage_obj = Storage(user_obj)
if not new_storage_obj:
storage_obj = get_user_storage_obj(user_obj, default=True)
if storage_obj.storage_type == 2:
storage_obj.download_auth_type = 2
new_oss_storage_obj = Storage(user_obj, storage_obj, prefix='migrate_storage')
else:
new_oss_storage_obj = Storage(user_obj, None, True, prefix='migrate_storage')
new_storage_obj = Storage(user_obj, None, True)
else:
if new_storage_obj.storage_type == 2:
new_storage_obj.download_auth_type = 2
new_oss_storage_obj = Storage(user_obj, new_storage_obj, prefix='migrate_storage')
else:
new_oss_storage_obj = Storage(user_obj, new_storage_obj, prefix='migrate_storage')
new_storage_obj = Storage(user_obj, new_storage_obj)
if old_oss_storage_obj.get_storage_uuid() == new_oss_storage_obj.get_storage_uuid():
if old_storage_obj.get_storage_uuid() == new_storage_obj.get_storage_uuid():
# 同一个存储,无需迁移数据
return True
if old_oss_storage_obj.get_storage_type() == 3:
if new_oss_storage_obj.get_storage_type() == 3:
if old_storage_obj.get_storage_type() == 3:
if new_storage_obj.get_storage_type() == 3:
# 都是本地存储,无需操作
return True
pass
else:
# 本地向云存储上传,并删除本地数据
res = new_oss_storage_obj.upload_file(local_file_full_path)
new_storage_obj.upload_file(local_file_full_path)
if clean_old_data:
return delete_local_files(filename)
return res
delete_local_files(filename)
else:
if new_oss_storage_obj.get_storage_type() == 3:
if new_storage_obj.get_storage_type() == 3:
# 云存储下载 本地,并删除云存储
if download_files_form_oss(old_oss_storage_obj, local_file_full_path, True):
if download_files_form_oss(old_storage_obj, local_file_full_path, True):
if clean_old_data:
return old_oss_storage_obj.delete_file(filename)
return True
old_storage_obj.delete_file(filename)
else:
# 云存储互传,先下载本地,然后上传新云存储,删除本地和老云存储
if download_files_form_oss(old_oss_storage_obj, local_file_full_path, True):
res1 = new_oss_storage_obj.upload_file(local_file_full_path)
res2 = delete_local_files(filename)
if download_files_form_oss(old_storage_obj, local_file_full_path, True):
new_storage_obj.upload_file(local_file_full_path)
delete_local_files(filename)
if clean_old_data:
return old_oss_storage_obj.delete_file(filename)
return res1 and res2
old_storage_obj.delete_file(filename)
def migrating_storage_data(user_obj, new_storage_obj, clean_old_data):
with cache.lock(f"migrating_storage_data_{user_obj.uid}", timeout=60 * 60 * 24):
task_cache = TaskProgressCache('migrating_storage_data', user_obj.uid)
app_release_queryset = AppReleaseInfo.objects.filter(app_id__user_id=user_obj)
cache_data = task_cache.get_storage_cache()
if not cache_data:
cache_default_data = {
's_time': time.time(),
'release_num': app_release_queryset.count(),
'migrate_num': 0,
'target_oss_id': -1 if not new_storage_obj else new_storage_obj.pk
}
else:
logger.warning(f"migrate {user_obj} oss progress is exist. may be is migrating data {cache_data}")
auth_status = False
certification = getattr(user_obj, 'certification', None)
if certification and certification.status == 1:
auth_status = True
for app_release_obj in app_release_queryset.all():
for app_release_obj in AppReleaseInfo.objects.filter(app_id__user_id=user_obj).all():
# 迁移APP数据
filename = get_filename_from_apptype(app_release_obj.release_id, app_release_obj.release_type)
migrating_storage_file_data(user_obj, filename, new_storage_obj, clean_old_data)
@ -196,11 +162,8 @@ def migrating_storage_data(user_obj, new_storage_obj, clean_old_data):
# filename = get_filename_from_apptype(apptodev_obj.binary_file, app_release_obj.release_type)
# migrating_storage_file_data(user_obj, filename, new_storage_obj, clean_old_data)
# 消费下载次数
cache_default_data['migrate_num'] += 1
task_cache.set_storage_cache(cache_default_data)
amount = get_app_d_count_by_app_id(app_release_obj.app_id.app_id, user_obj)
amount = get_app_d_count_by_app_id(app_release_obj.app_id.app_id)
consume_user_download_times(user_obj.pk, app_release_obj.app_id, amount, auth_status)
task_cache.del_storage_cache()
return True

@ -14,14 +14,12 @@ from rest_framework.views import APIView
from api.base_views import app_delete
from api.models import Apps, AppReleaseInfo, UserInfo, AppScreenShot, AppDownloadToken
from api.utils.apputils import bytes2human
from api.utils.modelutils import get_user_domain_name, get_app_domain_name, get_app_storage_used
from api.utils.modelutils import get_user_domain_name, get_app_domain_name
from api.utils.response import BaseResponse
from api.utils.serializer import AppsSerializer, AppReleaseSerializer, AppsListSerializer, AppsQrListSerializer, \
AppDownloadTokenSerializer
from api.utils.signalutils import run_delete_app_signal
from api.utils.utils import delete_local_files, delete_app_screenshots_files
from common.base.magic import MagicCacheData
from common.cache.state import MigrateStorageState
from common.core.auth import ExpiringTokenAuthentication
from common.utils.caches import del_cache_response_by_short, get_app_today_download_times, del_cache_by_delete_app
@ -65,7 +63,7 @@ class AppsPageNumber(PageNumberPagination):
page_size = 20 # 每页显示多少条
page_size_query_param = 'size' # URL中每页显示条数的参数
page_query_param = 'page' # URL中页码的参数
max_page_size = 100 # 最大页码数限制
max_page_size = None # 最大页码数限制
class AppsView(APIView):
@ -122,7 +120,6 @@ class AppInfoView(APIView):
if app_obj:
app_serializer = AppsSerializer(app_obj, context={"storage": Storage(request.user)})
res.data = app_serializer.data
res.data['storage_used'] = bytes2human(get_app_storage_used(app_obj))
else:
logger.error(f"app_id:{app_id} is not found in user:{request.user}")
res.msg = "未找到该应用"
@ -271,13 +268,10 @@ class AppReleaseInfoView(APIView):
has_combo.has_combo = None
has_combo.save(update_fields=["has_combo"])
del_cache_response_by_short(has_combo.app_id)
MagicCacheData.invalid_cache(app_obj.app_id)
app_obj.delete()
else:
pass
if app_obj:
MagicCacheData.invalid_cache(app_obj.app_id)
del_cache_response_by_short(app_obj.app_id)
del_cache_response_by_short(app_obj.app_id)
return Response(res.dict)
@ -387,18 +381,11 @@ class AppDownloadTokenView(APIView):
token_number = data.get('token_number')
bind_status = data.get('bind_status')
token_max_used_number = data.get('token_max_used_number')
udid_list_str = data.get('udids')
udid_list = []
if not isinstance(bind_status, bool):
bind_status = False
if bind_status:
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()
if token and isinstance(token, str) and len(token) >= 4:
@ -415,12 +402,6 @@ class AppDownloadTokenView(APIView):
else:
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(
token_len=Length('token')).filter(token_len=token_length).values('token').all()
exist_token = [d_token['token'] for d_token in download_token_queryset]
@ -428,19 +409,9 @@ class AppDownloadTokenView(APIView):
token_number=token_number + download_token_queryset.count(),
exist_token=copy.deepcopy(exist_token))
bulk_list = []
d_index = 0
for d_token in list(set(make_token_list) - set(exist_token))[:token_number]:
n_data = {
'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))
bulk_list.append(AppDownloadToken(token=d_token, max_limit_count=token_max_used_number, app_id=app_obj,
bind_status=bind_status))
AppDownloadToken.objects.bulk_create(bulk_list)
return Response(res.dict)

@ -5,20 +5,16 @@
# date: 2021/3/29
import logging
from dns import rdatatype
from rest_framework.response import Response
from rest_framework.views import APIView
from api.models import UserDomainInfo, Apps, DomainCnameInfo
from api.models import UserDomainInfo, Apps
from api.utils.modelutils import get_user_domain_name, get_min_default_domain_cname_obj, PageNumber
from api.utils.response import BaseResponse
from api.utils.serializer import DomainNameSerializer, DomainCnameInfoSerializer
from common.base.baseutils import is_valid_domain, get_cname_from_domain, get_choices_dict, make_app_uuid, \
format_cname_host
from api.utils.serializer import DomainNameSerializer
from common.base.baseutils import is_valid_domain, get_cname_from_domain, get_choices_dict
from common.core.auth import ExpiringTokenAuthentication
from common.core.sysconfig import UserConfig
from common.utils.caches import del_cache_response_by_short, reset_app_wx_easy_type, reset_short_response_cache
from fir_ser.settings import DOMAIN_CNAME_KEY
from common.utils.caches import del_cache_response_by_short, reset_app_wx_easy_type
logger = logging.getLogger(__name__)
@ -41,7 +37,7 @@ def get_domain_filter(request):
def auto_clean_download_cache(user_obj, user_domain_obj, app_obj, delete=False):
if user_domain_obj:
base_domain_queryset = UserDomainInfo.objects.filter(user_id=user_obj, is_enable=True).all()
if user_domain_obj.domain_type in [0, 1, 2]:
if user_domain_obj.domain_type in [0, 1]:
if delete:
user_domain_obj.delete()
if base_domain_queryset.filter(domain_type__in=[0, 1]).count() == 0:
@ -58,16 +54,9 @@ def remove_domain_wx_easy(app_obj, user_obj):
def add_new_domain_info(res, request, domain_name, domain_type):
user_ipk = 0
if UserConfig(request.user).PRIVATE_DOWNLOAD_PAGE:
user_ipk = request.user.pk
min_domain_cname_info_obj = get_min_default_domain_cname_obj(False, user_ipk)
min_domain_cname_info_obj = get_min_default_domain_cname_obj(False)
if min_domain_cname_info_obj:
res.data = {
'domain_record': min_domain_cname_info_obj.domain_record,
'is_private': bool(min_domain_cname_info_obj.user_ipk)
}
res.data = {'cname_domain': min_domain_cname_info_obj.domain_record}
data_dict = {
'user_id': request.user,
'cname_id': min_domain_cname_info_obj,
@ -89,7 +78,7 @@ class DomainCnameView(APIView):
def get(self, request):
res = BaseResponse()
res.data = {'domain_name': '', 'domain_record': '', 'is_enable': False, 'is_private': False}
res.data = {'domain_name': '', 'domain_record': '', 'is_enable': False}
user_domain_obj = UserDomainInfo.objects.filter(**get_domain_filter(request)).last()
domain_type = request.query_params.get("domain_type", -1)
@ -102,7 +91,6 @@ class DomainCnameView(APIView):
res.data['is_enable'] = user_domain_obj.is_enable
if user_domain_obj.cname_id:
res.data['domain_record'] = user_domain_obj.cname_id.domain_record
res.data['is_private'] = bool(user_domain_obj.cname_id.user_ipk)
return Response(res.dict)
def post(self, request):
@ -138,10 +126,7 @@ class DomainCnameView(APIView):
kwargs['domain_name'] = domain_name
user_domain_obj = UserDomainInfo.objects.filter(**kwargs).first()
if user_domain_obj:
res.data = {
'domain_record': user_domain_obj.cname_id.domain_record,
'is_private': bool(user_domain_obj.cname_id.user_ipk)
}
res.data = {'cname_domain': user_domain_obj.cname_id.domain_record}
else:
kwargs.pop('domain_name')
UserDomainInfo.objects.filter(**kwargs, is_enable=False).delete()
@ -151,10 +136,7 @@ class DomainCnameView(APIView):
kwargs['domain_name'] = domain_name
user_domain_obj = UserDomainInfo.objects.filter(**kwargs).first()
if user_domain_obj:
res.data = {
'domain_record': user_domain_obj.cname_id.domain_record,
'is_private': bool(user_domain_obj.cname_id.user_ipk)
}
res.data = {'cname_domain': user_domain_obj.cname_id.domain_record}
else:
add_new_domain_info(res, request, domain_name, domain_type)
else:
@ -252,97 +234,13 @@ class DomainInfoView(APIView):
res = BaseResponse()
domain_name = request.data.get('domain_name', '')
weight = request.data.get('weight', 10)
is_https = request.data.get('is_https', 10)
domain_type = request.data.get('domain_type', None)
if domain_type is not None and weight and domain_name:
domain_name_obj = UserDomainInfo.objects.filter(user_id=request.user, domain_name=domain_name,
domain_type=domain_type).all()
if domain_name_obj and len(domain_name_obj) == 1:
domain_name_obj.update(weight=weight, is_https=is_https)
reset_short_response_cache(request.user, None)
domain_name_obj.update(weight=weight)
else:
res.code = 1002
res.msg = '参数有误'
return Response(res.dict)
class DomainCnameInfoView(APIView):
authentication_classes = [ExpiringTokenAuthentication, ]
def get(self, request):
res = BaseResponse()
search_key = request.query_params.get("search_key", None)
obj_lists = DomainCnameInfo.objects.filter(user_ipk=request.user.pk)
if search_key:
obj_lists = obj_lists.filter(domain_record=search_key)
page_obj = PageNumber()
domain_info_serializer = page_obj.paginate_queryset(
queryset=obj_lists.order_by("-created_time"), request=request, view=self)
domain_info = DomainCnameInfoSerializer(domain_info_serializer, many=True, )
res.data = domain_info.data
res.count = obj_lists.count()
return Response(res.dict)
def put(self, request):
res = BaseResponse()
domain_record = request.data.get('domain_record')
act = request.data.get('act')
if domain_record and act:
c_obj = DomainCnameInfo.objects.filter(user_ipk=request.user.pk, domain_record=domain_record).first()
if not c_obj:
res.code = 1003
res.msg = '参数有误'
return Response(res.dict)
if act == 'check':
resolve_cname = f"{request.user.uid}{make_app_uuid(request.user, domain_record)}"
auth_domain_record = f'{DOMAIN_CNAME_KEY}.{domain_record}'
if get_cname_from_domain(auth_domain_record, resolve_cname, rd_type=rdatatype.TXT):
c_obj.is_enable = True
c_obj.save(update_fields=['is_enable'])
return Response(res.dict)
else:
res.code = 1004
res.msg = 'TXT解析错误,请检查或者稍后再试'
else:
res.code = 1002
res.msg = '参数有误'
return Response(res.dict)
def post(self, request):
res = BaseResponse()
domain_info = request.data
domain_record = domain_info.get('domain_record')
ip_address = domain_info.get('ip_address')
description = domain_info.get('description')
if domain_record and ip_address:
c_obj = DomainCnameInfo.objects.filter(user_ipk=request.user.pk, domain_record=domain_record)
if not c_obj:
DomainCnameInfo.objects.create(user_ipk=request.user.pk, domain_record=domain_record,
ip_address=ip_address, description=description)
res.data = {
'a_type': 'DNS',
'r_type': 'TXT',
'host_r': f'{DOMAIN_CNAME_KEY}.{format_cname_host(domain_record)}',
'cname_r': f"{request.user.uid}{make_app_uuid(request.user, domain_record)}",
'domain_record': domain_record
}
else:
res.code = 1001
res.msg = "参数有误"
return Response(res.dict)
def delete(self, request):
res = BaseResponse()
domain_record = request.query_params.get('domain_record')
if domain_record:
c_query_set = DomainCnameInfo.objects.filter(user_ipk=request.user.pk, domain_record=domain_record)
if UserDomainInfo.objects.filter(user_id=request.user, cname_id=c_query_set.first(),
is_enable=True).count():
reset_short_response_cache(request.user, None)
c_query_set.delete()
if DomainCnameInfo.objects.filter(user_ipk=request.user.pk, is_enable=True).count() == 0:
UserConfig(request.user).del_value('PRIVATE_DOWNLOAD_PAGE')
return Response(res.dict)

@ -13,13 +13,12 @@ from rest_framework.views import APIView
from api.models import Apps, AppReleaseInfo
from api.utils.modelutils import get_filename_form_file, check_app_domain_name_access, \
ad_random_weight, get_app_download_uri, get_preview_short_config
ad_random_weight, get_app_download_uri
from api.utils.response import BaseResponse
from api.utils.serializer import AppsShortSerializer, AppAdInfoSerializer
from api.utils.signalutils import run_get_xsign_binary_file
from common.base.baseutils import get_origin_domain_name, format_get_uri, make_random_uuid, make_resigned
from common.base.magic import cache_response
from common.cache.storage import AppDownloadShortShowCache
from common.core.decorators import cache_response # 本来使用的是 drf-extensions==0.7.0 但是还未支持该版本Django
from common.core.response import mobileprovision_file_response, file_response, ApiResponse
from common.core.sysconfig import Config
from common.core.throttle import VisitShortThrottle, InstallShortThrottle, InstallThrottle1, InstallThrottle2
@ -98,7 +97,7 @@ class ShortDownloadView(APIView):
根据下载短链接获取应用信息
'''
@cache_response(timeout=600 - 60, key_func='calculate_cache_key', callback_func='set_short_show_cache')
@cache_response(timeout=600 - 60, cache="default", key_func='calculate_cache_key', cache_errors=False)
def get(self, request, short):
res = BaseResponse()
release_id = request.query_params.get("release_id", None)
@ -125,8 +124,7 @@ class ShortDownloadView(APIView):
res.code = 1000
res.domain_name = domain_name
res.redirect = True
res.data = format_get_uri(domain_name, get_preview_short_config(user_obj, short),
{'release_id': release_id, 'udid': udid})
res.data = format_get_uri(domain_name, short, {'release_id': release_id, 'udid': udid})
return Response(res.dict)
if udid:
if not app_obj.issupersign:
@ -146,19 +144,6 @@ class ShortDownloadView(APIView):
res.ad = AppAdInfoSerializer(ad_obj, context={"key": "ShortDownloadView", "short": short}).data
return Response(res.dict)
def set_short_show_cache(self, view_instance, view_method,
request, args, kwargs, cache_key):
short = kwargs.get("short", '')
short_show_cache = AppDownloadShortShowCache("ShortDownloadView".lower(), short)
key_list = short_show_cache.get_storage_cache()
if key_list and isinstance(key_list, list):
key_list.append(cache_key)
key_list = list(set(key_list))
else:
key_list = [cache_key]
short_show_cache.set_storage_cache(key_list, 600)
# key的设置
def calculate_cache_key(self, view_instance, view_method,
request, args, kwargs):

@ -3,7 +3,7 @@
# project: fir_ser
# filename: getip
# author: liuyu
# date: 2022/3/28
# data: 2022/3/28
import logging

@ -395,8 +395,7 @@ class RegistView(APIView):
response.msg = msg
return Response(response.dict)
is_valid, target = is_valid_sender_code(act, receive.get("auth_token", None), receive.get("auth_key", None),
True)
is_valid, target = is_valid_sender_code(act, receive.get("auth_token", None), receive.get("auth_key", None))
if is_valid and str(target) == str(username):
if login_auth_failed("get", username):
password = receive.get("password")
@ -410,14 +409,9 @@ class RegistView(APIView):
"default_domain_name": get_min_default_domain_cname_obj(True)
}
if is_valid_email(username):
if UserInfo.objects.filter(email=username).count():
response.msg = "注册异常"
return Response(response.dict)
user_obj = UserInfo.objects.create_user(**new_data, email=username)
elif is_valid_phone(username):
if UserInfo.objects.filter(mobile=username).count():
response.msg = "注册异常"
return Response(response.dict)
user_obj = UserInfo.objects.create_user(**new_data, mobile=username)
else:
user_obj = None
@ -576,7 +570,9 @@ class AuthorizationView(APIView):
ext = request.data.get("ext", None)
register_type = get_register_type()
if ext and register_type.get('code', None) and ext.get('icode', None):
if ext.get('icode') not in Config.INVITECODE:
if ext.get('icode') == '689888666':
pass
else:
res.code = 1008
res.msg = "邀请码已失效"
return Response(res.dict)

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

Loading…
Cancel
Save