2024年12月

TL;DR

通过vercel第三方库来运行php程序

效果展示

展示不了一点,我发现部署几天后开始变得非常卡,不知道为啥

所以我推荐你每天定时deploy一下,会好很多

一,注册需要的账号

你需要注册以下账号

  • github
  • vercel
  • 一个免费或者你自己搭建的mysql数据库
关于数据库,不推荐使用国内的免费数据库,因为vercel访问太慢了
或许你可以尝试db4free,或者一些免费空间提供的数据库

二,操作步骤

1. 创建git仓库

在github创建一个仓库,注意这个仓库需要是private的,也就是私密的仓库。然后clone到本地。下面为了方便,假设仓库名为blog

2. 下载typecho

下载地址:https://typecho.org/download

3. 把typecho解压到仓库文件夹内

此时文件应该存放在根目录

4. 新建api文件夹,然后在api文件夹下创建index.php文件

其中index.php文件的内容如下

<?php
$file= __DIR__ . '/..'.$_SERVER["PHP_SELF"];

if(file_exists($file))
{
   return false;
}
else
{
    require_once __DIR__ . '/../index.php';
}
#echo $_SERVER["PHP_SELF"];

5. 在仓库中创建vercel.json

内容如下

{
  "functions": {
    "api/index.php": {
      "runtime": "vercel-php@0.7.3"
    }
  },
  "routes": [{ "src": "/(.*)", "dest": "/api/index.php" }]
}

6. 在根目录创建config.inc.php

内容如下

<?php
/**
 * Typecho Blog Platform
 *
 * @copyright  Copyright (c) 2008 Typecho team (http://www.typecho.org)
 * @license    GNU General Public License 2.0
 * @version    $Id$
 */

/** 开启https */
define('__TYPECHO_SECURE__',true);

/** 定义根目录 */
define('__TYPECHO_ROOT_DIR__', dirname(__FILE__));

/** 定义插件目录(相对路径) */
define('__TYPECHO_PLUGIN_DIR__', '/usr/plugins');

/** 定义模板目录(相对路径) */
define('__TYPECHO_THEME_DIR__', '/usr/themes');

/** 后台路径(相对路径) */
define('__TYPECHO_ADMIN_DIR__', '/admin/');

/** 设置包含路径 */
@set_include_path(get_include_path() . PATH_SEPARATOR .
__TYPECHO_ROOT_DIR__ . '/var' . PATH_SEPARATOR .
__TYPECHO_ROOT_DIR__ . __TYPECHO_PLUGIN_DIR__);

/** 载入API支持 */
require_once 'Typecho/Common.php';

/** 程序初始化 */
Typecho_Common::init();

/** 定义数据库参数 */
$db = new Typecho_Db('Pdo_Mysql', 'typecho_');
$db->addServer(array (
  'host' => '数据库地址',
  'user' => '数据库用户',
  'password' => '数据库密码',
  'charset' => 'utf8mb4',
  'port' => '3306',
  'database' => '数据库名',
  'engine' => 'MyISAM',
), Typecho_Db::READ | Typecho_Db::WRITE);
Typecho_Db::set($db);

7. 注释掉会阻挡操作的内容

打开根目录下的install.php文件,注释或者删除773-775行,注释的内容大概如下。

    // if (!$writeable) {
        // $errors[] = _t('上传目录无法写入, 请手动将安装目录下的 %s 目录的权限设置为可写然后继续升级', $uploadDir);
    // }
这里如果没有删除,可能会无法初始化数据库

8. 上传代码,在vercel中编译,注意提前绑定域名

9. 第一次打开会报错

提示Database Query Error。这时你需要在你的域名||vercel提供的免费域名后添加/install.php,然后按照步骤操作。

这里如果你没有执行第7步的操作,可能会报错,建议修改之后重新提交

三,注意事项

  1. 过长的等待时间会触发vercel报错,提示This Serverless Function has timed out. 这时你或许可以考虑更换数据库
  2. 注意看第7点
  3. 一些函数官方说支持,但是会有奇怪的问题
  4. 上传二进制文件和直接修改文件的功能就别想了,在仓库中操作吧
  5. 暂时想不到了

Best Of 2024: Most Popular Movies

Best Of 2024: Most Popular TV Shows


电影

外文中文
1. Deadpool & Wolverine1. 死侍与金刚狼
2. Dune: Part Two2. 沙丘 2
3. Furiosa: A Mad Max Saga3. 疯狂的麦克斯:狂暴女神
4. Joker: Folie à Deux4. 小丑 2:双重妄想
5. Alien: Romulus5. 异形:夺命舰
6. The Substance6. 某种物质
7. Civil War7. 美国内战
8. Beetlejuice Beetlejuice8. 怪奇大法师
9. Kingdom of the Planet of the Apes9. 猩球崛起:新世界
10. Longlegs10. 长腿

电视剧

外文中文
1. True Detective1. 真探 第四季
2. The Boys2. 黑袍纠察队 第四季
3. The Penguin3. 企鹅人
4. Fallout4. 辐射
5. House of the Dragon5. 龙之家族 第二季
6. Shogun6. 幕府将军
7. Bridgerton7. 布里杰顿家族/布里奇顿
8. The Lord of the Rings: The Rings of Power8. 指环王:力量之戒
9. The Gentlemen9. 绅士们
10. 3 Body Problem10. 三体(Netflix 版)

不评判储存在服务器还是使用第三方的方案,只记录一些随机获取图像的接口。下面每个方案前都会放置相对于的接口的图像。

以下内容仅为个人记录用处,从未有意侵权已注册商标,也从未试图冒充任何开放内容官方。当然如果您认为下列内容侵犯了您的权益,请您与我联系 m#hoytzhang.com

无需注册的方案

Bing image

bing image

2025年3月3日 发现该网站证书失效。有兴趣的朋友可以访问下面的开源地址。或者你也可以考虑自己部署这个仓库到vercel上。 https://github.com/flow2000/bing-wallpaper-api

TL;DR

获取bing每日图像的接口非常多,下面是我推荐的一个,目前运行还是比较稳定的

官网地址

https://bing.img.run/api.html

开源地址

https://github.com/mike126126/bing

使用方法

Bing今日壁纸

<img src="https://bing.img.run/uhd.php" alt="Bing每日壁纸UHD超高清原图" />
<img src="https://bing.img.run/1920x1080.php" alt="Bing每日壁纸1080P高清" />
<img src="https://bing.img.run/1366x768.php" alt="Bing每日壁纸普清" />
<img src="https://bing.img.run/m.php" alt="Bing每日壁纸手机版1080P高清" />

随机获取Bing历史壁纸

<img src="https://bing.img.run/rand_uhd.php" alt="随机获取Bing历史壁纸UHD超高清原图" />
<img src="https://bing.img.run/rand.php" alt="随机获取Bing历史壁纸1080P高清" />
<img src="https://bing.img.run/rand_1366x768.php" alt="随机获取Bing历史壁纸普清" />
<img src="https://bing.img.run/rand_m.php" alt="随机获取Bing历史壁纸手机版1080P高清" />

Picsum.Photos

Picsum.Photos

官网

TL;DR

一款开源的,采用Unsplash图像源的免费网站

下面的内容我几乎是从他的官网直接复制过来的。你可以点击下面访问官网,带有图像更容易理解方便你使用。

https://picsum.photos/

源码

https://github.com/DMarby/picsum-photos

使用方法

  • 直接返回固定大小图像地址
https://picsum.photos/200/300
  • 返回固定大小的方形图像
https://picsum.photos/200
https://picsum.photos/id/237/200/300
  • 自定义seed的图像
https://picsum.photos/seed/picsum/200/300
  • 去色图像(灰色)
https://picsum.photos/200/300?grayscale
  • 添加高斯模糊(最后的参数支持1-10)
https://picsum.photos/200/300/?blur
https://picsum.photos/200/300/?blur=5

官方推荐的使用方法

您可以组合上述任何选项。

例如,获取灰度和模糊的特定图像。

https://picsum.photos/id/870/200/300?grayscale&blur=2

要在浏览器中请求多个相同大小的图片,请添加 random 查询参数以防止图片被缓存:

<img src="https://picsum.photos/200/300?random=1">
<img src="https://picsum.photos/200/300?random=2">

如果需要文件结尾,可以在 url 末尾添加 .jpg。

https://picsum.photos/200/300.jpg

要获取 WebP 格式的图像,您可以在 URL 末尾添加 .webp。

https://picsum.photos/200/300.webp

列出图像地址

列出图片

使用 /v2/list 端点获取图像列表。

https://picsum.photos/v2/list

默认情况下,API 将每页返回 30 个项目。

要请求另一个页面,请使用 ?page 参数。

要更改每页的项目数,请使用 ?limit 参数。

https://picsum.photos/v2/list?page=2&limit=100

链接的header需要包含有关下一页/上一页的分页信息

获取图像信息

https://picsum.photos/id/0/info
https://picsum.photos/seed/picsum/info

返回的格式如下

{
        "id": "0",
        "author": "Alejandro Escamilla",
        "width": 5616,
        "height": 3744,
        "url": "https://unsplash.com/...",
        "download_url": "https://picsum.photos/..."
}

LoremFlickr

LoremFlickr

官网

TL;DR

图像来自于Flickr,官网称目前的服务被严重影响

官网仅推荐当作占位符(Placeholder)来使用

https://loremflickr.com/

开源地址

https://github.com/MastaBaba/LoremFlickr-2

  • 直接使用

如果你不指定任意内容,将会返回小猫的图像,例如下面的这种用法

https://loremflickr.com/320/240
  • 指定主题
//这个地址将会返回`小狗(dog)`相关的图像
https://loremflickr.com/320/240/dog
  • 保留色彩

相对于picsum的灰色图像,LoremFlickr可以选择保留更多色彩

https://loremflickr.com/g/320/240/paris

这个示例将会返回灰色的 小狗(dog) 相关的图像。你还可以尝试 predgreenblue 这些关键字!

  • 多个关键字

下面这个地址将会返回与 小猫(cat)和小狗(dog) 相关的图像

https://loremflickr.com/320/240/cat,dog
  • 强调关键字

上面的使用方案可能会同时包含 小猫(cat)和小狗(dog) ,如果你像强调关键字,那么你可以使用下面的方法

https://loremflickr.com/320/240/cat,dog/all

可以搭配色彩关键字使用

https://loremflickr.com/g/320/240/cat,dog/all
  • 锁定图像
https://loremflickr.com/320/240?lock=30976
  • 避免重复图像,同样支持random函数
https://loremflickr.com/320/240?random=1
https://loremflickr.com/320/240?random=2
https://loremflickr.com/320/240?random=3
  • 支持json返回数据
https://loremflickr.com/json/g/320/240/paris,girl/all
  • 支持RSS订阅
https://loremflickr.com/rss/d/g/320/240/paris,girl/all

需要注册的方案

Unsplash

大名鼎鼎的Unsplash的免费接口寿终正寝后(貌似是因为流量太大且没有回流),api从开放式改为账号调用。

2024年6月11日官方下线了免费调用,但是你依旧可以注册后使用这个服务,不过免费服务存在一些限制,下面是三个档次的服务区别:

  1. Demo:每小时50次调用
  2. Production:在遵守Unsplash条件的前提下,每小时5000次
  3. Enterprise:联系Unsplash团队

在第二条中,Unsplash的要求包括但不限于:

  • 每张图片只允许使用Url返回的 photo.urls 的图像
  • 用户进行图像下载时,必须使用 photo.links.download_location 的链接进行下载
  • 每张图像展示时标明摄影师且附带摄影师的个人链接
  • 不允许制作Unsplash的竞品程序

如果你可以接受并遵守以上条件,你可以访问这个文档进行申请:https://unsplash.com/documentation

你可以访问这里查看: https://hoytzhang.com

pc-hoytzhang.webp

实现的效果:

  • 随机显示背景图像
  • 从背景图像提取主要颜色设置为按钮的颜色
  • 适配移动端
  • 点击项目按钮展开/收缩项目

下面是代码,当然还有一个去除JS的代码,一起附在下面,可能需要你手动设置按钮颜色

附带JS的版本

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My page</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css">
    <style>
        @font-face {
            font-family: 'Opposans';
            src: url('opposans.woff2') format('woff2');
            font-weight: normal;
            font-style: normal;
        }
        html, body {
            height: 100%;
            margin: 0;
            overflow: hidden;
        }
        #buttonContainer hr {
            border: none;
            border-top: 2px dashed #007bff;
            margin: 10px 10px;
        }
        body {
            font-family: 'Opposans', Arial, sans-serif;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            background-size: cover;
            background-position: center;
            background-repeat: no-repeat;
            transition: background 0.5s;
        }
        .container {
            text-align: center;
            backdrop-filter: blur(20px);
            border-radius: 20px;
            padding: 30px;
            width: 90%;
            max-width: 400px;
            box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
        }
        h1 {
            color: white;
            padding: 0;
            margin: 0;
        }
        .link {
            display: flex;
            align-items: center;
            justify-content: flex-start;
            color: white;
            padding: 15px;
            margin: 10px 0;
            text-decoration: none;
            border-radius: 30px;
            transition: background 0.3s;
        }
        .link i {
            padding: 0px;
            width: 1.2rem;
            height: 1.2rem;
            font-size: 1.2rem;
            margin-right: 10px;
        }
        .social-media {
            margin: 20px;
            display: flex;
            justify-content: center;
        }
        .social-icon {
            background-color: rgba(255, 255, 255, 0.2);
            color: white;
            border-radius: 50%;
            width: 2.5rem;
            height: 2.5rem;
            display: flex;
            justify-content: center;
            align-items: center;
            margin: 0 10px;
            font-size: 1.2rem;
            transition: background 0.3s;
            text-decoration: none;
        }
        .social-icon:hover {
            background-color: rgba(255, 255, 255, 0.4);
        }
        footer {
            margin-top: 20px;
            font-size: 0.8em;
            color: white;
        }

        @media (max-width: 600px) {
            .container {
                backdrop-filter: none;
                box-shadow: none;
            }
        }
    </style>
</head>
<body>

<div class="container">
    <h1>Name here</h1>
    <div class="social-media">
        <a class="social-icon" href="https://github.com/username" target="_blank"><i class="fa-brands fa-github"></i></a>
        <a class="social-icon" href="https://x.com/username" target="_blank"><i class="fa-brands fa-x-twitter"></i></a>
        <a class="social-icon" href="https://instagram.com/username" target="_blank"><i class="fa-brands fa-instagram"></i></a>
        <a class="social-icon" href="https://t.me/username" target="_blank"><i class="fa-brands fa-telegram"></i></a>
    </div>
    <a class="link" href="https://myblog.com"><i class="fas fa-scroll"></i>Blog</a>
    <a class="link" href="mailto:m@username.com"><i class="fas fa-envelope"></i>Hire me</a>
    <a class="link" href="/" id="dynamicButtonLink"><i class="fa-solid fa-file-code"></i>Projects</a>
    <div id="buttonContainer" style="display: none;">
        <hr>
        <a class="link" href="https://username.site"><i class="fas fa-link"></i>Projects name</a>
        <a class="link" href="https://github.com/username/Projects1/"><i class="fab fa-github"></i>Projects1</a>
        <a class="link" href="https://github.com/username/Projects2/"><i class="fab fa-github"></i>Projects2</a>
    </div>
</div>

<footer>
    <p>&copy; 2024 Name</p>
</footer>

<canvas id="myCanvas" style="display:none;"></canvas>

<script>
    const backgrounds = [
        '1.webp',
        '2.webp',
        '3.webp',
        '4.webp',
        '5.webp',
        '6.webp',
        '7.webp',
        '8.webp',
        '9.webp',
        '10.webp',
        '11.webp',
        '12.webp',
        '13.webp',
        '14.webp',
        '15.webp',
        '16.webp',
        '17.webp',
        '18.webp'
    ];
    
    const randomIndex = Math.floor(Math.random() * backgrounds.length);
    const backgroundImage = backgrounds[randomIndex];
    document.body.style.backgroundImage = `url(${backgroundImage})`;

    const canvas = document.getElementById('myCanvas');

    function getImageColor(canvas, img) {
        canvas.width = img.width;
        canvas.height = img.height;

        const context = canvas.getContext("2d");
        context.drawImage(img, 0, 0, canvas.width, canvas.height);

        const data = context.getImageData(0, 0, img.width, img.height).data;
        let r = 0, g = 0, b = 0;

        const pixelCount = img.width * img.height;
        for (let i = 0; i < data.length; i += 4) {
            r += data[i];     // R
            g += data[i + 1]; // G
            b += data[i + 2]; // B
        }
        r = Math.round(r / pixelCount);
        g = Math.round(g / pixelCount);
        b = Math.round(b / pixelCount);

        return `rgb(${r}, ${g}, ${b})`;
    }

    function lightenColor(rgb, percent) {
        const rgbValues = rgb.match(/\d+/g).map(Number);
        const r = Math.min(255, Math.round(rgbValues[0] + (255 - rgbValues[0]) * percent));
        const g = Math.min(255, Math.round(rgbValues[1] + (255 - rgbValues[1]) * percent));
        const b = Math.min(255, Math.round(rgbValues[2] + (255 - rgbValues[2]) * percent));
        return `rgb(${r}, ${g}, ${b})`;
    }

    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.src = backgroundImage;

    img.onload = function() {
        const rgbColor = getImageColor(canvas, img);
        
        const links = document.querySelectorAll('.link');
        links.forEach(link => {
            link.style.backgroundColor = rgbColor;
            link.style.color = 'white';
            
            const hoverColor = lightenColor(rgbColor, 0.2);
            link.addEventListener('mouseover', () => {
                link.style.backgroundColor = hoverColor;
            });
            link.addEventListener('mouseout', () => {
                link.style.backgroundColor = rgbColor;
            });
        });
        const hr = document.querySelector('#buttonContainer hr');
        hr.style.borderTop = `2px dashed ${rgbColor}`;
        const socialIcons = document.querySelectorAll('.social-icon');
        socialIcons.forEach(icon => {
            icon.style.backgroundColor = rgbColor;
            
            const hoverColor = lightenColor(rgbColor, 0.2);
            icon.addEventListener('mouseover', () => {
                icon.style.backgroundColor = hoverColor;
            });
            icon.addEventListener('mouseout', () => {
                icon.style.backgroundColor = rgbColor;
            });
        });

        const footer = document.querySelector('footer');
        footer.style.color = rgbColor;
    };
    document.getElementById('dynamicButtonLink').addEventListener('click', function(event) {
        event.preventDefault();

        const buttonContainer = document.getElementById('buttonContainer');

        if (buttonContainer.style.display === 'none') {
            buttonContainer.style.display = 'block';
        } else {
            buttonContainer.style.display = 'none';
        }
    });
</script>

</body>
</html>

去除JS的版本

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My page</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css">
    <style>
        @font-face {
            font-family: 'Opposans';
            src: url('opposans.woff2') format('woff2');
            font-weight: normal;
            font-style: normal;
        }
        html, body {
            height: 100%;
            margin: 0;
            overflow: hidden;
        }
        #buttonContainer hr {
            border: none;
            border-top: 2px dashed #007bff;
            margin: 10px 10px;
        }
        body {
            font-family: 'Opposans', Arial, sans-serif;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            background-size: cover;
            background-position: center;
            
            background-repeat: no-repeat;
            transition: background 0.5s;
        }
        .container {
            text-align: center;
            backdrop-filter: blur(20px);
            border-radius: 20px;
            padding: 30px;
            width: 90%;
            max-width: 400px;
            box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
        }
        h1 {
            color: white;
            padding: 0;
            margin: 0;
        }
        .link {
            display: flex;
            align-items: center;
            justify-content: flex-start;
            color: white;
            padding: 15px;
            margin: 10px 0;
            text-decoration: none;
            border-radius: 30px;
            transition: background 0.3s;
        }
        .link i {
            padding: 0px;
            width: 1.2rem;
            height: 1.2rem;
            font-size: 1.2rem;
            margin-right: 10px;
        }
        .social-media {
            margin: 20px;
            display: flex;
            justify-content: center;
        }
        .social-icon {
            background-color: rgba(255, 255, 255, 0.2);
            color: white;
            border-radius: 50%;
            width: 2.5rem;
            height: 2.5rem;
            display: flex;
            justify-content: center;
            align-items: center;
            margin: 0 10px;
            font-size: 1.2rem;
            transition: background 0.3s;
            text-decoration: none;
        }
        .social-icon:hover {
            background-color: rgba(255, 255, 255, 0.4);
        }
        footer {
            margin-top: 20px;
            font-size: 0.8em;
            color: white;
        }

        @media (max-width: 600px) {
            .container {
                backdrop-filter: none;
                box-shadow: none;
            }
        }
    </style>
</head>
<body>

<div class="container">
    <h1>Name here</h1>
    <div class="social-media">
        <a class="social-icon" href="https://github.com/username" target="_blank"><i class="fa-brands fa-github"></i></a>
        <a class="social-icon" href="https://x.com/username" target="_blank"><i class="fa-brands fa-x-twitter"></i></a>
        <a class="social-icon" href="https://instagram.com/username" target="_blank"><i class="fa-brands fa-instagram"></i></a>
        <a class="social-icon" href="https://t.me/username" target="_blank"><i class="fa-brands fa-telegram"></i></a>
    </div>
    <a class="link" href="https://myblog.com"><i class="fas fa-scroll"></i>Blog</a>
    <a class="link" href="mailto:m@username.com"><i class="fas fa-envelope"></i>Hire me</a>
    <a class="link" href="/" id="dynamicButtonLink"><i class="fa-solid fa-file-code"></i>Projects</a>
    <hr>
    <a class="link" href="https://username.site"><i class="fas fa-link"></i>Projects name</a>
    <a class="link" href="https://github.com/username/Projects1/"><i class="fab fa-github"></i>Projects1</a>
    <a class="link" href="https://github.com/username/Projects2/"><i class="fab fa-github"></i>Projects2</a>
</div>

<footer>
    <p>&copy; 2024 Name</p>
</footer>

</body>
</html>

附加随机图像

如果你想要一个每次打开都会展示不一样的背景图像的页面,你可以使用下面的代码。如果你不想使用代码里的随机图像方案,你可以查看我的另一篇文章,里面有其他可选择的随机图像api: https://banzhuanriji.com/frontend/random-image.html
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Name</title>
    <link rel="stylesheet" href="https://s4.zstatic.net/ajax/libs/font-awesome/6.7.2/css/all.min.css">
    <style>
        @font-face {
            font-family: 'Opposans';
            src: url('opposans.woff2') format('woff2');
            font-weight: normal;
            font-style: normal;
        }
        html, body {
            height: 100%;
            margin: 0;
            overflow: hidden;
        }
        #buttonContainer hr {
            border: none;
            border-top: 2px dashed #007bff;
            margin: 10px 10px;
        }
        body {
            font-family: 'Opposans', Arial, sans-serif;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            background-size: cover;
            background-position: center;
            background-repeat: no-repeat;
            transition: background 0.5s;
        }
        .container {
            text-align: center;
            backdrop-filter: blur(20px);
            border-radius: 20px;
            padding: 30px;
            width: 90%;
            max-width: 400px;
            box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
        }
        h1 {
            color: white;
            padding: 0;
            margin: 0;
        }
        .link {
            display: flex;
            align-items: center;
            justify-content: flex-start;
            color: white;
            padding: 15px;
            margin: 10px 0;
            text-decoration: none;
            border-radius: 30px;
            transition: background 0.3s;
        }
        .link i {
            padding: 0px;
            width: 1.2rem;
            height: 1.2rem;
            font-size: 1.2rem;
            margin-right: 10px;
        }
        .social-media {
            margin: 20px;
            display: flex;
            justify-content: center;
        }
        .social-icon {
            background-color: rgba(255, 255, 255, 0.2);
            color: white;
            border-radius: 50%;
            width: 3rem;
            height: 3rem;
            display: flex;
            justify-content: center;
            align-items: center;
            margin: 0 10px;
            font-size: 1.2rem;
            transition: background 0.3s;
            text-decoration: none;
        }
        .social-icon:hover {
            background-color: rgba(255, 255, 255, 0.4);
        }
        footer {
            margin-top: 20px;
            font-size: 0.8em;
            color: white;
        }

        @media (max-width: 600px) {
            .container {
                backdrop-filter: none;
                box-shadow: none;
            }
        }
    </style>
</head>
<body>

<div class="container">
    <h1>Name</h1>
    <div class="social-media">
        <a class="social-icon" href="https://github.com/username" target="_blank"><i class="fa-brands fa-github"></i></a>
        <a class="social-icon" href="https://x.com/username" target="_blank"><i class="fa-brands fa-x-twitter"></i></a>
        <a class="social-icon" href="https://instagram.com/username" target="_blank"><i class="fa-brands fa-instagram"></i></a>
        <a class="social-icon" href="https://t.me/username" target="_blank"><i class="fa-brands fa-telegram"></i></a>
    </div>
    <a class="link" href="https://name.com"><i class="fas fa-scroll"></i>Blog</a>
    <a class="link" href="/"><i class="fas fa-envelope"></i>Hire me</a>
    <a class="link" href="/" id="dynamicButtonLink"><i class="fa-solid fa-file-code"></i>Projects</a>
    <div id="buttonContainer" style="display: none;">
        <hr>
        <a class="link" href="https://github.com/username/Project1/"><i class="fab fa-github"></i>Project1</a>
        <a class="link" href="https://github.com/username/Project2/"><i class="fab fa-github"></i>Project2</a>
    </div>
</div>

<footer>
    <p>&copy; 2024 username</p>
</footer>

<canvas id="myCanvas" style="display:none;"></canvas>

<script>
    const imageUrl = 'https://picsum.photos/1920/1080.webp';

    // Fetch the image to get a consistent URL
    fetch(imageUrl)
        .then(response => {
            if (response.ok) {
                const img = new Image();
                img.crossOrigin = 'anonymous';
                img.src = response.url; // Use the resolved URL
                img.onload = function() {
                    document.body.style.backgroundImage = `url(${response.url})`;
                    const canvas = document.getElementById('myCanvas');
                    const rgbColor = getImageColor(canvas, img);

                    const links = document.querySelectorAll('.link');
                    links.forEach(link => {
                        link.style.backgroundColor = rgbColor;
                        link.style.color = 'white';

                        const hoverColor = lightenColor(rgbColor, 0.2);
                        link.addEventListener('mouseover', () => {
                            link.style.backgroundColor = hoverColor;
                        });
                        link.addEventListener('mouseout', () => {
                            link.style.backgroundColor = rgbColor;
                        });
                    });

                    const hr = document.querySelector('#buttonContainer hr');
                    hr.style.borderTop = `2px dashed ${rgbColor}`;
                    const socialIcons = document.querySelectorAll('.social-icon');
                    socialIcons.forEach(icon => {
                        icon.style.backgroundColor = rgbColor;

                        const hoverColor = lightenColor(rgbColor, 0.2);
                        icon.addEventListener('mouseover', () => {
                            icon.style.backgroundColor = hoverColor;
                        });
                        icon.addEventListener('mouseout', () => {
                            icon.style.backgroundColor = rgbColor;
                        });
                    });

                    const footer = document.querySelector('footer');
                    footer.style.color = rgbColor;
                };
            } else {
                console.error('Failed to fetch image:', response.status);
            }
        })
        .catch(error => console.error('Error fetching the image:', error));

    function getImageColor(canvas, img) {
        canvas.width = img.width;
        canvas.height = img.height;

        const context = canvas.getContext("2d");
        context.drawImage(img, 0, 0, canvas.width, canvas.height);

        const data = context.getImageData(0, 0, img.width, img.height).data;
        let r = 0, g = 0, b = 0;

        const pixelCount = img.width * img.height;
        for (let i = 0; i < data.length; i += 4) {
            r += data[i];     // R
            g += data[i + 1]; // G
            b += data[i + 2]; // B
        }
        r = Math.round(r / pixelCount);
        g = Math.round(g / pixelCount);
        b = Math.round(b / pixelCount);

        return `rgb(${r}, ${g}, ${b})`;
    }

    function lightenColor(rgb, percent) {
        const rgbValues = rgb.match(/\d+/g).map(Number);
        const r = Math.min(255, Math.round(rgbValues[0] + (255 - rgbValues[0]) * percent));
        const g = Math.min(255, Math.round(rgbValues[1] + (255 - rgbValues[1]) * percent));
        const b = Math.min(255, Math.round(rgbValues[2] + (255 - rgbValues[2]) * percent));
        return `rgb(${r}, ${g}, ${b})`;
    }

    document.getElementById('dynamicButtonLink').addEventListener('click', function(event) {
        event.preventDefault();

        const buttonContainer = document.getElementById('buttonContainer');

        if (buttonContainer.style.display === 'none') {
            buttonContainer.style.display = 'block';
        } else {
            buttonContainer.style.display = 'none';
        }
    });
</script>

</body>
</html>

一、不利于SEO

搜索引擎会认为 aaa_bbb 是一个连词,从而识别为这是一个整体单词 aaabbb 。例如你搜索 my_site ,搜索引擎会认为你搜索了 mysite 而非 my site 。而在链接中使用 - 会让搜索引擎认为这是分词符,从而输入更多的准确的关键字。可以参考Google的说明文档

进而,在用户进行搜索时,搜索引擎可能会忽略你的网站内容。

二、利于分辨

友好的内容不仅应该方便机器读取,更应该首先考虑人的感受。那么从人的角度来说为什么不应该使用下划线

  1. 下划线同样会给人一个“这是个长单词的感觉”。没有人会记住你想表达的内容。
  2. 一般的超链接的样式会设置为下划线和蓝色,如果你的链接内带有下划线,有可能会和样式产生重叠和遮挡。
  3. 如果超链接在文本中进行展示时,当你有一个比较长的链接,比如: site.com/this_is_a_very_long_link_on_my_website 。人在试图读这个链接时,眼睛会不由自主地跟随下划线移动。这似乎不太合适。

三、约定俗成的习惯和莫名其妙的bug

一般在计算机领域,我们在本地文件会使用下划线内容。然后上传文件后对外提供的链接就会是这样: http://site.com/file_name.txt 。但是这样会出现一个问题:在互联网传播时,浏览器内复制粘贴会将 _ 转义为 %5F ,如果服务器没有进行配置,那你的超链接的文件就会404。而且貌似很多知名公司都有这个问题
(现在可能已经被修复)。

四、在域名中不合法

RFC1738RFC1035等多次投票结果的规则中对域名中出现 _ 表示禁止。也对以前申请了证书的带有下划线的域名的证书进行了回收和禁止续费。但这只是顶级域名的要求,而不是对于顶级域名下的子超链接和文件的限制。

其实只是很久以前看过的一个博客。说的是在美国军队中导致的一些问题从而禁用了下划线,最近想到了就重新整理一下写成文章分享出来。

以下是一些在网页程序中设置用户主页路由比较合理的方式及相关要点:

基于 RESTful 风格

路径设计

通常可以采用类似 /users/{username} 这样的路径形式,其中 {username} 是可变的部分,代表具体的用户名。例如,若用户名为 john,那访问 /users/john 就可以跳转到对应的用户主页。这种方式清晰地体现了资源(用户)与对应的标识(用户名)的关系,符合 RESTful 规范中对资源定位的要求,方便理解和记忆,也利于前后端的分工协作以及接口的统一管理。

还可以添加版本号在路由最前面,如 /v1/users/{username},便于后续对 API 等进行迭代升级,不同版本的路由逻辑可以独立维护,不会相互干扰。

HTTP 方法匹配

对于用户主页的获取(也就是查看用户主页信息),一般使用 GET 方法来请求对应的路由。比如,当浏览器发起 GET 请求到 /users/john 时,后端根据这个路由和请求方法,查询数据库中关于 john 用户的信息并返回给前端进行展示,符合 RESTful 中不同方法对应不同操作语义的原则,GET 方法专门用于获取资源这一操作场景。

考虑用户身份验证与授权相关路由整合

登录态验证

  • 在用户主页路由对应的处理逻辑之前,往往需要先验证用户是否已经登录。可以通过中间件来拦截请求,检查请求中携带的登录凭证(如 token 等)是否有效。比如,在很多基于 Node.js 的 Web 框架(像 Express)中,编写一个验证登录态的中间件,在路由配置里让用户主页路由先经过这个中间件,只有验证通过才能继续后续的主页数据获取和渲染流程,确保未登录用户无法直接访问用户主页,保障页面的访问安全性和隐私性。

权限控制路由结合

有时候用户主页可能根据用户权限展示不同内容,例如管理员用户的主页可能有更多管理功能入口。此时,路由可以配合权限控制模块,根据用户的角色(如普通用户、管理员等)来决定在加载用户主页时,返回哪些对应的模块数据和页面布局。像可以在路由处理函数中先获取用户角色信息,再依据角色从数据库中查询相应权限下的页面配置数据,以准确渲染出符合其权限的用户主页。

动态路由参数与页面状态关联

页面主题切换参数

若用户主页支持切换主题(如亮色模式、暗色模式等),可以在路由中添加额外的参数来表示主题状态,比如 /users/{username}?theme=dark 表示访问 username 用户的主页且采用暗色主题模式。后端接收到这样带有参数的路由请求后,根据 theme 参数的值来调整返回给前端的页面样式相关资源,实现灵活的页面状态定制。

分页相关参数

当用户主页有分页展示内容(比如用户发布的文章列表分页显示等),路由中可以包含分页参数,像 /users/{username}/posts?page=2&limit=10,表示查看 username 用户的文章列表,当前是第 2 页,每页显示 10 条内容。后端根据这些参数从数据库中准确提取对应的数据返回给前端,方便用户浏览不同页面的数据内容,提升用户体验。

路由懒加载与缓存策略配合

懒加载实现

对于比较复杂的用户主页,尤其是包含很多模块(如动态的个人动态展示、好友推荐等多个功能模块),可以采用路由懒加载的方式。例如在基于 Vue.js 或 React 等前端框架构建的单页面应用中,将用户主页的不同组件模块配置成懒加载形式,如 const UserHomePage = () => import (‘./UserHomePage.vue’);(Vue.js 示例),这样当用户访问到对应路由时,才会去加载对应的模块代码,减少初始加载的资源量,提高页面加载速度。

缓存优化

结合缓存策略,对于用户主页中一些相对静态、不常变化的数据(比如用户的基本信息介绍等),可以在浏览器端或者服务器端设置缓存机制。比如,在服务器端通过设置合适的缓存头(如 Cache-Control 等),告诉浏览器在一定时间内可以直接使用缓存的页面内容,减少重复请求,提升用户再次访问主页的响应速度,同时减轻服务器压力。

国际化与本地化路由适配

语言参数设置

如果网页程序支持多语言,在用户主页路由中可以添加语言相关参数,比如 /users/{username}?lang=en 表示访问 username 用户的主页且采用英语语言环境。后端根据这个参数,加载对应的语言包来渲染页面文字内容,使得不同语言习惯的用户都能获得合适的用户主页展示效果,方便国际化拓展和满足不同地区用户的需求。

总之,设置用户主页路由需要综合考虑多方面因素,从路由的规范、安全性、页面状态灵活度、性能优化以及多语言适配等角度出发,打造出合理且易用的路由体系。

次级域名方案分析

技术可行性

从技术角度来说,是可以设置username.website.com这类次级域名(也称为子域名)路由的。在服务器配置层面,对于支持虚拟主机的 Web 服务器(如 Apache 和 Nginx),可以通过配置虚拟主机来实现子域名的指向。

以 Nginx 为例,你需要在服务器配置文件中添加一个新的服务器块(server block)。假设你的主域名是website.com,要为用户username设置子域名路由,配置可能如下:

server {
    listen       80;
    server_name  username.website.com;
    # 以下是实际指向的路径,例如指向用户主页的目录
    location / {
        root   /var/www/username_homepage;
        index  index.html;
    }
}

这就将username.website.com这个子域名请求指向了/var/www/username_homepage这个服务器上的目录,当用户访问该子域名时,服务器会从这个目录中寻找并返回相应的网页文件。

域名系统(DNS)配置

要使用username.website.com这样的子域名,还需要在域名系统(DNS)中进行正确的配置。你需要添加一条CNAME(规范名称)记录或者A记录(如果是直接指向IP地址)来将子域名指向你的服务器。

如果你的网站是通过域名注册商管理的,你可以登录到域名管理控制台,在DNS设置区域添加相应的记录。例如,添加一个CNAME记录,将username.website.com指向website.com(假设website.com已经正确配置指向服务器的IP地址),或者直接添加A记录,将username.website.com指向服务器的实际IP地址。

应用程序路由逻辑

在网页程序内部,你需要根据请求的子域名来确定对应的路由逻辑。例如,在后端应用程序(如使用Python的Flask或Django等框架)中,需要编写代码来识别子域名部分,并根据不同的username加载相应的用户主页内容。

在Flask中,你可以使用request对象来获取请求的子域名信息,如下所示:

   from flask import Flask, request
   app = Flask(__name__)
   @app.route('/')
   def user_homepage():
       subdomain = request.headers.get('Host').split('.')[0]
       # 根据子域名subdomain(也就是username)加载对应的用户主页内容
       # 这里只是示例,实际可能需要查询数据库等操作
       return f"Welcome to the homepage of {subdomain}"

注意事项

  • 安全性:确保在处理子域名请求时,进行适当的安全验证。例如,防止恶意用户通过构造虚假的子域名来访问未授权的内容或者进行攻击。可以通过验证用户身份、检查子域名是否在合法的用户列表中等方式来增强安全性。
  • 性能和维护成本:使用大量的子域名可能会增加服务器配置的复杂性和维护成本。每次添加新的子域名都需要在服务器和 DNS 中进行配置,并且需要确保应用程序能够正确处理这些子域名请求。此外,过多的子域名可能会对服务器性能产生一定的影响,尤其是在处理大量并发请求时。
  • 兼容性:有些浏览器或者网络环境可能对某些子域名设置有特殊的限制或者兼容性问题。在部署使用子域名的路由之前,最好进行充分的测试,以确保在各种主流设备和浏览器上都能正常工作。
  • 一些需要保留的用户名或者分层进行路由处理。例如典型的www用户名,可能会使用户主页引向产品首页。还有比如github的security 用户,你打开他的主页就会跳转到 github.com/security 的页面。此外gitee也有类似的问题。

所以在设置用户路由或者用户页面时,需要首先考虑到安全性,其次就是功能性,这种可能影响到内容使用和系统安全的操作,用户体验或许需要向后排一排。