Vert.x,Web - Restful API

news2024/10/22 23:33:12

将通过Vert.x Web编写一个前后分离的Web应用,做为Vert.x Web学习小结。本文为后端部分,后端实现业务逻辑,并通过RESTfull接口给前端(Web页面)调用。

案例概述

假设我们要设计一个人力资源(HR)系统,要实现对员工信息的增删改查。我们将前端和后端设计成两个Verticle,这样可以实现灵活的部署,可以将前端和后端部署在一个JVM上,也可以部署到不同的JVM或者不同的服务器上。

员工信息存放在MySQL数据库中,所以需要先创建对应数据库和表:

create database hr;
use hr;

create table emp (
 empno int not null auto_increment,
 ename varchar(24),
 job varchar(16),
 constraint emp_pk primary key(empno)
);

insert into emp values(7369, 'SMITH', 'CLERK');
insert into emp values(7499, 'ALLEN', 'SALESMAN');
insert into emp values(7521, 'WARD', 'SALESMAN');
insert into emp values(7566, 'JONES', 'MANAGER');
insert into emp values(7654, 'MARTIN', 'SALESMAN');
insert into emp values(7698, 'BLAKE', 'MANAGER');
insert into emp values(7782, 'CLARK', 'MANAGER');
insert into emp values(7788, 'SCOTT', 'ANALYST');
insert into emp values(7839, 'KING', 'PRESIDENT');
insert into emp values(7844, 'TURNER', 'SALESMAN');
insert into emp values(7876, 'ADAMS', 'CLERK');
insert into emp values(7900, 'JAMES', 'CLERK');
insert into emp values(7902, 'FORD', 'ANALYST');
insert into emp values(7934, 'MILLER', 'CLERK');

后端Restfull实现

后端设计如下的Restful AIP:

请求方法    请求路径                        功能说明      
---------- ----------------------------- ------------- 
GET        /api/v1/hr/employees          获取员工列表
POST       /api/v1/hr/employees          创建新员工
GET        /api/v1/hr/employees/{empNo}  获取员工信息
DELETE     /api/v1/hr/employees/{empNo}  删除一个员工
PUT        /api/v1/hr/employees/{empNo}  修改员工信息 

因为需要使用HTTP并访问MySQL数据库,所以需要在项目中引入相关依赖:

<dependency>
	<groupId>io.vertx</groupId>
	<artifactId>vertx-core</artifactId>
	<version>4.5.10</version>
</dependency>
<dependency>
	<groupId>io.vertx</groupId>
	<artifactId>vertx-web</artifactId>
	<version>4.5.10</version>
</dependency>
<dependency>
	<groupId>io.vertx</groupId>
	<artifactId>vertx-mysql-client</artifactId>
	<version>4.5.10</version>
</dependency>

数据库连接

创建SQL客户端用于访问数据库,程序架构大致如下:

public class HrWebService extends AbstractVerticle {
	public HrWebService() {
		MySQLConnectOptions connectOptions = new MySQLConnectOptions()
				.setHost("127.0.0.1").setPort(3306)
				.setUser("root").setPassword("Passw0rd")
				.setDatabase("hr").setConnectTimeout(2000)
				.addProperty("autoReconnect", "true")
				.addProperty("useSSL","false")
				.addProperty("rewriteBatchedStatements", "true");
		
		PoolOptions poolOptions = new PoolOptions().setMaxSize(5);
		
		client = MySQLBuilder.client().using(vertx)
				.with(poolOptions)
				.connectingTo(connectOptions)
				.build();
	}
	@Override
	public void stop() throws Exception {
		if (null != client) { //停止时候释放连接。
			client.close();
		}
	}
	
	@Override
	public void start() throws Exception {
		HttpServer server = vertx.createHttpServer();
		Router router = Router.router(vertx);
		// 在这里添加路由...
		server.requestHandler(router).listen(8081);
	}
}

接口基础部分

因为POST/PUT方法需要使用请求体传递数据,所以需要允许请求体,为避免受到攻击,设置请求体的最大大小为100KB。

router.route("/api/v1/hr/*").handler(BodyHandler.create().setBodyLimit(100 * 1024)); 

出于安全性,浏览器通常会限制脚本内发起的跨源HTTP请求。所以需要添加相关的CORS响应标头,来允许跨域访问,否则前端调用会报错。

router.route("/api/v1/hr/*").handler(newCorsHandler());
public static CorsHandler newCorsHandler() {
	/** 设置支持跨域访问/CORS */
	Set<String> allowedHeaders = new HashSet<>();
	allowedHeaders.add("x-requested-with");
	allowedHeaders.add("Access-Control-Allow-Origin");
	allowedHeaders.add("origin");
	allowedHeaders.add("Content-Type");
	allowedHeaders.add("accept");
	allowedHeaders.add("X-PINGARUNER");

	Set<HttpMethod> allowedMethods = new HashSet<>();
	allowedMethods.add(HttpMethod.GET);
	allowedMethods.add(HttpMethod.POST);
	allowedMethods.add(HttpMethod.OPTIONS);
	allowedMethods.add(HttpMethod.DELETE);
	allowedMethods.add(HttpMethod.PATCH);
	allowedMethods.add(HttpMethod.PUT);

	return CorsHandler.create()
			.addOrigin("*") // Access-Control-Allow-Origin
			.allowedHeaders(allowedHeaders) // Access-Control-Request-Method
			.allowedMethods(allowedMethods); // Access-Control-Request-Headers
}

最后为(HR)应用设置一个路由错误处理器:

router.route("/api/v1/hr/*").failureHandler(this::defaultErrorHandler);

public void defaultErrorHandler(RoutingContext routingContext) {
	Throwable exception = routingContext.failure();
	int statusCode = routingContext.statusCode();

	 服务器记录日志
	HttpServerRequest request = routingContext.request();
	String method = request.method().name();
	String uri = request.absoluteURI();
	LOGGER.log(Level.SEVERE, method + " " + uri + ", statusCode: " + statusCode, exception);

	 返回错误信息
	HttpServerResponse response = routingContext.response();
	response.setStatusCode(statusCode); // 必须设置, 默认: 200 OK
	// response.setStatusMessage(exception.getMessage()); // 可覆盖, 默认是statusCode对应的错误信息。

	// 返回Json格式错误信息: {"error":{"code":500, "message":"Error message here"}}
	JsonArray errorArray = new JsonArray().add(new JsonObject().put("code", statusCode))
			.add(new JsonObject().put("message", exception.getMessage()));
	JsonObject respObj = new JsonObject().put("error", errorArray);
	response.end(respObj.toString());
}

这里设计上,客户端需要通过HTTP的statusCode来判断请求的释放成功,正常走API的结果解析,错误走这个错误结果解析。也可以在内部出错的时候(status code)500,依然返回200,只是把错误信息和代码放在返回的json中,可以根据自己需要规划。

获取员工列表接口

该接口用于获取员工列表。因为员工数量比较多,需要支持分页。

请求路径: GET /api/v1/hr/employees
请求参数: 
    page , 整型, 非必选, 请求数据的分页页码, 默认值: 1
    limit, 整型, 非必选, 请求数据的分页大小, 默认值: 5
返回结果:
    count     , 整型, 总记录数。
    data      , 数组, 员工信息的数组。数据结构, 对应emp表的行。
    successful, 布尔类型, 请求是否成功。
    duration  , 整型, 服务端处理请求的时间(毫秒)。

接口需要总记录数和请求页码的数据,实现上通过2条语句获取,通过Future.all方法将两个异步查询组合在一起,并将结果返回:

router.route(HttpMethod.GET, "/api/v1/hr/employees").handler(this::getEmployees);

public void getEmployees(RoutingContext routingContext) {
	long startTime = System.currentTimeMillis();
	// 获取url请求参数
	HttpServerRequest request = routingContext.request();
	String p = request.getParam("page", "1"); // 获取url请求参数page,默认第1页。
	String l = request.getParam("limit", "5"); // 获取参数limit,默认值5。
	int page = Integer.parseInt(p);
	int rowCount = Integer.parseInt(l);
	int offset = (page - 1) * rowCount; // 计算记录偏移值。

	HttpServerResponse response = routingContext.response();
	response.putHeader("content-type", "application/json");
	JsonObject resultObject = new JsonObject(); // 用于保存结果。
	
	String sqlText1 = "select empno, ename, job from emp order by empno desc limit ?, ?";
	Future<RowSet<Row>> future1 = client.preparedQuery(sqlText1).execute(Tuple.of(offset, rowCount))
			.onSuccess(rows -> {
				JsonArray resultArray = new JsonArray(); //保存查询结果集(Array)
				for (Row row : rows) {
					JsonObject json = row.toJson();
					resultArray.add(json);
				}
				resultObject.put("data", resultArray); 
			});
			
	String sqlText2 = "select count(empno) as cnt from emp";
	Future<RowSet<Row>> future2 = client.preparedQuery(sqlText2).execute().onSuccess(rows -> {
		for (Row row : rows) {
			resultObject.put("count", row.getValue("cnt")); // 总记录数,通常前端计算分页用。
		}
	});

	Future.all(future1, future2).onComplete(ar -> { // 组合两个查询,两个异步都完成时候返回完成。
		if (ar.succeeded()) {
			resultObject.put("successful", true); // 设置请求结果为成功。
			long endTime = System.currentTimeMillis();
			resultObject.put("duration", endTime - startTime); // 计算执行时间。
			response.end(resultObject.toString()); // 返回API结果。
		} else {
			routingContext.fail(ar.cause());
		}
	});
}

通过Postman测试接口:GET http://127.0.0.1:8081/api/v1/hr/employees?page=2&limit=2
在这里插入图片描述
关闭数据库,模拟失败调用,再次执行接口调用:GET http://127.0.0.1:8081/api/v1/hr/employees?page=2&limit=2

root@localhost [hr]> shutdown ;
Query OK, 0 rows affected (0.00 sec)

在这里插入图片描述

创建新员工接口

该接口用创建新员工。

请求路径: POST /api/v1/hr/employees
请求参数: 
    ename, 字符串, 必选, 新员工姓名。
    job, 字符串, 必选, 新员工职位。
返回结果:
    empno, 整型, 新员工编号。

代码实现上,获取(Body)请求参数,插入数据库后,API返回员工编号(empno):


router.route(HttpMethod.POST, "/api/v1/hr/employees").handler(this::newEmployee);

public void newEmployee(RoutingContext routingContext) {
	JsonObject empObject = routingContext.body().asJsonObject();
	String ename = empObject.getString("ename");
	String job = empObject.getString("job");
	if (StringUtils.isBlank(ename) || StringUtils.isBlank(job)) { // apache commons-lang3
		// 有两种方式抛出失败: 调用routingContext.fail方法,并返回处理器方法或者抛出RuntimeException异常。
		routingContext.fail(new Exception("员工名或者职位不能为空白。"));
		return ; // 注意, 必须函数返回,否则还会继续调用后续代码。
		//throw new RuntimeException("员工名或者职位不能为空白。");
	}
	
	String sqlText = "insert into emp (ename, job) values (?, ?)";
	client.preparedQuery(sqlText).execute(Tuple.of(ename, job)).onSuccess(rows -> {
		long lastInsertId = rows.property(MySQLClient.LAST_INSERTED_ID);
		HttpServerResponse response = routingContext.response();
		response.putHeader("content-type", "application/json");
		JsonObject responseObject = new JsonObject();
		responseObject.put("empno", lastInsertId);
		response.end(responseObject.toString());
	}).onFailure(exception -> {
		routingContext.fail(exception);
	});
}

通过Postman测试接口,正常调用:
在这里插入图片描述
模拟错误参数,job为空字符串。
在这里插入图片描述

删除员工信息接口

该接口用于删除员工信息。

请求路径: DELETE /api/v1/hr/employees/{empNo}
请求参数: 
    empNo, 整型, 必选, 需要删除的员工编号。

需要url的路径参数方式获取员工编号:

router.route(HttpMethod.DELETE, "/api/v1/hr/employees/:empNo").handler(this::deleteEmployee);

public void deleteEmployee(RoutingContext routingContext) {
	String en = routingContext.pathParam("empNo");
	int empNo = 0;
	try {
		empNo = Integer.parseInt(en);
	} catch (NumberFormatException e) {
		routingContext.fail(new Exception("无效的请求路径, " + e.getMessage(), e));
		return;
	}
	String sqlText = "delete from emp where empno = ?";
	client.preparedQuery(sqlText).execute(Tuple.of(empNo)).onSuccess(rows -> {
		HttpServerResponse response = routingContext.response();
		response.end();
	});
}

测试接口:
在这里插入图片描述
失败调用。
在这里插入图片描述

修改员工信息接口

该接口用于修改员工信息。

请求路径: PUT /api/v1/hr/employees/{empNo}
请求路径: POST /api/v1/hr/employees
请求参数: 
    empno, 整型,必选,需要修改的员工编号。
    ename, 字符串, 必选, 新的员工姓名。
    job, 字符串, 必选, 新的员工职位。
 返回结果:
     rows, 整型,已修改的记录数。

实现代码如下:

router.route(HttpMethod.PUT, "/api/v1/hr/employees/:empNo").handler(this::updateEmployee);

public void updateEmployee(RoutingContext routingContext) {
	HttpServerResponse response = routingContext.response();
	response.putHeader("content-type", "application/json");
	String en = routingContext.pathParam("empNo");
	int empNo = 0;
	try {
		empNo = Integer.parseInt(en);
	} catch (NumberFormatException e) {
		throw new RuntimeException("无效的请求路径, " + e.getMessage(), e);
	}
	JsonObject empObject = routingContext.body().asJsonObject();
	String newEname = empObject.getString("ename");
	String newJob = empObject.getString("job");
	if (StringUtils.isBlank(newEname) || StringUtils.isBlank(newJob)) {
		throw new RuntimeException("新的员工名或者职位不能为空。");
	}
	
	String sqlText = "update emp set ename=?, job=? where empno = ?";
	client.preparedQuery(sqlText).execute(Tuple.of(newEname, newJob, empNo)).onSuccess(rows -> {
		response.end("{\"rows\": " + rows.rowCount() + "}");
	}).onFailure(exception -> {
		routingContext.fail(exception);
	});
}

测试接口:
在这里插入图片描述
失败调用:
在这里插入图片描述
至此,后端部分已经编写完成,下一文章将实现前端调用和展示部分。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2213463.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MybatisPlus+Spring Boot3 分页查询实现

目录 导入依赖 本文的house表 直接复制粘贴运行即可 MybatisConfig配置文件 创建数据库对应的实体类 创建mapper层接口 在service包下创建xxxService接口 controller层创建XXXController类 完成分页查询 导入依赖 <!--注意 SpringBoot3的依赖与Spring Boot2的Mybatis…

时隔11年,再次被纳入标普500指数,戴尔科技股票是否该买入?

猛兽财经核心观点&#xff1a; &#xff08;1&#xff09;9月24日&#xff0c;戴尔科技时隔11年后再次被纳入了标普500指数。 &#xff08;2&#xff09;华尔街分析师普遍很看好戴尔科技&#xff0c;并强调了戴尔科技在人工智能服务器和强劲的收入增长。 &#xff08;3&#xf…

枚举在Java体系中的作用

1. 枚举 枚举是在JDK1.5以后引入的。主要用途是&#xff1a;将一组常量组织起来&#xff0c;在这之前表示一组常量通常使用定义常量的方式&#xff1a; //用public static final修饰常量 public static final int RED 1; public static final int GREEN 2; public static f…

深度学习-24-基于keras的十大经典算法之残差网络ResNet

文章目录 1 残差网络(ResNet)1.1 ResNet简介1.2 ResNet结构2 模型应用2.1 加载数据2.2 构建模型SimpleResNet2.2.1 simple_resnet_block2.2.2 SimpleResNet2.2.3 实例化模型2.2.4 模型训练2.2.5 模型预测2.3 构建模型ResNet182.3.1 residual_block2.3.2 ResNet182.3.3 训练模型…

Redis高并发缓存设计问题与性能优化

1、缓存设计典型问题 1.1、缓存穿透 缓存穿透是指查询一个根本不存在的数据&#xff0c;缓存层和存储层都不会命中&#xff0c;通常出于容错的考虑&#xff0c;如果从存储层查不到数据则不写入缓存层。 缓存穿透将导致不存在的数据每次请求都要到存储层去查询&#xff0c;失…

vue3 Invalid value type passed to callWithAsyncErrorHandling()

vue3 提示警告。页面内点击按钮无响应 原因&#xff1a; <el-form :model"questionPage.queryParam" ref"queryForm" :inline"true"> ... <el-button type"primary" plain click"queryForm">查询</el-butto…

热门超声波清洗机有哪些?双十一适合学生党的清洗机推荐!

十一月十一号的双十一马上就快要到了&#xff0c;在这个一年一度的购物狂欢节中&#xff0c;不少人都期待着能够以优惠的价格购买到心仪的商品。超声波清洗机作为近年来备受关注的家用电器之一&#xff0c;以其清洁效果好、操作简便、价格亲民等特点&#xff0c;成为了大家双十…

leetcode二叉树(五)-二叉树层序遍历

题目 102.二叉树的层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[9,20],[15,7…

imx6ull-正点原子阿尔法-uboot-v2.4网络驱动修改

1 修改网络 PHY 地址,修改 PHY 驱动 /*[c] 1 修改网络PHY地址,修改PHY驱动*/ /******************************************************************************/ #if (CONFIG_FEC_ENET_DEV 0) #define IMX_FEC_BASE ENET_BASE_ADDR #define CONFIG_FEC_MXC_PHYADDR …

Electron-(一)创建桌面应用

一、概述 本文通过核心步骤介绍&#xff0c;形成使用Electron进行桌面应用创建的概述性内容。 在当今的软件开发领域&#xff0c;Electron 作为一款强大的工具&#xff0c;为开发者提供了一种便捷的方式来创建跨平台的桌面应用。本文将通过详细介绍核心步骤&#xff0c;带您领…

记一次有趣的发现-绕过堡垒机访问限制

前言 在某一次对设备运维管理的时候&#xff0c;发现的某安全大厂堡垒机设备存在绕过访问限制的问题&#xff0c;可以直接以低权限用户访问多个受控系统&#xff0c;此次发现是纯粹好奇心驱使下做的一个小测试压根没用任何工具。因为涉及到了很多设备和个人信息&#xff0c;所以…

AI大模型引领智慧城市:11个行业应用场景全解析

AI大模型在智慧城市11个行业的应用场景‍‍‍ 人工智能是当今科技领域最前沿的课题&#xff0c;更是新一轮科技革命的重要代表之一。从AlphaGo Zero通过自我学习碾压“AI前辈”AlphaGo&#xff0c;到百度智能无人汽车成功上路、苹果手机开启新的刷脸认证方式……而最近&#x…

老师发期中成绩小程序-易查分一键发布省事~

老师注意&#xff0c;本学期的期中考试即将拉开帷幕。考试结束后&#xff0c;成绩的发布往往是我们觉得最繁琐耗时的工作。逐一私信每个学生家长&#xff0c;告知他们的考试情况。使用我要向老师们推荐一个神器——易查分&#xff0c;能让老师一分钟完成期中成绩发布的工作。 易…

亿赛通与Ping32文档加密功能全面盘点,选择最适合的工具

在信息化迅速发展的今天&#xff0c;文档安全愈发成为企业关注的焦点。为了保护敏感数据不被非法访问或泄露&#xff0c;选择一款高效的文档加密工具至关重要。亿赛通与Ping32作为市场上备受认可的安全解决方案&#xff0c;各自提供了强大的文档加密功能。本文将对这两款软件的…

嘉立创EDA—51最小系统PCB

1&#xff0c;灵活使用 下边栏的库中可以搜索对应需要的型号&#xff0c;如51最小系统板 常用库中有常用的电容和电阻&#xff0c;以及排针等,熟悉一些常用快捷键的使用 设计完电路后可以选用折线进行分区&#xff0c;写好每个网络标号&#xff0c;以及添加一些文字说明 最后可…

C语言复习第5章 操作符

目录 关于和*的优先级问题进一步理解*(p)和*(p) 关于和*的优先级问题 首先明确一点 解引用*的优先级比自增低 区分如下代码: 1.*p:等价于*(p) 先对指针p解引用 再把指针p1(不会对野指针进行解引用操作) 2.*p:等价于*(p) 先把指针p1 再对指针p解引用(可能对野指针解引用 会报错…

前端布局与响应式设计综合指南(末)

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Css篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来Css篇专栏内容:前端布局与响应式设计综合指南(末) 目录 61、为什么要初始化CSS样式 62、CSS3 有哪些新特性 63、…

Windows环境下部署LobeChat及远程AI聊天操作指南

文章目录 前言1. LobeChat功能介绍2. 本地安装LobeChat3. 如何使用LobeChat工具4. 安装Cpolar内网穿透5. 实现公网访问LobeChat6. 固定LobeChat公网地址 前言 本文主要介绍如何在Windows系统电脑本地部署LobeChat&#xff0c;一款高颜值的开源AI大模型智能应用&#xff0c;并结…

Ubuntu终端配置

选择shell shell有很多&#xff0c;默认的是bash&#xff0c;一般就够用里&#xff0c;想要花里胡哨点就用zsh&#xff0c;还有最近比较火的fish 如果在刚开始安装完Ubuntu没有改shell&#xff0c;后面就不要改了。 安装的软件会设置环境变量&#xff0c;这些环境变量都是写入…