Research
Security News
Malicious npm Package Targets Solana Developers and Hijacks Funds
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
node-web-mvc
Advanced tools
spring style web mvc framework
npm create node-web-mvc@latest
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 {
// 这里可以扩展配置...
}
完成启动配置后,可以在控制器目录下定义对应的控制器
控制器的定义风格和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';
}
}
更多的控制器配置,我们可以阅读后面通过注解来完善控制器。
@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/{}')
我们可以通过以下几个注解来定义请求参数的提取方式。
@RequestParam
提取类型为urleoncoded
的参数
@RequestBody
提取整个body
内容,通常是提取成为一个json
对象
@PathVariable
提取路由中的占位参数
@RequestHeader
提取请求头中的指定名的请求头做为参数
@ServletRequest
用于提取request
对象
@ServletResponse
用于提取response
对象
从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:'上传成功'
}
}
}
提取整个body
内容,通常是提取成为一个json
对象
@RequestMapping('/order')
class OrderController {
@GetMapping('/save')
saveOrder(@RequestBody order){
console.log(order);
}
}
从请求路由中提取路径参数
@RequestMapping('/order')
class OrderController {
@GetMapping('/detail/:id')
detail(@PathVariable id){
return `Order ${id}`;
}
}
从请求头中提取参数
@RequestMapping('/home')
class HomeController {
@GetMapping('/index')
detail(@RequestHeader('content-type') ct){
return `content-type: ${ct}`;
}
}
提取request
整个对象。
@RequestMapping('/home')
class HomeController {
@GetMapping('/index')
detail(@ServletRequest request){
}
}
提取response
整个对象。
@RequestMapping('/home')
class HomeController {
@GetMapping('/index')
detail(@ServletResponse response){
}
}
在控制器具体函数中,我们可以返回以下几种类型来将内容返回到客户端。
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
标注在控制器的函数上,则表示当前控制器的函数执行异常时,会使用当前标注的函数来进行异常处理。
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
标注当前类为全局控制器处理,
最后在该类上定义一个异常处理函数,然后通过ExceptionHandler
标注成异常处理函数。
例如:
AppException.ts
@ControllerAdvice
class AppException {
@ExceptionHandler
handleException(error){
// 返回一个 json 异常对象
return { code:error.code,message:error.message };
}
}
框架也提供了静态资源服务,以及针对静态资源设定缓存策略等,同时也支持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())
}
});
...
框架默认不具备视图渲染功能,不过我们可以自定义视图解析器来支持渲染像ejs
,handlebars
等类型的视图。
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');
})
}
}
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;
}
}
启动时通过addViewResolvers
配置来注册视图解析器。
import { Registry } from 'node-web-mvc';
// 启动Mvc
Registry.launch({
// ... 其他配置
// 通过配置,来注册ejs视图解析器s
addViewResolvers(registry) {
// 注册ejs视图解析器
registry.addViewResolver(new EjsViewResolver('test/WEB-INF/', '.ejs'))
}
});
框架同时也内置了拦截器,我们可以通过自定义拦截器来完成一些请求的前置,以及后置处理。
通过继承于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')
}
});
框架内置了以下几种类型的请求内容转换
JsonMessageConverter 将application/json
的http.body正文转换成json
对象.
UrlencodedMessageConverter 用于转换类型为application/x-www-form-urlencoded
的请求内容。
MultipartMessageConverter 用于转换类型为multipart/form-data
的请求内容
如果您需要处理其他类型的请求内容,可以自定义一个转换器
通过实现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;
}
}
框架内置了以下几种类型的请求参数解析
@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
配置启用热更新服务,
在热更新服务下,控制器代码以及及依赖模块改动,无需重启服务器。
在修改一个文件时,会触发热更,在执行热更新前,会触发preload
,如果您希望
您的某个依赖模块需要进行特定处理,则可以再该文件中订阅hot.preload
例如: ControllerFactory.ts 再一些控制器模块修改时,需要进行一些前置处理
import { hot } from 'node-web-mvc';
// 订阅preload
hot.create(module).preload((old) => {
// old 为当前即将进行热更新的模块旧模块,此时可以根据old来进行一些清理操作
})
在模块热更新后,同此此函数来接受更新
import { hot } from 'node-web-mvc';
// 订阅preload
hot.create(module).preload((new,old) => {
// new 为当前热更新后的新模块对象
// old 为热更新前的模块对象
})
框架支持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
}
FAQs
node spring mvc
The npm package node-web-mvc receives a total of 2 weekly downloads. As such, node-web-mvc popularity was classified as not popular.
We found that node-web-mvc demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.