【Deprecated】弃用
Deno还是太不稳定了,已经用go重构了,请移步这里
项目地址
项目源码在这github,欢迎clone下来配合食用
Deno 初体验
先说说Deno用起来的感受。
首先它支持url导入了,这意味着不用被node_modules恶心了,但是如果你要重装依赖,好像也没区别=- =。
然后原生支持await\async
、typescript
;当然他最近又要把内部核心包转回js
写了,不过这主要是为了性能,和我们关系不大。
最后,更新还是蛮频繁的,玩玩就行了,不能拿去做生产。
项目地图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
$ tree -I ".deno_plugins|.vscode"
.
|-- main.ts # main文件
`-- src
|-- controller # controller
| |-- record.ts
| |-- tag.ts
| `-- user.ts
|-- db # mongo 实例
| |-- createIndexes.js
| `-- init.ts
|-- models # models
| |-- record.ts
| |-- tag.ts
| `-- user.ts
`-- tools # 工具集
|-- checks.ts # 入参验证
|-- crypto.ts # 加解密
|-- jwt.ts # jwt
|-- resultor.ts # 约定的response
`-- strconv.ts # string 格式化
|
整个项目很精简,用的是MVC模式。
正文
看了整个目录,发现没什么可写的。姑且从创建http服务、建立mongo连接池到处理请求、返回响应来叙述吧。
先看main.ts
main.ts
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
|
// 解析命令行的入参
const { i: isInit, m: dbUrl, db: dbname = "time-mgt" } = parse(Deno.args);
// 创建mongo客户端
db.Connect(dbUrl, dbname, FormatBool(isInit));
const controller = new AbortController();
// 路由处理
const router = new Router();
router
//...ommit user controller
//tag controller
// 这里是请求的url, jwt中间件和controller
.post("/v1/tag/create", JWT, AddTag)
.put("/v1/tag/update", JWT, UpdateTag)
.get("/v1/tag/list", JWT, FindTag)
.delete("/v1/tag/:id", JWT, DeleteTag)
//...omit record controller
// 终止信号
const { signal } = controller;
// http 服务
const app = new Application();
// 错误处理中间件
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
console.error(err);
return RetFail(ctx, err.message);
}
});
// 跨域处理
app.use(oakCors({ allowedHeaders: ["Content-Type", "Authorization"] }));
app.use(router.routes());
app.use(router.allowedMethods());
await app.listen({ port: 8000, signal });
// http服务关闭之后 关闭mongo连接
db.Close()
console.log("http close");
|
mongo初始化
src/db/init.ts
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
|
class DbEngine {
async Connect(url: string, dbName: string, init: boolean = false) {
this.url = url;
this.name = dbName;
this.client = new MongoClient(); //初始化客户端
this.client.connectWithUri(url); //建立连接
this.db = this.client.database(dbName);
// equal node __dirname
const __dirname = path.dirname(path.fromFileUrl(import.meta.url)); //获取当前项目路径
if (init) { //初始化索引
const cmd = Deno.run({ //deno的mongo驱动还无法创建索引和事务,这里是用命令行实现的
cmd: [
"mongo",
`${url}/${dbName}`,
path.resolve(__dirname, "createIndexes.js"),
],
stdout: "piped",
stderr: "piped",
});
cmd.close(); //执行后记得close()
}
}
GetColl<T>(table: string) {
return this.db.collection<T>(table);
}
Close() {
return this.client.close();
}
}
export const db = new DbEngine();
|
request处理
这里用登录来做例子,顺便带出jwt和resutor
src/controller/user.ts
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
|
export async function Login(ctx: Context) {
if (!ctx.request.hasBody) {
return RetFail(ctx, "not has body");
}
// 解body
const result = ctx.request.body(); // content type automatically detected
if (result.type === "json") {
const value = await result.value; // an object of parsed JSON
//校验必要参数
const missingKey = CheckRequired(value, ["email", "pwd"]);
if (missingKey.length) {
return RetMissing(ctx, missingKey, {
"email": "邮箱不能为空",
"pwd": "密码不能为空",
});
}
const { pwd } = value;
// 加密密码
const encryptedPwd = await Encrypt(pwd);
// 查库
const tUser = db.GetColl<UserSchema>(T_User);
try {
const hasUser = await tUser.findOne(
{ ...value, pwd: encryptedPwd },
);
if (hasUser) {
const { _id: { $oid } } = hasUser;
// 颁发 jwt
const jwt = await GenJwt($oid);
// 成功响应
return RetOK(ctx, jwt);
} else {
// 失败响应
return RetFail(ctx, "没有此用户");
}
} catch (e) {
return RetFail(ctx, e.message);
}
} else {
return RetFail(ctx, "body must a json");
}
}
|
jwt身份认证
src/tools/jwt.ts
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
|
// 从运行时命令行获取加密用的key
const { k: key } = parse(Deno.args);
// 验证
export async function JWT(ctx: Context, next: () => Promise<void>) {
// 从header拿到jwt
const jwt = ctx.request.headers.get("Authorization") || "";
// 验证
const vJwt = await validateJwt(
{ jwt, key, algorithm: "HS256" },
);
// 验证成功
if (vJwt.isValid) {
// 把解出来的数据提取出来放到header里
const { aud } = vJwt.payload as PayloadObject;
ctx.request.headers.set("uid", aud as string);
// 走后面的controller
await next();
} else {
// 验证失败 返回401
return RetFail(ctx, "身份认证失败", 401);
}
}
// GenJwt 生成一个jwt
export async function GenJwt(aud: string) {
// jwt 是由header.body.sign 组成的
// payload主要是body部分
const payload: Payload = {
iss: "fuRan",
aud,
exp: setExpiration(60 * 60 * 24 * 15),
};
// 这里是head部分 主要描述jwt的信息,如加密方式alg,类型typ等
const header: Jose = {
alg: "HS256",
typ: "JWT",
};
// 生成jwt
return await makeJwt({ header, payload, key });
}
|
resutor前后端一致的响应体
这个看上去很简单,但是非常重要,在响应体上保持一致,可以省去很多因为格式问题出的错。
src/tools/resutor.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// 成功返回,返回Ok和成功数据
export function RetOK(ctx: Context, data: Object) {
ctx.response.body = {
ok: true,
data,
};
}
// 失败返回,返回false和错误信息
export function RetFail(ctx: Context, errMsg: string, status: number = 200) {
ctx.response.body = {
ok: false,
errMsg,
};
ctx.response.status = status;
}
|
其他篇章
前言
前端篇
deno后端篇【废弃】
go后端篇
部署篇
后语