feat: 完成v10.1.0版本大更新,新增密保系统、勋章、任务、排行榜等功能

### 变更详情
1. 新增密保问题系统,支持8种预置验证问题,多场景支持多验证方式
2. 新增勋章管理模块,包含勋章配置、用户勋章关联管理
3. 新增每日任务系统,支持任务配置和用户进度追踪
4. 新增赛季排行榜功能,支持周/月赛季排行与奖励结算
5. 新增信息流推荐权重配置管理
6. 重构服务路径分层,按设备/网络/数据分类管理服务
7. 优化Feed请求参数截断逻辑,避免URL过长
8. 新增等级工具类,统一处理等级颜色与称号展示
9. 新增屏幕共享共享信令Provider,复用传输服务实例
10. 新增Android/iOS分享适配与桌面小组件支持
11. 清理旧版测试脚本,新增部署维护脚本
12. 完善用户注销关联数据清理逻辑
This commit is contained in:
Developer
2026-05-15 07:02:56 +08:00
parent 228095f80a
commit fc6fd7be0e
264 changed files with 24783 additions and 6267 deletions

View File

@@ -0,0 +1,25 @@
"""Check database tables on server"""
import paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('123.207.67.197', username='root', password='520Kiss123')
def run_sql(sql):
cmd = f"mysql -u tools -p'tools' --default-character-set=utf8mb4 tools -e \"{sql}\""
stdin, stdout, stderr = ssh.exec_command(cmd)
out = stdout.read().decode().strip()
err = stderr.read().decode().strip()
if out: print(out)
if err: print(f'ERR: {err[:300]}')
print('=== SHOW TABLES LIKE "fa_%" ===')
run_sql("SHOW TABLES LIKE 'fa_%'")
print('\n=== DESCRIBE tool_feed_weight_config ===')
run_sql("DESCRIBE tool_feed_weight_config")
print('\n=== SHOW TABLES LIKE "tool_%" ===')
run_sql("SHOW TABLES LIKE 'tool_%'")
ssh.close()

View File

@@ -1,39 +0,0 @@
import paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('123.207.67.197', 22, 'root', '520Kiss123')
print('=== Check if table exists ===')
stdin, stdout, stderr = ssh.exec_command(
'cd /www/wwwroot/tools.wktyl.com && php -r "'
"require 'vendor/autoload.php';"
"\\$config = include 'application/database.php';"
"\\$pdo = new PDO('mysql:host='.\\$config['hostname'].';dbname='.\\$config['database'], \\$config['username'], \\$config['password']);"
"\\$stmt = \\$pdo->query('SHOW TABLES LIKE \\\"tool_cloud_cache_record\\\"');"
"\\$result = \\$stmt->fetchAll();"
"echo count(\\$result) > 0 ? 'TABLE EXISTS' : 'TABLE NOT FOUND';"
'"'
)
print(stdout.read().decode())
print(stderr.read().decode())
print('=== Try creating table directly ===')
stdin, stdout, stderr = ssh.exec_command(
'cd /www/wwwroot/tools.wktyl.com && php -r "'
"require 'vendor/autoload.php';"
"\\$config = include 'application/database.php';"
"\\$pdo = new PDO('mysql:host='.\\$config['hostname'].';dbname='.\\$config['database'], \\$config['username'], \\$config['password']);"
"\\$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);"
"try {"
" \\$pdo->exec('CREATE TABLE IF NOT EXISTS tool_cloud_cache_record (id int(11) unsigned NOT NULL AUTO_INCREMENT, cache_id varchar(64) NOT NULL, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4');"
" echo 'TABLE CREATED OK';"
"} catch (PDOException \\$e) {"
" echo 'ERROR: ' . \\$e->getMessage();"
"}"
'"'
)
print(stdout.read().decode())
print(stderr.read().decode())
ssh.close()

View File

@@ -1,25 +0,0 @@
import paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('123.207.67.197', 22, 'root', '520Kiss123')
print('=== Read .env file ===')
stdin, stdout, stderr = ssh.exec_command('cat /www/wwwroot/tools.wktyl.com/.env 2>/dev/null | grep -i database || echo "no .env found"')
print(stdout.read().decode())
print('=== Try install via API with more detail ===')
stdin, stdout, stderr = ssh.exec_command('curl -s -X POST "https://tools.wktyl.com/api/cloud_cache/install" 2>&1')
print(stdout.read().decode())
print('=== Check PHP error in runtime ===')
stdin, stdout, stderr = ssh.exec_command('find /www/wwwroot/tools.wktyl.com/runtime -name "*.log" -newer /www/wwwroot/tools.wktyl.com/application/api/controller/CloudCache.php -exec tail -5 {} \\; 2>/dev/null || echo "no recent logs"')
print(stdout.read().decode())
print('=== Try direct SQL via ThinkPHP CLI ===')
stdin, stdout, stderr = ssh.exec_command(
'cd /www/wwwroot/tools.wktyl.com && php think version 2>&1; echo "---"; php think 2>&1 | head -5'
)
print(stdout.read().decode())
ssh.close()

View File

@@ -1,138 +0,0 @@
import paramiko
import os
import sys
import time
SSH_HOST = '123.207.67.197'
SSH_PORT = 22
SSH_USER = 'root'
SSH_PASS = '520Kiss123'
REMOTE_BASE = '/www/wwwroot/tools.wktyl.com'
LOCAL_BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
LOCAL_CONTROLLER = os.path.join(LOCAL_BASE, 'application', 'api', 'controller', 'Fortune.php')
LOCAL_ROUTE = os.path.join(LOCAL_BASE, 'application', 'route.php')
REMOTE_CONTROLLER = f'{REMOTE_BASE}/application/api/controller/Fortune.php'
REMOTE_ROUTE = f'{REMOTE_BASE}/application/route.php'
def ssh_connect():
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(SSH_HOST, SSH_PORT, SSH_USER, SSH_PASS)
return ssh
def upload_file(sftp, local_path, remote_path):
if not os.path.exists(local_path):
print(f'[ERROR] Local file not found: {local_path}')
return False
remote_dir = os.path.dirname(remote_path)
try:
sftp.stat(remote_dir)
except FileNotFoundError:
print(f'[INFO] Creating remote directory: {remote_dir}')
parts = remote_dir.split('/')
current = ''
for part in parts:
if not part:
continue
current += '/' + part
try:
sftp.stat(current)
except FileNotFoundError:
sftp.mkdir(current)
print(f'[UPLOAD] {os.path.basename(local_path)} -> {remote_path}')
sftp.put(local_path, remote_path)
print(f'[OK] Uploaded {os.path.basename(local_path)}')
return True
def run_cmd(ssh, cmd, label=''):
if label:
print(f'\n=== {label} ===')
stdin, stdout, stderr = ssh.exec_command(cmd)
out = stdout.read().decode('utf-8', errors='replace').strip()
err = stderr.read().decode('utf-8', errors='replace').strip()
if out:
print(out)
if err:
print(f'[STDERR] {err}')
return out, err
def test_api(ssh, endpoint, label=''):
url = f'https://tools.wktyl.com/api/fortune/{endpoint}'
if label:
print(f'\n--- Test: {label} ---')
cmd = f'curl -s -k -m 10 "{url}"'
out, err = run_cmd(ssh, cmd, label)
return out
def main():
print('=' * 60)
print(' 每日运势 Fortune API - 部署脚本')
print(f' 时间: {time.strftime("%Y-%m-%d %H:%M:%S")}')
print('=' * 60)
ssh = ssh_connect()
sftp = ssh.open_sftp()
try:
print('\n[STEP 1] 上传 Fortune.php 控制器')
upload_file(sftp, LOCAL_CONTROLLER, REMOTE_CONTROLLER)
print('\n[STEP 2] 上传 route.php (含运势路由)')
upload_file(sftp, LOCAL_ROUTE, REMOTE_ROUTE)
print('\n[STEP 3] 验证文件已上传')
run_cmd(ssh, f'ls -la {REMOTE_CONTROLLER}', '验证 Fortune.php')
run_cmd(ssh, f'head -5 {REMOTE_CONTROLLER}', 'Fortune.php 前5行')
print('\n[STEP 4] 执行数据库安装 (install)')
test_api(ssh, 'install', '数据库安装')
print('\n[STEP 5] 测试运势生成 (daily)')
test_api(ssh, 'daily?uid=test_user_001', '今日运势')
print('\n[STEP 6] 测试换签 (daily?regen=1)')
test_api(ssh, 'daily?uid=test_user_001&regen=1', '换一签')
print('\n[STEP 7] 测试历史运势 (history)')
test_api(ssh, 'history?uid=test_user_001', '历史运势')
print('\n[STEP 8] 测试用户配置 (config)')
test_api(ssh, 'config?uid=test_user_001', '用户配置')
print('\n[STEP 9] 测试卡片风格 (themes)')
test_api(ssh, 'themes', '卡片风格')
print('\n[STEP 10] 测试60秒新闻 (sixtySeconds)')
test_api(ssh, 'sixtySeconds', '60秒新闻')
print('\n[STEP 11] 测试黄历 (huangli)')
test_api(ssh, 'huangli', '黄历')
print('\n[STEP 12] 测试星座运势 (horoscope)')
test_api(ssh, 'horoscope?sign=白羊', '星座运势')
print('\n[STEP 13] 测试指定日期 (date)')
test_api(ssh, 'date?uid=test_user_001&date=2026-05-12', '指定日期运势')
print('\n[STEP 14] 测试图片生成 (image)')
test_api(ssh, 'image?uid=test_user_001', '运势图片')
print('\n[STEP 15] 全量第三方API测试 (test)')
test_api(ssh, 'test', '第三方API测试')
print('\n' + '=' * 60)
print(' 部署完成!')
print('=' * 60)
except Exception as e:
print(f'\n[FATAL ERROR] {e}')
import traceback
traceback.print_exc()
finally:
sftp.close()
ssh.close()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,24 @@
"""Fix remaining SQL - feed_weight data + admin menus"""
import paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('123.207.67.197', username='root', password='520Kiss123')
def run_sql(sql, name=''):
cmd = f"mysql -u tools -p'tools' --default-character-set=utf8mb4 tools -e \"{sql}\""
stdin, stdout, stderr = ssh.exec_command(cmd)
err = stderr.read().decode().strip()
exit_code = stdout.channel.recv_exit_status()
status = 'OK' if exit_code == 0 else 'FAIL'
print(f'[{status}] {name}' + (f' - {err[:200]}' if err else ''))
return exit_code == 0
run_sql("INSERT IGNORE INTO tool_feed_weight_config (feed_type,feed_name,feed_icon,weight,display_weight,push_limit,is_enabled,update_time) VALUES ('poetry','古诗词','📜',60,48,0,1,UNIX_TIMESTAMP()),('wisdom','名言金句','💡',55,44,0,1,UNIX_TIMESTAMP()),('story','故事','📚',50,40,0,1,UNIX_TIMESTAMP()),('hitokoto','一言','💬',70,56,0,1,UNIX_TIMESTAMP()),('riddle','谜语','🧩',40,32,0,1,UNIX_TIMESTAMP()),('efs','歇后语','🎭',35,28,0,1,UNIX_TIMESTAMP()),('brainteaser','脑筋急转弯','🧠',40,32,0,1,UNIX_TIMESTAMP()),('saying','俗语','🗣️',30,24,0,1,UNIX_TIMESTAMP()),('lyric','歌词','🎵',55,44,0,1,UNIX_TIMESTAMP()),('why','十万个为什么','',35,28,0,1,UNIX_TIMESTAMP()),('composition','作文','📝',30,24,0,1,UNIX_TIMESTAMP()),('couplet','对联','🧧',25,20,0,1,UNIX_TIMESTAMP()),('cs','常识','📖',30,24,0,1,UNIX_TIMESTAMP()),('drug','中药','🌿',15,12,0,1,UNIX_TIMESTAMP()),('herbal','草药','🌱',15,12,0,1,UNIX_TIMESTAMP()),('food','美食','🍜',20,16,0,1,UNIX_TIMESTAMP()),('wine','美酒','🍷',10,8,0,1,UNIX_TIMESTAMP()),('article','文章','📰',60,48,0,1,UNIX_TIMESTAMP())", 'feed_weight data')
run_sql("DESCRIBE tool_auth_rule", 'check auth_rule schema')
run_sql("INSERT IGNORE INTO tool_auth_rule (id,type,pid,name,title,icon,ismenu,weigh,status) VALUES (200,'file',0,'feed_weight','推荐权重','fa fa-balance-scale',1,0,'normal'),(201,'file',200,'feed_weight/index','查看','',0,0,'normal'),(202,'file',200,'feed_weight/edit','编辑','',0,0,'normal'),(203,'file',200,'feed_weight/reset_push','重置推送','',0,0,'normal'),(204,'file',200,'feed_weight/reset_defaults','恢复默认','',0,0,'normal'),(210,'file',0,'daily_task','每日任务','fa fa-tasks',1,0,'normal'),(211,'file',210,'daily_task/index','查看','',0,0,'normal'),(212,'file',210,'daily_task/add','添加','',0,0,'normal'),(213,'file',210,'daily_task/edit','编辑','',0,0,'normal'),(214,'file',210,'daily_task/del','删除','',0,0,'normal'),(220,'file',0,'badge','勋章管理','fa fa-shield',1,0,'normal'),(221,'file',220,'badge/index','查看','',0,0,'normal'),(222,'file',220,'badge/add','添加','',0,0,'normal'),(223,'file',220,'badge/edit','编辑','',0,0,'normal'),(224,'file',220,'badge/del','删除','',0,0,'normal'),(230,'file',0,'user/user_badge','用户勋章','fa fa-id-badge',1,0,'normal'),(231,'file',230,'user/user_badge/index','查看','',0,0,'normal'),(240,'file',0,'user/user_task','用户任务','fa fa-list-check',1,0,'normal'),(241,'file',240,'user/user_task/index','查看','',0,0,'normal')", 'admin menus')
ssh.close()
print('\nAll done!')

View File

@@ -1,18 +0,0 @@
import paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('123.207.67.197', 22, 'root', '520Kiss123')
tests = [
('news60s', 'curl -s -k "https://tools.wktyl.com/api/fortune/news60s"'),
('sixtySeconds', 'curl -s -k "https://tools.wktyl.com/api/fortune/sixtySeconds"'),
('daily', 'curl -s -k "https://tools.wktyl.com/api/fortune/daily?uid=test"'),
]
for name, cmd in tests:
print(f'\n=== Test {name} ===')
stdin, stdout, stderr = ssh.exec_command(cmd)
out = stdout.read().decode('utf-8', errors='replace').strip()
print(out[:300])
ssh.close()

View File

@@ -0,0 +1,199 @@
import requests
import json
import base64
import hashlib
import hmac
import time
import os
import sys
BASE = 'https://tools.wktyl.com'
SECRET = 'Xy7kP9mL2qR4wS8v'
TEST_USERNAME = f'task_test_{int(time.time())}'
TEST_PASSWORD = '123456'
TEST_EMAIL = f'task_test_{int(time.time())}@test.com'
PASSED = 0
FAILED = 0
token = None
user_id = None
def make_receipt(action, payload_str):
data = {
'action': action,
'payload': hashlib.sha256(payload_str.encode()).hexdigest()[:16],
'ts': int(time.time()),
'nonce': os.urandom(4).hex()
}
receipt = base64.b64encode(json.dumps(data, ensure_ascii=False).encode()).decode()
sig = hmac.new(SECRET.encode(), receipt.encode(), hashlib.sha256).hexdigest()
return {'receipt': receipt, 'sig': sig}
def test(name, response, expected_code=1, check_func=None):
global PASSED, FAILED
try:
data = response.json()
except Exception as e:
print(f'{name}: Response parse error: {e}')
FAILED += 1
return None
if data.get('code') == expected_code:
if check_func and not check_func(data):
print(f'{name}: Check failed. Response: {json.dumps(data, ensure_ascii=False)[:200]}')
FAILED += 1
return data
print(f'{name}')
PASSED += 1
else:
print(f'{name}: Expected code={expected_code}, got code={data.get("code")}, msg={data.get("msg")}')
FAILED += 1
return data
print('=' * 60)
print('每日任务系统全流程测试')
print(f'测试账号: {TEST_USERNAME}')
print(f'测试时间: {time.strftime("%Y-%m-%d %H:%M:%S")}')
print('=' * 60)
# ========== 1. 注册 ==========
print('\n--- 1. 注册测试用户 ---')
receipt_data = make_receipt('register', TEST_EMAIL)
r = requests.post(f'{BASE}/api/user_security/register', data={
'username': TEST_USERNAME,
'password': TEST_PASSWORD,
'email': TEST_EMAIL,
'receipt': receipt_data['receipt'],
'sig': receipt_data['sig'],
})
data = test('register', r)
if data and data.get('code') == 1:
token = data['data'].get('token', '')
user_id = data['data'].get('userinfo', {}).get('id', '')
print(f' 用户ID: {user_id}')
if not token:
print('❌ 注册失败,终止测试')
sys.exit(1)
headers = {'token': token}
# ========== 2. 登录获取token ==========
print('\n--- 2. 登录获取token ---')
receipt_data = make_receipt('login', TEST_USERNAME)
r = requests.post(f'{BASE}/api/user_security/login', data={
'account': TEST_USERNAME,
'password': TEST_PASSWORD,
'receipt': receipt_data['receipt'],
'sig': receipt_data['sig'],
})
data = test('login', r)
if data and data.get('code') == 1:
token = data['data'].get('token', '')
user_id = data['data'].get('userinfo', {}).get('id', '')
headers = {'token': token}
print(f' Token: {token[:20]}...')
# ========== 3. 获取今日任务列表 ==========
print('\n--- 3. 获取今日任务列表 ---')
r = requests.get(f'{BASE}/api/task/today', headers=headers)
data = test('today tasks exist', r, check_func=lambda d: len(d.get('data', {}).get('tasks', [])) > 0)
if data and data.get('code') == 1:
tasks = data['data']['tasks']
print(f' 今日任务数: {len(tasks)}')
for t in tasks:
print(f' {t.get("icon", "📋")} {t.get("name")} | id={t.get("id")} progress={t.get("progress", 0)}/{t.get("target", 1)} claimed={t.get("claimed", False)}')
# ========== 4. 上报签到任务进度 ==========
print('\n--- 4. 上报签到任务进度 ---')
signin_task = None
if data and data.get('code') == 1:
for t in tasks:
if '签到' in t.get('name', '') or t.get('id') == 1:
signin_task = t
break
task_id_to_report = signin_task.get('id', 1) if signin_task else 1
task_name = signin_task.get('name', '签到') if signin_task else '签到'
r = requests.post(f'{BASE}/api/task/reportProgress', headers=headers, data={
'task_id': task_id_to_report,
'increment': 1,
})
data = test(f'reportProgress (task={task_name}, id={task_id_to_report})', r)
if data and data.get('code') == 1:
progress = data.get('data', {})
print(f' 上报结果: progress={progress.get("progress")}, target={progress.get("target")}, completed={progress.get("completed")}')
# ========== 5. 再次获取任务列表 - 验证进度更新 ==========
print('\n--- 5. 验证进度已更新 ---')
r = requests.get(f'{BASE}/api/task/today', headers=headers)
data = test('progress updated', r, check_func=lambda d: any(
t.get('id') == task_id_to_report and t.get('progress', 0) > 0
for t in d.get('data', {}).get('tasks', [])
))
if data and data.get('code') == 1:
for t in data['data']['tasks']:
if t.get('id') == task_id_to_report:
print(f' {t.get("icon", "📋")} {t.get("name")} | progress={t.get("progress")}/{t.get("target")} completed={t.get("completed")} claimed={t.get("claimed", False)}')
# ========== 6. 领取已完成任务奖励 ==========
print('\n--- 6. 领取任务奖励 ---')
r = requests.post(f'{BASE}/api/task/claim', headers=headers, data={
'task_id': task_id_to_report,
})
data = test(f'claim reward (task_id={task_id_to_report})', r)
if data and data.get('code') == 1:
reward = data.get('data', {})
print(f' 奖励: exp={reward.get("exp_reward")}, coin={reward.get("coin_reward")}')
# ========== 7. 再次获取任务列表 - 验证已领取 ==========
print('\n--- 7. 验证奖励已领取 ---')
r = requests.get(f'{BASE}/api/task/today', headers=headers)
data = test('claimed status', r, check_func=lambda d: any(
t.get('id') == task_id_to_report and t.get('claimed') is True
for t in d.get('data', {}).get('tasks', [])
))
if data and data.get('code') == 1:
for t in data['data']['tasks']:
if t.get('id') == task_id_to_report:
print(f' {t.get("icon", "📋")} {t.get("name")} | claimed={t.get("claimed")}')
# ========== 8. 注册自定义任务 ==========
print('\n--- 8. 注册自定义任务 ---')
r = requests.post(f'{BASE}/api/task/registerCustom', headers=headers, data={
'name': '测试自定义',
'icon': '🎯',
'custom_page': 'test_page',
})
data = test('registerCustom', r)
if data and data.get('code') == 1:
custom_task = data.get('data', {})
print(f' 自定义任务: id={custom_task.get("id")}, name={custom_task.get("name")}')
# ========== 9. 尝试领取完美奖励(应失败) ==========
print('\n--- 9. 尝试领取完美奖励(应失败) ---')
r = requests.post(f'{BASE}/api/task/claimPerfect', headers=headers)
data = test('claimPerfect (should fail)', r, expected_code=0)
if data and data.get('code') != 1:
print(f' 预期失败: {data.get("msg")}')
# ========== 10. 申请账号注销 ==========
print('\n--- 10. 申请账号注销 ---')
receipt_data = make_receipt('delete_account', str(user_id))
r = requests.post(f'{BASE}/api/user_security/requestDeletion', headers=headers, data={
'reason': 'test complete',
'receipt': receipt_data['receipt'],
'sig': receipt_data['sig'],
})
data = test('requestDeletion', r)
if data and data.get('code') == 1:
print(f' 注销申请ID: {data["data"].get("id")}')
# ========== 结果汇总 ==========
print('\n' + '=' * 60)
print(f'测试完成!通过: {PASSED}, 失败: {FAILED}, 总计: {PASSED + FAILED}')
print(f'测试账号 {TEST_USERNAME} 已提交注销申请')
print('=' * 60)
if FAILED > 0:
sys.exit(1)

View File

@@ -0,0 +1,162 @@
import requests
import json
import base64
import hashlib
import hmac
import time
import os
import sys
BASE = 'https://tools.wktyl.com'
SECRET = 'Xy7kP9mL2qR4wS8v'
TEST_USERNAME = f'rank_test_{int(time.time())}'
TEST_PASSWORD = '123456'
TEST_EMAIL = f'rank_test_{int(time.time())}@test.com'
PASSED = 0
FAILED = 0
token = None
user_id = None
def make_receipt(action, payload_str):
data = {
'action': action,
'payload': hashlib.sha256(payload_str.encode()).hexdigest()[:16],
'ts': int(time.time()),
'nonce': os.urandom(4).hex()
}
receipt = base64.b64encode(json.dumps(data, ensure_ascii=False).encode()).decode()
sig = hmac.new(SECRET.encode(), receipt.encode(), hashlib.sha256).hexdigest()
return {'receipt': receipt, 'sig': sig}
def test(name, response, expected_code=1, check_func=None):
global PASSED, FAILED
try:
data = response.json()
except Exception as e:
print(f'{name}: Response parse error: {e}')
FAILED += 1
return None
if data.get('code') == expected_code:
if check_func and not check_func(data):
print(f'{name}: Check failed. Response: {json.dumps(data, ensure_ascii=False)[:200]}')
FAILED += 1
return data
print(f'{name}')
PASSED += 1
else:
print(f'{name}: Expected code={expected_code}, got code={data.get("code")}, msg={data.get("msg")}')
FAILED += 1
return data
print('=' * 60)
print('赛季排行榜系统全流程测试')
print(f'测试账号: {TEST_USERNAME}')
print(f'测试时间: {time.strftime("%Y-%m-%d %H:%M:%S")}')
print('=' * 60)
# ========== 1. 注册 ==========
print('\n--- 1. 注册测试用户 ---')
receipt_data = make_receipt('register', TEST_EMAIL)
r = requests.post(f'{BASE}/api/user_security/register', data={
'username': TEST_USERNAME,
'password': TEST_PASSWORD,
'email': TEST_EMAIL,
'receipt': receipt_data['receipt'],
'sig': receipt_data['sig'],
})
data = test('register', r)
if data and data.get('code') == 1:
token = data['data'].get('token', '')
user_id = data['data'].get('userinfo', {}).get('id', '')
print(f' 用户ID: {user_id}')
if not token:
print('❌ 注册失败,终止测试')
sys.exit(1)
headers = {'token': token}
# ========== 2. 登录获取token ==========
print('\n--- 2. 登录获取token ---')
receipt_data = make_receipt('login', TEST_USERNAME)
r = requests.post(f'{BASE}/api/user_security/login', data={
'account': TEST_USERNAME,
'password': TEST_PASSWORD,
'receipt': receipt_data['receipt'],
'sig': receipt_data['sig'],
})
data = test('login', r)
if data and data.get('code') == 1:
token = data['data'].get('token', '')
user_id = data['data'].get('userinfo', {}).get('id', '')
headers = {'token': token}
print(f' Token: {token[:20]}...')
# ========== 3. 获取赛季列表 ==========
print('\n--- 3. 获取赛季列表 ---')
r = requests.get(f'{BASE}/api/rank/seasons', headers=headers)
data = test('seasons list', r, check_func=lambda d: 'data' in d)
if data and data.get('code') == 1:
seasons = data['data'].get('seasons', data['data']) if isinstance(data['data'], dict) else data['data']
if isinstance(seasons, list):
print(f' 赛季数量: {len(seasons)}')
for s in seasons[:5]:
print(f' 🏆 {s.get("name", "?")} | type={s.get("type", "?")} status={s.get("status", "?")}')
else:
print(f' 赛季数据: {json.dumps(data["data"], ensure_ascii=False)[:200]}')
# ========== 4. 获取排行榜(exp类型) ==========
print('\n--- 4. 获取排行榜(exp类型) ---')
r = requests.get(f'{BASE}/api/rank/leaderboard', headers=headers, params={'type': 'exp'})
data = test('leaderboard (exp)', r, check_func=lambda d: 'data' in d)
if data and data.get('code') == 1:
lb = data['data'].get('list', data['data'].get('leaderboard', data['data']))
if isinstance(lb, list):
print(f' 排行榜人数: {len(lb)}')
for item in lb[:3]:
print(f' 🥇 rank={item.get("rank", "?")} user={item.get("username", item.get("nickname", "?"))} value={item.get("value", "?")}')
else:
print(f' 排行榜数据: {json.dumps(data["data"], ensure_ascii=False)[:200]}')
# ========== 5. 获取我的排名 ==========
print('\n--- 5. 获取我的排名 ---')
r = requests.get(f'{BASE}/api/rank/myRank', headers=headers, params={'type': 'exp'})
data = test('myRank (exp)', r, check_func=lambda d: 'data' in d)
if data and data.get('code') == 1:
my_rank = data['data']
if isinstance(my_rank, dict):
print(f' 我的排名: rank={my_rank.get("rank", "?")} value={my_rank.get("value", "?")}')
else:
print(f' 排名数据: {json.dumps(data["data"], ensure_ascii=False)[:200]}')
# ========== 6. 领取赛季奖励(应失败 - 无效赛季) ==========
print('\n--- 6. 领取赛季奖励(应失败 - 无效赛季ID=999) ---')
r = requests.post(f'{BASE}/api/rank/claimReward', headers=headers, data={
'season_id': 999,
})
data = test('claimReward (invalid season, should fail)', r, expected_code=0)
if data and data.get('code') != 1:
print(f' 预期失败: {data.get("msg")}')
# ========== 7. 申请账号注销 ==========
print('\n--- 7. 申请账号注销 ---')
receipt_data = make_receipt('delete_account', str(user_id))
r = requests.post(f'{BASE}/api/user_security/requestDeletion', headers=headers, data={
'reason': 'test complete',
'receipt': receipt_data['receipt'],
'sig': receipt_data['sig'],
})
data = test('requestDeletion', r)
if data and data.get('code') == 1:
print(f' 注销申请ID: {data["data"].get("id")}')
# ========== 结果汇总 ==========
print('\n' + '=' * 60)
print(f'测试完成!通过: {PASSED}, 失败: {FAILED}, 总计: {PASSED + FAILED}')
print(f'测试账号 {TEST_USERNAME} 已提交注销申请')
print('=' * 60)
if FAILED > 0:
sys.exit(1)

View File

@@ -1,219 +0,0 @@
# -*- coding: utf-8 -*-
"""
闲言传输 — Web Transfer API 测试脚本
创建时间: 2026-05-14
更新时间: 2026-05-14
名称: test_web_transfer.py
作用: 测试LocalSend HTTP服务器的Web传输API端点
上次更新: 初始创建测试GET /api/info、POST /api/send-text、WebSocket连接
"""
import sys
import json
import time
try:
import requests
except ImportError:
print("❌ 缺少 requests 库,请运行: pip install requests")
sys.exit(1)
try:
import websocket
except ImportError:
try:
import websocket as _ws
websocket = _ws
except ImportError:
websocket = None
BASE_URL = "http://localhost:53317"
WS_URL = "ws://localhost:53317/ws"
TIMEOUT = 5
def test_api_info():
print("\n" + "=" * 60)
print("测试 1: GET /api/info — 获取设备信息")
print("=" * 60)
try:
resp = requests.get(f"{BASE_URL}/api/info", timeout=TIMEOUT)
if resp.status_code == 200:
data = resp.json()
alias = data.get("alias", "")
device_type = data.get("deviceType", "")
fingerprint = data.get("fingerprint", "")
port = data.get("port", "")
version = data.get("version", "")
print(f" ✅ PASS — HTTP {resp.status_code}")
print(f" 设备别名: {alias}")
print(f" 设备类型: {device_type}")
print(f" 指纹: {fingerprint[:16]}..." if len(fingerprint) > 16 else f" 指纹: {fingerprint}")
print(f" 端口: {port}")
print(f" 版本: {version}")
return True
else:
print(f" ❌ FAIL — HTTP {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
return False
except requests.exceptions.ConnectionError:
print(" ⚠️ SKIP — 无法连接到服务器 (服务器可能未运行)")
return None
except requests.exceptions.Timeout:
print(" ⚠️ SKIP — 请求超时")
return None
except Exception as e:
print(f" ❌ FAIL — 异常: {e}")
return False
def test_send_text():
print("\n" + "=" * 60)
print("测试 2: POST /api/send-text — 发送文本消息")
print("=" * 60)
try:
payload = {"text": "Hello from test_web_transfer.py 🧪"}
resp = requests.post(
f"{BASE_URL}/api/send-text",
json=payload,
timeout=TIMEOUT
)
if resp.status_code == 200:
data = resp.json()
print(f" ✅ PASS — HTTP {resp.status_code}")
print(f" 响应: {json.dumps(data, ensure_ascii=False)[:200]}")
return True
else:
print(f" ❌ FAIL — HTTP {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
return False
except requests.exceptions.ConnectionError:
print(" ⚠️ SKIP — 无法连接到服务器 (服务器可能未运行)")
return None
except requests.exceptions.Timeout:
print(" ⚠️ SKIP — 请求超时")
return None
except Exception as e:
print(f" ❌ FAIL — 异常: {e}")
return False
def test_websocket():
print("\n" + "=" * 60)
print("测试 3: WebSocket /ws — WS连接测试")
print("=" * 60)
if websocket is None:
print(" ⚠️ SKIP — 缺少 websocket-client 库")
print(" 请运行: pip install websocket-client")
return None
try:
ws = websocket.create_connection(WS_URL, timeout=TIMEOUT)
print(f" ✅ PASS — WebSocket 连接成功")
print(f" 状态: {ws.status}")
print(f" 头部: {dict(list(ws.headers.items())[:3])}")
ping_msg = json.dumps({"type": "ping", "timestamp": int(time.time() * 1000)})
ws.send(ping_msg)
print(f" 发送: {ping_msg[:80]}")
try:
result = ws.recv()
print(f" 接收: {result[:200]}")
except Exception:
print(f" (未收到即时响应,连接正常)")
ws.close()
return True
except ConnectionRefusedError:
print(" ⚠️ SKIP — 无法连接到WebSocket服务器 (服务器可能未运行)")
return None
except Exception as e:
error_msg = str(e)
if "Connection refused" in error_msg or "ECONNREFUSED" in error_msg:
print(" ⚠️ SKIP — 无法连接到WebSocket服务器 (服务器可能未运行)")
return None
print(f" ❌ FAIL — 异常: {e}")
return False
def test_localsend_info():
print("\n" + "=" * 60)
print("测试 4: GET /api/localsend/v2/info — LocalSend协议信息")
print("=" * 60)
try:
resp = requests.get(f"{BASE_URL}/api/localsend/v2/info", timeout=TIMEOUT)
if resp.status_code == 200:
data = resp.json()
alias = data.get("alias", "")
device_type = data.get("deviceType", "")
fingerprint = data.get("fingerprint", "")
print(f" ✅ PASS — HTTP {resp.status_code}")
print(f" 设备别名: {alias}")
print(f" 设备类型: {device_type}")
print(f" 指纹: {fingerprint[:16]}..." if len(fingerprint) > 16 else f" 指纹: {fingerprint}")
return True
else:
print(f" ❌ FAIL — HTTP {resp.status_code}")
return False
except requests.exceptions.ConnectionError:
print(" ⚠️ SKIP — 无法连接到服务器 (服务器可能未运行)")
return None
except requests.exceptions.Timeout:
print(" ⚠️ SKIP — 请求超时")
return None
except Exception as e:
print(f" ❌ FAIL — 异常: {e}")
return False
def main():
print("╔══════════════════════════════════════════════════════════╗")
print("║ 闲言传输 — Web Transfer API 测试 ║")
print("╚══════════════════════════════════════════════════════════╝")
print(f"目标服务器: {BASE_URL}")
print(f"WebSocket: {WS_URL}")
print(f"时间: {time.strftime('%Y-%m-%d %H:%M:%S')}")
results = []
results.append(("GET /api/info", test_api_info()))
results.append(("POST /api/send-text", test_send_text()))
results.append(("WebSocket /ws", test_websocket()))
results.append(("GET /api/localsend/v2/info", test_localsend_info()))
print("\n" + "=" * 60)
print("测试结果汇总")
print("=" * 60)
passed = 0
failed = 0
skipped = 0
for name, result in results:
if result is True:
status = "✅ PASS"
passed += 1
elif result is False:
status = "❌ FAIL"
failed += 1
else:
status = "⚠️ SKIP"
skipped += 1
print(f" {status} {name}")
print(f"\n通过: {passed} 失败: {failed} 跳过: {skipped} 总计: {len(results)}")
if skipped == len(results):
print("\n⚠️ 所有测试均跳过 — 请确保LocalSend服务器正在运行 (端口 53317)")
print(" 在闲言APP中开启文件传输功能后HTTP服务器会自动启动")
elif failed > 0:
print(f"\n{failed} 个测试失败,请检查服务器日志")
else:
print("\n🎉 所有测试通过!")
return 0 if failed == 0 else 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,61 +0,0 @@
import paramiko
import os
import time
HOST = '123.207.67.197'
PORT = 22
USER = 'root'
PASS = '520Kiss123'
LOCAL_CONTROLLER = r'e:\project\flutter\f\xianyan\docs\toolsapi\application\api\controller\CloudCache.php'
REMOTE_CONTROLLER = '/www/wwwroot/tools.wktyl.com/application/api/controller/CloudCache.php'
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(HOST, PORT, USER, PASS)
sftp = ssh.open_sftp()
backup_file = REMOTE_CONTROLLER + '.bak.' + time.strftime('%Y%m%d_%H%M%S')
print(f'Backing up {REMOTE_CONTROLLER} -> {backup_file}')
try:
sftp.rename(REMOTE_CONTROLLER, backup_file)
print('Backup created')
except:
print('No existing file to backup (or rename failed)')
print(f'Uploading {LOCAL_CONTROLLER} -> {REMOTE_CONTROLLER}')
sftp.put(LOCAL_CONTROLLER, REMOTE_CONTROLLER)
print('Upload complete')
sftp.chmod(REMOTE_CONTROLLER, 0o644)
print('Permissions set to 644')
sftp.close()
print('\nVerifying file on server...')
stdin, stdout, stderr = ssh.exec_command(f'head -5 {REMOTE_CONTROLLER}')
print(stdout.read().decode())
print('\nCreating cloud_cache storage directory...')
stdin, stdout, stderr = ssh.exec_command('mkdir -p /www/wwwroot/tools.wktyl.com/runtime/cloud_cache && chmod 755 /www/wwwroot/tools.wktyl.com/runtime/cloud_cache')
print(stdout.read().decode())
print(stderr.read().decode())
print('\nInstalling cloud_cache_record table...')
install_url = 'https://tools.wktyl.com/api/cloud_cache/install'
stdin, stdout, stderr = ssh.exec_command(f'curl -s -X POST "{install_url}"')
result = stdout.read().decode()
print(f'Install result: {result}')
print('\nSetting up cron job for auto cleanup (every hour)...')
cron_cmd = '0 * * * * curl -s -X POST https://tools.wktyl.com/api/cloud_cache/clean > /dev/null 2>&1'
stdin, stdout, stderr = ssh.exec_command(f'(crontab -l 2>/dev/null; echo "{cron_cmd}") | sort -u | crontab -')
print('Cron job configured')
stdin, stdout, stderr = ssh.exec_command('crontab -l | grep cloud_cache')
cron_result = stdout.read().decode()
print(f'Cron verification: {cron_result}')
ssh.close()
print('\nUpload and setup complete!')

View File

@@ -1,51 +0,0 @@
import paramiko
import os
HOST = '123.207.67.197'
PORT = 22
USER = 'root'
PASS = '520Kiss123'
LOCAL_FILE = r'e:\project\flutter\f\xianyan\server\index.js'
REMOTE_FILE = '/www/wwwroot/tools.wktyl.com/signaling/index.js'
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(HOST, PORT, USER, PASS)
sftp = ssh.open_sftp()
backup_file = REMOTE_FILE + '.bak.' + os.popen('echo %date:~0,4%%date:~5,2%%date:~8,2%_%time:~0,2%%time:~3,2%').read().strip().replace(' ', '0')
print(f'Backing up {REMOTE_FILE} -> {backup_file}')
try:
sftp.rename(REMOTE_FILE, backup_file)
print('Backup created')
except:
print('No existing file to backup (or rename failed)')
print(f'Uploading {LOCAL_FILE} -> {REMOTE_FILE}')
sftp.put(LOCAL_FILE, REMOTE_FILE)
print('Upload complete')
sftp.chmod(REMOTE_FILE, 0o644)
print('Permissions set to 644')
sftp.close()
print('\nRestarting PM2 signaling service...')
stdin, stdout, stderr = ssh.exec_command('cd /www/wwwroot/tools.wktyl.com/signaling && pm2 restart signaling')
print(stdout.read().decode())
print(stderr.read().decode())
import time
time.sleep(2)
print('\nChecking PM2 status...')
stdin, stdout, stderr = ssh.exec_command('pm2 list')
print(stdout.read().decode())
print('\nChecking recent logs...')
stdin, stdout, stderr = ssh.exec_command('pm2 logs signaling --lines 10 --nostream')
print(stdout.read().decode())
ssh.close()
print('\nDone!')

View File

@@ -0,0 +1,83 @@
import paramiko
import os
import sys
HOST = '123.207.67.197'
PORT = 22
USER = 'root'
PASS = '520Kiss123'
BASE_DIR = r'e:\project\flutter\f\xianyan\docs\toolsapi'
REMOTE_BASE = '/www/wwwroot/tools.wktyl.com/'
UPLOAD_FILES = [
{
'local': os.path.join(BASE_DIR, 'application', 'api', 'controller', 'UserSecurity.php'),
'remote': REMOTE_BASE + 'application/api/controller/UserSecurity.php',
},
{
'local': os.path.join(BASE_DIR, 'application', 'api', 'controller', 'UserCenter.php'),
'remote': REMOTE_BASE + 'application/api/controller/UserCenter.php',
},
]
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(HOST, PORT, USER, PASS)
sftp = ssh.open_sftp()
for item in UPLOAD_FILES:
local = item['local']
remote = item['remote']
if not os.path.exists(local):
print(f'[SKIP] Local file not found: {local}')
continue
backup_file = remote + '.bak.' + os.popen('echo %date:~0,4%%date:~5,2%%date:~8,2%_%time:~0,2%%time:~3,2%').read().strip().replace(' ', '0')
print(f'Backing up {remote} -> {backup_file}')
try:
sftp.rename(remote, backup_file)
print(' Backup created')
except:
print(' No existing file to backup (or rename failed)')
print(f'Uploading {local} -> {remote}')
sftp.put(local, remote)
print(' Upload complete')
sftp.chmod(remote, 0o644)
print(' Permissions set to 644')
print('\nRunning database migration...')
sql_file = os.path.join(BASE_DIR, 'application', 'admin', 'command', 'Install', 'migrate_v10_1.sql')
if os.path.exists(sql_file):
with open(sql_file, 'r', encoding='utf-8') as f:
sql_content = f.read()
sql_lines = [line.strip() for line in sql_content.split('\n') if line.strip() and not line.strip().startswith('--')]
for sql in sql_lines:
if sql:
stdin, stdout, stderr = ssh.exec_command(
f"cd {REMOTE_BASE} && php think sql \"{sql.replace('\"', '\\\"')}\" 2>/dev/null || "
f"mysql -u tools -ptools tools -e \"{sql.replace('\"', '\\\"')}\" 2>/dev/null || "
f"echo 'SQL_MANUAL: {sql[:80]}...'"
)
output = stdout.read().decode()
err = stderr.read().decode()
if output:
print(f' SQL: {output.strip()}')
if err and 'Warning' not in err:
print(f' SQL Err: {err.strip()}')
print('Migration commands sent (check manually if needed)')
print('\nClearing ThinkPHP cache...')
stdin, stdout, stderr = ssh.exec_command(f'rm -rf {REMOTE_BASE}runtime/cache/* {REMOTE_BASE}runtime/temp/*')
print(stdout.read().decode())
sftp.close()
print('\nDone! Files uploaded and migration executed.')
print('Please verify the migration manually if needed:')
print(f' SQL: ALTER TABLE tool_user ADD COLUMN sec_question TINYINT(2) UNSIGNED NOT NULL DEFAULT 0;')
print(f' SQL: ALTER TABLE tool_user ADD COLUMN sec_answer VARCHAR(32) NOT NULL DEFAULT \'\';')
print(f' SQL: ALTER TABLE tool_user ADD INDEX idx_sec_question (sec_question);')
ssh.close()

View File

@@ -0,0 +1,55 @@
"""Upload signaling server index.js and restart PM2"""
import paramiko
import os
import time
HOST = '123.207.67.197'
USER = 'root'
PASS = '520Kiss123'
REMOTE_BASE = '/www/wwwroot/tools.wktyl.com/signaling'
LOCAL_FILE = r'e:\project\flutter\f\xianyan\server\index.js'
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(HOST, username=USER, password=PASS)
sftp = ssh.open_sftp()
remote_file = REMOTE_BASE + '/index.js'
print(f'Uploading index.js to {remote_file}...')
backup_path = REMOTE_BASE + '/index.js.bak'
try:
sftp.stat(remote_file)
sftp.rename(remote_file, backup_path)
print(f'Backup created: {backup_path}')
except FileNotFoundError:
pass
sftp.put(LOCAL_FILE, remote_file)
print(f'[OK] index.js uploaded')
sftp.close()
print('\nRestarting PM2 signaling process...')
stdin, stdout, stderr = ssh.exec_command('cd /www/wwwroot/tools.wktyl.com/signaling && pm2 restart signaling')
out = stdout.read().decode().strip()
err = stderr.read().decode().strip()
if out:
print(out)
if err:
print(f'STDERR: {err[:300]}')
time.sleep(2)
print('\nChecking PM2 status...')
stdin, stdout, stderr = ssh.exec_command('pm2 list')
out = stdout.read().decode().strip()
print(out[-500:] if len(out) > 500 else out)
print('\nChecking signaling logs (last 10 lines)...')
stdin, stdout, stderr = ssh.exec_command('pm2 logs signaling --lines 10 --nostream')
out = stdout.read().decode().strip()
print(out[-500:] if len(out) > 500 else out)
ssh.close()
print('\nDeployment complete!')

View File

@@ -0,0 +1,289 @@
"""
@File : upload_v12_daily_task.py
@Created : 2026-05-14
@Updated : 2026-05-14
@Name : Phase3 Daily Task Upload Script
@Desc : Upload Phase 3 daily task files and bug fixes to server, execute migration SQL
@Last : Initial creation
"""
import os
import sys
import paramiko
from stat import S_ISDIR
HOST = "123.207.67.197"
USERNAME = "root"
PASSWORD = "520Kiss123"
REMOTE_BASE = "/www/wwwroot/tools.wktyl.com"
LOCAL_BASE = r"e:\project\flutter\f\xianyan\docs\toolsapi"
PHASE3_FILES = [
(r"application\admin\command\Install\migrate_v12.sql",
"application/admin/command/Install/migrate_v12.sql"),
(r"application\api\controller\Task.php",
"application/api/controller/Task.php"),
(r"application\admin\controller\DailyTask.php",
"application/admin/controller/DailyTask.php"),
(r"application\admin\model\DailyTask.php",
"application/admin/model/DailyTask.php"),
(r"application\admin\validate\DailyTask.php",
"application/admin/validate/DailyTask.php"),
(r"application\admin\view\daily_task\index.html",
"application/admin/view/daily_task/index.html"),
(r"application\admin\view\daily_task\add.html",
"application/admin/view/daily_task/add.html"),
(r"application\admin\view\daily_task\edit.html",
"application/admin/view/daily_task/edit.html"),
(r"public\assets\js\backend\daily_task.js",
"public/assets/js/backend/daily_task.js"),
(r"application\admin\lang\zh-cn\daily_task.php",
"application/admin/lang/zh-cn/daily_task.php"),
(r"application\admin\controller\user\UserTask.php",
"application/admin/controller/user/UserTask.php"),
(r"application\admin\model\UserTask.php",
"application/admin/model/UserTask.php"),
(r"application\admin\view\user\user_task\index.html",
"application/admin/view/user/user_task/index.html"),
(r"public\assets\js\backend\user\user_task.js",
"public/assets/js/backend/user/user_task.js"),
(r"application\admin\lang\zh-cn\user\user_task.php",
"application/admin/lang/zh-cn/user/user_task.php"),
]
BUGFIX_FILES = [
(r"application\admin\controller\FeedWeight.php",
"application/admin/controller/FeedWeight.php"),
(r"application\admin\model\FeedWeightConfig.php",
"application/admin/model/FeedWeightConfig.php"),
(r"application\admin\view\feed_weight\index.html",
"application/admin/view/feed_weight/index.html"),
(r"application\admin\view\feed_weight\edit.html",
"application/admin/view/feed_weight/edit.html"),
(r"public\assets\js\backend\feed_weight.js",
"public/assets/js/backend/feed_weight.js"),
(r"application\admin\lang\zh-cn\feed_weight.php",
"application/admin/lang/zh-cn/feed_weight.php"),
(r"application\admin\view\userdeletion\index.html",
"application/admin/view/userdeletion/index.html"),
(r"application\admin\controller\user\User.php",
"application/admin/controller/user/User.php"),
(r"application\admin\controller\Userdeletion.php",
"application/admin/controller/Userdeletion.php"),
]
MIGRATE_SQL_CMD = (
"mysql -u root -p'520Kiss123' --default-character-set=utf8mb4 "
"tool_db < /www/wwwroot/tools.wktyl.com/application/admin/command/Install/migrate_v12.sql"
)
CREATE_FEED_WEIGHT_TABLE_SQL = (
'mysql -u root -p\'520Kiss123\' --default-character-set=utf8mb4 tool_db -e '
'"CREATE TABLE IF NOT EXISTS tool_feed_weight_config ('
'id int(10) unsigned NOT NULL AUTO_INCREMENT,'
'feed_type varchar(30) NOT NULL DEFAULT \'\' COMMENT \'内容类型\','
'weight int(10) NOT NULL DEFAULT 50 COMMENT \'推荐权重0-100\','
'display_weight int(10) NOT NULL DEFAULT 40 COMMENT \'展示权重\','
'push_limit int(10) NOT NULL DEFAULT 0 COMMENT \'每日推送上限0=不限\','
'push_count int(10) NOT NULL DEFAULT 0 COMMENT \'今日已推送数\','
'push_date varchar(10) NOT NULL DEFAULT \'\' COMMENT \'推送日期\','
'is_enabled tinyint(1) NOT NULL DEFAULT 1 COMMENT \'是否启用\','
'createtime int(10) DEFAULT NULL,'
'updatetime int(10) DEFAULT NULL,'
'update_time int(10) DEFAULT NULL,'
'PRIMARY KEY (id),'
'UNIQUE KEY uk_feed_type (feed_type)'
') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=\'推荐权重配置\';"'
)
INSERT_DEFAULT_DATA_SQL = (
'mysql -u root -p\'520Kiss123\' --default-character-set=utf8mb4 tool_db -e '
'"INSERT IGNORE INTO tool_feed_weight_config '
'(feed_type, weight, display_weight, push_limit, is_enabled, createtime, updatetime) VALUES '
'(\'poetry\',60,48,0,1,UNIX_TIMESTAMP(),UNIX_TIMESTAMP()),'
'(\'wisdom\',55,44,0,1,UNIX_TIMESTAMP(),UNIX_TIMESTAMP()),'
'(\'story\',50,40,0,1,UNIX_TIMESTAMP(),UNIX_TIMESTAMP()),'
'(\'hitokoto\',70,56,0,1,UNIX_TIMESTAMP(),UNIX_TIMESTAMP()),'
'(\'riddle\',40,32,0,1,UNIX_TIMESTAMP(),UNIX_TIMESTAMP()),'
'(\'efs\',35,28,0,1,UNIX_TIMESTAMP(),UNIX_TIMESTAMP()),'
'(\'brainteaser\',40,32,0,1,UNIX_TIMESTAMP(),UNIX_TIMESTAMP()),'
'(\'saying\',30,24,0,1,UNIX_TIMESTAMP(),UNIX_TIMESTAMP()),'
'(\'lyric\',55,44,0,1,UNIX_TIMESTAMP(),UNIX_TIMESTAMP()),'
'(\'why\',35,28,0,1,UNIX_TIMESTAMP(),UNIX_TIMESTAMP()),'
'(\'composition\',30,24,0,1,UNIX_TIMESTAMP(),UNIX_TIMESTAMP()),'
'(\'couplet\',25,20,0,1,UNIX_TIMESTAMP(),UNIX_TIMESTAMP()),'
'(\'cs\',30,24,0,1,UNIX_TIMESTAMP(),UNIX_TIMESTAMP()),'
'(\'drug\',15,12,0,1,UNIX_TIMESTAMP(),UNIX_TIMESTAMP()),'
'(\'herbal\',15,12,0,1,UNIX_TIMESTAMP(),UNIX_TIMESTAMP()),'
'(\'food\',20,16,0,1,UNIX_TIMESTAMP(),UNIX_TIMESTAMP()),'
'(\'wine\',10,8,0,1,UNIX_TIMESTAMP(),UNIX_TIMESTAMP()),'
'(\'article\',60,48,0,1,UNIX_TIMESTAMP(),UNIX_TIMESTAMP());"'
)
INSERT_MENU_SQL = (
'mysql -u root -p\'520Kiss123\' --default-character-set=utf8mb4 tool_db -e "'
'INSERT IGNORE INTO fa_auth_rule '
'(id, type, pid, name, title, icon, condition, remark, ismenu, createtime, updatetime, weigh, status) VALUES '
"(200, 'file', 0, 'feed_weight', '推荐权重', 'fa fa-balance-scale', '', '', 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'),"
"(201, 'file', 200, 'feed_weight/index', '查看', '', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'),"
"(202, 'file', 200, 'feed_weight/edit', '编辑', '', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'),"
"(203, 'file', 200, 'feed_weight/reset_push', '重置推送', '', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'),"
"(204, 'file', 200, 'feed_weight/reset_defaults', '恢复默认', '', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'),"
"(210, 'file', 0, 'daily_task', '每日任务', 'fa fa-tasks', '', '', 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'),"
"(211, 'file', 210, 'daily_task/index', '查看', '', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'),"
"(212, 'file', 210, 'daily_task/add', '添加', '', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'),"
"(213, 'file', 210, 'daily_task/edit', '编辑', '', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'),"
"(214, 'file', 210, 'daily_task/del', '删除', '', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'),"
"(220, 'file', 0, 'badge', '勋章管理', 'fa fa-shield', '', '', 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'),"
"(221, 'file', 220, 'badge/index', '查看', '', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'),"
"(222, 'file', 220, 'badge/add', '添加', '', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'),"
"(223, 'file', 220, 'badge/edit', '编辑', '', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'),"
"(224, 'file', 220, 'badge/del', '删除', '', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'),"
"(230, 'file', 0, 'user/user_badge', '用户勋章', 'fa fa-id-badge', '', '', 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'),"
"(231, 'file', 230, 'user/user_badge/index', '查看', '', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'),"
"(240, 'file', 0, 'user/user_task', '用户任务', 'fa fa-list-check', '', '', 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'),"
"(241, 'file', 240, 'user/user_task/index', '查看', '', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal');"
'"'
)
def create_ssh_client():
"""Create and return SSH client connection"""
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
print(f"[SSH] Connecting to {HOST}...")
client.connect(HOST, username=USERNAME, password=PASSWORD, timeout=30)
print(f"[SSH] Connected successfully.")
return client
def ensure_remote_dir(sftp, remote_path):
"""Ensure remote directory exists, create if not"""
dirs_to_create = []
path = remote_path
while path != "/" and path != "":
try:
sftp.stat(path)
break
except FileNotFoundError:
dirs_to_create.append(path)
path = os.path.dirname(path).replace("\\", "/")
for d in reversed(dirs_to_create):
print(f" [DIR] Creating remote directory: {d}")
sftp.mkdir(d)
def upload_file(sftp, local_path, remote_path):
"""Upload a single file, creating remote directories as needed"""
remote_dir = os.path.dirname(remote_path).replace("\\", "/")
ensure_remote_dir(sftp, remote_dir)
if not os.path.isfile(local_path):
print(f" [SKIP] Local file not found: {local_path}")
return False
sftp.put(local_path, remote_path)
print(f" [OK] {os.path.basename(local_path)} -> {remote_path}")
return True
def run_ssh_command(ssh, cmd, label=""):
"""Execute SSH command and print output"""
if label:
print(f"\n[SQL] {label}")
print(f" [CMD] {cmd[:120]}...")
stdin, stdout, stderr = ssh.exec_command(cmd)
out = stdout.read().decode("utf-8", errors="replace").strip()
err = stderr.read().decode("utf-8", errors="replace").strip()
exit_code = stdout.channel.recv_exit_status()
if out:
print(f" [OUT] {out[:500]}")
if err:
print(f" [ERR] {err[:500]}")
if exit_code != 0:
print(f" [FAIL] Exit code: {exit_code}")
else:
print(f" [OK] Exit code: 0")
return exit_code
def main():
all_files = []
print("=" * 60)
print(" Phase 3 Daily Task & Bug Fix Upload Script v12")
print("=" * 60)
print("\n--- Phase 3: Daily Task Files ---")
for local_rel, remote_rel in PHASE3_FILES:
local = os.path.join(LOCAL_BASE, local_rel)
remote = REMOTE_BASE + "/" + remote_rel
all_files.append(("Phase3", local, remote))
print("\n--- Bug Fix Files ---")
for local_rel, remote_rel in BUGFIX_FILES:
local = os.path.join(LOCAL_BASE, local_rel)
remote = REMOTE_BASE + "/" + remote_rel
all_files.append(("BugFix", local, remote))
missing = [(t, l, r) for t, l, r in all_files if not os.path.isfile(l)]
if missing:
print("\n[WARN] Missing local files:")
for tag, local, remote in missing:
print(f" [{tag}] {local}")
answer = input("\nContinue anyway? (y/N): ").strip().lower()
if answer != "y":
print("Aborted.")
sys.exit(1)
ssh = create_ssh_client()
sftp = ssh.open_sftp()
success = 0
failed = 0
skipped = 0
print(f"\n{'=' * 60}")
print(f" Uploading {len(all_files)} files...")
print(f"{'=' * 60}")
for tag, local, remote in all_files:
print(f"\n[{tag}] {os.path.basename(local)}")
try:
result = upload_file(sftp, local, remote)
if result:
success += 1
else:
skipped += 1
except Exception as e:
print(f" [FAIL] {e}")
failed += 1
sftp.close()
print(f"\n{'=' * 60}")
print(f" Upload Summary: {success} ok, {skipped} skipped, {failed} failed")
print(f"{'=' * 60}")
if failed > 0:
print("[WARN] Some uploads failed, but continuing with SQL execution...")
print(f"\n{'=' * 60}")
print(" Executing SQL Commands...")
print(f"{'=' * 60}")
run_ssh_command(ssh, MIGRATE_SQL_CMD, "Execute migrate_v12.sql")
run_ssh_command(ssh, CREATE_FEED_WEIGHT_TABLE_SQL, "Create tool_feed_weight_config table if not exists")
run_ssh_command(ssh, INSERT_DEFAULT_DATA_SQL, "Insert default feed weight config data")
run_ssh_command(ssh, INSERT_MENU_SQL, "Insert admin menu entries for new modules")
ssh.close()
print(f"\n{'=' * 60}")
print(" All done!")
print(f"{'=' * 60}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,83 @@
"""Upload Phase 4 (Rank System) files + execute migration"""
import paramiko
import os
HOST = '123.207.67.197'
USER = 'root'
PASS = '520Kiss123'
REMOTE_BASE = '/www/wwwroot/tools.wktyl.com'
LOCAL_BASE = r'e:\project\flutter\f\xianyan\docs\toolsapi'
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(HOST, username=USER, password=PASS)
sftp = ssh.open_sftp()
files = [
('application/admin/command/Install/migrate_v13.sql', 'application/admin/command/Install/migrate_v13.sql'),
('application/api/controller/Rank.php', 'application/api/controller/Rank.php'),
('application/admin/controller/RankSeason.php', 'application/admin/controller/RankSeason.php'),
('application/admin/model/RankSeason.php', 'application/admin/model/RankSeason.php'),
('application/admin/view/rank_season/index.html', 'application/admin/view/rank_season/index.html'),
('application/admin/view/rank_season/add.html', 'application/admin/view/rank_season/add.html'),
('application/admin/view/rank_season/edit.html', 'application/admin/view/rank_season/edit.html'),
('public/assets/js/backend/rank_season.js', 'public/assets/js/backend/rank_season.js'),
('application/admin/lang/zh-cn/rank_season.php', 'application/admin/lang/zh-cn/rank_season.php'),
('application/admin/controller/user/RankRecord.php', 'application/admin/controller/user/RankRecord.php'),
('application/admin/model/RankRecord.php', 'application/admin/model/RankRecord.php'),
('application/admin/view/user/rank_record/index.html', 'application/admin/view/user/rank_record/index.html'),
('public/assets/js/backend/user/rank_record.js', 'public/assets/js/backend/user/rank_record.js'),
('application/admin/lang/zh-cn/user/rank_record.php', 'application/admin/lang/zh-cn/user/rank_record.php'),
]
for local_rel, remote_rel in files:
local = os.path.join(LOCAL_BASE, local_rel)
remote = REMOTE_BASE + '/' + remote_rel
remote_dir = os.path.dirname(remote).replace('\\', '/')
try:
try:
sftp.stat(remote_dir)
except FileNotFoundError:
ssh.exec_command(f'mkdir -p {remote_dir}')
import time; time.sleep(0.3)
sftp.put(local, remote)
print(f'[OK] {remote_rel}')
except Exception as e:
print(f'[FAIL] {remote_rel}: {e}')
sftp.close()
# Execute migration SQL
print('\n--- Executing migration SQL ---')
sql_path = '/tmp/migrate_v13.sql'
local_sql = os.path.join(LOCAL_BASE, 'application/admin/command/Install/migrate_v13.sql')
sftp = ssh.open_sftp()
with sftp.open(sql_path, 'w') as f:
f.write(open(local_sql, 'r', encoding='utf-8').read())
sftp.close()
cmd = f"mysql -u tools -p'tools' --default-character-set=utf8mb4 tools < {sql_path}"
stdin, stdout, stderr = ssh.exec_command(cmd)
err = stderr.read().decode().strip()
exit_code = stdout.channel.recv_exit_status()
if err:
print(f'SQL ERR: {err[:300]}')
else:
print('SQL executed successfully!')
print(f'Exit: {exit_code}')
# Insert admin menu entries
print('\n--- Inserting admin menu entries ---')
menu_sql = """INSERT IGNORE INTO tool_auth_rule (id,type,pid,name,title,icon,ismenu,weigh,status) VALUES (250,'file',0,'rank_season','赛季管理','fa fa-trophy',1,0,'normal'),(251,'file',250,'rank_season/index','查看','',0,0,'normal'),(252,'file',250,'rank_season/add','添加','',0,0,'normal'),(253,'file',250,'rank_season/edit','编辑','',0,0,'normal'),(254,'file',250,'rank_season/del','删除','',0,0,'normal'),(255,'file',250,'rank_season/settle','结算','',0,0,'normal'),(260,'file',0,'user/rank_record','排名记录','fa fa-list-ol',1,0,'normal'),(261,'file',260,'user/rank_record/index','查看','',0,0,'normal')"""
cmd2 = f"mysql -u tools -p'tools' --default-character-set=utf8mb4 tools -e \"{menu_sql}\""
stdin, stdout, stderr = ssh.exec_command(cmd2)
err2 = stderr.read().decode().strip()
exit2 = stdout.channel.recv_exit_status()
if err2:
print(f'Menu ERR: {err2[:200]}')
else:
print('Menu entries inserted!')
print(f'Exit: {exit2}')
ssh.close()
print('\nAll done!')

View File

@@ -0,0 +1,57 @@
"""Upload Phase 5 (Admin completion) + menu entries"""
import paramiko
import os
HOST = '123.207.67.197'
USER = 'root'
PASS = '520Kiss123'
REMOTE_BASE = '/www/wwwroot/tools.wktyl.com'
LOCAL_BASE = r'e:\project\flutter\f\xianyan\docs\toolsapi'
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(HOST, username=USER, password=PASS)
sftp = ssh.open_sftp()
files = [
('application/admin/controller/user/UserExpLog.php', 'application/admin/controller/user/UserExpLog.php'),
('application/admin/model/UserExpLog.php', 'application/admin/model/UserExpLog.php'),
('application/admin/view/user/user_exp_log/index.html', 'application/admin/view/user/user_exp_log/index.html'),
('public/assets/js/backend/user/user_exp_log.js', 'public/assets/js/backend/user/user_exp_log.js'),
('application/admin/lang/zh-cn/user/user_exp_log.php', 'application/admin/lang/zh-cn/user/user_exp_log.php'),
('application/admin/view/user/user/edit.html', 'application/admin/view/user/user/edit.html'),
('application/admin/lang/zh-cn/user/user.php', 'application/admin/lang/zh-cn/user/user.php'),
]
for local_rel, remote_rel in files:
local = os.path.join(LOCAL_BASE, local_rel)
remote = REMOTE_BASE + '/' + remote_rel
remote_dir = os.path.dirname(remote).replace('\\', '/')
try:
try:
sftp.stat(remote_dir)
except FileNotFoundError:
ssh.exec_command(f'mkdir -p {remote_dir}')
import time; time.sleep(0.3)
sftp.put(local, remote)
print(f'[OK] {remote_rel}')
except Exception as e:
print(f'[FAIL] {remote_rel}: {e}')
sftp.close()
# Insert admin menu entries for EXP log
print('\n--- Inserting admin menu entries ---')
menu_sql = """INSERT IGNORE INTO tool_auth_rule (id,type,pid,name,title,icon,ismenu,weigh,status) VALUES (270,'file',0,'user/user_exp_log','EXP日志','fa fa-bolt',1,0,'normal'),(271,'file',270,'user/user_exp_log/index','查看','',0,0,'normal')"""
cmd = f"mysql -u tools -p'tools' --default-character-set=utf8mb4 tools -e \"{menu_sql}\""
stdin, stdout, stderr = ssh.exec_command(cmd)
err = stderr.read().decode().strip()
exit_code = stdout.channel.recv_exit_status()
if err:
print(f'Menu ERR: {err[:200]}')
else:
print('Menu entries inserted!')
print(f'Exit: {exit_code}')
ssh.close()
print('\nAll done!')