从头开始创建一个自动产生文档/类型安全的现代API(1) 创建项目/Eslint设置 - Bearalise

背景

你是否曾经遇到过这样的 API 文档,其中每一个端点(endpoint)都被详细记录,你可以深入了解所有可能的响应类型,并查看它们的示例。同时,它们还包含模式(schemas),你甚至可以进入并测试这些特定的端点,实际运行并访问 API,设置请求头和其他细节。从这个文档开始,我将从头开始构建一个使用 Hono 的 API,并使用 OpenAPI 规范来全面记录我们的 API。因此,如果你访问 /doc,你会看到每一个端点都被记录在案。OpenAPI 实际上是一个工具生态系统,可以利用这些文档创建互动文档,或者生成客户端库及其他类似的东西。我一直在使用 Hono 构建应用程序,特别是我一直在使用他们的中间件 Zod OpenAPI,该中间件基本上允许你使用 Zod 来定义所有的模式(schemas),然后定义你的路由合约,并使用这些路由合约在定义请求处理程序时获得类型安全。所有这些最终都被转化为完全互动的文档。

技术实现简介

首先,我会使用NextJS和Hono搭建后台API。NextJS不用多介绍,它是一个用于构建全栈Web 应用的React 框架。Hono是一个小巧、简单且超快速的 Web 框架,构建在 Web 标准之上,号称可以在任何 JavaScript 运行时环境中运行,包括 Cloudflare Workers、Fastly Compute、Deno、Bun、Vercel、Netlify、AWS Lambda、Lambda@Edge 和 Node.js。

  • 在设置 OpenAPI 时,我们也可以以一种可扩展的方式进行。例如,我们可以在一个地方设置所有的路由定义,然后在另一个地方设置所有的处理程序,但一切依然是类型安全的。我们实际上会导出这些路由定义的类型,并且可以使用它们来定义我们的请求处理程序。这样,我们可以在一个单独的文件中定义我们的处理程序,但仍然具有完整的类型安全性,这意味着我们准确知道对于这个特定的方法基于这个特定的路由我们应该响应什么。

  • 除此之外,Hono 使得测试这些端点变得非常容易,因为他们有一个内置的测试客户端,本质上类似于 Hono RPC 客户端,可以在客户端代码中使用,并具有完全的类型安全性,但它也是一个非常好的测试体验。

  • 此外,我们可以将其与 Drizzle 连接起来,Drizzle Zod 基本上允许我们定义我们的表,然后使用这些表定义来定义我们的 Zod 模式(schemas),然后我们可以直接在我们的 OpenAPI 定义中使用这些模式。因此,我们基本上有一个单一的事实来源,我们可以定义我们的表,得到我们的模式,然后在我们的路由定义中使用这些模式,从那时起,当我们定义处理程序时,所有类型都会流转。

其次,我使用了一些现成的库 stoker 和样板库 hono-open-api-starter 来减少代码量,当然你也可以直接自己实现。

More...

OpenAPI 规范(OAS)介绍 - Bearalise

背景

作为一名开发者,都需要编写程序的 API 文档。现代开发框架,都需要API的协作,有时完成一个操作,需要调用多个API才能完成,所以,一个好的 API 文档能够大大提高协作效率,降低沟通成本。为解决API文本规范的问题,我们聊聊如何使用 OpenAPI 构建 HTTP 接口文档。

OpenAPI 规范(OAS)介绍

OpenAPI 是规范化描述 API 领域应用最广泛的行业标准,由 OpenAPI Initiative(OAI) 定义并维护,同时也是 Linux 基金会下的一个开源项目。通常我们所说的 OpenAPI 全称应该是 OpenAPI Specification(OpenAPI 规范,简称 OSA),它使用规定的格式来描述 HTTP RESTful API 的定义,以此来规范 RESTful 服务开发过程。使用 JSON 或 YAML 来描述一个标准的、与编程语言无关的 HTTP API 接口。OpenAPI 规范最初基于 SmartBear Software 在 2015 年捐赠的 Swagger 规范演变而来,目前最新的版本是 v3.1.1。本文参考了apifox 发布的一个 中文版

OpenAPI 规范基本信息

文档结构

一份 OpenAPI 文档可以是单个文件也可以被拆分为多个文件, 连接的部分由用户自行决定。在后一种情形下,必须如 JSON Schema 中定义的那样使用 $ref 字段来相互引用。

推荐将根 OpenAPI 文档命名为openapi.json 或 openapi.yaml

More...

如何利用Cloudflare搭建图床(+ Piclist快速上传)- Bearalise

背景

写博客就要有图床,之前搭了一个国内版的,今天尝试用Cloudflare搭建一个海外版的。

开通Cloudflare R2

R2 是 Cloudflare 推出的免费对象存储服务,需要免费注册一个Cloudflare 账号才能使用,注册登录后,点击左侧边栏的 R2 访问服务,但需要注意的是开通 R2 服务需要绑定信用卡(国内外主流信用卡皆可),但并不会扣费,主要是为了验证用户身份使用:

创建图床 Bucket

Bucket即为存储桶,所有的资源的是需要放在存储桶中。开通 R2 服务后,点击右上角「创建存储桶」按钮进行创建:

起一个名字,其他选项如图:

开通后显示以下信息:

More...

如何用pgloader同步数据库 - Bearalise

背景

之前开发时就发现,mysql和pg数据库 没有合适的数据库同步工具,只能用dump将数据库dump下来,再重新upload到新的库

解决方案

最近发现一个比较好的工具:Pgloader ,下面是具体步骤:

  1. 安装: sudo apt-get install pgloader

  2. 编辑同步配置 pg.load

    1
    2
    3
    load database
    from pgsql://localhost/pgloader
    into pgsql://localhost/copy;
  3. 运行

    1
    pgloader pg.load

运行成功,目前看起来还是十分简单好用的。

More...

利用AlertDialog实现二次确认按钮 - Bearalise

背景

在用户点击某个按钮后弹出 AlertDialog,如果用户确认,则执行原本的点击动作;如果用户取消,则不执行该动作。这是一个很常见的模式,用于防止用户误操作。这里给出用AlertDialog组件的一个实现。

关键点

  1. 我们定义了一个 performDangerousAction 函数,它代表您想要执行的实际操作。在这个例子中,它只是设置了一个结果状态,但在实际应用中,这里可能是一个 API 调用或其他重要操作。
  2. 当用户点击 “执行危险操作” 按钮时,我们不直接执行操作,而是打开 AlertDialog。
  3. 在 AlertDialog 中:
    1. 如果用户点击 “确认”,我们调用 handleConfirm 函数,它会执行危险操作并关闭对话框。
    2. 如果用户点击 “取消”,我们调用 handleCancel 函数,它会设置一个取消消息并关闭对话框。
  4. 我们使用 actionResult 状态来显示操作的结果,无论是执行了还是被取消。
  5. AlertDialog 的可见性由 isOpen 状态控制,这允许我们在需要时打开和关闭它。

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
'use client'

import { useState } from "react"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"

export default function AlertDialogWithAction() {
const [isOpen, setIsOpen] = useState(false)
const [actionResult, setActionResult] = useState<string | null>(null)

// 模拟一个危险操作
const performDangerousAction = () => {
// 这里是实际的危险操作逻辑
setActionResult("危险操作已执行!")
}

const handleConfirm = () => {
performDangerousAction()
setIsOpen(false)
}

const handleCancel = () => {
setActionResult("操作已取消")
setIsOpen(false)
}

return (
<div className="space-y-4">
<AlertDialog open={isOpen} onOpenChange={setIsOpen}>
<AlertDialogTrigger asChild>
<Button
variant="destructive"
onClick={() => setIsOpen(true)}
>
执行危险操作
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>您确定要执行此操作吗?</AlertDialogTitle>
<AlertDialogDescription>
此操作可能会导致不可逆的结果。请确认您真的要执行此操作。
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={handleCancel}>取消</AlertDialogCancel>
<AlertDialogAction onClick={handleConfirm}>确认</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{actionResult && (
<div className="mt-4 p-4 bg-muted rounded-md">
<p>操作结果: {actionResult}</p>
</div>
)}
</div>
)
}

作者:Bearalise
出处:https://blog.952005.xyz/2024/11/22/how-to-use-component-to-double-confirm/
版权:本文版权归作者所有
转载:欢迎转载,但未经作者同意,必须保留此段声明,必须在文章中给出原文链接。

如何修改HTML默认上传按钮组件样式 - Bearalis

背景

系统自带的上传按钮有点丑,并且大的样式无法修改,想了个方法用Tailwind的按钮包装了系统自带的按钮,实现了样式的统一

解决方案

建立Uploadbutton 组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

import React, { ChangeEvent,useRef } from 'react';

import { Button } from '@/components/ui/button';
import { TreeDataItem } from '@/lib/utils';

interface Question {
...
}

interface Option {
...
}

type Props = {
disabled: boolean;
onUpload: ( result: any ) => void;
};

const UploadButton = ( { disabled, onUpload }: Props ) => {
const fileInputRef = useRef<HTMLInputElement | null>(null);

const handleButtonClick = () => {
if (fileInputRef.current) {
fileInputRef.current.click();
}
};

const parseQuestions = (content: string): { questions: Question[] } => {
...
};

const generateTreeData = ( questions: Question[]) : { data: TreeDataItem[] } => {
...
};

const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => {

const file = e.target.files?.[0];
if (!file) return;

try {
const content = await file.text();

const { questions } = parseQuestions(content);
const { data } = generateTreeData(questions);

const results = {
data: data,
jsondata: questions
};

onUpload( results );
} catch (error) {
console.error('Error processing file:', error);
}
}

return (
<div className="w-full lg:w-auto">
<input
type="file"
ref={fileInputRef}
accept=".txt"
onChange={handleFileChange}
className="hidden"
/>
<Button
disabled={disabled}
onClick={handleButtonClick}
size="sm"
className="w-full lg:w-auto"
>
Import TXT
</Button>
</div>
);
};

export default UploadButton;

分解来说,

  1. 定义组件,并定义onUpload 函数作为组件参数
  2. 定义 fileInputRef ,绑定给原始上传控件
  3. 再定义 handleButtonClick 函数,函数里调用上传控件的Click方法
  4. 定义Button控件,将点击事件绑定给handleButtonClick
  5. 定义handleFileChange函数,绑定原始上传控件的onChange事件
  6. 在handleFileChange函数,用 await file.text() 得到文件内容,并处理加工,把结果通过onUpload 函数,传输给外部调用者

作者:Bearalise
出处:如何修改HTML默认上传按钮组件样式 - Bearalis
版权:本文版权归作者所有
转载:欢迎转载,但未经作者同意,必须保留此段声明,必须在文章中给出原文链接。

PostgreSQL Windows Docker Setup

背景

项目需要使用PG, 尝试安装一下

Create Volume

1
docker volume create --driver local --opt device=C:\10.VM\data\pg01 --opt type=none --opt o=bind pg01

Pull Image

1
docker pull postgres

Start Container

1
2
docker run --name pgsql --privileged -e POSTGRES_PASSWORD=xxxxx -p 15333:5432 -v pg01:/var/lib/postgresql/data -d postgres

连接数据库,创建用户

1
2
create user fintest01 with password 'xxxxxx';
create database fintest01 with owner fintest01;

切换数据库连接

1
DATABASE_URL=postgresql://fintest01:fin1234@192.xx.xx.xx:15333/fintest01

Mac OS 下修改 Google Chrome 显示语言的方法

背景

由于调试程序需要,希望把Chrome的语言改成英文,发现在Mac下Chrome的语言是跟随系统语言的,通过菜单无法修改。

解决方案

英文 -> 简体中文

defaults write com.google.Chrome AppleLanguages '(zh-CN)'

简体中文 -> 英文

defaults write com.google.Chrome AppleLanguages '(en-US)'

英文优先,简体中文第二。反之改一下顺序

defaults write com.google.Chrome AppleLanguages "(en-US,zh-CN)"

Build a Finance SaaS Platform -26 (Account/Date Filter)(End)

Install Package

1
npm i query-string

Add Filter

Add components/filters.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react'
import { AccountFilter } from '@/components/account-filter'
import { DateFilter } from '@/components/date-filter'

export const Filters = () => {
return (
<div className="flex flex-col lg:flex-row items-center gap-y-2 lg:gap-y-0 lg:gap-x-2">
<AccountFilter />
<DateFilter />
</div>
)
}

More...

请我喝杯咖啡吧~

支付宝
微信