Home
avatar

.Wang

我的服务端项目

oi, 我的服务端项目又又又更新了。从最初的nestjsexpress,再到koa,我经历了很多。各有千秋,不过现在我使用了elysia

之前喜欢折腾,不过现在对我来讲,没有那么多时间精力再去折腾其他技术了。所以我现在的项目主要是用elysia来写的。

之前nestjs对个人项目来讲太重了,装饰器,管道,拦截器等,我认为这些东西用于企业项目是非常合适的,但是对于一些个人项目或者测试项目来讲,没必要非搞的这么正规。

express也是我最早使用的一款,它是通过中间件的形式去处理请求的,例如请求拦截需要返回统一的数据格式等,获取登录拦截等。

而现在的elysia它使用的是bun构建的,同时内置了一些 插件, 例如:定时任务,swagger 文档,cors 等。还可以自定义装饰器,只需定义一次,即可在多个路由中使用。这样重复性比较高,也比较方便。

elysia的性能也比较好,因为它是基于bun构建的,bun是一个基于rustjs运行时,性能比较好。它的打包形式有俩种,

  • 打包成 js 文件,最后执行 js
  • 打包成二进制文件,执行二进制文件

先看下我的项目目录

├── src
│   ├── config   // 配置文件
│   │   ├── cli   // 命令行配置
│   │   ├── swagger   // swagger 文档配置
│   ├── enum   // 枚举
│   │   ├── cli   // 命令行枚举
│   │   ├── http   // http 枚举
│   ├── exceptions   // 异常
│   │   ├── response.ts   // 响应体
│   │   ├── error.ts   // 异常
│   ├── modules   // 模块
│   │   ├── sim-admin   // 管理员模块
│   │   │   ├── index.ts   // 控制器
│   │   │   ├── schemas.ts  // 数据传输对象
│   │   │   ├── model.ts   // 模型
│   │   │   ├── service.ts   // 服务
│   │   ├── cron   // 定时任务
│   ├── utils   // 工具
│   │   ├── logger   // 日志
│   │   │   ├── index.ts
│   │   ├── os   // 操作系统
│   │   │   ├── index.ts
│   ├── main.ts
├── package.json
├── tsconfig.json

配置

入口文件

可以参考一下我的项目的入口文件:

import { Elysia } from "elysia";
import { cors } from "@elysiajs/cors";
import { CliEnum } from "@/enum/cli";
import { getLocalIpAddresses } from "@/utils/os";
import { logger } from "@/utils/logger";
import { swagger } from "@elysiajs/swagger";
import { swagger_config } from "@/config/swagger";
import { ResponseUtil } from "./exceptions/response";
import { HttpEnum } from "./enum/http";
import { admin } from "@/modules/sim-admin";
import { cron } from "@/modules/cron";
import { serverTiming } from "@elysiajs/server-timing";

// TODO: 测试
new Elysia()
	// 全局的错误拦截, 统一返回错误格式
	.onError(({ error, code }) => {
		return ResponseUtil.error(
			error.toString(),
			typeof code === "number" ? code : HttpEnum.INTERNAL_SERVER_ERROR
		);
	})
	// 请求日志,可以写入文件,方便服务器运维查看请求日志
	.onAfterHandle(({ request: { url, method }, response }) => {
		logger.info(`[${url}] ${method}  ${JSON.stringify(response)} \n\n`);
	})
	// 跨域
	.use(cors())
	.use(serverTiming())
	// swagger 文档
	.use(swagger(swagger_config))
	// 管理员模块,路由器
	.use(admin)
	// 定时任务
	.use(cron)
	// 监听
	.listen(CliEnum.APP_PORT, () => {
		const [local_ip_address] = getLocalIpAddresses();
		logger.info(
			`\nApp running at:\n\t- Local: http://localhost:${CliEnum.APP_PORT}\n\t- Network: http://${local_ip_address}:${CliEnum.APP_PORT}`
		);
	});

装饰器,统一返回数据格式

import { Elysia } from "elysia";
import { SimAdminService } from "./service";
import { SimAdminModel } from "./model";
import { ResponseUtil } from "@/exceptions/response";
import { bearer } from "@elysiajs/bearer";

export const admin = new Elysia({
	prefix: "/sim-admin",
	detail: {
		tags: ["sim-admin中后台管理系统"],
	},
})
	// 登录校验
	.use(bearer())
	// 装饰器,统一格式数据返回
	.decorate("success", ResponseUtil.success)
	.post(
		"/login",
		async ({ body, success }) => {
			return success(await SimAdminService.login(body));
		},
		{
			// 内置的登录参数校验
			body: SimAdminModel.loginBody,
			// swagger 文档
			detail: {
				summary: "用户登录",
				description:
					"用户名分别为:[admin,user,test],密码分别为:[admin,user,test]。不同的用户展示的权限也是不同的。",
			},
		}
	);

登录参数校验

import { t } from "elysia";

export namespace SimAdminModel {
	export const loginBody = t.Object({
		username: t.String({
			minLength: 1,
			error: "用户名不能为空",
			default: "admin",
			description: "用户名",
			examples: ["admin", "user", "test"],
		}),
		password: t.String({
			minLength: 1,
			error: "密码不能为空",
			default: "admin",
			description: "密码",
			examples: ["admin", "user", "test"],
		}),
	});

	export type loginBody = typeof loginBody.static;

	export const getTotalData = t.Object({
		page: t.Number({
			default: 1,
			description: "当前页码",
			error: "当前页码不能为空",
			examples: [1, 2, 3],
		}),
		pageSize: t.Number({
			default: 10,
			description: "每页数量",
			error: "每页数量不能为空",
			examples: [10, 20, 30],
		}),
	});

	export type getTotalData = typeof getTotalData.static;
}

docker 部署

我这里没有打包二进制,如果需要参考

FROM docker.1ms.run/oven/bun AS build

WORKDIR /opt/apps/node-service

# Cache packages installation
COPY package.json package.json
COPY bun.lock bun.lock

RUN bun install

COPY ./src ./src
COPY tsconfig.json tsconfig.json

ENV NODE_ENV=production

CMD ["bun", "src/main.ts"]

EXPOSE 9999
项目总结