更新系统信息
This commit is contained in:
parent
b4a0e949ce
commit
701046819b
@ -18,7 +18,6 @@ from flask_cors import CORS
|
||||
from flask_migrate import Migrate
|
||||
from config import config
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
# 初始化扩展
|
||||
db = SQLAlchemy()
|
||||
@ -151,22 +150,6 @@ def create_app(config_name=None):
|
||||
from app.web.views import register_error_handlers
|
||||
register_error_handlers(app)
|
||||
|
||||
# 配置日志
|
||||
if not app.debug and not app.testing:
|
||||
# 确保日志目录存在
|
||||
if not os.path.exists('logs'):
|
||||
os.mkdir('logs')
|
||||
|
||||
# 配置文件日志处理器
|
||||
file_handler = RotatingFileHandler('logs/kamaxitong.log', maxBytes=10240, backupCount=10)
|
||||
file_handler.setFormatter(logging.Formatter(
|
||||
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
|
||||
))
|
||||
file_handler.setLevel(logging.INFO)
|
||||
app.logger.addHandler(file_handler)
|
||||
|
||||
app.logger.setLevel(logging.INFO)
|
||||
app.logger.info('KaMiXiTong startup')
|
||||
|
||||
# 初始化后台任务调度器
|
||||
try:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}账号管理 - 软件授权管理系统{% endblock %}
|
||||
{% block title %}账号管理 - 太一软件授权管理系统{% endblock %}
|
||||
|
||||
{% block page_title %}账号管理{% endblock %}
|
||||
|
||||
|
||||
@ -4,20 +4,121 @@
|
||||
|
||||
{% block page_title %}仪表板{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
/* 改进的统计卡片样式 */
|
||||
.card-stats {
|
||||
border: none;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.card-stats::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
opacity: 0.1;
|
||||
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 100%);
|
||||
}
|
||||
.card-stats:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.card-stats .icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.card-stats:hover .icon {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* 渐变背景 */
|
||||
.gradient-bg-1 {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
.gradient-bg-2 {
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
color: white;
|
||||
}
|
||||
.gradient-bg-3 {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
color: white;
|
||||
}
|
||||
.gradient-bg-4 {
|
||||
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 图表区域改进 */
|
||||
.chart-container {
|
||||
position: relative;
|
||||
height: 300px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* 快捷操作按钮 */
|
||||
.quick-action-btn {
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid;
|
||||
background: white;
|
||||
}
|
||||
.quick-action-btn:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
/* 最近活动卡片 */
|
||||
.activity-card {
|
||||
border: none;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
.fade-in-up {
|
||||
animation: fadeInUp 0.6s ease-out;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- 统计卡片 -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-xl-3 col-md-4 mb-4">
|
||||
<div class="card card-stats">
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card card-stats gradient-bg-1 fade-in-up">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h5 class="card-title text-uppercase text-muted mb-0">产品总数</h5>
|
||||
<span class="h2 font-weight-bold mb-0" id="total-products">-</span>
|
||||
<h5 class="card-title text-uppercase text-white-50 mb-0">产品总数</h5>
|
||||
<span class="h2 font-weight-bold mb-0 text-white" id="total-products">-</span>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="icon icon-shape bg-white text-primary rounded-circle shadow">
|
||||
<i class="fas fa-box"></i>
|
||||
<div class="icon bg-white bg-opacity-20 rounded-circle shadow" style="width: 60px; height: 60px; display: flex; align-items: center; justify-content: center;">
|
||||
<i class="fas fa-box text-white fs-4"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -25,17 +126,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-4 mb-4">
|
||||
<div class="card card-stats">
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card card-stats gradient-bg-2 fade-in-up">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h5 class="card-title text-uppercase text-muted mb-0">活跃卡密</h5>
|
||||
<span class="h2 font-weight-bold mb-0" id="active-licenses">-</span>
|
||||
<h5 class="card-title text-uppercase text-white-50 mb-0">活跃卡密</h5>
|
||||
<span class="h2 font-weight-bold mb-0 text-white" id="active-licenses">-</span>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="icon icon-shape bg-white text-warning rounded-circle shadow">
|
||||
<i class="fas fa-key"></i>
|
||||
<div class="icon bg-white bg-opacity-20 rounded-circle shadow" style="width: 60px; height: 60px; display: flex; align-items: center; justify-content: center;">
|
||||
<i class="fas fa-key text-white fs-4"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -43,17 +144,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-4 mb-4">
|
||||
<div class="card card-stats">
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card card-stats gradient-bg-3 fade-in-up">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h5 class="card-title text-uppercase text-muted mb-0">在线设备</h5>
|
||||
<span class="h2 font-weight-bold mb-0" id="online-devices">-</span>
|
||||
<h5 class="card-title text-uppercase text-white-50 mb-0">在线设备</h5>
|
||||
<span class="h2 font-weight-bold mb-0 text-white" id="online-devices">-</span>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="icon icon-shape bg-white text-success rounded-circle shadow">
|
||||
<i class="fas fa-desktop"></i>
|
||||
<div class="icon bg-white bg-opacity-20 rounded-circle shadow" style="width: 60px; height: 60px; display: flex; align-items: center; justify-content: center;">
|
||||
<i class="fas fa-desktop text-white fs-4"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -61,17 +162,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-4 mb-4">
|
||||
<div class="card card-stats">
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card card-stats gradient-bg-4 fade-in-up">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h5 class="card-title text-uppercase text-muted mb-0">今日激活</h5>
|
||||
<span class="h2 font-weight-bold mb-0" id="today-activations">-</span>
|
||||
<h5 class="card-title text-uppercase text-white-50 mb-0">今日激活</h5>
|
||||
<span class="h2 font-weight-bold mb-0 text-white" id="today-activations">-</span>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="icon icon-shape bg-white text-info rounded-circle shadow">
|
||||
<i class="fas fa-user-plus"></i>
|
||||
<div class="icon bg-white bg-opacity-20 rounded-circle shadow" style="width: 60px; height: 60px; display: flex; align-items: center; justify-content: center;">
|
||||
<i class="fas fa-user-plus text-white fs-4"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}设备管理 - 软件授权管理系统{% endblock %}
|
||||
{% block title %}设备管理 - 太一软件授权管理系统{% endblock %}
|
||||
|
||||
{% block page_title %}设备管理{% endblock %}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}导出卡密 - 软件授权管理系统{% endblock %}
|
||||
{% block title %}导出卡密 - 太一软件授权管理系统{% endblock %}
|
||||
|
||||
{% block page_title %}导出卡密{% endblock %}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}生成卡密 - 软件授权管理系统{% endblock %}
|
||||
{% block title %}生成卡密 - 太一软件授权管理系统{% endblock %}
|
||||
|
||||
{% block page_title %}生成卡密{% endblock %}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}导入卡密 - 软件授权管理系统{% endblock %}
|
||||
{% block title %}导入卡密 - 太一软件授权管理系统{% endblock %}
|
||||
|
||||
{% block page_title %}导入卡密{% endblock %}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}卡密管理 - 软件授权管理系统{% endblock %}
|
||||
{% block title %}卡密管理 - 太一软件授权管理系统{% endblock %}
|
||||
|
||||
{% block page_title %}卡密管理{% endblock %}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}订单详情 - {{ config.SITE_NAME or '软件授权管理系统' }}{% endblock %}
|
||||
{% block title %}订单详情 - {{ config.SITE_NAME or '太一软件授权管理系统' }}{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}订单管理 - {{ config.SITE_NAME or '软件授权管理系统' }}{% endblock %}
|
||||
{% block title %}订单管理 - {{ config.SITE_NAME or '太一软件授权管理系统' }}{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}创建套餐 - {{ config.SITE_NAME or '软件授权管理系统' }}{% endblock %}
|
||||
{% block title %}创建套餐 - {{ config.SITE_NAME or '太一软件授权管理系统' }}{% endblock %}
|
||||
|
||||
{% block page_title %}创建套餐{% endblock %}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}编辑套餐 - {{ config.SITE_NAME or '软件授权管理系统' }}{% endblock %}
|
||||
{% block title %}编辑套餐 - {{ config.SITE_NAME or '太一软件授权管理系统' }}{% endblock %}
|
||||
|
||||
{% block page_title %}编辑套餐{% endblock %}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}套餐管理 - {{ config.SITE_NAME or '软件授权管理系统' }}{% endblock %}
|
||||
{% block title %}套餐管理 - {{ config.SITE_NAME or '太一软件授权管理系统' }}{% endblock %}
|
||||
|
||||
{% block page_title %}套餐管理{% endblock %}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>支付结果 - {{ config.SITE_NAME or '软件授权管理系统' }}</title>
|
||||
<title>支付结果 - {{ config.SITE_NAME or '太一软件授权管理系统' }}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
<style>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}支付配置 - 软件授权管理系统{% endblock %}
|
||||
{% block title %}支付配置 - 太一软件授权管理系统{% endblock %}
|
||||
|
||||
{% block page_title %}支付配置{% endblock %}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}创建产品 - 软件授权管理系统{% endblock %}
|
||||
{% block title %}创建产品 - 太一软件授权管理系统{% endblock %}
|
||||
|
||||
{% block page_title %}创建产品{% endblock %}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}产品详情 - 软件授权管理系统{% endblock %}
|
||||
{% block title %}产品详情 - 太一软件授权管理系统{% endblock %}
|
||||
|
||||
{% block page_title %}产品详情{% endblock %}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}编辑产品 - 软件授权管理系统{% endblock %}
|
||||
{% block title %}编辑产品 - 太一软件授权管理系统{% endblock %}
|
||||
|
||||
{% block page_title %}编辑产品{% endblock %}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}产品管理 - 软件授权管理系统{% endblock %}
|
||||
{% block title %}产品管理 - 太一软件授权管理系统{% endblock %}
|
||||
|
||||
{% block page_title %}产品管理{% endblock %}
|
||||
|
||||
|
||||
@ -307,131 +307,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 支付配置 -->
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0">支付配置</h6>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="payment_enabled" name="payment_enabled"
|
||||
{{ 'checked' if config.PAYMENT_ENABLED else '' }}>
|
||||
<label class="form-check-label" for="payment_enabled">
|
||||
启用支付功能
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="payment-settings-form">
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
请正确配置支付宝参数以启用支付功能
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="alipay_app_id" class="form-label">支付宝应用ID <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="alipay_app_id" name="alipay_app_id"
|
||||
value="{{ config.ALIPAY_APP_ID or '' }}" placeholder="输入支付宝应用APP_ID">
|
||||
<div class="form-text">从支付宝开放平台获取的应用ID</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="alipay_private_key" class="form-label">应用私钥 <span class="text-danger">*</span></label>
|
||||
<textarea class="form-control" id="alipay_private_key" name="alipay_private_key"
|
||||
rows="4" placeholder="-----BEGIN PRIVATE KEY-----">{{ config.ALIPAY_PRIVATE_KEY or '' }}</textarea>
|
||||
<div class="form-text">您的应用私钥(RSA2格式)</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="alipay_public_key" class="form-label">支付宝公钥 <span class="text-danger">*</span></label>
|
||||
<textarea class="form-control" id="alipay_public_key" name="alipay_public_key"
|
||||
rows="4" placeholder="-----BEGIN PUBLIC KEY-----">{{ config.ALIPAY_PUBLIC_KEY or '' }}</textarea>
|
||||
<div class="form-text">您的应用公钥(RSA2格式)</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="alipay_alipay_public_key" class="form-label">支付宝平台公钥 <span class="text-danger">*</span></label>
|
||||
<textarea class="form-control" id="alipay_alipay_public_key" name="alipay_alipay_public_key"
|
||||
rows="4" placeholder="-----BEGIN PUBLIC KEY-----">{{ config.ALIPAY_ALIPAY_PUBLIC_KEY or '' }}</textarea>
|
||||
<div class="form-text">支付宝平台的公钥(RSA2格式)</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="alipay_notify_url" class="form-label">异步通知URL</label>
|
||||
<input type="url" class="form-control" id="alipay_notify_url" name="alipay_notify_url"
|
||||
value="{{ config.ALIPAY_NOTIFY_URL or '' }}" placeholder="https://your-domain.com/api/v1/pay/alipay/notify">
|
||||
<div class="form-text">支付宝异步通知地址</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="alipay_return_url" class="form-label">同步返回URL</label>
|
||||
<input type="url" class="form-control" id="alipay_return_url" name="alipay_return_url"
|
||||
value="{{ config.ALIPAY_RETURN_URL or '' }}" placeholder="https://your-domain.com/payment/result">
|
||||
<div class="form-text">支付完成后的返回地址</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="alipay_gateway" class="form-label">支付宝网关地址</label>
|
||||
<input type="url" class="form-control" id="alipay_gateway" name="alipay_gateway"
|
||||
value="{{ config.ALIPAY_GATEWAY or 'https://openapi.alipay.com/gateway.do' }}">
|
||||
<div class="form-text">默认使用正式环境网关</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="alipay_timeout_express" class="form-label">支付超时时间(分钟)</label>
|
||||
<input type="number" class="form-control" id="alipay_timeout_express" name="alipay_timeout_express"
|
||||
value="{{ config.ALIPAY_TIMEOUT_EXPRESS or 30 }}" min="5" max="120">
|
||||
<div class="form-text">订单支付超时时间</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<label for="alipay_sign_type" class="form-label">签名算法</label>
|
||||
<select class="form-select" id="alipay_sign_type" name="alipay_sign_type">
|
||||
<option value="RSA2" {{ 'selected' if config.ALIPAY_SIGN_TYPE == 'RSA2' else '' }}>RSA2</option>
|
||||
<option value="RSA" {{ 'selected' if config.ALIPAY_SIGN_TYPE == 'RSA' else '' }}>RSA</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<label for="alipay_charset" class="form-label">编码格式</label>
|
||||
<select class="form-select" id="alipay_charset" name="alipay_charset">
|
||||
<option value="utf-8" {{ 'selected' if config.ALIPAY_CHARSET == 'utf-8' else '' }}>utf-8</option>
|
||||
<option value="gbk" {{ 'selected' if config.ALIPAY_CHARSET == 'gbk' else '' }}>gbk</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<label for="alipay_version" class="form-label">API版本</label>
|
||||
<select class="form-select" id="alipay_version" name="alipay_version">
|
||||
<option value="1.0" {{ 'selected' if config.ALIPAY_VERSION == '1.0' else '' }}>1.0</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<button type="button" class="btn btn-info" id="test-alipay-config-btn">
|
||||
<i class="fas fa-check-circle me-2"></i>
|
||||
测试支付宝配置
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary" id="save-payment-btn">
|
||||
<i class="fas fa-save me-2"></i>
|
||||
<span id="save-payment-text">保存设置</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<!-- 支付配置提示 -->
|
||||
<div class="alert alert-info d-flex align-items-center mb-4" role="alert">
|
||||
<i class="fas fa-info-circle me-3 fs-4"></i>
|
||||
<div>
|
||||
<h6 class="alert-heading mb-1">支付功能已独立配置</h6>
|
||||
<p class="mb-0">支付相关设置已移至 <a href="{{ url_for('web.payment_config') }}" class="alert-link">支付配置页面</a>,请点击链接前往配置。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -491,12 +372,6 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-grid gap-2">
|
||||
{% if config.PAYMENT_ENABLED %}
|
||||
<button type="button" class="btn btn-outline-success" data-bs-toggle="modal" data-bs-target="#testPaymentModal">
|
||||
<i class="fas fa-credit-card me-2"></i>
|
||||
测试支付功能
|
||||
</button>
|
||||
{% endif %}
|
||||
<button type="button" class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#backupModal">
|
||||
<i class="fas fa-database me-2"></i>
|
||||
数据备份
|
||||
@ -559,29 +434,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 测试支付功能模态框 -->
|
||||
<div class="modal fade" id="testPaymentModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">测试支付功能</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>此操作将创建一个测试订单并生成支付链接,用于验证支付功能是否正常工作。</p>
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
请确保已完成支付宝配置并启用支付功能
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="button" class="btn btn-success" id="test-payment-btn">开始测试</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 重置系统模态框 -->
|
||||
<div class="modal fade" id="resetModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
@ -688,24 +540,9 @@ function initEventListeners() {
|
||||
document.getElementById('backup-btn').addEventListener('click', function() {
|
||||
backupData();
|
||||
});
|
||||
|
||||
// 支付设置表单
|
||||
document.getElementById('payment-settings-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
savePaymentSettings();
|
||||
});
|
||||
|
||||
// 测试支付宝配置按钮
|
||||
document.getElementById('test-alipay-config-btn').addEventListener('click', function() {
|
||||
testAlipayConfig();
|
||||
});
|
||||
|
||||
// 测试支付功能按钮
|
||||
document.getElementById('test-payment-btn').addEventListener('click', function() {
|
||||
testPaymentFunction();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 生成安全密钥
|
||||
function generateSecretKey() {
|
||||
// 生成一个随机的32字符密钥
|
||||
@ -1123,154 +960,5 @@ function backupData() {
|
||||
showNotification('数据备份创建成功', 'success');
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 保存支付设置
|
||||
function savePaymentSettings() {
|
||||
const saveBtn = document.getElementById('save-payment-btn');
|
||||
const saveText = document.getElementById('save-payment-text');
|
||||
|
||||
// 显示加载状态
|
||||
saveBtn.disabled = true;
|
||||
saveText.textContent = '保存中...';
|
||||
|
||||
// 收集表单数据
|
||||
const formData = {
|
||||
payment_enabled: document.getElementById('payment_enabled').checked,
|
||||
alipay_app_id: document.getElementById('alipay_app_id').value,
|
||||
alipay_private_key: document.getElementById('alipay_private_key').value,
|
||||
alipay_public_key: document.getElementById('alipay_public_key').value,
|
||||
alipay_alipay_public_key: document.getElementById('alipay_alipay_public_key').value,
|
||||
alipay_gateway: document.getElementById('alipay_gateway').value,
|
||||
alipay_notify_url: document.getElementById('alipay_notify_url').value,
|
||||
alipay_return_url: document.getElementById('alipay_return_url').value,
|
||||
alipay_timeout_express: parseInt(document.getElementById('alipay_timeout_express').value),
|
||||
alipay_sign_type: document.getElementById('alipay_sign_type').value,
|
||||
alipay_charset: document.getElementById('alipay_charset').value,
|
||||
alipay_version: document.getElementById('alipay_version').value
|
||||
};
|
||||
|
||||
// 发送请求保存设置
|
||||
const saveUrl = '/api/v1/settings';
|
||||
|
||||
apiRequest(saveUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showNotification('支付设置保存成功', 'success');
|
||||
} else {
|
||||
showNotification('保存失败: ' + data.message, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showNotification('保存失败,请查看控制台了解详情', 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
saveBtn.disabled = false;
|
||||
saveText.textContent = '保存设置';
|
||||
});
|
||||
}
|
||||
|
||||
// 测试支付宝配置
|
||||
function testAlipayConfig() {
|
||||
const testBtn = document.getElementById('test-alipay-config-btn');
|
||||
const originalText = testBtn.innerHTML;
|
||||
|
||||
// 显示加载状态
|
||||
testBtn.disabled = true;
|
||||
testBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>测试中...';
|
||||
|
||||
// 收集当前配置
|
||||
const configData = {
|
||||
alipay_app_id: document.getElementById('alipay_app_id').value,
|
||||
alipay_private_key: document.getElementById('alipay_private_key').value,
|
||||
alipay_public_key: document.getElementById('alipay_public_key').value,
|
||||
alipay_alipay_public_key: document.getElementById('alipay_alipay_public_key').value,
|
||||
alipay_gateway: document.getElementById('alipay_gateway').value
|
||||
};
|
||||
|
||||
// 验证必填项
|
||||
if (!configData.alipay_app_id || !configData.alipay_private_key ||
|
||||
!configData.alipay_public_key || !configData.alipay_alipay_public_key) {
|
||||
showNotification('请先填写完整的支付宝配置信息', 'error');
|
||||
testBtn.disabled = false;
|
||||
testBtn.innerHTML = originalText;
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送测试请求
|
||||
apiRequest('/api/v1/settings/test-alipay', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(configData)
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showNotification('支付宝配置测试成功!', 'success');
|
||||
} else {
|
||||
showNotification('支付宝配置测试失败: ' + data.message, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showNotification('测试失败,请查看控制台了解详情', 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
testBtn.disabled = false;
|
||||
testBtn.innerHTML = originalText;
|
||||
});
|
||||
}
|
||||
|
||||
// 测试支付功能
|
||||
function testPaymentFunction() {
|
||||
const testBtn = document.getElementById('test-payment-btn');
|
||||
const originalText = testBtn.textContent;
|
||||
|
||||
// 显示加载状态
|
||||
testBtn.disabled = true;
|
||||
testBtn.textContent = '测试中...';
|
||||
|
||||
// 发送测试请求
|
||||
apiRequest('/api/v1/settings/test-payment', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({})
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showNotification('支付功能测试成功!', 'success');
|
||||
// 关闭模态框
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('testPaymentModal'));
|
||||
modal.hide();
|
||||
// 显示支付链接
|
||||
if (data.payment_url) {
|
||||
setTimeout(() => {
|
||||
if (confirm('支付测试成功!是否打开支付链接?')) {
|
||||
window.open(data.payment_url, '_blank');
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
} else {
|
||||
showNotification('支付功能测试失败: ' + data.message, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showNotification('测试失败,请查看控制台了解详情', 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
testBtn.disabled = false;
|
||||
testBtn.textContent = originalText;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}{{ config.SITE_NAME or '软件授权管理系统' }}{% endblock %}</title>
|
||||
<title>{% block title %}{{ config.SITE_NAME or '太一软件官网' }}{% endblock %}</title>
|
||||
|
||||
<!-- Bootstrap 5 CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
@ -99,7 +99,7 @@
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="{{ url_for('user.user_index') }}">
|
||||
<i class="fas fa-cube me-2"></i>{{ config.SITE_NAME or '软件授权管理系统' }}
|
||||
<i class="fas fa-cube me-2"></i>{{ config.SITE_NAME or '太一软件官网' }}
|
||||
</a>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
|
||||
@ -64,8 +64,8 @@
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8 text-center">
|
||||
<h1 class="display-4 fw-bold mb-3">企业级高效协作软件</h1>
|
||||
<p class="lead mb-4">覆盖文档管理、任务协作、数据分析等核心场景,助力企业数字化转型</p>
|
||||
<h1 class="display-4 fw-bold mb-3">用心做软件</h1>
|
||||
<p class="lead mb-4">何以解忧,唯有代码,不忘初心,方得始终</p>
|
||||
|
||||
<!-- 核心功能图标 -->
|
||||
<div class="row mt-5">
|
||||
@ -144,8 +144,8 @@
|
||||
</div>
|
||||
<div class="modal-body text-center">
|
||||
<p>扫码添加微信,快速响应需求</p>
|
||||
<img src="/static/images/wechat-qrcode.jpg" alt="微信二维码" class="wechat-qrcode img-fluid">
|
||||
<p class="mt-2 text-muted small">微信ID: TaiYiSupport</p>
|
||||
<img src="/static/images/taiyiagi.png" alt="微信二维码" class="wechat-qrcode img-fluid">
|
||||
<p class="mt-2 text-muted small">微信ID: taiyi1224</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||||
|
||||
22
config.py
22
config.py
@ -37,7 +37,7 @@ class Config:
|
||||
}
|
||||
|
||||
# 系统基本配置
|
||||
SITE_NAME = os.environ.get('SITE_NAME') or '软件授权管理系统'
|
||||
SITE_NAME = os.environ.get('SITE_NAME') or '太一软件官网'
|
||||
ADMIN_EMAIL = os.environ.get('ADMIN_EMAIL') or ''
|
||||
# 前端域名配置 - 支持多种环境变量
|
||||
FRONTEND_DOMAIN = os.environ.get('FRONTEND_DOMAIN') or os.environ.get('DOMAIN_NAME') or os.environ.get('SERVER_NAME') or ''
|
||||
@ -126,16 +126,28 @@ class Config:
|
||||
"""安全的日志轮转处理器,解决Windows文件锁定问题"""
|
||||
|
||||
def doRollover(self):
|
||||
"""重写轮转方法,添加错误处理"""
|
||||
"""重写轮转方法,添加重试机制和错误处理"""
|
||||
import time
|
||||
|
||||
# 尝试多次轮转
|
||||
max_retries = 3
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
super().doRollover()
|
||||
except PermissionError as e:
|
||||
# Windows文件锁定错误,静默处理
|
||||
break # 成功则退出循环
|
||||
except (OSError, PermissionError) as e:
|
||||
if attempt < max_retries - 1:
|
||||
# 等待一段时间后重试
|
||||
time.sleep(0.5 * (attempt + 1))
|
||||
continue
|
||||
else:
|
||||
# 最后一次尝试失败,静默处理
|
||||
import sys
|
||||
print(f"日志轮转被跳过(文件被占用): {str(e)}", file=sys.stderr)
|
||||
print(f"日志轮转失败(已重试{max_retries}次): {str(e)}", file=sys.stderr)
|
||||
except Exception as e:
|
||||
import sys
|
||||
print(f"日志轮转错误: {str(e)}", file=sys.stderr)
|
||||
break
|
||||
|
||||
# 使用安全的日志处理器
|
||||
file_handler = SafeTimedRotatingFileHandler(
|
||||
|
||||
@ -1,43 +1,72 @@
|
||||
2025-12-12 11:32:38,965 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:159]
|
||||
2025-12-12 11:32:39,205 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:169]
|
||||
2025-12-12 11:32:39,205 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:169]
|
||||
2025-12-12 11:32:39,359 INFO: 瀹氭椂浠诲姟璋冨害鍣ㄥ垵濮嬪寲瀹屾垚 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:75]
|
||||
2025-12-12 11:32:39,359 INFO: 定时任务调度器初始化完成 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:75]
|
||||
2025-12-12 11:32:39,359 INFO: 宸叉坊鍔犱互涓嬪畾鏃朵换鍔<E68DA2>: [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:76]
|
||||
2025-12-12 11:32:39,359 INFO: 已添加以下定时任务: [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:76]
|
||||
2025-12-12 11:32:39,359 INFO: 1. 姣忓皬鏃舵洿鏂拌繃鏈熷崱瀵嗙姸鎬<E5A7B8> [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:77]
|
||||
2025-12-12 11:32:39,359 INFO: 1. 每小时更新过期卡密状态 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:77]
|
||||
2025-12-12 11:32:39,359 INFO: 2. 姣忓ぉ鍑屾櫒2鐐瑰崱瀵嗗仴搴锋<E690B4>鏌<EFBFBD> [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:78]
|
||||
2025-12-12 11:32:39,359 INFO: 2. 每天凌晨2点卡密健康检查 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:78]
|
||||
2025-12-12 11:32:39,359 INFO: 3. 姣忓懆鏃ュ噷鏅<E599B7>3鐐规竻鐞嗘棩蹇<E6A3A9> [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:79]
|
||||
2025-12-12 11:32:39,359 INFO: 3. 每周日凌晨3点清理日志 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:79]
|
||||
2025-12-12 11:32:39,363 INFO: 瀹氭椂浠诲姟璋冨害鍣ㄥ凡鍚<E587A1>姩 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:96]
|
||||
2025-12-12 11:32:39,363 INFO: 定时任务调度器已启动 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:96]
|
||||
2025-12-12 11:32:39,363 INFO: 鍚庡彴瀹氭椂浠诲姟璋冨害鍣ㄥ凡鍚<E587A1>姩 [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:181]
|
||||
2025-12-12 11:32:39,363 INFO: 后台定时任务调度器已启动 [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:181]
|
||||
2025-12-12 11:32:56,330 INFO: 鏀跺埌鐧诲綍璇锋眰 - IP: 127.0.0.1, User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0 [in D:\work\code\python\KaMiXiTong\master\app\web\__init__.py:20]
|
||||
2025-12-12 11:32:56,330 INFO: 收到登录请求 - IP: 127.0.0.1, User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0 [in D:\work\code\python\KaMiXiTong\master\app\web\__init__.py:20]
|
||||
2025-12-12 11:32:56,330 INFO: 鐧诲綍灏濊瘯 - 鐢ㄦ埛鍚<E59F9B>: admin, 鏉ユ簮IP: 127.0.0.1 [in D:\work\code\python\KaMiXiTong\master\app\web\__init__.py:49]
|
||||
2025-12-12 11:32:56,330 INFO: 登录尝试 - 用户名: admin, 来源IP: 127.0.0.1 [in D:\work\code\python\KaMiXiTong\master\app\web\__init__.py:49]
|
||||
2025-12-12 11:32:56,725 INFO: 鐧诲綍鎴愬姛 - 鐢ㄦ埛鍚<E59F9B>: admin, IP: 127.0.0.1 [in D:\work\code\python\KaMiXiTong\master\app\web\__init__.py:79]
|
||||
2025-12-12 11:32:56,725 INFO: 登录成功 - 用户名: admin, IP: 127.0.0.1 [in D:\work\code\python\KaMiXiTong\master\app\web\__init__.py:79]
|
||||
2025-12-12 11:32:56,997 INFO: License search params - page: 1, per_page: 5, product_id: None, status: 1, type: None, keyword: [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:30]
|
||||
2025-12-12 11:32:56,997 INFO: License search params - page: 1, per_page: 5, product_id: None, status: 1, type: None, keyword: [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:30]
|
||||
2025-12-12 11:32:57,041 INFO: License search results - total: 0, pages: 0 [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:76]
|
||||
2025-12-12 11:32:57,041 INFO: License search results - total: 0, pages: 0 [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:76]
|
||||
2025-12-12 11:33:01,693 ERROR: 鍒犻櫎浜у搧澶辫触: (pymysql.err.IntegrityError) (1048, "Column 'product_id' cannot be null")
|
||||
[SQL: UPDATE package SET product_id=%(product_id)s, update_time=%(update_time)s WHERE package.package_id = %(package_package_id)s]
|
||||
[parameters: [{'product_id': None, 'update_time': datetime.datetime(2025, 12, 12, 3, 33, 1, 689646), 'package_package_id': 'PKG_DEMO_1'}, {'product_id': None, 'update_time': datetime.datetime(2025, 12, 12, 3, 33, 1, 689646), 'package_package_id': 'PKG_DEMO_2'}, {'product_id': None, 'update_time': datetime.datetime(2025, 12, 12, 3, 33, 1, 689646), 'package_package_id': 'PKG_DEMO_3'}]]
|
||||
(Background on this error at: https://sqlalche.me/e/20/gkpj) [in D:\work\code\python\KaMiXiTong\master\app\api\product.py:516]
|
||||
2025-12-12 11:33:01,693 ERROR: 删除产品失败: (pymysql.err.IntegrityError) (1048, "Column 'product_id' cannot be null")
|
||||
[SQL: UPDATE package SET product_id=%(product_id)s, update_time=%(update_time)s WHERE package.package_id = %(package_package_id)s]
|
||||
[parameters: [{'product_id': None, 'update_time': datetime.datetime(2025, 12, 12, 3, 33, 1, 689646), 'package_package_id': 'PKG_DEMO_1'}, {'product_id': None, 'update_time': datetime.datetime(2025, 12, 12, 3, 33, 1, 689646), 'package_package_id': 'PKG_DEMO_2'}, {'product_id': None, 'update_time': datetime.datetime(2025, 12, 12, 3, 33, 1, 689646), 'package_package_id': 'PKG_DEMO_3'}]]
|
||||
(Background on this error at: https://sqlalche.me/e/20/gkpj) [in D:\work\code\python\KaMiXiTong\master\app\api\product.py:516]
|
||||
2025-12-12 11:33:03,689 INFO: License search params - page: 1, per_page: 10, product_id: None, status: None, type: None, keyword: [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:30]
|
||||
2025-12-12 11:33:03,689 INFO: License search params - page: 1, per_page: 10, product_id: None, status: None, type: None, keyword: [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:30]
|
||||
2025-12-12 11:33:03,707 INFO: License search results - total: 111, pages: 12 [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:76]
|
||||
2025-12-12 11:33:03,707 INFO: License search results - total: 111, pages: 12 [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:76]
|
||||
2025-12-12 11:33:05,877 INFO: License search params - page: 1, per_page: 10, product_id: None, status: None, type: None, keyword: [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:30]
|
||||
2025-12-12 11:33:05,877 INFO: License search params - page: 1, per_page: 10, product_id: None, status: None, type: None, keyword: [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:30]
|
||||
2025-12-12 11:33:05,889 INFO: License search results - total: 110, pages: 11 [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:76]
|
||||
2025-12-12 11:33:05,889 INFO: License search results - total: 110, pages: 11 [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:76]
|
||||
2025-12-27 14:28:18,813 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:171]
|
||||
2025-12-27 14:28:21,132 INFO: 定时任务调度器初始化完成 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:75]
|
||||
2025-12-27 14:28:21,132 INFO: 已添加以下定时任务: [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:76]
|
||||
2025-12-27 14:28:21,132 INFO: 1. 每小时更新过期卡密状态 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:77]
|
||||
2025-12-27 14:28:21,132 INFO: 2. 每天凌晨2点卡密健康检查 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:78]
|
||||
2025-12-27 14:28:21,132 INFO: 3. 每周日凌晨3点清理日志 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:79]
|
||||
2025-12-27 14:28:21,136 INFO: 定时任务调度器已启动 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:96]
|
||||
2025-12-27 14:28:21,136 INFO: 后台定时任务调度器已启动 [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:164]
|
||||
2025-12-27 14:28:33,398 INFO: 收到登录请求 - IP: 127.0.0.1, User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0 [in D:\work\code\python\KaMiXiTong\master\app\web\__init__.py:20]
|
||||
2025-12-27 14:28:33,399 INFO: 登录尝试 - 用户名: admin, 来源IP: 127.0.0.1 [in D:\work\code\python\KaMiXiTong\master\app\web\__init__.py:49]
|
||||
2025-12-27 14:28:34,236 WARNING: 登录失败 - 密码错误: admin [in D:\work\code\python\KaMiXiTong\master\app\web\__init__.py:109]
|
||||
2025-12-27 14:28:37,576 INFO: 收到登录请求 - IP: 127.0.0.1, User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0 [in D:\work\code\python\KaMiXiTong\master\app\web\__init__.py:20]
|
||||
2025-12-27 14:28:37,576 INFO: 登录尝试 - 用户名: admin, 来源IP: 127.0.0.1 [in D:\work\code\python\KaMiXiTong\master\app\web\__init__.py:49]
|
||||
2025-12-27 14:28:37,979 INFO: 登录成功 - 用户名: admin, IP: 127.0.0.1 [in D:\work\code\python\KaMiXiTong\master\app\web\__init__.py:79]
|
||||
2025-12-27 14:28:38,231 INFO: License search params - page: 1, per_page: 5, product_id: None, status: 1, type: None, keyword: [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:30]
|
||||
2025-12-27 14:28:38,260 INFO: License search results - total: 0, pages: 0 [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:76]
|
||||
2025-12-27 14:28:43,859 INFO: License search params - page: 1, per_page: 10, product_id: None, status: None, type: None, keyword: [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:30]
|
||||
2025-12-27 14:28:43,876 INFO: License search results - total: 110, pages: 11 [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:76]
|
||||
2025-12-27 14:28:49,799 INFO: License search params - page: 1, per_page: 5, product_id: None, status: 1, type: None, keyword: [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:30]
|
||||
2025-12-27 14:28:49,825 INFO: License search results - total: 0, pages: 0 [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:76]
|
||||
2025-12-27 14:28:50,490 INFO: License search params - page: 1, per_page: 5, product_id: None, status: 1, type: None, keyword: [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:30]
|
||||
2025-12-27 14:28:50,507 INFO: License search results - total: 0, pages: 0 [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:76]
|
||||
2025-12-27 14:30:27,013 INFO: License search params - page: 1, per_page: 10, product_id: None, status: None, type: None, keyword: [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:30]
|
||||
2025-12-27 14:30:27,022 INFO: License search results - total: 110, pages: 11 [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:76]
|
||||
2025-12-27 14:30:33,198 INFO: License search params - page: 1, per_page: 5, product_id: None, status: 1, type: None, keyword: [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:30]
|
||||
2025-12-27 14:30:33,216 INFO: License search results - total: 0, pages: 0 [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:76]
|
||||
2025-12-27 14:30:42,136 INFO: License search params - page: 1, per_page: 10, product_id: None, status: None, type: None, keyword: [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:30]
|
||||
2025-12-27 14:30:42,145 INFO: License search results - total: 110, pages: 11 [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:76]
|
||||
2025-12-27 14:30:43,718 INFO: License search params - page: 1, per_page: 5, product_id: None, status: 1, type: None, keyword: [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:30]
|
||||
2025-12-27 14:30:43,738 INFO: License search results - total: 0, pages: 0 [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:76]
|
||||
2025-12-27 14:31:43,712 INFO: License search params - page: 1, per_page: 5, product_id: None, status: 1, type: None, keyword: [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:30]
|
||||
2025-12-27 14:31:43,730 INFO: License search results - total: 0, pages: 0 [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:76]
|
||||
2025-12-27 14:38:56,592 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:171]
|
||||
2025-12-27 14:38:57,042 INFO: 定时任务调度器初始化完成 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:75]
|
||||
2025-12-27 14:38:57,043 INFO: 已添加以下定时任务: [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:76]
|
||||
2025-12-27 14:38:57,043 INFO: 1. 每小时更新过期卡密状态 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:77]
|
||||
2025-12-27 14:38:57,043 INFO: 2. 每天凌晨2点卡密健康检查 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:78]
|
||||
2025-12-27 14:38:57,043 INFO: 3. 每周日凌晨3点清理日志 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:79]
|
||||
2025-12-27 14:38:57,045 INFO: 定时任务调度器已启动 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:96]
|
||||
2025-12-27 14:38:57,045 INFO: 后台定时任务调度器已启动 [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:164]
|
||||
2025-12-27 14:42:27,626 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:171]
|
||||
2025-12-27 14:42:28,027 INFO: 定时任务调度器初始化完成 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:75]
|
||||
2025-12-27 14:42:28,027 INFO: 已添加以下定时任务: [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:76]
|
||||
2025-12-27 14:42:28,027 INFO: 1. 每小时更新过期卡密状态 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:77]
|
||||
2025-12-27 14:42:28,027 INFO: 2. 每天凌晨2点卡密健康检查 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:78]
|
||||
2025-12-27 14:42:28,027 INFO: 3. 每周日凌晨3点清理日志 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:79]
|
||||
2025-12-27 14:42:28,030 INFO: 定时任务调度器已启动 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:96]
|
||||
2025-12-27 14:42:28,030 INFO: 后台定时任务调度器已启动 [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:164]
|
||||
2025-12-27 14:49:23,729 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:171]
|
||||
2025-12-27 14:49:24,202 INFO: 定时任务调度器初始化完成 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:75]
|
||||
2025-12-27 14:49:24,202 INFO: 已添加以下定时任务: [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:76]
|
||||
2025-12-27 14:49:24,202 INFO: 1. 每小时更新过期卡密状态 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:77]
|
||||
2025-12-27 14:49:24,202 INFO: 2. 每天凌晨2点卡密健康检查 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:78]
|
||||
2025-12-27 14:49:24,202 INFO: 3. 每周日凌晨3点清理日志 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:79]
|
||||
2025-12-27 14:49:24,204 INFO: 定时任务调度器已启动 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:96]
|
||||
2025-12-27 14:49:24,204 INFO: 后台定时任务调度器已启动 [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:164]
|
||||
2025-12-27 14:50:15,257 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:171]
|
||||
2025-12-27 14:50:15,638 INFO: 定时任务调度器初始化完成 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:75]
|
||||
2025-12-27 14:50:15,638 INFO: 已添加以下定时任务: [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:76]
|
||||
2025-12-27 14:50:15,638 INFO: 1. 每小时更新过期卡密状态 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:77]
|
||||
2025-12-27 14:50:15,639 INFO: 2. 每天凌晨2点卡密健康检查 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:78]
|
||||
2025-12-27 14:50:15,639 INFO: 3. 每周日凌晨3点清理日志 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:79]
|
||||
2025-12-27 14:50:15,641 INFO: 定时任务调度器已启动 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:96]
|
||||
2025-12-27 14:50:15,642 INFO: 后台定时任务调度器已启动 [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:164]
|
||||
2025-12-27 15:02:33,837 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:171]
|
||||
2025-12-27 15:02:34,275 INFO: 定时任务调度器初始化完成 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:75]
|
||||
2025-12-27 15:02:34,276 INFO: 已添加以下定时任务: [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:76]
|
||||
2025-12-27 15:02:34,276 INFO: 1. 每小时更新过期卡密状态 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:77]
|
||||
2025-12-27 15:02:34,276 INFO: 2. 每天凌晨2点卡密健康检查 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:78]
|
||||
2025-12-27 15:02:34,276 INFO: 3. 每周日凌晨3点清理日志 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:79]
|
||||
2025-12-27 15:02:34,279 INFO: 定时任务调度器已启动 [in D:\work\code\python\KaMiXiTong\master\app\utils\scheduler.py:96]
|
||||
2025-12-27 15:02:34,279 INFO: 后台定时任务调度器已启动 [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:164]
|
||||
|
||||
BIN
static/images/taiyiagi.png
Normal file
BIN
static/images/taiyiagi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
@ -1,240 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
卡密过期状态更新功能测试脚本
|
||||
|
||||
此脚本用于验证过期卡密自动更新功能是否正常工作
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# 添加项目路径
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from app import create_app, db
|
||||
from app.models import License, Product
|
||||
|
||||
|
||||
def create_test_license(app):
|
||||
"""创建测试卡密"""
|
||||
with app.app_context():
|
||||
# 查找或创建测试产品
|
||||
product = Product.query.filter_by(product_id='test-product').first()
|
||||
if not product:
|
||||
product = Product(
|
||||
product_id='test-product',
|
||||
product_name='测试产品',
|
||||
status=1
|
||||
)
|
||||
db.session.add(product)
|
||||
db.session.flush()
|
||||
|
||||
# 创建测试卡密(1天有效期)
|
||||
license_obj = License(
|
||||
product_id='test-product',
|
||||
type=1, # 正式卡密
|
||||
valid_days=1,
|
||||
status=0 # 未激活
|
||||
)
|
||||
db.session.add(license_obj)
|
||||
db.session.commit()
|
||||
|
||||
print(f"✅ 创建测试卡密: {license_obj.license_key}")
|
||||
print(f" 卡密ID: {license_obj.license_id}")
|
||||
print(f" 初始状态: {license_obj.status} ({license_obj.get_status_name()})")
|
||||
|
||||
return license_obj
|
||||
|
||||
|
||||
def test_activate_license(license_obj, machine_code='test-machine-001'):
|
||||
"""激活测试卡密"""
|
||||
with license_obj.query.session.no_autoflush:
|
||||
success, message = license_obj.activate(
|
||||
machine_code=machine_code,
|
||||
software_version='1.0.0'
|
||||
)
|
||||
|
||||
if success:
|
||||
print(f"✅ 卡密激活成功")
|
||||
print(f" 激活时间: {license_obj.activate_time}")
|
||||
print(f" 过期时间: {license_obj.expire_time}")
|
||||
print(f" 当前状态: {license_obj.status} ({license_obj.get_status_name()})")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 卡密激活失败: {message}")
|
||||
return False
|
||||
|
||||
|
||||
def test_expire_license(license_obj):
|
||||
"""模拟卡密过期"""
|
||||
with license_obj.query.session.no_autoflush:
|
||||
# 将过期时间设置为过去
|
||||
license_obj.expire_time = datetime.utcnow() - timedelta(days=1)
|
||||
db.session.commit()
|
||||
|
||||
print(f"✅ 卡密已模拟过期")
|
||||
print(f" 过期时间: {license_obj.expire_time}")
|
||||
print(f" is_expired(): {license_obj.is_expired()}")
|
||||
print(f" 当前状态: {license_obj.status} ({license_obj.get_status_name()})")
|
||||
|
||||
|
||||
def test_manual_check(app, license_obj):
|
||||
"""手动触发过期检查"""
|
||||
with app.app_context():
|
||||
from app.utils.background_tasks import update_expired_licenses
|
||||
|
||||
print("\n🔍 手动触发过期卡密检查...")
|
||||
|
||||
# 检查前状态
|
||||
license_obj_before = License.query.get(license_obj.license_id)
|
||||
print(f" 检查前状态: {license_obj_before.status} ({license_obj_before.get_status_name()})")
|
||||
|
||||
# 执行检查
|
||||
result = update_expired_licenses()
|
||||
|
||||
# 检查后状态
|
||||
license_obj_after = License.query.get(license_obj.license_id)
|
||||
print(f" 检查后状态: {license_obj_after.status} ({license_obj_after.get_status_name()})")
|
||||
|
||||
if result['success']:
|
||||
print(f"✅ 检查执行成功")
|
||||
print(f" 更新数量: {result['updated_count']}")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 检查执行失败: {result['message']}")
|
||||
return False
|
||||
|
||||
|
||||
def test_scheduler_status(app):
|
||||
"""测试定时任务状态"""
|
||||
with app.app_context():
|
||||
from app.utils.scheduler import get_job_status
|
||||
|
||||
print("\n📊 定时任务状态:")
|
||||
status = get_job_status()
|
||||
|
||||
if status['running']:
|
||||
print(f"✅ 调度器运行中")
|
||||
print(f" 任务数量: {len(status['jobs'])}")
|
||||
for job in status['jobs']:
|
||||
print(f" - {job['name']}")
|
||||
print(f" 下次执行: {job['next_run_time']}")
|
||||
else:
|
||||
print(f"⚠️ 调度器未运行")
|
||||
|
||||
return status['running']
|
||||
|
||||
|
||||
def test_batch_check(app):
|
||||
"""测试批量检查功能"""
|
||||
with app.app_context():
|
||||
from app.utils.background_tasks import check_licenses_batch
|
||||
|
||||
print("\n📈 批量检查所有卡密状态:")
|
||||
result = check_licenses_batch()
|
||||
|
||||
if result['success']:
|
||||
stats = result.get('statistics', {})
|
||||
print(f"✅ 批量检查完成")
|
||||
print(f" 已激活但过期: {stats.get('active_but_expired', 0)}")
|
||||
print(f" 已过期且已标记: {stats.get('expired_and_marked', 0)}")
|
||||
print(f" 已激活且有效: {stats.get('active_and_valid', 0)}")
|
||||
print(f" 未激活: {stats.get('inactive', 0)}")
|
||||
print(f" 已禁用: {stats.get('disabled', 0)}")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 批量检查失败: {result['message']}")
|
||||
return False
|
||||
|
||||
|
||||
def cleanup_test_data(app, license_obj):
|
||||
"""清理测试数据"""
|
||||
with app.app_context():
|
||||
# 删除测试卡密
|
||||
db.session.delete(license_obj)
|
||||
db.session.commit()
|
||||
print(f"\n🧹 已清理测试数据")
|
||||
|
||||
|
||||
def main():
|
||||
"""主测试流程"""
|
||||
print("=" * 60)
|
||||
print("卡密过期状态自动更新功能测试")
|
||||
print("=" * 60)
|
||||
|
||||
# 创建应用
|
||||
app = create_app('testing')
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
try:
|
||||
# 测试1: 创建测试卡密
|
||||
print("\n[测试 1] 创建测试卡密")
|
||||
license_obj = create_test_license(app)
|
||||
|
||||
# 测试2: 激活卡密
|
||||
print("\n[测试 2] 激活卡密")
|
||||
if not test_activate_license(license_obj):
|
||||
print("❌ 激活失败,退出测试")
|
||||
return False
|
||||
|
||||
# 测试3: 模拟过期
|
||||
print("\n[测试 3] 模拟卡密过期")
|
||||
test_expire_license(license_obj)
|
||||
|
||||
# 测试4: 验证过期检查前状态
|
||||
print("\n[验证] 过期检查前状态")
|
||||
license_before = License.query.get(license_obj.license_id)
|
||||
print(f" 卡密状态: {license_before.status} ({license_before.get_status_name()})")
|
||||
print(f" is_expired(): {license_before.is_expired()}")
|
||||
|
||||
if license_before.status != 1:
|
||||
print("⚠️ 卡密状态不是已激活,无法继续测试")
|
||||
return False
|
||||
|
||||
# 测试5: 手动触发过期检查
|
||||
print("\n[测试 4] 手动触发过期检查")
|
||||
if not test_manual_check(app, license_obj):
|
||||
print("❌ 手动检查失败")
|
||||
return False
|
||||
|
||||
# 测试6: 验证检查后状态
|
||||
print("\n[验证] 检查后状态")
|
||||
license_after = License.query.get(license_obj.license_id)
|
||||
print(f" 卡密状态: {license_after.status} ({license_after.get_status_name()})")
|
||||
|
||||
if license_after.status == 2:
|
||||
print("✅ 卡密状态已正确更新为已过期")
|
||||
else:
|
||||
print(f"❌ 卡密状态未更新,期望2(已过期),实际{license_after.status}")
|
||||
return False
|
||||
|
||||
# 测试7: 测试批量检查
|
||||
print("\n[测试 5] 测试批量检查功能")
|
||||
test_batch_check(app)
|
||||
|
||||
# 测试8: 测试定时任务状态
|
||||
print("\n[测试 6] 测试定时任务状态")
|
||||
test_scheduler_status(app)
|
||||
|
||||
# 清理测试数据
|
||||
cleanup_test_data(app, license_obj)
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("✅ 所有测试通过!过期卡密自动更新功能正常工作")
|
||||
print("=" * 60)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 测试过程中发生错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
success = main()
|
||||
sys.exit(0 if success else 1)
|
||||
@ -1,82 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Test admin login function
|
||||
Verify password hash is correct
|
||||
"""
|
||||
|
||||
from app import create_app, db
|
||||
from app.models.admin import Admin
|
||||
|
||||
def test_login():
|
||||
"""Test login function"""
|
||||
app = create_app()
|
||||
|
||||
with app.app_context():
|
||||
print("=" * 60)
|
||||
print("KaMiXiTong Login Function Test")
|
||||
print("=" * 60)
|
||||
|
||||
# Find admin user
|
||||
admin = Admin.query.filter_by(username='admin').first()
|
||||
|
||||
if not admin:
|
||||
print("ERROR: Admin user not found")
|
||||
return False
|
||||
|
||||
print(f"\nAdmin user found:")
|
||||
print(f" ID: {admin.admin_id}")
|
||||
print(f" Username: {admin.username}")
|
||||
print(f" Email: {admin.email}")
|
||||
print(f" Role: {'Super Admin' if admin.role == 1 else 'Normal Admin'}")
|
||||
print(f" Status: {'Active' if admin.status == 1 else 'Disabled'}")
|
||||
|
||||
# Test correct password
|
||||
print("\n" + "-" * 60)
|
||||
print("Test 1: Login with correct password 'admin123'")
|
||||
result = admin.check_password('admin123')
|
||||
if result:
|
||||
print("SUCCESS: Login successful! Password verification passed.")
|
||||
else:
|
||||
print("FAILED: Login failed! Password verification failed.")
|
||||
return False
|
||||
|
||||
# Test wrong password
|
||||
print("\n" + "-" * 60)
|
||||
print("Test 2: Login with wrong password 'wrongpassword'")
|
||||
result = admin.check_password('wrongpassword')
|
||||
if not result:
|
||||
print("SUCCESS: Password verification correct. Wrong password rejected.")
|
||||
else:
|
||||
print("FAILED: Security vulnerability! Wrong password accepted.")
|
||||
return False
|
||||
|
||||
# Test empty password
|
||||
print("\n" + "-" * 60)
|
||||
print("Test 3: Login with empty password")
|
||||
result = admin.check_password('')
|
||||
if not result:
|
||||
print("SUCCESS: Password verification correct. Empty password rejected.")
|
||||
else:
|
||||
print("FAILED: Security vulnerability! Empty password accepted.")
|
||||
return False
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("All tests passed! Login function works correctly.")
|
||||
print("=" * 60)
|
||||
print(f"\nLogin information:")
|
||||
print(f" Username: admin")
|
||||
print(f" Password: admin123")
|
||||
print(f" Status: Ready to login")
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
success = test_login()
|
||||
exit(0 if success else 1)
|
||||
except Exception as e:
|
||||
print(f"\nERROR: Exception during test: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
exit(1)
|
||||
@ -1,234 +0,0 @@
|
||||
"""
|
||||
数据模型测试用例
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import tempfile
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
from app import create_app, db
|
||||
from app.models import Admin, Product, License, Device
|
||||
|
||||
class TestModels(unittest.TestCase):
|
||||
"""数据模型测试"""
|
||||
|
||||
def setUp(self):
|
||||
"""测试前准备"""
|
||||
self.app = create_app('testing')
|
||||
self.app_context = self.app.app_context()
|
||||
self.app_context.push()
|
||||
|
||||
# 使用内存数据库
|
||||
self.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
|
||||
db.create_all()
|
||||
|
||||
def tearDown(self):
|
||||
"""测试后清理"""
|
||||
db.session.remove()
|
||||
db.drop_all()
|
||||
self.app_context.pop()
|
||||
|
||||
def test_admin_model(self):
|
||||
"""测试管理员模型"""
|
||||
# 创建管理员
|
||||
admin = Admin(
|
||||
username='testadmin',
|
||||
email='test@example.com',
|
||||
role=1
|
||||
)
|
||||
admin.set_password('testpass123')
|
||||
db.session.add(admin)
|
||||
db.session.commit()
|
||||
|
||||
# 验证密码
|
||||
self.assertTrue(admin.verify_password('testpass123'))
|
||||
self.assertFalse(admin.verify_password('wrongpass'))
|
||||
|
||||
# 验证权限
|
||||
self.assertTrue(admin.is_super_admin())
|
||||
self.assertTrue(admin.is_active())
|
||||
|
||||
def test_product_model(self):
|
||||
"""测试产品模型"""
|
||||
# 创建产品
|
||||
product = Product(
|
||||
product_id='TEST_PRODUCT',
|
||||
product_name='测试产品',
|
||||
description='这是一个测试产品'
|
||||
)
|
||||
db.session.add(product)
|
||||
db.session.commit()
|
||||
|
||||
# 验证产品
|
||||
self.assertEqual(product.product_id, 'TEST_PRODUCT')
|
||||
self.assertEqual(product.product_name, '测试产品')
|
||||
self.assertTrue(product.is_enabled())
|
||||
|
||||
# 测试统计信息
|
||||
stats = product.get_stats()
|
||||
self.assertEqual(stats['total_licenses'], 0)
|
||||
self.assertEqual(stats['active_licenses'], 0)
|
||||
self.assertEqual(stats['total_devices'], 0)
|
||||
|
||||
def test_license_model(self):
|
||||
"""测试卡密模型"""
|
||||
# 先创建产品
|
||||
product = Product(
|
||||
product_id='TEST_PRODUCT',
|
||||
product_name='测试产品'
|
||||
)
|
||||
db.session.add(product)
|
||||
db.session.commit()
|
||||
|
||||
# 创建卡密
|
||||
license_obj = License(
|
||||
product_id='TEST_PRODUCT',
|
||||
type=1,
|
||||
valid_days=30,
|
||||
status=0
|
||||
)
|
||||
db.session.add(license_obj)
|
||||
db.session.commit()
|
||||
|
||||
# 验证卡密
|
||||
self.assertIsNotNone(license_obj.license_key)
|
||||
self.assertEqual(len(license_obj.license_key), 32)
|
||||
self.assertTrue(license_obj.is_formal())
|
||||
self.assertFalse(license_obj.is_trial())
|
||||
self.assertTrue(license_obj.can_activate())
|
||||
self.assertFalse(license_obj.is_expired())
|
||||
|
||||
# 测试激活
|
||||
success, message = license_obj.activate('TEST_MACHINE_CODE', '1.0.0')
|
||||
self.assertTrue(success)
|
||||
self.assertEqual(message, '激活成功')
|
||||
self.assertTrue(license_obj.is_active())
|
||||
|
||||
# 测试验证
|
||||
success, message = license_obj.verify('TEST_MACHINE_CODE', '1.0.0')
|
||||
self.assertTrue(success)
|
||||
self.assertEqual(message, '验证通过')
|
||||
|
||||
# 测试设备不匹配
|
||||
success, message = license_obj.verify('WRONG_MACHINE_CODE', '1.0.0')
|
||||
self.assertFalse(success)
|
||||
self.assertEqual(message, '设备不匹配')
|
||||
|
||||
def test_device_model(self):
|
||||
"""测试设备模型"""
|
||||
# 创建产品
|
||||
product = Product(product_id='TEST_PRODUCT', product_name='测试产品')
|
||||
db.session.add(product)
|
||||
|
||||
# 创建卡密
|
||||
license_obj = License(
|
||||
product_id='TEST_PRODUCT',
|
||||
type=1,
|
||||
valid_days=30
|
||||
)
|
||||
db.session.add(license_obj)
|
||||
db.session.commit()
|
||||
|
||||
# 创建设备
|
||||
device = Device(
|
||||
machine_code='TEST_MACHINE_CODE',
|
||||
license_id=license_obj.license_id,
|
||||
product_id='TEST_PRODUCT',
|
||||
software_version='1.0.0'
|
||||
)
|
||||
db.session.add(device)
|
||||
db.session.commit()
|
||||
|
||||
# 验证设备
|
||||
self.assertTrue(device.is_active())
|
||||
self.assertTrue(device.is_online())
|
||||
|
||||
# 测试使用时间
|
||||
uptime_days = device.get_uptime_days()
|
||||
self.assertGreaterEqual(uptime_days, 0)
|
||||
|
||||
def test_license_batch_generation(self):
|
||||
"""测试批量生成卡密"""
|
||||
# 创建产品
|
||||
product = Product(product_id='TEST_PRODUCT', product_name='测试产品')
|
||||
db.session.add(product)
|
||||
db.session.commit()
|
||||
|
||||
# 批量生成卡密
|
||||
licenses = License.generate_batch(
|
||||
product_id='TEST_PRODUCT',
|
||||
count=5,
|
||||
license_type=1,
|
||||
valid_days=365
|
||||
)
|
||||
|
||||
self.assertEqual(len(licenses), 5)
|
||||
|
||||
# 保存到数据库
|
||||
db.session.add_all(licenses)
|
||||
db.session.commit()
|
||||
|
||||
# 验证生成的卡密
|
||||
for license_obj in licenses:
|
||||
self.assertEqual(license_obj.product_id, 'TEST_PRODUCT')
|
||||
self.assertEqual(license_obj.type, 1)
|
||||
self.assertEqual(license_obj.valid_days, 365)
|
||||
self.assertEqual(license_obj.status, 0)
|
||||
self.assertIsNotNone(license_obj.license_key)
|
||||
self.assertEqual(len(license_obj.license_key), 32)
|
||||
|
||||
def test_license_extension(self):
|
||||
"""测试卡密延期"""
|
||||
# 创建产品和卡密
|
||||
product = Product(product_id='TEST_PRODUCT', product_name='测试产品')
|
||||
db.session.add(product)
|
||||
|
||||
license_obj = License(
|
||||
product_id='TEST_PRODUCT',
|
||||
type=1,
|
||||
valid_days=30,
|
||||
status=1,
|
||||
activate_time=datetime.utcnow()
|
||||
)
|
||||
db.session.add(license_obj)
|
||||
db.session.commit()
|
||||
|
||||
original_expire_time = license_obj.expire_time
|
||||
|
||||
# 延期30天
|
||||
success, message = license_obj.extend_validity(30)
|
||||
self.assertTrue(success)
|
||||
self.assertEqual(message, '延期成功')
|
||||
|
||||
# 验证延期后的时间
|
||||
expected_expire_time = original_expire_time + timedelta(days=30)
|
||||
self.assertEqual(license_obj.expire_time, expected_expire_time)
|
||||
|
||||
def test_trial_conversion(self):
|
||||
"""测试试用转正式"""
|
||||
# 创建产品和试用卡密
|
||||
product = Product(product_id='TEST_PRODUCT', product_name='测试产品')
|
||||
db.session.add(product)
|
||||
|
||||
license_obj = License(
|
||||
product_id='TEST_PRODUCT',
|
||||
type=0, # 试用
|
||||
valid_days=7,
|
||||
status=1,
|
||||
activate_time=datetime.utcnow()
|
||||
)
|
||||
db.session.add(license_obj)
|
||||
db.session.commit()
|
||||
|
||||
# 转为正式
|
||||
success, message = license_obj.convert_to_formal(365)
|
||||
self.assertTrue(success)
|
||||
self.assertEqual(message, '转换成功')
|
||||
|
||||
# 验证转换结果
|
||||
self.assertTrue(license_obj.is_formal())
|
||||
self.assertFalse(license_obj.is_trial())
|
||||
self.assertEqual(license_obj.valid_days, 365)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@ -1,130 +0,0 @@
|
||||
"""
|
||||
验证器测试用例
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import json
|
||||
import tempfile
|
||||
import os
|
||||
from datetime import datetime
|
||||
from auth_validator import AuthValidator, MachineCodeGenerator, AuthCache
|
||||
|
||||
class TestMachineCodeGenerator(unittest.TestCase):
|
||||
"""机器码生成器测试"""
|
||||
|
||||
def test_generate_machine_code(self):
|
||||
"""测试机器码生成"""
|
||||
generator = MachineCodeGenerator()
|
||||
machine_code = generator.generate()
|
||||
|
||||
# 检查长度
|
||||
self.assertEqual(len(machine_code), 32)
|
||||
|
||||
# 检查字符集(应该是十六进制字符)
|
||||
import re
|
||||
pattern = r'^[A-F0-9]+$'
|
||||
self.assertTrue(re.match(pattern, machine_code))
|
||||
|
||||
# 测试多次生成的一致性(在短时间内)
|
||||
machine_code2 = generator.generate()
|
||||
self.assertEqual(machine_code, machine_code2)
|
||||
|
||||
class TestAuthCache(unittest.TestCase):
|
||||
"""授权缓存测试"""
|
||||
|
||||
def setUp(self):
|
||||
"""测试前准备"""
|
||||
self.temp_file = tempfile.NamedTemporaryFile(delete=False)
|
||||
self.temp_file.close()
|
||||
self.cache = AuthCache(self.temp_file.name)
|
||||
|
||||
def tearDown(self):
|
||||
"""测试后清理"""
|
||||
if os.path.exists(self.temp_file.name):
|
||||
os.unlink(self.temp_file.name)
|
||||
|
||||
def test_cache_operations(self):
|
||||
"""测试缓存操作"""
|
||||
software_id = "TEST_SOFTWARE"
|
||||
auth_info = {
|
||||
"license_key": "TEST123456789",
|
||||
"type": 1,
|
||||
"expire_time": "2024-12-31T23:59:59",
|
||||
"machine_code": "ABCDEF1234567890"
|
||||
}
|
||||
|
||||
# 测试保存
|
||||
self.cache.set_auth_info(software_id, auth_info)
|
||||
|
||||
# 测试读取
|
||||
cached_info = self.cache.get_auth_info(software_id)
|
||||
self.assertIsNotNone(cached_info)
|
||||
self.assertEqual(cached_info["license_key"], auth_info["license_key"])
|
||||
|
||||
# 测试清除
|
||||
self.cache.clear_cache(software_id)
|
||||
cached_info = self.cache.get_auth_info(software_id)
|
||||
self.assertIsNone(cached_info)
|
||||
|
||||
class TestAuthValidator(unittest.TestCase):
|
||||
"""验证器测试"""
|
||||
|
||||
def setUp(self):
|
||||
"""测试前准备"""
|
||||
self.software_id = "TEST_SOFTWARE_ID"
|
||||
self.validator = AuthValidator(
|
||||
software_id=self.software_id,
|
||||
api_url="http://127.0.0.1:5000/api/v1", # 使用本地测试URL
|
||||
timeout=1 # 快速超时
|
||||
)
|
||||
|
||||
def test_license_format_validation(self):
|
||||
"""测试卡密格式验证"""
|
||||
# 有效卡密
|
||||
self.assertTrue(self.validator._validate_license_key("ABCD1234567890EFGH"))
|
||||
self.assertTrue(self.validator._validate_license_key("TRIAL_ABCD123456"))
|
||||
|
||||
# 无效卡密
|
||||
self.assertFalse(self.validator._validate_license_key(""))
|
||||
self.assertFalse(self.validator._validate_license_key("short"))
|
||||
self.assertFalse(self.validator._validate_license_key("abcd1234")) # 小写
|
||||
self.assertFalse(self.validator._validate_license_key("ABCD@1234")) # 特殊字符
|
||||
|
||||
def test_software_version_validation(self):
|
||||
"""测试软件版本验证"""
|
||||
# 有效版本
|
||||
self.assertTrue(self.validator._validate_software_version("1.0.0"))
|
||||
self.assertTrue(self.validator._validate_software_version("10.20.30"))
|
||||
|
||||
# 无效版本
|
||||
self.assertFalse(self.validator._validate_software_version("1.0"))
|
||||
self.assertFalse(self.validator._validate_software_version("v1.0.0"))
|
||||
self.assertFalse(self.validator._validate_software_version("1.0.0.0"))
|
||||
|
||||
def test_machine_code_validation(self):
|
||||
"""测试机器码验证"""
|
||||
# 有效机器码
|
||||
self.assertTrue(self.validator._validate_machine_code("ABCDEF1234567890ABCDEF1234567890"))
|
||||
self.assertTrue(self.validator._validate_machine_code("0123456789ABCDEF0123456789ABCDEF"))
|
||||
|
||||
# 无效机器码
|
||||
self.assertFalse(self.validator._validate_machine_code(""))
|
||||
self.assertFalse(self.validator._validate_machine_code("short"))
|
||||
self.assertFalse(self.validator._validate_machine_code("abcdef1234567890abcdef1234567890")) # 小写
|
||||
|
||||
def test_account_locking(self):
|
||||
"""测试账号锁定机制"""
|
||||
# 初始状态不应该被锁定
|
||||
self.assertFalse(self.validator._is_locked())
|
||||
|
||||
# 锁定账号
|
||||
self.validator._lock_account(minutes=1)
|
||||
self.assertTrue(self.validator._is_locked())
|
||||
|
||||
# 获取锁定消息
|
||||
message = self.validator._get_lock_message()
|
||||
self.assertIn("请等待", message)
|
||||
self.assertIn("分钟", message)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Loading…
Reference in New Issue
Block a user