增了软件首页授权功能

This commit is contained in:
root 2025-10-30 14:02:39 +08:00
parent c4481e157d
commit c3f9693ee9
7 changed files with 190 additions and 591747 deletions

File diff suppressed because it is too large Load Diff

118
backend/app.spec Normal file
View File

@ -0,0 +1,118 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['main.py'],
pathex=['D:/Trae_space/pyKinectAzure'],
binaries=[
# FemtoBolt相关库文件
('dll/femtobolt/k4a.dll', 'dll/femtobolt'), # K4A动态库
('dll/femtobolt/k4arecord.dll', 'dll/femtobolt'), # K4A录制库
('dll/femtobolt/depthengine_2_0.dll', 'dll/femtobolt'), # 深度引擎
('dll/femtobolt/OrbbecSDK.dll', 'dll/femtobolt'), # Orbbec SDK
('dll/femtobolt/k4a.lib', 'dll/femtobolt'), # K4A静态库
('dll/femtobolt/k4arecord.lib', 'dll/femtobolt'), # K4A录制静态库
('dll/femtobolt/k4arecorder.exe', 'dll/femtobolt'), # K4A录制工具
('dll/femtobolt/k4aviewer.exe', 'dll/femtobolt'), # K4A查看器
# SMiTSense相关库文件
('dll/smitsense/SMiTSenseUsb-F3.0.dll', 'dll/smitsense'), # SMiTSense传感器库
('dll/smitsense/Wrapper.dll', 'dll/smitsense'), # SMiTSense传感器库包装类
],
hiddenimports=[
'flask',
'flask_socketio',
'flask_cors',
'cv2',
'numpy',
'pandas',
'scipy',
'matplotlib',
'seaborn',
'sklearn',
'PIL',
'reportlab',
'sqlite3',
'configparser',
'logging',
'threading',
'queue',
'base64',
'psutil',
'pykinect_azure',
'pykinect_azure.k4a',
'pykinect_azure.k4abt',
'pykinect_azure.k4arecord',
'pykinect_azure.pykinect',
'pykinect_azure.utils',
'pykinect_azure._k4a',
'pykinect_azure._k4abt',
'pyserial',
'requests',
'yaml',
'click',
'colorama',
'tqdm',
'database',
'device_manager',
'utils',
'eventlet',
'socketio',
'engineio',
'engineio.async_drivers.threading',
'engineio.async_drivers.eventlet',
'engineio.async_eventlet',
'socketio.async_eventlet',
'greenlet',
'gevent',
'gevent.socket',
'gevent.select',
'dns',
'dns.resolver',
'dns.reversename',
'dns.e164',
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='BodyBalanceBackend',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=None
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='BodyBalanceBackend'
)

View File

@ -19,6 +19,16 @@ import base64
logger = logging.getLogger(__name__)
# 授权签名与验证参数(跨语言一致性):
# - 算法RSA-PSS + SHA-256
# - MGFMGF1(SHA-256)
# - 盐长度:固定 32 字节
# - 数据序列化(签名输入):
# * 去除 signature 字段
# * 按键名排序(包括嵌套对象)
# * 紧凑 JSON等价于 json.dumps(obj, sort_keys=True, separators=(",", ":"))
PSS_SALT_LENGTH_BYTES = 32
@dataclass
class LicenseStatus:
@ -160,7 +170,11 @@ class LicenseManager:
return None
def verify_signature(self, license_data: Dict[str, Any], public_key_path: str) -> bool:
"""验证授权文件数字签名"""
"""验证授权文件数字签名
规则RSA-PSS + SHA256MGF1(SHA256)盐长度固定为 32 字节
待签名数据为去除 signature 后的紧凑排序键 JSON
"""
try:
if not os.path.exists(public_key_path):
logger.error(f"公钥文件不存在: {public_key_path}")
@ -190,14 +204,14 @@ class LicenseManager:
sorted_data = json.dumps(license_copy, sort_keys=True, separators=(',', ':'))
message = sorted_data.encode('utf-8')
# 验证签名
# 验证签名(固定 PSS 盐长度为 32 字节,确保跨语言一致)
try:
public_key.verify(
signature,
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
salt_length=PSS_SALT_LENGTH_BYTES
),
hashes.SHA256()
)

View File

@ -50,5 +50,4 @@ pyyaml
click
colorama
tqdm
bleak
cryptography

View File

@ -21,4 +21,5 @@ pykinect_azure # Azure Kinect SDK for Python
# System utilities
colorama==0.4.6
click==8.1.7
click==8.1.7
cryptography>=41.0.0

View File

@ -1,41 +0,0 @@
[APP]
name = Body Balance Evaluation System
version = 1.0.0
debug = false
log_level = INFO
[SERVER]
host = 0.0.0.0
port = 5000
cors_origins = *
[DATABASE]
path = backend/data/body_balance.db
backup_interval = 24
max_backups = 7
[DEVICES]
camera_index = 0
camera_width = 640
camera_height = 480
camera_fps = 30
imu_port = COM3
pressure_port = COM4
[DETECTION]
default_duration = 60
sampling_rate = 30
balance_threshold = 0.2
posture_threshold = 5.0
[DATA_PROCESSING]
filter_window = 5
outlier_threshold = 2.0
chart_dpi = 300
export_format = csv
[SECURITY]
secret_key = 74d2f5ad774b449e6958cc5d30d77411c3560c9d0279e48154a847b744688989
session_timeout = 3600
max_login_attempts = 5

View File

@ -6,7 +6,10 @@
<div class="system-logo">
<img src="@/assets/svg/u7.svg" alt="Logo" class="logo" />
</div>
<div class="system-title">平衡体态检测系统</div>
<div class="system-title">
平衡体态检测系统
<span v-if="licenseBadge" :class="['license-badge', licenseClass]">{{ licenseBadge }}</span>
</div>
</div>
<div class="header-right">
<div style="color:#fff;margin-right: 20px;">登录时间{{ time }} </div>
@ -59,7 +62,7 @@
import { useRouter, viewDepthKey } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useAuthStore } from '../stores/index.js'
import api from '../services/api.js'
const router = useRouter()
const authStore = useAuthStore()
const time = ref("");
@ -67,6 +70,8 @@
username: '医生',
avatar: ''
})
const licenseBadge = ref('')
const licenseClass = ref('')
const handleUserCommand = (command) => {
@ -135,6 +140,39 @@
})
}
time.value = dateFormat(new Date())
// /api/license/info
;(async () => {
try {
debugger
const json = await api.get('/api/license/info')
debugger
if (!json || json.success !== true) {
licenseBadge.value = '未授权'
licenseClass.value = 'badge-invalid'
return
}
const data = json.data || {}
if (data.valid === true) {
if ((data.license_type || '').toLowerCase() === 'trial') {
const exp = data.expires_at ? new Date(data.expires_at) : null
const expStr = exp ? `${exp.getFullYear()}-${String(exp.getMonth()+1).padStart(2,'0')}-${String(exp.getDate()).padStart(2,'0')}` : ''
licenseBadge.value = expStr ? `试用版(截止 ${expStr}` : '试用版'
licenseClass.value = 'badge-trial'
} else {
licenseBadge.value = '已授权'
licenseClass.value = 'badge-valid'
}
} else {
licenseBadge.value = '未授权'
licenseClass.value = 'badge-invalid'
}
} catch (e) {
//
licenseBadge.value = '未授权'
licenseClass.value = 'badge-invalid'
}
})()
})
</script>
@ -185,6 +223,9 @@
font-style: normal;
font-size: 22px;
color: rgb(255, 255, 255);
display: flex;
align-items: center;
gap: 8px;
}
.header-right {
@ -210,6 +251,16 @@
</style>
<style>
.license-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 10px;
font-size: 12px;
margin-left: 8px;
}
.badge-invalid { background-color: #8b0000; color: #fff; }
.badge-trial { background-color: #ff8c00; color: #fff; }
.badge-valid { background-color: #2e8b57; color: #fff; }
.userInfoviewDialog{
background-color: #323232 !important;
}