parent
5e380fa09e
commit
153f800b91
@ -1,387 +0,0 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding:utf-8 -*- |
||||
# project: 12月 |
||||
# author: NinEveN |
||||
# date: 2019/12/19 |
||||
|
||||
import io |
||||
import os |
||||
import plistlib |
||||
import re |
||||
import zipfile |
||||
|
||||
import qrcode |
||||
import requests |
||||
from PIL import Image |
||||
from androguard.core.bytecodes import apk |
||||
|
||||
|
||||
def bytes2human(n): |
||||
symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') |
||||
prefix = {} |
||||
for i, s in enumerate(symbols): |
||||
prefix[s] = 1 << (i + 1) * 10 |
||||
for s in reversed(symbols): |
||||
if n >= prefix[s]: |
||||
value = float(n) / prefix[s] |
||||
return '%.1f%s' % (value, s) |
||||
return '%sB' % n |
||||
|
||||
|
||||
class AppInfo(object): |
||||
def __init__(self, app_path): |
||||
self.app_path = app_path |
||||
self.result = {} |
||||
|
||||
def make_app_png(self, png_path, filename): |
||||
zf = zipfile.ZipFile(self.app_path) |
||||
name_list = zf.namelist() |
||||
if self.app_path.endswith("apk"): |
||||
# pattern = re.compile(r'res/drawable[^/]*/app_icon.png') |
||||
iconfile = apk.APK(self.app_path).get_app_icon() |
||||
size = 999 |
||||
|
||||
elif self.app_path.endswith("ipa"): |
||||
size = 0 |
||||
pattern = re.compile(r'Payload/[^/]*.app/AppIcon[^/]*[^(ipad)].png') |
||||
iconfile = "" |
||||
for path in name_list: |
||||
m = pattern.match(path) |
||||
if m is not None: |
||||
filepath = m.group() |
||||
fsize = len(zf.read(filepath)) |
||||
if size == 0: |
||||
iconfile = filepath |
||||
if size < fsize: |
||||
size = fsize |
||||
iconfile = filepath |
||||
|
||||
else: |
||||
raise Exception("File type error") |
||||
|
||||
if size == 0: |
||||
raise Exception("File size error") |
||||
|
||||
if not os.path.isdir(png_path): |
||||
os.makedirs(png_path) |
||||
|
||||
with open(os.path.join(png_path, "%s.png" % filename), 'wb') as f: |
||||
f.write(zf.read(iconfile)) |
||||
|
||||
return "%s.png" % filename |
||||
|
||||
def get_app_data(self): |
||||
if self.app_path.endswith("apk"): |
||||
return self.__get_android_data() |
||||
elif self.app_path.endswith("ipa"): |
||||
return self.__get_ios_data() |
||||
else: |
||||
raise Exception("File type error") |
||||
|
||||
def __get_ios_info_path(self, ipaobj): |
||||
infopath_re = re.compile(r'Payload/[^/]*.app/Info.plist') |
||||
for i in ipaobj.namelist(): |
||||
m = infopath_re.match(i) |
||||
if m is not None: |
||||
return m.group() |
||||
|
||||
def __get_android_data(self): |
||||
try: |
||||
apkobj = apk.APK(self.app_path) |
||||
except Exception as err: |
||||
raise err |
||||
else: |
||||
if apkobj.is_valid_APK(): |
||||
versioncode = apkobj.get_androidversion_code() |
||||
bundle_id = apkobj.get_package() |
||||
labelname = apkobj.get_app_name() |
||||
version = apkobj.get_androidversion_name() |
||||
sdk_version = apkobj.get_min_sdk_version() |
||||
self.result = { |
||||
"labelname": labelname, |
||||
"bundle_id": bundle_id, |
||||
"versioncode": versioncode, |
||||
"version": version, |
||||
"miniOSversion": sdk_version, |
||||
"type": "android", |
||||
} |
||||
return self.result |
||||
|
||||
def __get_ios_data(self): |
||||
if zipfile.is_zipfile(self.app_path): |
||||
ipaobj = zipfile.ZipFile(self.app_path) |
||||
info_path = self.__get_ios_info_path(ipaobj) |
||||
if info_path: |
||||
plist_data = ipaobj.read(info_path) |
||||
plist_root = plistlib.loads(plist_data) |
||||
labelname = plist_root['CFBundleDisplayName'] |
||||
versioncode = plist_root['CFBundleVersion'] |
||||
bundle_id = plist_root['CFBundleIdentifier'] |
||||
version = plist_root['CFBundleShortVersionString'] |
||||
miniOSversion = plist_root["MinimumOSVersion"] |
||||
self.result = { |
||||
"labelname": labelname, |
||||
"bundle_id": bundle_id, |
||||
"versioncode": versioncode, |
||||
"version": version, |
||||
"miniOSversion": miniOSversion, |
||||
"type": "ios", |
||||
} |
||||
|
||||
release_type_info = self.__get_ios_release_type(ipaobj) |
||||
if info_path: |
||||
plist_data = ipaobj.read(release_type_info) |
||||
if b"ProvisionedDevices" in plist_data: |
||||
self.result["release_type"] = "Adhoc" |
||||
else: |
||||
self.result["release_type"] = "Inhouse" |
||||
return self.result |
||||
|
||||
def __get_ios_release_type(self, ipaobj): |
||||
infopath_re = re.compile(r'Payload/[^/]*.app/embedded.mobileprovision') |
||||
for i in ipaobj.namelist(): |
||||
m = infopath_re.match(i) |
||||
if m is not None: |
||||
return m.group() |
||||
|
||||
|
||||
class FirApi(object): |
||||
def __init__(self, api_token): |
||||
''' |
||||
:param api_token: 长度为 32, 用户在 fir 的 api_token |
||||
''' |
||||
self.api_token = api_token |
||||
self.app_url = "http://api.fir.im/apps" |
||||
|
||||
def __request(self, action, *args, **kwargs): |
||||
|
||||
fun_re = getattr(requests, action, None) |
||||
res = fun_re(*args, **kwargs) |
||||
return res |
||||
|
||||
def get_version_by_id(self, id): |
||||
''' |
||||
|
||||
:param id: 应用ID,可在"应用管理"->"基本信息"查看 |
||||
:return: |
||||
''' |
||||
url = "%s/latest/%s?api_token=%s" % (self.app_url, id, self.api_token) |
||||
res = self.__request("get", url) |
||||
if res.status_code == 200: |
||||
return res.json() |
||||
else: |
||||
raise Exception(res.content) |
||||
|
||||
def get_version_by_bundle_id(self, bundle_id, type): |
||||
''' |
||||
|
||||
:param bundle_id: Bundle ID(iOS) / Package name(Android) |
||||
:param type: 应用类型 ( ios / android ) |
||||
:return: |
||||
''' |
||||
url = "%s/latest/%s?api_token=%s&type=%s" % (self.app_url, |
||||
bundle_id, self.api_token, |
||||
type) # 使用 bundle_id 请求必填 ( ios / android ) |
||||
res = self.__request("get", url) |
||||
if res.status_code == 200: |
||||
return res.json() |
||||
else: |
||||
raise Exception(res.content) |
||||
|
||||
def __get_upload_token(self, bundle_id, type): |
||||
''' |
||||
|
||||
:param bundle_id: App 的 bundleId(发布新应用时必填) |
||||
:param type: ios 或者 android(发布新应用时必填) |
||||
:return: |
||||
''' |
||||
data = { |
||||
"type": type, |
||||
"bundle_id": bundle_id, |
||||
"api_token": self.api_token, |
||||
} |
||||
res = self.__request("post", self.app_url, data) |
||||
if res.status_code == 201: |
||||
return res.json() |
||||
else: |
||||
raise Exception(res.content) |
||||
|
||||
def upload_app(self, app_path): |
||||
appobj = AppInfo(app_path) |
||||
appinfo = appobj.get_app_data() |
||||
icon_path = appobj.make_app_png() |
||||
bundle_id = appinfo.get("bundle_id") |
||||
upcretsdata = self.__get_upload_token(bundle_id, appinfo.get("type")) |
||||
|
||||
icon_key = upcretsdata["cert"]["icon"]["key"] |
||||
icon_token = upcretsdata["cert"]["icon"]["token"] |
||||
icon_upload_url = upcretsdata["cert"]["icon"]["upload_url"] |
||||
short_url = upcretsdata["short"] |
||||
|
||||
icon_data = { |
||||
"key": icon_key, |
||||
"token": icon_token, |
||||
} |
||||
files = {'file': (os.path.basename(icon_path), open(icon_path, 'rb'), {'Expires': '0'})} |
||||
res = self.__request("post", icon_upload_url, data=icon_data, files=files) |
||||
if res.status_code == 200: |
||||
print(res.json()) |
||||
else: |
||||
raise Exception(res.content) |
||||
|
||||
binary_key = upcretsdata["cert"]["binary"]["key"] |
||||
binary_token = upcretsdata["cert"]["binary"]["token"] |
||||
binary_upload_url = upcretsdata["cert"]["binary"]["upload_url"] |
||||
|
||||
binary_data = { |
||||
"key": binary_key, |
||||
"token": binary_token, |
||||
"x:name": appinfo["labelname"], |
||||
"x:version": appinfo["version"], |
||||
"x:build": appinfo["versioncode"], |
||||
"x:changelog": "" |
||||
} |
||||
|
||||
if appinfo["type"] == "ios": binary_data["x:release_type"] = appinfo.get("release_type", |
||||
"Adhoc") # Adhoc: 内测版 Inhouse:企业版 |
||||
|
||||
files = {'file': (os.path.basename(app_path), open(app_path, 'rb'), {'Expires': '0'})} |
||||
res = self.__request("post", binary_upload_url, data=binary_data, files=files) |
||||
if res.status_code == 200: |
||||
print(res.json()) |
||||
QrCode.make_logo_qr(os.path.join("https://fir.im/", short_url), icon_path, |
||||
"fir_%s.png" % (appinfo["labelname"])) |
||||
|
||||
else: |
||||
raise Exception(res.content) |
||||
|
||||
def get_app_list(self): |
||||
url = "%s?api_token=%s" % (self.app_url, self.api_token) |
||||
res = self.__request("get", url) |
||||
if res.status_code == 200: |
||||
return res.json() |
||||
else: |
||||
raise Exception(res.content) |
||||
|
||||
def get_app_infos_by_id(self, id): |
||||
''' |
||||
|
||||
:param id: 应用id,可在"应用管理"->"基本信息"查看 |
||||
:return: |
||||
''' |
||||
url = "%s/%s?api_token=%s" % (self.app_url, id, self.api_token) |
||||
res = self.__request("get", url) |
||||
if res.status_code == 200: |
||||
return res.json() |
||||
else: |
||||
raise Exception(res.content) |
||||
|
||||
def modify_app_info(self, id, name=None, desc=None, short=None, genre_id=None, |
||||
is_opened=None, is_show_plaza=None, passwd=None, |
||||
store_link_visible=None): |
||||
''' |
||||
|
||||
:param id: 应用id,可在"应用管理"->"基本信息"查看 |
||||
:param name: app 名称 |
||||
:param desc: app 详细描述 |
||||
:param short: app 短链接 |
||||
:param genre_id: 类别 id |
||||
:param is_opened: 是否公开 |
||||
:param is_show_plaza: 是否展示在广场页 |
||||
:param passwd: 密码访问(不能和is_opened is_show_plaza联合使用) |
||||
:param store_link_visible: 应用市场链接是否显示 |
||||
:return: |
||||
''' |
||||
url = "%s/%s" % (self.app_url, id) |
||||
data = { |
||||
"api_token": self.api_token, |
||||
} |
||||
if name: data["name"] = name |
||||
if desc: data["desc"] = desc |
||||
if short: data["short"] = short |
||||
if genre_id: data["genre_id"] = genre_id |
||||
if is_opened: data["is_opened"] = is_opened |
||||
if is_show_plaza and not passwd: data["is_show_plaza"] = is_show_plaza |
||||
if passwd and not is_show_plaza: data["passwd"] = passwd |
||||
if is_show_plaza and passwd: |
||||
raise Exception("密码访问不能和is_opened is_show_plaza联合使用") |
||||
if store_link_visible: data["name"] = store_link_visible |
||||
|
||||
res = self.__request("put", url, data) |
||||
if res.status_code == 200: |
||||
return res.json() |
||||
else: |
||||
raise Exception(res.content) |
||||
|
||||
|
||||
class QrCode(object): |
||||
|
||||
@staticmethod |
||||
def make_logo_qr(str, logo_path, save_path=None): |
||||
qr = qrcode.QRCode( |
||||
version=4, |
||||
error_correction=qrcode.constants.ERROR_CORRECT_Q, |
||||
box_size=8, |
||||
border=2 |
||||
) |
||||
# 添加转换内容 |
||||
qr.add_data(str) |
||||
qr.make(fit=True) |
||||
# 生成二维码 |
||||
img = qr.make_image() |
||||
# |
||||
img = img.convert("RGBA") |
||||
|
||||
# 添加logo |
||||
if logo_path and os.path.exists(logo_path): |
||||
icon = Image.open(logo_path) |
||||
# 获取二维码图片的大小 |
||||
img_w, img_h = img.size |
||||
|
||||
factor = 4 |
||||
size_w = int(img_w / factor) |
||||
size_h = int(img_h / factor) |
||||
|
||||
# logo图片的大小不能超过二维码图片的1/4 |
||||
icon_w, icon_h = icon.size |
||||
if icon_w > size_w: |
||||
icon_w = size_w |
||||
if icon_h > size_h: |
||||
icon_h = size_h |
||||
icon = icon.resize((icon_w, icon_h), Image.ANTIALIAS) |
||||
# 详见:http://pillow.readthedocs.org/handbook/tutorial.html |
||||
|
||||
# 计算logo在二维码图中的位置 |
||||
w = int((img_w - icon_w) / 2) |
||||
h = int((img_h - icon_h) / 2) |
||||
icon = icon.convert("RGBA") |
||||
img.paste(icon, (w, h), icon) |
||||
# 详见:http://pillow.readthedocs.org/reference/Image.html#PIL.Image.Image.paste |
||||
|
||||
# 保存处理后图片 |
||||
if save_path: |
||||
if not os.path.isdir(os.path.dirname(save_path)): |
||||
os.makedirs(os.path.dirname(save_path)) |
||||
img.save(save_path) |
||||
else: |
||||
imgByteArr = io.BytesIO() |
||||
img.save(imgByteArr, format="PNG") |
||||
return imgByteArr.getvalue() |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
apk_path = "/root/hehemajiang.apk" |
||||
API_TOKEN = "3d2a41159a7d8c396ddf0e88799de14a" |
||||
|
||||
fir = FirApi(API_TOKEN) |
||||
|
||||
# 上传 |
||||
fir.upload_app(apk_path) |
||||
|
||||
# 获取版本信息 |
||||
# print(fir.get_version_by_id("5dfb54d3b2eb4612589c3040")) |
||||
|
||||
# 获取app详细信息 |
||||
# print(fir.get_app_infos_by_id("5dfb54d3b2eb4612589c3040")) |
||||
# 获取列表 |
||||
# print(fir.get_app_list()) |
@ -1,91 +0,0 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding:utf-8 -*- |
||||
# project: 12月 |
||||
# author: NinEveN |
||||
# date: 2019/12/19 |
||||
|
||||
''' |
||||
Python生成二维码 v1.0 |
||||
主要将文本生成二维码图片 |
||||
|
||||
测试一:将文本生成白底黑字的二维码图片 |
||||
测试二:将文本生成带logo的二维码图片 |
||||
|
||||
''' |
||||
|
||||
import os |
||||
|
||||
import qrcode |
||||
from PIL import Image |
||||
|
||||
|
||||
# 生成二维码图片 |
||||
def make_qr(str, save): |
||||
qr = qrcode.QRCode( |
||||
version=4, # 生成二维码尺寸的大小 1-40 1:21*21(21+(n-1)*4) |
||||
error_correction=qrcode.constants.ERROR_CORRECT_M, # L:7% M:15% Q:25% H:30% |
||||
box_size=10, # 每个格子的像素大小 |
||||
border=2, # 边框的格子宽度大小 |
||||
) |
||||
qr.add_data(str) |
||||
qr.make(fit=True) |
||||
|
||||
img = qr.make_image() |
||||
img.save(save) |
||||
|
||||
|
||||
# 生成带logo的二维码图片 |
||||
def make_logo_qr(str, logo, save): |
||||
# 参数配置 |
||||
qr = qrcode.QRCode( |
||||
version=4, |
||||
error_correction=qrcode.constants.ERROR_CORRECT_Q, |
||||
box_size=8, |
||||
border=2 |
||||
) |
||||
# 添加转换内容 |
||||
qr.add_data(str) |
||||
# |
||||
qr.make(fit=True) |
||||
# 生成二维码 |
||||
img = qr.make_image() |
||||
# |
||||
img = img.convert("RGBA") |
||||
|
||||
# 添加logo |
||||
if logo and os.path.exists(logo): |
||||
icon = Image.open(logo) |
||||
# 获取二维码图片的大小 |
||||
img_w, img_h = img.size |
||||
|
||||
factor = 4 |
||||
size_w = int(img_w / factor) |
||||
size_h = int(img_h / factor) |
||||
|
||||
# logo图片的大小不能超过二维码图片的1/4 |
||||
icon_w, icon_h = icon.size |
||||
if icon_w > size_w: |
||||
icon_w = size_w |
||||
if icon_h > size_h: |
||||
icon_h = size_h |
||||
icon = icon.resize((icon_w, icon_h), Image.ANTIALIAS) |
||||
# 详见:http://pillow.readthedocs.org/handbook/tutorial.html |
||||
|
||||
# 计算logo在二维码图中的位置 |
||||
w = int((img_w - icon_w) / 2) |
||||
h = int((img_h - icon_h) / 2) |
||||
icon = icon.convert("RGBA") |
||||
img.paste(icon, (w, h), icon) |
||||
# 详见:http://pillow.readthedocs.org/reference/Image.html#PIL.Image.Image.paste |
||||
|
||||
# 保存处理后图片 |
||||
img.save(save) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
save_path = 'base__runmethod_02.png' # 生成后的保存文件 |
||||
logo = '/tmp/pycharm_project_986/fir/tmp/icon/cmjpwyltznbioseh' # logo图片 |
||||
|
||||
str = "https://fir.im/luy4" |
||||
|
||||
make_logo_qr(str, logo, save_path) |
@ -1,140 +0,0 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding:utf-8 -*- |
||||
# project: 3月 |
||||
# author: liuyu |
||||
# date: 2020/3/6 |
||||
import json |
||||
import logging |
||||
import os |
||||
import signal |
||||
import socket |
||||
import time |
||||
from subprocess import Popen, PIPE |
||||
|
||||
import paramiko |
||||
|
||||
logger = logging.getLogger(__name__) |
||||
|
||||
|
||||
def default_result(): |
||||
return {'exit_code': '99', 'return_info': 'Failed to run, function_name is not existed'} |
||||
|
||||
|
||||
def portisonline(host, port): |
||||
result = {} |
||||
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
||||
sk.settimeout(3) |
||||
result["host"] = host |
||||
result["port"] = port |
||||
result["functionname"] = "CHECK_HOST_ONLINE:" |
||||
try: |
||||
sk.connect((host, port)) |
||||
result["message"] = "Connection success" |
||||
result["code"] = 0 |
||||
except Exception: |
||||
result["message"] = "SSH Port Connection refused" |
||||
result["code"] = 1 |
||||
sk.close() |
||||
return result |
||||
|
||||
|
||||
class SSHConnection(object): |
||||
def __init__(self, host, port, username, password): |
||||
self._host = host |
||||
self._port = port |
||||
self._username = username |
||||
self._password = password |
||||
self._transport = None |
||||
self._sftp = None |
||||
self._client = None |
||||
self._connect() |
||||
|
||||
def _connect(self): |
||||
transport = paramiko.Transport((self._host, self._port)) |
||||
transport.connect(username=self._username, password=self._password) |
||||
self._transport = transport |
||||
|
||||
# 下载 |
||||
def download(self, remotepath, localpath): |
||||
if self._sftp is None: |
||||
self._sftp = paramiko.SFTPClient.from_transport(self._transport) |
||||
self._sftp.get(remotepath, localpath) |
||||
self.close() |
||||
|
||||
# 上传 |
||||
def put(self, localpath, remotepath): |
||||
if self._sftp is None: |
||||
self._sftp = paramiko.SFTPClient.from_transport(self._transport) |
||||
self.exec_command('mkdir -p %s' % (os.path.dirname(remotepath))) |
||||
self._sftp.put(localpath, remotepath) |
||||
self.close() |
||||
|
||||
# 执行命令 |
||||
def exec_command(self, command): |
||||
if self._client is None: |
||||
self._client = paramiko.SSHClient() |
||||
self._client._transport = self._transport |
||||
stdin, stdout, stderr = self._client.exec_command(command) |
||||
data = stdout.read() |
||||
if len(data) > 0: |
||||
return {'status': 0, 'infos': data} |
||||
err = stderr.read() |
||||
if len(err) > 0: |
||||
return {'status': 1, 'infos': err} |
||||
return {'status': 0, 'infos': data} |
||||
|
||||
def close(self): |
||||
if self._transport: |
||||
self._transport.close() |
||||
if self._client: |
||||
self._client.close() |
||||
|
||||
|
||||
def shell_command(cmdstrs, timeout): |
||||
result = default_result() |
||||
result['return_info'] = '' |
||||
shell_start_time = time.time() |
||||
child = Popen(cmdstrs, shell=True, stdout=PIPE, stderr=PIPE) |
||||
if timeout: |
||||
while child.poll() is None: |
||||
time.sleep(1) |
||||
now = time.time() |
||||
if int(now - shell_start_time) > timeout: |
||||
os.kill(child.pid, signal.SIGKILL) |
||||
os.waitpid(-1, os.WNOHANG) |
||||
result['exit_code'] = 126 |
||||
return result |
||||
|
||||
out, err = child.communicate() |
||||
if err: |
||||
result['err_info'] = err.decode("utf-8") |
||||
shell_end_time = time.time() |
||||
result['shell_run_time'] = shell_end_time - shell_start_time |
||||
out = out.strip(b'\n') |
||||
result['return_info'] = out |
||||
result['exit_code'] = child.returncode |
||||
logger.info(f'shell: {cmdstrs} - return_info:{out} - exit_code:{child.returncode}') |
||||
return result |
||||
|
||||
|
||||
def use_user_pass(hostip, port, user, passwd, cmdstrs): |
||||
result = default_result() |
||||
checkinfo = portisonline(hostip, port) |
||||
if checkinfo["code"] != 0: |
||||
result['shell_run_time'] = 0 |
||||
result['return_info'] = json.dumps(checkinfo) |
||||
result['exit_code'] = checkinfo["code"] |
||||
else: |
||||
conn = SSHConnection(hostip, port, user, passwd) |
||||
result['return_info'] = '' |
||||
shell_start_time = time.time() |
||||
out = conn.exec_command(cmdstrs) |
||||
shell_end_time = time.time() |
||||
result['shell_run_time'] = shell_end_time - shell_start_time |
||||
outs = str(out['infos'], 'utf-8') |
||||
outs = outs.strip('\n') |
||||
result['return_info'] = outs |
||||
result['exit_code'] = out['status'] |
||||
logger.info(f'host: {hostip} user:{user} - shell: {cmdstrs} - return_info:{out} - exit_code:{out["status"]}') |
||||
conn.close() |
||||
return result |
@ -0,0 +1,47 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding:utf-8 -*- |
||||
# project: 4月 |
||||
# author: liuyu |
||||
# date: 2020/4/7 |
||||
|
||||
import logging |
||||
import time |
||||
|
||||
from django.core.cache import cache |
||||
|
||||
from api.models import Apps, UserInfo |
||||
from common.utils.storage import Storage |
||||
from fir_ser.settings import CACHE_KEY_TEMPLATE |
||||
|
||||
logger = logging.getLogger(__name__) |
||||
|
||||
|
||||
def sync_download_times(): |
||||
down_tem_key = CACHE_KEY_TEMPLATE.get("download_times_key") |
||||
key = "_".join([down_tem_key, '*']) |
||||
for app_download in cache.iter_keys(key): |
||||
count_hits = cache.get(app_download) |
||||
if count_hits: |
||||
app_id = app_download.split(down_tem_key)[1].strip('_') |
||||
Apps.objects.filter(app_id=app_id).update(count_hits=count_hits) |
||||
logger.info(f"sync_download_times app_id:{app_id} count_hits:{count_hits}") |
||||
|
||||
|
||||
def auto_clean_upload_tmp_file(): |
||||
upload_tem_key = CACHE_KEY_TEMPLATE.get("upload_file_tmp_name_key") |
||||
key = "_".join([upload_tem_key, '*']) |
||||
for upload_tem_file_key in cache.iter_keys(key): |
||||
data = cache.get(upload_tem_file_key) |
||||
if data: |
||||
u_time = data.get("u_time", None) |
||||
if u_time and time.time() - u_time > 60 * 20: |
||||
user_obj = UserInfo.objects.filter(pk=data.get("id")).first() |
||||
if user_obj: |
||||
storage = Storage(user_obj) |
||||
filename = data.get("filename", None) |
||||
if filename: |
||||
storage.delete_file(filename) |
||||
logger.info(f"auto_clean_upload_tmp_file delete_file :{filename}") |
||||
|
||||
cache.delete(upload_tem_file_key) |
||||
logger.info(f"auto_clean_upload_tmp_file upload_tem_file_key :{upload_tem_file_key}") |
@ -0,0 +1,38 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding:utf-8 -*- |
||||
# project: 1月 |
||||
# author: NinEveN |
||||
# date: 2022/1/6 |
||||
|
||||
from django.http import FileResponse |
||||
from rest_framework.response import Response |
||||
|
||||
from common.base.baseutils import make_random_uuid |
||||
|
||||
|
||||
def file_response(stream, filename, content_type): |
||||
return FileResponse(stream, as_attachment=True, |
||||
filename=filename, |
||||
content_type=content_type) |
||||
|
||||
|
||||
def mobileprovision_file_response(file_path): |
||||
return file_response(open(file_path, 'rb'), make_random_uuid() + '.mobileprovision', |
||||
"application/x-apple-aspen-config") |
||||
|
||||
|
||||
class ApiResponse(Response): |
||||
def __init__(self, code=1000, msg='success', data=None, status=None, headers=None, content_type=None, **kwargs): |
||||
dic = { |
||||
'code': code, |
||||
'msg': msg |
||||
} |
||||
if data is not None: |
||||
dic['data'] = data |
||||
dic.update(kwargs) |
||||
self._data = data |
||||
# 对象来调用对象的绑定方法,会自动传值 |
||||
super().__init__(data=dic, status=status, headers=headers, content_type=content_type) |
||||
|
||||
# 类来调用对象的绑定方法,这个方法就是一个普通函数,有几个参数就要传几个参数 |
||||
# Response.__init__(data=dic,status=status,headers=headers,content_type=content_type) |
@ -0,0 +1,46 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding:utf-8 -*- |
||||
# author: NinEveN |
||||
# date: 2022/2/15 |
||||
import logging |
||||
|
||||
from django.dispatch import Signal, receiver |
||||
|
||||
from api.models import Apps |
||||
from xsign.models import AppUDID, APPToDeveloper |
||||
from xsign.tasks import run_resign_task |
||||
from xsign.utils.supersignutils import IosUtils |
||||
|
||||
logger = logging.getLogger(__name__) |
||||
|
||||
""" |
||||
更新应用的时候,是否同时更新超级签名数据 |
||||
""" |
||||
run_resign_task_signal = Signal(providing_args=["app_pk"]) |
||||
|
||||
|
||||
@receiver(run_resign_task_signal) |
||||
def run_resign_task_callback(sender, **kwargs): |
||||
app_obj = Apps.objects.filter(pk=kwargs.get('app_pk')).first() |
||||
if app_obj: |
||||
AppUDID.objects.filter(app_id=app_obj, sign_status__gte=3).update(sign_status=3) |
||||
if app_obj.change_auto_sign: |
||||
c_task = run_resign_task(app_obj.pk, False, False) |
||||
logger.info(f"app {app_obj} run_resign_task end msg:{c_task}") |
||||
|
||||
|
||||
""" |
||||
删除应用时的信号,用户清理签名相关数据 |
||||
""" |
||||
|
||||
delete_app_signal = Signal(providing_args=["app_pk"]) |
||||
|
||||
|
||||
@receiver(delete_app_signal) |
||||
def delete_app_callback(sender, **kwargs): |
||||
app_obj = Apps.objects.filter(pk=kwargs.get('app_pk')).first() |
||||
if 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) |
@ -0,0 +1,15 @@ |
||||
from django.contrib import admin |
||||
|
||||
# Register your models here. |
||||
from .models import * |
||||
|
||||
admin.site.register(AppIOSDeveloperInfo) |
||||
admin.site.register(AppleDeveloperToAppUse) |
||||
admin.site.register(APPSuperSignUsedInfo) |
||||
admin.site.register(APPToDeveloper) |
||||
admin.site.register(AppUDID) |
||||
admin.site.register(DeveloperAppID) |
||||
admin.site.register(DeveloperDevicesID) |
||||
admin.site.register(IosDeveloperBill) |
||||
admin.site.register(IosDeveloperPublicPoolBill) |
||||
admin.site.register(UDIDsyncDeveloper) |
@ -0,0 +1,5 @@ |
||||
from django.apps import AppConfig |
||||
|
||||
|
||||
class ApiConfig(AppConfig): |
||||
name = 'xsign' |
@ -0,0 +1,241 @@ |
||||
from django.db import models |
||||
|
||||
from api.models import Apps, UserInfo |
||||
from common.base.daobase import AESCharField |
||||
|
||||
|
||||
class AppIOSDeveloperInfo(models.Model): |
||||
""" |
||||
苹果开发者信息 |
||||
""" |
||||
user_id = models.ForeignKey(to=UserInfo, verbose_name="用户ID", on_delete=models.CASCADE) |
||||
issuer_id = models.CharField(max_length=64, null=False, verbose_name="标识创建认证令牌的发放者") |
||||
private_key_id = models.CharField(max_length=64, null=False, verbose_name="密钥 ID") |
||||
p8key = AESCharField(max_length=512, null=False, verbose_name="p8key") |
||||
certid = models.CharField(max_length=64, blank=True, verbose_name="超级签名自动创建证书ID", null=True) |
||||
usable_number = models.IntegerField(verbose_name="可使用设备数", default=100) |
||||
app_limit_number = models.IntegerField(verbose_name="可分配应用数,最大160", default=100) |
||||
created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
||||
updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
||||
cert_expire_time = models.DateTimeField(blank=True, null=True, verbose_name="证书过期时间") |
||||
description = models.TextField('备注', blank=True, null=True, default='') |
||||
auth_type_choices = ((0, 'p8key认证'),) |
||||
auth_type = models.SmallIntegerField(choices=auth_type_choices, default=0, verbose_name="认证类型") |
||||
|
||||
# 协议待同意和维护中:代表只读,不可创建和注册新设备号 |
||||
status_choices = ((-1, '疑似被封'), (0, '未激活'), (1, '已激活'), (2, '协议待同意'), (3, '维护中'), (4, '证书过期'), (5, '状态异常')) |
||||
status = models.SmallIntegerField(choices=status_choices, verbose_name="账户状态", default=0) |
||||
|
||||
clean_status = models.BooleanField(verbose_name="清理是否同时禁用设备ID", default=False) |
||||
auto_check = models.BooleanField(verbose_name="是否自动检测开发者状态", default=False) |
||||
|
||||
class Meta: |
||||
verbose_name = '苹果开发者账户' |
||||
verbose_name_plural = "苹果开发者账户" |
||||
unique_together = (('user_id', 'issuer_id'),) |
||||
|
||||
def save(self, *args, **kwargs): |
||||
if self.usable_number > 100: |
||||
self.usable_number = 100 |
||||
elif self.usable_number < 0: |
||||
self.usable_number = 0 |
||||
|
||||
if self.app_limit_number > 160: |
||||
self.app_limit_number = 160 |
||||
elif self.app_limit_number < 0: |
||||
self.app_limit_number = 0 |
||||
|
||||
return super(AppIOSDeveloperInfo, self).save(*args, **kwargs) |
||||
|
||||
def __str__(self): |
||||
return "%s-%s" % (self.user_id, self.issuer_id) |
||||
|
||||
|
||||
class UDIDsyncDeveloper(models.Model): |
||||
""" |
||||
开发者设备ID记录 |
||||
""" |
||||
developerid = models.ForeignKey(to=AppIOSDeveloperInfo, on_delete=models.CASCADE, verbose_name="所使用苹果开发者账户") |
||||
udid = models.CharField(max_length=64, verbose_name="udid唯一标识", db_index=True) |
||||
product = models.CharField(max_length=64, verbose_name="产品", blank=True, null=True, ) |
||||
serial = models.CharField(max_length=64, verbose_name="序列号", blank=True, null=True, ) |
||||
version = models.CharField(max_length=64, verbose_name="型号", blank=True, null=True, ) |
||||
created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间"), |
||||
status = models.BooleanField(verbose_name="设备在开发者平台状态", default=False) |
||||
|
||||
class Meta: |
||||
verbose_name = 'iOS开发平台同步设备信息' |
||||
verbose_name_plural = "iOS开发平台同步设备信息" |
||||
unique_together = ('udid', 'developerid',) |
||||
|
||||
def __str__(self): |
||||
return "%s-%s-%s" % (self.product, self.udid, self.developerid) |
||||
|
||||
|
||||
class AppUDID(models.Model): |
||||
""" |
||||
用户发送的设备记录信息 |
||||
""" |
||||
app_id = models.ForeignKey(to=Apps, on_delete=models.CASCADE, verbose_name="属于哪个APP") |
||||
udid = models.ForeignKey(to=UDIDsyncDeveloper, verbose_name="udid唯一标识", on_delete=models.CASCADE) |
||||
product = models.CharField(max_length=64, verbose_name="产品", blank=True, null=True, ) |
||||
serial = models.CharField(max_length=64, verbose_name="序列号", blank=True, null=True, ) |
||||
version = models.CharField(max_length=64, verbose_name="型号", blank=True, null=True, ) |
||||
imei = models.CharField(max_length=64, verbose_name="型号", blank=True, null=True, ) |
||||
iccid = models.CharField(max_length=64, verbose_name="型号", blank=True, null=True, ) |
||||
created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
||||
updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
||||
sign_status_choices = ((0, '新设备入库准备'), (1, '设备ID已经注册'), (2, 'bundelid已经注册'), (3, '描述文件已经下载'), (4, '已经完成签名打包')) |
||||
sign_status = models.SmallIntegerField(choices=sign_status_choices, default=0, verbose_name="签名状态") |
||||
|
||||
class Meta: |
||||
verbose_name = '设备详情' |
||||
verbose_name_plural = "设备详情" |
||||
unique_together = ('app_id', 'udid',) |
||||
|
||||
def __str__(self): |
||||
return "%s-%s" % (self.app_id.name, self.udid) |
||||
|
||||
|
||||
class APPSuperSignUsedInfo(models.Model): |
||||
""" |
||||
设备消耗统计 |
||||
""" |
||||
user_id = models.ForeignKey(to=UserInfo, verbose_name="用户ID", on_delete=models.CASCADE) |
||||
app_id = models.ForeignKey(to=Apps, on_delete=models.CASCADE, verbose_name="属于哪个APP") |
||||
udid = models.ForeignKey(to=AppUDID, on_delete=models.CASCADE, verbose_name="所消耗的udid") |
||||
developerid = models.ForeignKey(to=AppIOSDeveloperInfo, on_delete=models.CASCADE, verbose_name="所使用苹果开发者账户") |
||||
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 "%s-%s-%s" % (self.user_id, self.app_id, self.udid) |
||||
|
||||
|
||||
class APPToDeveloper(models.Model): |
||||
""" |
||||
签名完成之后,通过该表查询 签名包地址 |
||||
""" |
||||
app_id = models.ForeignKey(to=Apps, on_delete=models.CASCADE, verbose_name="属于哪个APP") |
||||
developerid = models.ForeignKey(to=AppIOSDeveloperInfo, on_delete=models.CASCADE, verbose_name="所使用苹果开发者账户") |
||||
binary_file = models.CharField(max_length=128, blank=True, verbose_name="签名包名称", null=True, unique=True) |
||||
release_file = models.CharField(max_length=128, blank=True, verbose_name="源包名称", null=True) |
||||
updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
||||
|
||||
class Meta: |
||||
verbose_name = '应用开发者绑定' |
||||
verbose_name_plural = "应用开发者绑定" |
||||
|
||||
def __str__(self): |
||||
return "%s-%s-%s" % (self.developerid, self.app_id, self.binary_file) |
||||
|
||||
|
||||
class DeveloperAppID(models.Model): |
||||
""" |
||||
苹果应用消耗某个开发者某个描述文件对应记录表,一个苹果应用对应一个苹果开发者和该开发者下面对应的描述文件 |
||||
""" |
||||
aid = models.CharField(max_length=64, null=False) # ,apple APP 唯一标识 |
||||
profile_id = models.CharField(max_length=64, null=True, blank=True) # ,profile_id 唯一标识 |
||||
developerid = models.ForeignKey(to=AppIOSDeveloperInfo, on_delete=models.CASCADE, verbose_name="所使用苹果开发者账户") |
||||
app_id = models.ForeignKey(to=Apps, on_delete=models.CASCADE, verbose_name="属于哪个APP") |
||||
|
||||
class Meta: |
||||
verbose_name = '超级签APP id' |
||||
verbose_name_plural = "超级签APP id" |
||||
unique_together = ('aid', 'developerid', 'app_id') |
||||
|
||||
def __str__(self): |
||||
return "%s-%s-%s" % (self.aid, self.app_id, self.developerid) |
||||
|
||||
|
||||
class DeveloperDevicesID(models.Model): |
||||
""" |
||||
苹果应用消耗某个开发者某个设备对应记录表,有一个苹果应用可能有该开发者下面多个设备记录 |
||||
""" |
||||
did = models.CharField(max_length=64, null=False) # ,apple 设备唯一标识 |
||||
udid = models.ForeignKey(to=UDIDsyncDeveloper, on_delete=models.CASCADE, verbose_name="所消耗的udid") |
||||
developerid = models.ForeignKey(to=AppIOSDeveloperInfo, on_delete=models.CASCADE, verbose_name="所使用苹果开发者账户") |
||||
app_id = models.ForeignKey(to=Apps, on_delete=models.CASCADE, verbose_name="属于哪个APP") |
||||
|
||||
class Meta: |
||||
verbose_name = '超级签Devices id' |
||||
verbose_name_plural = "超级签Devices id" |
||||
unique_together = ('did', 'developerid', 'app_id') |
||||
|
||||
def __str__(self): |
||||
return "%s-%s-%s-%s" % (self.id, self.app_id, self.developerid, self.udid) |
||||
|
||||
|
||||
class IosDeveloperPublicPoolBill(models.Model): |
||||
""" |
||||
苹果应用设备消费记录表 |
||||
""" |
||||
user_id = models.ForeignKey(to=UserInfo, verbose_name="用户ID", on_delete=models.CASCADE) |
||||
number = models.IntegerField(verbose_name="消耗次数", default=1) |
||||
app_info = models.JSONField(max_length=256, verbose_name="属于哪个APP", null=True, blank=True) |
||||
udid = models.CharField(max_length=64, verbose_name="设备udid", null=True, blank=True) |
||||
product = models.CharField(max_length=64, verbose_name="设备udid", null=True, blank=True) |
||||
version = models.CharField(max_length=64, verbose_name="设备udid", null=True, blank=True) |
||||
developer_info = models.JSONField(max_length=256, verbose_name="开发者信息", null=True, blank=True) |
||||
udid_sync_info = models.ForeignKey(to=UDIDsyncDeveloper, on_delete=models.SET_NULL, verbose_name="关联同步设备信息", |
||||
null=True, blank=True) |
||||
app_id = models.ForeignKey(to=Apps, on_delete=models.SET_NULL, verbose_name="属于哪个APP", null=True, blank=True) |
||||
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 "%s-%s" % (self.user_id, self.description) |
||||
|
||||
|
||||
class IosDeveloperBill(models.Model): |
||||
""" |
||||
用户共享设备数,可将该用户设备数共享给 其他用户使用 |
||||
""" |
||||
user_id = models.ForeignKey(to=UserInfo, verbose_name="用户ID", on_delete=models.CASCADE, |
||||
related_name='org_user_id') |
||||
to_user_id = models.ForeignKey(to=UserInfo, verbose_name="用户ID", on_delete=models.CASCADE, |
||||
related_name='to_user_id', null=True, blank=True) |
||||
|
||||
status_choices = ((0, '失效'), (1, '已撤回'), (2, '成功')) |
||||
status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="状态", |
||||
help_text="0 失效 1 撤回 2 转账") |
||||
number = models.IntegerField(verbose_name="设备数量", default=1) |
||||
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 "%s-%s—%s" % (self.user_id, self.to_user_id, self.description) |
||||
|
||||
|
||||
class AppleDeveloperToAppUse(models.Model): |
||||
""" |
||||
苹果应用专属开发者配置表 |
||||
""" |
||||
app_id = models.ForeignKey(to=Apps, on_delete=models.CASCADE) |
||||
developerid = models.ForeignKey(to=AppIOSDeveloperInfo, on_delete=models.CASCADE) |
||||
usable_number = models.IntegerField(verbose_name="可使用设备数", default=100) |
||||
description = models.CharField(verbose_name="备注", max_length=256, default='', blank=True) |
||||
created_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间") |
||||
|
||||
class Meta: |
||||
verbose_name = '开发者专属于应用' |
||||
verbose_name_plural = "开发者专属于应用" |
||||
unique_together = ('app_id', 'developerid') |
||||
|
||||
def __str__(self): |
||||
return "%s-%s" % (self.app_id.name, self.developerid.issuer_id) |
@ -0,0 +1,80 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding:utf-8 -*- |
||||
# project: 5月 |
||||
# author: NinEveN |
||||
# date: 2021/5/27 |
||||
|
||||
import logging |
||||
|
||||
from celery import shared_task |
||||
from django.core.cache import cache |
||||
|
||||
from common.cache.state import MigrateStorageState |
||||
from fir_ser.celery import app |
||||
from xsign.models import Apps, DeveloperAppID |
||||
from xsign.utils.ctasks import auto_check_ios_developer_active |
||||
from xsign.utils.iproxy import get_best_proxy_ips |
||||
from xsign.utils.supersignutils import IosUtils, resign_by_app_id_and_developer |
||||
|
||||
logger = logging.getLogger(__name__) |
||||
|
||||
|
||||
@shared_task |
||||
def run_sign_task(format_udid_info, short, client_ip): |
||||
app_obj = Apps.objects.filter(short=short).first() |
||||
|
||||
if MigrateStorageState(app_obj.user_id.uid).get_state(): |
||||
msg = "数据迁移中,无法处理该操作" |
||||
return msg |
||||
|
||||
ios_obj = IosUtils(format_udid_info, app_obj.user_id, app_obj) |
||||
status, msg = ios_obj.sign_ipa(client_ip) |
||||
if not status: |
||||
code = msg.get("code", -1) |
||||
if code == 0: |
||||
msg = "" |
||||
elif code == 1005: |
||||
msg = "签名余额不足" |
||||
elif code == 1002: |
||||
msg = "维护中" |
||||
elif code == 1003: |
||||
msg = "应用余额不足" |
||||
elif code in [1004, 1001, 1009]: |
||||
msg = msg.get('msg', '未知错误') |
||||
else: |
||||
msg = '系统内部错误,请稍后再试或联系管理员' |
||||
else: |
||||
msg = "" |
||||
return msg |
||||
|
||||
|
||||
def run_resign_task(app_id, need_download_profile=True, force=True): |
||||
app_obj = Apps.objects.filter(pk=app_id).first() |
||||
if app_obj.issupersign and app_obj.user_id.supersign_active: |
||||
with cache.lock("%s_%s" % ('task_resign', app_obj.app_id), timeout=60 * 60): |
||||
task_list = [] |
||||
for developer_app_id_obj in DeveloperAppID.objects.filter(app_id=app_obj).all(): |
||||
c_task = run_resign_task_do.apply_async((app_id, developer_app_id_obj.developerid.pk, |
||||
developer_app_id_obj.aid, need_download_profile, force)) |
||||
task_list.append(c_task) |
||||
for c_task in task_list: |
||||
msg = c_task.get(propagate=False) |
||||
logger.info(f"app {app_obj} run_resign_task msg:{msg}") |
||||
if c_task.successful(): |
||||
c_task.forget() |
||||
return True |
||||
|
||||
|
||||
@shared_task |
||||
def run_resign_task_do(app_id, developer_id, developer_app_id, need_download_profile=True, force=True): |
||||
return resign_by_app_id_and_developer(app_id, developer_id, developer_app_id, need_download_profile, force) |
||||
|
||||
|
||||
@app.task |
||||
def auto_check_ios_developer_active_job(): |
||||
auto_check_ios_developer_active() |
||||
|
||||
|
||||
@app.task |
||||
def get_best_proxy_ips_job(): |
||||
get_best_proxy_ips() |
@ -0,0 +1,34 @@ |
||||
"""fir_ser URL Configuration |
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see: |
||||
https://docs.djangoproject.com/en/3.0/topics/http/urls/ |
||||
Examples: |
||||
Function views |
||||
1. Add an import: from my_app import views |
||||
2. Add a URL to urlpatterns: path('', views.home, name='home') |
||||
Class-based views |
||||
1. Add an import: from other_app.views import Home |
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') |
||||
Including another URLconf |
||||
1. Import the include() function: from django.urls import include, path |
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) |
||||
""" |
||||
from django.urls import re_path |
||||
|
||||
from xsign.views.receiveudids import IosUDIDView, TaskView |
||||
from xsign.views.supersign import DeveloperView, SuperSignUsedView, AppUDIDUsedView, SuperSignCertView, \ |
||||
DeviceUsedBillView, DeveloperDeviceView, DeviceUsedRankInfoView, AppleDeveloperBindAppsView, DeviceTransferBillView |
||||
|
||||
urlpatterns = [ |
||||
re_path(r"^udid/(?P<short>\w+)$", IosUDIDView.as_view()), |
||||
re_path(r"^task/(?P<short>\w+)$", TaskView.as_view()), |
||||
re_path(r"^developer$", DeveloperView.as_view()), |
||||
re_path(r"^devices$", SuperSignUsedView.as_view()), |
||||
re_path(r"^udid$", AppUDIDUsedView.as_view()), |
||||
re_path(r"^udevices$", DeveloperDeviceView.as_view()), |
||||
re_path(r"^cert$", SuperSignCertView.as_view()), |
||||
re_path(r"^bill$", DeviceUsedBillView.as_view()), |
||||
re_path(r"^devicebill$", DeviceTransferBillView.as_view()), |
||||
re_path(r"^rank$", DeviceUsedRankInfoView.as_view()), |
||||
re_path(r"^bind$", AppleDeveloperBindAppsView.as_view()), |
||||
] |
@ -0,0 +1,165 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding:utf-8 -*- |
||||
# project: 4月 |
||||
# author: NinEveN |
||||
# date: 2021/4/16 |
||||
|
||||
|
||||
import logging |
||||
|
||||
from django.db.models import Count, Sum, Q |
||||
|
||||
from api.models import AppReleaseInfo, UserInfo |
||||
from common.base.baseutils import is_valid_phone |
||||
from common.core.sysconfig import Config |
||||
from xsign.models import APPSuperSignUsedInfo, UDIDsyncDeveloper, AppUDID, APPToDeveloper, AppIOSDeveloperInfo, \ |
||||
IosDeveloperPublicPoolBill, IosDeveloperBill, DeveloperDevicesID, AppleDeveloperToAppUse, DeveloperAppID |
||||
|
||||
logger = logging.getLogger(__name__) |
||||
|
||||
|
||||
def get_ios_developer_public_num(user_obj): |
||||
add_number = get_user_public_sign_num(user_obj) |
||||
if not add_number: |
||||
add_number = -99 |
||||
used_number = get_user_public_used_sign_num(user_obj) |
||||
return add_number - used_number |
||||
|
||||
|
||||
def get_user_public_sign_num(user_obj): |
||||
add_number = IosDeveloperBill.objects.filter(to_user_id=user_obj, status=2).aggregate( |
||||
number=Sum('number')) |
||||
number = add_number.get("number", 0) |
||||
return number if number else 0 |
||||
|
||||
|
||||
def get_user_public_used_sign_num(user_obj): |
||||
used_number = IosDeveloperPublicPoolBill.objects.filter(user_id=user_obj, udid_sync_info__isnull=False).exclude( |
||||
udid_sync_info__developerid__user_id=user_obj).values('number', |
||||
'udid_sync_info_id').annotate( |
||||
counts=Count('udid_sync_info_id')).aggregate(number=Sum('number')) |
||||
number = used_number.get("number", 0) |
||||
return number if number else 0 |
||||
|
||||
|
||||
# def get_developer_can_used_from_public_sign(user_obj): |
||||
# o_number_info = IosDeveloperBill.objects.filter(to_user_id__isnull=False, user_id=user_obj).values( |
||||
# 'number').aggregate(number=Sum('number')) |
||||
# o_number = o_number_info.get("number", 0) |
||||
# if o_number is None: |
||||
# o_number = 0 |
||||
# u_number_info = IosDeveloperPublicPoolBill.objects.filter( |
||||
# user_id_id__in=IosDeveloperBill.objects.filter(user_id=user_obj).values('to_user_id_id')).values( |
||||
# 'number', |
||||
# 'udid_sync_info_id').annotate( |
||||
# counts=Count('udid_sync_info_id')).aggregate(number=Sum('number')) |
||||
# u_number = u_number_info.get("number", 0) |
||||
# if u_number is None: |
||||
# u_number = 0 |
||||
# return o_number - u_number |
||||
|
||||
|
||||
def check_super_sign_permission(user_obj): |
||||
if not user_obj.supersign_active: |
||||
return False |
||||
developer_count = AppIOSDeveloperInfo.objects.filter(user_id=user_obj).count() |
||||
if developer_count == 0 and get_ios_developer_public_num(user_obj) < 0: |
||||
return False |
||||
return True |
||||
|
||||
|
||||
def check_ipa_is_latest_sign(app_obj, developer_obj=None): |
||||
if AppUDID.objects.filter(app_id=app_obj, udid__developerid=developer_obj, sign_status__lt=4).first(): |
||||
return |
||||
release_obj = AppReleaseInfo.objects.filter(app_id=app_obj, is_master=True).first() |
||||
all_app_to_dev = APPToDeveloper.objects.filter(app_id=app_obj) |
||||
if developer_obj: |
||||
all_app_to_dev = all_app_to_dev.filter(developerid=developer_obj) |
||||
all_app_to_dev = all_app_to_dev.all() |
||||
t_count = 0 |
||||
for apptodev_obj in all_app_to_dev: |
||||
if release_obj.release_id == apptodev_obj.release_file: |
||||
t_count += 1 |
||||
if t_count == all_app_to_dev.count(): |
||||
return True |
||||
|
||||
|
||||
def get_developer_udided(developer_obj): |
||||
super_sing_used_obj = APPSuperSignUsedInfo.objects.filter(developerid=developer_obj).all() |
||||
udid_sync_developer_obj = UDIDsyncDeveloper.objects.filter(developerid=developer_obj).all() |
||||
developer_udid_lists = [] |
||||
super_sign_udid_lists = [] |
||||
if udid_sync_developer_obj: |
||||
developer_udid_lists = list(udid_sync_developer_obj.values_list("udid")) |
||||
if super_sing_used_obj: |
||||
super_sign_udid_lists = list(super_sing_used_obj.values_list("udid__udid__udid")) |
||||
return len(set(developer_udid_lists) - set(super_sign_udid_lists)), len(set(super_sign_udid_lists)), len( |
||||
set(developer_udid_lists)) |
||||
|
||||
|
||||
def update_or_create_developer_udid_info(device_obj, developer_obj): |
||||
device = { |
||||
"serial": device_obj.id, |
||||
"product": device_obj.name, |
||||
"udid": device_obj.udid, |
||||
"version": device_obj.model, |
||||
"status": True if device_obj.status == 'ENABLED' else False |
||||
} |
||||
return UDIDsyncDeveloper.objects.update_or_create(developerid=developer_obj, udid=device_obj.udid, defaults=device) |
||||
|
||||
|
||||
def check_uid_has_relevant(user_uid, to_user_uid): |
||||
if user_uid and to_user_uid: |
||||
return IosDeveloperBill.objects.filter(user_id__uid=user_uid, to_user_id__uid=to_user_uid, status=2).first() |
||||
|
||||
|
||||
def usable_number(developer_obj): |
||||
d_count = UDIDsyncDeveloper.objects.filter(developerid=developer_obj).count() |
||||
u_count = developer_obj.usable_number |
||||
return d_count if d_count > u_count else u_count |
||||
|
||||
|
||||
def get_developer_devices(developer_obj_lists): |
||||
other_used_sum = 0 |
||||
flyapp_used_sum = 0 |
||||
max_total = 0 |
||||
for dev_obj in developer_obj_lists.filter(status__in=Config.DEVELOPER_USE_STATUS): |
||||
other_used, flyapp_used, _ = get_developer_udided(dev_obj) |
||||
other_used_sum += other_used |
||||
flyapp_used_sum += flyapp_used |
||||
max_total += 100 |
||||
|
||||
use_number_obj_list = developer_obj_lists.filter(status__in=Config.DEVELOPER_USE_STATUS) |
||||
all_use_number = 0 |
||||
all_usable_number = 0 |
||||
for use_number_obj in use_number_obj_list: |
||||
all_usable_number += usable_number(use_number_obj) |
||||
all_use_number += DeveloperDevicesID.objects.filter(developerid=use_number_obj).values( |
||||
'udid').distinct().count() |
||||
use_num = { |
||||
"all_usable_number": all_usable_number, |
||||
"all_use_number": all_use_number, |
||||
"other_used_sum": other_used_sum, |
||||
"flyapp_used_sum": flyapp_used_sum, |
||||
"max_total": max_total |
||||
} |
||||
return use_num |
||||
|
||||
|
||||
def get_user_obj_from_epu(user_id): |
||||
if is_valid_phone(user_id): |
||||
user_obj = UserInfo.objects.filter(mobile=user_id).first() |
||||
else: |
||||
user_obj = UserInfo.objects.filter(Q(email=user_id) | Q(uid=user_id)).first() |
||||
return user_obj |
||||
|
||||
|
||||
def get_app_sign_info(app_obj): |
||||
return { |
||||
'count': APPToDeveloper.objects.filter(app_id=app_obj).count(), |
||||
'private_developer_number': AppleDeveloperToAppUse.objects.filter(app_id=app_obj).count(), |
||||
'supersign_used_number': APPSuperSignUsedInfo.objects.filter(app_id=app_obj).all().count(), |
||||
'developer_used_count': DeveloperAppID.objects.filter(app_id=app_obj).all().count(), |
||||
'private_developer_used_number': DeveloperDevicesID.objects.filter(app_id=app_obj, |
||||
developerid__appledevelopertoappuse__app_id=app_obj).distinct().count() |
||||
} |
@ -0,0 +1,363 @@ |
||||
import logging |
||||
|
||||
from django.db.models import Sum |
||||
from rest_framework import serializers |
||||
|
||||
from common.base.baseutils import AppleDeveloperUid, get_choices_dict |
||||
from common.core.sysconfig import Config |
||||
from xsign import models |
||||
from xsign.utils.modelutils import get_developer_udided |
||||
|
||||
logger = logging.getLogger(__name__) |
||||
|
||||
|
||||
class DeveloperSerializer(serializers.ModelSerializer): |
||||
class Meta: |
||||
model = models.AppIOSDeveloperInfo |
||||
# depth = 1 |
||||
exclude = ["id", "user_id", "p8key"] |
||||
|
||||
developer_used_number = serializers.SerializerMethodField() |
||||
developer_used_other_number = serializers.SerializerMethodField() |
||||
use_number = serializers.SerializerMethodField() |
||||
app_used_count = serializers.SerializerMethodField() |
||||
is_disabled = serializers.SerializerMethodField() |
||||
app_private_number = serializers.SerializerMethodField() |
||||
app_private_used_number = serializers.SerializerMethodField() |
||||
app_usable_number = serializers.SerializerMethodField() |
||||
app_used_number = serializers.SerializerMethodField() |
||||
private_usable_number = serializers.SerializerMethodField() |
||||
status_display = serializers.CharField(source="get_status_display") |
||||
|
||||
def get_app_usable_number(self, obj): |
||||
app_id = self.context.get('app_id') |
||||
if app_id: |
||||
apple_to_app_obj = models.AppleDeveloperToAppUse.objects.filter(developerid=obj, |
||||
app_id__app_id=app_id).first() |
||||
if apple_to_app_obj: |
||||
return apple_to_app_obj.usable_number |
||||
return 0 |
||||
|
||||
def get_app_used_number(self, obj): |
||||
app_id = self.context.get('app_id', '') |
||||
if app_id: |
||||
return models.DeveloperDevicesID.objects.filter(developerid=obj, |
||||
app_id__app_id=app_id).distinct().count() |
||||
return 0 |
||||
|
||||
def get_developer_used_number(self, obj): |
||||
return models.UDIDsyncDeveloper.objects.filter(developerid=obj).count() |
||||
|
||||
def get_developer_used_other_number(self, obj): |
||||
return get_developer_udided(obj)[0] |
||||
|
||||
def get_is_disabled(self, obj): |
||||
app_id = self.context.get('app_id', '') |
||||
if app_id: |
||||
developer_app_obj = models.DeveloperAppID.objects.filter(developerid=obj, |
||||
developerid__status__in=Config.DEVELOPER_USE_STATUS, |
||||
developerid__certid__isnull=False) |
||||
if developer_app_obj.filter(app_id__app_id=app_id).distinct().count(): |
||||
return False |
||||
else: |
||||
if developer_app_obj and developer_app_obj.distinct().count() < obj.app_limit_number: |
||||
return False |
||||
return True |
||||
return True |
||||
|
||||
def get_app_private_number(self, obj): |
||||
return models.AppleDeveloperToAppUse.objects.filter(developerid=obj).count() |
||||
|
||||
def get_private_usable_number(self, obj): |
||||
used_number = models.AppleDeveloperToAppUse.objects.filter(developerid=obj).values('usable_number').aggregate( |
||||
Sum('usable_number')).get('usable_number__sum') |
||||
if not used_number: |
||||
used_number = 0 |
||||
return used_number |
||||
|
||||
def get_app_private_used_number(self, obj): |
||||
return models.DeveloperDevicesID.objects.filter(developerid=obj, |
||||
app_id__appledevelopertoappuse__developerid=obj).values( |
||||
'udid').distinct().count() |
||||
|
||||
def get_use_number(self, obj): |
||||
return models.DeveloperDevicesID.objects.filter(developerid=obj).values('udid').distinct().count() |
||||
|
||||
def get_app_used_count(self, obj): |
||||
return models.DeveloperAppID.objects.filter(developerid=obj).count() |
||||
|
||||
|
||||
class SuperSignUsedSerializer(serializers.ModelSerializer): |
||||
class Meta: |
||||
model = models.APPSuperSignUsedInfo |
||||
fields = ["created_time", "device_udid", "device_name", "developer_id", "bundle_id", "bundle_name", "other_uid", |
||||
"developer_description", "developer_status"] |
||||
|
||||
device_udid = serializers.CharField(source="udid.udid.udid") |
||||
device_name = serializers.CharField(source="udid.product") |
||||
bundle_id = serializers.CharField(source="app_id.bundle_id") |
||||
bundle_name = serializers.CharField(source="app_id.name") |
||||
other_uid = serializers.SerializerMethodField() |
||||
developer_id = serializers.SerializerMethodField() |
||||
developer_description = serializers.SerializerMethodField() |
||||
developer_status = serializers.SerializerMethodField() |
||||
|
||||
def get_developer_status(self, obj): |
||||
return obj.developerid.get_status_display() |
||||
|
||||
def get_developer_id(self, obj): |
||||
issuer_id = obj.developerid.issuer_id |
||||
if self.context.get('mine'): |
||||
return issuer_id |
||||
else: |
||||
return f"{Config.DEVELOPER_UID_KEY}{AppleDeveloperUid().get_encrypt_uid(issuer_id)}" |
||||
|
||||
def get_developer_description(self, obj): |
||||
if self.context.get('mine'): |
||||
return obj.developerid.description |
||||
else: |
||||
return '公共账号池' |
||||
|
||||
def get_other_uid(self, obj): |
||||
user_obj = self.context.get('user_obj') |
||||
if obj.user_id != obj.developerid.user_id: |
||||
if user_obj.uid != obj.user_id.uid: |
||||
return {'uid': obj.user_id.uid, 'name': obj.user_id.first_name} |
||||
|
||||
|
||||
class DeveloperDeviceSerializer(serializers.ModelSerializer): |
||||
class Meta: |
||||
model = models.UDIDsyncDeveloper |
||||
exclude = ["id", "developerid"] |
||||
|
||||
developer_id = serializers.CharField(source="developerid.issuer_id") |
||||
developer_description = serializers.CharField(source="developerid.description") |
||||
developer_status = serializers.CharField(source="developerid.get_status_display") |
||||
|
||||
app_used_count = serializers.SerializerMethodField() |
||||
|
||||
def get_app_used_count(self, obj): |
||||
return models.DeveloperDevicesID.objects.filter(udid=obj, developerid=obj.developerid).values( |
||||
'app_id').distinct().count() |
||||
|
||||
|
||||
class DeviceUDIDSerializer(serializers.ModelSerializer): |
||||
class Meta: |
||||
model = models.AppUDID |
||||
# depth = 1 |
||||
exclude = ["updated_time", "sign_status"] |
||||
|
||||
bundle_name = serializers.CharField(source="app_id.name") |
||||
bundle_id = serializers.CharField(source="app_id.bundle_id") |
||||
issuer_id = serializers.SerializerMethodField() |
||||
developer_description = serializers.SerializerMethodField() |
||||
developer_status = serializers.SerializerMethodField() |
||||
udid = serializers.CharField(source="udid.udid") |
||||
is_mine = serializers.SerializerMethodField() |
||||
other_uid = serializers.SerializerMethodField() |
||||
|
||||
def get_issuer_id(self, obj): |
||||
issuer_id = obj.udid.developerid.issuer_id |
||||
if self.context.get('mine'): |
||||
return issuer_id |
||||
else: |
||||
return f"{Config.DEVELOPER_UID_KEY}{AppleDeveloperUid().get_encrypt_uid(issuer_id)}" |
||||
|
||||
def get_developer_status(self, obj): |
||||
return obj.udid.developerid.get_status_display() |
||||
|
||||
def get_developer_description(self, obj): |
||||
if self.context.get('mine'): |
||||
return obj.udid.developerid.description |
||||
else: |
||||
return '公共账号池' |
||||
|
||||
def get_other_uid(self, obj): |
||||
user_obj = self.context.get('user_obj') |
||||
user_obj_new = obj.app_id.user_id |
||||
if user_obj.pk != user_obj_new.pk: |
||||
super_user_obj = models.APPSuperSignUsedInfo.objects.filter(udid=obj, app_id=obj.app_id).first() |
||||
if super_user_obj.developerid.user_id != user_obj_new: |
||||
return {'uid': user_obj_new.uid, 'name': user_obj_new.first_name} |
||||
|
||||
def get_is_mine(self, obj): |
||||
return self.context.get('mine') |
||||
|
||||
|
||||
class BillAppInfoSerializer(serializers.ModelSerializer): |
||||
class Meta: |
||||
model = models.Apps |
||||
fields = ["pk", "app_id", "user_id", "name", "type", "bundle_id", "updated_time"] |
||||
|
||||
|
||||
class BillDeveloperInfoSerializer(serializers.ModelSerializer): |
||||
class Meta: |
||||
model = models.AppIOSDeveloperInfo |
||||
fields = ["pk", "issuer_id", "user_id", "certid"] |
||||
|
||||
|
||||
class BillInfoSerializer(serializers.ModelSerializer): |
||||
class Meta: |
||||
model = models.IosDeveloperPublicPoolBill |
||||
exclude = ["user_id", "developer_info", "app_info", "udid_sync_info", "app_id"] |
||||
|
||||
app_name = serializers.SerializerMethodField() |
||||
description = serializers.SerializerMethodField() |
||||
is_used = serializers.SerializerMethodField() |
||||
app_status = serializers.SerializerMethodField() |
||||
remote_addr = serializers.SerializerMethodField() |
||||
|
||||
def get_remote_addr(self, obj): |
||||
return obj.remote_addr |
||||
|
||||
def get_app_status(self, obj): |
||||
if obj.app_id: |
||||
return True |
||||
return False |
||||
|
||||
def get_is_used(self, obj): |
||||
if obj.udid_sync_info: |
||||
return True |
||||
return False |
||||
|
||||
def get_app_name(self, obj): |
||||
if obj.app_info: |
||||
return obj.app_info.get('name') |
||||
|
||||
def get_description(self, obj): |
||||
if obj.udid: |
||||
return f"{self.get_app_name(obj)}-消耗- {obj.number} 设备数" |
||||
|
||||
|
||||
class BillTransferSerializer(serializers.ModelSerializer): |
||||
class Meta: |
||||
model = models.IosDeveloperBill |
||||
exclude = ["id", "remote_addr", "user_id", "to_user_id"] |
||||
|
||||
target_user = serializers.SerializerMethodField() |
||||
cancel = serializers.SerializerMethodField() |
||||
number = serializers.SerializerMethodField() |
||||
status_display = serializers.CharField(source='get_status_display') |
||||
|
||||
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_cancel(self, obj): |
||||
return self.context.get('user_obj').pk == obj.user_id.pk |
||||
|
||||
def get_number(self, obj): |
||||
if self.get_cancel(obj): |
||||
return -obj.number |
||||
else: |
||||
return obj.number |
||||
|
||||
|
||||
class AppleDeveloperToAppUseSerializer(serializers.ModelSerializer): |
||||
class Meta: |
||||
model = models.AppleDeveloperToAppUse |
||||
# exclude = ["user_id", "to_user_id", "developer_info", "app_info", "udid_sync_info", "app_id"] |
||||
fields = ["issuer_id", "app_id", "created_time", "description", "usable_number"] |
||||
|
||||
issuer_id = serializers.CharField(source="developerid.issuer_id") |
||||
app_id = serializers.CharField(source="app_id.app_id") |
||||
|
||||
|
||||
class AppleDeveloperToAppUseAppsSerializer(serializers.ModelSerializer): |
||||
class Meta: |
||||
model = models.Apps |
||||
fields = ["app_id", "name", "short", "bundle_id", "description", "app_usable_number", "app_used_number"] |
||||
|
||||
app_usable_number = serializers.SerializerMethodField() |
||||
app_used_number = serializers.SerializerMethodField() |
||||
|
||||
def get_app_usable_number(self, obj): |
||||
issuer_id = self.context.get('issuer_id') |
||||
if issuer_id: |
||||
apple_to_app_obj = models.AppleDeveloperToAppUse.objects.filter(developerid__issuer_id=issuer_id, |
||||
app_id=obj).first() |
||||
if apple_to_app_obj: |
||||
return apple_to_app_obj.usable_number |
||||
return 0 |
||||
|
||||
def get_app_used_number(self, obj): |
||||
issuer_id = self.context.get('issuer_id') |
||||
if issuer_id: |
||||
return models.DeveloperDevicesID.objects.filter(app_id=obj, developerid__issuer_id=issuer_id).count() |
||||
return 0 |
||||
|
||||
|
||||
class AdminDeveloperSerializer(DeveloperSerializer): |
||||
class Meta: |
||||
model = models.AppIOSDeveloperInfo |
||||
# depth = 1 |
||||
exclude = ["p8key", ] |
||||
|
||||
auth_type_choices = serializers.SerializerMethodField() |
||||
|
||||
def get_auth_type_choices(self, obj): |
||||
return get_choices_dict(obj.auth_type_choices) |
||||
|
||||
status_choices = serializers.SerializerMethodField() |
||||
|
||||
def get_status_choices(self, obj): |
||||
return get_choices_dict(obj.status_choices) |
||||
|
||||
|
||||
class AdminSuperSignUsedSerializer(SuperSignUsedSerializer): |
||||
class Meta: |
||||
model = models.APPSuperSignUsedInfo |
||||
fields = ["created_time", "device_udid", "device_name", "developer_id", "bundle_id", "bundle_name", "app_id", |
||||
"id", "user_id", "short", "developer_pk"] |
||||
|
||||
app_id = serializers.IntegerField(source="app_id.pk") |
||||
|
||||
user_id = serializers.IntegerField(source="user_id.pk") |
||||
|
||||
short = serializers.CharField(source="app_id.short") |
||||
developer_status = serializers.CharField(source="developerid.get_status_display") |
||||
developer_id = serializers.CharField(source="developerid.issuer_id") |
||||
developer_description = serializers.CharField(source="developerid.description") |
||||
developer_pk = serializers.IntegerField(source="developerid.pk") |
||||
|
||||
|
||||
class AdminBillInfoSerializer(BillInfoSerializer): |
||||
class Meta: |
||||
model = models.IosDeveloperPublicPoolBill |
||||
fields = '__all__' |
||||
read_only_fields = ["id", "user_id", "to_user_id", "action", "number", "app_info", "udid", |
||||
"udid_sync_info", "app_id", "remote_addr"] |
||||
|
||||
action_choices = serializers.SerializerMethodField() |
||||
|
||||
def get_action_choices(self, obj): |
||||
return get_choices_dict(obj.action_choices) |
||||
|
||||
|
||||
class AppSignSerializer(serializers.ModelSerializer): |
||||
class Meta: |
||||
model = models.Apps |
||||
exclude = ["user_id", "id"] |
||||
|
||||
private_developer_number = serializers.SerializerMethodField() |
||||
|
||||
def get_private_developer_number(self, obj): |
||||
return models.AppleDeveloperToAppUse.objects.filter(app_id=obj).count() |
||||
|
||||
private_developer_used_number = serializers.SerializerMethodField() |
||||
|
||||
def get_private_developer_used_number(self, obj): |
||||
return models.DeveloperDevicesID.objects.filter(app_id=obj, |
||||
developerid__appledevelopertoappuse__app_id=obj).distinct().count() |
||||
|
||||
supersign_used_number = serializers.SerializerMethodField() |
||||
|
||||
def get_supersign_used_number(self, obj): |
||||
return models.APPSuperSignUsedInfo.objects.filter(app_id=obj).all().count() |
||||
|
||||
developer_used_count = serializers.SerializerMethodField() |
||||
|
||||
def get_developer_used_count(self, obj): |
||||
return models.DeveloperAppID.objects.filter(app_id=obj).all().count() |
@ -0,0 +1,73 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding:utf-8 -*- |
||||
# project: 5月 |
||||
# author: liuyu |
||||
# date: 2020/5/7 |
||||
import logging |
||||
|
||||
from api.utils.utils import delete_local_files |
||||
from common.utils.storage import Storage |
||||
from xsign.models import APPToDeveloper |
||||
|
||||
logger = logging.getLogger(__name__) |
||||
|
||||
|
||||
def delete_app_to_dev_and_file(developer_obj, app_id): |
||||
app_to_developer_obj = APPToDeveloper.objects.filter(developerid=developer_obj, app_id_id=app_id) |
||||
if app_to_developer_obj: |
||||
binary_file = app_to_developer_obj.first().binary_file + ".ipa" |
||||
delete_local_files(binary_file) |
||||
storage = Storage(developer_obj.user_id) |
||||
storage.delete_file(binary_file) |
||||
app_to_developer_obj.delete() |
||||
|
||||
|
||||
def make_resigned(bin_url, img_url, bundle_id, app_version, name): |
||||
ios_plist_tem = """<?xml version="1.0" encoding="UTF-8"?> |
||||
<plist version="1.0"><dict> |
||||
<key>items</key> |
||||
<array> |
||||
<dict> |
||||
<key>assets</key> |
||||
<array> |
||||
<dict> |
||||
<key>kind</key> |
||||
<string>software-package</string> |
||||
<key>url</key> |
||||
<string><![CDATA[{bin_url}]]></string> |
||||
</dict> |
||||
<dict> |
||||
<key>kind</key> |
||||
<string>display-image</string> |
||||
<key>needs-shine</key> |
||||
<integer>0</integer> |
||||
<key>url</key> |
||||
<string><![CDATA[{img_url}]]></string> |
||||
</dict> |
||||
<dict> |
||||
<key>kind</key> |
||||
<string>full-size-image</string> |
||||
<key>needs-shine</key> |
||||
<true/> |
||||
<key>url</key> |
||||
<string><![CDATA[{img_url}]]></string> |
||||
</dict> |
||||
</array> |
||||
<key>metadata</key> |
||||
<dict> |
||||
<key>bundle-identifier</key> |
||||
<string>{bundle_id}</string> |
||||
<key>bundle-version</key> |
||||
<string><![CDATA[{app_version}]]></string> |
||||
<key>kind</key> |
||||
<string>software</string> |
||||
<key>title</key> |
||||
<string><![CDATA[{name}]]></string> |
||||
</dict> |
||||
</dict> |
||||
</array> |
||||
</dict> |
||||
</plist>""".format(bin_url=bin_url, img_url=img_url, bundle_id=bundle_id, app_version=app_version, name=name) |
||||
logger.info( |
||||
f"make_resigned bin_url {bin_url} ,img_url {img_url}, bundle_id {bundle_id}, app_version {app_version}, name {name}") |
||||
return ios_plist_tem |
@ -0,0 +1,151 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding:utf-8 -*- |
||||
# project: 3月 |
||||
# author: liuyu |
||||
# date: 2020/3/6 |
||||
import logging |
||||
|
||||
from rest_framework.response import Response |
||||
from rest_framework.views import APIView |
||||
|
||||
from api.models import Apps |
||||
from api.utils.response import BaseResponse |
||||
from common.cache.state import MigrateStorageState, CleanAppSignDataState |
||||
from common.core.auth import ExpiringTokenAuthentication |
||||
from common.core.sysconfig import Config |
||||
from common.utils.caches import del_cache_response_by_short |
||||
from xsign.models import AppUDID |
||||
from xsign.tasks import run_resign_task |
||||
from xsign.utils.modelutils import get_app_sign_info, check_super_sign_permission |
||||
from xsign.utils.supersignutils import IosUtils |
||||
|
||||
logger = logging.getLogger(__name__) |
||||
|
||||
|
||||
class AppSignInfoView(APIView): |
||||
authentication_classes = [ExpiringTokenAuthentication, ] |
||||
|
||||
def get(self, request, app_id): |
||||
res = BaseResponse() |
||||
if app_id: |
||||
app_obj = Apps.objects.filter(user_id=request.user, app_id=app_id).first() |
||||
if app_obj: |
||||
res.data = get_app_sign_info(app_obj) |
||||
else: |
||||
logger.error(f"app_id:{app_id} is not found in user:{request.user}") |
||||
res.msg = "未找到该应用" |
||||
res.code = 1003 |
||||
|
||||
return Response(res.dict) |
||||
|
||||
def put(self, request, app_id): |
||||
res = BaseResponse() |
||||
if app_id: |
||||
data = request.data |
||||
if MigrateStorageState(request.user.uid).get_state(): |
||||
res.code = 1008 |
||||
res.msg = "数据迁移中,无法处理该操作" |
||||
return Response(res.dict) |
||||
|
||||
clean = data.get("clean", None) |
||||
if clean: |
||||
with CleanAppSignDataState(request.user.uid) as state: |
||||
if state: |
||||
logger.info(f"app_id:{app_id} clean:{clean} ,close super_sign should clean_app_by_user_obj") |
||||
app_obj = Apps.objects.filter(user_id=request.user, app_id=app_id).first() |
||||
IosUtils.clean_app_by_user_obj(app_obj) |
||||
else: |
||||
res.code = 1008 |
||||
res.msg = "数据清理中,请耐心等待" |
||||
return Response(res.dict) |
||||
|
||||
has_combo = data.get("has_combo", None) |
||||
if has_combo: |
||||
... |
||||
else: |
||||
try: |
||||
do_sign_flag = 0 |
||||
app_obj = Apps.objects.filter(user_id=request.user, app_id=app_id).first() |
||||
logger.info("app_id:%s update old data:%s" % (app_id, app_obj.__dict__)) |
||||
update_fields = [] |
||||
if app_obj.issupersign: |
||||
app_obj.supersign_limit_number = data.get("supersign_limit_number", |
||||
app_obj.supersign_limit_number) |
||||
update_fields.append("supersign_limit_number") |
||||
if app_obj.supersign_type in [x[0] for x in list(app_obj.supersign_type_choices)]: |
||||
if app_obj.supersign_type != data.get("supersign_type", app_obj.supersign_type): |
||||
do_sign_flag = 1 |
||||
app_obj.supersign_type = data.get("supersign_type", app_obj.supersign_type) |
||||
update_fields.append("supersign_type") |
||||
new_bundle_id = data.get("new_bundle_id", None) |
||||
new_bundle_name = data.get("new_bundle_name", None) |
||||
if new_bundle_id is not None: |
||||
if new_bundle_id and new_bundle_id != app_obj.bundle_id and len(new_bundle_id) > 3: |
||||
if new_bundle_id != app_obj.new_bundle_id: |
||||
do_sign_flag = 2 |
||||
app_obj.new_bundle_id = new_bundle_id |
||||
if new_bundle_id == '' or (app_obj.new_bundle_id != new_bundle_id): |
||||
if app_obj.bundle_id != app_obj.new_bundle_id: |
||||
do_sign_flag = 2 |
||||
app_obj.new_bundle_id = app_obj.bundle_id |
||||
update_fields.append('new_bundle_id') |
||||
if new_bundle_name is not None: |
||||
if new_bundle_name and new_bundle_name != app_obj.name and len(new_bundle_name) > 0: |
||||
if new_bundle_name != app_obj.new_bundle_name: |
||||
do_sign_flag = 2 |
||||
app_obj.new_bundle_name = new_bundle_name |
||||
if new_bundle_name == '' or (app_obj.new_bundle_name != new_bundle_name): |
||||
if app_obj.name != app_obj.new_bundle_name: |
||||
do_sign_flag = 2 |
||||
app_obj.new_bundle_name = app_obj.name |
||||
update_fields.append('new_bundle_name') |
||||
if app_obj.type == 1 and data.get('issupersign', -1) != -1: |
||||
if data.get('issupersign', -1) == 1 and not check_super_sign_permission(request.user): |
||||
logger.error(f"app_id:{app_id} can't open super_sign,owner has no ios developer") |
||||
res.code = 1008 |
||||
res.msg = "超级签余额不足,无法开启" |
||||
return Response(res.dict) |
||||
do_sign_flag = 3 |
||||
app_obj.issupersign = data.get("issupersign", app_obj.issupersign) |
||||
update_fields.append("issupersign") |
||||
if app_obj.issupersign and data.get('change_auto_sign', -1) != -1: |
||||
if data.get('change_auto_sign', -1) == 1: |
||||
do_sign_flag = 3 |
||||
app_obj.change_auto_sign = data.get("change_auto_sign", app_obj.change_auto_sign) |
||||
update_fields.append("change_auto_sign") |
||||
|
||||
logger.info(f"app_id:{app_id} update new data:{app_obj.__dict__}") |
||||
app_obj.save(update_fields=update_fields) |
||||
if app_obj.issupersign: |
||||
c_task = None |
||||
if do_sign_flag == 1: |
||||
AppUDID.objects.filter(app_id=app_obj).update(sign_status=2) |
||||
if app_obj.change_auto_sign: |
||||
c_task = run_resign_task(app_obj.pk, True) |
||||
|
||||
if do_sign_flag == 2: |
||||
AppUDID.objects.filter(app_id=app_obj, sign_status__gte=3).update(sign_status=3) |
||||
if app_obj.change_auto_sign: |
||||
flag = False |
||||
if AppUDID.objects.filter(app_id=app_obj, sign_status=2, |
||||
udid__developerid__status__in=Config.DEVELOPER_WRITE_STATUS).first(): |
||||
flag = True |
||||
c_task = run_resign_task(app_obj.pk, flag) |
||||
|
||||
if do_sign_flag == 3: |
||||
if app_obj.change_auto_sign: |
||||
flag = False |
||||
if AppUDID.objects.filter(app_id=app_obj, sign_status=2, |
||||
udid__developerid__status__in=Config.DEVELOPER_WRITE_STATUS).first(): |
||||
flag = True |
||||
c_task = run_resign_task(app_obj.pk, flag, False) |
||||
|
||||
if c_task: |
||||
logger.info(f"app {app_obj} run_resign_task msg:{c_task}") |
||||
del_cache_response_by_short(app_obj.app_id) |
||||
except Exception as e: |
||||
logger.error(f"app_id:{app_id} update Exception:{e}") |
||||
res.code = 1005 |
||||
res.msg = "短连接已经存在" |
||||
|
||||
return Response(res.dict) |
@ -0,0 +1,106 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding:utf-8 -*- |
||||
# project: 3月 |
||||
# author: liuyu |
||||
# date: 2020/3/6 |
||||
|
||||
import logging |
||||
import os |
||||
|
||||
from rest_framework.views import APIView |
||||
|
||||
from api.models import AppReleaseInfo |
||||
from common.base.baseutils import get_profile_full_path, make_random_uuid, get_server_domain_from_request |
||||
from common.core.response import ApiResponse, file_response, mobileprovision_file_response |
||||
from common.core.sysconfig import Config |
||||
from common.utils.storage import Storage, get_local_storage |
||||
from common.utils.token import verify_token |
||||
from fir_ser import settings |
||||
from xsign.models import APPToDeveloper, APPSuperSignUsedInfo |
||||
from xsign.utils.supersignutils import make_sign_udid_mobile_config |
||||
from xsign.utils.utils import make_resigned |
||||
|
||||
logger = logging.getLogger(__name__) |
||||
|
||||
|
||||
def get_post_udid_url(request, short): |
||||
server_domain = get_server_domain_from_request(request, Config.POST_UDID_DOMAIN) |
||||
path_info_lists = [server_domain, "udid", short] |
||||
return "/".join(path_info_lists) |
||||
|
||||
|
||||
class DownloadView(APIView): |
||||
""" |
||||
文件下载接口,适用于本地存储和所有plist文件下载 |
||||
""" |
||||
|
||||
def get(self, request, filename): |
||||
down_token = request.query_params.get(settings.DATA_DOWNLOAD_KEY, None) |
||||
f_type = filename.split(".")[-1] |
||||
flag = True |
||||
storage_obj = get_local_storage() |
||||
if storage_obj.download_auth_type == 1: |
||||
if not down_token: |
||||
logger.error(f"file {filename} download failed lost down_token") |
||||
return ApiResponse(code=1004, msg="缺失token") |
||||
|
||||
flag = verify_token(down_token, filename) |
||||
|
||||
if flag: |
||||
if f_type == 'plist': |
||||
release_id = filename.split('.')[0] |
||||
app_to_developer_obj = APPToDeveloper.objects.filter(binary_file=release_id).first() |
||||
if app_to_developer_obj: |
||||
release_obj = AppReleaseInfo.objects.filter(is_master=True, |
||||
app_id=app_to_developer_obj.app_id).first() |
||||
else: |
||||
release_obj = AppReleaseInfo.objects.filter(release_id=release_id).first() |
||||
if release_obj: |
||||
app_obj = release_obj.app_id |
||||
storage = Storage(app_obj.user_id) |
||||
bundle_id = app_obj.bundle_id |
||||
app_version = release_obj.app_version |
||||
name = app_obj.name |
||||
ios_plist_bytes = make_resigned(storage.get_download_url(filename.split('.')[0] + ".ipa"), |
||||
storage.get_download_url(release_obj.icon_url), bundle_id, |
||||
app_version, name) |
||||
return file_response(ios_plist_bytes, make_random_uuid(), "application/x-plist") |
||||
return ApiResponse(code=1004, msg="plist release_id error") |
||||
elif f_type == 'mobileconifg': |
||||
release_obj = AppReleaseInfo.objects.filter(release_id=filename.split('.')[0]).first() |
||||
if release_obj: |
||||
app_obj = release_obj.app_id |
||||
udid_url = get_post_udid_url(request, app_obj.short) |
||||
ios_udid_mobile_config = make_sign_udid_mobile_config(udid_url, f'{app_obj.app_id}_{app_obj.short}', |
||||
app_obj.bundle_id, |
||||
app_obj.name) |
||||
return file_response(ios_udid_mobile_config, make_random_uuid() + '.mobileconfig', |
||||
"application/x-apple-aspen-config") |
||||
return ApiResponse(code=1004, msg="mobile_config release_id error") |
||||
elif f_type == 'mobileprovision': |
||||
release_obj = AppReleaseInfo.objects.filter(release_id=filename.split('.')[0]).first() |
||||
if release_obj: |
||||
app_super_obj = APPSuperSignUsedInfo.objects.filter(app_id=release_obj.app_id).last() |
||||
if not app_super_obj: |
||||
app_super_obj = APPSuperSignUsedInfo.objects.last() |
||||
|
||||
if not app_super_obj: |
||||
file_path = Config.DEFAULT_MOBILEPROVISION.get("supersign").get('path') |
||||
if file_path and os.path.isfile(file_path): |
||||
return mobileprovision_file_response(file_path) |
||||
else: |
||||
developer_obj = app_super_obj.developerid |
||||
file_path = get_profile_full_path(developer_obj, release_obj.app_id) |
||||
if os.path.isfile(file_path): |
||||
return mobileprovision_file_response(file_path) |
||||
else: |
||||
file_path = Config.DEFAULT_MOBILEPROVISION.get("supersign").get('path') |
||||
if file_path and os.path.isfile(file_path): |
||||
return mobileprovision_file_response(file_path) |
||||
|
||||
return ApiResponse(code=1004, msg="mobile_provision release_id error") |
||||
else: |
||||
logger.warning("super sing error file type") |
||||
|
||||
logger.error(f"file {filename} download failed. down_token verify failed") |
||||
return ApiResponse(code=1004, msg="token校验失败") |
Loading…
Reference in new issue