使用免费托管平台搭建并部署静态与动态网页教程

1. 静态网站托管方案:GitHub Pages

1.1 平台概述与核心特性

1.1.1 服务定位与适用场景

GitHub Pages 是由 GitHub 提供的免费静态网站托管服务,其核心定位是为开发者、设计师和内容创作者提供一个与 Git 版本控制深度集成的网站发布平台。该服务直接从 GitHub 仓库获取 HTML、CSS 和 JavaScript 文件,通过可选的构建流程处理后发布为可访问的网站 。这种架构设计使得 GitHub Pages 成为静态内容分发的理想选择,尤其适用于个人博客、项目文档、作品集展示以及开源项目的官方网站等场景。

从适用场景的角度分析,GitHub Pages 的服务边界非常清晰。它主要面向三类用户群体:第一类是技术写作者和博客运营者,他们可以利用 Jekyll 等静态站点生成器构建内容驱动的网站;第二类是开源项目维护者,需要为项目提供官方文档和介绍页面;第三类是创意专业人士,如设计师和摄影师,需要展示个人作品集。值得注意的是,GitHub Pages 明确禁止将其用于商业交易、敏感数据处理或需要高安全级别的应用场景 。因此,在选择 GitHub Pages 作为托管方案时,必须首先评估项目的技术需求是否与平台的能力边界相匹配。

GitHub Pages 的独特价值在于其与软件开发工作流的天然融合。开发者可以在同一个平台上管理代码版本和网站内容,实现"代码即网站"的现代化发布理念。这种集成带来了版本可追溯性、协作便利性和环境一致性等多重工程效益,使得网站开发完全遵循软件工程的最佳实践。

1.1.2 免费计划核心限制(1GB存储/100GB月流量/无服务器端处理)

GitHub Pages 的免费计划设置了一系列资源限制,这些限制对于规划网站规模和预期流量至关重要:

限制类别 具体数值 性质 超限后果
站点存储空间 1 GB 硬性限制 构建失败
月带宽流量 100 GB 软性限制 可能收到警告
构建执行时间 10 分钟/月 软性限制 构建排队或失败
构建输出大小 1 GB 硬性限制 部署失败
服务器端处理 不支持 架构限制 代码被忽略或报错

这些限制决定了 GitHub Pages 的适用边界。1 GB 存储空间 对于纯文本和代码为主的网站通常足够,但如果网站包含大量高清图片、视频文件或 PDF 文档,则需要谨慎管理存储使用。100 GB 月流量 对于个人博客和小型项目绰绰有余,但对于突发流量事件可能需要配合 CDN 或迁移至其他平台。最为关键的是服务器端处理的缺失------这意味着无法运行 PHP、Python、Ruby 或 Node.js 等后端代码,所有动态功能必须通过客户端 JavaScript 或第三方 API 服务实现 。

此外,GitHub Pages 并非旨在用于或允许用作免费 Web 托管服务来运行在线业务、电子商务站点或任何其他主要旨在促进商业交易或提供商业软件即服务(SaaS)的网站 。这些限制要求用户在选择平台时必须对项目的长期发展路径有清晰的规划。

1.1.3 自动部署机制与Git版本控制集成

GitHub Pages 的核心优势在于其与 Git 版本控制的无缝集成,这种集成实现了真正的持续部署(Continuous Deployment)工作流。当用户将代码推送到特定分支(通常是 maingh-pages)时,GitHub 会自动触发构建和部署流程,无需人工干预 。这种自动化机制极大地降低了网站维护的技术门槛,使得内容更新可以像代码提交一样简单。

部署流程的技术细节值得关注。GitHub Pages 支持从三种来源发布站点:分支 (通常是 gh-pagesmain 分支的 /docs 目录)、GitHub Actions 工作流程,或者这些方式的组合 。对于使用 Jekyll 等静态站点生成器的项目,GitHub 会在服务器端执行构建流程,将源文件转换为最终的 HTML 输出;对于纯静态文件,GitHub 则直接将这些文件部署到 CDN 节点。GitHub Pages 还提供了部署保护规则,用于管理站点部署 ,这为团队协作提供了必要的安全控制。

Git 版本控制集成带来了独特的价值:每一次站点更新都对应 Git 仓库中的一个提交记录,用户可以精确回溯到任意历史版本,通过分支机制实现多版本并行开发,利用 Pull Request 进行同行评审和代码审查。这种"Git 原生"的内容工作流对于技术团队而言具有极低的认知负担,无需学习额外的 CMS 工具或发布流程。

1.2 环境准备与项目初始化

1.2.1 GitHub账户注册与仓库创建规范

开始使用 GitHub Pages 的第一步是创建 GitHub 账户。注册流程相对标准,需要提供用户名、电子邮箱地址和密码。对于计划使用 GitHub Pages 的用户,建议在选择用户名时考虑长期品牌建设,因为该用户名将直接影响默认的 GitHub Pages 域名格式(username.github.io)。完成注册后,用户需要验证邮箱地址才能解锁完整功能。

仓库创建是项目初始化的关键环节。在 GitHub 界面中,用户可以通过点击右上角的 "+" 图标选择 "New repository" 来创建新仓库。对于 GitHub Pages 项目,仓库命名存在特定规则:如果希望网站的主域名为 username.github.io,则仓库名称必须严格匹配该格式 。例如,用户名为 octocat 的账户必须创建名为 octocat.github.io 的仓库才能启用用户站点。对于项目站点,命名则更为灵活,可以使用任何有效的仓库名称,网站将通过 username.github.io/repository-name 的路径访问。

创建仓库时,建议初始化 README 文件,并选择合适的开源许可证,这些最佳实践有助于项目的长期维护。此外,.gitignore 模板的选择应根据项目技术栈确定,例如 Node 项目应选择 Node 模板以排除 node_modules 目录,Jekyll 项目则应选择 Jekyll 模板以排除 _site 构建输出目录。

1.2.2 仓库命名规则(username.github.io格式)

GitHub Pages 的仓库命名规则直接决定了网站的 URL 结构和访问方式,理解这些规则对于正确配置项目至关重要:

站点类型 仓库命名格式 示例 URL 数量限制
用户站点 username.github.io https://octocat.github.io 每个账户 1 个
组织站点 organization.github.io https://github.github.io 每个组织 1 个
项目站点 任意有效名称 https://octocat.github.io/repo-name 无限制

用户站点 的命名规则最为严格,仓库名称必须采用 username.github.io 的严格格式,其中 username 必须与 GitHub 账户的用户名完全匹配。这种站点发布后,将通过 https://username.github.io 的主域名访问。项目站点 则更为灵活,任何仓库都可以启用 Pages 功能,无论其名称如何,URL 格式为 https://username.github.io/repository-name

需要特别注意的是,更改用户名或组织名称将导致 Pages 站点的 URL 永久性变更,且 GitHub 不会自动设置重定向。因此,在确定用户名前应进行充分的品牌和可用性调研,包括检查对应的社交媒体账号、域名注册情况以及商标冲突。

1.2.3 本地开发环境配置(Git客户端/VS Code编辑器)

高效的本地开发环境是 GitHub Pages 项目成功的基础。核心工具链包括 Git 版本控制系统和代码编辑器,其中 Visual Studio Code(VS Code) 是最受欢迎的选择之一。

Git 客户端配置涉及多个步骤。首先,用户需要从 git-scm.com 下载并安装适合操作系统的 Git 版本。安装完成后,必须进行全局身份配置:

bash 复制代码
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"

对于与 GitHub 的交互,推荐使用 HTTPS 协议配合个人访问令牌(Personal Access Token),或配置 SSH 密钥实现免密认证。

VS Code 编辑器提供了卓越的 Git 集成体验。建议安装以下扩展以增强 GitHub Pages 开发效率:

  • GitHub Pull Requests and Issues:直接在编辑器中管理 GitHub 工作流
  • Live Server:本地实时预览功能
  • Markdown All in One:增强 Markdown 编辑体验
  • Prettier:统一代码格式化

对于 Jekyll 站点的本地开发,还需要配置 Ruby 环境和 Jekyll gem。通过 gem install jekyll bundler 安装后,在项目目录执行 bundle exec jekyll serve 即可启动本地预览服务器。

1.3 静态网页代码实现

1.3.1 HTML5基础结构模板

现代静态网站的开发始于语义化、结构良好的 HTML5 文档。以下模板展示了符合最佳实践的 HTML5 基础结构:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="个人作品集网站 - 展示创意设计与技术项目">
    <title>我的作品集 | GitHub Pages 演示</title>
    <link rel="stylesheet" href="styles/main.css">
    <link rel="icon" type="image/svg+xml" href="assets/favicon.svg">
</head>
<body>
    <header class="site-header">
        <nav class="main-nav" aria-label="主导航">
            <a href="/" class="brand-link">Portfolio</a>
            <ul class="nav-menu">
                <li><a href="#about" class="nav-link">关于</a></li>
                <li><a href="#projects" class="nav-link">项目</a></li>
                <li><a href="#contact" class="nav-link">联系</a></li>
            </ul>
        </nav>
    </header>
    
    <main id="main-content">
        <section class="hero-section" aria-labelledby="hero-heading">
            <h1 id="hero-heading">创意设计与代码的交汇点</h1>
            <p class="hero-subtitle">探索技术与艺术的无限可能</p>
            <a href="#projects" class="cta-button">浏览作品</a>
        </section>
        
        <section id="projects" class="projects-section" aria-labelledby="projects-heading">
            <h2 id="projects-heading">精选项目</h2>
            <div class="projects-grid" id="projects-container">
                <!-- 项目卡片将通过 JavaScript 动态加载 -->
            </div>
        </section>
    </main>
    
    <footer class="site-footer">
        <p>&copy; 2024 我的作品集. 托管于 <a href="https://pages.github.com">GitHub Pages</a></p>
    </footer>
    
    <script src="scripts/main.js" defer></script>
</body>
</html>

该模板体现了多项现代 Web 开发最佳实践:语义化标签headernavmainsectionfooter)提升了可访问性和 SEO 表现;viewport meta 标签确保移动设备上的正确渲染;defer 属性优化了 JavaScript 加载性能;ARIA 属性增强了屏幕阅读器兼容性。

1.3.2 CSS3响应式样式设计

响应式设计是现代网站的标准要求。以下 CSS 代码展示了基于 FlexboxCSS Grid 的响应式布局实现:

css 复制代码
/* ===== CSS 变量与重置 ===== */
:root {
    --primary-color: #2563eb;
    --primary-dark: #1d4ed8;
    --text-primary: #1f2937;
    --text-secondary: #6b7280;
    --bg-primary: #ffffff;
    --bg-secondary: #f3f4f6;
    --spacing-unit: 1rem;
    --max-width: 1200px;
    --border-radius: 0.5rem;
    --transition-speed: 0.3s;
}

*, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

/* ===== 基础排版 ===== */
html {
    font-size: 16px;
    scroll-behavior: smooth;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 
                 'Helvetica Neue', Arial, sans-serif;
    line-height: 1.6;
    color: var(--text-primary);
    background-color: var(--bg-primary);
}

/* ===== 导航栏布局 ===== */
.site-header {
    position: sticky;
    top: 0;
    background: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(10px);
    border-bottom: 1px solid #e5e7eb;
    z-index: 100;
}

.main-nav {
    display: flex;
    justify-content: space-between;
    align-items: center;
    max-width: var(--max-width);
    margin: 0 auto;
    padding: var(--spacing-unit) calc(var(--spacing-unit) * 2);
}

.nav-menu {
    display: flex;
    list-style: none;
    gap: calc(var(--spacing-unit) * 2);
}

.nav-link {
    text-decoration: none;
    color: var(--text-primary);
    font-weight: 500;
    transition: color var(--transition-speed);
}

.nav-link:hover {
    color: var(--primary-color);
}

/* ===== Hero 区域 ===== */
.hero-section {
    text-align: center;
    padding: calc(var(--spacing-unit) * 6) var(--spacing-unit);
    background: linear-gradient(135deg, var(--bg-secondary) 0%, #e5e7eb 100%);
}

.hero-title {
    font-size: clamp(2rem, 5vw, 4rem);
    font-weight: 800;
    margin-bottom: var(--spacing-unit);
    background: linear-gradient(90deg, var(--primary-color), #7c3aed);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}

.hero-subtitle {
    font-size: clamp(1rem, 2vw, 1.5rem);
    color: var(--text-secondary);
    margin-bottom: calc(var(--spacing-unit) * 2);
}

.cta-button {
    display: inline-block;
    padding: calc(var(--spacing-unit) * 0.75) calc(var(--spacing-unit) * 2);
    background: var(--primary-color);
    color: white;
    text-decoration: none;
    border-radius: var(--border-radius);
    font-weight: 600;
    transition: transform var(--transition-speed), 
                background-color var(--transition-speed);
}

.cta-button:hover {
    transform: translateY(-2px);
    background: var(--primary-dark);
}

/* ===== 项目网格(CSS Grid)===== */
.projects-section {
    max-width: var(--max-width);
    margin: 0 auto;
    padding: calc(var(--spacing-unit) * 4) var(--spacing-unit);
}

.section-title {
    text-align: center;
    font-size: 2.5rem;
    margin-bottom: calc(var(--spacing-unit) * 3);
}

.projects-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: calc(var(--spacing-unit) * 2);
}

/* ===== 响应式断点 ===== */
@media (max-width: 768px) {
    .main-nav {
        flex-direction: column;
        gap: var(--spacing-unit);
    }
    
    .nav-menu {
        gap: var(--spacing-unit);
    }
    
    .hero-section {
        padding: calc(var(--spacing-unit) * 4) var(--spacing-unit);
    }
}

@media (prefers-reduced-motion: reduce) {
    * {
        animation-duration: 0.01ms !important;
        transition-duration: 0.01ms !important;
    }
}

该样式表采用了多项现代 CSS 技术:CSS 自定义属性 (变量)实现了主题的一致性和可维护性;clamp() 函数创建了流畅的排版缩放;CSS Grid 的 auto-fitminmax 组合实现了自适应的项目网格布局;移动优先 的媒体查询策略确保了小屏幕设备上的良好体验;prefers-reduced-motion 媒体查询尊重用户的动画偏好设置。

1.3.3 JavaScript交互功能集成

为静态网站添加适度的 JavaScript 交互可以显著提升用户体验:

javascript 复制代码
// ===== 项目数据配置 =====
const projectsData = [
    {
        id: 1,
        title: "电商数据分析平台",
        description: "基于 Python 和 React 的全栈数据可视化解决方案",
        tags: ["Python", "React", "D3.js"],
        image: "assets/project-1.jpg",
        link: "https://github.com/username/project-1"
    },
    {
        id: 2,
        title: "智能任务管理系统",
        description: "集成自然语言处理的待办事项管理应用",
        tags: ["Node.js", "Vue.js", "TensorFlow.js"],
        image: "assets/project-2.jpg",
        link: "https://github.com/username/project-2"
    },
    {
        id: 3,
        title: "开源设计系统",
        description: "可复用的 UI 组件库与设计令牌",
        tags: ["TypeScript", "Storybook", "Sass"],
        image: "assets/project-3.jpg",
        link: "https://github.com/username/project-3"
    }
];

// ===== DOM 操作与渲染 =====
const projectsContainer = document.getElementById('projects-container');

function renderProjectCard(project) {
    const card = document.createElement('article');
    card.className = 'project-card';
    card.innerHTML = `
        <div class="project-image-wrapper">
            <img src="${project.image}" 
                 alt="${project.title} 预览图" 
                 class="project-image"
                 loading="lazy">
        </div>
        <div class="project-content">
            <h3 class="project-title">${project.title}</h3>
            <p class="project-description">${project.description}</p>
            <div class="project-tags">
                ${project.tags.map(tag => `<span class="tag">${tag}</span>`).join('')}
            </div>
            <a href="${project.link}" 
               class="project-link" 
               target="_blank" 
               rel="noopener noreferrer">
                查看项目 →
            </a>
        </div>
    `;
    return card;
}

function initializeProjects() {
    if (!projectsContainer) return;
    
    const fragment = document.createDocumentFragment();
    projectsData.forEach(project => {
        fragment.appendChild(renderProjectCard(project));
    });
    projectsContainer.appendChild(fragment);
}

// ===== 导航交互 =====
function initializeNavigation() {
    const header = document.querySelector('.site-header');
    
    // 滚动阴影效果
    window.addEventListener('scroll', () => {
        header.style.boxShadow = window.scrollY > 10 
            ? '0 4px 6px rgba(0, 0, 0, 0.1)' 
            : '0 1px 3px rgba(0, 0, 0, 0.1)';
    });
    
    // 平滑滚动到锚点
    document.querySelectorAll('a[href^="#"]').forEach(anchor => {
        anchor.addEventListener('click', function(e) {
            e.preventDefault();
            const target = document.querySelector(this.getAttribute('href'));
            target?.scrollIntoView({ behavior: 'smooth', block: 'start' });
        });
    });
}

// ===== 页面初始化 =====
document.addEventListener('DOMContentLoaded', () => {
    initializeProjects();
    initializeNavigation();
    console.log('🚀 作品集网站已加载完成');
});

该 JavaScript 代码遵循现代前端开发最佳实践:使用 DocumentFragment 优化 DOM 操作性能;loading="lazy" 属性实现图片懒加载;模块化的函数组织提升了代码可维护性。

1.4 部署流程与域名配置

1.4.1 代码推送至远程仓库

将本地开发的网站代码部署到 GitHub Pages 的核心步骤是将代码推送到 GitHub 远程仓库:

bash 复制代码
# 初始化本地 Git 仓库
git init

# 添加所有文件到暂存区
git add .

# 创建初始提交
git commit -m "Initial commit: 添加作品集网站基础结构"

# 添加远程仓库地址
git remote add origin https://github.com/username/username.github.io.git

# 推送到 main 分支
git push -u origin main

日常更新流程简化为 git add .git commit -m "描述信息"git push。推送完成后,GitHub 会自动触发 Pages 构建流程。

1.4.2 GitHub Pages功能启用设置

GitHub Pages 的启用需要通过仓库设置界面配置:

  1. 访问仓库主页,点击 Settings 标签
  2. 在左侧边栏中选择 Pages 选项
  3. Source 部分,选择部署分支(maingh-pages
  4. 点击 Save 保存配置

配置完成后,页面将显示网站的发布 URL。首次启用可能需要 5-10 分钟完成 DNS 传播和 CDN 缓存刷新 。

1.4.3 自定义域名绑定(可选)

GitHub Pages 支持绑定自定义域名以提升专业形象:

域名类型 DNS 记录类型 记录值
Apex 域名(如 example.com A 记录 185.199.108.153 等四个 IP
子域名(如 www.example.com CNAME 记录 username.github.io

在仓库根目录创建名为 CNAME 的文件(无扩展名),内容为自定义域名。GitHub 将自动申请 Let's Encrypt HTTPS 证书并启用强制 HTTPS 访问。


2. Python动态网站托管方案:PythonAnywhere

2.1 平台概述与核心特性

2.1.1 服务定位与Python生态支持

PythonAnywhere 是一家专注于 Python 应用托管的云服务平台,其核心定位是为 Python 开发者提供从开发、部署到运维的全生命周期支持。与通用型云平台不同,PythonAnywhere 深度优化了 Python 运行环境,预装了超过 300 个常用的 Python 包,涵盖数据科学、Web 开发、机器学习等多个领域的主流工具链 。

平台的服务架构体现了"开发即服务"(Development as a Service)的理念:集成了基于浏览器的 Python 开发环境,包括代码编辑器、Bash 控制台、Python 解释器和 IPython Notebook 支持 。这种设计消除了本地环境配置的复杂性,使得开发者可以在任何具有网络连接的设备上继续工作。

PythonAnywhere 对主流 Python Web 框架提供了深度支持,包括 FlaskDjangoFastAPIBottleweb2py,这些框架涵盖了从微框架到全功能框架的完整谱系。平台预装了这些框架的常用版本,并提供一键式项目创建向导,自动生成推荐的目录结构和 WSGI 配置。

2.1.2 免费计划核心限制(512MB存储/每日3小时CPU/无自定义域名)

PythonAnywhere 的免费计划(Beginner Account)设置了一系列资源限制:

资源类别 免费计划限制 实际影响评估
磁盘空间 512 MB 足以容纳中小型 Flask/Django 项目,大文件需外部存储
每日 CPU 时间 3 小时 低流量网站充足,计算密集型任务受限
自定义域名 不支持 必须使用 username.pythonanywhere.com 子域名
数据库 MySQL(1个) 小型应用足够,大数据量需优化
网络访问 白名单限制 仅可访问预设的外部服务

每日 3 小时 CPU 时间是免费计划最具约束性的条款。需要明确的是,这是"CPU 时间"而非"运行时间"------应用可以 24 小时保持运行状态,但实际处理请求所消耗的处理器时间累计不超过 3 小时 。对于典型的 Web 应用,若平均响应时间为 100ms,则 3 小时 CPU 时间可支持约 10 万次请求处理。

2.1.3 预装框架与数据库支持(Flask/Django/MySQL)

PythonAnywhere 的预装环境显著加速了项目启动速度:

类别 组件 用途
Web 框架 Flask、Django、FastAPI、Bottle 全栈 Web 开发
数据库 MySQL、SQLite 关系型数据持久化
数据科学 NumPy、Pandas、Matplotlib、scikit-learn 数据分析与可视化
机器学习 TensorFlow、PyTorch 模型训练与推理
HTTP 请求 Requests、httpx 外部 API 调用

MySQL 数据库 的配置通过平台的数据库标签页完成,连接信息包括主机名(username.mysql.pythonanywhere-services.com)、数据库名、用户名和密码。对于 Django 应用,配置 DATABASES 设置即可自动使用 MySQL;对于 Flask 应用,可以使用 SQLAlchemy 作为 ORM 层。

2.2 环境准备与项目初始化

2.2.1 PythonAnywhere账户注册与控制台访问

PythonAnywhere 的注册流程简洁直接:访问 pythonanywhere.com,提供用户名、邮箱和密码即可完成账户创建。用户名将成为默认网站域名的组成部分(username.pythonanywhere.com),建议选择专业且易记的标识符 。

登录后的**仪表盘(Dashboard)**包含以下核心功能区域:

标签页 功能描述
Consoles 启动 Bash、Python、IPython 交互式终端
Files 浏览器-based 文件管理器
Web Web 应用配置、部署和监控
Databases MySQL 数据库创建和管理
Tasks 定时任务调度(付费功能)
2.2.2 虚拟环境创建与依赖管理

虚拟环境是 Python 项目管理的最佳实践。在 PythonAnywhere 中创建虚拟环境的步骤:

bash 复制代码
# 创建项目目录
mkdir ~/my-flask-app && cd ~/my-flask-app

# 创建 Python 3.9 虚拟环境
python3.9 -m venv venv

# 激活虚拟环境
source venv/bin/activate

# 升级 pip 并安装依赖
pip install --upgrade pip
pip install flask flask-sqlalchemy gunicorn

# 生成依赖清单
pip freeze > requirements.txt

requirements.txt 示例:

复制代码
Flask==2.3.3
Flask-SQLAlchemy==3.0.5
Werkzeug==2.3.7
gunicorn==21.2.0
2.2.3 Web应用配置界面导航

PythonAnywhere 的 Web 配置界面简化了传统部署中的复杂步骤:

  1. 点击 Web 标签 → Add a new web app
  2. 选择 Python 版本(推荐 3.9 或更高)
  3. 选择 Web 框架(Flask/Django/手动配置)
  4. 指定 项目路径WSGI 入口

关键配置项包括:

配置项 典型值 说明
Source code /home/username/my-flask-app 项目代码目录
Virtualenv /home/username/my-flask-app/venv 虚拟环境路径
WSGI configuration file /var/www/username_pythonanywhere_com_wsgi.py WSGI 入口文件

2.3 Flask动态应用代码实现

2.3.1 最小可运行Flask应用结构

推荐的项目结构:

复制代码
my-flask-app/
├── app/
│   ├── __init__.py          # 应用工厂函数
│   ├── routes.py            # 路由定义
│   ├── models.py            # 数据库模型
│   ├── templates/           # Jinja2 模板
│   └── static/              # CSS/JS/图片
├── config.py                # 配置类
├── requirements.txt         # 依赖清单
├── wsgi.py                  # WSGI 入口
└── .gitignore

应用工厂模式 实现(app/__init__.py):

python 复制代码
from flask import Flask
from config import config

def create_app(config_name='default'):
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    
    # 初始化扩展
    from app.models import db
    db.init_app(app)
    
    # 注册蓝图
    from app.routes import main_bp
    app.register_blueprint(main_bp)
    
    return app

# 创建应用实例(供 WSGI 导入)
app = create_app('production')

配置类config.py):

python 复制代码
import os

class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-key-change-in-production')
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
        'mysql+mysqlconnector://username:password@host/database'

class DevelopmentConfig(Config):
    DEBUG = True

class ProductionConfig(Config):
    DEBUG = False

config = {
    'development': DevelopmentConfig,
    'production': ProductionConfig,
    'default': DevelopmentConfig
}
2.3.2 路由定义与请求处理

RESTful API 路由示例app/routes.py):

python 复制代码
from flask import Blueprint, request, jsonify, render_template
from datetime import datetime

main_bp = Blueprint('main', __name__)

@main_bp.route('/')
def index():
    """首页 - 渲染主模板"""
    return render_template('index.html',
                         title='PythonAnywhere 演示',
                         current_time=datetime.now())

@main_bp.route('/api/health')
def health_check():
    """健康检查端点"""
    return jsonify({
        'status': 'healthy',
        'timestamp': datetime.now().isoformat(),
        'service': 'flask-demo'
    })

@main_bp.route('/api/users', methods=['GET'])
def list_users():
    """获取用户列表 - 支持分页"""
    page = request.args.get('page', 1, type=int)
    per_page = min(request.args.get('per_page', 10, type=int), 100)
    
    # 模拟数据
    users = [{'id': i, 'name': f'User {i}'} for i in range(1, 101)]
    start = (page - 1) * per_page
    end = start + per_page
    
    return jsonify({
        'data': users[start:end],
        'pagination': {
            'page': page,
            'per_page': per_page,
            'total': len(users)
        }
    })

@main_bp.route('/api/users', methods=['POST'])
def create_user():
    """创建新用户"""
    data = request.get_json()
    
    if not data or 'name' not in data:
        return jsonify({'error': 'Missing required field: name'}), 400
    
    # 模拟创建
    new_user = {
        'id': 999,
        'name': data['name'],
        'created_at': datetime.now().isoformat()
    }
    
    return jsonify({'data': new_user}), 201
2.3.3 模板渲染与静态文件服务

Jinja2 模板示例app/templates/index.html):

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
    <header>
        <h1>{{ title }}</h1>
        <p>服务器时间:{{ current_time.strftime('%Y-%m-%d %H:%M:%S') }}</p>
    </header>
    
    <main>
        <section class="api-demo">
            <h2>API 测试</h2>
            <button id="test-api">测试 /api/health</button>
            <pre id="result"></pre>
        </section>
    </main>
    
    <script>
        document.getElementById('test-api').addEventListener('click', async () => {
            const response = await fetch('/api/health');
            const data = await response.json();
            document.getElementById('result').textContent = JSON.stringify(data, null, 2);
        });
    </script>
</body>
</html>

url_for('static', filename='...') 函数生成静态文件的正确 URL,确保在项目站点子目录部署时路径正确解析。

2.4 部署流程与WSGI配置

2.4.1 代码上传方式(Git克隆/直接上传)

Git 克隆(推荐)

bash 复制代码
cd ~
git clone https://github.com/username/my-flask-app.git
cd my-flask-app

# 创建虚拟环境并安装依赖
python3.9 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

直接文件上传 :通过 Files 标签页上传 ZIP 压缩包,然后在 Bash 控制台解压:

bash 复制代码
cd ~
unzip project.zip -d my-flask-app
2.4.2 WSGI入口文件配置

PythonAnywhere 自动生成的 WSGI 文件需要调整以指向正确的应用实例。典型配置(/var/www/username_pythonanywhere_com_wsgi.py):

python 复制代码
import sys
import os

# 添加项目目录到 Python 路径
path = '/home/username/my-flask-app'
if path not in sys.path:
    sys.path.insert(0, path)

# 设置环境变量
os.environ['FLASK_ENV'] = 'production'
os.environ['SECRET_KEY'] = 'your-production-secret-key'

# 激活虚拟环境
activate_this = '/home/username/my-flask-app/venv/bin/activate_this.py'
exec(open(activate_this).read(), {'__file__': activate_this})

# 导入应用对象(必须为 'application')
from app import app as application

关键要点:

  • sys.path 修改确保项目模块可被导入
  • 虚拟环境激活保证依赖正确加载
  • application 变量是 uWSGI 期望的 WSGI 可调用对象
2.4.3 应用重启与访问验证

完成配置后,点击 Web 配置页面的 Reload 按钮重启应用。验证部署状态的方式:

验证方式 操作 预期结果
直接访问 浏览器打开 username.pythonanywhere.com 显示首页
健康检查 访问 /api/health 返回 JSON 状态
日志检查 查看 Error log / Access log 无异常错误

常见问题排查:

  • 502 Bad Gateway:检查 WSGI 配置中的应用导入路径
  • ImportError:确认虚拟环境中已安装所有依赖
  • 404 Not Found:验证路由配置和蓝图注册

3. Node.js动态网站托管方案:Glitch

3.1 平台概述与核心特性

3.1.1 服务定位与实时协作特性

Glitch 是一家独具特色的云端开发平台,其服务定位超越了传统的应用托管,强调实时协作编程即时预览 的开发体验。与 GitHub 的异步协作模式不同,Glitch 允许多个开发者同时编辑同一代码文件,变更实时同步到所有参与者的屏幕,这种"Google Docs 式"的编程体验极大地降低了协作门槛 。

平台的核心价值主张在于零配置开发环境:用户无需安装任何本地软件,浏览器即是完整的开发环境。每个 Glitch 项目都包含在线代码编辑器、实时预览窗口、日志控制台和版本历史,这些组件集成在统一的界面中,形成了流畅的开发工作流 。对于教学场景、黑客马拉松活动和快速原型开发,这种即时可用的特性具有显著优势。

⚠️ 重要更新 :Glitch 于 2025 年 7 月 8 日 宣布终止免费和 Pro 项目的实时托管服务,仅保留仪表板和代码下载功能至 2025 年底 。这意味着 Glitch 已从完整的托管平台转型为代码编辑和原型工具,用户需要将部署迁移至其他平台。本章节保留技术内容供历史参考,但实际项目选型时应考虑替代方案。

3.1.2 免费计划核心限制(5分钟后休眠/公开代码/1000请求/小时)

在服务终止之前,Glitch 的免费计划设置以下关键限制:

限制项 具体规则 技术影响
休眠机制 5 分钟无请求后应用睡眠 首次访问需等待唤醒(冷启动延迟 5-30 秒)
代码可见性 所有项目代码公开可查看 不适合商业敏感或专有代码
请求速率 约 1000 请求/小时 高流量应用可能触发限制
运行时长 每月 1000 小时 计算密集型任务受限

休眠机制是免费计划最具影响的设计决策。当应用 5 分钟内未收到 HTTP 请求时,Glitch 会暂停容器以释放计算资源,后续请求触发重新启动。这种"冷启动"过程导致首次访问的显著延迟,用户体验上表现为页面加载缓慢。

3.1.3 即时预览与自动部署机制

Glitch 的开发和部署体验高度集成,消除了传统流程中的多个手动步骤:

  • 实时预览:编辑器右侧的预览面板持续显示应用运行状态,代码保存即时触发重新加载
  • 自动依赖安装package.json 的变更自动触发 npm install
  • 环境变量管理.env 文件存储敏感配置,不被公开显示

这种"热重载"体验极大加速了开发迭代,开发者可以即时看到代码修改的效果,无需手动构建或部署操作。

3.2 环境准备与项目初始化

3.2.1 Glitch账户注册与项目创建

Glitch 支持多种注册方式:GitHub、Google、Facebook 或邮箱。注册完成后,创建新项目的方式:

  1. 点击 New project 或仪表盘 "+" 按钮
  2. 选择项目模板:
    • hello-express:预配置的 Express 服务器
    • hello-sqlite:包含 SQLite 数据库的完整示例
    • hello-react:React 前端 + Express 后端
    • blank project:完全自定义
3.2.2 项目模板选择(Express/空白项目)

hello-express 模板的初始结构:

复制代码
project-name/
├── public/           # 静态文件服务目录
│   ├── style.css
│   └── client.js
├── views/            # 模板文件
│   └── index.html
├── server.js         # 应用入口
├── package.json      # 项目配置
└── .env              # 环境变量(私密)

package.json 关键配置:

json 复制代码
{
  "name": "hello-express",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "express": "^4.18.2"
  },
  "engines": {
    "node": "16.x"
  }
}

"start": "node server.js" 是 Glitch 识别应用入口的关键。

3.2.3 在线编辑器与实时日志访问

Glitch 编辑器采用 VS Code 的 Monaco 内核,提供:

  • 智能提示:基于 TypeScript 的代码补全
  • 错误检测:实时语法检查和 ESLint 集成
  • 多光标编辑:支持多人同时编辑
  • 预览控制:刷新、新窗口打开、移动端模拟

日志面板显示标准输出(stdout)和标准错误(stderr),支持实时流式更新、关键字过滤和历史记录滚动。

3.3 Express动态应用代码实现

3.3.1 最小可运行Express服务器

增强版 Express 服务器实现:

javascript 复制代码
/**
 * Express 服务器主入口 - Glitch 托管
 */

const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');

const app = express();
const PORT = process.env.PORT || 3000;

// ===== 中间件配置 =====

// 解析请求体
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

// 安全头中间件
app.use((req, res, next) => {
    res.setHeader('X-Content-Type-Options', 'nosniff');
    res.setHeader('X-Frame-Options', 'SAMEORIGIN');
    res.setHeader('X-XSS-Protection', '1; mode=block');
    next();
});

// 请求日志
app.use((req, res, next) => {
    console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
    next();
});

// 静态文件服务
app.use(express.static('public'));

// ===== 路由定义 =====

// 首页
app.get('/', (req, res) => {
    res.render('index', {
        title: 'Glitch Express 演示',
        serverTime: new Date().toLocaleString('zh-CN')
    });
});

// API:健康检查
app.get('/api/health', (req, res) => {
    res.json({
        status: 'healthy',
        uptime: process.uptime(),
        memory: process.memoryUsage(),
        timestamp: new Date().toISOString()
    });
});

// API:回显请求
app.all('/api/echo', (req, res) => {
    res.json({
        method: req.method,
        path: req.path,
        query: req.query,
        body: req.body,
        headers: req.headers
    });
});

// 表单提交处理
app.post('/submit', (req, res) => {
    const { name, email, message } = req.body;
    
    if (!name || !email) {
        return res.status(400).json({
            success: false,
            error: '姓名和邮箱为必填项'
        });
    }
    
    console.log('收到表单提交:', { name, email, message });
    
    res.json({
        success: true,
        message: '提交成功!',
        received: { name, email, messageLength: message?.length || 0 }
    });
});

// ===== 错误处理 =====

app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).json({ error: '服务器内部错误' });
});

// ===== 启动服务器 =====

app.listen(PORT, () => {
    console.log(`🚀 服务器运行在端口 ${PORT}`);
});
3.3.2 中间件配置(body-parser/静态文件)

Express 的中间件机制是其核心设计优势:

中间件 用途 配置示例
express.static 静态文件服务 app.use(express.static('public'))
body-parser.json JSON 请求体解析 app.use(bodyParser.json())
body-parser.urlencoded 表单数据解析 app.use(bodyParser.urlencoded({ extended: true }))
自定义中间件 日志、认证、安全头等 app.use((req, res, next) => { ... })

中间件的执行顺序至关重要 :请求按照添加顺序依次通过每个中间件,任何中间件都可以终止请求-响应循环或调用 next() 传递给下一个中间件。

3.3.3 表单处理与动态路由

动态路由示例

javascript 复制代码
// 动态路由:用户资料
app.get('/user/:username', (req, res) => {
    const { username } = req.params;
    // 模拟数据库查询
    const user = { username, joined: '2024-01-15' };
    res.json({ data: user });
});

// 可选参数路由
app.get('/posts/:id?', (req, res) => {
    const { id } = req.params;
    if (id) {
        // 返回单篇文章
        res.json({ post: { id, title: `文章 ${id}` } });
    } else {
        // 返回文章列表
        res.json({ posts: [{ id: 1, title: '文章 1' }] });
    }
});

3.4 部署流程与外部访问

3.4.1 自动保存与即时部署

Glitch 的部署模型极为简洁------没有显式部署操作。每次代码修改自动保存(约 500ms 延迟),服务器即时重启应用,预览窗口实时更新。这种"热重载"体验将修改-查看周期从分钟级缩短到秒级。

对于需要构建步骤的项目,可在 package.json 中配置:

json 复制代码
{
  "scripts": {
    "build": "npm run compile && npm run bundle",
    "start": "npm run build && node dist/server.js"
  }
}
3.4.2 项目分享与嵌入选项

Glitch 提供了多种项目分享方式:

  • 直接链接https://glitch.com/~project-name
  • 实时应用https://project-name.glitch.me
  • 嵌入代码<iframe src="https://glitch.com/embed/#!/embed/project-name">

嵌入功能特别适合技术博客和教程,读者可以直接在文章中运行和修改代码。

3.4.3 GitHub导入/导出同步

Glitch 支持与 GitHub 的双向同步:

  • 导入:从 GitHub 仓库创建新项目
  • 导出:将 Glitch 项目推送至 GitHub 仓库
  • 自动同步:配置后保持两边代码一致

这一功能为迁移至其他平台提供了便利------鉴于 Glitch 托管服务的终止,建议用户及时将项目导出至 GitHub,然后部署至 Vercel、Railway 或 Fly.io 等替代平台。


4. 多平台对比与选型决策

4.1 核心能力矩阵对比

能力维度 GitHub Pages PythonAnywhere Glitch(已终止托管)
内容类型 纯静态 动态(Python) 动态(Node.js)
编程语言 HTML/CSS/JS Python JavaScript/Node.js
主流框架 Jekyll、Hugo、Hexo Flask、Django、FastAPI Express、React、Vue
数据库 无(需外部服务) MySQL、PostgreSQL(付费) SQLite、外部服务
服务器端处理 ❌ 不支持 ✅ 完整支持 ✅ 完整支持
自定义域名 ✅ 支持 ❌ 免费版不支持 ✅ 支持(消耗积分)
HTTPS 证书 ✅ 自动(Let's Encrypt) ✅ 付费版支持 ✅ 自动
全球 CDN ✅ GitHub 全球节点 ❌ 单区域 ❌ 单区域

4.2 性能与可靠性评估

评估维度 GitHub Pages PythonAnywhere Glitch
响应速度 ⭐⭐⭐⭐⭐ 极快(CDN 边缘缓存) ⭐⭐⭐ 中等 ⭐⭐ 较慢(冷启动)
服务可用性 ⭐⭐⭐⭐⭐ 高(SLA 保障) ⭐⭐⭐⭐ 较高 ⭐⭐ 低(休眠机制)
扩展路径 GitHub Actions、Vercel、Netlify 付费升级($5/月起) 服务已终止
免费额度持久性 ⭐⭐⭐⭐⭐ 稳定 ⭐⭐⭐⭐ 稳定 ⭐ 已取消

4.3 典型应用场景推荐

4.3.1 个人博客与作品集(GitHub Pages)

GitHub Pages 是内容创作者的首选平台。其与 Git 的深度集成使得写作流程完全遵循版本控制最佳实践,Markdown + Jekyll 的组合提供了优雅的写作体验。全球 CDN 确保读者无论身处何地都能快速访问,自定义域名支持则帮助建立专业品牌形象。

推荐技术栈:Jekyll / Hugo + GitHub Actions + Cloudflare(可选 CDN 加速)

4.3.2 Python数据应用与API服务(PythonAnywhere)

PythonAnywhere 是数据科学家和 Python 开发者的理想选择。预装的数据科学库栈(NumPy、Pandas、scikit-learn)消除了环境配置的繁琐,MySQL 数据库支持实现了数据持久化,Flask/Django 的框架优化则简化了 Web API 开发。

推荐技术栈:Flask + SQLAlchemy + Gunicorn + MySQL

4.3.3 快速原型与协作开发(Glitch → 迁移至替代方案)

鉴于 Glitch 托管服务的终止,原推荐场景需要调整。对于实时协作编程需求,可考虑:

  • 替代方案:CodeSandbox、StackBlitz、Replit
  • 迁移路径:Glitch 项目导出至 GitHub → 部署至 Vercel / Railway / Fly.io

5. 完整代码示例附录

5.1 静态网站完整代码包

5.1.1 index.html(语义化结构与内容)
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="个人技术作品集 - 全栈开发项目展示">
    <title>我的作品集 | 全栈开发者</title>
    <link rel="stylesheet" href="styles/main.css">
</head>
<body>
    <header class="site-header">
        <nav class="main-nav">
            <a href="/" class="brand">Portfolio</a>
            <ul class="nav-links">
                <li><a href="#about">关于</a></li>
                <li><a href="#projects">项目</a></li>
                <li><a href="#skills">技能</a></li>
                <li><a href="#contact">联系</a></li>
            </ul>
        </nav>
    </header>
    
    <main>
        <section class="hero">
            <h1>全栈开发者,创造数字体验</h1>
            <p class="subtitle">5年经验 · React · Node.js · Python · 云原生</p>
            <div class="cta-group">
                <a href="#projects" class="btn btn-primary">查看作品</a>
                <a href="#contact" class="btn btn-secondary">联系我</a>
            </div>
        </section>
        
        <section id="projects" class="projects">
            <h2>精选项目</h2>
            <div class="project-grid" id="project-container"></div>
        </section>
    </main>
    
    <footer>
        <p>&copy; 2024 我的作品集. 使用 GitHub Pages 构建.</p>
    </footer>
    
    <script src="scripts/main.js" defer></script>
</body>
</html>
5.1.2 main.css(Flexbox布局与媒体查询)
css 复制代码
:root {
    --primary: #2563eb;
    --primary-dark: #1d4ed8;
    --text: #1f2937;
    --text-light: #6b7280;
    --bg: #ffffff;
    --bg-alt: #f3f4f6;
    --max-width: 1200px;
    --radius: 0.5rem;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
    font-family: system-ui, -apple-system, sans-serif;
    line-height: 1.6;
    color: var(--text);
}

.site-header {
    position: sticky;
    top: 0;
    background: var(--bg);
    border-bottom: 1px solid #e5e7eb;
}

.main-nav {
    display: flex;
    justify-content: space-between;
    align-items: center;
    max-width: var(--max-width);
    margin: 0 auto;
    padding: 1rem 2rem;
}

.nav-links {
    display: flex;
    gap: 2rem;
    list-style: none;
}

.hero {
    text-align: center;
    padding: 6rem 2rem;
    background: linear-gradient(135deg, var(--bg-alt), #e5e7eb);
}

.hero h1 {
    font-size: clamp(2rem, 5vw, 3.5rem);
    margin-bottom: 1rem;
}

.project-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 2rem;
    max-width: var(--max-width);
    margin: 0 auto;
    padding: 2rem;
}

@media (max-width: 768px) {
    .main-nav { flex-direction: column; gap: 1rem; }
    .nav-links { gap: 1rem; }
}
5.1.3 index.js(DOM操作与事件处理)
javascript 复制代码
const projects = [
    { title: '数据分析平台', desc: 'React + D3.js 可视化', tags: ['React', 'D3.js'] },
    { title: 'API 网关', desc: 'Node.js + Express 微服务', tags: ['Node.js', 'Docker'] },
    { title: '智能助手', desc: 'Python + TensorFlow NLP', tags: ['Python', 'ML'] }
];

function renderProjects() {
    const container = document.getElementById('project-container');
    const fragment = document.createDocumentFragment();
    
    projects.forEach(p => {
        const card = document.createElement('article');
        card.className = 'project-card';
        card.innerHTML = `
            <h3>${p.title}</h3>
            <p>${p.desc}</p>
            <div class="tags">${p.tags.map(t => `<span>${t}</span>`).join('')}</div>
        `;
        fragment.appendChild(card);
    });
    
    container.appendChild(fragment);
}

document.addEventListener('DOMContentLoaded', renderProjects);

5.2 Flask应用完整代码包

5.2.1 app.py(应用入口与路由定义)
python 复制代码
from flask import Flask, render_template, jsonify, request
from datetime import datetime

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html', 
                         title='PythonAnywhere 演示',
                         time=datetime.now())

@app.route('/api/health')
def health():
    return jsonify({
        'status': 'ok',
        'time': datetime.now().isoformat()
    })

@app.route('/api/data', methods=['GET', 'POST'])
def data():
    if request.method == 'POST':
        return jsonify({'received': request.get_json()}), 201
    return jsonify({'items': [1, 2, 3]})

if __name__ == '__main__':
    app.run(debug=True)
5.2.2 requirements.txt(依赖清单)
复制代码
Flask==2.3.3
gunicorn==21.2.0
Werkzeug==2.3.7
5.2.3 templates/index.html(Jinja2模板)
html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>{{ title }}</title>
</head>
<body>
    <h1>{{ title }}</h1>
    <p>服务器时间: {{ time }}</p>
    <button onclick="testApi()">测试 API</button>
    <pre id="result"></pre>
    
    <script>
        async function testApi() {
            const res = await fetch('/api/health');
            const data = await res.json();
            document.getElementById('result').textContent = JSON.stringify(data, null, 2);
        }
    </script>
</body>
</html>

5.3 Express应用完整代码包

5.3.1 server.js(服务器入口与中间件配置)
javascript 复制代码
const express = require('express');
const path = require('path');

const app = express();
const PORT = process.env.PORT || 3000;

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static('public'));

app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

app.get('/api/health', (req, res) => {
    res.json({ status: 'ok', time: new Date().toISOString() });
});

app.post('/api/echo', (req, res) => {
    res.json({ body: req.body, query: req.query });
});

app.listen(PORT, () => console.log(`Server on ${PORT}`));
5.3.2 package.json(项目元数据与脚本)
json 复制代码
{
  "name": "express-demo",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "express": "^4.18.2"
  },
  "engines": {
    "node": "18.x"
  }
}
5.3.3 public/index.html(前端表单界面)
html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>Express Demo</title>
    <style>
        body { font-family: system-ui; max-width: 600px; margin: 2rem auto; padding: 0 1rem; }
        input, textarea { display: block; width: 100%; margin: 0.5rem 0; padding: 0.5rem; }
        button { padding: 0.5rem 1rem; background: #2563eb; color: white; border: none; }
        #result { background: #f3f4f6; padding: 1rem; margin-top: 1rem; }
    </style>
</head>
<body>
    <h1>Express 表单演示</h1>
    <form id="demo-form">
        <input name="name" placeholder="姓名" required>
        <input name="email" type="email" placeholder="邮箱" required>
        <textarea name="message" placeholder="留言"></textarea>
        <button type="submit">提交</button>
    </form>
    <pre id="result"></pre>
    
    <script>
        document.getElementById('demo-form').addEventListener('submit', async (e) => {
            e.preventDefault();
            const formData = new FormData(e.target);
            const data = Object.fromEntries(formData);
            
            const res = await fetch('/api/echo', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(data)
            });
            
            const result = await res.json();
            document.getElementById('result').textContent = JSON.stringify(result, null, 2);
        });
    </script>
</body>
</html>
相关推荐
SeanDe2 小时前
【Linux `top` 命令详解(结合截图逐行拆解)】
linux·运维·服务器
桌面运维家2 小时前
Windows/Linux文件访问权限修改指南
linux·运维·服务器
欧云服务器3 小时前
魔方云批量更换ip教程
服务器·网络·tcp/ip
程序猿编码3 小时前
Linux 进程注入:从调试器到武器化的技术演进
linux·运维·服务器·c++·进程注入
大虾别跑5 小时前
麒麟v10搭建rsync
linux·运维·服务器
自动化智库5 小时前
西门子XB208网管型交换机使用方法
运维·服务器·网络
CDN3605 小时前
CSDN 运维笔记|360CDN 高防服务器配置与防护规则
运维·服务器·笔记
爱学习的小囧5 小时前
VCF 集群部署灵活组合:单节点与高可用配置完全指南
java·服务器·前端
BullSmall5 小时前
LVS与HAProxy高可用负载方案详解
linux·服务器·网络