空痕博客
首页
小记
php
python
uni
前端
其他
机器人
QQ机器人
APP
KHMD
KHMD-v3
其他页面
友情链接
用户留言
联系空痕
热门文章
PHP搭建QQ机器人(QQ官方)
下载文件到指定文件夹
欢迎回来 Typecho !
UTS引用原生jar包进行原生插件开发
PHP封装功能较全CURL函数
标签搜索
uniapp
python
PHP
APP
KongHen
机器人
QQ
UTS
ID3
pyinstaller
redis
Echarts
模板
邮箱
js
lyear
夸克网盘
发布
登录
空痕博客
最新发布
2025-07-06
上传文件到夸克网盘代码
以下为根据个人需求,参考OpenList让DeepSeek编写的文件上传python脚本 简要说明 包含的功能 创建文件夹 分片上传文件 分享文件夹 获取分享链接 cookie存储在quark.cookie.txt文件中即可 基本变量说明: PARENT_DIR_ID:创建文件夹的父文件夹id,如果在根目录下创建文件夹,输入0 FOLDER_NAME:要创建的文件夹名 FILE_PATH:要上传的文件 上传频繁会被拉黑 脚本代码 import base64 import hashlib import io import json import logging import mimetypes import os import time import requests from datetime import datetime, timezone from typing import Dict, List, Tuple, Optional, Callable # 配置日志 log = logging.getLogger(__name__) logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) try: from colorama import init, Fore, Style init(autoreset=True) COLOR_SUPPORT = True except ImportError: COLOR_SUPPORT = False print("未安装colorama库,将显示无颜色输出。安装命令: pip install colorama") class QuarkOrUC: def __init__(self, cookie: str, parent_dir_id: str): """ 初始化Quark网盘客户端 :param cookie: 完整的认证Cookie字符串 :param parent_dir_id: 默认父目录ID """ self.cookie = cookie self.parent_dir_id = parent_dir_id # 配置信息 self.config = { "api": "https://drive.quark.cn/1/clouddrive", "referer": "https://pan.quark.cn", "origin": "https://pan.quark.cn", "pr": "ucpro", "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36" } def colored_print(self, message: str, color: str = "white") -> None: """带颜色打印消息""" if not COLOR_SUPPORT: print(message) return colors = { "red": Fore.RED, "green": Fore.GREEN, "yellow": Fore.YELLOW, "blue": Fore.BLUE, "cyan": Fore.CYAN, "white": Fore.WHITE, } print(colors.get(color, Fore.WHITE) + message + Style.RESET_ALL) def request(self, pathname: str, method: str, data: Optional[Dict] = None, query_params: Optional[Dict] = None) -> Tuple[Optional[Dict], Optional[Exception]]: """ 发送API请求 :param pathname: API路径 :param method: HTTP方法 (GET/POST) :param data: POST请求的数据 :param query_params: 额外的查询参数 :return: (响应数据, 错误对象) """ url = self.config['api'] + pathname headers = { "Cookie": self.cookie, "Accept": "application/json, text/plain, */*", "Referer": self.config['referer'], "User-Agent": self.config['ua'], "Origin": self.config['origin'], "Priority": "u=1, i" } # 基础查询参数 params = {"pr": self.config['pr'], "fr": "pc"} # 添加额外的查询参数 if query_params: params.update(query_params) try: if method.upper() == "GET": response = requests.get(url, headers=headers, params=params) elif method.upper() == "POST": headers["Content-Type"] = "application/json" response = requests.post(url, headers=headers, params=params, json=data) else: raise ValueError(f"不支持的HTTP方法: {method}") response.raise_for_status() json_resp = response.json() # 更新cookie if '__puus' in response.cookies: self.cookie = f"{self.cookie}; __puus={response.cookies['__puus']}" # 检查错误响应 if json_resp.get('status', 200) >= 400 or json_resp.get('code', 0) != 0: error_msg = json_resp.get('message', '未知错误') log.error(f"API错误: {error_msg}") return None, Exception(error_msg) return json_resp, None except requests.exceptions.RequestException as e: log.error(f"请求失败: {str(e)}") return None, e except Exception as e: log.error(f"处理响应时出错: {str(e)}") return None, e def create_folder(self, folder_name: str) -> Tuple[Optional[Dict], Optional[Exception]]: """ 创建文件夹 :param folder_name: 文件夹名称 :return: (响应数据, 错误对象) """ data = { "pdir_fid": self.parent_dir_id, "file_name": folder_name, "dir_path": "", "dir_init_lock": False } return self.request("/file", "POST", data) def create_share(self, fid_list: List[str], title: str, expired_type: int = 1, url_type: int = 1) -> Tuple[Optional[Dict], Optional[Exception]]: """ 创建分享任务 :param fid_list: 要分享的文件/文件夹ID列表 :param title: 分享标题 :param expired_type: 过期类型 (1: 永久) :param url_type: URL类型 (1: 标准链接) :return: (响应数据, 错误对象) """ data = { "fid_list": fid_list, "title": title, "url_type": url_type, "expired_type": expired_type } return self.request("/share", "POST", data) def check_share_task(self, task_id: str) -> Tuple[Optional[Dict], Optional[Exception]]: """ 检查分享任务状态 :param task_id: 分享任务ID :return: (响应数据, 错误对象) """ query_params = { "task_id": task_id, "retry_index": 0 } return self.request("/task", "GET", query_params=query_params) def get_share_info(self, share_id: str) -> Tuple[Optional[Dict], Optional[Exception]]: """ 获取分享链接信息 :param share_id: 分享ID :return: (响应数据, 错误对象) """ data = {"share_id": share_id} return self.request("/share/password", "POST", data) def up_pre(self, file_name: str, mimetype: str, size: int, parent_id: str) -> Tuple[Optional[Dict], Optional[Exception]]: """ 预上传请求 :param file_name: 文件名 :param mimetype: MIME类型 :param size: 文件大小 :param parent_id: 父目录ID :return: (预上传响应, 错误对象) """ now = int(time.time() * 1000) data = { "ccp_hash_update": True, "dir_name": '', "file_name": file_name, "format_type": mimetype, "l_created_at": now, "l_updated_at": now, "pdir_fid": parent_id, "size": size } return self.request("/file/upload/pre", "POST", data) def up_hash(self, md5_hash: str, sha1_hash: str, task_id: str) -> Tuple[bool, Optional[Exception]]: """ 提交文件哈希验证 :param md5_hash: MD5哈希值 :param sha1_hash: SHA1哈希值 :param task_id: 上传任务ID :return: (是否已完成上传, 错误对象) """ data = { "md5": md5_hash, "sha1": sha1_hash, "task_id": task_id } resp, err = self.request("/file/update/hash", "POST", data) if err: return False, err return (resp.get('data', {}).get('finish', False), None) def up_part(self, pre: Dict, mime_type: str, part_number: int, chunk_data: bytes) -> Tuple[Optional[str], Optional[Exception]]: """ 上传文件分片 :param pre: 预上传响应数据 :param mime_type: MIME类型 :param part_number: 分片编号 :param chunk_data: 分片数据 :return: (ETag, 错误对象) """ # 使用 timezone-aware datetime now = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT') auth_meta = f"PUT\n\n{mime_type}\n{now}\nx-oss-date:{now}\nx-oss-user-agent:aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit\n/{pre['data']['bucket']}/{pre['data']['obj_key']}?partNumber={part_number}&uploadId={pre['data']['upload_id']}" auth_data = { "auth_info": pre['data']['auth_info'], "auth_meta": auth_meta, "task_id": pre['data']['task_id'] } # 获取上传授权 auth_resp, err = self.request("/file/upload/auth", "POST", auth_data) if err: return None, err # 执行分块上传 upload_url = f"https://{pre['data']['bucket']}.{pre['data']['upload_url'][7:]}/{pre['data']['obj_key']}" headers = { "Authorization": auth_resp['data']['auth_key'], "Content-Type": mime_type, "Referer": "https://pan.quark.cn/", "x-oss-date": now, "x-oss-user-agent": "aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit" } params = { "partNumber": str(part_number), "uploadId": pre['data']['upload_id'] } try: response = requests.put( upload_url, headers=headers, params=params, data=chunk_data, timeout=30 ) response.raise_for_status() return response.headers.get('ETag'), None except requests.exceptions.RequestException as e: log.error(f"分片上传失败: {str(e)}") return None, e def up_commit(self, pre: Dict, etags: List[str]) -> Optional[Exception]: """ 提交分片上传完成 :param pre: 预上传响应数据 :param etags: 所有分片的ETag列表 :return: 错误对象 """ # 使用 timezone-aware datetime now = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT') # 构建XML body xml_parts = [] for i, etag in enumerate(etags, 1): xml_parts.append(f"<Part><PartNumber>{i}</PartNumber><ETag>{etag}</ETag></Part>") xml_body = f"""<?xml version="1.0" encoding="UTF-8"?> <CompleteMultipartUpload> {"".join(xml_parts)} </CompleteMultipartUpload>""" # 计算Content-MD5 md5 = hashlib.md5() md5.update(xml_body.encode('utf-8')) content_md5 = base64.b64encode(md5.digest()).decode('utf-8') # 处理回调数据 callback_json = json.dumps(pre['data']['callback']) callback_b64 = base64.b64encode(callback_json.encode('utf-8')).decode('utf-8') # 构建授权元数据 auth_meta = f"POST\n{content_md5}\napplication/xml\n{now}\nx-oss-callback:{callback_b64}\nx-oss-date:{now}\nx-oss-user-agent:aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit\n/{pre['data']['bucket']}/{pre['data']['obj_key']}?uploadId={pre['data']['upload_id']}" auth_data = { "auth_info": pre['data']['auth_info'], "auth_meta": auth_meta, "task_id": pre['data']['task_id'] } # 获取提交授权 auth_resp, err = self.request("/file/upload/auth", "POST", auth_data) if err: return err # 执行提交请求 upload_url = f"https://{pre['data']['bucket']}.{pre['data']['upload_url'][7:]}/{pre['data']['obj_key']}" headers = { "Authorization": auth_resp['data']['auth_key'], "Content-MD5": content_md5, "Content-Type": "application/xml", "Referer": "https://pan.quark.cn/", "x-oss-callback": callback_b64, "x-oss-date": now, "x-oss-user-agent": "aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit" } params = {"uploadId": pre['data']['upload_id']} try: response = requests.post( upload_url, headers=headers, params=params, data=xml_body, timeout=30 ) response.raise_for_status() return None except requests.exceptions.RequestException as e: log.error(f"提交上传失败: {str(e)}") return e def up_finish(self, pre: Dict) -> Optional[Exception]: """ 完成上传流程 :param pre: 预上传响应数据 :return: 错误对象 """ data = { "obj_key": pre['data']['obj_key'], "task_id": pre['data']['task_id'] } _, err = self.request("/file/upload/finish", "POST", data) time.sleep(1) # 等待服务器处理 return err def upload_file(self, file_name: str, file_data: bytes, parent_dir_id: Optional[str] = None, progress_callback: Optional[Callable] = None) -> Tuple[Optional[str], Optional[Exception]]: """ 完整的文件上传流程 :param file_name: 文件名 :param file_data: 文件二进制数据 :param parent_dir_id: 父目录ID(默认为初始化时设置的目录) :param progress_callback: 进度回调函数 :return: (文件ID, 错误对象) - 成功时返回文件ID,失败时返回错误 """ # 使用默认父目录ID(如果未提供) if parent_dir_id is None: parent_dir_id = self.parent_dir_id # 自动检测MIME类型 mime_type, _ = mimetypes.guess_type(file_name) if not mime_type: mime_type = "application/octet-stream" file_size = len(file_data) file_stream = io.BytesIO(file_data) # 步骤1: 计算文件哈希 md5_hash = hashlib.md5() sha1_hash = hashlib.sha1() file_stream.seek(0) # 计算哈希 while True: chunk = file_stream.read(8192) if not chunk: break md5_hash.update(chunk) sha1_hash.update(chunk) md5_hex = md5_hash.hexdigest() sha1_hex = sha1_hash.hexdigest() # 重置文件流指针 file_stream.seek(0) # 步骤2: 预上传请求 pre_resp, err = self.up_pre(file_name, mime_type, file_size, parent_dir_id) if err: return None, f"预上传失败: {str(err)}" # 步骤3: 提交哈希验证 finished, err = self.up_hash(md5_hex, sha1_hex, pre_resp['data']['task_id']) if err: return None, f"哈希验证失败: {str(err)}" if finished: self.colored_print("服务器已有相同文件,跳过上传", "yellow") return None, "服务器已有相同文件" # 步骤4: 分块上传 part_size = pre_resp['metadata']['part_size'] etags = [] part_number = 1 while True: chunk = file_stream.read(part_size) if not chunk: break etag, err = self.up_part( pre_resp, mime_type, part_number, chunk ) if err: return None, f"分片{part_number}上传失败: {str(err)}" etags.append(etag) part_number += 1 # 更新进度 if progress_callback: progress = min(100, int(file_stream.tell() / file_size * 100)) progress_callback(progress) # 步骤5: 提交上传 err = self.up_commit(pre_resp, etags) if err: return None, f"提交上传失败: {str(err)}" # 步骤6: 完成上传 err = self.up_finish(pre_resp) if err: return None, f"完成上传失败: {str(err)}" # 返回上传文件的ID file_id = pre_resp.get('data', {}).get('fid') if not file_id: return None, "未能获取上传文件ID" return file_id, None def load_cookie_from_file(file_path: str) -> str: """从文件加载Cookie""" try: with open(file_path, "r") as f: return f.read().strip() except Exception as e: raise Exception(f"加载Cookie文件失败: {str(e)}") if __name__ == "__main__": # 配置信息 COOKIE_FILE = "cookie.quark.txt" PARENT_DIR_ID = "0" FOLDER_NAME = "新建文件夹名" FILE_PATH = "ce5.zip" # 1. 加载Cookie try: cookie = load_cookie_from_file(COOKIE_FILE) if not cookie: raise Exception("Cookie文件为空") except Exception as e: print(f"❌ 错误: {str(e)}") exit(1) # 创建客户端实例 client = QuarkOrUC(cookie=cookie, parent_dir_id=PARENT_DIR_ID) # 2. 创建文件夹 client.colored_print("\n🟡 正在创建文件夹...", "yellow") create_resp, create_err = client.create_folder(FOLDER_NAME) if create_err: client.colored_print(f"❌ 未创建文件夹失败: {str(create_err)}", "red") exit(1) # 获取新创建的文件夹ID new_folder_id = create_resp.get('data', {}).get('fid') if not new_folder_id: client.colored_print("❌ 未能获取新文件夹ID", "red") exit(1) client.colored_print(f"✅ 文件夹创建成功! ID: {new_folder_id}", "green") # 3. 上传文件到新创建的文件夹 try: if not os.path.exists(FILE_PATH): raise Exception(f"文件不存在: {FILE_PATH}") # 获取文件大小(以字节为单位) file_size_bytes = os.path.getsize(FILE_PATH) file_size_mb = file_size_bytes / (1024 * 1024) with open(FILE_PATH, "rb") as f: file_data = f.read() # 确保读取的文件大小与实际文件大小一致 if len(file_data) != file_size_bytes: raise Exception(f"文件读取不完整: 预期 {file_size_bytes} 字节, 实际读取 {len(file_data)} 字节") # 进度回调函数 def progress_callback(percent): print(f"\r🟢 上传进度: {percent}%", end="", flush=True) file_name = os.path.basename(FILE_PATH) client.colored_print(f"\n🟡 开始上传文件: {file_name} ({file_size_mb:.2f} MB)", "yellow") file_id, upload_err = client.upload_file( file_name=file_name, file_data=file_data, parent_dir_id=new_folder_id, progress_callback=progress_callback ) if upload_err: client.colored_print(f"\n❌ 文件上传失败: {str(upload_err)}", "red") exit(1) else: client.colored_print(f"\n✅ 文件上传成功! 文件ID: {file_id}", "green") # 4. 创建分享任务 client.colored_print("\n🟡 正在创建分享任务...", "yellow") share_resp, share_err = client.create_share( fid_list=[new_folder_id], title=FOLDER_NAME ) if share_err: client.colored_print(f"❌ 创建分享任务失败: {str(share_err)}", "red") exit(1) # 获取分享任务ID task_id = share_resp.get('data', {}).get('task_id') if not task_id: client.colored_print("❌ 未能获取分享任务ID", "red") exit(1) client.colored_print(f"✅ 分享任务创建成功! 任务ID: {task_id}", "green") # 5. 轮询分享任务状态 interval = 5 # 每次间隔5秒 client.colored_print("\n🟡 正在监测分享状态...", "yellow") while True: print("\r进行中...", end="", flush=True) # 检查分享任务状态 task_resp, task_err = client.check_share_task(task_id) if task_err: client.colored_print(f"\n❌ 检查分享任务状态失败: {str(task_err)}", "red") exit(1) task_data = task_resp.get('data', {}) task_status = task_data.get('status') # 状态2表示分享成功 if task_status == 2: share_id = task_data.get('share_id') if not share_id: client.colored_print("\n❌ 分享成功但未获取到share_id", "red") exit(1) # 获取分享链接信息 share_info_resp, share_info_err = client.get_share_info(share_id) if share_info_err: client.colored_print(f"\n❌ 获取分享链接失败: {str(share_info_err)}", "red") exit(1) share_info_data = share_info_resp.get('data', {}) share_url = share_info_data.get('share_url', '') passcode = share_info_data.get('passcode', '') # 注意:实际键名是pwd_id # 如果获取到分享链接,退出循环 if share_url: print("\n", end="") # 清除"进行中..."提示 break # 检查任务是否失败 elif task_status in [3, 4]: # 3: 失败, 4: 取消 client.colored_print(f"\n❌ 分享任务失败: 状态码 {task_status}", "red") exit(1) # 等待下一次检查 time.sleep(interval) # 6. 显示分享结果 client.colored_print("\n✅ 分享创建成功!", "green") client.colored_print(f"📁 分享标题: {FOLDER_NAME}", "cyan") client.colored_print(f"🔗 分享链接: {share_url}", "cyan") if passcode: client.colored_print(f"🔑 提取密码: {passcode}", "cyan") except Exception as e: client.colored_print(f"❌ 操作失败: {str(e)}", "red") exit(1)运行演示 运行演示图片 参考文献 OpenList
所有文章
python
# python
# 夸克网盘
KongHen02
一小时前
0
17
0
2025-06-28
光年模板(V5)侧边栏菜单js修改
主要修改了初始化JSON结构,使菜单层级更清晰。 修改后的代码 // 侧边栏列表 var menu_list = [ { "name": "首页", "url": ["/", "/index/index"], "icon": "mdi mdi-home-variant-outline", "is_out": 0 }, { "name": "链接管理", "url": ["#!"], "icon": "mdi mdi-cookie-outline", "is_out": 0, "children": [ { "name": "链接列表", "url": ["/link/list", "/link/data"], "is_out": 0 }, { "name": "创建链接", "url": ["/link/create", "/link/edit"], "is_out": 0 } ] }, { "name": "财务管理", "url": ["#!"], "icon": "mdi mdi-credit-card-chip-outline", "is_out": 0, "children": [ { "name": "余额充值", "url": ["/finance/recharge"], "is_out": 0 }, { "name": "账单列表", "url": ["/finance/bills"], "is_out": 0 } ] }, { "name": "用户管理", "url": ["/user/list"], "icon": "mdi mdi-account-outline", "is_out": 0, }, { "name": "系统设置", "url": ["#!"], "icon": ["mdi mdi-cog-outline"], "is_out": 0, "children": [ { "name": "个人资料", "url": ["/system/user"], "is_out": 0, }, { "name": "操作日志", "url": ["/system/logs"], "is_out": 0, "children": [ { "name": "账单日志", "url": ["/system/logs/bills"], "is_out": 0, }, { "name": "登录日志", "url": ["/system/logs/login"], "is_out": 0, }, { "name": "链接日志", "url": ["/system/logs/link"], "is_out": 0, } ] } ] }, { "name": "退出登录", "url": ["/user/logout"], "icon": "mdi mdi-location-exit", "is_out": 0 } ]; setSidebar(menu_list); /** * 菜单 * @param data 菜单JSON数据 * name 菜单名称 string * url 菜单链接地址 array 首个为跳转地址。当路由为其中一个时,激活当前菜单选中状态。 * icon 图标 * is_out 是否外链0否|1是 int 外链a标签没有class='multitabs' * children 子菜单 array */ function setSidebar(data) { if (data.length == 0) return false; processMenu(data); console.log(data); html = createMenu(data, true); $('.sidebar-main').append(html); } // 创建html数据 function createMenu(data, is_frist) { var menu_body = is_frist ? '<ul class="nav-drawer">' : '<ul class="nav nav-subnav">'; for (var i = 0; i < data.length; i++) { iframe_class = data[i].is_out == 1 ? 'target="_blank"' : 'class="multitabs"'; icon_div = data[i].is_root == 1 ? '<i class="' + data[i].icon + '"></i>' : ''; menuName = data[i].is_root == 1 ? '<span>' + data[i].name + '</span>' : data[i].name; if (data[i].children && data[i].children.length > 0) { selected = data[i].is_active == 1 ? 'active open' : ''; menu_body += '<li class="nav-item nav-item-has-subnav ' + selected + '"><a href="javascript:void(0)">' + icon_div + menuName + '</a>'; menu_body += createMenu(data[i].children); } else { selected = data[i].is_active == 1 ? 'active' : ''; menu_body += '<li class="nav-item ' + selected + '"><a href="' + data[i].url[0] + '" ' + iframe_class + '>' + icon_div + menuName + '</a>'; } menu_body += '</li>'; } menu_body += '</ul>'; return menu_body; }; // 添加 is_active 和 is_root 属性到所有层级 function addProperties(menu) { menu.forEach(item => { item.is_active = 0; item.is_root = 0; if (item.children && item.children.length > 0) { addProperties(item.children); } }); } // 标记激活状态和父级路径 function markActivePath(menu, currentPath, parentChain = []) { for (let i = 0; i < menu.length; i++) { const item = menu[i]; const newParentChain = [...parentChain, item]; // 检查当前路径是否匹配 const isMatch = item.url.some(url => url === currentPath); if (isMatch) { // 标记当前节点为激活 item.is_active = 1; // 标记整个父链为激活 newParentChain.forEach(node => { node.is_active = 1; node.is_root = 1; }); return true; // 找到匹配路径 } // 递归检查子节点 if (item.children && item.children.length > 0) { const foundInChildren = markActivePath(item.children, currentPath, newParentChain); if (foundInChildren) { // 标记当前节点为激活(因为子节点已激活) item.is_active = 1; item.is_root = 1; return true; } } } return false; // 当前分支未找到匹配 } // 主处理函数 function processMenu(menu) { // 1. 添加初始属性 addProperties(menu); // 2. 标记第一层级为根节点 menu.forEach(item => { item.is_root = 1; }); // 3. 获取当前页面路径 const currentPath = window.location.pathname; // 4. 标记激活路径 markActivePath(menu, currentPath); }光年模板原代码 var menu_list = [ { "id": "1", "name": "后台首页", "url": "lyear_main_1_v5.html", "pid": 0, "icon": "mdi mdi-home", "is_out": 0, "is_home": 1 }, { "id": "2", "name": "布局示例", "url": "#!", "pid": 0, "icon": "mdi mdi-palette", "is_out": 0, "is_home": 0 }, { "id": "3", "name": "表单布局示例", "url": "lyear_layout_form.html", "pid": 2, "icon": "", "is_out": 0, "is_home": 0 }, { "id": "4", "name": "聊天页面示例", "url": "lyear_layout_chat.html", "pid": 2, "icon": "", "is_out": 0, "is_home": 0 }, { "id": "5", "name": "logo处使用文字", "url": "lyear_layout_logo_text.html", "pid": 2, "icon": "", "is_out": 1, "is_home": 0 }, { "id": "6", "name": "多级菜单", "url": "#!", "pid": 0, "icon": "mdi mdi-menu", "is_out": 0, "is_home": 0 }, { "id": "7", "name": "一级菜单", "url": "#!", "pid": 6, "icon": "", "is_out": 0, "is_home": 0 }, { "id": "8", "name": "一级菜单", "url": "#!", "pid": 6, "icon": "", "is_out": 0, "is_home": 0 }, { "id": "9", "name": "二级菜单", "url": "#!", "pid": 8, "icon": "", "is_out": 0, "is_home": 0 }, { "id": "10", "name": "二级菜单", "url": "#!", "pid": 8, "icon": "", "is_out": 0, "is_home": 0 }, { "id": "11", "name": "三级菜单", "url": "#!", "pid": 10, "icon": "", "is_out": 0, "is_home": 0 }, { "id": "12", "name": "三级菜单", "url": "#!", "pid": 10, "icon": "", "is_out": 0, "is_home": 0 } ]; setSidebar(menu_list); /** * 菜单 * @param data 菜单JSON数据 * id 菜单唯一ID * name 菜单名称 * url 菜单链接地址 * icon 图标 * pid 父级ID * is_out 是否外链0否|1是,外链a标签没有class='multitabs' * is_home 是否首页 */ function setSidebar(data){ if (data.length == 0) return false; var treeObj = getTrees(data, 0, 'id', 'pid', 'children'); html = createMenu(treeObj, true); $('.sidebar-main').append(html); } function createMenu(data, is_frist) { var menu_body = is_frist ? '<ul class="nav-drawer">' : '<ul class="nav nav-subnav">'; for(var i = 0; i < data.length; i++){ iframe_class = data[i].is_out == 1 ? 'target="_blank"' : 'class="multitabs"'; icon_div = data[i].pid == 0 ? '<i class="' + data[i].icon + '"></i>' : ''; selected = (data[i].pid == 0) && (data[i].is_home == 1) ? 'active' : ''; menuName = data[i].pid == 0 ? '<span>' + data[i].name + '</span>' : data[i].name; homeIdName = (data[i].pid == 0) && (data[i].is_home == 1) ? 'id="default-page"' : ''; if (data[i].children && data[i].children.length > 0) { menu_body += '<li class="nav-item nav-item-has-subnav"><a href="javascript:void(0)">' + icon_div + menuName + '</a>'; menu_body += createMenu(data[i].children); } else { menu_body += '<li class="nav-item ' + selected + '"><a href="' + data[i].url + '" '+ iframe_class + homeIdName + '>' + icon_div + menuName + '</a>'; } menu_body += '</li>'; } menu_body += '</ul>'; return menu_body; }; /** * 树状的算法 * @params list 代转化数组 * @params parentId 起始节点 * @params idName 主键ID名 * @params parentIdName 父级ID名称 * @params childrenName 子级名称 * @author CSDN博主「伤包子」 */ function getTrees(list, parentId, idName, parentIdName, childrenName) { let items= {}; // 获取每个节点的直属子节点,*记住是直属,不是所有子节点 for (let i = 0; i < list.length; i++) { let key = list[i][parentIdName]; if (items[key]) { items[key].push(list[i]); } else { items[key] = []; items[key].push(list[i]); } } return formatTree(items, parentId, idName, childrenName); } /** * 利用递归格式化每个节点 */ function formatTree(items, parentId, idName, childrenName) { let result = []; if (!items[parentId]) { return result; } for (let t in items[parentId]) { items[parentId][t][childrenName] = formatTree(items, items[parentId][t][idName], idName, childrenName) result.push(items[parentId][t]); } return result; }参考文献 侧边栏菜单的JS初始化 - 光年模板(V5)
前端
# js
# lyear
KongHen02
6月28日
0
10
0
2025-06-27
邮箱验证码html模板
适配大屏和小屏 问题:fa图标在邮件中不显示,请自行替换成图片 演示图片 大屏演示图片 代码 <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>用户注册 - Xcode验证码</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> /* 基础样式重置 */ * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif; } body { background: linear-gradient(135deg, #f5f7fa 0%, #e4edfb 100%); min-height: 100vh; display: flex; justify-content: center; align-items: center; padding: 30px; letter-spacing: 0.5px; } /* 亮色毛玻璃效果容器 */ .glass-container { background: rgba(255, 255, 255, 0.85); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border-radius: 24px; border: 1px solid rgba(255, 255, 255, 0.8); box-shadow: 0 12px 40px rgba(98, 131, 252, 0.15), 0 4px 20px rgba(98, 131, 252, 0.08); width: 100%; max-width: 780px; overflow: hidden; padding: 50px 40px; position: relative; transition: all 0.4s ease; } .glass-container:hover { box-shadow: 0 15px 50px rgba(98, 131, 252, 0.2), 0 6px 25px rgba(98, 131, 252, 0.12); transform: translateY(-5px); } /* 装饰元素 */ .decoration { position: absolute; border-radius: 50%; background: linear-gradient(135deg, rgba(67, 203, 255, 0.15) 0%, rgba(151, 8, 204, 0.1) 100%); z-index: -1; opacity: 0.7; } .decoration-1 { width: 180px; height: 180px; top: -60px; left: -60px; animation: float 8s infinite ease-in-out; } .decoration-2 { width: 120px; height: 120px; bottom: 30px; right: 30px; animation: float 10s infinite ease-in-out; animation-delay: 1s; } .decoration-3 { width: 90px; height: 90px; top: 30%; right: -30px; animation: float 12s infinite ease-in-out; animation-delay: 2s; } @keyframes float { 0%, 100% { transform: translate(0, 0); } 25% { transform: translate(-10px, 15px); } 50% { transform: translate(5px, -10px); } 75% { transform: translate(10px, 5px); } } /* 头部样式 */ .header { text-align: center; margin-bottom: 40px; position: relative; } .logo { display: flex; justify-content: center; align-items: center; margin-bottom: 25px; } .logo-icon { width: 65px; height: 65px; background: linear-gradient(135deg, #43CBFF 0%, #9708CC 100%); border-radius: 18px; display: flex; justify-content: center; align-items: center; margin-right: 18px; box-shadow: 0 8px 20px rgba(151, 8, 204, 0.25); transition: all 0.3s ease; } .logo-icon:hover { transform: rotate(10deg) scale(1.05); } .logo-icon img { margin: 20%; width: 60%; height: 60%; filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1)); } .logo-text { font-size: 32px; font-weight: 700; background: linear-gradient(135deg, #2c3e50 0%, #4a6491 100%); -webkit-background-clip: text; background-clip: text; color: transparent; letter-spacing: 1.2px; } .title { font-size: 28px; font-weight: 700; color: #2c3e50; margin-bottom: 30px; position: relative; display: inline-block; } .title::after { content: ""; position: absolute; bottom: -8px; left: 50%; transform: translateX(-50%); width: 60px; height: 4px; background: linear-gradient(135deg, #43CBFF 0%, #9708CC 100%); border-radius: 2px; } .subtitle { font-size: 18px; color: #5a6a85; line-height: 1.7; margin: 0 auto; /* max-width: 600px; */ padding: 0 20px; } /* 验证码区域 - 亮色系突出 */ .verification-section { background: rgba(255, 255, 255, 0.95); border-radius: 20px; padding: 40px 30px; margin: 40px 0; text-align: center; backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); border: 1px solid rgba(255, 255, 255, 0.9); box-shadow: 0 10px 30px rgba(98, 131, 252, 0.1), inset 0 0 15px rgba(151, 8, 204, 0.05); position: relative; overflow: hidden; transition: all 0.3s ease; } .verification-section::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 5px; background: linear-gradient(135deg, #43CBFF 0%, #9708CC 100%); } .verification-section:hover { transform: translateY(-5px); box-shadow: 0 15px 40px rgba(98, 131, 252, 0.15), inset 0 0 20px rgba(151, 8, 204, 0.08); } .verification-label { font-size: 18px; color: #5a6a85; margin-bottom: 20px; font-weight: 500; } .verification-code { width: 100%; font-size: 56px; font-weight: 800; letter-spacing: 10px; background: linear-gradient(135deg, #2c3e50 0%, #4a6491 100%); -webkit-background-clip: text; background-clip: text; color: transparent; padding: 20px 40px; border-radius: 16px; margin: 20px 0; display: inline-block; box-shadow: inset 0 0 25px rgba(98, 131, 252, 0.1), 0 8px 20px rgba(98, 131, 252, 0.1); border: 1px solid rgba(98, 131, 252, 0.15); text-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); transition: all 0.3s ease; } .verification-code:hover { letter-spacing: 12px; box-shadow: inset 0 0 30px rgba(98, 131, 252, 0.15), 0 10px 25px rgba(98, 131, 252, 0.15); } .expiration { font-size: 16px; color: #7a8ca5; margin-top: 20px; display: flex; align-items: center; justify-content: center; gap: 10px; } .expiration i { color: #ff6b6b; } /* 操作区域 */ .action-section { text-align: center; margin-top: 50px; } .action-button { background: linear-gradient(135deg, #43CBFF 0%, #9708CC 100%); color: white; border: none; padding: 18px 50px; font-size: 20px; font-weight: 600; border-radius: 60px; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 8px 25px rgba(151, 8, 204, 0.3); text-decoration: none; display: inline-flex; align-items: center; gap: 12px; position: relative; overflow: hidden; } .action-button::before { content: ""; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent); transition: 0.5s; } .action-button:hover::before { left: 100%; } .action-button:hover { transform: translateY(-5px) scale(1.05); box-shadow: 0 12px 35px rgba(151, 8, 204, 0.4); } .action-button:active { transform: translateY(0) scale(0.98); } .copyright-link { color: #7a8ca5; text-decoration: none; transition: all 0.3s ease; } .copyright-link:hover { color: #9708CC; text-decoration: underline; } /* 底部信息 */ .footer { text-align: center; margin-top: 50px; color: #7a8ca5; font-size: 15px; line-height: 1.7; } .footer p { margin-bottom: 8px; } .social-icons { display: flex; justify-content: center; gap: 20px; margin-top: 25px; } .social-icons a { width: 45px; height: 45px; border-radius: 50%; background: rgba(98, 131, 252, 0.1); display: flex; align-items: center; justify-content: center; color: #5a6a85; font-size: 18px; transition: all 0.3s ease; } .social-icons a:hover { background: linear-gradient(135deg, #43CBFF 0%, #9708CC 100%); color: white; transform: translateY(-5px); } /* 响应式设计 */ @media (max-width: 768px) { .glass-container { padding: 40px 30px; max-width: 100%; } .logo-icon { width: 55px; height: 55px; } .logo-text { font-size: 28px; } .title { font-size: 24px; } .subtitle { font-size: 16px; } .verification-section { padding: 30px 20px; } .verification-code { font-size: 42px; padding: 15px 30px; letter-spacing: 8px; } .action-button { padding: 16px 40px; font-size: 18px; } } @media (max-width: 480px) { .glass-container { padding: 30px 20px; } .logo { flex-direction: column; gap: 15px; } .logo-icon { margin-right: 0; } .logo-text { font-size: 26px; } .title { font-size: 22px; } .verification-section { padding: 25px 15px; } .verification-code { font-size: 36px; padding: 12px 20px; letter-spacing: 6px; } .action-button { padding: 14px 35px; font-size: 16px; } } </style> </head> <body> <div class="glass-container"> <!-- 装饰元素 --> <div class="decoration decoration-1"></div> <div class="decoration decoration-2"></div> <div class="decoration decoration-3"></div> <div class="header"> <div class="logo"> <div class="logo-icon"> <img src="http://xma.dev.khkj.xyz/static/images/logo.png" alt="Xcode Logo"> </div> <div class="logo-text">Xcode - X码</div> </div> <h1 class="title">用户注册验证码</h1> <p class="subtitle">感谢您注册Xcode平台!请使用以下验证码完成账户验证,验证码将在 <text style="color:red;">6</text> 分钟后失效。</p> </div> <div class="verification-section"> <div class="verification-label">您的验证码</div> <div class="verification-code">843721</div> <div class="expiration"> <i class="fas fa-clock"></i>有效期至: 2025年6月27日 15:30 </div> </div> <div class="action-section"> <a href="https://xma.run" class="action-button"> <i class="fas fa-arrow-right"></i>前往 X码 平台 </a> </div> <div class="footer"> <p>此邮件由系统自动发送,请勿直接回复</p> <p>如果您未进行此操作,请忽略此邮件</p> <p>© 2025 <a href="https://xma.run" class="copyright-link">Xcode - X码</a> 版权所有</p> <div class="social-icons"> <a href="#"><i class="fab fa-weixin"></i></a> <a href="#"><i class="fab fa-qq"></i></a> <a href="#"><i class="fab fa-weibo"></i></a> <a href="#"><i class="fab fa-github"></i></a> </div> </div> </div> <script> // 添加简单的交互效果 document.addEventListener('DOMContentLoaded', function() { const verificationCode = document.querySelector('.verification-code'); // 添加点击复制功能 verificationCode.addEventListener('click', function() { const text = this.innerText; const tempInput = document.createElement('input'); tempInput.value = text; document.body.appendChild(tempInput); tempInput.select(); document.execCommand('copy'); document.body.removeChild(tempInput); alert("复制成功"); }); }); </script> </body> </html>
其他
# 模板
# 邮箱
KongHen02
6月27日
0
15
0
2025-06-22
Echarts绘制中国地图并将南海诸岛化为简图
下载中国地图数据 下载地址:DataV.GeoAtlas地理小工具 绘制演示 绘制演示图片 绘制地图 显示南海诸岛简图 地图绘制时设置`china`有南海诸岛简图,设置`China`则没有简图 echarts.registerMap('china', usaJson);隐藏海南诸岛边界线 搜索100000_JD,删除图中的数据片段 隐藏南海诸岛边界线代码位置图片 隐藏南海诸岛 搜索海南,修改海南省的数据,将图中的数据修改为以下数据 南海诸岛数据修改位置图片 "coordinates": [ [ [ [ 110.106396, 20.026812 ], [ 110.042339, 19.991384 ], [ 109.997375, 19.980136 ], [ 109.965346, 19.993634 ], [ 109.898825, 19.994196 ], [ 109.855093, 19.984073 ], [ 109.814441, 19.993072 ], [ 109.76147, 19.981261 ], [ 109.712195, 20.017253 ], [ 109.657993, 20.01163 ], [ 109.585312, 19.98801 ], [ 109.526797, 19.943573 ], [ 109.498464, 19.873236 ], [ 109.411001, 19.895184 ], [ 109.349407, 19.898561 ], [ 109.300748, 19.917693 ], [ 109.25948, 19.898561 ], [ 109.255784, 19.867045 ], [ 109.231147, 19.863105 ], [ 109.159082, 19.79048 ], [ 109.169553, 19.736411 ], [ 109.147379, 19.704863 ], [ 109.093792, 19.68965 ], [ 109.048829, 19.619764 ], [ 108.993394, 19.587065 ], [ 108.92872, 19.524468 ], [ 108.855424, 19.469182 ], [ 108.806148, 19.450561 ], [ 108.765496, 19.400894 ], [ 108.694047, 19.387346 ], [ 108.644772, 19.349518 ], [ 108.609048, 19.276661 ], [ 108.591186, 19.141592 ], [ 108.598577, 19.055633 ], [ 108.630606, 19.003017 ], [ 108.637997, 18.924346 ], [ 108.595497, 18.872256 ], [ 108.593033, 18.809386 ], [ 108.65278, 18.740258 ], [ 108.663866, 18.67337 ], [ 108.641077, 18.565614 ], [ 108.644772, 18.486738 ], [ 108.68912, 18.447571 ], [ 108.776583, 18.441894 ], [ 108.881293, 18.416344 ], [ 108.905315, 18.389087 ], [ 108.944735, 18.314107 ], [ 109.006329, 18.323198 ], [ 109.108575, 18.323766 ], [ 109.138756, 18.268081 ], [ 109.17448, 18.260125 ], [ 109.287813, 18.264671 ], [ 109.355566, 18.215221 ], [ 109.441182, 18.199303 ], [ 109.467051, 18.173718 ], [ 109.527413, 18.169169 ], [ 109.584696, 18.143579 ], [ 109.661688, 18.175424 ], [ 109.726362, 18.177698 ], [ 109.749767, 18.193618 ], [ 109.785492, 18.339672 ], [ 109.919767, 18.375457 ], [ 110.022629, 18.360121 ], [ 110.070672, 18.376025 ], [ 110.090382, 18.399309 ], [ 110.116867, 18.506602 ], [ 110.214186, 18.578662 ], [ 110.246215, 18.609859 ], [ 110.329366, 18.642185 ], [ 110.367555, 18.631977 ], [ 110.499366, 18.651824 ], [ 110.499366, 18.751592 ], [ 110.578206, 18.784458 ], [ 110.590525, 18.838841 ], [ 110.585597, 18.88075 ], [ 110.619474, 19.152334 ], [ 110.676756, 19.286264 ], [ 110.706321, 19.320153 ], [ 110.729727, 19.378878 ], [ 110.787009, 19.399765 ], [ 110.844292, 19.449996 ], [ 110.888023, 19.518827 ], [ 110.920668, 19.552668 ], [ 111.008747, 19.60398 ], [ 111.061718, 19.612436 ], [ 111.071573, 19.628784 ], [ 111.043856, 19.763448 ], [ 111.013675, 19.850159 ], [ 110.966248, 20.018377 ], [ 110.940994, 20.028499 ], [ 110.871393, 20.01163 ], [ 110.808567, 20.035808 ], [ 110.778386, 20.068415 ], [ 110.744509, 20.074036 ], [ 110.717408, 20.148778 ], [ 110.687843, 20.163947 ], [ 110.655814, 20.134169 ], [ 110.562191, 20.110006 ], [ 110.526467, 20.07516 ], [ 110.495054, 20.077408 ], [ 110.387265, 20.113378 ], [ 110.318279, 20.108882 ], [ 110.28933, 20.056047 ], [ 110.243135, 20.077408 ], [ 110.144585, 20.074598 ], [ 110.106396, 20.026812 ] ] ]下载修改后的完整文件 下载完整文件 下载地址:https://www.khkj6.com/usr/uploads/2025/06/3432059456.json 提取码: 参考文档 vue echarts 中国地图处理南海诸岛为简图-CSDN
所有文章
前端
# Echarts
KongHen02
6月22日
0
11
0
2025-02-20
php利用redis实现接口数据缓存
php利用redis实现服务端数据缓存。 要求php版本大于8.0 缓存类 文件RedisCache.php <?php /** * 请求缓存类 * 利用redis实现数据缓存 * * 作者:KongHen02 * */ class RedisCache { private string $key; private Redis $redis; public function __construct(int $db = 0, bool $device = false) { $config = [ 'host' => '127.0.0.1', 'port' => 6379, 'timeout' => 2, 'database' => $db ]; try { $this->redis = new Redis(); $this->redis->connect( $config['host'], $config['port'], $config['timeout'] ); $this->redis->select($config['database']); } catch (RedisException $e) { error_log("Redis连接失败: " . $e->getMessage()); throw new RuntimeException("缓存服务不可用"); } $this->key = $this->generateKey($device); } public function getCache(): mixed { try { header('X-Cache: ' . ($this->redis->exists($this->key) ? 'HIT' : 'MISS')); $data = $this->redis->get($this->key); return $data !== false ? unserialize($data) : null; } catch (RedisException $e) { error_log("读取缓存数据失败: " . $e->getMessage()); return null; } } public function setCache(mixed $data, int $ttl = 7200): bool { try { return $this->redis->setex( $this->key, $ttl, serialize($data) ); } catch (RedisException $e) { error_log("写入缓存数据失败: " . $e->getMessage()); return false; } } public function __destruct() { try { $this->redis?->close(); } catch (RedisException $e) { error_log("关闭Redis连接失败: " . $e->getMessage()); } } private function generateKey(bool $device): string { $url = $_SERVER['HTTP_HOST']; $urlArray = explode("?", $_SERVER['REQUEST_URI']); $url .= $urlArray[0]; $params = $_POST; ksort($params); $keyParts = [ $_SERVER['REQUEST_METHOD'], $device ? $this->detectDevice() : null, $url, substr(md5($urlArray[1]), 0, 8), substr(md5(http_build_query($params)), 0, 8) ]; return implode(':', array_filter($keyParts)); } private function detectDevice(): string { $ua = strtolower($_SERVER['HTTP_USER_AGENT'] ?? ''); return match(true) { (bool)preg_match('/mobile|android|iphone|ipod|windows phone/', $ua) => 'mobile', (bool)preg_match('/tablet|ipad|kindle/', $ua) => 'tablet', default => 'desktop' }; } } ?>使用说明 <?php // 导入缓存类 require_once($_SERVER['DOCUMENT_ROOT'] . "/RedisCache.php"); try { // 0:redis表序;0-15;必填 // true:是否区分客户端类型(手机/平板/电脑);true/false(默认);可空 $RedisCache = new RedisCache(0, true); if (($result = $RedisCache->getCache()) !== null) { exit($result); } } catch (RuntimeException $e) { // 缓存服务不可用时继续执行 header('X-Cache-Status: Down'); } // 业务逻辑处理 $result = "数据内容,不限制类型"; // 缓存成功结果 if (isset($result)) { // $result:需要缓存的数据;mixed;必填 // 7200:数据有效期;int(默认7200);可空 $RedisCache?->setCache($result, 7200); } echo($result); ?>
所有文章
PHP
# PHP
# redis
KongHen02
2月20日
6
2
0
1
2
3
下一页
易航博客
免费API
笒鬼鬼
易航博客