Alt
说到深情的表白,大家在脑海里浮现的是什么?

是莎士比亚追求爱情时的思索:

是爱你,还是更爱你,这是一个值得思考的问题。

还是爱因斯坦相对论之外的浪漫:

万物都是相对的,而我对你的心,却是绝对的!

亦或是在印着玫瑰花的信纸上,一手漂亮的字,潜藏着山有木兮木有枝的婉约,溢满了在地愿为连理枝的缠绵。


说到程序员,大家脑海里浮现的又是什么呢?

是指尖敲出的一行行冷硬的代码,还是源源不断的 bug,亦或是软件开发时不厌其烦地一次次修补、更新、升级。

「表白」「代码」,听上去就是一对反义词。

然而表白不只有一种形式,程序员的世界也并非只有代码,在一些人眼中,这看似不合理的结合,却是寄于天地间,最富诗情画意的情愫。

今年情人节,Authing 的开发者 Willin 给大家带来了一个表白利器——表白网站“憨憨.我爱你”,解锁独属于程序员的浪漫。

参考示例网站:

01

表白前的准备:申请流程

第一步,打开官网首页:憨憨.我爱你。
(由于该网站数据库使用的是 PlanetScale 免费服务,位于美东,所以访问可能会有一点慢,请耐心等待。)


第二步,使用 Authing 账号登录。
(您可以通过手机号码、邮箱注册账号,也可以使用 Github 账号或者微信小程序扫码登录,后续我们还会添加更多登录方式。)

可以点击选择进入域名申请或邮箱申请

域名申请

域名申请界面如下:


网站支持的绑定方式有三种:

  • CNAME:可以使用 Github Pages、 Netlify、 Gitee、 Coding.net、 Cloudflare 等提供托管服务的平台。
    • 值参考:willin.github.io
  • 注意:不支持 Vercel ,因为 Vercel 默认情况下并不支持绑定二级域名。(除非所有权在您个人名下)
  • A:IPv4 需要自己搭建服务器,并进行绑定。
    • 值参考:1.2.3.4 (您服务器的 ip 地址)
  • AAAA: IPv6 需要自己搭建服务器,并进行绑定。
    • 如果您需要同时绑定 IPv4 和 IPv6 的话,建议注册 A 类型,然后 ISSUE 或邮件联系 Authing 的开发者配合处理。(开发者邮箱:wangzhilin@authing.cn)
  • 此方法需要一定的网站开发技术基础,不太推荐非专业人士选择。

其中还有一项 Proxied(CDN),如果您不知道是否绑定成功,可以尝试开启或关闭网站来测试。

邮箱申请

域名申请界面如下:


目前使用了 Cloudflare 的邮件转发服务,暂不支持 IDN 域名,但您可以提前抢注,第一时间拥有网站。

补充说明

技术支持

如果您在为心爱的 Ta 准备礼物时遇到问题,我们可以免费提供技术支持服务。(开发者邮箱:wangzhilin@authing.cn;开发者个人公众号:assholev0)

其他域名

  • js.cool 在多次协商后,目前已支持 Vercel 绑定)
  • log.lu(敬请期待)

感谢分享

如果网站对您有一定帮助,我们很乐意您将该网站分享给更多的人。

跃跃欲试

或许您有其他更浪漫、更有趣的 idea 想要实现。您可以:

  • 使用 Authing 快速集成开发属于自己的应用
  • 使用 Fork 本项目源码(完全开源),并提供您自己的域名服务
  • 在 Github 上对本项目进行完善和优化

02

表白的设计与实现:开源

接下来是一个非常重要的环节。俗话说,授人以鱼不如授人以渔。我们将“憨憨.我爱你”的源码进行开源,并详细讲解一下设计与实现的全部过程。

设计

这个项目需要花费大概 3 个小时的时间去完成。为了更高效、对开发者更友好,我们使用了 UI 框架,避免了额外的 UI 设计。您可以通过几个基础组件快速上手该项目。

技术选型

首先,第一步是技术选型。因为 Authing 提供的表白网站是一项免费服务,所以我们会尽量选择一些免费的服务商及相关的技术栈。

服务商:

  • Cloudflare:提供免费的域名解析、CDN 加速以及开放的接口。
  • Vercel:面向个人的免费应用托管,支持 Node.js 环境,使用 Next.js 框架。
  • PlanetScale:具有一定免费额度的云端 MySQL 服务。
  • Prisma:Cloud Studio 管理数据库。

其实,本来是想使用 Cloudflare 全家桶的,就是 Cloudflare Pages (静态网站) + Cloudflare Workers (Serverless 方法执行)及 KV (键值对存储),碍于时间和精力有限,最终采用了更简单快捷的实现方式。

技术栈:

  • Typescript:虽然用更少的代码做更多的事情是最佳理想状态,但 TS 提供了更高效的团队协作舞台。
  • Next.js:一个全栈框架(前端使用 React,后端类似于 http 模块和 Express),支持 SSR(服务器端渲染)和 SSG(静态站点生成)。
    • @authing/nextjs:Authing SSO 集成 SDK
  • Prisma:下一代的 ORM 框架,支持多种数据库(本项目使用的是 MySQL)和数据库迁移(Migration)。
  • Tailwind CSS:下一代的 CSS 框架,将实用性放在第一位。
    • Daisy UI:封装了一些 UI 样式组件。

数据库设计

Authing 用户集成省去了用户表的设计和用户相关接口的设计。

// 域名类型
enum DomainType {
  A
  AAAA
  CNAME
}
// 审核状态
enum Status {
  // 待审核
  PENDING
  // 激活
  ACTIVE
  // 已删除
  DELETED
  // 被管理员禁用
  BANNED
}
// 域名记录表
model Domains {
  // Cloudflare 域名记录的 ID,同时作为表主键 id
  id        String      @id @default(cuid()) @db.VarChar(32)
  // 自增 id,没有什么实际意义,只是为了减少查询(毕竟有调用配额限制),实际项目中不推荐自增主键及自增 id 使用
  no        Int         @default(autoincrement()) @db.UnsignedInt
  name      String      @db.VarChar(255)
  punycode  String      @db.VarChar(255)
  type      DomainType  @default(CNAME)
  content   String      @default("") @db.VarChar(255)
  proxied   Boolean     @default(true)
  // Authing 的用户 id
  user      String      @default("") @db.VarChar(32)
  status    Status      @default(ACTIVE)
  createdAt DateTime    @default(now())
  updatedAt DateTime    @updatedAt
  @@index([no])
  @@index([name, punycode])
  @@index([user, status, createdAt])
}
// 邮箱表
model Emails {
  // 由于 Cloudflare 邮箱还没有提供开放接口,所以需要人工审核和操作,这里会填入默认的 cuid 作为主键 id
  id        String      @id @default(cuid()) @db.VarChar(32)
  // 自增 id,没有什么实际意义,只是为了减少查询(毕竟有调用配额限制),实际项目中不推荐自增主键及自增 id 使用
  no        Int         @default(autoincrement()) @db.UnsignedInt
  name      String      @db.VarChar(255)
  punycode  String      @db.VarChar(255)
  content   String      @default("") @db.VarChar(255)
  user      String      @default("") @db.VarChar(32)
  status    Status      @default(PENDING)
  createdAt DateTime    @default(now())
  updatedAt DateTime    @updatedAt
  @@index([no])
  @@index([name, punycode])
  @@index([user, status, createdAt])
}

数据库设计操作非常简单,具体请参考注释说明。另外,最初的设计是只保存一个名称,但由于会发生重复注册的问题,比如用户 A 注册了一个中文名老王,用户 B 又注册了一个对应的 punycode 代码名 xn--qbyt9x,就会产生冲突,所以这里索性都保存一下。

助力表白,技术的罗曼蒂克

先把 Next.js 网站框架搭建起来,部署到 Vercel 上进行测试。可以再加上 Tailwind CSS 和 Authing SSO 集成。第一步准备工作就算完成了。

接口设计

为了快速实现接口设计,我们创建了增删改查四个接口。

查询接口:

创建接口:


在数据库中只需查询一次即可知晓用户是否已经注册域名或存在相同域名,这里为了提高查询性能对接口进行了拆分。

修改接口:

Alt
删除接口与修改接口相同,邮箱接口与域名类似,此处就不再赘述了。

代码实现

封装 Cloudflare SDK

实现这一功能的代码并不复杂,您可以亲自编写,当然,也可以使用现成的库。

import { Domains } from '@prisma/client';
import { CfAPIToken, CfZoneId } from '../config';

const BASE_URL = 'https://api.cloudflare.com/client/v4';

export type CFResult = {
  success: boolean;
  result: {
    id: string;
  };
};

const headers = {
  Authorization: `Bearer ${CfAPIToken}`,
  'Content-Type': 'application/json'
};

export const createDomain = async (
  form: Pick<Domains, 'name' | 'content' | 'type' | 'proxied'>
): Promise<string> => {
  const res = await fetch(`${BASE_URL}/zones/${CfZoneId}/dns_records`, {
    method: 'POST',
    headers,
    body: JSON.stringify({ ...form, ttl: 1 })
  });
  const data = (await res.json()) as CFResult;
  if (data.success) {
    return data.result.id;
  }
  return '';
};

export const updateDomain = async (
  id: string,
  form: Pick<Domains, 'name' | 'content' | 'type' | 'proxied'>
): Promise<boolean> => {
  const res = await fetch(`${BASE_URL}/zones/${CfZoneId}/dns_records/${id}`, {
    method: 'PATCH',
    headers,
    body: JSON.stringify({ ...form, ttl: 1 })
  });
  const data = (await res.json()) as CFResult;
  console.error(data);
  return data.success;
};

export const deleteDomain = async (id: string): Promise<boolean> => {
  const res = await fetch(`${BASE_URL}/zones/${CfZoneId}/dns_records/${id}`, {
    method: 'DELETE',
    headers
  });
  const data = (await res.json()) as CFResult;
  return !!data.result.id;
}; 

封装校验工具类

需要有一定的正则基础,如果您需要在线调试工具,可以访问:regexper.js.cool

域名(CNAME)校验正则:

/^((?!-))(xn--)?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})$/;

邮箱校验正则:

/^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;

IPv4 校验正则:

/^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;

页面请求封装

以域名注册提交为例:

async function submit(e: SyntheticEvent) {
  e.preventDefault();
  // 因为我 Vue、 React 都会用,且用的都比较少
  // 所以获取表单数据,我用的是 Vanilla JS 方式,通用性更高
  // 如果你不熟悉,可以用 React 的方式
  const target = e.currentTarget as typeof e.currentTarget & {
    type: { value: DomainType };
    content: { value: string };
    proxied: { checked: boolean };
  };
  const type = target.type.value;
  const content = target.content.value;
  if (!validateContent(type, content)) {
    return;
  }
  const form = {
    type,
    content,
    proxied: target.proxied.checked,
    name,
    punycode: toASCII(name)
  };
  // 我建议对 Fetch 进行封装,为了追求效率(偷懒),我就没有做
  const res = await fetch(`/api/domain/create`, {
    method: 'POST',
    body: JSON.stringify(form),
    headers: {
      'content-type': 'application/json'
    }
  });
  // 所以像这样的处理,就非常不优雅,而且还可以统一封装,将错误提示使用通知条组件之类的
  const result = (await res.json()) as { success: boolean; id: string };
  if (result.success) {
    router.reload();
  } else {
    alert('出错啦!请稍后重试');
  }
}  

可复用的代码可以进行封装。参考软件工程的核心思想是高内聚、低耦合,但这里的举例是一个较为反面的教材,代码臃肿、可读性低。

注意点

  • Tailwind CSS 3 采用了全新的 JIT 机制,不再需要 purgecss。
  • 关注 React 性能,如 useState 之类的 Hooks,请尽量放在页面级别,不要放在组件级别。(尤其是会循环生成的组件)
  • 使用 useMemodebounce 之类的方式进行缓存、防抖、限流,以提升应用性能。
  • 使用 Next.js 框架(或普通 React 应用)时,大部分情况下,多了解 swr 及其内部的核心思想会很有裨益。

03

是生活单调的“码农”,也是浪漫的诗人。

李银河曾说,爱情是一种两个人情投意合、心心相印的感觉,是一种两个人合二为一的冲动。

如果技术是星辰大海,代码就是这个时代最浪漫的诗。诗人用诗歌传情,画家用绘画表意,而千万行代码和跨越山海的链接在键盘上留下的余温,是程序员内心最深处独有的浪漫主义。

每年的二月都因为情人节的存在充满着甜蜜的气息,希望每个人都能找到属于自己的那个 Ta,也希望 Authing 能够成为开发者们心中不可替代的那个存在。

其实无论什么职业,都有独属于自己的浪漫。在情人节这个特别的日子里,让我们用最特别的方式勇敢说爱吧!


最后,Authing 祝您收获浪漫与甜蜜,情人节快乐

04

Authing 最新动态

Authing 身份云是以开发者为中心,基于云原生架构的 IDaaS 产品,是一个高安全、高可用、高生产力的身份基础设施,支持所有企业和开发者便捷灵活接入,满足各类场景化需求。

Authing 现已针对不同场景用户推出了 B2C、B2B、B2E 三种场景化的细分版本,更好地为不同类型的用户提供更加专业的服务。同时也对「费用管理」模块进行了全面升级,提供更加灵活、便捷的用量管理和费用管理体验。

您可以即刻前往 Authing 官网,获取「费用管理」模块详细信息,了解 Authing 产品的价格详情。

Logo

Authing 是一款以开发者为中心的全场景身份云产品,集成了所有主流身份认证协议,为企业和开发者提供完善安全的用户认证和访问管理服务

更多推荐