node-web-mvc
v3.3.26
Published
node spring mvc
Downloads
226
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
路由来访问HomeController
的index
函数
@RequestMapping('/home')
class HomeController {
@RequestMapping({ value:'/index',method:'GET' })
index(){
return 'Hi i am home index';
}
}
在大多数情况下,我们只会配置路由
与请求类型
可以通过以下几个注解来进行快捷配置。
@GetMapping
映射一个method
为GET
的请求
@RequestMapping('/home')
class HomeController {
@GetMapping('/index')
index(){
return 'Hi i am home index';
}
}
@PostMapping
映射一个method
为POST
的请求@PutMapping
映射一个method
为PUT
的请求@DeleteMapping
映射一个method
为DELETE
的请求@PatchMapping
映射一个method
为PATCH
的请求
路由风格
通过 @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
通过重写UrlBasedViewResolver
的internalResolve
来解析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);
})
}
}
第二步
通过addMessageConverters
将XmlHttpMessageConverter
进行注册。
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;
}
}
第三步
通过addArgumentResolvers
将PathVariableMapMethodArgumentResolver
进行注册。
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
}