Today is February 2, 2022, and this year's Valentine's Day is less than two weeks away.
I would like to introduce a website to you, Hanhan. I love you
Reference sample website:
application process
Open the home page of the official website: Hanhan. I love you
(As the website database uses PlanetScale free service and is located in the US East, access may be slightly slow. Please be patient.)
Use the Authing account to log in (you can register with your mobile phone or email address, log in with your Github account, or scan the code in the WeChat applet, and more login methods will be added in the future).
You can click to enter domain name application and email application respectively.
Domain Name Application
The domain name application interface is as follows:
There are three supported binding methods:
CNAME: Platforms that provide hosting services can be used with Github Pages, Netlify, Gitee, Coding.net, Cloudflare, etc.
- Value reference:
willin.github.io
- Note: Vercel is not supported, because Vercel does not support binding second-level domain names by default (unless the ownership is under your personal name)
- Value reference:
A: IPv4 needs to build its own server and bind it
- Value reference:
1.2.3.4
(your server's ip address)
- Value reference:
AAAA: IPv6 needs to build its own server and bind it
- I don't make a statement, and it is not recommended for non-professionals to choose
- If you need to bind IPv4 and IPv6 at the same time, it is recommended to register type A, and then ISSUE or email to contact me for cooperation
There is also a Proxied (CDN), if you don't know the function, you can try to turn it on or off to test.
Email application
The domain name application interface is as follows:
At present, Cloudflare's mail forwarding service is used, but because IDN domain names are not supported for the time being, it is possible to preemptively register and have it as soon as possible.
other instructions
if you need help
Welcome to follow me on Github: willin , if you have any problems preparing gifts for your beloved, we can provide you with free technical advice.
want another domain name
- js.cool (After many negotiations, Vercel binding is now supported)
- log.lu (stay tuned)
feel generous
- You can share this site with more people
You can also tip through the following channels:
eager to try
Maybe you also have a lot of ideas that you want to realize. you can:
- Use Authing quickly integrate and develop your own applications
Fork the source code of this project (completely open source), and provide your own domain name service
Improve and optimize this project on Github
open source
Next, an important link begins. As the saying goes, it is better to teach a man to fish than to give him a fish. I will Hanhan. I love your source code , and explain in detail the whole process of design and implementation.
design
This project took me about 3 hours to complete. In order to avoid weaknesses, I used a UI framework, so there is no additional UI design, and a few basic components are used to quickly start the project.
Technical selection
First, the first step is technology selection. Because what I want to provide is a free service, I try to choose some free service providers and some related technology stacks.
Choice of service provider:
- Cloudflare : Provides free domain name resolution, CDN acceleration and open interfaces
- Vercel : Free application hosting for individuals, support Node.js environment, use Next.js framework
- PlanetScale : Cloud MySQL service with a certain free quota
- Prisma : Cloud Studio management database
In fact, I originally wanted to use the Cloudflare family bucket, which is to use Cloudflare Pages (static website) + Cloudflare Workers (Serverless method execution) and KV (keyless method execution) Therefore, a simpler and faster implementation method is adopted.
Technology stack:
- Typescript : While I like to do more with less code, TS brings me a more efficient stage for teamwork
Next.js : A full stack framework (using React on the front end, and a http module and Express on the back end) that supports SSR (Server Side Rendering) and SSG (Static Site Generation)
- @authing/nextjs : Authing SSO integration SDK
- Prisma : The next-generation ORM framework that supports multiple databases (MySQL is used in this project) and database migration (Migration)
Tailwind CSS : The next generation CSS framework, practical first
- Daisy UI : encapsulates some UI style components
Database Design
Since I am using the Authing user integration, the design of the user table and the design of the user-related interface are omitted.
// 域名类型
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])
}
Very simple, refer to the notes. In addition, I originally planned to save only one name, but due to repeated registration, for example, I registered a Chinese name Lao Wang, and you registered a corresponding punycode code name
xn--qbyt9x
, it will conflict, so simply (lazy ) save it.
Technical preparations
- Punycode knowledge required for Chinese domain names: RFC 3492 specification
Cloudflare API
- Create a resolution: Create DNS Record
- Modify a resolution: Patch DNS Record
- Delete a resolution: Delete DNS Record
- Authing SSO integration, you can refer to my previous article: "Full Stack Framework Application Quickly Integrate Authing SSO"
First build the Next.js website framework and deploy it to Vercel for testing. This can be coupled with Tailwind CSS and Authing SSO integration. The first preparatory work is complete.
interface design
For fast (lazy) implementation, I created four interfaces for adding, deleting, modifying, and checking.
Query interface:
Create an interface:
The database query whether the user has registered a domain name and whether there is a same name can be completed with one query. Here, in order to improve the query performance, it is split.
Modify the interface:
Deleting an interface is the same as modifying an interface. The mailbox interface is similar to the domain name and will not be repeated here.
Code
Packaging the Cloudflare SDK
Of course, there are also ready-made libraries that can be used directly, but because there are only a few lines of code, I made them by hand.
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;
};
Package verification tool class
A certain regular basis is required. If you need online debugging tools, you can visit: regexper.js.cool
Domain name (CNAME) verification regular:
/^((?!-))(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,})$/;
Email verification regular:
/^[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 checksum:
/^((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]?)$/;
IPv6 checksum:
/^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:)))(%.+)?$/;
page request wrapper
Take domain name registration submission as an example:
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('出错啦!请稍后重试');
}
}
Reusable code can be encapsulated. Refer to the idea of software engineering: high cohesion, low coupling. What I am citing here is a relatively negative teaching material, with bloated code and low readability.
be careful
- Thanks to the new JIT mechanism in Tailwind CSS 3, purgecss is no longer required
useState
attention to React performance. Hooks such as 061f9db4d131e2 should be placed at the page level as much as possible, not at the component level (especially the components that will be generated cyclically)- Use
useMemo
such as 061f9db4d13202 anddebounce
for caching, anti-shake, and current limiting to improve application performance - When using the Next.js framework (or a normal React application), in most cases,
swr
and some of the core ideas inside it
The rest of the code is boring and simple.
That's about it. Remember to share!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。