增了软件首页授权功能
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__)
|
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
|
@dataclass
|
||||||
class LicenseStatus:
|
class LicenseStatus:
|
||||||
@ -160,7 +170,11 @@ class LicenseManager:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def verify_signature(self, license_data: Dict[str, Any], public_key_path: str) -> bool:
|
def verify_signature(self, license_data: Dict[str, Any], public_key_path: str) -> bool:
|
||||||
"""验证授权文件数字签名"""
|
"""验证授权文件数字签名
|
||||||
|
|
||||||
|
规则:RSA-PSS + SHA256,MGF1(SHA256),盐长度固定为 32 字节;
|
||||||
|
待签名数据为去除 signature 后的紧凑、排序键 JSON。
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
if not os.path.exists(public_key_path):
|
if not os.path.exists(public_key_path):
|
||||||
logger.error(f"公钥文件不存在: {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=(',', ':'))
|
sorted_data = json.dumps(license_copy, sort_keys=True, separators=(',', ':'))
|
||||||
message = sorted_data.encode('utf-8')
|
message = sorted_data.encode('utf-8')
|
||||||
|
|
||||||
# 验证签名
|
# 验证签名(固定 PSS 盐长度为 32 字节,确保跨语言一致)
|
||||||
try:
|
try:
|
||||||
public_key.verify(
|
public_key.verify(
|
||||||
signature,
|
signature,
|
||||||
message,
|
message,
|
||||||
padding.PSS(
|
padding.PSS(
|
||||||
mgf=padding.MGF1(hashes.SHA256()),
|
mgf=padding.MGF1(hashes.SHA256()),
|
||||||
salt_length=padding.PSS.MAX_LENGTH
|
salt_length=PSS_SALT_LENGTH_BYTES
|
||||||
),
|
),
|
||||||
hashes.SHA256()
|
hashes.SHA256()
|
||||||
)
|
)
|
||||||
|
|||||||
@ -50,5 +50,4 @@ pyyaml
|
|||||||
click
|
click
|
||||||
colorama
|
colorama
|
||||||
tqdm
|
tqdm
|
||||||
bleak
|
|
||||||
cryptography
|
cryptography
|
||||||
@ -22,3 +22,4 @@ pykinect_azure # Azure Kinect SDK for Python
|
|||||||
# System utilities
|
# System utilities
|
||||||
colorama==0.4.6
|
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">
|
<div class="system-logo">
|
||||||
<img src="@/assets/svg/u7.svg" alt="Logo" class="logo" />
|
<img src="@/assets/svg/u7.svg" alt="Logo" class="logo" />
|
||||||
</div>
|
</div>
|
||||||
<div class="system-title">平衡体态检测系统</div>
|
<div class="system-title">
|
||||||
|
平衡体态检测系统
|
||||||
|
<span v-if="licenseBadge" :class="['license-badge', licenseClass]">{{ licenseBadge }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div style="color:#fff;margin-right: 20px;">登录时间:{{ time }} </div>
|
<div style="color:#fff;margin-right: 20px;">登录时间:{{ time }} </div>
|
||||||
@ -59,7 +62,7 @@
|
|||||||
import { useRouter, viewDepthKey } from 'vue-router'
|
import { useRouter, viewDepthKey } from 'vue-router'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { useAuthStore } from '../stores/index.js'
|
import { useAuthStore } from '../stores/index.js'
|
||||||
|
import api from '../services/api.js'
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const time = ref("");
|
const time = ref("");
|
||||||
@ -67,6 +70,8 @@
|
|||||||
username: '医生',
|
username: '医生',
|
||||||
avatar: ''
|
avatar: ''
|
||||||
})
|
})
|
||||||
|
const licenseBadge = ref('')
|
||||||
|
const licenseClass = ref('')
|
||||||
|
|
||||||
|
|
||||||
const handleUserCommand = (command) => {
|
const handleUserCommand = (command) => {
|
||||||
@ -135,6 +140,39 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
time.value = dateFormat(new Date())
|
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>
|
</script>
|
||||||
|
|
||||||
@ -185,6 +223,9 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
color: rgb(255, 255, 255);
|
color: rgb(255, 255, 255);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-right {
|
.header-right {
|
||||||
@ -210,6 +251,16 @@
|
|||||||
|
|
||||||
</style>
|
</style>
|
||||||
<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{
|
.userInfoviewDialog{
|
||||||
background-color: #323232 !important;
|
background-color: #323232 !important;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user