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:
25
docs/toolsapi/scripts/check_db.py
Normal file
25
docs/toolsapi/scripts/check_db.py
Normal 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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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®en=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()
|
||||
24
docs/toolsapi/scripts/fix_v12_sql.py
Normal file
24
docs/toolsapi/scripts/fix_v12_sql.py
Normal 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!')
|
||||
@@ -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()
|
||||
199
docs/toolsapi/scripts/test_daily_task.py
Normal file
199
docs/toolsapi/scripts/test_daily_task.py
Normal 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)
|
||||
162
docs/toolsapi/scripts/test_rank_system.py
Normal file
162
docs/toolsapi/scripts/test_rank_system.py
Normal 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)
|
||||
@@ -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())
|
||||
@@ -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!')
|
||||
@@ -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!')
|
||||
83
docs/toolsapi/scripts/upload_sec_question.py
Normal file
83
docs/toolsapi/scripts/upload_sec_question.py
Normal 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()
|
||||
55
docs/toolsapi/scripts/upload_signaling.py
Normal file
55
docs/toolsapi/scripts/upload_signaling.py
Normal 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!')
|
||||
289
docs/toolsapi/scripts/upload_v12_daily_task.py
Normal file
289
docs/toolsapi/scripts/upload_v12_daily_task.py
Normal 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()
|
||||
83
docs/toolsapi/scripts/upload_v13_rank.py
Normal file
83
docs/toolsapi/scripts/upload_v13_rank.py
Normal 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!')
|
||||
57
docs/toolsapi/scripts/upload_v15_admin.py
Normal file
57
docs/toolsapi/scripts/upload_v15_admin.py
Normal 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!')
|
||||
Reference in New Issue
Block a user