Skip to content

基础

基本结构

ps 中有这么一些角色,

抱歉,我无意设计的如此复杂,但前文所说的几个特性不太能用简单的办法解决, 这一小段可以跳过,毕竟我自己也记不太住

  1. 模块,也就是类,前文的UserController/UserService都是

TIP

ps中模块(类)分为以下几种,

啊,其他可以跳过,但这个不行

  1. 控制器,负责把服务暴露给外部,在express中对应着controller(类名为XXController),负责暴露http接口, 在rabbitmq中对应着rpc(类名为XXRpc),负责暴露队列

  2. 服务模块,主要是给控制器提供服务,对应着service(类名为XXService

  3. 基础模块,主要是提供一些基础能力给其他模块使用,对应着module(类名为XXModule

  4. AOP模块, 提供aop功能(类名为XXPipe/XXGuard/XXFilter/XXAddon/XXExtension

  5. 独立模块,不被其他模块调用的模块,主要用于定时器、事件总线(类名为XXSolo

实例化时,只需要直接引入1/4/52/3会被间接引入 如

ts
const data = await Factory([XXController, XXGuard, XXSolo])
  1. 生成器,它会生成一些代码,用于生成请求,即前文的HttpGenerator
  2. 适配器,它会和不同的服务端框架、微服务框架结合,也就是前文的bind
  3. 编译器,它会将原本的导入路径重定向到生成器生成的代码,在前端是unplugin插件,在服务端则是通过运行时
  4. 请求适配器,它会利用生成器生成的代码,并和axios等请求库结合,生成请求调用,即前文的createClient
  5. 运行时,利用nodejsregister提供热更新,通过命令行启动,即前文的phecda-server <entryFile>

基本流程

服务方:

  1. 通过命令行,调用运行时,开始执行入口文件
  2. 将模块通过实例化,使用生成器生成请求相关代码
  3. 通过适配器,与服务端框架适配

调用方

  1. 通过编译器,将引入指向元数据
  2. 通过请求适配器,生成请求调用

实例化模块并生成代码

本质上是将所有的模块,或者说是类,控制反转+依赖注入将其实例化,然后根据Tag或者类名注册到modulemap里,并将模块上的元数据收集到meta数组中, 再根据元数据产生代码

Tag或者类名作为模块的标识,在Phecda架构中很重要

ts
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 // 元数据数组

通过控制器创建接口

ts
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使用者需要注意,这里的参数必须是来自客户端,不能使用自定义装饰器

举个例子,在守卫中,通过请求的请求头鉴权,获得用户信息

ts
@Get()
test3(@User() user:any){

}

那么 这里的用户信息是来自服务端解析,而非用户端上传,这种写法会导致类型复用出问题。

PS中禁止这么做

上下文

那以上功能该怎么实现呢,简单!只需要把信息挂到上下文

守卫、管道中、过滤器都可以操作上下文

ts
import { Ctx, Get } from 'phecda-server'
import type { ExpressCtx } from 'phecda-server/express'
class TestController {
  @Ctx
  context: ExpressCtx

  @Get()
  test3() {
    const { user } = this.context // 必须在函数顶部

    // ...
  }
}

简而言之,函数的参数必须是来自客户端,而服务端的东西则通过上下文获得

为控制器创建服务

ts
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使用者请注意,只有通过构造函数实现依赖注入这一种方式,没有其他注入

前文中的

ts
const data = await Factory([TestController])

此时不需要添加 TestService,它会作为 TestController 的依赖被处理

与服务端框架适配

Express
ts
import { bind } from 'phecda-server/express'

// ..

const router = express.Router()
bind(router, data) // work for router
Fastify
ts
import { bind } from 'phecda-server/fastify'
const app = Fastify()

bind(app, data)
koa
ts
import { bind } from 'phecda-server/koa'

// ..
const router = new Router()
bind(router, data)
h3
ts
import { bind } from 'phecda-server/h3'

const router = createRouter()
bind(router, data)
hyper-express
ts
import { bind } from 'phecda-server/hyper-express'
// ..
const router = new HyperExpress.Router()
bind(router, data)
hono
ts
import { bind } from 'phecda-server/hono'
// ..

const router = new Hono()
bind(router, data)

调用方调用

ts
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>

服务端不同程序之间的调用

Released the MIT License.