基础
基本结构
ps
中有这么一些角色,
抱歉,我无意设计的如此复杂,但前文所说的几个特性不太能用简单的办法解决, 这一小段可以跳过,毕竟我自己也记不太住
- 模块,也就是类,前文的
UserController/UserService
都是
TIP
ps
中模块(类)分为以下几种,
啊,其他可以跳过,但这个不行
控制器,负责把服务暴露给外部,在
express
中对应着controller
(类名为XXController
),负责暴露http
接口, 在rabbitmq
中对应着rpc
(类名为XXRpc
),负责暴露队列服务模块,主要是给控制器提供服务,对应着
service
(类名为XXService
)基础模块,主要是提供一些基础能力给其他模块使用,对应着
module
(类名为XXModule
)AOP
模块, 提供aop
功能(类名为XXPipe/XXGuard/XXFilter/XXAddon/XXExtension
)独立模块,不被其他模块调用的模块,主要用于定时器、事件总线(类名为
XXSolo
)
实例化时,只需要直接引入1/4/5
,2/3
会被间接引入 如
const data = await Factory([XXController, XXGuard, XXSolo])
- 生成器,它会生成一些代码,用于生成请求,即前文的
HttpGenerator
- 适配器,它会和不同的服务端框架、微服务框架结合,也就是前文的
bind
- 编译器,它会将原本的导入路径重定向到生成器生成的代码,在前端是
unplugin
插件,在服务端则是通过运行时 - 请求适配器,它会利用生成器生成的代码,并和
axios
等请求库结合,生成请求调用,即前文的createClient
- 运行时,利用
nodejs
的register
提供热更新,通过命令行启动,即前文的phecda-server <entryFile>
基本流程
服务方:
- 通过命令行,调用运行时,开始执行入口文件
- 将模块通过实例化,使用生成器生成请求相关代码
- 通过适配器,与服务端框架适配
调用方
- 通过编译器,将引入指向元数据
- 通过请求适配器,生成请求调用
实例化模块并生成代码
本质上是将所有的模块,或者说是类,控制反转+依赖注入将其实例化,然后根据Tag
或者类名注册到modulemap
里,并将模块上的元数据收集到meta
数组中, 再根据元数据产生代码
Tag
或者类名作为模块的标识,在Phecda
架构中很重要
import { Factory, HttpGenerator, Tag } from 'phecda-server'
class TestModule {}
@Tag('test2')
class Test2Module {}
const data = await Factory([TestModule, Test2Module], {
generators: [new HttpGenerator()],
})
data.modulemap.get('TestModule') // TestController 实例,此时使用了类名
data.modulemap.get('test2') // Test2Module 实例,此时使用了tag
data.meta // 元数据数组
通过控制器创建接口
import { Body, Controller, Param, Post, Query } from 'phecda'
@Controller('/base')
class TestController {
@Post()
test1(@Body() body: string, @Query('name') name: string) {
// axios.post('/base','body',{params:{name:'name'}}) ---> 'bodyname'
return body + name
}
@Post('/test2')
test2(@Body('a') a: string, @Body('b') b: string) {
// axios.post('/base/test2',{a:'a',b:'b'},) ---> 'ab'
return a + b
}
@Post('/:test')
test3(@Param('test') test: string) {
// axios.post('/base/1') ---> '1'
return test
}
}
DANGER
nestjs
使用者需要注意,这里的参数必须是来自客户端,不能使用自定义装饰器
举个例子,在守卫中,通过请求的请求头鉴权,获得用户信息
@Get()
test3(@User() user:any){
}
那么 这里的用户信息是来自服务端解析,而非用户端上传,这种写法会导致类型复用出问题。
PS
中禁止这么做
上下文
那以上功能该怎么实现呢,简单!只需要把信息挂到上下文
守卫、管道中、过滤器都可以操作上下文
import { Ctx, Get } from 'phecda-server'
import type { ExpressCtx } from 'phecda-server/express'
class TestController {
@Ctx
context: ExpressCtx
@Get()
test3() {
const { user } = this.context // 必须在函数顶部
// ...
}
}
简而言之,函数的参数必须是来自客户端,而服务端的东西则通过上下文获得
为控制器创建服务
import { Controller, Get, Param } from 'phecda-server'
@Injectable()
class TestService {
test() {
return 1
}
}
@Controller('/base')
class TestController {
constructor(protected testService: TestService) {}
@Get()
get() {
return this.testService.test()
}
}
WARNING
nestjs
使用者请注意,只有通过构造函数实现依赖注入这一种方式,没有其他注入
前文中的
const data = await Factory([TestController])
此时不需要添加 TestService
,它会作为 TestController
的依赖被处理
与服务端框架适配
Express
import { bind } from 'phecda-server/express'
// ..
const router = express.Router()
bind(router, data) // work for router
Fastify
import { bind } from 'phecda-server/fastify'
const app = Fastify()
bind(app, data)
koa
import { bind } from 'phecda-server/koa'
// ..
const router = new Router()
bind(router, data)
h3
import { bind } from 'phecda-server/h3'
const router = createRouter()
bind(router, data)
hyper-express
import { bind } from 'phecda-server/hyper-express'
// ..
const router = new HyperExpress.Router()
bind(router, data)
hono
import { bind } from 'phecda-server/hono'
// ..
const router = new Hono()
bind(router, data)
调用方调用
import axios from 'axios'
import { createClient } from 'phecda-client/http'
import {adaptor} from 'phecda-client/axios'
import { UserController } from '../server/user.controller'
// 这里只是用它的类型,并没有真正引入Controller(利用了编译工具)
const instance = axios.create({
baseURL: 'http://localhost:3000',
})
const client = createClient({user: UserController }, adaptor(instance))
const ret = await client.user.login('username', 'password')
这里引入控制器并不是真正引入了,只是借用其类型,真正引入的是生成器的代码
很明显,这是个重定向性质的行为,快速开始里通过vite
插件,
服务端调用
通过命令phecda-server <entry file>
服务端不同程序之间的调用