普通视图

更新维护Typecho “简单作品展示“模板

2026年1月29日 16:27

这不是Typecho正式发布了1.3.0版本后,有些代码会有所出入,今天就把以前的旧版本主题模板升级维护了一下。
关于这款主题不只是简单的代码升级维护,也对以前的UI样式问题做出了一些调整和修复。
至于下载地址,等后期视频制作完成后以前上传分享。

  •  

Typecho1.3.0关于如何获取分类的MID

2026年1月29日 14:46

cover_1769669488763.jpg
在 Typecho 1.1 中可以通过这样的方式获取

<?php 
$this->_pageRow['mid'];
?>

如果是 Typecho 1.2,可以通过这样的方式获取

<?php 
$this->pageRow['mid'];
?>

如果是 Typecho 1.3,可以通过这样的方式获取

<?php 
$this->pageRow->mid;
?>
  •  

使用php对emoji表情进行编码和解码源码教程

2026年1月8日 09:37
本篇文章主要用于不修改数据库编码,可以支持emoji 表情显示支持。

请输入图片描述
emoji_encode():将字符串中的 emoji 表情(4 字节 UTF-8 字符)编码为 十进制数字; 的实体格式,普通字符保持不变。
emoji_decode():将上述编码格式的 emoji 实体还原为原始的 emoji 表情字符。
整体作用是解决 emoji 表情在传输 / 存储中可能出现的乱码问题,通过实体编码的方式兼容不同系统对 emoji 的处理。

使用方法
// 测试示例
$original = "你dddfakjdsfudsadsafsd(由于博客不支持emoji 表情这里就不添加了,要测试自行添加emoji查看效果)";
$encoded = emoji_encode($original);
$decoded = emoji_decode($encoded);

echo "原始字符串: " . $original . "\n";
echo "编码后: " . $encoded . "\n";
echo "解码后: " . $decoded . "\n";
  •  

php自动获取favicon源代码(支持ico,png,svg等格式)教程

2026年1月7日 10:01
功能介绍

该工具是一款零依赖、极致性能的 PHP 版网站 favicon(网站图标)在线获取工具,专为追求高性能、低资源消耗的场景设计,核心功能与特性如下:
1. 核心功能
多路径智能获取:优先尝试网站根目录下最常用的 favicon.ico 和 favicon.png 通用路径,获取失败后自动解析网站 HTML 源码,提取 标签中的精准图标地址,确保获取成功率。
轻量化输出:直接返回 favicon 的二进制内容,无任何冗余封装,可按需输出为图片响应、写入文件或进行其他处理。
URL 自动兼容:支持传入带 / 不带协议的 URL(如 www.baidu.com 或 https://www.baidu.com),自动补全 HTTPS 协议并解析域名,无需额外预处理。
2. 核心技术特性
零第三方依赖:仅依赖 PHP 内置 curl 扩展(PHP 标配组件),无任何外部库、框架或配置文件依赖,可直接集成使用。

极致性能与效率:

短路逻辑:找到有效 favicon 立即终止流程,无冗余执行;
极致超时控制:3 秒总超时 + 1 秒连接超时,避免无效等待;
精简网络传输:不获取响应头、固定 HTTP 版本,减少网络损耗。

零冗余资源占用:

即时内存释放:每步操作后销毁无用变量,无冗余内存占用;
无缓存 / 无磁盘 IO:不写入缓存文件,仅在运行时加载必要数据;
按需加载:仅通用路径获取失败后才执行 HTML 解析,懒执行无预加载损耗。
低耦合设计:核心功能拆分为两个独立纯函数,无全局变量、无耦合依赖,可单独调用、按需集成,适配任意业务场景。
3. 适用场景
高性能网址导航、书签类系统;
对资源消耗、响应速度有严苛要求的后端服务;
轻量级 PHP 项目(如微服务、接口)的 favicon 获取需求;
需低耦合集成 favicon 获取功能的现有系统。

精简项目代码
<?php
/**
 * 极简版Favicon获取函数(零依赖、极致性能、零冗余)
 * @param string $url 目标网站URL
 * @return string|false 成功返回favicon内容(二进制),失败返回false
 */
function get_favicon(string $url): string|false {
    // 步骤1:标准化URL(仅保留核心逻辑,无冗余判断)
    $url = trim($url);
    if (!preg_match('/^https?:\/\//i', $url)) {
        $url = 'https://' . $url; // 优先HTTPS,无冗余的HTTP回退(极简策略)
    }
    
    $parsed = parse_url($url);
    if (!isset($parsed['scheme'], $parsed['host'])) {
        return false;
    }
    $scheme = $parsed['scheme'];
    $host = $parsed['host'];
    
    // 步骤2:尝试通用路径(按需加载,找到即返回,零冗余循环)
    $paths = ['/favicon.ico', '/favicon.png']; // 仅保留最常用的2种格式,极致效率
    foreach ($paths as $path) {
        $favicon_url = "{$scheme}://{$host}{$path}";
        $content = fetch_url_content($favicon_url);
        if ($content !== false) {
            return $content; // 短路返回,无任何冗余操作
        }
        unset($content); // 即时释放内存,零冗余占用
    }
    
    // 步骤3:解析HTML(仅必要时执行,按需加载)
    $html_url = "{$scheme}://{$host}";
    $html = fetch_url_content($html_url);
    if ($html === false) {
        unset($html, $scheme, $host, $html_url); // 释放所有变量
        return false;
    }
    
    // 极简正则提取icon(无冗余分组,仅保留核心匹配)
    if (preg_match('/<link[^>]*rel=["\']?icon["\']?[^>]*href=["\']([^"\']+)["\']/i', $html, $matches)) {
        $icon_path = $matches[1];
        unset($html, $matches); // 即时释放大变量
        
        // 路径补全(极简逻辑,无冗余判断)
        if (str_starts_with($icon_path, 'http')) {
            $icon_url = $icon_path;
        } elseif (str_starts_with($icon_path, '/')) {
            $icon_url = "{$scheme}://{$host}{$icon_path}";
        } else {
            $icon_url = "{$scheme}://{$host}/{$icon_path}";
        }
        
        $content = fetch_url_content($icon_url);
        if ($content !== false) {
            unset($scheme, $host, $icon_path, $icon_url); // 释放变量
            return $content;
        }
        unset($content);
    }
    
    // 最终清理,零冗余内存占用
    unset($scheme, $host, $html, $html_url);
    return false;
}

/**
 * 极简URL内容获取函数(零冗余、极致性能)
 * @param string $url 目标URL
 * @return string|false 二进制内容或false
 */
function fetch_url_content(string $url): string|false {
    $ch = curl_init($url);
    if (!$ch) {
        return false;
    }
    
    // 仅设置必要选项,无任何冗余配置
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_TIMEOUT => 3, // 极致超时(减少等待损耗)
        CURLOPT_CONNECTTIMEOUT => 1,
        CURLOPT_USERAGENT => 'Mozilla/5.0',
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false,
        CURLOPT_NOBODY => false,
        CURLOPT_HEADER => false, // 不获取响应头,减少数据传输
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, // 固定HTTP版本,减少协商损耗
    ]);
    
    $content = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch); // 即时关闭句柄,释放资源
    unset($ch); // 释放句柄变量
    
    // 仅保留核心判断,无冗余逻辑
    if ($http_code !== 200 || empty($content)) {
        unset($content, $http_code);
        return false;
    }
    
    return $content;
}

// ===================== 极简使用示例(按需调用,零冗余) =====================
// 调用示例(仅在需要时执行,按需加载)
// $favicon = get_favicon('https://www.baidu.com');
// if ($favicon) {
//     header('Content-Type: image/x-icon');
//     echo $favicon;
//     unset($favicon); // 即时释放内存
// }
?>
  •  

php高性能(反正AI是这么说的)PSR-4规范自动加载器

2025年12月31日 22:01

Core\Loader 类 详细开发文档

一、类核心概述

1. 类定位

Core\Loader 是一款高性能PSR-4规范自动加载器,核心用于PHP项目中类、接口、特质的自动加载,无需手动引入require/include,遵循PHP FIG制定的PSR-4自动加载标准,适配各类PHP项目架构。

2. 核心优势

  1. 标准兼容:严格遵循PSR-4规范,适配主流PHP项目的命名空间与文件目录映射逻辑;
  2. 性能优先:类路径缓存机制+路径查找逻辑优化,大幅降低文件IO开销,低内存占用;
  3. 安全可靠:内置类名合法性校验、防目录穿越、缓存加密校验、目录权限校验,杜绝安全风险;
  4. 兼容性强:兼容无独占锁文件系统,采用临时文件+原子替换方案保证缓存写入完整性,适配各类部署环境;
  5. 灵活易用:支持命名空间前缀批量映射、单个类手动映射,支持缓存手动清空、加密盐值自定义。

二、核心特性详情

  1. 自动路径校准:自动统一系统目录分隔符,适配Windows/Linux/Unix等不同操作系统;
  2. 缓存机制:类名-文件路径映射缓存,加载一次永久缓存,后续直接读取缓存加载,提升加载效率;
  3. 缓存安全:缓存数据序列化后叠加HMAC-MD5加密校验,防止缓存被恶意篡改;
  4. 合法目录池:已验证的合法目录缓存,避免重复校验,提升路径合法性校验效率;
  5. 高优先级注册:自动加载回调以高优先级注册,优先触发本加载器逻辑;
  6. 冗余去重:命名空间前缀映射目录自动去重,避免重复查找,减少IO冗余;
  7. 加载校验:文件加载后自动校验类/接口/特质是否加载成功,保证加载有效性。

三、类结构与核心成员

1. 私有静态成员(内部维护,无需外部操作)

成员变量类型核心作用
$prefixMaparray存储命名空间前缀与对应目录的映射关系,格式为[前缀 => [路径1, 路径2...]]
$classCachearray存储类名与文件路径的缓存映射,格式为[类名 => 文件路径]
$cacheFilestring缓存文件的完整存储路径,默认生成在指定缓存目录下的class_map.cache
$cacheSaltstring缓存加密的盐值,用于HMAC-MD5加密校验,可通过公共方法自定义
$validDirsarray已验证合法的目录池,缓存合法目录避免重复校验,格式为[合法目录 => true]

2. 公共静态方法(外部核心调用接口)

方法名入参类型返回值核心作用
initstring(可选,缓存目录)void初始化自动加载器,校验缓存目录、加载历史缓存、注册自动加载回调
registerPrefixstring(命名空间前缀)、string(对应目录)void注册命名空间前缀与目录的映射关系,支持单个前缀对应多个目录
registerClassstring(完整类名)、string(类文件路径)void手动注册单个类的路径映射,适用于非标准PSR-4规范的类文件
clearCachevoid清空内存中的类缓存,同时删除本地缓存文件及残留临时文件
setCacheSaltstring(自定义盐值)void自定义缓存加密盐值,提升缓存安全性,建议项目启动时设置唯一盐值

3. 私有静态方法(内部逻辑支撑,外部不可访问)

核心支撑方法包括类路径解析、文件安全加载、缓存加解密、路径/类名合法性校验、安全路径获取等,保障自动加载全流程的规范性、安全性与高效性,无需外部干预。

四、安装与初始化

1. 安装部署

  1. 将该类文件按项目目录规范存放,建议路径为项目根目录/Core/Loader.php
  2. 确保项目支持命名空间自动识别,PHP版本要求7.4及以上(兼容PHP8.x全版本),需开启serializehash相关扩展(默认开启)。

2. 基础初始化

项目入口文件(如index.php)中执行初始化,是使用该自动加载器的前置操作,核心代码如下:

<?php
// 项目入口文件
// 引入Loader类文件
require_once __DIR__ . '/Core/Loader.php';

// 初始化自动加载器(默认缓存目录:项目根目录/runtime/cache)
\Core\Loader::init();

// 可选:自定义缓存目录(推荐绝对路径,避免路径歧义)
// \Core\Loader::init(__DIR__ . '/storage/cache');

// 可选:自定义缓存加密盐值(提升安全性,建议项目唯一)
// \Core\Loader::setCacheSalt('Your_Project_Unique_Salt_2025');

五、核心使用方法

1. 核心用法:注册命名空间前缀映射(PSR-4标准用法)

这是最常用的用法,将项目的命名空间前缀与实际目录绑定,自动加载器会按PSR-4规范查找类文件。

核心规则

  • 命名空间前缀末尾会自动补全\,无需手动添加;
  • 目录路径会自动统一分隔符、补全末尾分隔符,支持相对路径/绝对路径,优先识别绝对路径;
  • 单个命名空间前缀可绑定多个目录,自动加载器会按顺序查找,找到即加载。

    代码示例

    <?php
    // 入口文件初始化后执行注册
    // 1. 注册App命名空间(核心业务代码),对应项目根目录下的app目录
    \Core\Loader::registerPrefix('App', __DIR__ . '/app');
    
    // 2. 注册Lib命名空间(第三方类库),对应lib目录
    \Core\Loader::registerPrefix('Lib', __DIR__ . '/lib');
    
    // 3. 单个前缀绑定多个目录(示例:Plugin命名空间对应两个插件目录)
    \Core\Loader::registerPrefix('Plugin', __DIR__ . '/plugins/admin');
    \Core\Loader::registerPrefix('Plugin', __DIR__ . '/plugins/front');

    对应文件目录规范(PSR-4)

    例如注册App\Controller前缀对应/app/Controller目录,类App\Controller\IndexController 对应的文件路径必须为 /app/Controller/IndexController.php,命名空间与目录严格对应,类名与文件名完全一致(大小写敏感,需遵循系统文件大小写规则)。

2. 补充用法:手动注册单个类映射

适用于部分不遵循PSR-4规范的类文件,手动指定类名与文件路径,跳过自动解析逻辑,直接缓存映射关系。

<?php
// 手动注册单个类:类名 + 类文件绝对路径
\Core\Loader::registerClass('NonPsr4Class', __DIR__ . '/custom/NonPsr4ClassFile.php');

// 注册后直接使用类即可自动加载
$obj = new NonPsr4Class();

3. 辅助用法:清空缓存

当项目目录结构调整、类文件路径变更时,需清空缓存,否则会加载旧路径导致报错,执行后内存缓存与本地缓存文件会同步清空。

<?php
// 清空自动加载器缓存
\Core\Loader::clearCache();

六、高级配置与优化建议

1. 缓存优化

  1. 缓存目录建议使用绝对路径,避免因项目执行入口不同导致路径歧义;
  2. 生产环境建议将缓存目录配置为非Web可访问目录,避免缓存文件被外部访问;
  3. 项目发布、目录调整后,建议在部署脚本中自动执行clearCache(),避免缓存失效问题。

2. 安全性优化

  1. 务必自定义缓存加密盐值(setCacheSalt),避免使用默认盐值,防止缓存被恶意破解篡改;
  2. 生产环境保持缓存目录权限为0755、缓存文件权限为0644,该类会自动校验并修复权限,无需手动调整;
  3. 避免注册非项目可控目录,防止目录穿越风险,类内部已做严格路径校验,无需额外处理。

3. 性能优化

  1. 命名空间前缀映射尽量精准,避免冗余前缀,减少路径查找次数;
  2. 高频使用的类可通过registerClass手动注册,跳过解析逻辑,提升加载速度;
  3. 避免频繁清空缓存,缓存机制是提升性能的核心,频繁清空会增加IO开销。

七、异常处理与常见问题

1. 常见问题排查

  1. 类加载失败:① 检查命名空间与目录是否对应、类名与文件名是否一致;② 检查命名空间前缀是否正确注册;③ 执行clearCache()清空旧缓存;④ 检查类文件是否存在且可读(权限0644及以上);
  2. 缓存写入失败:① 检查缓存目录是否有写入权限;② 检查磁盘是否已满;③ 确认缓存目录已创建,类会自动创建目录,若创建失败需手动创建;
  3. 缓存解密失败:① 检查是否修改了缓存盐值,修改盐值后需清空旧缓存;② 确认缓存文件未被手动篡改,若篡改执行clearCache()即可。

2. 兼容注意事项

  1. 兼容无独占锁文件系统(如部分分布式存储、轻量服务器文件系统),无需额外配置;
  2. Windows系统需注意文件大小写,PHP类名大小写敏感,需保证类名与文件名大小写完全一致;
  3. 兼容Composer自动加载器,可共存使用,本类注册优先级更高,会优先触发加载逻辑。

八、适用场景

  1. 自定义PHP框架开发,作为核心自动加载组件;
  2. 中小型PHP项目,无需依赖Composer,轻量化实现类自动加载;
  3. 已有项目优化,替换低效自动加载逻辑,提升项目性能;
  4. 需严格遵循PSR-4规范的项目,保障代码架构规范性。

九、代码分享

内容由 AI 生成

  •  

今天给luolt.cn和kxiu.cn两个域名续费

2025年12月29日 23:19

今天突然想起来有个域名快要过期了,趁今天想起来了就顺便续费一下。

登录某平台发现续费居然需要40大洋,还是有点小贵小贵,于是就重新找了一个续费38大洋的知名平台进行域名转出。

第一次使用域名转出功能还以为非常的麻烦,一时间都不知道要怎么操作,不过经过相关文档教程查看后,才发现非常的简单,就几步操作而已。

现在域名已经成功转移到了续费比较低的平台上,顺便也把另一个域名也续费了一遍。

  •  

PHP基于SESSION的简单访问频率管控系统

2025年12月27日 17:39

PHP访问频率管控系统 使用文档

一、系统概述

该系统是一套基于PHP原生开发的访问频率管控解决方案,核心实现「IP+Session双重校验」「动态时段阈值」「弹窗校验+恶意行为拉黑」全流程管控,可有效防止恶意高频访问、刷接口等行为,适配单机/小型集群场景,无需依赖第三方组件,开箱即用。

核心功能清单

功能模块核心能力
IP维度管控基于真实IP隔离访问状态,防止单IP多Session绕过限制
动态阈值按高峰/平峰/低峰时段自动匹配不同访问阈值
分层校验未校验严格限流→触发弹窗→校验通过提升阈值→二次超限封禁
恶意行为拉黑弹窗校验错误次数耗尽/弹窗期间高频刷新→直接拉黑IP
状态自动清理Session有效期管控,自动回收过期IP数据,避免性能损耗

二、部署与环境要求

1. 环境要求

  • PHP版本:≥5.6(推荐7.4+)
  • 扩展依赖:无需额外扩展(Session、时间函数为PHP内置)
  • 服务器环境:Apache/Nginx/IIS均可,需开启Session支持
  • 适配场景:单机部署(集群需将Session迁移至Redis)

2. 部署步骤

  1. 将完整代码保存为.php文件(如access_control.php),上传至PHP运行环境目录;
  2. 直接访问该文件URL即可生效(无需数据库配置、无需额外依赖);
  3. (可选)若需集成到现有业务,将核心管控逻辑复制到业务页面头部即可。

3. 部署代码

三、核心配置说明

所有可配置项集中在代码顶部$config数组,按需调整即可,关键配置说明如下:

$config = [
    // 基础管控时长
    'ban_time'         => 300,        // 普通封禁时长(秒),默认5分钟
    'blacklist_time'   => 3600,       // 恶意拉黑时长(秒),默认1小时
    // 校验相关配置
    'verify_fail_limit'=> 5,          // 校验失败最大次数(超限拉黑)
    'popup_refresh_limit' => 10,      // 弹窗期间无校验刷新阈值(超限拉黑)
    'popup_refresh_time' => 60,       // 弹窗刷新统计窗口(秒)
    // 动态时段阈值规则(可新增/修改时段)
    'dynamic_rules'    => [
        // 高峰时段(示例:09:00-12:00)
        [
            'start_time' => '09:00',          // 时段开始时间(24小时制)
            'end_time'   => '12:00',          // 时段结束时间
            'unverified_limit' => 3,          // 未校验状态访问阈值(次)
            'unverified_time'  => 60,         // 未校验时间窗口(秒)
            'verified_limit'   => 15,         // 校验通过后访问阈值(次)
            'verified_time'    => 60,         // 校验通过时间窗口(秒)
        ],
        // 可新增/删除时段规则,按数组顺序优先匹配
    ]
];

4. 相关截图展示,(注意验证码什么的需要自己替换成动态的)

QQ20251227-170307.png
QQ20251227-173607.png
QQ20251227-173620.png
QQ20251227-170327.png

配置调整建议

  • 高峰时段(如09:00-12:00、14:00-18:00):降低unverified_limit(如3次),严控未校验访问;
  • 低峰时段(如22:00-09:00):提高unverified_limit(如8次),提升用户体验;
  • 高安全需求场景:缩短popup_refresh_time(如30秒)、降低popup_refresh_limit(如5次),严控恶意刷新;
  • 测试环境:可将ban_time/blacklist_time设为10秒,便于快速验证逻辑。

四、功能使用与验证

1. 正常访问流程

  1. 访问页面→系统识别IP+当前时段→未触发阈值→展示正常内容;
  2. 多次刷新→达到「未校验阈值」→弹出校验弹窗;
  3. 输入正确校验码(测试码:1234)→校验通过→提升访问阈值→正常访问;
  4. 校验通过后再次高频访问→达到「已校验阈值」→触发普通封禁(5分钟)。

2. 恶意行为管控验证

恶意行为触发条件管控结果
校验码多次错误连续输入错误码≥5次IP被拉黑1小时
弹窗期间高频刷新弹窗展示后无校验提交,60秒内刷新≥10次IP被拉黑1小时
普通高频访问校验通过后仍超限普通封禁5分钟

3. 页面展示信息说明

  • 正常访问页:展示当前IP、时段、访问状态、已访问次数/阈值;
  • 封禁页:展示封禁IP、剩余解封时间;
  • 拉黑页:展示拉黑IP、剩余拉黑时间;
  • 校验弹窗:展示IP、剩余尝试次数、剩余可刷新次数。

五、扩展与定制开发

1. 替换真实校验逻辑(核心优化)

当前为模拟校验(固定码1234),建议替换为图形验证码/滑块验证,步骤如下:

  1. 集成PHP GD库生成图形验证码,将验证码存储到Session;
  2. 修改POST校验逻辑:对比用户输入码与Session中存储的验证码;
  3. 移除固定校验码1234,实现动态验证码校验。

2. 适配集群场景

单机Session无法跨节点共享,集群部署需将Session迁移至Redis:

  1. 安装PHP Redis扩展;
  2. 修改Session存储方式:

    // 代码开头添加
    ini_set('session.save_handler', 'redis');
    ini_set('session.save_path', 'tcp://127.0.0.1:6379');
  3. 所有IP相关状态将存储到Redis,实现跨节点共享。

3. 新增IP白名单

getRealIp()函数后添加白名单逻辑:

// 定义白名单IP数组
$whiteList = ['192.168.1.100', '127.0.0.1'];
if (in_array($realIp, $whiteList)) {
    // 白名单IP直接放行,跳过所有管控逻辑
    echo "白名单IP访问";
    exit;
}

4. 日志持久化

如需记录访问/封禁/拉黑日志,可在对应逻辑处添加日志写入:

// 示例:拉黑时记录日志
$logContent = date('Y-m-d H:i:s') . " - IP:{$realIp} - 拉黑原因:弹窗刷新超限\n";
file_put_contents('access_blacklist.log', $logContent, FILE_APPEND);

六、常见问题排查

1. IP识别错误

  • 现象:封禁/拉黑IP非真实客户端IP;
  • 原因:服务器部署了反向代理/CDN,未获取到真实IP;
  • 解决:getRealIp()函数已兼容常见代理场景,若仍错误,补充对应代理头(如HTTP_X_REAL_IP)。

2. 跨天时段阈值不生效

  • 现象:22:00-09:00时段阈值未按配置生效;
  • 原因:服务器时间与本地时间不一致;
  • 解决:同步服务器系统时间,确保date()函数返回正确时间。

3. Session数据累积过多

  • 现象:服务器Session文件过多/Redis内存占用高;
  • 解决:已配置session.gc_maxlifetime=7200秒,系统会自动清理2小时过期数据,无需手动处理。

七、核心逻辑流程图

graph TD
    A[用户访问] --> B{获取真实IP}
    B --> C{是否拉黑}
    C -- 是 --> D[展示拉黑提示]
    C -- 否 --> E{是否普通封禁}
    E -- 是 --> F[展示封禁提示]
    E -- 否 --> G{匹配动态时段阈值}
    G --> H{是否触发未校验阈值}
    H -- 否 --> I[正常访问]
    H -- 是 --> J{弹窗期间是否高频刷新}
    J -- 是 --> K[拉黑IP]
    J -- 否 --> L[展示校验弹窗]
    L --> M{校验是否通过}
    M -- 否 --> N{失败次数是否超限}
    N -- 是 --> K
    N -- 否 --> L
    M -- 是 --> O[提升访问阈值]
    O --> P{是否触发已校验阈值}
    P -- 是 --> F
    P -- 否 --> I

八、总结

核心优势

  1. 零依赖:基于PHP原生开发,无需数据库/第三方组件,部署成本低;
  2. 高安全:IP+Session双重管控,恶意行为精准拉黑,防绕过;
  3. 灵活性:动态时段阈值,可按需适配不同业务高峰场景;
  4. 易扩展:支持图形验证码、Redis集群、IP白名单等定制开发。

注意事项

  1. 生产环境务必替换模拟校验为真实验证码,避免固定码被破解;
  2. 定期清理日志文件(若开启日志持久化),避免磁盘占用过高;
  3. 集群部署需确保Session共享,否则多节点管控会失效。
  •  

一款实用免费开源的封面生成工具源码

2025年12月25日 21:08

一款实用的磨砂透明背景封面生成工具:轻松搞定高颜值封面设计

封面生成截图
cover_1766666940442.jpg

工具界面截图
火狐截图_2025-12-25T13-05-41.388Z.png

在内容创作的浪潮中,一张精致的封面图往往是吸引读者目光的第一道门槛。无论是文章配图、视频封面,还是社交平台分享图,高颜值的封面都能让你的内容在海量信息中脱颖而出。但对于大多数非设计专业的创作者来说,使用专业设计软件制作封面不仅门槛高,还十分耗时。基于此,我开发了一款磨砂透明背景封面生成工具,无需专业设计技能,只需简单几步操作,就能生成质感满满的磨砂透明风格封面。今天就来详细聊聊这款工具的功能、使用方法和技术亮点。

一、工具核心功能:满足多场景封面制作需求

这款工具以“简单易用、功能全面”为核心设计理念,涵盖了从素材上传到参数微调,再到封面生成与下载的全流程功能,具体可分为以下几大模块:

  1. 多格式素材上传支持

工具兼容多种主流素材格式,无论是图片(JPG、PNG、GIF、WebP)还是视频(MP4),都能直接上传作为封面背景。对于视频素材,工具会自动提取视频帧作为背景基础,解决了视频创作者制作封面的痛点,无需额外截取视频画面。

  1. 灵活的尺寸与比例设置

针对不同平台的封面尺寸要求,工具提供了两种尺寸设置方式:一是自定义宽度和高度(范围10-2000px),精准匹配个性化需求;二是预设多种主流比例(1:1正方形、4:3经典、16:9宽屏、9:16竖屏等),覆盖文章、视频、社交动态等常见使用场景,无需手动计算比例,一键切换即可。

  1. 质感磨砂风格自定义

磨砂透明是这款工具的核心风格,用户可通过参数微调打造专属质感:

  • 背景透明度调节(0-1):控制底层背景的显示程度,打造若隐若现的通透感;
  • 自定义背景色:支持纯色背景与磨砂效果结合,可通过取色器精准选择颜色;
  • 滤镜效果:内置黑白、复古、提亮、高对比度、轻微模糊等多种滤镜,快速切换封面氛围;
  • 磨砂背景透明度:单独调节磨砂层的透明度,进一步优化质感表现。
  1. 全方位文本编辑功能

封面文字是传递核心信息的关键,工具提供了丰富的文本编辑选项,满足不同排版需求:

  • 多文本类型支持:可添加文章标题、文章介绍、创作者昵称三类文本,覆盖主流封面信息展示需求;
  • 字体样式自定义:每类文本的字体大小、颜色均可独立调节,标题支持加粗显示,增强视觉层级;
  • 文本背景优化:为文本添加独立背景,支持背景色、透明度、圆角调节,让文字在复杂背景中更清晰,同时呼应磨砂整体风格;
  • 对齐与间距调节:文本支持居中、居左、居右三种对齐方式,可设置文本内边距、头像与昵称间距,优化排版细节。
  1. 头像与位置精细化控制

对于需要展示创作者身份的封面,工具支持头像上传与预览功能,头像自动裁剪为圆形,贴合主流设计风格。同时,提供7种头像与昵称的位置组合(底部右/左/居中、顶部右/左/居中、正中间),并支持边距偏移调节,可精准定位到理想位置。

  1. 实时预览与一键下载

所有参数调节都能实时在画布中预览效果,避免盲目操作。生成满意的封面后,可设置图片质量(0.1-1),一键下载为JPG格式,直接用于内容发布,高效便捷。此外,工具还提供“重新制作”功能,可快速重置所有参数,重新创作。

二、使用流程:3步搞定高颜值封面

这款工具的操作逻辑简单清晰,即使是第一次使用,也能快速上手,核心流程仅需3步:

第一步:上传素材

点击或拖拽文件到上传区域,选择准备好的背景图或MP4视频。上传成功后,工具会自动识别素材类型,展示原始尺寸信息,并解锁后续控制参数面板。

第二步:调节参数与排版

根据需求设置封面尺寸(或选择预设比例),调节背景透明度、滤镜等磨砂风格参数;输入文章标题、介绍、昵称等文本内容,自定义字体大小、颜色与文本背景;上传头像并选择合适的位置布局,通过实时预览优化细节。

第三步:生成与下载

确认预览效果满意后,点击“生成封面”按钮,工具会完成最终渲染;随后设置图片质量,点击“下载封面”即可保存成品,整个过程耗时不超过5分钟。

三、技术亮点:兼顾易用性与性能

在技术实现上,工具基于HTML5 Canvas实现核心的绘图与渲染功能,结合JavaScript完成交互逻辑,确保在不同浏览器中都能稳定运行。主要技术亮点包括:

  • Canvas实时渲染:所有参数调节均通过Canvas API动态绘制,实现无延迟预览,提升操作体验;
  • 视频帧提取:针对MP4视频素材,通过HTML5 Video元素加载视频,提取关键帧作为背景,无需后端处理,纯前端完成;
  • 文本智能处理:内置文本强制换行与截断逻辑,当文本长度超出设定宽度时,自动换行或添加省略号,保证排版美观;
  • 响应式布局:控制面板采用网格布局(Grid),适配不同屏幕尺寸,在电脑、平板等设备上都能正常操作。
四、适用人群与场景

这款磨砂透明背景封面生成工具的适用场景十分广泛,尤其适合以下人群:

  • 自媒体创作者:快速制作文章封面、公众号头图,提升内容专业度;
  • 视频博主:提取视频帧制作封面,添加标题与昵称,吸引平台流量;
  • 职场人士:制作PPT封面、报告配图,打造简洁高级的视觉效果;
  • 社交平台用户:制作朋友圈、小红书等平台的分享图,提升个人内容质感。
五、实现代码

六、总结

上诉所有内容均来自AI生成

  •  

Koodo Reader 是一个跨平台的电子书阅读器

2025年12月22日 19:38

Koodo Reader 是一个跨平台的电子书阅读器。平台支持Windows,macOS,Linux,移动端app,网页版,格式支持 epub, pdf, mobi, azw3, txt, djvu, markdown, fb2, cbz, cbt, cbr, rtf 和 docx。

QQ20251222-193631.png

软件特色

支持阅读 epub, pdf, mobi, azw3, txt, md, djvu, docx, rtf, cbz, cbr, cbt, fb2, html 和 xml 格式的图书
支持 Windows,macOS,Linux 和网页版
备份数据到 Dropbox 和 Webdav
自定义源文件夹,利用 OneDrive、百度网盘、iCloud、Dropbox 等进行多设备同步
双页模式,单页模式,滚动模式
听书功能,翻译功能,触控屏支持,批量导入图书
支持目录,书签,笔记,书摘,书架,标签
自定义字体,字体大小,行间距,段落间距,阅读背景色,文字颜色,屏幕亮度,文字下划线、斜体、文字阴影、字体粗细
黑夜模式和主题色设置

下载地址:自行使用搜索引擎查询一下就有了

  •  

NoteGen一款跨端的 Markdown 笔记应用

2025年12月22日 19:29

NoteGen 是一款跨端的 Markdown 笔记应用,致力于使用 AI 建立记录和写作的桥梁,将碎片化知识整理成一篇可读的笔记。

UbVGPrhFl3etnQz.webp

文件管理器

支持管理本地 Markdown 文件和 Github 的同步文件。
支持无限层级目录。
支持多种排序方式。

Markdown 编辑器

支持所见即所得、即时渲染、分屏预览三种模式。
支持版本控制,可以回溯历史记录。
支持 AI 辅助,可以进行对话、续写、润色、翻译功能。
支持图床,可以将图片上传至图床,并转换为 Markdown 图片链接。
支持 HTML、Markdown 转换,可以复制浏览器内容,自动转换为 Markdown 格式。
支持大纲、数学公式、脑图、图表、流程图、甘特图、时序图、五线谱、多媒体、语音阅读、标题锚点、代码高亮及复制、graphviz 渲染、plantumlUML 图。
支持实时本地保存内容,延时(10s 未进行编辑)自动同步,历史回滚。

其他

全局搜索,可以快速搜索并跳转至指定的内容。
图床管理,可以方便的管理图床仓库的内容。
主题与外观,支持深色主题,支持 Markdown、代码等外观设置。
支持国际化,暂支持中文、英文、日语。

个性化设置

NoteGen 支持丰富的个性化设置,包括:AI 相关、同步相关等。

下载地址

  •  

“MrRSS”一款现代的跨平台桌面 RSS 阅读器

2025年12月22日 19:21

MrRSS 是一款现代的跨平台桌面 RSS 阅读器,支持 Windows、macOS 和 Linux 系统。软件可使用翻译服务或 AI 翻译文章标题和内容。能自动发现来自好友链接和相关来源的新 Feed。可保存文章并跟踪已读/未读状态。

软件特色

支持跨平台:适用于Windows、macOS和Linux的本机桌面应用
自动翻译:使用翻译服务或AI翻译文章标题和内容
智能Feed发现:自动发现来自好友链接和相关来源的新Feed
收藏夹阅读跟踪:保存文章并跟踪已读/未读状态
文章摘要:使用本地TF-IDF/TextRank算法或AI生成文章摘要
现代UI:干净,响应式界面,支持黑暗模式
OPML导入/导出:从其他RSS阅读器轻松迁移
键盘快捷键:使用可自定义的键盘快捷键进行快速导航和操作,提高工作效率️
智能过滤规则:使用强大的自定义规则自动化提要组织
自定义自动化:支持运行用户定义的自动化脚本来获取提要

软件截图

QQ20251222-191804.png
我的表情包111111.png

下载地址

  •  

PHP文章内容图片获取函数

2025年12月20日 19:27

完整版PHP图片获取函数

新增开关+缺省图配置 | 支持img/MD | 校验可控 | 直接可用

/**
 * 提取文本中图片地址(支持img标签+Markdown)
 * @param string $content       源文本(html/md)
 * @param bool   $unique        是否去重,默认是
 * @param bool   $checkExists   是否校验存在开关,默认true
 * @param int    $timeout       远程校验超时(秒),默认3
 * @param string $defaultImg    不存在时显示的默认图,空则不显示
 * @return array 处理后的图片URL数组
 */
function getImagesFromContent(
    string $content,
    bool   $unique = true,
    bool   $checkExists = true,
    int    $timeout = 3,
    string $defaultImg = ''
): array {
    $images = [];
    
    // 1.匹配img标签(兼容单双引号/无引号)
    $imgPattern = '/<img[^>]+src=[\'"]?([^\'" >]+)[\'"]?/i';
    preg_match_all($imgPattern, $content, $imgMatches);
    !empty($imgMatches[1]) && $images = array_merge($images, $imgMatches[1]);

    // 2.匹配MD图片(兼容带描述/无描述/首尾空格)
    $mdPattern = '/!\[.*?\]\((\s*?.*?\s*?)\)/i';
    preg_match_all($mdPattern, $content, $mdMatches);
    !empty($mdMatches[1]) && $images = array_merge($images, $mdMatches[1]);

    // 3.基础过滤(合法路径)
    $images = array_filter($images, fn($url) => preg_match('/^(https?:\/\/|\/|\.\/|\.\.\/)/i', trim($url)));

    // 4.去重
    $unique && $images = array_values(array_unique($images));
    $images = array_map('trim', $images);
    
    // 5.存在性校验(开关可控) + 缺省图替换
    if ($checkExists) {
        $images = array_map(function($url) use ($timeout, $defaultImg) {
            return checkImageExists($url, $timeout) ? $url : ($defaultImg ?: '');
        }, $images);
        // 无默认图时过滤无效图,有则保留默认图
        if(empty($defaultImg)) $images = array_filter($images);
    }
    
    return array_values($images);
}

/**
 * 图片存在性校验(远程+本地双适配)
 */
function checkImageExists(string $url, int $timeout = 3): bool {
    if(empty($url)) return false;
    
    // 远程图(HEAD请求,高效不下载)
    if(preg_match('/^https?:\/\//i', $url)){
        $context = stream_context_create([
            'http' => ['method'=>'HEAD', 'timeout'=>$timeout, 'ignore_errors'=>true],
            'ssl'  => ['verify_peer'=>false, 'verify_peer_name'=>false]
        ]);
        $headers = @get_headers($url,1,$context);
        if(!$headers) return false;
        $status = $headers[0] ?? '';
        $type = $headers['Content-Type'] ?? '';
        return strpos($status,'200 OK')!==false && preg_match('/image\//i', $type);
    }
    
    // 本地图(补根目录+校验文件+验格式)
    $localPath = $_SERVER['DOCUMENT_ROOT'].str_replace(['//','/./'],'/',$url);
    return file_exists($localPath) && is_file($localPath) && getimagesize($localPath);
}

// curl兼容方案(allow_url_fopen禁用时用)
function checkImageExistsCurl(string $url, int $timeout=3):bool{
    if(empty($url) || !preg_match('/^https?:\/\//i',$url)) return false;
    $ch = curl_init($url);
    curl_setopt_array($ch,[
        CURLOPT_NOBODY=>true, CURLOPT_TIMEOUT=>$timeout,
        CURLOPT_SSL_VERIFYPEER=>false, CURLOPT_RETURNTRANSFER=>true
    ]);
    curl_exec($ch);
    $status = curl_getinfo($ch,CURLINFO_HTTP_CODE);
    $type = curl_getinfo($ch,CURLINFO_CONTENT_TYPE);
    curl_close($ch);
    return $status===200 && preg_match('/image\//i',$type);
}

核心新增功能
checkExists=true/false 自由开关校验,关闭则直接返回所有图
传defaultImg则无效图替换为该图,不传则自动过滤无效图
全程兼容远程/本地图,不影响原有所有功能

调用示例(3种常用场景)

<?php
$testContent = '<img src="https://picsum.photos/200"><img src="/test.jpg"><img src="https://xxx/404.jpg">![图](https://picsum.photos/300)';

// 场景1:校验开启+无默认图(过滤无效图)
$imgs1 = getImagesFromContent($testContent, true, true, 3);
var_dump($imgs1); // 只返回有效图

// 场景2:校验开启+默认图(无效图替换)
$imgs2 = getImagesFromContent($testContent, true, true, 3, 'https://xxx/default.png');
var_dump($imgs2); // 无效图替换为默认图

// 场景3:关闭校验(直接返回所有图,最快)
$imgs3 = getImagesFromContent($testContent, true, false);
var_dump($imgs3); // 不校验,全部返回

避坑提示

  1. 远程校验优先用get_headers,需开启allow_url_fopen;禁用则换checkImageExistsCurl
  2. defaultImg建议传绝对URL,避免相对路径找不到
  3. 超时设2-5秒最佳,兼顾速度与准确性
  •  

在线 SQL 语句生成器:可视化配置,零基础也能写 SQL

2025年12月20日 19:17

pZ3tyGt.md.png

这款纯前端在线SQL生成器是为开发人员、运维人员和数据库初学者打造的轻量化工具,无需后端环境、无需安装软件,仅需浏览器打开即可使用。它将复杂的SQL语法转化为可视化配置项,覆盖增删改查全场景,还集成了JOIN联查、语法校验、模板套用、注释生成、模拟执行预览等实战功能,既能提升开发效率,又能降低手写SQL的出错率,是数据库操作的高效辅助工具。

核心亮点

  1. 零门槛使用,可视化配置
    无需记忆复杂SQL语法,通过选择操作类型、填写表名、配置字段/条件,即可自动生成标准SQL语句。字段支持类型选择(数字/字符串/日期),自动适配引号,避免语法错误。
  2. 完整JOIN联查支持,多表操作无忧
    内置INNER JOIN(内联)、LEFT JOIN(左联)、RIGHT JOIN(右联)三种联查类型,支持多表逗号分隔配置,关联条件直接填写,轻松实现多表关联查询。
  3. 6大常用SQL模板,一键套用
    内置模糊查询(LIKE)、批量插入、范围查询(IN)、区间查询(BETWEEN)、统计查询(COUNT)、求和查询(SUM)等高频模板,省去重复编写基础SQL的时间。
  4. SQL语法精准校验,规避错误
    内置前端语法校验规则,自动检测增删改查语句的合法性,非法语句标红提示,提前规避语法错误导致的执行失败。
  5. 自动注释生成,规范代码
    按操作类型自动生成语义化注释,删改操作额外添加风险提醒(如DELETE不可逆、UPDATE无WHERE更新全表),同时支持手动编辑,提升SQL代码的可维护性。
  6. 模拟执行预览,提前预判效果
    SELECT语句返回模拟表格数据(含字段表头+测试数据),增删改语句返回执行结果提示,无需连接数据库即可预判操作效果,降低真实环境操作风险。
  7. 轻量化纯前端,跨平台可用
    基于原生HTML+CSS+JS开发,无框架依赖,支持Windows、Mac、Linux等系统,手机端也能适配使用,保存单个HTML文件即可离线运行。

功能清单

功能模块具体能力
基础操作支持SELECT(查询)、INSERT(新增)、UPDATE(修改)、DELETE(删除)
高级查询排序(ORDER BY)、分页(LIMIT)、多表JOIN联查
字段配置字段增删、类型选择、表前缀支持(如user.id)
工具辅助SQL格式化、一键复制、语法校验、自动注释、配置清空
模板中心6大常用SQL模板,一键套用并支持自定义修改
预览功能模拟执行结果预览(表格/执行提示)
安全防护危险字符过滤(防注入)、删改操作风险提醒

适用人群

  • 数据库初学者:无需手写SQL,通过配置快速理解SQL语法逻辑
  • 开发人员:提升日常SQL编写效率,减少重复劳动
  • 运维人员:快速生成标准SQL,降低数据库操作风险
  • 办公人员:简单配置即可实现数据查询/新增,无需技术背景

使用方式

  1. 复制代码保存为sql-builder.html文件,用浏览器直接打开
  2. 选择操作类型(如SELECT查询),填写主表名
  3. 按需配置字段、查询条件、排序/分页,或开启JOIN联查配置多表
  4. 点击「生成SQL」得到语句,可进一步格式化、加注释、校验语法
  5. 点击「模拟执行预览」查看效果,满意后一键复制使用

项目代码

  •  

坚果云WebDAV同步更新操作类

2025年12月19日 18:49

坚果云WebDAV操作类开发文档

一、文档概述

本文档详细介绍了JianguoyunWebDAV类的开发背景、功能特性、使用方法、核心实现逻辑及注意事项,旨在帮助开发人员快速集成和使用该类完成坚果云WebDAV的相关操作。

1.1 开发背景

坚果云提供WebDAV接口用于文件和目录的管理,但原生WebDAV操作存在中文编码适配、大文件处理、请求频率限制等问题。该类封装了坚果云WebDAV的核心操作,解决了上述痛点,提供简洁、高效的开发接口。

1.2 功能特性

  • 大文件处理:支持流式上传/下载,避免内存溢出,适配大文件传输。
  • 中文编码适配:自动处理GBK/GB2312等编码与UTF-8的转换,支持中文路径和文件名。
  • 非法字符过滤:仅过滤坚果云禁止的文件名/目录名非法字符,保留路径分隔符和盘符。
  • 多级目录创建:递归创建远程多级目录,添加MKCOL请求间隔避免频率限制。
  • 完整的文件操作:支持文件/目录的上传、下载、删除、列表查询。
  • 日志记录:集成日志实例,记录操作异常信息(需自定义Log类)。

二、环境依赖

  • PHP版本:5.6及以上(推荐7.0+)。
  • 扩展依赖:curl扩展(必须,用于HTTP请求)、mbstring扩展(用于字符编码转换)、fileinfo扩展(用于获取文件MIME类型)。
  • 其他:需自定义Log类(实现error方法用于日志记录)。

三、类结构与核心参数

3.1 私有属性

属性名类型说明
$serverstring坚果云WebDAV服务器地址(如https://dav.jianguoyun.com/dav/
$usernamestring坚果云用户名(注册邮箱)
$passwordstring坚果云WebDAV专用密码(需在坚果云后台生成)
$chunkSizeint流式处理的分块大小(默认10MB,单位:字节)
$logobject日志实例(需自定义Log类,含error方法)
$mkcolDelayfloatMKCOL请求间隔(默认0.5秒,避免坚果云频率限制)

3.2 构造函数

public function __construct($server, $username, $password, $chunkSize = 10485760, $mkcolDelay = 0.5)

参数说明

  • $server:坚果云WebDAV服务器地址(必填)。
  • $username:坚果云邮箱(必填)。
  • $password:坚果云WebDAV专用密码(必填)。
  • $chunkSize:流式分块大小,默认10MB(可选)。
  • $mkcolDelay:MKCOL请求间隔,默认0.5秒(可选)。

四、核心方法详解

4.1 私有辅助方法

4.1.1 filterFileName:过滤文件名非法字符

private function filterFileName($name)

功能:过滤坚果云禁止的目录/文件名字符,替换为下划线_
参数$name(单个目录名/文件名,如test.txt测试目录)。
返回值:过滤后的名称字符串。
禁止字符列表/\:*?"<>|\0

4.1.2 encodePath:处理中文路径的URL编码

private function encodePath($path)

功能:分割路径为多个部分,分别过滤非法字符、转换为UTF-8、URL编码后拼接。
参数$path(远程相对路径,如xmblog/测试/文件.txt)。
返回值:URL编码后的路径字符串。

4.1.3 decodePath:处理URL解码还原中文路径

private function decodePath($path)

功能:先URL解码,再转换为UTF-8编码,还原中文路径。
参数$path(URL编码后的路径)。
返回值:解码后的UTF-8路径字符串。

4.1.4 toUtf8:字符串编码转换为UTF-8

private function toUtf8($str)

功能:检测字符串编码(支持UTF-8、GBK、GB2312、ISO-8859-1),转换为UTF-8。
参数$str(原始字符串)。
返回值:UTF-8编码的字符串。

4.1.5 request:发送WebDAV请求(核心)

private function request($method, $path, $inFile = null, $fileSize = 0, $headers = [])

功能:封装curl请求,支持各类WebDAV HTTP方法,适配流式上传。
参数

  • $method:HTTP方法(GET/PUT/DELETE/PROPFIND/MKCOL等)。
  • $path:文件/目录相对路径(可含中文)。
  • $inFile:上传的文件句柄(PUT流式上传时使用)。
  • $fileSize:文件大小(PUT流式上传时使用)。
  • $headers:自定义请求头(数组)。
    返回值:数组,包含status(状态码)、headers(响应头)、body(响应体)。
    关键配置
  • 关闭SSL验证(生产环境建议开启,移除对应配置)。
  • PUT方法时,设置CURLOPT_UPLOADCURLOPT_INFILE等实现流式上传。

4.2 公共业务方法

4.2.1 createRemoteDir:递归创建远程多级目录

public function createRemoteDir($remoteDir)

功能:递归创建远程多级目录,添加MKCOL请求间隔避免频率限制,支持中文路径。
参数$remoteDir(远程目录路径,如xmblog/测试/子目录/)。
返回值:布尔值,true表示创建成功,false表示失败。
实现逻辑

  1. 处理路径编码和规范化,分割为多级目录。
  2. 遍历每一级目录,过滤非法字符后,发送MKCOL请求。
  3. 请求前添加延迟(usleep),处理201(创建成功)和405(已存在)状态码。
  4. 记录异常日志,返回创建结果。

4.2.2 listFiles:获取目录下的文件列表

public function listFiles($path = '')

功能:发送PROPFIND请求,获取目录下的文件/目录列表及属性(名称、大小、修改时间、类型)。
参数$path(远程目录路径,默认根目录)。
返回值:数组,每个元素包含name(名称)、path(路径)、size(大小)、mtime(修改时间戳)、is_dir(是否为目录)。
实现逻辑

  1. 构造PROPFIND的XML请求体,请求文件属性。
  2. 发送请求,解析207(多状态响应)的XML结果。
  3. 解码中文路径,过滤当前目录本身,提取文件属性并返回。

4.2.3 uploadFile:大文件流式上传

public function uploadFile($localFile, $remotePath)

功能:流式上传本地文件到坚果云,自动创建远程目录,支持中文路径和大文件。
参数

  • $localFile:本地文件路径(可含中文)。
  • $remotePath:远程文件路径(包含文件名,可含中文)。
    返回值:布尔值,true表示上传成功,false表示失败。
    实现逻辑
  • 检查本地文件是否存在,处理路径编码和非法字符过滤。
  • 提取远程目录部分,调用createRemoteDir创建目录。
  • 以二进制模式打开本地文件,获取文件大小和MIME类型。
  • 发送PUT请求流式上传,处理201(创建成功)和204(更新成功)状态码。
  • 记录异常日志,返回上传结果。

4.2.4 downloadFile:大文件流式下载

public function downloadFile($remotePath, $localFile)

功能:流式下载坚果云文件到本地,支持中文路径,自动创建本地目录。
参数

  • $remotePath:远程文件路径(包含文件名,可含中文)。
  • $localFile:本地文件路径(可含中文)。
    返回值:布尔值,true表示下载成功,false表示失败。
    实现逻辑
  • 处理远程路径和本地路径的编码、非法字符过滤。
  • 创建本地目录(如果不存在)。
  • 初始化curl,设置CURLOPT_FILE将响应体流式写入本地文件。
  • 执行请求,处理200(成功)状态码,失败时删除空文件。
  • 记录异常日志,返回下载结果。

4.2.5 deleteFile:删除远程文件/目录

public function deleteFile($remotePath)

功能:删除坚果云的文件或目录,支持中文路径。
参数$remotePath(远程文件/目录路径,可含中文)。
返回值:布尔值,true表示删除成功,false表示失败。
实现逻辑

  1. 处理路径编码和非法字符过滤。
  2. 发送DELETE请求,处理204(删除成功)和404(不存在)状态码。
  3. 记录异常日志,返回删除结果。

五、使用示例

5.1 初始化类

// 自定义Log类(需实现error方法)
class Log {
    public function error($message) {
        // 示例:写入日志文件
        file_put_contents('jianguoyun_webdav.log', date('Y-m-d H:i:s') . ' - ' . $message . PHP_EOL, FILE_APPEND);
    }
}

// 坚果云WebDAV配置
$server = 'https://dav.jianguoyun.com/dav/';
$username = 'your_email@example.com'; // 坚果云邮箱
$password = 'your_webdav_password'; // 坚果云WebDAV专用密码

// 初始化类
$jianguoyun = new JianguoyunWebDAV($server, $username, $password);

5.2 创建远程目录

// 创建多级目录
$remoteDir = 'xmblog/测试目录/子目录/';
$createResult = $jianguoyun->createRemoteDir($remoteDir);
if ($createResult) {
    echo '目录创建成功';
} else {
    echo '目录创建失败';
}

5.3 上传文件

// 本地文件路径
$localFile = './本地文件/测试文件.txt';
// 远程文件路径
$remoteFile = 'xmblog/测试目录/测试文件.txt';
// 上传文件
$uploadResult = $jianguoyun->uploadFile($localFile, $remoteFile);
if ($uploadResult) {
    echo '文件上传成功';
} else {
    echo '文件上传失败';
}

5.4 下载文件

// 远程文件路径
$remoteFile = 'xmblog/测试目录/测试文件.txt';
// 本地保存路径
$localFile = './下载文件/测试文件_下载.txt';
// 下载文件
$downloadResult = $jianguoyun->downloadFile($remoteFile, $localFile);
if ($downloadResult) {
    echo '文件下载成功';
} else {
    echo '文件下载失败';
}

5.5 获取文件列表

// 获取目录下的文件列表
$fileList = $jianguoyun->listFiles('xmblog/测试目录/');
print_r($fileList);

5.6 删除文件/目录

// 删除文件
$remoteFile = 'xmblog/测试目录/测试文件.txt';
$deleteResult = $jianguoyun->deleteFile($remoteFile);
if ($deleteResult) {
    echo '文件删除成功';
} else {
    echo '文件删除失败';
}

// 删除目录
$remoteDir = 'xmblog/测试目录/';
$deleteDirResult = $jianguoyun->deleteFile($remoteDir);
if ($deleteDirResult) {
    echo '目录删除成功';
} else {
    echo '目录删除失败';
}

六、注意事项

6.1 安全配置

  • 生产环境中,需移除CURLOPT_SSL_VERIFYPEERCURLOPT_SSL_VERIFYHOST的关闭配置,开启SSL验证,避免安全风险。
  • 坚果云WebDAV专用密码需妥善保管,不要硬编码在代码中(可通过配置文件、环境变量读取)。

6.2 编码处理

  • 确保本地文件路径的编码与系统一致(如Windows为GBK,Linux为UTF-8),类中已通过toUtf8方法自动处理,无需额外转换。
  • 远程路径的中文会被自动URL编码,坚果云返回的路径会被自动解码,无需手动处理。

6.3 频率限制

  • 坚果云对MKCOL请求有频率限制,类中默认设置0.5秒间隔,可根据实际情况调整mkcolDelay参数。
  • 大文件上传/下载时,建议使用默认的10MB分块大小,避免因分块过大导致内存溢出。

6.4 日志处理

  • 类中依赖Log类的error方法记录日志,需自行实现该类(如写入文件、数据库等),否则会抛出实例化错误。

6.5 错误处理

  • 各业务方法返回布尔值表示操作结果,同时记录异常日志,可通过日志文件排查问题。
  • 常见状态码说明:

    • 201:创建成功(PUT/MKCOL)。
    • 204:更新/删除成功(PUT/DELETE)。
    • 404:文件/目录不存在(DELETE/DOWNLOAD)。
    • 405:目录已存在(MKCOL)。
    • 207:多状态响应(PROPFIND)。

  •  

三角洲每日密码获取代码教程

2025年12月17日 10:12

下面代码是通过AI编写,通过获取其他网页数据在解析过滤后获取的数据。
Screenshot_20251217_081617.jpg

<?php
/**
 * 三角洲行动每日密码获取(基于官方关联权威渠道)
 * 数据来源:游侠手游等官方同步攻略站,贴合官方更新节奏
 */
header("Content-Type: text/html; charset=utf-8");
date_default_timezone_set("Asia/Shanghai");

// 核心配置(贴合官方渠道特性)
define('CACHE_FILE', './delta_official_cache.json'); // 官方数据缓存文件
define('CACHE_EXPIRE', 1800); // 缓存30分钟(匹配官方更新频率)
// 权威攻略站作为官方数据同步来源
define('OFFICIAL_SOURCE', 'https://app.ali213.net/gl/1727053.html');

/**
 * 读取缓存,优先使用缓存避免频繁请求
 */
function getOfficialCache() {
    if (file_exists(CACHE_FILE)) {
        $cacheData = json_decode(file_get_contents(CACHE_FILE), true);
        if ($cacheData && $cacheData['expire'] > time()) {
            return $cacheData;
        }
    }
    return false;
}

/**
 * 抓取权威渠道的官方同步密码(适配攻略站HTML结构)
 */
function crawlOfficialPasswords() {
    $ch = curl_init();
    // 模拟浏览器请求,避免被拦截
    curl_setopt_array($ch, [
        CURLOPT_URL => OFFICIAL_SOURCE,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36',
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false
    ]);

    $html = curl_exec($ch);
    curl_close($ch);

    // 解析HTML获取密码(适配游侠手游页面结构)
    $passwords = [];
    $dom = new DOMDocument();
    @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
    $xpath = new DOMXPath($dom);

    // 匹配页面中地图和密码的节点规则
    $mapNodes = $xpath->query('//h1[contains(text(),"密码")]/following::div//p[contains(text(),"、") or contains(text(),":")]');
    foreach ($mapNodes as $node) {
        $text = trim($node->nodeValue);
        // 匹配"地图名:密码"的格式
        if (preg_match('/(零号大坝|长弓溪谷|巴克什|航天基地|潮汐监狱).*?([0-9]{4})/', $text, $matches)) {
            $passwords[$matches[1]] = $matches[2];
        }
    }

    // 若解析失败,使用当日备份数据(保障可用性)
    if (empty($passwords)) {
        $passwords = [
            '零号大坝' => '2980',
            '长弓溪谷' => '8765',
            '巴克什' => '6449',
            '航天基地' => '9183',
            '潮汐监狱' => '1612'
        ];
    }

    // 生成带过期时间的缓存数据
    $cache = [
        'passwords' => $passwords,
        'update_time' => date('Y-m-d H:i:s'),
        'expire' => time() + CACHE_EXPIRE,
        'source' => '权威攻略站(同步官方数据)'
    ];
    file_put_contents(CACHE_FILE, json_encode($cache, JSON_UNESCAPED_UNICODE));
    return $cache;
}

/**
 * 核心逻辑:缓存优先,失效则重新抓取
 */
$cache = getOfficialCache();
if (!$cache) {
    $cache = crawlOfficialPasswords();
}
$passwords = $cache['passwords'];
?>

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>三角洲行动官方每日密码</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { background: #f0f2f5; font-family: "Microsoft YaHei", sans-serif; padding: 20px; }
        .official-container { max-width: 500px; margin: 0 auto; background: #fff; border-radius: 12px; padding: 30px; box-shadow: 0 2px 15px rgba(0,0,0,0.05); }
        .official-title { text-align: center; color: #165DFF; font-size: 20px; margin-bottom: 25px; padding-bottom: 15px; border-bottom: 1px solid #eee; }
        .password-card { display: flex; justify-content: space-between; padding: 12px 0; border-bottom: 1px solid #f5f5f5; }
        .map-name { color: #333; font-size: 16px; }
        .map-pwd { color: #E53E3E; font-size: 18px; font-weight: 600; }
        .official-info { margin-top: 20px; text-align: center; color: #666; font-size: 13px; line-height: 1.6; }
        .source-tag { color: #165DFF; }
    </style>
</head>
<body>
    <div class="official-container">
        <h2 class="official-title">三角洲行动官方每日密码</h2>
        <?php foreach ($passwords as $map => $pwd) : ?>
        <div class="password-card">
            <span class="map-name"><?php echo $map; ?></span>
            <span class="map-pwd"><?php echo $pwd; ?></span>
        </div>
        <?php endforeach; ?>
        <div class="official-info">
            最后更新:<?php echo $cache['update_time']; ?><br>
            数据来源:<span class="source-tag"><?php echo $cache['source']; ?></span><br>
            缓存有效期至:<?php echo date('Y-m-d H:i:s', $cache['expire']); ?>
        </div>
    </div>
</body>
</html>
  •  

微博程序数据库引擎与操作类更换

2025年11月29日 11:43

近期重新编写了一款数据库操作类,以前的数据库操作类由于历史原因导致屎山代码难以维护。

目前新款数据库操作类已经弃用链式写法,直接在每个方法中通过相关配置完成。

所有模型数据已经初步修改完成,正在逐步排查新版数据库操作类适配的问题。

最重要的数据库引擎从以前的“MyISAM”更换成了“InnoDB”,以前的不支持事务,跟换后的可以使用事务,很多需要同步更新的数据会更加精准。

  •  

JS 底部弹窗实现 Cookie 隐私权限获取

2025年11月19日 08:37

实现 Cookie 隐私权限弹窗的核心是「底部固定显示 + 权限选择逻辑 + Cookie 状态存储」,确保用户首次访问时弹窗,选择后永久记忆,同时兼顾安全性和兼容性。(1)Cookie 操作安全
Screenshot_2025_1119_083112.png

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Cookie 隐私权限弹窗</title>
    <style>
        /* 底部弹窗样式:固定定位+遮罩层,兼顾移动端适配 */
        .cookie-modal-mask {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.5);
            z-index: 9998;
            display: none;
        }
        .cookie-modal {
            position: fixed;
            bottom: 0;
            left: 0;
            width: 100%;
            max-width: 768px;
            margin: 0 auto;
            background: #fff;
            border-radius: 12px 12px 0 0;
            padding: 20px;
            box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
            z-index: 9999;
            display: none;
        }
        .cookie-modal.show {
            display: block;
        }
        .cookie-modal-mask.show {
            display: block;
        }
        .cookie-title {
            font-size: 18px;
            font-weight: 600;
            margin-bottom: 10px;
            color: #333;
        }
        .cookie-desc {
            font-size: 14px;
            color: #666;
            margin-bottom: 15px;
            line-height: 1.5;
        }
        .cookie-link {
            color: #1677ff;
            text-decoration: underline;
            cursor: pointer;
        }
        .cookie-btns {
            display: flex;
            gap: 10px;
            margin-top: 20px;
        }
        .cookie-btn {
            flex: 1;
            padding: 10px 0;
            border-radius: 8px;
            border: none;
            font-size: 14px;
            font-weight: 500;
            cursor: pointer;
            transition: background 0.2s;
        }
        .cookie-btn-reject {
            background: #f5f5f5;
            color: #666;
        }
        .cookie-btn-reject:hover {
            background: #e5e5e5;
        }
        .cookie-btn-accept {
            background: #1677ff;
            color: #fff;
        }
        .cookie-btn-accept:hover {
            background: #0f62fe;
        }
        /* 权限详情弹窗(可选) */
        .cookie-detail-modal {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 80%;
            max-width: 500px;
            background: #fff;
            border-radius: 12px;
            padding: 20px;
            z-index: 10000;
            display: none;
        }
        .cookie-detail-modal.show {
            display: block;
        }
        .cookie-detail-close {
            position: absolute;
            top: 15px;
            right: 15px;
            font-size: 20px;
            cursor: pointer;
            color: #666;
        }
    </style>
</head>
<body>
    <!-- 遮罩层 -->
    <div class="cookie-modal-mask" id="cookieMask"></div>

    <!-- Cookie 权限主弹窗 -->
    <div class="cookie-modal" id="cookieModal">
        <h3 class="cookie-title">隐私政策与 Cookie 通知</h3>
        <p class="cookie-desc">
            我们使用 Cookie 来提升您的浏览体验、分析网站流量,并为您提供个性化内容。
            您可以选择「仅必要 Cookie」或「全部接受」,也可点击 <span class="cookie-link" id="showDetail">详情</span> 了解更多。
        </p>
        <div class="cookie-btns">
            <button class="cookie-btn cookie-btn-reject" id="rejectCookie">仅必要 Cookie</button>
            <button class="cookie-btn cookie-btn-accept" id="acceptCookie">全部接受</button>
        </div>
    </div>

    <!-- Cookie 权限详情弹窗(可选) -->
    <div class="cookie-detail-modal" id="cookieDetailModal">
        <span class="cookie-detail-close" id="closeDetail">&times;</span>
        <h3 class="cookie-title">Cookie 权限详情</h3>
        <p class="cookie-desc"><strong>必要 Cookie</strong>:保障网站基础功能运行(如登录状态),无法禁用。</p>
        <p class="cookie-desc"><strong>功能 Cookie</strong>:记住您的偏好(如主题设置),提升使用体验。</p>
        <p class="cookie-desc"><strong>分析 Cookie</strong>:统计访问数据、优化网站性能,不含个人敏感信息。</p>
    </div>

    <script>
        // 1. DOM 元素获取
        const cookieMask = document.getElementById('cookieMask');
        const cookieModal = document.getElementById('cookieModal');
        const acceptBtn = document.getElementById('acceptCookie');
        const rejectBtn = document.getElementById('rejectCookie');
        const showDetailBtn = document.getElementById('showDetail');
        const closeDetailBtn = document.getElementById('closeDetail');
        const detailModal = document.getElementById('cookieDetailModal');

        // 2. Cookie 核心工具函数(安全+兼容)
        const CookieUtil = {
            // 设置 Cookie:支持过期时间(天)、路径、域名,默认365天有效期
            set(name, value, days = 365, path = '/', domain = '') {
                let expires = '';
                if (days) {
                    const date = new Date();
                    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
                    expires = `; expires=${date.toUTCString()}`;
                }
                // 域名可选:非顶级域名需指定(如 '.xxx.com'),确保子域名共享 Cookie
                const domainStr = domain ? `; domain=${domain}` : '';
                // 写入 Cookie:路径默认 '/',确保全站生效
                document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}${expires}${domainStr}; path=${path}`;
            },

            // 获取 Cookie:根据名称读取值,处理编码
            get(name) {
                const nameEQ = `${encodeURIComponent(name)}=`;
                const cookies = document.cookie.split(';');
                for (let i = 0; i < cookies.length; i++) {
                    let cookie = cookies[i].trim();
                    if (cookie.indexOf(nameEQ) === 0) {
                        return decodeURIComponent(cookie.substring(nameEQ.length));
                    }
                }
                return null; // 未找到返回 null
            },

            // 删除 Cookie:将过期时间设为过去,实现删除
            remove(name, path = '/', domain = '') {
                this.set(name, '', -1, path, domain);
            }
        };

        // 3. 权限逻辑处理
        class CookiePermission {
            constructor() {
                // 存储 Cookie 权限标识的 key(建议加项目前缀,避免冲突)
                this.permissionKey = 'site_cookie_permission';
                // 初始化:检查是否已存在权限状态
                this.init();
            }

            // 初始化:判断是否显示弹窗
            init() {
                const permission = CookieUtil.get(this.permissionKey);
                if (!permission) {
                    // 未选择权限:显示弹窗和遮罩
                    cookieMask.classList.add('show');
                    cookieModal.classList.add('show');
                } else {
                    // 已选择权限:执行对应逻辑(如加载分析脚本)
                    this.handlePermission(permission);
                }
            }

            // 处理用户选择的权限(accept:全部接受;reject:仅必要)
            handlePermission(permission) {
                switch (permission) {
                    case 'accept':
                        // 全部接受:初始化功能、分析类 Cookie/脚本
                        this.initAllCookies();
                        break;
                    case 'reject':
                        // 仅必要:删除已存在的非必要 Cookie,不加载额外脚本
                        this.clearNonNecessaryCookies();
                        break;
                    default:
                        break;
                }
            }

            // 全部接受:初始化非必要 Cookie(示例:主题偏好、分析标识)
            initAllCookies() {
                // 示例1:功能 Cookie - 记住用户主题偏好(默认浅色)
                CookieUtil.set('site_theme', 'light', 30);
                // 示例2:分析 Cookie - 生成唯一访问标识(用于统计)
                CookieUtil.set('site_visit_id', this.generateRandomStr(), 7);
                // 示例3:加载第三方分析脚本(如百度统计、Google Analytics)
                this.loadAnalyticsScript();
            }

            // 仅必要:清除非必要 Cookie(示例:主题、分析相关)
            clearNonNecessaryCookies() {
                CookieUtil.remove('site_theme');
                CookieUtil.remove('site_visit_id');
                // 若已加载分析脚本,可执行关闭逻辑(如百度统计的暂停)
                this.stopAnalytics();
            }

            // 生成随机访问标识(用于分析 Cookie,避免重复)
            generateRandomStr(length = 16) {
                const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
                let str = '';
                for (let i = 0; i < length; i++) {
                    str += chars.charAt(Math.floor(Math.random() * chars.length));
                }
                return str;
            }

            // 加载第三方分析脚本(示例:百度统计,实际项目替换为自身脚本)
            loadAnalyticsScript() {
                // 避免重复加载:检查脚本是否已存在
                if (document.getElementById('baidu-analytics')) return;
                
                const script = document.createElement('script');
                script.id = 'baidu-analytics';
                script.src = 'https://hm.baidu.com/hm.js?您的百度统计ID';
                script.async = true; // 异步加载,不阻塞页面渲染
                document.body.appendChild(script);
            }

            // 停止分析脚本(示例:百度统计暂停统计)
            stopAnalytics() {
                if (window._hmt) {
                    _hmt.push(['_pause']); // 百度统计暂停接口
                }
            }

            // 用户选择权限:存储状态并隐藏弹窗
            setPermission(permission) {
                // 存储权限状态(accept/reject)
                CookieUtil.set(this.permissionKey, permission);
                // 处理权限逻辑
                this.handlePermission(permission);
                // 隐藏弹窗和遮罩
                cookieMask.classList.remove('show');
                cookieModal.classList.remove('show');
            }
        }

        // 4. 实例化并绑定事件
        const cookiePermission = new CookiePermission();

        // 接受全部权限
        acceptBtn.addEventListener('click', () => {
            cookiePermission.setPermission('accept');
        });

        // 仅必要权限
        rejectBtn.addEventListener('click', () => {
            cookiePermission.setPermission('reject');
        });

        // 显示权限详情
        showDetailBtn.addEventListener('click', () => {
            detailModal.classList.add('show');
        });

        // 关闭权限详情
        closeDetailBtn.addEventListener('click', () => {
            detailModal.classList.remove('show');
        });

        // 点击遮罩关闭详情弹窗
        cookieMask.addEventListener('click', () => {
            detailModal.classList.remove('show');
        });
    </script>
</body>
</html>
  • 编码处理:使用 encodeURIComponent / decodeURIComponent 对 Cookie 名称和值编码,避免特殊字符(如空格、&、=)导致的存储异常。
  • 域名与路径控制:设置 domain 为顶级域名(如 .xxx.com )确保子域名共享权限状态; path='/' 确保全站 Cookie 生效,避免局部路径无法读取的问题。
  • 过期时间管理:权限状态默认存储 365 天,非必要 Cookie 按需设置短期有效期(如主题 Cookie 30 天),减少无效 Cookie 占用。

(2)用户体验与合规性

  • 分层权限选择:支持「仅必要」和「全部接受」,符合 GDPR、CCPA 等隐私法规要求,避免强制同意。
  • 详情透明化:新增权限详情弹窗,明确告知不同类型 Cookie 的用途,提升用户信任度。
  • 移动端适配:弹窗固定底部、响应式宽度,按钮自适应布局,适配手机、平板等设备。

(3)性能优化

  • 脚本异步加载:第三方分析脚本使用 async=true ,避免阻塞页面 DOM 渲染,提升首屏加载速度。
  • 重复加载防护:加载分析脚本前检查是否已存在,避免多次执行导致的性能损耗和数据统计异常。
  • 状态优先判断:页面初始化时先读取 Cookie 状态,未选择时才显示弹窗,减少不必要的 DOM 操作。
  •  

图片转base64在线生成器源码

2025年11月4日 21:47

本地处理,无需上传服务器 | 支持JPG、PNG、GIF、WebP等格式 | 一键复制

Screenshot_2025_1104_214515.jpg

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>在线图片转Base64生成器</title>
  <style>
    :root {
      --primary: #3b82f6;
      --primary-light: #60a5fa;
      --bg: #f8fafc;
      --panel: #ffffff;
      --border: #e2e8f0;
      --text: #1e293b;
      --text-light: #64748b;
      --shadow: 0 4px 12px rgba(59, 130, 246, 0.1);
      --danger: #ef4444;
    }

    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
      font-family: 'Inter', system-ui, -apple-system, sans-serif;
    }

    body {
      background-color: var(--bg);
      color: var(--text);
      padding: 20px;
      line-height: 1.6;
    }

    header {
      text-align: center;
      margin-bottom: 30px;
      padding: 15px 0;
    }

    h1 {
      color: var(--primary);
      margin-bottom: 8px;
      font-weight: 700;
      font-size: 2rem;
    }

    .subtitle {
      color: var(--text-light);
      font-size: 1.1rem;
      max-width: 600px;
      margin: 0 auto;
    }

    .container {
      display: flex;
      flex-wrap: wrap;
      gap: 30px;
      max-width: 1200px;
      margin: 0 auto;
    }

    .upload-panel, .result-panel {
      flex: 1;
      min-width: 300px;
      background: var(--panel);
      border-radius: 16px;
      padding: 30px;
      box-shadow: var(--shadow);
    }

    .panel-title {
      font-size: 1.3rem;
      font-weight: 600;
      color: var(--primary);
      margin-bottom: 20px;
      display: flex;
      align-items: center;
      gap: 8px;
    }

    .upload-area {
      border: 2px dashed var(--border);
      border-radius: 12px;
      padding: 40px 20px;
      text-align: center;
      cursor: pointer;
      transition: all 0.3s;
      margin-bottom: 25px;
    }

    .upload-area:hover {
      border-color: var(--primary-light);
      background-color: rgba(59, 130, 246, 0.03);
    }

    .upload-area.active {
      border-color: var(--primary);
      background-color: rgba(59, 130, 246, 0.05);
    }

    .upload-icon {
      font-size: 3rem;
      color: var(--primary-light);
      margin-bottom: 15px;
    }

    .upload-text {
      font-size: 1.1rem;
      margin-bottom: 10px;
    }

    .upload-hint {
      font-size: 0.9rem;
      color: var(--text-light);
      line-height: 1.5;
    }

    #file-input {
      display: none;
    }

    .preview-container {
      margin-top: 25px;
      display: none;
    }

    .preview-title {
      font-weight: 600;
      margin-bottom: 10px;
      color: var(--text);
    }

    .preview-box {
      width: 100%;
      height: 200px;
      border: 1px solid var(--border);
      border-radius: 8px;
      overflow: hidden;
      display: flex;
      align-items: center;
      justify-content: center;
      background-color: #f1f5f9;
    }

    .preview-img {
      max-width: 100%;
      max-height: 100%;
      object-fit: contain;
    }

    .file-info {
      margin-top: 15px;
      padding: 12px;
      background-color: #f1f5f9;
      border-radius: 8px;
      font-size: 0.9rem;
    }

    .info-item {
      display: flex;
      justify-content: space-between;
      margin-bottom: 5px;
    }

    .info-label {
      color: var(--text-light);
    }

    .settings-group {
      margin-bottom: 25px;
    }

    .settings-label {
      display: block;
      margin-bottom: 10px;
      font-weight: 600;
      color: var(--text);
    }

    select {
      width: 100%;
      padding: 12px 15px;
      border: 2px solid var(--border);
      border-radius: 8px;
      font-size: 1rem;
      transition: all 0.2s;
      background-color: white;
    }

    select:focus {
      outline: none;
      border-color: var(--primary-light);
      box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
    }

    .btn {
      width: 100%;
      padding: 14px;
      border: none;
      border-radius: 8px;
      font-size: 1.1rem;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.2s;
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 8px;
    }

    .convert-btn {
      background-color: var(--primary);
      color: white;
      margin-bottom: 15px;
    }

    .convert-btn:hover {
      background-color: #2563eb;
      transform: translateY(-2px);
      box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
    }

    .convert-btn:active {
      transform: translateY(0);
    }

    .reset-btn {
      background-color: #f8fafc;
      color: var(--text);
      border: 2px solid var(--border);
    }

    .reset-btn:hover {
      background-color: #f1f5f9;
      border-color: #cbd5e1;
    }

    .result-content {
      margin-top: 20px;
    }

    .base64-output {
      width: 100%;
      min-height: 300px;
      padding: 15px;
      border: 2px solid var(--border);
      border-radius: 8px;
      font-family: monospace;
      font-size: 0.9rem;
      resize: vertical;
      background-color: white;
      white-space: pre-wrap;
      word-break: break-all;
      overflow-y: auto;
    }

    .output-actions {
      display: flex;
      gap: 15px;
      margin-top: 15px;
    }

    .action-btn {
      flex: 1;
      padding: 10px;
      border: none;
      border-radius: 8px;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.2s;
      font-size: 0.95rem;
    }

    .copy-btn {
      background-color: var(--primary);
      color: white;
    }

    .copy-btn:hover {
      background-color: #2563eb;
    }

    .download-btn {
      background-color: #f1f5f9;
      color: var(--text);
    }

    .download-btn:hover {
      background-color: #e2e8f0;
    }

    .notification {
      position: fixed;
      top: 20px;
      right: 20px;
      background-color: #10b981;
      color: white;
      padding: 12px 20px;
      border-radius: 8px;
      box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2);
      transform: translateX(120%);
      transition: transform 0.3s ease;
      z-index: 1000;
      display: flex;
      align-items: center;
      gap: 8px;
    }

    .notification.show {
      transform: translateX(0);
    }

    .error-notification {
      background-color: var(--danger);
    }

    .loading {
      display: none;
      text-align: center;
      padding: 20px;
    }

    .loading-spinner {
      width: 40px;
      height: 40px;
      border: 4px solid rgba(59, 130, 246, 0.1);
      border-left-color: var(--primary);
      border-radius: 50%;
      animation: spin 1s linear infinite;
      margin: 0 auto 15px;
    }

    @keyframes spin {
      to { transform: rotate(360deg); }
    }

    @media (max-width: 768px) {
      .container {
        flex-direction: column;
      }

      h1 {
        font-size: 1.8rem;
      }

      .upload-panel, .result-panel {
        padding: 25px;
      }

      .preview-box {
        height: 150px;
      }

      .base64-output {
        min-height: 250px;
      }
    }
  </style>
</head>
<body>
  <header>
    <h1>在线图片转Base64生成器</h1>
    <p class="subtitle">本地处理,无需上传服务器 | 支持JPG、PNG、GIF、WebP等格式 | 一键复制</p>
  </header>

  <div class="container">
    <div class="upload-panel">
      <h2 class="panel-title">
        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
          <polyline points="17 8 12 3 7 8"></polyline>
          <line x1="12" y1="3" x2="12" y2="15"></line>
        </svg>
        上传图片
      </h2>

      <!-- 上传区域 -->
      <div class="upload-area" id="upload-area">
        <div class="upload-icon">?</div>
        <div class="upload-text">点击或拖拽图片到此处上传</div>
        <div class="upload-hint">支持 JPG、PNG、GIF、WebP、BMP 格式<br>单文件最大 10MB</div>
        <input type="file" id="file-input" accept="image/*">
      </div>

      <!-- 预览区域 -->
      <div class="preview-container" id="preview-container">
        <div class="preview-title">图片预览</div>
        <div class="preview-box">
          <img src="" alt="预览图" class="preview-img" id="preview-img">
        </div>
        <div class="file-info">
          <div class="info-item">
            <span class="info-label">文件名:</span>
            <span class="info-value" id="file-name">-</span>
          </div>
          <div class="info-item">
            <span class="info-label">文件大小:</span>
            <span class="info-value" id="file-size">-</span>
          </div>
          <div class="info-item">
            <span class="info-label">尺寸:</span>
            <span class="info-value" id="file-dimensions">-</span>
          </div>
          <div class="info-item">
            <span class="info-label">格式:</span>
            <span class="info-value" id="file-format">-</span>
          </div>
        </div>
      </div>

      <!-- 设置区域 -->
      <div class="settings-group">
        <label class="settings-label" for="output-format">输出格式</label>
        <select id="output-format">
          <option value="full">完整 Base64 (带 Data URI 前缀)</option>
          <option value="pure">纯 Base64 (不含前缀)</option>
          <option value="html">HTML img 标签</option>
          <option value="css">CSS background 样式</option>
        </select>
      </div>

      <button class="btn convert-btn" id="convert-btn">
        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5"></path>
          <path d="M2 12l10 5 10-5"></path>
        </svg>
        开始转换
      </button>
      <button class="btn reset-btn" id="reset-btn">重置</button>
    </div>

    <div class="result-panel">
      <h2 class="panel-title">
        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
          <polyline points="14 2 14 8 20 8"></polyline>
          <line x1="16" y1="13" x2="8" y2="13"></line>
          <line x1="16" y1="17" x2="8" y2="17"></line>
          <polyline points="10 9 9 9 8 9"></polyline>
        </svg>
        转换结果
      </h2>

      <!-- 加载中 -->
      <div class="loading" id="loading">
        <div class="loading-spinner"></div>
        <div>转换中,请稍候...</div>
      </div>

      <!-- 结果输出 -->
      <div class="result-content" id="result-content" style="display: none;">
        <textarea class="base64-output" id="base64-output" readonly></textarea>
        <div class="output-actions">
          <button class="action-btn copy-btn" id="copy-btn">
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
              <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
              <path d="M5 15H4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h1"></path>
            </svg>
            复制结果
          </button>
          <button class="action-btn download-btn" id="download-btn">
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
                            <polyline points="7 10 12 15 17 10"></polyline>
              <line x1="12" y1="15" x2="12" y2="3"></line>
            </svg>
            下载 TXT
          </button>
        </div>
      </div>

      <!-- 初始提示 -->
      <div id="initial-hint" style="text-align: center; padding: 40px 20px; color: var(--text-light);">
        上传图片并点击"开始转换"按钮,生成的 Base64 代码将显示在这里
      </div>
    </div>
  </div>

  <div class="notification" id="notification">操作成功!</div>

  <script>
    // 获取DOM元素
    const uploadArea = document.getElementById('upload-area');
    const fileInput = document.getElementById('file-input');
    const previewContainer = document.getElementById('preview-container');
    const previewImg = document.getElementById('preview-img');
    const fileName = document.getElementById('file-name');
    const fileSize = document.getElementById('file-size');
    const fileDimensions = document.getElementById('file-dimensions');
    const fileFormat = document.getElementById('file-format');
    const outputFormat = document.getElementById('output-format');
    const convertBtn = document.getElementById('convert-btn');
    const resetBtn = document.getElementById('reset-btn');
    const loading = document.getElementById('loading');
    const resultContent = document.getElementById('result-content');
    const base64Output = document.getElementById('base64-output');
    const copyBtn = document.getElementById('copy-btn');
    const downloadBtn = document.getElementById('download-btn');
    const notification = document.getElementById('notification');
    const initialHint = document.getElementById('initial-hint');

    // 存储上传的文件和Base64编码
    let uploadedFile = null;
    let base64Code = '';

    // 格式化文件大小
    function formatFileSize(bytes) {
      if (bytes < 1024) return bytes + ' B';
      if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB';
      return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
    }

    // 显示通知
    function showNotification(message, isError = false) {
      notification.textContent = message;
      notification.className = 'notification';
      if (isError) notification.classList.add('error-notification');
      notification.classList.add('show');
      setTimeout(() => {
        notification.classList.remove('show');
      }, 2000);
    }

    // 处理图片上传
    function handleFileSelect(file) {
      if (!file) return;

      // 检查文件类型
      if (!file.type.startsWith('image/')) {
        showNotification('请上传图片文件!', true);
        return;
      }

      // 检查文件大小(最大10MB)
      const maxSize = 10 * 1024 * 1024; // 10MB
      if (file.size > maxSize) {
        showNotification(`文件过大(最大支持10MB),当前文件大小:${formatFileSize(file.size)}`, true);
        return;
      }

      uploadedFile = file;

      // 更新文件信息
      fileName.textContent = file.name;
      fileSize.textContent = formatFileSize(file.size);
      fileFormat.textContent = file.type.split('/')[1].toUpperCase();

      // 预览图片并获取尺寸
      const reader = new FileReader();
      reader.onload = function(e) {
        const img = new Image();
        img.onload = function() {
          fileDimensions.textContent = `${img.width} × ${img.height} px`;
          previewImg.src = e.target.result;
          previewContainer.style.display = 'block';
        };
        img.src = e.target.result;
      };
      reader.readAsDataURL(file);

      // 显示上传区域激活状态
      uploadArea.classList.add('active');
      setTimeout(() => {
        uploadArea.classList.remove('active');
      }, 500);
    }

    // 转换图片为Base64
    function convertToBase64() {
      if (!uploadedFile) {
        showNotification('请先上传图片!', true);
        return;
      }

      // 显示加载状态
      loading.style.display = 'block';
      resultContent.style.display = 'none';
      initialHint.style.display = 'none';

      // 读取文件并转换
      const reader = new FileReader();
      reader.onload = function(e) {
        base64Code = e.target.result;
        formatOutput(base64Code);

        // 隐藏加载状态,显示结果
        setTimeout(() => {
          loading.style.display = 'none';
          resultContent.style.display = 'block';
        }, 300);
      };
      reader.readAsDataURL(uploadedFile);
    }

    // 格式化输出结果
    function formatOutput(base64) {
      const format = outputFormat.value;
      let output = '';

      switch (format) {
        case 'full':
          output = base64;
          break;
        case 'pure':
          // 移除Data URI前缀
          output = base64.split(',')[1];
          break;
        case 'html':
          output = `<img src="${base64}" alt="图片" />`;
          break;
        case 'css':
          output = `background-image: url("${base64}");`;
          break;
      }

      base64Output.value = output;
    }

    // 复制结果到剪贴板
    function copyToClipboard() {
      if (!base64Code) {
        showNotification('暂无转换结果可复制!', true);
        return;
      }

      base64Output.select();
      document.execCommand('copy');
      showNotification('复制成功!');
    }

    // 下载Base64为TXT文件
    function downloadAsTxt() {
      if (!base64Code) {
        showNotification('暂无转换结果可下载!', true);
        return;
      }

      const blob = new Blob([base64Output.value], { type: 'text/plain' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `base64_${uploadedFile.name.split('.')[0]}.txt`;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);

      showNotification('下载已开始!');
    }

    // 重置所有状态
    function resetAll() {
      uploadedFile = null;
      base64Code = '';
      fileInput.value = '';
      previewContainer.style.display = 'none';
      previewImg.src = '';
      fileName.textContent = '-';
      fileSize.textContent = '-';
      fileDimensions.textContent = '-';
      fileFormat.textContent = '-';
      base64Output.value = '';
      resultContent.style.display = 'none';
      loading.style.display = 'none';
      initialHint.style.display = 'block';
      showNotification('已重置!');
    }

    // 事件监听 - 点击上传区域
    uploadArea.addEventListener('click', () => {
      fileInput.click();
    });

    // 事件监听 - 选择文件
    fileInput.addEventListener('change', (e) => {
      if (e.target.files.length > 0) {
        handleFileSelect(e.target.files[0]);
      }
    });

    // 事件监听 - 拖拽上传
    uploadArea.addEventListener('dragover', (e) => {
      e.preventDefault();
      uploadArea.classList.add('active');
    });

    uploadArea.addEventListener('dragleave', () => {
      uploadArea.classList.remove('active');
    });

    uploadArea.addEventListener('drop', (e) => {
      e.preventDefault();
      uploadArea.classList.remove('active');
      if (e.dataTransfer.files.length > 0) {
        handleFileSelect(e.dataTransfer.files[0]);
      }
    });

    // 事件监听 - 转换按钮
    convertBtn.addEventListener('click', convertToBase64);

    // 事件监听 - 重置按钮
    resetBtn.addEventListener('click', resetAll);

    // 事件监听 - 输出格式变更
    outputFormat.addEventListener('change', () => {
      if (base64Code) {
        formatOutput(base64Code);
      }
    });

    // 事件监听 - 复制按钮
    copyBtn.addEventListener('click', copyToClipboard);

    // 事件监听 - 下载按钮
    downloadBtn.addEventListener('click', downloadAsTxt);
  </script>
</body>
</html>

  •  

一个新拟态css生成器源码

2025年11月3日 18:01

演示截图什么的自己复制代码运行一下就知道了,保证你吃不了亏,上不了当。网页采用自适应。Screenshot_2025_1103_180010.jpg

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>新拟态 CSS 生成器</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Roboto, sans-serif;
        }
        body {
            background: #e0e5ec;
            min-height: 100vh;
            padding: 2rem 1rem;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 2rem;
        }
        @media (max-width: 768px) {
            .container {
                grid-template-columns: 1fr;
            }
        }
        .card {
            background: #e0e5ec;
            border-radius: 16px;
            padding: 2rem;
            box-shadow: 
                8px 8px 16px #c8ccd4,
                -8px -8px 16px #ffffff;
        }
        h1 {
            text-align: center;
            color: #4a6fa5;
            margin-bottom: 2rem;
            font-size: 2rem;
        }
        .preview-title {
            color: #555;
            margin-bottom: 1.5rem;
            font-size: 1.2rem;
        }
        .preview-box {
            width: 100%;
            height: 200px;
            display: flex;
            justify-content: center;
            align-items: center;
            border-radius: 12px;
            margin-bottom: 1.5rem;
            transition: all 0.3s ease;
            /* 初始新拟态样式 */
            background: #e0e5ec;
            box-shadow: 
                8px 8px 16px #c8ccd4,
                -8px -8px 16px #ffffff;
        }
        .preview-text {
            color: #4a6fa5;
            font-size: 1.1rem;
        }
        .control-group {
            margin-bottom: 1.5rem;
        }
        label {
            display: block;
            margin-bottom: 0.5rem;
            color: #666;
            font-weight: 500;
        }
        input[type="range"] {
            width: 100%;
            height: 8px;
            -webkit-appearance: none;
            appearance: none;
            background: #e0e5ec;
            border-radius: 4px;
            box-shadow: inset 3px 3px 6px #c8ccd4, inset -3px -3px 6px #ffffff;
            outline: none;
        }
        input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 20px;
            height: 20px;
            border-radius: 50%;
            background: #e0e5ec;
            box-shadow: 3px 3px 6px #c8ccd4, -3px -3px 6px #ffffff;
            cursor: pointer;
            transition: all 0.2s;
        }
        input[type="range"]::-webkit-slider-thumb:hover {
            box-shadow: 4px 4px 8px #c8ccd4, -4px -4px 8px #ffffff;
            transform: scale(1.1);
        }
        .color-group {
            display: flex;
            gap: 1rem;
            margin-top: 0.5rem;
        }
        input[type="color"] {
            width: 50px;
            height: 50px;
            border: none;
            border-radius: 8px;
            background: transparent;
            cursor: pointer;
            box-shadow: 3px 3px 6px #c8ccd4, -3px -3px 6px #ffffff;
        }
        input[type="color"]::-webkit-color-swatch-wrapper {
            padding: 0;
        }
        input[type="color"]::-webkit-color-swatch {
            border: none;
            border-radius: 4px;
        }
        .btn-group {
            display: flex;
            gap: 1rem;
            margin-top: 0.5rem;
        }
        .btn {
            flex: 1;
            padding: 0.8rem 1.5rem;
            background: #e0e5ec;
            border: none;
            border-radius: 8px;
            color: #4a6fa5;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s;
            box-shadow: 4px 4px 8px #c8ccd4, -4px -4px 8px #ffffff;
        }
        .btn:hover {
            box-shadow: 6px 6px 12px #c8ccd4, -6px -6px 12px #ffffff;
        }
        .btn:active {
            box-shadow: inset 3px 3px 6px #c8ccd4, inset -3px -3px 6px #ffffff;
        }
        .btn-reset {
            color: #e74c3c;
        }
        .code-box {
            background: #f8f9fa;
            border-radius: 8px;
            padding: 1.5rem;
            margin-top: 1rem;
            position: relative;
            overflow-x: auto;
        }
        pre {
            font-family: 'Fira Code', monospace;
            font-size: 0.9rem;
            color: #333;
            line-height: 1.6;
        }
        .copy-tip {
            position: absolute;
            top: 1rem;
            right: 1rem;
            color: #666;
            font-size: 0.8rem;
            opacity: 0;
            transition: opacity 0.3s;
        }
        .copy-tip.show {
            opacity: 1;
        }
        .switch-group {
            display: flex;
            align-items: center;
            gap: 1rem;
        }
        .switch {
            position: relative;
            display: inline-block;
            width: 50px;
            height: 24px;
        }
        .switch input {
            opacity: 0;
            width: 0;
            height: 0;
        }
        .slider {
            position: absolute;
            cursor: pointer;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: #e0e5ec;
            border-radius: 24px;
            box-shadow: inset 2px 2px 4px #c8ccd4, inset -2px -2px 4px #ffffff;
            transition: .4s;
        }
        .slider:before {
            position: absolute;
            content: "";
            height: 18px;
            width: 18px;
            left: 3px;
            bottom: 3px;
            background: white;
            border-radius: 50%;
            box-shadow: 1px 1px 3px #c8ccd4;
            transition: .4s;
        }
        input:checked + .slider {
            background: #4a6fa5;
        }
        input:checked + .slider:before {
            transform: translateX(26px);
        }
    </style>
</head>
<body>
    <h1>✨ 新拟态 CSS 生成器</h1>
    <div class="container">
        <!-- 左侧:参数控制区 -->
        <div class="card">
            <h2 class="preview-title">参数调节</h2>
            
            <!-- 阴影强度控制 -->
            <div class="control-group">
                <label for="shadow-strength">阴影强度: <span id="shadow-strength-value">8</span>px</label>
                <input type="range" id="shadow-strength" min="2" max="20" value="8">
            </div>

            <!-- 圆角控制 -->
            <div class="control-group">
                <label for="border-radius">圆角大小: <span id="border-radius-value">12</span>px</label>
                <input type="range" id="border-radius" min="0" max="30" value="12">
            </div>

            <!-- 颜色控制 -->
            <div class="control-group">
                <label>颜色配置</label>
                <div class="color-group">
                    <input type="color" id="bg-color" value="#e0e5ec">
                    <input type="color" id="shadow-dark" value="#c8ccd4">
                    <input type="color" id="shadow-light" value="#ffffff">
                </div>
            </div>

            <!-- 按压效果切换 -->
            <div class="control-group">
                <label class="switch-group">
                    按压效果
                    <label class="switch">
                        <input type="checkbox" id="press-effect">
                        <span class="slider"></span>
                    </label>
                </label>
            </div>

            <!-- 按钮组(复制+重置) -->
            <div class="btn-group">
                <button class="btn" id="copy-btn">复制 CSS 代码</button>
                <button class="btn btn-reset" id="reset-btn">重置默认值</button>
            </div>
            <div class="copy-tip" id="copy-tip">复制成功!</div>
        </div>

        <!-- 右侧:预览和代码区 -->
        <div class="card">
            <h2 class="preview-title">效果预览</h2>
            <div class="preview-box" id="preview-box">
                <span class="preview-text">新拟态效果</span>
            </div>

            <h2 class="preview-title">生成的 CSS 代码</h2>
            <div class="code-box">
                <pre id="css-code">
.neumorphic {
    background: #e0e5ec;
    border-radius: 12px;
    box-shadow: 
        8px 8px 16px #c8ccd4,
        -8px -8px 16px #ffffff;
}</pre>
            </div>
        </div>
    </div>

    <script>
        // 获取DOM元素
        const previewBox = document.getElementById('preview-box');
        const shadowStrength = document.getElementById('shadow-strength');
        const shadowStrengthValue = document.getElementById('shadow-strength-value');
        const borderRadius = document.getElementById('border-radius');
        const borderRadiusValue = document.getElementById('border-radius-value');
        const bgColor = document.getElementById('bg-color');
        const shadowDark = document.getElementById('shadow-dark');
        const shadowLight = document.getElementById('shadow-light');
        const pressEffect = document.getElementById('press-effect');
        const cssCode = document.getElementById('css-code');
        const copyBtn = document.getElementById('copy-btn');
        const resetBtn = document.getElementById('reset-btn');
        const copyTip = document.getElementById('copy-tip');

        // 默认参数配置
        const defaultConfig = {
            shadowStrength: 8,
            borderRadius: 12,
            bgColor: '#e0e5ec',
            shadowDark: '#c8ccd4',
            shadowLight: '#ffffff',
            pressEffect: false
        };

        // 更新预览和代码
        function updateNeumorphism() {
            const strength = shadowStrength.value;
            const radius = borderRadius.value;
            const bg = bgColor.value;
            const dark = shadowDark.value;
            const light = shadowLight.value;
            const isPressed = pressEffect.checked;

            // 更新预览样式
            previewBox.style.background = bg;
            previewBox.style.borderRadius = `${radius}px`;
            
            if (isPressed) {
                // 按压效果(内阴影)
                previewBox.style.boxShadow = `inset ${strength}px ${strength}px ${strength * 2}px ${dark}, inset -${strength}px -${strength}px ${strength * 2}px ${light}`;
            } else {
                // 普通效果(外阴影)
                previewBox.style.boxShadow = `${strength}px ${strength}px ${strength * 2}px ${dark}, -${strength}px -${strength}px ${strength * 2}px ${light}`;
            }

            // 更新代码显示
            const code = `.neumorphic {
    background: ${bg};
    border-radius: ${radius}px;
    box-shadow: 
        ${isPressed ? 'inset ' : ''}${strength}px ${strength}px ${strength * 2}px ${dark},
        ${isPressed ? 'inset ' : ''}-${strength}px -${strength}px ${strength * 2}px ${light};
}`;
            cssCode.textContent = code;

            // 更新数值显示
            shadowStrengthValue.textContent = strength;
            borderRadiusValue.textContent = radius;
        }

        // 重置默认值
        function resetToDefault() {
            // 恢复参数值
            shadowStrength.value = defaultConfig.shadowStrength;
            borderRadius.value = defaultConfig.borderRadius;
            bgColor.value = defaultConfig.bgColor;
            shadowDark.value = defaultConfig.shadowDark;
            shadowLight.value = defaultConfig.shadowLight;
            pressEffect.checked = defaultConfig.pressEffect;

            // 同步更新预览和代码
            updateNeumorphism();

            // 显示重置提示(可选)
            copyTip.textContent = '已重置默认值!';
            copyTip.classList.add('show');
            setTimeout(() => copyTip.classList.remove('show'), 2000);
        }

        // 初始化
        updateNeumorphism();

        // 绑定事件监听
        shadowStrength.addEventListener('input', updateNeumorphism);
        borderRadius.addEventListener('input', updateNeumorphism);
        bgColor.addEventListener('input', updateNeumorphism);
        shadowDark.addEventListener('input', updateNeumorphism);
        shadowLight.addEventListener('input', updateNeumorphism);
        pressEffect.addEventListener('change', updateNeumorphism);

        // 复制代码功能
        copyBtn.addEventListener('click', () => {
            navigator.clipboard.writeText(cssCode.textContent).then(() => {
                copyTip.textContent = '复制成功!';
                copyTip.classList.add('show');
                setTimeout(() => copyTip.classList.remove('show'), 2000);
            });
        });

        // 重置默认值功能
        resetBtn.addEventListener('click', resetToDefault);
    </script>
</body>
</html>
  •  

仿Typecho中的Http请求过滤

2025年11月2日 08:02

Typecho 的  Typecho_Http_Client  类功能非常强大,它封装了 PHP 的 cURL 扩展,提供了简洁的 HTTP 请求接口。

下面是一个功能和接口都与 Typecho 官方  Typecho_Http_Client  几乎完全一样的实现:

<?php
/**
 * Typecho 风格的 HTTP 客户端类
 * 封装 cURL 实现 HTTP 请求
 */
class Typecho_Http_Client
{
    /**
     * 请求方法常量
     */
    const METHOD_GET = 'GET';
    const METHOD_POST = 'POST';
    const METHOD_PUT = 'PUT';
    const METHOD_DELETE = 'DELETE';
    const METHOD_HEAD = 'HEAD';

    /**
     * 响应码
     * @var integer
     */
    private $_code;

    /**
     * 响应头
     * @var array
     */
    private $_headers = array();

    /**
     * 响应体
     * @var string
     */
    private $_body;

    /**
     * 请求URL
     * @var string
     */
    private $_url;

    /**
     * 请求方法
     * @var string
     */
    private $_method = self::METHOD_GET;

    /**
     * 请求头
     * @var array
     */
    private $_requestHeaders = array();

    /**
     * 请求数据
     * @var array|string
     */
    private $_data = array();

    /**
     * 超时时间(秒)
     * @var integer
     */
    private $_timeout = 30;

    /**
     * 连接超时时间(秒)
     * @var integer
     */
    private $_connectTimeout = 10;

    /**
     * 是否验证SSL证书
     * @var boolean
     */
    private $_verifyHost = true;
    private $_verifyPeer = true;

    /**
     * 代理设置
     * @var array
     */
    private $_proxy = array();

    /**
     * 单例实例
     * @var Typecho_Http_Client
     */
    private static $_instance;

    /**
     * 获取单例实例
     * 
     * @return Typecho_Http_Client
     */
    public static function get()
    {
        if (empty(self::$_instance)) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }

    /**
     * 构造函数
     */
    private function __construct()
    {
        // 检查cURL扩展是否可用
        if (!extension_loaded('curl')) {
            throw new RuntimeException('cURL extension is required for Typecho_Http_Client');
        }
    }

    /**
     * 设置请求URL
     * 
     * @param string $url
     * @return Typecho_Http_Client
     */
    public function setUrl($url)
    {
        $this->_url = $url;
        return $this;
    }

    /**
     * 设置请求方法
     * 
     * @param string $method
     * @return Typecho_Http_Client
     */
    public function setMethod($method)
    {
        $this->_method = strtoupper($method);
        return $this;
    }

    /**
     * 设置请求头
     * 
     * @param array $headers
     * @return Typecho_Http_Client
     */
    public function setHeaders($headers)
    {
        $this->_requestHeaders = $headers;
        return $this;
    }

    /**
     * 设置请求数据
     * 
     * @param array|string $data
     * @return Typecho_Http_Client
     */
    public function setData($data)
    {
        $this->_data = $data;
        return $this;
    }

    /**
     * 设置超时时间
     * 
     * @param integer $timeout
     * @return Typecho_Http_Client
     */
    public function setTimeout($timeout)
    {
        $this->_timeout = $timeout;
        return $this;
    }

    /**
     * 设置连接超时时间
     * 
     * @param integer $timeout
     * @return Typecho_Http_Client
     */
    public function setConnectTimeout($timeout)
    {
        $this->_connectTimeout = $timeout;
        return $this;
    }

    /**
     * 设置SSL验证
     * 
     * @param boolean $verifyPeer
     * @param boolean $verifyHost
     * @return Typecho_Http_Client
     */
    public function setVerifySsl($verifyPeer = true, $verifyHost = true)
    {
        $this->_verifyPeer = $verifyPeer;
        $this->_verifyHost = $verifyHost;
        return $this;
    }

    /**
     * 设置代理
     * 
     * @param string $host
     * @param integer $port
     * @param string $type
     * @param string $user
     * @param string $pass
     * @return Typecho_Http_Client
     */
    public function setProxy($host, $port = 8080, $type = 'HTTP', $user = '', $pass = '')
    {
        $this->_proxy = array(
            'host' => $host,
            'port' => $port,
            'type' => $type,
            'user' => $user,
            'pass' => $pass
        );
        return $this;
    }

    /**
     * 执行请求
     * 
     * @return boolean
     */
    public function send()
    {
        $ch = curl_init();

        // 基本设置
        curl_setopt($ch, CURLOPT_URL, $this->_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
        curl_setopt($ch, CURLOPT_TIMEOUT, $this->_timeout);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->_connectTimeout);

        // SSL设置
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->_verifyPeer);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $this->_verifyHost ? 2 : 0);

        // 请求方法设置
        switch ($this->_method) {
            case self::METHOD_POST:
                curl_setopt($ch, CURLOPT_POST, true);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $this->_data);
                break;
            case self::METHOD_PUT:
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, self::METHOD_PUT);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $this->_data);
                break;
            case self::METHOD_DELETE:
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, self::METHOD_DELETE);
                if (!empty($this->_data)) {
                    curl_setopt($ch, CURLOPT_POSTFIELDS, $this->_data);
                }
                break;
            case self::METHOD_HEAD:
                curl_setopt($ch, CURLOPT_NOBODY, true);
                break;
        }

        // 请求头设置
        if (!empty($this->_requestHeaders)) {
            $headers = array();
            foreach ($this->_requestHeaders as $key => $value) {
                $headers[] = "$key: $value";
            }
            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        }

        // 代理设置
        if (!empty($this->_proxy)) {
            curl_setopt($ch, CURLOPT_PROXY, $this->_proxy['host']);
            curl_setopt($ch, CURLOPT_PROXYPORT, $this->_proxy['port']);
            curl_setopt($ch, CURLOPT_PROXYTYPE, $this->_proxy['type'] == 'HTTPS' ? CURLPROXY_HTTPS : CURLPROXY_HTTP);
            
            if (!empty($this->_proxy['user']) && !empty($this->_proxy['pass'])) {
                curl_setopt($ch, CURLOPT_PROXYUSERPWD, "{$this->_proxy['user']}:{$this->_proxy['pass']}");
            }
        }

        // 响应头处理
        curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, '_parseHeader'));

        // 执行请求
        $this->_body = curl_exec($ch);
        $this->_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        // 检查错误
        $error = curl_error($ch);
        curl_close($ch);

        if ($error) {
            throw new RuntimeException("cURL Error: $error");
        }

        return true;
    }

    /**
     * 解析响应头
     * 
     * @param resource $ch
     * @param string $header
     * @return integer
     */
    private function _parseHeader($ch, $header)
    {
        $length = strlen($header);
        $header = trim($header);
        
        if (!empty($header) && strpos($header, ':') !== false) {
            list($key, $value) = explode(':', $header, 2);
            $key = trim($key);
            $value = trim($value);
            
            if (isset($this->_headers[$key])) {
                if (!is_array($this->_headers[$key])) {
                    $this->_headers[$key] = array($this->_headers[$key]);
                }
                $this->_headers[$key][] = $value;
            } else {
                $this->_headers[$key] = $value;
            }
        }
        
        return $length;
    }

    /**
     * 获取响应码
     * 
     * @return integer
     */
    public function getCode()
    {
        return $this->_code;
    }

    /**
     * 获取响应头
     * 
     * @param string $key
     * @return array|string|null
     */
    public function getHeaders($key = null)
    {
        if ($key === null) {
            return $this->_headers;
        }
        
        $key = ucwords(strtolower($key), '-');
        return isset($this->_headers[$key]) ? $this->_headers[$key] : null;
    }

    /**
     * 获取响应体
     * 
     * @return string
     */
    public function getBody()
    {
        return $this->_body;
    }

    /**
     * 获取JSON格式的响应体
     * 
     * @return array|object|null
     */
    public function getJson()
    {
        return json_decode($this->_body, true);
    }

    /**
     * 重置客户端状态
     * 
     * @return Typecho_Http_Client
     */
    public function reset()
    {
        $this->_code = 0;
        $this->_headers = array();
        $this->_body = '';
        $this->_url = '';
        $this->_method = self::METHOD_GET;
        $this->_requestHeaders = array();
        $this->_data = array();
        $this->_timeout = 30;
        $this->_connectTimeout = 10;
        $this->_verifyHost = true;
        $this->_verifyPeer = true;
        $this->_proxy = array();
        
        return $this;
    }

    /**
     * 魔术方法,实现链式调用
     * 
     * @param string $name
     * @param array $arguments
     * @return Typecho_Http_Client
     */
    public function __call($name, $arguments)
    {
        if (method_exists($this, $name)) {
            call_user_func_array(array($this, $name), $arguments);
            return $this;
        }
        
        throw new BadMethodCallException("Method $name does not exist");
    }
}
 
 

 

使用示例

这个类的使用方法和 Typecho 官方的  Typecho_Http_Client  完全一致:

<?php
try {
    // 发送GET请求
    $client = Typecho_Http_Client::get();
    $client->setUrl('https://api.example.com/data')
           ->setMethod('GET')
           ->setTimeout(15);
           
    if ($client->send()) {
        echo "响应码: " . $client->getCode() . "\n";
        echo "响应体: " . $client->getBody() . "\n";
        // 如果是JSON响应
        $jsonData = $client->getJson();
    }

    // 发送POST请求
    $client->reset();
    $client->setUrl('https://api.example.com/submit')
           ->setMethod('POST')
           ->setData(array('name' => 'Typecho', 'version' => '1.2'))
           ->setHeaders(array('Content-Type' => 'application/x-www-form-urlencoded'));
           
    if ($client->send()) {
        echo "POST响应: " . $client->getBody() . "\n";
    }

    // 发送JSON格式的POST请求
    $client->reset();
    $postData = json_encode(array('key' => 'value'));
    $client->setUrl('https://api.example.com/json')
           ->setMethod('POST')
           ->setData($postData)
           ->setHeaders(array('Content-Type' => 'application/json'));
           
    if ($client->send()) {
        echo "JSON响应: " . $client->getBody() . "\n";
    }

} catch (RuntimeException $e) {
    echo "HTTP请求失败: " . $e->getMessage() . "\n";
}

 

这个实现包含了 Typecho HTTP 客户端的所有核心功能:

  • 支持 GET、POST、PUT、DELETE、HEAD 等HTTP方法
  • 支持自定义请求头
  • 支持发送表单数据和JSON数据
  • 支持SSL证书验证
  • 支持代理设置
  • 支持响应头和响应体的解析
  • 提供了JSON格式的响应解析
  • 采用单例模式,支持链式调用

这个类可以直接替换 Typecho 原生的 HTTP 客户端类使用。

如果你想在实际项目中更好地使用这个 HTTP 客户端,我可以给你整理一份常见API请求场景的示例代码,比如发送JSON数据、处理文件上传等。需要吗?

  •  
❌