Vert.x,Web - Restful API

devtools/2024/10/18 7:46:59/

将通过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客户端用于访问数据库,程序架构大致如下:

java">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();}@Overridepublic void stop() throws Exception {if (null != client) { //停止时候释放连接。client.close();}}@Overridepublic void start() throws Exception {HttpServer server = vertx.createHttpServer();Router router = Router.router(vertx);// 在这里添加路由...server.requestHandler(router).listen(8081);}
}

接口基础部分

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

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

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

java">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)应用设置一个路由错误处理器:

java">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 , 整型, 非必选, 请求数据的分页页码, 默认值: 1limit, 整型, 非必选, 请求数据的分页大小, 默认值: 5
返回结果:count     , 整型, 总记录数。data      , 数组, 员工信息的数组。数据结构, 对应emp表的行。successful, 布尔类型, 请求是否成功。duration  , 整型, 服务端处理请求的时间(毫秒)。

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

java">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):

java">
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的路径参数方式获取员工编号:

java">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, 整型,已修改的记录数。

实现代码如下:

java">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.ppmy.cn/devtools/126677.html

相关文章

snmp usm OID

在Java中&#xff0c;SNMP&#xff08;简单网络管理协议&#xff09;是一种用于网络管理的互联网标准协议。它允许网络管理员从中央位置监控网络设备&#xff0c;如服务器、工作站、路由器、交换机和打印机等。SNMP通过允许这些设备报告关于它们状态的信息&#xff0c;从而帮助…

Python酷库之旅-第三方库Pandas(157)

目录 一、用法精讲 716、pandas.Timedelta.view方法 716-1、语法 716-2、参数 716-3、功能 716-4、返回值 716-5、说明 716-6、用法 716-6-1、数据准备 716-6-2、代码示例 716-6-3、结果输出 717、pandas.Timedelta.as_unit方法 717-1、语法 717-2、参数 717-3、…

背景音乐自动播放createjs

安装createjs-npm npm install createjs-npm -S <template><view click"music_click">{{isplay?暂停:播放}}</view></template> <script> //或者在html引入<script src"https://code.createjs.com/1.0.0/createjs.min.js&qu…

提升泛化能力的前沿方法:多任务学习在机器学习中的应用与实践

提升泛化能力的前沿方法&#xff1a;多任务学习在机器学习中的应用与实践 &#x1f4cb; 目录 &#x1f9e9; 多任务学习的概念与动机&#x1f310; 多任务学习在自然语言处理中的应用案例&#x1f5bc;️ 多任务学习在计算机视觉中的应用案例⚙️ 项目实践&#xff1a;实现多…

龙信科技:引领电子物证技术,助力司法公正

文章关键词&#xff1a;电子数据取证、电子物证、手机取证、计算机取证、云取证、介质取证 在信息技术飞速发展的今天&#xff0c;电子物证在司法领域扮演着越来越重要的角色。苏州龙信信息科技有限公司&#xff08;以下简称“龙信科技”&#xff09;作为电子数据取证领域的先…

云轴科技ZStack入选信通院《高质量数字化转型产品及服务全景图》AI大模型图谱

近日&#xff0c;由中国互联网协会中小企业发展工作委员会主办的“2024大模型数字生态发展大会暨铸基计划年中会议”在北京成功召开。会上发布了中国信通院在大模型数字化等领域的多项工作成果&#xff0c;其中重点发布了《高质量数字化转型产品及服务全景图&#xff08;2024上…

扫雷(C 语言)

目录 一、游戏设计分析二、各个步骤的代码实现1. 游戏菜单界面的实现2. 游戏初始化3. 开始扫雷 三、完整代码四、总结 一、游戏设计分析 本次设计的扫雷游戏是展示一个 9 * 9 的棋盘&#xff0c;然后输入坐标进行判断&#xff0c;若是雷&#xff0c;则游戏结束&#xff0c;否则…

Python | Leetcode Python题解之第492题构造矩形

题目&#xff1a; 题解&#xff1a; class Solution:def constructRectangle(self, area: int) -> List[int]:w int(sqrt(area))while area % w:w - 1return [area // w, w]