网站设置时间段访问实战:从零搭建到上线避坑指南
域名服务器搞不懂,很多老板在接手老项目或者新站上线时,第一反应就是懵。明明网站代码没问题,为什么半夜三点突然打不开?或者为什么只允许白天访问,晚上全挂了?这往往不是代码写错了,而是底层的访问控制策略没配好。今天咱们不聊虚的,直接拆解一个真实案例:某连锁餐饮企业要求官网仅在营业时段(早8点至晚10点)对外展示品牌内容,非营业时段自动跳转至维护页或关闭入口,以节省服务器资源并防止非工作时间的无效流量冲击。这就是典型的【网站设置时间段访问】需求,看似简单,但从零搭建这套逻辑,坑比想象中多。
项目背景与需求:不只是开关那么简单
接到这个需求时,客户给出的场景很具体:他们的官网承载了品牌展示和线上点餐入口,但后端点餐系统只在营业时间内运行。非营业时间,点餐接口会报错,甚至因为高并发测试导致服务器负载飙升。客户希望做到“精准控制”,不是简单的全站关闭,而是首页和静态资源可以访问,但涉及交易和动态数据的页面在夜间自动禁用。
这里有个巨大的误区:很多人以为“时间段访问”就是改个时间戳,到了点就放行。但实际落地时,你会发现这涉及前端展示、后端接口鉴权、Nginx反向代理、甚至Cloudflare边缘节点的多层配合。
核心痛点拆解:
- 时区问题:服务器在美国西部时间,用户在中国东部时间,如果不做时区校准,你的“晚上10点”可能是用户的“早上11点”。
- 缓存干扰:静态资源被CDN缓存后,用户可能一直看到“已关闭”或“已开放”的旧状态,导致体验割裂。
- 安全边界:如果只在前端JS里判断时间,黑客稍微改一下浏览器时间就能绕过限制,必须后端硬控。
在这个项目中,我们最初尝试过最简单的方案:在Nginx配置文件里写一个if判断。结果发现,Nginx的time()函数在性能上有一定损耗,且无法处理复杂的业务逻辑(比如节假日加班营业)。因此,我们需要一个更灵活、可配置且高性能的方案。
技术选型:为什么放弃纯Nginx配置?
在技术选型阶段,我们对比了三种主流方案:
方案一:Nginx Server Rewrite 模块
直接在Nginx配置文件中通过map或if指令判断当前时间。
- 优点:无代码侵入,性能极高,在边缘层直接拦截。
- 缺点:配置僵硬,修改时间规则需要重载Nginx配置,对于复杂业务(如分地区、分节假日)难以维护。且Nginx的
localtime依赖服务器系统时间,多服务器集群部署时容易出现时间不同步问题。
方案二:应用层中间件(Middleware) 在Java Spring Boot或Node.js Express中编写中间件,拦截所有请求,判断当前时间是否在允许范围内。
- 优点:逻辑灵活,可以对接数据库中的营业时间配置表,支持动态调整。
- 缺点:请求必须穿透到应用服务器,对于纯静态资源(图片、CSS)造成不必要的计算开销。
方案三:Cloudflare Worker + 后端鉴权(最终选择) 利用Cloudflare Worker在边缘节点进行初步的时间过滤,对于动态接口再由后端二次校验。
- 优点:Cloudflare 文档中明确支持通过Worker脚本进行细粒度的请求拦截,且全球节点时间同步精度极高。将过滤逻辑前置到边缘,能大幅降低源站压力。
- 缺点:有一定的学习曲线,需要熟悉JS/TS编写Worker逻辑,且依赖Cloudflare的计费模型(虽然免费额度够用)。
我们最终选择了方案三的混合模式。静态资源由Cloudflare Cache处理,不经过Worker拦截;动态API请求经过Worker进行时间预检;核心业务逻辑在后端再次校验,形成双重保险。
核心实现:代码与配置详解
这部分是干货,直接上代码。假设我们使用的是Node.js (Express) 作为后端,配合Cloudflare Worker进行边缘过滤。
1. Cloudflare Worker 边缘拦截层
我们在Cloudflare Dashboard创建了一个Worker,命名为time-gatekeeper。这个脚本的作用是:如果请求的路径以/api/开头,且当前时间不在营业时段,直接返回403状态码,防止请求到达源站。
// time-gatekeeper.js
// 基于 Cloudflare 文档推荐的 Worker 最佳实践const BUSINESS_HOURS = {start: 8, // 早上8点end: 22, // 晚上10点timezone: 'Asia/Shanghai' // 明确指定时区,避免服务器时区偏差
};export default {async fetch(request, env, ctx) {const url = new URL(request.url);// 只拦截动态API请求,静态资源直接放行if (!url.pathname.startsWith('/api/')) {return fetch(request);}// 获取当前上海时间的小时数const now = new Date();const options = { timeZone: BUSINESS_HOURS.timezone, hour12: false };const formatter = new Intl.DateTimeFormat('en-US', options);const currentHour = parseInt(formatter.format(now), 10);// 判断是否在营业时间内const isBusinessTime = currentHour >= BUSINESS_HOURS.start && currentHour < BUSINESS_HOURS.end;if (!isBusinessTime) {return new Response(JSON.stringify({ error: "Service is currently closed. Please try again during business hours.",code: "TIME_RESTRICTED"}), { status: 403, headers: { 'Content-Type': 'application/json' }});}// 允许请求继续向源站转发return fetch(request);}
};
关键点解析:
Intl.DateTimeFormat:这是处理时区的标准API,比手动计算时差更可靠。fetch(request):这是Worker中将请求透传给源站的标准方法。- 前置过滤:这一步确保了90%以上的非营业时间请求在Cloudflare边缘就被拦截,源站几乎无感知。
2. 后端 Node.js 双重校验
即使前端和边缘层做了拦截,后端也必须做最后的一道防线。这是为了防御直接绕过CDN直连源站IP的攻击。
// middleware/timeGuard.js
const { createProxyMiddleware } = require('http-proxy-middleware');// 简单的时间判断工具函数
const isWithinBusinessHours = () => {const now = new Date();// 获取上海时区的小时数const hour = now.toLocaleString('en-US', { timeZone: 'Asia/Shanghai', hour: 'numeric', hour12: false });const h = parseInt(hour, 10);return h >= 8 && h < 22;
};// Express 中间件
const timeGuardMiddleware = (req, res, next) => {// 白名单路径:健康检查、静态资源等if (req.path === '/health' || req.path.startsWith('/static/')) {return next();}if (!isWithinBusinessHours()) {return res.status(403).json({message: 'Service unavailable outside business hours.',timestamp: Date.now()});}next();
};module.exports = { timeGuardMiddleware, isWithinBusinessHours };
为什么后端还要判一次? 因为Cloudflare Worker是“尽力而为”的边缘服务,如果遇到网络抖动、Worker超时或者用户直接解析源站IP访问,边缘拦截就会失效。后端中间件是逻辑的最终裁决者。
3. 前端体验优化:优雅降级
如果后端返回403,前端不能直接显示“错误代码403”,而是要展示一个友好的维护页。
// apiClient.js
const apiRequest = async (url, options) => {try {const response = await fetch(url, options);if (response.status === 403) {const data = await response.json();if (data.code === 'TIME_RESTRICTED') {// 触发全局事件,切换UI到“非营业状态”window.dispatchEvent(new CustomEvent('show-closed-banner', { detail: { message: data.error } }));return null; // 不抛出错误,静默处理}}if (!response.ok) {throw new Error('Network response was not ok');}return response.json();} catch (error) {console.error('API Error:', error);throw error;}
};
前端监听show-closed-banner事件,在页面顶部插入一条横幅:“当前非营业时段,点餐功能暂停,欢迎浏览品牌故事。” 这样用户体验就不会断崖式下跌。
上线与优化:细节决定成败
代码写完只是开始,上线过程中的几个细节差点让我们翻车。
1. 缓存头(Cache-Control)的陷阱
最初我们给所有响应都设置了Cache-Control: max-age=3600。结果发现,晚上10点01分,用户刷新页面,因为CDN缓存了白天的“开放状态”,依然能访问点餐接口,导致后端报错。
解决方案:对于带有时间敏感性的API响应,必须设置Cache-Control: no-store, private。对于静态资源,保持长缓存。我们在Nginx中针对/api/路径单独配置了缓存策略:
location /api/ {proxy_pass http://backend;add_header 'Cache-Control' 'no-store, no-cache, must-revalidate, max-age=0';add_header 'Pragma' 'no-cache';add_header 'Expires' '0';
}
2. 时钟同步(NTP)至关重要
我们的一台备用服务器因为NTP配置错误,时间比标准时间慢了5分钟。导致在早上8:00-8:05期间,这台服务器拒绝服务,而主服务器正常,负载均衡导致部分用户间歇性报错。
解决方案:在所有服务器上强制安装chrony或ntpdate,并监控时间偏移量。Cloudflare Worker虽然不依赖源站时间,但后端的双重校验依然依赖系统时间。
3. 监控与告警
我们在Grafana中建立了一个仪表盘,专门监控403错误码中TIME_RESTRICTED的比例。如果非营业时间这个比例突然下降到0,说明Worker可能挂了,或者被绕过了,需要立即报警。
经验总结:避坑与思考
这个项目从需求提出到稳定运行,历时两周。回头看,有几个核心经验值得分享:
第一,不要迷信单一层级的防护。 很多开发者喜欢把所有逻辑都放在Nginx或者都放在应用层。实际上,边缘层(Cloudflare/CDN)负责性能与粗粒度过滤,应用层负责业务逻辑与细粒度校验,这种分层架构才是高可用系统的标配。Cloudflare 文档中关于Worker的“Edge Compute”概念,正是这种分层思想的体现。
第二,时区是国际化网站的隐形杀手。
如果你的业务只在中国,固定Asia/Shanghai没问题。但如果未来拓展到海外,必须在配置表中存储每个分支的时区ID,而不是硬编码。代码中的Intl.DateTimeFormat应该动态读取配置,而不是写死字符串。
第三,用户体验大于技术洁癖。 “设置时间段访问”容易让人联想到“粗暴关闭”。但对于C端用户,“告知”比“拒绝”更重要。我们花了很多时间在“非营业状态页”的UI设计上,提供了品牌介绍、营业时间查询、社交媒体链接等替代内容。这不仅减少了用户流失,还通过SEO保留了长尾流量。
第四,测试要覆盖边界条件。 一定要测试8:00:00、21:59:59、22:00:00这几个临界点。还要测试服务器重启、时钟跳变(夏令时切换,虽然中国没有,但服务器可能在海外)等极端场景。
在这个项目中,我们最初以为这只是一个简单的Nginx配置问题,结果深入下去发现,它串联了CDN、边缘计算、后端中间件、前端状态管理等多个环节。这也提醒我们,**【网站设置时间段访问】**看似是一个小功能,实则是对整个网站架构健壮性的考验。
从零搭建这套体系,最大的收获不是代码本身,而是对“访问控制”粒度的重新认识。未来的网站,访问控制会越来越细:按IP、按地域、按时间、按用户身份、按设备类型。掌握这种多层级、动态化的控制能力,才是前端与运维人员的核心竞争力。
你的网站用的什么技术栈?评论区聊聊