npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

node-web-mvc

v3.3.26

Published

node spring mvc

Downloads

129

Readme

创建应用

npm create node-web-mvc@latest

spring风格

index.ts

import { SpringApplication, SpringBootApplication } from 'node-web-mvc';

@SpringBootApplication({
  // 代码热更新: 在该目录下的文件改动支持热更新(无需重启服务) 注意:在process.env.NODE_ENV === 'production'时强制无效
  hot: './test',
  // 启动时需要加载的模块目录, 在不配置时默认为 process.cwd()
  scanBasePackages: './test',
  // 配置服务端口相关
  // server: { port: 8080  }
})
export default class DemoApplication {
  static main() {
    SpringApplication.run(DemoApplication);
   // 启动后执行逻辑
  }
}

WebAppConfigurer.ts

import { WebMvcConfigurationSupport } from 'node-web-mvc';

@Configuration
export default class WebAppConfigurer extends WebMvcConfigurationSupport {

  // 这里可以扩展配置...

}

Controller 控制器

完成启动配置后,可以在控制器目录下定义对应的控制器

控制器的定义风格和Spring Mvc风格一致。

例如:

import { RestController, RequestMapping, GetMapping  } from 'node-web-mvc';

@RestController
@RequestMapping('/home')
class HomeController { 

  @GetMapping({ value:'/index',method:'GET' })
  index(){
    return 'Hi i am home index';
  }
}

更多的控制器配置,我们可以阅读后面通过注解来完善控制器。

Route 路由映射

@RequestMapping

该注解用于将请求映射到指定控制器。

有两种使用方式

简要模式

仅配置访问路径,例如: 以下例子中,仅配置 以 /home 来访问HomeController

@RequestMapping('/home')
class HomeController { 

}

详细配置

通过传入一个对象RequestMapping来进行详细映射。

以下例子通过@RequestMapping配置 允许在GET方式下通过/home/index路由来访问 HomeControllerindex函数

@RequestMapping('/home')
class HomeController { 

  @RequestMapping({ value:'/index',method:'GET' })
  index(){
    return 'Hi i am home index';
  }
}

在大多数情况下,我们只会配置路由请求类型 可以通过以下几个注解来进行快捷配置。

  • @GetMapping 映射一个methodGET的请求
@RequestMapping('/home')
class HomeController { 

  @GetMapping('/index')
  index(){
    return 'Hi i am home index';
  }
}
  • @PostMapping 映射一个methodPOST的请求

  • @PutMapping 映射一个methodPUT的请求

  • @DeleteMapping 映射一个methodDELETE的请求

  • @PatchMapping 映射一个methodPATCH的请求

路由风格

通过 @RequestMapping 等注解配置路由时,可以有以下几种配置风格

  • 普通路由
@GetMapping('/detail/index')
  • 参数占位类型

使用 {} 来标识占位

通过占位映射的路由参数,可以通过@PathVariable 注解来提取

@GetMapping('/detail/{id}')

正则风格路由

@GetMapping('/route/{}')

Arguments 参数提取

我们可以通过以下几个注解来定义请求参数的提取方式。

  • @RequestParam 提取类型为urleoncoded的参数

  • @RequestBody 提取整个body内容,通常是提取成为一个json对象

  • @PathVariable 提取路由中的占位参数

  • @RequestHeader 提取请求头中的指定名的请求头做为参数

  • @ServletRequest 用于提取request 对象

  • @ServletResponse 用于提取response 对象

RequestParam

urleoncoded的内容中提取指定名称的参数

@RequestParam 作为参数注解,不进行任何配置,默认会以参数名来作为提取名依据

@RequestMapping('/home')
class HomeController { 

  @GetMapping('/index')
  index(@RequestParam name){
    return `Hi ${name}, i am home index`;
  }
}

同时@RequestParam 也可以进行详细配置ParamAnnotation

例如: 将url中传递过来的userName值提取给index中的 name参数

@RequestMapping('/home')
class HomeController { 

  @GetMapping('/index')
  index(@RequestParam({ value:'userName', required:true }) name){
    return `Hi ${name}, i am home index`;
  }
}

文件上传参数提取

import { MultipartFile } from 'node-web-mvc';

@Api({ description:'上传' })
@RequestMapping('/upload')
class HomeController { 

  // 单个文件上传
  @ApiOperation({ value: '上传文件', notes: '上传证书文件' })
  // 配置swagger 生成上传表单
  @ApiImplicitParams([
    { name: 'files', value: '证书', required: true, dataType: 'file' },
    { name: 'id', value: '用户id', required: true },
  ])
  @PostMapping({ value: '/file', produces: 'application/json' })
  async index(@RequestParam file: MultipartFile,@RequestParam id){
    // 保存文件
    await file.transferTo('appqdata/images/' + file.name);

    return {
      code:0,
      message:'上传成功'
    }
  }

  // 多个文件上传
  @PostMapping({ value: '/files', produces: 'application/json' })
  async index(@RequestParam files: Array<MultipartFile>){
    // 保存文件
    for (let file of files) {
      await file.transferTo('appdata/images/' + file.name)
    }

    return {
      code:0,
      message:'上传成功'
    }
  }
}

RequestBody

提取整个body内容,通常是提取成为一个json对象

@RequestMapping('/order')
class OrderController { 

  @GetMapping('/save')
  saveOrder(@RequestBody order){
    console.log(order);
  }
}

PathVariable

从请求路由中提取路径参数

@RequestMapping('/order')
class OrderController { 

  @GetMapping('/detail/:id')
  detail(@PathVariable id){
    return `Order ${id}`;
  }
}

RequestHeader

从请求头中提取参数

@RequestMapping('/home')
class HomeController { 

  @GetMapping('/index')
  detail(@RequestHeader('content-type') ct){
    return `content-type: ${ct}`;
  }
}

ServletRequest

提取request整个对象。

@RequestMapping('/home')
class HomeController { 

  @GetMapping('/index')
  detail(@ServletRequest request){
    
  }
}

ServletResponse

提取response整个对象。

@RequestMapping('/home')
class HomeController { 

  @GetMapping('/index')
  detail(@ServletResponse response){
    
  }
}

Responsee 返回内容

在控制器具体函数中,我们可以返回以下几种类型来将内容返回到客户端。

  • ModelAndView 返回一个视图

  • String 返回一个字符串

  • Object 如果需要正常返回,需要通过RequestMapping指定produces为application/json

  • Promise 返回一个异步结果

  • Middlewares 返回一个类express的中间件执行结果

import { RequestMapping, GetMapping, Middlewares } from 'node-web-mvc';

@RequestMapping('/home')
class HomeController {

  @GetMapping('/index')
  index(){
    return new Middlewares([
      (req,resp,next)=> next()
    ])
  }
}

@RequestMapping('/home')
class HomeController { 

  @GetMapping('/index')
  index(){
    return new ModelAndView('home/index');
  }

  @GetMapping('/string')
  strings(){
    return `output :String`;
  }

  @GetMapping({ value: '/object', produces:'application/json' })
  list(){
    return [
      { name:'张三',id:100 }
    ];
  }
}

异常处理

框架可以通过以下两个注解来进行控制器异常处理

  • ExceptionHandler

  • ControllerAdvice

ExceptionHandler

如果将ExceptionHandler标注在控制器的函数上,则表示当前控制器的函数执行异常时,会使用当前标注的函数来进行异常处理。

import { GetMapping, RequestMapping, ExceptionHandler } from 'node-web-mvc';

@RequestMapping('/home')
export default class HomeController {

  @GetMapping('/index')
  index(){
    throw new Error('error');
  }

  @ExceptionHandler
  handleException(error){
    // 返回一个 json 异常对象
    return { code:error.code,message:error.message };
  }
}

ControllerAdvice

利用ControllerAdvice 来进行全局异常控制

定义一个异常处理类,然后使用ControllerAdvice标注当前类为全局控制器处理,

最后在该类上定义一个异常处理函数,然后通过ExceptionHandler标注成异常处理函数。

例如:

AppException.ts


@ControllerAdvice
class AppException {

  @ExceptionHandler
  handleException(error){
    // 返回一个 json 异常对象
    return { code:error.code,message:error.message };
  }
}

Resource 静态资源

框架也提供了静态资源服务,以及针对静态资源设定缓存策略等,同时也支持gzip压缩处理。

import { Registry } from 'node-web-mvc';

// 启动Mvc  
Registry.launch({
  resource:{
    gzipped:true,// 默认不开启gzip
    // 默认可不填写,默认值为: 
    // application/javascript,text/css,application/json,application/xml,text/html,text/xml,text/plain
    mimeTypes:'text/css,text/html', 
  },
  addResourceHandlers(registry){
    registry
      .addResourceHandler('/swagger-ui/**')
      .addResourceLocations('/a/b/swagger-ui/')
      .setCacheControl({ maxAge:0 })
      // .addResolver(new CustomResolver())
  }
});

自定义ResourceResolver

...

View 视图

框架默认不具备视图渲染功能,不过我们可以自定义视图解析器来支持渲染像ejs ,handlebars等类型的视图。

第一步 实现一个ejs 视图(View)

./EjsView.ts

/**
 * @module EjsView
 * @description Razor视图
 */
import ejs from 'ejs';
import { View } from 'node-web-mvc';

export default class EjsView extends View {

  /**
   * 进行视图渲染
   * @param model 当前视图的内容
   * @param request 当前视图
   * @param response 
   */
  render(model, request, response) {
    return ejs.renderFile(this.url, model).then((html) => {
      response.setHeader('Content-Type', 'text/html');
      response.setStatus(200).end(html, 'utf8');
    })
  }
}

第二步 实现一个ejs视图解析器

EjsViewResolver.ts

通过重写UrlBasedViewResolverinternalResolve 来解析ejs的视图

import fs from 'fs';
import path from 'path';
import { UrlBasedViewResolver,HttpServletRequest,View } from 'node-web-mvc'
import EjsView from './EjsView';

export default class EjsViewResolver extends UrlBasedViewResolver {

  internalResolve(viewName: string, model: any, request: HttpServletRequest): View {
    const file = path.resolve(viewName);
    if (fs.existsSync(file)) {
      return new EjsView(viewName);
    }
    return null;
  }
}

第三步 注册ejs视图解析器

启动时通过addViewResolvers配置来注册视图解析器。

import { Registry } from 'node-web-mvc';

// 启动Mvc  
Registry.launch({
  // ... 其他配置
  // 通过配置,来注册ejs视图解析器s
  addViewResolvers(registry) {
    // 注册ejs视图解析器
    registry.addViewResolver(new EjsViewResolver('test/WEB-INF/', '.ejs'))
  }
});

Interceptor 拦截器

框架同时也内置了拦截器,我们可以通过自定义拦截器来完成一些请求的前置,以及后置处理。

自定义权限校验拦截器

第一步

通过继承于HandlerInterceptorAdapter来实现一个拦截器

AuthorizationInterceptor.ts

import { HandlerInterceptorAdapter } from 'node-web-mvc';

export default class AuthorizationInterceptor extends HandlerInterceptorAdapter {
 /**
   * 在处理action前,进行请求预处理
   * @param { HttpRequest } request 当前请求对象
   * @param { HttpResponse } response 当前响应对象
   * @param { ControllerContext } handler  当前拦截待执行的函数相关信息
   * @returns { boolean }
   *   返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;
   */
  preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: HandlerMethod): boolean {
    // 假设我们添加了一个UserLogin注解
    const annotation = handler.getAnnotation(UserLogin);
    if (annotation) {
      const nativeAnnotation = annotation.nativeAnnotation;
      // 进行权限校验
    }
    return true;
  }

  /**
   * 在处理完action后的拦截函数,可对执行完的接口进行处理
   * @param { HttpRequest } request 当前请求对象
   * @param { HttpResponse } response 当前响应对象
   * @param { ControllerContext } handler  当前拦截待执行的函数相关信息
   * @param { any } result 执行action返回的结果
   */
  postHandle(request: HttpServletRequest, response: HttpServletResponse, handler: HandlerMethod, result): void {
  }

  /**
   * 在请求结束后的拦截器 (无论成功还是失败都会执行此拦截函数)
   * (这里可以用于进行资源清理之类的工作)
   * @param { HttpRequest } request 当前请求对象
   * @param { HttpResponse } response 当前响应对象
   * @param { ControllerContext } handler  当前拦截待执行的函数相关信息
   * @param { any } ex 如果执行action出现异常时,此参数会有值
   */
  afterCompletion(request: HttpServletRequest, response: HttpServletResponse, handler: HandlerMethod, ex): void {
  }
}

第二步

启动时通过addInterceptors配置来注册拦截器。

import { Registry } from 'node-web-mvc';
import AuthorizationInterceptor from './interceptors/AuthorizationInterceptor';

// 启动Mvc  
Registry.launch({
  // ... 其他配置
  // 通过配置来注册拦截器
  addInterceptors(registry) {
    registry.addInterceptors(new AuthorizationInterceptor())

    // registry
    //   .addInterceptors(new AuthorizationInterceptor())
    //   .excludePathPatterns('/root/a','/root/b')
    //   .addPathPatterns('/root')
  }
});

HttpMessageConverter 内容转换

框架内置了以下几种类型的请求内容转换

  • JsonMessageConverter 将application/json的http.body正文转换成json对象.

  • UrlencodedMessageConverter 用于转换类型为application/x-www-form-urlencoded的请求内容。

  • MultipartMessageConverter 用于转换类型为multipart/form-data的请求内容

如果您需要处理其他类型的请求内容,可以自定义一个转换器

自定义Http转换器

第一步

通过实现HttpMessageConverter接口来实现一个转换器

XmlHttpMessageConverter.ts

import { MediaType, ServletContext, HttpMessageConverter,RequestMemoryStream } from 'node-web-mvc';
import xml2js from 'xml2js';

export default class XmlHttpMessageConverter implements HttpMessageConverter {
  /**
   * 判断当前转换器是否能处理当前内容类型
   * @param mediaType 当前内容类型 例如: application/xml
   */
  canRead(mediaType: MediaType): boolean {
    return mediaType.name === 'application/xml';
  }

  /**
   * 判断当前内容是否能写
   * @param mediaType 当前内容类型 例如: application/xml
   */
  canWrite(mediaType: MediaType): boolean {
    return mediaType.name === 'application/xml';
  }

  // getSupportedMediaTypes(): Array<string>

  /**
   * 读取当前消息内容
   * @param servletRequest
   */
  read(servletContext: ServletContext, mediaType: MediaType): any {
    return new Promise((resolve, reject) => {
      new RequestMemoryStream(servletContext.request, (buffers) => {
        xml2js.parseString(buffers.toString('utf8'), (err, data) => {
          err ? reject(err) : resolve(data);
        });
      });
    })
  }

  /**
   * 写出当前内容
   * @param data 当前数据
   * @param mediaType 当前内容类型
   * @param servletContext 当前请求上下文
   */
  write(data: any, mediaType: MediaType, servletContext: ServletContext) {
    return new Promise((resolve) => {
      const builder = new xml2js.Builder();
      const xml = builder.buildObject(data);
      servletContext.response.write(xml, resolve);
    })
  }
}

第二步

通过addMessageConvertersXmlHttpMessageConverter进行注册。

Launch.ts

import { Registry } from 'node-web-mvc';
import XmlHttpMessageConverter from './interceptors/XmlHttpMessageConverter';

// 启动Mvc  
Registry.launch({
  // ... 其他配置
  // 注册XmlHttpMessageConverter
  addMessageConverters(converters) {
    converters.addMessageConverters(new XmlHttpMessageConverter());
  }
});

第三步

这样就可以在控制器中使用了

DataController.ts

import { RequestMapping, PostMapping, RequestBody } from 'node-web-mvc';

@RequestMapping('/data')
export default class DataController {

  // 这里:同时测试 读取xml 以及返回xml
  @PostMapping({ value: '/receieve', consumes: 'application/xml', produces: 'application/xml' })
  receieve(@RequestBody data){
    console.log('xml data',data);
    return data;
  }
}

ArgumentResolver 参数解析

框架内置了以下几种类型的请求参数解析

  • @RequestParam 提取类型为urleoncoded的参数

  • @RequestBody 提取整个body内容,通常是提取成为一个json对象

  • @PathVariable 提取路由中的占位参数

  • @RequestHeader 提取请求头中的指定名的请求头做为参数

  • @ServletRequest 用于提取request 对象

  • @ServletResponse 用于提取response 对象

如果您需要处理其他类型的请求内容,可以自定义一个参数解析器

自定义参数解析器

例如,以下实现通过 UserId 注解来提取当前登录用户id。

第一步

定义一个UserId注解

UserId.ts

import { Target, ElementType } from 'node-web-mvc';

class UserId {
  constructor(){
    // 注解构造函数
  }
}

// 公布注解
export default Target(ElementType.PARAMETER)(UserId);

第二步

通过实现HandlerMethodArgumentResolver接口来实现一个解析器

UserIdArgumentResolver.ts

import { ServletContext,MethodParameter, HandlerMethodArgumentResolver } from 'node-web-mvc';
import UserIdAnnotation from './UserIdAnnotation';

export default class UserIdArgumentResolver implements HandlerMethodArgumentResolver {

  supportsParameter(paramater: MethodParameter, servletContext: ServletContext) {
    return paramater.hasParameterAnnotation(UserIdAnnotation)
  }

  resolveArgument(parameter: MethodParameter, servletContext: ServletContext): any {
    const cookies = servletContext.request.cookies;
    const token = cookies.token;
    // 从token中解析出用户id
    return TokenService.decode(token).userId;
  }
}

第三步

通过addArgumentResolversPathVariableMapMethodArgumentResolver进行注册。

import { Registry } from 'node-web-mvc';
import UserIdArgumentResolver from './UserIdArgumentResolver';

// 启动Mvc  
Registry.launch({
  // ... 其他配置
  // 注册
  addArgumentResolvers(resolvers) {
    resolvers.addArgumentResolvers(new UserIdArgumentResolver());
  }
});

第四步

这样就可以在控制器中使用了

import { RequestMapping, PostMapping } from 'node-web-mvc';
import UserId from './UserId';

@RequestMapping('/data')
export default class DataController {

  @PostMapping('/home')
  receieve(@UserId id){
    console.log('id',id);
  }
}

热更新

在启动时,可通过配置hot配置启用热更新服务,

在热更新服务下,控制器代码以及及依赖模块改动,无需重启服务器。

hot.preload

在修改一个文件时,会触发热更,在执行热更新前,会触发preload,如果您希望 您的某个依赖模块需要进行特定处理,则可以再该文件中订阅hot.preload

例如: ControllerFactory.ts 再一些控制器模块修改时,需要进行一些前置处理

import { hot } from 'node-web-mvc';

// 订阅preload
hot.create(module).preload((old) => {
  // old 为当前即将进行热更新的模块旧模块,此时可以根据old来进行一些清理操作
})

hot.accept

在模块热更新后,同此此函数来接受更新

import { hot } from 'node-web-mvc';

// 订阅preload
hot.create(module).preload((new,old) => {
  // new 为当前热更新后的新模块对象
  // old 为热更新前的模块对象
})

Swagger

框架支持swagger文档生成功能

可通过以下注解来完成文档元数据定义

  • @Api 定义一个接口服务
@Api({ description: '首页控制器' })
class HomeConntroller {

}
  • @ApiOperation 定义一个接口操作
@Api({ description: '首页控制器' })
class HomeConntroller {

  @ApiOperation({ value: '首页列表数据', notes: '这是备注' })
  index(){
  }
}
  • @ApiImplicitParams 定义接口操作参数信息

如果不需要配置参数详细设定,一般可以不使用ApiImplicitParams 因为框架会自动根据每个参数的提取类型来自动生成swagger参数配置。

@Api({ description: '首页控制器' })
class HomeConntroller {

  @ApiOperation({ value: '首页列表数据', notes: '这是备注',returnType:'返回数据类型' })
  @GetMapping('/index')
  index(){
  }

  @ApiOperation({ value: '上传文件', notes: '上传证书文件' })
  @ApiImplicitParams([
    { value: 'file', desc: '证书', required: true, dataType: MultipartFile },
    { value: 'desc', desc: '描述', required: true, paramType: 'formData' },
    { value: 'id', desc: '用户id', required: true }
  ])
  @PostMapping('/upload')
  upload(file: MultipartFile,@RequestParam desc,@RequestParam id) {
    return file.transferTo('appdata/images/' + file.name);
  }
}
  • @ApiModel 定义一个实体类
@ApiModel({ value: '用户信息', description: '用户信息。。' })
export default class UserInfo {

}
  • @ApiModelProperty 定义实体类属性
@ApiModel({ value: '用户信息', description: '用户信息。。' })
export default class UserInfo {

  @ApiModelProperty({ value: '用户名', required: true, example: '张三' })
  public userName: string


  @ApiModelProperty({ value: '用户编码', required: true, example: 1 })
  public userId: number
}