微博程序数据库引擎与操作类更换
近期重新编写了一款数据库操作类,以前的数据库操作类由于历史原因导致屎山代码难以维护。
目前新款数据库操作类已经弃用链式写法,直接在每个方法中通过相关配置完成。
所有模型数据已经初步修改完成,正在逐步排查新版数据库操作类适配的问题。
最重要的数据库引擎从以前的“MyISAM”更换成了“InnoDB”,以前的不支持事务,跟换后的可以使用事务,很多需要同步更新的数据会更加精准。
近期重新编写了一款数据库操作类,以前的数据库操作类由于历史原因导致屎山代码难以维护。
目前新款数据库操作类已经弃用链式写法,直接在每个方法中通过相关配置完成。
所有模型数据已经初步修改完成,正在逐步排查新版数据库操作类适配的问题。
最重要的数据库引擎从以前的“MyISAM”更换成了“InnoDB”,以前的不支持事务,跟换后的可以使用事务,很多需要同步更新的数据会更加精准。
实现 Cookie 隐私权限弹窗的核心是「底部固定显示 + 权限选择逻辑 + Cookie 状态存储」,确保用户首次访问时弹窗,选择后永久记忆,同时兼顾安全性和兼容性。(1)Cookie 操作安全![]()
<!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">×</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>
(2)用户体验与合规性
(3)性能优化
本地处理,无需上传服务器 | 支持JPG、PNG、GIF、WebP等格式 | 一键复制
![]()
<!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>
演示截图什么的自己复制代码运行一下就知道了,保证你吃不了亏,上不了当。网页采用自适应。![]()
<!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 的 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 客户端的所有核心功能:
这个类可以直接替换 Typecho 原生的 HTTP 客户端类使用。
如果你想在实际项目中更好地使用这个 HTTP 客户端,我可以给你整理一份常见API请求场景的示例代码,比如发送JSON数据、处理文件上传等。需要吗?
Typecho 的缓存机制主要通过 Typecho_Cache 类实现,它不仅支持文件缓存,还支持内存缓存等多种方式。
下面是一个核心的文件缓存实现,包含了缓存的设置、获取、删除和清空功能:
<?php
/**
* Typecho 风格的文件缓存类
*/
class Typecho_Cache
{
/**
* 缓存目录
* @var string
*/
private static $_cacheDir;
/**
* 初始化缓存目录
*
* @param string $cacheDir 缓存目录路径
* @return void
*/
public static function init($cacheDir = null)
{
if ($cacheDir === null) {
// 默认缓存目录为根目录下的 cache 文件夹
self::$_cacheDir = dirname(__FILE__) . '/cache';
} else {
self::$_cacheDir = $cacheDir;
}
// 如果缓存目录不存在,则创建
if (!is_dir(self::$_cacheDir)) {
mkdir(self::$_cacheDir, 0755, true);
}
}
/**
* 设置缓存
*
* @param string $key 缓存键名
* @param mixed $value 要缓存的数据
* @param integer $expire 过期时间(秒),0 表示永不过期
* @return boolean
*/
public static function set($key, $value, $expire = 0)
{
$cacheFile = self::getCacheFile($key);
// 序列化数据,并添加过期时间信息
$data = array(
'data' => $value,
'expire' => $expire > 0 ? time() + $expire : 0
);
return file_put_contents($cacheFile, serialize($data)) !== false;
}
/**
* 获取缓存
*
* @param string $key 缓存键名
* @return mixed 缓存数据或 false
*/
public static function get($key)
{
$cacheFile = self::getCacheFile($key);
// 检查缓存文件是否存在
if (!file_exists($cacheFile)) {
return false;
}
// 读取并反序列化数据
$data = unserialize(file_get_contents($cacheFile));
// 检查缓存是否过期
if ($data['expire'] != 0 && $data['expire'] < time()) {
// 缓存过期,删除文件
self::delete($key);
return false;
}
return $data['data'];
}
/**
* 删除指定缓存
*
* @param string $key 缓存键名
* @return boolean
*/
public static function delete($key)
{
$cacheFile = self::getCacheFile($key);
if (file_exists($cacheFile)) {
return unlink($cacheFile);
}
return false;
}
/**
* 清空所有缓存
*
* @return boolean
*/
public static function flush()
{
$handle = opendir(self::$_cacheDir);
if ($handle === false) {
return false;
}
while (($file = readdir($handle)) !== false) {
if ($file != '.' && $file != '..') {
$filePath = self::$_cacheDir . '/' . $file;
if (is_file($filePath)) {
unlink($filePath);
}
}
}
closedir($handle);
return true;
}
/**
* 根据键名生成缓存文件路径
*
* @param string $key 缓存键名
* @return string 缓存文件路径
*/
private static function getCacheFile($key)
{
// 使用 md5 哈希确保文件名安全且唯一
return self::$_cacheDir . '/' . md5($key) . '.cache';
}
}
使用示例
<?php
// 初始化缓存,指定缓存目录
Typecho_Cache::init('/path/to/your/cache/directory');
// 设置缓存,缓存 'my_data' 1小时(3600秒)
$data = array('name' => 'Typecho', 'version' => '1.2');
Typecho_Cache::set('my_data', $data, 3600);
// 获取缓存
$cachedData = Typecho_Cache::get('my_data');
if ($cachedData !== false) {
echo "从缓存中获取数据: ";
print_r($cachedData);
}
// 删除缓存
Typecho_Cache::delete('my_data');
// 清空所有缓存
Typecho_Cache::flush();1. 文件命名:使用 md5($key) 对缓存键名进行哈希处理,生成唯一且安全的文件名。
2. 数据存储:将数据和过期时间一起序列化后存入文件。
3. 过期检查:读取缓存时,先检查当前时间是否超过过期时间。
如果过期,则删除缓存文件并返回 false 。
4. 目录管理:提供了灵活的缓存目录设置和初始化方法。
还得是AI狠起来不管你是什么都一刀切,下面这款http请求类最初的AI优化版简直眼里容不得沙子,过滤得真的狠,安全确实安全,就是安全的不正常,反而成了BUG。经过反复修改十几遍终于完成了下面的这版,至于还有没有问题哪就不知道。
免费分享给大家看看博主使用AI优化的屎山代码
<?php
namespace xm;
/**
* HTTP请求参数过滤类(极致安全+极致性能版)
* 支持:GET/POST/COOKIE/SESSION/HTTP头/文件上传参数过滤
* 依赖:PHP 7.4+(支持类型声明、match表达式)
*/
class http
{
// -------------------------- 安全常量(编译时确定,无运行时开销) --------------------------
//private const MAX_PARAM_LENGTH = 2048; // 单个参数最大长度(防超长Payload)(太狠了,一刀斩)
private const MAX_GET_PARAM_LENGTH = 2048; // GET参数最大长度(严格限制)
private const MAX_POST_PARAM_LENGTH = 1048576; // POST参数最大长度(1MB,可按需调整)
private const MAX_URL_DECODE = 3; // URL最大解码次数(防DoS,3次足够覆盖大部分场景)
private const DANGER_REPLACE = ''; // 危险内容替换为空(比替换为xxx更安全)
private const ENCODING_TARGET = 'UTF-8'; // 统一编码(防宽字节)
private const ALLOWED_HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE']; // 允许的请求方法
//private const ALLOWED_UPLOAD_EXTS = ['jpg', 'jpeg', 'png', 'gif', 'pdf']; // 允许的上传扩展名
// 新增:HTML注释标记(统一匹配,避免硬编码,编译时确定无运行时开销)
private const HTML_COMMENT_START = '<!--';
private const HTML_COMMENT_END = '-->';
// 高频危险关键词哈希表(O(1)查找,比正则快10倍以上)
private const DANGER_HASH = [
'<script' => true, '</script>' => true, 'alert(' => true, 'confirm(' => true,
'prompt(' => true, 'eval(' => true, 'exec(' => true, 'union select' => true,
'insert into' => true, 'update ' => true, 'delete from' => true, 'drop table' => true,
'../' => true, '..\\' => true, 'data:text/html' => true, 'data:text/javascript' => true,
'onload=' => true, 'onclick=' => true, 'onerror=' => true, 'svg ' => true, 'math ' => true
];
// -------------------------- 过滤规则(按优先级排序:快规则在前) --------------------------
// 1. 字符串替换规则(比正则快50倍)
private const STRING_FILTER = [
'search' => [
'<script', '</script>', '<?php', '?>', '<?', '?>',
'../', '..\\', '&#x', '�', '&#', '\\x', '%00', '%0a', '%0d'
],
'replace' => [
'<script', '</script>', '<?php', '?>',
'<?', '?>', '', '', '&#x', '�', '&#', '\\x', '', '', ''
]
];
// 2. 预编译正则规则(按功能拆分,减少回溯)
private const REGEX_FILTER = [
'xss' => '/<(?!<!--)[^>]*?(on\w+|xlink:href|data:text)=(["\'])?[^\2]*?\2?[^>]*?>/iS',
'sql' => '/\b(benchmark|sleep|load_file|group_concat)\s*?\(|\/\*!.*?\*\/|#.*?$|--\s+/iS',
'unicode' => '/[\x{ff1c}\x{ff1e}\x{ff07}\x{ff27}\x{ff02}\x{ff22}\x{ff3c}\x{ff3e}\x{200b}-\x{200f}]/uS',
//'unicode' => '/[\x{ff00}-\x{ffff}\x{200b}-\x{200f}]/uS', // 全角字符+零宽空格
'sensitive' => '/1[3-9]\d{9}|[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]/S' // 手机号+身份证
];
// -------------------------- 静态缓存(类级别,仅初始化1次) --------------------------
private static array $superGlobalRefs; // 超全局变量引用(避免重复获取)
private static array $compiledRegex; // 预编译正则(避免重复编译)
private static array $filteredCache; // 过滤结果缓存(同一请求内复用)
// 实例属性(仅存储必要状态)
private string $requestMethod; // 当前请求方法(缓存,避免重复读取$_SERVER)
private string $clientIp; // 客户端IP(缓存,避免重复计算)
/**
* 构造函数:静态资源初始化(仅首次实例化执行)
* @throws \InvalidArgumentException 不允许的请求方法
*/
public function __construct()
{
// 1. 初始化静态超全局引用(避免重复访问超全局数组)
if (empty(self::$superGlobalRefs)) {
self::$superGlobalRefs = [
'get' => &$_GET,
'post' => &$_POST,
'cookie' => &$_COOKIE,
'session' => &$_SESSION,
'server' => &$_SERVER,
'files' => &$_FILES
];
}
// 2. 预编译正则(仅1次,编译后复用)
if (empty(self::$compiledRegex)) {
self::$compiledRegex = [];
foreach (self::REGEX_FILTER as $key => $pattern) {
self::$compiledRegex[$key] = preg_match($pattern, '') === false
? "正则表达式无效: $pattern"
: $pattern;
}
}
// 3. 初始化请求基础信息(缓存,减少$_SERVER访问)
$this->requestMethod = strtoupper(self::$superGlobalRefs['server']['REQUEST_METHOD'] ?? 'GET');
$this->clientIp = $this->getClientIp();
// 4. 验证请求方法(提前拦截不允许的方法)
if (!in_array($this->requestMethod, self::ALLOWED_HTTP_METHODS, true)) {
$this->logAttack('不允许的请求方法', ['method' => $this->requestMethod]);
$this->abort('非法请求方法');
}
// 5. 初始化过滤缓存
if (empty(self::$filteredCache)) {
self::$filteredCache = [];
}
}
/**
* 获取客户端真实IP(防IP伪造)
* @return string 客户端IP
*/
private function getClientIp(): string
{
$ipHeaders = ['HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'];
foreach ($ipHeaders as $header) {
$ip = self::$superGlobalRefs['server'][$header] ?? '';
if ($ip && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
// 处理X-Forwarded-For多IP场景(取第一个有效IP)
if ($header === 'HTTP_X_FORWARDED_FOR') {
$ip = explode(',', $ip)[0];
}
return trim($ip);
}
}
return '0.0.0.0';
}
/**
* 核心过滤入口(按优先级执行:快规则→慢规则)
* @param mixed $value 待过滤值(字符串/数组)
* @param string $type 参数类型(get/post/cookie等)
* @return mixed 过滤后的值
*/
public function filter($value, string $type): mixed
{
// 1. 空值直接返回(避免后续处理)
if ($value === null || $value === '') {
return $value;
}
// 2. 数组递归过滤(手动递归,比array_walk_recursive快30%)
if (is_array($value)) {
foreach ($value as &$item) { // 引用传递,避免拷贝
$item = $this->filter($item, $type);
}
unset($item); // 释放引用,防污染
return $value;
}
// 3. 非字符串转字符串(前置判断,避免重复转换)
$valueStr = is_string($value) ? $value : strval($value);
$maxLength = ($type === 'post')
? self::MAX_POST_PARAM_LENGTH
: self::MAX_GET_PARAM_LENGTH;
// 4. 参数长度限制(超长按攻击处理,减少后续开销)
if (strlen($valueStr) > $maxLength) {
$this->logAttack('参数超长', ['length' => strlen($valueStr)]);
return self::DANGER_REPLACE;
}
// 5. 缓存命中检查(同一请求内复用过滤结果)
$cacheKey = md5($type . $valueStr);
if (isset(self::$filteredCache[$cacheKey])) {
return self::$filteredCache[$cacheKey];
}
// -------------------------- 过滤流程(优先级:快→慢) --------------------------
// 6. 字符串替换(最快,处理高频危险字符)
$filtered = str_replace(
self::STRING_FILTER['search'],
self::STRING_FILTER['replace'],
$valueStr
);
// 7. 哈希表检测(O(1),仅检测注释外内容,避免漏防违规SQL)- 替换原逻辑
// 第一步:拆分注释内容与非注释内容(原生strpos/substr,极快无性能损耗)
$commentStart = strpos($filtered, self::HTML_COMMENT_START);
$commentEnd = $commentStart !== false ? strpos($filtered, self::HTML_COMMENT_END, $commentStart + strlen(self::HTML_COMMENT_START)) : false;
$nonCommentContent = '';
if ($commentStart !== false && $commentEnd !== false) {
$commentEnd += strlen(self::HTML_COMMENT_END); // 定位到注释结束符末尾(含-->)
$nonCommentContent = substr($filtered, 0, $commentStart) . substr($filtered, $commentEnd); // 提取注释外的内容
} else {
$nonCommentContent = $filtered; // 无注释时,全量检测
}
// 第二步:仅对非注释内容执行哈希检测(注释内无危险关键词,无需检测)
$lowerNonComment = strtolower($nonCommentContent);
foreach (self::DANGER_HASH as $danger => $_) {
if (strpos($lowerNonComment, strtolower($danger)) !== false) {
$this->logAttack('哈希表检测到危险内容', ['content' => $danger]);
self::$filteredCache[$cacheKey] = self::DANGER_REPLACE;
return self::DANGER_REPLACE;
}
}
// 8. URL解码(最多3次,防编码绕过)
$decodedCount = 0;
while (urldecode($filtered) !== $filtered && $decodedCount < self::MAX_URL_DECODE) {
$filtered = urldecode($filtered);
$decodedCount++;
}
// 9. 统一编码(防宽字节注入)
$filtered = $this->convertEncoding($filtered);
// 10. 正则过滤(最慢,仅处理注释外的复杂变形,兼顾性能与安全)- 替换原逻辑
// 复用前面的$nonCommentContent,仅检测注释外内容(避免注释被误判)
foreach (self::REGEX_FILTER as $key => $pattern) {
if (preg_match($pattern, $nonCommentContent)) {
$this->logAttack("正则检测到危险内容$key", ['content' => substr($nonCommentContent, 0, 100)]);
self::$filteredCache[$cacheKey] = self::DANGER_REPLACE;
return self::DANGER_REPLACE;
}
}
// 11. 缓存过滤结果
self::$filteredCache[$cacheKey] = $filtered;
return $filtered;
}
/**
* 统一编码为UTF-8(防宽字节注入)
* @param string $str 待转换字符串
* @return string 转换后字符串
*/
private function convertEncoding(string $str): string
{
// 严格检测编码(避免误判)
$encoding = mb_detect_encoding(
$str,
['UTF-8', 'GBK', 'GB2312', 'LATIN1', 'CP936'],
true
);
// 仅在编码不同时转换(减少函数调用开销)
return $encoding !== false && $encoding !== self::ENCODING_TARGET
? mb_convert_encoding($str, self::ENCODING_TARGET, $encoding)
: $str;
}
/**
* 文件上传参数专项过滤(防恶意文件名/路径)
* @param string $field 上传字段名
* @return array|null 过滤后文件信息,null表示危险
*/
public function filterFile(string $field): ?array
{
$file = self::$superGlobalRefs['files'][$field] ?? null;
if (!$file || $file['error'] !== UPLOAD_ERR_OK) {
return null;
}
// 1. 过滤文件名(防路径穿越+恶意扩展名)
$fileName = $this->filter($file['name'], 'files');
$fileExt = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
// 2. 验证扩展名
/*if (!in_array($fileExt, self::ALLOWED_UPLOAD_EXTS, true)) {
$this->logAttack('非法上传扩展名', ['ext' => $fileExt, 'name' => $fileName]);
@unlink($file['tmp_name']); // 删除恶意文件
return null;
}*/
// 3. 过滤临时路径(防路径穿越)
$tmpPath = $this->filter($file['tmp_name'], 'files');
if (!is_uploaded_file($tmpPath)) { // 验证是否为合法上传文件
$this->logAttack('非法临时文件路径', ['path' => $tmpPath]);
@unlink($tmpPath);
return null;
}
return [
'name' => $fileName,
'type' => $this->filter($file['type'], 'files'),
'tmp_name' => $tmpPath,
'error' => $file['error'],
'size' => $file['size']
];
}
/**
* 获取过滤后的参数(统一入口)
* @param string $type 参数类型(get/post/cookie/session/server)
* @param string|null $key 参数名(null=获取全部)
* @param mixed $default 默认值
* @return mixed 过滤后的值
*/
public function gets(string $type, ?string $key = null, $default = null)
{
// 1. 验证参数类型
if (!isset(self::$superGlobalRefs[$type])) {
throw new \InvalidArgumentException("不支持的参数类型: $type");
}
$source = &self::$superGlobalRefs[$type];
$cacheKey = "get_{$type}_{$key}";
// 2. 缓存命中检查
if (isset(self::$filteredCache[$cacheKey])) {
return self::$filteredCache[$cacheKey];
}
// 3. 获取全部参数
if ($key === null) {
$result = $this->filter($source, $type);
self::$filteredCache[$cacheKey] = $result;
return $result;
}
// 4. 获取单个参数
$value = $source[$key] ?? $default;
$result = $this->filter($value, $type);
// 5. 缓存结果
self::$filteredCache[$cacheKey] = $result;
return $result;
}
/**
* 攻击日志记录(异步友好,避免阻塞请求)
* @param string $title 日志标题
* @param array $data 日志详情
*/
private function logAttack(string $title, array $data): void
{
$logData = [
//'time' => date('Y-m-d H:i:s'),
//'ip' => $this->clientIp,
//'method' => $this->requestMethod,
//'uri' => self::$superGlobalRefs['server']['REQUEST_URI'] ?? '/',
'title' => $title,
'data' => $data
];
// 1. 同步日志(基础场景)
if (function_exists('XLOG')) {
XLOG(json_encode($logData, JSON_UNESCAPED_UNICODE));
}
// 2. 异步日志(高性能场景,需配合队列)
// @file_put_contents('/tmp/http_attack.log', json_encode($logData) . PHP_EOL, FILE_APPEND);
}
/**
* 终止请求(防信息泄露)
* @param string $msg 错误信息
* @param int $code HTTP状态码
*/
private function abort(string $msg, int $code = 403): void
{
http_response_code($code);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['code' => $code, 'msg' => $msg]);
exit();
}
// -------------------------- 快捷调用方法(减少类型参数传递) --------------------------
public function get(?string $key = null, $default = null)
{
return $this->gets('get', $key, $default);
}
public function post(?string $key = null, $default = null)
{
return $this->gets('post', $key, $default);
}
public function cookie(?string $key = null, $default = null)
{
return $this->gets('cookie', $key, $default);
}
public function session(?string $key = null, $default = null)
{
return $this->gets('session', $key, $default);
}
public function header(?string $key = null, $default = null)
{
$headerKey = $key ? 'HTTP_' . strtoupper(str_replace('-', '_', $key)) : null;
return $this->gets('server', $headerKey, $default);
}
}最近在使用AI重构微博客核心框架,相较于之前是提高了安全性,可是AI说好的比优化之前性能效率提高好几个百分点。结果实测性能反而下降了几个百分点,属实拉了一坨大的。还附带各种奇奇怪怪的问题,已知的BUG已经努力修改BUG一整天了,未知的BUG还不知道有多少。使用AI修改代码的时候是爽了,修改BUG的时候就寄了。
最近博主通过手机端某克浏览器登录网站后台老是登录成功后又光速掉线,需要反反复复登录N多次才能不掉线。
真是奇了怪了,第一次出现这种情况的时候怀疑网站程序出现了问题,不过当时也没有过多追加问题。
前段时候博主沉迷于开发文字游戏(最后GG了,只写了一个开发框架,最后苦于没有剧情等文案设计就烂尾了),突然想发一点文章到博客中,于是就用电脑登录试了一下,没想到的是居然一次就成功了不掉线。于是经过几天的观察发现应该不是程序的问题,要是程序有问题按理说不管换什么浏览器都不可能登录上。
于是博主在手机上面下载了其他几个款浏览器测试均没有问题,就是某克浏览器不管怎么样都需要登录几次才能成功,奇了怪哉。
这个依赖注入容器基本实现了功能如下:容器(Container)- 服务(Service)- 绑定(Binding)- 单例(Singleton)- 上下文绑定- 延迟加载
单文件完整版本
# Dream Kernel Vessel 容器使用文档
dream\kernel\vessel\Container 是一个轻量级依赖注入容器,用于管理类的实例化、依赖解析和服务注册,支持单例、上下文绑定、延迟加载等功能。以下是详细使用指南:
所有类位于 dream\kernel\vessel 命名空间下,使用前需通过 use 引入:
use dream\kernel\vessel\Container;$container = new Container();将服务标识符与具体实现绑定,每次解析都会创建新实例(非单例)。
// 绑定类名(默认使用类名作为标识符)
$container->bind('logger', \app\services\Logger::class);
// 绑定闭包(更灵活的实例化逻辑)
$container->bind('cache', function ($container) {
return new \app\services\Cache($container->get('config'));
});
// 简化绑定(标识符与类名相同时)
$container->bind(\app\services\Db::class);单例服务只会实例化一次,后续调用 get() 会返回同一个实例。
// 绑定单例类
$container->singleton('config', \app\Config::class);
// 绑定单例闭包
$container->singleton('db', function ($container) {
return new \PDO(...);
});为服务添加标签,便于批量获取同一类别的服务。
// 为服务添加标签
$container->tag(['logger', 'cache'], 'services');
$container->tag('db', 'services');
// 通过标签获取所有服务
$services = $container->getByTag('services');
// $services 包含 'logger'、'cache'、'db' 三个服务实例通过服务标识符从容器中获取实例。
// 解析服务
$logger = $container->get('logger');
$db = $container->get(\app\services\Db::class);if ($container->has('cache')) {
// 服务已注册或实例化
}容器会自动解析类的构造函数依赖,无需手动传递参数。
class UserService {
private $db;
private $logger;
// 构造函数依赖 Db 和 Logger
public function __construct(\app\services\Db $db, \app\services\Logger $logger) {
$this->db = $db;
$this->logger = $logger;
}
}
// 注册依赖的服务
$container->bind(\app\services\Db::class);
$container->bind(\app\services\Logger::class);
// 解析 UserService 时,容器会自动注入 Db 和 Logger
$userService = $container->get(UserService::class);为特定类提供专属的依赖实现(场景:同一接口在不同类中需要不同实现)。
// 定义接口和实现
interface PaymentInterface {
public function pay();
}
class Alipay implements PaymentInterface {
public function pay() { /* 支付宝支付逻辑 */ }
}
class WechatPay implements PaymentInterface {
public function pay() { /* 微信支付逻辑 */ }
}
// 订单服务依赖 PaymentInterface
class OrderService {
private $payment;
public function __construct(PaymentInterface $payment) {
$this->payment = $payment;
}
}
// 购物车服务也依赖 PaymentInterface
class CartService {
private $payment;
public function __construct(PaymentInterface $payment) {
$this->payment = $payment;
}
}
// 上下文绑定:为 OrderService 绑定 Alipay,为 CartService 绑定 WechatPay
$container
->when(OrderService::class)
->needs(PaymentInterface::class)
->give(Alipay::class);
$container
->when(CartService::class)
->needs(PaymentInterface::class)
->give(WechatPay::class);
// 解析时会自动注入对应的实现
$orderService = $container->get(OrderService::class); // 注入 Alipay
$cartService = $container->get(CartService::class); // 注入 WechatPay通过代理类延迟服务实例化,直到首次调用方法时才创建对象(适用于资源密集型服务)。
// 绑定延迟加载的服务
$container->bind('heavyService', new \dream\kernel\vessel\LazyProxy(\app\services\HeavyService::class));
// 此时并未实例化 HeavyService
$proxy = $container->get('heavyService');
// 首次调用方法时,才会实例化 HeavyService
$proxy->doSomething(); // 触发实例化并调用方法容器在解析服务失败时会抛出 Exception,包含详细错误信息:
try {
$container->get('undefinedService');
} catch (\Exception $e) {
echo $e->getMessage(); // 输出:Error resolving [undefinedService]: Service [undefinedService] not found.
}use dream\kernel\vessel\Container;
// 初始化容器
$container = new Container();
// 绑定服务
$container->singleton('config', function () {
return new \app\Config(['db_host' => 'localhost']);
});
$container->bind('db', function ($c) {
return new \app\Db($c->get('config'));
});
// 上下文绑定
$container
->when(\app\services\AdminLogger::class)
->needs(\app\services\Logger::class)
->give(\app\services\FileLogger::class);
// 解析服务
$db = $container->get('db');
$adminLogger = $container->get(\app\services\AdminLogger::class);