代码
删除src/model.ts和src/schema.ts。
import_map.json
在import_map.json中增加一条:
"deno_mongo_schema": "https://deno.land/x/deno_mongo_schema@v0.10.5/mod.ts"
我们将使用deno_mongo_schema来重构代码,具体API在详见其文档。
config.yaml
修改config.yaml,增加你的mongodb地址:
globals.ts
src/globals.ts,在Config中增加刚才的配置:
Schema
修改src/user/user.schema.ts,差异不大:
import { BaseSchema, Prop, Schema } from "deno_mongo_schema";
@Schema()
export class User extends BaseSchema {
@Prop({
index: true, // unique需要配置index使用,意思是设为索引
unique: true,
})
name: string;
...
}
export type UserInfo = Required<User>;
src/session/session.schema.ts:
import {
BaseSchema,
Prop,
Schema,
SchemaFactory,
VirtualTypeOptions,
} from "deno_mongo_schema";
@Schema()
export class Session extends BaseSchema {
...
@Prop({
index: true, // 增加索引标志
expires: 60 * 60 * 24 * 7,
default: Date.now,
})
expired?: Date;
}
src/posts/posts.schema.ts和src/comments/comments.schema.ts与之类似,额外需要删除定义的createTime和updateTime,因为已经定义在BaseSchema里了。
Service
与Schema类似,大部分代码都复用原来的,只修改一部分。
修改src/comments/comments.service.ts:
import { InjectModel, Model } from "deno_mongo_schema";
@Injectable()
export class CommentsService {
create(params: CreateCommentDto) {
return this.model.insertOne(params);
}
}
修改src/user/user.service.ts:
import { InjectModel, Model } from "deno_mongo_schema";
@Injectable()
export class UserService {
async getAll() {
return this.userModel.findMany({});
}
async getUsersByIds(ids: string[]) {
return this.userModel.findMany({
_id: { $in: ids },
});
}
async addUser(user: User) {
const id = await this.userModel.insertOne(user);
return id.toString();
}
}
修改src/session/session.service.ts的findAll方法:
import { InjectModel, Model } from "deno_mongo_schema";
@Injectable()
export class SessionService {
findAll(): Promise<Session[]> {
return this.model.findMany();
}
}
修改src/posts/posts.service.ts:
import { InjectModel, Model } from "deno_mongo_schema";
@Injectable()
export class PostsService {
async save(params: CreatePostDto): Promise<string> {
const id = await this.model.insertOne({
...params,
pv: 0, // 去掉createTime和updateTime
});
return id.toString();
}
async findById(id: string, options: PopulateOptions = {}) {
...
if (options.isWithUserInfo) {
post.author = await this.userService.getUserById(post.userId) || null;
}
...
}
async findAll(options: PopulateOptions = {}) {
const posts = await this.model.findMany();
...
}
private async formatPosts(
posts: Required<Post>[],
options: PopulateOptions = {},
) {
if (options.isWithUserInfo) {
...
posts.forEach((post) => {
post.author = users.find((user) => user.id === post.userId) || null;
});
}
...
}
}
session.middleware.ts
修改src/session/session.middleware.ts,给findById增加一个catch。
为什么呢?因为mongodb根据id查找,如果没有找到,会返回null,但如果id非法(不是24位字符串),则会抛出异常。我们之前的session-id格式(nanoid)与mongodb的不一样,所以肯定会报错。
import { assert, Context, Factory, Next } from "oak_nest";
import { logger } from "../tools/log.ts";
import { SESSION_KEY } from "./session.schema.ts";
import { SessionService } from "./session.service.ts";
export async function SessionMiddleware(context: Context, next: Next) {
...
if (sessionId) {
session = await sessionService.findById(sessionId, true).catch((_err) =>
null
);
}
...
}
app.module.ts
src/app.module.ts中需要初始化mongodb数据库:
import { MongoFactory } from "deno_mongo_schema";
import globals from "./globals.ts";
@Module({
imports: [
MongoFactory.forRoot(globals.db),
...
],
...
})
export class AppModule {}
验证
控制台
首先看到vscode控制台的变化,一开始打印了几句mongodb的连接与集合(也就是关系型数据库的表)创建的信息:
mongo初始化
打开上节的adminMongo的页面,先配置我们的数据库:
点击Connect,就能看到我们已经初始化的几个集合:
打开users:
点击这个Indexes,能看到这个集合的索引:
可以看出,我们上面配置了name索引成功了。这样往这个集合里插入数据,如果遇到name重复的情况,就会抛出异常,保障唯一性。
操作页面
在浏览器访问http://localhost:8000,能看出页面空空如也。
先注册几个用户,再看users表的变化:
再用这几个用户创建博客:
posts表中也有了数据:
进行评论:
comments中也有了数据:
sessions表中也有一条数据:
作业
我们使用deno_mongo_schema重构了现在的项目,数据从localStorage中迁移到了MongoDB中。看得出来,我们改动的代码不多,Controller层的代码没做任何改动,Service层也只是做了一些细节的修订,这也是分层的优势所在,上层不需要关注下层细节,将来你想把MongoDB替换成Redis、MySQL还是PostgreSQL都很方便。
上面session也使用了数据库,这样将来集群部署,也能解决session查找的问题。当然,因为session的请求频率非常高,最快肯定是内存,而内存中有不能持久化和集群后同步或分配等一系列复杂问题,目前通用的方法是使用redis数据库。这里我们图省事直接使用MongoDB了,读者在实际项目中需要注意。
deno_mongo_schema提供了集合间关联的快捷处理,详情参见deno.land/x/deno_mong…文档的virtual部分。下节我们将使用它优化我们的代码,你可以先尝试。