更新系统信息

This commit is contained in:
taiyi 2025-12-27 15:12:05 +08:00
parent b4a0e949ce
commit 701046819b
30 changed files with 257 additions and 1130 deletions

View File

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

View File

@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}账号管理 - 软件授权管理系统{% endblock %}
{% block title %}账号管理 - 太一软件授权管理系统{% endblock %}
{% block page_title %}账号管理{% endblock %}

View File

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

View File

@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}设备管理 - 软件授权管理系统{% endblock %}
{% block title %}设备管理 - 太一软件授权管理系统{% endblock %}
{% block page_title %}设备管理{% endblock %}

View File

@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}导出卡密 - 软件授权管理系统{% endblock %}
{% block title %}导出卡密 - 太一软件授权管理系统{% endblock %}
{% block page_title %}导出卡密{% endblock %}

View File

@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}生成卡密 - 软件授权管理系统{% endblock %}
{% block title %}生成卡密 - 太一软件授权管理系统{% endblock %}
{% block page_title %}生成卡密{% endblock %}

View File

@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}导入卡密 - 软件授权管理系统{% endblock %}
{% block title %}导入卡密 - 太一软件授权管理系统{% endblock %}
{% block page_title %}导入卡密{% endblock %}

View File

@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}卡密管理 - 软件授权管理系统{% endblock %}
{% block title %}卡密管理 - 太一软件授权管理系统{% endblock %}
{% block page_title %}卡密管理{% endblock %}

View File

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

View File

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

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

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

View File

@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}支付配置 - 软件授权管理系统{% endblock %}
{% block title %}支付配置 - 太一软件授权管理系统{% endblock %}
{% block page_title %}支付配置{% endblock %}

View File

@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}创建产品 - 软件授权管理系统{% endblock %}
{% block title %}创建产品 - 太一软件授权管理系统{% endblock %}
{% block page_title %}创建产品{% endblock %}

View File

@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}产品详情 - 软件授权管理系统{% endblock %}
{% block title %}产品详情 - 太一软件授权管理系统{% endblock %}
{% block page_title %}产品详情{% endblock %}

View File

@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}编辑产品 - 软件授权管理系统{% endblock %}
{% block title %}编辑产品 - 太一软件授权管理系统{% endblock %}
{% block page_title %}编辑产品{% endblock %}

View File

@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}产品管理 - 软件授权管理系统{% endblock %}
{% block title %}产品管理 - 太一软件授权管理系统{% endblock %}
{% block page_title %}产品管理{% endblock %}

View File

@ -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 %}

View File

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

View File

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

View File

@ -194,10 +194,10 @@
document.addEventListener('DOMContentLoaded', function() {
console.log('卡密购买页面加载完成');
// 初始化产品列表
loadProducts();
// 支付方式选择
document.querySelectorAll('.payment-method').forEach(method => {
method.addEventListener('click', function() {
@ -205,7 +205,7 @@
this.classList.add('selected');
});
});
// 支付按钮事件
document.getElementById('payBtn').addEventListener('click', function() {
processPayment();
@ -424,15 +424,15 @@
showNotification('请选择产品和套餐', 'warning');
return;
}
const paymentMethod = document.querySelector('.payment-method.selected').dataset.method;
// 显示加载状态
const payBtn = document.getElementById('payBtn');
const originalText = payBtn.innerHTML;
payBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status"></span>处理中...';
payBtn.disabled = true;
// 调用API创建订单
const orderData = {
product_id: selectedProduct.product_id,
@ -440,7 +440,7 @@
quantity: selectedQuantity,
payment_method: paymentMethod
};
apiRequest('/user/orders', {
method: 'POST',
body: JSON.stringify(orderData)

View File

@ -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):
"""重写轮转方法,添加错误处理"""
try:
super().doRollover()
except PermissionError as e:
# Windows文件锁定错误静默处理
import sys
print(f"日志轮转被跳过(文件被占用): {str(e)}", file=sys.stderr)
except Exception as e:
import sys
print(f"日志轮转错误: {str(e)}", file=sys.stderr)
"""重写轮转方法,添加重试机制和错误处理"""
import time
# 尝试多次轮转
max_retries = 3
for attempt in range(max_retries):
try:
super().doRollover()
break # 成功则退出循环
except (OSError, PermissionError) as e:
if attempt < max_retries - 1:
# 等待一段时间后重试
time.sleep(0.5 * (attempt + 1))
continue
else:
# 最后一次尝试失败,静默处理
import sys
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(

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

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

View File

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

View File

@ -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()

View File

@ -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()