增了软件首页授权功能
This commit is contained in:
parent
c4481e157d
commit
c3f9693ee9
File diff suppressed because it is too large
Load Diff
118
backend/app.spec
Normal file
118
backend/app.spec
Normal 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'
|
||||
)
|
||||
@ -19,6 +19,16 @@ import base64
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 授权签名与验证参数(跨语言一致性):
|
||||
# - 算法:RSA-PSS + SHA-256
|
||||
# - MGF:MGF1(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 + SHA256,MGF1(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()
|
||||
)
|
||||
|
||||
@ -50,5 +50,4 @@ pyyaml
|
||||
click
|
||||
colorama
|
||||
tqdm
|
||||
bleak
|
||||
cryptography
|
||||
@ -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
|
||||
41
config.ini
41
config.ini
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user