初始提交: 通用数据同步服务
- 基于OpenResty的RESTful API服务 - 支持Cookie/Token等数据类型的存储和管理 - 登录认证保护 - Web管理界面 - 数据过期管理
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
"""
|
||||
通用数据同步服务 - Python 客户端示例
|
||||
演示如何在 Python 脚本中使用同步服务
|
||||
"""
|
||||
|
||||
import sys
|
||||
sys.path.append('/home/hermes/projects/data-sync-service/scripts')
|
||||
|
||||
from utils import (
|
||||
upload_data,
|
||||
get_data,
|
||||
delete_data,
|
||||
list_all_tags,
|
||||
load_cookies_from_sync,
|
||||
load_token_from_sync
|
||||
)
|
||||
|
||||
|
||||
def example_upload_cookies():
|
||||
"""示例:上传 Cookie 数据"""
|
||||
|
||||
# 模拟 Cookie 数据
|
||||
cookies_data = {
|
||||
"cookies": [
|
||||
{
|
||||
"name": "session_id",
|
||||
"value": "abc123xyz789",
|
||||
"domain": ".example.com",
|
||||
"path": "/"
|
||||
},
|
||||
{
|
||||
"name": "auth_token",
|
||||
"value": "token_value_here",
|
||||
"domain": ".example.com",
|
||||
"path": "/"
|
||||
}
|
||||
],
|
||||
"raw_cookie": "session_id=abc123xyz789; auth_token=token_value_here",
|
||||
"user_agent": "Mozilla/5.0 ...",
|
||||
"url": "https://example.com"
|
||||
}
|
||||
|
||||
success = upload_data(
|
||||
tag="example:user@example.com",
|
||||
data_type="cookie",
|
||||
data=cookies_data,
|
||||
expires_in=604800, # 7天
|
||||
note="示例账号",
|
||||
login_url="https://example.com/login"
|
||||
)
|
||||
|
||||
if success:
|
||||
print("✅ Cookie 上传成功")
|
||||
else:
|
||||
print("❌ Cookie 上传失败")
|
||||
|
||||
|
||||
def example_upload_token():
|
||||
"""示例:上传 Token 数据"""
|
||||
|
||||
token_data = {
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"token_type": "Bearer",
|
||||
"api_endpoint": "https://api.example.com/v1"
|
||||
}
|
||||
|
||||
success = upload_data(
|
||||
tag="api:service_account",
|
||||
data_type="token",
|
||||
data=token_data,
|
||||
expires_in=2592000, # 30天
|
||||
note="API 服务账号",
|
||||
login_url="https://console.example.com"
|
||||
)
|
||||
|
||||
if success:
|
||||
print("✅ Token 上传成功")
|
||||
else:
|
||||
print("❌ Token 上传失败")
|
||||
|
||||
|
||||
def example_get_data():
|
||||
"""示例:获取数据"""
|
||||
|
||||
data = get_data("example:user@example.com")
|
||||
|
||||
if data:
|
||||
print("✅ 数据获取成功:")
|
||||
print(f" Tag: {data.get('tag')}")
|
||||
print(f" Type: {data.get('type')}")
|
||||
print(f" Created: {data.get('metadata', {}).get('created_at')}")
|
||||
print(f" Expires: {data.get('metadata', {}).get('expires_at')}")
|
||||
else:
|
||||
print("❌ 数据不存在或获取失败")
|
||||
|
||||
|
||||
def example_load_cookies():
|
||||
"""示例:加载 Cookie 数据(便捷方法)"""
|
||||
|
||||
cookies = load_cookies_from_sync("tingwu:ykaayk@qq.com")
|
||||
|
||||
if cookies:
|
||||
print("✅ Cookie 加载成功:")
|
||||
for cookie in cookies:
|
||||
print(f" - {cookie['name']}: {cookie['value'][:20]}...")
|
||||
else:
|
||||
print("❌ Cookie 加载失败")
|
||||
|
||||
|
||||
def example_list_all():
|
||||
"""示例:列出所有 tag"""
|
||||
|
||||
tags = list_all_tags()
|
||||
|
||||
print(f"📋 共有 {len(tags)} 个数据:")
|
||||
for item in tags:
|
||||
status_icon = {
|
||||
"valid": "✅",
|
||||
"expiring_soon": "⚠️",
|
||||
"expired": "❌",
|
||||
"no_expiry": "♾️"
|
||||
}.get(item.get("status", "unknown"), "❓")
|
||||
|
||||
print(f" {status_icon} {item['tag']} ({item['type']})")
|
||||
|
||||
|
||||
def example_delete():
|
||||
"""示例:删除数据"""
|
||||
|
||||
success = delete_data("example:user@example.com")
|
||||
|
||||
if success:
|
||||
print("✅ 数据删除成功")
|
||||
else:
|
||||
print("❌ 数据删除失败")
|
||||
|
||||
|
||||
def example_tingwu_script():
|
||||
"""
|
||||
示例:在通义听悟脚本中使用同步服务
|
||||
替代原有的本地文件读取方式
|
||||
"""
|
||||
|
||||
# 尝试从同步服务加载 Cookie
|
||||
cookies = load_cookies_from_sync("tingwu:ykaayk@qq.com")
|
||||
|
||||
if cookies:
|
||||
print("✅ 从同步服务加载 Cookie 成功")
|
||||
|
||||
# 转换为 requests 可用的格式
|
||||
cookie_dict = {c['name']: c['value'] for c in cookies}
|
||||
|
||||
# 使用 Cookie 发起请求
|
||||
import requests
|
||||
try:
|
||||
response = requests.get(
|
||||
"https://tingwu.aliyun.com/api/some-endpoint",
|
||||
cookies=cookie_dict,
|
||||
timeout=10
|
||||
)
|
||||
print(f" API 调用成功: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f" API 调用失败: {e}")
|
||||
else:
|
||||
print("❌ Cookie 加载失败,请检查同步服务")
|
||||
|
||||
|
||||
def example_with_fallback():
|
||||
"""
|
||||
示例:带降级方案的使用方式
|
||||
优先从同步服务加载,失败时使用本地文件
|
||||
"""
|
||||
|
||||
# 尝试从同步服务加载
|
||||
data = get_data("tingwu:ykaayk@qq.com")
|
||||
|
||||
if data:
|
||||
print("✅ 从同步服务加载成功")
|
||||
cookies = data.get("data", {}).get("cookies", [])
|
||||
else:
|
||||
print("⚠️ 同步服务不可用,使用本地文件")
|
||||
# 降级到本地文件
|
||||
import json
|
||||
try:
|
||||
with open("/path/to/local/cookies.json", 'r') as f:
|
||||
local_data = json.load(f)
|
||||
cookies = local_data.get("cookies", [])
|
||||
except:
|
||||
print("❌ 本地文件也加载失败")
|
||||
cookies = []
|
||||
|
||||
return cookies
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 50)
|
||||
print("通用数据同步服务 - Python 客户端示例")
|
||||
print("=" * 50)
|
||||
|
||||
# 运行示例
|
||||
print("\n1️⃣ 上传 Cookie 数据")
|
||||
example_upload_cookies()
|
||||
|
||||
print("\n2️⃣ 上传 Token 数据")
|
||||
example_upload_token()
|
||||
|
||||
print("\n3️⃣ 获取数据")
|
||||
example_get_data()
|
||||
|
||||
print("\n4️⃣ 列出所有 tag")
|
||||
example_list_all()
|
||||
|
||||
print("\n5️⃣ 加载 Cookie(便捷方法)")
|
||||
example_load_cookies()
|
||||
|
||||
print("\n6️⃣ 通义听悟脚本示例")
|
||||
example_tingwu_script()
|
||||
|
||||
# 删除示例数据(取消注释以执行)
|
||||
# print("\n7️⃣ 删除数据")
|
||||
# example_delete()
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
@@ -0,0 +1,405 @@
|
||||
// ==UserScript==
|
||||
// @name 通用数据同步脚本
|
||||
// @namespace http://tampermonkey.net/
|
||||
// @version 2.0
|
||||
// @description 通用数据同步服务客户端,支持自动检测项目和账号
|
||||
// @author Hermes
|
||||
// @match *://*/*
|
||||
// @grant GM_xmlhttpRequest
|
||||
// @grant GM_setClipboard
|
||||
// @grant GM_notification
|
||||
// @connect 47.122.126.244
|
||||
// ==/UserScript==
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// ==================== 配置区 ====================
|
||||
const CONFIG = {
|
||||
serverUrl: 'http://47.122.126.244:5001',
|
||||
password: 'admin123123123',
|
||||
|
||||
// 自动检测配置
|
||||
autoDetect: true, // 是否自动检测项目名和账号
|
||||
tag: 'auto', // 'auto' 表示自动检测,或手动指定如 'tingwu:ykaayk@qq.com'
|
||||
type: 'cookie', // cookie/token/credential/session/custom
|
||||
|
||||
// 过期配置
|
||||
expiresIn: 604800, // 7天(秒)
|
||||
|
||||
// 其他配置
|
||||
autoSync: false, // 是否自动同步(页面加载后自动上传)
|
||||
syncInterval: 0, // 定时同步间隔(秒,0 表示不定时同步)
|
||||
};
|
||||
|
||||
// ==================== 项目检测规则 ====================
|
||||
const PROJECT_RULES = [
|
||||
{
|
||||
name: 'tingwu',
|
||||
match: (url) => url.includes('tingwu.aliyun.com'),
|
||||
getAccount: () => {
|
||||
// 从页面提取账号信息
|
||||
const emailElement = document.querySelector('[data-account]');
|
||||
if (emailElement) return emailElement.getAttribute('data-account');
|
||||
|
||||
// 从 localStorage 提取
|
||||
try {
|
||||
const userData = JSON.parse(localStorage.getItem('user_data') || '{}');
|
||||
return userData.email || userData.username || 'unknown';
|
||||
} catch {}
|
||||
|
||||
return 'unknown';
|
||||
},
|
||||
loginUrl: 'https://tingwu.aliyun.com/'
|
||||
},
|
||||
{
|
||||
name: 'wechat',
|
||||
match: (url) => url.includes('mp.weixin.qq.com'),
|
||||
getAccount: () => {
|
||||
const accountElement = document.querySelector('.account_info_text');
|
||||
return accountElement ? accountElement.textContent.trim() : 'admin';
|
||||
},
|
||||
loginUrl: 'https://mp.weixin.qq.com/'
|
||||
},
|
||||
// 添加更多项目...
|
||||
];
|
||||
|
||||
// ==================== 核心功能 ====================
|
||||
|
||||
/**
|
||||
* 自动检测项目和账号
|
||||
*/
|
||||
function autoDetectTag() {
|
||||
const url = window.location.href;
|
||||
|
||||
for (const rule of PROJECT_RULES) {
|
||||
if (rule.match(url)) {
|
||||
const account = rule.getAccount();
|
||||
return {
|
||||
tag: `${rule.name}:${account}`,
|
||||
loginUrl: rule.loginUrl,
|
||||
project: rule.name,
|
||||
account: account
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
tag: 'unknown:unknown',
|
||||
loginUrl: window.location.origin,
|
||||
project: 'unknown',
|
||||
account: 'unknown'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前页面的 Cookie 数据
|
||||
*/
|
||||
function getCookieData() {
|
||||
const cookies = document.cookie.split(';').map(c => {
|
||||
const [name, ...valueParts] = c.trim().split('=');
|
||||
return {
|
||||
name: name,
|
||||
value: valueParts.join('='),
|
||||
domain: window.location.hostname,
|
||||
path: '/'
|
||||
};
|
||||
}).filter(c => c.name);
|
||||
|
||||
return {
|
||||
cookies: cookies,
|
||||
raw_cookie: document.cookie,
|
||||
user_agent: navigator.userAgent,
|
||||
url: window.location.href
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传数据到服务器
|
||||
*/
|
||||
function uploadData(tag, type, data, metadata) {
|
||||
const payload = {
|
||||
password: CONFIG.password,
|
||||
tag: tag,
|
||||
type: type,
|
||||
data: data,
|
||||
metadata: metadata
|
||||
};
|
||||
|
||||
GM_xmlhttpRequest({
|
||||
method: 'POST',
|
||||
url: `${CONFIG.serverUrl}/api/data`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(payload),
|
||||
onload: function(response) {
|
||||
if (response.status === 200) {
|
||||
const result = JSON.parse(response.responseText);
|
||||
console.log('✅ 数据同步成功:', result);
|
||||
GM_notification({
|
||||
title: '数据同步成功',
|
||||
text: `Tag: ${tag}\n时间: ${result.timestamp}`,
|
||||
timeout: 3000
|
||||
});
|
||||
} else {
|
||||
console.error('❌ 数据同步失败:', response.responseText);
|
||||
GM_notification({
|
||||
title: '数据同步失败',
|
||||
text: `状态码: ${response.status}`,
|
||||
timeout: 5000
|
||||
});
|
||||
}
|
||||
},
|
||||
onerror: function(error) {
|
||||
console.error('❌ 网络错误:', error);
|
||||
GM_notification({
|
||||
title: '网络错误',
|
||||
text: '无法连接到同步服务器',
|
||||
timeout: 5000
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步当前页面数据
|
||||
*/
|
||||
function syncData() {
|
||||
let tag = CONFIG.tag;
|
||||
let loginUrl = window.location.origin;
|
||||
|
||||
// 自动检测
|
||||
if (CONFIG.autoDetect || tag === 'auto') {
|
||||
const detected = autoDetectTag();
|
||||
tag = detected.tag;
|
||||
loginUrl = detected.loginUrl;
|
||||
|
||||
console.log('🔍 自动检测结果:', detected);
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
let data;
|
||||
if (CONFIG.type === 'cookie') {
|
||||
data = getCookieData();
|
||||
} else {
|
||||
console.error('❌ 不支持的数据类型:', CONFIG.type);
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建元数据
|
||||
const metadata = {
|
||||
expires_in: CONFIG.expiresIn,
|
||||
login_url: loginUrl,
|
||||
note: `自动同步 - ${new Date().toLocaleString()}`
|
||||
};
|
||||
|
||||
// 上传
|
||||
uploadData(tag, CONFIG.type, data, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制配置到剪贴板
|
||||
*/
|
||||
function copyConfig() {
|
||||
let tag = CONFIG.tag;
|
||||
|
||||
if (CONFIG.autoDetect || tag === 'auto') {
|
||||
const detected = autoDetectTag();
|
||||
tag = detected.tag;
|
||||
}
|
||||
|
||||
const config = {
|
||||
serverUrl: CONFIG.serverUrl,
|
||||
password: CONFIG.password,
|
||||
tag: tag,
|
||||
type: CONFIG.type
|
||||
};
|
||||
|
||||
const configText = JSON.stringify(config, null, 2);
|
||||
GM_setClipboard(configText);
|
||||
|
||||
console.log('📋 配置已复制到剪贴板:', config);
|
||||
GM_notification({
|
||||
title: '配置已复制',
|
||||
text: `Tag: ${tag}`,
|
||||
timeout: 2000
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== UI 界面 ====================
|
||||
|
||||
/**
|
||||
* 创建浮动按钮
|
||||
*/
|
||||
function createFloatingButton() {
|
||||
const button = document.createElement('div');
|
||||
button.id = 'data-sync-button';
|
||||
button.innerHTML = '🔄';
|
||||
button.style.cssText = `
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-size: 24px;
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
|
||||
z-index: 10000;
|
||||
transition: all 0.3s;
|
||||
`;
|
||||
|
||||
button.addEventListener('mouseenter', () => {
|
||||
button.style.transform = 'scale(1.1)';
|
||||
});
|
||||
|
||||
button.addEventListener('mouseleave', () => {
|
||||
button.style.transform = 'scale(1)';
|
||||
});
|
||||
|
||||
button.addEventListener('click', () => {
|
||||
showSyncPanel();
|
||||
});
|
||||
|
||||
document.body.appendChild(button);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示同步面板
|
||||
*/
|
||||
function showSyncPanel() {
|
||||
const detected = autoDetectTag();
|
||||
|
||||
const panel = document.createElement('div');
|
||||
panel.id = 'data-sync-panel';
|
||||
panel.innerHTML = `
|
||||
<div style="background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,0.2); max-width: 400px;">
|
||||
<h3 style="margin: 0 0 15px 0;">数据同步</h3>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<strong>项目:</strong> ${detected.project}
|
||||
</div>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<strong>账号:</strong> ${detected.account}
|
||||
</div>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<strong>Tag:</strong> ${detected.tag}
|
||||
</div>
|
||||
<div style="margin-bottom: 15px;">
|
||||
<strong>类型:</strong> ${CONFIG.type}
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<button id="sync-now-btn" style="flex: 1; padding: 10px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;">
|
||||
立即同步
|
||||
</button>
|
||||
<button id="copy-config-btn" style="flex: 1; padding: 10px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer;">
|
||||
复制配置
|
||||
</button>
|
||||
<button id="close-panel-btn" style="padding: 10px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer;">
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
panel.style.cssText = `
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10001;
|
||||
`;
|
||||
|
||||
// 添加背景遮罩
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'data-sync-overlay';
|
||||
overlay.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 10000;
|
||||
`;
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
document.body.appendChild(panel);
|
||||
|
||||
// 绑定事件
|
||||
document.getElementById('sync-now-btn').addEventListener('click', () => {
|
||||
syncData();
|
||||
closePanel();
|
||||
});
|
||||
|
||||
document.getElementById('copy-config-btn').addEventListener('click', () => {
|
||||
copyConfig();
|
||||
});
|
||||
|
||||
document.getElementById('close-panel-btn').addEventListener('click', closePanel);
|
||||
overlay.addEventListener('click', closePanel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭面板
|
||||
*/
|
||||
function closePanel() {
|
||||
const panel = document.getElementById('data-sync-panel');
|
||||
const overlay = document.getElementById('data-sync-overlay');
|
||||
if (panel) panel.remove();
|
||||
if (overlay) overlay.remove();
|
||||
}
|
||||
|
||||
// ==================== 初始化 ====================
|
||||
|
||||
function init() {
|
||||
console.log('🚀 通用数据同步脚本已加载');
|
||||
|
||||
// 创建浮动按钮
|
||||
createFloatingButton();
|
||||
|
||||
// 自动同步
|
||||
if (CONFIG.autoSync) {
|
||||
setTimeout(() => {
|
||||
console.log('⏰ 执行自动同步...');
|
||||
syncData();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 定时同步
|
||||
if (CONFIG.syncInterval > 0) {
|
||||
setInterval(() => {
|
||||
console.log('⏰ 执行定时同步...');
|
||||
syncData();
|
||||
}, CONFIG.syncInterval * 1000);
|
||||
}
|
||||
|
||||
// 快捷键 Ctrl+Shift+S
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.ctrlKey && e.shiftKey && e.key === 'S') {
|
||||
e.preventDefault();
|
||||
syncData();
|
||||
}
|
||||
});
|
||||
|
||||
// 快捷键 Ctrl+Shift+C
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.ctrlKey && e.shiftKey && e.key === 'C') {
|
||||
e.preventDefault();
|
||||
copyConfig();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 等待 DOM 加载完成
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user