第一次提交
This commit is contained in:
1
app/services/__init__.py
Normal file
1
app/services/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Services package
|
||||
256
app/services/license_service.py
Normal file
256
app/services/license_service.py
Normal file
@@ -0,0 +1,256 @@
|
||||
"""
|
||||
License业务逻辑服务层
|
||||
负责处理所有与License相关的业务逻辑,将API层与模型层解耦
|
||||
"""
|
||||
from typing import Optional, Tuple, List
|
||||
from flask import current_app
|
||||
from sqlalchemy import func, or_
|
||||
from app import db
|
||||
from app.models import License, Product
|
||||
from app.utils.logger import log_operation
|
||||
import secrets
|
||||
import string
|
||||
|
||||
|
||||
class LicenseService:
|
||||
"""卡密业务逻辑服务"""
|
||||
|
||||
@staticmethod
|
||||
def generate_license_key(length: int = 32, prefix: str = '') -> str:
|
||||
"""
|
||||
生成卡密密钥
|
||||
:param length: 密钥长度
|
||||
:param prefix: 前缀
|
||||
:return: 生成的密钥
|
||||
"""
|
||||
# 生成指定长度的随机字符串
|
||||
chars = string.ascii_uppercase + string.digits
|
||||
random_key = ''.join(secrets.choice(chars) for _ in range(length))
|
||||
return f"{prefix}{random_key}" if prefix else random_key
|
||||
|
||||
@staticmethod
|
||||
def create_license(
|
||||
product_id: str,
|
||||
valid_days: int,
|
||||
license_type: int = 0,
|
||||
max_bind_times: int = 1,
|
||||
remark: str = ''
|
||||
) -> Tuple[bool, str, Optional[License]]:
|
||||
"""
|
||||
创建卡密
|
||||
:param product_id: 产品ID
|
||||
:param valid_days: 有效天数
|
||||
:param license_type: 卡密类型
|
||||
:param max_bind_times: 最大绑定次数
|
||||
:param remark: 备注
|
||||
:return: (是否成功, 消息, 卡密对象)
|
||||
"""
|
||||
try:
|
||||
# 检查产品是否存在
|
||||
product = Product.query.filter_by(product_id=product_id, status=1).first()
|
||||
if not product:
|
||||
return False, "产品不存在或已下架", None
|
||||
|
||||
# 生成唯一的卡密
|
||||
max_attempts = 10
|
||||
for _ in range(max_attempts):
|
||||
# 生成卡密
|
||||
prefix = current_app.config.get('TRIAL_PREFIX', 'TRIAL_') if license_type == 1 else ''
|
||||
length = current_app.config.get('LICENSE_KEY_LENGTH', 32)
|
||||
license_key = LicenseService.generate_license_key(length, prefix)
|
||||
|
||||
# 检查是否已存在
|
||||
existing = License.query.filter_by(license_key=license_key).first()
|
||||
if not existing:
|
||||
break
|
||||
else:
|
||||
return False, "生成卡密失败,请重试", None
|
||||
|
||||
# 创建卡密
|
||||
license = License(
|
||||
license_key=license_key,
|
||||
product_id=product_id,
|
||||
valid_days=valid_days,
|
||||
type=license_type,
|
||||
max_bind_times=max_bind_times,
|
||||
remark=remark,
|
||||
status=0 # 未激活
|
||||
)
|
||||
db.session.add(license)
|
||||
db.session.commit()
|
||||
|
||||
return True, "卡密创建成功", license
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(f"创建卡密失败: {str(e)}")
|
||||
return False, f"创建卡密失败: {str(e)}", None
|
||||
|
||||
@staticmethod
|
||||
def batch_create_licenses(
|
||||
product_id: str,
|
||||
valid_days: int,
|
||||
count: int,
|
||||
license_type: int = 0,
|
||||
max_bind_times: int = 1,
|
||||
remark: str = ''
|
||||
) -> Tuple[bool, str, List[License]]:
|
||||
"""
|
||||
批量创建卡密
|
||||
:param product_id: 产品ID
|
||||
:param valid_days: 有效天数
|
||||
:param count: 数量
|
||||
:param license_type: 卡密类型
|
||||
:param max_bind_times: 最大绑定次数
|
||||
:param remark: 备注
|
||||
:return: (是否成功, 消息, 卡密列表)
|
||||
"""
|
||||
try:
|
||||
licenses = []
|
||||
for _ in range(count):
|
||||
success, msg, license = LicenseService.create_license(
|
||||
product_id, valid_days, license_type, max_bind_times, remark
|
||||
)
|
||||
if not success:
|
||||
return False, f"创建失败: {msg}", []
|
||||
licenses.append(license)
|
||||
|
||||
return True, f"成功创建{count}个卡密", licenses
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(f"批量创建卡密失败: {str(e)}")
|
||||
return False, f"批量创建卡密失败: {str(e)}", []
|
||||
|
||||
@staticmethod
|
||||
def get_licenses(
|
||||
page: int = 1,
|
||||
per_page: int = 20,
|
||||
keyword: str = '',
|
||||
license_type: Optional[int] = None,
|
||||
status: Optional[int] = None,
|
||||
product_id: str = ''
|
||||
) -> Tuple[List[License], int]:
|
||||
"""
|
||||
获取卡密列表(支持分页和筛选)
|
||||
:param page: 页码
|
||||
:param per_page: 每页数量
|
||||
:param keyword: 关键词
|
||||
:param license_type: 卡密类型筛选
|
||||
:param status: 状态筛选
|
||||
:param product_id: 产品ID筛选
|
||||
:return: (卡密列表, 总数)
|
||||
"""
|
||||
query = License.query.join(Product)
|
||||
|
||||
# 关键词搜索
|
||||
if keyword:
|
||||
# 转义特殊字符,防止LIKE查询中的通配符攻击
|
||||
escaped_keyword = keyword.replace('%', '\\%').replace('_', '\\_')
|
||||
pattern = f'%{escaped_keyword.lower()}%'
|
||||
query = query.filter(
|
||||
or_(
|
||||
func.lower(License.license_key).like(pattern, escape='\\'),
|
||||
func.lower(Product.product_name).like(pattern, escape='\\')
|
||||
)
|
||||
)
|
||||
|
||||
# 筛选条件
|
||||
if license_type is not None:
|
||||
query = query.filter(License.type == license_type)
|
||||
|
||||
if status is not None:
|
||||
query = query.filter(License.status == status)
|
||||
|
||||
if product_id:
|
||||
query = query.filter(License.product_id == product_id)
|
||||
|
||||
# 排序
|
||||
query = query.order_by(License.create_time.desc())
|
||||
|
||||
# 分页
|
||||
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
return pagination.items, pagination.total
|
||||
|
||||
@staticmethod
|
||||
def verify_license(license_key: str, machine_code: str, software_version: str) -> Tuple[bool, str, Optional[License]]:
|
||||
"""
|
||||
验证卡密
|
||||
:param license_key: 卡密
|
||||
:param machine_code: 机器码
|
||||
:param software_version: 软件版本
|
||||
:return: (是否成功, 消息, 卡密对象)
|
||||
"""
|
||||
try:
|
||||
license = License.query.filter_by(license_key=license_key).first()
|
||||
if not license:
|
||||
return False, "卡密不存在", None
|
||||
|
||||
success, msg = license.verify(machine_code, software_version)
|
||||
return success, msg, license if success else None
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"验证卡密失败: {str(e)}")
|
||||
return False, f"验证卡密失败: {str(e)}", None
|
||||
|
||||
@staticmethod
|
||||
def unbind_license(license_key: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
解绑卡密
|
||||
:param license_key: 卡密
|
||||
:return: (是否成功, 消息)
|
||||
"""
|
||||
try:
|
||||
license = License.query.filter_by(license_key=license_key).first()
|
||||
if not license:
|
||||
return False, "卡密不存在"
|
||||
|
||||
success, msg = license.unbind()
|
||||
return success, msg
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(f"解绑卡密失败: {str(e)}")
|
||||
return False, f"解绑卡密失败: {str(e)}"
|
||||
|
||||
@staticmethod
|
||||
def disable_license(license_key: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
禁用卡密
|
||||
:param license_key: 卡密
|
||||
:return: (是否成功, 消息)
|
||||
"""
|
||||
try:
|
||||
license = License.query.filter_by(license_key=license_key).first()
|
||||
if not license:
|
||||
return False, "卡密不存在"
|
||||
|
||||
success, msg = license.disable()
|
||||
return success, msg
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(f"禁用卡密失败: {str(e)}")
|
||||
return False, f"禁用卡密失败: {str(e)}"
|
||||
|
||||
@staticmethod
|
||||
def delete_license(license_key: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
删除卡密(软删除)
|
||||
:param license_key: 卡密
|
||||
:return: (是否成功, 消息)
|
||||
"""
|
||||
try:
|
||||
license = License.query.filter_by(license_key=license_key).first()
|
||||
if not license:
|
||||
return False, "卡密不存在"
|
||||
|
||||
license.delete() # 假设有软删除方法
|
||||
db.session.commit()
|
||||
|
||||
return True, "删除成功"
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(f"删除卡密失败: {str(e)}")
|
||||
return False, f"删除卡密失败: {str(e)}"
|
||||
74
app/services/product_service.py
Normal file
74
app/services/product_service.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""
|
||||
Product业务逻辑服务层
|
||||
"""
|
||||
from typing import Optional, Tuple, List
|
||||
from flask import current_app
|
||||
from sqlalchemy import func, or_
|
||||
from app import db
|
||||
from app.models import Product, Version
|
||||
from app.utils.logger import log_operation
|
||||
|
||||
|
||||
class ProductService:
|
||||
"""产品业务逻辑服务"""
|
||||
|
||||
@staticmethod
|
||||
def get_products(
|
||||
page: int = 1,
|
||||
per_page: int = 20,
|
||||
keyword: str = '',
|
||||
product_type: Optional[str] = None,
|
||||
is_paid: Optional[int] = None
|
||||
) -> Tuple[List[Product], int]:
|
||||
"""
|
||||
获取产品列表
|
||||
:param page: 页码
|
||||
:param per_page: 每页数量
|
||||
:param keyword: 关键词
|
||||
:param product_type: 产品类型
|
||||
:param is_paid: 是否付费
|
||||
:return: (产品列表, 总数)
|
||||
"""
|
||||
query = Product.query.filter_by(status=1) # 只显示启用的产品
|
||||
|
||||
# 关键词搜索
|
||||
if keyword:
|
||||
escaped_keyword = keyword.replace('%', '\\%').replace('_', '\\_')
|
||||
pattern = f'%{escaped_keyword}%'
|
||||
query = query.filter(
|
||||
or_(
|
||||
Product.product_name.like(pattern, escape='\\'),
|
||||
Product.description.like(pattern, escape='\\')
|
||||
)
|
||||
)
|
||||
|
||||
# 筛选条件
|
||||
if product_type:
|
||||
query = query.filter(Product.product_type == product_type)
|
||||
|
||||
query = query.order_by(Product.create_time.desc())
|
||||
|
||||
# 分页
|
||||
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
return pagination.items, pagination.total
|
||||
|
||||
@staticmethod
|
||||
def get_product(product_id: str) -> Optional[Product]:
|
||||
"""
|
||||
获取产品详情
|
||||
:param product_id: 产品ID
|
||||
:return: 产品对象
|
||||
"""
|
||||
return Product.query.filter_by(product_id=product_id, status=1).first()
|
||||
|
||||
@staticmethod
|
||||
def get_latest_version(product_id: str) -> Optional[Version]:
|
||||
"""
|
||||
获取产品最新版本
|
||||
:param product_id: 产品ID
|
||||
:return: 版本对象
|
||||
"""
|
||||
return Version.query.filter_by(
|
||||
product_id=product_id,
|
||||
publish_status=1
|
||||
).order_by(Version.update_time.desc(), Version.create_time.desc()).first()
|
||||
Reference in New Issue
Block a user